@ixfx/components 0.7.2 → 0.7.4

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 (82) hide show
  1. package/bundle/index.d.ts +805 -137
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +2168 -42
  4. package/bundle/index.js.map +1 -1
  5. package/dist/ac-text.js +1 -1
  6. package/dist/ac-token.js +1 -1
  7. package/dist/crumbs.js +3 -3
  8. package/dist/{data-grid-Bh4YrQGK.d.ts → data-grid-BddUjAti.d.ts} +33 -1
  9. package/dist/data-grid-BddUjAti.d.ts.map +1 -0
  10. package/dist/data-grid.d.ts +1 -1
  11. package/dist/data-grid.js +180 -7
  12. package/dist/data-grid.js.map +1 -1
  13. package/dist/detail-list-Br0WAnry.d.ts +167 -0
  14. package/dist/detail-list-Br0WAnry.d.ts.map +1 -0
  15. package/dist/detail-list.d.ts +3 -0
  16. package/dist/detail-list.js +1115 -0
  17. package/dist/detail-list.js.map +1 -0
  18. package/dist/grid-list-B_orPsot.d.ts +304 -0
  19. package/dist/grid-list-B_orPsot.d.ts.map +1 -0
  20. package/dist/grid-list.d.ts +3 -0
  21. package/dist/grid-list.js +933 -0
  22. package/dist/grid-list.js.map +1 -0
  23. package/dist/{grouped-item-lister-DnWB-eFT.d.ts → grouped-item-lister-fdZHVxFM.d.ts} +2 -2
  24. package/dist/{grouped-item-lister-DnWB-eFT.d.ts.map → grouped-item-lister-fdZHVxFM.d.ts.map} +1 -1
  25. package/dist/grouped-item-lister.d.ts +1 -1
  26. package/dist/index.d.ts +267 -124
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +469 -979
  29. package/dist/index.js.map +1 -1
  30. package/dist/interaction-Cr13azbM.js +3 -0
  31. package/dist/list-item-animation-DuiwgN3l.js +112 -0
  32. package/dist/list-item-animation-DuiwgN3l.js.map +1 -0
  33. package/dist/list-selection-controller-CUo-Hhcm.js +338 -0
  34. package/dist/list-selection-controller-CUo-Hhcm.js.map +1 -0
  35. package/dist/{list-selection-types-DSuRNWpx.d.ts → list-selection-types-B5ioL-dG.d.ts} +6 -1
  36. package/dist/list-selection-types-B5ioL-dG.d.ts.map +1 -0
  37. package/dist/{menu-CXDFJxNm.js → menu-Bg90Qr0B.js} +7 -7
  38. package/dist/menu-Bg90Qr0B.js.map +1 -0
  39. package/dist/{menu-item-D2S4i8u1.js → menu-item-Bx-Fs7jq.js} +3 -3
  40. package/dist/{menu-item-D2S4i8u1.js.map → menu-item-Bx-Fs7jq.js.map} +1 -1
  41. package/dist/menu.js +2 -2
  42. package/dist/miller.d.ts +1 -1
  43. package/dist/miller.js +2 -2
  44. package/dist/narrowed-text.js +2 -2
  45. package/dist/plots.js +1 -1
  46. package/dist/select-horiz.js +2 -2
  47. package/dist/{tickled-controller-CRzdIEjJ.d.ts → tickled-controller-Bn3YdQsM.d.ts} +13 -2
  48. package/dist/{tickled-controller-CRzdIEjJ.d.ts.map → tickled-controller-Bn3YdQsM.d.ts.map} +1 -1
  49. package/dist/{tickled-controller-h9GmJ_bU.js → tickled-controller-C66DgJHl.js} +16 -1
  50. package/dist/tickled-controller-C66DgJHl.js.map +1 -0
  51. package/dist/{tree-Ch4W8qXQ.js → tree-B74zdI60.js} +116 -17
  52. package/dist/tree-B74zdI60.js.map +1 -0
  53. package/dist/tree.d.ts +6 -3
  54. package/dist/tree.d.ts.map +1 -1
  55. package/dist/tree.js +1 -1
  56. package/dist/vertical-list-CQSEVeOT.d.ts +140 -0
  57. package/dist/vertical-list-CQSEVeOT.d.ts.map +1 -0
  58. package/dist/vertical-list.d.ts +3 -121
  59. package/dist/vertical-list.js +710 -1
  60. package/dist/vertical-list.js.map +1 -0
  61. package/dist/{xy-axis-DWizs0bt.js → xy-axis-VrX8dmvS.js} +2 -2
  62. package/dist/{xy-axis-DWizs0bt.js.map → xy-axis-VrX8dmvS.js.map} +1 -1
  63. package/docs-user/README.md +2 -0
  64. package/docs-user/data-grid.md +51 -2
  65. package/docs-user/detail-list.md +80 -15
  66. package/docs-user/grid-list.md +274 -0
  67. package/docs-user/index.json +11 -2
  68. package/docs-user/infra-tickled.md +163 -0
  69. package/docs-user/llms.txt +2 -0
  70. package/docs-user/tree.md +13 -0
  71. package/docs-user/vertical-list.md +51 -1
  72. package/llms.txt +2 -0
  73. package/package.json +17 -2
  74. package/dist/data-grid-Bh4YrQGK.d.ts.map +0 -1
  75. package/dist/interaction-D5XdKzbR.js +0 -2
  76. package/dist/list-selection-types-DSuRNWpx.d.ts.map +0 -1
  77. package/dist/menu-CXDFJxNm.js.map +0 -1
  78. package/dist/tickled-controller-h9GmJ_bU.js.map +0 -1
  79. package/dist/tree-Ch4W8qXQ.js.map +0 -1
  80. package/dist/vertical-list-ClnqTkF3.js +0 -814
  81. package/dist/vertical-list-ClnqTkF3.js.map +0 -1
  82. package/dist/vertical-list.d.ts.map +0 -1
@@ -0,0 +1,1115 @@
1
+ import { t as TickledController } from "./tickled-controller-C66DgJHl.js";
2
+ import { t as themeFallbacks } from "./fallbacks-DNI9xt0F.js";
3
+ import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
4
+ import "./icon-DcEEGQa_.js";
5
+ import { t as DataController } from "./data-controller-DOlmqNKb.js";
6
+ import { t as createElementIncrSearch } from "./element-search-Y8SRfPGM.js";
7
+ import { i as keyForElement, t as ListSelectionController } from "./list-selection-controller-CUo-Hhcm.js";
8
+ import { n as animateListItemIn, r as animateListItemOut } from "./list-item-animation-DuiwgN3l.js";
9
+ import { t as scrollFadeStyles } from "./scroll-fade-CAs1EabA.js";
10
+ import { LitElement, css, html, nothing, render } from "lit";
11
+ import { property, query, state } from "lit/decorators.js";
12
+ //#region src/detail-list/detail-list.ts
13
+ const ITEM_SELECTOR = `:scope > li`;
14
+ const SEARCH_HIGHLIGHT_KEY = `detail-list-search`;
15
+ const LABEL_SPAN_CSS = `overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0`;
16
+ let DetailListElement = class DetailListElement extends LitElement {
17
+ constructor(..._args2) {
18
+ super(..._args2);
19
+ this.selectionMode = `single`;
20
+ this.interactionMode = `standard`;
21
+ this.sort = `none`;
22
+ this.sortBy = ``;
23
+ this.items = [];
24
+ this.displayProperty = ``;
25
+ this.iconProperty = ``;
26
+ this.keyProperty = ``;
27
+ this.itemWrap = `stop`;
28
+ this._searchVisible = false;
29
+ this._searchQuery = ``;
30
+ this.#itemData = /* @__PURE__ */ new WeakMap();
31
+ this.#grid = [];
32
+ this.#positions = /* @__PURE__ */ new Map();
33
+ this.#geometryScheduled = false;
34
+ this.#animatedItems = /* @__PURE__ */ new WeakSet();
35
+ this.#builtFromProvider = [];
36
+ this.#providerBuilt = 0;
37
+ this.#data = new DataController(this);
38
+ this.selection = new ListSelectionController({
39
+ getOrderedItems: () => this._getVisibleItems(),
40
+ isSelectable: (el) => this._isCheckable(el),
41
+ getModes: () => ({
42
+ selectionMode: this.selectionMode,
43
+ interactionMode: this.interactionMode
44
+ }),
45
+ getKey: keyForElement,
46
+ onChange: (selected, previous) => {
47
+ this._syncItemStates();
48
+ this._dispatch(`list-select`, {
49
+ selected,
50
+ previous,
51
+ selectedKeys: this.selection.selectedKeys
52
+ });
53
+ }
54
+ });
55
+ this.tickled = new TickledController(this, {
56
+ getItems: () => this._getVisibleItems().map((el) => ({
57
+ id: el,
58
+ enabled: !el.hasAttribute(`not-checked`)
59
+ })),
60
+ onTickle: (id) => {
61
+ if (id !== void 0) this._dispatch(`list-tickle`, { item: id });
62
+ },
63
+ onStateChange: () => this._syncItemStates(),
64
+ itemWrap: this.itemWrap
65
+ });
66
+ this._onPointerOver = (e) => {
67
+ const item = this._findListItem(e.composedPath());
68
+ if (item) this.tickled.handlePointerEnter(item);
69
+ };
70
+ this._onPointerLeave = (e) => {
71
+ this.tickled.handlePointerLeave(e);
72
+ };
73
+ this._onKeyDown = (e) => {
74
+ if ((e.ctrlKey || e.metaKey) && e.key === `f`) {
75
+ e.preventDefault();
76
+ this._openSearch();
77
+ return;
78
+ }
79
+ if ((e.ctrlKey || e.metaKey) && e.key === `a`) {
80
+ if (this.selection.canSelectAll()) {
81
+ e.preventDefault();
82
+ this.selection.selectAll();
83
+ }
84
+ return;
85
+ }
86
+ if (this._searchVisible && e.key === `Escape`) {
87
+ e.preventDefault();
88
+ this._closeSearch();
89
+ return;
90
+ }
91
+ switch (e.key) {
92
+ case `ArrowDown`:
93
+ e.preventDefault();
94
+ this.#navigateDown();
95
+ if (e.shiftKey && this.interactionMode === `vscode`) {
96
+ const cur = this.tickled.tickledId;
97
+ if (cur) this.selection.rangeTo(cur);
98
+ }
99
+ break;
100
+ case `ArrowUp`:
101
+ e.preventDefault();
102
+ this.#navigateUp();
103
+ if (e.shiftKey && this.interactionMode === `vscode`) {
104
+ const cur = this.tickled.tickledId;
105
+ if (cur) this.selection.rangeTo(cur);
106
+ }
107
+ break;
108
+ case `ArrowRight`:
109
+ e.preventDefault();
110
+ this.#navigateRight();
111
+ break;
112
+ case `ArrowLeft`:
113
+ e.preventDefault();
114
+ this.#navigateLeft();
115
+ break;
116
+ case `Home`:
117
+ e.preventDefault();
118
+ this.#navigateTo(this.#findFirstCheckable());
119
+ break;
120
+ case `End`:
121
+ e.preventDefault();
122
+ this.#navigateTo(this.#findLastCheckable());
123
+ break;
124
+ case `Enter`: {
125
+ const id = this.tickled.tickledId;
126
+ if (id) {
127
+ e.preventDefault();
128
+ this._dispatch(`list-item-click`, { item: id });
129
+ this._activateItem(id);
130
+ }
131
+ break;
132
+ }
133
+ case ` `: {
134
+ const id = this.tickled.tickledId;
135
+ if (id) {
136
+ e.preventDefault();
137
+ this.selection.handleSpace(id);
138
+ }
139
+ break;
140
+ }
141
+ case `Escape`: this.selection.clear();
142
+ }
143
+ };
144
+ this._onClick = (e) => {
145
+ const item = this._findListItem(e.composedPath());
146
+ if (!item) return;
147
+ this.focus();
148
+ this._dispatch(`list-item-click`, { item });
149
+ this.selection.handleClick(item, e);
150
+ };
151
+ this._onDblClick = (e) => {
152
+ const item = this._findListItem(e.composedPath());
153
+ if (item) this._activateItem(item);
154
+ };
155
+ this._onWheel = (e) => {
156
+ e.preventDefault();
157
+ this._listEl.scrollLeft += e.deltaY;
158
+ };
159
+ this._onSearchInput = (e) => {
160
+ const input = e.target;
161
+ this._searchQuery = input.value;
162
+ this.#search?.setQuery(this._searchQuery).then(() => this.#recomputeGeometry());
163
+ };
164
+ this._onSearchKeydown = (e) => {
165
+ if (e.key === `Escape`) {
166
+ e.stopPropagation();
167
+ this._closeSearch();
168
+ } else if (e.key === `ArrowDown`) {
169
+ e.preventDefault();
170
+ this.focus();
171
+ this.#navigateDown();
172
+ } else if (e.key === `ArrowUp`) {
173
+ e.preventDefault();
174
+ this.focus();
175
+ this.#navigateUp();
176
+ } else if (e.key === `ArrowRight`) e.preventDefault();
177
+ else if (e.key === `ArrowLeft`) e.preventDefault();
178
+ else if (e.key === `Enter`) {
179
+ e.preventDefault();
180
+ e.stopPropagation();
181
+ const id = this.tickled.tickledId ?? this.#findFirstCheckable();
182
+ if (id) {
183
+ this._dispatch(`list-item-click`, { item: id });
184
+ this._activateItem(id);
185
+ this._closeSearch();
186
+ }
187
+ } else if (e.key === ` `) {
188
+ const id = this.tickled.tickledId;
189
+ if (id) {
190
+ e.preventDefault();
191
+ this.selection.handleSpace(id);
192
+ }
193
+ }
194
+ };
195
+ }
196
+ #search;
197
+ #itemData;
198
+ #grid;
199
+ #positions;
200
+ #geometryScheduled;
201
+ #resizeObserver;
202
+ #mutationObserver;
203
+ /** Items that have already played (or been skipped for) their enter animation. */
204
+ #animatedItems;
205
+ #builtFromProvider;
206
+ #providerBuilt;
207
+ #data;
208
+ connectedCallback() {
209
+ super.connectedCallback();
210
+ if (!this.hasAttribute(`tabindex`)) this.tabIndex = 0;
211
+ this.addEventListener(`pointerover`, this._onPointerOver);
212
+ this.addEventListener(`pointerleave`, this._onPointerLeave);
213
+ this.addEventListener(`keydown`, this._onKeyDown);
214
+ this.addEventListener(`click`, this._onClick);
215
+ this.addEventListener(`dblclick`, this._onDblClick);
216
+ this.addEventListener(`wheel`, this._onWheel);
217
+ this.#resizeObserver = new ResizeObserver(() => this.#scheduleGeometry());
218
+ this.#resizeObserver.observe(this);
219
+ this.#mutationObserver = new MutationObserver((records) => {
220
+ this.#processChildren();
221
+ this.#animateChildMutations(records);
222
+ this.#scheduleGeometry();
223
+ });
224
+ this.#mutationObserver.observe(this, {
225
+ childList: true,
226
+ subtree: false,
227
+ attributeFilter: [`hidden`]
228
+ });
229
+ for (const el of this.querySelectorAll(ITEM_SELECTOR)) this.#animatedItems.add(el);
230
+ }
231
+ /**
232
+ * Collapse-in items appended to the light DOM (however they got there). A bulk
233
+ * populate is left alone. Geometry is re-read once the animations settle so
234
+ * keyboard navigation lines back up with the final layout.
235
+ */
236
+ #animateChildMutations(records) {
237
+ if (!this.isConnected) return;
238
+ const fresh = [];
239
+ for (const record of records) for (const node of record.addedNodes) {
240
+ if (!(node instanceof HTMLElement) || node.parentElement !== this || node.localName !== `li`) continue;
241
+ if (this.#animatedItems.has(node)) continue;
242
+ this.#animatedItems.add(node);
243
+ fresh.push(node);
244
+ }
245
+ if (fresh.length === 0) return;
246
+ const total = this.querySelectorAll(ITEM_SELECTOR).length;
247
+ if (fresh.length >= total) return;
248
+ for (const el of fresh) animateListItemIn(el);
249
+ window.setTimeout(() => this.#scheduleGeometry(), 230);
250
+ }
251
+ disconnectedCallback() {
252
+ super.disconnectedCallback();
253
+ this.removeEventListener(`pointerover`, this._onPointerOver);
254
+ this.removeEventListener(`pointerleave`, this._onPointerLeave);
255
+ this.removeEventListener(`keydown`, this._onKeyDown);
256
+ this.removeEventListener(`click`, this._onClick);
257
+ this.removeEventListener(`dblclick`, this._onDblClick);
258
+ this.removeEventListener(`wheel`, this._onWheel);
259
+ this.#resizeObserver?.disconnect();
260
+ this.#resizeObserver = void 0;
261
+ this.#mutationObserver?.disconnect();
262
+ this.#mutationObserver = void 0;
263
+ this.#search?.dispose();
264
+ this.#search = void 0;
265
+ }
266
+ firstUpdated() {
267
+ this.#processChildren();
268
+ if (this.filterPredicate) this.#applyFilterPredicate();
269
+ this.#search = createElementIncrSearch({
270
+ container: this,
271
+ itemSelector: ITEM_SELECTOR,
272
+ getLabel: (el) => el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``,
273
+ getKey: (el) => el.getAttribute(`data-key`) ?? el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``,
274
+ highlightKey: SEARCH_HIGHLIGHT_KEY
275
+ });
276
+ this.#recomputeGeometry();
277
+ }
278
+ updated(changed) {
279
+ super.updated(changed);
280
+ if (changed.has(`provider`)) {
281
+ this.#builtFromProvider = [];
282
+ this.#providerBuilt = 0;
283
+ this.#data.provider = this.provider;
284
+ queueMicrotask(() => {
285
+ if (this.provider) this.#data.request(void 0);
286
+ });
287
+ return;
288
+ }
289
+ if (changed.has(`items`) && this.provider === void 0 && this.items.length > 0) this.#rebuildFromObjects(this.items);
290
+ this.#syncFromProvider();
291
+ if (changed.has(`sort`) || changed.has(`sortBy`) || changed.has(`sortComparator`)) this.#applySort();
292
+ if (changed.has(`filterPredicate`)) {
293
+ this.#applyFilterPredicate();
294
+ this.#scheduleGeometry();
295
+ }
296
+ if (changed.has(`interactionMode`)) {
297
+ this.selection.clearAnchor();
298
+ this._syncItemStates();
299
+ } else if (changed.has(`checkboxRenderer`) && this.interactionMode === `checked`) this._syncCheckboxes();
300
+ }
301
+ /** The most recently selected item, or `undefined` if nothing is selected. */
302
+ get selectedItem() {
303
+ return [...this.selection.selected].at(-1);
304
+ }
305
+ /** The full current selection set (read-only). */
306
+ get selectedItems() {
307
+ return this.selection.selected;
308
+ }
309
+ /**
310
+ * Force an immediate geometry recompute. Called automatically on resize,
311
+ * DOM changes, sort/filter/search changes — only needed when layout is
312
+ * changed by outside forces (e.g. an external font load).
313
+ */
314
+ refreshLayout() {
315
+ this.#recomputeGeometry();
316
+ }
317
+ addItem(item, options) {
318
+ let el;
319
+ if (typeof item === `string`) {
320
+ const li = document.createElement(`li`);
321
+ this._setLabel(li, item);
322
+ el = li;
323
+ } else el = item;
324
+ if (options?.notChecked) el.setAttribute(`not-checked`, ``);
325
+ this.appendChild(el);
326
+ this.#ensureIcon(el);
327
+ if (this.sort !== `none`) this.#applySort();
328
+ if (this.interactionMode === `checked`) this._syncCheckboxes();
329
+ }
330
+ /**
331
+ * Collapse + fade `item` out, then detach it; the rows after it slide up to
332
+ * close the gap. Resolves once removed. Immediate under reduced motion.
333
+ */
334
+ removeItem(item) {
335
+ this.selection.deselect(item);
336
+ return animateListItemOut(item).then(() => {
337
+ item.remove();
338
+ if (this.interactionMode === `checked`) this._syncCheckboxes();
339
+ this.#recomputeGeometry();
340
+ });
341
+ }
342
+ /** {@link removeItem} for several items at once. */
343
+ removeItems(items) {
344
+ return Promise.all([...items].map((item) => this.removeItem(item))).then(() => void 0);
345
+ }
346
+ clearItems() {
347
+ this.replaceChildren();
348
+ this.selection.reset();
349
+ this.#builtFromProvider = [];
350
+ this.#providerBuilt = 0;
351
+ this._syncItemStates();
352
+ }
353
+ /**
354
+ * Replace the entire item list in one call.
355
+ * Equivalent to `clearItems()` followed by `addItem()` for each element.
356
+ */
357
+ setItems(items) {
358
+ this.clearItems();
359
+ for (const item of items) {
360
+ this.appendChild(item);
361
+ this.#ensureIcon(item);
362
+ }
363
+ if (this.sort !== `none`) this.#applySort();
364
+ if (this.interactionMode === `checked`) this._syncCheckboxes();
365
+ }
366
+ /**
367
+ * Get the source object behind an `<li>` built from an `items` array or
368
+ * provider. Returns `undefined` for declarative / programmatic elements.
369
+ */
370
+ getItemData(item) {
371
+ return this.#itemData.get(item);
372
+ }
373
+ /** Dispatch a command to this list. No-op by default (grouped-lister parity). */
374
+ dispatch(_commandId, _args) {}
375
+ select(item) {
376
+ this.selection.replace(item);
377
+ }
378
+ selectMany(items) {
379
+ this.selection.selectMany(items);
380
+ }
381
+ deselect(item) {
382
+ this.selection.deselect(item);
383
+ }
384
+ clearSelection() {
385
+ this.selection.clear();
386
+ }
387
+ selectAll() {
388
+ if (this.selection.canSelectAll()) this.selection.selectAll();
389
+ }
390
+ /** The selection as stable `data-key` / `data-value` strings. */
391
+ get selectedKeys() {
392
+ return this.selection.selectedKeys;
393
+ }
394
+ /** The key of the item under the cursor, or `undefined`. */
395
+ get tickledKey() {
396
+ const id = this.tickled.tickledId;
397
+ return id ? keyForElement(id) : void 0;
398
+ }
399
+ /** Replace the selection with the items carrying these keys (unknown keys ignored). */
400
+ selectKeys(keys) {
401
+ this.selection.selectKeys(keys, (key) => this.#itemForKey(key));
402
+ }
403
+ /** Add items by key to the selection (multi-select modes only). */
404
+ addSelectedKeys(keys) {
405
+ this.selection.addKeys(keys, (key) => this.#itemForKey(key));
406
+ }
407
+ /** Remove items by key from the selection. */
408
+ removeSelectedKeys(keys) {
409
+ this.selection.removeKeys(keys, (key) => this.#itemForKey(key));
410
+ }
411
+ /** Move the cursor to the item with this key and scroll it into view. */
412
+ focusKey(key) {
413
+ const el = this.#itemForKey(key);
414
+ if (!el) return;
415
+ this.tickled.focus(el);
416
+ el.scrollIntoView({
417
+ block: `nearest`,
418
+ inline: `nearest`
419
+ });
420
+ }
421
+ #itemForKey(key) {
422
+ for (const el of this.querySelectorAll(ITEM_SELECTOR)) if (keyForElement(el) === key) return el;
423
+ }
424
+ *selectedFilter(kind) {
425
+ const wanted = (el) => {
426
+ switch (kind) {
427
+ case `selected`: return el.hasAttribute(`data-selected`);
428
+ case `checked`: return el.hasAttribute(`data-checked`);
429
+ case `either`: return el.hasAttribute(`data-selected`) || el.hasAttribute(`data-checked`);
430
+ }
431
+ };
432
+ for (const el of this.querySelectorAll(ITEM_SELECTOR)) {
433
+ if (el.hidden) continue;
434
+ if (wanted(el)) yield el;
435
+ }
436
+ }
437
+ #rebuildFromObjects(items) {
438
+ const keyOf = (el) => el.getAttribute(`data-key`) ?? el.getAttribute(`data-search-label`) ?? null;
439
+ const previousKeys = new Set([...this.selection.selected].map(keyOf).filter((k) => k !== null));
440
+ const anchorKey = this.selection.anchor ? keyOf(this.selection.anchor) : null;
441
+ this.replaceChildren(...items.map((item, index) => this.#createItemEl(item, index)));
442
+ if (previousKeys.size > 0 || anchorKey !== null) {
443
+ const next = [];
444
+ let anchor;
445
+ for (const el of this.querySelectorAll(ITEM_SELECTOR)) {
446
+ const key = keyOf(el);
447
+ if (key !== null && previousKeys.has(key)) next.push(el);
448
+ if (key !== null && key === anchorKey) anchor = el;
449
+ }
450
+ this.selection.seed(next, anchor);
451
+ }
452
+ if (this.sort !== `none`) this.#applySort();
453
+ if (this.interactionMode === `checked`) this._syncCheckboxes();
454
+ this._syncItemStates();
455
+ this.#scheduleGeometry();
456
+ }
457
+ #createItemEl(item, index) {
458
+ const li = document.createElement(`li`);
459
+ this.#itemData.set(li, item);
460
+ const content = this.formatter ? this.formatter(item, index) : this.displayProperty ? this.#propertyValue(item, this.displayProperty) : item;
461
+ if (typeof content === `string`) {
462
+ this._setLabel(li, content);
463
+ if (this.iconProperty) {
464
+ const icon = this.#propertyValue(item, this.iconProperty);
465
+ if (typeof icon === `string` && icon.length > 0) li.setAttribute(`data-icon`, icon);
466
+ }
467
+ } else if (content instanceof Node) li.appendChild(content);
468
+ if (this.keyProperty) {
469
+ const key = this.#propertyValue(item, this.keyProperty);
470
+ if (key !== void 0 && key !== null) li.setAttribute(`data-key`, String(key));
471
+ }
472
+ this.#ensureIcon(li);
473
+ return li;
474
+ }
475
+ #propertyValue(item, prop) {
476
+ if (typeof item !== `object` || item === null || !(prop in item)) return void 0;
477
+ return item[prop];
478
+ }
479
+ #syncFromProvider() {
480
+ if (this.provider === void 0) return;
481
+ const items = this.#data.items;
482
+ if (items === this.#builtFromProvider) return;
483
+ let prefix = 0;
484
+ while (prefix < this.#builtFromProvider.length && prefix < items.length && this.#builtFromProvider[prefix] === items[prefix]) prefix++;
485
+ if (prefix === this.#builtFromProvider.length && prefix <= items.length) {
486
+ const tail = items.slice(prefix);
487
+ for (const item of tail) this.appendChild(this.#createItemEl(item, this.#providerBuilt++));
488
+ if (this.sort !== `none`) this.#applySort();
489
+ if (this.interactionMode === `checked`) this._syncCheckboxes();
490
+ this._syncItemStates();
491
+ this.#scheduleGeometry();
492
+ } else {
493
+ this.#providerBuilt = 0;
494
+ this.#rebuildFromObjects(items);
495
+ }
496
+ this.#builtFromProvider = items;
497
+ }
498
+ #applyFilterPredicate() {
499
+ if (!this.filterPredicate) {
500
+ for (const el of this.querySelectorAll(ITEM_SELECTOR)) el.removeAttribute(`hidden`);
501
+ return;
502
+ }
503
+ for (const el of this.querySelectorAll(ITEM_SELECTOR)) if (this.filterPredicate(el)) el.removeAttribute(`hidden`);
504
+ else el.setAttribute(`hidden`, ``);
505
+ }
506
+ #applySort() {
507
+ if (this.sort === `none`) return;
508
+ const rows = [...this.querySelectorAll(ITEM_SELECTOR)];
509
+ if (rows.length < 2) return;
510
+ const dir = this.sort === `descending` ? -1 : 1;
511
+ const meta = (el) => ({
512
+ element: el,
513
+ data: this.getItemData(el),
514
+ label: el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``
515
+ });
516
+ let cmp;
517
+ if (this.sortComparator) {
518
+ const user = this.sortComparator;
519
+ cmp = (a, b) => user(meta(a), meta(b));
520
+ } else if (this.sortBy) {
521
+ const prop = this.sortBy;
522
+ const key = (el) => {
523
+ const v = this.#propertyValue(this.getItemData(el), prop);
524
+ if (typeof v === `number`) return v;
525
+ return v === void 0 || v === null ? `` : String(v);
526
+ };
527
+ cmp = (a, b) => {
528
+ const ka = key(a);
529
+ const kb = key(b);
530
+ if (typeof ka === `number` && typeof kb === `number`) return ka - kb;
531
+ return String(ka).localeCompare(String(kb), void 0, { numeric: true });
532
+ };
533
+ } else cmp = (a, b) => meta(a).label.localeCompare(meta(b).label, void 0, { numeric: true });
534
+ const indexed = rows.map((el, i) => ({
535
+ el,
536
+ i
537
+ }));
538
+ indexed.sort((a, b) => cmp(a.el, b.el) * dir || a.i - b.i);
539
+ for (const { el } of indexed) this.appendChild(el);
540
+ this.#scheduleGeometry();
541
+ }
542
+ #scheduleGeometry() {
543
+ if (this.#geometryScheduled) return;
544
+ this.#geometryScheduled = true;
545
+ requestAnimationFrame(() => {
546
+ this.#geometryScheduled = false;
547
+ this.#recomputeGeometry();
548
+ });
549
+ }
550
+ #recomputeGeometry() {
551
+ this.#grid = [];
552
+ this.#positions.clear();
553
+ if (!this.isConnected) return;
554
+ const columns = [];
555
+ for (const el of this._getVisibleItems()) {
556
+ const rect = el.getBoundingClientRect();
557
+ if (rect.width === 0 && rect.height === 0) continue;
558
+ let ci = columns.findIndex((c) => Math.abs(c.left - rect.left) <= 1);
559
+ if (ci === -1) {
560
+ columns.push({
561
+ left: rect.left,
562
+ items: []
563
+ });
564
+ ci = columns.length - 1;
565
+ }
566
+ columns[ci].items.push(el);
567
+ }
568
+ columns.sort((a, b) => a.left - b.left);
569
+ for (const col of columns) col.items.sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
570
+ this.#grid = columns.map((c) => c.items);
571
+ this.#grid.forEach((col, colIdx) => col.forEach((el, rowIdx) => this.#positions.set(el, {
572
+ col: colIdx,
573
+ row: rowIdx
574
+ })));
575
+ }
576
+ #positionOf(el) {
577
+ let pos = this.#positions.get(el);
578
+ if (pos === void 0) {
579
+ this.#recomputeGeometry();
580
+ pos = this.#positions.get(el);
581
+ }
582
+ return pos;
583
+ }
584
+ #itemAt(col, row) {
585
+ return this.#grid[col]?.[row];
586
+ }
587
+ #gridSize() {
588
+ if (this.#grid.length === 0) this.#recomputeGeometry();
589
+ return {
590
+ cols: this.#grid.length,
591
+ rows: Math.max(0, ...this.#grid.map((c) => c.length))
592
+ };
593
+ }
594
+ #navigateTo(target) {
595
+ if (!target) return;
596
+ this.tickled.handleKeyboardNavigation(target);
597
+ this.tickled.tickledId?.scrollIntoView({
598
+ block: `nearest`,
599
+ inline: `nearest`
600
+ });
601
+ }
602
+ #findNextCheckable(fromCol, fromRow, deltaCol, deltaRow) {
603
+ const { cols } = this.#gridSize();
604
+ const maxIterations = this._getVisibleItems().length + 1;
605
+ let col = deltaCol !== 0 ? fromCol + deltaCol : fromCol;
606
+ let row = deltaRow !== 0 ? fromRow + deltaRow : fromRow;
607
+ let wrappedColumns = false;
608
+ for (let iterations = 0; iterations < maxIterations; iterations++) {
609
+ const colLength = this.#grid[col]?.length ?? 0;
610
+ if (row < 0 || row >= colLength) {
611
+ if (deltaCol !== 0 || wrappedColumns) {
612
+ if (this.itemWrap === `wrap`) {
613
+ if (deltaCol < 0) col = cols - 1;
614
+ else if (deltaCol > 0) col = 0;
615
+ wrappedColumns = true;
616
+ } else return;
617
+ }
618
+ if (deltaRow > 0) {
619
+ if (col + 1 < cols) {
620
+ col = col + 1;
621
+ row = 0;
622
+ wrappedColumns = true;
623
+ } else if (this.itemWrap === `wrap`) {
624
+ col = 0;
625
+ row = 0;
626
+ wrappedColumns = true;
627
+ } else return;
628
+ } else if (deltaRow < 0) {
629
+ if (col - 1 >= 0) {
630
+ col = col - 1;
631
+ row = (this.#grid[col]?.length ?? 1) - 1;
632
+ wrappedColumns = true;
633
+ } else if (this.itemWrap === `wrap`) {
634
+ col = cols - 1;
635
+ row = (this.#grid[col]?.length ?? 1) - 1;
636
+ wrappedColumns = true;
637
+ } else return;
638
+ } else if (deltaCol !== 0) {
639
+ const targetColLength = this.#grid[col]?.length ?? 0;
640
+ if (row >= targetColLength && targetColLength > 0) row = targetColLength - 1;
641
+ }
642
+ if (col === fromCol && row === fromRow + deltaRow) return;
643
+ continue;
644
+ }
645
+ const item = this.#itemAt(col, row);
646
+ if (item && this._isCheckable(item)) return item;
647
+ row += deltaRow;
648
+ }
649
+ }
650
+ #findFirstCheckable() {
651
+ return this._getVisibleItems().find((item) => this._isCheckable(item));
652
+ }
653
+ #findLastCheckable() {
654
+ const visibleItems = this._getVisibleItems();
655
+ for (let i = visibleItems.length - 1; i >= 0; i--) if (this._isCheckable(visibleItems[i])) return visibleItems[i];
656
+ }
657
+ #navigateDown() {
658
+ this.#recomputeGeometry();
659
+ const cur = this.tickled.tickledId ? this.#positionOf(this.tickled.tickledId) : void 0;
660
+ if (!cur) {
661
+ this.#navigateTo(this.#findFirstCheckable());
662
+ return;
663
+ }
664
+ const next = this.#findNextCheckable(cur.col, cur.row, 0, 1);
665
+ if (next) this.#navigateTo(next);
666
+ }
667
+ #navigateUp() {
668
+ this.#recomputeGeometry();
669
+ const cur = this.tickled.tickledId ? this.#positionOf(this.tickled.tickledId) : void 0;
670
+ if (!cur) {
671
+ this.#navigateTo(this.#findLastCheckable());
672
+ return;
673
+ }
674
+ const next = this.#findNextCheckable(cur.col, cur.row, 0, -1);
675
+ if (next) this.#navigateTo(next);
676
+ }
677
+ #navigateRight() {
678
+ this.#recomputeGeometry();
679
+ const cur = this.tickled.tickledId ? this.#positionOf(this.tickled.tickledId) : void 0;
680
+ if (!cur) {
681
+ this.#navigateTo(this.#findFirstCheckable());
682
+ return;
683
+ }
684
+ const next = this.#findNextCheckable(cur.col, cur.row, 1, 0);
685
+ if (next) this.#navigateTo(next);
686
+ else if (this.itemWrap === `wrap`) this.#navigateTo(this.#findFirstCheckable());
687
+ }
688
+ #navigateLeft() {
689
+ this.#recomputeGeometry();
690
+ const cur = this.tickled.tickledId ? this.#positionOf(this.tickled.tickledId) : void 0;
691
+ if (!cur) {
692
+ this.#navigateTo(this.#findFirstCheckable());
693
+ return;
694
+ }
695
+ const next = this.#findNextCheckable(cur.col, cur.row, -1, 0);
696
+ if (next) this.#navigateTo(next);
697
+ else if (this.itemWrap === `wrap`) this.#navigateTo(this.#findLastCheckable());
698
+ }
699
+ _getVisibleItems() {
700
+ return Array.from(this.querySelectorAll(ITEM_SELECTOR)).filter((el) => {
701
+ if (el.hidden) return false;
702
+ if (this.filterPredicate && !this.filterPredicate(el)) return false;
703
+ return true;
704
+ });
705
+ }
706
+ /**
707
+ * Set the column width to fit the widest visible item.
708
+ *
709
+ * This is opt-in; detail lists continue to use `--detail-list-column-width`
710
+ * (or its 220px fallback) until this method is called. The measured value is
711
+ * set directly on this element, so it takes precedence over an inherited
712
+ * value. Returns the applied width in pixels, or 0 when there are no items.
713
+ */
714
+ async fitColumnWidth() {
715
+ await this.updateComplete;
716
+ const items = this._getVisibleItems();
717
+ if (items.length === 0) return 0;
718
+ const previousWidth = this.style.getPropertyValue(`--detail-list-column-width`);
719
+ const previousPriority = this.style.getPropertyPriority(`--detail-list-column-width`);
720
+ this.style.setProperty(`--detail-list-column-width`, `max-content`);
721
+ const width = Math.ceil(Math.max(...items.map((item) => item.getBoundingClientRect().width)));
722
+ if (previousWidth) this.style.setProperty(`--detail-list-column-width`, previousWidth, previousPriority);
723
+ else this.style.removeProperty(`--detail-list-column-width`);
724
+ this.style.setProperty(`--detail-list-column-width`, `${width}px`);
725
+ this.#scheduleGeometry();
726
+ return width;
727
+ }
728
+ _isCheckable(item) {
729
+ if (this.interactionMode !== `checked`) return true;
730
+ return !item.hasAttribute(`not-checked`);
731
+ }
732
+ _setLabel(li, label) {
733
+ const span = document.createElement(`span`);
734
+ span.style.cssText = LABEL_SPAN_CSS;
735
+ span.textContent = label;
736
+ li.appendChild(span);
737
+ li.setAttribute(`data-search-label`, label);
738
+ }
739
+ /**
740
+ * Inject a leading `<ixfx-icon>` into an item carrying `data-icon`.
741
+ * Idempotent — skips when the item already starts with an icon.
742
+ */
743
+ #ensureIcon(li) {
744
+ const name = li.getAttribute(`data-icon`);
745
+ if (!name) return;
746
+ if (li.firstElementChild?.tagName === `IXFX-ICON`) return;
747
+ const icon = document.createElement(`ixfx-icon`);
748
+ icon.setAttribute(`name`, name);
749
+ icon.style.marginInlineEnd = `var(--detail-list-icon-gap, 6px)`;
750
+ icon.style.flexShrink = `0`;
751
+ li.prepend(icon);
752
+ }
753
+ /** Scan light-DOM children for unprocessed `data-icon` items. */
754
+ #processChildren() {
755
+ for (const el of this.querySelectorAll(ITEM_SELECTOR)) this.#ensureIcon(el);
756
+ }
757
+ _syncItemStates() {
758
+ for (const el of this.querySelectorAll(ITEM_SELECTOR)) {
759
+ el.toggleAttribute(`data-tickled`, this.tickled.isTickled(el));
760
+ el.toggleAttribute(`data-selected`, this.selection.selected.has(el));
761
+ if (this.interactionMode === `checked`) el.toggleAttribute(`data-checked`, !el.hasAttribute(`not-checked`) && this.selection.selected.has(el));
762
+ else el.removeAttribute(`data-checked`);
763
+ }
764
+ this.dataset.inputMode = this.tickled.inputMode;
765
+ this._syncCheckboxes();
766
+ }
767
+ _syncCheckboxes() {
768
+ const inCheckedMode = this.interactionMode === `checked`;
769
+ const wantsCustom = typeof this.checkboxRenderer === `function`;
770
+ for (const el of this.querySelectorAll(`li`)) {
771
+ const existing = el.querySelector(`.ixfx-list-checkbox`);
772
+ if (!inCheckedMode) {
773
+ if (existing) existing.remove();
774
+ continue;
775
+ }
776
+ if (el.hasAttribute(`not-checked`)) {
777
+ if (existing) existing.remove();
778
+ continue;
779
+ }
780
+ const existingIsCustom = existing?.classList.contains(`ixfx-list-checkbox-custom`) ?? false;
781
+ if (existing && existingIsCustom !== wantsCustom) existing.remove();
782
+ let cb = el.querySelector(`.ixfx-list-checkbox`);
783
+ if (!cb) {
784
+ if (wantsCustom) {
785
+ const opts = this.checkboxRenderer;
786
+ const margin = opts.margin ?? `0`;
787
+ const width = opts.width ?? `24px`;
788
+ cb = this._createCustomCheckbox(el, margin, width);
789
+ } else cb = this._createNativeCheckboxCell(el);
790
+ el.prepend(cb);
791
+ }
792
+ const checked = this.selection.selected.has(el);
793
+ if (wantsCustom) {
794
+ cb.setAttribute(`aria-checked`, String(checked));
795
+ cb.toggleAttribute(`data-checked`, checked);
796
+ if (this.checkboxRenderer) {
797
+ const renderer = typeof this.checkboxRenderer === `function` ? this.checkboxRenderer : this.checkboxRenderer.renderer;
798
+ render(renderer(checked), cb);
799
+ }
800
+ } else {
801
+ const input = cb.querySelector(`input.ixfx-list-checkbox`);
802
+ if (input) input.checked = checked;
803
+ }
804
+ }
805
+ }
806
+ /**
807
+ * Native checkbox cell: a span that stretches to the row's full cross-axis
808
+ * (so the entire column height is a click target) with a real `<input>`
809
+ * centred inside. The span owns the click; the input is purely visual and
810
+ * has `pointer-events:none` so a single click produces one toggle.
811
+ */
812
+ _createNativeCheckboxCell(el) {
813
+ const cell = document.createElement(`span`);
814
+ cell.className = `ixfx-list-checkbox ixfx-list-checkbox-native`;
815
+ 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`;
816
+ cell.tabIndex = -1;
817
+ cell.setAttribute(`role`, `checkbox`);
818
+ cell.setAttribute(`part`, `checkbox`);
819
+ cell.addEventListener(`click`, (e) => {
820
+ e.stopPropagation();
821
+ this.selection.toggle(el);
822
+ });
823
+ const input = document.createElement(`input`);
824
+ input.type = `checkbox`;
825
+ input.className = `ixfx-list-checkbox`;
826
+ input.style.cssText = `pointer-events:none;cursor:pointer;margin:0`;
827
+ cell.appendChild(input);
828
+ return cell;
829
+ }
830
+ /** Custom checkbox cell: same shape as the native cell; renderer content inside. */
831
+ _createCustomCheckbox(el, margin, width) {
832
+ const span = document.createElement(`span`);
833
+ span.className = `ixfx-list-checkbox ixfx-list-checkbox-custom`;
834
+ 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`;
835
+ span.tabIndex = -1;
836
+ span.setAttribute(`role`, `checkbox`);
837
+ span.setAttribute(`part`, `checkbox`);
838
+ span.addEventListener(`click`, (e) => {
839
+ e.stopPropagation();
840
+ this.selection.toggle(el);
841
+ });
842
+ return span;
843
+ }
844
+ _findListItem(path) {
845
+ for (const target of path) {
846
+ if (!(target instanceof Element)) continue;
847
+ if (target === this) break;
848
+ if (target.parentElement === this && target.matches(`li`)) return target;
849
+ }
850
+ }
851
+ _dispatch(name, detail) {
852
+ this.dispatchEvent(new CustomEvent(name, {
853
+ detail,
854
+ bubbles: true,
855
+ composed: true
856
+ }));
857
+ }
858
+ _openSearch() {
859
+ this._searchVisible = true;
860
+ this.updateComplete.then(() => this._searchInputEl?.focus());
861
+ }
862
+ _closeSearch() {
863
+ this._searchVisible = false;
864
+ this._searchQuery = ``;
865
+ this.#search?.clearQuery().then(() => this.#recomputeGeometry());
866
+ this.focus();
867
+ }
868
+ _activateItem(item) {
869
+ if (this.interactionMode !== `manual`) this.selection.replace(item);
870
+ this._dispatch(`list-activate`, { item });
871
+ }
872
+ static {
873
+ this.styles = [
874
+ themeFallbacks,
875
+ scrollFadeStyles(`x`),
876
+ css`
877
+ :host {
878
+ display: block;
879
+ position: relative;
880
+ background: var(--detail-list-bg, var(--surface-3));
881
+ color: var(--detail-list-text, var(--surface-3-text));
882
+ border-radius: var(--detail-list-radius, var(--radius-s));
883
+ border: 1px solid var(--detail-list-border, var(--border));
884
+ width: 100%;
885
+ height: 100%;
886
+ overflow: hidden;
887
+ box-sizing: border-box;
888
+ outline: none;
889
+ --_detail-list-item-padding:
890
+ var(--ixfx-detail-list-item-padding, var(--detail-list-item-padding, 0 var(--space-xs, 3px)));
891
+ }
892
+
893
+ :host(:focus-visible) {
894
+ outline: 2px solid var(--accent);
895
+ outline-offset: 2px;
896
+ }
897
+
898
+ .container {
899
+ position: relative;
900
+ width: 100%;
901
+ height: 100%;
902
+ }
903
+
904
+ ul {
905
+ list-style: none;
906
+ margin: 0;
907
+ padding: 0;
908
+ /* keep the last column clear of the scrollbar / right edge */
909
+ padding-inline-end: calc(var(--detail-list-column-gap, 8px) + 4px);
910
+ width: 100%;
911
+ height: 100%;
912
+ display: flex;
913
+ flex-flow: column wrap;
914
+ align-content: flex-start;
915
+ column-gap: var(--detail-list-column-gap, 8px);
916
+ overflow-x: auto;
917
+ overflow-y: hidden;
918
+ box-sizing: border-box;
919
+ /* Scrollbar theming: consumers override these CSS variables to control
920
+ the native scrollbar (scrollbar-width, scrollbar-gutter, scrollbar-color). */
921
+ scrollbar-width: var(--detail-list-scrollbar-width, auto);
922
+ scrollbar-gutter: var(--detail-list-scrollbar-gutter, auto);
923
+ scrollbar-color: var(--detail-list-scrollbar-color, auto);
924
+ }
925
+
926
+ ::slotted([hidden]) {
927
+ display: none !important;
928
+ }
929
+
930
+ ::slotted(li) {
931
+ box-sizing: border-box;
932
+ display: flex;
933
+ align-items: center;
934
+ flex: none;
935
+ width: var(--detail-list-column-width, 220px);
936
+ height: var(--detail-list-item-height, 28px);
937
+ padding: var(--_detail-list-item-padding);
938
+ cursor: pointer;
939
+ user-select: none;
940
+ transition: background var(--transition, 0.1s ease-out),
941
+ outline-color var(--transition, 0.1s ease-out);
942
+ outline: 1px solid transparent;
943
+ }
944
+
945
+ /* In checked mode, only the checkbox column is clickable; the row body
946
+ gets a default cursor so it doesn't suggest otherwise. */
947
+ :host([interaction-mode="checked"]) ::slotted(li) {
948
+ cursor: default;
949
+ }
950
+
951
+ ::slotted([data-tickled]) {
952
+ background: var(--item-bg-tickled);
953
+ outline: var(--item-border-tickled);
954
+ }
955
+
956
+ ::slotted([data-selected]) {
957
+ background: var(--item-bg-selected);
958
+ color: var(--item-text-selected);
959
+ outline: 1px solid transparent;
960
+ }
961
+
962
+ ::slotted([data-selected][data-tickled]) {
963
+ background: var(--item-bg-selected-tickled);
964
+ outline: 2px dashed var(--surface-3);
965
+ outline-offset: -2px;
966
+ }
967
+
968
+ /* checked mode: the injected checkbox communicates selection — suppress bg */
969
+ :host([interaction-mode="checked"]) ::slotted([data-selected]) {
970
+ background: transparent;
971
+ color: inherit;
972
+ outline: 1px solid transparent;
973
+ }
974
+
975
+ :host([interaction-mode="checked"]) ::slotted([data-selected][data-tickled]) {
976
+ background: var(--item-bg-tickled);
977
+ color: inherit;
978
+ outline: var(--item-border-tickled);
979
+ }
980
+
981
+ .loading {
982
+ position: absolute;
983
+ bottom: 8px;
984
+ right: 8px;
985
+ z-index: 10;
986
+ background: var(--surface-4, #2a2a2a);
987
+ border: 1px solid var(--border, #444);
988
+ border-radius: var(--radius-m, 4px);
989
+ padding: 2px 8px;
990
+ font-size: var(--text-s, 11px);
991
+ color: var(--surface-muted-text, #888);
992
+ }
993
+
994
+ .loading[hidden] {
995
+ display: none;
996
+ }
997
+
998
+ .search-overlay {
999
+ position: absolute;
1000
+ top: 8px;
1001
+ right: 8px;
1002
+ z-index: 10;
1003
+ display: flex;
1004
+ align-items: center;
1005
+ gap: 4px;
1006
+ background: var(--surface-4, #2a2a2a);
1007
+ border: 1px solid var(--border, #444);
1008
+ border-radius: var(--radius-m, 4px);
1009
+ padding: 4px 6px;
1010
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
1011
+ }
1012
+
1013
+ .search-overlay[hidden] {
1014
+ display: none;
1015
+ }
1016
+
1017
+ .search-input {
1018
+ background: transparent;
1019
+ border: none;
1020
+ outline: none;
1021
+ color: inherit;
1022
+ font-size: var(--text-m, 0.875em);
1023
+ min-width: 140px;
1024
+ }
1025
+
1026
+ .search-close {
1027
+ background: transparent;
1028
+ border: none;
1029
+ color: var(--surface-muted-text, #888);
1030
+ cursor: pointer;
1031
+ font-size: 1.1em;
1032
+ line-height: 1;
1033
+ padding: 0 2px;
1034
+ }
1035
+
1036
+ .search-close:hover {
1037
+ color: inherit;
1038
+ }
1039
+ `
1040
+ ];
1041
+ }
1042
+ render() {
1043
+ const loading = this.#data.loading;
1044
+ const progress = this.#data.progress;
1045
+ return html`
1046
+ <div class="container">
1047
+ <ul part="list" class="scroll-fade-x">
1048
+ <slot></slot>
1049
+ </ul>
1050
+ <div class="loading" part="loading" ?hidden=${!loading}>
1051
+ ${loading ? progress !== void 0 ? `${Math.round(progress * 100)}%` : `loading…` : nothing}
1052
+ </div>
1053
+ <div
1054
+ class="search-overlay"
1055
+ part="search-overlay"
1056
+ ?hidden=${!this._searchVisible}
1057
+ >
1058
+ <input
1059
+ type="text"
1060
+ class="search-input"
1061
+ placeholder="Search…"
1062
+ .value=${this._searchQuery}
1063
+ @input=${this._onSearchInput}
1064
+ @keydown=${this._onSearchKeydown}
1065
+ />
1066
+ <button class="search-close" @click=${this._closeSearch} title="Close search (Esc)">×</button>
1067
+ </div>
1068
+ </div>
1069
+ `;
1070
+ }
1071
+ };
1072
+ __decorate([property({
1073
+ type: String,
1074
+ attribute: `selection-mode`
1075
+ })], DetailListElement.prototype, "selectionMode", void 0);
1076
+ __decorate([property({
1077
+ type: String,
1078
+ attribute: `interaction-mode`
1079
+ })], DetailListElement.prototype, "interactionMode", void 0);
1080
+ __decorate([property({ type: String })], DetailListElement.prototype, "sort", void 0);
1081
+ __decorate([property({
1082
+ type: String,
1083
+ attribute: `sort-by`
1084
+ })], DetailListElement.prototype, "sortBy", void 0);
1085
+ __decorate([property({ attribute: false })], DetailListElement.prototype, "sortComparator", void 0);
1086
+ __decorate([property({ attribute: false })], DetailListElement.prototype, "items", void 0);
1087
+ __decorate([property({ attribute: false })], DetailListElement.prototype, "provider", void 0);
1088
+ __decorate([property({
1089
+ type: String,
1090
+ attribute: `display-property`
1091
+ })], DetailListElement.prototype, "displayProperty", void 0);
1092
+ __decorate([property({ attribute: false })], DetailListElement.prototype, "formatter", void 0);
1093
+ __decorate([property({
1094
+ type: String,
1095
+ attribute: `icon-property`
1096
+ })], DetailListElement.prototype, "iconProperty", void 0);
1097
+ __decorate([property({
1098
+ type: String,
1099
+ attribute: `key-property`
1100
+ })], DetailListElement.prototype, "keyProperty", void 0);
1101
+ __decorate([property({ attribute: false })], DetailListElement.prototype, "filterPredicate", void 0);
1102
+ __decorate([property({
1103
+ type: String,
1104
+ attribute: `item-wrap`
1105
+ })], DetailListElement.prototype, "itemWrap", void 0);
1106
+ __decorate([property({ attribute: false })], DetailListElement.prototype, "checkboxRenderer", void 0);
1107
+ __decorate([state()], DetailListElement.prototype, "_searchVisible", void 0);
1108
+ __decorate([state()], DetailListElement.prototype, "_searchQuery", void 0);
1109
+ __decorate([query(`.search-input`)], DetailListElement.prototype, "_searchInputEl", void 0);
1110
+ __decorate([query(`ul`)], DetailListElement.prototype, "_listEl", void 0);
1111
+ DetailListElement = __decorate([safeCustomElement(`ixfx-detail-list`)], DetailListElement);
1112
+ //#endregion
1113
+ export { DetailListElement };
1114
+
1115
+ //# sourceMappingURL=detail-list.js.map