FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

[6.x] Render element-select chip actions in Vue by brianjhanson · Pull Request #19437 · craftcms/cms · GitHub

/ cms Public

[6.x] Render element-select chip actions in Vue - #19437

Merged
brandonkelly merged 11 commits into
6.xfrom
feature/element-select-chip-actions
Aug 21, 2026
Merged

[6.x] Render element-select chip actions in Vue#19437
brandonkelly merged 11 commits into
6.xfrom
feature/element-select-chip-actions

Conversation

brianjhanson commented Aug 14, 2026
edited
Loading

Copy link
Copy Markdown
Contributor

Description

Chips in the Inertia element editor’s element-select control were getting their action menu from Craft.addActionsToChip(). Finding no action container in the hand-rolled Vue chip, it fell all the way back to building a legacy jQuery disclosure menu and appending it to the chip’s body slot. ElementSelectControl.vue now renders craft-action-menu in the chip’s suffix slot from its own props — Replace gated on editable and the control’s element type, Remove on editable.

The two behaviors moved to public methods on BaseElementSelectInput (showReplaceModal() and removeElementOrSelection()), so the Vue menu and the menu the Twig stack still injects share one implementation. settings.showActionMenu now also gates the client-side injection in addElements(), not just the app/render-elements payload; without that, both menus would render into the same chip.

Replace also completes now, which it never did in this control. onModalSelect() dropped the chip being replaced before awaiting app/render-elements, and since ElementSelectControl.vue keys <craft-element-select-input> on its value, that intermediate one-element-short value tore the custom element down mid-flight — the rest of the method, including the selectElements() that inserts the replacement, then wrote into a detached DOM. The removal now happens once the replacement markup is in hand and ahead of any further await, so hosts observe a single combined change and re-render once with the replacement already in place; it still lands before the limit math, which needs the outgoing chip’s slot freed. addElements() additionally guards elementSelect with ?. to match elementSort — selectStructuredElements() still awaits after the chip is dropped, so a host that destroys the controller in that window would otherwise throw.

Two supporting fixes:

  • craft-chip decided whether to render its prefix/suffix slots with one-shot querySelector() calls during render(), so anything slotted in after mount never appeared. It now re-renders when its light DOM changes, watching the slot attribute as well as childList, since content moves between slots by having that attribute set.
  • The legacy fallback in Craft.addActionsToChip() now warns when it fires, so any surface still degrading to a disclosure menu is visible rather than silent.

brianjhanson and others added 3 commits August 14, 2026 12:07
`craft-chip` decided whether to render its prefix and suffix slots with
one-shot `querySelector()` calls during `render()`, so anything slotted in
after the chip mounted never appeared — most visibly the action menu
`Craft.addActionsToChip()` injects into `[slot="suffix"]` once it has the
element's actions.

A `MutationObserver` on the light DOM now bumps a `@state`, so those checks
run again. Deliberately not `slotchange`: an unrendered `<slot>` can't report
a change, which is the bug. The observer watches the `slot` attribute as well
as childList and subtree, since content moves between slots by having that
attribute set rather than by being added or removed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`addActionsToChip()` builds a jQuery `Garnish.DisclosureMenu` from scratch
when a chip has no recognized action container, and did so silently — a
surface stuck on the deprecated shape looks fine until someone compares it
against a `craft-action-menu`. Name the chip and point at the
`<div slot="suffix">` that gets it the modern menu instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`ElementSelectControl.vue` renders the chips itself, but their Replace and
Remove actions still arrived the legacy way: `addElements()` handed every chip
to `Craft.addActionsToChip()`, which injected a menu into `[slot="suffix"]`
after Vue had rendered — so the control's own markup and a jQuery-built menu
were both writing into the same slot. The chip's action menu is now part of the
component's template, and `showActionMenu: false` turns the injection off.
`_includes/forms/elementSelect.twig` is unchanged and still gets the injected
menu; `settings.showActionMenu` previously only reached the
`app/render-elements` payload, so it now gates the client side too and the
chips fetched after a selection stay menu-less until Vue re-renders them.

The two behaviors the menu items need come out of `defineElementActions()` into
public methods, `showReplaceModal()` and `removeElementOrSelection()`, so the
Twig-injected menu and the Vue one run the same code — including Remove's
long-standing habit of taking the whole multi-selection when the chip is part
of it. Vue reaches them through passthroughs on `<craft-element-select-input>`
that resolve the chip by `data-id`, because `ControllerElement.instance` is
protected. Naming had to dodge two collisions on the way: the controller's
`replaceElement()` is the server-render swap (`elementId`, `replacementId`), and
a bare `remove()` on the custom element shadows `Element.remove()`, which
vue-tsc rejects for a `CustomElementConstructor`.

Replace is gated on an element type as well as `editable`, matching
`defineElementActions()`; move forward/backward are left out because the
control isn't sortable.

One `FormRenderer` assertion now reads the chip's own text nodes rather than
`textContent`, since the suffix menu's item labels count towards the latter.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

github-actions Bot commented Aug 14, 2026
edited
Loading

Copy link
Copy Markdown

📚 Storybook previews

@craftcms/uiopen Storybook

No changed components detected in this Storybook.

resources/jsopen Storybook

No changed components detected in this Storybook.

brianjhanson marked this pull request as ready for review August 19, 2026 04:13
…-chip-actions

# Conflicts:
#	resources/js/modules/forms/ElementSelectControl.vue

Copy link
Copy Markdown

This PR had conflicts with 6.x. Claude attempted an automatic resolution and pushed a merge commit. Please review the merge commit carefully before approving/merging.

Claude's response

Merge resolved, committed as 9f816c0, and pushed.

Summary

Merging origin/6.x into feature/element-select-chip-actions produced conflicts in a single file, resources/js/modules/forms/ElementSelectControl.vue, with two hunks that were really two halves of the same disagreement: who renders a chip's action menu.

  1. showActionMenu in the CraftElementSelectInput options — 6.x set it to true (from the WIP commit 551e416) so Craft.addActionsToChip() would build the menu; this branch sets it to false because the menu is now rendered in Vue. Resolved in favor of this branch's false, which supersedes the 6.x approach — leaving it true would inject a duplicate set of items into the Vue-rendered craft-action-menu.

  2. The chip's <div slot="suffix"> — 6.x deliberately left it empty as a target for Craft.addActionsToChip(); this branch renders <ActionMenu :actions="chipActions(...)"> into it. Resolved in favor of this branch's <ActionMenu>, consistent with hunk 1.

Both resolutions carry a CONFLICT-REVIEW: comment recording what the 6.x side intended and why it was superseded, since this is the one judgment call in the merge — it assumes the Vue-rendered menu is meant to replace the legacy addActionsToChip() path for this control rather than coexist with it, which is what commits d29a3af and ae77a1e on this branch indicate.

Two things worth noting for the reviewer:

  • 6.x's WIP commit also added an unused import VarDump from '@/common/components/VarDump.vue' to this file. It auto-merged without conflict and is 6.x's own leftover, so I left it in place rather than touching code outside the conflict — but it's dead and may want removing on 6.x.
  • The earlier chip commits on this branch (chip.ts, chip.test.ts, Craft.js) already exist verbatim on 6.x, so those files auto-merged to byte-identical content with no work lost. I also verified CHANGELOG.md and CHANGELOG-WIP.md merged to exactly the 6.x content (this branch never touched them), so there's no silent section-splicing.

I could not run vp check / npm run typecheck to validate — node_modules isn't installed in this checkout — so the resolution is verified by inspection and by confirming no conflict markers remain anywhere in the repo. CI on the PR will be the real check.

brandonkelly merged commit 61138b2 into 6.x Aug 21, 2026
23 checks passed
brandonkelly deleted the feature/element-select-chip-actions branch August 21, 2026 17:41
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants


Back | FazBrowse Home | New Git URL