@ixfx/components 0.13.2 → 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 (76) hide show
  1. package/bundle/index.d.ts +97 -14
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +2183 -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-Ohxqsy0g.d.ts → flat-list-Y98IqH00.d.ts} +2 -2
  18. package/dist/{flat-list-Ohxqsy0g.d.ts.map → flat-list-Y98IqH00.d.ts.map} +1 -1
  19. package/dist/flat-list.d.ts +1 -1
  20. package/dist/flat-list.js +5 -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/grid-list.md +81 -4
  66. package/docs-user/index.json +1 -1
  67. package/docs-user/miller.md +59 -3
  68. package/docs-user/tree.md +63 -2
  69. package/docs-user/vertical-list.md +9 -1
  70. package/package.json +6 -1
  71. package/dist/detail-list.js.map +0 -1
  72. package/dist/grid-list-DjIl910A.js.map +0 -1
  73. package/dist/incr-search-D91M4Ozq.js.map +0 -1
  74. package/dist/tab-list-v8mKX7Fu.d.ts.map +0 -1
  75. package/dist/tree-DGveys4K.js.map +0 -1
  76. package/dist/tree.d.ts.map +0 -1
@@ -2,7 +2,7 @@ import { t as TickledController } from "./tickled-controller-D-HZymaZ.js";
2
2
  import { n as tickledStyles, t as tickledItemStyles } from "./tickled-styles-BrAHEj1q.js";
3
3
  import { t as themeFallbacks } from "./fallbacks-DQ_5OsmR.js";
4
4
  import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
5
- import { c as createElementListSearchAdapter, d as createDataListSearchAdapter, i as ListSearchController, n as listSearchStyles, o as registerListSearchCommands, r as LIST_SEARCH_MODES, u as keyForElement } from "./incr-search-D91M4Ozq.js";
5
+ import { c as createElementListSearchAdapter, d as createDataListSearchAdapter, i as ListSearchController, n as listSearchStyles, o as registerListSearchCommands, r as LIST_SEARCH_MODES, u as keyForElement } from "./incr-search-JIhwB49y.js";
6
6
  import { i as resolveGroupKey, r as partitionByGroup } from "./grouping-RYPnHkrS.js";
7
7
  import { t as ListNativeDragController } from "./list-native-drag-controller-B9xO0XWn.js";
8
8
  import { r as ListDragSelectController, t as ListSelectionController } from "./list-selection-controller-BeE70lbB.js";
@@ -401,16 +401,24 @@ function warnDuplicateKeys(keys) {
401
401
  }
402
402
  if (dupes.size > 0) console.warn(`ixfx-grid-list: duplicate item key(s) ${[...dupes].map((k) => `"${k}"`).join(`, `)} — the first occurrence wins for key-based operations.`);
403
403
  }
404
+ /** Key → first effective index, built from `keys` (first occurrence wins on duplicates). */
405
+ function indexByKey(keys) {
406
+ const index = /* @__PURE__ */ new Map();
407
+ keys.forEach((k, i) => {
408
+ if (!index.has(k)) index.set(k, i);
409
+ });
410
+ return index;
411
+ }
404
412
  var ElementGridListSource = class {
405
413
  #host;
406
414
  #items = [];
407
- #slotNames = /* @__PURE__ */ new WeakMap();
408
415
  #syntheticKeys = /* @__PURE__ */ new WeakMap();
409
416
  #seq = 0;
410
417
  #predicate;
411
418
  #searchKeys;
412
419
  #effectiveIndices = [];
413
420
  #keysCache;
421
+ #indexCache;
414
422
  #grouping;
415
423
  #groups = [];
416
424
  #groupByKey = /* @__PURE__ */ new Map();
@@ -429,7 +437,8 @@ var ElementGridListSource = class {
429
437
  return eff === void 0 ? `` : this.#itemKey(this.#items[eff]);
430
438
  }
431
439
  indexOfKey(key) {
432
- return this.#effectiveIndices.findIndex((i) => this.#itemKey(this.#items[i]) === key);
440
+ this.#indexCache ??= indexByKey(this.keys());
441
+ return this.#indexCache.get(key) ?? -1;
433
442
  }
434
443
  keys() {
435
444
  this.#keysCache ??= this.#effectiveIndices.map((i) => this.#itemKey(this.#items[i]));
@@ -452,11 +461,11 @@ var ElementGridListSource = class {
452
461
  const eff = this.#effectiveIndices[index];
453
462
  return eff !== void 0 && !this.#items[eff].hasAttribute(`not-checked`);
454
463
  }
455
- renderCellContent(index) {
456
- const eff = this.#effectiveIndices[index];
457
- if (eff === void 0) return void 0;
458
- return html`<slot name=${this.slotNameFor(this.#items[eff])}></slot>`;
459
- }
464
+ /**
465
+ * Element mode cells project their item through a per-cell `<slot>` that
466
+ * `GridListElement` renders and assigns itself (see its `#applyCellSlots`).
467
+ */
468
+ renderCellContent(_index) {}
460
469
  iconAt(_index) {}
461
470
  iconColorAt(_index) {}
462
471
  textColorAt(_index) {}
@@ -478,10 +487,6 @@ var ElementGridListSource = class {
478
487
  }
479
488
  refresh() {
480
489
  this.#items = Array.from(this.#host.querySelectorAll(ITEM_SELECTOR$1)).filter((el) => !el.hidden);
481
- for (const el of this.#items) {
482
- const name = this.slotNameFor(el);
483
- if (el.getAttribute(`slot`) !== name) el.setAttribute(`slot`, name);
484
- }
485
490
  warnDuplicateKeys(this.#items.map((el) => this.#itemKey(el)));
486
491
  this.#recomputeEffective();
487
492
  }
@@ -493,16 +498,6 @@ var ElementGridListSource = class {
493
498
  rawElementAt(index) {
494
499
  return this.#items[index];
495
500
  }
496
- /** Stable per-element `<slot>` name. */
497
- slotNameFor(el) {
498
- let name = this.#slotNames.get(el);
499
- if (name === void 0) {
500
- this.#seq += 1;
501
- name = `gl-item-${this.#seq}`;
502
- this.#slotNames.set(el, name);
503
- }
504
- return name;
505
- }
506
501
  resolveKeyToElement(key) {
507
502
  if (isSyntheticKey(key)) return this.#items.find((el) => this.#syntheticKeys.get(el) === key);
508
503
  return this.#items.find((el) => keyForElement(el) === key);
@@ -520,6 +515,7 @@ var ElementGridListSource = class {
520
515
  }
521
516
  this.#applyGroupingTo(effective);
522
517
  this.#keysCache = void 0;
518
+ this.#indexCache = void 0;
523
519
  }
524
520
  #applyGroupingTo(effective) {
525
521
  if (this.#grouping === void 0) {
@@ -555,6 +551,7 @@ var DataGridListSource = class {
555
551
  #searchKeys;
556
552
  #effectiveIndices = [];
557
553
  #keysCache;
554
+ #indexCache;
558
555
  #grouping;
559
556
  #groups = [];
560
557
  #groupByKey = /* @__PURE__ */ new Map();
@@ -575,7 +572,8 @@ var DataGridListSource = class {
575
572
  return eff === void 0 ? `` : this.#adapter.key(this.#items[eff], eff);
576
573
  }
577
574
  indexOfKey(key) {
578
- return this.#effectiveIndices.findIndex((i) => this.#adapter.key(this.#items[i], i) === key);
575
+ this.#indexCache ??= indexByKey(this.keys());
576
+ return this.#indexCache.get(key) ?? -1;
579
577
  }
580
578
  keys() {
581
579
  this.#keysCache ??= this.#effectiveIndices.map((i) => this.#adapter.key(this.#items[i], i));
@@ -668,6 +666,7 @@ var DataGridListSource = class {
668
666
  this.#groupByKey = grouped.groupByKey;
669
667
  }
670
668
  this.#keysCache = void 0;
669
+ this.#indexCache = void 0;
671
670
  }
672
671
  };
673
672
  //#endregion
@@ -694,6 +693,15 @@ function sameKeys(a, b) {
694
693
  for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
695
694
  return true;
696
695
  }
696
+ /**
697
+ * Set `attr` on exactly the elements in `now`, removing it from those in
698
+ * `previous` that no longer qualify. Returns the new marked set.
699
+ */
700
+ function syncMarker(attr, previous, now) {
701
+ for (const el of previous) if (!now.has(el)) el.removeAttribute(attr);
702
+ for (const el of now) if (!el.hasAttribute(attr)) el.setAttribute(attr, ``);
703
+ return now;
704
+ }
697
705
  let GridListElement = class GridListElement extends LitElement {
698
706
  constructor(..._args) {
699
707
  super(..._args);
@@ -728,6 +736,11 @@ let GridListElement = class GridListElement extends LitElement {
728
736
  this.#snapTimer = 0;
729
737
  this.#items = [];
730
738
  this.#itemWrap = `stop`;
739
+ this.#cellIds = /* @__PURE__ */ new Map();
740
+ this.#nextCellId = 0;
741
+ this.#recycledKeys = /* @__PURE__ */ new Set();
742
+ this.#windowSlots = [];
743
+ this.#slottedItems = /* @__PURE__ */ new Map();
731
744
  this.#entering = /* @__PURE__ */ new Set();
732
745
  this.#reflowTimer = 0;
733
746
  this.#sourceElement = new ElementGridListSource(this);
@@ -851,6 +864,8 @@ let GridListElement = class GridListElement extends LitElement {
851
864
  }
852
865
  });
853
866
  this.#groupByChanged = false;
867
+ this.#markedTickled = /* @__PURE__ */ new Set();
868
+ this.#markedSelected = /* @__PURE__ */ new Set();
854
869
  this.#selectedSnapshot = /* @__PURE__ */ new Set();
855
870
  this.#onScroll = (e) => {
856
871
  const el = e.target;
@@ -1340,6 +1355,14 @@ let GridListElement = class GridListElement extends LitElement {
1340
1355
  .cell.entering {
1341
1356
  animation: ixfx-list-item-in var(--grid-list-item-move-duration, 200ms) ease both;
1342
1357
  }
1358
+ /* A recycled cell has just been handed from one item to another (see
1359
+ #assignCellIds); it must not animate from the previous item's state
1360
+ or position. */
1361
+ .cell.recycled,
1362
+ .cell.recycled > .thumb,
1363
+ .cell.recycled > .thumb::after {
1364
+ transition: none !important;
1365
+ }
1343
1366
  @media (prefers-reduced-motion: reduce) {
1344
1367
  :host([reflowing]) .group-header { transition: none; }
1345
1368
  :host([reflowing]) .cell { transition: background var(--transition), border-color var(--transition); }
@@ -1371,11 +1394,39 @@ let GridListElement = class GridListElement extends LitElement {
1371
1394
  #viewportRef;
1372
1395
  #resizeObserver;
1373
1396
  #mutationObserver;
1397
+ /**
1398
+ * Watches item (direct child) attributes that change the item set: `hidden`
1399
+ * drops an item from layout, `data-key` / `data-value` change its identity.
1400
+ * Separate from {@link #mutationObserver} because attribute observation of
1401
+ * children needs `subtree`, which must not apply to the child list.
1402
+ */
1403
+ #itemAttrObserver;
1374
1404
  #scrollRaf;
1375
1405
  /** Debounce handle for {@link snapToItem} — fires once scrolling settles. */
1376
1406
  #snapTimer;
1377
1407
  #items;
1378
1408
  #itemWrap;
1409
+ /**
1410
+ * Cell recycling. Each rendered key holds a stable cell id, and `repeat` is
1411
+ * keyed by that id rather than the item key, so a cell scrolled out of the
1412
+ * window hands its DOM to an item scrolling in instead of being destroyed and
1413
+ * recreated. Item-keyed `repeat` gets steadily slower in Chrome when every key
1414
+ * in the window is new (a scrollbar drag / fling), and recreating ~100 cells a
1415
+ * frame is costly in any case. See {@link #assignCellIds}.
1416
+ */
1417
+ #cellIds;
1418
+ #nextCellId;
1419
+ /** Keys whose cell was taken over from another item in the latest render. */
1420
+ #recycledKeys;
1421
+ /**
1422
+ * Element mode: each cell has a fixed `<slot name="gl-cell-<id>">`, and the
1423
+ * light-DOM item it shows is pointed at it via its `slot` attribute. Moving
1424
+ * that attribute is far cheaper than renaming slots, which makes the browser
1425
+ * re-match every light-DOM child. `#windowSlots` is the assignment the latest
1426
+ * render wants (applied in `updated()`); `#slottedItems` is what's applied.
1427
+ */
1428
+ #windowSlots;
1429
+ #slottedItems;
1379
1430
  /** Items added mid-list that are still playing their enter animation. */
1380
1431
  #entering;
1381
1432
  /** Timer that ends the `[reflowing]` window opened by a mid-list insertion. */
@@ -1563,9 +1614,15 @@ let GridListElement = class GridListElement extends LitElement {
1563
1614
  this.addEventListener(`pointermove`, this.#onHostPointerMove, { passive: true });
1564
1615
  this.addEventListener(`pointerleave`, this.#onHostPointerLeave);
1565
1616
  this.#mutationObserver ??= new MutationObserver((records) => this.#onChildrenChanged(records));
1566
- this.#mutationObserver.observe(this, {
1567
- childList: true,
1568
- attributeFilter: [`hidden`]
1617
+ this.#mutationObserver.observe(this, { childList: true });
1618
+ this.#itemAttrObserver ??= new MutationObserver((records) => this.#onItemAttributesChanged(records));
1619
+ this.#itemAttrObserver.observe(this, {
1620
+ subtree: true,
1621
+ attributeFilter: [
1622
+ `hidden`,
1623
+ `data-key`,
1624
+ `data-value`
1625
+ ]
1569
1626
  });
1570
1627
  this.#searchRegistry ??= createCommandRegistry();
1571
1628
  this.#ensureSearch();
@@ -1581,6 +1638,7 @@ let GridListElement = class GridListElement extends LitElement {
1581
1638
  this.#resizeObserver?.disconnect();
1582
1639
  this.#resizeObserver = void 0;
1583
1640
  this.#mutationObserver?.disconnect();
1641
+ this.#itemAttrObserver?.disconnect();
1584
1642
  this.#cancelScrollRaf();
1585
1643
  this.#cancelSnapTimer();
1586
1644
  this.#unregisterSearchCommands?.();
@@ -1592,6 +1650,10 @@ let GridListElement = class GridListElement extends LitElement {
1592
1650
  }
1593
1651
  if (this.#nativeDragging || this.#nativeDrag.isDragging) this.#clearNativeDragState();
1594
1652
  }
1653
+ /** Only attribute changes on items themselves matter, not on their content. */
1654
+ #onItemAttributesChanged(records) {
1655
+ if (records.some((r) => r.target.parentNode === this)) this.#onChildrenChanged([]);
1656
+ }
1595
1657
  #onChildrenChanged(records) {
1596
1658
  const previousLength = this.#items.length;
1597
1659
  this.#syncItems();
@@ -1608,7 +1670,7 @@ let GridListElement = class GridListElement extends LitElement {
1608
1670
  const tickledId = this.tickled.tickledId;
1609
1671
  if (tickledId && gone.includes(tickledId)) this.tickled.focus(void 0);
1610
1672
  }
1611
- this._syncItemStates();
1673
+ this._syncItemStates(true);
1612
1674
  this.#noteChildMutation(previousLength, records);
1613
1675
  this.requestUpdate();
1614
1676
  this.updateComplete.then(() => this.#emitViewportChange());
@@ -1680,6 +1742,11 @@ let GridListElement = class GridListElement extends LitElement {
1680
1742
  const w = entry.contentRect.width;
1681
1743
  const h = entry.contentRect.height;
1682
1744
  if (w <= 0 || h <= 0) return;
1745
+ if (w === this._viewportWidth && h === this._viewportHeight) continue;
1746
+ const horizontal = this.orientation === `row`;
1747
+ const prevOffset = horizontal ? this._scrollLeft : this._scrollTop;
1748
+ const prevExtent = horizontal ? this._viewportWidth : this._viewportHeight;
1749
+ if (prevExtent > 0 && prevOffset > 1) this.#captureCentreAnchor(prevOffset, prevExtent, this.itemSize, this.gap, this.captionStyle, this.captionHeight, this.orientation);
1683
1750
  this._viewportWidth = w;
1684
1751
  this._viewportHeight = h;
1685
1752
  }
@@ -1841,7 +1908,11 @@ let GridListElement = class GridListElement extends LitElement {
1841
1908
  }
1842
1909
  updated(changed) {
1843
1910
  super.updated(changed);
1844
- this.#syncItems();
1911
+ const pending = this.#mutationObserver?.takeRecords();
1912
+ const pendingAttrs = this.#itemAttrObserver?.takeRecords();
1913
+ if (pending !== void 0 && pending.length > 0) this.#onChildrenChanged(pending);
1914
+ else if (pendingAttrs !== void 0 && pendingAttrs.length > 0) this.#onItemAttributesChanged(pendingAttrs);
1915
+ if (this.#source instanceof ElementGridListSource) this.#applyCellSlots();
1845
1916
  if (changed.has(`selectionMode`) || changed.has(`interactionMode`)) {
1846
1917
  this.selection.clearAnchor();
1847
1918
  this.#syncAriaMultiselectable();
@@ -2193,13 +2264,36 @@ let GridListElement = class GridListElement extends LitElement {
2193
2264
  this._scrollTop = next;
2194
2265
  }
2195
2266
  }
2196
- _syncItemStates() {
2267
+ /** Light-DOM items currently carrying `data-tickled` / `data-selected`. */
2268
+ #markedTickled;
2269
+ #markedSelected;
2270
+ /**
2271
+ * Mirror cursor / selection onto the light-DOM items as `data-tickled` /
2272
+ * `data-selected`. Only the items whose state changed are touched, so a
2273
+ * cursor move costs O(selection), not O(items). `full` re-checks every item —
2274
+ * used when the child list changes, so markers set before the item was
2275
+ * ours (or left on a removed item) are corrected.
2276
+ */
2277
+ _syncItemStates(full = false) {
2197
2278
  const selected = this.selection.selectedKeys;
2198
- for (const el of this.#items) {
2199
- const key = keyForElement(el);
2200
- el.toggleAttribute(`data-tickled`, key !== void 0 && this.tickled.isTickled(key));
2201
- el.toggleAttribute(`data-selected`, key !== void 0 && selected.has(key));
2279
+ const tickledId = this.tickled.tickledId;
2280
+ const elementFor = (key) => {
2281
+ if (isSyntheticKey(key)) return void 0;
2282
+ const index = this.#source.indexOfKey(key);
2283
+ return index < 0 ? void 0 : this.#items[index];
2284
+ };
2285
+ const tickledEl = tickledId === void 0 ? void 0 : elementFor(tickledId);
2286
+ const selectedEls = /* @__PURE__ */ new Set();
2287
+ for (const key of selected) {
2288
+ const el = elementFor(key);
2289
+ if (el) selectedEls.add(el);
2202
2290
  }
2291
+ if (full) for (const el of this.#items) {
2292
+ this.#markedTickled.add(el);
2293
+ this.#markedSelected.add(el);
2294
+ }
2295
+ this.#markedTickled = syncMarker(`data-tickled`, this.#markedTickled, tickledEl ? /* @__PURE__ */ new Set([tickledEl]) : /* @__PURE__ */ new Set());
2296
+ this.#markedSelected = syncMarker(`data-selected`, this.#markedSelected, selectedEls);
2203
2297
  this.setAttribute(`data-input-mode`, this.tickled.inputMode);
2204
2298
  }
2205
2299
  #emitSelect(selected, previous) {
@@ -2269,8 +2363,57 @@ let GridListElement = class GridListElement extends LitElement {
2269
2363
  `;
2270
2364
  }
2271
2365
  #renderDataCells(win) {
2366
+ const slice = this.#windowSlice(win);
2367
+ return repeat(slice, (index) => this.#cellIds.get(this.#source.keyAt(index)), (index) => this.#renderDataCell(index, win));
2368
+ }
2369
+ #cellSlotName(key) {
2370
+ return `gl-cell-${this.#cellIds.get(key)}`;
2371
+ }
2372
+ /**
2373
+ * Element mode: point each rendered item's `slot` at its cell, and clear it on
2374
+ * items that left the window (or the DOM) so they project nowhere.
2375
+ */
2376
+ #applyCellSlots() {
2377
+ const next = /* @__PURE__ */ new Map();
2378
+ for (const [el, name] of this.#windowSlots) {
2379
+ next.set(el, name);
2380
+ if (el.getAttribute(`slot`) !== name) el.setAttribute(`slot`, name);
2381
+ }
2382
+ for (const el of this.#slottedItems.keys()) if (!next.has(el)) el.removeAttribute(`slot`);
2383
+ this.#slottedItems = next;
2384
+ }
2385
+ /** Effective indices in the rendered window, with cell ids assigned to their keys. */
2386
+ #windowSlice(win) {
2272
2387
  const slice = win.ranges.flatMap(([start, end]) => Array.from({ length: end - start }, (_, i) => start + i));
2273
- return repeat(slice, (index) => this.#source.keyAt(index), (index) => this.#renderDataCell(index, win));
2388
+ this.#assignCellIds(slice.map((index) => this.#source.keyAt(index)));
2389
+ return slice;
2390
+ }
2391
+ /**
2392
+ * Give every key in the window a cell id: keys already rendered keep theirs;
2393
+ * new keys take the ids freed by keys that left the window (recycling that
2394
+ * cell's DOM), and only mint a fresh id when none is free.
2395
+ */
2396
+ #assignCellIds(keys) {
2397
+ const inWindow = new Set(keys);
2398
+ const free = [];
2399
+ for (const [key, id] of this.#cellIds) if (!inWindow.has(key)) free.push(id);
2400
+ const next = /* @__PURE__ */ new Map();
2401
+ for (const key of keys) {
2402
+ const id = this.#cellIds.get(key);
2403
+ if (id !== void 0) next.set(key, id);
2404
+ }
2405
+ const recycled = /* @__PURE__ */ new Set();
2406
+ for (const key of keys) {
2407
+ if (next.has(key)) continue;
2408
+ const id = free.pop();
2409
+ if (id === void 0) next.set(key, this.#nextCellId++);
2410
+ else {
2411
+ next.set(key, id);
2412
+ recycled.add(key);
2413
+ }
2414
+ }
2415
+ this.#cellIds = next;
2416
+ this.#recycledKeys = recycled;
2274
2417
  }
2275
2418
  #renderDataCell(index, win) {
2276
2419
  const key = this.#source.keyAt(index);
@@ -2290,10 +2433,12 @@ let GridListElement = class GridListElement extends LitElement {
2290
2433
  const icon = appearanceAbsent(rawIcon) ? void 0 : rawIcon;
2291
2434
  return html`
2292
2435
  <div
2293
- class="cell${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}${marquee ? ` marquee` : ``}${this.#nativeDragging && key === this.#dragOriginKey ? ` dragging-origin` : ``}"
2436
+ class="cell${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}${marquee ? ` marquee` : ``}${this.#recycledKeys.has(key) ? ` recycled` : ``}${this.#nativeDragging && key === this.#dragOriginKey ? ` dragging-origin` : ``}"
2294
2437
  part="item${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}"
2295
2438
  role="option"
2296
2439
  aria-selected=${selected ? `true` : `false`}
2440
+ aria-setsize=${this.#source.count}
2441
+ aria-posinset=${index + 1}
2297
2442
  style="width:${this.itemSize}px;height:${cellHeight}px;transform:translate(${x}px,${y}px)"
2298
2443
  data-index=${index}
2299
2444
  data-key=${key}
@@ -2370,8 +2515,9 @@ let GridListElement = class GridListElement extends LitElement {
2370
2515
  current.startLoop?.();
2371
2516
  }
2372
2517
  #renderElementCells(win) {
2373
- const slice = win.ranges.flatMap(([start, end]) => Array.from({ length: end - start }, (_, i) => start + i));
2374
- return repeat(slice, (index) => this.#source.keyAt(index), (index) => this.#renderCell(this.#items[index], index, win));
2518
+ const slice = this.#windowSlice(win);
2519
+ this.#windowSlots = slice.map((index) => [this.#items[index], this.#cellSlotName(this.#source.keyAt(index))]);
2520
+ return repeat(slice, (index) => this.#cellIds.get(this.#source.keyAt(index)), (index) => this.#renderCell(this.#items[index], index, win));
2375
2521
  }
2376
2522
  #renderHeaders(win) {
2377
2523
  if (win.headers.length === 0) return nothing;
@@ -2380,6 +2526,7 @@ let GridListElement = class GridListElement extends LitElement {
2380
2526
  <ixfx-list-group-header
2381
2527
  class="group-header"
2382
2528
  part="group-header"
2529
+ role="presentation"
2383
2530
  data-group-key=${h.key}
2384
2531
  label=${h.label}
2385
2532
  count=${h.count}
@@ -2408,10 +2555,12 @@ let GridListElement = class GridListElement extends LitElement {
2408
2555
  const captionCss = separate ? `height:${Math.max(0, this.captionHeight - 4)}px` : style === `under` ? `height:${this.captionHeight}px` : ``;
2409
2556
  return html`
2410
2557
  <div
2411
- class="cell${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}${entering ? ` entering` : ``}${marquee ? ` marquee` : ``}${this.#nativeDragging && key === this.#dragOriginKey ? ` dragging-origin` : ``}"
2558
+ class="cell${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}${entering ? ` entering` : ``}${marquee ? ` marquee` : ``}${this.#recycledKeys.has(key) ? ` recycled` : ``}${this.#nativeDragging && key === this.#dragOriginKey ? ` dragging-origin` : ``}"
2412
2559
  part="item${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}"
2413
2560
  role="option"
2414
2561
  aria-selected=${selected ? `true` : `false`}
2562
+ aria-setsize=${this.#source.count}
2563
+ aria-posinset=${index + 1}
2415
2564
  style="width:${this.itemSize}px;height:${cellHeight}px;transform:translate(${x}px,${y}px)"
2416
2565
  data-index=${index}
2417
2566
  data-key=${key}
@@ -2424,7 +2573,7 @@ let GridListElement = class GridListElement extends LitElement {
2424
2573
  @pointerenter=${() => this.#handleCellPointerEnter(key)}
2425
2574
  >
2426
2575
  <div class="thumb" part="thumb" style=${thumbStyle}>
2427
- ${this.#source.renderCellContent(index)}
2576
+ <slot name=${this.#cellSlotName(key)}></slot>
2428
2577
  </div>
2429
2578
  ${showCaption ? this.#renderCaption(caption, captionCss, tickled) : ``}
2430
2579
  </div>
@@ -2660,4 +2809,4 @@ GridListElement = __decorate([safeCustomElement(`ixfx-grid-list`)], GridListElem
2660
2809
  //#endregion
2661
2810
  export { GridListElement as t };
2662
2811
 
2663
- //# sourceMappingURL=grid-list-DjIl910A.js.map
2812
+ //# sourceMappingURL=grid-list-DeqAd9-h.js.map