@ixfx/components 0.11.7 → 0.11.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/bundle/index.d.ts +153 -2
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +1129 -164
  4. package/bundle/index.js.map +1 -1
  5. package/dist/{detail-list-CgkOHYOa.d.ts → detail-list-lE_5UWVl.d.ts} +34 -3
  6. package/dist/detail-list-lE_5UWVl.d.ts.map +1 -0
  7. package/dist/detail-list.d.ts +3 -2
  8. package/dist/detail-list.js +271 -6
  9. package/dist/detail-list.js.map +1 -1
  10. package/dist/{grid-list-D6AfWfwn.d.ts → grid-list-Bc-RkDoO.d.ts} +42 -1
  11. package/dist/grid-list-Bc-RkDoO.d.ts.map +1 -0
  12. package/dist/{grid-list-3fGhdaFA.js → grid-list-Bwaf61is.js} +659 -158
  13. package/dist/grid-list-Bwaf61is.js.map +1 -0
  14. package/dist/grid-list.d.ts +3 -2
  15. package/dist/grid-list.js +2 -1
  16. package/dist/grouping-CUKgmkMm.d.ts +63 -0
  17. package/dist/grouping-CUKgmkMm.d.ts.map +1 -0
  18. package/dist/grouping-RYPnHkrS.js +53 -0
  19. package/dist/grouping-RYPnHkrS.js.map +1 -0
  20. package/dist/index.d.ts +5 -3
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +5 -3
  23. package/dist/index.js.map +1 -1
  24. package/dist/list-group-header.d.ts +26 -0
  25. package/dist/list-group-header.d.ts.map +1 -0
  26. package/dist/list-group-header.js +122 -0
  27. package/dist/list-group-header.js.map +1 -0
  28. package/dist/{list-selection-controller-SA4piSBi.js → list-selection-controller-BeE70lbB.js} +42 -4
  29. package/dist/list-selection-controller-BeE70lbB.js.map +1 -0
  30. package/dist/vertical-list.js +1 -1
  31. package/docs-user/README.md +1 -0
  32. package/docs-user/detail-list.md +28 -0
  33. package/docs-user/grid-list.md +35 -3
  34. package/docs-user/index.json +7 -2
  35. package/docs-user/list-group-header.md +14 -0
  36. package/docs-user/llms.txt +1 -0
  37. package/llms.txt +1 -0
  38. package/package.json +5 -1
  39. package/dist/detail-list-CgkOHYOa.d.ts.map +0 -1
  40. package/dist/grid-list-3fGhdaFA.js.map +0 -1
  41. package/dist/grid-list-D6AfWfwn.d.ts.map +0 -1
  42. package/dist/list-selection-controller-SA4piSBi.js.map +0 -1
package/bundle/index.js CHANGED
@@ -5549,6 +5549,56 @@ __decorate([state()], RadioGroup.prototype, "_icons", void 0);
5549
5549
  __decorate([query(`slot`)], RadioGroup.prototype, "_slot", void 0);
5550
5550
  RadioGroup = __decorate([safeCustomElement(`ixfx-radio-group`)], RadioGroup);
5551
5551
  //#endregion
5552
+ //#region src/interaction/grouping.ts
5553
+ /** Label shown for the empty group key. */
5554
+ const EMPTY_GROUP_LABEL = `(none)`;
5555
+ /** Default header label: the key, or `(none)` for the empty key. */
5556
+ function defaultGroupLabel(key) {
5557
+ return key === `` ? EMPTY_GROUP_LABEL : key;
5558
+ }
5559
+ /** Coerce a raw group value to a key, as data-grid does. */
5560
+ function toGroupKey(value) {
5561
+ return String(value ?? ``);
5562
+ }
5563
+ /**
5564
+ * Group key for `item`. A string `groupBy` is resolved through `readNamed`
5565
+ * (property or `data-*` lookup, supplied by the host); a function is called.
5566
+ */
5567
+ function resolveGroupKey(groupBy, item, index, readNamed) {
5568
+ if (typeof groupBy === `string`) return toGroupKey(readNamed(item, groupBy));
5569
+ return toGroupKey(groupBy(item, index));
5570
+ }
5571
+ /**
5572
+ * Split `items` into groups in first-encounter order. Single pass, O(n).
5573
+ * Item order within a group is preserved.
5574
+ */
5575
+ function partitionByGroup(items, keyOf, labelOf) {
5576
+ const byKey = /* @__PURE__ */ new Map();
5577
+ for (let i = 0; i < items.length; i++) {
5578
+ const key = keyOf(items[i], i);
5579
+ const existing = byKey.get(key);
5580
+ if (existing) existing.items.push(items[i]);
5581
+ else byKey.set(key, {
5582
+ key,
5583
+ items: [items[i]],
5584
+ firstIndex: i
5585
+ });
5586
+ }
5587
+ return [...byKey.values()].map((g) => ({
5588
+ key: g.key,
5589
+ items: g.items,
5590
+ firstIndex: g.firstIndex,
5591
+ label: labelOf ? labelOf(g.key, g.items) : defaultGroupLabel(g.key)
5592
+ }));
5593
+ }
5594
+ /** Immutable toggle of `key` in `set`. */
5595
+ function toggleInSet(set, key) {
5596
+ const next = new Set(set);
5597
+ if (next.has(key)) next.delete(key);
5598
+ else next.add(key);
5599
+ return next;
5600
+ }
5601
+ //#endregion
5552
5602
  //#region src/interaction/list-drag-preview-default.ts
5553
5603
  const CARD_DEFAULT = 72;
5554
5604
  const CARD_MIN = 48;
@@ -6670,6 +6720,10 @@ var ListSelectionController = class {
6670
6720
  if (next.has(item)) next.delete(item);
6671
6721
  else {
6672
6722
  if (!this.canSelectMultiple()) next.clear();
6723
+ if (this.#clamped()) {
6724
+ const g = this.#groupOf(item);
6725
+ for (const other of [...next]) if (this.#groupOf(other) !== g) next.delete(other);
6726
+ }
6673
6727
  next.add(item);
6674
6728
  }
6675
6729
  this.#apply(next);
@@ -6688,6 +6742,22 @@ var ListSelectionController = class {
6688
6742
  this.replace(item);
6689
6743
  return;
6690
6744
  }
6745
+ if (this.#clamped()) {
6746
+ const group = this.#groupOf(this.#anchor);
6747
+ if (group !== this.#groupOf(item)) {
6748
+ this.replace(item);
6749
+ return;
6750
+ }
6751
+ const inGroup = ordered.filter((t) => this.#groupOf(t) === group);
6752
+ const a = inGroup.indexOf(this.#anchor);
6753
+ const b = inGroup.indexOf(item);
6754
+ if (a === -1 || b === -1) {
6755
+ this.replace(item);
6756
+ return;
6757
+ }
6758
+ this.#apply(new Set(inGroup.slice(Math.min(a, b), Math.max(a, b) + 1).filter((t) => this.#config.isSelectable(t))));
6759
+ return;
6760
+ }
6691
6761
  const start = Math.min(anchorIdx, targetIdx);
6692
6762
  const end = Math.max(anchorIdx, targetIdx);
6693
6763
  this.#apply(new Set(ordered.slice(start, end + 1).filter((t) => this.#config.isSelectable(t))));
@@ -6699,8 +6769,12 @@ var ListSelectionController = class {
6699
6769
  }
6700
6770
  /** Select every ordered item that passes `isSelectable`. */
6701
6771
  selectAll() {
6702
- const ordered = this.#config.getOrderedItems();
6772
+ let ordered = this.#config.getOrderedItems();
6703
6773
  if (ordered.length === 0) return;
6774
+ if (this.#clamped()) {
6775
+ const group = (this.#anchor === void 0 ? null : this.#groupOf(this.#anchor)) ?? this.#config.getFallbackGroupKey?.() ?? null;
6776
+ if (group !== null) ordered = ordered.filter((t) => this.#groupOf(t) === group);
6777
+ }
6704
6778
  const next = new Set(ordered.filter((t) => this.#config.isSelectable(t)));
6705
6779
  if (next.size === 0) return;
6706
6780
  this.#apply(next);
@@ -6712,7 +6786,7 @@ var ListSelectionController = class {
6712
6786
  selectMany(items) {
6713
6787
  const { selectionMode } = this.#config.getModes();
6714
6788
  if (selectionMode !== `multiple`) throw new Error(`selectMany requires selectionMode="multiple" (current: "${selectionMode}")`);
6715
- const next = new Set([...items].filter((t) => this.#config.isSelectable(t)));
6789
+ const next = this.#clampToFirstGroup(new Set([...items].filter((t) => this.#config.isSelectable(t))));
6716
6790
  if (next.size === 0) return;
6717
6791
  this.#anchor = [...next].at(-1);
6718
6792
  this.#apply(next);
@@ -6737,7 +6811,7 @@ var ListSelectionController = class {
6737
6811
  */
6738
6812
  applyDragSelection(items) {
6739
6813
  if (!this.canDragSelect()) return;
6740
- const next = new Set([...items].filter((t) => this.#config.isSelectable(t)));
6814
+ const next = this.#clampToFirstGroup(new Set([...items].filter((t) => this.#config.isSelectable(t))));
6741
6815
  if (next.size === 0 && this.#selected.size === 0) return;
6742
6816
  this.#anchor = [...next].at(-1);
6743
6817
  this.#apply(next);
@@ -6866,6 +6940,20 @@ var ListSelectionController = class {
6866
6940
  this.#selected = new Set(items);
6867
6941
  this.#anchor = anchor;
6868
6942
  }
6943
+ /** Whether gestures must stay inside one group. */
6944
+ #clamped() {
6945
+ return this.#config.getGroupKey !== void 0 && !(this.#config.crossGroupSelection?.() ?? false);
6946
+ }
6947
+ #groupOf(item) {
6948
+ return this.#config.getGroupKey?.(item) ?? null;
6949
+ }
6950
+ /** Keep only items in the group of the first item (in display order) when clamped. */
6951
+ #clampToFirstGroup(items) {
6952
+ if (!this.#clamped() || items.size === 0) return new Set(items);
6953
+ const first = this.#config.getOrderedItems().find((t) => items.has(t)) ?? [...items][0];
6954
+ const group = this.#groupOf(first);
6955
+ return new Set([...items].filter((t) => this.#groupOf(t) === group));
6956
+ }
6869
6957
  #apply(next) {
6870
6958
  const previous = this.#selected;
6871
6959
  this.#selected = next;
@@ -17173,8 +17261,125 @@ __decorate([state()], DataTableElement.prototype, "_detailIndex", void 0);
17173
17261
  __decorate([state()], DataTableElement.prototype, "_stacked", void 0);
17174
17262
  DataTableElement = __decorate([safeCustomElement(`ixfx-data-table`)], DataTableElement);
17175
17263
  //#endregion
17264
+ //#region src/list-group-header/list-group-header.ts
17265
+ let ListGroupHeaderElement = class ListGroupHeaderElement extends LitElement {
17266
+ constructor(..._args) {
17267
+ super(..._args);
17268
+ this.label = ``;
17269
+ this.count = 0;
17270
+ this.collapsed = false;
17271
+ this.orientation = `horizontal`;
17272
+ }
17273
+ static {
17274
+ this.styles = css`
17275
+ :host {
17276
+ --_height: var(--list-group-header-height, 28px);
17277
+ --_border: var(--list-group-header-border, var(--border));
17278
+
17279
+ display: flex;
17280
+ align-items: center;
17281
+ gap: var(--space-s);
17282
+ box-sizing: border-box;
17283
+ min-height: var(--_height);
17284
+ padding: 0 var(--space-s);
17285
+ background: transparent;
17286
+ color: var(--list-group-header-text, var(--surface-3-text));
17287
+ border-bottom: 1px solid var(--_border);
17288
+ font-weight: 600;
17289
+ cursor: pointer;
17290
+ user-select: none;
17291
+ }
17292
+
17293
+ .caret {
17294
+ display: inline-flex;
17295
+ transition: transform 0.15s;
17296
+ }
17297
+
17298
+ :host([collapsed]) .caret {
17299
+ transform: rotate(-90deg);
17300
+ }
17301
+
17302
+ .count {
17303
+ opacity: 0.6;
17304
+ font-weight: 400;
17305
+ font-size: 0.9em;
17306
+ }
17307
+
17308
+ .label {
17309
+ overflow: hidden;
17310
+ text-overflow: ellipsis;
17311
+ white-space: nowrap;
17312
+ min-width: 0;
17313
+ }
17314
+
17315
+ .divider {
17316
+ flex: 1;
17317
+ height: 1px;
17318
+ background: var(--_border);
17319
+ margin-left: var(--space-s);
17320
+ }
17321
+
17322
+ /* Single-row (grid-list \`row\` orientation) layout: a narrow column cell. */
17323
+ :host([orientation="vertical"]) {
17324
+ flex-direction: column;
17325
+ justify-content: flex-start;
17326
+ padding: var(--space-s) 0;
17327
+ border-bottom: 0;
17328
+ border-right: 1px solid var(--_border);
17329
+ min-height: 0;
17330
+ }
17331
+
17332
+ :host([orientation="vertical"]) .divider {
17333
+ width: 1px;
17334
+ height: auto;
17335
+ margin-left: 0;
17336
+ margin-top: var(--space-s);
17337
+ }
17338
+
17339
+ @media (prefers-reduced-motion: reduce) {
17340
+ .caret { transition: none; }
17341
+ }
17342
+ `;
17343
+ }
17344
+ connectedCallback() {
17345
+ super.connectedCallback();
17346
+ this.setAttribute(`role`, `button`);
17347
+ }
17348
+ willUpdate() {
17349
+ this.setAttribute(`aria-expanded`, this.collapsed ? `false` : `true`);
17350
+ this.setAttribute(`aria-label`, `${this.label}, ${this.count} ${this.count === 1 ? `item` : `items`}`);
17351
+ }
17352
+ render() {
17353
+ return html`
17354
+ <span class="caret"><ixfx-icon name="chevron-down" size="12"></ixfx-icon></span>
17355
+ <span class="label" part="label">${this.label}</span>
17356
+ <span class="count" part="count">(${this.count})</span>
17357
+ <span class="divider" part="divider"></span>
17358
+ `;
17359
+ }
17360
+ };
17361
+ __decorate([property({
17362
+ type: String,
17363
+ reflect: true
17364
+ })], ListGroupHeaderElement.prototype, "label", void 0);
17365
+ __decorate([property({
17366
+ type: Number,
17367
+ reflect: true
17368
+ })], ListGroupHeaderElement.prototype, "count", void 0);
17369
+ __decorate([property({
17370
+ type: Boolean,
17371
+ reflect: true
17372
+ })], ListGroupHeaderElement.prototype, "collapsed", void 0);
17373
+ __decorate([property({
17374
+ type: String,
17375
+ reflect: true
17376
+ })], ListGroupHeaderElement.prototype, "orientation", void 0);
17377
+ ListGroupHeaderElement = __decorate([safeCustomElement(`ixfx-list-group-header`)], ListGroupHeaderElement);
17378
+ //#endregion
17176
17379
  //#region src/detail-list/detail-list.ts
17177
17380
  const ITEM_SELECTOR$3 = `:scope > li`;
17381
+ const GROUP_HEADER_ATTR = `data-ixfx-group-header`;
17382
+ const GROUP_COLLAPSED_ATTR = `data-group-collapsed`;
17178
17383
  const SEARCH_HIGHLIGHT_KEY = `detail-list-search`;
17179
17384
  const NAV_KEYS = /* @__PURE__ */ new Set([
17180
17385
  `ArrowDown`,
@@ -17232,14 +17437,26 @@ let DetailListElement = class DetailListElement extends LitElement {
17232
17437
  this.keyProperty = ``;
17233
17438
  this.itemWrap = `stop`;
17234
17439
  this.itemsDraggable = false;
17440
+ this.groupBy = null;
17441
+ this.crossGroupSelection = false;
17442
+ this._collapsedGroups = /* @__PURE__ */ new Set();
17235
17443
  this.#itemData = /* @__PURE__ */ new WeakMap();
17236
17444
  this.#grid = [];
17237
17445
  this.#positions = /* @__PURE__ */ new Map();
17446
+ this.#tops = /* @__PURE__ */ new Map();
17238
17447
  this.#geometryScheduled = false;
17239
17448
  this.#sortRaf = 0;
17240
17449
  this.#viewportScrollRaf = 0;
17241
17450
  this.#animatedItems = /* @__PURE__ */ new WeakSet();
17242
17451
  this.#builtFromProvider = [];
17452
+ this.#groupIndex = /* @__PURE__ */ new Map();
17453
+ this.#groupList = [];
17454
+ this.#headerEls = /* @__PURE__ */ new Map();
17455
+ this.#seq = /* @__PURE__ */ new WeakMap();
17456
+ this.#seqNext = 0;
17457
+ this.#wasGrouped = false;
17458
+ this.#groupsScheduled = false;
17459
+ this.#groupByChanged = false;
17243
17460
  this.#providerBuilt = 0;
17244
17461
  this.#data = new DataController(this);
17245
17462
  this.selection = new ListSelectionController({
@@ -17250,6 +17467,12 @@ let DetailListElement = class DetailListElement extends LitElement {
17250
17467
  interactionMode: this.interactionMode
17251
17468
  }),
17252
17469
  getKey: keyForElement,
17470
+ getGroupKey: (el) => this.#groupIndex.get(el) ?? null,
17471
+ crossGroupSelection: () => this.crossGroupSelection,
17472
+ getFallbackGroupKey: () => {
17473
+ const t = this.tickled.tickledId;
17474
+ return t ? this.#groupIndex.get(t) ?? null : null;
17475
+ },
17253
17476
  onChange: (selected, previous) => {
17254
17477
  this._syncItemStates();
17255
17478
  this._dispatch(`list-select`, {
@@ -17331,6 +17554,7 @@ let DetailListElement = class DetailListElement extends LitElement {
17331
17554
  if (this._listEl) this._listEl.scrollLeft += dx;
17332
17555
  },
17333
17556
  shouldStart: (event) => {
17557
+ if (this.#findGroupHeader(event.composedPath())) return false;
17334
17558
  this.#blankItemPressPending = this.itemsDraggable && this.#isBlankItemPress(event);
17335
17559
  return !this.itemsDraggable || this.#blankItemPressPending;
17336
17560
  }
@@ -17422,7 +17646,13 @@ let DetailListElement = class DetailListElement extends LitElement {
17422
17646
  };
17423
17647
  this._onClick = (e) => {
17424
17648
  if (this.#dragSelect.isDragging || this.#nativeDrag.isDragging || this.#postDragSuppressTimer !== 0) return;
17425
- const item = this._findListItem(e.composedPath());
17649
+ const path = e.composedPath();
17650
+ const header = this.#findGroupHeader(path);
17651
+ if (header) {
17652
+ this.toggleGroup(header.dataset.groupKey ?? ``);
17653
+ return;
17654
+ }
17655
+ const item = this._findListItem(path);
17426
17656
  if (!item) return;
17427
17657
  this.focus();
17428
17658
  this._dispatch(`list-item-click`, { item });
@@ -17441,6 +17671,8 @@ let DetailListElement = class DetailListElement extends LitElement {
17441
17671
  #itemData;
17442
17672
  #grid;
17443
17673
  #positions;
17674
+ /** Viewport-relative `top` per item at the last geometry pass — lines rows up across columns. */
17675
+ #tops;
17444
17676
  #geometryScheduled;
17445
17677
  #sortRaf;
17446
17678
  #viewportScrollRaf;
@@ -17452,6 +17684,17 @@ let DetailListElement = class DetailListElement extends LitElement {
17452
17684
  /** Items that have already played (or been skipped for) their enter animation. */
17453
17685
  #animatedItems;
17454
17686
  #builtFromProvider;
17687
+ /** Element → group key, for the selection controller. Empty while ungrouped. */
17688
+ #groupIndex;
17689
+ #groupList;
17690
+ #headerEls;
17691
+ /** Insertion sequence per item, so regrouping starts from the original order. */
17692
+ #seq;
17693
+ #seqNext;
17694
+ #wasGrouped;
17695
+ #groupsScheduled;
17696
+ #groupByChanged;
17697
+ #groupObserver;
17455
17698
  #providerBuilt;
17456
17699
  #data;
17457
17700
  #nativeDragging;
@@ -17568,6 +17811,7 @@ let DetailListElement = class DetailListElement extends LitElement {
17568
17811
  this.#processChildren();
17569
17812
  this.#animateChildMutations(records);
17570
17813
  this.#scheduleGeometry();
17814
+ this.#scheduleGroups();
17571
17815
  });
17572
17816
  this.#mutationObserver.observe(this, {
17573
17817
  childList: true,
@@ -17577,6 +17821,7 @@ let DetailListElement = class DetailListElement extends LitElement {
17577
17821
  for (const el of this.querySelectorAll(ITEM_SELECTOR$3)) this.#animatedItems.add(el);
17578
17822
  this.#searchRegistry ??= createCommandRegistry();
17579
17823
  this.#syncSearchCommands();
17824
+ this.#scheduleGroups();
17580
17825
  }
17581
17826
  /**
17582
17827
  * Collapse-in items appended to the light DOM (however they got there). A bulk
@@ -17618,6 +17863,8 @@ let DetailListElement = class DetailListElement extends LitElement {
17618
17863
  this.#resizeObserver = void 0;
17619
17864
  this.#mutationObserver?.disconnect();
17620
17865
  this.#mutationObserver = void 0;
17866
+ this.#groupObserver?.disconnect();
17867
+ this.#groupObserver = void 0;
17621
17868
  this.#unregisterSearchCommands?.();
17622
17869
  this.#unregisterSearchCommands = void 0;
17623
17870
  this.#searchRegistry = void 0;
@@ -17632,8 +17879,22 @@ let DetailListElement = class DetailListElement extends LitElement {
17632
17879
  this.#recomputeGeometry();
17633
17880
  this._syncNativeDraggable();
17634
17881
  }
17882
+ willUpdate(changed) {
17883
+ super.willUpdate(changed);
17884
+ if (changed.has(`groupBy`) && this.hasUpdated) {
17885
+ if (this._collapsedGroups.size > 0) this._collapsedGroups = /* @__PURE__ */ new Set();
17886
+ this.#groupByChanged = true;
17887
+ }
17888
+ }
17635
17889
  updated(changed) {
17636
17890
  super.updated(changed);
17891
+ if (changed.has(`groupBy`) || changed.has(`groupLabel`) || changed.has(`_collapsedGroups`)) {
17892
+ this.#scheduleGroups();
17893
+ if (this.#groupByChanged) {
17894
+ this.#groupByChanged = false;
17895
+ this._dispatch(`list-group-change`, { groupBy: this.groupBy });
17896
+ }
17897
+ }
17637
17898
  if (changed.has(`searchModes`)) this.#syncSearchCommands();
17638
17899
  if (changed.has(`provider`)) {
17639
17900
  this.#builtFromProvider = [];
@@ -17650,6 +17911,7 @@ let DetailListElement = class DetailListElement extends LitElement {
17650
17911
  if (changed.has(`filterPredicate`)) {
17651
17912
  this.#applyFilterPredicate();
17652
17913
  this.#scheduleGeometry();
17914
+ this.#scheduleGroups();
17653
17915
  }
17654
17916
  if (changed.has(`interactionMode`)) {
17655
17917
  this.selection.clearAnchor();
@@ -17689,6 +17951,7 @@ let DetailListElement = class DetailListElement extends LitElement {
17689
17951
  this.#ensureIcon(el);
17690
17952
  if (this.sort !== `none`) this.#applySort();
17691
17953
  if (this.interactionMode === `checked`) this._syncCheckboxes();
17954
+ this.#scheduleGroups();
17692
17955
  }
17693
17956
  /**
17694
17957
  * Collapse + fade `item` out, then detach it; the rows after it slide up to
@@ -17698,6 +17961,7 @@ let DetailListElement = class DetailListElement extends LitElement {
17698
17961
  this.selection.deselect(item);
17699
17962
  return animateListItemOut(item).then(() => {
17700
17963
  item.remove();
17964
+ this.#scheduleGroups();
17701
17965
  if (this.interactionMode === `checked`) this._syncCheckboxes();
17702
17966
  this.#recomputeGeometry();
17703
17967
  });
@@ -17708,6 +17972,8 @@ let DetailListElement = class DetailListElement extends LitElement {
17708
17972
  }
17709
17973
  clearItems() {
17710
17974
  this.replaceChildren();
17975
+ this.#headerEls.clear();
17976
+ this.#scheduleGroups();
17711
17977
  this.selection.reset();
17712
17978
  this.#builtFromProvider = [];
17713
17979
  this.#providerBuilt = 0;
@@ -17725,6 +17991,7 @@ let DetailListElement = class DetailListElement extends LitElement {
17725
17991
  }
17726
17992
  if (this.sort !== `none`) this.#applySort();
17727
17993
  if (this.interactionMode === `checked`) this._syncCheckboxes();
17994
+ this.#scheduleGroups();
17728
17995
  }
17729
17996
  /**
17730
17997
  * Get the source object behind an `<li>` built from an `items` array or
@@ -17844,6 +18111,7 @@ let DetailListElement = class DetailListElement extends LitElement {
17844
18111
  if (this.interactionMode === `checked`) this._syncCheckboxes();
17845
18112
  this._syncItemStates();
17846
18113
  this.#scheduleGeometry();
18114
+ this.#scheduleGroups();
17847
18115
  }
17848
18116
  #createItemEl(item, index) {
17849
18117
  const li = document.createElement(`li`);
@@ -17895,6 +18163,7 @@ let DetailListElement = class DetailListElement extends LitElement {
17895
18163
  if (this.interactionMode === `checked`) this._syncCheckboxes();
17896
18164
  this._syncItemStates();
17897
18165
  this.#scheduleGeometry();
18166
+ this.#scheduleGroups();
17898
18167
  } else {
17899
18168
  this.#providerBuilt = 0;
17900
18169
  this.#rebuildFromObjects(items);
@@ -17913,6 +18182,16 @@ let DetailListElement = class DetailListElement extends LitElement {
17913
18182
  if (this.sort === `none`) return;
17914
18183
  const rows = [...this.querySelectorAll(ITEM_SELECTOR$3)];
17915
18184
  if (rows.length < 2) return;
18185
+ if (this.#groupingActive()) {
18186
+ this.#scheduleGroups();
18187
+ return;
18188
+ }
18189
+ for (const el of this.#sortRows(rows)) this.appendChild(el);
18190
+ this.#scheduleGeometry();
18191
+ this.#scheduleGroups();
18192
+ }
18193
+ /** Stable sort of `rows` by the current `sort` / `sortBy` / `sortComparator`. */
18194
+ #sortRows(rows) {
17916
18195
  const dir = this.sort === `descending` ? -1 : 1;
17917
18196
  const meta = (el) => ({
17918
18197
  element: el,
@@ -17942,9 +18221,147 @@ let DetailListElement = class DetailListElement extends LitElement {
17942
18221
  i
17943
18222
  }));
17944
18223
  indexed.sort((a, b) => cmp(a.el, b.el) * dir || a.i - b.i);
17945
- for (const { el } of indexed) this.appendChild(el);
18224
+ return indexed.map(({ el }) => el);
18225
+ }
18226
+ #groupingActive() {
18227
+ return this.groupBy !== null && this.groupBy !== void 0 && this.groupBy !== ``;
18228
+ }
18229
+ /** Coalesce regrouping to one pass per microtask (streaming batches, search keystrokes). */
18230
+ #scheduleGroups() {
18231
+ if (this.#groupsScheduled || !this.#groupingActive() && !this.#wasGrouped) return;
18232
+ this.#groupsScheduled = true;
18233
+ queueMicrotask(() => {
18234
+ this.#groupsScheduled = false;
18235
+ this.#syncGroups();
18236
+ });
18237
+ }
18238
+ #groupValue(el, name) {
18239
+ return this.#propertyValue(this.getItemData(el), name) ?? el.getAttribute(`data-${name}`);
18240
+ }
18241
+ #groupKeyOf(el) {
18242
+ const by = this.groupBy;
18243
+ if (typeof by === `string`) return toGroupKey(this.#groupValue(el, by));
18244
+ return toGroupKey(by(this.getItemData(el), el));
18245
+ }
18246
+ #isItemVisible(el) {
18247
+ return !el.hidden && (!this.filterPredicate || this.filterPredicate(el));
18248
+ }
18249
+ #headerFor(key) {
18250
+ let header = this.#headerEls.get(key);
18251
+ if (!header) {
18252
+ header = document.createElement(`ixfx-list-group-header`);
18253
+ header.setAttribute(GROUP_HEADER_ATTR, ``);
18254
+ header.dataset.groupKey = key;
18255
+ this.#headerEls.set(key, header);
18256
+ }
18257
+ return header;
18258
+ }
18259
+ /**
18260
+ * Bring the light DOM into grouped order: original order → sort → partition
18261
+ * into contiguous groups, each preceded by a header row. Idempotent — it only
18262
+ * touches the DOM when the order actually changes, so it never loops through
18263
+ * the mutation observer. Ungrouping restores the original (sorted) order.
18264
+ */
18265
+ #syncGroups() {
18266
+ const active = this.#groupingActive();
18267
+ if (!active && !this.#wasGrouped) return;
18268
+ const items = [...this.querySelectorAll(ITEM_SELECTOR$3)];
18269
+ for (const el of items) if (!this.#seq.has(el)) this.#seq.set(el, this.#seqNext++);
18270
+ let ordered = items.toSorted((a, b) => this.#seq.get(a) - this.#seq.get(b));
18271
+ if (this.sort !== `none` && ordered.length > 1) ordered = this.#sortRows(ordered);
18272
+ const nodes = [];
18273
+ const info = [];
18274
+ const index = /* @__PURE__ */ new Map();
18275
+ const liveHeaders = /* @__PURE__ */ new Set();
18276
+ if (active) {
18277
+ const label = this.groupLabel;
18278
+ const parts = partitionByGroup(ordered, (el) => this.#groupKeyOf(el), label ? (key, els) => label(key, els.map((el) => this.getItemData(el) ?? el)) : void 0);
18279
+ for (const part of parts) {
18280
+ const count = part.items.filter((el) => this.#isItemVisible(el)).length;
18281
+ const collapsed = count > 0 && this._collapsedGroups.has(part.key);
18282
+ if (count > 0) {
18283
+ const header = this.#headerFor(part.key);
18284
+ header.label = part.label;
18285
+ header.count = count;
18286
+ header.collapsed = collapsed;
18287
+ liveHeaders.add(part.key);
18288
+ nodes.push(header);
18289
+ info.push({
18290
+ key: part.key,
18291
+ label: part.label,
18292
+ count,
18293
+ collapsed: this._collapsedGroups.has(part.key)
18294
+ });
18295
+ }
18296
+ for (const el of part.items) {
18297
+ index.set(el, part.key);
18298
+ el.toggleAttribute(GROUP_COLLAPSED_ATTR, collapsed);
18299
+ nodes.push(el);
18300
+ }
18301
+ }
18302
+ } else {
18303
+ for (const el of ordered) el.removeAttribute(GROUP_COLLAPSED_ATTR);
18304
+ nodes.push(...ordered);
18305
+ }
18306
+ for (const [key, header] of this.#headerEls) if (!liveHeaders.has(key)) {
18307
+ header.remove();
18308
+ this.#headerEls.delete(key);
18309
+ }
18310
+ this.#groupIndex = index;
18311
+ this.#groupList = info;
18312
+ this.#wasGrouped = active;
18313
+ const current = [...this.children].filter((c) => c.matches(`li`) || c.hasAttribute(GROUP_HEADER_ATTR));
18314
+ if (current.length !== nodes.length || current.some((c, i) => c !== nodes[i])) this.append(...nodes);
18315
+ if (this.tickled.tickledId?.hasAttribute(GROUP_COLLAPSED_ATTR)) this.tickled.focus(void 0);
18316
+ this.#syncGroupObserver(active);
17946
18317
  this.#scheduleGeometry();
17947
18318
  }
18319
+ /** Filter-search hides rows via `hidden` on the `<li>`s — regroup when that changes. */
18320
+ #syncGroupObserver(active) {
18321
+ if (!active) {
18322
+ this.#groupObserver?.disconnect();
18323
+ this.#groupObserver = void 0;
18324
+ return;
18325
+ }
18326
+ if (this.#groupObserver || !this.isConnected) return;
18327
+ this.#groupObserver = new MutationObserver(() => this.#scheduleGroups());
18328
+ this.#groupObserver.observe(this, {
18329
+ subtree: true,
18330
+ attributes: true,
18331
+ attributeFilter: [`hidden`]
18332
+ });
18333
+ }
18334
+ /** Groups in display order (empty when ungrouped). */
18335
+ get groups() {
18336
+ return this.#groupList;
18337
+ }
18338
+ /** Collapse or expand one group. Fires `list-group-toggle` when the state changes. */
18339
+ setGroupCollapsed(groupKey, collapsed) {
18340
+ if (this._collapsedGroups.has(groupKey) === collapsed) return;
18341
+ const next = new Set(this._collapsedGroups);
18342
+ if (collapsed) next.add(groupKey);
18343
+ else next.delete(groupKey);
18344
+ this._collapsedGroups = next;
18345
+ this._dispatch(`list-group-toggle`, {
18346
+ groupKey,
18347
+ collapsed
18348
+ });
18349
+ }
18350
+ toggleGroup(groupKey) {
18351
+ this.setGroupCollapsed(groupKey, !this._collapsedGroups.has(groupKey));
18352
+ }
18353
+ collapseAllGroups() {
18354
+ for (const g of this.#groupList) this.setGroupCollapsed(g.key, true);
18355
+ }
18356
+ expandAllGroups() {
18357
+ for (const key of [...this._collapsedGroups]) this.setGroupCollapsed(key, false);
18358
+ }
18359
+ #findGroupHeader(path) {
18360
+ for (const target of path) {
18361
+ if (target === this) break;
18362
+ if (target instanceof HTMLElement && target.parentElement === this && target.hasAttribute(GROUP_HEADER_ATTR)) return target;
18363
+ }
18364
+ }
17948
18365
  #scheduleGeometry() {
17949
18366
  if (this.#geometryScheduled) return;
17950
18367
  this.#geometryScheduled = true;
@@ -17956,11 +18373,13 @@ let DetailListElement = class DetailListElement extends LitElement {
17956
18373
  #recomputeGeometry() {
17957
18374
  this.#grid = [];
17958
18375
  this.#positions.clear();
18376
+ this.#tops.clear();
17959
18377
  if (!this.isConnected) return;
17960
18378
  const columns = [];
17961
18379
  for (const el of this._getVisibleItems()) {
17962
18380
  const rect = el.getBoundingClientRect();
17963
18381
  if (rect.width === 0 && rect.height === 0) continue;
18382
+ this.#tops.set(el, rect.top);
17964
18383
  let ci = columns.findIndex((c) => Math.abs(c.left - rect.left) <= 1);
17965
18384
  if (ci === -1) {
17966
18385
  columns.push({
@@ -18081,6 +18500,30 @@ let DetailListElement = class DetailListElement extends LitElement {
18081
18500
  const next = this.#findNextCheckable(cur.col, cur.row, 0, -1);
18082
18501
  if (next) this.#navigateTo(next);
18083
18502
  }
18503
+ /**
18504
+ * Left/Right target. Ungrouped lists keep the row-index rule. Grouped lists
18505
+ * have headers at arbitrary heights, so an item's row index no longer equals
18506
+ * its visual row — pick the item in the neighbouring column whose top is
18507
+ * nearest the cursor's (ties go to the upper one).
18508
+ */
18509
+ #horizontalTarget(from, dir) {
18510
+ if (this.#groupList.length === 0) return this.#findNextCheckable(from.col, from.row, dir, 0);
18511
+ const column = this.#grid[from.col + dir];
18512
+ const current = this.tickled.tickledId;
18513
+ const top = current ? this.#tops.get(current) : void 0;
18514
+ if (!column || top === void 0) return void 0;
18515
+ let best;
18516
+ let bestDelta = Number.POSITIVE_INFINITY;
18517
+ for (const el of column) {
18518
+ if (!this._isCheckable(el)) continue;
18519
+ const delta = Math.abs((this.#tops.get(el) ?? Number.POSITIVE_INFINITY) - top);
18520
+ if (delta < bestDelta) {
18521
+ best = el;
18522
+ bestDelta = delta;
18523
+ }
18524
+ }
18525
+ return best;
18526
+ }
18084
18527
  #navigateRight() {
18085
18528
  this.#recomputeGeometry();
18086
18529
  const cur = this.tickled.tickledId ? this.#positionOf(this.tickled.tickledId) : void 0;
@@ -18088,7 +18531,7 @@ let DetailListElement = class DetailListElement extends LitElement {
18088
18531
  this.#navigateTo(this.#findFirstCheckable());
18089
18532
  return;
18090
18533
  }
18091
- const next = this.#findNextCheckable(cur.col, cur.row, 1, 0);
18534
+ const next = this.#horizontalTarget(cur, 1);
18092
18535
  if (next) this.#navigateTo(next);
18093
18536
  else if (this.itemWrap === `wrap`) this.#navigateTo(this.#findFirstCheckable());
18094
18537
  }
@@ -18099,13 +18542,14 @@ let DetailListElement = class DetailListElement extends LitElement {
18099
18542
  this.#navigateTo(this.#findFirstCheckable());
18100
18543
  return;
18101
18544
  }
18102
- const next = this.#findNextCheckable(cur.col, cur.row, -1, 0);
18545
+ const next = this.#horizontalTarget(cur, -1);
18103
18546
  if (next) this.#navigateTo(next);
18104
18547
  else if (this.itemWrap === `wrap`) this.#navigateTo(this.#findLastCheckable());
18105
18548
  }
18106
18549
  _getVisibleItems() {
18107
18550
  return Array.from(this.querySelectorAll(ITEM_SELECTOR$3)).filter((el) => {
18108
18551
  if (el.hidden) return false;
18552
+ if (el.hasAttribute(GROUP_COLLAPSED_ATTR)) return false;
18109
18553
  if (this.filterPredicate && !this.filterPredicate(el)) return false;
18110
18554
  return true;
18111
18555
  });
@@ -18394,6 +18838,20 @@ let DetailListElement = class DetailListElement extends LitElement {
18394
18838
  display: none !important;
18395
18839
  }
18396
18840
 
18841
+ /* Rows of a collapsed group leave the flow; their header stays. */
18842
+ ::slotted([data-group-collapsed]) {
18843
+ display: none !important;
18844
+ }
18845
+
18846
+ /* Group headers are rows in the column flow, the same size as an item. */
18847
+ ::slotted(ixfx-list-group-header) {
18848
+ box-sizing: border-box;
18849
+ flex: none;
18850
+ width: var(--detail-list-column-width, 220px);
18851
+ height: var(--detail-list-item-height, 28px);
18852
+ --list-group-header-height: var(--detail-list-group-header-height, var(--detail-list-item-height, 28px));
18853
+ }
18854
+
18397
18855
  ::slotted(li) {
18398
18856
  box-sizing: border-box;
18399
18857
  display: flex;
@@ -18570,6 +19028,14 @@ __decorate([property({
18570
19028
  })], DetailListElement.prototype, "itemsDraggable", void 0);
18571
19029
  __decorate([property({ attribute: false })], DetailListElement.prototype, "dragPreviewRenderer", void 0);
18572
19030
  __decorate([property({ attribute: false })], DetailListElement.prototype, "dragPreviewSource", void 0);
19031
+ __decorate([property({ attribute: `group-by` })], DetailListElement.prototype, "groupBy", void 0);
19032
+ __decorate([property({ attribute: false })], DetailListElement.prototype, "groupLabel", void 0);
19033
+ __decorate([property({
19034
+ type: Boolean,
19035
+ attribute: `cross-group-selection`,
19036
+ reflect: true
19037
+ })], DetailListElement.prototype, "crossGroupSelection", void 0);
19038
+ __decorate([state()], DetailListElement.prototype, "_collapsedGroups", void 0);
18573
19039
  __decorate([query(`ul`)], DetailListElement.prototype, "_listEl", void 0);
18574
19040
  DetailListElement = __decorate([safeCustomElement(`ixfx-detail-list`)], DetailListElement);
18575
19041
  //#endregion
@@ -24209,7 +24675,374 @@ let FormElement = class FormElement extends LitElement {
24209
24675
  };
24210
24676
  FormElement = __decorate([safeCustomElement(`ixfx-form`)], FormElement);
24211
24677
  //#endregion
24678
+ //#region src/grid-list/grid-list-layout.ts
24679
+ /**
24680
+ * `itemWidth` is the square's width; `rowHeight` is the full cell height
24681
+ * (square, plus the caption band in `separate` / `under` styles).
24682
+ * `scrollOffset` is `scrollTop` for `grid`/`column` and `scrollLeft` for `row`.
24683
+ */
24684
+ function computeGridWindow(count, itemWidth, rowHeight, gap, viewportWidth, viewportHeight, scrollOffset, orientation, overscan = 2) {
24685
+ const w = Math.max(1, itemWidth);
24686
+ const h = Math.max(1, rowHeight);
24687
+ const g = Math.max(0, gap);
24688
+ const colStride = w + g;
24689
+ const rowStride = h + g;
24690
+ const offset = Math.max(0, scrollOffset);
24691
+ if (orientation === `row`) {
24692
+ const rows = count === 0 ? 0 : 1;
24693
+ const totalWidth = count === 0 ? 0 : (count - 1) * colStride + w;
24694
+ if (count === 0 || viewportWidth <= 0) return {
24695
+ cols: count,
24696
+ rows,
24697
+ totalHeight: 0,
24698
+ totalWidth,
24699
+ startIndex: 0,
24700
+ endIndex: 0,
24701
+ colStride,
24702
+ rowStride,
24703
+ horizontal: true
24704
+ };
24705
+ const firstVisible = Math.max(0, Math.floor(offset / colStride));
24706
+ const lastVisible = Math.ceil((offset + viewportWidth) / colStride);
24707
+ return {
24708
+ cols: count,
24709
+ rows,
24710
+ totalHeight: 0,
24711
+ totalWidth,
24712
+ startIndex: Math.max(0, firstVisible - overscan),
24713
+ endIndex: Math.min(count, lastVisible + overscan),
24714
+ colStride,
24715
+ rowStride,
24716
+ horizontal: true
24717
+ };
24718
+ }
24719
+ const cols = orientation === `column` ? 1 : Math.max(1, Math.floor((Math.max(0, viewportWidth) + g) / colStride));
24720
+ const rows = count === 0 ? 0 : Math.ceil(count / cols);
24721
+ const totalHeight = rows === 0 ? 0 : (rows - 1) * rowStride + h;
24722
+ if (count === 0 || viewportHeight <= 0) return {
24723
+ cols,
24724
+ rows,
24725
+ totalHeight,
24726
+ totalWidth: 0,
24727
+ startIndex: 0,
24728
+ endIndex: 0,
24729
+ colStride,
24730
+ rowStride,
24731
+ horizontal: false
24732
+ };
24733
+ const firstVisibleRow = Math.max(0, Math.floor(offset / rowStride));
24734
+ const lastVisibleRow = Math.ceil((offset + viewportHeight) / rowStride);
24735
+ const rowStart = Math.max(0, firstVisibleRow - overscan);
24736
+ const rowEnd = Math.min(rows, lastVisibleRow + overscan);
24737
+ return {
24738
+ cols,
24739
+ rows,
24740
+ totalHeight,
24741
+ totalWidth: 0,
24742
+ startIndex: rowStart * cols,
24743
+ endIndex: Math.min(count, rowEnd * cols),
24744
+ colStride,
24745
+ rowStride,
24746
+ horizontal: false
24747
+ };
24748
+ }
24749
+ function buildSections(p, cols, stride, itemExtent) {
24750
+ const g = Math.max(0, p.gap);
24751
+ const horizontal = p.orientation === `row`;
24752
+ const lineCount = (n) => horizontal ? n : Math.ceil(n / cols);
24753
+ const extentFor = (n) => n === 0 ? 0 : (lineCount(n) - 1) * stride + itemExtent;
24754
+ if (!p.groups) {
24755
+ const end = extentFor(p.count);
24756
+ return {
24757
+ sections: [{
24758
+ group: void 0,
24759
+ first: 0,
24760
+ count: p.count,
24761
+ headerOffset: 0,
24762
+ itemsOffset: 0,
24763
+ end
24764
+ }],
24765
+ total: end,
24766
+ count: p.count
24767
+ };
24768
+ }
24769
+ const headerSize = horizontal ? p.headerWidth : p.headerHeight;
24770
+ const sections = [];
24771
+ let cursor = 0;
24772
+ let first = 0;
24773
+ for (const group of p.groups) {
24774
+ const count = group.collapsed ? 0 : group.count;
24775
+ const itemsOffset = cursor + headerSize + g;
24776
+ const end = count === 0 ? cursor + headerSize : itemsOffset + extentFor(count);
24777
+ sections.push({
24778
+ group,
24779
+ first,
24780
+ count,
24781
+ headerOffset: cursor,
24782
+ itemsOffset,
24783
+ end
24784
+ });
24785
+ first += count;
24786
+ cursor = end + g;
24787
+ }
24788
+ return {
24789
+ sections,
24790
+ total: sections.length === 0 ? 0 : sections[sections.length - 1].end,
24791
+ count: first
24792
+ };
24793
+ }
24794
+ /** Index of the last section whose `headerOffset` is <= `offset` (0 when before all). */
24795
+ function sectionAtOffset(sections, offset) {
24796
+ let lo = 0;
24797
+ let hi = sections.length - 1;
24798
+ while (lo < hi) {
24799
+ const mid = lo + hi + 1 >> 1;
24800
+ if (sections[mid].headerOffset <= offset) lo = mid;
24801
+ else hi = mid - 1;
24802
+ }
24803
+ return lo;
24804
+ }
24805
+ /** Index of the section holding effective item `index` (skips empty sections). */
24806
+ function sectionOfIndex(sections, index) {
24807
+ let lo = 0;
24808
+ let hi = sections.length - 1;
24809
+ while (lo < hi) {
24810
+ const mid = lo + hi + 1 >> 1;
24811
+ if (sections[mid].first <= index) lo = mid;
24812
+ else hi = mid - 1;
24813
+ }
24814
+ let s = lo;
24815
+ while (s < sections.length - 1 && sections[s].count === 0) s++;
24816
+ return s;
24817
+ }
24818
+ function buildLayout(p) {
24819
+ const horizontal = p.orientation === `row`;
24820
+ const w = Math.max(1, p.itemWidth);
24821
+ const h = Math.max(1, p.rowHeight);
24822
+ const g = Math.max(0, p.gap);
24823
+ const colStride = w + g;
24824
+ const rowStride = h + g;
24825
+ const cols = horizontal ? 1 : p.orientation === `column` ? 1 : Math.max(1, Math.floor((Math.max(0, p.viewportWidth) + g) / colStride));
24826
+ const stride = horizontal ? colStride : rowStride;
24827
+ const itemExtent = horizontal ? w : h;
24828
+ const { sections, total, count } = buildSections(p, cols, stride, itemExtent);
24829
+ const grouped = p.groups !== void 0;
24830
+ const headerSize = horizontal ? p.headerWidth : p.headerHeight;
24831
+ const viewportMain = horizontal ? p.viewportWidth : p.viewportHeight;
24832
+ const cellHeight = rowStride - g;
24833
+ const rowY = Math.max(0, (p.viewportHeight - cellHeight) / 2);
24834
+ const positionOf = (index) => {
24835
+ const si = sectionOfIndex(sections, index);
24836
+ const s = sections[si];
24837
+ const local = Math.max(0, index - s.first);
24838
+ if (horizontal) return {
24839
+ x: s.itemsOffset + local * colStride,
24840
+ y: rowY,
24841
+ col: 0,
24842
+ line: local,
24843
+ section: si
24844
+ };
24845
+ const col = cols <= 1 ? 0 : local % cols;
24846
+ const line = cols <= 1 ? local : Math.floor(local / cols);
24847
+ return {
24848
+ x: col * colStride,
24849
+ y: s.itemsOffset + line * rowStride,
24850
+ col,
24851
+ line,
24852
+ section: si
24853
+ };
24854
+ };
24855
+ const window = (offset, overscan = 2) => {
24856
+ const off = Math.max(0, offset);
24857
+ const headers = [];
24858
+ const ranges = [];
24859
+ const lo = off - overscan * stride;
24860
+ const hi = off + viewportMain + overscan * stride;
24861
+ let firstSection = 0;
24862
+ if (sections.length > 1) {
24863
+ let a = 0;
24864
+ let b = sections.length - 1;
24865
+ while (a < b) {
24866
+ const mid = a + b >> 1;
24867
+ if (sections[mid].end >= lo) b = mid;
24868
+ else a = mid + 1;
24869
+ }
24870
+ firstSection = a;
24871
+ }
24872
+ if (viewportMain > 0) for (let si = firstSection; si < sections.length; si++) {
24873
+ const s = sections[si];
24874
+ if (s.headerOffset > hi) break;
24875
+ if (s.group && s.headerOffset + headerSize >= lo) headers.push({
24876
+ key: s.group.key,
24877
+ label: s.group.label,
24878
+ count: s.group.count,
24879
+ collapsed: s.group.collapsed,
24880
+ offset: s.headerOffset,
24881
+ size: headerSize
24882
+ });
24883
+ if (s.count === 0) continue;
24884
+ const rel = off - s.itemsOffset;
24885
+ const viewport = rel < 0 ? viewportMain + rel : viewportMain;
24886
+ if (viewport <= 0) continue;
24887
+ const sw = computeGridWindow(s.count, w, h, g, horizontal ? viewport : p.viewportWidth, horizontal ? p.viewportHeight : viewport, Math.max(0, rel), p.orientation, overscan);
24888
+ if (sw.endIndex > sw.startIndex) ranges.push([s.first + sw.startIndex, s.first + sw.endIndex]);
24889
+ }
24890
+ return {
24891
+ cols: horizontal ? count : cols,
24892
+ rows: 0,
24893
+ totalHeight: horizontal ? 0 : total,
24894
+ totalWidth: horizontal ? total : 0,
24895
+ startIndex: ranges.length > 0 ? ranges[0][0] : 0,
24896
+ endIndex: ranges.length > 0 ? ranges[ranges.length - 1][1] : 0,
24897
+ colStride,
24898
+ rowStride,
24899
+ horizontal,
24900
+ headers,
24901
+ ranges
24902
+ };
24903
+ };
24904
+ const extentOf = (index) => {
24905
+ const { line, section } = positionOf(index);
24906
+ const s = sections[section];
24907
+ const start = s.itemsOffset + line * stride;
24908
+ return [line === 0 && s.group ? s.headerOffset : start, start + itemExtent];
24909
+ };
24910
+ const neighbour = (index, dLine) => {
24911
+ if (count === 0) return index;
24912
+ const si = sectionOfIndex(sections, index);
24913
+ const s = sections[si];
24914
+ const local = index - s.first;
24915
+ const col = local % cols;
24916
+ if (dLine > 0) {
24917
+ const t = local + cols;
24918
+ if (t <= s.count - 1) return s.first + t;
24919
+ const lastLine = Math.floor((s.count - 1) / cols);
24920
+ if (Math.floor(local / cols) < lastLine) return s.first + s.count - 1;
24921
+ for (let n = si + 1; n < sections.length; n++) if (sections[n].count > 0) return sections[n].first + Math.min(col, sections[n].count - 1);
24922
+ return s.first + s.count - 1;
24923
+ }
24924
+ const t = local - cols;
24925
+ if (t >= 0) return s.first + t;
24926
+ for (let n = si - 1; n >= 0; n--) {
24927
+ const prev = sections[n];
24928
+ if (prev.count > 0) {
24929
+ const line = Math.floor((prev.count - 1) / cols);
24930
+ return prev.first + Math.min(line * cols + col, prev.count - 1);
24931
+ }
24932
+ }
24933
+ return s.first;
24934
+ };
24935
+ const hitTest = (x0, x1, y0, y1) => {
24936
+ const out = [];
24937
+ if (count === 0) return out;
24938
+ const main0 = horizontal ? x0 : y0;
24939
+ const main1 = horizontal ? x1 : y1;
24940
+ const start = sectionAtOffset(sections, main0 - stride);
24941
+ for (let si = Math.max(0, start); si < sections.length; si++) {
24942
+ const s = sections[si];
24943
+ if (s.itemsOffset > main1) break;
24944
+ if (s.count === 0 || s.end < main0) continue;
24945
+ if (horizontal) {
24946
+ const a = Math.max(0, Math.floor((x0 - s.itemsOffset) / colStride));
24947
+ const b = Math.min(s.count - 1, Math.floor((x1 - s.itemsOffset) / colStride));
24948
+ for (let i = a; i <= b; i++) out.push(s.first + i);
24949
+ } else {
24950
+ const colLo = Math.max(0, Math.floor(x0 / colStride));
24951
+ const colHi = Math.min(cols - 1, Math.floor(x1 / colStride));
24952
+ const rowLo = Math.max(0, Math.floor((y0 - s.itemsOffset) / rowStride));
24953
+ const rowHi = Math.floor((y1 - s.itemsOffset) / rowStride);
24954
+ for (let row = rowLo; row <= rowHi; row++) for (let col = colLo; col <= colHi; col++) {
24955
+ const local = row * cols + col;
24956
+ if (local >= 0 && local < s.count) out.push(s.first + local);
24957
+ }
24958
+ }
24959
+ }
24960
+ return out;
24961
+ };
24962
+ const anchorAt = (mainOffset) => {
24963
+ if (count === 0) return {
24964
+ index: 0,
24965
+ lineFrac: 0
24966
+ };
24967
+ let si = sectionAtOffset(sections, mainOffset);
24968
+ while (si < sections.length - 1 && sections[si].count === 0) si++;
24969
+ const s = sections[si];
24970
+ if (s.count === 0) return {
24971
+ index: 0,
24972
+ lineFrac: 0
24973
+ };
24974
+ const centreLine = Math.max(0, mainOffset - s.itemsOffset) / stride;
24975
+ const lineFloor = Math.max(0, Math.floor(centreLine));
24976
+ const local = horizontal ? Math.min(s.count - 1, lineFloor) : Math.min(s.count - 1, lineFloor * cols + Math.max(0, Math.floor((cols - 1) / 2)));
24977
+ return {
24978
+ index: s.first + local,
24979
+ lineFrac: centreLine - lineFloor
24980
+ };
24981
+ };
24982
+ const offsetOfAnchor = (index, lineFrac) => {
24983
+ const { line, section } = positionOf(index);
24984
+ return sections[section].itemsOffset + (line + lineFrac) * stride;
24985
+ };
24986
+ const snapTarget = (offset) => {
24987
+ const si = sectionAtOffset(sections, offset);
24988
+ const s = sections[si];
24989
+ let rel = Math.round((offset - s.itemsOffset) / stride);
24990
+ if (grouped) {
24991
+ const lines = s.count === 0 ? 0 : horizontal ? s.count : Math.ceil(s.count / cols);
24992
+ rel = Math.max(0, Math.min(Math.max(0, lines - 1), rel));
24993
+ }
24994
+ const candidates = [s.itemsOffset + rel * stride];
24995
+ if (grouped) {
24996
+ candidates.push(s.headerOffset);
24997
+ if (si + 1 < sections.length) candidates.push(sections[si + 1].headerOffset);
24998
+ }
24999
+ return candidates.reduce((best, c) => Math.abs(c - offset) < Math.abs(best - offset) ? c : best);
25000
+ };
25001
+ return {
25002
+ horizontal,
25003
+ cols,
25004
+ colStride,
25005
+ rowStride,
25006
+ grouped,
25007
+ count,
25008
+ totalExtent: total,
25009
+ window,
25010
+ positionOf,
25011
+ extentOf,
25012
+ neighbour,
25013
+ hitTest,
25014
+ anchorAt,
25015
+ offsetOfAnchor,
25016
+ snapTarget
25017
+ };
25018
+ }
25019
+ //#endregion
24212
25020
  //#region src/grid-list/grid-list-source.ts
25021
+ /** Reorder `effective` raw indices into contiguous groups, dropping collapsed ones. */
25022
+ function applyGrouping(effective, cfg, keyOfRaw) {
25023
+ const parts = partitionByGroup(effective, (raw) => cfg.groupKeyOf(raw), cfg.labelOf ? (key, raws) => cfg.labelOf(key, raws.map((r) => cfg.itemAt(r))) : void 0);
25024
+ const order = [];
25025
+ const groups = [];
25026
+ const groupByKey = /* @__PURE__ */ new Map();
25027
+ for (const part of parts) {
25028
+ const collapsed = cfg.collapsed.has(part.key);
25029
+ groups.push({
25030
+ key: part.key,
25031
+ label: part.label,
25032
+ count: part.items.length,
25033
+ collapsed
25034
+ });
25035
+ for (const raw of part.items) {
25036
+ groupByKey.set(keyOfRaw(raw), part.key);
25037
+ if (!collapsed) order.push(raw);
25038
+ }
25039
+ }
25040
+ return {
25041
+ order,
25042
+ groups,
25043
+ groupByKey
25044
+ };
25045
+ }
24213
25046
  const ITEM_SELECTOR$2 = `:scope > *`;
24214
25047
  const SYNTHETIC_PREFIX = `gl-auto-`;
24215
25048
  /** Whether `key` is an auto-assigned key for a keyless light-DOM item. */
@@ -24236,6 +25069,9 @@ var ElementGridListSource = class {
24236
25069
  #searchKeys;
24237
25070
  #effectiveIndices = [];
24238
25071
  #keysCache;
25072
+ #grouping;
25073
+ #groups = [];
25074
+ #groupByKey = /* @__PURE__ */ new Map();
24239
25075
  constructor(host) {
24240
25076
  this.#host = host;
24241
25077
  this.refresh();
@@ -24288,6 +25124,16 @@ var ElementGridListSource = class {
24288
25124
  if (`searchKeys` in opts) this.#searchKeys = opts.searchKeys;
24289
25125
  this.#recomputeEffective();
24290
25126
  }
25127
+ setGrouping(config) {
25128
+ this.#grouping = config;
25129
+ this.#recomputeEffective();
25130
+ }
25131
+ groups() {
25132
+ return this.#groups;
25133
+ }
25134
+ groupKeyOfKey(key) {
25135
+ return this.#groupByKey.get(key) ?? null;
25136
+ }
24291
25137
  refresh() {
24292
25138
  this.#items = Array.from(this.#host.querySelectorAll(ITEM_SELECTOR$2)).filter((el) => !el.hidden);
24293
25139
  for (const el of this.#items) {
@@ -24330,9 +25176,21 @@ var ElementGridListSource = class {
24330
25176
  }
24331
25177
  effective.push(i);
24332
25178
  }
24333
- this.#effectiveIndices = effective;
25179
+ this.#applyGroupingTo(effective);
24334
25180
  this.#keysCache = void 0;
24335
25181
  }
25182
+ #applyGroupingTo(effective) {
25183
+ if (this.#grouping === void 0) {
25184
+ this.#effectiveIndices = effective;
25185
+ this.#groups = [];
25186
+ this.#groupByKey = /* @__PURE__ */ new Map();
25187
+ return;
25188
+ }
25189
+ const grouped = applyGrouping(effective, this.#grouping, (raw) => this.#itemKey(this.#items[raw]));
25190
+ this.#effectiveIndices = grouped.order;
25191
+ this.#groups = grouped.groups;
25192
+ this.#groupByKey = grouped.groupByKey;
25193
+ }
24336
25194
  #itemKey(el) {
24337
25195
  const userKey = keyForElement(el);
24338
25196
  if (userKey !== void 0) return userKey;
@@ -24355,6 +25213,9 @@ var DataGridListSource = class {
24355
25213
  #searchKeys;
24356
25214
  #effectiveIndices = [];
24357
25215
  #keysCache;
25216
+ #grouping;
25217
+ #groups = [];
25218
+ #groupByKey = /* @__PURE__ */ new Map();
24358
25219
  constructor(items, adapter) {
24359
25220
  this.#items = items;
24360
25221
  this.#adapter = adapter;
@@ -24420,6 +25281,16 @@ var DataGridListSource = class {
24420
25281
  if (`searchKeys` in opts) this.#searchKeys = opts.searchKeys;
24421
25282
  this.#recomputeEffective();
24422
25283
  }
25284
+ setGrouping(config) {
25285
+ this.#grouping = config;
25286
+ this.#recomputeEffective();
25287
+ }
25288
+ groups() {
25289
+ return this.#groups;
25290
+ }
25291
+ groupKeyOfKey(key) {
25292
+ return this.#groupByKey.get(key) ?? null;
25293
+ }
24423
25294
  refresh() {
24424
25295
  this.#recomputeEffective();
24425
25296
  }
@@ -24444,7 +25315,16 @@ var DataGridListSource = class {
24444
25315
  if (this.#searchKeys !== void 0 && !this.#searchKeys.has(this.#adapter.key(this.#items[i], i))) continue;
24445
25316
  effective.push(i);
24446
25317
  }
24447
- this.#effectiveIndices = effective;
25318
+ if (this.#grouping === void 0) {
25319
+ this.#effectiveIndices = effective;
25320
+ this.#groups = [];
25321
+ this.#groupByKey = /* @__PURE__ */ new Map();
25322
+ } else {
25323
+ const grouped = applyGrouping(effective, this.#grouping, (raw) => this.#adapter.key(this.#items[raw], raw));
25324
+ this.#effectiveIndices = grouped.order;
25325
+ this.#groups = grouped.groups;
25326
+ this.#groupByKey = grouped.groupByKey;
25327
+ }
24448
25328
  this.#keysCache = void 0;
24449
25329
  }
24450
25330
  };
@@ -24460,76 +25340,11 @@ const SNAP_DEBOUNCE_MS = 120;
24460
25340
  * to finish. Kept a touch above the default `--grid-list-item-move-duration`.
24461
25341
  */
24462
25342
  const REFLOW_WINDOW_MS = 320;
24463
- function computeGridWindow(count, itemWidth, rowHeight, gap, viewportWidth, viewportHeight, scrollOffset, orientation, overscan = 2) {
24464
- const w = Math.max(1, itemWidth);
24465
- const h = Math.max(1, rowHeight);
24466
- const g = Math.max(0, gap);
24467
- const colStride = w + g;
24468
- const rowStride = h + g;
24469
- const offset = Math.max(0, scrollOffset);
24470
- if (orientation === `row`) {
24471
- const rows = count === 0 ? 0 : 1;
24472
- const totalWidth = count === 0 ? 0 : (count - 1) * colStride + w;
24473
- if (count === 0 || viewportWidth <= 0) return {
24474
- cols: count,
24475
- rows,
24476
- totalHeight: 0,
24477
- totalWidth,
24478
- startIndex: 0,
24479
- endIndex: 0,
24480
- colStride,
24481
- rowStride,
24482
- horizontal: true
24483
- };
24484
- const firstVisible = Math.max(0, Math.floor(offset / colStride));
24485
- const lastVisible = Math.ceil((offset + viewportWidth) / colStride);
24486
- return {
24487
- cols: count,
24488
- rows,
24489
- totalHeight: 0,
24490
- totalWidth,
24491
- startIndex: Math.max(0, firstVisible - overscan),
24492
- endIndex: Math.min(count, lastVisible + overscan),
24493
- colStride,
24494
- rowStride,
24495
- horizontal: true
24496
- };
24497
- }
24498
- const cols = orientation === `column` ? 1 : Math.max(1, Math.floor((Math.max(0, viewportWidth) + g) / colStride));
24499
- const rows = count === 0 ? 0 : Math.ceil(count / cols);
24500
- const totalHeight = rows === 0 ? 0 : (rows - 1) * rowStride + h;
24501
- if (count === 0 || viewportHeight <= 0) return {
24502
- cols,
24503
- rows,
24504
- totalHeight,
24505
- totalWidth: 0,
24506
- startIndex: 0,
24507
- endIndex: 0,
24508
- colStride,
24509
- rowStride,
24510
- horizontal: false
24511
- };
24512
- const firstVisibleRow = Math.max(0, Math.floor(offset / rowStride));
24513
- const lastVisibleRow = Math.ceil((offset + viewportHeight) / rowStride);
24514
- const rowStart = Math.max(0, firstVisibleRow - overscan);
24515
- const rowEnd = Math.min(rows, lastVisibleRow + overscan);
24516
- return {
24517
- cols,
24518
- rows,
24519
- totalHeight,
24520
- totalWidth: 0,
24521
- startIndex: rowStart * cols,
24522
- endIndex: Math.min(count, rowEnd * cols),
24523
- colStride,
24524
- rowStride,
24525
- horizontal: false
24526
- };
25343
+ function readProperty(item, name) {
25344
+ return typeof item === `object` && item !== null ? item[name] : void 0;
24527
25345
  }
24528
- function columnOf(index, cols) {
24529
- return cols <= 1 ? 0 : index % cols;
24530
- }
24531
- function rowOf(index, cols) {
24532
- return cols <= 1 ? index : Math.floor(index / cols);
25346
+ function readAttribute(el, name) {
25347
+ return el.getAttribute(`data-${name}`);
24533
25348
  }
24534
25349
  /** Shallow key-array equality, treating `undefined` (never dispatched) as different. */
24535
25350
  function sameKeys$1(a, b) {
@@ -24557,6 +25372,11 @@ let GridListElement = class GridListElement extends LitElement {
24557
25372
  this.marqueeFromCellBlank = false;
24558
25373
  this.snapToItem = false;
24559
25374
  this.items = [];
25375
+ this.groupBy = null;
25376
+ this.crossGroupSelection = false;
25377
+ this.groupHeaderHeight = 28;
25378
+ this.groupHeaderWidth = 120;
25379
+ this._collapsedGroups = /* @__PURE__ */ new Set();
24560
25380
  this._scrollTop = 0;
24561
25381
  this._scrollLeft = 0;
24562
25382
  this._viewportHeight = 0;
@@ -24581,6 +25401,12 @@ let GridListElement = class GridListElement extends LitElement {
24581
25401
  interactionMode: this.interactionMode
24582
25402
  }),
24583
25403
  getKey: (k) => k,
25404
+ getGroupKey: (k) => this.groupBy === null ? null : this.#source.groupKeyOfKey(k),
25405
+ crossGroupSelection: () => this.crossGroupSelection,
25406
+ getFallbackGroupKey: () => {
25407
+ const k = this.tickled.tickledId;
25408
+ return k !== void 0 && this.groupBy !== null ? this.#source.groupKeyOfKey(k) : null;
25409
+ },
24584
25410
  onChange: (selected, previous) => {
24585
25411
  this._syncItemStates();
24586
25412
  this.requestUpdate();
@@ -24678,10 +25504,11 @@ let GridListElement = class GridListElement extends LitElement {
24678
25504
  },
24679
25505
  shouldStart: (event) => {
24680
25506
  if (this.marqueeFromCellBlank && this.#isCellBlankPress(event)) return true;
24681
- for (const node of event.composedPath()) if (node instanceof Element && node.classList.contains(`cell`)) return false;
25507
+ for (const node of event.composedPath()) if (node instanceof Element && (node.classList.contains(`cell`) || node.classList.contains(`group-header`))) return false;
24682
25508
  return true;
24683
25509
  }
24684
25510
  });
25511
+ this.#groupByChanged = false;
24685
25512
  this.#selectedSnapshot = /* @__PURE__ */ new Set();
24686
25513
  this.#onScroll = (e) => {
24687
25514
  const el = e.target;
@@ -24731,15 +25558,25 @@ let GridListElement = class GridListElement extends LitElement {
24731
25558
  const count = this.#source.count;
24732
25559
  if (count === 0) return;
24733
25560
  const win = this.#currentWindow();
24734
- const move = (delta) => {
24735
- const from = this.tickled.tickledId ? this.#source.indexOfKey(this.tickled.tickledId) : -1;
24736
- const nextIndex = Math.max(0, Math.min(count - 1, from + delta));
25561
+ const layout = this.#layout();
25562
+ const current = () => this.tickled.tickledId ? this.#source.indexOfKey(this.tickled.tickledId) : -1;
25563
+ const goto = (nextIndex) => {
24737
25564
  const nextKey = this.#source.keyAt(nextIndex);
24738
25565
  if (nextKey) {
24739
25566
  this.tickled.handleKeyboardNavigation(nextKey);
24740
25567
  this.#scrollItemIntoView(nextKey);
24741
25568
  }
24742
25569
  };
25570
+ const move = (delta) => goto(Math.max(0, Math.min(count - 1, current() + delta)));
25571
+ const moveLines = (dir, lines = 1) => {
25572
+ let index = current();
25573
+ if (index < 0) {
25574
+ move(dir * lines * win.cols);
25575
+ return;
25576
+ }
25577
+ for (let i = 0; i < lines; i++) index = layout.neighbour(index, dir);
25578
+ goto(index);
25579
+ };
24743
25580
  const perLine = this.orientation === `grid` ? win.cols : 1;
24744
25581
  switch (e.key) {
24745
25582
  case `ArrowLeft`:
@@ -24749,10 +25586,12 @@ let GridListElement = class GridListElement extends LitElement {
24749
25586
  move(1);
24750
25587
  break;
24751
25588
  case `ArrowUp`:
24752
- move(-perLine);
25589
+ if (this.orientation === `grid`) moveLines(-1);
25590
+ else move(-perLine);
24753
25591
  break;
24754
25592
  case `ArrowDown`:
24755
- move(perLine);
25593
+ if (this.orientation === `grid`) moveLines(1);
25594
+ else move(perLine);
24756
25595
  break;
24757
25596
  case `Home`:
24758
25597
  this.tickled.handleKeyboardNavigation(`first`);
@@ -24767,7 +25606,8 @@ let GridListElement = class GridListElement extends LitElement {
24767
25606
  const mainExtent = win.horizontal ? this._viewportWidth : this._viewportHeight;
24768
25607
  const mainStride = win.horizontal ? win.colStride : win.rowStride;
24769
25608
  const visibleLines = Math.max(1, Math.floor(mainExtent / mainStride));
24770
- move((e.key === `PageDown` ? 1 : -1) * visibleLines * perLine);
25609
+ if (this.orientation === `grid`) moveLines(e.key === `PageDown` ? 1 : -1, visibleLines);
25610
+ else move((e.key === `PageDown` ? 1 : -1) * visibleLines * perLine);
24771
25611
  break;
24772
25612
  }
24773
25613
  case `Enter`: {
@@ -24848,6 +25688,11 @@ let GridListElement = class GridListElement extends LitElement {
24848
25688
  position: relative;
24849
25689
  width: 100%;
24850
25690
  }
25691
+ .group-header {
25692
+ position: absolute;
25693
+ inset: 0 auto auto 0;
25694
+ z-index: 1;
25695
+ }
24851
25696
  .cell {
24852
25697
  position: absolute;
24853
25698
  inset: 0 auto auto 0;
@@ -25141,6 +25986,9 @@ let GridListElement = class GridListElement extends LitElement {
25141
25986
  specificity. Survivors slide to their new slot; a freshly inserted cell
25142
25987
  also fades in. (A removed item's element is already gone, so there's
25143
25988
  nothing left to fade — the gap simply closes as the others slide.) */
25989
+ :host([reflowing]) .group-header {
25990
+ transition: transform var(--grid-list-item-move-duration, 200ms) cubic-bezier(0.2, 0, 0, 1);
25991
+ }
25144
25992
  :host([reflowing]) .cell {
25145
25993
  transition:
25146
25994
  transform var(--grid-list-item-move-duration, 200ms) cubic-bezier(0.2, 0, 0, 1),
@@ -25151,6 +25999,7 @@ let GridListElement = class GridListElement extends LitElement {
25151
25999
  animation: ixfx-list-item-in var(--grid-list-item-move-duration, 200ms) ease both;
25152
26000
  }
25153
26001
  @media (prefers-reduced-motion: reduce) {
26002
+ :host([reflowing]) .group-header { transition: none; }
25154
26003
  :host([reflowing]) .cell { transition: background var(--transition), border-color var(--transition); }
25155
26004
  .cell.entering { animation: none; }
25156
26005
  }
@@ -25292,31 +26141,13 @@ let GridListElement = class GridListElement extends LitElement {
25292
26141
  * so virtualized off-screen items are covered without DOM measurement.
25293
26142
  */
25294
26143
  #marqueeHitTest(rect) {
25295
- const count = this.#source.count;
25296
- if (count === 0) return [];
26144
+ if (this.#source.count === 0) return [];
25297
26145
  const horizontal = this.orientation === `row`;
25298
- const win = computeGridWindow(count, this.itemSize, this.#rowHeight(), this.gap, Math.max(1, this._viewportWidth), Math.max(1, this._viewportHeight), horizontal ? this._scrollLeft : this._scrollTop, this.orientation, 0);
25299
- const out = [];
25300
- if (horizontal) {
25301
- const x0 = rect.x + this._scrollLeft;
25302
- const x1 = x0 + rect.width;
25303
- const lo = Math.max(0, Math.floor(x0 / win.colStride));
25304
- const hi = Math.min(count - 1, Math.floor(x1 / win.colStride));
25305
- for (let i = lo; i <= hi; i++) out.push(i);
25306
- } else {
25307
- const x0 = rect.x + this._scrollLeft;
25308
- const x1 = x0 + rect.width;
25309
- const y0 = rect.y + this._scrollTop;
25310
- const y1 = y0 + rect.height;
25311
- const colLo = Math.max(0, Math.floor(x0 / win.colStride));
25312
- const colHi = Math.min(win.cols - 1, Math.floor(x1 / win.colStride));
25313
- const rowLo = Math.max(0, Math.floor(y0 / win.rowStride));
25314
- const rowHi = Math.floor(y1 / win.rowStride);
25315
- for (let row = rowLo; row <= rowHi; row++) for (let col = colLo; col <= colHi; col++) {
25316
- const index = row * win.cols + col;
25317
- if (index >= 0 && index < count) out.push(index);
25318
- }
25319
- }
26146
+ const x0 = rect.x + this._scrollLeft;
26147
+ const x1 = x0 + rect.width;
26148
+ const y0 = horizontal ? 0 : rect.y + this._scrollTop;
26149
+ const y1 = horizontal ? 0 : y0 + rect.height;
26150
+ const out = this.#layout().hitTest(x0, x1, y0, y1);
25320
26151
  const keys = [];
25321
26152
  for (const index of out) {
25322
26153
  const key = this.#source.keyAt(index);
@@ -25466,6 +26297,10 @@ let GridListElement = class GridListElement extends LitElement {
25466
26297
  if (previousLength === 0 && removedCount === 0) return;
25467
26298
  if (added.length > 0 && added.length === this.#items.length) return;
25468
26299
  for (const el of added) this.#entering.add(el);
26300
+ this.#openReflowWindow();
26301
+ }
26302
+ /** Open (or extend) the `[reflowing]` window so moved cells and headers slide. */
26303
+ #openReflowWindow() {
25469
26304
  this.toggleAttribute(`reflowing`, true);
25470
26305
  if (this.#reflowTimer) clearTimeout(this.#reflowTimer);
25471
26306
  this.#reflowTimer = window.setTimeout(() => {
@@ -25522,6 +26357,7 @@ let GridListElement = class GridListElement extends LitElement {
25522
26357
  this.#sourceElement = new ElementGridListSource(this);
25523
26358
  this.#source = this.#sourceElement;
25524
26359
  }
26360
+ this.#source.setGrouping(this.#groupingConfig());
25525
26361
  this.#applyFilters();
25526
26362
  if (this.#searchFilterKeys) this.#source.setFilters({ searchKeys: this.#searchFilterKeys });
25527
26363
  }
@@ -25563,6 +26399,17 @@ let GridListElement = class GridListElement extends LitElement {
25563
26399
  }
25564
26400
  this.search?.refresh();
25565
26401
  }
26402
+ const groupsChanged = changed.has(`groupBy`) || changed.has(`groupLabel`) || changed.has(`_collapsedGroups`);
26403
+ if (groupsChanged && this.hasUpdated) {
26404
+ if (changed.has(`groupBy`)) {
26405
+ if (this._collapsedGroups.size > 0) this._collapsedGroups = /* @__PURE__ */ new Set();
26406
+ this.#groupByChanged = true;
26407
+ }
26408
+ this.#applyGroupingToSource();
26409
+ this.#dropUnreachableCursor();
26410
+ this.#openReflowWindow();
26411
+ this.updateComplete.then(() => this.#emitViewportChange());
26412
+ } else if (groupsChanged) this.#applyGroupingToSource();
25566
26413
  if (changed.has(`itemSize`) || changed.has(`gap`)) {
25567
26414
  this.itemSize = Math.max(MIN_ITEM_SIZE, Math.round(this.itemSize));
25568
26415
  this.gap = Math.max(0, Math.round(this.gap));
@@ -25583,6 +26430,62 @@ let GridListElement = class GridListElement extends LitElement {
25583
26430
  }
25584
26431
  }
25585
26432
  }
26433
+ #groupByChanged;
26434
+ /** Grouping config for the active source, or `undefined` when ungrouped. */
26435
+ #groupingConfig() {
26436
+ const by = this.groupBy;
26437
+ if (by === null || by === void 0 || by === ``) return void 0;
26438
+ const collapsed = this._collapsedGroups;
26439
+ const src = this.#source;
26440
+ if (src instanceof DataGridListSource) return {
26441
+ groupKeyOf: (raw) => resolveGroupKey(by, src.rawItemAt(raw), raw, readProperty),
26442
+ itemAt: (raw) => src.rawItemAt(raw),
26443
+ labelOf: this.groupLabel,
26444
+ collapsed
26445
+ };
26446
+ const elements = src;
26447
+ return {
26448
+ groupKeyOf: (raw) => resolveGroupKey(by, elements.rawElementAt(raw), raw, readAttribute),
26449
+ itemAt: (raw) => elements.rawElementAt(raw),
26450
+ labelOf: this.groupLabel,
26451
+ collapsed
26452
+ };
26453
+ }
26454
+ /** Push the current grouping into the source and refresh the element-mode item cache. */
26455
+ #applyGroupingToSource() {
26456
+ this.#source.setGrouping(this.#groupingConfig());
26457
+ if (this.#source instanceof ElementGridListSource) this.#items = this.#source.effectiveElements();
26458
+ }
26459
+ /** The cursor can't stay on an item that a collapsed group just removed. */
26460
+ #dropUnreachableCursor() {
26461
+ const id = this.tickled.tickledId;
26462
+ if (id !== void 0 && this.#source.indexOfKey(id) < 0) this.tickled.focus(void 0);
26463
+ }
26464
+ /** Groups in display order (empty when ungrouped). */
26465
+ get groups() {
26466
+ return this.groupBy === null ? [] : this.#source.groups();
26467
+ }
26468
+ /** Collapse or expand one group. Fires `list-group-toggle` when the state changes. */
26469
+ setGroupCollapsed(groupKey, collapsed) {
26470
+ if (this._collapsedGroups.has(groupKey) === collapsed) return;
26471
+ const next = new Set(this._collapsedGroups);
26472
+ if (collapsed) next.add(groupKey);
26473
+ else next.delete(groupKey);
26474
+ this._collapsedGroups = next;
26475
+ this._dispatch(`list-group-toggle`, {
26476
+ groupKey,
26477
+ collapsed
26478
+ });
26479
+ }
26480
+ toggleGroup(groupKey) {
26481
+ this.setGroupCollapsed(groupKey, !this._collapsedGroups.has(groupKey));
26482
+ }
26483
+ collapseAllGroups() {
26484
+ for (const g of this.groups) this.setGroupCollapsed(g.key, true);
26485
+ }
26486
+ expandAllGroups() {
26487
+ for (const key of [...this._collapsedGroups]) this.setGroupCollapsed(key, false);
26488
+ }
25586
26489
  #syncAriaMultiselectable() {
25587
26490
  this.setAttribute(`aria-multiselectable`, this.selection.canSelectMultiple() ? `true` : `false`);
25588
26491
  }
@@ -25622,6 +26525,10 @@ let GridListElement = class GridListElement extends LitElement {
25622
26525
  }
25623
26526
  this.#applyZoomAnchor();
25624
26527
  this.#activateTickledPan();
26528
+ if (this.#groupByChanged) {
26529
+ this.#groupByChanged = false;
26530
+ this._dispatch(`list-group-change`, { groupBy: this.groupBy });
26531
+ }
25625
26532
  }
25626
26533
  /** Re-anchor the scroll so the item captured in `willUpdate` stays centred. */
25627
26534
  #applyZoomAnchor() {
@@ -25630,41 +26537,33 @@ let GridListElement = class GridListElement extends LitElement {
25630
26537
  this.#zoomAnchor = void 0;
25631
26538
  const vp = this.#viewportRef.value;
25632
26539
  if (!vp) return;
25633
- const win = this.#currentWindow();
25634
- if (win.horizontal) {
25635
- if (this._viewportWidth <= 0) return;
25636
- const targetCenter = (index + lineFrac) * win.colStride;
25637
- const maxScroll = Math.max(0, win.totalWidth - this._viewportWidth);
25638
- const next = Math.min(maxScroll, Math.max(0, targetCenter - this._viewportWidth / 2));
26540
+ const layout = this.#layout();
26541
+ const horizontal = layout.horizontal;
26542
+ const extent = horizontal ? this._viewportWidth : this._viewportHeight;
26543
+ if (extent <= 0) return;
26544
+ const targetCenter = layout.offsetOfAnchor(index, lineFrac);
26545
+ const maxScroll = Math.max(0, layout.totalExtent - extent);
26546
+ const next = Math.min(maxScroll, Math.max(0, targetCenter - extent / 2));
26547
+ if (horizontal) {
25639
26548
  if (Math.abs(vp.scrollLeft - next) > .5) {
25640
26549
  vp.scrollLeft = next;
25641
26550
  this._scrollLeft = next;
25642
26551
  }
25643
- return;
25644
- }
25645
- if (this._viewportHeight <= 0) return;
25646
- const targetCenter = ((win.cols > 0 ? Math.floor(index / win.cols) : 0) + lineFrac) * win.rowStride;
25647
- const maxScroll = Math.max(0, win.totalHeight - this._viewportHeight);
25648
- const next = Math.min(maxScroll, Math.max(0, targetCenter - this._viewportHeight / 2));
25649
- if (Math.abs(vp.scrollTop - next) > .5) {
26552
+ } else if (Math.abs(vp.scrollTop - next) > .5) {
25650
26553
  vp.scrollTop = next;
25651
26554
  this._scrollTop = next;
25652
26555
  }
25653
26556
  }
25654
26557
  #captureCentreAnchor(prevOffset, prevExtent, prevItemSize, prevGap, prevCaptionStyle, prevCaptionHeight, prevOrientation) {
25655
- const count = this.#source?.count ?? 0;
25656
- if (count === 0) return;
26558
+ if ((this.#source?.count ?? 0) === 0) return;
25657
26559
  const prevRowHeight = prevItemSize + (prevCaptionStyle === `separate` || prevCaptionStyle === `under` ? prevCaptionHeight : 0);
25658
- const horizontal = prevOrientation === `row`;
25659
- const prev = computeGridWindow(count, prevItemSize, prevRowHeight, prevGap, this._viewportWidth, this._viewportHeight, prevOffset, prevOrientation);
25660
- const mainStride = horizontal ? prev.colStride : prev.rowStride;
25661
- const centerLine = (prevOffset + prevExtent / 2) / mainStride;
25662
- const lineFloor = Math.max(0, Math.floor(centerLine));
25663
- const index = horizontal ? Math.min(count - 1, lineFloor) : Math.min(count - 1, lineFloor * prev.cols + Math.max(0, Math.floor((prev.cols - 1) / 2)));
25664
- this.#zoomAnchor = {
25665
- index,
25666
- lineFrac: centerLine - lineFloor
25667
- };
26560
+ const prev = this.#buildLayout({
26561
+ itemWidth: prevItemSize,
26562
+ rowHeight: prevRowHeight,
26563
+ gap: prevGap,
26564
+ orientation: prevOrientation
26565
+ });
26566
+ this.#zoomAnchor = prev.anchorAt(prevOffset + prevExtent / 2);
25668
26567
  }
25669
26568
  /**
25670
26569
  * Re-read the viewport after an ancestor's visibility/layout changed.
@@ -25878,37 +26777,75 @@ let GridListElement = class GridListElement extends LitElement {
25878
26777
  return this.#windowKeys(this.#currentWindow(true));
25879
26778
  }
25880
26779
  #windowKeys(win) {
25881
- return this.#source.keys().slice(win.startIndex, win.endIndex).filter((k) => !isSyntheticKey(k));
26780
+ const keys = this.#source.keys();
26781
+ const out = [];
26782
+ for (const [start, end] of win.ranges) for (let i = start; i < end; i++) if (!isSyntheticKey(keys[i])) out.push(keys[i]);
26783
+ return out;
26784
+ }
26785
+ /** Layout params for the current geometry, with optional overrides (used for zoom anchoring). */
26786
+ #layoutParams(over = {}) {
26787
+ return {
26788
+ count: this.#source.count,
26789
+ groups: this.groupBy === null ? void 0 : this.#source.groups(),
26790
+ itemWidth: this.itemSize,
26791
+ rowHeight: this.#rowHeight(),
26792
+ gap: this.gap,
26793
+ viewportWidth: this._viewportWidth,
26794
+ viewportHeight: this._viewportHeight,
26795
+ orientation: this.orientation,
26796
+ headerHeight: this.groupHeaderHeight,
26797
+ headerWidth: this.groupHeaderWidth,
26798
+ ...over
26799
+ };
26800
+ }
26801
+ #buildLayout(over = {}) {
26802
+ return buildLayout(this.#layoutParams(over));
26803
+ }
26804
+ #layoutCache;
26805
+ /** The current layout, rebuilt only when its inputs change (never per scroll). */
26806
+ #layout() {
26807
+ const params = this.#layoutParams();
26808
+ const cached = this.#layoutCache;
26809
+ if (cached) {
26810
+ const a = cached.params;
26811
+ let same = a.groups === params.groups;
26812
+ for (const k of Object.keys(params)) {
26813
+ if (!same) break;
26814
+ same = a[k] === params[k];
26815
+ }
26816
+ if (same) return cached.layout;
26817
+ }
26818
+ const layout = buildLayout(params);
26819
+ this.#layoutCache = {
26820
+ params,
26821
+ layout
26822
+ };
26823
+ return layout;
25882
26824
  }
25883
26825
  #currentWindow(noOverscan = false) {
25884
- const overscan = noOverscan ? 0 : 2;
25885
- return computeGridWindow(this.#source.count, this.itemSize, this.#rowHeight(), this.gap, this._viewportWidth, this._viewportHeight, this.orientation === `row` ? this._scrollLeft : this._scrollTop, this.orientation, overscan);
26826
+ return this.#layout().window(this.orientation === `row` ? this._scrollLeft : this._scrollTop, noOverscan ? 0 : 2);
25886
26827
  }
25887
26828
  /** Adjust the scroll offset so `key` is fully within the viewport. */
25888
26829
  #scrollItemIntoView(key) {
25889
26830
  const index = this.#source.indexOfKey(key);
25890
26831
  const vp = this.#viewportRef.value;
25891
26832
  if (index < 0 || !vp) return;
25892
- const win = this.#currentWindow();
25893
- if (win.horizontal) {
25894
- const left = index * win.colStride;
25895
- const right = left + this.itemSize;
26833
+ const [start, end] = this.#layout().extentOf(index);
26834
+ if (this.orientation === `row`) {
25896
26835
  const viewLeft = vp.scrollLeft;
25897
26836
  let next = viewLeft;
25898
- if (left < viewLeft) next = left;
25899
- else if (right > viewLeft + this._viewportWidth) next = right - this._viewportWidth;
26837
+ if (start < viewLeft) next = start;
26838
+ else if (end > viewLeft + this._viewportWidth) next = end - this._viewportWidth;
25900
26839
  if (next !== viewLeft) {
25901
26840
  vp.scrollLeft = next;
25902
26841
  this._scrollLeft = next;
25903
26842
  }
25904
26843
  return;
25905
26844
  }
25906
- const top = (win.cols > 0 ? Math.floor(index / win.cols) : 0) * win.rowStride;
25907
- const bottom = top + this.#rowHeight();
25908
26845
  const viewTop = vp.scrollTop;
25909
26846
  let next = viewTop;
25910
- if (top < viewTop) next = top;
25911
- else if (bottom > viewTop + this._viewportHeight) next = bottom - this._viewportHeight;
26847
+ if (start < viewTop) next = start;
26848
+ else if (end > viewTop + this._viewportHeight) next = end - this._viewportHeight;
25912
26849
  if (next !== viewTop) {
25913
26850
  vp.scrollTop = next;
25914
26851
  this._scrollTop = next;
@@ -25982,6 +26919,7 @@ let GridListElement = class GridListElement extends LitElement {
25982
26919
  >
25983
26920
  <div class="spacer" style=${spacerStyle}>
25984
26921
  ${this.#source instanceof DataGridListSource ? this.#renderDataCells(win) : this.#renderElementCells(win)}
26922
+ ${this.#renderHeaders(win)}
25985
26923
  </div>
25986
26924
  ${this.#marqueeRect ? html`<div class="marquee" part="marquee" style=${this.#marqueeStyle(this.#marqueeRect)}></div>` : ``}
25987
26925
  </div>
@@ -25989,7 +26927,7 @@ let GridListElement = class GridListElement extends LitElement {
25989
26927
  `;
25990
26928
  }
25991
26929
  #renderDataCells(win) {
25992
- const slice = Array.from({ length: win.endIndex - win.startIndex }, (_, i) => win.startIndex + i);
26930
+ const slice = win.ranges.flatMap(([start, end]) => Array.from({ length: end - start }, (_, i) => start + i));
25993
26931
  return repeat(slice, (index) => this.#source.keyAt(index), (index) => this.#renderDataCell(index, win));
25994
26932
  }
25995
26933
  #renderDataCell(index, win) {
@@ -26004,8 +26942,7 @@ let GridListElement = class GridListElement extends LitElement {
26004
26942
  const caption = style === `hidden` ? `` : this.#source.labelAt(index);
26005
26943
  const showCaption = caption !== ``;
26006
26944
  const cellHeight = win.rowStride - this.gap;
26007
- const x = win.horizontal ? index * win.colStride : columnOf(index, win.cols) * win.colStride;
26008
- const y = win.horizontal ? Math.max(0, (this._viewportHeight - cellHeight) / 2) : rowOf(index, win.cols) * win.rowStride;
26945
+ const { x, y } = this.#layout().positionOf(index);
26009
26946
  const captionCss = separate ? `height:${Math.max(0, this.captionHeight - 4)}px` : style === `under` ? `height:${this.captionHeight}px` : ``;
26010
26947
  const rawIcon = this.#source.iconAt(index);
26011
26948
  const icon = appearanceAbsent(rawIcon) ? void 0 : rawIcon;
@@ -26091,8 +27028,25 @@ let GridListElement = class GridListElement extends LitElement {
26091
27028
  current.startLoop?.();
26092
27029
  }
26093
27030
  #renderElementCells(win) {
26094
- const slice = this.#items.slice(win.startIndex, win.endIndex);
26095
- return repeat(slice, (_el, i) => this.#source.keyAt(win.startIndex + i), (el, i) => this.#renderCell(el, win.startIndex + i, win));
27031
+ const slice = win.ranges.flatMap(([start, end]) => Array.from({ length: end - start }, (_, i) => start + i));
27032
+ return repeat(slice, (index) => this.#source.keyAt(index), (index) => this.#renderCell(this.#items[index], index, win));
27033
+ }
27034
+ #renderHeaders(win) {
27035
+ if (win.headers.length === 0) return nothing;
27036
+ const horizontal = win.horizontal;
27037
+ return repeat(win.headers, (h) => `__group__${h.key}`, (h) => html`
27038
+ <ixfx-list-group-header
27039
+ class="group-header"
27040
+ part="group-header"
27041
+ data-group-key=${h.key}
27042
+ label=${h.label}
27043
+ count=${h.count}
27044
+ orientation=${horizontal ? `vertical` : `horizontal`}
27045
+ ?collapsed=${h.collapsed}
27046
+ style=${horizontal ? `width:${h.size}px;height:100%;transform:translate(${h.offset}px,0)` : `width:100%;height:${h.size}px;transform:translate(0,${h.offset}px)`}
27047
+ @click=${() => this.toggleGroup(h.key)}
27048
+ ></ixfx-list-group-header>
27049
+ `);
26096
27050
  }
26097
27051
  #renderCell(el, index, win) {
26098
27052
  const key = this.#source.keyAt(index);
@@ -26107,8 +27061,7 @@ let GridListElement = class GridListElement extends LitElement {
26107
27061
  const caption = style === `hidden` ? `` : this.#captionFor(el);
26108
27062
  const showCaption = caption !== ``;
26109
27063
  const cellHeight = win.rowStride - this.gap;
26110
- const x = win.horizontal ? index * win.colStride : columnOf(index, win.cols) * win.colStride;
26111
- const y = win.horizontal ? Math.max(0, (this._viewportHeight - cellHeight) / 2) : rowOf(index, win.cols) * win.rowStride;
27064
+ const { x, y } = this.#layout().positionOf(index);
26112
27065
  const thumbStyle = separate ? `height:${this.itemSize}px` : ``;
26113
27066
  const captionCss = separate ? `height:${Math.max(0, this.captionHeight - 4)}px` : style === `under` ? `height:${this.captionHeight}px` : ``;
26114
27067
  return html`
@@ -26207,16 +27160,12 @@ let GridListElement = class GridListElement extends LitElement {
26207
27160
  #snapToNearestLine() {
26208
27161
  const vp = this.#viewportRef.value;
26209
27162
  if (!vp) return;
26210
- const win = this.#currentWindow();
26211
- const horizontal = win.horizontal;
26212
- const stride = horizontal ? win.colStride : win.rowStride;
26213
- if (stride <= 0) return;
27163
+ const layout = this.#layout();
27164
+ const horizontal = layout.horizontal;
26214
27165
  const offset = horizontal ? vp.scrollLeft : vp.scrollTop;
26215
- const extent = horizontal ? win.totalWidth : win.totalHeight;
26216
27166
  const viewportExtent = horizontal ? this._viewportWidth : this._viewportHeight;
26217
- const maxScroll = Math.max(0, extent - viewportExtent);
26218
- const index = Math.round(offset / stride);
26219
- const target = Math.min(maxScroll, Math.max(0, index * stride));
27167
+ const maxScroll = Math.max(0, layout.totalExtent - viewportExtent);
27168
+ const target = Math.min(maxScroll, Math.max(0, layout.snapTarget(offset)));
26220
27169
  if (Math.abs(target - offset) < .5) return;
26221
27170
  vp.scrollTo(horizontal ? {
26222
27171
  left: target,
@@ -26244,7 +27193,7 @@ let GridListElement = class GridListElement extends LitElement {
26244
27193
  if (mode === `manual`) return;
26245
27194
  if (this.selectionMode === `none` && mode !== `checked` && mode !== `sticky`) return;
26246
27195
  if (this.#dragSelect.isDragging || this.#nativeDragging || this.#nativeDrag.isDragging || this.#postDragSuppressTimer !== 0) return;
26247
- for (const node of e.composedPath()) if (node instanceof Element && node.classList.contains(`cell`)) return;
27196
+ for (const node of e.composedPath()) if (node instanceof Element && (node.classList.contains(`cell`) || node.classList.contains(`group-header`))) return;
26248
27197
  this.selection.clear();
26249
27198
  }
26250
27199
  #startPostDragSuppressTimer() {
@@ -26345,6 +27294,22 @@ __decorate([property({
26345
27294
  })], GridListElement.prototype, "snapToItem", void 0);
26346
27295
  __decorate([property({ attribute: false })], GridListElement.prototype, "items", void 0);
26347
27296
  __decorate([property({ attribute: false })], GridListElement.prototype, "itemAdapter", void 0);
27297
+ __decorate([property({ attribute: `group-by` })], GridListElement.prototype, "groupBy", void 0);
27298
+ __decorate([property({ attribute: false })], GridListElement.prototype, "groupLabel", void 0);
27299
+ __decorate([property({
27300
+ type: Boolean,
27301
+ attribute: `cross-group-selection`,
27302
+ reflect: true
27303
+ })], GridListElement.prototype, "crossGroupSelection", void 0);
27304
+ __decorate([property({
27305
+ type: Number,
27306
+ attribute: `group-header-height`
27307
+ })], GridListElement.prototype, "groupHeaderHeight", void 0);
27308
+ __decorate([property({
27309
+ type: Number,
27310
+ attribute: `group-header-width`
27311
+ })], GridListElement.prototype, "groupHeaderWidth", void 0);
27312
+ __decorate([state()], GridListElement.prototype, "_collapsedGroups", void 0);
26348
27313
  __decorate([state()], GridListElement.prototype, "_scrollTop", void 0);
26349
27314
  __decorate([state()], GridListElement.prototype, "_scrollLeft", void 0);
26350
27315
  __decorate([state()], GridListElement.prototype, "_viewportHeight", void 0);
@@ -53949,6 +54914,6 @@ function init() {
53949
54914
  console.log(`Init`);
53950
54915
  }
53951
54916
  //#endregion
53952
- export { AcTextElement, AcTokenElement, BottomDrawerElement, ButtonElement, Checkbox, CheckedSelect, ColourPicker, ColourPickerPopup, CrumbNavigationElement, CrumbPathController, CrumbPathElement, DEFAULT_SIBLING_SPACING, DataController, DataDisplayComponent, DataGridElement, DataTableElement, DetailListElement, DockHostElement, DockPanelElement, EditableLabel, EditableNumber, FormElement, FormGroupElement, FormPartElement, GridListElement, GroupedItemListerElement, HyperbolicTreeListElement, ICON_CARET_RIGHT, ICON_CHECK, ICON_CHEVRON_DOWN, ICON_CLOSE, IcicleTreeListElement, IncrSearchTreeController as IncrSearchMillerController, IncrSearchTreeController, IxfxHexEditorElement, IxfxIconElement, IxfxTimelineElement, KeyedElementIndex, LIST_ITEM_MOTION_MS, LIST_SEARCH_COMMAND_IDS, LIST_SEARCH_MODES, LabelledRadialInput, LabelledRangeInput, LabelledSliderInput, LedElement, ListDragSelectController, ListNativeDragController, ListSearchController, ListSelectionController, menu_exports as Menu, MillerBaseElement, MillerListElement, MillerTreeController, MulticolListElement, NarrowedTextElement, NotificationManager, NotificationPillElement, PanTextElement, PanelElement, PanelGroupElement, PlotDataSeries, PlotHistogram, PlotMultiAxis, PlotSingleAxis, PlotXyAxis, PolarPad, RadialInputElement, RadioGroup, RangeElement, RangeInput, RangeMultiElement, RatingElement, SelectHorizElement, ShapefillValueElement, SliderInputElement, SlidingNumberElement, SnackbarElement, SnapContainer, SplitButton, SplitLayoutElement, SplitPanelElement, SwipeElement, TabController, TabListElement, TabListItemElement, TabPanelElement, TabPanelsElement, TextShimmerElement, TickledController, TimelineController, TimelineTrackTabElement, TitlebarElement, ToolbarElement, ToolbarGroupElement, TooltipElement, TransitoryLabelElement, TreeBaseElement, TreeController, TreeDataModel, TreeDropController, TreeKeyboardNavController, TreeListElement, TreemapListElement, TwoSplitLayoutElement, VerticalListElement, XyPad, addPanelToStack, animateListItemIn, animateListItemOut, applyAgeModification, applyHighlights, applyIsometry, applySnapshot, applyTreeDrop, attachTimelineInteractions, bottomDrawerCommandId, buildHighlightTargets, buildModel, captionForEntry, clampToDisk, clearHighlights, collectContainerNames, collectDataResult, collectPanelIds, collectStackPaths, composeIsometry, computeCanonicalPositions, computeColour, computeHyperbolicLayout, computeIcicleLayout, computeLeafGeoms, computeTreemapLayout, createAcCompletionProvider, createDataListSearchAdapter, createDefaultListDragPreview, createElementIncrSearch, createElementListSearchAdapter, createEmptyLayout, createEmptyStack, createFuzzySearch, createIncrSearch, createPillRenderer, createSplit, createStack, createTreeListAdapter, createTreeListSearchAdapter, createVerticalListAdapter, decomposeViewIsometry, distribute, dropTargetFor, euclideanRadiusForHyperbolicDistance, extractStack, findHyperbolicIdOf, findHyperbolicKeyPath, findHyperbolicNode, findHyperbolicPath, findHyperbolicPathById, findIcicleNode, findIciclePath, findNode, findPanelNode, findParentOf, findStackOf, findStackPathByName, findTreemapNode, findTreemapPath, geodesicInterpolate, geodesicPath, geodesicSegment, getActiveDragPanelId, getIcon, getIconEntry, getIconFont, getLeafNeighbours, hasIcon, hyperbolicBranchId, hyperbolicChildIds, hyperbolicDistanceForEuclideanRadius, hyperbolicIdDepth, hyperbolicRelativeFrame, iconBus, identityIsometry, init, insertBeside, invertIsometry, isDragging, isHyperbolicDescendantId, isMultiMode, isSplit, isStack, keyForElement, listItemFadeKeyframes, listSearchStyles, mobiusAdd, mobiusFromOrigin, mobiusToOrigin, monitorIndeterminate, moveLeafNodeToEdge, moveLeafStep, norm, normalise, normaliseValue, normalize, panelGapIndex, parseColourScaleAttr, parseLayout, poincareDistance, pointerOverAnyHost, prefersReducedMotion, preserveRestoreWeights, pruneDraggedDescendants, readBaseColour, registerFontIcon, registerIcon, registerListSearchCommands, removePanelFromStack, resolveCssVars, resolveGrid, resolveLeafPath, resolveTracks, rotationIsometry, serializeLayout, setActive, setIconFont, setPanelRestoreWeight, startListItemOutAnimation, svgToDataUri, swapLeaves, tickledItemStyles, tickledStyles, timelineStyles, toSnapshot, toolbarGapIndex, translationFromOrigin, viewIsometry, walk, wrapRoot };
54917
+ export { AcTextElement, AcTokenElement, BottomDrawerElement, ButtonElement, Checkbox, CheckedSelect, ColourPicker, ColourPickerPopup, CrumbNavigationElement, CrumbPathController, CrumbPathElement, DEFAULT_SIBLING_SPACING, DataController, DataDisplayComponent, DataGridElement, DataTableElement, DetailListElement, DockHostElement, DockPanelElement, EMPTY_GROUP_LABEL, EditableLabel, EditableNumber, FormElement, FormGroupElement, FormPartElement, GridListElement, GroupedItemListerElement, HyperbolicTreeListElement, ICON_CARET_RIGHT, ICON_CHECK, ICON_CHEVRON_DOWN, ICON_CLOSE, IcicleTreeListElement, IncrSearchTreeController as IncrSearchMillerController, IncrSearchTreeController, IxfxHexEditorElement, IxfxIconElement, IxfxTimelineElement, KeyedElementIndex, LIST_ITEM_MOTION_MS, LIST_SEARCH_COMMAND_IDS, LIST_SEARCH_MODES, LabelledRadialInput, LabelledRangeInput, LabelledSliderInput, LedElement, ListDragSelectController, ListGroupHeaderElement, ListNativeDragController, ListSearchController, ListSelectionController, menu_exports as Menu, MillerBaseElement, MillerListElement, MillerTreeController, MulticolListElement, NarrowedTextElement, NotificationManager, NotificationPillElement, PanTextElement, PanelElement, PanelGroupElement, PlotDataSeries, PlotHistogram, PlotMultiAxis, PlotSingleAxis, PlotXyAxis, PolarPad, RadialInputElement, RadioGroup, RangeElement, RangeInput, RangeMultiElement, RatingElement, SelectHorizElement, ShapefillValueElement, SliderInputElement, SlidingNumberElement, SnackbarElement, SnapContainer, SplitButton, SplitLayoutElement, SplitPanelElement, SwipeElement, TabController, TabListElement, TabListItemElement, TabPanelElement, TabPanelsElement, TextShimmerElement, TickledController, TimelineController, TimelineTrackTabElement, TitlebarElement, ToolbarElement, ToolbarGroupElement, TooltipElement, TransitoryLabelElement, TreeBaseElement, TreeController, TreeDataModel, TreeDropController, TreeKeyboardNavController, TreeListElement, TreemapListElement, TwoSplitLayoutElement, VerticalListElement, XyPad, addPanelToStack, animateListItemIn, animateListItemOut, applyAgeModification, applyHighlights, applyIsometry, applySnapshot, applyTreeDrop, attachTimelineInteractions, bottomDrawerCommandId, buildHighlightTargets, buildModel, captionForEntry, clampToDisk, clearHighlights, collectContainerNames, collectDataResult, collectPanelIds, collectStackPaths, composeIsometry, computeCanonicalPositions, computeColour, computeHyperbolicLayout, computeIcicleLayout, computeLeafGeoms, computeTreemapLayout, createAcCompletionProvider, createDataListSearchAdapter, createDefaultListDragPreview, createElementIncrSearch, createElementListSearchAdapter, createEmptyLayout, createEmptyStack, createFuzzySearch, createIncrSearch, createPillRenderer, createSplit, createStack, createTreeListAdapter, createTreeListSearchAdapter, createVerticalListAdapter, decomposeViewIsometry, defaultGroupLabel, distribute, dropTargetFor, euclideanRadiusForHyperbolicDistance, extractStack, findHyperbolicIdOf, findHyperbolicKeyPath, findHyperbolicNode, findHyperbolicPath, findHyperbolicPathById, findIcicleNode, findIciclePath, findNode, findPanelNode, findParentOf, findStackOf, findStackPathByName, findTreemapNode, findTreemapPath, geodesicInterpolate, geodesicPath, geodesicSegment, getActiveDragPanelId, getIcon, getIconEntry, getIconFont, getLeafNeighbours, hasIcon, hyperbolicBranchId, hyperbolicChildIds, hyperbolicDistanceForEuclideanRadius, hyperbolicIdDepth, hyperbolicRelativeFrame, iconBus, identityIsometry, init, insertBeside, invertIsometry, isDragging, isHyperbolicDescendantId, isMultiMode, isSplit, isStack, keyForElement, listItemFadeKeyframes, listSearchStyles, mobiusAdd, mobiusFromOrigin, mobiusToOrigin, monitorIndeterminate, moveLeafNodeToEdge, moveLeafStep, norm, normalise, normaliseValue, normalize, panelGapIndex, parseColourScaleAttr, parseLayout, partitionByGroup, poincareDistance, pointerOverAnyHost, prefersReducedMotion, preserveRestoreWeights, pruneDraggedDescendants, readBaseColour, registerFontIcon, registerIcon, registerListSearchCommands, removePanelFromStack, resolveCssVars, resolveGrid, resolveGroupKey, resolveLeafPath, resolveTracks, rotationIsometry, serializeLayout, setActive, setIconFont, setPanelRestoreWeight, startListItemOutAnimation, svgToDataUri, swapLeaves, tickledItemStyles, tickledStyles, timelineStyles, toGroupKey, toSnapshot, toggleInSet, toolbarGapIndex, translationFromOrigin, viewIsometry, walk, wrapRoot };
53953
54918
 
53954
54919
  //# sourceMappingURL=index.js.map