@rozie-ui/sortable-list-lit 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,772 @@
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ //#region \0rolldown/runtime.js
6
+ var __create = Object.create;
7
+ var __defProp = Object.defineProperty;
8
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
9
+ var __getOwnPropNames = Object.getOwnPropertyNames;
10
+ var __getProtoOf = Object.getPrototypeOf;
11
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
14
+ key = keys[i];
15
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
16
+ get: ((k) => from[k]).bind(null, key),
17
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
18
+ });
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
23
+ value: mod,
24
+ enumerable: true
25
+ }) : target, mod));
26
+ //#endregion
27
+ let lit = require("lit");
28
+ let lit_decorators_js = require("lit/decorators.js");
29
+ let _lit_labs_preact_signals = require("@lit-labs/preact-signals");
30
+ let _rozie_runtime_lit = require("@rozie/runtime-lit");
31
+ let lit_directives_repeat_js = require("lit/directives/repeat.js");
32
+ let lit_directives_keyed_js = require("lit/directives/keyed.js");
33
+ let sortablejs = require("sortablejs");
34
+ sortablejs = __toESM(sortablejs, 1);
35
+ //#region src/internal/useSortableJS.ts
36
+ /**
37
+ * useSortableJS — framework-agnostic SortableJS-vs-reconciler bridge.
38
+ *
39
+ * Why this exists
40
+ * ---------------
41
+ * Wrapping SortableJS for cross-framework consumption (Rozie's
42
+ * `examples/SortableList.rozie`) has four recurring fragilities every wrapper
43
+ * library independently re-discovers:
44
+ *
45
+ * 1. **Three-handler ambiguity.** SortableJS fires distinct `onUpdate`,
46
+ * `onAdd`, and `onRemove` events for the same logical drag in cross-list
47
+ * moves. Inline handlers in user code can ONLY disambiguate by reading
48
+ * `e.from === listEl` / `e.to === listEl`, which is the same disambiguation
49
+ * `onEnd` can perform in a SINGLE handler. Collapsing to one onEnd path
50
+ * removes the per-handler boilerplate.
51
+ *
52
+ * 2. **Fragile event shapes.** When SortableJS falls back to the mouse-event
53
+ * drag path (Playwright's mouse.move/down/up sequence; some touch devices;
54
+ * HTML5 DnD aborts), `e.item` may not be a valid Node by the time the
55
+ * handler runs (the source-side `onRemove` fires AFTER the destination
56
+ * has already detached the element). Inline
57
+ * `listEl.insertBefore(e.item, …)` throws inside SortableJS's
58
+ * `_dispatchEvent`. SortableJS catches and swallows the throw silently —
59
+ * the model writeback never runs, leaving the framework's view stale.
60
+ * THIS is the root cause of the duplicate-on-drop bug surfaced by the
61
+ * `sortable-nested-solid-deeper` VR spec.
62
+ *
63
+ * 3. **`e.oldIndex` is occasionally null.** Fallback-mode events lose the
64
+ * index. Identity-based item lookup (`items().indexOf(stashedItem)`) is
65
+ * strictly more reliable.
66
+ *
67
+ * 4. **Lit lit-html `repeat`-cache desync.** Every reconciler EXCEPT Lit
68
+ * handles the SortableJS DOM-restore + model writeback dance cleanly.
69
+ * Lit needs `$reconcileAfterDomMutation()` (a Rozie sigil that lowers to
70
+ * `__rozieReconcileAfterDomMutation(this)` on Lit + `void 0` elsewhere).
71
+ * The helper invokes the `afterCommit` callback after each `onCommit`;
72
+ * users wire `afterCommit: () => $reconcileAfterDomMutation()` to honour
73
+ * the Lit requirement without leaking lit-html internals into user code.
74
+ *
75
+ * Cross-target API contract
76
+ * -------------------------
77
+ * The helper is vanilla JS. It has NO framework imports. The same exported
78
+ * symbol resolves identically across React, Vue, Svelte, Angular, Solid and
79
+ * Lit consumer builds via the colocated relative `./internal/useSortableJS`
80
+ * copy vendored into each leaf package by scripts/codegen.mjs.
81
+ *
82
+ * Caller contract:
83
+ * - `items: () => T[]` is called fresh on every event. Wire it to the
84
+ * current snapshot (`$props.items` etc.).
85
+ * - `onCommit(next)` is called once per successful drag commit with the
86
+ * new items array. The caller writes it to whatever reactive surface
87
+ * drives re-renders.
88
+ * - `afterCommit?()` is called immediately after each `onCommit`. Used on
89
+ * Lit to invoke `$reconcileAfterDomMutation()`.
90
+ *
91
+ * Returns `{ destroy, instance }`. The caller wires
92
+ * - teardown via `return handle.destroy` from `$onMount`
93
+ * - runtime option updates via `instance.option('disabled', v)` from
94
+ * `$watch(() => $props.disabled, ...)`.
95
+ *
96
+ * @public
97
+ */
98
+ /** Symbol stashed on `e.item` between `onStart` and `onEnd` to ferry the
99
+ * dragged item DATA (not just the DOM node) across cross-list moves and
100
+ * survive `e.oldIndex` going null in fallback-mode events. The double
101
+ * underscore prefix signals "Rozie-internal, do not collide with user
102
+ * data". */
103
+ const ROZIE_ITEM_STASH_KEY = "__rozieItem";
104
+ /** Read the stashed item, falling back to a `null`/`undefined` when the
105
+ * element doesn't have the property. Centralised so the cast lives in
106
+ * exactly one place. */
107
+ function readStash(item) {
108
+ if (item === null || typeof item !== "object") return void 0;
109
+ return item[ROZIE_ITEM_STASH_KEY];
110
+ }
111
+ /** Try-catch wrapper for DOM operations that may throw on fragile event
112
+ * paths. Returning `false` lets the caller proceed to the model writeback
113
+ * — the framework's re-render off the new model brings the DOM back into
114
+ * sync (may flicker briefly, but vastly better than today's silent drop). */
115
+ function safeDom(op) {
116
+ try {
117
+ op();
118
+ return true;
119
+ } catch {
120
+ return false;
121
+ }
122
+ }
123
+ /**
124
+ * Wire a SortableJS instance to a reactive items array. See module doc
125
+ * for the full rationale + cross-target contract.
126
+ */
127
+ function useSortableJS(listEl, opts) {
128
+ const baseOptions = { ...opts.options ?? {} };
129
+ /** Item-scoped child lookup. `listEl.children` now also includes the
130
+ * non-draggable `#header` / `#footer` slot holes (direct children of the
131
+ * list container), so the DOM-restore sites must index ONLY the item rows —
132
+ * `:scope > .rozie-sortable-item` — to keep the restore index item-relative
133
+ * and correct (no off-by-one from header/footer). */
134
+ const itemChildAt = (i) => listEl.querySelectorAll(":scope > .rozie-sortable-item")[i] ?? null;
135
+ delete baseOptions.onStart;
136
+ delete baseOptions.onEnd;
137
+ delete baseOptions.onUpdate;
138
+ delete baseOptions.onAdd;
139
+ delete baseOptions.onRemove;
140
+ delete baseOptions.onClone;
141
+ /** Stash dragged item DATA on `e.item` so cross-list moves can recover
142
+ * it on the destination side. */
143
+ const handleStart = (e) => {
144
+ const current = opts.items();
145
+ const fromIdx = typeof e.oldDraggableIndex === "number" ? e.oldDraggableIndex : typeof e.oldIndex === "number" ? e.oldIndex : -1;
146
+ if (fromIdx >= 0 && fromIdx < current.length) {
147
+ const item = e.item;
148
+ if (item !== null && typeof item === "object") item[ROZIE_ITEM_STASH_KEY] = current[fromIdx];
149
+ }
150
+ opts.onStart?.(e);
151
+ };
152
+ /** The single point of truth for committed drags. Disambiguates via
153
+ * `e.from` and `e.to`.
154
+ *
155
+ * SortableJS's event lifecycle is asymmetric across lists: `onEnd` fires
156
+ * ONLY on the source list (the one that owned `e.item` at drag start).
157
+ * The destination list of a cross-list move receives an `onAdd` event,
158
+ * NOT an `onEnd`. We register this handler against BOTH:
159
+ * - source-list `onEnd` — handles same-list reorder + cross-list source
160
+ * - destination-list `onAdd` — handles cross-list destination
161
+ *
162
+ * The single function services both event names because the
163
+ * `from === listEl` / `to === listEl` disambiguation is sufficient to
164
+ * determine our role; SortableJS's event name doesn't carry additional
165
+ * information we need. Registering both is the load-bearing fix — the
166
+ * inline-handlers design DID register onAdd; collapsing to onEnd-only
167
+ * would lose the destination-side commit entirely. */
168
+ const handleCommit = (e) => {
169
+ try {
170
+ const fromUs = e.from === listEl;
171
+ const toUs = e.to === listEl;
172
+ const sameList = fromUs && toUs;
173
+ if (!fromUs && !toUs) return;
174
+ const current = opts.items();
175
+ const kind = sameList ? "reorder" : fromUs ? "remove" : "add";
176
+ const stashedItem = readStash(e.item);
177
+ const oldIndexHint = typeof e.oldDraggableIndex === "number" ? e.oldDraggableIndex : typeof e.oldIndex === "number" ? e.oldIndex : -1;
178
+ const newIndexHint = typeof e.newDraggableIndex === "number" ? e.newDraggableIndex : typeof e.newIndex === "number" ? e.newIndex : -1;
179
+ let next;
180
+ let oldIndex;
181
+ let newIndex;
182
+ let moved;
183
+ if (sameList) {
184
+ const movedFromIdx = stashedItem !== void 0 && current.indexOf(stashedItem) !== -1 ? current.indexOf(stashedItem) : oldIndexHint;
185
+ if (movedFromIdx < 0 || movedFromIdx >= current.length) return;
186
+ moved = current[movedFromIdx];
187
+ safeDom(() => {
188
+ const ref = itemChildAt(movedFromIdx);
189
+ listEl.insertBefore(e.item, ref);
190
+ });
191
+ next = [...current];
192
+ next.splice(movedFromIdx, 1);
193
+ const targetIdx = newIndexHint >= 0 && newIndexHint <= next.length ? newIndexHint : next.length;
194
+ next.splice(targetIdx, 0, moved);
195
+ oldIndex = movedFromIdx;
196
+ newIndex = targetIdx;
197
+ } else if (fromUs) {
198
+ if (e.pullMode === "clone") return;
199
+ const movedFromIdx = stashedItem !== void 0 && current.indexOf(stashedItem) !== -1 ? current.indexOf(stashedItem) : oldIndexHint;
200
+ if (movedFromIdx < 0 || movedFromIdx >= current.length) return;
201
+ moved = current[movedFromIdx];
202
+ safeDom(() => {
203
+ const ref = itemChildAt(movedFromIdx);
204
+ listEl.insertBefore(e.item, ref);
205
+ });
206
+ next = [...current];
207
+ next.splice(movedFromIdx, 1);
208
+ oldIndex = movedFromIdx;
209
+ newIndex = -1;
210
+ } else {
211
+ moved = stashedItem;
212
+ if (moved === void 0) {
213
+ safeDom(() => {
214
+ e.item?.remove?.();
215
+ });
216
+ return;
217
+ }
218
+ safeDom(() => {
219
+ e.item?.remove?.();
220
+ });
221
+ next = [...current];
222
+ const targetIdx = newIndexHint >= 0 && newIndexHint <= next.length ? newIndexHint : next.length;
223
+ next.splice(targetIdx, 0, moved);
224
+ oldIndex = -1;
225
+ newIndex = targetIdx;
226
+ }
227
+ queueMicrotask(() => {
228
+ opts.onCommit(next);
229
+ opts.afterCommit?.();
230
+ opts.onChange?.({
231
+ kind,
232
+ oldIndex,
233
+ newIndex,
234
+ item: moved
235
+ });
236
+ });
237
+ } finally {
238
+ if (e.from === listEl) {
239
+ const item = e.item;
240
+ if (item !== null && typeof item === "object") delete item[ROZIE_ITEM_STASH_KEY];
241
+ opts.onEnd?.(e);
242
+ }
243
+ }
244
+ };
245
+ /** Bridge the `__rozieItem` stash from the original DOM node to its
246
+ * clone when SortableJS is in clone mode (`group.pull === 'clone'`).
247
+ *
248
+ * Clone-mode lifecycle:
249
+ * 1. `onStart` fires on the source — we stash the dragged item DATA
250
+ * on `e.item.__rozieItem`.
251
+ * 2. SortableJS clones the node — `clone` is the new DOM element
252
+ * that physically travels to the destination.
253
+ * 3. `onClone` fires with `{ item, clone }` — THIS HOOK — we copy
254
+ * the stash from `item` to `clone` so step 4 can find it.
255
+ * 4. `onAdd` fires on the destination with `e.item === clone`
256
+ * (NOT the original). Destination-side `handleCommit` reads
257
+ * `__rozieItem` off the clone via `readStash` → recovers the
258
+ * dragged item DATA, splices it into its array.
259
+ * 5. `onEnd` fires on the source with `e.item === original`.
260
+ * `handleCommit` (clone-mode short-circuit) returns early so
261
+ * no spurious `remove` change is fired; the `finally` cleans
262
+ * up the stash on the original.
263
+ *
264
+ * The `clone` DOM property is not in SortableJS's strict d.ts for
265
+ * `SortableEvent` (it's documented in their JS source but not the
266
+ * exported types), so we widen the parameter type locally. */
267
+ const handleClone = (e) => {
268
+ const original = e.item;
269
+ const clone = e.clone;
270
+ if (original !== null && typeof original === "object" && clone !== null && typeof clone === "object" && ROZIE_ITEM_STASH_KEY in original) clone[ROZIE_ITEM_STASH_KEY] = original[ROZIE_ITEM_STASH_KEY];
271
+ };
272
+ const instance = new sortablejs.default(listEl, {
273
+ draggable: ".rozie-sortable-item",
274
+ ...baseOptions,
275
+ onStart: handleStart,
276
+ onEnd: handleCommit,
277
+ onAdd: handleCommit,
278
+ onClone: handleClone
279
+ });
280
+ return {
281
+ destroy: () => {
282
+ instance.destroy();
283
+ },
284
+ instance
285
+ };
286
+ }
287
+ //#endregion
288
+ //#region \0@oxc-project+runtime@0.127.0/helpers/decorate.js
289
+ function __decorate(decorators, target, key, desc) {
290
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
291
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
292
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
293
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
294
+ }
295
+ //#endregion
296
+ //#region src/SortableList.ts
297
+ let SortableList = class SortableList extends (0, _lit_labs_preact_signals.SignalWatcher)(lit.LitElement) {
298
+ constructor(..._args) {
299
+ super(..._args);
300
+ this._items_attr = [];
301
+ this._itemsControllable = (0, _rozie_runtime_lit.createLitControllableProperty)({
302
+ host: this,
303
+ eventName: "items-change",
304
+ defaultValue: [],
305
+ initialControlledValue: void 0
306
+ });
307
+ this.itemKey = null;
308
+ this.handle = null;
309
+ this.group = null;
310
+ this.animation = 150;
311
+ this.disabled = false;
312
+ this.disableKeyboard = false;
313
+ this.options = {};
314
+ this.labelFor = null;
315
+ this.ghostClass = null;
316
+ this.chosenClass = null;
317
+ this.dragClass = null;
318
+ this.filter = null;
319
+ this.easing = null;
320
+ this.forceFallback = false;
321
+ this.swapThreshold = 1;
322
+ this.cloneable = false;
323
+ this.listClass = "";
324
+ this.itemClass = "";
325
+ this.itemStyle = null;
326
+ this._liftedIndex = (0, _lit_labs_preact_signals.signal)(null);
327
+ this._ariaLiveText = (0, _lit_labs_preact_signals.signal)("");
328
+ this.__rozieFirstUpdateDone = false;
329
+ this._hasSlotHeader = false;
330
+ this._hasSlotDefault = false;
331
+ this._hasSlotFooter = false;
332
+ this._disconnectCleanups = [];
333
+ this._rozieTornDown = false;
334
+ this._rozieReconcileSeq = 0;
335
+ this.instance = null;
336
+ this.__rowKey = {
337
+ map: /* @__PURE__ */ new WeakMap(),
338
+ seq: 0
339
+ };
340
+ this.keyFor = (item, index) => {
341
+ if (typeof this.itemKey === "function") return this.itemKey(item, index);
342
+ if (typeof this.itemKey === "string" && item !== null && typeof item === "object" && item[this.itemKey] != null) return item[this.itemKey];
343
+ if (item !== null && typeof item === "object" || typeof item === "function") {
344
+ if (!this.__rowKey.map.has(item)) this.__rowKey.map.set(item, "__rk" + this.__rowKey.seq++);
345
+ return this.__rowKey.map.get(item);
346
+ }
347
+ return index;
348
+ };
349
+ this.itemClassFor = (item, index) => {
350
+ const v = this.itemClass;
351
+ return typeof v === "function" ? v(item, index) : v;
352
+ };
353
+ this.itemStyleFor = (item, index) => {
354
+ const s = typeof this.itemStyle === "function" ? this.itemStyle(item, index) : this.itemStyle;
355
+ return s == null || s === "" ? null : s;
356
+ };
357
+ this.getLabel = (idx) => {
358
+ const item = this.items[idx];
359
+ if (this.labelFor !== null) return this.labelFor(item, idx);
360
+ if (item !== null && typeof item === "object" && "label" in item) return item.label;
361
+ return String(item);
362
+ };
363
+ this.keyboardEnabled = () => !this.disabled && !this.disableKeyboard;
364
+ this.onRowKeyDown = ($event, index) => {
365
+ if (!this.keyboardEnabled()) return;
366
+ const key = $event.key;
367
+ if (key === " " || key === "Spacebar" || key === "Enter") {
368
+ $event.preventDefault();
369
+ if (this._liftedIndex.value === null) {
370
+ this._liftedIndex.value = index;
371
+ this._ariaLiveText.value = "Lifted " + this.getLabel(index);
372
+ return;
373
+ }
374
+ const dropped = this.getLabel(this._liftedIndex.value);
375
+ const at = this._liftedIndex.value;
376
+ this._liftedIndex.value = null;
377
+ this._ariaLiveText.value = "Dropped " + dropped + " at position " + (at + 1);
378
+ return;
379
+ }
380
+ if (key === "Escape") {
381
+ if (this._liftedIndex.value === null) return;
382
+ $event.preventDefault();
383
+ const cancelled = this.getLabel(this._liftedIndex.value);
384
+ this._liftedIndex.value = null;
385
+ this._ariaLiveText.value = "Cancelled lift of " + cancelled;
386
+ return;
387
+ }
388
+ if (key === "ArrowDown" || key === "ArrowUp") {
389
+ if (this._liftedIndex.value === null) return;
390
+ $event.preventDefault();
391
+ const dir = key === "ArrowDown" ? 1 : -1;
392
+ const from = this._liftedIndex.value;
393
+ const to = from + dir;
394
+ if (to < 0 || to >= this.items.length) return;
395
+ const next = [...this.items];
396
+ const [moved] = next.splice(from, 1);
397
+ next.splice(to, 0, moved);
398
+ this._itemsControllable.write(next);
399
+ this._liftedIndex.value = to;
400
+ this._ariaLiveText.value = "Moved " + this.getLabel(to) + " to position " + (to + 1);
401
+ queueMicrotask(() => (this.renderRoot.querySelectorAll("[role=\"listitem\"]")?.[to])?.focus?.());
402
+ this.dispatchEvent(new CustomEvent("change", {
403
+ detail: {
404
+ oldIndex: from,
405
+ newIndex: to,
406
+ item: moved
407
+ },
408
+ bubbles: true,
409
+ composed: true
410
+ }));
411
+ }
412
+ };
413
+ }
414
+ static {
415
+ this.styles = lit.css`
416
+ .rozie-sortable-wrap[data-rozie-s-0af24eae] { display: block; }
417
+ .rozie-sortable-list[data-rozie-s-0af24eae] { display: block; }
418
+ .rozie-sortable-item[data-rozie-s-0af24eae] { display: block; outline: none; }
419
+ .rozie-sortable-item[data-rozie-s-0af24eae]:focus { outline: 2px solid rgba(0, 102, 204, 0.6); outline-offset: -2px; }
420
+ .rozie-sortable-item-lifted[data-rozie-s-0af24eae] {
421
+ background: rgba(0, 102, 204, 0.08);
422
+ box-shadow: 0 0 0 2px rgba(0, 102, 204, 0.4) inset;
423
+ }
424
+ .rozie-sortable-aria-live[data-rozie-s-0af24eae] {
425
+ position: absolute;
426
+ width: 1px;
427
+ height: 1px;
428
+ padding: 0;
429
+ margin: -1px;
430
+ overflow: hidden;
431
+ clip: rect(0, 0, 0, 0);
432
+ white-space: nowrap;
433
+ border: 0;
434
+ }
435
+ `;
436
+ }
437
+ _armListeners() {
438
+ {
439
+ const slotEl = this.shadowRoot?.querySelector("slot[name=\"header\"]");
440
+ if (slotEl !== null && slotEl !== void 0) {
441
+ const update = () => {
442
+ this._hasSlotHeader = this._slotHeaderElements.length > 0;
443
+ };
444
+ slotEl.addEventListener("slotchange", update);
445
+ this._disconnectCleanups.push(() => slotEl.removeEventListener("slotchange", update));
446
+ update();
447
+ }
448
+ }
449
+ {
450
+ const slotEl = this.shadowRoot?.querySelector("slot:not([name])");
451
+ if (slotEl !== null && slotEl !== void 0) {
452
+ const update = () => {
453
+ this._hasSlotDefault = this._slotDefaultElements.length > 0;
454
+ };
455
+ slotEl.addEventListener("slotchange", update);
456
+ this._disconnectCleanups.push(() => slotEl.removeEventListener("slotchange", update));
457
+ update();
458
+ }
459
+ }
460
+ {
461
+ const slotEl = this.shadowRoot?.querySelector("slot[name=\"footer\"]");
462
+ if (slotEl !== null && slotEl !== void 0) {
463
+ const update = () => {
464
+ this._hasSlotFooter = this._slotFooterElements.length > 0;
465
+ };
466
+ slotEl.addEventListener("slotchange", update);
467
+ this._disconnectCleanups.push(() => slotEl.removeEventListener("slotchange", update));
468
+ update();
469
+ }
470
+ }
471
+ }
472
+ connectedCallback() {
473
+ this._hasSlotHeader = Array.from(this.children).some((el) => el.getAttribute("slot") === "header");
474
+ this._hasSlotDefault = Array.from(this.children).some((el) => !el.hasAttribute("slot") && (el.nodeType !== 3 || (el.textContent?.trim().length ?? 0) > 0));
475
+ this._hasSlotFooter = Array.from(this.children).some((el) => el.getAttribute("slot") === "footer");
476
+ super.connectedCallback();
477
+ if (this.hasUpdated && this._rozieTornDown) {
478
+ this._rozieTornDown = false;
479
+ this._armListeners();
480
+ }
481
+ }
482
+ firstUpdated() {
483
+ this._armListeners();
484
+ this._disconnectCleanups.push((() => this.instance?.destroy()));
485
+ const sortable = useSortableJS(this._refListEl, {
486
+ items: () => this.items,
487
+ onCommit: (next) => {
488
+ this._itemsControllable.write(next);
489
+ },
490
+ options: {
491
+ animation: this.animation,
492
+ disabled: this.disabled,
493
+ group: this.cloneable && typeof this.group === "string" ? {
494
+ name: this.group,
495
+ pull: "clone",
496
+ put: true
497
+ } : this.group,
498
+ handle: this.handle,
499
+ ghostClass: this.ghostClass,
500
+ chosenClass: this.chosenClass,
501
+ dragClass: this.dragClass,
502
+ filter: this.filter,
503
+ forceFallback: this.forceFallback,
504
+ swapThreshold: this.swapThreshold,
505
+ easing: this.easing,
506
+ ...this.options
507
+ },
508
+ afterCommit: () => (0, _rozie_runtime_lit.__rozieReconcileAfterDomMutation)(this),
509
+ onChange: ({ kind, oldIndex, newIndex, item }) => {
510
+ if (kind === "reorder") this.dispatchEvent(new CustomEvent("change", {
511
+ detail: {
512
+ oldIndex,
513
+ newIndex,
514
+ item
515
+ },
516
+ bubbles: true,
517
+ composed: true
518
+ }));
519
+ else if (kind === "add") this.dispatchEvent(new CustomEvent("add", {
520
+ detail: {
521
+ newIndex,
522
+ item
523
+ },
524
+ bubbles: true,
525
+ composed: true
526
+ }));
527
+ else if (kind === "remove") this.dispatchEvent(new CustomEvent("remove", {
528
+ detail: {
529
+ oldIndex,
530
+ item
531
+ },
532
+ bubbles: true,
533
+ composed: true
534
+ }));
535
+ },
536
+ onStart: (e) => this.dispatchEvent(new CustomEvent("start", {
537
+ detail: e,
538
+ bubbles: true,
539
+ composed: true
540
+ })),
541
+ onEnd: (e) => this.dispatchEvent(new CustomEvent("end", {
542
+ detail: e,
543
+ bubbles: true,
544
+ composed: true
545
+ }))
546
+ });
547
+ this.instance = sortable.instance;
548
+ }
549
+ updated(changedProperties) {
550
+ if (this.__rozieFirstUpdateDone && changedProperties.has("disabled")) ((v) => this.instance?.option("disabled", v))(this.disabled);
551
+ if (this.__rozieFirstUpdateDone && changedProperties.has("group")) ((v) => this.instance?.option("group", v))(this.group);
552
+ if (this.__rozieFirstUpdateDone && changedProperties.has("handle")) ((v) => this.instance?.option("handle", v))(this.handle);
553
+ if (this.__rozieFirstUpdateDone && changedProperties.has("ghostClass")) ((v) => this.instance?.option("ghostClass", v))(this.ghostClass);
554
+ if (this.__rozieFirstUpdateDone && changedProperties.has("chosenClass")) ((v) => this.instance?.option("chosenClass", v))(this.chosenClass);
555
+ if (this.__rozieFirstUpdateDone && changedProperties.has("dragClass")) ((v) => this.instance?.option("dragClass", v))(this.dragClass);
556
+ if (this.__rozieFirstUpdateDone && changedProperties.has("filter")) ((v) => this.instance?.option("filter", v))(this.filter);
557
+ if (this.__rozieFirstUpdateDone && changedProperties.has("easing")) ((v) => this.instance?.option("easing", v))(this.easing);
558
+ this.__rozieFirstUpdateDone = true;
559
+ }
560
+ disconnectedCallback() {
561
+ super.disconnectedCallback();
562
+ queueMicrotask(() => {
563
+ if (this.isConnected || this._rozieTornDown) return;
564
+ this._rozieTornDown = true;
565
+ for (const fn of this._disconnectCleanups) fn();
566
+ this._disconnectCleanups = [];
567
+ });
568
+ }
569
+ attributeChangedCallback(name, old, value) {
570
+ super.attributeChangedCallback(name, old, value);
571
+ if (name === "items") this._itemsControllable.notifyAttributeChange(value);
572
+ }
573
+ render() {
574
+ return lit.html`
575
+ <div class="rozie-sortable-wrap" ${(0, _rozie_runtime_lit.rozieSpread)(this.$attrs)} ${(0, _rozie_runtime_lit.rozieListeners)(this.$listeners)} data-rozie-ref="__rozieRoot" data-rozie-s-0af24eae>
576
+ <div class="${(0, _rozie_runtime_lit.rozieClass)(["rozie-sortable-list", this.listClass])}" part="list" data-rozie-ref="listEl" data-rozie-s-0af24eae>${(0, lit_directives_keyed_js.keyed)(this._rozieReconcileSeq ?? 0, lit.html`
577
+ <slot name="header"></slot>
578
+ ${(0, lit_directives_repeat_js.repeat)(this.items, (item, index) => this.keyFor(item, index), (item, index) => lit.html`<div class="${(0, _rozie_runtime_lit.rozieClass)([
579
+ "rozie-sortable-item",
580
+ this.itemClassFor(item, index),
581
+ { "rozie-sortable-item-lifted": this._liftedIndex.value === index }
582
+ ])}" key=${(0, _rozie_runtime_lit.rozieAttr)(this.keyFor(item, index))} style=${(0, _rozie_runtime_lit.rozieStyle)(this.itemStyleFor(item, index))} data-id=${(0, _rozie_runtime_lit.rozieAttr)(this.keyFor(item, index))} role="listitem" tabindex=${(0, _rozie_runtime_lit.rozieAttr)(this.keyboardEnabled() ? 0 : null)} @keydown=${($event) => {
583
+ this.onRowKeyDown($event, index);
584
+ }} data-rozie-s-0af24eae>
585
+ ${this.__rozieDefaultSlot__ !== void 0 ? this.__rozieDefaultSlot__({
586
+ item,
587
+ index
588
+ }) : lit.html`<slot data-rozie-params=${(() => {
589
+ try {
590
+ return JSON.stringify({
591
+ item,
592
+ index
593
+ });
594
+ } catch {
595
+ return "{}";
596
+ }
597
+ })()}></slot>`}
598
+ </div>`)}
599
+ <slot name="footer"></slot>
600
+ `)}</div>
601
+ <div class="rozie-sortable-aria-live" data-rozie-sortable-aria-live="" aria-live="polite" aria-atomic="true" data-rozie-s-0af24eae>${this._ariaLiveText.value}</div>
602
+ </div>
603
+ `;
604
+ }
605
+ getInstance() {
606
+ return this.instance;
607
+ }
608
+ toArray() {
609
+ return this.instance ? this.instance.toArray() : [];
610
+ }
611
+ sort(order, useAnimation = true) {
612
+ this.instance?.sort(order, useAnimation);
613
+ }
614
+ option(name, value) {
615
+ if (!this.instance) return void 0;
616
+ if (value === void 0) return this.instance.option(name);
617
+ this.instance.option(name, value);
618
+ return value;
619
+ }
620
+ get items() {
621
+ return this._itemsControllable.read();
622
+ }
623
+ set items(v) {
624
+ this._itemsControllable.notifyPropertyWrite(v);
625
+ }
626
+ /**
627
+ * Plan 14-05 — cross-framework attribute fallthrough source. Reads the
628
+ * host custom element's attributes on each call so a consumer-side bound
629
+ * attribute flows through on every render. The `rozieSpread` directive
630
+ * (D-02) does the cross-render diff downstream.
631
+ *
632
+ * Phase 15 follow-up Bug A — declared-prop attribute names are filtered
633
+ * out so `$attrs` returns "rest after declared props" (semantic parity
634
+ * with React/Vue/Svelte/Solid/Angular). Both Lit attribute-naming
635
+ * forms are folded into the skip set: kebab-case for model props
636
+ * (explicit `attribute:`) AND lowercased property name (Lit's default).
637
+ */
638
+ get $attrs() {
639
+ const __skip = new Set([
640
+ "items",
641
+ "item-key",
642
+ "itemkey",
643
+ "handle",
644
+ "group",
645
+ "animation",
646
+ "disabled",
647
+ "disable-keyboard",
648
+ "disablekeyboard",
649
+ "options",
650
+ "label-for",
651
+ "labelfor",
652
+ "ghost-class",
653
+ "ghostclass",
654
+ "chosen-class",
655
+ "chosenclass",
656
+ "drag-class",
657
+ "dragclass",
658
+ "filter",
659
+ "easing",
660
+ "force-fallback",
661
+ "forcefallback",
662
+ "swap-threshold",
663
+ "swapthreshold",
664
+ "cloneable",
665
+ "list-class",
666
+ "listclass",
667
+ "item-class",
668
+ "itemclass",
669
+ "item-style",
670
+ "itemstyle"
671
+ ]);
672
+ const out = {};
673
+ for (const a of Array.from(this.attributes)) {
674
+ if (__skip.has(a.name)) continue;
675
+ out[a.name] = a.value;
676
+ }
677
+ return out;
678
+ }
679
+ /**
680
+ * Phase 15 D-19 — consumer-passed listener cluster placeholder.
681
+ * Lit attaches event listeners directly on the host element via
682
+ * `addEventListener` (no per-instance prop rest binding), so the
683
+ * runtime value is undefined; the `rozieListeners` directive's
684
+ * nullish coercion (`obj ?? {}`) handles the no-op cleanly.
685
+ * The declaration exists to satisfy `tsc --noEmit` on consumer
686
+ * projects with strict mode — bare `$listeners` in `render()`
687
+ * would otherwise raise TS2304 (Cannot find name).
688
+ */
689
+ get $listeners() {}
690
+ };
691
+ __decorate([(0, lit_decorators_js.property)({
692
+ type: Array,
693
+ attribute: "items"
694
+ })], SortableList.prototype, "_items_attr", void 0);
695
+ __decorate([(0, lit_decorators_js.property)({ type: String })], SortableList.prototype, "itemKey", void 0);
696
+ __decorate([(0, lit_decorators_js.property)({
697
+ type: String,
698
+ reflect: true
699
+ })], SortableList.prototype, "handle", void 0);
700
+ __decorate([(0, lit_decorators_js.property)({
701
+ type: String,
702
+ reflect: true
703
+ })], SortableList.prototype, "group", void 0);
704
+ __decorate([(0, lit_decorators_js.property)({
705
+ type: Number,
706
+ reflect: true
707
+ })], SortableList.prototype, "animation", void 0);
708
+ __decorate([(0, lit_decorators_js.property)({
709
+ type: Boolean,
710
+ reflect: true
711
+ })], SortableList.prototype, "disabled", void 0);
712
+ __decorate([(0, lit_decorators_js.property)({
713
+ type: Boolean,
714
+ reflect: true
715
+ })], SortableList.prototype, "disableKeyboard", void 0);
716
+ __decorate([(0, lit_decorators_js.property)({ type: Object })], SortableList.prototype, "options", void 0);
717
+ __decorate([(0, lit_decorators_js.property)({ type: Function })], SortableList.prototype, "labelFor", void 0);
718
+ __decorate([(0, lit_decorators_js.property)({
719
+ type: String,
720
+ reflect: true
721
+ })], SortableList.prototype, "ghostClass", void 0);
722
+ __decorate([(0, lit_decorators_js.property)({
723
+ type: String,
724
+ reflect: true
725
+ })], SortableList.prototype, "chosenClass", void 0);
726
+ __decorate([(0, lit_decorators_js.property)({
727
+ type: String,
728
+ reflect: true
729
+ })], SortableList.prototype, "dragClass", void 0);
730
+ __decorate([(0, lit_decorators_js.property)({
731
+ type: String,
732
+ reflect: true
733
+ })], SortableList.prototype, "filter", void 0);
734
+ __decorate([(0, lit_decorators_js.property)({
735
+ type: String,
736
+ reflect: true
737
+ })], SortableList.prototype, "easing", void 0);
738
+ __decorate([(0, lit_decorators_js.property)({
739
+ type: Boolean,
740
+ reflect: true
741
+ })], SortableList.prototype, "forceFallback", void 0);
742
+ __decorate([(0, lit_decorators_js.property)({
743
+ type: Number,
744
+ reflect: true
745
+ })], SortableList.prototype, "swapThreshold", void 0);
746
+ __decorate([(0, lit_decorators_js.property)({
747
+ type: Boolean,
748
+ reflect: true
749
+ })], SortableList.prototype, "cloneable", void 0);
750
+ __decorate([(0, lit_decorators_js.property)({ type: String })], SortableList.prototype, "listClass", void 0);
751
+ __decorate([(0, lit_decorators_js.property)({ type: String })], SortableList.prototype, "itemClass", void 0);
752
+ __decorate([(0, lit_decorators_js.property)({ type: String })], SortableList.prototype, "itemStyle", void 0);
753
+ __decorate([(0, lit_decorators_js.query)("[data-rozie-ref=\"listEl\"]")], SortableList.prototype, "_refListEl", void 0);
754
+ __decorate([(0, lit_decorators_js.query)("[data-rozie-ref=\"__rozieRoot\"]")], SortableList.prototype, "_ref__rozieRoot", void 0);
755
+ __decorate([(0, lit_decorators_js.state)()], SortableList.prototype, "_hasSlotHeader", void 0);
756
+ __decorate([(0, lit_decorators_js.queryAssignedElements)({
757
+ slot: "header",
758
+ flatten: true
759
+ })], SortableList.prototype, "_slotHeaderElements", void 0);
760
+ __decorate([(0, lit_decorators_js.state)()], SortableList.prototype, "_hasSlotDefault", void 0);
761
+ __decorate([(0, lit_decorators_js.queryAssignedElements)({ flatten: true })], SortableList.prototype, "_slotDefaultElements", void 0);
762
+ __decorate([(0, lit_decorators_js.property)({ attribute: false })], SortableList.prototype, "__rozieDefaultSlot__", void 0);
763
+ __decorate([(0, lit_decorators_js.state)()], SortableList.prototype, "_hasSlotFooter", void 0);
764
+ __decorate([(0, lit_decorators_js.queryAssignedElements)({
765
+ slot: "footer",
766
+ flatten: true
767
+ })], SortableList.prototype, "_slotFooterElements", void 0);
768
+ SortableList = __decorate([(0, lit_decorators_js.customElement)("rozie-sortable-list")], SortableList);
769
+ var SortableList_default = SortableList;
770
+ //#endregion
771
+ exports.SortableList = SortableList_default;
772
+ exports.default = SortableList_default;