Repository navigation
[6.x] Stop core's control panel loading jQuery and Garnish - #20320
Closed
brianjhanson wants to merge 47 commits into
Closed
brianjhanson wants to merge 47 commits into
brianjhanson wants to merge 47 commits into
Conversation
Core never runs the CP screen slideout, element action trigger, createElementEditor(), data and element table sorters, sortable checkbox select or tag select input; only the adapter and its legacy scripts do. Core's entry points stop loading and exporting them, and their Craft.* globals move to compat.ts, so they can keep jQuery. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The auth method listing refresh parses the new markup with a template element, and the entry type override settings serialize their inputs with serializeFormInputs(). The grouped entry type manager only declared $. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Chips with no action container fall back to the legacy disclosure menu, which only the adapter provides. The fallback now checks for that plugin first and builds its markup with the DOM, so without the adapter it does nothing instead of throwing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Craft.Grid laid out dashboard widgets and field layout tabs through jQuery collections and jQuery's box-model measurements. It works on plain elements now, measuring content boxes itself, and still offers $container and $items as jQuery collections for the legacy dashboard when jQuery is loaded. The dashboard and field layout designer pass it elements. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The preview modal builds its markup with the DOM and inserts preview HTML with appendElementHtml(), which runs its scripts as jQuery's append() did. $container stays, as a jQuery collection when jQuery is loaded, for preview templates. The focal point editor drags with pointer events, which also covers touch, and its listeners now come off when it's destroyed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The CP modal builds its dialog with the DOM, inserts content with appendElementHtml(), and keeps its delta-tracking state in private fields instead of jQuery's data(); a serializer legacy code sets through data() is still honoured. The element deletion manager builds its blockers with the DOM, and both keep $container-style getters for legacy code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Craft.charts draws into plain elements, deep-merges its settings with a helper that matches jQuery's $.extend(true, …), and measures and positions its tooltip itself. $container, $chart and $tip stay as jQuery getters for legacy chart widgets. The content-box measurement the grid and focal point editor each had moves to a shared contentSize() helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Craft.Slideout keeps its container, outer container, shade and trigger as plain elements and positions and transitions them with the DOM. The $-prefixed properties stay as jQuery getters for the CP screen slideout and other legacy subclasses. The auth method setup slideout and two-step verification read the plain container. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The create button listens for clicks, which a craft-button also fires on Enter and Space, instead of Garnish's jQuery activate event. A chip's edit shortcut uses a dblclick listener and VueUse's onLongPress for touch, replacing the jQuery touch events plugin's taphold. The legacy disclosure menu is only booted through jQuery when the adapter has loaded it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only a handful of plugins read them, and they need Craft 6 updates anyway. CP screen slideouts keep `$container`, since they're jQuery-based and plugins subclass them through the adapter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The element select template rendered an inline `new Craft.XSelectInput()`, so core relation fields needed the Craft globals. It now always renders the matching custom element, passing a plugin's class name as `input-class`, and the Craft.* registrations move to the adapter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plugins subclass Craft.BaseElementSelectInput and Craft.AssetSelectInput and use their jQuery members, so the adapter keeps copies of the current classes for Craft.*. That frees core's classes to drop jQuery. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A refresh queued before unmount ran against the destroyed grid and threw. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The base, entry and asset select inputs keep plain elements (container, elementsContainer, elements…) and use native events: click for the Add button, dblclick and a long press for editing, and craft-reorder for the reorder buttons. The element markup helpers and file preview modal take plain elements too, and the preview works with native selects. The adapter's copies keep the jQuery versions for Craft.*. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Field layout and nested element refreshes serialize the form natively, the legacy element editor is looked up only when jQuery is present, element editors read a chip's data attributes directly, and the asset mover's Undo button listens for click. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The disclosure menu items, the 2FA setup page and the entry type override chip listen with addEventListener; the menu only reaches for the legacy instance when jQuery is loaded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Core's control panel no longer needs them. The adapter's Craft.js asset brings jQuery, Garnish, touch events and Velocity for plugins, and the legacy money input depends on Garnish itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Categories fields no longer emit an inline script; their input boots from the element select custom element. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MenuHtml::disclosureMenu() builds a <craft-action-menu>, so core's menus no longer need the jQuery disclosure menu plugin. Groups keep their headings, the selected item is marked current, and statuses and info icons carry over. The adapter binds a MenuHtml that keeps the legacy markup its jQuery code and plugins rely on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only the adapter's legacy screens, slideouts and element editor create it; core renders <craft-tabs>. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
📚 Storybook previews@craftcms/ui — open Storybook No changed components detected in this Storybook. resources/js — open Storybook No changed components detected in this Storybook. |
Core's FocalPointAsset and Craft.FocalPoint were removed, so the deprecated adapter bundle referenced a class that no longer exists, which failed PHPStan. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a41f-35f1-75f6-a070-f628cc4e7276 Co-authored-by: Amp <amp@ampcode.com>
CpAsset no longer loads jQuery, so the Link field, the entry type compatibility check, the range input and the relation field settings threw on installs without the Yii2 adapter, stopping every inline script after them. customSelect.twig's legacy menu only works with the adapter, so it now returns early without jQuery. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a41f-35f1-75f6-a070-f628cc4e7276 Co-authored-by: Amp <amp@ampcode.com>
Moving a controller element (Matrix move up/down, drag-sorting) destroyed and rebuilt its controller, duplicating chip actions and, on Assets fields, the uploader. Teardown now waits a microtask and keeps the controller when the element is back in the document. A plugin's input-class is now waited for, for up to 10 seconds, so markup inserted before the plugin's script boots once it loads. AssetSelectInput now tears down its uploader and the controls it adds. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a41f-35f1-75f6-a070-f628cc4e7276 Co-authored-by: Amp <amp@ampcode.com>
The template had become macros only, so plugin includes rendered nothing. It now renders through disclosureMenu(), so the adapter's legacy menu applies there too. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a41f-35f1-75f6-a070-f628cc4e7276 Co-authored-by: Amp <amp@ampcode.com>
The tip was measured while hidden, so its width was 0: tips near the right edge never flipped, and RTL tips covered their point. jQuery's width() used to measure hidden elements by showing them briefly. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a41f-35f1-75f6-a070-f628cc4e7276 Co-authored-by: Amp <amp@ampcode.com>
Edit and Open in Image Editor went to the global window listeners, so Edit lost the input's prevalidate setting and an image saved as a new asset no longer replaced its chip. The input now claims those events for its own chips, as the jQuery version did. Also drop replaced chips from the selection after a structured selection, and use the field's own spinner rather than the search input's. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a41f-35f1-75f6-a070-f628cc4e7276 Co-authored-by: Amp <amp@ampcode.com>
LinkField was only reachable through jQuery's data cache, so LinkInput and the element link types' script couldn't reach it without the adapter. The script also read the element select input from that cache, which the ported input no longer sets. LinkField.forContainer() finds it now, and the script listens for the input's DOM events. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a41f-35f1-75f6-a070-f628cc4e7276 Co-authored-by: Amp <amp@ampcode.com>
Screen.vue read Tabs from window.Craft, which only the adapter sets now, and read each tab's href through jQuery. It imports Tabs instead, and Tabs events carry the tab element as well as $tab. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a41f-35f1-75f6-a070-f628cc4e7276 Co-authored-by: Amp <amp@ampcode.com>
# Conflicts: # yii2-adapter/legacy/web/assets/focalpoint/FocalPointAsset.php
appendElementHtml() recreated only top-level scripts, so a script inside a wrapper element was cloned as already started and never ran. jQuery's .html() ran them, so modal, file preview and element deletion content that wraps its init scripts stopped working when those moved off jQuery. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a41f-35f1-75f6-a070-f628cc4e7276 Co-authored-by: Amp <amp@ampcode.com>
Recreating nested scripts all at once let an inline init script run before an external library ahead of it had loaded, and let the promise resolve before that load finished. Activate them one at a time, awaiting each external script through the same path as top-level ones, so they run in order and rejectOnError covers their failures. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a41f-35f1-75f6-a070-f628cc4e7276 Co-authored-by: Amp <amp@ampcode.com>
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Unawaited blocker-details insertion can leave controls uninitialized and execute body scripts before dependencies load, while unstable nested action IDs can recreate grouped actions and lose keyboard focus.
2 open issues
1 resolved since last review
Review effort: Balanced
Groups now keep their items, which get random IDs. Only top-level IDs were removed, so an unchanged menu's payload differed on every render and ActionList.vue recreated its grouped items. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a41f-35f1-75f6-a070-f628cc4e7276 Co-authored-by: Amp <amp@ampcode.com>
The details markup was appended without waiting, so initLegacyUi() could run before controls after an external script were added, and the response's body scripts could run before those scripts loaded. Amp-Thread-ID: https://ampcode.com/threads/T-01a1a41f-35f1-75f6-a070-f628cc4e7276 Co-authored-by: Amp <amp@ampcode.com>
brandonkelly
approved these changes
Nov 4, 2026
Member
|
@brianjhanson Claude made some changes in response to a few rounds of Copilot reviews. If they look good to you, feel free to merge. |
Contributor
Author
|
After chatting with @i-just this morning, I'm doing a pass of this branch against CKEditor to make sure we still have everything we need in place. Hoping to wrap that up today. |
This was referenced Nov 5, 2026
Contributor
Author
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
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Stacked on #20276. Core's control panel JavaScript no longer needs jQuery or Garnish; the Yii2 adapter loads them for itself and for plugins.
Description
What moved where:
$-prefixed jQuery getters. A GitHub search found few plugins reading them.CpScreenSlideoutkeeps$containerfor adapter callers.elementSelect.twigalways renders the custom element. A plugin'sjsClassboots throughinput-class, so subclasses of the adapter'sCraft.BaseElementSelectInputkeep working.$.Evidence
New regression tests fail on the old code and pass now:
In a browser on a core-only install (
window.jQueryundefined), these load with no console errors: the dashboard, the entry editor and its tabs, the entry type and section settings, the assets and users indexes, and the system report. The element select inputs and a rendered menu work there too.Local CI run on this branch:
vp fmt --check,vp lint,lint:styles, the Tailwind class check,typecheck,@craftcms/ui#check:types: passtest:ui3409, unit 2315, browser 122: passpint,rector --dry-run,phpstan: passpest12,039 passed;composer tests-adapter784 passedServer-built menus on a bridged
cp/Screen, driven in Chromium on a core-only install: a throwawayasCpScreen()route with action and context menu items, plus the breadcrumb template rendered into its content.window.jQuerywas undefined.In core, breadcrumbs reach the page as data and
Breadcrumbs.vuerenders them, so_layouts/components/crumbs.twigonly runs for a screen that turns off fragment collection. Nothing in core does that.🤖 Generated with Claude Code