@ixfx/components 0.7.5 → 0.8.0

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 (72) hide show
  1. package/bundle/index.d.ts +256 -17
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +1461 -165
  4. package/bundle/index.js.map +1 -1
  5. package/dist/ac-token.js +1 -1
  6. package/dist/{data-grid-CtJ35v8L.d.ts → data-grid-D-z7bx5-.d.ts} +2 -2
  7. package/dist/{data-grid-CtJ35v8L.d.ts.map → data-grid-D-z7bx5-.d.ts.map} +1 -1
  8. package/dist/data-grid.d.ts +1 -1
  9. package/dist/data-grid.js +1 -1
  10. package/dist/{detail-list-BS5dRDda.d.ts → detail-list-_ZJHYEBr.d.ts} +3 -3
  11. package/dist/{detail-list-BS5dRDda.d.ts.map → detail-list-_ZJHYEBr.d.ts.map} +1 -1
  12. package/dist/detail-list.d.ts +2 -2
  13. package/dist/detail-list.js +1 -1
  14. package/dist/grid-list-nRUQOcEL.js +1537 -0
  15. package/dist/grid-list-nRUQOcEL.js.map +1 -0
  16. package/dist/{grid-list-D-GbghSH.d.ts → grid-list-u33vmKzb.d.ts} +116 -15
  17. package/dist/grid-list-u33vmKzb.d.ts.map +1 -0
  18. package/dist/grid-list.d.ts +1 -1
  19. package/dist/grid-list.js +1 -993
  20. package/dist/{incr-search-DpbSJtgj.js → incr-search-C05WjbZh.js} +40 -3
  21. package/dist/incr-search-C05WjbZh.js.map +1 -0
  22. package/dist/incr-search.d.ts +2 -2
  23. package/dist/incr-search.js +2 -2
  24. package/dist/{index-CBS4z1v-.d.ts → index-h6gxtfHH.d.ts} +34 -2
  25. package/dist/index-h6gxtfHH.d.ts.map +1 -0
  26. package/dist/index.d.ts +60 -11
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +342 -15
  29. package/dist/index.js.map +1 -1
  30. package/dist/led.d.ts +33 -1
  31. package/dist/led.d.ts.map +1 -1
  32. package/dist/led.js +157 -13
  33. package/dist/led.js.map +1 -1
  34. package/dist/miller.d.ts +1 -1
  35. package/dist/miller.js +1 -1
  36. package/dist/narrowed-text.js +1 -1
  37. package/dist/panel.d.ts +3 -1
  38. package/dist/panel.d.ts.map +1 -1
  39. package/dist/panel.js +31 -1
  40. package/dist/panel.js.map +1 -1
  41. package/dist/plots.d.ts +2 -2
  42. package/dist/plots.js +1 -1
  43. package/dist/{titlebar-Bj5pfdw_.d.ts → titlebar-YQ-jVQoS.d.ts} +2 -2
  44. package/dist/{titlebar-Bj5pfdw_.d.ts.map → titlebar-YQ-jVQoS.d.ts.map} +1 -1
  45. package/dist/titlebar.d.ts +1 -1
  46. package/dist/titlebar.js +83 -1
  47. package/dist/titlebar.js.map +1 -1
  48. package/dist/{tree-DAii2A1g.js → tree-Chptss7I.js} +2 -2
  49. package/dist/{tree-DAii2A1g.js.map → tree-Chptss7I.js.map} +1 -1
  50. package/dist/tree.d.ts +1 -1
  51. package/dist/tree.js +1 -1
  52. package/dist/{vertical-list-DwtCPy1b.d.ts → vertical-list-CFuFDygi.d.ts} +2 -2
  53. package/dist/{vertical-list-DwtCPy1b.d.ts.map → vertical-list-CFuFDygi.d.ts.map} +1 -1
  54. package/dist/vertical-list.d.ts +1 -1
  55. package/dist/vertical-list.js +1 -1
  56. package/dist/{xy-axis-5KhVTNoX.d.ts → xy-axis-BfP9hKLj.d.ts} +25 -2
  57. package/dist/{xy-axis-5KhVTNoX.d.ts.map → xy-axis-BfP9hKLj.d.ts.map} +1 -1
  58. package/dist/{xy-axis-VrX8dmvS.js → xy-axis-DzM1brBZ.js} +141 -8
  59. package/dist/xy-axis-DzM1brBZ.js.map +1 -0
  60. package/docs-user/grid-list.md +99 -14
  61. package/docs-user/index.json +3 -3
  62. package/docs-user/led.md +37 -7
  63. package/docs-user/llms.txt +1 -1
  64. package/docs-user/panel.md +68 -13
  65. package/docs-user/titlebar.md +3 -4
  66. package/llms.txt +1 -1
  67. package/package.json +1 -1
  68. package/dist/grid-list-D-GbghSH.d.ts.map +0 -1
  69. package/dist/grid-list.js.map +0 -1
  70. package/dist/incr-search-DpbSJtgj.js.map +0 -1
  71. package/dist/index-CBS4z1v-.d.ts.map +0 -1
  72. package/dist/xy-axis-VrX8dmvS.js.map +0 -1
package/bundle/index.js CHANGED
@@ -2499,6 +2499,43 @@ var IncrSearchTreeController = class {
2499
2499
  }
2500
2500
  };
2501
2501
  //#endregion
2502
+ //#region src/incr-search/data-list-search-adapter.ts
2503
+ /**
2504
+ * A {@link ListSearchAdapter} over a data-backed list. Unlike the element
2505
+ * adapter it never materialises an element per item — it reads keys and text
2506
+ * straight from the source by index. Highlights are painted on whatever
2507
+ * captions are currently rendered in the shadow window; offscreen matches paint
2508
+ * when scrolled into view. See `docs/infra-incremental.md`.
2509
+ */
2510
+ function createDataListSearchAdapter(host, options) {
2511
+ return {
2512
+ getItems: () => Array.from({ length: options.rawCount() }, (_, i) => ({
2513
+ key: options.rawKeyAt(i),
2514
+ index: i
2515
+ })),
2516
+ getLabel: (item) => options.rawSearchTextAt(item.index),
2517
+ getKey: (item) => item.key,
2518
+ focusItem: (item) => {
2519
+ if (item) host.focusKey(item.key);
2520
+ },
2521
+ activateItem: (item) => {
2522
+ if (options.onActivate) options.onActivate(item.key);
2523
+ else host.focusKey(item.key);
2524
+ },
2525
+ selectItems: (items) => {
2526
+ host.selectKeys(items.map((i) => i.key));
2527
+ },
2528
+ applyFilter: (matched) => {
2529
+ options.onFilterChange?.(matched);
2530
+ },
2531
+ highlightRoot: options.getHighlightRoot,
2532
+ highlightSelector: `.cell .caption`,
2533
+ highlightKeyOf: (el) => el.closest(`.cell`)?.getAttribute(`data-key`) ?? void 0,
2534
+ highlightText: (el) => el.textContent ?? ``,
2535
+ highlightKey: `grid-list-search`
2536
+ };
2537
+ }
2538
+ //#endregion
2502
2539
  //#region src/interaction/keyed-list.ts
2503
2540
  /**
2504
2541
  * Helpers for key-based selection on the Element-flavoured list components
@@ -2794,7 +2831,6 @@ var ListSearchController = class {
2794
2831
  #filterActive = false;
2795
2832
  constructor(host, options) {
2796
2833
  this.#host = host;
2797
- host.addController(this);
2798
2834
  this.#adapter = options.adapter;
2799
2835
  const requested = new Set(options.modes ?? LIST_SEARCH_MODES);
2800
2836
  this.#modes = LIST_SEARCH_MODES.filter((m) => requested.has(m));
@@ -2805,6 +2841,7 @@ var ListSearchController = class {
2805
2841
  fzf: options.fzf
2806
2842
  });
2807
2843
  this.#unsub = this.#search.onChange((state) => this.#onState(state));
2844
+ host.addController(this);
2808
2845
  }
2809
2846
  /** `false` when no modes are available — the host should register no commands and render no field. */
2810
2847
  get enabled() {
@@ -13340,7 +13377,7 @@ __decorate([state()], DataTableElement.prototype, "_stacked", void 0);
13340
13377
  DataTableElement = __decorate([safeCustomElement(`ixfx-data-table`)], DataTableElement);
13341
13378
  //#endregion
13342
13379
  //#region src/detail-list/detail-list.ts
13343
- const ITEM_SELECTOR$2 = `:scope > li`;
13380
+ const ITEM_SELECTOR$3 = `:scope > li`;
13344
13381
  const SEARCH_HIGHLIGHT_KEY = `detail-list-search`;
13345
13382
  const LABEL_SPAN_CSS = `overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0`;
13346
13383
  let DetailListElement = class DetailListElement extends i {
@@ -13392,7 +13429,7 @@ let DetailListElement = class DetailListElement extends i {
13392
13429
  itemWrap: this.itemWrap
13393
13430
  });
13394
13431
  this.search = new ListSearchController(this, { adapter: createElementListSearchAdapter(this, {
13395
- itemSelector: ITEM_SELECTOR$2,
13432
+ itemSelector: ITEM_SELECTOR$3,
13396
13433
  getLabel: (el) => el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``,
13397
13434
  highlightKey: SEARCH_HIGHLIGHT_KEY
13398
13435
  }) });
@@ -13531,7 +13568,7 @@ let DetailListElement = class DetailListElement extends i {
13531
13568
  subtree: false,
13532
13569
  attributeFilter: [`hidden`]
13533
13570
  });
13534
- for (const el of this.querySelectorAll(ITEM_SELECTOR$2)) this.#animatedItems.add(el);
13571
+ for (const el of this.querySelectorAll(ITEM_SELECTOR$3)) this.#animatedItems.add(el);
13535
13572
  this.#searchRegistry ??= createCommandRegistry();
13536
13573
  this.#syncSearchCommands();
13537
13574
  }
@@ -13550,7 +13587,7 @@ let DetailListElement = class DetailListElement extends i {
13550
13587
  fresh.push(node);
13551
13588
  }
13552
13589
  if (fresh.length === 0) return;
13553
- const total = this.querySelectorAll(ITEM_SELECTOR$2).length;
13590
+ const total = this.querySelectorAll(ITEM_SELECTOR$3).length;
13554
13591
  if (fresh.length >= total) return;
13555
13592
  for (const el of fresh) animateListItemIn(el);
13556
13593
  window.setTimeout(() => this.#scheduleGeometry(), 230);
@@ -13727,7 +13764,7 @@ let DetailListElement = class DetailListElement extends i {
13727
13764
  });
13728
13765
  }
13729
13766
  #itemForKey(key) {
13730
- for (const el of this.querySelectorAll(ITEM_SELECTOR$2)) if (keyForElement(el) === key) return el;
13767
+ for (const el of this.querySelectorAll(ITEM_SELECTOR$3)) if (keyForElement(el) === key) return el;
13731
13768
  }
13732
13769
  *selectedFilter(kind) {
13733
13770
  const wanted = (el) => {
@@ -13737,7 +13774,7 @@ let DetailListElement = class DetailListElement extends i {
13737
13774
  case `either`: return el.hasAttribute(`data-selected`) || el.hasAttribute(`data-checked`);
13738
13775
  }
13739
13776
  };
13740
- for (const el of this.querySelectorAll(ITEM_SELECTOR$2)) {
13777
+ for (const el of this.querySelectorAll(ITEM_SELECTOR$3)) {
13741
13778
  if (el.hidden) continue;
13742
13779
  if (wanted(el)) yield el;
13743
13780
  }
@@ -13750,7 +13787,7 @@ let DetailListElement = class DetailListElement extends i {
13750
13787
  if (previousKeys.size > 0 || anchorKey !== null) {
13751
13788
  const next = [];
13752
13789
  let anchor;
13753
- for (const el of this.querySelectorAll(ITEM_SELECTOR$2)) {
13790
+ for (const el of this.querySelectorAll(ITEM_SELECTOR$3)) {
13754
13791
  const key = keyOf(el);
13755
13792
  if (key !== null && previousKeys.has(key)) next.push(el);
13756
13793
  if (key !== null && key === anchorKey) anchor = el;
@@ -13805,15 +13842,15 @@ let DetailListElement = class DetailListElement extends i {
13805
13842
  }
13806
13843
  #applyFilterPredicate() {
13807
13844
  if (!this.filterPredicate) {
13808
- for (const el of this.querySelectorAll(ITEM_SELECTOR$2)) el.removeAttribute(`hidden`);
13845
+ for (const el of this.querySelectorAll(ITEM_SELECTOR$3)) el.removeAttribute(`hidden`);
13809
13846
  return;
13810
13847
  }
13811
- for (const el of this.querySelectorAll(ITEM_SELECTOR$2)) if (this.filterPredicate(el)) el.removeAttribute(`hidden`);
13848
+ for (const el of this.querySelectorAll(ITEM_SELECTOR$3)) if (this.filterPredicate(el)) el.removeAttribute(`hidden`);
13812
13849
  else el.setAttribute(`hidden`, ``);
13813
13850
  }
13814
13851
  #applySort() {
13815
13852
  if (this.sort === `none`) return;
13816
- const rows = [...this.querySelectorAll(ITEM_SELECTOR$2)];
13853
+ const rows = [...this.querySelectorAll(ITEM_SELECTOR$3)];
13817
13854
  if (rows.length < 2) return;
13818
13855
  const dir = this.sort === `descending` ? -1 : 1;
13819
13856
  const meta = (el) => ({
@@ -14005,7 +14042,7 @@ let DetailListElement = class DetailListElement extends i {
14005
14042
  else if (this.itemWrap === `wrap`) this.#navigateTo(this.#findLastCheckable());
14006
14043
  }
14007
14044
  _getVisibleItems() {
14008
- return Array.from(this.querySelectorAll(ITEM_SELECTOR$2)).filter((el) => {
14045
+ return Array.from(this.querySelectorAll(ITEM_SELECTOR$3)).filter((el) => {
14009
14046
  if (el.hidden) return false;
14010
14047
  if (this.filterPredicate && !this.filterPredicate(el)) return false;
14011
14048
  return true;
@@ -14060,10 +14097,10 @@ let DetailListElement = class DetailListElement extends i {
14060
14097
  }
14061
14098
  /** Scan light-DOM children for unprocessed `data-icon` items. */
14062
14099
  #processChildren() {
14063
- for (const el of this.querySelectorAll(ITEM_SELECTOR$2)) this.#ensureIcon(el);
14100
+ for (const el of this.querySelectorAll(ITEM_SELECTOR$3)) this.#ensureIcon(el);
14064
14101
  }
14065
14102
  _syncItemStates() {
14066
- for (const el of this.querySelectorAll(ITEM_SELECTOR$2)) {
14103
+ for (const el of this.querySelectorAll(ITEM_SELECTOR$3)) {
14067
14104
  el.toggleAttribute(`data-tickled`, this.tickled.isTickled(el));
14068
14105
  el.toggleAttribute(`data-selected`, this.selection.selected.has(el));
14069
14106
  if (this.interactionMode === `checked`) el.toggleAttribute(`data-checked`, !el.hasAttribute(`not-checked`) && this.selection.selected.has(el));
@@ -16086,7 +16123,7 @@ SplitLayoutElement = __decorate([safeCustomElement(`ixfx-split-layout`)], SplitL
16086
16123
  //#endregion
16087
16124
  //#region src/tabs/tab-list.ts
16088
16125
  /** Pointer movement threshold (px) before a press becomes a drag. */
16089
- const DRAG_THRESHOLD_PX$3 = 5;
16126
+ const DRAG_THRESHOLD_PX$4 = 5;
16090
16127
  let TabListElement = class TabListElement extends i {
16091
16128
  constructor(..._args) {
16092
16129
  super(..._args);
@@ -16200,7 +16237,7 @@ let TabListElement = class TabListElement extends i {
16200
16237
  const dy = event.clientY - state.startY;
16201
16238
  const distance = Math.hypot(dx, dy);
16202
16239
  if (!state.active) {
16203
- if (distance < DRAG_THRESHOLD_PX$3) return;
16240
+ if (distance < DRAG_THRESHOLD_PX$4) return;
16204
16241
  this.#beginDrag(event);
16205
16242
  }
16206
16243
  state.ghost?.move(event.clientX, event.clientY);
@@ -17050,12 +17087,23 @@ let TitlebarElement = class TitlebarElement extends i {
17050
17087
  const showCaret = this.collapsible && this.toggleInteraction !== `disabled`;
17051
17088
  const implicitToggle = this.collapsible && this.toggleInteraction === `implicit`;
17052
17089
  const caretRotation = isVertical ? this.collapsed ? `` : `-90deg` : this.collapsed ? `-90deg` : ``;
17090
+ const treeVariant = this.variant === `tree`;
17053
17091
  return b`
17054
17092
  <div
17055
17093
  class="bar"
17056
17094
  role="toolbar"
17057
17095
  @click=${implicitToggle ? this._onToggleClick : A}
17058
17096
  >
17097
+ ${treeVariant && showCaret ? b`
17098
+ <button
17099
+ class="caret-btn"
17100
+ type="button"
17101
+ aria-label=${this.collapsed ? `Expand panel` : `Collapse panel`}
17102
+ @click=${this._onToggleClick}
17103
+ >
17104
+ <ixfx-icon name=${caretIcon} prominence .rotate=${caretRotation.length > 0 ? caretRotation : A}></ixfx-icon>
17105
+ </button>
17106
+ ` : A}
17059
17107
  <div class="title-text-wrapper">
17060
17108
  ${this.iconName ? b`<ixfx-icon class="title-icon" name=${this.iconName}></ixfx-icon>` : A}
17061
17109
  <span class="title-text">${this.title}</span>
@@ -17073,7 +17121,7 @@ let TitlebarElement = class TitlebarElement extends i {
17073
17121
  <ixfx-icon name="close"></ixfx-icon>
17074
17122
  </button>
17075
17123
  ` : A}
17076
- ${showCaret ? b`
17124
+ ${!treeVariant && showCaret ? b`
17077
17125
  <button
17078
17126
  class="caret-btn"
17079
17127
  type="button"
@@ -17296,6 +17344,77 @@ let TitlebarElement = class TitlebarElement extends i {
17296
17344
  -1px 0 0 var(--surface-5),
17297
17345
  1px 0 0 var(--surface-1);
17298
17346
  }
17347
+
17348
+ /* === Tree variant: VS Code explorer / Outline style === */
17349
+ :host([variant="tree"]) .bar {
17350
+ background: transparent;
17351
+ padding: 0 var(--space-xs);
17352
+ min-height: unset;
17353
+ gap: var(--space-xs);
17354
+ }
17355
+
17356
+ :host([variant="tree"][orientation="vertical"]) .bar {
17357
+ padding: var(--space-xs) 0;
17358
+ flex-direction: column;
17359
+ min-width: unset;
17360
+ writing-mode: horizontal-tb;
17361
+ }
17362
+
17363
+ :host([variant="tree"]) .title-text-wrapper {
17364
+ display: inline-flex;
17365
+ align-items: center;
17366
+ gap: var(--space-xs);
17367
+ padding: 0;
17368
+ background: transparent;
17369
+ }
17370
+
17371
+ :host([variant="tree"]) .title-icon {
17372
+ background: transparent;
17373
+ flex-shrink: 0;
17374
+ }
17375
+
17376
+ :host([variant="tree"]) .title-text {
17377
+ font-weight: normal;
17378
+ color: var(--surface-3-text);
17379
+ padding: 0;
17380
+ background: transparent;
17381
+ box-shadow: none;
17382
+ flex: unset;
17383
+ width: auto;
17384
+ white-space: nowrap;
17385
+ }
17386
+
17387
+ :host([variant="tree"][orientation="vertical"]) .title-text {
17388
+ writing-mode: vertical-rl;
17389
+ transform: rotate(180deg);
17390
+ padding: 0;
17391
+ }
17392
+
17393
+ :host([variant="tree"]) .caret-btn {
17394
+ background: transparent;
17395
+ opacity: var(--prominence-idle-opacity);
17396
+ padding: 0;
17397
+ }
17398
+
17399
+ :host([variant="tree"]) .caret-btn:hover {
17400
+ background: transparent;
17401
+ opacity: var(--prominence-tickled-opacity);
17402
+ }
17403
+
17404
+ :host([variant="tree"]) .toolbar {
17405
+ margin-left: auto;
17406
+ }
17407
+
17408
+ :host([variant="tree"][orientation="vertical"]) .toolbar {
17409
+ flex-direction: row;
17410
+ margin-left: 0;
17411
+ margin-top: auto;
17412
+ }
17413
+
17414
+ :host([variant="tree"]) ixfx-icon {
17415
+ width: 1em;
17416
+ height: 1em;
17417
+ }
17299
17418
  `];
17300
17419
  }
17301
17420
  };
@@ -17331,7 +17450,7 @@ TitlebarElement = __decorate([safeCustomElement(`ixfx-titlebar`)], TitlebarEleme
17331
17450
  //#endregion
17332
17451
  //#region src/dock/dock-panel.ts
17333
17452
  /** Pointer movement threshold before a titlebar press becomes a dock drag. */
17334
- const DRAG_THRESHOLD_PX$2 = 5;
17453
+ const DRAG_THRESHOLD_PX$3 = 5;
17335
17454
  let DockPanelElement = class DockPanelElement extends i {
17336
17455
  constructor(..._args) {
17337
17456
  super(..._args);
@@ -17363,7 +17482,7 @@ let DockPanelElement = class DockPanelElement extends i {
17363
17482
  this._onPointerMove = (event) => {
17364
17483
  if (this._dragPointerId === null || event.pointerId !== this._dragPointerId) return;
17365
17484
  if (!this._dragStarted) {
17366
- if (Math.hypot(event.clientX - this._dragStartX, event.clientY - this._dragStartY) < DRAG_THRESHOLD_PX$2) return;
17485
+ if (Math.hypot(event.clientX - this._dragStartX, event.clientY - this._dragStartY) < DRAG_THRESHOLD_PX$3) return;
17367
17486
  this._dragStarted = true;
17368
17487
  const pointerId = this._dragPointerId;
17369
17488
  this._teardownDragListeners();
@@ -17518,7 +17637,7 @@ DockPanelElement = __decorate([safeCustomElement(`ixfx-dock-panel`)], DockPanelE
17518
17637
  /** Fraction of the leaf rect (from each edge) that forms the split bands. */
17519
17638
  const BAND = .25;
17520
17639
  /** Pointer movement threshold before a press becomes a dock drag (matches tab-list). */
17521
- const DRAG_THRESHOLD_PX$1 = 5;
17640
+ const DRAG_THRESHOLD_PX$2 = 5;
17522
17641
  /** Tolerance (px) when matching a child gutter endpoint to a parent gutter line. */
17523
17642
  const JUNCTION_TOL_PX = 6;
17524
17643
  /** Junction points closer than this merge into one handle (a 4-way cross yields one per side). */
@@ -17633,7 +17752,7 @@ let DockHostElement = class DockHostElement extends i {
17633
17752
  this._onTabDragMove = (event) => {
17634
17753
  const pending = this._tabDragPending;
17635
17754
  if (pending === null || event.pointerId !== pending.pointerId) return;
17636
- if (Math.hypot(event.clientX - pending.startX, event.clientY - pending.startY) < DRAG_THRESHOLD_PX$1) return;
17755
+ if (Math.hypot(event.clientX - pending.startX, event.clientY - pending.startY) < DRAG_THRESHOLD_PX$2) return;
17637
17756
  this._tabDragPending = null;
17638
17757
  this._teardownTabDragListeners();
17639
17758
  this._suppressNextClick();
@@ -19333,7 +19452,7 @@ function WheelNudgeMixin(superClass) {
19333
19452
  }
19334
19453
  //#endregion
19335
19454
  //#region src/editable-label/editable-number.ts
19336
- const DRAG_THRESHOLD_PX = 3;
19455
+ const DRAG_THRESHOLD_PX$1 = 3;
19337
19456
  let EditableNumber = class EditableNumber extends WheelNudgeMixin(EditableLabel) {
19338
19457
  #dragStartY;
19339
19458
  #dragStartValue;
@@ -19450,7 +19569,7 @@ let EditableNumber = class EditableNumber extends WheelNudgeMixin(EditableLabel)
19450
19569
  if (this._editing) return;
19451
19570
  if (this.disabled || this.noDrag) return;
19452
19571
  const deltaY = this.#dragStartY - e.clientY;
19453
- if (!this.#dragging && Math.abs(deltaY) < DRAG_THRESHOLD_PX) return;
19572
+ if (!this.#dragging && Math.abs(deltaY) < DRAG_THRESHOLD_PX$1) return;
19454
19573
  this.#dragging = true;
19455
19574
  this.#hasDragged = true;
19456
19575
  this.toggleAttribute(`dragging`, true);
@@ -20059,6 +20178,226 @@ let FormElement = class FormElement extends i {
20059
20178
  };
20060
20179
  FormElement = __decorate([safeCustomElement(`ixfx-form`)], FormElement);
20061
20180
  //#endregion
20181
+ //#region src/grid-list/grid-list-source.ts
20182
+ const ITEM_SELECTOR$2 = `:scope > *`;
20183
+ const SYNTHETIC_PREFIX = `gl-auto-`;
20184
+ /** Whether `key` is an auto-assigned key for a keyless light-DOM item. */
20185
+ function isSyntheticKey(key) {
20186
+ return key.startsWith(SYNTHETIC_PREFIX);
20187
+ }
20188
+ function warnDuplicateKeys(keys) {
20189
+ const seen = /* @__PURE__ */ new Set();
20190
+ const dupes = /* @__PURE__ */ new Set();
20191
+ for (const k of keys) {
20192
+ if (isSyntheticKey(k)) continue;
20193
+ if (seen.has(k)) dupes.add(k);
20194
+ else seen.add(k);
20195
+ }
20196
+ if (dupes.size > 0) console.warn(`ixfx-grid-list: duplicate item key(s) ${[...dupes].map((k) => `"${k}"`).join(`, `)} — the first occurrence wins for key-based operations.`);
20197
+ }
20198
+ var ElementGridListSource = class {
20199
+ #host;
20200
+ #items = [];
20201
+ #slotNames = /* @__PURE__ */ new WeakMap();
20202
+ #syntheticKeys = /* @__PURE__ */ new WeakMap();
20203
+ #seq = 0;
20204
+ #predicate;
20205
+ #searchKeys;
20206
+ #effectiveIndices = [];
20207
+ #keysCache;
20208
+ constructor(host) {
20209
+ this.#host = host;
20210
+ this.refresh();
20211
+ }
20212
+ get count() {
20213
+ return this.#effectiveIndices.length;
20214
+ }
20215
+ get rawCount() {
20216
+ return this.#items.length;
20217
+ }
20218
+ keyAt(index) {
20219
+ const eff = this.#effectiveIndices[index];
20220
+ return eff === void 0 ? `` : this.#itemKey(this.#items[eff]);
20221
+ }
20222
+ indexOfKey(key) {
20223
+ return this.#effectiveIndices.findIndex((i) => this.#itemKey(this.#items[i]) === key);
20224
+ }
20225
+ keys() {
20226
+ this.#keysCache ??= this.#effectiveIndices.map((i) => this.#itemKey(this.#items[i]));
20227
+ return this.#keysCache;
20228
+ }
20229
+ labelAt(index) {
20230
+ const eff = this.#effectiveIndices[index];
20231
+ return eff === void 0 ? `` : this.#captionFor(this.#items[eff]);
20232
+ }
20233
+ rawKeyAt(index) {
20234
+ const el = this.#items[index];
20235
+ return el === void 0 ? `` : this.#itemKey(el);
20236
+ }
20237
+ rawSearchTextAt(index) {
20238
+ const el = this.#items[index];
20239
+ if (el === void 0) return ``;
20240
+ return el.getAttribute(`data-search-label`) ?? el.getAttribute(`data-label`) ?? el.querySelector(`[data-grid-list-caption]`)?.textContent?.trim() ?? el.textContent?.trim() ?? ``;
20241
+ }
20242
+ isSelectable(index) {
20243
+ const eff = this.#effectiveIndices[index];
20244
+ return eff !== void 0 && !this.#items[eff].hasAttribute(`not-checked`);
20245
+ }
20246
+ renderCellContent(index) {
20247
+ const eff = this.#effectiveIndices[index];
20248
+ if (eff === void 0) return void 0;
20249
+ return b`<slot name=${this.slotNameFor(this.#items[eff])}></slot>`;
20250
+ }
20251
+ setFilters(opts) {
20252
+ if (`predicate` in opts) this.#predicate = opts.predicate;
20253
+ if (`searchKeys` in opts) this.#searchKeys = opts.searchKeys;
20254
+ this.#recomputeEffective();
20255
+ }
20256
+ refresh() {
20257
+ this.#items = Array.from(this.#host.querySelectorAll(ITEM_SELECTOR$2)).filter((el) => !el.hidden);
20258
+ for (const el of this.#items) {
20259
+ const name = this.slotNameFor(el);
20260
+ if (el.getAttribute(`slot`) !== name) el.setAttribute(`slot`, name);
20261
+ }
20262
+ warnDuplicateKeys(this.#items.map((el) => this.#itemKey(el)));
20263
+ this.#recomputeEffective();
20264
+ }
20265
+ /** Effective-order elements — the array `GridListElement` renders and slices. */
20266
+ effectiveElements() {
20267
+ return this.#effectiveIndices.map((i) => this.#items[i]);
20268
+ }
20269
+ /** Raw-order element at `index`, or `undefined`. */
20270
+ rawElementAt(index) {
20271
+ return this.#items[index];
20272
+ }
20273
+ /** Stable per-element `<slot>` name. */
20274
+ slotNameFor(el) {
20275
+ let name = this.#slotNames.get(el);
20276
+ if (name === void 0) {
20277
+ this.#seq += 1;
20278
+ name = `gl-item-${this.#seq}`;
20279
+ this.#slotNames.set(el, name);
20280
+ }
20281
+ return name;
20282
+ }
20283
+ resolveKeyToElement(key) {
20284
+ if (isSyntheticKey(key)) return this.#items.find((el) => this.#syntheticKeys.get(el) === key);
20285
+ return this.#items.find((el) => keyForElement(el) === key);
20286
+ }
20287
+ resolveKeyToData(_key) {}
20288
+ #recomputeEffective() {
20289
+ const effective = [];
20290
+ for (let i = 0; i < this.#items.length; i++) {
20291
+ if (this.#predicate !== void 0 && !this.#predicate(i)) continue;
20292
+ if (this.#searchKeys !== void 0) {
20293
+ const key = this.#itemKey(this.#items[i]);
20294
+ if (!isSyntheticKey(key) && !this.#searchKeys.has(key)) continue;
20295
+ }
20296
+ effective.push(i);
20297
+ }
20298
+ this.#effectiveIndices = effective;
20299
+ this.#keysCache = void 0;
20300
+ }
20301
+ #itemKey(el) {
20302
+ const userKey = keyForElement(el);
20303
+ if (userKey !== void 0) return userKey;
20304
+ let synthetic = this.#syntheticKeys.get(el);
20305
+ if (synthetic === void 0) {
20306
+ this.#seq += 1;
20307
+ synthetic = `${SYNTHETIC_PREFIX}${this.#seq}`;
20308
+ this.#syntheticKeys.set(el, synthetic);
20309
+ }
20310
+ return synthetic;
20311
+ }
20312
+ #captionFor(el) {
20313
+ return el.getAttribute(`data-label`) ?? el.querySelector(`[data-grid-list-caption]`)?.textContent?.trim() ?? ``;
20314
+ }
20315
+ };
20316
+ var DataGridListSource = class {
20317
+ #items;
20318
+ #adapter;
20319
+ #predicate;
20320
+ #searchKeys;
20321
+ #effectiveIndices = [];
20322
+ #keysCache;
20323
+ constructor(items, adapter) {
20324
+ this.#items = items;
20325
+ this.#adapter = adapter;
20326
+ warnDuplicateKeys(items.map((it, i) => adapter.key(it, i)));
20327
+ this.#recomputeEffective();
20328
+ }
20329
+ get count() {
20330
+ return this.#effectiveIndices.length;
20331
+ }
20332
+ get rawCount() {
20333
+ return this.#items.length;
20334
+ }
20335
+ keyAt(index) {
20336
+ const eff = this.#effectiveIndices[index];
20337
+ return eff === void 0 ? `` : this.#adapter.key(this.#items[eff], eff);
20338
+ }
20339
+ indexOfKey(key) {
20340
+ return this.#effectiveIndices.findIndex((i) => this.#adapter.key(this.#items[i], i) === key);
20341
+ }
20342
+ keys() {
20343
+ this.#keysCache ??= this.#effectiveIndices.map((i) => this.#adapter.key(this.#items[i], i));
20344
+ return this.#keysCache;
20345
+ }
20346
+ labelAt(index) {
20347
+ const eff = this.#effectiveIndices[index];
20348
+ if (eff === void 0) return ``;
20349
+ return this.#adapter.label?.(this.#items[eff], eff) ?? this.#adapter.key(this.#items[eff], eff);
20350
+ }
20351
+ rawKeyAt(index) {
20352
+ return index < 0 || index >= this.#items.length ? `` : this.#adapter.key(this.#items[index], index);
20353
+ }
20354
+ rawSearchTextAt(index) {
20355
+ if (index < 0 || index >= this.#items.length) return ``;
20356
+ const item = this.#items[index];
20357
+ return this.#adapter.searchableText?.(item, index) ?? this.#adapter.label?.(item, index) ?? ``;
20358
+ }
20359
+ isSelectable(index) {
20360
+ const eff = this.#effectiveIndices[index];
20361
+ return eff !== void 0 && !this.#adapter.disabled?.(this.#items[eff], eff);
20362
+ }
20363
+ renderCellContent(index) {
20364
+ const eff = this.#effectiveIndices[index];
20365
+ return eff === void 0 ? void 0 : this.#adapter.render(this.#items[eff], eff);
20366
+ }
20367
+ setFilters(opts) {
20368
+ if (`predicate` in opts) this.#predicate = opts.predicate;
20369
+ if (`searchKeys` in opts) this.#searchKeys = opts.searchKeys;
20370
+ this.#recomputeEffective();
20371
+ }
20372
+ refresh() {
20373
+ this.#recomputeEffective();
20374
+ }
20375
+ /** Raw-order item at `index`, or `undefined`. */
20376
+ rawItemAt(index) {
20377
+ return this.#items[index];
20378
+ }
20379
+ /** Effective-order item at `index`, or `undefined`. */
20380
+ getItemAt(index) {
20381
+ const eff = this.#effectiveIndices[index];
20382
+ return eff === void 0 ? void 0 : this.#items[eff];
20383
+ }
20384
+ resolveKeyToElement(_key) {}
20385
+ resolveKeyToData(key) {
20386
+ const idx = this.#items.findIndex((item, i) => this.#adapter.key(item, i) === key);
20387
+ return idx >= 0 ? this.#items[idx] : void 0;
20388
+ }
20389
+ #recomputeEffective() {
20390
+ const effective = [];
20391
+ for (let i = 0; i < this.#items.length; i++) {
20392
+ if (this.#predicate !== void 0 && !this.#predicate(i)) continue;
20393
+ if (this.#searchKeys !== void 0 && !this.#searchKeys.has(this.#adapter.key(this.#items[i], i))) continue;
20394
+ effective.push(i);
20395
+ }
20396
+ this.#effectiveIndices = effective;
20397
+ this.#keysCache = void 0;
20398
+ }
20399
+ };
20400
+ //#endregion
20062
20401
  //#region src/grid-list/grid-list.ts
20063
20402
  const ITEM_SELECTOR$1 = `:scope > *`;
20064
20403
  const MIN_ITEM_SIZE = 16;
@@ -20153,6 +20492,7 @@ let GridListElement = class GridListElement extends i {
20153
20492
  this.captionBackdrop = `scrim`;
20154
20493
  this.captionHeight = 24;
20155
20494
  this.itemWrap = `stop`;
20495
+ this.items = [];
20156
20496
  this._scrollTop = 0;
20157
20497
  this._scrollLeft = 0;
20158
20498
  this._viewportHeight = 0;
@@ -20163,42 +20503,37 @@ let GridListElement = class GridListElement extends i {
20163
20503
  this.#itemWrap = `stop`;
20164
20504
  this.#entering = /* @__PURE__ */ new Set();
20165
20505
  this.#reflowTimer = 0;
20166
- this.#slotNames = /* @__PURE__ */ new WeakMap();
20167
- this.#slotSeq = 0;
20506
+ this.#sourceElement = new ElementGridListSource(this);
20507
+ this.#source = this.#sourceElement;
20168
20508
  this.selection = new ListSelectionController({
20169
- getOrderedItems: () => this._getVisibleItems(),
20170
- isSelectable: () => true,
20509
+ getOrderedItems: () => this.#source.keys(),
20510
+ isSelectable: (k) => {
20511
+ const i = this.#source.indexOfKey(k);
20512
+ return i >= 0 && this.#source.isSelectable(i);
20513
+ },
20171
20514
  getModes: () => ({
20172
20515
  selectionMode: this.selectionMode,
20173
20516
  interactionMode: this.interactionMode
20174
20517
  }),
20175
- getKey: keyForElement,
20518
+ getKey: (k) => k,
20176
20519
  onChange: (selected, previous) => {
20177
20520
  this._syncItemStates();
20178
20521
  this.requestUpdate();
20179
- this._dispatch(`list-select`, {
20180
- selected,
20181
- previous,
20182
- selectedKeys: this.selection.selectedKeys
20183
- });
20522
+ this.#emitSelect(selected, previous);
20184
20523
  }
20185
20524
  });
20186
20525
  this.tickled = new TickledController(this, {
20187
- getItems: () => this.#items.map((el) => ({
20188
- id: el,
20189
- enabled: !el.hasAttribute(`not-checked`)
20526
+ getItems: () => this.#source.keys().map((k, i) => ({
20527
+ id: k,
20528
+ enabled: this.#source.isSelectable(i)
20190
20529
  })),
20191
- onTickle: (id) => {
20192
- if (id !== void 0) this._dispatch(`list-tickle`, { item: id });
20530
+ onTickle: (k) => {
20531
+ if (k !== void 0) this.#emitItemEvent(`list-tickle`, k);
20193
20532
  },
20194
20533
  onStateChange: () => this._syncItemStates(),
20195
20534
  itemWrap: this.#itemWrap
20196
20535
  });
20197
- this.search = new ListSearchController(this, { adapter: createElementListSearchAdapter(this, {
20198
- itemSelector: ITEM_SELECTOR$1,
20199
- getLabel: (el) => el.getAttribute(`data-label`) ?? el.querySelector(`[data-grid-list-caption]`)?.textContent?.trim() ?? el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``,
20200
- highlightKey: `grid-list-search`
20201
- }) });
20536
+ this.#selectedSnapshot = /* @__PURE__ */ new Set();
20202
20537
  this.#onScroll = (e) => {
20203
20538
  const el = e.target;
20204
20539
  this.#cancelScrollRaf();
@@ -20218,16 +20553,16 @@ let GridListElement = class GridListElement extends i {
20218
20553
  }
20219
20554
  return;
20220
20555
  }
20221
- const count = this.#items.length;
20556
+ const count = this.#source.count;
20222
20557
  if (count === 0) return;
20223
20558
  const win = this.#currentWindow();
20224
20559
  const move = (delta) => {
20225
- const from = this.tickled.tickledId ? this.#items.indexOf(this.tickled.tickledId) : -1;
20560
+ const from = this.tickled.tickledId ? this.#source.indexOfKey(this.tickled.tickledId) : -1;
20226
20561
  const nextIndex = Math.max(0, Math.min(count - 1, from + delta));
20227
- const next = this.#items[nextIndex];
20228
- if (next) {
20229
- this.tickled.handleKeyboardNavigation(next);
20230
- this.#scrollItemIntoView(next);
20562
+ const nextKey = this.#source.keyAt(nextIndex);
20563
+ if (nextKey) {
20564
+ this.tickled.handleKeyboardNavigation(nextKey);
20565
+ this.#scrollItemIntoView(nextKey);
20231
20566
  }
20232
20567
  };
20233
20568
  const perLine = this.orientation === `grid` ? win.cols : 1;
@@ -20264,7 +20599,7 @@ let GridListElement = class GridListElement extends i {
20264
20599
  const id = this.tickled.tickledId;
20265
20600
  if (id) {
20266
20601
  e.preventDefault();
20267
- this._dispatch(`list-item-click`, { item: id });
20602
+ this.#emitItemEvent(`list-item-click`, id);
20268
20603
  this.#onCellActivate(id);
20269
20604
  }
20270
20605
  break;
@@ -20319,6 +20654,12 @@ let GridListElement = class GridListElement extends i {
20319
20654
  height: 100%;
20320
20655
  overflow-y: auto;
20321
20656
  overflow-x: hidden;
20657
+ /* Consumer control over the native scrollbar (both orientations): set
20658
+ --grid-list-scrollbar-width to "none" to hide it or "thin" to slim it.
20659
+ The container is also exposed as ::part(viewport). */
20660
+ scrollbar-width: var(--grid-list-scrollbar-width, auto);
20661
+ scrollbar-gutter: var(--grid-list-scrollbar-gutter, auto);
20662
+ scrollbar-color: var(--grid-list-scrollbar-color, auto);
20322
20663
  }
20323
20664
  :host([orientation="row"]) .viewport {
20324
20665
  overflow-x: auto;
@@ -20564,13 +20905,47 @@ let GridListElement = class GridListElement extends i {
20564
20905
  */
20565
20906
  #zoomAnchor;
20566
20907
  /**
20567
- * Per-item slot name. Each cell renders its own named `<slot>` and the
20568
- * matching light-DOM child carries `slot="<name>"`, so a child is projected
20569
- * into exactly one cell. (A single unnamed slot would collapse every child
20570
- * into the first cell.)
20908
+ * The active view over the items. Element mode projects light-DOM children
20909
+ * through named `<slot>`s (owned by {@link ElementGridListSource}); data mode
20910
+ * renders from {@link items} via {@link itemAdapter}. Everything else here
20911
+ * addresses items by effective index + key and never branches on the mode.
20912
+ *
20913
+ * Initialised eagerly (element mode) so the public getters — `count`,
20914
+ * `selectedKeys`, … — are safe to touch before the first Lit update. The
20915
+ * first `willUpdate` rebuilds it once the light DOM / `itemAdapter` is known.
20571
20916
  */
20572
- #slotNames;
20573
- #slotSeq;
20917
+ #sourceElement;
20918
+ #source;
20919
+ #searchMode;
20920
+ #searchFilterKeys;
20921
+ /** (Re)build {@link search} for the current mode; a no-op if it already matches. */
20922
+ #ensureSearch() {
20923
+ const mode = this.itemAdapter !== void 0 ? `data` : `element`;
20924
+ if (this.#searchMode === mode && this.search !== void 0) return;
20925
+ const previous = this.search;
20926
+ if (previous) {
20927
+ previous.hostDisconnected();
20928
+ this.removeController(previous);
20929
+ }
20930
+ this.search = mode === `data` ? new ListSearchController(this, { adapter: createDataListSearchAdapter(this, {
20931
+ rawCount: () => this.#source?.rawCount ?? 0,
20932
+ rawKeyAt: (i) => this.#source?.rawKeyAt(i) ?? ``,
20933
+ rawSearchTextAt: (i) => this.#source?.rawSearchTextAt(i) ?? ``,
20934
+ onFilterChange: (keys) => {
20935
+ this.#searchFilterKeys = keys;
20936
+ this.#source?.setFilters({ searchKeys: keys });
20937
+ this.requestUpdate();
20938
+ this.updateComplete.then(() => this.#emitViewportChange());
20939
+ },
20940
+ onActivate: (key) => this.#onCellActivate(key),
20941
+ getHighlightRoot: () => this.renderRoot
20942
+ }) }) : new ListSearchController(this, { adapter: createElementListSearchAdapter(this, {
20943
+ itemSelector: ITEM_SELECTOR$1,
20944
+ getLabel: (el) => el.getAttribute(`data-label`) ?? el.querySelector(`[data-grid-list-caption]`)?.textContent?.trim() ?? el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``,
20945
+ highlightKey: `grid-list-search`
20946
+ }) });
20947
+ this.#searchMode = mode;
20948
+ }
20574
20949
  #searchRegistry;
20575
20950
  #unregisterSearchCommands;
20576
20951
  /** Command registry carrying this grid's `list.search.*` commands. */
@@ -20585,7 +20960,7 @@ let GridListElement = class GridListElement extends i {
20585
20960
  return LIST_SEARCH_MODES.filter((m) => requested.has(m));
20586
20961
  }
20587
20962
  #syncSearchCommands() {
20588
- if (!this.#searchRegistry) return;
20963
+ if (!this.#searchRegistry || !this.search) return;
20589
20964
  this.search.setModes(this.#effectiveSearchModes());
20590
20965
  this.#unregisterSearchCommands?.();
20591
20966
  this.#unregisterSearchCommands = registerListSearchCommands(this.#searchRegistry, this.search);
@@ -20593,6 +20968,8 @@ let GridListElement = class GridListElement extends i {
20593
20968
  connectedCallback() {
20594
20969
  super.connectedCallback();
20595
20970
  if (!this.hasAttribute(`tabindex`)) this.tabIndex = 0;
20971
+ if (!this.hasAttribute(`role`)) this.setAttribute(`role`, `listbox`);
20972
+ this.#syncAriaMultiselectable();
20596
20973
  for (const [attr, value] of [
20597
20974
  [`caption-style`, this.captionStyle],
20598
20975
  [`caption-align`, this.captionAlign],
@@ -20606,6 +20983,7 @@ let GridListElement = class GridListElement extends i {
20606
20983
  attributeFilter: [`hidden`]
20607
20984
  });
20608
20985
  this.#searchRegistry ??= createCommandRegistry();
20986
+ this.#ensureSearch();
20609
20987
  this.#syncSearchCommands();
20610
20988
  }
20611
20989
  disconnectedCallback() {
@@ -20626,6 +21004,19 @@ let GridListElement = class GridListElement extends i {
20626
21004
  #onChildrenChanged(records) {
20627
21005
  const previousLength = this.#items.length;
20628
21006
  this.#syncItems();
21007
+ const removedKeys = [];
21008
+ for (const record of records ?? []) for (const node of record.removedNodes) {
21009
+ if (node.nodeType !== Node.ELEMENT_NODE) continue;
21010
+ const key = keyForElement(node);
21011
+ if (key !== void 0) removedKeys.push(key);
21012
+ }
21013
+ if (removedKeys.length > 0) {
21014
+ const present = new Set(this.#source.keys());
21015
+ const gone = removedKeys.filter((k) => !present.has(k));
21016
+ if (gone.length > 0) this.selection.removeKeys(gone, (k) => k);
21017
+ const tickledId = this.tickled.tickledId;
21018
+ if (tickledId && gone.includes(tickledId)) this.tickled.focus(void 0);
21019
+ }
20629
21020
  this._syncItemStates();
20630
21021
  this.#noteChildMutation(previousLength, records);
20631
21022
  this.requestUpdate();
@@ -20667,33 +21058,86 @@ let GridListElement = class GridListElement extends i {
20667
21058
  }, REFLOW_WINDOW_MS);
20668
21059
  }
20669
21060
  #emitViewportChange() {
20670
- this._dispatch(`grid-list-viewport-change`, { visibleKeys: this.visibleKeys() });
21061
+ this._dispatch(`grid-list-viewport-change`, {
21062
+ renderedKeys: this.renderedKeys(),
21063
+ visibleKeys: this.visibleKeys()
21064
+ });
20671
21065
  }
20672
21066
  firstUpdated() {
21067
+ if (!this.#source) this.#rebuildSource();
20673
21068
  const el = this.#viewportRef.value;
20674
21069
  if (!el) return;
20675
- this._viewportWidth = el.clientWidth;
20676
- this._viewportHeight = el.clientHeight;
20677
21070
  this.#resizeObserver = new ResizeObserver((entries) => {
20678
21071
  for (const entry of entries) {
20679
- this._viewportWidth = entry.contentRect.width;
20680
- this._viewportHeight = entry.contentRect.height;
21072
+ const w = entry.contentRect.width;
21073
+ const h = entry.contentRect.height;
21074
+ if (w <= 0 || h <= 0) return;
21075
+ this._viewportWidth = w;
21076
+ this._viewportHeight = h;
20681
21077
  }
20682
21078
  });
20683
21079
  this.#resizeObserver.observe(el);
21080
+ this.#measureViewport(false);
20684
21081
  this.#syncItems();
20685
21082
  this.requestUpdate();
20686
21083
  }
21084
+ /** (Re)construct {@link #source} for the current mode and re-apply the filters. */
21085
+ #rebuildSource() {
21086
+ if (this.itemAdapter !== void 0) {
21087
+ this.#source = new DataGridListSource(this.items, this.itemAdapter);
21088
+ this.#sourceElement = void 0;
21089
+ } else {
21090
+ this.#sourceElement = new ElementGridListSource(this);
21091
+ this.#source = this.#sourceElement;
21092
+ }
21093
+ this.#applyFilters();
21094
+ if (this.#searchFilterKeys) this.#source.setFilters({ searchKeys: this.#searchFilterKeys });
21095
+ }
21096
+ /** Push the current filter predicate into whichever source is active. */
21097
+ #applyFilters() {
21098
+ if (this.#source instanceof ElementGridListSource) {
21099
+ const fp = this.filterPredicate;
21100
+ const src = this.#source;
21101
+ src.setFilters({ predicate: fp ? (i) => fp(src.rawElementAt(i)) : void 0 });
21102
+ this.#items = src.effectiveElements();
21103
+ } else if (this.#source instanceof DataGridListSource) {
21104
+ const ip = this.itemFilterPredicate;
21105
+ const src = this.#source;
21106
+ src.setFilters({ predicate: ip ? (i) => ip(src.rawItemAt(i), i) : void 0 });
21107
+ }
21108
+ }
20687
21109
  willUpdate(changed) {
20688
21110
  if (changed.has(`searchModes`)) this.#syncSearchCommands();
20689
- if (changed.has(`filterPredicate`)) this.#syncItems();
21111
+ if (changed.has(`filterPredicate`) || changed.has(`itemFilterPredicate`)) {
21112
+ this.#applyFilters();
21113
+ this.updateComplete.then(() => this.#emitViewportChange());
21114
+ }
21115
+ if (changed.has(`items`) || changed.has(`itemAdapter`)) {
21116
+ this.#zoomAnchor = void 0;
21117
+ const prevKeys = this.#source?.keys() ?? [];
21118
+ const modeFlipped = this.itemAdapter !== void 0 !== this.#source instanceof DataGridListSource;
21119
+ this.#rebuildSource();
21120
+ const nextKeys = this.#source.keys();
21121
+ const nextKeySet = new Set(nextKeys);
21122
+ const removedKeys = prevKeys.filter((k) => !nextKeySet.has(k));
21123
+ if (removedKeys.length > 0) this.selection.removeKeys(removedKeys, (k) => k);
21124
+ const tickledId = this.tickled.tickledId;
21125
+ if (tickledId && !nextKeySet.has(tickledId)) this.tickled.focus(void 0);
21126
+ this._syncItemStates();
21127
+ this.updateComplete.then(() => this.#emitViewportChange());
21128
+ if (modeFlipped) {
21129
+ this.#ensureSearch();
21130
+ this.#syncSearchCommands();
21131
+ }
21132
+ this.search?.refresh();
21133
+ }
20690
21134
  if (changed.has(`itemSize`) || changed.has(`gap`)) {
20691
21135
  this.itemSize = Math.max(MIN_ITEM_SIZE, Math.round(this.itemSize));
20692
21136
  this.gap = Math.max(0, Math.round(this.gap));
20693
21137
  }
20694
21138
  if (changed.has(`captionHeight`)) this.captionHeight = Math.max(0, Math.round(this.captionHeight));
20695
21139
  if (changed.has(`itemWrap`)) this.#itemWrap = this.itemWrap;
20696
- if ((changed.has(`itemSize`) || changed.has(`gap`) || changed.has(`captionStyle`) || changed.has(`captionHeight`) || changed.has(`orientation`)) && this.hasUpdated && this.#items.length > 0) {
21140
+ if ((changed.has(`itemSize`) || changed.has(`gap`) || changed.has(`captionStyle`) || changed.has(`captionHeight`) || changed.has(`orientation`)) && this.hasUpdated && this.#source.count > 0) {
20697
21141
  const prevOrientation = changed.get(`orientation`) ?? this.orientation;
20698
21142
  const horizontal = prevOrientation === `row`;
20699
21143
  const prevOffset = horizontal ? this._scrollLeft : this._scrollTop;
@@ -20703,19 +21147,13 @@ let GridListElement = class GridListElement extends i {
20703
21147
  const prevGap = changed.get(`gap`) ?? this.gap;
20704
21148
  const prevCaptionStyle = changed.get(`captionStyle`) ?? this.captionStyle;
20705
21149
  const prevCaptionHeight = changed.get(`captionHeight`) ?? this.captionHeight;
20706
- const prevRowHeight = prevItemSize + (prevCaptionStyle === `separate` || prevCaptionStyle === `under` ? prevCaptionHeight : 0);
20707
- const prev = computeGridWindow(this.#items.length, prevItemSize, prevRowHeight, prevGap, this._viewportWidth, this._viewportHeight, prevOffset, prevOrientation);
20708
- const mainStride = horizontal ? prev.colStride : prev.rowStride;
20709
- const centerLine = (prevOffset + prevExtent / 2) / mainStride;
20710
- const lineFloor = Math.max(0, Math.floor(centerLine));
20711
- const index = horizontal ? Math.min(this.#items.length - 1, lineFloor) : Math.min(this.#items.length - 1, lineFloor * prev.cols + Math.max(0, Math.floor((prev.cols - 1) / 2)));
20712
- this.#zoomAnchor = {
20713
- index,
20714
- lineFrac: centerLine - lineFloor
20715
- };
21150
+ this.#captureCentreAnchor(prevOffset, prevExtent, prevItemSize, prevGap, prevCaptionStyle, prevCaptionHeight, prevOrientation);
20716
21151
  }
20717
21152
  }
20718
21153
  }
21154
+ #syncAriaMultiselectable() {
21155
+ this.setAttribute(`aria-multiselectable`, this.selection.canSelectMultiple() ? `true` : `false`);
21156
+ }
20719
21157
  /** Full cell height: the square, plus a caption band in `separate` / `under`. */
20720
21158
  #rowHeight() {
20721
21159
  const captionBand = this.captionStyle === `separate` || this.captionStyle === `under`;
@@ -20727,7 +21165,10 @@ let GridListElement = class GridListElement extends i {
20727
21165
  updated(changed) {
20728
21166
  super.updated(changed);
20729
21167
  this.#syncItems();
20730
- if (changed.has(`selectionMode`) || changed.has(`interactionMode`)) this.selection.clearAnchor();
21168
+ if (changed.has(`selectionMode`) || changed.has(`interactionMode`)) {
21169
+ this.selection.clearAnchor();
21170
+ this.#syncAriaMultiselectable();
21171
+ }
20731
21172
  if (changed.has(`orientation`)) {
20732
21173
  const vp = this.#viewportRef.value;
20733
21174
  if (vp) {
@@ -20740,6 +21181,11 @@ let GridListElement = class GridListElement extends i {
20740
21181
  }
20741
21182
  }
20742
21183
  }
21184
+ const vp = this.#viewportRef.value;
21185
+ if (vp) {
21186
+ if (Math.abs(vp.scrollTop - this._scrollTop) > .5) vp.scrollTop = this._scrollTop;
21187
+ if (Math.abs(vp.scrollLeft - this._scrollLeft) > .5) vp.scrollLeft = this._scrollLeft;
21188
+ }
20743
21189
  this.#applyZoomAnchor();
20744
21190
  }
20745
21191
  /** Re-anchor the scroll so the item captured in `willUpdate` stays centred. */
@@ -20770,30 +21216,67 @@ let GridListElement = class GridListElement extends i {
20770
21216
  this._scrollTop = next;
20771
21217
  }
20772
21218
  }
20773
- #syncItems() {
20774
- this.#items = Array.from(this.querySelectorAll(ITEM_SELECTOR$1)).filter((el) => !el.hidden && (this.filterPredicate === void 0 || this.filterPredicate(el)));
20775
- for (const el of this.#items) {
20776
- const name = this.#slotNameFor(el);
20777
- if (el.getAttribute(`slot`) !== name) el.setAttribute(`slot`, name);
20778
- }
21219
+ #captureCentreAnchor(prevOffset, prevExtent, prevItemSize, prevGap, prevCaptionStyle, prevCaptionHeight, prevOrientation) {
21220
+ const count = this.#source?.count ?? 0;
21221
+ if (count === 0) return;
21222
+ const prevRowHeight = prevItemSize + (prevCaptionStyle === `separate` || prevCaptionStyle === `under` ? prevCaptionHeight : 0);
21223
+ const horizontal = prevOrientation === `row`;
21224
+ const prev = computeGridWindow(count, prevItemSize, prevRowHeight, prevGap, this._viewportWidth, this._viewportHeight, prevOffset, prevOrientation);
21225
+ const mainStride = horizontal ? prev.colStride : prev.rowStride;
21226
+ const centerLine = (prevOffset + prevExtent / 2) / mainStride;
21227
+ const lineFloor = Math.max(0, Math.floor(centerLine));
21228
+ const index = horizontal ? Math.min(count - 1, lineFloor) : Math.min(count - 1, lineFloor * prev.cols + Math.max(0, Math.floor((prev.cols - 1) / 2)));
21229
+ this.#zoomAnchor = {
21230
+ index,
21231
+ lineFrac: centerLine - lineFloor
21232
+ };
20779
21233
  }
20780
- #slotNameFor(el) {
20781
- let name = this.#slotNames.get(el);
20782
- if (name === void 0) {
20783
- this.#slotSeq += 1;
20784
- name = `gl-item-${this.#slotSeq}`;
20785
- this.#slotNames.set(el, name);
21234
+ /**
21235
+ * Re-read the viewport after an ancestor's visibility/layout changed.
21236
+ * Safe while hidden (zero-sized reads are ignored); forces a repaint of the
21237
+ * virtual window even when the size is unchanged.
21238
+ */
21239
+ refreshLayout() {
21240
+ this.#measureViewport(true);
21241
+ }
21242
+ /** Alias of {@link refreshLayout}. */
21243
+ remeasure() {
21244
+ this.#measureViewport(true);
21245
+ }
21246
+ #measureViewport(forceRender = false) {
21247
+ const vp = this.#viewportRef.value;
21248
+ if (!vp) return;
21249
+ const w = vp.clientWidth;
21250
+ const h = vp.clientHeight;
21251
+ if (w <= 0 || h <= 0) return;
21252
+ const horizontal = this.orientation === `row`;
21253
+ const changed = w !== this._viewportWidth || h !== this._viewportHeight;
21254
+ if (changed) {
21255
+ const prevOffset = horizontal ? this._scrollLeft : this._scrollTop;
21256
+ const prevExtent = horizontal ? w : h;
21257
+ this.#captureCentreAnchor(prevOffset, prevExtent, this.itemSize, this.gap, this.captionStyle, this.captionHeight, this.orientation);
21258
+ this._viewportWidth = w;
21259
+ this._viewportHeight = h;
20786
21260
  }
20787
- return name;
21261
+ if (forceRender && !changed) {
21262
+ this.#captureCentreAnchor(horizontal ? this._scrollLeft : this._scrollTop, horizontal ? w : h, this.itemSize, this.gap, this.captionStyle, this.captionHeight, this.orientation);
21263
+ this.requestUpdate();
21264
+ }
21265
+ if (changed || forceRender) this.updateComplete.then(() => this.#emitViewportChange());
20788
21266
  }
20789
- _getVisibleItems() {
20790
- return this.#items.filter((el) => {
20791
- if (el.hidden) return false;
20792
- return true;
20793
- });
21267
+ /**
21268
+ * Element mode only: re-read the light DOM into the source and refresh the
21269
+ * `#items` cache + filter. A no-op in data mode (the source is rebuilt through
21270
+ * `#rebuildSource` when `items` / `itemAdapter` change).
21271
+ */
21272
+ #syncItems() {
21273
+ if (this.#source instanceof ElementGridListSource) {
21274
+ this.#source.refresh();
21275
+ this.#applyFilters();
21276
+ }
20794
21277
  }
20795
21278
  get count() {
20796
- return this.#items.length;
21279
+ return this.#source.count;
20797
21280
  }
20798
21281
  /**
20799
21282
  * Number of cells currently rendered in the shadow DOM. With virtualization
@@ -20804,20 +21287,38 @@ let GridListElement = class GridListElement extends i {
20804
21287
  return this.renderRoot.querySelectorAll(`.cell`).length;
20805
21288
  }
20806
21289
  get selectedItems() {
20807
- return this.selection.selected;
21290
+ const els = /* @__PURE__ */ new Set();
21291
+ for (const key of this.selection.selectedKeys) {
21292
+ const el = this.#source.resolveKeyToElement(key);
21293
+ if (el) els.add(el);
21294
+ }
21295
+ return els;
20808
21296
  }
20809
- /** The selection as stable `data-key` / `data-value` strings. */
21297
+ /**
21298
+ * The selection as stable `data-key` / `data-value` strings. Auto-assigned
21299
+ * keys for keyless light-DOM items are omitted (they are not a stable contract
21300
+ * for the consumer).
21301
+ */
20810
21302
  get selectedKeys() {
20811
- return this.selection.selectedKeys;
21303
+ const out = /* @__PURE__ */ new Set();
21304
+ for (const k of this.selection.selectedKeys) if (!isSyntheticKey(k)) out.add(k);
21305
+ return out;
20812
21306
  }
20813
21307
  select(item) {
20814
- this.selection.replace(item);
21308
+ const key = keyForElement(item);
21309
+ if (key) this.selection.replace(key);
20815
21310
  }
20816
21311
  selectMany(items) {
20817
- this.selection.selectMany(items);
21312
+ const keys = [];
21313
+ for (const item of items) {
21314
+ const key = keyForElement(item);
21315
+ if (key) keys.push(key);
21316
+ }
21317
+ this.selection.selectMany(keys);
20818
21318
  }
20819
21319
  deselect(item) {
20820
- this.selection.deselect(item);
21320
+ const key = keyForElement(item);
21321
+ if (key) this.selection.deselect(key);
20821
21322
  }
20822
21323
  clearSelection() {
20823
21324
  this.selection.clear();
@@ -20831,44 +21332,126 @@ let GridListElement = class GridListElement extends i {
20831
21332
  * have a `data-key` (or `data-value`).
20832
21333
  */
20833
21334
  selectKeys(keys) {
20834
- this.selection.selectKeys(keys, (key) => this.getItem(key));
21335
+ this.selection.selectKeys(keys, (k) => k);
20835
21336
  }
20836
21337
  /** Add items by key to the selection (multi-select modes only). */
20837
21338
  addSelectedKeys(keys) {
20838
- this.selection.addKeys(keys, (key) => this.getItem(key));
21339
+ this.selection.addKeys(keys, (k) => k);
20839
21340
  }
20840
21341
  /** Remove items by key from the selection. */
20841
21342
  removeSelectedKeys(keys) {
20842
- this.selection.removeKeys(keys, (key) => this.getItem(key));
21343
+ this.selection.removeKeys(keys, (k) => k);
21344
+ }
21345
+ /**
21346
+ * Data mode only: raw index of `key` in `this.items`, or `-1`. Data-mode keys
21347
+ * come straight from the adapter, so this is exact even under a filter/search.
21348
+ */
21349
+ #rawIndexOfKey(key) {
21350
+ const adapter = this.itemAdapter;
21351
+ if (!adapter) return -1;
21352
+ return this.items.findIndex((it, i) => adapter.key(it, i) === key);
21353
+ }
21354
+ /**
21355
+ * Data mode: replace the whole item array. Selection and cursor keep any key
21356
+ * that is still present; keys that vanish are dropped (one `list-select`).
21357
+ * A no-op in element mode.
21358
+ */
21359
+ setItems(items) {
21360
+ if (this.itemAdapter) this.items = items;
21361
+ }
21362
+ /**
21363
+ * Data mode: append items to the end. Scroll position, selection and cursor
21364
+ * are untouched; appended items don't play the mid-list `[reflowing]` slide.
21365
+ * A no-op in element mode.
21366
+ */
21367
+ appendItems(items) {
21368
+ if (this.itemAdapter && items.length > 0) this.items = [...this.items, ...items];
21369
+ }
21370
+ /**
21371
+ * Data mode: replace the item carrying `key`. A visible cell re-renders in
21372
+ * place (the keyed subtree is kept); an offscreen item shows its new content
21373
+ * when scrolled into view. Scroll and selection are untouched. Unknown keys
21374
+ * are ignored.
21375
+ */
21376
+ updateItem(key, item) {
21377
+ if (!this.itemAdapter) return;
21378
+ const idx = this.#rawIndexOfKey(key);
21379
+ if (idx < 0) return;
21380
+ const next = [...this.items];
21381
+ next[idx] = item;
21382
+ this.items = next;
21383
+ }
21384
+ /**
21385
+ * Data mode: remove the items carrying these keys. Removed keys are dropped
21386
+ * from selection and cursor (via the `items` change). A no-op in element mode.
21387
+ */
21388
+ removeItems(keys) {
21389
+ if (!this.itemAdapter) return;
21390
+ const adapter = this.itemAdapter;
21391
+ const keySet = new Set(keys);
21392
+ this.items = this.items.filter((it, i) => !keySet.has(adapter.key(it, i)));
21393
+ }
21394
+ /**
21395
+ * Data mode: drop every item. Selection and cursor are reset and one
21396
+ * `list-select` with empty sets is emitted. A no-op in element mode.
21397
+ */
21398
+ clearItems() {
21399
+ if (!this.itemAdapter) return;
21400
+ this.selection.reset();
21401
+ this.tickled.focus(void 0);
21402
+ this.items = [];
21403
+ this.#emitSelect(/* @__PURE__ */ new Set(), /* @__PURE__ */ new Set());
20843
21404
  }
20844
21405
  /** Move the cursor to the item with this key and scroll it into view. */
20845
21406
  focusKey(key) {
20846
- const el = this.getItem(key);
20847
- if (!el) return;
20848
- this.tickled.focus(el);
20849
- this.#scrollItemIntoView(el);
21407
+ if (this.#source.indexOfKey(key) < 0) return;
21408
+ this.tickled.focus(key);
21409
+ this.#scrollItemIntoView(key);
20850
21410
  }
20851
21411
  /** The key of the item under the cursor, or `undefined`. */
20852
21412
  get tickledKey() {
20853
- const id = this.tickled.tickledId;
20854
- return id ? keyForElement(id) : void 0;
21413
+ return this.tickled.tickledId;
20855
21414
  }
21415
+ /** Element mode: the light-DOM element carrying `key`, or `undefined`. */
20856
21416
  getItem(key) {
20857
- return this.#items.find((el) => keyForElement(el) === key);
21417
+ return this.#source.resolveKeyToElement(key);
20858
21418
  }
21419
+ /** Element mode: the light-DOM element at effective index `index`. */
20859
21420
  getItemByIndex(index) {
20860
21421
  return this.#items[index];
20861
21422
  }
21423
+ /** Data mode: the data value carrying `key`, or `undefined`. */
21424
+ getData(key) {
21425
+ return this.#source.resolveKeyToData(key);
21426
+ }
20862
21427
  getKey(item) {
20863
21428
  return keyForElement(item);
20864
21429
  }
21430
+ /**
21431
+ * Keys of items in the rendered window (viewport + overscan) — the priority
21432
+ * set for prefetching. Use {@link visibleKeys} for the strict viewport
21433
+ * intersection. Auto-assigned keyless-item keys are omitted.
21434
+ */
21435
+ renderedKeys() {
21436
+ return this.#windowKeys(this.#currentWindow());
21437
+ }
21438
+ /**
21439
+ * Keys of items actually intersecting the viewport (no overscan).
21440
+ * Auto-assigned keyless-item keys are omitted.
21441
+ */
20865
21442
  visibleKeys() {
20866
- const win = this.#currentWindow();
20867
- return this.#items.slice(win.startIndex, win.endIndex).map((el) => keyForElement(el)).filter((k) => k !== void 0);
21443
+ return this.#windowKeys(this.#currentWindow(true));
21444
+ }
21445
+ #windowKeys(win) {
21446
+ return this.#source.keys().slice(win.startIndex, win.endIndex).filter((k) => !isSyntheticKey(k));
20868
21447
  }
20869
- /** Adjust the scroll offset so `el` is fully within the viewport. */
20870
- #scrollItemIntoView(el) {
20871
- const index = this.#items.indexOf(el);
21448
+ #currentWindow(noOverscan = false) {
21449
+ const overscan = noOverscan ? 0 : 2;
21450
+ 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);
21451
+ }
21452
+ /** Adjust the scroll offset so `key` is fully within the viewport. */
21453
+ #scrollItemIntoView(key) {
21454
+ const index = this.#source.indexOfKey(key);
20872
21455
  const vp = this.#viewportRef.value;
20873
21456
  if (index < 0 || !vp) return;
20874
21457
  const win = this.#currentWindow();
@@ -20897,12 +21480,43 @@ let GridListElement = class GridListElement extends i {
20897
21480
  }
20898
21481
  }
20899
21482
  _syncItemStates() {
21483
+ const selected = this.selection.selectedKeys;
20900
21484
  for (const el of this.#items) {
20901
- el.toggleAttribute(`data-tickled`, this.tickled.isTickled(el));
20902
- el.toggleAttribute(`data-selected`, this.selection.selected.has(el));
21485
+ const key = keyForElement(el);
21486
+ el.toggleAttribute(`data-tickled`, key !== void 0 && this.tickled.isTickled(key));
21487
+ el.toggleAttribute(`data-selected`, key !== void 0 && selected.has(key));
20903
21488
  }
20904
21489
  this.setAttribute(`data-input-mode`, this.tickled.inputMode);
20905
21490
  }
21491
+ #emitSelect(selected, previous) {
21492
+ const selectedEls = /* @__PURE__ */ new Set();
21493
+ const previousEls = /* @__PURE__ */ new Set();
21494
+ for (const key of selected) {
21495
+ const el = this.#source.resolveKeyToElement(key);
21496
+ if (el) selectedEls.add(el);
21497
+ }
21498
+ for (const key of previous) {
21499
+ const el = this.#source.resolveKeyToElement(key);
21500
+ if (el) previousEls.add(el);
21501
+ }
21502
+ this._dispatch(`list-select`, {
21503
+ selected: selectedEls,
21504
+ previous: previousEls,
21505
+ selectedKeys: this.selectedKeys,
21506
+ previousKeys: new Set([...previous].filter((k) => !isSyntheticKey(k)))
21507
+ });
21508
+ }
21509
+ #emitItemEvent(name, key) {
21510
+ const index = this.#source.indexOfKey(key);
21511
+ const element = this.#source.resolveKeyToElement(key);
21512
+ const item = element ?? this.#source.resolveKeyToData(key);
21513
+ this._dispatch(name, {
21514
+ item,
21515
+ key,
21516
+ index,
21517
+ element
21518
+ });
21519
+ }
20906
21520
  _dispatch(name, detail) {
20907
21521
  this.dispatchEvent(new CustomEvent(name, {
20908
21522
  detail,
@@ -20910,9 +21524,11 @@ let GridListElement = class GridListElement extends i {
20910
21524
  composed: true
20911
21525
  }));
20912
21526
  }
21527
+ /** Selected keys, snapshotted once per render so the cell renderers don't each rebuild the set. */
21528
+ #selectedSnapshot;
20913
21529
  render() {
20914
21530
  const win = this.#currentWindow();
20915
- const slice = this.#items.slice(win.startIndex, win.endIndex);
21531
+ this.#selectedSnapshot = this.selection.selectedKeys;
20916
21532
  const spacerStyle = win.horizontal ? `width:${win.totalWidth}px;height:100%` : `height:${win.totalHeight}px`;
20917
21533
  const scrollFade = win.horizontal ? `scroll-fade-x` : `scroll-fade-y`;
20918
21534
  return b`
@@ -20924,15 +21540,56 @@ let GridListElement = class GridListElement extends i {
20924
21540
  @scroll=${this.#onScroll}
20925
21541
  >
20926
21542
  <div class="spacer" style=${spacerStyle}>
20927
- ${repeat(slice, (el) => this.#slotNameFor(el), (el, i) => this.#renderCell(el, win.startIndex + i, win))}
21543
+ ${this.#source instanceof DataGridListSource ? this.#renderDataCells(win) : this.#renderElementCells(win)}
20928
21544
  </div>
20929
21545
  </div>
20930
- ${this.search.renderField()}
21546
+ ${this.search?.renderField()}
20931
21547
  `;
20932
21548
  }
21549
+ #renderDataCells(win) {
21550
+ const slice = Array.from({ length: win.endIndex - win.startIndex }, (_, i) => win.startIndex + i);
21551
+ return repeat(slice, (index) => this.#source.keyAt(index), (index) => this.#renderDataCell(index, win));
21552
+ }
21553
+ #renderDataCell(index, win) {
21554
+ const key = this.#source.keyAt(index);
21555
+ const selected = this.#selectedSnapshot.has(key);
21556
+ const tickled = this.tickled.isTickled(key);
21557
+ const style = this.captionStyle;
21558
+ const separate = style === `separate`;
21559
+ const caption = style === `hidden` ? `` : this.#source.labelAt(index);
21560
+ const showCaption = caption !== ``;
21561
+ const cellHeight = win.rowStride - this.gap;
21562
+ const x = win.horizontal ? index * win.colStride : columnOf(index, win.cols) * win.colStride;
21563
+ const y = win.horizontal ? Math.max(0, (this._viewportHeight - cellHeight) / 2) : rowOf(index, win.cols) * win.rowStride;
21564
+ const captionCss = separate ? `height:${Math.max(0, this.captionHeight - 4)}px` : style === `under` ? `height:${this.captionHeight}px` : ``;
21565
+ return b`
21566
+ <div
21567
+ class="cell${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}"
21568
+ part="item${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}"
21569
+ role="option"
21570
+ aria-selected=${selected ? `true` : `false`}
21571
+ style="width:${this.itemSize}px;height:${cellHeight}px;transform:translate(${x}px,${y}px)"
21572
+ data-index=${index}
21573
+ data-key=${key}
21574
+ ?data-selected=${selected}
21575
+ ?data-tickled=${tickled}
21576
+ @click=${(e) => this.#onCellClick(e, key)}
21577
+ @dblclick=${() => this.#onCellActivate(key)}
21578
+ @pointerenter=${() => this.tickled.handlePointerEnter(key)}
21579
+ >
21580
+ <div class="content" part="content">${this.#source.renderCellContent(index)}</div>
21581
+ ${showCaption ? b`<div class="caption" part="caption" style=${captionCss} title=${caption}>${caption}</div>` : ``}
21582
+ </div>
21583
+ `;
21584
+ }
21585
+ #renderElementCells(win) {
21586
+ const slice = this.#items.slice(win.startIndex, win.endIndex);
21587
+ return repeat(slice, (_el, i) => this.#source.keyAt(win.startIndex + i), (el, i) => this.#renderCell(el, win.startIndex + i, win));
21588
+ }
20933
21589
  #renderCell(el, index, win) {
20934
- const selected = this.selection.selected.has(el);
20935
- const tickled = this.tickled.isTickled(el);
21590
+ const key = this.#source.keyAt(index);
21591
+ const selected = this.#selectedSnapshot.has(key);
21592
+ const tickled = this.tickled.isTickled(key);
20936
21593
  const entering = this.#entering.has(el);
20937
21594
  const style = this.captionStyle;
20938
21595
  const separate = style === `separate`;
@@ -20946,17 +21603,22 @@ let GridListElement = class GridListElement extends i {
20946
21603
  return b`
20947
21604
  <div
20948
21605
  class="cell${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}${entering ? ` entering` : ``}"
20949
- part="item"
21606
+ part="item${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}"
21607
+ role="option"
21608
+ aria-selected=${selected ? `true` : `false`}
20950
21609
  style="width:${this.itemSize}px;height:${cellHeight}px;transform:translate(${x}px,${y}px)"
20951
21610
  data-index=${index}
20952
- @click=${(e) => this.#onCellClick(e, el)}
20953
- @dblclick=${() => this.#onCellActivate(el)}
20954
- @pointerenter=${() => this.tickled.handlePointerEnter(el)}
21611
+ data-key=${key}
21612
+ ?data-selected=${selected}
21613
+ ?data-tickled=${tickled}
21614
+ @click=${(e) => this.#onCellClick(e, key)}
21615
+ @dblclick=${() => this.#onCellActivate(key)}
21616
+ @pointerenter=${() => this.tickled.handlePointerEnter(key)}
20955
21617
  >
20956
21618
  <div class="thumb" part="thumb" style=${thumbStyle}>
20957
- <slot name=${this.#slotNameFor(el)}></slot>
21619
+ ${this.#source.renderCellContent(index)}
20958
21620
  </div>
20959
- ${showCaption ? b`<div class="caption" part="caption" style=${captionCss}>${caption}</div>` : ``}
21621
+ ${showCaption ? b`<div class="caption" part="caption" style=${captionCss} title=${caption}>${caption}</div>` : ``}
20960
21622
  </div>
20961
21623
  `;
20962
21624
  }
@@ -20967,16 +21629,13 @@ let GridListElement = class GridListElement extends i {
20967
21629
  this.#scrollRaf = 0;
20968
21630
  }
20969
21631
  }
20970
- #onCellClick(e, item) {
20971
- this.selection.handleClick(item, e);
20972
- this._dispatch(`list-item-click`, { item });
21632
+ #onCellClick(e, key) {
21633
+ this.selection.handleClick(key, e);
21634
+ this.#emitItemEvent(`list-item-click`, key);
20973
21635
  }
20974
- #onCellActivate(item) {
20975
- if (this.interactionMode !== `manual`) this.selection.replace(item);
20976
- this._dispatch(`list-activate`, { item });
20977
- }
20978
- #currentWindow() {
20979
- return computeGridWindow(this.#items.length, this.itemSize, this.#rowHeight(), this.gap, this._viewportWidth, this._viewportHeight, this.orientation === `row` ? this._scrollLeft : this._scrollTop, this.orientation);
21636
+ #onCellActivate(key) {
21637
+ if (this.interactionMode !== `manual`) this.selection.replace(key);
21638
+ this.#emitItemEvent(`list-activate`, key);
20980
21639
  }
20981
21640
  };
20982
21641
  __decorate([property({
@@ -21025,11 +21684,14 @@ __decorate([property({
21025
21684
  attribute: `item-wrap`
21026
21685
  })], GridListElement.prototype, "itemWrap", void 0);
21027
21686
  __decorate([property({ attribute: false })], GridListElement.prototype, "filterPredicate", void 0);
21687
+ __decorate([property({ attribute: false })], GridListElement.prototype, "itemFilterPredicate", void 0);
21028
21688
  __decorate([property({
21029
21689
  type: String,
21030
21690
  attribute: `search-modes`,
21031
21691
  reflect: true
21032
21692
  })], GridListElement.prototype, "searchModes", void 0);
21693
+ __decorate([property({ attribute: false })], GridListElement.prototype, "items", void 0);
21694
+ __decorate([property({ attribute: false })], GridListElement.prototype, "itemAdapter", void 0);
21033
21695
  __decorate([state()], GridListElement.prototype, "_scrollTop", void 0);
21034
21696
  __decorate([state()], GridListElement.prototype, "_scrollLeft", void 0);
21035
21697
  __decorate([state()], GridListElement.prototype, "_viewportHeight", void 0);
@@ -21049,6 +21711,7 @@ let PanelElement = class PanelElement extends i {
21049
21711
  this.toggleInteraction = `implicit`;
21050
21712
  this.variant = `titlebar`;
21051
21713
  this.hideTitlebar = false;
21714
+ this.keepToolbar = false;
21052
21715
  }
21053
21716
  _onTitlebarToggle(event) {
21054
21717
  event.stopPropagation();
@@ -21068,6 +21731,7 @@ let PanelElement = class PanelElement extends i {
21068
21731
  this.remove();
21069
21732
  }
21070
21733
  render() {
21734
+ const showToolbar = this.keepToolbar || !this.collapsed;
21071
21735
  return b`
21072
21736
  ${this.hideTitlebar ? A : b`
21073
21737
  <ixfx-titlebar
@@ -21082,7 +21746,7 @@ let PanelElement = class PanelElement extends i {
21082
21746
  @toggle=${this._onTitlebarToggle}
21083
21747
  @close=${this._onCloseClick}
21084
21748
  >
21085
- <slot name="toolbar" slot="toolbar"></slot>
21749
+ ${showToolbar ? b`<slot name="toolbar" slot="toolbar"></slot>` : A}
21086
21750
  </ixfx-titlebar>
21087
21751
  `}
21088
21752
 
@@ -21182,6 +21846,30 @@ let PanelElement = class PanelElement extends i {
21182
21846
  :host([variant="midline"][orientation="vertical"]) .content-wrapper {
21183
21847
  background: transparent;
21184
21848
  }
21849
+
21850
+ /* === Tree variant: VS Code explorer / Outline style === */
21851
+
21852
+ :host([variant="tree"]) {
21853
+ border: none !important;
21854
+ border-radius: 0 !important;
21855
+ background: transparent;
21856
+ }
21857
+
21858
+ :host([variant="tree"]) .content-inner {
21859
+ box-shadow: none;
21860
+ background: transparent;
21861
+ padding: 0;
21862
+ }
21863
+
21864
+ :host([variant="tree"]) .content-wrapper {
21865
+ background: transparent;
21866
+ }
21867
+
21868
+ :host([variant="tree"][orientation="vertical"]) .content-inner {
21869
+ width: max-content;
21870
+ height: 100%;
21871
+ transform: none;
21872
+ }
21185
21873
  `];
21186
21874
  }
21187
21875
  };
@@ -21217,6 +21905,10 @@ __decorate([property({
21217
21905
  type: Boolean,
21218
21906
  attribute: `hide-titlebar`
21219
21907
  })], PanelElement.prototype, "hideTitlebar", void 0);
21908
+ __decorate([property({
21909
+ type: Boolean,
21910
+ attribute: `keep-toolbar`
21911
+ })], PanelElement.prototype, "keepToolbar", void 0);
21220
21912
  PanelElement = __decorate([safeCustomElement(`ixfx-panel`)], PanelElement);
21221
21913
  //#endregion
21222
21914
  //#region src/grouped-item-lister/grouped-item-lister.ts
@@ -27885,15 +28577,62 @@ let LedElement = class LedElement extends i {
27885
28577
  this.value = 0;
27886
28578
  this.lit = false;
27887
28579
  this.color = `oklch(50% 0.15 25)`;
28580
+ this.glow = false;
28581
+ this.mode = `brightness`;
27888
28582
  this.blinking = false;
27889
28583
  this.rate = 500;
28584
+ this.attackTime = 40;
28585
+ this.releaseTime = 400;
28586
+ this.pulseRetrigger = false;
28587
+ this.#appliedMode = `brightness`;
28588
+ this.#pulseStart = 0;
28589
+ this.#pulsePeak = 0;
28590
+ this.#pulseFrom = 0;
28591
+ this.#pulseLevel = 0;
28592
+ this.#pulseTick = (now) => {
28593
+ const attack = Math.max(0, this.attackTime);
28594
+ const release = Math.max(0, this.releaseTime);
28595
+ const from = this.#pulseFrom;
28596
+ const peak = this.#pulsePeak;
28597
+ const elapsed = now - this.#pulseStart;
28598
+ let level;
28599
+ if (attack > 0 && elapsed < attack) level = from + (peak - from) * (elapsed / attack);
28600
+ else if (elapsed < attack + release) level = release > 0 ? peak * (1 - (elapsed - attack) / release) : 0;
28601
+ else level = 0;
28602
+ level = Math.max(0, Math.min(Math.max(from, peak), level));
28603
+ this.#pulseLevel = level;
28604
+ this.style.setProperty(`--value`, String(level));
28605
+ if (elapsed < attack + release) this.#pulseRaf = requestAnimationFrame(this.#pulseTick);
28606
+ else {
28607
+ this.#pulseRaf = void 0;
28608
+ this.#pulseLevel = 0;
28609
+ }
28610
+ };
27890
28611
  }
27891
28612
  #blinkInterval;
28613
+ #appliedMode;
28614
+ #pulseRaf;
28615
+ #pulseStart;
28616
+ #pulsePeak;
28617
+ #pulseFrom;
28618
+ #pulseLevel;
27892
28619
  willUpdate(changedProperties) {
27893
- if (changedProperties.has(`lit`)) this.value = this.lit ? 1 : 0;
27894
- if (changedProperties.has(`value`) || changedProperties.has(`lit`)) this.style.setProperty(`--value`, String(this.value));
27895
- if (changedProperties.has(`blinking`) || changedProperties.has(`rate`)) this.#updateBlinking();
28620
+ if (changedProperties.has(`blinking`)) {
28621
+ if (this.blinking) this.mode = `blink`;
28622
+ else if (this.mode === `blink`) this.mode = `brightness`;
28623
+ }
28624
+ if (this.mode !== this.#appliedMode) {
28625
+ this.#appliedMode = this.mode;
28626
+ this.#onModeChange();
28627
+ }
28628
+ if (changedProperties.has(`lit`)) {
28629
+ if (this.lit) this.value = 1;
28630
+ else if (changedProperties.get(`lit`) !== void 0) this.value = 0;
28631
+ }
28632
+ if (changedProperties.has(`color`)) this.style.setProperty(`--_color-attr`, this.color);
28633
+ if (this.mode !== `pulse` && (changedProperties.has(`value`) || changedProperties.has(`lit`))) this.style.setProperty(`--value`, String(this.value));
27896
28634
  if (changedProperties.has(`rate`)) {
28635
+ this.#updateBlinking();
27897
28636
  const transitionMs = this.rate < 300 ? this.rate * .5 : 150;
27898
28637
  this.style.setProperty(`--transition-duration`, `${transitionMs}ms`);
27899
28638
  }
@@ -27901,18 +28640,43 @@ let LedElement = class LedElement extends i {
27901
28640
  disconnectedCallback() {
27902
28641
  super.disconnectedCallback();
27903
28642
  this.#stopBlinking();
28643
+ this.#stopPulse();
27904
28644
  }
27905
- #updateBlinking() {
27906
- if (this.blinking && this.rate > 0) {
27907
- this.#stopBlinking();
27908
- this.#blinkInterval = window.setInterval(() => {
27909
- this.value = this.value === 0 ? 1 : 0;
27910
- }, this.rate);
27911
- } else {
27912
- this.#stopBlinking();
27913
- this.value = 0;
28645
+ /**
28646
+ * Pulse-mode trigger: lights the LED to {@link value} using an attack/release
28647
+ * envelope, then decays back to unlit. Calling again restarts the envelope
28648
+ * (from 0 if {@link pulseRetrigger} is set, otherwise from the current level).
28649
+ * No-op unless {@link mode} is `pulse`.
28650
+ */
28651
+ pulse() {
28652
+ if (this.mode !== `pulse`) return;
28653
+ this.#pulsePeak = this.value;
28654
+ this.#pulseFrom = this.pulseRetrigger ? 0 : this.#pulseLevel;
28655
+ this.#pulseStart = performance.now();
28656
+ if (this.#pulseRaf === void 0) this.#pulseRaf = requestAnimationFrame(this.#pulseTick);
28657
+ }
28658
+ #onModeChange() {
28659
+ this.#stopPulse();
28660
+ this.#pulseLevel = 0;
28661
+ this.blinking = this.mode === `blink`;
28662
+ this.#updateBlinking();
28663
+ this.style.setProperty(`--value`, this.mode === `pulse` ? `0` : String(this.value));
28664
+ }
28665
+ #pulseTick;
28666
+ #stopPulse() {
28667
+ if (this.#pulseRaf !== void 0) {
28668
+ cancelAnimationFrame(this.#pulseRaf);
28669
+ this.#pulseRaf = void 0;
27914
28670
  }
27915
28671
  }
28672
+ #updateBlinking() {
28673
+ const wasBlinking = this.#blinkInterval !== void 0;
28674
+ this.#stopBlinking();
28675
+ if (this.mode === `blink` && this.rate > 0) this.#blinkInterval = window.setInterval(() => {
28676
+ this.value = this.value === 0 ? 1 : 0;
28677
+ }, this.rate);
28678
+ else if (wasBlinking || this.mode === `blink`) this.value = 0;
28679
+ }
27916
28680
  #stopBlinking() {
27917
28681
  if (this.#blinkInterval !== void 0) {
27918
28682
  clearInterval(this.#blinkInterval);
@@ -27926,6 +28690,8 @@ let LedElement = class LedElement extends i {
27926
28690
  this.styles = i$3`
27927
28691
  :host {
27928
28692
  --_size: var(--size, 1ex);
28693
+ /* Resolved colour: explicit --colour wins, then the \`color\` attribute, then a default. */
28694
+ --_colour: var(--colour, var(--_color-attr, oklch(50% 0.15 25)));
27929
28695
  display: inline-block;
27930
28696
  min-width: 1ex;
27931
28697
  min-height: 1ex;
@@ -27935,12 +28701,61 @@ let LedElement = class LedElement extends i {
27935
28701
  }
27936
28702
 
27937
28703
  div {
28704
+ position: relative;
27938
28705
  width: 100%;
27939
28706
  height: 100%;
27940
28707
  border-radius: 50%;
27941
- background: color-mix(in oklch, var(--colour, oklch(50% 0.15 25)), white calc((1 - var(--value, 0)) * 50%));
28708
+ background: color-mix(in oklch, var(--_colour), white calc((1 - var(--value, 0)) * 50%));
27942
28709
  transition: background var(--transition-duration, 150ms) ease-out;
27943
28710
  }
28711
+
28712
+ /* Glow: a more realistic domed LED with a specular highlight and coloured bloom. */
28713
+ :host([glow]) {
28714
+ /* Body darkens toward black as the LED dims, so an unlit LED reads as a dark dome. */
28715
+ --_body: color-mix(in oklch, var(--_colour), black calc((1 - var(--value, 0)) * 60%));
28716
+ overflow: visible;
28717
+ }
28718
+
28719
+ :host([glow]) div {
28720
+ background:
28721
+ radial-gradient(circle at 32% 28%,
28722
+ color-mix(in oklch, var(--_body), white 55%) 0%,
28723
+ var(--_body) 45%,
28724
+ color-mix(in oklch, var(--_body), black 32%) 100%);
28725
+ box-shadow:
28726
+ /* inner rim shading for depth */
28727
+ inset 0 calc(var(--_size) * -0.1) calc(var(--_size) * 0.18)
28728
+ color-mix(in oklch, var(--_body), black 45%),
28729
+ inset 0 calc(var(--_size) * 0.08) calc(var(--_size) * 0.14)
28730
+ color-mix(in oklch, var(--_body), white 25%),
28731
+ /* coloured bloom, scaled by value so it only glows when lit */
28732
+ 0 0 calc(var(--_size) * 0.6 * var(--value, 0)) calc(var(--_size) * 0.12 * var(--value, 0))
28733
+ var(--_colour),
28734
+ 0 0 calc(var(--_size) * 1.7 * var(--value, 0)) calc(var(--_size) * 0.5 * var(--value, 0))
28735
+ color-mix(in oklch, var(--_colour), transparent 45%);
28736
+ transition:
28737
+ background var(--transition-duration, 150ms) ease-out,
28738
+ box-shadow var(--transition-duration, 150ms) ease-out;
28739
+ }
28740
+
28741
+ /* Specular highlight blob near the top-left. */
28742
+ :host([glow]) div::after {
28743
+ content: "";
28744
+ position: absolute;
28745
+ inset: 0;
28746
+ border-radius: 50%;
28747
+ background: radial-gradient(ellipse 55% 45% at 30% 22%,
28748
+ color-mix(in oklch, white, transparent 10%) 0%,
28749
+ transparent 60%);
28750
+ pointer-events: none;
28751
+ }
28752
+
28753
+ /* Pulse mode animates --value frame-by-frame, so drop the CSS transition
28754
+ that would otherwise smear the envelope. Placed last to win over the
28755
+ glow rule for elements matching both. */
28756
+ :host([mode="pulse"]) div {
28757
+ transition: none;
28758
+ }
27944
28759
  `;
27945
28760
  }
27946
28761
  };
@@ -27951,6 +28766,15 @@ __decorate([property({
27951
28766
  reflect: true
27952
28767
  })], LedElement.prototype, "lit", void 0);
27953
28768
  __decorate([property({ type: String })], LedElement.prototype, "color", void 0);
28769
+ __decorate([property({
28770
+ type: Boolean,
28771
+ attribute: `glow`,
28772
+ reflect: true
28773
+ })], LedElement.prototype, "glow", void 0);
28774
+ __decorate([property({
28775
+ type: String,
28776
+ reflect: true
28777
+ })], LedElement.prototype, "mode", void 0);
27954
28778
  __decorate([property({
27955
28779
  type: Boolean,
27956
28780
  attribute: `blinking`,
@@ -27960,6 +28784,18 @@ __decorate([property({
27960
28784
  type: Number,
27961
28785
  attribute: `rate`
27962
28786
  })], LedElement.prototype, "rate", void 0);
28787
+ __decorate([property({
28788
+ type: Number,
28789
+ attribute: `attack-time`
28790
+ })], LedElement.prototype, "attackTime", void 0);
28791
+ __decorate([property({
28792
+ type: Number,
28793
+ attribute: `release-time`
28794
+ })], LedElement.prototype, "releaseTime", void 0);
28795
+ __decorate([property({
28796
+ type: Boolean,
28797
+ attribute: `pulse-retrigger`
28798
+ })], LedElement.prototype, "pulseRetrigger", void 0);
27963
28799
  LedElement = __decorate([safeCustomElement(`ixfx-led`)], LedElement);
27964
28800
  //#endregion
27965
28801
  //#region src/miller/miller-base.ts
@@ -30127,6 +30963,66 @@ __decorate([query(`.text`)], PanTextElement.prototype, "_textEl", void 0);
30127
30963
  PanTextElement = __decorate([safeCustomElement(`ixfx-pan-text`)], PanTextElement);
30128
30964
  //#endregion
30129
30965
  //#region src/panel/panel-group.ts
30966
+ /** Pointer movement threshold (px) before a press becomes a drag. */
30967
+ const DRAG_THRESHOLD_PX = 5;
30968
+ /**
30969
+ * Pure helper: nearest gap index for a pointer position along one axis.
30970
+ * `edges` are the [start, end] of each sibling (excluding the drag source),
30971
+ * in DOM order. Returns 0..edges.length.
30972
+ */
30973
+ function panelGapIndex(edges, pos) {
30974
+ let bestGap = 0;
30975
+ let bestDist = Number.POSITIVE_INFINITY;
30976
+ for (let i = 0; i < edges.length; i++) {
30977
+ const [start, end] = edges[i];
30978
+ if (pos < (start + end) / 2) {
30979
+ const d = Math.abs(pos - start);
30980
+ if (d < bestDist) {
30981
+ bestDist = d;
30982
+ bestGap = i;
30983
+ }
30984
+ } else {
30985
+ const d = Math.abs(pos - end);
30986
+ if (d < bestDist) {
30987
+ bestDist = d;
30988
+ bestGap = i + 1;
30989
+ }
30990
+ }
30991
+ }
30992
+ return bestGap;
30993
+ }
30994
+ let panelGhostStylesInjected = false;
30995
+ /**
30996
+ * Global ghost styling so the drag preview matches `ixfx-titlebar` chrome
30997
+ * (font, colours, padding). Injected once; the ghost lives outside shadow DOM
30998
+ * (body/theme container) so component styles cannot reach it.
30999
+ */
31000
+ function injectPanelGhostStyles() {
31001
+ if (panelGhostStylesInjected) return;
31002
+ panelGhostStylesInjected = true;
31003
+ const style = document.createElement(`style`);
31004
+ style.textContent = `
31005
+ .ixfx-drag-ghost.ixfx-panel-group-ghost {
31006
+ padding: var(--space-s, 8px) var(--space-m, 16px);
31007
+ background: var(--surface-4);
31008
+ color: var(--surface-3-text);
31009
+ border: none;
31010
+ border-radius: var(--radius-s, 4px);
31011
+ font-family: var(--font-family);
31012
+ font-size: var(--text-m);
31013
+ font-weight: var(--font-weight-bold, 600);
31014
+ min-height: 2em;
31015
+ user-select: none;
31016
+ -webkit-user-select: none;
31017
+ }
31018
+ .ixfx-drag-ghost.ixfx-panel-group-ghost ixfx-icon {
31019
+ width: 1.25em;
31020
+ height: 1.25em;
31021
+ flex-shrink: 0;
31022
+ }
31023
+ `;
31024
+ document.head.appendChild(style);
31025
+ }
30130
31026
  let PanelGroupElement = class PanelGroupElement extends i {
30131
31027
  constructor(..._args) {
30132
31028
  super(..._args);
@@ -30134,22 +31030,105 @@ let PanelGroupElement = class PanelGroupElement extends i {
30134
31030
  this.variant = `titlebar`;
30135
31031
  this.toggleInteraction = `implicit`;
30136
31032
  this.collapsible = true;
30137
- this.allowMultiple = false;
30138
- this.gap = `var(--space-m)`;
31033
+ this.accordion = false;
31034
+ this.allowReorder = false;
31035
+ this._dragState = null;
31036
+ this._suppressNextClick = false;
31037
+ this._onClickCapture = (event) => {
31038
+ if (!this._suppressNextClick) return;
31039
+ this._suppressNextClick = false;
31040
+ event.preventDefault();
31041
+ event.stopPropagation();
31042
+ };
31043
+ this._onKeyDown = (event) => {
31044
+ const state = this._dragState;
31045
+ if (event.key !== `Escape` || !state?.active) return;
31046
+ event.preventDefault();
31047
+ const pointerId = state.pointerId;
31048
+ this._abortDrag();
31049
+ this._endDrag(pointerId);
31050
+ };
31051
+ this._onPointerDown = (event) => {
31052
+ if (!this.allowReorder || event.button !== 0) return;
31053
+ const target = this._findDragTarget(event);
31054
+ if (!target?.parentNode) return;
31055
+ const fromIndex = Array.prototype.indexOf.call(target.parentNode.children, target);
31056
+ if (fromIndex < 0) return;
31057
+ this._dragState = {
31058
+ pointerId: event.pointerId,
31059
+ source: target,
31060
+ fromIndex,
31061
+ startX: event.clientX,
31062
+ startY: event.clientY,
31063
+ active: false,
31064
+ ghost: null,
31065
+ gapIndex: void 0
31066
+ };
31067
+ window.addEventListener(`pointermove`, this._onPointerMove);
31068
+ window.addEventListener(`pointerup`, this._onPointerUp);
31069
+ window.addEventListener(`pointercancel`, this._onPointerCancel);
31070
+ };
31071
+ this._onPointerMove = (event) => {
31072
+ const state = this._dragState;
31073
+ if (!state || event.pointerId !== state.pointerId) return;
31074
+ const distance = Math.hypot(event.clientX - state.startX, event.clientY - state.startY);
31075
+ if (!state.active) {
31076
+ if (distance < DRAG_THRESHOLD_PX) return;
31077
+ this._beginDrag(event);
31078
+ }
31079
+ state.ghost?.move(event.clientX, event.clientY);
31080
+ const gap = this._computeGapIndex(event.clientX, event.clientY);
31081
+ if (gap !== state.gapIndex) {
31082
+ state.gapIndex = gap;
31083
+ this.requestUpdate();
31084
+ }
31085
+ };
31086
+ this._onPointerUp = (event) => {
31087
+ const state = this._dragState;
31088
+ if (!state || event.pointerId !== state.pointerId) return;
31089
+ if (state.active) {
31090
+ const gap = this._computeGapIndex(event.clientX, event.clientY);
31091
+ this._commitDrag(state.source, state.fromIndex, gap);
31092
+ }
31093
+ this._endDrag(event.pointerId);
31094
+ };
31095
+ this._onPointerCancel = (event) => {
31096
+ const state = this._dragState;
31097
+ if (!state || event.pointerId !== state.pointerId) return;
31098
+ this._abortDrag();
31099
+ this._endDrag(event.pointerId);
31100
+ };
30139
31101
  }
30140
31102
  _onPanelToggle(e) {
30141
31103
  const panel = e.target;
30142
31104
  if (panel.collapsed) return;
30143
- if (this.allowMultiple) return;
31105
+ if (!this.accordion) return;
30144
31106
  for (const p of this._panels) if (p !== panel) p.collapsed = true;
30145
31107
  }
31108
+ connectedCallback() {
31109
+ super.connectedCallback();
31110
+ this.addEventListener(`click`, this._onClickCapture, true);
31111
+ }
31112
+ disconnectedCallback() {
31113
+ this.removeEventListener(`click`, this._onClickCapture, true);
31114
+ if (this._dragState) {
31115
+ this._abortDrag();
31116
+ this._endDrag(this._dragState.pointerId);
31117
+ }
31118
+ super.disconnectedCallback();
31119
+ }
30146
31120
  firstUpdated() {
31121
+ if (this.gap !== void 0) this.style.setProperty(`--group-gap`, this.gap);
30147
31122
  this.updateComplete.then(() => {
30148
31123
  this._propagateAll();
30149
31124
  });
30150
31125
  }
30151
31126
  updated(changed) {
30152
31127
  super.updated(changed);
31128
+ if (changed.has(`gap`)) {
31129
+ if (this.gap !== void 0) this.style.setProperty(`--group-gap`, this.gap);
31130
+ else this.style.removeProperty(`--group-gap`);
31131
+ }
30153
31132
  if (changed.has(`variant`) || changed.has(`toggleInteraction`) || changed.has(`orientation`) || changed.has(`collapsible`)) this.updateComplete.then(() => {
30154
31133
  this._propagateAll();
30155
31134
  });
@@ -30164,19 +31143,167 @@ let PanelGroupElement = class PanelGroupElement extends i {
30164
31143
  else panel.removeAttribute(`collapsible`);
30165
31144
  }
30166
31145
  }
31146
+ _onSlotChange() {
31147
+ this.updateComplete.then(() => {
31148
+ this._propagateAll();
31149
+ });
31150
+ }
31151
+ /**
31152
+ * Walk composedPath for the nearest ixfx-panel, requiring the press to have
31153
+ * gone through an ixfx-titlebar (titlebar-anywhere drag). Bails when the
31154
+ * path crosses interactive chrome (buttons/links/inputs/toolbar/caret/close)
31155
+ * so those controls always win over dragging.
31156
+ */
31157
+ _findDragTarget(event) {
31158
+ if (!this.allowReorder) return null;
31159
+ const path = event.composedPath();
31160
+ let panel = null;
31161
+ let viaTitlebar = false;
31162
+ for (const node of path) {
31163
+ if (!(node instanceof HTMLElement)) continue;
31164
+ const tag = node.tagName;
31165
+ if (tag === `BUTTON` || tag === `A` || tag === `INPUT` || tag === `SELECT` || tag === `TEXTAREA`) return null;
31166
+ if (node.classList.contains(`toolbar`) || node.classList.contains(`close-btn`) || node.classList.contains(`caret-btn`)) return null;
31167
+ if (node.hasAttribute?.(`slot`) && node.getAttribute(`slot`) === `toolbar`) return null;
31168
+ if (tag === `IXFX-TITLEBAR`) viaTitlebar = true;
31169
+ if (!panel && tag === `IXFX-PANEL`) panel = node;
31170
+ }
31171
+ if (!panel || !viaTitlebar) return null;
31172
+ return panel;
31173
+ }
31174
+ _beginDrag(event) {
31175
+ const state = this._dragState;
31176
+ if (!state || state.active) return;
31177
+ try {
31178
+ this.setPointerCapture(event.pointerId);
31179
+ } catch {}
31180
+ state.active = true;
31181
+ this.setAttribute(`dragging`, ``);
31182
+ state.source.setAttribute(`drag-source`, ``);
31183
+ state.ghost = this._createGhost(state.source);
31184
+ state.ghost?.move(event.clientX, event.clientY);
31185
+ }
31186
+ _abortDrag() {
31187
+ const state = this._dragState;
31188
+ if (!state) return;
31189
+ state.source.removeAttribute(`drag-source`);
31190
+ if (state.gapIndex !== void 0) {
31191
+ state.gapIndex = void 0;
31192
+ this.requestUpdate();
31193
+ }
31194
+ }
31195
+ _endDrag(pointerId) {
31196
+ const state = this._dragState;
31197
+ if (!state) return;
31198
+ state.ghost?.destroy();
31199
+ state.ghost = null;
31200
+ this.removeAttribute(`dragging`);
31201
+ this._dragState = null;
31202
+ window.removeEventListener(`pointermove`, this._onPointerMove);
31203
+ window.removeEventListener(`pointerup`, this._onPointerUp);
31204
+ window.removeEventListener(`pointercancel`, this._onPointerCancel);
31205
+ try {
31206
+ this.releasePointerCapture(pointerId);
31207
+ } catch {}
31208
+ this.requestUpdate();
31209
+ }
31210
+ _commitDrag(source, fromIndex, gapIndex) {
31211
+ source.removeAttribute(`drag-source`);
31212
+ const siblings = (this._panels ?? []).filter((el) => el !== source);
31213
+ const target = gapIndex === void 0 ? siblings.length : Math.max(0, Math.min(gapIndex, siblings.length));
31214
+ const parent = source.parentNode;
31215
+ if (!parent) return;
31216
+ const ref = siblings[target] ?? null;
31217
+ parent.insertBefore(source, ref);
31218
+ const toIndex = Array.prototype.indexOf.call(parent.children, source);
31219
+ if (toIndex === fromIndex) return;
31220
+ this._suppressNextClick = true;
31221
+ this.dispatchEvent(new CustomEvent(`reorder`, {
31222
+ bubbles: true,
31223
+ composed: true,
31224
+ detail: {
31225
+ fromIndex,
31226
+ toIndex,
31227
+ panel: source
31228
+ }
31229
+ }));
31230
+ }
31231
+ _createGhost(source) {
31232
+ const content = document.createElement(`span`);
31233
+ content.style.display = `inline-flex`;
31234
+ content.style.alignItems = `center`;
31235
+ content.style.padding = `0 var(--space-s, 8px)`;
31236
+ content.style.minWidth = `0`;
31237
+ if (source.iconName) {
31238
+ const icon = document.createElement(`ixfx-icon`);
31239
+ icon.name = source.iconName;
31240
+ content.appendChild(icon);
31241
+ }
31242
+ const label = document.createElement(`span`);
31243
+ label.textContent = (source.title || (source.textContent ?? ``).trim()).slice(0, 60);
31244
+ label.style.paddingLeft = `1ch`;
31245
+ label.style.overflow = `hidden`;
31246
+ label.style.textOverflow = `ellipsis`;
31247
+ label.style.whiteSpace = `nowrap`;
31248
+ content.appendChild(label);
31249
+ try {
31250
+ injectPanelGhostStyles();
31251
+ return createDragGhost({
31252
+ anchor: source,
31253
+ content,
31254
+ className: `ixfx-panel-group-ghost`
31255
+ });
31256
+ } catch {
31257
+ return null;
31258
+ }
31259
+ }
31260
+ /**
31261
+ * Nearest panel gap to the pointer, ignoring the drag source.
31262
+ * The perpendicular axis is ignored — the pointer always resolves to a gap.
31263
+ */
31264
+ _computeGapIndex(clientX, clientY) {
31265
+ const state = this._dragState;
31266
+ if (!state) return;
31267
+ const siblings = (this._panels ?? []).filter((el) => el !== state.source);
31268
+ if (siblings.length === 0) return 0;
31269
+ const useX = this.orientation === `horizontal`;
31270
+ const pos = useX ? clientX : clientY;
31271
+ return panelGapIndex(siblings.map((el) => {
31272
+ const rect = el.getBoundingClientRect();
31273
+ return [useX ? rect.left : rect.top, useX ? rect.right : rect.bottom];
31274
+ }), pos);
31275
+ }
31276
+ _computeCaretStyle() {
31277
+ const state = this._dragState;
31278
+ if (!state?.active || state.gapIndex === void 0) return;
31279
+ const siblings = (this._panels ?? []).filter((el) => el !== state.source);
31280
+ if (siblings.length === 0) return;
31281
+ const hostRect = this.getBoundingClientRect();
31282
+ if (!hostRect) return;
31283
+ const gap = Math.max(0, Math.min(state.gapIndex, siblings.length));
31284
+ const rect = gap < siblings.length ? siblings[gap].getBoundingClientRect() : siblings[siblings.length - 1].getBoundingClientRect();
31285
+ if (this.orientation === `horizontal`) return `transform: translate(${(gap < siblings.length ? rect.left : rect.right) - hostRect.left - 1}px, 0); width: 2px; height: ${hostRect.height}px;`;
31286
+ return `transform: translate(0, ${(gap < siblings.length ? rect.top : rect.bottom) - hostRect.top - 1}px); width: ${hostRect.width}px; height: 2px;`;
31287
+ }
30167
31288
  render() {
31289
+ const caretStyle = this._computeCaretStyle();
30168
31290
  return b`
30169
31291
  <slot
30170
31292
  @toggle=${this._onPanelToggle}
31293
+ @slotchange=${this._onSlotChange}
31294
+ @pointerdown=${this._onPointerDown}
31295
+ @keydown=${this._onKeyDown}
30171
31296
  ></slot>
31297
+ ${caretStyle ? b`<div class="drop-caret" style=${caretStyle}></div>` : A}
30172
31298
  `;
30173
31299
  }
30174
31300
  static {
30175
31301
  this.styles = i$3`
30176
31302
  :host {
31303
+ position: relative;
30177
31304
  display: flex;
30178
31305
  flex-direction: column;
30179
- gap: var(--group-gap, var(--space-m));
31306
+ gap: var(--group-gap, var(--space-l));
30180
31307
  }
30181
31308
 
30182
31309
  :host([orientation="horizontal"]) {
@@ -30197,6 +31324,36 @@ let PanelGroupElement = class PanelGroupElement extends i {
30197
31324
  flex-basis: content;
30198
31325
  min-width: 0;
30199
31326
  }
31327
+
31328
+ :host([allow-reorder]) ::slotted(ixfx-panel) {
31329
+ cursor: grab;
31330
+ }
31331
+
31332
+ :host([allow-reorder]) ::slotted(ixfx-panel[drag-source]) {
31333
+ cursor: grabbing;
31334
+ opacity: 0.4;
31335
+ }
31336
+
31337
+ /* While dragging, suppress text selection inside the panels. */
31338
+ :host([dragging]),
31339
+ :host([dragging]) ::slotted(*) {
31340
+ user-select: none;
31341
+ -webkit-user-select: none;
31342
+ }
31343
+
31344
+ :host([dragging]) ::slotted(ixfx-panel) {
31345
+ cursor: grabbing;
31346
+ }
31347
+
31348
+ .drop-caret {
31349
+ position: absolute;
31350
+ top: 0;
31351
+ left: 0;
31352
+ background: var(--accent);
31353
+ border-radius: 1px;
31354
+ pointer-events: none;
31355
+ z-index: 1;
31356
+ }
30200
31357
  `;
30201
31358
  }
30202
31359
  };
@@ -30212,8 +31369,14 @@ __decorate([property({
30212
31369
  __decorate([property({ type: Boolean })], PanelGroupElement.prototype, "collapsible", void 0);
30213
31370
  __decorate([property({
30214
31371
  type: Boolean,
30215
- attribute: `allow-multiple`
30216
- })], PanelGroupElement.prototype, "allowMultiple", void 0);
31372
+ attribute: `accordion`,
31373
+ reflect: true
31374
+ })], PanelGroupElement.prototype, "accordion", void 0);
31375
+ __decorate([property({
31376
+ type: Boolean,
31377
+ attribute: `allow-reorder`,
31378
+ reflect: true
31379
+ })], PanelGroupElement.prototype, "allowReorder", void 0);
30217
31380
  __decorate([property()], PanelGroupElement.prototype, "gap", void 0);
30218
31381
  __decorate([queryAssignedElements({ slot: `` })], PanelGroupElement.prototype, "_panels", void 0);
30219
31382
  PanelGroupElement = __decorate([safeCustomElement(`ixfx-panel-group`)], PanelGroupElement);
@@ -30926,11 +32089,18 @@ let PlotHistogram = class PlotHistogram extends i {
30926
32089
  this.valueTooltips = `hover`;
30927
32090
  this.tooltipDelay = 400;
30928
32091
  this.valueUpdateInterval = 500;
32092
+ this.interactionMode = `inspect`;
30929
32093
  this.valueFormat = (valueMin, valueMax, frequency) => `${valueMin.toFixed(2)} - ${valueMax.toFixed(2)}: ${frequency}`;
30930
32094
  this._width = 100;
30931
32095
  this._height = 40;
30932
32096
  this._hoveredIndex = null;
30933
32097
  this._inlineOverlayText = null;
32098
+ this._coloredRangeMin = null;
32099
+ this._coloredRangeMax = null;
32100
+ this._selectionStart = null;
32101
+ this._selectionEnd = null;
32102
+ this._isDragging = false;
32103
+ this._selectionOverlayText = null;
30934
32104
  this.#series = new PlotDataSeries({
30935
32105
  capacityLimit: 100,
30936
32106
  persistentScaling: true
@@ -30943,6 +32113,22 @@ let PlotHistogram = class PlotHistogram extends i {
30943
32113
  this.#tooltipPosition = null;
30944
32114
  this.#valueRefreshTimer = null;
30945
32115
  this.#onPointerMove = (e) => {
32116
+ if (this.interactionMode === `select` && this._isDragging) {
32117
+ const index = this.#bucketIndexFromPosition(e.clientX, e.clientY);
32118
+ this._selectionEnd = index;
32119
+ const start = Math.min(this._selectionStart, this._selectionEnd);
32120
+ const end = Math.max(this._selectionStart, this._selectionEnd);
32121
+ const bars = this.#computeBars();
32122
+ if (start <= end) {
32123
+ const startBar = bars[start];
32124
+ const endBar = bars[end];
32125
+ this.setColoredRange(startBar.valueMin, endBar.valueMax);
32126
+ this._selectionOverlayText = `Select: ${startBar.valueMin.toFixed(2)} – ${endBar.valueMax.toFixed(2)}`;
32127
+ }
32128
+ this.requestUpdate();
32129
+ return;
32130
+ }
32131
+ if (this.interactionMode === `select`) return;
30946
32132
  if (this.valueTooltips === `none`) return;
30947
32133
  const index = this.#bucketIndexFromPointer(e.clientX, e.clientY);
30948
32134
  const bucketIndex = index >= 0 ? index : null;
@@ -30950,10 +32136,34 @@ let PlotHistogram = class PlotHistogram extends i {
30950
32136
  else this.#updateBubbleTooltip(bucketIndex, e.clientX, e.clientY);
30951
32137
  };
30952
32138
  this.#onPointerLeave = () => {
32139
+ if (this.interactionMode === `select`) {
32140
+ if (this._isDragging) {
32141
+ this._isDragging = false;
32142
+ this._selectionOverlayText = null;
32143
+ this.requestUpdate();
32144
+ }
32145
+ return;
32146
+ }
30953
32147
  if (this.valueTooltips === `none`) return;
30954
32148
  if (this.valueTooltips === `inline`) this.#setInlineIndex(null);
30955
32149
  else this.#updateBubbleTooltip(null, 0, 0);
30956
32150
  };
32151
+ this.#onPointerDown = (e) => {
32152
+ if (this.interactionMode !== `select`) return;
32153
+ const index = this.#bucketIndexFromPosition(e.clientX, e.clientY);
32154
+ this._isDragging = true;
32155
+ this._selectionStart = index;
32156
+ this._selectionEnd = index;
32157
+ const bars = this.#computeBars();
32158
+ const startBar = bars[index];
32159
+ const endBar = bars[index];
32160
+ this.setColoredRange(startBar.valueMin, endBar.valueMax);
32161
+ this._selectionOverlayText = `Select: ${startBar.valueMin.toFixed(2)} – ${endBar.valueMax.toFixed(2)}`;
32162
+ this.requestUpdate();
32163
+ };
32164
+ this.#onPointerUp = () => {
32165
+ if (this.interactionMode === `select`) this._isDragging = false;
32166
+ };
30957
32167
  }
30958
32168
  #series;
30959
32169
  #hasProgrammaticData;
@@ -30977,6 +32187,59 @@ let PlotHistogram = class PlotHistogram extends i {
30977
32187
  this.#series.clear();
30978
32188
  this.requestUpdate();
30979
32189
  }
32190
+ setColoredRange(min, max) {
32191
+ this._coloredRangeMin = min;
32192
+ this._coloredRangeMax = max;
32193
+ this.requestUpdate();
32194
+ }
32195
+ clearColoredRange() {
32196
+ this._coloredRangeMin = null;
32197
+ this._coloredRangeMax = null;
32198
+ this.requestUpdate();
32199
+ }
32200
+ setSelection(startBarIndex, endBarIndex) {
32201
+ const bars = this.#computeBars();
32202
+ const start = Math.min(startBarIndex, endBarIndex);
32203
+ const end = Math.max(startBarIndex, endBarIndex);
32204
+ const clampedStart = Math.max(0, Math.min(start, bars.length - 1));
32205
+ const clampedEnd = Math.max(0, Math.min(end, bars.length - 1));
32206
+ this._selectionStart = clampedStart;
32207
+ this._selectionEnd = clampedEnd;
32208
+ if (clampedStart <= clampedEnd) {
32209
+ const startBar = bars[clampedStart];
32210
+ const endBar = bars[clampedEnd];
32211
+ this.setColoredRange(startBar.valueMin, endBar.valueMax);
32212
+ }
32213
+ this.requestUpdate();
32214
+ }
32215
+ clearSelection() {
32216
+ this._selectionStart = null;
32217
+ this._selectionEnd = null;
32218
+ this.requestUpdate();
32219
+ }
32220
+ getSelectedRange() {
32221
+ if (this._selectionStart === null || this._selectionEnd === null) return {
32222
+ barIndices: null,
32223
+ dataValues: null
32224
+ };
32225
+ const bars = this.#computeBars();
32226
+ const start = Math.min(this._selectionStart, this._selectionEnd);
32227
+ const end = Math.max(this._selectionStart, this._selectionEnd);
32228
+ const clampedStart = Math.max(0, Math.min(start, bars.length - 1));
32229
+ const clampedEnd = Math.max(0, Math.min(end, bars.length - 1));
32230
+ const startBar = bars[clampedStart];
32231
+ const endBar = bars[clampedEnd];
32232
+ return {
32233
+ barIndices: {
32234
+ start: clampedStart,
32235
+ end: clampedEnd
32236
+ },
32237
+ dataValues: {
32238
+ min: startBar.valueMin,
32239
+ max: endBar.valueMax
32240
+ }
32241
+ };
32242
+ }
30980
32243
  firstUpdated(_changed) {
30981
32244
  this._width = this.clientWidth || 100;
30982
32245
  this._height = this.clientHeight || 40;
@@ -31111,6 +32374,10 @@ let PlotHistogram = class PlotHistogram extends i {
31111
32374
  }
31112
32375
  });
31113
32376
  }
32377
+ #isBarInColoredRange(bar) {
32378
+ if (this._coloredRangeMin === null || this._coloredRangeMax === null) return true;
32379
+ return bar.valueMin < this._coloredRangeMax && bar.valueMax > this._coloredRangeMin;
32380
+ }
31114
32381
  /** Index of the bar rect under the pointer, or -1 if none (matches click hit-testing). */
31115
32382
  #bucketIndexFromPointer(clientX, clientY) {
31116
32383
  const rect = this.getBoundingClientRect();
@@ -31118,6 +32385,18 @@ let PlotHistogram = class PlotHistogram extends i {
31118
32385
  const y = clientY - rect.top;
31119
32386
  return this.#computeBars().findIndex((b) => x >= b.x && x <= b.x + b.width && y >= b.y && y <= b.y + b.height);
31120
32387
  }
32388
+ /** Gets bucket index from position, clamping to valid range (0 to bucketCount-1). */
32389
+ #bucketIndexFromPosition(clientX, clientY) {
32390
+ const { w, h, isHorizontal, padding, dataMin, dataSpan, bucketCount, availableMain } = this.#computeGeometry();
32391
+ const rect = this.getBoundingClientRect();
32392
+ const x = clientX - rect.left;
32393
+ const y = clientY - rect.top;
32394
+ let relativePos;
32395
+ if (isHorizontal) relativePos = (x - padding) / availableMain;
32396
+ else relativePos = (y - padding) / availableMain;
32397
+ relativePos = Math.max(0, Math.min(1, relativePos));
32398
+ return Math.floor(relativePos * bucketCount);
32399
+ }
31121
32400
  #tooltipIndex;
31122
32401
  #tooltipVisible;
31123
32402
  #tooltipShowTimer;
@@ -31252,15 +32531,23 @@ let PlotHistogram = class PlotHistogram extends i {
31252
32531
  }
31253
32532
  #onPointerMove;
31254
32533
  #onPointerLeave;
32534
+ #onPointerDown;
32535
+ #onPointerUp;
31255
32536
  render() {
31256
32537
  const { w: w$7, h, padding } = this.#computeGeometry();
31257
- const barsContent = this.#computeBars().map((b, i) => w`
31258
- <rect class="bar" x=${b.x} y=${b.y} width=${b.width} height=${b.height}
31259
- style="--value: ${b.relativeValue}"
31260
- @click=${() => this.#emitClick(i, b.valueMin, b.valueMax, b.relativeValue)} />
31261
- `);
32538
+ const barsContent = this.#computeBars().map((b, i) => {
32539
+ const greyStyle = this.#isBarInColoredRange(b) ? `` : `filter: grayscale(1)`;
32540
+ return w`
32541
+ <rect class="bar" x=${b.x} y=${b.y} width=${b.width} height=${b.height}
32542
+ style="--value: ${b.relativeValue}; ${greyStyle}"
32543
+ @click=${() => this.#emitClick(i, b.valueMin, b.valueMax, b.relativeValue)} />
32544
+ `;
32545
+ });
31262
32546
  const textColor = `var(--plot-text, var(--surface-2-text, #fff))`;
31263
- const overlayText = this.valueTooltips === `inline` && this._inlineOverlayText !== null ? this._inlineOverlayText : this.title;
32547
+ let overlayText;
32548
+ if (this.interactionMode === `select` && this._selectionOverlayText !== null) overlayText = this._selectionOverlayText;
32549
+ else if (this.valueTooltips === `inline` && this._inlineOverlayText !== null) overlayText = this._inlineOverlayText;
32550
+ else overlayText = this.title;
31264
32551
  const titleContent = overlayText ? w`<text x=${padding + 2} y=${padding + Math.min(10, h * .6)}
31265
32552
  class="overlay-text" fill=${textColor}
31266
32553
  style="opacity: 0.6">${overlayText}</text>` : w``;
@@ -31268,7 +32555,9 @@ let PlotHistogram = class PlotHistogram extends i {
31268
32555
  <svg width=${w$7} height=${h} viewBox="0 0 ${w$7} ${h}"
31269
32556
  xmlns="http://www.w3.org/2000/svg"
31270
32557
  @pointermove=${this.#onPointerMove}
31271
- @pointerleave=${this.#onPointerLeave}>
32558
+ @pointerleave=${this.#onPointerLeave}
32559
+ @pointerdown=${this.#onPointerDown}
32560
+ @pointerup=${this.#onPointerUp}>
31272
32561
  <rect width=${w$7} height=${h} fill="transparent" />
31273
32562
  ${barsContent}
31274
32563
  ${titleContent}
@@ -31330,10 +32619,17 @@ __decorate([property({
31330
32619
  type: Number,
31331
32620
  attribute: `value-update-interval`
31332
32621
  })], PlotHistogram.prototype, "valueUpdateInterval", void 0);
32622
+ __decorate([property({ attribute: `interaction-mode` })], PlotHistogram.prototype, "interactionMode", void 0);
31333
32623
  __decorate([state()], PlotHistogram.prototype, "_width", void 0);
31334
32624
  __decorate([state()], PlotHistogram.prototype, "_height", void 0);
31335
32625
  __decorate([state()], PlotHistogram.prototype, "_hoveredIndex", void 0);
31336
32626
  __decorate([state()], PlotHistogram.prototype, "_inlineOverlayText", void 0);
32627
+ __decorate([state()], PlotHistogram.prototype, "_coloredRangeMin", void 0);
32628
+ __decorate([state()], PlotHistogram.prototype, "_coloredRangeMax", void 0);
32629
+ __decorate([state()], PlotHistogram.prototype, "_selectionStart", void 0);
32630
+ __decorate([state()], PlotHistogram.prototype, "_selectionEnd", void 0);
32631
+ __decorate([state()], PlotHistogram.prototype, "_isDragging", void 0);
32632
+ __decorate([state()], PlotHistogram.prototype, "_selectionOverlayText", void 0);
31337
32633
  PlotHistogram = __decorate([safeCustomElement(`ixfx-plot-histogram`)], PlotHistogram);
31338
32634
  //#endregion
31339
32635
  //#region src/plots/single-axis.ts
@@ -39524,6 +40820,6 @@ function init() {
39524
40820
  console.log(`Init`);
39525
40821
  }
39526
40822
  //#endregion
39527
- export { AcTextElement, AcTokenElement, ButtonElement, Checkbox, ColourPicker, ColourPickerPopup, CrumbNavigationElement, CrumbPathController, CrumbPathElement, DataController, DataDisplayComponent, DataGridElement, DataTableElement, DetailListElement, DockHostElement, DockPanelElement, EditableLabel, EditableNumber, FormElement, FormGroupElement, FormPartElement, GridListElement, GroupedItemListerElement, ICON_CARET_RIGHT, ICON_CHECK, ICON_CHEVRON_DOWN, ICON_CLOSE, IncrSearchTreeController as IncrSearchMillerController, IncrSearchTreeController, IxfxHexEditorElement, IxfxIconElement, IxfxTimelineElement, KeyedElementIndex, LIST_ITEM_MOTION_MS, LIST_SEARCH_COMMAND_IDS, LIST_SEARCH_MODES, LabelledRadialInput, LabelledRangeInput, LabelledSliderInput, LedElement, ListSearchController, ListSelectionController, menu_exports as Menu, MillerBaseElement, MillerListElement, MillerTreeController, NarrowedTextElement, NotificationManager, NotificationPillElement, PanTextElement, PanelElement, PanelGroupElement, PlotDataSeries, PlotHistogram, PlotMultiAxis, PlotSingleAxis, PlotXyAxis, PolarPad, RadialInputElement, RadioGroup, RangeElement, RangeInput, RangeMultiElement, RatingElement, SelectHorizElement, 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, TreeKeyboardNavController, TreeListElement, TwoSplitLayoutElement, VerticalListElement, XyPad, addPanelToStack, animateListItemIn, animateListItemOut, applyAgeModification, applyHighlights, applySnapshot, attachTimelineInteractions, buildHighlightTargets, buildModel, clearHighlights, collectContainerNames, collectDataResult, collectPanelIds, collectStackPaths, computeColour, computeLeafGeoms, createAcCompletionProvider, createElementIncrSearch, createElementListSearchAdapter, createEmptyLayout, createEmptyStack, createFuzzySearch, createIncrSearch, createPillRenderer, createSplit, createStack, createTreeListAdapter, createTreeListSearchAdapter, createVerticalListAdapter, distribute, extractStack, findNode, findParentOf, findStackOf, findStackPathByName, getActiveDragPanelId, getIcon, getIconEntry, getIconFont, getLeafNeighbours, hasIcon, iconBus, init, insertBeside, isDragging, isMultiMode, isSplit, isStack, keyForElement, listItemFadeKeyframes, listSearchStyles, monitorIndeterminate, moveLeafNodeToEdge, moveLeafStep, normalise, normalize, parseColourScaleAttr, parseLayout, pointerOverAnyHost, prefersReducedMotion, readBaseColour, registerFontIcon, registerIcon, registerListSearchCommands, removePanelFromStack, resolveCssVars, resolveGrid, resolveLeafPath, resolveTracks, serializeLayout, setActive, setIconFont, svgToDataUri, swapLeaves, tickledItemStyles, tickledStyles, timelineStyles, toSnapshot, walk, wrapRoot };
40823
+ export { AcTextElement, AcTokenElement, ButtonElement, Checkbox, ColourPicker, ColourPickerPopup, CrumbNavigationElement, CrumbPathController, CrumbPathElement, DataController, DataDisplayComponent, DataGridElement, DataTableElement, DetailListElement, DockHostElement, DockPanelElement, EditableLabel, EditableNumber, FormElement, FormGroupElement, FormPartElement, GridListElement, GroupedItemListerElement, ICON_CARET_RIGHT, ICON_CHECK, ICON_CHEVRON_DOWN, ICON_CLOSE, IncrSearchTreeController as IncrSearchMillerController, IncrSearchTreeController, IxfxHexEditorElement, IxfxIconElement, IxfxTimelineElement, KeyedElementIndex, LIST_ITEM_MOTION_MS, LIST_SEARCH_COMMAND_IDS, LIST_SEARCH_MODES, LabelledRadialInput, LabelledRangeInput, LabelledSliderInput, LedElement, ListSearchController, ListSelectionController, menu_exports as Menu, MillerBaseElement, MillerListElement, MillerTreeController, NarrowedTextElement, NotificationManager, NotificationPillElement, PanTextElement, PanelElement, PanelGroupElement, PlotDataSeries, PlotHistogram, PlotMultiAxis, PlotSingleAxis, PlotXyAxis, PolarPad, RadialInputElement, RadioGroup, RangeElement, RangeInput, RangeMultiElement, RatingElement, SelectHorizElement, 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, TreeKeyboardNavController, TreeListElement, TwoSplitLayoutElement, VerticalListElement, XyPad, addPanelToStack, animateListItemIn, animateListItemOut, applyAgeModification, applyHighlights, applySnapshot, attachTimelineInteractions, buildHighlightTargets, buildModel, clearHighlights, collectContainerNames, collectDataResult, collectPanelIds, collectStackPaths, computeColour, computeLeafGeoms, createAcCompletionProvider, createDataListSearchAdapter, createElementIncrSearch, createElementListSearchAdapter, createEmptyLayout, createEmptyStack, createFuzzySearch, createIncrSearch, createPillRenderer, createSplit, createStack, createTreeListAdapter, createTreeListSearchAdapter, createVerticalListAdapter, distribute, extractStack, findNode, findParentOf, findStackOf, findStackPathByName, getActiveDragPanelId, getIcon, getIconEntry, getIconFont, getLeafNeighbours, hasIcon, iconBus, init, insertBeside, isDragging, isMultiMode, isSplit, isStack, keyForElement, listItemFadeKeyframes, listSearchStyles, monitorIndeterminate, moveLeafNodeToEdge, moveLeafStep, normalise, normalize, panelGapIndex, parseColourScaleAttr, parseLayout, pointerOverAnyHost, prefersReducedMotion, readBaseColour, registerFontIcon, registerIcon, registerListSearchCommands, removePanelFromStack, resolveCssVars, resolveGrid, resolveLeafPath, resolveTracks, serializeLayout, setActive, setIconFont, svgToDataUri, swapLeaves, tickledItemStyles, tickledStyles, timelineStyles, toSnapshot, walk, wrapRoot };
39528
40824
 
39529
40825
  //# sourceMappingURL=index.js.map