You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -48,7 +48,7 @@ Per-framework column = the de-facto leader for that framework (React = `@dnd-kit
48
48
49
49
⁵ **No two-way data binding.**`@dnd-kit`, `@angular/cdk`, and `@thisbeyond/solid-dnd` hand you a drag-end event (`onDragEnd` / `cdkDropListDropped` / drag store) and you mutate state yourself (CDK ships a `moveItemInArray` helper, but you call it). `vuedraggable` is the exception with real `v-model`. Rozie gives every target a two-way `items` array — pass an array, get a reordered array back, no manual `onChange → setState` wiring.
50
50
51
-
⁶ **Rozie's `SortableList`has no `$expose` imperative handle** — it predates the Phase-21 `$expose` capability that every other `@rozie-ui` port ships. See [G1](#gap-status-what-shipped-what-s-still-deferred).
51
+
⁶ **Rozie's `SortableList`now ships a uniform `$expose` imperative handle** — `getInstance` (raw SortableJS instance escape hatch) / `toArray` / `sort` / `option` — the *same* four verbs on all six targets, grabbed with each framework's native ref mechanism. The competitors all expose *something* (the dnd-kit context, the SortableJS instance, `CdkDropList`), but each its own way and per-framework; Rozie's is one shape everywhere. See [G1](#gap-status-what-shipped-what-s-still-deferred).
52
52
53
53
## Where Rozie wins today
54
54
@@ -57,6 +57,7 @@ Per-framework column = the de-facto leader for that framework (React = `@dnd-kit
57
57
-**Two-way bound `items` array** (`r-model:items`) on all six — the thing every dnd-kit / CDK / solid-dnd consumer wires by hand. Pass an array, render rows through the scoped default slot, get the reordered array back.
58
58
-**Cross-list sync + nesting from one source** — `SortableListPair` (atomic transfer across two bound arrays) and `SortableListNested` / `KanbanColumn` (reorderable columns of reorderable cards), the same `.rozie` compiled to all six targets.
59
59
-**Custom drag handles via `$classSelector`** — resolves on every target including React's scoped-CSS (authored class names render literally; `$classSelector` lowers to the literal `".grip"` per target and typo-checks it against your `<style>` at compile time).
60
+
-**A uniform imperative handle** (`$expose`) — `getInstance` / `toArray` / `sort` / `option`, the *same* four verbs on all six targets, grabbed with each framework's native ref. `getInstance()` is the raw-SortableJS escape hatch, so the full engine API is one hop away. The competitors all expose *something* (the dnd-kit context, the SortableJS instance, `CdkDropList`) — but each its own way, per framework. See the [showcase Imperative handle section](/guide/sortable-list#imperative-handle).
60
61
-**The hard part solved once** — the SortableJS-direct-DOM-mutation-vs-framework-reconciler dance (the reason these wrappers exist at all) is encapsulated in `useSortableJS()` plus the [`$reconcileAfterDomMutation()`](/guide/features#r-external-and-reconcileafterdommutation-—-dom-the-framework-doesn-t-own) sigil, hardened against SortableJS's fragile fallback-mode event shapes, across all six keyed reconcilers.
61
62
62
63
The ✅ cells in Rozie's row are pinned per target by the [sortable-drag VR spec](https://github.com/One-Learning-Community/rozie.js/blob/main/tests/visual-regression/specs/sortable-drag.spec.ts) — which measures *Rozie's* behavior across targets and says nothing measured about the competitors' behavior.
@@ -67,7 +68,7 @@ This page concedes where the standalone libraries are genuinely ahead — that's
67
68
68
69
| Gap | Who has it | Severity | Rozie status |
69
70
| --- | --- | --- | --- |
70
-
|**G1 — Imperative `$expose` handle**|`@dnd-kit` (sensors/context), CDK (`CdkDropList`) |**Medium**|**⏳ Deferred** — `SortableList` predates the Phase-21 `$expose` capability, so there is no uniform handle to reach the SortableJS instance or trigger a programmatic sort. Every other `@rozie-ui` port ships `$expose`; this is the holdout. |
71
+
|**G1 — Imperative `$expose` handle**|`@dnd-kit` (sensors/context), CDK (`CdkDropList`) |**Medium**|**✅ SHIPPED** — a uniform 4-verb handle (`getInstance` / `toArray` / `sort` / `option`) on all six targets, the same shape everywhere, grabbed with each framework's native ref. `getInstance()` is the raw-SortableJS escape hatch; rows now carry `data-id` so `toArray()` / `sort()` operate on the rendered key order. See the [showcase Imperative handle section](/guide/sortable-list#imperative-handle). |
71
72
| G2 — Live reconcile of construction-time knobs | (engine-level) | Low |**⏳ Deferred (by design)** — `forceFallback` / `swapThreshold` / `cloneable` are construction-time-only SortableJS knobs; changing them at runtime requires re-keying the component (the [documented re-mount pattern](/guide/sortable-list#remount-on-construction-time-only-changes)). The runtime-updatable props *are* live-reconciled via `instance.option()`. |
72
73
| G3 — List virtualization for large datasets |`@dnd-kit` (+ virtualizers) | Medium |**⏳ Deferred** — SortableJS renders all rows; very large lists want windowing. dnd-kit composes with `@tanstack/virtual`; Rozie has no virtualization story yet. |
73
74
| G4 — Multi-select / multi-drag |`@dnd-kit`, SortableJS MultiDrag plugin | Low |**⏳ Deferred** — SortableJS's `MultiDrag` plugin is not mounted, so dragging multiple rows at once isn't wired. (Plain SortableJS options pass through via `:options`; plugins need a mount Rozie doesn't yet bridge.) |
@@ -76,7 +77,6 @@ This page concedes where the standalone libraries are genuinely ahead — that's
76
77
## Honest caveats
77
78
78
79
-**Modern React leans dnd-kit, not SortableJS.**`@dnd-kit` (~17.0M/wk) is the React drag-and-drop standard in 2026 — sensors, virtualization, a rich ecosystem — and `react-beautiful-dnd` (~2.32M/wk), though deprecated/archived, is still everywhere. Rozie wraps **SortableJS**, a different engine with a simpler DOM-mutation model and its own tradeoffs. For a single-React app that needs virtualization or dnd-kit's sensor model, dnd-kit is the better pick. Rozie's value is cross-framework reach plus the keyboard / a11y / two-way contract from one source — not "better than dnd-kit on React."
79
-
-**No imperative handle yet (G1).** This is the one place `@rozie-ui/sortable-list` is behind its own sibling ports; it's the top roadmap item for this component.
80
80
-**Angular CDK and svelte-dnd-action are first-rate native toolkits.** CDK (first-party, connected lists, keyboard) and svelte-dnd-action (FLIP animations, Svelte 5, actively maintained) are excellent *single-framework* choices. The matrix scores cross-framework reach, not single-framework ergonomics.
81
81
-**`@rozie-ui/sortable-list` is `0.1.0`.** The surface (17 props / 5 events / scoped row slot) is stable and VR-pinned, but younger than the established libraries — and it inherits SortableJS's engine-level limitations (touch-fallback fragility, no windowing) along with its strengths.
Copy file name to clipboardExpand all lines: docs/guide/sortable-list.md
+30Lines changed: 30 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -107,6 +107,36 @@ The default slot receives `{ item, index }`:
107
107
108
108
To rename a slot param to a more readable local name in nested-template contexts, use the slot-param rename form `{ item: column }` — see [scoped slot params](/guide/features#slots-with-scoped-params).
109
109
110
+
### Imperative handle
111
+
112
+
Beyond props and the two-way `items` array, the component exposes imperative methods declared once in the Rozie source via `$expose`. Grab a handle with your framework's native ref mechanism (React `useRef` / Vue template ref / Svelte `bind:this` / Angular `viewChild` / Solid callback ref / the Lit custom element itself) and call them directly:
113
+
114
+
| Method | Description |
115
+
| --- | --- |
116
+
|`getInstance`| Return the underlying SortableJS instance for direct API access — the raw-engine escape hatch (`save`, `closest`, … are one hop away). `null` before mount and after destroy. |
117
+
|`toArray`| Return the current order as an array of `data-id` strings. Each row carries `data-id="<key>"` (the same [`itemKey`](#api)-derived key as the reconciler), so the array reflects the live key order. `[]` before mount. |
118
+
|`sort`| Reorder the list by an array of `data-id` strings — `sort(order, useAnimation = true)`. |
119
+
|`option`| Read or set a live SortableJS option — `option(name)` gets, `option(name, value)` sets. The runtime escape hatch for any SortableJS option beyond the curated props (and the construction-time-only ones, within SortableJS's own limits). |
const order =sl.current?.toArray(); // current key order
130
+
sl.current?.option('disabled', true); // disable at runtime
131
+
const instance =sl.current?.getInstance(); // raw SortableJS instance
132
+
```
133
+
134
+
The four verb names are clear of all sixteen prop names and the five events (`option` is a distinct identifier from the `options` prop), so the `$expose` collision discipline (ROZ121) passes with no renames.
135
+
136
+
::: tip `toArray` / `sort` rely on `data-id`
137
+
Each rendered row carries `data-id="<key>"`, derived from [`itemKey`](#api) (falling back to the item value, then the index). Set `itemKey` for object lists so `toArray()` / `sort()` operate on stable keys rather than `"[object Object]"`.
0 commit comments