Skip to content

[6.x] Inertia entry type#19128

Draft
brianjhanson wants to merge 28 commits into
6.xfrom
feature/inertia-entry-type
Draft

[6.x] Inertia entry type#19128
brianjhanson wants to merge 28 commits into
6.xfrom
feature/inertia-entry-type

Conversation

@brianjhanson

@brianjhanson brianjhanson commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

I've pulled in #19129 and moved the entry type settings page into Inertia in order to see how it feels. At the moment I'm feeling pretty good about the effort to bring Garnish into the modern era. Lots of work to do and things to figure out, but I'm thinking in general we'll pull things out into a separate library (@craftcms/garnish at the momenet, but it may make sense to just move it into @craftcms/cp). The @craftcms/legacy package will pull that in to handle the internals and Garnish.* / Craft.* calls via the shim. Modern code can just pull the components in directly.

New @craftcms/cp components

  • input-color + select-color — color picker (lifted from [6.x] Inertia asset transforms settings #18943).
  • select-rich + select-invoker — rich <select> replacement (lifted from [6.x] Inertia element indexes #19095).
  • action-menu (+ action-menu.types.ts, action-item updates) — replaces the
    legacy disclosure menu.
  • icon — enhancements to support the icon picker.
  • input — hidden-input support.
  • Vue wrapper generator (generate-vue-wrappers.js) updates, plus stories/tests for the
    new components.

Icon picker

  • New IconPicker.vue and useAsyncIcon composable for the Inertia/Vue icon picker.
  • Currently in the resources/ directory (not @craftcms/cp) because the icons are a part of Craft's asset bundle and not part of the @craftcms/cp package. In the future that may change.

brianjhanson and others added 13 commits June 17, 2026 15:08
…kage

Introduce packages/craftcms-garnish: a modern, tree-shakeable TypeScript
rewrite of the legacy jQuery-based Garnish UI library, alongside an opt-in
compatibility layer so existing consumers keep working unchanged.

- garnish-core: native ES-class Base, three event systems (instance
  EventEmitter, class-level bus, namespaced DOM listener registry), custom
  events (activate/textchange/resize) without $.event.special,
  UiLayerManager/EscManager, focusable matcher, and the full utility surface.
  Zero jQuery in the modern entry (dist/index.js has no jQuery references).
- Modal: vertical-slice PoC component (Velocity -> Web Animations API).
- compat: opt-in ./compat entry restoring Garnish.Base.extend(), this.base(),
  window.Garnish, and jQuery-collection args; jQuery is an optional peer
  dependency of this entry only.
- Tooling: tsdown ESM build, Vitest (happy-dom) with 121 passing tests, a Vite
  playground (npm run dev), TSDoc on public APIs, README + docs/.

Draggable/resizable Modal (BaseDrag/DragMove) is deferred; see
docs/00-migration-plan.md for the full migration plan and effort estimate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port the legacy jQuery drag base into the modern package, jQuery-free, and
use it to complete Modal's deferred draggable/resizable support.

- src/drag/base-drag.ts: BaseDrag extends Base, modernized to Pointer Events
  (dropping the legacy mouse+touch shim), with a native requestAnimationFrame
  auto-scroll loop, a WeakMap drag registry, multi-touch pointer-id guarding,
  and the full subclass contract (addItems/removeItems/startDragging/drag/
  stopDragging + onDragStart/onDrag/onDragStop hooks; beforeDragStart/dragStart/
  drag/dragStop events).
- src/drag-move.ts: replace the throwing PoC stub with the real DragMove.
- src/utils/scroll.ts: shared axis-aware getScrollParent (animation.ts now
  consumes it); add getOuterWidth/Height reserved for the future Drag port.
- src/modal.ts: remove the draggable/resizable throw blocks; draggable uses
  DragMove (container or dragHandleSelector), resizable uses a BaseDrag-driven
  resize handle with RTL-aware math; both torn down in destroy().
- Playground: draggable/header-handle/resizable Modal demos, standalone
  DragMove/BaseDrag boxes (incl. axis-locked), and an auto-scroll demo.
- Docs: BaseDrag/DragMove API reference, updated Modal status, design +
  impl notes (docs/07, docs/08).

162 tests pass; the modern entry stays jQuery-free. Live drag/auto-scroll/
resize and touch behavior are validated via the playground (not unit-testable
in happy-dom). Drag/DragDrop/DragSort remain the next drag-cluster modules.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port the next two drag-cluster modules onto the modern BaseDrag.

- src/drag/drag.ts: Drag extends BaseDrag — native helper-clone creation
  (cloneNode(true), border-box sizing, input-name blanking), cursor lag-follow
  positioning, and Web Animations API return-to-source / fade-out of helpers
  (mirrors Modal._fade: tracked, cancelable, prefers-reduced-motion gated).
  Preserves the legacy startDragging hook ordering (onBeforeDragStart before
  helpers are built).
- src/drag/drag-drop.ts: DragDrop extends Drag — drop-target resolution with
  native rect/hitTest hit detection and onDropTargetChange (fires on change
  only); $activeDropTarget is a raw HTMLElement.
- src/utils/misc.ts: promote shared isPlainObject (base-drag.ts now imports it).
- src/index.ts: Drag/DragDrop named exports + on the Garnish namespace.
- Playground: "Drag with helpers (clones + return-to-source)" and
  "DragDrop — drop targets & hit detection" demos.
- Docs: Drag/DragDrop API reference + status (only DragSort now pending in the
  drag cluster); design (09) + impl notes (10).

215 tests pass; modern entry stays jQuery-free. Live helper trailing, return
animation, and drop hover are validated via the playground (not unit-testable
in happy-dom).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DragMove.onDrag wrote the cursor's page coordinates straight into
style.left/top, which only works when the element's containing block is at
the page origin (e.g. a <body>-relative Modal). For an absolutely-positioned
element nested in a positioned ancestor, left/top are resolved against that
ancestor's padding box, so the element jumped by the container's page offset
and flew off-screen as soon as the drag started.

Convert the page-coordinate target into the element's containing-block
coordinates by subtracting its offsetParent origin (getOffset + clientLeft/Top
- scrollLeft/Top). Elements with no positioned ancestor (offsetParent null/
body/html) return {0,0}, so Modal and other body-relative draggers are
unchanged. Also fix the playground's raw-BaseDrag demo to position via the
cursor delta from a captured start (offset-parent-agnostic).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds `DragSort` (the sortable-list dragger) to `@craftcms/garnish`, completing
the Phase-2 drag cluster (BaseDrag → DragMove → Drag → DragDrop → DragSort).

`DragSort extends Drag` and ports the legacy `DragSort.js` faithfully, fully
jQuery-free:

- Live insertion feedback: the draggee block (+ optional `insertion` placeholder)
  is re-inserted into the DOM at the closest landing spot as the cursor moves.
- `_getClosestItem` spatial hit-test (axis-aware x/y/Euclidean distance, outward
  walk with a monotonic-distance early-skip, `canInsertBefore`/`canInsertAfter`
  gating) backed by a per-drag midpoint cache (`_precalculateMidpoints`, only the
  moved item + neighbors recomputed per move, viewport filter for lists > 200).
- `magnetStrength`, `moveTargetItemToFront`, axis options, and the
  `insertionPointChange` / `sortChange` / `dragStart` / `dragStop` events
  (legacy names preserved).
- jQuery removed throughout: `.insertBefore/After` → `ChildNode.before/after`;
  `.offset()` → `getOffset`; `.index()` → manual sibling/`$items` index;
  `$().add()` DOM re-sort → `compareDocumentPosition`; `$.contains` →
  `Node.contains`; `$.data` midpoints → a `Map`.

Also: named + `Garnish`-namespace exports; 32 happy-dom tests (247 total);
a "DragSort — reorderable list" playground demo; design note (doc 11) + impl
notes (doc 12); README and API reference updated (DragSort supported, drag
cluster COMPLETE).

Verified: check:types, test (247), build, playground:build, check:format all
green; `dist/index.js` is jQuery-free (grep count 0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds `HUD` (Phase 3) — the anchored popover/bubble with smart 4-way
positioning, a tip/arrow, scroll-follow, focus trapping, and UiLayerManager
layer + Escape integration — as a jQuery-free `class HUD extends Base`. This
was the last FieldLayoutDesigner overlay blocker.

- src/hud.ts: faithful port of legacy HUD.js. jQuery removals mapped to the
  modern utils — `.scrollParent()`→getScrollParent, `.offset()`/getOffset,
  `.outerWidth/Height`→getOuterWidth/Height, `:focusable`→getFocusableElements/
  getKeyboardFocusableElements, `$.data`→WeakMap, `Garnish.within`→within, RAF
  re-export, UiLayerManager via the registry. Legacy HUD has no Velocity, so
  show/hide stay as display toggles (no WAAPI conversion). Body content box
  measured via getBoundingClientRect (jQuery `.width()/.height()` parity, and
  mockable in happy-dom).
- src/index.ts: named exports HUD + HUDSettings/HUDOrientation/HUDBodyContents,
  and HUD on the legacy-shaped Garnish namespace.
- tests/hud.test.ts: 39 tests (settings/defaults, construction + param-shift,
  updateBody, show/hide/toggle + events, layer/Esc/shade, submit, focus,
  4-way positioning with mocked rects, updateRecords, destroy).
- playground: section 11 "HUD — anchored popover" (edge-anchored triggers +
  event log + HUD/tip CSS).
- docs: 13-hud-design.md, 14-hud-impl-notes.md; README + 06-api-reference
  mark HUD supported.

Gates: check:types, test (286, +39), build, playground:build, check:format
all green; dist/index.js jQuery refs = 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A draggable Modal is position:fixed, so its offsetParent is null and its
left/top resolve against the viewport, not the page. containingBlockOrigin
treated a null offsetParent as the page origin {0,0}, so the first drag
frame wrote a page-Y target (which includes scrollY) into a viewport-relative
top — jumping the modal down by the page scroll offset.

Split the null-offsetParent branch: a position:fixed element's containing
block is the viewport anchored at the scroll offset, so its origin is
{scrollX, scrollY}. Absolute (no positioned ancestor) and detached elements
still resolve to {0,0}; the positioned-ancestor branch is unchanged. Adds a
regression test asserting a fixed target with scrollY set lands
viewport-relative (no jump).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port Garnish's largest remaining UI component (~1,008 LOC) to the modern,
jQuery-free TypeScript/ESM package, following the HUD/Modal overlay patterns.

`class DisclosureMenu extends Base<DisclosureMenuSettings>`:
- Resolves the menu panel from the trigger's aria-controls (or next sibling),
  relocates it to <body>, and toggles via aria-expanded.
- Above/below + left/center/right anchored positioning (scroll/resize-aware).
- Full keyboard nav (arrows + Tab cycle), type-ahead search, focus management.
- UiLayerManager layer + Escape shortcut; outside-mousedown dismissal.
- Instant show; WAAPI fade-out on hide (reduced-motion aware) — Velocity removed.
- Item/group builders (addItem/addItems/addGroup/addHr/removeItem/...) the
  ~19 CP consumer sites rely on; per-item onActivate/callback selection.
- Optional withSearchInput live item filter.

jQuery removals: $(trigger)->getElement, :focusable->focusable matcher,
.scrollParent()->getScrollParent, .velocity('fadeOut')->WAAPI,
$.data('disclosureMenu'/'searchText')->WeakMaps, .find/closest/prevUntil/
nextUntil->native DOM. Craft.getUrl/t/initUiElements routed through an optional
global; $(el).formsubmit() omitted (sets the formsubmit class + data-action).

Exports DisclosureMenu (+ settings/item types) as named exports and on the
legacy-shaped Garnish namespace. Adds 49 happy-dom tests (suite: 336 passing),
a playground section 12 demo, design doc 15, impl notes 16, and README +
06-api-reference entries marking DisclosureMenu supported.

dist/index.js stays jQuery-free (grep -ciE "jquery|\$(" -> 0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the single-page Vite playground (12 demo sections in one ~900-line
main.ts) with Storybook, one story file per component.

- Add .storybook config (framework @storybook/html-vite to match Garnish's
  imperative widgets; addons docs/a11y/themes at 10.4.1, mirroring @craftcms/cp).
  preview.css migrates the demo globals from playground/styles.css.
- Add stories/ (top-level, mirroring tests/) with all 12 playground demos:
  modal (basic + draggable/resizable), focus (matcher + trap), compat
  (.extend/this.base), base-drag (standalone boxes + auto-scroll), drag
  (helpers + return/fade), drag-drop, drag-sort, hud, disclosure-menu
  (dropdown + filterable), utilities. Shared stories/_log.ts event-log helper
  (panel + drag-event wiring + layout) and stories/_helpers.ts modal builders.
  Stories import the real source from ../src; args/argTypes drive settings.
- package.json: repoint "dev" to Storybook, add "storybook" + "build:storybook",
  remove "playground:build"; extend format globs to stories + .storybook; add
  Storybook devDeps at ^10.4.1.
- tsconfig.json includes stories + .storybook; remove playground/ and the
  package-root vite.config.ts (it only served the playground).
- README + new docs/17-storybook-notes.md document the Storybook workflow and
  how future component ports add a stories/<name>.stories.ts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the hand-rolled in-canvas event-log panel with Storybook's built-in
Actions panel (`storybook/actions`, part of core in 10.x — no extra addon).

- stories/_log.ts: createEventLog() now wraps `action()`, memoizing one named
  action per tag so events group by tag in the Actions panel. Drop the panel/
  clear/initialMessage API; storyLayout(main) just tags the demo container.
- Update all story call sites: createEventLog(), storyLayout(main), drop the
  isError third arg.
- preview.css: remove the .pg-log* panel styles; .pg-story is now a simple
  single-column demo container, not a two-column flex with a side panel.
- Update docs/17-storybook-notes.md and README to describe the Actions panel.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread src/Http/Controllers/Settings/EntryTypesController.php Outdated
Comment thread src/Cp/FieldLayoutDesigner/FieldLayoutDesigner.php
brianjhanson and others added 15 commits June 18, 2026 16:21
Convert the ported FieldLayoutDesigner to native DOM + WeakMaps, keeping
thin jQuery only where it hands off to Craft's still-jQuery widgets
(Craft.Grid/Listbox/SlidePicker/SortableCheckboxSelect/Slideout, the
.disclosureMenu() plugin, and form .serialize()).

- All of FLD's own jQuery converted to native: createElement/querySelector,
  classList, dataset, textContent/innerHTML/value, native tree ops,
  getOffset/getOuterHeight, Web Animations API for fades.
- .data() replaced with module-level WeakMaps in support.ts
  (fld-tab/fld-element/hud/cvd + drag midpoints) and dataset reads.
- Garnish Drag $items/$draggee/helpers used as native arrays (wrappers
  removed); $insertion/$caboose are native too.
- README updated to describe the native conversion and remaining seams.

Gates: FLD typecheck clean, eslint clean. Full vite build is env-blocked
in this worktree (no vendor/) and not run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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