@rozie-ui/sortable-list-lit 0.1.5 → 0.1.8
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/LICENSE +1 -1
- package/dist/index.cjs +28 -12
- package/dist/index.d.cts +86 -15
- package/dist/index.d.mts +86 -15
- package/dist/index.mjs +28 -12
- package/package.json +2 -2
- package/src/SortableList.ts +106 -33
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2026
|
|
3
|
+
Copyright (c) 2026 One Learning Community LTD
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/dist/index.cjs
CHANGED
|
@@ -333,19 +333,22 @@ let SortableList = class SortableList extends (0, _lit_labs_preact_signals.Signa
|
|
|
333
333
|
this._rozieTornDown = false;
|
|
334
334
|
this._rozieReconcileSeq = 0;
|
|
335
335
|
this.instance = null;
|
|
336
|
-
this.
|
|
337
|
-
|
|
338
|
-
seq: 0
|
|
339
|
-
};
|
|
336
|
+
this.__rowKeyMap = /* @__PURE__ */ new WeakMap();
|
|
337
|
+
this.__rowKeySeq = 0;
|
|
340
338
|
this.keyFor = (item, index) => {
|
|
341
339
|
if (typeof this.itemKey === "function") return this.itemKey(item, index);
|
|
342
340
|
if (typeof this.itemKey === "string" && item !== null && typeof item === "object" && item[this.itemKey] != null) return item[this.itemKey];
|
|
343
341
|
if (item !== null && typeof item === "object" || typeof item === "function") {
|
|
344
|
-
if (!this.
|
|
345
|
-
return this.
|
|
342
|
+
if (!this.__rowKeyMap.has(item)) this.__rowKeyMap.set(item, "__rk" + this.__rowKeySeq++);
|
|
343
|
+
return this.__rowKeyMap.get(item);
|
|
346
344
|
}
|
|
347
345
|
return index;
|
|
348
346
|
};
|
|
347
|
+
this.resolveGroup = () => this.cloneable && typeof this.group === "string" ? {
|
|
348
|
+
name: this.group,
|
|
349
|
+
pull: "clone",
|
|
350
|
+
put: true
|
|
351
|
+
} : this.group ?? void 0;
|
|
349
352
|
this.itemClassFor = (item, index) => {
|
|
350
353
|
const v = this.itemClass;
|
|
351
354
|
return typeof v === "function" ? v(item, index) : v;
|
|
@@ -362,6 +365,7 @@ let SortableList = class SortableList extends (0, _lit_labs_preact_signals.Signa
|
|
|
362
365
|
};
|
|
363
366
|
this.keyboardEnabled = () => !this.disabled && !this.disableKeyboard;
|
|
364
367
|
this.onRowKeyDown = ($event, index) => {
|
|
368
|
+
if ($event.target !== $event.currentTarget) return;
|
|
365
369
|
if (!this.keyboardEnabled()) return;
|
|
366
370
|
const key = $event.key;
|
|
367
371
|
if (key === " " || key === "Spacebar" || key === "Enter") {
|
|
@@ -413,6 +417,7 @@ let SortableList = class SortableList extends (0, _lit_labs_preact_signals.Signa
|
|
|
413
417
|
}
|
|
414
418
|
static {
|
|
415
419
|
this.styles = lit.css`
|
|
420
|
+
:host{display:contents}
|
|
416
421
|
.rozie-sortable-wrap[data-rozie-s-0af24eae] { display: block; }
|
|
417
422
|
.rozie-sortable-list[data-rozie-s-0af24eae] { display: block; }
|
|
418
423
|
.rozie-sortable-item[data-rozie-s-0af24eae] { display: block; outline: none; }
|
|
@@ -490,11 +495,7 @@ let SortableList = class SortableList extends (0, _lit_labs_preact_signals.Signa
|
|
|
490
495
|
options: {
|
|
491
496
|
animation: this.animation,
|
|
492
497
|
disabled: this.disabled,
|
|
493
|
-
group: this.
|
|
494
|
-
name: this.group,
|
|
495
|
-
pull: "clone",
|
|
496
|
-
put: true
|
|
497
|
-
} : this.group,
|
|
498
|
+
group: this.resolveGroup(),
|
|
498
499
|
handle: this.handle,
|
|
499
500
|
ghostClass: this.ghostClass,
|
|
500
501
|
chosenClass: this.chosenClass,
|
|
@@ -548,7 +549,15 @@ let SortableList = class SortableList extends (0, _lit_labs_preact_signals.Signa
|
|
|
548
549
|
}
|
|
549
550
|
updated(changedProperties) {
|
|
550
551
|
if (this.__rozieFirstUpdateDone && changedProperties.has("disabled")) ((v) => this.instance?.option("disabled", v))(this.disabled);
|
|
551
|
-
if (this.__rozieFirstUpdateDone && changedProperties.has("group"))
|
|
552
|
+
if (this.__rozieFirstUpdateDone && changedProperties.has("group")) {
|
|
553
|
+
this.group;
|
|
554
|
+
this.instance?.option("group", this.resolveGroup());
|
|
555
|
+
}
|
|
556
|
+
if (this.__rozieFirstUpdateDone && changedProperties.has("cloneable")) {
|
|
557
|
+
this.cloneable;
|
|
558
|
+
this.instance?.option("group", this.resolveGroup());
|
|
559
|
+
}
|
|
560
|
+
if (this.__rozieFirstUpdateDone && changedProperties.has("swapThreshold")) ((v) => this.instance?.option("swapThreshold", v))(this.swapThreshold);
|
|
552
561
|
if (this.__rozieFirstUpdateDone && changedProperties.has("handle")) ((v) => this.instance?.option("handle", v))(this.handle);
|
|
553
562
|
if (this.__rozieFirstUpdateDone && changedProperties.has("ghostClass")) ((v) => this.instance?.option("ghostClass", v))(this.ghostClass);
|
|
554
563
|
if (this.__rozieFirstUpdateDone && changedProperties.has("chosenClass")) ((v) => this.instance?.option("chosenClass", v))(this.chosenClass);
|
|
@@ -634,9 +643,16 @@ let SortableList = class SortableList extends (0, _lit_labs_preact_signals.Signa
|
|
|
634
643
|
* with React/Vue/Svelte/Solid/Angular). Both Lit attribute-naming
|
|
635
644
|
* forms are folded into the skip set: kebab-case for model props
|
|
636
645
|
* (explicit `attribute:`) AND lowercased property name (Lit's default).
|
|
646
|
+
*
|
|
647
|
+
* command-palette-per-level-virtual / portal-through-portal cluster —
|
|
648
|
+
* `data-rozie-ref` is ALWAYS skipped too (a reserved compiler bookkeeping
|
|
649
|
+
* attribute, never a consumer prop) so a parent-assigned `ref=` on this
|
|
650
|
+
* component's own host tag can never clobber this component's OWN
|
|
651
|
+
* internal `data-rozie-ref` ref markers via fallthrough re-application.
|
|
637
652
|
*/
|
|
638
653
|
get $attrs() {
|
|
639
654
|
const __skip = new Set([
|
|
655
|
+
"data-rozie-ref",
|
|
640
656
|
"items",
|
|
641
657
|
"item-key",
|
|
642
658
|
"itemkey",
|
package/dist/index.d.cts
CHANGED
|
@@ -5,27 +5,89 @@ import { LitElement } from "lit";
|
|
|
5
5
|
declare const SortableList_base: typeof LitElement;
|
|
6
6
|
declare class SortableList extends SortableList_base {
|
|
7
7
|
static styles: _$lit.CSSResult;
|
|
8
|
+
/**
|
|
9
|
+
* The bound items array. The sole `model: true` prop — two-way bind it (`r-model:items` / `v-model:items` / `bind:items` / `[(items)]`) and SortableList writes the re-ordered array back whenever a drag, cross-list move, or keyboard reorder commits, with no manual `onChange → setState` wiring.
|
|
10
|
+
* @example
|
|
11
|
+
* <SortableList r-model:items="$data.todos" itemKey="id" />
|
|
12
|
+
*/
|
|
8
13
|
_items_attr: any[];
|
|
9
14
|
private _itemsControllable;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
/**
|
|
16
|
+
* The per-row key the framework reconciler tracks each item by across a reorder — either a property name (e.g. `itemKey="id"` reads `item.id`) or an `(item, index) => key` function. With neither, id-less object items get a stable synthetic key via an internal `WeakMap` (survives reorder by object identity); primitive items fall back to index — pass a function for reorderable duplicate primitives.
|
|
17
|
+
*/
|
|
18
|
+
itemKey: string | (((...args: any[]) => any) | null);
|
|
19
|
+
/**
|
|
20
|
+
* CSS selector identifying the per-row drag handle, so a drag starts only from that element rather than anywhere in the row. Authored class names render literally on every target (React included), so a plain `.grip` works; `$classSelector('grip')` is an optional, typo-checked way to author it.
|
|
21
|
+
*/
|
|
22
|
+
handle: string | null;
|
|
23
|
+
/**
|
|
24
|
+
* SortableJS group name enabling cross-list drag — two lists sharing a `group` accept items between each other (the source fires `remove`, the destination fires `add`). Set `cloneable: true` to flip a string group into clone-mode.
|
|
25
|
+
*/
|
|
26
|
+
group: string | null;
|
|
27
|
+
/**
|
|
28
|
+
* Reorder animation duration in milliseconds. `0` disables the animation. Runtime-updatable.
|
|
29
|
+
*/
|
|
13
30
|
animation: number;
|
|
31
|
+
/**
|
|
32
|
+
* Temporarily disable dragging without unmounting — reapplied live via `instance.option('disabled', v)` (no remount). Also suppresses keyboard reordering: a disabled list is not sortable by any input, so rows lose their `tabindex` and the keydown handler no-ops.
|
|
33
|
+
*/
|
|
14
34
|
disabled: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Opt out of keyboard reordering (Space lift / Arrow move / Esc cancel / Enter drop) while leaving pointer drag enabled. Rows drop out of the tab order (no `tabindex`) and the keydown handler no-ops. Keyboard access is gated on `!disabled && !disableKeyboard`.
|
|
37
|
+
*/
|
|
15
38
|
disableKeyboard: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Verbatim SortableJS options pass-through for anything not covered by the named props. The named props win on key conflict but `options` lands AFTER them in the merge so consumers can override defaults; handler keys (`onStart`, `onEnd`, `onUpdate`, `onAdd`, `onRemove`, `onClone`) are stripped — the helper owns those paths.
|
|
41
|
+
*/
|
|
16
42
|
options: any;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Optional `(item, idx) => string` returning the screen-reader label for the aria-live announcer during keyboard drag. Defaults to `item.label` (or `String(item)` when no `label` field exists).
|
|
45
|
+
*/
|
|
46
|
+
labelFor: ((...args: any[]) => any) | null;
|
|
47
|
+
/**
|
|
48
|
+
* Class name applied to the drop-placeholder (ghost) element while dragging. Forwarded live via `instance.option`, so toggling it at runtime takes effect without a remount.
|
|
49
|
+
*/
|
|
50
|
+
ghostClass: string | null;
|
|
51
|
+
/**
|
|
52
|
+
* Class name applied to the currently-chosen item while dragging. Forwarded live via `instance.option` (no remount needed to change it).
|
|
53
|
+
*/
|
|
54
|
+
chosenClass: string | null;
|
|
55
|
+
/**
|
|
56
|
+
* Class name applied to the dragging element. Only takes effect in fallback mode (`forceFallback: true`). Forwarded live via `instance.option`.
|
|
57
|
+
*/
|
|
58
|
+
dragClass: string | null;
|
|
59
|
+
/**
|
|
60
|
+
* CSS selector that prevents drag initiation on matching rows (locked items). SortableJS checks it at `mousedown`/`touchstart` and aborts the drag if it matches. A `data-*` attribute selector (e.g. `[data-locked]`) is the most robust choice across all targets.
|
|
61
|
+
*/
|
|
62
|
+
filter: string | null;
|
|
63
|
+
/**
|
|
64
|
+
* CSS easing function for the reorder animation (e.g. `'ease-in'`, `'cubic-bezier(0.4, 0, 0.2, 1)'`). Runtime-updatable.
|
|
65
|
+
*/
|
|
66
|
+
easing: string | null;
|
|
67
|
+
/**
|
|
68
|
+
* Force SortableJS's mouse-event drag path over HTML5 DnD — useful for touch devices, consistent cross-browser behavior, and synthetic test drivers (and `dragClass` only applies in this mode). **Construction-time only**: SortableJS reads it once at construction, so re-key the `<SortableList>` to toggle it at runtime.
|
|
69
|
+
*/
|
|
23
70
|
forceFallback: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* SortableJS swap threshold (0..1) — a lower value makes rows swap earlier as the dragged item overlaps a neighbor. Reapplied live via `instance.option('swapThreshold', v)` — SortableJS reads it on every dragover, so no remount is needed.
|
|
73
|
+
*/
|
|
24
74
|
swapThreshold: number;
|
|
75
|
+
/**
|
|
76
|
+
* High-level prop that REPLACES a string `group` with SortableJS's `{ name, pull: 'clone', put: true }` clone-mode object form — the source deposits a COPY onto the destination and keeps its own array unchanged (the palette → canvas pattern). With `group: null` it is a no-op (a clone-mode list with no group name has no peer to clone into). Reapplied live — toggling `cloneable` (or changing `group`) recomputes the clone-mode shape and reapplies it via `instance.option('group', …)`, no remount.
|
|
77
|
+
*/
|
|
25
78
|
cloneable: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Extra class(es) merged onto the list container (the SortableJS root) alongside the base `rozie-sortable-list` class. Accepts a `String`, `Array`, or `Object` (Vue-style class binding), normalized identically across all six targets — the hook for bridging a CSS framework (`.list-group`) or a flex/grid parent onto the component.
|
|
81
|
+
*/
|
|
26
82
|
listClass: string | any[] | any;
|
|
27
|
-
|
|
28
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Extra class(es) merged onto every item row alongside the base `rozie-sortable-item` class. Accepts a `String`, `Array`, or `Object` (Vue-style class binding) applied uniformly, OR an `(item, index) => class` function for per-row classes evaluated at render time. Normalized identically across all six targets.
|
|
85
|
+
*/
|
|
86
|
+
itemClass: string | any[] | any | (((...args: any[]) => any) | null);
|
|
87
|
+
/**
|
|
88
|
+
* Per-row inline style applied to the `.rozie-sortable-item` wrapper. Accepts a CSS `String`, a flat style object (`Record<string, string | number>`), or an `(item, index) => string | object` function for per-row styling. Because it lands on the wrapper — the direct child of the list container — it can drive CSS-grid placement (`grid-column` / `grid-row` / `align-self`) when `listClass` sets `display: grid`. Normalized per target; `null` / empty drops the attribute.
|
|
89
|
+
*/
|
|
90
|
+
itemStyle: string | any | (((...args: any[]) => any) | null);
|
|
29
91
|
private _liftedIndex;
|
|
30
92
|
private _ariaLiveText;
|
|
31
93
|
private _refListEl;
|
|
@@ -52,11 +114,14 @@ declare class SortableList extends SortableList_base {
|
|
|
52
114
|
attributeChangedCallback(name: string, old: string | null, value: string | null): void;
|
|
53
115
|
render(): _$lit.TemplateResult<1>;
|
|
54
116
|
instance: any;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
seq: number;
|
|
58
|
-
};
|
|
117
|
+
__rowKeyMap: WeakMap<object, any>;
|
|
118
|
+
__rowKeySeq: number;
|
|
59
119
|
keyFor: (item: any, index: any) => any;
|
|
120
|
+
resolveGroup: () => string | {
|
|
121
|
+
name: string;
|
|
122
|
+
pull: "clone";
|
|
123
|
+
put: true;
|
|
124
|
+
} | undefined;
|
|
60
125
|
itemClassFor: (item: any, index: any) => any;
|
|
61
126
|
itemStyleFor: (item: any, index: any) => any;
|
|
62
127
|
getLabel: (idx: any) => any;
|
|
@@ -79,6 +144,12 @@ declare class SortableList extends SortableList_base {
|
|
|
79
144
|
* with React/Vue/Svelte/Solid/Angular). Both Lit attribute-naming
|
|
80
145
|
* forms are folded into the skip set: kebab-case for model props
|
|
81
146
|
* (explicit `attribute:`) AND lowercased property name (Lit's default).
|
|
147
|
+
*
|
|
148
|
+
* command-palette-per-level-virtual / portal-through-portal cluster —
|
|
149
|
+
* `data-rozie-ref` is ALWAYS skipped too (a reserved compiler bookkeeping
|
|
150
|
+
* attribute, never a consumer prop) so a parent-assigned `ref=` on this
|
|
151
|
+
* component's own host tag can never clobber this component's OWN
|
|
152
|
+
* internal `data-rozie-ref` ref markers via fallthrough re-application.
|
|
82
153
|
*/
|
|
83
154
|
private get $attrs();
|
|
84
155
|
/**
|
package/dist/index.d.mts
CHANGED
|
@@ -5,27 +5,89 @@ import { LitElement } from "lit";
|
|
|
5
5
|
declare const SortableList_base: typeof LitElement;
|
|
6
6
|
declare class SortableList extends SortableList_base {
|
|
7
7
|
static styles: _$lit.CSSResult;
|
|
8
|
+
/**
|
|
9
|
+
* The bound items array. The sole `model: true` prop — two-way bind it (`r-model:items` / `v-model:items` / `bind:items` / `[(items)]`) and SortableList writes the re-ordered array back whenever a drag, cross-list move, or keyboard reorder commits, with no manual `onChange → setState` wiring.
|
|
10
|
+
* @example
|
|
11
|
+
* <SortableList r-model:items="$data.todos" itemKey="id" />
|
|
12
|
+
*/
|
|
8
13
|
_items_attr: any[];
|
|
9
14
|
private _itemsControllable;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
/**
|
|
16
|
+
* The per-row key the framework reconciler tracks each item by across a reorder — either a property name (e.g. `itemKey="id"` reads `item.id`) or an `(item, index) => key` function. With neither, id-less object items get a stable synthetic key via an internal `WeakMap` (survives reorder by object identity); primitive items fall back to index — pass a function for reorderable duplicate primitives.
|
|
17
|
+
*/
|
|
18
|
+
itemKey: string | (((...args: any[]) => any) | null);
|
|
19
|
+
/**
|
|
20
|
+
* CSS selector identifying the per-row drag handle, so a drag starts only from that element rather than anywhere in the row. Authored class names render literally on every target (React included), so a plain `.grip` works; `$classSelector('grip')` is an optional, typo-checked way to author it.
|
|
21
|
+
*/
|
|
22
|
+
handle: string | null;
|
|
23
|
+
/**
|
|
24
|
+
* SortableJS group name enabling cross-list drag — two lists sharing a `group` accept items between each other (the source fires `remove`, the destination fires `add`). Set `cloneable: true` to flip a string group into clone-mode.
|
|
25
|
+
*/
|
|
26
|
+
group: string | null;
|
|
27
|
+
/**
|
|
28
|
+
* Reorder animation duration in milliseconds. `0` disables the animation. Runtime-updatable.
|
|
29
|
+
*/
|
|
13
30
|
animation: number;
|
|
31
|
+
/**
|
|
32
|
+
* Temporarily disable dragging without unmounting — reapplied live via `instance.option('disabled', v)` (no remount). Also suppresses keyboard reordering: a disabled list is not sortable by any input, so rows lose their `tabindex` and the keydown handler no-ops.
|
|
33
|
+
*/
|
|
14
34
|
disabled: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Opt out of keyboard reordering (Space lift / Arrow move / Esc cancel / Enter drop) while leaving pointer drag enabled. Rows drop out of the tab order (no `tabindex`) and the keydown handler no-ops. Keyboard access is gated on `!disabled && !disableKeyboard`.
|
|
37
|
+
*/
|
|
15
38
|
disableKeyboard: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Verbatim SortableJS options pass-through for anything not covered by the named props. The named props win on key conflict but `options` lands AFTER them in the merge so consumers can override defaults; handler keys (`onStart`, `onEnd`, `onUpdate`, `onAdd`, `onRemove`, `onClone`) are stripped — the helper owns those paths.
|
|
41
|
+
*/
|
|
16
42
|
options: any;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Optional `(item, idx) => string` returning the screen-reader label for the aria-live announcer during keyboard drag. Defaults to `item.label` (or `String(item)` when no `label` field exists).
|
|
45
|
+
*/
|
|
46
|
+
labelFor: ((...args: any[]) => any) | null;
|
|
47
|
+
/**
|
|
48
|
+
* Class name applied to the drop-placeholder (ghost) element while dragging. Forwarded live via `instance.option`, so toggling it at runtime takes effect without a remount.
|
|
49
|
+
*/
|
|
50
|
+
ghostClass: string | null;
|
|
51
|
+
/**
|
|
52
|
+
* Class name applied to the currently-chosen item while dragging. Forwarded live via `instance.option` (no remount needed to change it).
|
|
53
|
+
*/
|
|
54
|
+
chosenClass: string | null;
|
|
55
|
+
/**
|
|
56
|
+
* Class name applied to the dragging element. Only takes effect in fallback mode (`forceFallback: true`). Forwarded live via `instance.option`.
|
|
57
|
+
*/
|
|
58
|
+
dragClass: string | null;
|
|
59
|
+
/**
|
|
60
|
+
* CSS selector that prevents drag initiation on matching rows (locked items). SortableJS checks it at `mousedown`/`touchstart` and aborts the drag if it matches. A `data-*` attribute selector (e.g. `[data-locked]`) is the most robust choice across all targets.
|
|
61
|
+
*/
|
|
62
|
+
filter: string | null;
|
|
63
|
+
/**
|
|
64
|
+
* CSS easing function for the reorder animation (e.g. `'ease-in'`, `'cubic-bezier(0.4, 0, 0.2, 1)'`). Runtime-updatable.
|
|
65
|
+
*/
|
|
66
|
+
easing: string | null;
|
|
67
|
+
/**
|
|
68
|
+
* Force SortableJS's mouse-event drag path over HTML5 DnD — useful for touch devices, consistent cross-browser behavior, and synthetic test drivers (and `dragClass` only applies in this mode). **Construction-time only**: SortableJS reads it once at construction, so re-key the `<SortableList>` to toggle it at runtime.
|
|
69
|
+
*/
|
|
23
70
|
forceFallback: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* SortableJS swap threshold (0..1) — a lower value makes rows swap earlier as the dragged item overlaps a neighbor. Reapplied live via `instance.option('swapThreshold', v)` — SortableJS reads it on every dragover, so no remount is needed.
|
|
73
|
+
*/
|
|
24
74
|
swapThreshold: number;
|
|
75
|
+
/**
|
|
76
|
+
* High-level prop that REPLACES a string `group` with SortableJS's `{ name, pull: 'clone', put: true }` clone-mode object form — the source deposits a COPY onto the destination and keeps its own array unchanged (the palette → canvas pattern). With `group: null` it is a no-op (a clone-mode list with no group name has no peer to clone into). Reapplied live — toggling `cloneable` (or changing `group`) recomputes the clone-mode shape and reapplies it via `instance.option('group', …)`, no remount.
|
|
77
|
+
*/
|
|
25
78
|
cloneable: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Extra class(es) merged onto the list container (the SortableJS root) alongside the base `rozie-sortable-list` class. Accepts a `String`, `Array`, or `Object` (Vue-style class binding), normalized identically across all six targets — the hook for bridging a CSS framework (`.list-group`) or a flex/grid parent onto the component.
|
|
81
|
+
*/
|
|
26
82
|
listClass: string | any[] | any;
|
|
27
|
-
|
|
28
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Extra class(es) merged onto every item row alongside the base `rozie-sortable-item` class. Accepts a `String`, `Array`, or `Object` (Vue-style class binding) applied uniformly, OR an `(item, index) => class` function for per-row classes evaluated at render time. Normalized identically across all six targets.
|
|
85
|
+
*/
|
|
86
|
+
itemClass: string | any[] | any | (((...args: any[]) => any) | null);
|
|
87
|
+
/**
|
|
88
|
+
* Per-row inline style applied to the `.rozie-sortable-item` wrapper. Accepts a CSS `String`, a flat style object (`Record<string, string | number>`), or an `(item, index) => string | object` function for per-row styling. Because it lands on the wrapper — the direct child of the list container — it can drive CSS-grid placement (`grid-column` / `grid-row` / `align-self`) when `listClass` sets `display: grid`. Normalized per target; `null` / empty drops the attribute.
|
|
89
|
+
*/
|
|
90
|
+
itemStyle: string | any | (((...args: any[]) => any) | null);
|
|
29
91
|
private _liftedIndex;
|
|
30
92
|
private _ariaLiveText;
|
|
31
93
|
private _refListEl;
|
|
@@ -52,11 +114,14 @@ declare class SortableList extends SortableList_base {
|
|
|
52
114
|
attributeChangedCallback(name: string, old: string | null, value: string | null): void;
|
|
53
115
|
render(): _$lit.TemplateResult<1>;
|
|
54
116
|
instance: any;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
seq: number;
|
|
58
|
-
};
|
|
117
|
+
__rowKeyMap: WeakMap<object, any>;
|
|
118
|
+
__rowKeySeq: number;
|
|
59
119
|
keyFor: (item: any, index: any) => any;
|
|
120
|
+
resolveGroup: () => string | {
|
|
121
|
+
name: string;
|
|
122
|
+
pull: "clone";
|
|
123
|
+
put: true;
|
|
124
|
+
} | undefined;
|
|
60
125
|
itemClassFor: (item: any, index: any) => any;
|
|
61
126
|
itemStyleFor: (item: any, index: any) => any;
|
|
62
127
|
getLabel: (idx: any) => any;
|
|
@@ -79,6 +144,12 @@ declare class SortableList extends SortableList_base {
|
|
|
79
144
|
* with React/Vue/Svelte/Solid/Angular). Both Lit attribute-naming
|
|
80
145
|
* forms are folded into the skip set: kebab-case for model props
|
|
81
146
|
* (explicit `attribute:`) AND lowercased property name (Lit's default).
|
|
147
|
+
*
|
|
148
|
+
* command-palette-per-level-virtual / portal-through-portal cluster —
|
|
149
|
+
* `data-rozie-ref` is ALWAYS skipped too (a reserved compiler bookkeeping
|
|
150
|
+
* attribute, never a consumer prop) so a parent-assigned `ref=` on this
|
|
151
|
+
* component's own host tag can never clobber this component's OWN
|
|
152
|
+
* internal `data-rozie-ref` ref markers via fallthrough re-application.
|
|
82
153
|
*/
|
|
83
154
|
private get $attrs();
|
|
84
155
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -306,19 +306,22 @@ let SortableList = class SortableList extends SignalWatcher(LitElement) {
|
|
|
306
306
|
this._rozieTornDown = false;
|
|
307
307
|
this._rozieReconcileSeq = 0;
|
|
308
308
|
this.instance = null;
|
|
309
|
-
this.
|
|
310
|
-
|
|
311
|
-
seq: 0
|
|
312
|
-
};
|
|
309
|
+
this.__rowKeyMap = /* @__PURE__ */ new WeakMap();
|
|
310
|
+
this.__rowKeySeq = 0;
|
|
313
311
|
this.keyFor = (item, index) => {
|
|
314
312
|
if (typeof this.itemKey === "function") return this.itemKey(item, index);
|
|
315
313
|
if (typeof this.itemKey === "string" && item !== null && typeof item === "object" && item[this.itemKey] != null) return item[this.itemKey];
|
|
316
314
|
if (item !== null && typeof item === "object" || typeof item === "function") {
|
|
317
|
-
if (!this.
|
|
318
|
-
return this.
|
|
315
|
+
if (!this.__rowKeyMap.has(item)) this.__rowKeyMap.set(item, "__rk" + this.__rowKeySeq++);
|
|
316
|
+
return this.__rowKeyMap.get(item);
|
|
319
317
|
}
|
|
320
318
|
return index;
|
|
321
319
|
};
|
|
320
|
+
this.resolveGroup = () => this.cloneable && typeof this.group === "string" ? {
|
|
321
|
+
name: this.group,
|
|
322
|
+
pull: "clone",
|
|
323
|
+
put: true
|
|
324
|
+
} : this.group ?? void 0;
|
|
322
325
|
this.itemClassFor = (item, index) => {
|
|
323
326
|
const v = this.itemClass;
|
|
324
327
|
return typeof v === "function" ? v(item, index) : v;
|
|
@@ -335,6 +338,7 @@ let SortableList = class SortableList extends SignalWatcher(LitElement) {
|
|
|
335
338
|
};
|
|
336
339
|
this.keyboardEnabled = () => !this.disabled && !this.disableKeyboard;
|
|
337
340
|
this.onRowKeyDown = ($event, index) => {
|
|
341
|
+
if ($event.target !== $event.currentTarget) return;
|
|
338
342
|
if (!this.keyboardEnabled()) return;
|
|
339
343
|
const key = $event.key;
|
|
340
344
|
if (key === " " || key === "Spacebar" || key === "Enter") {
|
|
@@ -386,6 +390,7 @@ let SortableList = class SortableList extends SignalWatcher(LitElement) {
|
|
|
386
390
|
}
|
|
387
391
|
static {
|
|
388
392
|
this.styles = css`
|
|
393
|
+
:host{display:contents}
|
|
389
394
|
.rozie-sortable-wrap[data-rozie-s-0af24eae] { display: block; }
|
|
390
395
|
.rozie-sortable-list[data-rozie-s-0af24eae] { display: block; }
|
|
391
396
|
.rozie-sortable-item[data-rozie-s-0af24eae] { display: block; outline: none; }
|
|
@@ -463,11 +468,7 @@ let SortableList = class SortableList extends SignalWatcher(LitElement) {
|
|
|
463
468
|
options: {
|
|
464
469
|
animation: this.animation,
|
|
465
470
|
disabled: this.disabled,
|
|
466
|
-
group: this.
|
|
467
|
-
name: this.group,
|
|
468
|
-
pull: "clone",
|
|
469
|
-
put: true
|
|
470
|
-
} : this.group,
|
|
471
|
+
group: this.resolveGroup(),
|
|
471
472
|
handle: this.handle,
|
|
472
473
|
ghostClass: this.ghostClass,
|
|
473
474
|
chosenClass: this.chosenClass,
|
|
@@ -521,7 +522,15 @@ let SortableList = class SortableList extends SignalWatcher(LitElement) {
|
|
|
521
522
|
}
|
|
522
523
|
updated(changedProperties) {
|
|
523
524
|
if (this.__rozieFirstUpdateDone && changedProperties.has("disabled")) ((v) => this.instance?.option("disabled", v))(this.disabled);
|
|
524
|
-
if (this.__rozieFirstUpdateDone && changedProperties.has("group"))
|
|
525
|
+
if (this.__rozieFirstUpdateDone && changedProperties.has("group")) {
|
|
526
|
+
this.group;
|
|
527
|
+
this.instance?.option("group", this.resolveGroup());
|
|
528
|
+
}
|
|
529
|
+
if (this.__rozieFirstUpdateDone && changedProperties.has("cloneable")) {
|
|
530
|
+
this.cloneable;
|
|
531
|
+
this.instance?.option("group", this.resolveGroup());
|
|
532
|
+
}
|
|
533
|
+
if (this.__rozieFirstUpdateDone && changedProperties.has("swapThreshold")) ((v) => this.instance?.option("swapThreshold", v))(this.swapThreshold);
|
|
525
534
|
if (this.__rozieFirstUpdateDone && changedProperties.has("handle")) ((v) => this.instance?.option("handle", v))(this.handle);
|
|
526
535
|
if (this.__rozieFirstUpdateDone && changedProperties.has("ghostClass")) ((v) => this.instance?.option("ghostClass", v))(this.ghostClass);
|
|
527
536
|
if (this.__rozieFirstUpdateDone && changedProperties.has("chosenClass")) ((v) => this.instance?.option("chosenClass", v))(this.chosenClass);
|
|
@@ -607,9 +616,16 @@ let SortableList = class SortableList extends SignalWatcher(LitElement) {
|
|
|
607
616
|
* with React/Vue/Svelte/Solid/Angular). Both Lit attribute-naming
|
|
608
617
|
* forms are folded into the skip set: kebab-case for model props
|
|
609
618
|
* (explicit `attribute:`) AND lowercased property name (Lit's default).
|
|
619
|
+
*
|
|
620
|
+
* command-palette-per-level-virtual / portal-through-portal cluster —
|
|
621
|
+
* `data-rozie-ref` is ALWAYS skipped too (a reserved compiler bookkeeping
|
|
622
|
+
* attribute, never a consumer prop) so a parent-assigned `ref=` on this
|
|
623
|
+
* component's own host tag can never clobber this component's OWN
|
|
624
|
+
* internal `data-rozie-ref` ref markers via fallthrough re-application.
|
|
610
625
|
*/
|
|
611
626
|
get $attrs() {
|
|
612
627
|
const __skip = new Set([
|
|
628
|
+
"data-rozie-ref",
|
|
613
629
|
"items",
|
|
614
630
|
"item-key",
|
|
615
631
|
"itemkey",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rozie-ui/sortable-list-lit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"src"
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@rozie/runtime-lit": "0.
|
|
43
|
+
"@rozie/runtime-lit": "0.2.1"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"lit": "^3.2",
|
package/src/SortableList.ts
CHANGED
|
@@ -14,6 +14,7 @@ interface RozieDefaultSlotCtx {
|
|
|
14
14
|
@customElement('rozie-sortable-list')
|
|
15
15
|
export default class SortableList extends SignalWatcher(LitElement) {
|
|
16
16
|
static styles = css`
|
|
17
|
+
:host{display:contents}
|
|
17
18
|
.rozie-sortable-wrap[data-rozie-s-0af24eae] { display: block; }
|
|
18
19
|
.rozie-sortable-list[data-rozie-s-0af24eae] { display: block; }
|
|
19
20
|
.rozie-sortable-item[data-rozie-s-0af24eae] { display: block; outline: none; }
|
|
@@ -35,28 +36,90 @@ export default class SortableList extends SignalWatcher(LitElement) {
|
|
|
35
36
|
}
|
|
36
37
|
`;
|
|
37
38
|
|
|
39
|
+
/**
|
|
40
|
+
* The bound items array. The sole `model: true` prop — two-way bind it (`r-model:items` / `v-model:items` / `bind:items` / `[(items)]`) and SortableList writes the re-ordered array back whenever a drag, cross-list move, or keyboard reorder commits, with no manual `onChange → setState` wiring.
|
|
41
|
+
* @example
|
|
42
|
+
* <SortableList r-model:items="$data.todos" itemKey="id" />
|
|
43
|
+
*/
|
|
38
44
|
@property({ type: Array, attribute: 'items' }) _items_attr: any[] = [];
|
|
39
45
|
private _itemsControllable = createLitControllableProperty<any[]>({ host: this, eventName: 'items-change', defaultValue: [], initialControlledValue: undefined });
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
/**
|
|
47
|
+
* The per-row key the framework reconciler tracks each item by across a reorder — either a property name (e.g. `itemKey="id"` reads `item.id`) or an `(item, index) => key` function. With neither, id-less object items get a stable synthetic key via an internal `WeakMap` (survives reorder by object identity); primitive items fall back to index — pass a function for reorderable duplicate primitives.
|
|
48
|
+
*/
|
|
49
|
+
@property({ type: String }) itemKey: string | (((...args: any[]) => any) | null) = null;
|
|
50
|
+
/**
|
|
51
|
+
* CSS selector identifying the per-row drag handle, so a drag starts only from that element rather than anywhere in the row. Authored class names render literally on every target (React included), so a plain `.grip` works; `$classSelector('grip')` is an optional, typo-checked way to author it.
|
|
52
|
+
*/
|
|
53
|
+
@property({ type: String, reflect: true }) handle: string | null = null;
|
|
54
|
+
/**
|
|
55
|
+
* SortableJS group name enabling cross-list drag — two lists sharing a `group` accept items between each other (the source fires `remove`, the destination fires `add`). Set `cloneable: true` to flip a string group into clone-mode.
|
|
56
|
+
*/
|
|
57
|
+
@property({ type: String, reflect: true }) group: string | null = null;
|
|
58
|
+
/**
|
|
59
|
+
* Reorder animation duration in milliseconds. `0` disables the animation. Runtime-updatable.
|
|
60
|
+
*/
|
|
43
61
|
@property({ type: Number, reflect: true }) animation: number = 150;
|
|
62
|
+
/**
|
|
63
|
+
* Temporarily disable dragging without unmounting — reapplied live via `instance.option('disabled', v)` (no remount). Also suppresses keyboard reordering: a disabled list is not sortable by any input, so rows lose their `tabindex` and the keydown handler no-ops.
|
|
64
|
+
*/
|
|
44
65
|
@property({ type: Boolean, reflect: true }) disabled: boolean = false;
|
|
66
|
+
/**
|
|
67
|
+
* Opt out of keyboard reordering (Space lift / Arrow move / Esc cancel / Enter drop) while leaving pointer drag enabled. Rows drop out of the tab order (no `tabindex`) and the keydown handler no-ops. Keyboard access is gated on `!disabled && !disableKeyboard`.
|
|
68
|
+
*/
|
|
45
69
|
@property({ type: Boolean, reflect: true }) disableKeyboard: boolean = false;
|
|
70
|
+
/**
|
|
71
|
+
* Verbatim SortableJS options pass-through for anything not covered by the named props. The named props win on key conflict but `options` lands AFTER them in the merge so consumers can override defaults; handler keys (`onStart`, `onEnd`, `onUpdate`, `onAdd`, `onRemove`, `onClone`) are stripped — the helper owns those paths.
|
|
72
|
+
*/
|
|
46
73
|
@property({ type: Object }) options: any = {};
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
@property({ type:
|
|
51
|
-
|
|
52
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Optional `(item, idx) => string` returning the screen-reader label for the aria-live announcer during keyboard drag. Defaults to `item.label` (or `String(item)` when no `label` field exists).
|
|
76
|
+
*/
|
|
77
|
+
@property({ type: Function }) labelFor: ((...args: any[]) => any) | null = null;
|
|
78
|
+
/**
|
|
79
|
+
* Class name applied to the drop-placeholder (ghost) element while dragging. Forwarded live via `instance.option`, so toggling it at runtime takes effect without a remount.
|
|
80
|
+
*/
|
|
81
|
+
@property({ type: String, reflect: true }) ghostClass: string | null = null;
|
|
82
|
+
/**
|
|
83
|
+
* Class name applied to the currently-chosen item while dragging. Forwarded live via `instance.option` (no remount needed to change it).
|
|
84
|
+
*/
|
|
85
|
+
@property({ type: String, reflect: true }) chosenClass: string | null = null;
|
|
86
|
+
/**
|
|
87
|
+
* Class name applied to the dragging element. Only takes effect in fallback mode (`forceFallback: true`). Forwarded live via `instance.option`.
|
|
88
|
+
*/
|
|
89
|
+
@property({ type: String, reflect: true }) dragClass: string | null = null;
|
|
90
|
+
/**
|
|
91
|
+
* CSS selector that prevents drag initiation on matching rows (locked items). SortableJS checks it at `mousedown`/`touchstart` and aborts the drag if it matches. A `data-*` attribute selector (e.g. `[data-locked]`) is the most robust choice across all targets.
|
|
92
|
+
*/
|
|
93
|
+
@property({ type: String, reflect: true }) filter: string | null = null;
|
|
94
|
+
/**
|
|
95
|
+
* CSS easing function for the reorder animation (e.g. `'ease-in'`, `'cubic-bezier(0.4, 0, 0.2, 1)'`). Runtime-updatable.
|
|
96
|
+
*/
|
|
97
|
+
@property({ type: String, reflect: true }) easing: string | null = null;
|
|
98
|
+
/**
|
|
99
|
+
* Force SortableJS's mouse-event drag path over HTML5 DnD — useful for touch devices, consistent cross-browser behavior, and synthetic test drivers (and `dragClass` only applies in this mode). **Construction-time only**: SortableJS reads it once at construction, so re-key the `<SortableList>` to toggle it at runtime.
|
|
100
|
+
*/
|
|
53
101
|
@property({ type: Boolean, reflect: true }) forceFallback: boolean = false;
|
|
102
|
+
/**
|
|
103
|
+
* SortableJS swap threshold (0..1) — a lower value makes rows swap earlier as the dragged item overlaps a neighbor. Reapplied live via `instance.option('swapThreshold', v)` — SortableJS reads it on every dragover, so no remount is needed.
|
|
104
|
+
*/
|
|
54
105
|
@property({ type: Number, reflect: true }) swapThreshold: number = 1;
|
|
106
|
+
/**
|
|
107
|
+
* High-level prop that REPLACES a string `group` with SortableJS's `{ name, pull: 'clone', put: true }` clone-mode object form — the source deposits a COPY onto the destination and keeps its own array unchanged (the palette → canvas pattern). With `group: null` it is a no-op (a clone-mode list with no group name has no peer to clone into). Reapplied live — toggling `cloneable` (or changing `group`) recomputes the clone-mode shape and reapplies it via `instance.option('group', …)`, no remount.
|
|
108
|
+
*/
|
|
55
109
|
@property({ type: Boolean, reflect: true }) cloneable: boolean = false;
|
|
110
|
+
/**
|
|
111
|
+
* Extra class(es) merged onto the list container (the SortableJS root) alongside the base `rozie-sortable-list` class. Accepts a `String`, `Array`, or `Object` (Vue-style class binding), normalized identically across all six targets — the hook for bridging a CSS framework (`.list-group`) or a flex/grid parent onto the component.
|
|
112
|
+
*/
|
|
56
113
|
@property({ type: String }) listClass: string | any[] | any = '';
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
114
|
+
/**
|
|
115
|
+
* Extra class(es) merged onto every item row alongside the base `rozie-sortable-item` class. Accepts a `String`, `Array`, or `Object` (Vue-style class binding) applied uniformly, OR an `(item, index) => class` function for per-row classes evaluated at render time. Normalized identically across all six targets.
|
|
116
|
+
*/
|
|
117
|
+
@property({ type: String }) itemClass: string | any[] | any | (((...args: any[]) => any) | null) = '';
|
|
118
|
+
/**
|
|
119
|
+
* Per-row inline style applied to the `.rozie-sortable-item` wrapper. Accepts a CSS `String`, a flat style object (`Record<string, string | number>`), or an `(item, index) => string | object` function for per-row styling. Because it lands on the wrapper — the direct child of the list container — it can drive CSS-grid placement (`grid-column` / `grid-row` / `align-self`) when `listClass` sets `display: grid`. Normalized per target; `null` / empty drops the attribute.
|
|
120
|
+
*/
|
|
121
|
+
@property({ type: String }) itemStyle: string | any | (((...args: any[]) => any) | null) = null;
|
|
122
|
+
private _liftedIndex = signal<any>(null);
|
|
60
123
|
private _ariaLiveText = signal('');
|
|
61
124
|
@query('[data-rozie-ref="listEl"]') private _refListEl!: HTMLElement;
|
|
62
125
|
@query('[data-rozie-ref="__rozieRoot"]') private _ref__rozieRoot!: HTMLElement;
|
|
@@ -136,17 +199,7 @@ private __rozieFirstUpdateDone = false;
|
|
|
136
199
|
options: {
|
|
137
200
|
animation: this.animation,
|
|
138
201
|
disabled: this.disabled,
|
|
139
|
-
|
|
140
|
-
// `group` with SortableJS's `{ name, pull: 'clone', put: true }`
|
|
141
|
-
// object form. When `cloneable:false`, pass `$props.group` through
|
|
142
|
-
// verbatim. When `cloneable:true` AND `$props.group` is null,
|
|
143
|
-
// leave it null — a clone-mode list without a group name is not
|
|
144
|
-
// meaningful (no peer list can join the cross-list flow).
|
|
145
|
-
group: this.cloneable && typeof this.group === 'string' ? {
|
|
146
|
-
name: this.group,
|
|
147
|
-
pull: 'clone',
|
|
148
|
-
put: true
|
|
149
|
-
} : this.group,
|
|
202
|
+
group: this.resolveGroup(),
|
|
150
203
|
handle: this.handle,
|
|
151
204
|
ghostClass: this.ghostClass,
|
|
152
205
|
chosenClass: this.chosenClass,
|
|
@@ -212,7 +265,9 @@ private __rozieFirstUpdateDone = false;
|
|
|
212
265
|
|
|
213
266
|
updated(changedProperties: Map<string, unknown>): void {
|
|
214
267
|
if (this.__rozieFirstUpdateDone && (changedProperties.has('disabled'))) { const __watchVal = (() => this.disabled)(); ((v: any) => this.instance?.option('disabled', v))(__watchVal); }
|
|
215
|
-
if (this.__rozieFirstUpdateDone && (changedProperties.has('group'))) { const __watchVal = (() => this.group)(); ((
|
|
268
|
+
if (this.__rozieFirstUpdateDone && (changedProperties.has('group'))) { const __watchVal = (() => this.group)(); (() => this.instance?.option('group', this.resolveGroup()))(); }
|
|
269
|
+
if (this.__rozieFirstUpdateDone && (changedProperties.has('cloneable'))) { const __watchVal = (() => this.cloneable)(); (() => this.instance?.option('group', this.resolveGroup()))(); }
|
|
270
|
+
if (this.__rozieFirstUpdateDone && (changedProperties.has('swapThreshold'))) { const __watchVal = (() => this.swapThreshold)(); ((v: any) => this.instance?.option('swapThreshold', v))(__watchVal); }
|
|
216
271
|
if (this.__rozieFirstUpdateDone && (changedProperties.has('handle'))) { const __watchVal = (() => this.handle)(); ((v: any) => this.instance?.option('handle', v))(__watchVal); }
|
|
217
272
|
if (this.__rozieFirstUpdateDone && (changedProperties.has('ghostClass'))) { const __watchVal = (() => this.ghostClass)(); ((v: any) => this.instance?.option('ghostClass', v))(__watchVal); }
|
|
218
273
|
if (this.__rozieFirstUpdateDone && (changedProperties.has('chosenClass'))) { const __watchVal = (() => this.chosenClass)(); ((v: any) => this.instance?.option('chosenClass', v))(__watchVal); }
|
|
@@ -242,7 +297,7 @@ private __rozieFirstUpdateDone = false;
|
|
|
242
297
|
<div class="rozie-sortable-wrap" ${rozieSpread(this.$attrs)} ${rozieListeners(this.$listeners)} data-rozie-ref="__rozieRoot" data-rozie-s-0af24eae>
|
|
243
298
|
<div class="${(rozieClass(['rozie-sortable-list', this.listClass]))}" part="list" data-rozie-ref="listEl" data-rozie-s-0af24eae>${keyed(this._rozieReconcileSeq ?? 0, html`
|
|
244
299
|
<slot name="header"></slot>
|
|
245
|
-
${repeat<any>(this.items, (item, index) => this.keyFor(item, index), (item, index) => html`<div class="${(rozieClass(['rozie-sortable-item', this.itemClassFor(item, index), { 'rozie-sortable-item-lifted': this._liftedIndex.value === index }]))}" key=${rozieAttr(this.keyFor(item, index))} style=${rozieStyle(this.itemStyleFor(item, index))} data-id=${rozieAttr(this.keyFor(item, index))} role="listitem" tabindex=${rozieAttr(this.keyboardEnabled() ? 0 : null)} @keydown=${($event:
|
|
300
|
+
${repeat<any>(this.items, (item, index) => this.keyFor(item, index), (item, index) => html`<div class="${(rozieClass(['rozie-sortable-item', this.itemClassFor(item, index), { 'rozie-sortable-item-lifted': this._liftedIndex.value === index }]))}" key=${rozieAttr(this.keyFor(item, index))} style=${rozieStyle(this.itemStyleFor(item, index))} data-id=${rozieAttr(this.keyFor(item, index))} role="listitem" tabindex=${rozieAttr(this.keyboardEnabled() ? 0 : null)} @keydown=${($event: KeyboardEvent & { currentTarget: HTMLDivElement; target: HTMLDivElement }) => { this.onRowKeyDown($event, index); }} data-rozie-s-0af24eae>
|
|
246
301
|
${this.__rozieDefaultSlot__ !== undefined ? this.__rozieDefaultSlot__({item: item, index: index}) : html`<slot data-rozie-params=${(() => { try { return JSON.stringify({item: item, index: index}); } catch { return '{}'; } })()}></slot>`}
|
|
247
302
|
</div>`)}
|
|
248
303
|
<slot name="footer"></slot>
|
|
@@ -254,10 +309,9 @@ private __rozieFirstUpdateDone = false;
|
|
|
254
309
|
|
|
255
310
|
instance: any = null;
|
|
256
311
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
};
|
|
312
|
+
__rowKeyMap = new WeakMap();
|
|
313
|
+
|
|
314
|
+
__rowKeySeq = 0;
|
|
261
315
|
|
|
262
316
|
keyFor = (item: any, index: any) => {
|
|
263
317
|
// (a) function itemKey: consumer-supplied (item, index) => key.
|
|
@@ -271,16 +325,22 @@ private __rozieFirstUpdateDone = false;
|
|
|
271
325
|
// (c) id-less object (or function) item: assign-on-first-sight WeakMap
|
|
272
326
|
// synthetic id. Survives reorder because it is keyed by object identity.
|
|
273
327
|
if (item !== null && typeof item === 'object' || typeof item === 'function') {
|
|
274
|
-
if (!this.
|
|
275
|
-
this.
|
|
328
|
+
if (!this.__rowKeyMap.has(item)) {
|
|
329
|
+
this.__rowKeyMap.set(item, '__rk' + this.__rowKeySeq++);
|
|
276
330
|
}
|
|
277
|
-
return this.
|
|
331
|
+
return this.__rowKeyMap.get(item);
|
|
278
332
|
}
|
|
279
333
|
// (d) primitive item: fall back to index. NOTE: duplicate primitives are
|
|
280
334
|
// unsafe to reorder this way — pass a function itemKey for those.
|
|
281
335
|
return index;
|
|
282
336
|
};
|
|
283
337
|
|
|
338
|
+
resolveGroup = () => this.cloneable && typeof this.group === 'string' ? {
|
|
339
|
+
name: this.group,
|
|
340
|
+
pull: 'clone' as const,
|
|
341
|
+
put: true as const
|
|
342
|
+
} : this.group ?? undefined;
|
|
343
|
+
|
|
284
344
|
itemClassFor = (item: any, index: any) => {
|
|
285
345
|
const v = this.itemClass;
|
|
286
346
|
return typeof v === 'function' ? v(item, index) : v;
|
|
@@ -301,6 +361,13 @@ private __rozieFirstUpdateDone = false;
|
|
|
301
361
|
keyboardEnabled = () => !this.disabled && !this.disableKeyboard;
|
|
302
362
|
|
|
303
363
|
onRowKeyDown = ($event: any, index: any) => {
|
|
364
|
+
// Origin guard (quick 260716-ggq): reorder keys apply ONLY when the row
|
|
365
|
+
// element ITSELF is focused. A slotted interactive child (an <input>,
|
|
366
|
+
// <button>, etc. rendered into the row's default slot) bubbles its own
|
|
367
|
+
// keydown up to this handler — without this guard, Space/Enter typed into
|
|
368
|
+
// that child would be hijacked for lift/drop instead of reaching the
|
|
369
|
+
// child. Must run FIRST, before the keyboardEnabled() check.
|
|
370
|
+
if ($event.target !== $event.currentTarget) return;
|
|
304
371
|
// Defense-in-depth: when keyboard reordering is off the rows carry no
|
|
305
372
|
// tabindex and can't receive focus, but a consumer-focused row (or a
|
|
306
373
|
// programmatic .focus()) must still no-op here rather than reorder.
|
|
@@ -392,9 +459,15 @@ private __rozieFirstUpdateDone = false;
|
|
|
392
459
|
* with React/Vue/Svelte/Solid/Angular). Both Lit attribute-naming
|
|
393
460
|
* forms are folded into the skip set: kebab-case for model props
|
|
394
461
|
* (explicit `attribute:`) AND lowercased property name (Lit's default).
|
|
462
|
+
*
|
|
463
|
+
* command-palette-per-level-virtual / portal-through-portal cluster —
|
|
464
|
+
* `data-rozie-ref` is ALWAYS skipped too (a reserved compiler bookkeeping
|
|
465
|
+
* attribute, never a consumer prop) so a parent-assigned `ref=` on this
|
|
466
|
+
* component's own host tag can never clobber this component's OWN
|
|
467
|
+
* internal `data-rozie-ref` ref markers via fallthrough re-application.
|
|
395
468
|
*/
|
|
396
469
|
private get $attrs(): Record<string, string> {
|
|
397
|
-
const __skip = new Set<string>(['items', 'item-key', 'itemkey', 'handle', 'group', 'animation', 'disabled', 'disable-keyboard', 'disablekeyboard', 'options', 'label-for', 'labelfor', 'ghost-class', 'ghostclass', 'chosen-class', 'chosenclass', 'drag-class', 'dragclass', 'filter', 'easing', 'force-fallback', 'forcefallback', 'swap-threshold', 'swapthreshold', 'cloneable', 'list-class', 'listclass', 'item-class', 'itemclass', 'item-style', 'itemstyle']);
|
|
470
|
+
const __skip = new Set<string>(['data-rozie-ref', 'items', 'item-key', 'itemkey', 'handle', 'group', 'animation', 'disabled', 'disable-keyboard', 'disablekeyboard', 'options', 'label-for', 'labelfor', 'ghost-class', 'ghostclass', 'chosen-class', 'chosenclass', 'drag-class', 'dragclass', 'filter', 'easing', 'force-fallback', 'forcefallback', 'swap-threshold', 'swapthreshold', 'cloneable', 'list-class', 'listclass', 'item-class', 'itemclass', 'item-style', 'itemstyle']);
|
|
398
471
|
const out: Record<string, string> = {};
|
|
399
472
|
for (const a of Array.from(this.attributes)) {
|
|
400
473
|
if (__skip.has(a.name)) continue;
|