Skip to content

feat(marko): add Marko v6 adapter#2023

Open
defunkt-dev wants to merge 28 commits into
clauderic:mainfrom
defunkt-dev:main
Open

feat(marko): add Marko v6 adapter#2023
defunkt-dev wants to merge 28 commits into
clauderic:mainfrom
defunkt-dev:main

Conversation

@defunkt-dev

@defunkt-dev defunkt-dev commented Apr 18, 2026

Copy link
Copy Markdown

Summary

Adds @dnd-kit/marko, a MarkoJS v6 adapter for @dnd-kit/dom as the sixth major framework adapter alongside React, Vue, Svelte, Solid, and vanilla.

End-user API mirrors the other adapters: a root <drag-drop-provider> delivers a DragDropManager to descendant consumers via implicit context, and consumer tags (<create-draggable>, <create-droppable>, <create-sortable>, <drag-overlay>) register entities without explicit prop threading.

<drag-drop-provider onDragEnd(e) { items = move(items, e); }>
  <ul>
    <for|item, index| of=items by=(i) => i.id>
      <create-sortable|sortable| id=item.id index=index>
        <li/$el>
          <lifecycle onMount() { sortable.setElement($el()); }/>
          ${item.label}
        </li>
      </create-sortable>
    </for>
  </ul>
</drag-drop-provider>

Context delivery

Marko v6 has no public context API today. After discussion with the Marko core team, this adapter uses the officially-recommended <let-global>
pattern — reactive $global backed by a per-key subscription Map. An internal <let-global> tag lives in packages/marko/tags/let-global/. The provider writes the manager to $global.__dndKit_manager during the render phase (via <const> IIFE, before children render), so consumers see it immediately at mount without timing workarounds.

This replaces our earlier translator-based prototype, which depended on unstable compiler internals the Marko team explicitly ruled out exposing to third parties. The <let-global> approach uses only public, documented APIs.

Reactivity bridge

dnd-kit uses @preact/signals-core for entity state (isDragging, isDropTarget, etc.). Marko's reactivity is a separate compile-time graph over <let> variables. <create-deep-signal> bridges the two with per-property Preact effect() subscriptions lazily created on first Proxy access; when a Preact signal fires, a dirty counter increments, which Marko sees and re-renders from.

Deep-equality prop syncing

Matches the pattern applied to the React adapter in #2021. Consumer tags (create-sortable, create-draggable, create-droppable) now deep-compare sensors, modifiers, plugins, accept, and transition before reassigning them to the entity on re-render. Without this, inline literals like plugins={[MyPlugin]} create a new array reference every render, triggering redundant Preact reactive cascades and causing timing bugs in the Feedback plugin during drag.

Lockfile format

bun.lockb (binary) → bun.lock (text). Side effect of regenerating the
lockfile under bun 1.3+, which defaults to text format. If the maintainer
prefers staying on binary, happy to revert; this PR just reflects what
bun install produced after the merge conflict resolution.

Changes outside packages/marko/

Genuine additions for the Marko adapter

  • .changeset/config.json: add @dnd-kit/marko to the fixed array so the
    Marko adapter versions in lockstep with the other adapters.
  • apps/docs/*: Marko navigation anchor, "Getting Started" card, logo asset,
    6 new MDX pages (quickstart + 5 tag pages), and a Storybook config entry
    (port 6011).
  • Root README.md: Marko added to the Getting Started and Packages tables.
  • apps/docs/docs/marko/components/drag-overlay.mdx: corrected ParamField
    bullet-list indentation (MDX parser rejected the original).

Pre-existing upstream build issues patched

These all worked on the maintainer's machine via bun hoisting or stale turbo
cache, but fail on a clean checkout. Each is a one-line fix in package
metadata; nothing source-level was changed.

  • config/typescript/vanilla.json: ES2015ES2017.
    packages/geometry/src/types/axis.ts uses Object.values.
  • packages/dom/tsconfig.json: skipLibCheck: true.
  • packages/dom/package.json + tsup.config.ts: declare @dnd-kit/helpers
    as dependency and tsup external. OptimisticSortingPlugin.ts imports
    move from it; was relying on workspace hoisting.
  • Root package.json: add @types/node devDep so StyleInjector.ts
    process.env.NODE_ENV reference type-checks in the dom DTS build.
  • apps/stories-marko/package.json: declare http-server explicitly
    (Playwright webServer invokes npx http-server).
  • apps/stories-shared/package.json: declare @playwright/test explicitly
    (tests/fixtures.ts imports it).
  • apps/docs/package.json: declare shiki, @shikijs/transformers,
    @codemirror/{view,state,commands,lang-javascript,language},
    @lezer/highlight, and remark-gfm as devDeps. All are imported by
    upstream's src/components/CodeBlock.tsx and SandpackCodeEditor.tsx.

Pre-existing upstream build issues not fixed (out of scope)

Worth flagging so the maintainer is aware:

  • packages/react: useDroppable.ts imports @dnd-kit/collision but it's
    not in deps or tsup external. Same class of bug as the dom/helpers fix.
  • packages/vue: TS d.ts portability error in DragDropProvider.ts
    (TS2742 — type leaks through abstract/node_modules/@dnd-kit/geometry).

Testing

  • 7 SSR unit tests in packages/marko/tests/ssr.test.ts — cover compilation to HTML mode and rendering of sortable, draggable, droppable, and overlay fixtures. Verify that DragDropManager is never constructed during SSR and that consumer tags render with default state.
  • 13 Playwright e2e tests in apps/stories-marko/tests/ — use the shared suite from apps/stories-shared/tests/. Cover vertical sortable (basic + drag handle + keyboard + cancel), draggable (pointer + keyboard + handle + state classes), and droppable (single + multiple zones + pointer + keyboard cancel).
  • Stories in apps/stories-marko/stories/ — 6 story groups covering the same scenarios, parallel in structure to the Svelte and Solid adapters. All verified manually in Storybook.

All 20 tests pass. bun run build and the existing adapter test suites (React, Svelte, Vue, Solid) continue to pass — nothing shared was altered behaviorally.

Documentation

Six new MDX pages under apps/docs/docs/marko/:

  • quickstart.mdx
  • components/drag-drop-provider.mdx
  • components/drag-overlay.mdx
  • tags/create-draggable.mdx
  • tags/create-droppable.mdx
  • tags/create-sortable.mdx

Marko uses <Story> embeds (Storybook iframe on port 6011) like the other adapters. CodeSandbox embeds are omitted because Sandpack does not support Marko. So, static fenced code blocks are used instead.

The packages/marko/README.md also documents the full public API with examples.

Known limitations (documented)

  • Nested <drag-drop-provider> — two providers of the same type will conflict on the shared $global key. For single-manager pages (the common case, including TanStack Table row reordering) this is a non-issue. A stack-based fix is planned for v2 after Marko v6 ships its native context API.
  • <drag-overlay> children — do not render <create-draggable>, <create-droppable>, or <create-sortable> inside the overlay. React/Svelte/Vue solve this with scoped context to prevent duplicate ID conflicts; Marko v6 has no scoped-context primitive yet. Documented in the overlay page.

Related

Changeset

  • @dnd-kit/markomajor

@changeset-bot

changeset-bot Bot commented Apr 18, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1555b04

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 11 packages
Name Type
@dnd-kit/marko Major
@dnd-kit/abstract Major
@dnd-kit/collision Major
@dnd-kit/dom Major
@dnd-kit/geometry Major
@dnd-kit/helpers Major
@dnd-kit/react Major
@dnd-kit/state Major
@dnd-kit/vue Major
@dnd-kit/solid Major
@dnd-kit/svelte Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@defunkt-dev

Copy link
Copy Markdown
Author

@clauderic can you pls help with this?

Bullet list inside <ParamField> had zero-indent items and no separating
blank line, causing MDX parser to misattribute the closing tag to the
list scope. Standardize on 2-space indent with surrounding blank lines.
OptimisticSortingPlugin.ts imports 'move' from @dnd-kit/helpers but
the package was missing from dom's dependencies and tsup external list.
Pre-existing upstream issue exposed by clean install.
StyleInjector.ts in @dnd-kit/dom references process.env.NODE_ENV
without @types/node available; tsup DTS build fails on a fresh
checkout. Add at workspace root since @types/node is conventionally
a monorepo-wide shared type.
Both packages previously relied on bun hoisting deps from sibling
workspace packages. Lockfile regeneration after upstream merge changed
the hoist targets, breaking npx and Node resolution respectively.
Declaring deps where they're used makes the contract correct and
immune to future hoist changes.
apps/docs/src/components/CodeBlock.tsx and SandpackCodeEditor.tsx
import shiki/*, @shikijs/transformers, @codemirror/*, @lezer/highlight,
and remark-gfm — none of which were in package.json. Worked locally
via hoisting; broke on fresh install.
@defunkt-dev

Copy link
Copy Markdown
Author

@clauderic can you pls help with this?

@clauderic clauderic left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Adds @dnd-kit/marko, a new Marko v6 framework adapter for @dnd-kit/dom, alongside React/Vue/Svelte/Solid. End-user API mirrors the other adapters (<drag-drop-provider> + consumer tags). Includes SSR unit tests, Playwright e2e tests, Storybook stories, and 6 new docs pages. The PR also bundles several pre-existing upstream build fixes and a lockfile format change.

This is a large PR (~8.7k additions). Whether to take on another framework adapter is a strategic decision for @clauderic — I'm leaving that to the maintainer and flagging the concrete things that need attention regardless of that call.

Feedback

Blocking issues

  • .changeset/yellow-mice-warn.md — bump type is major. The project is pre-1.0 and only patch / minor are allowed per the changeset rules. For a brand-new package that ships alongside the existing fixed-versioning set this should be minor. The .changeset/config.json change adds @dnd-kit/marko to the fixed array, so it will release in lockstep with the rest of the adapters at the next coordinated bump — a major here would force a major-version bump on every package in the fixed group.

Suggestions

  • PR size & scope — at 8.7k additions across 70+ files this is well past the threshold where splitting helps. The PR notes already cleanly separate the contents into three groups. Consider splitting into:
    1. The pre-existing upstream build fixes — these stand on their own and would be uncontroversial:
      • config/typescript/vanilla.json (ES2015 → ES2017)
      • packages/dom/tsconfig.json (skipLibCheck: true) and packages/dom/package.json + tsup.config.ts (declare @dnd-kit/helpers)
      • root package.json (@types/node)
      • apps/docs/package.json (shiki, codemirror, lezer deps)
      • apps/stories-shared/package.json (@playwright/test)
    2. The bun.lockbbun.lock format change — this is a one-line repo-wide decision (whether to track text or binary lockfiles) and should be deliberate, not a side effect.
    3. The Marko adapter itself + docs + stories.
      Each piece becomes much easier to review and revert independently.
  • PR base/head — the head branch is defunkt-dev:main, which works but means the contributor's fork's main is now tied to this PR. A feature branch like feat/marko-adapter is friendlier for follow-ups and for the contributor's own fork hygiene.
  • packages/marko/src/runtime/renderer.ts:1 — the double-queueMicrotask rationale and its tie to actions.ts await points is well-explained in the JSDoc. Worth keeping that comment as-is; it'll be the first thing anyone debugs.
  • Upstream issues flagged but not fixed — the PR notes call out packages/react (missing @dnd-kit/collision in deps/tsup external) and packages/vue (TS2742). Worth filing as separate issues so they aren't lost; I can do that if it's helpful.
  • Marko-specific limitations are honestly documented (nested providers, overlay children). Good. The maintainer should weigh whether the v6-no-scoped-context constraint on overlay children is acceptable given the existing API contract on other adapters.

Changeset

  • Status: present.
  • Bump type: incorrectmajor not allowed pre-1.0. Should be minor. (See blocking issues.)
  • Package name: correct (@dnd-kit/marko).

Overall

A lot of careful work went into this — the deep-equality prop syncing, the <let-global> context bridge, the per-property Preact signal effect bridge, and the SSR + Playwright test coverage all reflect real attention to how the adapter has to interoperate with the existing core. The honest enumeration of upstream issues (fixed and unfixed) is appreciated. The main asks here are mechanical (changeset bump type + splitting); the bigger question of whether to take on Marko is for @clauderic. Thanks for the thoroughness!


[claude-review]

@defunkt-dev

Copy link
Copy Markdown
Author

Thank you for looking into this. im working on this & will update this soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants