@ixfx/components 0.13.1 → 0.13.3

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.
Files changed (78) hide show
  1. package/bundle/index.d.ts +100 -14
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +2211 -636
  4. package/bundle/index.js.map +1 -1
  5. package/dist/ac-token.js +1 -1
  6. package/dist/{data-grid-Ck1Qx2H1.d.ts → data-grid-DHWJN66w.d.ts} +7 -5
  7. package/dist/{data-grid-Ck1Qx2H1.d.ts.map → data-grid-DHWJN66w.d.ts.map} +1 -1
  8. package/dist/data-grid.d.ts +1 -1
  9. package/dist/data-grid.js +210 -120
  10. package/dist/data-grid.js.map +1 -1
  11. package/dist/detail-list-C96B5Txz.js +2001 -0
  12. package/dist/detail-list-C96B5Txz.js.map +1 -0
  13. package/dist/{detail-list-C1xzr1z7.d.ts → detail-list-dX7jLO6b.d.ts} +12 -3
  14. package/dist/{detail-list-C1xzr1z7.d.ts.map → detail-list-dX7jLO6b.d.ts.map} +1 -1
  15. package/dist/detail-list.d.ts +2 -2
  16. package/dist/detail-list.js +1 -1680
  17. package/dist/{flat-list-DEDG6epz.d.ts → flat-list-Y98IqH00.d.ts} +5 -2
  18. package/dist/flat-list-Y98IqH00.d.ts.map +1 -0
  19. package/dist/flat-list.d.ts +1 -1
  20. package/dist/flat-list.js +33 -1
  21. package/dist/flat-list.js.map +1 -1
  22. package/dist/{grid-list-pefLORIr.d.ts → grid-list-CoT9e1jB.d.ts} +9 -2
  23. package/dist/{grid-list-pefLORIr.d.ts.map → grid-list-CoT9e1jB.d.ts.map} +1 -1
  24. package/dist/{grid-list-DjIl910A.js → grid-list-DeqAd9-h.js} +189 -40
  25. package/dist/grid-list-DeqAd9-h.js.map +1 -0
  26. package/dist/grid-list.d.ts +1 -1
  27. package/dist/grid-list.js +1 -1
  28. package/dist/{incr-search-D91M4Ozq.js → incr-search-JIhwB49y.js} +27 -5
  29. package/dist/incr-search-JIhwB49y.js.map +1 -0
  30. package/dist/incr-search.d.ts +1 -1
  31. package/dist/incr-search.js +1 -1
  32. package/dist/index-CnQPCywE.d.ts +325 -0
  33. package/dist/index-CnQPCywE.d.ts.map +1 -0
  34. package/dist/{index-CxZ61DO1.d.ts → index-Dlah7Zfi.d.ts} +8 -1
  35. package/dist/{index-CxZ61DO1.d.ts.map → index-Dlah7Zfi.d.ts.map} +1 -1
  36. package/dist/index.d.ts +9 -9
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +46 -25
  39. package/dist/index.js.map +1 -1
  40. package/dist/miller.d.ts +1 -1
  41. package/dist/miller.d.ts.map +1 -1
  42. package/dist/miller.js +146 -100
  43. package/dist/miller.js.map +1 -1
  44. package/dist/narrowed-text.js +1 -1
  45. package/dist/split-layout-C5SYbDrq.d.ts.map +1 -1
  46. package/dist/{split-layout-CXotkdbx.js → split-layout-Deoxy47d.js} +6 -1
  47. package/dist/{split-layout-CXotkdbx.js.map → split-layout-Deoxy47d.js.map} +1 -1
  48. package/dist/split-layout.js +1 -1
  49. package/dist/{tab-list-v8mKX7Fu.d.ts → tab-list-D_dmImJM.d.ts} +2 -3
  50. package/dist/tab-list-D_dmImJM.d.ts.map +1 -0
  51. package/dist/tabs.d.ts +1 -1
  52. package/dist/tabs.js +22 -0
  53. package/dist/tabs.js.map +1 -1
  54. package/dist/{tree-DGveys4K.js → tree-DD8OaqsK.js} +1010 -230
  55. package/dist/tree-DD8OaqsK.js.map +1 -0
  56. package/dist/tree.d.ts +4 -267
  57. package/dist/tree.js +1 -1
  58. package/dist/{vertical-list-CqBc-t8w.d.ts → vertical-list-lj_19DCx.d.ts} +3 -2
  59. package/dist/{vertical-list-CqBc-t8w.d.ts.map → vertical-list-lj_19DCx.d.ts.map} +1 -1
  60. package/dist/vertical-list.d.ts +1 -1
  61. package/dist/vertical-list.js +113 -24
  62. package/dist/vertical-list.js.map +1 -1
  63. package/docs-user/data-grid.md +74 -4
  64. package/docs-user/detail-list.md +65 -0
  65. package/docs-user/flat-list.md +14 -2
  66. package/docs-user/grid-list.md +81 -4
  67. package/docs-user/index.json +2 -2
  68. package/docs-user/miller.md +59 -3
  69. package/docs-user/tree.md +63 -2
  70. package/docs-user/vertical-list.md +9 -1
  71. package/package.json +6 -1
  72. package/dist/detail-list.js.map +0 -1
  73. package/dist/flat-list-DEDG6epz.d.ts.map +0 -1
  74. package/dist/grid-list-DjIl910A.js.map +0 -1
  75. package/dist/incr-search-D91M4Ozq.js.map +0 -1
  76. package/dist/tab-list-v8mKX7Fu.d.ts.map +0 -1
  77. package/dist/tree-DGveys4K.js.map +0 -1
  78. package/dist/tree.d.ts.map +0 -1
@@ -0,0 +1,2001 @@
1
+ import { t as TickledController } from "./tickled-controller-D-HZymaZ.js";
2
+ import { t as themeFallbacks } from "./fallbacks-DQ_5OsmR.js";
3
+ import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
4
+ import "./icon-1gpwtPx8.js";
5
+ import { t as DataController } from "./data-controller-DOlmqNKb.js";
6
+ import { c as createElementListSearchAdapter, i as ListSearchController, n as listSearchStyles, o as registerListSearchCommands, r as LIST_SEARCH_MODES, u as keyForElement } from "./incr-search-JIhwB49y.js";
7
+ import { a as toGroupKey, r as partitionByGroup } from "./grouping-RYPnHkrS.js";
8
+ import { t as ListNativeDragController } from "./list-native-drag-controller-B9xO0XWn.js";
9
+ import { r as ListDragSelectController, t as ListSelectionController } from "./list-selection-controller-BeE70lbB.js";
10
+ import { n as animateListItemIn, r as animateListItemOut } from "./list-item-animation-BUoEKi5s.js";
11
+ import { t as scrollFadeStyles } from "./scroll-fade-BrNUIYnm.js";
12
+ import { n as appearanceColor, t as appearanceAbsent } from "./item-appearance-BjcMyauw.js";
13
+ import "./list-group-header.js";
14
+ import { LitElement, css, html, nothing, render } from "lit";
15
+ import { property, query, state } from "lit/decorators.js";
16
+ import { createCommandRegistry } from "@clinth/ui-commands";
17
+ //#region src/detail-list/detail-list-geometry.ts
18
+ const EMPTY_GEOMETRY = {
19
+ columns: [],
20
+ positions: /* @__PURE__ */ new Map(),
21
+ tops: /* @__PURE__ */ new Map(),
22
+ lefts: /* @__PURE__ */ new Map()
23
+ };
24
+ function contentOrigin(scroller) {
25
+ const r = scroller.getBoundingClientRect();
26
+ return {
27
+ left: r.left - scroller.scrollLeft,
28
+ top: r.top - scroller.scrollTop
29
+ };
30
+ }
31
+ /** Items closer than this (px) horizontally share a column (sub-pixel layout). */
32
+ const COLUMN_TOLERANCE_PX = 1;
33
+ /**
34
+ * Measure `items` (one rect read each — a single layout flush) and bucket them
35
+ * into columns. Zero-sized items (e.g. `display:none`) are skipped.
36
+ */
37
+ function measureGeometry(items, origin) {
38
+ const measured = [];
39
+ for (const el of items) {
40
+ const r = el.getBoundingClientRect();
41
+ if (r.width === 0 && r.height === 0) continue;
42
+ measured.push({
43
+ el,
44
+ left: r.left - origin.left,
45
+ top: r.top - origin.top,
46
+ width: r.width
47
+ });
48
+ }
49
+ measured.sort((a, b) => a.left - b.left || a.top - b.top);
50
+ const columns = [];
51
+ const positions = /* @__PURE__ */ new Map();
52
+ const tops = /* @__PURE__ */ new Map();
53
+ const lefts = /* @__PURE__ */ new Map();
54
+ for (const m of measured) {
55
+ let col = columns.at(-1);
56
+ if (!col || m.left - col.left > COLUMN_TOLERANCE_PX) {
57
+ col = {
58
+ left: m.left,
59
+ width: m.width,
60
+ items: []
61
+ };
62
+ columns.push(col);
63
+ }
64
+ positions.set(m.el, {
65
+ col: columns.length - 1,
66
+ row: col.items.length
67
+ });
68
+ col.items.push(m.el);
69
+ col.width = Math.max(col.width, m.width);
70
+ tops.set(m.el, m.top);
71
+ lefts.set(m.el, m.left);
72
+ }
73
+ return {
74
+ columns,
75
+ positions,
76
+ tops,
77
+ lefts
78
+ };
79
+ }
80
+ /**
81
+ * Whether `geometry` still matches layout, judged by re-reading a few probe
82
+ * elements. Layout shifts that matter (column width, row height, reflow after
83
+ * an insert) move at least one of first / cursor / last.
84
+ */
85
+ function geometryMatches(geometry, probes, origin) {
86
+ for (const el of probes) {
87
+ if (!el) continue;
88
+ const left = geometry.lefts.get(el);
89
+ const top = geometry.tops.get(el);
90
+ if (left === void 0 || top === void 0) return false;
91
+ const r = el.getBoundingClientRect();
92
+ if (Math.abs(r.left - origin.left - left) > .5 || Math.abs(r.top - origin.top - top) > .5) return false;
93
+ }
94
+ return true;
95
+ }
96
+ /** First index in `[lo, hi)` for which `pred` holds, given `pred` is monotonic (false… then true…). */
97
+ function partitionPoint(lo, hi, pred) {
98
+ while (lo < hi) {
99
+ const mid = lo + hi >> 1;
100
+ if (pred(mid)) hi = mid;
101
+ else lo = mid + 1;
102
+ }
103
+ return lo;
104
+ }
105
+ /**
106
+ * Items (in DOM order) whose box intersects the content-space span
107
+ * `[start, end)`, found with O(log n) layout reads — no full measure.
108
+ * Relies on column flow: DOM order is column order, so `left` only moves in
109
+ * one direction along the list (increasing for LTR, decreasing for RTL).
110
+ */
111
+ function itemsInSpanByOrder(items, origin, start, end) {
112
+ if (items.length === 0) return [];
113
+ const box = (i) => {
114
+ const r = items[i].getBoundingClientRect();
115
+ return {
116
+ left: r.left - origin.left,
117
+ right: r.right - origin.left
118
+ };
119
+ };
120
+ const first = box(0);
121
+ const last = box(items.length - 1);
122
+ if (first.left === last.left) return first.right > start && first.left < end ? items : [];
123
+ const n = items.length;
124
+ if (last.left > first.left) {
125
+ const from = partitionPoint(0, n, (i) => box(i).right > start);
126
+ const to = partitionPoint(from, n, (i) => box(i).left >= end);
127
+ return items.slice(from, to);
128
+ }
129
+ const from = partitionPoint(0, n, (i) => box(i).left < end);
130
+ const to = partitionPoint(from, n, (i) => box(i).right <= start);
131
+ return items.slice(from, to);
132
+ }
133
+ //#endregion
134
+ //#region src/detail-list/detail-list.ts
135
+ const ITEM_SELECTOR = `:scope > li`;
136
+ const GROUP_HEADER_ATTR = `data-ixfx-group-header`;
137
+ const GROUP_COLLAPSED_ATTR = `data-group-collapsed`;
138
+ const SEARCH_HIGHLIGHT_KEY = `detail-list-search`;
139
+ const NAV_KEYS = /* @__PURE__ */ new Set([
140
+ `ArrowDown`,
141
+ `ArrowUp`,
142
+ `ArrowLeft`,
143
+ `ArrowRight`,
144
+ `Home`,
145
+ `End`
146
+ ]);
147
+ const LABEL_SPAN_CSS = `overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0`;
148
+ /**
149
+ * Layout for the per-item badge wrapper `#ensureBadge` adds. Applied as inline
150
+ * `cssText` rather than shadow CSS because `::slotted()` only matches the
151
+ * host's *direct* children — the badge lives inside a slotted `<li>`, so a
152
+ * `::slotted()` rule could never reach it. The values still read the CSS
153
+ * custom property, so it stays themeable from the outside.
154
+ */
155
+ const BADGE_EL_CSS = `flex-shrink:0;display:inline-flex;align-items:center;margin-inline-start:auto;padding-inline-start:var(--detail-list-badge-gap,8px)`;
156
+ function pointInRect(x, y, rect) {
157
+ return rect.width > 0 && rect.height > 0 && x >= rect.left && x < rect.right && y >= rect.top && y < rect.bottom;
158
+ }
159
+ /** Hit-test the painted content, not a layout wrapper that may fill the row. */
160
+ function contentAtPoint(node, x, y) {
161
+ if (node instanceof Text) {
162
+ if (!node.textContent?.trim()) return false;
163
+ const range = document.createRange();
164
+ range.selectNodeContents(node);
165
+ return Array.from(range.getClientRects()).some((rect) => pointInRect(x, y, rect));
166
+ }
167
+ if (!(node instanceof Element)) return false;
168
+ if (node.hasAttribute(`data-drag-select-blank`)) return false;
169
+ if (node.hasAttribute(`data-drag-select-content`)) return pointInRect(x, y, node.getBoundingClientRect());
170
+ if (Array.from(node.childNodes).some((child) => contentAtPoint(child, x, y))) return true;
171
+ return node.childNodes.length === 0 && pointInRect(x, y, node.getBoundingClientRect());
172
+ }
173
+ /** Shallow key-array equality, treating `undefined` (never dispatched) as different. */
174
+ function sameKeys(a, b) {
175
+ if (a === void 0 || a.length !== b.length) return false;
176
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
177
+ return true;
178
+ }
179
+ function sameMembers(a, b) {
180
+ if (a.size !== b.size) return false;
181
+ for (const el of a) if (!b.has(el)) return false;
182
+ return true;
183
+ }
184
+ /** Remove `attr` from elements leaving the set, add it to elements entering it. */
185
+ function applySetDiff(prev, next, attr) {
186
+ for (const el of prev) if (!next.has(el)) el.removeAttribute(attr);
187
+ for (const el of next) if (!prev.has(el)) el.setAttribute(attr, ``);
188
+ }
189
+ /** Attributes on an item that change list membership, visibility, keys or selectability. */
190
+ const ITEM_CACHE_ATTRS = [
191
+ `hidden`,
192
+ GROUP_COLLAPSED_ATTR,
193
+ `not-checked`,
194
+ `data-key`,
195
+ `data-value`
196
+ ];
197
+ /** Whether a mutation record can change the item caches (direct children / their cache attributes). */
198
+ function affectsItemCaches(host, record) {
199
+ if (record.type === `childList`) return record.target === host;
200
+ return record.target.parentElement === host;
201
+ }
202
+ /**
203
+ * Make `parent`'s managed children equal `nodes`, in order, with the fewest
204
+ * DOM moves — a single insert stays a single move instead of re-appending
205
+ * every row. `nodes` must contain every managed child; unmanaged children
206
+ * (text, foreign elements) are left where they are.
207
+ */
208
+ function syncChildOrder(parent, nodes, managed) {
209
+ const nextManaged = (from) => {
210
+ let el = from;
211
+ while (el && !managed(el)) el = el.nextElementSibling;
212
+ return el;
213
+ };
214
+ let cursor = nextManaged(parent.firstElementChild);
215
+ for (const node of nodes) {
216
+ if (node === cursor) {
217
+ cursor = nextManaged(cursor.nextElementSibling);
218
+ continue;
219
+ }
220
+ parent.insertBefore(node, cursor);
221
+ }
222
+ }
223
+ let DetailListElement = class DetailListElement extends LitElement {
224
+ constructor(..._args) {
225
+ super(..._args);
226
+ this.selectionMode = `single`;
227
+ this.interactionMode = `standard`;
228
+ this.sort = `none`;
229
+ this.sortBy = ``;
230
+ this.items = [];
231
+ this.displayProperty = ``;
232
+ this.iconProperty = ``;
233
+ this.iconColorProperty = ``;
234
+ this.textColorProperty = ``;
235
+ this.badgeProperty = ``;
236
+ this.keyProperty = ``;
237
+ this.itemWrap = `stop`;
238
+ this.itemsDraggable = false;
239
+ this.groupBy = null;
240
+ this.crossGroupSelection = false;
241
+ this._collapsedGroups = /* @__PURE__ */ new Set();
242
+ this.#itemData = /* @__PURE__ */ new WeakMap();
243
+ this.#itemIndex = /* @__PURE__ */ new WeakMap();
244
+ this.#geometry = EMPTY_GEOMETRY;
245
+ this.#geometryDirty = true;
246
+ this.#geometryScheduled = false;
247
+ this.#appliedSelected = /* @__PURE__ */ new Set();
248
+ this.#appliedChecked = /* @__PURE__ */ new Set();
249
+ this.#itemStatesFullSync = true;
250
+ this.#hasCheckboxes = false;
251
+ this.#draggableApplied = false;
252
+ this.#sortRaf = 0;
253
+ this.#viewportScrollRaf = 0;
254
+ this.#animatedItems = /* @__PURE__ */ new WeakSet();
255
+ this.#builtFromProvider = [];
256
+ this.#groupIndex = /* @__PURE__ */ new Map();
257
+ this.#groupList = [];
258
+ this.#headerEls = /* @__PURE__ */ new Map();
259
+ this.#seq = /* @__PURE__ */ new WeakMap();
260
+ this.#seqNext = 0;
261
+ this.#wasGrouped = false;
262
+ this.#groupsScheduled = false;
263
+ this.#groupByChanged = false;
264
+ this.#providerBuilt = 0;
265
+ this.#data = new DataController(this);
266
+ this.selection = new ListSelectionController({
267
+ getOrderedItems: () => this._getVisibleItems(),
268
+ isSelectable: (el) => this._isCheckable(el),
269
+ getModes: () => ({
270
+ selectionMode: this.selectionMode,
271
+ interactionMode: this.interactionMode
272
+ }),
273
+ getKey: keyForElement,
274
+ getGroupKey: (el) => this.#groupIndex.get(el) ?? null,
275
+ crossGroupSelection: () => this.crossGroupSelection,
276
+ getFallbackGroupKey: () => {
277
+ const t = this.tickled.tickledId;
278
+ return t ? this.#groupIndex.get(t) ?? null : null;
279
+ },
280
+ onChange: (selected, previous) => {
281
+ this._syncItemStates();
282
+ this._dispatch(`list-select`, {
283
+ selected,
284
+ previous,
285
+ selectedKeys: this.selection.selectedKeys
286
+ });
287
+ }
288
+ });
289
+ this.tickled = new TickledController(this, {
290
+ getItems: () => {
291
+ const visible = this._getVisibleItems();
292
+ this.#tickledItemsCache ??= visible.map((el) => ({
293
+ id: el,
294
+ enabled: !el.hasAttribute(`not-checked`)
295
+ }));
296
+ return this.#tickledItemsCache;
297
+ },
298
+ onTickle: (id) => {
299
+ if (id !== void 0) this._dispatch(`list-tickle`, { item: id });
300
+ },
301
+ onStateChange: () => this._syncItemStates(),
302
+ itemWrap: this.itemWrap,
303
+ hoverDisabled: () => this.#dragSelect.isDragging || this.#nativeDrag.isDragging
304
+ });
305
+ this.#nativeDragging = false;
306
+ this.#postDragSuppressTimer = 0;
307
+ this.#blankItemPressPending = false;
308
+ this.#clearBlankItemPress = () => {
309
+ this.#blankItemPressPending = false;
310
+ };
311
+ this.#nativeDrag = new ListNativeDragController(this, {
312
+ source: `detail-list`,
313
+ isEnabled: () => this.itemsDraggable && this.selectionMode !== `none` && this._getVisibleItems().length > 0,
314
+ resolveEventToKey: (target) => {
315
+ if (!(target instanceof Element)) return void 0;
316
+ const li = target.closest?.(`li`);
317
+ if (!li || !this.contains(li)) return void 0;
318
+ return keyForElement(li);
319
+ },
320
+ prepareSelection: (originKey) => this.#prepareNativeDrag(originKey),
321
+ getMetrics: () => ({}),
322
+ getRenderer: () => this.dragPreviewRenderer,
323
+ getDragPreviewSourceResolver: () => this.dragPreviewSource,
324
+ dispatch: (detail) => this._dispatchCancelable(`list-native-drag-start`, detail),
325
+ onActiveChange: (active, originKey) => {
326
+ this.#nativeDragging = active;
327
+ this.toggleAttribute(`data-native-dragging`, active);
328
+ for (const el of this.querySelectorAll(`li`)) el.toggleAttribute(`data-drag-origin`, active && originKey !== void 0 && keyForElement(el) === originKey);
329
+ if (active) {
330
+ const count = this.selection.selected.size;
331
+ this.setAttribute(`aria-label`, count <= 1 ? `Dragging 1 item` : `Dragging ${count} items`);
332
+ } else this.removeAttribute(`aria-label`);
333
+ this.requestUpdate();
334
+ },
335
+ onEnd: () => {
336
+ this.#nativeDragging = false;
337
+ this.toggleAttribute(`data-native-dragging`, false);
338
+ this.removeAttribute(`aria-label`);
339
+ for (const el of this.querySelectorAll(`li`)) el.removeAttribute(`data-drag-origin`);
340
+ this.#startPostDragSuppressTimer();
341
+ },
342
+ syncLightDomDraggable: () => this._syncNativeDraggable(),
343
+ shouldStart: () => !this.#blankItemPressPending
344
+ });
345
+ this.#dragSelect = new ListDragSelectController(this, {
346
+ getSurface: () => this.renderRoot.querySelector(`.container`),
347
+ isEnabled: () => this.selection.canDragSelect() && this._getVisibleItems().length > 0 && !this.hasAttribute(`disable-drag-select`),
348
+ hitTest: (rect) => this.#marqueeHitTest(rect),
349
+ getBaseSelection: () => this.selection.selected,
350
+ onPreview: (preview, rect) => {
351
+ this.#marqueeRect = rect;
352
+ for (const el of this._getVisibleItems()) el.toggleAttribute(`data-marquee`, preview.has(el));
353
+ this.requestUpdate();
354
+ },
355
+ onCommit: (finalSet) => {
356
+ this.selection.applyDragSelection(finalSet);
357
+ for (const el of this._getVisibleItems()) el.removeAttribute(`data-marquee`);
358
+ this.#marqueeRect = void 0;
359
+ this.requestUpdate();
360
+ },
361
+ autoScroll: (dx, _dy) => {
362
+ if (this._listEl) this._listEl.scrollLeft += dx;
363
+ },
364
+ shouldStart: (event) => {
365
+ if (this.#findGroupHeader(event.composedPath())) return false;
366
+ this.#blankItemPressPending = this.itemsDraggable && this.#isBlankItemPress(event);
367
+ return !this.itemsDraggable || this.#blankItemPressPending;
368
+ }
369
+ });
370
+ this.search = new ListSearchController(this, { adapter: createElementListSearchAdapter(this, {
371
+ itemSelector: ITEM_SELECTOR,
372
+ getLabel: (el) => el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``,
373
+ highlightKey: SEARCH_HIGHLIGHT_KEY
374
+ }) });
375
+ this.#onListScroll = () => {
376
+ if (this.#viewportScrollRaf !== 0) return;
377
+ this.#viewportScrollRaf = requestAnimationFrame(() => {
378
+ this.#viewportScrollRaf = 0;
379
+ this.#emitViewportChangeIfChanged();
380
+ });
381
+ };
382
+ this._onPointerOver = (e) => {
383
+ const item = this._findListItem(e.composedPath());
384
+ if (item) this.tickled.handlePointerEnter(item);
385
+ };
386
+ this._onPointerLeave = (e) => {
387
+ this.tickled.handlePointerLeave(e);
388
+ };
389
+ this._endKeyboardNav = () => {
390
+ this.removeAttribute(`data-keyboard-nav`);
391
+ };
392
+ this._onKeyDown = (e) => {
393
+ if (this.search.isOpen) return;
394
+ if ((e.ctrlKey || e.metaKey) && e.key === `a`) {
395
+ if (this.selection.canSelectAll()) {
396
+ e.preventDefault();
397
+ this.selection.selectAll();
398
+ }
399
+ return;
400
+ }
401
+ if (NAV_KEYS.has(e.key) && !this.hasAttribute(`data-keyboard-nav`)) this.setAttribute(`data-keyboard-nav`, ``);
402
+ switch (e.key) {
403
+ case `ArrowDown`:
404
+ e.preventDefault();
405
+ this.#navigateDown();
406
+ if (e.shiftKey && this.interactionMode === `vscode`) {
407
+ const cur = this.tickled.tickledId;
408
+ if (cur) this.selection.rangeTo(cur);
409
+ }
410
+ break;
411
+ case `ArrowUp`:
412
+ e.preventDefault();
413
+ this.#navigateUp();
414
+ if (e.shiftKey && this.interactionMode === `vscode`) {
415
+ const cur = this.tickled.tickledId;
416
+ if (cur) this.selection.rangeTo(cur);
417
+ }
418
+ break;
419
+ case `ArrowRight`:
420
+ e.preventDefault();
421
+ this.#navigateRight();
422
+ break;
423
+ case `ArrowLeft`:
424
+ e.preventDefault();
425
+ this.#navigateLeft();
426
+ break;
427
+ case `Home`:
428
+ e.preventDefault();
429
+ this.#navigateTo(this.#findFirstCheckable());
430
+ break;
431
+ case `End`:
432
+ e.preventDefault();
433
+ this.#navigateTo(this.#findLastCheckable());
434
+ break;
435
+ case `Enter`: {
436
+ const id = this.tickled.tickledId;
437
+ if (id) {
438
+ e.preventDefault();
439
+ this._dispatch(`list-item-click`, { item: id });
440
+ this._activateItem(id);
441
+ }
442
+ break;
443
+ }
444
+ case ` `: {
445
+ const id = this.tickled.tickledId;
446
+ if (id) {
447
+ e.preventDefault();
448
+ this.selection.handleSpace(id);
449
+ }
450
+ break;
451
+ }
452
+ case `Escape`: this.selection.clear();
453
+ }
454
+ };
455
+ this._onClick = (e) => {
456
+ if (this.#dragSelect.isDragging || this.#nativeDrag.isDragging || this.#postDragSuppressTimer !== 0) return;
457
+ const path = e.composedPath();
458
+ const header = this.#findGroupHeader(path);
459
+ if (header) {
460
+ this.toggleGroup(header.dataset.groupKey ?? ``);
461
+ return;
462
+ }
463
+ const item = this._findListItem(path);
464
+ if (!item) return;
465
+ this.focus();
466
+ this._dispatch(`list-item-click`, { item });
467
+ this.selection.handleClick(item, e);
468
+ };
469
+ this._onDblClick = (e) => {
470
+ const item = this._findListItem(e.composedPath());
471
+ if (item) this._activateItem(item);
472
+ };
473
+ this._onWheel = (e) => {
474
+ e.preventDefault();
475
+ const delta = Math.abs(e.deltaX) > Math.abs(e.deltaY) ? e.deltaX : e.deltaY;
476
+ this._listEl.scrollLeft += delta;
477
+ };
478
+ }
479
+ #itemData;
480
+ /** The `index` each object item was formatted with — reuse is only safe at the same index. */
481
+ #itemIndex;
482
+ /** Measured column grid (content coordinates). See `#ensureGeometry`. */
483
+ #geometry;
484
+ /** Layout may have changed since `#geometry` was measured. */
485
+ #geometryDirty;
486
+ #geometryScheduled;
487
+ /**
488
+ * Watches child / visibility / key changes so the item caches below can be
489
+ * validated synchronously (via `takeRecords()`) on every read — they are
490
+ * never stale, even within the same task as the mutation.
491
+ */
492
+ #cacheObserver;
493
+ #allItemsCache;
494
+ #visibleCache;
495
+ #visibleCachePredicate;
496
+ #tickledItemsCache;
497
+ #renderedKeysCache;
498
+ #keyIndexCache;
499
+ /** Item states as last written to the DOM, so `_syncItemStates` can apply just the diff. */
500
+ #appliedTickled;
501
+ #appliedSelected;
502
+ #appliedChecked;
503
+ #itemStatesFullSync;
504
+ #hasCheckboxes;
505
+ #draggableApplied;
506
+ #sortRaf;
507
+ #viewportScrollRaf;
508
+ /** Key sets as of the last viewport-change dispatch, for dedupe. */
509
+ #lastViewportRenderedKeys;
510
+ #lastViewportVisibleKeys;
511
+ #resizeObserver;
512
+ #mutationObserver;
513
+ /** Items that have already played (or been skipped for) their enter animation. */
514
+ #animatedItems;
515
+ #builtFromProvider;
516
+ /** Element → group key, for the selection controller. Empty while ungrouped. */
517
+ #groupIndex;
518
+ #groupList;
519
+ #headerEls;
520
+ /** Insertion sequence per item, so regrouping starts from the original order. */
521
+ #seq;
522
+ #seqNext;
523
+ #wasGrouped;
524
+ #groupsScheduled;
525
+ #groupByChanged;
526
+ #groupObserver;
527
+ #providerBuilt;
528
+ #data;
529
+ #nativeDragging;
530
+ #postDragSuppressTimer;
531
+ #blankItemPressPending;
532
+ #clearBlankItemPress;
533
+ #nativeDrag;
534
+ #prepareNativeDrag(originKey) {
535
+ const visible = this._getVisibleItems();
536
+ const originEl = visible.find((el) => keyForElement(el) === originKey);
537
+ if (!originEl || !this._isCheckable(originEl)) return void 0;
538
+ if (!this.selection.selected.has(originEl)) this.selection.replace(originEl);
539
+ const selected = [];
540
+ visible.forEach((el, index) => {
541
+ if (!this.selection.selected.has(el)) return;
542
+ const key = keyForElement(el) ?? originKey;
543
+ selected.push({
544
+ key,
545
+ index,
546
+ item: this.#itemData.get(el) ?? el,
547
+ element: el
548
+ });
549
+ });
550
+ const origin = selected.find((entry) => entry.key === originKey);
551
+ if (!origin) return void 0;
552
+ return {
553
+ origin,
554
+ selected
555
+ };
556
+ }
557
+ _syncNativeDraggable() {
558
+ if (!this.isConnected) return;
559
+ if (!this.itemsDraggable && !this.#draggableApplied) return;
560
+ this.#draggableApplied = this.itemsDraggable;
561
+ for (const el of this.querySelectorAll(`li`)) if (el instanceof HTMLElement) el.draggable = this.itemsDraggable && this._isCheckable(el);
562
+ }
563
+ #startPostDragSuppressTimer() {
564
+ if (this.#postDragSuppressTimer) clearTimeout(this.#postDragSuppressTimer);
565
+ this.#postDragSuppressTimer = window.setTimeout(() => {
566
+ this.#postDragSuppressTimer = 0;
567
+ }, 0);
568
+ }
569
+ /** Live drag-select preview rect (surface-relative); the covered items carry `data-marquee`. */
570
+ #marqueeRect;
571
+ #dragSelect;
572
+ /** A row's padding/empty area may start marquee; its actual content may drag out. */
573
+ #isBlankItemPress(event) {
574
+ const path = event.composedPath();
575
+ const item = this._findListItem(path);
576
+ if (!item) return false;
577
+ for (const node of path) {
578
+ if (node === item) break;
579
+ if (node instanceof Element) {
580
+ if (node.matches(`input, button, a, select, textarea, [contenteditable], .ixfx-list-checkbox, [role="checkbox"]`)) return false;
581
+ if (node.hasAttribute(`data-drag-select-blank`)) return true;
582
+ if (node.hasAttribute(`data-drag-select-content`)) return false;
583
+ }
584
+ }
585
+ return !Array.from(item.childNodes).some((node) => contentAtPoint(node, event.clientX, event.clientY));
586
+ }
587
+ /** Collect visible items whose surface-relative box intersects `rect`. */
588
+ #marqueeHitTest(rect) {
589
+ const surface = this.renderRoot.querySelector(`.container`);
590
+ if (!surface) return [];
591
+ const box = surface.getBoundingClientRect();
592
+ const rx0 = rect.x;
593
+ const ry0 = rect.y;
594
+ const rx1 = rect.x + rect.width;
595
+ const ry1 = rect.y + rect.height;
596
+ const out = [];
597
+ for (const el of this._getVisibleItems()) {
598
+ const r = el.getBoundingClientRect();
599
+ const left = r.left - box.left;
600
+ const top = r.top - box.top;
601
+ if (left <= rx1 && left + r.width >= rx0 && top <= ry1 && top + r.height >= ry0) out.push(el);
602
+ }
603
+ return out;
604
+ }
605
+ #marqueeStyle(rect) {
606
+ return `left:${rect.x}px;top:${rect.y}px;width:${Math.max(0, rect.width)}px;height:${Math.max(0, rect.height)}px`;
607
+ }
608
+ #searchRegistry;
609
+ #unregisterSearchCommands;
610
+ /** Command registry carrying this list's `list.search.*` commands. */
611
+ get registry() {
612
+ return this.#searchRegistry;
613
+ }
614
+ #effectiveSearchModes() {
615
+ const raw = this.searchModes?.trim();
616
+ if (raw === void 0) return [...LIST_SEARCH_MODES];
617
+ const requested = new Set(raw.split(/\s+/));
618
+ return LIST_SEARCH_MODES.filter((m) => requested.has(m));
619
+ }
620
+ #syncSearchCommands() {
621
+ if (!this.#searchRegistry) return;
622
+ this.search.setModes(this.#effectiveSearchModes());
623
+ this.#unregisterSearchCommands?.();
624
+ this.#unregisterSearchCommands = registerListSearchCommands(this.#searchRegistry, this.search);
625
+ }
626
+ connectedCallback() {
627
+ super.connectedCallback();
628
+ if (!this.hasAttribute(`tabindex`)) this.tabIndex = 0;
629
+ this.addEventListener(`pointerover`, this._onPointerOver);
630
+ this.addEventListener(`pointerleave`, this._onPointerLeave);
631
+ this.addEventListener(`keydown`, this._onKeyDown);
632
+ this.addEventListener(`pointermove`, this._endKeyboardNav);
633
+ this.addEventListener(`pointerdown`, this._endKeyboardNav);
634
+ this.addEventListener(`click`, this._onClick);
635
+ this.addEventListener(`dblclick`, this._onDblClick);
636
+ this.addEventListener(`wheel`, this._onWheel);
637
+ window.addEventListener(`pointerup`, this.#clearBlankItemPress);
638
+ window.addEventListener(`pointercancel`, this.#clearBlankItemPress);
639
+ this.#resizeObserver = new ResizeObserver(() => this.#scheduleGeometry());
640
+ this.#resizeObserver.observe(this);
641
+ this.#mutationObserver = new MutationObserver((records) => {
642
+ this.#processAddedChildren(records);
643
+ this.#animateChildMutations(records);
644
+ this.#scheduleGeometry();
645
+ this.#scheduleGroups();
646
+ });
647
+ this.#mutationObserver.observe(this, {
648
+ childList: true,
649
+ subtree: false,
650
+ attributeFilter: [`hidden`]
651
+ });
652
+ for (const el of this.querySelectorAll(ITEM_SELECTOR)) this.#animatedItems.add(el);
653
+ this.#cacheObserver = new MutationObserver((records) => {
654
+ if (records.some((r) => affectsItemCaches(this, r))) this.#dropItemCaches();
655
+ });
656
+ this.#cacheObserver.observe(this, {
657
+ childList: true,
658
+ subtree: true,
659
+ attributes: true,
660
+ attributeFilter: ITEM_CACHE_ATTRS
661
+ });
662
+ this.#dropItemCaches();
663
+ this.#searchRegistry ??= createCommandRegistry();
664
+ this.#syncSearchCommands();
665
+ this.#scheduleGroups();
666
+ }
667
+ /**
668
+ * Collapse-in items appended to the light DOM (however they got there). A bulk
669
+ * populate is left alone. Geometry is re-read once the animations settle so
670
+ * keyboard navigation lines back up with the final layout.
671
+ */
672
+ #animateChildMutations(records) {
673
+ if (!this.isConnected) return;
674
+ const fresh = [];
675
+ for (const record of records) for (const node of record.addedNodes) {
676
+ if (!(node instanceof HTMLElement) || node.parentElement !== this || node.localName !== `li`) continue;
677
+ if (this.#animatedItems.has(node)) continue;
678
+ this.#animatedItems.add(node);
679
+ fresh.push(node);
680
+ }
681
+ if (fresh.length === 0) return;
682
+ const total = this.#allItems().length;
683
+ if (fresh.length >= total) return;
684
+ for (const el of fresh) animateListItemIn(el);
685
+ window.setTimeout(() => this.#scheduleGeometry(), 230);
686
+ }
687
+ disconnectedCallback() {
688
+ super.disconnectedCallback();
689
+ this.removeEventListener(`pointerover`, this._onPointerOver);
690
+ this.removeEventListener(`pointerleave`, this._onPointerLeave);
691
+ this.removeEventListener(`keydown`, this._onKeyDown);
692
+ this.removeEventListener(`pointermove`, this._endKeyboardNav);
693
+ this.removeEventListener(`pointerdown`, this._endKeyboardNav);
694
+ this.removeEventListener(`click`, this._onClick);
695
+ this.removeEventListener(`dblclick`, this._onDblClick);
696
+ this.removeEventListener(`wheel`, this._onWheel);
697
+ window.removeEventListener(`pointerup`, this.#clearBlankItemPress);
698
+ window.removeEventListener(`pointercancel`, this.#clearBlankItemPress);
699
+ this.#blankItemPressPending = false;
700
+ cancelAnimationFrame(this.#sortRaf);
701
+ cancelAnimationFrame(this.#viewportScrollRaf);
702
+ this.#viewportScrollRaf = 0;
703
+ this.#resizeObserver?.disconnect();
704
+ this.#resizeObserver = void 0;
705
+ this.#mutationObserver?.disconnect();
706
+ this.#mutationObserver = void 0;
707
+ this.#groupObserver?.disconnect();
708
+ this.#groupObserver = void 0;
709
+ this.#cacheObserver?.disconnect();
710
+ this.#cacheObserver = void 0;
711
+ this.#dropItemCaches();
712
+ this.#unregisterSearchCommands?.();
713
+ this.#unregisterSearchCommands = void 0;
714
+ this.#searchRegistry = void 0;
715
+ if (this.#postDragSuppressTimer) {
716
+ clearTimeout(this.#postDragSuppressTimer);
717
+ this.#postDragSuppressTimer = 0;
718
+ }
719
+ }
720
+ firstUpdated() {
721
+ this.#processChildren();
722
+ if (this.filterPredicate) this.#applyFilterPredicate();
723
+ this.#recomputeGeometry();
724
+ this.#scheduleViewportChangeEmit();
725
+ this._syncNativeDraggable();
726
+ }
727
+ willUpdate(changed) {
728
+ super.willUpdate(changed);
729
+ if (changed.has(`groupBy`) && this.hasUpdated) {
730
+ if (this._collapsedGroups.size > 0) this._collapsedGroups = /* @__PURE__ */ new Set();
731
+ this.#groupByChanged = true;
732
+ }
733
+ }
734
+ updated(changed) {
735
+ super.updated(changed);
736
+ if (changed.has(`groupBy`) || changed.has(`groupLabel`) || changed.has(`_collapsedGroups`)) {
737
+ this.#scheduleGroups();
738
+ if (this.#groupByChanged) {
739
+ this.#groupByChanged = false;
740
+ this._dispatch(`list-group-change`, { groupBy: this.groupBy });
741
+ }
742
+ }
743
+ if (changed.has(`searchModes`)) this.#syncSearchCommands();
744
+ if (changed.has(`provider`)) {
745
+ this.#builtFromProvider = [];
746
+ this.#providerBuilt = 0;
747
+ this.#data.provider = this.provider;
748
+ queueMicrotask(() => {
749
+ if (this.provider) this.#data.request(void 0);
750
+ });
751
+ return;
752
+ }
753
+ const shapeChanged = changed.has(`displayProperty`) || changed.has(`formatter`) || changed.has(`iconProperty`) || changed.has(`iconColorProperty`) || changed.has(`textColorProperty`) || changed.has(`badgeProperty`) || changed.has(`keyProperty`);
754
+ if ((changed.has(`items`) || shapeChanged) && this.provider === void 0 && this.items.length > 0) this.#rebuildFromObjects(this.items, !shapeChanged);
755
+ this.#syncFromProvider();
756
+ if (changed.has(`sort`) || changed.has(`sortBy`) || changed.has(`sortComparator`)) this.#applySort();
757
+ if (changed.has(`filterPredicate`)) {
758
+ this.#applyFilterPredicate();
759
+ this.#scheduleGeometry();
760
+ this.#scheduleGroups();
761
+ }
762
+ if (changed.has(`interactionMode`)) {
763
+ this.selection.clearAnchor();
764
+ this.#itemStatesFullSync = true;
765
+ this._syncItemStates();
766
+ this._syncNativeDraggable();
767
+ } else if (changed.has(`checkboxRenderer`) && this.interactionMode === `checked`) this._syncCheckboxes();
768
+ if (changed.has(`itemsDraggable`)) {
769
+ this._syncNativeDraggable();
770
+ this.#nativeDrag.refreshDraggable();
771
+ }
772
+ }
773
+ /** The most recently selected item, or `undefined` if nothing is selected. */
774
+ get selectedItem() {
775
+ return [...this.selection.selected].at(-1);
776
+ }
777
+ /** The full current selection set (read-only). */
778
+ get selectedItems() {
779
+ return this.selection.selected;
780
+ }
781
+ /**
782
+ * Force an immediate geometry recompute. Called automatically on resize,
783
+ * DOM changes, sort/filter/search changes — only needed when layout is
784
+ * changed by outside forces (e.g. an external font load).
785
+ */
786
+ refreshLayout() {
787
+ this.#recomputeGeometry();
788
+ this.#scheduleViewportChangeEmit();
789
+ }
790
+ addItem(item, options) {
791
+ let el;
792
+ if (typeof item === `string`) {
793
+ const li = document.createElement(`li`);
794
+ this._setLabel(li, item);
795
+ el = li;
796
+ } else el = item;
797
+ if (options?.notChecked) el.setAttribute(`not-checked`, ``);
798
+ this.appendChild(el);
799
+ this.#ensureIcon(el);
800
+ if (this.sort !== `none`) this.#applySort();
801
+ if (this.interactionMode === `checked`) this._syncCheckboxes();
802
+ this.#scheduleGroups();
803
+ }
804
+ /**
805
+ * Collapse + fade `item` out, then detach it; the rows after it slide up to
806
+ * close the gap. Resolves once removed. Immediate under reduced motion.
807
+ */
808
+ removeItem(item) {
809
+ this.selection.deselect(item);
810
+ return animateListItemOut(item).then(() => {
811
+ item.remove();
812
+ this.#scheduleGroups();
813
+ if (this.interactionMode === `checked`) this._syncCheckboxes();
814
+ this.#scheduleGeometry();
815
+ });
816
+ }
817
+ /** {@link removeItem} for several items at once. */
818
+ removeItems(items) {
819
+ return Promise.all([...items].map((item) => this.removeItem(item))).then(() => void 0);
820
+ }
821
+ clearItems() {
822
+ this.replaceChildren();
823
+ this.#headerEls.clear();
824
+ this.#scheduleGroups();
825
+ this.selection.reset();
826
+ this.#builtFromProvider = [];
827
+ this.#providerBuilt = 0;
828
+ this._syncItemStates();
829
+ }
830
+ /**
831
+ * Replace the entire item list in one call.
832
+ * Equivalent to `clearItems()` followed by `addItem()` for each element.
833
+ */
834
+ setItems(items) {
835
+ this.clearItems();
836
+ for (const item of items) {
837
+ this.appendChild(item);
838
+ this.#ensureIcon(item);
839
+ }
840
+ if (this.sort !== `none`) this.#applySort();
841
+ if (this.interactionMode === `checked`) this._syncCheckboxes();
842
+ this.#scheduleGroups();
843
+ }
844
+ /**
845
+ * Get the source object behind an `<li>` built from an `items` array or
846
+ * provider. Returns `undefined` for declarative / programmatic elements.
847
+ */
848
+ getItemData(item) {
849
+ return this.#itemData.get(item);
850
+ }
851
+ /**
852
+ * Keys of items currently intersecting the viewport — for prioritising
853
+ * viewport-first work (e.g. metadata/thumbnail fetches). Items are laid
854
+ * out as a fixed-height, horizontally-scrolling column grid, so this is a
855
+ * horizontal intersection test against the scroll container; every row of
856
+ * an intersecting column counts as visible. Hidden (filtered-out) and
857
+ * keyless items are omitted. Mirrors `ixfx-grid-list.visibleKeys()` /
858
+ * `ixfx-data-grid.getVisibleRange()`.
859
+ */
860
+ visibleKeys() {
861
+ const container = this._listEl;
862
+ if (!container) return [];
863
+ const start = container.scrollLeft;
864
+ const end = start + container.getBoundingClientRect().width;
865
+ const out = [];
866
+ for (const el of itemsInSpanByOrder(this._getVisibleItems(), contentOrigin(container), start, end)) {
867
+ const key = keyForElement(el);
868
+ if (key !== void 0) out.push(key);
869
+ }
870
+ return out;
871
+ }
872
+ /**
873
+ * Dispatch a command to this list — used by `ixfx-grouped-item-lister` to
874
+ * forward commands. Routes through the command registry (so `list.search.*`
875
+ * and any other registered commands work).
876
+ */
877
+ dispatch(commandId, args) {
878
+ this.#searchRegistry?.invoke(commandId, args);
879
+ }
880
+ select(item) {
881
+ this.selection.replace(item);
882
+ }
883
+ selectMany(items) {
884
+ this.selection.selectMany(items);
885
+ }
886
+ deselect(item) {
887
+ this.selection.deselect(item);
888
+ }
889
+ clearSelection() {
890
+ this.selection.clear();
891
+ }
892
+ selectAll() {
893
+ if (this.selection.canSelectAll()) this.selection.selectAll();
894
+ }
895
+ /** The selection as stable `data-key` / `data-value` strings. */
896
+ get selectedKeys() {
897
+ return this.selection.selectedKeys;
898
+ }
899
+ /** The key of the item under the cursor, or `undefined`. */
900
+ get tickledKey() {
901
+ const id = this.tickled.tickledId;
902
+ return id ? keyForElement(id) : void 0;
903
+ }
904
+ /** Replace the selection with the items carrying these keys (unknown keys ignored). */
905
+ selectKeys(keys) {
906
+ this.selection.selectKeys(keys, (key) => this.#itemForKey(key));
907
+ }
908
+ /** Add items by key to the selection (multi-select modes only). */
909
+ addSelectedKeys(keys) {
910
+ this.selection.addKeys(keys, (key) => this.#itemForKey(key));
911
+ }
912
+ /** Remove items by key from the selection. */
913
+ removeSelectedKeys(keys) {
914
+ this.selection.removeKeys(keys, (key) => this.#itemForKey(key));
915
+ }
916
+ /** Move the cursor to the item with this key and scroll it into view. */
917
+ focusKey(key) {
918
+ const el = this.#itemForKey(key);
919
+ if (!el) return;
920
+ this.tickled.focus(el);
921
+ el.scrollIntoView({
922
+ block: `nearest`,
923
+ inline: `nearest`
924
+ });
925
+ }
926
+ #itemForKey(key) {
927
+ if (!this.#keyIndexCache) {
928
+ const index = /* @__PURE__ */ new Map();
929
+ for (const el of this.#allItems()) {
930
+ const k = keyForElement(el);
931
+ if (k !== void 0 && !index.has(k)) index.set(k, el);
932
+ }
933
+ this.#keyIndexCache = index;
934
+ }
935
+ return this.#keyIndexCache.get(key);
936
+ }
937
+ *selectedFilter(kind) {
938
+ const wanted = (el) => {
939
+ switch (kind) {
940
+ case `selected`: return el.hasAttribute(`data-selected`);
941
+ case `checked`: return el.hasAttribute(`data-checked`);
942
+ case `either`: return el.hasAttribute(`data-selected`) || el.hasAttribute(`data-checked`);
943
+ }
944
+ };
945
+ for (const el of this.querySelectorAll(ITEM_SELECTOR)) {
946
+ if (el.hidden) continue;
947
+ if (wanted(el)) yield el;
948
+ }
949
+ }
950
+ #rebuildFromObjects(items, reuseRows = false) {
951
+ const keyOf = (el) => el.getAttribute(`data-key`) ?? el.getAttribute(`data-search-label`) ?? null;
952
+ const previousKeys = new Set([...this.selection.selected].map(keyOf).filter((k) => k !== null));
953
+ const anchorKey = this.selection.anchor ? keyOf(this.selection.anchor) : null;
954
+ if (reuseRows) this.#reconcileObjectRows(items);
955
+ else this.replaceChildren(...items.map((item, index) => this.#createItemEl(item, index)));
956
+ if (previousKeys.size > 0 || anchorKey !== null) {
957
+ const next = [];
958
+ let anchor;
959
+ for (const el of this.querySelectorAll(ITEM_SELECTOR)) {
960
+ const key = keyOf(el);
961
+ if (key !== null && previousKeys.has(key)) next.push(el);
962
+ if (key !== null && key === anchorKey) anchor = el;
963
+ }
964
+ this.selection.seed(next, anchor);
965
+ }
966
+ if (this.sort !== `none`) this.#applySort();
967
+ if (this.interactionMode === `checked`) this._syncCheckboxes();
968
+ this._syncItemStates();
969
+ this.#scheduleGeometry();
970
+ this.#scheduleGroups();
971
+ }
972
+ /**
973
+ * Update the rows to match `items`, keeping the `<li>` of every object that
974
+ * is still present (by identity) instead of rebuilding all of them — so
975
+ * `items = [...items, next]` creates one row, not thousands. A row built by
976
+ * a `formatter` is only kept at the same index, since the formatter receives
977
+ * the index.
978
+ */
979
+ #reconcileObjectRows(items) {
980
+ const reusable = /* @__PURE__ */ new Map();
981
+ for (const li of this.#allItems()) if (this.#itemData.has(li) && !reusable.has(this.#itemData.get(li))) reusable.set(this.#itemData.get(li), li);
982
+ const next = items.map((item, index) => {
983
+ const li = reusable.get(item);
984
+ if (li && (!this.formatter || this.#itemIndex.get(li) === index)) {
985
+ reusable.delete(item);
986
+ return li;
987
+ }
988
+ return this.#createItemEl(item, index);
989
+ });
990
+ const keep = new Set(next);
991
+ for (const li of this.#allItems()) if (!keep.has(li)) li.remove();
992
+ if (this.#groupingActive()) {
993
+ for (const li of next) this.#seq.set(li, this.#seqNext++);
994
+ this.append(...next.filter((li) => li.parentElement !== this));
995
+ this.#scheduleGroups();
996
+ return;
997
+ }
998
+ syncChildOrder(this, next, (el) => el.localName === `li`);
999
+ }
1000
+ #createItemEl(item, index) {
1001
+ const li = document.createElement(`li`);
1002
+ this.#itemData.set(li, item);
1003
+ this.#itemIndex.set(li, index);
1004
+ const content = this.formatter ? this.formatter(item, index) : this.displayProperty ? this.#propertyValue(item, this.displayProperty) : item;
1005
+ const textColor = this.#colorValue(item, this.textColorProperty);
1006
+ const iconColor = this.#colorValue(item, this.iconColorProperty);
1007
+ if (iconColor !== void 0) li.setAttribute(`data-icon-color`, iconColor);
1008
+ if (typeof content === `string`) {
1009
+ const span = this._setLabel(li, content);
1010
+ if (textColor !== void 0) span.style.color = textColor;
1011
+ if (this.iconProperty) {
1012
+ const icon = this.#propertyValue(item, this.iconProperty);
1013
+ if (typeof icon === `string` && icon.length > 0) li.setAttribute(`data-icon`, icon);
1014
+ }
1015
+ } else if (content instanceof Node) {
1016
+ li.appendChild(content);
1017
+ if (textColor !== void 0) li.style.color = textColor;
1018
+ }
1019
+ if (this.keyProperty) {
1020
+ const key = this.#propertyValue(item, this.keyProperty);
1021
+ if (key !== void 0 && key !== null) li.setAttribute(`data-key`, String(key));
1022
+ }
1023
+ this.#ensureIcon(li);
1024
+ this.#ensureBadge(li, this.#propertyValue(item, this.badgeProperty));
1025
+ return li;
1026
+ }
1027
+ #propertyValue(item, prop) {
1028
+ if (typeof item !== `object` || item === null || !(prop in item)) return void 0;
1029
+ return item[prop];
1030
+ }
1031
+ /** A non-empty string from `item[prop]`, else `undefined`. */
1032
+ #colorValue(item, prop) {
1033
+ return prop ? appearanceColor(this.#propertyValue(item, prop)) : void 0;
1034
+ }
1035
+ #syncFromProvider() {
1036
+ if (this.provider === void 0) return;
1037
+ const items = this.#data.items;
1038
+ if (items === this.#builtFromProvider) return;
1039
+ let prefix = 0;
1040
+ while (prefix < this.#builtFromProvider.length && prefix < items.length && this.#builtFromProvider[prefix] === items[prefix]) prefix++;
1041
+ if (prefix === this.#builtFromProvider.length && prefix <= items.length) {
1042
+ const tail = items.slice(prefix);
1043
+ for (const item of tail) this.appendChild(this.#createItemEl(item, this.#providerBuilt++));
1044
+ if (this.sort !== `none`) {
1045
+ cancelAnimationFrame(this.#sortRaf);
1046
+ this.#sortRaf = requestAnimationFrame(() => this.#applySort());
1047
+ }
1048
+ if (this.interactionMode === `checked`) this._syncCheckboxes();
1049
+ this._syncItemStates();
1050
+ this.#scheduleGeometry();
1051
+ this.#scheduleGroups();
1052
+ } else {
1053
+ this.#providerBuilt = 0;
1054
+ this.#rebuildFromObjects(items);
1055
+ }
1056
+ this.#builtFromProvider = items;
1057
+ }
1058
+ #applyFilterPredicate() {
1059
+ if (!this.filterPredicate) {
1060
+ for (const el of this.querySelectorAll(ITEM_SELECTOR)) el.removeAttribute(`hidden`);
1061
+ return;
1062
+ }
1063
+ for (const el of this.querySelectorAll(ITEM_SELECTOR)) if (this.filterPredicate(el)) el.removeAttribute(`hidden`);
1064
+ else el.setAttribute(`hidden`, ``);
1065
+ }
1066
+ #applySort() {
1067
+ if (this.sort === `none`) return;
1068
+ const rows = [...this.querySelectorAll(ITEM_SELECTOR)];
1069
+ if (rows.length < 2) return;
1070
+ if (this.#groupingActive()) {
1071
+ this.#scheduleGroups();
1072
+ return;
1073
+ }
1074
+ syncChildOrder(this, this.#sortRows(rows), (el) => el.localName === `li`);
1075
+ this.#scheduleGeometry();
1076
+ this.#scheduleGroups();
1077
+ }
1078
+ /** Stable sort of `rows` by the current `sort` / `sortBy` / `sortComparator`. */
1079
+ #sortRows(rows) {
1080
+ const dir = this.sort === `descending` ? -1 : 1;
1081
+ const metas = rows.map((el) => ({
1082
+ element: el,
1083
+ data: this.getItemData(el),
1084
+ label: el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``
1085
+ }));
1086
+ const collator = new Intl.Collator(void 0, { numeric: true });
1087
+ let cmp;
1088
+ if (this.sortComparator) {
1089
+ const user = this.sortComparator;
1090
+ cmp = (a, b) => user(metas[a], metas[b]);
1091
+ } else if (this.sortBy) {
1092
+ const prop = this.sortBy;
1093
+ const keys = metas.map((m) => {
1094
+ const v = this.#propertyValue(m.data, prop);
1095
+ if (typeof v === `number`) return v;
1096
+ return v === void 0 || v === null ? `` : String(v);
1097
+ });
1098
+ cmp = (a, b) => {
1099
+ const ka = keys[a];
1100
+ const kb = keys[b];
1101
+ if (typeof ka === `number` && typeof kb === `number`) return ka - kb;
1102
+ return collator.compare(String(ka), String(kb));
1103
+ };
1104
+ } else cmp = (a, b) => collator.compare(metas[a].label, metas[b].label);
1105
+ const order = rows.map((_, i) => i);
1106
+ order.sort((a, b) => cmp(a, b) * dir || a - b);
1107
+ return order.map((i) => rows[i]);
1108
+ }
1109
+ #groupingActive() {
1110
+ return this.groupBy !== null && this.groupBy !== void 0 && this.groupBy !== ``;
1111
+ }
1112
+ /** Coalesce regrouping to one pass per microtask (streaming batches, search keystrokes). */
1113
+ #scheduleGroups() {
1114
+ if (this.#groupsScheduled || !this.#groupingActive() && !this.#wasGrouped) return;
1115
+ this.#groupsScheduled = true;
1116
+ queueMicrotask(() => {
1117
+ this.#groupsScheduled = false;
1118
+ this.#syncGroups();
1119
+ });
1120
+ }
1121
+ #groupValue(el, name) {
1122
+ return this.#propertyValue(this.getItemData(el), name) ?? el.getAttribute(`data-${name}`);
1123
+ }
1124
+ #groupKeyOf(el) {
1125
+ const by = this.groupBy;
1126
+ if (typeof by === `string`) return toGroupKey(this.#groupValue(el, by));
1127
+ return toGroupKey(by(this.getItemData(el), el));
1128
+ }
1129
+ #isItemVisible(el) {
1130
+ return !el.hidden && (!this.filterPredicate || this.filterPredicate(el));
1131
+ }
1132
+ #headerFor(key) {
1133
+ let header = this.#headerEls.get(key);
1134
+ if (!header) {
1135
+ header = document.createElement(`ixfx-list-group-header`);
1136
+ header.setAttribute(GROUP_HEADER_ATTR, ``);
1137
+ header.dataset.groupKey = key;
1138
+ this.#headerEls.set(key, header);
1139
+ }
1140
+ return header;
1141
+ }
1142
+ /**
1143
+ * Bring the light DOM into grouped order: original order → sort → partition
1144
+ * into contiguous groups, each preceded by a header row. Idempotent — it only
1145
+ * touches the DOM when the order actually changes, so it never loops through
1146
+ * the mutation observer. Ungrouping restores the original (sorted) order.
1147
+ */
1148
+ #syncGroups() {
1149
+ const active = this.#groupingActive();
1150
+ if (!active && !this.#wasGrouped) return;
1151
+ const items = this.#allItems();
1152
+ for (const el of items) if (!this.#seq.has(el)) this.#seq.set(el, this.#seqNext++);
1153
+ let ordered = items.toSorted((a, b) => this.#seq.get(a) - this.#seq.get(b));
1154
+ if (this.sort !== `none` && ordered.length > 1) ordered = this.#sortRows(ordered);
1155
+ const nodes = [];
1156
+ const info = [];
1157
+ const index = /* @__PURE__ */ new Map();
1158
+ const liveHeaders = /* @__PURE__ */ new Set();
1159
+ if (active) {
1160
+ const label = this.groupLabel;
1161
+ const parts = partitionByGroup(ordered, (el) => this.#groupKeyOf(el), label ? (key, els) => label(key, els.map((el) => this.getItemData(el) ?? el)) : void 0);
1162
+ for (const part of parts) {
1163
+ const count = part.items.filter((el) => this.#isItemVisible(el)).length;
1164
+ const collapsed = count > 0 && this._collapsedGroups.has(part.key);
1165
+ if (count > 0) {
1166
+ const header = this.#headerFor(part.key);
1167
+ header.label = part.label;
1168
+ header.count = count;
1169
+ header.collapsed = collapsed;
1170
+ liveHeaders.add(part.key);
1171
+ nodes.push(header);
1172
+ info.push({
1173
+ key: part.key,
1174
+ label: part.label,
1175
+ count,
1176
+ collapsed: this._collapsedGroups.has(part.key)
1177
+ });
1178
+ }
1179
+ for (const el of part.items) {
1180
+ index.set(el, part.key);
1181
+ el.toggleAttribute(GROUP_COLLAPSED_ATTR, collapsed);
1182
+ nodes.push(el);
1183
+ }
1184
+ }
1185
+ } else {
1186
+ for (const el of ordered) el.removeAttribute(GROUP_COLLAPSED_ATTR);
1187
+ nodes.push(...ordered);
1188
+ }
1189
+ for (const [key, header] of this.#headerEls) if (!liveHeaders.has(key)) {
1190
+ header.remove();
1191
+ this.#headerEls.delete(key);
1192
+ }
1193
+ this.#groupIndex = index;
1194
+ this.#groupList = info;
1195
+ this.#wasGrouped = active;
1196
+ syncChildOrder(this, nodes, (c) => c.localName === `li` || c.hasAttribute(GROUP_HEADER_ATTR));
1197
+ if (this.tickled.tickledId?.hasAttribute(GROUP_COLLAPSED_ATTR)) this.tickled.focus(void 0);
1198
+ this.#syncGroupObserver(active);
1199
+ this.#scheduleGeometry();
1200
+ }
1201
+ /** Filter-search hides rows via `hidden` on the `<li>`s — regroup when that changes. */
1202
+ #syncGroupObserver(active) {
1203
+ if (!active) {
1204
+ this.#groupObserver?.disconnect();
1205
+ this.#groupObserver = void 0;
1206
+ return;
1207
+ }
1208
+ if (this.#groupObserver || !this.isConnected) return;
1209
+ this.#groupObserver = new MutationObserver(() => this.#scheduleGroups());
1210
+ this.#groupObserver.observe(this, {
1211
+ subtree: true,
1212
+ attributes: true,
1213
+ attributeFilter: [`hidden`]
1214
+ });
1215
+ }
1216
+ /** Groups in display order (empty when ungrouped). */
1217
+ get groups() {
1218
+ return this.#groupList;
1219
+ }
1220
+ /** Collapse or expand one group. Fires `list-group-toggle` when the state changes. */
1221
+ setGroupCollapsed(groupKey, collapsed) {
1222
+ if (this._collapsedGroups.has(groupKey) === collapsed) return;
1223
+ const next = new Set(this._collapsedGroups);
1224
+ if (collapsed) next.add(groupKey);
1225
+ else next.delete(groupKey);
1226
+ this._collapsedGroups = next;
1227
+ this._dispatch(`list-group-toggle`, {
1228
+ groupKey,
1229
+ collapsed
1230
+ });
1231
+ }
1232
+ toggleGroup(groupKey) {
1233
+ this.setGroupCollapsed(groupKey, !this._collapsedGroups.has(groupKey));
1234
+ }
1235
+ collapseAllGroups() {
1236
+ for (const g of this.#groupList) this.setGroupCollapsed(g.key, true);
1237
+ }
1238
+ expandAllGroups() {
1239
+ for (const key of [...this._collapsedGroups]) this.setGroupCollapsed(key, false);
1240
+ }
1241
+ #findGroupHeader(path) {
1242
+ for (const target of path) {
1243
+ if (target === this) break;
1244
+ if (target instanceof HTMLElement && target.parentElement === this && target.hasAttribute(GROUP_HEADER_ATTR)) return target;
1245
+ }
1246
+ }
1247
+ /**
1248
+ * Mark the layout dirty and, next frame, check for a viewport change.
1249
+ * Geometry moves (resize, filter, sort, DOM changes) can change the visible
1250
+ * set without a scroll, so every layout-changing path comes through here.
1251
+ * The grid itself is re-measured lazily, by whoever needs it next.
1252
+ */
1253
+ #scheduleGeometry() {
1254
+ this.#geometryDirty = true;
1255
+ if (this.#geometryScheduled) return;
1256
+ this.#geometryScheduled = true;
1257
+ requestAnimationFrame(() => {
1258
+ this.#geometryScheduled = false;
1259
+ this.#emitViewportChangeIfChanged();
1260
+ });
1261
+ }
1262
+ /** Re-measure the column grid now (one layout read per visible item). */
1263
+ #recomputeGeometry() {
1264
+ this.#geometryDirty = false;
1265
+ this.#geometry = this.isConnected && this._listEl ? measureGeometry(this._getVisibleItems(), contentOrigin(this._listEl)) : EMPTY_GEOMETRY;
1266
+ }
1267
+ /**
1268
+ * The column grid, re-measured only if it's known dirty or a few probe items
1269
+ * have moved (catches layout changes nothing told us about, e.g. a CSS
1270
+ * variable change). Cheap enough to call per keypress / scroll frame.
1271
+ */
1272
+ #ensureGeometry() {
1273
+ const visible = this._getVisibleItems();
1274
+ if (this.#geometryDirty || !this._listEl) {
1275
+ this.#recomputeGeometry();
1276
+ return this.#geometry;
1277
+ }
1278
+ const cursor = this.tickled.tickledId;
1279
+ const probes = [
1280
+ visible[0],
1281
+ cursor && this.#geometry.positions.has(cursor) ? cursor : void 0,
1282
+ visible.at(-1)
1283
+ ];
1284
+ if (!geometryMatches(this.#geometry, probes, contentOrigin(this._listEl))) this.#recomputeGeometry();
1285
+ return this.#geometry;
1286
+ }
1287
+ #positionOf(el) {
1288
+ let pos = this.#geometry.positions.get(el);
1289
+ if (pos === void 0) {
1290
+ this.#recomputeGeometry();
1291
+ pos = this.#geometry.positions.get(el);
1292
+ }
1293
+ return pos;
1294
+ }
1295
+ #column(col) {
1296
+ return this.#geometry.columns[col]?.items;
1297
+ }
1298
+ #itemAt(col, row) {
1299
+ return this.#column(col)?.[row];
1300
+ }
1301
+ #gridSize() {
1302
+ const { columns } = this.#ensureGeometry();
1303
+ return {
1304
+ cols: columns.length,
1305
+ rows: Math.max(0, ...columns.map((c) => c.items.length))
1306
+ };
1307
+ }
1308
+ #navigateTo(target) {
1309
+ if (!target) return;
1310
+ this.tickled.handleKeyboardNavigation(target);
1311
+ this.tickled.tickledId?.scrollIntoView({
1312
+ block: `nearest`,
1313
+ inline: `nearest`
1314
+ });
1315
+ }
1316
+ #findNextCheckable(fromCol, fromRow, deltaCol, deltaRow) {
1317
+ const { cols } = this.#gridSize();
1318
+ const maxIterations = this._getVisibleItems().length + 1;
1319
+ let col = deltaCol !== 0 ? fromCol + deltaCol : fromCol;
1320
+ let row = deltaRow !== 0 ? fromRow + deltaRow : fromRow;
1321
+ let wrappedColumns = false;
1322
+ for (let iterations = 0; iterations < maxIterations; iterations++) {
1323
+ const colLength = this.#column(col)?.length ?? 0;
1324
+ if (row < 0 || row >= colLength) {
1325
+ if (deltaCol !== 0 || wrappedColumns) {
1326
+ if (this.itemWrap === `wrap`) {
1327
+ if (deltaCol < 0) col = cols - 1;
1328
+ else if (deltaCol > 0) col = 0;
1329
+ wrappedColumns = true;
1330
+ } else return;
1331
+ }
1332
+ if (deltaRow > 0) {
1333
+ if (col + 1 < cols) {
1334
+ col = col + 1;
1335
+ row = 0;
1336
+ wrappedColumns = true;
1337
+ } else if (this.itemWrap === `wrap`) {
1338
+ col = 0;
1339
+ row = 0;
1340
+ wrappedColumns = true;
1341
+ } else return;
1342
+ } else if (deltaRow < 0) {
1343
+ if (col - 1 >= 0) {
1344
+ col = col - 1;
1345
+ row = (this.#column(col)?.length ?? 1) - 1;
1346
+ wrappedColumns = true;
1347
+ } else if (this.itemWrap === `wrap`) {
1348
+ col = cols - 1;
1349
+ row = (this.#column(col)?.length ?? 1) - 1;
1350
+ wrappedColumns = true;
1351
+ } else return;
1352
+ } else if (deltaCol !== 0) {
1353
+ const targetColLength = this.#column(col)?.length ?? 0;
1354
+ if (row >= targetColLength && targetColLength > 0) row = targetColLength - 1;
1355
+ }
1356
+ if (col === fromCol && row === fromRow + deltaRow) return;
1357
+ continue;
1358
+ }
1359
+ const item = this.#itemAt(col, row);
1360
+ if (item && this._isCheckable(item)) return item;
1361
+ row += deltaRow;
1362
+ }
1363
+ }
1364
+ #findFirstCheckable() {
1365
+ return this._getVisibleItems().find((item) => this._isCheckable(item));
1366
+ }
1367
+ #findLastCheckable() {
1368
+ const visibleItems = this._getVisibleItems();
1369
+ for (let i = visibleItems.length - 1; i >= 0; i--) if (this._isCheckable(visibleItems[i])) return visibleItems[i];
1370
+ }
1371
+ #navigateDown() {
1372
+ this.#ensureGeometry();
1373
+ const cur = this.tickled.tickledId ? this.#positionOf(this.tickled.tickledId) : void 0;
1374
+ if (!cur) {
1375
+ this.#navigateTo(this.#findFirstCheckable());
1376
+ return;
1377
+ }
1378
+ const next = this.#findNextCheckable(cur.col, cur.row, 0, 1);
1379
+ if (next) this.#navigateTo(next);
1380
+ }
1381
+ #navigateUp() {
1382
+ this.#ensureGeometry();
1383
+ const cur = this.tickled.tickledId ? this.#positionOf(this.tickled.tickledId) : void 0;
1384
+ if (!cur) {
1385
+ this.#navigateTo(this.#findLastCheckable());
1386
+ return;
1387
+ }
1388
+ const next = this.#findNextCheckable(cur.col, cur.row, 0, -1);
1389
+ if (next) this.#navigateTo(next);
1390
+ }
1391
+ /**
1392
+ * Left/Right target. Ungrouped lists keep the row-index rule. Grouped lists
1393
+ * have headers at arbitrary heights, so an item's row index no longer equals
1394
+ * its visual row — pick the item in the neighbouring column whose top is
1395
+ * nearest the cursor's (ties go to the upper one).
1396
+ */
1397
+ #horizontalTarget(from, dir) {
1398
+ if (this.#groupList.length === 0) return this.#findNextCheckable(from.col, from.row, dir, 0);
1399
+ const column = this.#column(from.col + dir);
1400
+ const current = this.tickled.tickledId;
1401
+ const top = current ? this.#geometry.tops.get(current) : void 0;
1402
+ if (!column || top === void 0) return void 0;
1403
+ let best;
1404
+ let bestDelta = Number.POSITIVE_INFINITY;
1405
+ for (const el of column) {
1406
+ if (!this._isCheckable(el)) continue;
1407
+ const delta = Math.abs((this.#geometry.tops.get(el) ?? Number.POSITIVE_INFINITY) - top);
1408
+ if (delta < bestDelta) {
1409
+ best = el;
1410
+ bestDelta = delta;
1411
+ }
1412
+ }
1413
+ return best;
1414
+ }
1415
+ #navigateRight() {
1416
+ this.#ensureGeometry();
1417
+ const cur = this.tickled.tickledId ? this.#positionOf(this.tickled.tickledId) : void 0;
1418
+ if (!cur) {
1419
+ this.#navigateTo(this.#findFirstCheckable());
1420
+ return;
1421
+ }
1422
+ const next = this.#horizontalTarget(cur, 1);
1423
+ if (next) this.#navigateTo(next);
1424
+ else if (this.itemWrap === `wrap`) this.#navigateTo(this.#findFirstCheckable());
1425
+ }
1426
+ #navigateLeft() {
1427
+ this.#ensureGeometry();
1428
+ const cur = this.tickled.tickledId ? this.#positionOf(this.tickled.tickledId) : void 0;
1429
+ if (!cur) {
1430
+ this.#navigateTo(this.#findFirstCheckable());
1431
+ return;
1432
+ }
1433
+ const next = this.#horizontalTarget(cur, -1);
1434
+ if (next) this.#navigateTo(next);
1435
+ else if (this.itemWrap === `wrap`) this.#navigateTo(this.#findLastCheckable());
1436
+ }
1437
+ /**
1438
+ * Visible items in DOM order. Cached — every per-hover / per-frame path
1439
+ * reads it — and validated on each call, so it is never stale.
1440
+ */
1441
+ _getVisibleItems() {
1442
+ this.#validateItemCaches();
1443
+ if (this.#visibleCachePredicate !== this.filterPredicate) {
1444
+ this.#dropVisibleCaches();
1445
+ this.#visibleCachePredicate = this.filterPredicate;
1446
+ }
1447
+ this.#visibleCache ??= this.#allItems().filter((el) => {
1448
+ if (el.hidden) return false;
1449
+ if (el.hasAttribute(GROUP_COLLAPSED_ATTR)) return false;
1450
+ if (this.filterPredicate && !this.filterPredicate(el)) return false;
1451
+ return true;
1452
+ });
1453
+ return this.#visibleCache;
1454
+ }
1455
+ /** Every item (`<li>` child), in DOM order. Cached like `_getVisibleItems`. */
1456
+ #allItems() {
1457
+ this.#validateItemCaches();
1458
+ this.#allItemsCache ??= [...this.querySelectorAll(ITEM_SELECTOR)];
1459
+ return this.#allItemsCache;
1460
+ }
1461
+ /** Drop the item caches if anything relevant changed since they were built. */
1462
+ #validateItemCaches() {
1463
+ if (!this.#cacheObserver) {
1464
+ this.#dropItemCaches();
1465
+ return;
1466
+ }
1467
+ if (this.#cacheObserver.takeRecords().some((r) => affectsItemCaches(this, r))) this.#dropItemCaches();
1468
+ }
1469
+ #dropItemCaches() {
1470
+ this.#allItemsCache = void 0;
1471
+ this.#keyIndexCache = void 0;
1472
+ this.#itemStatesFullSync = true;
1473
+ this.#dropVisibleCaches();
1474
+ }
1475
+ #dropVisibleCaches() {
1476
+ this.#visibleCache = void 0;
1477
+ this.#tickledItemsCache = void 0;
1478
+ this.#renderedKeysCache = void 0;
1479
+ this.#geometryDirty = true;
1480
+ }
1481
+ /**
1482
+ * Set the column width to fit the widest visible item.
1483
+ *
1484
+ * This is opt-in; detail lists continue to use `--detail-list-column-width`
1485
+ * (or its 220px fallback) until this method is called. The measured value is
1486
+ * set directly on this element, so it takes precedence over an inherited
1487
+ * value. Returns the applied width in pixels, or 0 when there are no items.
1488
+ */
1489
+ async fitColumnWidth() {
1490
+ await this.updateComplete;
1491
+ const items = this._getVisibleItems();
1492
+ if (items.length === 0) return 0;
1493
+ const previousWidth = this.style.getPropertyValue(`--detail-list-column-width`);
1494
+ const previousPriority = this.style.getPropertyPriority(`--detail-list-column-width`);
1495
+ this.style.setProperty(`--detail-list-column-width`, `max-content`);
1496
+ const width = Math.ceil(Math.max(...items.map((item) => item.getBoundingClientRect().width)));
1497
+ if (previousWidth) this.style.setProperty(`--detail-list-column-width`, previousWidth, previousPriority);
1498
+ else this.style.removeProperty(`--detail-list-column-width`);
1499
+ this.style.setProperty(`--detail-list-column-width`, `${width}px`);
1500
+ this.#scheduleGeometry();
1501
+ return width;
1502
+ }
1503
+ _isCheckable(item) {
1504
+ if (this.interactionMode !== `checked`) return true;
1505
+ return !item.hasAttribute(`not-checked`);
1506
+ }
1507
+ /** Appends the caption span; returns it so callers can style it per item. */
1508
+ _setLabel(li, label) {
1509
+ const span = document.createElement(`span`);
1510
+ span.style.cssText = LABEL_SPAN_CSS;
1511
+ span.textContent = label;
1512
+ li.appendChild(span);
1513
+ li.setAttribute(`data-search-label`, label);
1514
+ return span;
1515
+ }
1516
+ /**
1517
+ * Inject a leading `<ixfx-icon>` into an item carrying `data-icon`.
1518
+ * Idempotent — skips when the item already starts with an icon.
1519
+ */
1520
+ #ensureIcon(li) {
1521
+ const name = li.getAttribute(`data-icon`);
1522
+ if (!name) return;
1523
+ if (li.firstElementChild?.tagName === `IXFX-ICON`) return;
1524
+ const icon = document.createElement(`ixfx-icon`);
1525
+ icon.setAttribute(`name`, name);
1526
+ icon.style.marginInlineEnd = `var(--detail-list-icon-gap, 6px)`;
1527
+ icon.style.flexShrink = `0`;
1528
+ const color = li.getAttribute(`data-icon-color`);
1529
+ if (color) icon.style.color = color;
1530
+ li.prepend(icon);
1531
+ }
1532
+ /**
1533
+ * Append a right-justified Finder-style badge. `badge` may be a DOM `Node`
1534
+ * or anything Lit can render (typically a `TemplateResult`). Idempotent —
1535
+ * a second call replaces the existing badge.
1536
+ */
1537
+ #ensureBadge(li, badge) {
1538
+ if (appearanceAbsent(badge)) return;
1539
+ let holder = li.querySelector(`:scope > .item-badge`);
1540
+ if (!holder) {
1541
+ holder = document.createElement(`span`);
1542
+ holder.className = `item-badge`;
1543
+ holder.style.cssText = BADGE_EL_CSS;
1544
+ li.appendChild(holder);
1545
+ }
1546
+ holder.replaceChildren();
1547
+ if (badge instanceof Node) holder.appendChild(badge);
1548
+ else render(badge, holder);
1549
+ }
1550
+ /** Scan light-DOM children for unprocessed `data-icon` items. */
1551
+ #processChildren() {
1552
+ for (const el of this.#allItems()) this.#ensureIcon(el);
1553
+ }
1554
+ /** `#processChildren` for just the items a batch of mutations added. */
1555
+ #processAddedChildren(records) {
1556
+ for (const record of records) for (const node of record.addedNodes) if (node instanceof HTMLLIElement && node.parentElement === this) this.#ensureIcon(node);
1557
+ }
1558
+ /**
1559
+ * Reflect cursor / selection / checked state onto the items. After the item
1560
+ * set changes (or the interaction mode does) every item is visited; otherwise
1561
+ * only the items whose state changed are touched, so hovering is O(1).
1562
+ */
1563
+ _syncItemStates() {
1564
+ const selected = this.selection.selected;
1565
+ const checked = this.interactionMode === `checked` ? new Set([...selected].filter((el) => !el.hasAttribute(`not-checked`))) : /* @__PURE__ */ new Set();
1566
+ const tickled = this.tickled.tickledId;
1567
+ const selectionChanged = this.#itemStatesFullSync || !sameMembers(this.#appliedSelected, selected) || !sameMembers(this.#appliedChecked, checked);
1568
+ if (this.#itemStatesFullSync) {
1569
+ for (const el of this.#allItems()) {
1570
+ el.toggleAttribute(`data-tickled`, el === tickled);
1571
+ el.toggleAttribute(`data-selected`, selected.has(el));
1572
+ el.toggleAttribute(`data-checked`, checked.has(el));
1573
+ }
1574
+ this.#itemStatesFullSync = false;
1575
+ } else {
1576
+ if (tickled !== this.#appliedTickled) {
1577
+ this.#appliedTickled?.removeAttribute(`data-tickled`);
1578
+ tickled?.setAttribute(`data-tickled`, ``);
1579
+ }
1580
+ applySetDiff(this.#appliedSelected, selected, `data-selected`);
1581
+ applySetDiff(this.#appliedChecked, checked, `data-checked`);
1582
+ }
1583
+ this.#appliedTickled = tickled;
1584
+ this.#appliedSelected = new Set(selected);
1585
+ this.#appliedChecked = checked;
1586
+ if (this.dataset.inputMode !== this.tickled.inputMode) this.dataset.inputMode = this.tickled.inputMode;
1587
+ if (selectionChanged) this._syncCheckboxes();
1588
+ }
1589
+ _syncCheckboxes() {
1590
+ const inCheckedMode = this.interactionMode === `checked`;
1591
+ const wantsCustom = typeof this.checkboxRenderer === `function`;
1592
+ if (!inCheckedMode && !this.#hasCheckboxes) return;
1593
+ this.#hasCheckboxes = inCheckedMode;
1594
+ for (const el of this.querySelectorAll(`li`)) {
1595
+ const existing = el.querySelector(`.ixfx-list-checkbox`);
1596
+ if (!inCheckedMode) {
1597
+ if (existing) existing.remove();
1598
+ continue;
1599
+ }
1600
+ if (el.hasAttribute(`not-checked`)) {
1601
+ if (existing) existing.remove();
1602
+ continue;
1603
+ }
1604
+ const existingIsCustom = existing?.classList.contains(`ixfx-list-checkbox-custom`) ?? false;
1605
+ if (existing && existingIsCustom !== wantsCustom) existing.remove();
1606
+ let cb = el.querySelector(`.ixfx-list-checkbox`);
1607
+ if (!cb) {
1608
+ if (wantsCustom) {
1609
+ const opts = this.checkboxRenderer;
1610
+ const margin = opts.margin ?? `0`;
1611
+ const width = opts.width ?? `24px`;
1612
+ cb = this._createCustomCheckbox(el, margin, width);
1613
+ } else cb = this._createNativeCheckboxCell(el);
1614
+ el.prepend(cb);
1615
+ }
1616
+ const checked = this.selection.selected.has(el);
1617
+ if (wantsCustom) {
1618
+ cb.setAttribute(`aria-checked`, String(checked));
1619
+ cb.toggleAttribute(`data-checked`, checked);
1620
+ if (this.checkboxRenderer) {
1621
+ const renderer = typeof this.checkboxRenderer === `function` ? this.checkboxRenderer : this.checkboxRenderer.renderer;
1622
+ render(renderer(checked), cb);
1623
+ }
1624
+ } else {
1625
+ const input = cb.querySelector(`input.ixfx-list-checkbox`);
1626
+ if (input) input.checked = checked;
1627
+ }
1628
+ }
1629
+ }
1630
+ /**
1631
+ * Native checkbox cell: a span that stretches to the row's full cross-axis
1632
+ * (so the entire column height is a click target) with a real `<input>`
1633
+ * centred inside. The span owns the click; the input is purely visual and
1634
+ * has `pointer-events:none` so a single click produces one toggle.
1635
+ */
1636
+ _createNativeCheckboxCell(el) {
1637
+ const cell = document.createElement(`span`);
1638
+ cell.className = `ixfx-list-checkbox ixfx-list-checkbox-native`;
1639
+ cell.style.cssText = `display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;align-self:stretch;width:18px;margin:0 6px 0 0;cursor:pointer;pointer-events:auto`;
1640
+ cell.tabIndex = -1;
1641
+ cell.setAttribute(`role`, `checkbox`);
1642
+ cell.setAttribute(`part`, `checkbox`);
1643
+ cell.addEventListener(`click`, (e) => {
1644
+ e.stopPropagation();
1645
+ this.selection.toggle(el);
1646
+ });
1647
+ const input = document.createElement(`input`);
1648
+ input.type = `checkbox`;
1649
+ input.className = `ixfx-list-checkbox`;
1650
+ input.style.cssText = `pointer-events:none;cursor:pointer;margin:0`;
1651
+ cell.appendChild(input);
1652
+ return cell;
1653
+ }
1654
+ /** Custom checkbox cell: same shape as the native cell; renderer content inside. */
1655
+ _createCustomCheckbox(el, margin, width) {
1656
+ const span = document.createElement(`span`);
1657
+ span.className = `ixfx-list-checkbox ixfx-list-checkbox-custom`;
1658
+ span.style.cssText = `display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;align-self:stretch;width:${width};margin:${margin};cursor:pointer;pointer-events:auto`;
1659
+ span.tabIndex = -1;
1660
+ span.setAttribute(`role`, `checkbox`);
1661
+ span.setAttribute(`part`, `checkbox`);
1662
+ span.addEventListener(`click`, (e) => {
1663
+ e.stopPropagation();
1664
+ this.selection.toggle(el);
1665
+ });
1666
+ return span;
1667
+ }
1668
+ _findListItem(path) {
1669
+ for (const target of path) {
1670
+ if (!(target instanceof Element)) continue;
1671
+ if (target === this) break;
1672
+ if (target.parentElement === this && target.matches(`li`)) return target;
1673
+ }
1674
+ }
1675
+ _dispatch(name, detail) {
1676
+ this.dispatchEvent(new CustomEvent(name, {
1677
+ detail,
1678
+ bubbles: true,
1679
+ composed: true
1680
+ }));
1681
+ }
1682
+ _dispatchCancelable(name, detail) {
1683
+ return this.dispatchEvent(new CustomEvent(name, {
1684
+ detail,
1685
+ bubbles: true,
1686
+ composed: true,
1687
+ cancelable: true
1688
+ }));
1689
+ }
1690
+ /** Scroll on the `part="list"` element — rAF-coalesced viewport-change check. */
1691
+ #onListScroll;
1692
+ /** Coalesce non-scroll viewport checks to at most one per animation frame. */
1693
+ #scheduleViewportChangeEmit() {
1694
+ if (this.#viewportScrollRaf !== 0) return;
1695
+ this.#viewportScrollRaf = requestAnimationFrame(() => {
1696
+ this.#viewportScrollRaf = 0;
1697
+ this.#emitViewportChangeIfChanged();
1698
+ });
1699
+ }
1700
+ /**
1701
+ * Keys of everything currently rendered. The list has no virtual window —
1702
+ * every non-hidden item is in the DOM — so this is all keyed items, mirroring
1703
+ * `ixfx-vertical-list.renderedKeys()` in its non-virtualized mode.
1704
+ */
1705
+ #renderedKeys() {
1706
+ const visible = this._getVisibleItems();
1707
+ this.#renderedKeysCache ??= visible.flatMap((el) => {
1708
+ const key = keyForElement(el);
1709
+ return key === void 0 ? [] : [key];
1710
+ });
1711
+ return this.#renderedKeysCache;
1712
+ }
1713
+ /**
1714
+ * Dispatch `detail-list-viewport-change` with the current key sets — unless
1715
+ * neither set changed since the last dispatch, so coalesced frames and
1716
+ * no-op scrolls stay silent.
1717
+ */
1718
+ #emitViewportChangeIfChanged() {
1719
+ const renderedKeys = this.#renderedKeys();
1720
+ const visibleKeys = this.visibleKeys();
1721
+ if (sameKeys(this.#lastViewportRenderedKeys, renderedKeys) && sameKeys(this.#lastViewportVisibleKeys, visibleKeys)) return;
1722
+ this.#lastViewportRenderedKeys = renderedKeys;
1723
+ this.#lastViewportVisibleKeys = visibleKeys;
1724
+ this._dispatch(`detail-list-viewport-change`, {
1725
+ renderedKeys,
1726
+ visibleKeys
1727
+ });
1728
+ }
1729
+ _activateItem(item) {
1730
+ if (this.interactionMode !== `manual`) this.selection.replace(item);
1731
+ this._dispatch(`list-activate`, { item });
1732
+ }
1733
+ static {
1734
+ this.styles = [
1735
+ themeFallbacks,
1736
+ scrollFadeStyles(`x`),
1737
+ listSearchStyles,
1738
+ css`
1739
+ :host {
1740
+ display: block;
1741
+ position: relative;
1742
+ background: var(--detail-list-bg, var(--surface-3));
1743
+ color: var(--detail-list-text, var(--surface-3-text));
1744
+ border-radius: var(--detail-list-radius, var(--radius-s));
1745
+ border: 1px solid var(--detail-list-border, var(--border));
1746
+ width: 100%;
1747
+ height: 100%;
1748
+ overflow: hidden;
1749
+ box-sizing: border-box;
1750
+ outline: none;
1751
+ --_detail-list-item-padding:
1752
+ var(--ixfx-detail-list-item-padding, var(--detail-list-item-padding, 0 var(--space-xs, 3px)));
1753
+ }
1754
+
1755
+ :host(:focus-visible) {
1756
+ outline: 2px solid var(--accent);
1757
+ outline-offset: 2px;
1758
+ }
1759
+
1760
+ .container {
1761
+ position: relative;
1762
+ width: 100%;
1763
+ height: 100%;
1764
+ }
1765
+
1766
+ ul {
1767
+ list-style: none;
1768
+ margin: 0;
1769
+ padding: 0;
1770
+ /* keep the last column clear of the scrollbar / right edge */
1771
+ padding-inline-end: calc(var(--detail-list-column-gap, 8px) + 4px);
1772
+ width: 100%;
1773
+ height: 100%;
1774
+ display: flex;
1775
+ flex-flow: column wrap;
1776
+ align-content: flex-start;
1777
+ column-gap: var(--detail-list-column-gap, 8px);
1778
+ overflow-x: auto;
1779
+ overflow-y: hidden;
1780
+ box-sizing: border-box;
1781
+ /* Scrollbar theming: consumers override these CSS variables to control
1782
+ the native scrollbar (scrollbar-width, scrollbar-gutter, scrollbar-color). */
1783
+ scrollbar-width: var(--detail-list-scrollbar-width, auto);
1784
+ scrollbar-gutter: var(--detail-list-scrollbar-gutter, auto);
1785
+ scrollbar-color: var(--detail-list-scrollbar-color, auto);
1786
+ }
1787
+
1788
+ ::slotted([hidden]) {
1789
+ display: none !important;
1790
+ }
1791
+
1792
+ /* Rows of a collapsed group leave the flow; their header stays. */
1793
+ ::slotted([data-group-collapsed]) {
1794
+ display: none !important;
1795
+ }
1796
+
1797
+ /* Group headers are rows in the column flow, the same size as an item. */
1798
+ ::slotted(ixfx-list-group-header) {
1799
+ box-sizing: border-box;
1800
+ flex: none;
1801
+ width: var(--detail-list-column-width, 220px);
1802
+ height: var(--detail-list-item-height, 28px);
1803
+ --list-group-header-height: var(--detail-list-group-header-height, var(--detail-list-item-height, 28px));
1804
+ }
1805
+
1806
+ ::slotted(li) {
1807
+ box-sizing: border-box;
1808
+ display: flex;
1809
+ align-items: center;
1810
+ flex: none;
1811
+ /* Paint-isolates each row, so restyling one row (hover, selection)
1812
+ doesn't repaint the whole list: ~3.5x less render work per hover at
1813
+ 10k rows, for ~0.6ms more compositor commit per scroll frame. Paint
1814
+ only: layout/style containment add nothing here, and no size
1815
+ containment — fitColumnWidth() measures rows at max-content. */
1816
+ contain: paint;
1817
+ width: var(--detail-list-column-width, 220px);
1818
+ height: var(--detail-list-item-height, 28px);
1819
+ padding: var(--_detail-list-item-padding);
1820
+ cursor: pointer;
1821
+ user-select: none;
1822
+ transition: background var(--transition, 0.1s ease-out),
1823
+ outline-color var(--transition, 0.1s ease-out);
1824
+ outline: 1px solid transparent;
1825
+ }
1826
+
1827
+ /* While navigating by keyboard, key-repeat outpaces the 0.1s fade so the
1828
+ cursor smears across rows. Snap instead. */
1829
+ :host([data-keyboard-nav]) ::slotted(li) {
1830
+ transition: none;
1831
+ }
1832
+
1833
+ /* In checked mode, only the checkbox column is clickable; the row body
1834
+ gets a default cursor so it doesn't suggest otherwise. */
1835
+ :host([interaction-mode="checked"]) ::slotted(li) {
1836
+ cursor: default;
1837
+ }
1838
+
1839
+ ::slotted([data-tickled]) {
1840
+ background: var(--item-bg-tickled);
1841
+ outline: var(--item-border-tickled);
1842
+ }
1843
+
1844
+ ::slotted([data-selected]) {
1845
+ background: var(--item-bg-selected);
1846
+ color: var(--item-text-selected);
1847
+ outline: 1px solid transparent;
1848
+ }
1849
+
1850
+ ::slotted([data-selected][data-tickled]) {
1851
+ background: var(--item-bg-selected-tickled);
1852
+ outline: 2px dashed var(--surface-3);
1853
+ outline-offset: -2px;
1854
+ }
1855
+
1856
+ /* Drag-select (marquee) rectangle + covered-row preview.
1857
+ Style hooks: part="marquee", --detail-list-marquee-border,
1858
+ --detail-list-marquee-fill. */
1859
+ .marquee {
1860
+ position: absolute;
1861
+ pointer-events: none;
1862
+ z-index: 5;
1863
+ border: 1px solid var(--detail-list-marquee-border, var(--accent));
1864
+ background: var(--detail-list-marquee-fill, color-mix(in srgb, var(--accent) 15%, transparent));
1865
+ }
1866
+
1867
+ ::slotted([data-marquee]) {
1868
+ background: var(--item-bg-selected);
1869
+ color: var(--item-text-selected);
1870
+ }
1871
+
1872
+ ::slotted([data-drag-origin]) {
1873
+ opacity: 0.6;
1874
+ }
1875
+
1876
+ /* Per-item icon / badge (adapter mode) are styled inline by
1877
+ #ensureBadge — ::slotted() only reaches the host's direct children,
1878
+ and these spans live inside a slotted <li>. */
1879
+
1880
+ /* checked mode: the injected checkbox communicates selection — suppress bg */
1881
+ :host([interaction-mode="checked"]) ::slotted([data-selected]) {
1882
+ background: transparent;
1883
+ color: inherit;
1884
+ outline: 1px solid transparent;
1885
+ }
1886
+
1887
+ :host([interaction-mode="checked"]) ::slotted([data-selected][data-tickled]) {
1888
+ background: var(--item-bg-tickled);
1889
+ color: inherit;
1890
+ outline: var(--item-border-tickled);
1891
+ }
1892
+
1893
+ .loading {
1894
+ position: absolute;
1895
+ bottom: 8px;
1896
+ right: 8px;
1897
+ z-index: 10;
1898
+ background: var(--surface-4, #2a2a2a);
1899
+ border: 1px solid var(--border, #444);
1900
+ border-radius: var(--radius-m, 4px);
1901
+ padding: 2px 8px;
1902
+ font-size: var(--text-s, 11px);
1903
+ color: var(--surface-muted-text, #888);
1904
+ }
1905
+
1906
+ .loading[hidden] {
1907
+ display: none;
1908
+ }
1909
+ `
1910
+ ];
1911
+ }
1912
+ render() {
1913
+ const loading = this.#data.loading;
1914
+ const progress = this.#data.progress;
1915
+ return html`
1916
+ <div class="container">
1917
+ <ul part="list" class="scroll-fade-x" @scroll=${this.#onListScroll}>
1918
+ <slot></slot>
1919
+ </ul>
1920
+ ${this.#marqueeRect ? html`<div class="marquee" part="marquee" style=${this.#marqueeStyle(this.#marqueeRect)}></div>` : ``}
1921
+ <div class="loading" part="loading" ?hidden=${!loading}>
1922
+ ${loading ? progress !== void 0 ? `${Math.round(progress * 100)}%` : `loading…` : nothing}
1923
+ </div>
1924
+ ${this.search.renderField()}
1925
+ </div>
1926
+ `;
1927
+ }
1928
+ };
1929
+ __decorate([property({
1930
+ type: String,
1931
+ attribute: `selection-mode`
1932
+ })], DetailListElement.prototype, "selectionMode", void 0);
1933
+ __decorate([property({
1934
+ type: String,
1935
+ attribute: `interaction-mode`
1936
+ })], DetailListElement.prototype, "interactionMode", void 0);
1937
+ __decorate([property({ type: String })], DetailListElement.prototype, "sort", void 0);
1938
+ __decorate([property({
1939
+ type: String,
1940
+ attribute: `sort-by`
1941
+ })], DetailListElement.prototype, "sortBy", void 0);
1942
+ __decorate([property({ attribute: false })], DetailListElement.prototype, "sortComparator", void 0);
1943
+ __decorate([property({ attribute: false })], DetailListElement.prototype, "items", void 0);
1944
+ __decorate([property({ attribute: false })], DetailListElement.prototype, "provider", void 0);
1945
+ __decorate([property({
1946
+ type: String,
1947
+ attribute: `display-property`
1948
+ })], DetailListElement.prototype, "displayProperty", void 0);
1949
+ __decorate([property({ attribute: false })], DetailListElement.prototype, "formatter", void 0);
1950
+ __decorate([property({
1951
+ type: String,
1952
+ attribute: `icon-property`
1953
+ })], DetailListElement.prototype, "iconProperty", void 0);
1954
+ __decorate([property({
1955
+ type: String,
1956
+ attribute: `icon-color-property`
1957
+ })], DetailListElement.prototype, "iconColorProperty", void 0);
1958
+ __decorate([property({
1959
+ type: String,
1960
+ attribute: `text-color-property`
1961
+ })], DetailListElement.prototype, "textColorProperty", void 0);
1962
+ __decorate([property({
1963
+ type: String,
1964
+ attribute: `badge-property`
1965
+ })], DetailListElement.prototype, "badgeProperty", void 0);
1966
+ __decorate([property({
1967
+ type: String,
1968
+ attribute: `key-property`
1969
+ })], DetailListElement.prototype, "keyProperty", void 0);
1970
+ __decorate([property({ attribute: false })], DetailListElement.prototype, "filterPredicate", void 0);
1971
+ __decorate([property({
1972
+ type: String,
1973
+ attribute: `item-wrap`
1974
+ })], DetailListElement.prototype, "itemWrap", void 0);
1975
+ __decorate([property({ attribute: false })], DetailListElement.prototype, "checkboxRenderer", void 0);
1976
+ __decorate([property({
1977
+ type: String,
1978
+ attribute: `search-modes`,
1979
+ reflect: true
1980
+ })], DetailListElement.prototype, "searchModes", void 0);
1981
+ __decorate([property({
1982
+ type: Boolean,
1983
+ attribute: `items-draggable`,
1984
+ reflect: true
1985
+ })], DetailListElement.prototype, "itemsDraggable", void 0);
1986
+ __decorate([property({ attribute: false })], DetailListElement.prototype, "dragPreviewRenderer", void 0);
1987
+ __decorate([property({ attribute: false })], DetailListElement.prototype, "dragPreviewSource", void 0);
1988
+ __decorate([property({ attribute: `group-by` })], DetailListElement.prototype, "groupBy", void 0);
1989
+ __decorate([property({ attribute: false })], DetailListElement.prototype, "groupLabel", void 0);
1990
+ __decorate([property({
1991
+ type: Boolean,
1992
+ attribute: `cross-group-selection`,
1993
+ reflect: true
1994
+ })], DetailListElement.prototype, "crossGroupSelection", void 0);
1995
+ __decorate([state()], DetailListElement.prototype, "_collapsedGroups", void 0);
1996
+ __decorate([query(`ul`)], DetailListElement.prototype, "_listEl", void 0);
1997
+ DetailListElement = __decorate([safeCustomElement(`ixfx-detail-list`)], DetailListElement);
1998
+ //#endregion
1999
+ export { DetailListElement as t };
2000
+
2001
+ //# sourceMappingURL=detail-list-C96B5Txz.js.map