@llui/components 0.0.27 → 0.0.29

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.
@@ -58,10 +58,21 @@ export interface DragState {
58
58
  */
59
59
  toContainer: string;
60
60
  /**
61
- * Pointer Y at drag start (viewport coordinates). Used by CSS to make
62
- * the dragged item follow the pointer via translateY(deltaY).
61
+ * Pointer X at drag start (viewport coordinates). Used by 2D layouts
62
+ * to compute `deltaX = currentX - startX` alongside the Y axis. In 1D
63
+ * layouts X is tracked but ignored by the renderer.
64
+ */
65
+ startX: number;
66
+ /**
67
+ * Pointer Y at drag start (viewport coordinates). Used by CSS / the
68
+ * library's `style.transform` binding to make the dragged item follow
69
+ * the pointer.
63
70
  */
64
71
  startY: number;
72
+ /**
73
+ * Current pointer X (viewport coordinates). `deltaX = currentX - startX`.
74
+ */
75
+ currentX: number;
65
76
  /**
66
77
  * Current pointer Y (viewport coordinates). `deltaY = currentY - startY`.
67
78
  */
@@ -75,11 +86,13 @@ export type SortableMsg = {
75
86
  id: string;
76
87
  index: number;
77
88
  container: string;
89
+ x: number;
78
90
  y: number;
79
91
  } | {
80
92
  type: 'move';
81
93
  index: number;
82
94
  container: string;
95
+ x: number;
83
96
  y: number;
84
97
  } | {
85
98
  type: 'drop';
@@ -130,6 +143,32 @@ export interface SortableParts<S> {
130
143
  }
131
144
  export interface ConnectOptions {
132
145
  id: string;
146
+ /**
147
+ * Drag-target selection + render strategy.
148
+ *
149
+ * - `'1d'` (default) — single-axis, Y-only. `findTargetAt` picks
150
+ * by vertical distance; `style.transform` on the dragged item
151
+ * is `translateY(deltaY)`; non-dragged items between source
152
+ * and target emit `data-shift: 'up' | 'down'` so CSS can
153
+ * animate them via `translateY(±var(--sortable-shift))`.
154
+ * Correct for vertical lists; fails for 2D layouts (flex-wrap,
155
+ * grid) because same-row items collapse to the same midpoint
156
+ * distance.
157
+ *
158
+ * - `'2d'` — Euclidean target selection against 2D midpoints;
159
+ * dragged item follows both X and Y (`translate(dx, dy)`);
160
+ * non-dragged items between source and target get a per-item
161
+ * `style.transform = translate(deltaFromSnapshot)` that opens
162
+ * the correct gap regardless of row boundaries. `data-shift`
163
+ * is always `undefined` in 2D so CSS `translateY(var(--...))`
164
+ * rules don't conflict with the per-item transform.
165
+ *
166
+ * Keyboard navigation (`moveBy`) stays linear-array in both modes —
167
+ * arrow keys step through the array indices regardless of visual
168
+ * row, because that's what screen readers announce and what the
169
+ * underlying data order actually is.
170
+ */
171
+ layout?: '1d' | '2d';
133
172
  }
134
173
  export declare function connect<S>(get: (s: S) => SortableState, send: Send<SortableMsg>, opts: ConnectOptions): SortableParts<S>;
135
174
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"sortable.d.ts","sourceRoot":"","sources":["../../src/components/sortable.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sortable.d.ts","sourceRoot":"","sources":["../../src/components/sortable.ts"],"names":[],"mappings":"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"}
@@ -12,7 +12,9 @@ export function update(state, msg) {
12
12
  currentIndex: msg.index,
13
13
  fromContainer: msg.container,
14
14
  toContainer: msg.container,
15
+ startX: msg.x,
15
16
  startY: msg.y,
17
+ currentX: msg.x,
16
18
  currentY: msg.y,
17
19
  },
18
20
  },
@@ -23,6 +25,7 @@ export function update(state, msg) {
23
25
  return [state, []];
24
26
  if (state.dragging.currentIndex === msg.index &&
25
27
  state.dragging.toContainer === msg.container &&
28
+ state.dragging.currentX === msg.x &&
26
29
  state.dragging.currentY === msg.y) {
27
30
  return [state, []];
28
31
  }
@@ -32,6 +35,7 @@ export function update(state, msg) {
32
35
  ...state.dragging,
33
36
  currentIndex: msg.index,
34
37
  toContainer: msg.container,
38
+ currentX: msg.x,
35
39
  currentY: msg.y,
36
40
  },
37
41
  },
@@ -56,7 +60,9 @@ export function update(state, msg) {
56
60
  currentIndex: msg.index,
57
61
  fromContainer: msg.container,
58
62
  toContainer: msg.container,
63
+ startX: 0,
59
64
  startY: 0,
65
+ currentX: 0,
60
66
  currentY: 0,
61
67
  },
62
68
  },
@@ -75,6 +81,7 @@ export function update(state, msg) {
75
81
  export function connect(get, send, opts) {
76
82
  // The connect's `id` doubles as the cross-container identifier
77
83
  const containerId = opts.id;
84
+ const layout = opts.layout ?? '1d';
78
85
  const snapshots = new Map();
79
86
  function snapshotContainer(rootEl, cid) {
80
87
  const items = rootEl.querySelectorAll('[data-scope="sortable"][data-part="item"]');
@@ -83,7 +90,7 @@ export function connect(get, send, opts) {
83
90
  const idToIndex = new Map();
84
91
  items.forEach((item, i) => {
85
92
  const r = item.getBoundingClientRect();
86
- mids.push(r.top + r.height / 2);
93
+ mids.push({ x: r.left + r.width / 2, y: r.top + r.height / 2 });
87
94
  const itemId = item.dataset.id;
88
95
  if (itemId !== undefined)
89
96
  idToIndex.set(itemId, i);
@@ -99,11 +106,12 @@ export function connect(get, send, opts) {
99
106
  }
100
107
  }
101
108
  // Find the target index under the pointer using the drag-start snapshot.
102
- // Picks the index whose midpoint is closest to the pointer Y — stable
103
- // against items being visually transformed during the drag.
109
+ // 1D mode: picks by Y-only distance (original behavior). 2D mode: picks
110
+ // by Euclidean distance over {x, y} midpoints — required for flex-wrap
111
+ // / grid layouts where multiple items share a row and collapse to the
112
+ // same Y value. Both modes are stable against items being visually
113
+ // transformed during the drag because midpoints are taken pre-transform.
104
114
  function findTargetAt(e) {
105
- // Find which sortable root the pointer is over (using getBoundingClientRect
106
- // on roots, which are not transformed during drag).
107
115
  const roots = document.querySelectorAll('[data-scope="sortable"][data-part="root"]');
108
116
  for (const root of roots) {
109
117
  const r = root.getBoundingClientRect();
@@ -118,14 +126,33 @@ export function connect(get, send, opts) {
118
126
  if (!snap || snap.mids.length === 0)
119
127
  return { container: cid, index: 0 };
120
128
  const mids = snap.mids;
121
- // Find the index whose midpoint is closest to clientY
122
129
  let bestIdx = 0;
123
- let bestDist = Math.abs(e.clientY - mids[0]);
124
- for (let i = 1; i < mids.length; i++) {
125
- const d = Math.abs(e.clientY - mids[i]);
126
- if (d < bestDist) {
127
- bestDist = d;
128
- bestIdx = i;
130
+ if (layout === '2d') {
131
+ // Euclidean (squared monotonic with distance, saves a sqrt).
132
+ const dx0 = e.clientX - mids[0].x;
133
+ const dy0 = e.clientY - mids[0].y;
134
+ let bestDist = dx0 * dx0 + dy0 * dy0;
135
+ for (let i = 1; i < mids.length; i++) {
136
+ const dx = e.clientX - mids[i].x;
137
+ const dy = e.clientY - mids[i].y;
138
+ const d = dx * dx + dy * dy;
139
+ if (d < bestDist) {
140
+ bestDist = d;
141
+ bestIdx = i;
142
+ }
143
+ }
144
+ }
145
+ else {
146
+ // 1D — Y-only distance. Preserves the original behavior for
147
+ // vertical lists; same-row items in a flex-wrap would tie and
148
+ // the first match wins, which is the bug that motivates 2D.
149
+ let bestDist = Math.abs(e.clientY - mids[0].y);
150
+ for (let i = 1; i < mids.length; i++) {
151
+ const d = Math.abs(e.clientY - mids[i].y);
152
+ if (d < bestDist) {
153
+ bestDist = d;
154
+ bestIdx = i;
155
+ }
129
156
  }
130
157
  }
131
158
  return { container: cid, index: bestIdx };
@@ -143,7 +170,13 @@ export function connect(get, send, opts) {
143
170
  return;
144
171
  const hit = findTargetAt(e);
145
172
  if (hit !== null)
146
- send({ type: 'move', index: hit.index, container: hit.container, y: e.clientY });
173
+ send({
174
+ type: 'move',
175
+ index: hit.index,
176
+ container: hit.container,
177
+ x: e.clientX,
178
+ y: e.clientY,
179
+ });
147
180
  },
148
181
  onPointerUp: () => {
149
182
  snapshots.clear();
@@ -177,7 +210,15 @@ export function connect(get, send, opts) {
177
210
  // Shift direction for items BETWEEN the source and target (excluding the
178
211
  // dragged item itself). 'down' = item should translate down to make room;
179
212
  // 'up' = item should translate up. CSS controls the actual displacement.
213
+ //
214
+ // In 2D layout, `data-shift` is always undefined — the per-item
215
+ // `style.transform` below opens the correct gap directly. Keeping
216
+ // `data-shift` out of the 2D path prevents any author-provided
217
+ // CSS rule like `[data-shift] { translate: 0 var(--sortable-shift) }`
218
+ // from fighting with the computed transform.
180
219
  'data-shift': (s) => {
220
+ if (layout === '2d')
221
+ return undefined;
181
222
  const d = get(s).dragging;
182
223
  if (!d || d.fromContainer !== containerId || d.toContainer !== containerId)
183
224
  return undefined;
@@ -200,14 +241,65 @@ export function connect(get, send, opts) {
200
241
  }
201
242
  return undefined;
202
243
  },
203
- // The dragged item follows the pointer via translateY(deltaY). Other
204
- // items have no transform override data-shift CSS handles them.
244
+ // The dragged item follows the pointer. In 1D, translateY only; in
245
+ // 2D, both axes. Non-dragged items in 2D between source and target
246
+ // get a per-item translate computed from the snapshot — each item's
247
+ // vector is `snapshot[newSlot] - snapshot[ownSlot]` so the gap
248
+ // opens correctly regardless of row wrap. In 1D, non-dragged items
249
+ // emit `undefined` here and rely on the consumer's CSS `data-shift`
250
+ // rule.
205
251
  'style.transform': (s) => {
206
252
  const d = get(s).dragging;
207
- if (!d || d.id !== id || d.fromContainer !== containerId)
253
+ if (!d)
254
+ return undefined;
255
+ const isDragged = d.id === id && d.fromContainer === containerId;
256
+ if (isDragged) {
257
+ const deltaY = d.currentY - d.startY;
258
+ if (layout === '2d') {
259
+ const deltaX = d.currentX - d.startX;
260
+ return `translate(${deltaX}px, ${deltaY}px)`;
261
+ }
262
+ return `translateY(${deltaY}px)`;
263
+ }
264
+ // Non-dragged items: per-item displacement in 2D only.
265
+ if (layout !== '2d')
266
+ return undefined;
267
+ if (d.fromContainer !== containerId || d.toContainer !== containerId)
268
+ return undefined;
269
+ if (d.startIndex === d.currentIndex)
270
+ return undefined;
271
+ const snap = snapshots.get(containerId);
272
+ if (!snap)
273
+ return undefined;
274
+ const liveIndex = snap.idToIndex.get(id);
275
+ if (liveIndex === undefined)
276
+ return undefined;
277
+ // Which slot this item should visually occupy while the drag
278
+ // previews the reorder:
279
+ // drag-down (start < current): items at liveIndex in
280
+ // (start .. current] shift left-by-one in array order, so
281
+ // they take the slot at liveIndex - 1.
282
+ // drag-up (current < start): items at liveIndex in
283
+ // [current .. start) shift right-by-one, take slot
284
+ // liveIndex + 1.
285
+ let targetSlot;
286
+ if (d.startIndex < d.currentIndex) {
287
+ if (liveIndex <= d.startIndex || liveIndex > d.currentIndex)
288
+ return undefined;
289
+ targetSlot = liveIndex - 1;
290
+ }
291
+ else {
292
+ if (liveIndex < d.currentIndex || liveIndex >= d.startIndex)
293
+ return undefined;
294
+ targetSlot = liveIndex + 1;
295
+ }
296
+ const own = snap.mids[liveIndex];
297
+ const target = snap.mids[targetSlot];
298
+ if (!own || !target)
208
299
  return undefined;
209
- const deltaY = d.currentY - d.startY;
210
- return `translateY(${deltaY}px)`;
300
+ const dx = target.x - own.x;
301
+ const dy = target.y - own.y;
302
+ return `translate(${dx}px, ${dy}px)`;
211
303
  },
212
304
  'style.zIndex': (s) => {
213
305
  const d = get(s).dragging;
@@ -262,7 +354,14 @@ export function connect(get, send, opts) {
262
354
  // positions. Otherwise items shifting via CSS would cause the target
263
355
  // to oscillate as elementFromPoint hits different items.
264
356
  snapshotAll();
265
- send({ type: 'start', id, index: currentIndex, container: containerId, y: e.clientY });
357
+ send({
358
+ type: 'start',
359
+ id,
360
+ index: currentIndex,
361
+ container: containerId,
362
+ x: e.clientX,
363
+ y: e.clientY,
364
+ });
266
365
  },
267
366
  onKeyDown: (e) => {
268
367
  switch (e.key) {
@@ -1 +1 @@
1
- {"version":3,"file":"sortable.js","sourceRoot":"","sources":["../../src/components/sortable.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Send } from '@llui/dom'\n\n/**\n * Sortable — pointer-based reorderable list.\n *\n * State machine tracks the currently-dragged item and where it's hovering.\n * The app owns the actual array; listen for `drop` and use `reorder(arr, from, to)`\n * to compute the new order, or watch `currentIndex` during drag for live preview.\n *\n * ```ts\n * type State = { items: string[]; sort: SortableState }\n *\n * update: (state, msg) => {\n * switch (msg.type) {\n * case 'sort':\n * return [{ ...state, sort: sortable.update(state.sort, msg.msg)[0] }, []]\n * case 'drop': {\n * const d = state.sort.dragging\n * if (!d) return [state, []]\n * return [{ ...state, items: reorder(state.items, d.startIndex, d.currentIndex) }, []]\n * }\n * }\n * }\n *\n * view: ({ send, each, text }) => {\n * const s = sortable.connect<State>(s => s.sort, m => send({ type: 'sort', msg: m }), { id: 'list' })\n * return [\n * ul({ ...s.root, class: 'list' }, [\n * ...each({\n * items: (st) => st.items,\n * key: (x) => x,\n * render: ({ item, index }) => [\n * li({ ...s.item(item(), index()), class: 'item' }, [\n * div({ ...s.handle(item(), index()), class: 'handle' }, [text('⋮⋮')]),\n * text(item),\n * ]),\n * ],\n * }),\n * ]),\n * ]\n * }\n * ```\n *\n * Hook up pointermove/pointerup at the root (attachPointerHandlers) — or\n * wire them directly via `onPointerMove` / `onPointerUp` on the root part.\n */\n\nexport interface DragState {\n id: string\n startIndex: number\n currentIndex: number\n /**\n * Container the drag originated from. Defaults to the connect's `id` for\n * single-container sortables. Set when multiple sortables share state.\n */\n fromContainer: string\n /**\n * Container the pointer is currently over. Same as `fromContainer` for\n * single-container sortables. Differs when dragging across containers.\n */\n toContainer: string\n /**\n * Pointer Y at drag start (viewport coordinates). Used by CSS to make\n * the dragged item follow the pointer via translateY(deltaY).\n */\n startY: number\n /**\n * Current pointer Y (viewport coordinates). `deltaY = currentY - startY`.\n */\n currentY: number\n}\n\nexport interface SortableState {\n dragging: DragState | null\n}\n\nexport type SortableMsg =\n | { type: 'start'; id: string; index: number; container: string; y: number }\n | { type: 'move'; index: number; container: string; y: number }\n | { type: 'drop' }\n | { type: 'cancel' }\n // Keyboard: toggle between picking up and dropping at current position\n | { type: 'toggleGrab'; id: string; index: number; container: string }\n // Keyboard: shift currentIndex by delta (clamped ≥ 0)\n | { type: 'moveBy'; delta: number }\n\nexport function init(): SortableState {\n return { dragging: null }\n}\n\nexport function update(state: SortableState, msg: SortableMsg): [SortableState, never[]] {\n switch (msg.type) {\n case 'start':\n return [\n {\n dragging: {\n id: msg.id,\n startIndex: msg.index,\n currentIndex: msg.index,\n fromContainer: msg.container,\n toContainer: msg.container,\n startY: msg.y,\n currentY: msg.y,\n },\n },\n [],\n ]\n case 'move': {\n if (!state.dragging) return [state, []]\n if (\n state.dragging.currentIndex === msg.index &&\n state.dragging.toContainer === msg.container &&\n state.dragging.currentY === msg.y\n ) {\n return [state, []]\n }\n return [\n {\n dragging: {\n ...state.dragging,\n currentIndex: msg.index,\n toContainer: msg.container,\n currentY: msg.y,\n },\n },\n [],\n ]\n }\n case 'drop':\n return state.dragging ? [{ dragging: null }, []] : [state, []]\n case 'cancel':\n return state.dragging ? [{ dragging: null }, []] : [state, []]\n case 'toggleGrab':\n if (state.dragging) {\n // Already dragging — drop at current position\n return [{ dragging: null }, []]\n }\n // Pick up (keyboard — no pointer position)\n return [\n {\n dragging: {\n id: msg.id,\n startIndex: msg.index,\n currentIndex: msg.index,\n fromContainer: msg.container,\n toContainer: msg.container,\n startY: 0,\n currentY: 0,\n },\n },\n [],\n ]\n case 'moveBy': {\n if (!state.dragging) return [state, []]\n const next = Math.max(0, state.dragging.currentIndex + msg.delta)\n if (next === state.dragging.currentIndex) return [state, []]\n return [{ dragging: { ...state.dragging, currentIndex: next } }, []]\n }\n }\n}\n\nexport interface SortableParts<S> {\n root: {\n 'data-scope': 'sortable'\n 'data-part': 'root'\n 'data-container-id': string\n 'data-dragging': (s: S) => '' | undefined\n onPointerMove: (e: PointerEvent) => void\n onPointerUp: (e: PointerEvent) => void\n onPointerCancel: (e: PointerEvent) => void\n }\n item: (\n id: string,\n index: number,\n ) => {\n 'data-scope': 'sortable'\n 'data-part': 'item'\n 'data-index': string\n 'data-id': string\n 'data-dragging': (s: S) => '' | undefined\n 'data-over': (s: S) => '' | undefined\n 'data-shift': (s: S) => 'up' | 'down' | undefined\n 'style.transform': (s: S) => string | undefined\n 'style.zIndex': (s: S) => string | undefined\n }\n handle: (\n id: string,\n index: number,\n ) => {\n 'data-scope': 'sortable'\n 'data-part': 'handle'\n role: 'button'\n tabIndex: 0\n 'aria-grabbed': (s: S) => boolean\n 'aria-label': string\n onPointerDown: (e: PointerEvent) => void\n onKeyDown: (e: KeyboardEvent) => void\n }\n}\n\nexport interface ConnectOptions {\n id: string\n}\n\nexport function connect<S>(\n get: (s: S) => SortableState,\n send: Send<SortableMsg>,\n opts: ConnectOptions,\n): SortableParts<S> {\n // The connect's `id` doubles as the cross-container identifier\n const containerId = opts.id\n\n // Snapshots taken at drag start — stable throughout the drag so computing\n // the target index is not affected by items visually shifting via CSS.\n // Map: container-id → array of midpoint Y values for each item's original\n // bounding rect (sorted by index). The handler records this on pointerdown.\n interface Snapshot {\n mids: number[]\n // id → current DOM index at drag start. Used by data-shift to look up an\n // item's live position, since the `index` captured at render time is\n // frozen and goes stale after each() reconciles a reorder.\n idToIndex: Map<string, number>\n }\n const snapshots = new Map<string, Snapshot>()\n\n function snapshotContainer(rootEl: HTMLElement, cid: string): void {\n const items = rootEl.querySelectorAll<HTMLElement>('[data-scope=\"sortable\"][data-part=\"item\"]')\n // Read rects once — they're pre-transform (no drag shifts yet)\n const mids: number[] = []\n const idToIndex = new Map<string, number>()\n items.forEach((item, i) => {\n const r = item.getBoundingClientRect()\n mids.push(r.top + r.height / 2)\n const itemId = item.dataset.id\n if (itemId !== undefined) idToIndex.set(itemId, i)\n })\n snapshots.set(cid, { mids, idToIndex })\n }\n\n function snapshotAll(): void {\n const roots = document.querySelectorAll<HTMLElement>(\n '[data-scope=\"sortable\"][data-part=\"root\"]',\n )\n for (const root of roots) {\n const cid = root.dataset.containerId\n if (cid) snapshotContainer(root, cid)\n }\n }\n\n // Find the target index under the pointer using the drag-start snapshot.\n // Picks the index whose midpoint is closest to the pointer Y — stable\n // against items being visually transformed during the drag.\n function findTargetAt(e: PointerEvent): { container: string; index: number } | null {\n // Find which sortable root the pointer is over (using getBoundingClientRect\n // on roots, which are not transformed during drag).\n const roots = document.querySelectorAll<HTMLElement>(\n '[data-scope=\"sortable\"][data-part=\"root\"]',\n )\n for (const root of roots) {\n const r = root.getBoundingClientRect()\n if (e.clientX < r.left || e.clientX > r.right) continue\n if (e.clientY < r.top || e.clientY > r.bottom) continue\n const cid = root.dataset.containerId\n if (!cid) continue\n const snap = snapshots.get(cid)\n if (!snap || snap.mids.length === 0) return { container: cid, index: 0 }\n const mids = snap.mids\n // Find the index whose midpoint is closest to clientY\n let bestIdx = 0\n let bestDist = Math.abs(e.clientY - mids[0]!)\n for (let i = 1; i < mids.length; i++) {\n const d = Math.abs(e.clientY - mids[i]!)\n if (d < bestDist) {\n bestDist = d\n bestIdx = i\n }\n }\n return { container: cid, index: bestIdx }\n }\n return null\n }\n\n return {\n root: {\n 'data-scope': 'sortable',\n 'data-part': 'root',\n 'data-container-id': containerId,\n 'data-dragging': (s) => (get(s).dragging ? '' : undefined),\n onPointerMove: (e) => {\n if (!e.buttons) return\n const hit = findTargetAt(e)\n if (hit !== null)\n send({ type: 'move', index: hit.index, container: hit.container, y: e.clientY })\n },\n onPointerUp: () => {\n snapshots.clear()\n send({ type: 'drop' })\n },\n onPointerCancel: () => {\n snapshots.clear()\n send({ type: 'cancel' })\n },\n },\n item: (id, index) => ({\n 'data-scope': 'sortable',\n 'data-part': 'item',\n 'data-index': String(index),\n 'data-id': id,\n 'data-dragging': (s) => {\n const d = get(s).dragging\n return d?.id === id && d?.fromContainer === containerId ? '' : undefined\n },\n 'data-over': (s) => {\n const d = get(s).dragging\n if (!d || d.toContainer !== containerId) return undefined\n // Look up this item's CURRENT DOM index via the drag-start snapshot.\n // The `index` closed over here is frozen at initial render and goes\n // stale after each() reconciles a reorder.\n const snap = snapshots.get(containerId)\n const liveIndex = snap?.idToIndex.get(id) ?? index\n return d.currentIndex === liveIndex ? '' : undefined\n },\n // Shift direction for items BETWEEN the source and target (excluding the\n // dragged item itself). 'down' = item should translate down to make room;\n // 'up' = item should translate up. CSS controls the actual displacement.\n 'data-shift': (s) => {\n const d = get(s).dragging\n if (!d || d.fromContainer !== containerId || d.toContainer !== containerId) return undefined\n if (d.id === id) return undefined\n if (d.startIndex === d.currentIndex) return undefined\n // Look up this item's live DOM index — see note on data-over.\n const snap = snapshots.get(containerId)\n const liveIndex = snap?.idToIndex.get(id) ?? index\n if (d.startIndex < d.currentIndex) {\n // Dragging down: items between start+1 and current shift up\n if (liveIndex > d.startIndex && liveIndex <= d.currentIndex) return 'up'\n } else {\n // Dragging up: items between current and start-1 shift down\n if (liveIndex >= d.currentIndex && liveIndex < d.startIndex) return 'down'\n }\n return undefined\n },\n // The dragged item follows the pointer via translateY(deltaY). Other\n // items have no transform override — data-shift CSS handles them.\n 'style.transform': (s) => {\n const d = get(s).dragging\n if (!d || d.id !== id || d.fromContainer !== containerId) return undefined\n const deltaY = d.currentY - d.startY\n return `translateY(${deltaY}px)`\n },\n 'style.zIndex': (s) => {\n const d = get(s).dragging\n if (!d || d.id !== id || d.fromContainer !== containerId) return undefined\n return '10'\n },\n }),\n handle: (id, index) => ({\n 'data-scope': 'sortable',\n 'data-part': 'handle',\n role: 'button',\n tabIndex: 0,\n 'aria-grabbed': (s) => {\n const d = get(s).dragging\n return d?.id === id && d?.fromContainer === containerId\n },\n 'aria-label':\n 'Drag handle. Press space to pick up, arrow keys to move, space again to drop, escape to cancel.',\n onPointerDown: (e) => {\n e.preventDefault()\n const target = e.currentTarget as Element | null\n if (target && 'setPointerCapture' in target) {\n try {\n ;(target as Element & { setPointerCapture: (id: number) => void }).setPointerCapture(\n e.pointerId,\n )\n } catch {\n // Ignore — not all elements support pointer capture\n }\n }\n // Compute the CURRENT DOM index of this handle's item — the captured\n // `index` param is stale after a reorder (each() moves keyed nodes\n // without re-running render, so the closure's index is frozen at\n // initial mount). Walk up to find the containing item, then count its\n // position among sibling items.\n let currentIndex = index\n if (target) {\n const itemEl = (target as Element).closest<HTMLElement>(\n '[data-scope=\"sortable\"][data-part=\"item\"]',\n )\n const rootEl = (target as Element).closest<HTMLElement>(\n '[data-scope=\"sortable\"][data-part=\"root\"]',\n )\n if (itemEl && rootEl) {\n const items = rootEl.querySelectorAll<HTMLElement>(\n '[data-scope=\"sortable\"][data-part=\"item\"]',\n )\n for (let i = 0; i < items.length; i++) {\n if (items[i] === itemEl) {\n currentIndex = i\n break\n }\n }\n }\n }\n // Snapshot positions BEFORE the drag starts, so subsequent pointermove\n // events can resolve the target index against stable (pre-transform)\n // positions. Otherwise items shifting via CSS would cause the target\n // to oscillate as elementFromPoint hits different items.\n snapshotAll()\n send({ type: 'start', id, index: currentIndex, container: containerId, y: e.clientY })\n },\n onKeyDown: (e) => {\n switch (e.key) {\n case ' ':\n case 'Enter':\n e.preventDefault()\n send({ type: 'toggleGrab', id, index, container: containerId })\n return\n case 'Escape':\n e.preventDefault()\n send({ type: 'cancel' })\n return\n case 'ArrowDown':\n case 'ArrowRight':\n e.preventDefault()\n send({ type: 'moveBy', delta: 1 })\n return\n case 'ArrowUp':\n case 'ArrowLeft':\n e.preventDefault()\n send({ type: 'moveBy', delta: -1 })\n return\n }\n },\n }),\n }\n}\n\n// ── Reorder utility ────────────────────────────────────────────\n\n/**\n * Move an item in an array from one index to another, returning a new array.\n * Out-of-range indices are clamped to array bounds.\n */\nexport function reorder<T>(arr: readonly T[], from: number, to: number): T[] {\n const len = arr.length\n if (len === 0) return []\n const f = Math.max(0, Math.min(len - 1, from))\n const t = Math.max(0, Math.min(len - 1, to))\n if (f === t) return arr.slice()\n const result = arr.slice()\n const [item] = result.splice(f, 1)\n result.splice(t, 0, item)\n return result\n}\n\nexport const sortable = { init, update, connect, reorder }\n"]}
1
+ {"version":3,"file":"sortable.js","sourceRoot":"","sources":["../../src/components/sortable.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Send } from '@llui/dom'\n\n/**\n * Sortable — pointer-based reorderable list.\n *\n * State machine tracks the currently-dragged item and where it's hovering.\n * The app owns the actual array; listen for `drop` and use `reorder(arr, from, to)`\n * to compute the new order, or watch `currentIndex` during drag for live preview.\n *\n * ```ts\n * type State = { items: string[]; sort: SortableState }\n *\n * update: (state, msg) => {\n * switch (msg.type) {\n * case 'sort':\n * return [{ ...state, sort: sortable.update(state.sort, msg.msg)[0] }, []]\n * case 'drop': {\n * const d = state.sort.dragging\n * if (!d) return [state, []]\n * return [{ ...state, items: reorder(state.items, d.startIndex, d.currentIndex) }, []]\n * }\n * }\n * }\n *\n * view: ({ send, each, text }) => {\n * const s = sortable.connect<State>(s => s.sort, m => send({ type: 'sort', msg: m }), { id: 'list' })\n * return [\n * ul({ ...s.root, class: 'list' }, [\n * ...each({\n * items: (st) => st.items,\n * key: (x) => x,\n * render: ({ item, index }) => [\n * li({ ...s.item(item(), index()), class: 'item' }, [\n * div({ ...s.handle(item(), index()), class: 'handle' }, [text('⋮⋮')]),\n * text(item),\n * ]),\n * ],\n * }),\n * ]),\n * ]\n * }\n * ```\n *\n * Hook up pointermove/pointerup at the root (attachPointerHandlers) — or\n * wire them directly via `onPointerMove` / `onPointerUp` on the root part.\n */\n\nexport interface DragState {\n id: string\n startIndex: number\n currentIndex: number\n /**\n * Container the drag originated from. Defaults to the connect's `id` for\n * single-container sortables. Set when multiple sortables share state.\n */\n fromContainer: string\n /**\n * Container the pointer is currently over. Same as `fromContainer` for\n * single-container sortables. Differs when dragging across containers.\n */\n toContainer: string\n /**\n * Pointer X at drag start (viewport coordinates). Used by 2D layouts\n * to compute `deltaX = currentX - startX` alongside the Y axis. In 1D\n * layouts X is tracked but ignored by the renderer.\n */\n startX: number\n /**\n * Pointer Y at drag start (viewport coordinates). Used by CSS / the\n * library's `style.transform` binding to make the dragged item follow\n * the pointer.\n */\n startY: number\n /**\n * Current pointer X (viewport coordinates). `deltaX = currentX - startX`.\n */\n currentX: number\n /**\n * Current pointer Y (viewport coordinates). `deltaY = currentY - startY`.\n */\n currentY: number\n}\n\nexport interface SortableState {\n dragging: DragState | null\n}\n\nexport type SortableMsg =\n | { type: 'start'; id: string; index: number; container: string; x: number; y: number }\n | { type: 'move'; index: number; container: string; x: number; y: number }\n | { type: 'drop' }\n | { type: 'cancel' }\n // Keyboard: toggle between picking up and dropping at current position\n | { type: 'toggleGrab'; id: string; index: number; container: string }\n // Keyboard: shift currentIndex by delta (clamped ≥ 0)\n | { type: 'moveBy'; delta: number }\n\nexport function init(): SortableState {\n return { dragging: null }\n}\n\nexport function update(state: SortableState, msg: SortableMsg): [SortableState, never[]] {\n switch (msg.type) {\n case 'start':\n return [\n {\n dragging: {\n id: msg.id,\n startIndex: msg.index,\n currentIndex: msg.index,\n fromContainer: msg.container,\n toContainer: msg.container,\n startX: msg.x,\n startY: msg.y,\n currentX: msg.x,\n currentY: msg.y,\n },\n },\n [],\n ]\n case 'move': {\n if (!state.dragging) return [state, []]\n if (\n state.dragging.currentIndex === msg.index &&\n state.dragging.toContainer === msg.container &&\n state.dragging.currentX === msg.x &&\n state.dragging.currentY === msg.y\n ) {\n return [state, []]\n }\n return [\n {\n dragging: {\n ...state.dragging,\n currentIndex: msg.index,\n toContainer: msg.container,\n currentX: msg.x,\n currentY: msg.y,\n },\n },\n [],\n ]\n }\n case 'drop':\n return state.dragging ? [{ dragging: null }, []] : [state, []]\n case 'cancel':\n return state.dragging ? [{ dragging: null }, []] : [state, []]\n case 'toggleGrab':\n if (state.dragging) {\n // Already dragging — drop at current position\n return [{ dragging: null }, []]\n }\n // Pick up (keyboard — no pointer position)\n return [\n {\n dragging: {\n id: msg.id,\n startIndex: msg.index,\n currentIndex: msg.index,\n fromContainer: msg.container,\n toContainer: msg.container,\n startX: 0,\n startY: 0,\n currentX: 0,\n currentY: 0,\n },\n },\n [],\n ]\n case 'moveBy': {\n if (!state.dragging) return [state, []]\n const next = Math.max(0, state.dragging.currentIndex + msg.delta)\n if (next === state.dragging.currentIndex) return [state, []]\n return [{ dragging: { ...state.dragging, currentIndex: next } }, []]\n }\n }\n}\n\nexport interface SortableParts<S> {\n root: {\n 'data-scope': 'sortable'\n 'data-part': 'root'\n 'data-container-id': string\n 'data-dragging': (s: S) => '' | undefined\n onPointerMove: (e: PointerEvent) => void\n onPointerUp: (e: PointerEvent) => void\n onPointerCancel: (e: PointerEvent) => void\n }\n item: (\n id: string,\n index: number,\n ) => {\n 'data-scope': 'sortable'\n 'data-part': 'item'\n 'data-index': string\n 'data-id': string\n 'data-dragging': (s: S) => '' | undefined\n 'data-over': (s: S) => '' | undefined\n 'data-shift': (s: S) => 'up' | 'down' | undefined\n 'style.transform': (s: S) => string | undefined\n 'style.zIndex': (s: S) => string | undefined\n }\n handle: (\n id: string,\n index: number,\n ) => {\n 'data-scope': 'sortable'\n 'data-part': 'handle'\n role: 'button'\n tabIndex: 0\n 'aria-grabbed': (s: S) => boolean\n 'aria-label': string\n onPointerDown: (e: PointerEvent) => void\n onKeyDown: (e: KeyboardEvent) => void\n }\n}\n\nexport interface ConnectOptions {\n id: string\n /**\n * Drag-target selection + render strategy.\n *\n * - `'1d'` (default) — single-axis, Y-only. `findTargetAt` picks\n * by vertical distance; `style.transform` on the dragged item\n * is `translateY(deltaY)`; non-dragged items between source\n * and target emit `data-shift: 'up' | 'down'` so CSS can\n * animate them via `translateY(±var(--sortable-shift))`.\n * Correct for vertical lists; fails for 2D layouts (flex-wrap,\n * grid) because same-row items collapse to the same midpoint\n * distance.\n *\n * - `'2d'` — Euclidean target selection against 2D midpoints;\n * dragged item follows both X and Y (`translate(dx, dy)`);\n * non-dragged items between source and target get a per-item\n * `style.transform = translate(deltaFromSnapshot)` that opens\n * the correct gap regardless of row boundaries. `data-shift`\n * is always `undefined` in 2D so CSS `translateY(var(--...))`\n * rules don't conflict with the per-item transform.\n *\n * Keyboard navigation (`moveBy`) stays linear-array in both modes —\n * arrow keys step through the array indices regardless of visual\n * row, because that's what screen readers announce and what the\n * underlying data order actually is.\n */\n layout?: '1d' | '2d'\n}\n\nexport function connect<S>(\n get: (s: S) => SortableState,\n send: Send<SortableMsg>,\n opts: ConnectOptions,\n): SortableParts<S> {\n // The connect's `id` doubles as the cross-container identifier\n const containerId = opts.id\n const layout = opts.layout ?? '1d'\n\n // Snapshots taken at drag start — stable throughout the drag so computing\n // the target index is not affected by items visually shifting via CSS.\n // Map: container-id → array of midpoint {x, y} pairs for each item's\n // original bounding rect (sorted by index). The handler records this on\n // pointerdown. Always 2D internally; 1D layout's findTargetAt ignores X.\n interface Snapshot {\n mids: Array<{ x: number; y: number }>\n // id → current DOM index at drag start. Used by data-shift / per-item\n // transform to look up an item's live position, since the `index`\n // captured at render time is frozen and goes stale after each()\n // reconciles a reorder.\n idToIndex: Map<string, number>\n }\n const snapshots = new Map<string, Snapshot>()\n\n function snapshotContainer(rootEl: HTMLElement, cid: string): void {\n const items = rootEl.querySelectorAll<HTMLElement>('[data-scope=\"sortable\"][data-part=\"item\"]')\n // Read rects once — they're pre-transform (no drag shifts yet)\n const mids: Array<{ x: number; y: number }> = []\n const idToIndex = new Map<string, number>()\n items.forEach((item, i) => {\n const r = item.getBoundingClientRect()\n mids.push({ x: r.left + r.width / 2, y: r.top + r.height / 2 })\n const itemId = item.dataset.id\n if (itemId !== undefined) idToIndex.set(itemId, i)\n })\n snapshots.set(cid, { mids, idToIndex })\n }\n\n function snapshotAll(): void {\n const roots = document.querySelectorAll<HTMLElement>(\n '[data-scope=\"sortable\"][data-part=\"root\"]',\n )\n for (const root of roots) {\n const cid = root.dataset.containerId\n if (cid) snapshotContainer(root, cid)\n }\n }\n\n // Find the target index under the pointer using the drag-start snapshot.\n // 1D mode: picks by Y-only distance (original behavior). 2D mode: picks\n // by Euclidean distance over {x, y} midpoints — required for flex-wrap\n // / grid layouts where multiple items share a row and collapse to the\n // same Y value. Both modes are stable against items being visually\n // transformed during the drag because midpoints are taken pre-transform.\n function findTargetAt(e: PointerEvent): { container: string; index: number } | null {\n const roots = document.querySelectorAll<HTMLElement>(\n '[data-scope=\"sortable\"][data-part=\"root\"]',\n )\n for (const root of roots) {\n const r = root.getBoundingClientRect()\n if (e.clientX < r.left || e.clientX > r.right) continue\n if (e.clientY < r.top || e.clientY > r.bottom) continue\n const cid = root.dataset.containerId\n if (!cid) continue\n const snap = snapshots.get(cid)\n if (!snap || snap.mids.length === 0) return { container: cid, index: 0 }\n const mids = snap.mids\n let bestIdx = 0\n if (layout === '2d') {\n // Euclidean (squared — monotonic with distance, saves a sqrt).\n const dx0 = e.clientX - mids[0]!.x\n const dy0 = e.clientY - mids[0]!.y\n let bestDist = dx0 * dx0 + dy0 * dy0\n for (let i = 1; i < mids.length; i++) {\n const dx = e.clientX - mids[i]!.x\n const dy = e.clientY - mids[i]!.y\n const d = dx * dx + dy * dy\n if (d < bestDist) {\n bestDist = d\n bestIdx = i\n }\n }\n } else {\n // 1D — Y-only distance. Preserves the original behavior for\n // vertical lists; same-row items in a flex-wrap would tie and\n // the first match wins, which is the bug that motivates 2D.\n let bestDist = Math.abs(e.clientY - mids[0]!.y)\n for (let i = 1; i < mids.length; i++) {\n const d = Math.abs(e.clientY - mids[i]!.y)\n if (d < bestDist) {\n bestDist = d\n bestIdx = i\n }\n }\n }\n return { container: cid, index: bestIdx }\n }\n return null\n }\n\n return {\n root: {\n 'data-scope': 'sortable',\n 'data-part': 'root',\n 'data-container-id': containerId,\n 'data-dragging': (s) => (get(s).dragging ? '' : undefined),\n onPointerMove: (e) => {\n if (!e.buttons) return\n const hit = findTargetAt(e)\n if (hit !== null)\n send({\n type: 'move',\n index: hit.index,\n container: hit.container,\n x: e.clientX,\n y: e.clientY,\n })\n },\n onPointerUp: () => {\n snapshots.clear()\n send({ type: 'drop' })\n },\n onPointerCancel: () => {\n snapshots.clear()\n send({ type: 'cancel' })\n },\n },\n item: (id, index) => ({\n 'data-scope': 'sortable',\n 'data-part': 'item',\n 'data-index': String(index),\n 'data-id': id,\n 'data-dragging': (s) => {\n const d = get(s).dragging\n return d?.id === id && d?.fromContainer === containerId ? '' : undefined\n },\n 'data-over': (s) => {\n const d = get(s).dragging\n if (!d || d.toContainer !== containerId) return undefined\n // Look up this item's CURRENT DOM index via the drag-start snapshot.\n // The `index` closed over here is frozen at initial render and goes\n // stale after each() reconciles a reorder.\n const snap = snapshots.get(containerId)\n const liveIndex = snap?.idToIndex.get(id) ?? index\n return d.currentIndex === liveIndex ? '' : undefined\n },\n // Shift direction for items BETWEEN the source and target (excluding the\n // dragged item itself). 'down' = item should translate down to make room;\n // 'up' = item should translate up. CSS controls the actual displacement.\n //\n // In 2D layout, `data-shift` is always undefined — the per-item\n // `style.transform` below opens the correct gap directly. Keeping\n // `data-shift` out of the 2D path prevents any author-provided\n // CSS rule like `[data-shift] { translate: 0 var(--sortable-shift) }`\n // from fighting with the computed transform.\n 'data-shift': (s) => {\n if (layout === '2d') return undefined\n const d = get(s).dragging\n if (!d || d.fromContainer !== containerId || d.toContainer !== containerId) return undefined\n if (d.id === id) return undefined\n if (d.startIndex === d.currentIndex) return undefined\n // Look up this item's live DOM index — see note on data-over.\n const snap = snapshots.get(containerId)\n const liveIndex = snap?.idToIndex.get(id) ?? index\n if (d.startIndex < d.currentIndex) {\n // Dragging down: items between start+1 and current shift up\n if (liveIndex > d.startIndex && liveIndex <= d.currentIndex) return 'up'\n } else {\n // Dragging up: items between current and start-1 shift down\n if (liveIndex >= d.currentIndex && liveIndex < d.startIndex) return 'down'\n }\n return undefined\n },\n // The dragged item follows the pointer. In 1D, translateY only; in\n // 2D, both axes. Non-dragged items in 2D between source and target\n // get a per-item translate computed from the snapshot — each item's\n // vector is `snapshot[newSlot] - snapshot[ownSlot]` so the gap\n // opens correctly regardless of row wrap. In 1D, non-dragged items\n // emit `undefined` here and rely on the consumer's CSS `data-shift`\n // rule.\n 'style.transform': (s) => {\n const d = get(s).dragging\n if (!d) return undefined\n const isDragged = d.id === id && d.fromContainer === containerId\n if (isDragged) {\n const deltaY = d.currentY - d.startY\n if (layout === '2d') {\n const deltaX = d.currentX - d.startX\n return `translate(${deltaX}px, ${deltaY}px)`\n }\n return `translateY(${deltaY}px)`\n }\n // Non-dragged items: per-item displacement in 2D only.\n if (layout !== '2d') return undefined\n if (d.fromContainer !== containerId || d.toContainer !== containerId) return undefined\n if (d.startIndex === d.currentIndex) return undefined\n const snap = snapshots.get(containerId)\n if (!snap) return undefined\n const liveIndex = snap.idToIndex.get(id)\n if (liveIndex === undefined) return undefined\n // Which slot this item should visually occupy while the drag\n // previews the reorder:\n // drag-down (start < current): items at liveIndex in\n // (start .. current] shift left-by-one in array order, so\n // they take the slot at liveIndex - 1.\n // drag-up (current < start): items at liveIndex in\n // [current .. start) shift right-by-one, take slot\n // liveIndex + 1.\n let targetSlot: number\n if (d.startIndex < d.currentIndex) {\n if (liveIndex <= d.startIndex || liveIndex > d.currentIndex) return undefined\n targetSlot = liveIndex - 1\n } else {\n if (liveIndex < d.currentIndex || liveIndex >= d.startIndex) return undefined\n targetSlot = liveIndex + 1\n }\n const own = snap.mids[liveIndex]\n const target = snap.mids[targetSlot]\n if (!own || !target) return undefined\n const dx = target.x - own.x\n const dy = target.y - own.y\n return `translate(${dx}px, ${dy}px)`\n },\n 'style.zIndex': (s) => {\n const d = get(s).dragging\n if (!d || d.id !== id || d.fromContainer !== containerId) return undefined\n return '10'\n },\n }),\n handle: (id, index) => ({\n 'data-scope': 'sortable',\n 'data-part': 'handle',\n role: 'button',\n tabIndex: 0,\n 'aria-grabbed': (s) => {\n const d = get(s).dragging\n return d?.id === id && d?.fromContainer === containerId\n },\n 'aria-label':\n 'Drag handle. Press space to pick up, arrow keys to move, space again to drop, escape to cancel.',\n onPointerDown: (e) => {\n e.preventDefault()\n const target = e.currentTarget as Element | null\n if (target && 'setPointerCapture' in target) {\n try {\n ;(target as Element & { setPointerCapture: (id: number) => void }).setPointerCapture(\n e.pointerId,\n )\n } catch {\n // Ignore — not all elements support pointer capture\n }\n }\n // Compute the CURRENT DOM index of this handle's item — the captured\n // `index` param is stale after a reorder (each() moves keyed nodes\n // without re-running render, so the closure's index is frozen at\n // initial mount). Walk up to find the containing item, then count its\n // position among sibling items.\n let currentIndex = index\n if (target) {\n const itemEl = (target as Element).closest<HTMLElement>(\n '[data-scope=\"sortable\"][data-part=\"item\"]',\n )\n const rootEl = (target as Element).closest<HTMLElement>(\n '[data-scope=\"sortable\"][data-part=\"root\"]',\n )\n if (itemEl && rootEl) {\n const items = rootEl.querySelectorAll<HTMLElement>(\n '[data-scope=\"sortable\"][data-part=\"item\"]',\n )\n for (let i = 0; i < items.length; i++) {\n if (items[i] === itemEl) {\n currentIndex = i\n break\n }\n }\n }\n }\n // Snapshot positions BEFORE the drag starts, so subsequent pointermove\n // events can resolve the target index against stable (pre-transform)\n // positions. Otherwise items shifting via CSS would cause the target\n // to oscillate as elementFromPoint hits different items.\n snapshotAll()\n send({\n type: 'start',\n id,\n index: currentIndex,\n container: containerId,\n x: e.clientX,\n y: e.clientY,\n })\n },\n onKeyDown: (e) => {\n switch (e.key) {\n case ' ':\n case 'Enter':\n e.preventDefault()\n send({ type: 'toggleGrab', id, index, container: containerId })\n return\n case 'Escape':\n e.preventDefault()\n send({ type: 'cancel' })\n return\n case 'ArrowDown':\n case 'ArrowRight':\n e.preventDefault()\n send({ type: 'moveBy', delta: 1 })\n return\n case 'ArrowUp':\n case 'ArrowLeft':\n e.preventDefault()\n send({ type: 'moveBy', delta: -1 })\n return\n }\n },\n }),\n }\n}\n\n// ── Reorder utility ────────────────────────────────────────────\n\n/**\n * Move an item in an array from one index to another, returning a new array.\n * Out-of-range indices are clamped to array bounds.\n */\nexport function reorder<T>(arr: readonly T[], from: number, to: number): T[] {\n const len = arr.length\n if (len === 0) return []\n const f = Math.max(0, Math.min(len - 1, from))\n const t = Math.max(0, Math.min(len - 1, to))\n if (f === t) return arr.slice()\n const result = arr.slice()\n const [item] = result.splice(f, 1)\n result.splice(t, 0, item)\n return result\n}\n\nexport const sortable = { init, update, connect, reorder }\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llui/components",
3
- "version": "0.0.27",
3
+ "version": "0.0.29",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -457,12 +457,12 @@
457
457
  }
458
458
  },
459
459
  "peerDependencies": {
460
- "@llui/dom": "^0.0.27"
460
+ "@llui/dom": "^0.0.29"
461
461
  },
462
462
  "devDependencies": {
463
463
  "typescript": "^6.0.0",
464
464
  "vitest": "^4.1.2",
465
- "@llui/dom": "0.0.27"
465
+ "@llui/dom": "0.0.29"
466
466
  },
467
467
  "sideEffects": [
468
468
  "./dist/styles/theme.css",