@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Dan Krieger and Rozie.js contributors
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.__rowKey = {
337
- map: /* @__PURE__ */ new WeakMap(),
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.__rowKey.map.has(item)) this.__rowKey.map.set(item, "__rk" + this.__rowKey.seq++);
345
- return this.__rowKey.map.get(item);
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.cloneable && typeof this.group === "string" ? {
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")) ((v) => this.instance?.option("group", v))(this.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
- itemKey: string | (((...args: unknown[]) => unknown) | null);
11
- handle: string;
12
- group: string;
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
- labelFor: ((...args: unknown[]) => unknown) | null;
18
- ghostClass: string;
19
- chosenClass: string;
20
- dragClass: string;
21
- filter: string;
22
- easing: string;
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
- itemClass: string | any[] | any | (((...args: unknown[]) => unknown) | null);
28
- itemStyle: string | any | (((...args: unknown[]) => unknown) | null);
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
- __rowKey: {
56
- map: WeakMap<object, any>;
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
- itemKey: string | (((...args: unknown[]) => unknown) | null);
11
- handle: string;
12
- group: string;
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
- labelFor: ((...args: unknown[]) => unknown) | null;
18
- ghostClass: string;
19
- chosenClass: string;
20
- dragClass: string;
21
- filter: string;
22
- easing: string;
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
- itemClass: string | any[] | any | (((...args: unknown[]) => unknown) | null);
28
- itemStyle: string | any | (((...args: unknown[]) => unknown) | null);
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
- __rowKey: {
56
- map: WeakMap<object, any>;
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.__rowKey = {
310
- map: /* @__PURE__ */ new WeakMap(),
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.__rowKey.map.has(item)) this.__rowKey.map.set(item, "__rk" + this.__rowKey.seq++);
318
- return this.__rowKey.map.get(item);
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.cloneable && typeof this.group === "string" ? {
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")) ((v) => this.instance?.option("group", v))(this.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.5",
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.1.1"
43
+ "@rozie/runtime-lit": "0.2.1"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "lit": "^3.2",
@@ -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
- @property({ type: String }) itemKey: string | (((...args: unknown[]) => unknown) | null) = null;
41
- @property({ type: String, reflect: true }) handle: string = null;
42
- @property({ type: String, reflect: true }) group: string = null;
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
- @property({ type: Function }) labelFor: ((...args: unknown[]) => unknown) | null = null;
48
- @property({ type: String, reflect: true }) ghostClass: string = null;
49
- @property({ type: String, reflect: true }) chosenClass: string = null;
50
- @property({ type: String, reflect: true }) dragClass: string = null;
51
- @property({ type: String, reflect: true }) filter: string = null;
52
- @property({ type: String, reflect: true }) easing: string = null;
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
- @property({ type: String }) itemClass: string | any[] | any | (((...args: unknown[]) => unknown) | null) = '';
58
- @property({ type: String }) itemStyle: string | any | (((...args: unknown[]) => unknown) | null) = null;
59
- private _liftedIndex = signal(null);
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
- // `cloneable` is a high-level Rozie prop that REPLACES a string
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)(); ((v: any) => this.instance?.option('group', v))(__watchVal); }
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: Event) => { this.onRowKeyDown($event, index); }} data-rozie-s-0af24eae>
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
- __rowKey = {
258
- map: new WeakMap(),
259
- seq: 0
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.__rowKey.map.has(item)) {
275
- this.__rowKey.map.set(item, '__rk' + this.__rowKey.seq++);
328
+ if (!this.__rowKeyMap.has(item)) {
329
+ this.__rowKeyMap.set(item, '__rk' + this.__rowKeySeq++);
276
330
  }
277
- return this.__rowKey.map.get(item);
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;