diff --git a/AGENTS.md b/AGENTS.md index b8f00838ce0..bafa24d1f3c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -70,6 +70,10 @@ The CP has two parallel rendering stacks that are actively being consolidated: components in `resources/js/common/`. `HandleInertiaRequests` middleware provides shared CP config, navigation, and global props to all Inertia pages. The root Blade template is `resources/views/app.blade.php`. +VueUse (`@vueuse/core`) is installed — before hand-rolling composable behavior in Vue code (event listeners with +mount/unmount cleanup, media queries, storage, observers, etc.), check whether a VueUse utility already covers it +(e.g. `useEventListener` accepts ref targets and cleans up automatically, `useMediaQuery`). + **Legacy jQuery (old):** `resources/js/legacy.ts` loads the old surface. The individual jQuery modules live in `packages/craftcms-legacy/` and are bundled with webpack (separate from Vite). Pages still on this stack return `view()` from their controllers. diff --git a/packages/craftcms-legacy/cp/src/Craft.js b/packages/craftcms-legacy/cp/src/Craft.js index 622c9f7b839..cfc2f150510 100644 --- a/packages/craftcms-legacy/cp/src/Craft.js +++ b/packages/craftcms-legacy/cp/src/Craft.js @@ -73,7 +73,6 @@ import './js/LightSwitch.js'; import './js/LinkField.js'; import './js/LinkInput.js'; import './js/LivePreview.js'; -import './js/NestedElementManager.js'; import './js/PasswordInput.js'; import './js/Preview.js'; import './js/PreviewFileModal.js'; diff --git a/packages/craftcms-legacy/cp/src/js/CraftElementLabel.js b/packages/craftcms-legacy/cp/src/js/CraftElementLabel.js index b72d09367bd..520163aaf46 100644 --- a/packages/craftcms-legacy/cp/src/js/CraftElementLabel.js +++ b/packages/craftcms-legacy/cp/src/js/CraftElementLabel.js @@ -56,6 +56,13 @@ class CraftElementLabel extends HTMLElement { } update() { + // Bail while the label isn't measurable (e.g. before a containing web + // component's first shadow render assigns its slots) — a 0-width label + // would always register as overflowing. + if (this.scrollWidth === 0) { + return; + } + this.desiredWidth = this.calculateWidth(this.innerText); this.hasOverflow = this.desiredWidth > this.scrollWidth; diff --git a/packages/craftcms-legacy/cp/src/js/NestedElementManager.js b/packages/craftcms-legacy/cp/src/js/NestedElementManager.js deleted file mode 100644 index c7e63ff0400..00000000000 --- a/packages/craftcms-legacy/cp/src/js/NestedElementManager.js +++ /dev/null @@ -1,1220 +0,0 @@ -/** global: Craft */ - -/** - * Nested element manager - */ -Craft.NestedElementManager = Garnish.Base.extend( - { - $container: null, - $btnContainer: null, - elementType: null, - $createBtn: null, - $pasteBtn: null, - - // cards - $elements: null, - elementSort: null, - elementSelect: null, - - // index - elementIndex: null, - - /** - * @type {Craft.ElementEditor|null} - */ - elementEditor: null, - creatingElement: false, - - init: function (container, elementType, settings) { - this.$container = $(container); - this.elementType = elementType; - this.setSettings(settings, Craft.NestedElementManager.defaults); - - // Is this already a nested element manager? - if (this.$container.data('nestedElementManager')) { - console.warn( - 'Double-instantiating a nested element manager on an element' - ); - this.$container.data('nestedElementManager').destroy(); - } - - this.$container.data('nestedElementManager', this); - - if (this.settings.mode === 'cards') { - if (this.$container.is(':has(.elements)')) { - this.initCards(); - } - } else { - this.initElementIndex(); - } - - if (this.settings.canCreate) { - let $createBtn = Craft.ui - .createButton({ - icon: 'plus', - label: this.settings.createButtonLabel, - spinner: true, - }) - .addClass('icon disabled'); - - if (this.settings.mode === 'cards') { - $createBtn.addClass('dashed wrap'); - } - - this.addButton($createBtn); - - if (Array.isArray(this.settings.createAttributes)) { - const createMenuId = `menu-${Math.floor(Math.random() * 1000000)}`; - $('
', { - id: createMenuId, - class: 'menu menu--disclosure', - 'data-with-search-input': - this.settings.createAttributes.length > 5 ? 'true' : null, - }).insertAfter($createBtn); - $createBtn - .attr('aria-controls', createMenuId) - .attr('data-disclosure-trigger', 'true') - .addClass('menubtn') - .disclosureMenu(); - const disclosureMenu = $createBtn.data('disclosureMenu'); - - // can't use Object.groupBy() here because the group order matters - const groupedCreateAttributes = {}; - const groupOrder = []; - this.settings.createAttributes.forEach((attributes) => { - const group = attributes.group || Craft.t('app', 'General'); - if (!groupedCreateAttributes[group]) { - groupedCreateAttributes[group] = []; - groupOrder.push(group); - } - groupedCreateAttributes[group].push(attributes); - }); - const multiGroup = groupOrder.length > 1; - - groupOrder.forEach((group) => { - if (multiGroup) { - disclosureMenu.addHr(); - disclosureMenu.addGroup(group, false); - } - - groupedCreateAttributes[group].forEach((attributes) => { - disclosureMenu.addItem({ - icon: attributes.icon ? $(attributes.icon)[0] : null, - label: attributes.label, - iconColor: attributes.color, - onActivate: async () => { - $createBtn.addClass('loading'); - await this.createElement(attributes.attributes); - $createBtn.removeClass('loading'); - }, - }); - }); - }); - - if (multiGroup && this.settings.mode === 'cards') { - const $collapsedContainer = $( - '
' - ).insertAfter($createBtn); - $collapsedContainer.append($createBtn); - const $expandedContainer = $( - '