@ixfx/components 0.9.8 → 0.10.1

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 (57) hide show
  1. package/bundle/index.d.ts +807 -5
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +8928 -5526
  4. package/bundle/index.js.map +1 -1
  5. package/bundle/style.css +38 -85
  6. package/dist/crumbs.d.ts +1 -1
  7. package/dist/{data-grid-BUeZjnvj.d.ts → data-grid-BT1d_nlu.d.ts} +4 -3
  8. package/dist/{data-grid-BUeZjnvj.d.ts.map → data-grid-BT1d_nlu.d.ts.map} +1 -1
  9. package/dist/data-grid.d.ts +2 -2
  10. package/dist/data-grid.js +3 -1
  11. package/dist/data-grid.js.map +1 -1
  12. package/dist/{detail-list-CQguZg9h.d.ts → detail-list-BCaIN7nx.d.ts} +5 -4
  13. package/dist/detail-list-BCaIN7nx.d.ts.map +1 -0
  14. package/dist/detail-list.d.ts +3 -3
  15. package/dist/detail-list.js +3 -1
  16. package/dist/detail-list.js.map +1 -1
  17. package/dist/{grid-list-lTwg4lp7.d.ts → grid-list-CiwlNE4c.d.ts} +28 -4
  18. package/dist/{grid-list-lTwg4lp7.d.ts.map → grid-list-CiwlNE4c.d.ts.map} +1 -1
  19. package/dist/{grid-list-40blkeza.js → grid-list-DLfw5HI4.js} +68 -5
  20. package/dist/grid-list-DLfw5HI4.js.map +1 -0
  21. package/dist/grid-list.d.ts +2 -2
  22. package/dist/grid-list.js +1 -1
  23. package/dist/incr-search.d.ts +1 -1
  24. package/dist/{index--qnhBWZJ.d.ts → index-DPFrRhKL.d.ts} +2 -2
  25. package/dist/{index--qnhBWZJ.d.ts.map → index-DPFrRhKL.d.ts.map} +1 -1
  26. package/dist/index.d.ts +682 -11
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +2988 -277
  29. package/dist/index.js.map +1 -1
  30. package/dist/{list-native-drag-controller-B0hWdbtM.js → list-native-drag-controller-B9xO0XWn.js} +41 -8
  31. package/dist/list-native-drag-controller-B9xO0XWn.js.map +1 -0
  32. package/dist/{list-native-drag-controller-Cmpx7jnx.d.ts → list-native-drag-controller-BAuWAtyQ.d.ts} +3 -2
  33. package/dist/list-native-drag-controller-BAuWAtyQ.d.ts.map +1 -0
  34. package/dist/miller.d.ts +2 -2
  35. package/dist/style.css +38 -85
  36. package/dist/{tree-CVj7TIk2.js → tree-BuzWn18_.js} +4 -2
  37. package/dist/tree-BuzWn18_.js.map +1 -0
  38. package/dist/{tree-component-BW2iCFq9.d.ts → tree-component-DGmKBbpy.d.ts} +17 -2
  39. package/dist/tree-component-DGmKBbpy.d.ts.map +1 -0
  40. package/dist/tree.d.ts +4 -3
  41. package/dist/tree.d.ts.map +1 -1
  42. package/dist/tree.js +1 -1
  43. package/dist/{vertical-list-CHrXmmQ3.d.ts → vertical-list-BLvJ4-Zf.d.ts} +92 -4
  44. package/dist/vertical-list-BLvJ4-Zf.d.ts.map +1 -0
  45. package/dist/vertical-list.d.ts +2 -2
  46. package/dist/vertical-list.js +617 -28
  47. package/dist/vertical-list.js.map +1 -1
  48. package/docs-user/grid-list.md +14 -0
  49. package/docs-user/index.json +2 -2
  50. package/package.json +1 -1
  51. package/dist/detail-list-CQguZg9h.d.ts.map +0 -1
  52. package/dist/grid-list-40blkeza.js.map +0 -1
  53. package/dist/list-native-drag-controller-B0hWdbtM.js.map +0 -1
  54. package/dist/list-native-drag-controller-Cmpx7jnx.d.ts.map +0 -1
  55. package/dist/tree-CVj7TIk2.js.map +0 -1
  56. package/dist/tree-component-BW2iCFq9.d.ts.map +0 -1
  57. package/dist/vertical-list-CHrXmmQ3.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ import { i as ICON_CLOSE, n as ICON_CHECK, r as ICON_CHEVRON_DOWN, t as ICON_CAR
16
16
  import "./icons.js";
17
17
  import { t as prominenceStyles } from "./prominence-DlFShic3.js";
18
18
  import { n as buttonStyles, t as ButtonElement } from "./button-DsFbV4XD.js";
19
- import { n as captionForEntry, r as createDefaultListDragPreview, t as ListNativeDragController } from "./list-native-drag-controller-B0hWdbtM.js";
19
+ import { n as captionForEntry, r as createDefaultListDragPreview, t as ListNativeDragController } from "./list-native-drag-controller-B9xO0XWn.js";
20
20
  import { n as isMultiMode, r as ListDragSelectController, t as ListSelectionController } from "./list-selection-controller-SA4piSBi.js";
21
21
  import { a as prefersReducedMotion, i as listItemFadeKeyframes, n as animateListItemIn, o as startListItemOutAnimation, r as animateListItemOut, t as LIST_ITEM_MOTION_MS } from "./list-item-animation-BUoEKi5s.js";
22
22
  import "./interaction-BskmlA9t.js";
@@ -25,7 +25,7 @@ import { Checkbox } from "./checkbox.js";
25
25
  import { n as ColourPicker, t as ColourPickerPopup } from "./colour-picker-R73i0QTP.js";
26
26
  import { LabelledRangeInput } from "./labelled-range-input.js";
27
27
  import { t as collectDataResult } from "./data-provider-35TyWclX.js";
28
- import { a as TreeDataModel, c as pruneDraggedDescendants, i as TreeDropController, n as TreeListElement, o as applyTreeDrop, r as TreeBaseElement, s as dropTargetFor, t as TreeController } from "./tree-CVj7TIk2.js";
28
+ import { a as TreeDataModel, c as pruneDraggedDescendants, i as TreeDropController, n as TreeListElement, o as applyTreeDrop, r as TreeBaseElement, s as dropTargetFor, t as TreeController } from "./tree-BuzWn18_.js";
29
29
  import { t as scrollFadeStyles } from "./scroll-fade-CAs1EabA.js";
30
30
  import { NarrowedTextElement } from "./narrowed-text.js";
31
31
  import { CrumbNavigationElement } from "./crumbs.js";
@@ -40,7 +40,7 @@ import { TabListElement } from "./tabs.js";
40
40
  import { EditableLabel } from "./editable-label.js";
41
41
  import { n as EditableNumber, t as LabelledSliderInput } from "./labelled-slider-input-CF2RtzgG.js";
42
42
  import { t as FormElement } from "./form-CsXJX15p.js";
43
- import { t as GridListElement } from "./grid-list-40blkeza.js";
43
+ import { t as GridListElement } from "./grid-list-DLfw5HI4.js";
44
44
  import { PanelElement } from "./panel.js";
45
45
  import { GroupedItemListerElement } from "./grouped-item-lister.js";
46
46
  import { VerticalListElement } from "./vertical-list.js";
@@ -1429,20 +1429,37 @@ var CrumbPathController = class {
1429
1429
  addPaths(paths) {
1430
1430
  for (const path of paths) this.addPath(path);
1431
1431
  }
1432
+ addSegment(segment, path) {
1433
+ this.#pathed.add(segment, path);
1434
+ }
1435
+ addSegments(entries) {
1436
+ for (const { path, segment } of entries) this.addSegment(segment, path);
1437
+ }
1438
+ getIcon(cumulativePath) {
1439
+ const value = this.#pathed.getValue(cumulativePath);
1440
+ if (value === void 0) return;
1441
+ if (typeof value === `object` && value !== null && `value` in value) return value.value?.icon;
1442
+ return value.icon;
1443
+ }
1432
1444
  getPath() {
1433
1445
  return this.#element?.getPath() ?? ``;
1434
1446
  }
1435
1447
  setPath(path) {
1436
1448
  this.#element?.setPath(path);
1437
1449
  }
1450
+ #segmentIconLookup = (pathSegments) => {
1451
+ return this.getIcon(pathSegments.join(this.#separator));
1452
+ };
1438
1453
  connect(element) {
1439
1454
  this.#element = element;
1440
1455
  element.separator = this.#separator;
1441
1456
  element.siblingLookup = this.#siblingLookup.bind(this);
1457
+ if (!element.segmentIconLookup) element.segmentIconLookup = this.#segmentIconLookup;
1442
1458
  }
1443
1459
  disconnect() {
1444
1460
  if (this.#element) {
1445
1461
  this.#element.siblingLookup = void 0;
1462
+ if (this.#element.segmentIconLookup === this.#segmentIconLookup) this.#element.segmentIconLookup = void 0;
1446
1463
  this.#element = null;
1447
1464
  }
1448
1465
  }
@@ -1602,6 +1619,14 @@ let CrumbPathElement = class CrumbPathElement extends LitElement {
1602
1619
  if (isRoot) return rootSegments.slice(0, index + 1).join(this.separator);
1603
1620
  return [...rootSegments, ...segments.slice(0, index + 1)].join(this.separator);
1604
1621
  }
1622
+ #resolveSegmentIcon(fullPathSegments, index) {
1623
+ const direct = this.segmentIconLookup?.(fullPathSegments, index);
1624
+ if (typeof direct === `string` && direct.length > 0) return direct;
1625
+ const label = fullPathSegments[index];
1626
+ if (label === void 0) return;
1627
+ const parentKey = fullPathSegments.slice(0, index).join(this.separator);
1628
+ return (this._cachedSiblings.get(parentKey)?.find((s) => s.label === label))?.icon;
1629
+ }
1605
1630
  setPath(newPath) {
1606
1631
  this.#cleanupPending();
1607
1632
  if (this.pathRoot.length > 0 && !newPath.startsWith(this.pathRoot)) newPath = this.pathRoot;
@@ -1742,9 +1767,12 @@ let CrumbPathElement = class CrumbPathElement extends LitElement {
1742
1767
  #renderSegment(label, index, isLast) {
1743
1768
  const isOpen = this._openIdx === index;
1744
1769
  const cumPath = this.#getCumulativePath(index, false);
1770
+ const fullPathSegments = [...this.#getRootSegments(), ...this.#getSegments().slice(0, index + 1)];
1771
+ const icon = this.#resolveSegmentIcon(fullPathSegments, fullPathSegments.length - 1);
1745
1772
  return html`
1746
1773
  <div class="seg" data-cumulative=${cumPath} @click=${(e) => this.#onSegmentClick(index, e)}>
1747
1774
  <label title=${label} @click=${(e) => this.#onLabelClick(index, e)}>
1775
+ ${icon ? html`<ixfx-icon class="crumb-icon segment-icon" name=${icon}></ixfx-icon>` : nothing}
1748
1776
  ${label}
1749
1777
  </label>
1750
1778
  ${(!isLast || this.siblingLookup) && this._leafSegmentIndex !== index ? html`
@@ -1759,12 +1787,15 @@ let CrumbPathElement = class CrumbPathElement extends LitElement {
1759
1787
  `;
1760
1788
  }
1761
1789
  #renderRootSegment(label, index) {
1762
- const isLast = index === this.#getRootSegments().length - 1;
1790
+ const rootSegments = this.#getRootSegments();
1791
+ const isLast = index === rootSegments.length - 1;
1763
1792
  const isOpen = this._openIdx === index;
1764
1793
  const cumPath = this.#getCumulativePath(index, true);
1794
+ const fullPathSegments = rootSegments.slice(0, index + 1);
1795
+ const icon = this.#resolveSegmentIcon(fullPathSegments, index);
1765
1796
  return html`
1766
1797
  <div class="seg seg-root" data-cumulative=${cumPath} @click=${(e) => this.#onRootSegmentClick(index, e)}>
1767
- <label title=${label}>${label}</label>
1798
+ <label title=${label}>${icon ? html`<ixfx-icon class="crumb-icon segment-icon" name=${icon}></ixfx-icon>` : nothing}${label}</label>
1768
1799
  ${isLast && this.siblingLookup ? html`
1769
1800
  <div class="caret ${isOpen ? `open` : ``} ${this._confirmedLeafIndices.has(cumPath) ? `disabled` : ``}"
1770
1801
  style="anchor-name: ${this.#anchorName(cumPath)}"
@@ -1976,6 +2007,7 @@ let CrumbPathElement = class CrumbPathElement extends LitElement {
1976
2007
  label {
1977
2008
  display: flex;
1978
2009
  align-items: center;
2010
+ gap: var(--padding-s);
1979
2011
  white-space: nowrap;
1980
2012
  text-overflow: ellipsis;
1981
2013
  align-content: center;
@@ -2091,6 +2123,7 @@ __decorate([property({
2091
2123
  attribute: `path-root`
2092
2124
  })], CrumbPathElement.prototype, "pathRoot", void 0);
2093
2125
  __decorate([property({ type: Object })], CrumbPathElement.prototype, "siblingLookup", void 0);
2126
+ __decorate([property({ type: Object })], CrumbPathElement.prototype, "segmentIconLookup", void 0);
2094
2127
  __decorate([state()], CrumbPathElement.prototype, "_openIdx", void 0);
2095
2128
  __decorate([state()], CrumbPathElement.prototype, "_editing", void 0);
2096
2129
  __decorate([state()], CrumbPathElement.prototype, "_cachedSiblings", void 0);
@@ -3439,7 +3472,7 @@ TabListItemElement = __decorate([safeCustomElement(`ixfx-tab-list-item`)], TabLi
3439
3472
  //#endregion
3440
3473
  //#region src/dock/dock-panel.ts
3441
3474
  /** Pointer movement threshold before a titlebar press becomes a dock drag. */
3442
- const DRAG_THRESHOLD_PX$2 = 5;
3475
+ const DRAG_THRESHOLD_PX$3 = 5;
3443
3476
  let DockPanelElement = class DockPanelElement extends LitElement {
3444
3477
  constructor(..._args) {
3445
3478
  super(..._args);
@@ -3471,7 +3504,7 @@ let DockPanelElement = class DockPanelElement extends LitElement {
3471
3504
  this._onPointerMove = (event) => {
3472
3505
  if (this._dragPointerId === null || event.pointerId !== this._dragPointerId) return;
3473
3506
  if (!this._dragStarted) {
3474
- if (Math.hypot(event.clientX - this._dragStartX, event.clientY - this._dragStartY) < DRAG_THRESHOLD_PX$2) return;
3507
+ if (Math.hypot(event.clientX - this._dragStartX, event.clientY - this._dragStartY) < DRAG_THRESHOLD_PX$3) return;
3475
3508
  this._dragStarted = true;
3476
3509
  const pointerId = this._dragPointerId;
3477
3510
  this._teardownDragListeners();
@@ -3626,7 +3659,7 @@ DockPanelElement = __decorate([safeCustomElement(`ixfx-dock-panel`)], DockPanelE
3626
3659
  /** Fraction of the leaf rect (from each edge) that forms the split bands. */
3627
3660
  const BAND = .25;
3628
3661
  /** Pointer movement threshold before a press becomes a dock drag (matches tab-list). */
3629
- const DRAG_THRESHOLD_PX$1 = 5;
3662
+ const DRAG_THRESHOLD_PX$2 = 5;
3630
3663
  /** Tolerance (px) when matching a child gutter endpoint to a parent gutter line. */
3631
3664
  const JUNCTION_TOL_PX = 6;
3632
3665
  /** Junction points closer than this merge into one handle (a 4-way cross yields one per side). */
@@ -3742,7 +3775,7 @@ let DockHostElement = class DockHostElement extends LitElement {
3742
3775
  this._onTabDragMove = (event) => {
3743
3776
  const pending = this._tabDragPending;
3744
3777
  if (pending === null || event.pointerId !== pending.pointerId) return;
3745
- if (Math.hypot(event.clientX - pending.startX, event.clientY - pending.startY) < DRAG_THRESHOLD_PX$1) return;
3778
+ if (Math.hypot(event.clientX - pending.startX, event.clientY - pending.startY) < DRAG_THRESHOLD_PX$2) return;
3746
3779
  this._tabDragPending = null;
3747
3780
  this._teardownTabDragListeners();
3748
3781
  this._suppressNextClick();
@@ -5463,210 +5496,1201 @@ function createVerticalListAdapter(renderItem, options) {
5463
5496
  return adapter;
5464
5497
  }
5465
5498
  //#endregion
5466
- //#region src/notifier/pill-renderer.ts
5467
- const ROW_SPACING = 8;
5468
- const DEFAULT_ROW_HEIGHT = 40;
5469
- const STACK_OFFSET_STEP = 4;
5470
- const STACK_OPACITY_BASE = .7;
5471
- const STACK_OPACITY_DECAY = .15;
5472
- const STACK_OPACITY_MIN = .2;
5473
- function edgesOf(position) {
5474
- return {
5475
- vertical: position.startsWith(`top`) ? `top` : `bottom`,
5476
- horizontal: position.endsWith(`-left`) ? `left` : position.endsWith(`-right`) ? `right` : `middle`
5477
- };
5499
+ //#region src/icicle-tree/layout.ts
5500
+ /** Coerce a raw accessor result to a non-negative finite weight. */
5501
+ function normaliseValue(raw) {
5502
+ return typeof raw === `number` && Number.isFinite(raw) && raw > 0 ? raw : 0;
5478
5503
  }
5479
- function anchorStyles(position, safeArea) {
5480
- const { vertical, horizontal } = edgesOf(position);
5481
- const vMargin = `calc(var(--space-l, 12px) + ${(vertical === `top` ? safeArea.top : safeArea.bottom) ?? 0}px)`;
5482
- let css = `position: fixed; `;
5483
- css += vertical === `top` ? `top: ${vMargin}; bottom: auto; ` : `bottom: ${vMargin}; top: auto; `;
5484
- if (horizontal === `left`) {
5485
- const hMargin = `calc(var(--space-l, 12px) + ${safeArea.left ?? 0}px)`;
5486
- css += `left: ${hMargin}; right: auto;`;
5487
- } else if (horizontal === `right`) {
5488
- const hMargin = `calc(var(--space-l, 12px) + ${safeArea.right ?? 0}px)`;
5489
- css += `right: ${hMargin}; left: auto;`;
5490
- } else css += `left: 50%; right: auto;`;
5491
- return css;
5504
+ /** Depth-first search for the node with `key`. Returns `undefined` when absent. */
5505
+ function findIcicleNode(root, key) {
5506
+ if (!root) return void 0;
5507
+ if (root.item.key === key) return root;
5508
+ for (const child of root.children ?? []) {
5509
+ const hit = findIcicleNode(child, key);
5510
+ if (hit) return hit;
5511
+ }
5492
5512
  }
5493
- /** Entry-animation starting transform: pills slide in from the edge they're anchored to. */
5494
- function entryTransform(edges) {
5495
- const dy = edges.vertical === `top` ? -20 : 20;
5496
- if (edges.horizontal === `middle`) return `translateX(-50%) translate(0, ${dy}px)`;
5497
- return `translate(${edges.horizontal === `left` ? -20 : 20}px, ${dy}px)`;
5513
+ /**
5514
+ * Chain of nodes from `root` down to (and including) the node with `key`.
5515
+ * Returns `undefined` when the key is absent. The first entry is always `root`.
5516
+ */
5517
+ function findIciclePath(root, key) {
5518
+ if (!root) return void 0;
5519
+ if (root.item.key === key) return [root];
5520
+ for (const child of root.children ?? []) {
5521
+ const hit = findIciclePath(child, key);
5522
+ if (hit) return [root, ...hit];
5523
+ }
5498
5524
  }
5499
- function restingTransform(edges, dx, dy) {
5500
- if (edges.horizontal === `middle`) return `translateX(-50%) translate(${dx}px, ${dy}px)`;
5501
- return `translate(${dx}px, ${dy}px)`;
5525
+ /**
5526
+ * Compute the visible rectangles for an icicle view.
5527
+ *
5528
+ * Bands start at the zoom node's **children** (the zoom node itself is shown in
5529
+ * the breadcrumb strip, never as a rect — matching `TreeComponent.getNodes()`,
5530
+ * where the root is never listed). Each band spans 0–1; a rect's `x`/`width`
5531
+ * are fractions of its band, proportional to `valueAccessor` shares among
5532
+ * siblings. Siblings whose weights all normalise to `0` split the band equally.
5533
+ *
5534
+ * @param root True tree root. `undefined` (or a root without children) yields `[]`.
5535
+ * @param options `valueAccessor` (default equal weights), `maxDepth` (band count,
5536
+ * default unlimited), `zoomKey` (visual root, default true root; unknown keys
5537
+ * fall back to the true root).
5538
+ */
5539
+ function computeIcicleLayout(root, options = {}) {
5540
+ if (!root?.children?.length) return [];
5541
+ const { maxDepth = Number.POSITIVE_INFINITY, valueAccessor = () => 1, zoomKey, hideLeaves = false } = options;
5542
+ if (maxDepth <= 0) return [];
5543
+ const zoomNode = zoomKey === void 0 ? root : findIcicleNode(root, zoomKey) ?? root;
5544
+ const zoomPath = findIciclePath(root, zoomNode.item.key);
5545
+ const zoomDepth = zoomPath ? zoomPath.length - 2 : -1;
5546
+ const siblings = zoomNode.children ?? [];
5547
+ if (siblings.length === 0) return [];
5548
+ const isLeaf = (node) => node.item.isLeaf === true || node.children !== void 0 && node.children.length === 0;
5549
+ const rects = [];
5550
+ const layoutLevel = (nodes, depth, relativeDepth, offset, span, parentKey) => {
5551
+ const visible = hideLeaves ? nodes.filter((n) => !isLeaf(n)) : nodes;
5552
+ if (visible.length === 0 || relativeDepth >= maxDepth) return;
5553
+ const weights = visible.map((n) => normaliseValue(valueAccessor(n.item)));
5554
+ const total = weights.reduce((a, b) => a + b, 0);
5555
+ const setSize = visible.length;
5556
+ let cursor = offset;
5557
+ visible.forEach((node, index) => {
5558
+ const width = span * (total > 0 ? weights[index] / total : 1 / setSize);
5559
+ const children = node.children ?? [];
5560
+ rects.push({
5561
+ node,
5562
+ depth,
5563
+ relativeDepth,
5564
+ x: cursor,
5565
+ width,
5566
+ parentKey,
5567
+ index,
5568
+ setSize,
5569
+ hasChildren: children.length > 0 || node.children === void 0 && node.item.isLeaf !== true
5570
+ });
5571
+ if (children.length > 0) layoutLevel(children, depth + 1, relativeDepth + 1, cursor, width, node.item.key);
5572
+ cursor += width;
5573
+ });
5574
+ };
5575
+ layoutLevel(siblings, zoomDepth + 1, 0, 0, 1, zoomNode.item.key);
5576
+ return rects;
5502
5577
  }
5503
- function createPillRenderer(options = {}) {
5504
- const position = options.position ?? `top-right`;
5505
- const queueDisplay = options.queueDisplay ?? `list`;
5506
- const safeArea = options.safeArea ?? {};
5507
- const edges = edgesOf(position);
5508
- let container = null;
5509
- let hooks = null;
5510
- const pills = /* @__PURE__ */ new Map();
5511
- let rowHeight = DEFAULT_ROW_HEIGHT;
5512
- let measured = false;
5513
- function ensureContainer() {
5514
- if (container) return container;
5515
- container = document.createElement(`div`);
5516
- container.className = `ixfx-notification-container`;
5517
- container.setAttribute(`role`, `region`);
5518
- container.setAttribute(`aria-label`, `Notifications`);
5519
- document.body.appendChild(container);
5520
- return container;
5578
+ //#endregion
5579
+ //#region src/icicle-tree/icicletree-list.ts
5580
+ let IcicleTreeListElement = class IcicleTreeListElement extends LitElement {
5581
+ constructor(..._args) {
5582
+ super(..._args);
5583
+ this.exclusivity = `none`;
5584
+ this.selectionFilter = `all`;
5585
+ this.interactionMode = `standard`;
5586
+ this.orientation = `horizontal`;
5587
+ this.maxDepth = 4;
5588
+ this.minLabelWidth = 40;
5589
+ this.minVerticalLabelWidth = 16;
5590
+ this.hideLeaves = false;
5591
+ this.valueAccessor = () => 1;
5592
+ this._selectedNodes = /* @__PURE__ */ new Set();
5593
+ this.disableKeyboardNavigation = false;
5594
+ this.registry = void 0;
5595
+ this._zoomKey = void 0;
5596
+ this.search = new ListSearchController(this, { adapter: createTreeListSearchAdapter(this, {
5597
+ labelSelector: `.icicle-label`,
5598
+ highlightKey: `incr-search-highlight`
5599
+ }) });
5600
+ this.tickled = new TickledController(this, {
5601
+ getItems: () => this.#rects().map((r) => ({
5602
+ id: r.node.item.key,
5603
+ enabled: true
5604
+ })),
5605
+ onStateChange: () => {
5606
+ this.setAttribute(`data-input-mode`, this.tickled.inputMode);
5607
+ },
5608
+ onTickle: (id) => {
5609
+ if (id === void 0) return;
5610
+ const rect = this.#rects().find((r) => r.node.item.key === id);
5611
+ if (!rect) return;
5612
+ this.dispatchEvent(new CustomEvent(`tickle`, {
5613
+ detail: {
5614
+ node: rect.node,
5615
+ depth: rect.depth
5616
+ },
5617
+ bubbles: true,
5618
+ composed: true
5619
+ }));
5620
+ }
5621
+ });
5622
+ this.#pendingKeys = /* @__PURE__ */ new Set();
5623
+ this.#aborters = /* @__PURE__ */ new Map();
5521
5624
  }
5522
- function updateAttributes(el, notification) {
5523
- el.setAttribute(`notification-type`, notification.type);
5524
- el.setAttribute(`message`, notification.message);
5525
- if (notification.icon) el.setAttribute(`icon`, notification.icon);
5526
- else el.removeAttribute(`icon`);
5625
+ static {
5626
+ this.shadowRootOptions = {
5627
+ ...LitElement.shadowRootOptions,
5628
+ delegatesFocus: true
5629
+ };
5527
5630
  }
5528
- function createPillElement(notification) {
5529
- const el = document.createElement(`ixfx-notification-pill`);
5530
- el.setAttribute(`notification-id`, notification.id);
5531
- el.style.cssText = anchorStyles(position, safeArea);
5532
- updateAttributes(el, notification);
5533
- el.style.opacity = `0`;
5534
- el.style.transform = entryTransform(edges);
5535
- el.addEventListener(`pill-tap`, (e) => {
5536
- const customEvent = e;
5537
- hooks?.onTap(customEvent.detail.id);
5538
- });
5539
- return el;
5631
+ get selectedNodes() {
5632
+ return this._selectedNodes;
5540
5633
  }
5541
- function measureRowHeight(el) {
5542
- if (measured) return;
5543
- requestAnimationFrame(() => {
5544
- const height = el.getBoundingClientRect().height;
5545
- if (height > 0) {
5546
- rowHeight = height;
5547
- measured = true;
5548
- }
5634
+ /** Current zoom root. `undefined` when showing the full tree. Pure view-state — `root` is never mutated. */
5635
+ get focusedNode() {
5636
+ if (this._zoomKey === void 0) return void 0;
5637
+ return findIcicleNode(this.#effectiveRoot, this._zoomKey);
5638
+ }
5639
+ #modelUnsubscribe;
5640
+ #pendingKeys;
5641
+ #aborters;
5642
+ #anchorIndex;
5643
+ #ownedRegistry;
5644
+ #unregisterSearchCommands;
5645
+ #resizeObserver;
5646
+ get #effectiveRoot() {
5647
+ return this.model?.root ?? this.root;
5648
+ }
5649
+ #rects() {
5650
+ return computeIcicleLayout(this.#effectiveRoot, {
5651
+ valueAccessor: this.valueAccessor,
5652
+ maxDepth: this.maxDepth,
5653
+ zoomKey: this._zoomKey,
5654
+ hideLeaves: this.hideLeaves
5549
5655
  });
5550
5656
  }
5551
- /** Positions every currently-visible pill for its rank in `order` (index 0 = front/oldest). */
5552
- function applyLayout(order) {
5553
- const total = order.length;
5554
- order.forEach((notification, index) => {
5555
- const entry = pills.get(notification.id);
5556
- if (!entry) return;
5557
- const el = entry.element;
5558
- let dx = 0;
5559
- let dy = 0;
5560
- let opacity = 1;
5561
- let pointerEvents = `auto`;
5562
- let filter = `none`;
5563
- if (queueDisplay === `list`) {
5564
- const step = index * (rowHeight + ROW_SPACING);
5565
- dy = edges.vertical === `top` ? step : -step;
5566
- } else {
5567
- const step = index * STACK_OFFSET_STEP;
5568
- dy = edges.vertical === `top` ? step : -step;
5569
- dx = edges.horizontal === `left` ? step : edges.horizontal === `right` ? -step : 0;
5570
- if (index > 0) {
5571
- const hintIndex = index - 1;
5572
- opacity = Math.max(STACK_OPACITY_MIN, STACK_OPACITY_BASE - hintIndex * STACK_OPACITY_DECAY);
5573
- pointerEvents = `none`;
5574
- filter = `saturate(0.6)`;
5575
- }
5576
- }
5577
- el.style.transition = `opacity 0.2s ease-out, transform 0.2s ease-out`;
5578
- el.style.opacity = `${opacity}`;
5579
- el.style.zIndex = `${total - index}`;
5580
- el.style.pointerEvents = pointerEvents;
5581
- el.style.filter = filter;
5582
- el.style.transform = restingTransform(edges, dx, dy);
5657
+ connectedCallback() {
5658
+ super.connectedCallback();
5659
+ this.setAttribute(`data-input-mode`, `mouse`);
5660
+ const registry = this.#ownedRegistry = createCommandRegistry();
5661
+ registry.register({
5662
+ id: `select-all`,
5663
+ label: `Select All`,
5664
+ description: `Select all visible items`,
5665
+ execute: () => this.selectAll()
5583
5666
  });
5667
+ registry.register({
5668
+ id: `deselect-all`,
5669
+ label: `Deselect All`,
5670
+ description: `Clear all selection`,
5671
+ execute: () => this.deselectAll()
5672
+ });
5673
+ this.registry = registry;
5674
+ this.#syncSearchCommands();
5675
+ if (typeof ResizeObserver !== `undefined`) {
5676
+ this.#resizeObserver = new ResizeObserver(() => this.requestUpdate());
5677
+ this.#resizeObserver.observe(this);
5678
+ }
5584
5679
  }
5585
- function dismiss(id) {
5586
- const entry = pills.get(id);
5587
- if (!entry) return;
5588
- const el = entry.element;
5589
- el.setAttribute(`dismissing`, `true`);
5590
- const cleanup = () => {
5591
- el.removeEventListener(`transitionend`, cleanup);
5592
- el.remove();
5593
- pills.delete(id);
5594
- if (container && pills.size === 0) {
5595
- container.remove();
5596
- container = null;
5597
- measured = false;
5598
- rowHeight = DEFAULT_ROW_HEIGHT;
5680
+ disconnectedCallback() {
5681
+ super.disconnectedCallback();
5682
+ this.#modelUnsubscribe?.();
5683
+ this.#modelUnsubscribe = void 0;
5684
+ for (const aborter of this.#aborters.values()) aborter.abort();
5685
+ this.#aborters.clear();
5686
+ this.#pendingKeys.clear();
5687
+ this.#resizeObserver?.disconnect();
5688
+ this.#resizeObserver = void 0;
5689
+ if (this.#ownedRegistry) {
5690
+ this.#ownedRegistry.unregister(`select-all`);
5691
+ this.#ownedRegistry.unregister(`deselect-all`);
5692
+ this.#ownedRegistry = void 0;
5693
+ this.registry = void 0;
5694
+ }
5695
+ }
5696
+ #effectiveSearchModes() {
5697
+ const raw = this.searchModes?.trim();
5698
+ if (raw === void 0) return [...LIST_SEARCH_MODES];
5699
+ const requested = new Set(raw.split(/\s+/));
5700
+ return LIST_SEARCH_MODES.filter((m) => requested.has(m));
5701
+ }
5702
+ #syncSearchCommands() {
5703
+ if (!this.#ownedRegistry) return;
5704
+ this.search.setModes(this.#effectiveSearchModes());
5705
+ this.#unregisterSearchCommands?.();
5706
+ this.#unregisterSearchCommands = registerListSearchCommands(this.#ownedRegistry, this.search);
5707
+ }
5708
+ updated(changedProps) {
5709
+ if (changedProps.has(`root`) && this.root) this.#resetViewState();
5710
+ if (changedProps.has(`model`)) {
5711
+ this.#modelUnsubscribe?.();
5712
+ this.#modelUnsubscribe = void 0;
5713
+ if (this.model) {
5714
+ this.#modelUnsubscribe = this.model._subscribe((event) => {
5715
+ if (event.type === `root`) {
5716
+ this.#resetViewState();
5717
+ this.requestUpdate();
5718
+ } else if (event.type === `invalidate`) this.requestUpdate();
5719
+ });
5720
+ this.#resetViewState();
5599
5721
  }
5722
+ }
5723
+ if (changedProps.has(`interactionMode`)) {
5724
+ this.#anchorIndex = void 0;
5725
+ this.clearSelection();
5726
+ }
5727
+ if (changedProps.has(`searchModes`)) this.#syncSearchCommands();
5728
+ if (this._zoomKey !== void 0 && !findIcicleNode(this.#effectiveRoot, this._zoomKey)) this._zoomKey = void 0;
5729
+ this.#ensureVisibleChildren();
5730
+ this.#syncTooltip();
5731
+ }
5732
+ /** The host's own (viewport) extent — width in horizontal orientation, height in vertical. */
5733
+ #hostExtent() {
5734
+ return (this.orientation === `vertical` ? this.clientHeight : this.clientWidth) || 0;
5735
+ }
5736
+ /**
5737
+ * Whether `rect`'s label should be rotated or hidden for lack of space, per
5738
+ * {@link minLabelWidth} / {@link minVerticalLabelWidth}. `orientation="vertical"`
5739
+ * rects never rotate — hides outright at `minLabelWidth` instead, same as before.
5740
+ */
5741
+ #labelMode(rect) {
5742
+ const hostExtent = this.#hostExtent();
5743
+ if (hostExtent === 0) return {
5744
+ hidden: false,
5745
+ rotated: false
5746
+ };
5747
+ const vertical = this.orientation === `vertical`;
5748
+ const pixelWidth = rect.width * hostExtent;
5749
+ if (vertical) return {
5750
+ hidden: pixelWidth < this.minLabelWidth,
5751
+ rotated: false
5752
+ };
5753
+ if (pixelWidth < this.minVerticalLabelWidth) return {
5754
+ hidden: true,
5755
+ rotated: false
5756
+ };
5757
+ return {
5758
+ hidden: false,
5759
+ rotated: pixelWidth < this.minLabelWidth
5600
5760
  };
5601
- el.addEventListener(`transitionend`, cleanup);
5602
- setTimeout(cleanup, 300);
5603
5761
  }
5604
- function setNotifications(notifications, _queueSize) {
5605
- const cont = ensureContainer();
5606
- const idsToKeep = new Set(notifications.map((n) => n.id));
5607
- for (const notification of notifications) {
5608
- const existing = pills.get(notification.id);
5609
- if (existing) {
5610
- existing.notification = notification;
5611
- updateAttributes(existing.element, notification);
5612
- continue;
5613
- }
5614
- const el = createPillElement(notification);
5615
- pills.set(notification.id, {
5616
- element: el,
5617
- notification
5618
- });
5619
- cont.appendChild(el);
5620
- measureRowHeight(el);
5762
+ /** Anchor the shared tooltip popover to the tickled rect, if its label needs one. */
5763
+ #syncTooltip() {
5764
+ const popover = this._tooltipEl;
5765
+ if (!popover?.popover) return;
5766
+ const key = this.tickled.tickledId;
5767
+ const rect = key === void 0 ? void 0 : this.#rects().find((r) => r.node.item.key === key);
5768
+ const mode = rect ? this.#labelMode(rect) : void 0;
5769
+ const isOpen = popover.matches(`:popover-open`);
5770
+ if (rect && mode && (mode.hidden || mode.rotated)) {
5771
+ popover.textContent = rect.node.item.label;
5772
+ if (!isOpen) try {
5773
+ popover.showPopover();
5774
+ } catch {}
5775
+ } else if (isOpen) try {
5776
+ popover.hidePopover();
5777
+ } catch {}
5778
+ }
5779
+ #resetViewState() {
5780
+ this.#clearSelectionInternal();
5781
+ this.tickled.clearTickled();
5782
+ this._zoomKey = void 0;
5783
+ this.#anchorIndex = void 0;
5784
+ }
5785
+ #ensureVisibleChildren() {
5786
+ if (!this.model && !this.loadChildren) return;
5787
+ for (const rect of this.#rects()) {
5788
+ const { node } = rect;
5789
+ if (node.children !== void 0 || this.#pendingKeys.has(node.item.key)) continue;
5790
+ this.#loadChildren(node, rect.depth);
5621
5791
  }
5622
- for (const [id] of pills) if (!idsToKeep.has(id)) dismiss(id);
5623
- requestAnimationFrame(() => applyLayout(notifications));
5624
5792
  }
5625
- return {
5626
- attach(newHooks) {
5627
- hooks = newHooks;
5628
- },
5629
- detach() {
5630
- for (const [id] of pills) dismiss(id);
5631
- if (container) {
5632
- container.remove();
5633
- container = null;
5634
- }
5635
- },
5636
- setNotifications
5637
- };
5638
- }
5639
- //#endregion
5640
- //#region src/notifier/manager.ts
5641
- const options = {
5642
- renderer: void 0,
5643
- timeout: 4e3,
5644
- timeoutError: 8e3,
5645
- queue: true,
5646
- queueSize: 3,
5647
- tapBehaviour: `dismiss`,
5648
- tapHandlerDefault: void 0
5649
- };
5650
- /** Rank-ordered, index 0 = oldest/first (next to be evicted or timed out). Length never exceeds queueSize (or 1 when queuing is disabled). */
5651
- const visible = [];
5652
- let renderer = null;
5653
- let defaultRenderer = null;
5654
- function getDefaultRenderer() {
5655
- defaultRenderer ??= createPillRenderer({ position: `top-right` });
5656
- return defaultRenderer;
5657
- }
5658
- function getRenderer() {
5659
- return renderer ?? getDefaultRenderer();
5660
- }
5661
- function syncRenderer() {
5662
- getRenderer().setNotifications([...visible], options.queueSize);
5663
- }
5664
- function clearTimer(managed) {
5665
- if (managed.timer !== null) {
5666
- clearTimeout(managed.timer);
5667
- managed.timer = null;
5793
+ async #loadChildren(node, depth) {
5794
+ const key = node.item.key;
5795
+ this.#pendingKeys.add(key);
5796
+ const controller = new AbortController();
5797
+ this.#aborters.set(key, controller);
5798
+ this.requestUpdate();
5799
+ try {
5800
+ let children;
5801
+ if (this.model) children = await this.model.getChildren(node, depth, controller.signal);
5802
+ else children = [...await collectDataResult(this.loadChildren({
5803
+ node,
5804
+ depth
5805
+ }, controller.signal), controller.signal)];
5806
+ if (controller.signal.aborted) return;
5807
+ node.children = children;
5808
+ } catch {
5809
+ if (controller.signal.aborted) return;
5810
+ node.children = [];
5811
+ } finally {
5812
+ this.#pendingKeys.delete(key);
5813
+ this.#aborters.delete(key);
5814
+ if (!controller.signal.aborted) this.requestUpdate();
5815
+ }
5668
5816
  }
5669
- }
5817
+ #canSelect(node) {
5818
+ if (this.selectionFilter === `none`) return false;
5819
+ if (this.selectionFilter === `leaf`) return node.item.isLeaf === true;
5820
+ if (this.selectionFilter === `branch`) return node.item.isLeaf === false || node.children !== void 0 && node.children.length > 0;
5821
+ return true;
5822
+ }
5823
+ #clearSelectionInternal() {
5824
+ this._selectedNodes.clear();
5825
+ this.selectedNode = void 0;
5826
+ }
5827
+ #dispatchSelect(previous) {
5828
+ this.dispatchEvent(new CustomEvent(`select`, {
5829
+ detail: {
5830
+ selected: new Set(this._selectedNodes),
5831
+ previous
5832
+ },
5833
+ bubbles: true,
5834
+ composed: true
5835
+ }));
5836
+ }
5837
+ select(node) {
5838
+ if (!this.#canSelect(node)) return;
5839
+ const prev = new Set(this._selectedNodes);
5840
+ this._selectedNodes.clear();
5841
+ this._selectedNodes.add(node);
5842
+ this.selectedNode = node;
5843
+ this.#dispatchSelect(prev);
5844
+ this.requestUpdate();
5845
+ }
5846
+ deselect(node) {
5847
+ if (!this._selectedNodes.has(node)) return;
5848
+ const prev = new Set(this._selectedNodes);
5849
+ this._selectedNodes.delete(node);
5850
+ if (this.selectedNode === node) this.selectedNode = [...this._selectedNodes].at(-1);
5851
+ this.#dispatchSelect(prev);
5852
+ this.requestUpdate();
5853
+ }
5854
+ clearSelection() {
5855
+ if (this._selectedNodes.size === 0) return;
5856
+ const prev = new Set(this._selectedNodes);
5857
+ this.#clearSelectionInternal();
5858
+ this.#dispatchSelect(prev);
5859
+ this.requestUpdate();
5860
+ }
5861
+ selectAll() {
5862
+ const prev = new Set(this._selectedNodes);
5863
+ for (const rect of this.#rects()) {
5864
+ if (!this.#canSelect(rect.node)) continue;
5865
+ this._selectedNodes.add(rect.node);
5866
+ }
5867
+ this.selectedNode = [...this._selectedNodes].at(-1);
5868
+ this.#dispatchSelect(prev);
5869
+ this.requestUpdate();
5870
+ }
5871
+ deselectAll() {
5872
+ const prev = new Set(this._selectedNodes);
5873
+ this.#clearSelectionInternal();
5874
+ this.#dispatchSelect(prev);
5875
+ this.requestUpdate();
5876
+ }
5877
+ /** Replace the selection with `nodes` (subject to `selectionFilter`) — search "select" mode. */
5878
+ selectMany(nodes) {
5879
+ const prev = new Set(this._selectedNodes);
5880
+ this._selectedNodes = new Set([...nodes].filter((n) => this.#canSelect(n)));
5881
+ this.selectedNode = [...this._selectedNodes].at(-1);
5882
+ this.#dispatchSelect(prev);
5883
+ this.requestUpdate();
5884
+ }
5885
+ /**
5886
+ * Drill down so `node` becomes the visual root (ancestors move to the
5887
+ * breadcrumb strip). Pure view-state — `root` is never mutated.
5888
+ * Fires `zoom` and the semantic `expand` event.
5889
+ */
5890
+ zoomTo(node) {
5891
+ if (this._zoomKey === node.item.key) return;
5892
+ this._zoomKey = node.item.key;
5893
+ this.tickled.clearTickled();
5894
+ const depth = findIciclePath(this.#effectiveRoot, node.item.key)?.length;
5895
+ const absoluteDepth = depth === void 0 ? -1 : depth - 2;
5896
+ this.dispatchEvent(new CustomEvent(`zoom`, {
5897
+ detail: {
5898
+ node,
5899
+ depth: absoluteDepth
5900
+ },
5901
+ bubbles: true,
5902
+ composed: true
5903
+ }));
5904
+ this.dispatchEvent(new CustomEvent(`expand`, {
5905
+ detail: {
5906
+ node,
5907
+ depth: absoluteDepth
5908
+ },
5909
+ bubbles: true,
5910
+ composed: true
5911
+ }));
5912
+ this.requestUpdate();
5913
+ }
5914
+ /**
5915
+ * Zoom back out to the full tree. Fires `zoom` (with `node: undefined`) and
5916
+ * the semantic `collapse` event for the previous zoom root.
5917
+ */
5918
+ zoomOut() {
5919
+ if (this._zoomKey === void 0) return;
5920
+ const previous = this.focusedNode;
5921
+ const path = this._zoomKey === void 0 ? void 0 : findIciclePath(this.#effectiveRoot, this._zoomKey);
5922
+ const absoluteDepth = path === void 0 ? -1 : path.length - 2;
5923
+ this._zoomKey = void 0;
5924
+ this.tickled.clearTickled();
5925
+ this.dispatchEvent(new CustomEvent(`zoom`, {
5926
+ detail: {
5927
+ node: void 0,
5928
+ depth: -1
5929
+ },
5930
+ bubbles: true,
5931
+ composed: true
5932
+ }));
5933
+ if (previous) this.dispatchEvent(new CustomEvent(`collapse`, {
5934
+ detail: {
5935
+ node: previous,
5936
+ depth: absoluteDepth
5937
+ },
5938
+ bubbles: true,
5939
+ composed: true
5940
+ }));
5941
+ this.requestUpdate();
5942
+ }
5943
+ #canZoom(node) {
5944
+ if (node.item.isLeaf === true) return false;
5945
+ if (node.children !== void 0) return node.children.length > 0;
5946
+ return this.model !== void 0 || this.loadChildren !== void 0;
5947
+ }
5948
+ /**
5949
+ * Zoom so the node at `keys` (path from the root's children) is visible,
5950
+ * then select it. Returns `true` if the path was found.
5951
+ */
5952
+ navigateTo(keys) {
5953
+ const root = this.#effectiveRoot;
5954
+ if (!root?.children) return false;
5955
+ let current = root;
5956
+ const path = [];
5957
+ for (const key of keys) {
5958
+ const found = current.children?.find((n) => n.item.key === key);
5959
+ if (!found) return false;
5960
+ path.push(found);
5961
+ current = found;
5962
+ }
5963
+ const target = path.at(-1);
5964
+ if (path.length > this.maxDepth) this.zoomTo(path[path.length - this.maxDepth - 1]);
5965
+ else if (this._zoomKey !== void 0) this.zoomOut();
5966
+ this.select(target);
5967
+ this.tickled.setTickled(target.item.key);
5968
+ this.requestUpdate();
5969
+ return true;
5970
+ }
5971
+ /**
5972
+ * Zoom + select the first node for which `predicate` returns `true`.
5973
+ * Returns `true` if a matching node was found.
5974
+ */
5975
+ navigateToNode(predicate) {
5976
+ const root = this.#effectiveRoot;
5977
+ if (!root?.children) return false;
5978
+ const keyPath = this.#findKeyPath(root.children, predicate);
5979
+ if (!keyPath) return false;
5980
+ return this.navigateTo(keyPath);
5981
+ }
5982
+ /**
5983
+ * Zoom so the node with `key` is visible and move the keyboard cursor to it,
5984
+ * without changing the selection. Used by incremental-search "navigate" mode.
5985
+ */
5986
+ revealKey(key) {
5987
+ const root = this.#effectiveRoot;
5988
+ if (!root?.children) return false;
5989
+ const path = this.#findKeyPath(root.children, (n) => n.item.key === key);
5990
+ if (!path) return false;
5991
+ if (path.length > this.maxDepth) {
5992
+ const ancestor = this.#resolveKeyPath(path.slice(0, path.length - this.maxDepth));
5993
+ if (ancestor) this.zoomTo(ancestor);
5994
+ }
5995
+ this.tickled.setTickled(key);
5996
+ this.requestUpdate();
5997
+ return true;
5998
+ }
5999
+ #resolveKeyPath(keys) {
6000
+ let current = this.#effectiveRoot;
6001
+ for (const key of keys) {
6002
+ const found = key === current?.item.key ? current : current?.children?.find((n) => n.item.key === key);
6003
+ if (!found) return void 0;
6004
+ current = found;
6005
+ }
6006
+ return current;
6007
+ }
6008
+ #findKeyPath(nodes, predicate, prefix = []) {
6009
+ for (const node of nodes) {
6010
+ const path = [...prefix, node.item.key];
6011
+ if (predicate(node)) return path;
6012
+ if (node.children?.length) {
6013
+ const hit = this.#findKeyPath(node.children, predicate, path);
6014
+ if (hit) return hit;
6015
+ }
6016
+ }
6017
+ }
6018
+ /**
6019
+ * All nodes currently visible as rectangles, in render (band-major) order,
6020
+ * with absolute depth (root's children are depth 0).
6021
+ */
6022
+ getNodes() {
6023
+ return this.#rects().map((r) => ({
6024
+ node: r.node,
6025
+ depth: r.depth
6026
+ }));
6027
+ }
6028
+ #matchesFilter(node, depth) {
6029
+ if (!this.filterPredicate) return true;
6030
+ if (this.filterPredicate(node, depth)) return true;
6031
+ return node.children?.some((child) => this.#matchesFilter(child, depth + 1)) ?? false;
6032
+ }
6033
+ #handleItemClick(rect, index, event) {
6034
+ this.dispatchEvent(new CustomEvent(`item-click`, {
6035
+ detail: {
6036
+ node: rect.node,
6037
+ depth: rect.depth
6038
+ },
6039
+ bubbles: true,
6040
+ composed: true
6041
+ }));
6042
+ if (this.interactionMode === `standard` && (event.ctrlKey || event.metaKey)) {
6043
+ if (this._selectedNodes.has(rect.node)) this.deselect(rect.node);
6044
+ else if (this.#canSelect(rect.node)) {
6045
+ const prev = new Set(this._selectedNodes);
6046
+ this._selectedNodes.add(rect.node);
6047
+ this.selectedNode = rect.node;
6048
+ this.#dispatchSelect(prev);
6049
+ this.requestUpdate();
6050
+ }
6051
+ this.#anchorIndex = index;
6052
+ } else if (this.interactionMode === `standard` && event.shiftKey && this.#anchorIndex !== void 0) {
6053
+ const rects = this.#rects();
6054
+ const from = Math.min(this.#anchorIndex, index);
6055
+ const to = Math.max(this.#anchorIndex, index);
6056
+ const prev = new Set(this._selectedNodes);
6057
+ this._selectedNodes = new Set(rects.slice(from, to + 1).map((r) => r.node).filter((n) => this.#canSelect(n)));
6058
+ this.selectedNode = [...this._selectedNodes].at(-1);
6059
+ this.#dispatchSelect(prev);
6060
+ this.requestUpdate();
6061
+ } else if (this.interactionMode !== `manual`) {
6062
+ this.select(rect.node);
6063
+ this.#anchorIndex = index;
6064
+ }
6065
+ this.tickled.setTickled(rect.node.item.key);
6066
+ if (this.#canZoom(rect.node)) this.zoomTo(rect.node);
6067
+ }
6068
+ #handleItemDblClick(rect) {
6069
+ this.dispatchEvent(new CustomEvent(`item-dblclick`, {
6070
+ detail: {
6071
+ node: rect.node,
6072
+ depth: rect.depth
6073
+ },
6074
+ bubbles: true,
6075
+ composed: true
6076
+ }));
6077
+ this.dispatchEvent(new CustomEvent(`activate`, {
6078
+ detail: {
6079
+ node: rect.node,
6080
+ depth: rect.depth
6081
+ },
6082
+ bubbles: true,
6083
+ composed: true
6084
+ }));
6085
+ }
6086
+ #handleKeyDown(event) {
6087
+ if (this.disableKeyboardNavigation) return;
6088
+ const rects = this.#rects();
6089
+ if (rects.length === 0) return;
6090
+ const tickledKey = this.tickled.tickledId;
6091
+ const currentIndex = tickledKey === void 0 ? -1 : rects.findIndex((r) => r.node.item.key === tickledKey);
6092
+ const current = currentIndex >= 0 ? rects[currentIndex] : void 0;
6093
+ const moveTo = (rect) => {
6094
+ if (!rect) return;
6095
+ event.preventDefault();
6096
+ this.tickled.setTickled(rect.node.item.key);
6097
+ if (this.interactionMode === `implicit`) this.select(rect.node);
6098
+ };
6099
+ switch (event.key) {
6100
+ case `ArrowLeft`:
6101
+ case `ArrowRight`: {
6102
+ event.preventDefault();
6103
+ const dir = event.key === `ArrowRight` ? 1 : -1;
6104
+ if (!current) {
6105
+ moveTo(rects[0]);
6106
+ return;
6107
+ }
6108
+ const siblings = rects.filter((r) => r.relativeDepth === current.relativeDepth && r.parentKey === current.parentKey);
6109
+ moveTo(siblings[siblings.findIndex((r) => r.node.item.key === current.node.item.key) + dir] ?? (dir > 0 ? siblings[0] : siblings.at(-1)));
6110
+ return;
6111
+ }
6112
+ case `ArrowUp`:
6113
+ if (!current) {
6114
+ moveTo(rects[0]);
6115
+ return;
6116
+ }
6117
+ moveTo((current.parentKey === void 0 ? void 0 : rects.find((r) => r.node.item.key === current.parentKey)) ?? rects[0]);
6118
+ return;
6119
+ case `ArrowDown`:
6120
+ if (!current) {
6121
+ moveTo(rects[0]);
6122
+ return;
6123
+ }
6124
+ moveTo(rects.find((r) => r.parentKey === current.node.item.key));
6125
+ return;
6126
+ case `Home`:
6127
+ moveTo(rects[0]);
6128
+ return;
6129
+ case `End`:
6130
+ moveTo(rects.at(-1));
6131
+ return;
6132
+ case `Enter`:
6133
+ if (!current) return;
6134
+ event.preventDefault();
6135
+ if (this.interactionMode !== `manual`) this.select(current.node);
6136
+ this.dispatchEvent(new CustomEvent(`activate`, {
6137
+ detail: {
6138
+ node: current.node,
6139
+ depth: current.depth
6140
+ },
6141
+ bubbles: true,
6142
+ composed: true
6143
+ }));
6144
+ if (this.#canZoom(current.node)) this.zoomTo(current.node);
6145
+ return;
6146
+ case `Escape`: if (this._zoomKey !== void 0) {
6147
+ event.preventDefault();
6148
+ this.zoomOut();
6149
+ }
6150
+ }
6151
+ }
6152
+ #renderBreadcrumb() {
6153
+ const zoomNode = this.focusedNode;
6154
+ if (zoomNode === void 0) return nothing;
6155
+ return html`
6156
+ <nav class="breadcrumb" part="breadcrumb" aria-label="Icicle zoom path">
6157
+ <ixfx-crumb-navigation
6158
+ with-up
6159
+ .root=${this.#effectiveRoot}
6160
+ .selectedNode=${zoomNode}
6161
+ @select=${this.#onCrumbSelect}
6162
+ @item-click=${(e) => e.stopPropagation()}
6163
+ @tickle=${(e) => e.stopPropagation()}
6164
+ @expand=${(e) => e.stopPropagation()}
6165
+ ></ixfx-crumb-navigation>
6166
+ </nav>
6167
+ `;
6168
+ }
6169
+ /**
6170
+ * Translate crumb-trail selection (crumb clicks + the up-button) into zoom
6171
+ * state. The event is stopped: crumb clicks must not bubble as our `select`
6172
+ * (the selection set is unchanged by them).
6173
+ */
6174
+ #onCrumbSelect(event) {
6175
+ event.stopPropagation();
6176
+ const node = [...event.detail.selected].at(0);
6177
+ if (!node) return;
6178
+ if (node.item.key === this.#effectiveRoot?.item.key) this.zoomOut();
6179
+ else this.zoomTo(node);
6180
+ }
6181
+ #renderRect(rect) {
6182
+ const { node } = rect;
6183
+ const selected = this._selectedNodes.has(node);
6184
+ const tickled = this.tickled.isTickled(node.item.key);
6185
+ const filtered = !this.#matchesFilter(node, rect.depth);
6186
+ const pending = this.#pendingKeys.has(node.item.key);
6187
+ const sizePct = `${(rect.width * 100).toFixed(4)}%`;
6188
+ const xPct = `${(rect.x * 100).toFixed(4)}%`;
6189
+ const vertical = this.orientation === `vertical`;
6190
+ const { hidden: hideLabel, rotated } = this.#labelMode(rect);
6191
+ const needsTooltip = tickled && (hideLabel || rotated);
6192
+ let sizeStyle = vertical ? `top:${xPct};height:calc(${sizePct} - var(--icicletree-rect-gap))` : `left:${xPct};width:calc(${sizePct} - var(--icicletree-rect-gap))`;
6193
+ if (needsTooltip) sizeStyle += `;anchor-name:--icicle-rect-anchor`;
6194
+ return html`
6195
+ <div
6196
+ class="item rect ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${filtered ? `filtered` : ``} ${pending ? `loading` : ``} ${rect.width <= 0 ? `empty` : ``} ${rotated ? `rotated` : ``}"
6197
+ data-key=${node.item.key}
6198
+ role="treeitem"
6199
+ tabindex="-1"
6200
+ aria-level=${rect.depth + 1}
6201
+ aria-selected=${selected}
6202
+ aria-setsize=${rect.setSize}
6203
+ aria-posinset=${rect.index + 1}
6204
+ aria-label=${node.item.label}
6205
+ part="rect"
6206
+ style=${sizeStyle}
6207
+ @click=${(e) => this.#handleItemClick(rect, this.#rects().findIndex((r) => r.node.item.key === node.item.key), e)}
6208
+ @dblclick=${() => this.#handleItemDblClick(rect)}
6209
+ @pointerenter=${() => this.tickled.handlePointerEnter(node.item.key)}
6210
+ >
6211
+ <span class="icicle-label ${hideLabel ? `hide-label` : ``} ${rotated ? `label-vertical` : ``}" part="label">${node.item.label}</span>
6212
+ </div>
6213
+ `;
6214
+ }
6215
+ render() {
6216
+ const rects = this.#rects();
6217
+ const bands = /* @__PURE__ */ new Map();
6218
+ for (const rect of rects) {
6219
+ const band = bands.get(rect.relativeDepth) ?? [];
6220
+ band.push(rect);
6221
+ bands.set(rect.relativeDepth, band);
6222
+ }
6223
+ const vertical = this.orientation === `vertical`;
6224
+ return html`
6225
+ <div
6226
+ class="icicle ${vertical ? `vertical` : `horizontal`}"
6227
+ role="tree"
6228
+ tabindex="0"
6229
+ data-input-mode=${this.tickled.inputMode}
6230
+ @keydown=${(e) => this.#handleKeyDown(e)}
6231
+ @pointerleave=${() => this.tickled.clearTickled()}
6232
+ >
6233
+ ${this.#renderBreadcrumb()}
6234
+ ${rects.length === 0 ? html`<div class="empty-state">No data</div>` : [...bands.entries()].map(([depth, band]) => html`
6235
+ <div
6236
+ class="band"
6237
+ role="presentation"
6238
+ data-depth=${depth}
6239
+ >
6240
+ ${repeat(band, (r) => r.node.item.key, (r) => this.#renderRect(r))}
6241
+ </div>
6242
+ `)}
6243
+ </div>
6244
+ <div id="icicle-tooltip" popover="manual" part="tooltip" class="icicle-tooltip"></div>
6245
+ ${this.search.renderField()}
6246
+ `;
6247
+ }
6248
+ static {
6249
+ this.styles = [
6250
+ themeFallbacks,
6251
+ tickledStyles,
6252
+ tickledItemStyles,
6253
+ listSearchStyles,
6254
+ css`
6255
+ :host {
6256
+ --icicletree-band-height: 44px;
6257
+ --icicletree-band-gap: 2px;
6258
+ --icicletree-rect-gap: 2px;
6259
+ --icicletree-rect-bg: var(--surface-4);
6260
+ --icicletree-rect-text: var(--surface-4-text);
6261
+ --icicletree-font-size: var(--text-s);
6262
+
6263
+ display: block;
6264
+ overflow: auto;
6265
+ }
6266
+
6267
+ .icicle {
6268
+ display: flex;
6269
+ gap: var(--icicletree-band-gap);
6270
+ outline: none;
6271
+ }
6272
+
6273
+ .icicle.horizontal {
6274
+ flex-direction: column;
6275
+ }
6276
+
6277
+ .icicle.vertical {
6278
+ flex-direction: row;
6279
+ min-height: 200px;
6280
+ }
6281
+
6282
+ .breadcrumb {
6283
+ display: flex;
6284
+ align-items: center;
6285
+ padding: var(--space-xs) var(--space-s);
6286
+ background: var(--surface-3);
6287
+ color: var(--surface-3-text);
6288
+ border: var(--border-subtle);
6289
+ border-radius: var(--radius-m);
6290
+ font-size: var(--text-s);
6291
+ flex-shrink: 0;
6292
+ }
6293
+
6294
+ .breadcrumb ixfx-crumb-navigation {
6295
+ flex: 1;
6296
+ min-width: 0;
6297
+ }
6298
+
6299
+ .band {
6300
+ position: relative;
6301
+ }
6302
+
6303
+ .icicle.horizontal .band {
6304
+ height: var(--icicletree-band-height);
6305
+ }
6306
+
6307
+ .icicle.vertical .band {
6308
+ flex: 1 1 0;
6309
+ min-width: 0;
6310
+ height: 100%;
6311
+ }
6312
+
6313
+ .rect {
6314
+ box-sizing: border-box;
6315
+ position: absolute;
6316
+ overflow: hidden;
6317
+ display: flex;
6318
+ align-items: center;
6319
+ padding: 0 var(--space-s);
6320
+ background: var(--icicletree-rect-bg);
6321
+ color: var(--icicletree-rect-text);
6322
+ font-size: var(--icicletree-font-size);
6323
+ cursor: pointer;
6324
+ transition:
6325
+ width var(--transition),
6326
+ height var(--transition),
6327
+ left var(--transition),
6328
+ top var(--transition),
6329
+ opacity var(--transition),
6330
+ background-color var(--transition);
6331
+ }
6332
+
6333
+ .icicle.horizontal .rect {
6334
+ top: 0;
6335
+ height: 100%;
6336
+ }
6337
+
6338
+ /*
6339
+ * A rotated label needs its full allotted width to be usable — the
6340
+ * default left/right padding (--space-s, ~4px a side) can consume
6341
+ * most or all of a rect sized down to minVerticalLabelWidth, leaving
6342
+ * the rotated text clipped to invisible. #labelMode's width estimate
6343
+ * assumes the whole rect width is usable, so drop the padding here to
6344
+ * match that assumption instead of also budgeting for it in the
6345
+ * threshold (which would make already-narrow rects need even more).
6346
+ */
6347
+ .icicle.horizontal .rect.rotated {
6348
+ padding: 0;
6349
+ }
6350
+
6351
+ .icicle.vertical .rect {
6352
+ left: 0;
6353
+ width: 100%;
6354
+ padding: var(--space-s) 0;
6355
+ justify-content: center;
6356
+ writing-mode: horizontal-tb;
6357
+ }
6358
+
6359
+ .rect.filtered {
6360
+ opacity: 0.3;
6361
+ filter: saturate(0.4);
6362
+ }
6363
+
6364
+ .rect.empty {
6365
+ padding: 0;
6366
+ border: none;
6367
+ }
6368
+
6369
+ .rect.loading {
6370
+ cursor: progress;
6371
+ opacity: 0.6;
6372
+ }
6373
+
6374
+ .icicle-label {
6375
+ overflow: hidden;
6376
+ text-overflow: ellipsis;
6377
+ white-space: nowrap;
6378
+ flex: 1;
6379
+ min-width: 0;
6380
+ }
6381
+
6382
+ .icicle-label.hide-label {
6383
+ visibility: hidden;
6384
+ }
6385
+
6386
+ .icicle-label.label-vertical {
6387
+ writing-mode: vertical-rl;
6388
+ text-orientation: sideways;
6389
+ flex: none;
6390
+ width: 100%;
6391
+ height: 100%;
6392
+ padding: var(--space-2xs, 2px) 0;
6393
+ box-sizing: border-box;
6394
+ display: flex;
6395
+ align-items: center;
6396
+ justify-content: center;
6397
+ }
6398
+
6399
+ /*
6400
+ * Instant tooltip for rects whose label is rotated or hidden — anchored
6401
+ * via CSS anchor positioning (anchor-name set on the tickled rect in
6402
+ * #renderRect) rather than a JS-computed position, and shown/hidden via
6403
+ * the Popover API rather than the browser's own (slow, un-stylable)
6404
+ * title tooltip.
6405
+ */
6406
+ .icicle-tooltip {
6407
+ position: fixed;
6408
+ position-anchor: --icicle-rect-anchor;
6409
+ position-area: bottom center;
6410
+ position-try-fallbacks: top center;
6411
+ margin: var(--space-xs, 4px) 0 0;
6412
+ padding: var(--space-2xs, 2px) var(--space-xs, 6px);
6413
+ background: var(--surface-5);
6414
+ color: var(--surface-5-text);
6415
+ border: var(--border-subtle);
6416
+ border-radius: var(--radius-s, 4px);
6417
+ font-size: var(--text-s);
6418
+ white-space: nowrap;
6419
+ pointer-events: none;
6420
+ inset: auto;
6421
+ }
6422
+
6423
+ .icicle-tooltip:popover-open {
6424
+ display: block;
6425
+ }
6426
+
6427
+ .empty-state {
6428
+ padding: var(--space-l);
6429
+ color: var(--surface-muted-text);
6430
+ font-size: var(--text-s);
6431
+ }
6432
+ `
6433
+ ];
6434
+ }
6435
+ };
6436
+ __decorate([property({ type: Object })], IcicleTreeListElement.prototype, "root", void 0);
6437
+ __decorate([property({ attribute: false })], IcicleTreeListElement.prototype, "model", void 0);
6438
+ __decorate([property({ attribute: false })], IcicleTreeListElement.prototype, "loadChildren", void 0);
6439
+ __decorate([property({
6440
+ reflect: true,
6441
+ attribute: `exclusivity`
6442
+ })], IcicleTreeListElement.prototype, "exclusivity", void 0);
6443
+ __decorate([property({
6444
+ reflect: true,
6445
+ attribute: `selection-filter`
6446
+ })], IcicleTreeListElement.prototype, "selectionFilter", void 0);
6447
+ __decorate([property({
6448
+ type: String,
6449
+ attribute: `interaction-mode`
6450
+ })], IcicleTreeListElement.prototype, "interactionMode", void 0);
6451
+ __decorate([property({
6452
+ type: String,
6453
+ reflect: true
6454
+ })], IcicleTreeListElement.prototype, "orientation", void 0);
6455
+ __decorate([property({
6456
+ type: Number,
6457
+ attribute: `max-depth`
6458
+ })], IcicleTreeListElement.prototype, "maxDepth", void 0);
6459
+ __decorate([property({
6460
+ type: Number,
6461
+ attribute: `min-label-width`
6462
+ })], IcicleTreeListElement.prototype, "minLabelWidth", void 0);
6463
+ __decorate([property({
6464
+ type: Number,
6465
+ attribute: `min-vertical-label-width`
6466
+ })], IcicleTreeListElement.prototype, "minVerticalLabelWidth", void 0);
6467
+ __decorate([property({
6468
+ type: Boolean,
6469
+ attribute: `hide-leaves`,
6470
+ reflect: true
6471
+ })], IcicleTreeListElement.prototype, "hideLeaves", void 0);
6472
+ __decorate([property({ attribute: false })], IcicleTreeListElement.prototype, "valueAccessor", void 0);
6473
+ __decorate([property({ attribute: false })], IcicleTreeListElement.prototype, "selectedNode", void 0);
6474
+ __decorate([state()], IcicleTreeListElement.prototype, "_selectedNodes", void 0);
6475
+ __decorate([property({ attribute: false })], IcicleTreeListElement.prototype, "filterPredicate", void 0);
6476
+ __decorate([property({
6477
+ type: Boolean,
6478
+ attribute: `disable-keyboard-navigation`
6479
+ })], IcicleTreeListElement.prototype, "disableKeyboardNavigation", void 0);
6480
+ __decorate([property({ attribute: false })], IcicleTreeListElement.prototype, "registry", void 0);
6481
+ __decorate([property({
6482
+ type: String,
6483
+ attribute: `search-modes`,
6484
+ reflect: true
6485
+ })], IcicleTreeListElement.prototype, "searchModes", void 0);
6486
+ __decorate([state()], IcicleTreeListElement.prototype, "_zoomKey", void 0);
6487
+ __decorate([query(`#icicle-tooltip`)], IcicleTreeListElement.prototype, "_tooltipEl", void 0);
6488
+ IcicleTreeListElement = __decorate([safeCustomElement(`ixfx-icicletree-list`)], IcicleTreeListElement);
6489
+ //#endregion
6490
+ //#region src/notifier/pill-renderer.ts
6491
+ const ROW_SPACING = 8;
6492
+ const DEFAULT_ROW_HEIGHT = 40;
6493
+ const STACK_OFFSET_STEP = 4;
6494
+ const STACK_OPACITY_BASE = .7;
6495
+ const STACK_OPACITY_DECAY = .15;
6496
+ const STACK_OPACITY_MIN = .2;
6497
+ function edgesOf(position) {
6498
+ return {
6499
+ vertical: position.startsWith(`top`) ? `top` : `bottom`,
6500
+ horizontal: position.endsWith(`-left`) ? `left` : position.endsWith(`-right`) ? `right` : `middle`
6501
+ };
6502
+ }
6503
+ function anchorStyles(position, safeArea) {
6504
+ const { vertical, horizontal } = edgesOf(position);
6505
+ const vMargin = `calc(var(--space-l, 12px) + ${(vertical === `top` ? safeArea.top : safeArea.bottom) ?? 0}px)`;
6506
+ let css = `position: fixed; `;
6507
+ css += vertical === `top` ? `top: ${vMargin}; bottom: auto; ` : `bottom: ${vMargin}; top: auto; `;
6508
+ if (horizontal === `left`) {
6509
+ const hMargin = `calc(var(--space-l, 12px) + ${safeArea.left ?? 0}px)`;
6510
+ css += `left: ${hMargin}; right: auto;`;
6511
+ } else if (horizontal === `right`) {
6512
+ const hMargin = `calc(var(--space-l, 12px) + ${safeArea.right ?? 0}px)`;
6513
+ css += `right: ${hMargin}; left: auto;`;
6514
+ } else css += `left: 50%; right: auto;`;
6515
+ return css;
6516
+ }
6517
+ /** Entry-animation starting transform: pills slide in from the edge they're anchored to. */
6518
+ function entryTransform(edges) {
6519
+ const dy = edges.vertical === `top` ? -20 : 20;
6520
+ if (edges.horizontal === `middle`) return `translateX(-50%) translate(0, ${dy}px)`;
6521
+ return `translate(${edges.horizontal === `left` ? -20 : 20}px, ${dy}px)`;
6522
+ }
6523
+ function restingTransform(edges, dx, dy) {
6524
+ if (edges.horizontal === `middle`) return `translateX(-50%) translate(${dx}px, ${dy}px)`;
6525
+ return `translate(${dx}px, ${dy}px)`;
6526
+ }
6527
+ function createPillRenderer(options = {}) {
6528
+ const position = options.position ?? `top-right`;
6529
+ const queueDisplay = options.queueDisplay ?? `list`;
6530
+ const safeArea = options.safeArea ?? {};
6531
+ const edges = edgesOf(position);
6532
+ let container = null;
6533
+ let hooks = null;
6534
+ const pills = /* @__PURE__ */ new Map();
6535
+ let rowHeight = DEFAULT_ROW_HEIGHT;
6536
+ let measured = false;
6537
+ function ensureContainer() {
6538
+ if (container) return container;
6539
+ container = document.createElement(`div`);
6540
+ container.className = `ixfx-notification-container`;
6541
+ container.setAttribute(`role`, `region`);
6542
+ container.setAttribute(`aria-label`, `Notifications`);
6543
+ document.body.appendChild(container);
6544
+ return container;
6545
+ }
6546
+ function updateAttributes(el, notification) {
6547
+ el.setAttribute(`notification-type`, notification.type);
6548
+ el.setAttribute(`message`, notification.message);
6549
+ if (notification.icon) el.setAttribute(`icon`, notification.icon);
6550
+ else el.removeAttribute(`icon`);
6551
+ }
6552
+ function createPillElement(notification) {
6553
+ const el = document.createElement(`ixfx-notification-pill`);
6554
+ el.setAttribute(`notification-id`, notification.id);
6555
+ el.style.cssText = anchorStyles(position, safeArea);
6556
+ updateAttributes(el, notification);
6557
+ el.style.opacity = `0`;
6558
+ el.style.transform = entryTransform(edges);
6559
+ el.addEventListener(`pill-tap`, (e) => {
6560
+ const customEvent = e;
6561
+ hooks?.onTap(customEvent.detail.id);
6562
+ });
6563
+ return el;
6564
+ }
6565
+ function measureRowHeight(el) {
6566
+ if (measured) return;
6567
+ requestAnimationFrame(() => {
6568
+ const height = el.getBoundingClientRect().height;
6569
+ if (height > 0) {
6570
+ rowHeight = height;
6571
+ measured = true;
6572
+ }
6573
+ });
6574
+ }
6575
+ /** Positions every currently-visible pill for its rank in `order` (index 0 = front/oldest). */
6576
+ function applyLayout(order) {
6577
+ const total = order.length;
6578
+ order.forEach((notification, index) => {
6579
+ const entry = pills.get(notification.id);
6580
+ if (!entry) return;
6581
+ const el = entry.element;
6582
+ let dx = 0;
6583
+ let dy = 0;
6584
+ let opacity = 1;
6585
+ let pointerEvents = `auto`;
6586
+ let filter = `none`;
6587
+ if (queueDisplay === `list`) {
6588
+ const step = index * (rowHeight + ROW_SPACING);
6589
+ dy = edges.vertical === `top` ? step : -step;
6590
+ } else {
6591
+ const step = index * STACK_OFFSET_STEP;
6592
+ dy = edges.vertical === `top` ? step : -step;
6593
+ dx = edges.horizontal === `left` ? step : edges.horizontal === `right` ? -step : 0;
6594
+ if (index > 0) {
6595
+ const hintIndex = index - 1;
6596
+ opacity = Math.max(STACK_OPACITY_MIN, STACK_OPACITY_BASE - hintIndex * STACK_OPACITY_DECAY);
6597
+ pointerEvents = `none`;
6598
+ filter = `saturate(0.6)`;
6599
+ }
6600
+ }
6601
+ el.style.transition = `opacity 0.2s ease-out, transform 0.2s ease-out`;
6602
+ el.style.opacity = `${opacity}`;
6603
+ el.style.zIndex = `${total - index}`;
6604
+ el.style.pointerEvents = pointerEvents;
6605
+ el.style.filter = filter;
6606
+ el.style.transform = restingTransform(edges, dx, dy);
6607
+ });
6608
+ }
6609
+ function dismiss(id) {
6610
+ const entry = pills.get(id);
6611
+ if (!entry) return;
6612
+ const el = entry.element;
6613
+ el.setAttribute(`dismissing`, `true`);
6614
+ const cleanup = () => {
6615
+ el.removeEventListener(`transitionend`, cleanup);
6616
+ el.remove();
6617
+ pills.delete(id);
6618
+ if (container && pills.size === 0) {
6619
+ container.remove();
6620
+ container = null;
6621
+ measured = false;
6622
+ rowHeight = DEFAULT_ROW_HEIGHT;
6623
+ }
6624
+ };
6625
+ el.addEventListener(`transitionend`, cleanup);
6626
+ setTimeout(cleanup, 300);
6627
+ }
6628
+ function setNotifications(notifications, _queueSize) {
6629
+ const cont = ensureContainer();
6630
+ const idsToKeep = new Set(notifications.map((n) => n.id));
6631
+ for (const notification of notifications) {
6632
+ const existing = pills.get(notification.id);
6633
+ if (existing) {
6634
+ existing.notification = notification;
6635
+ updateAttributes(existing.element, notification);
6636
+ continue;
6637
+ }
6638
+ const el = createPillElement(notification);
6639
+ pills.set(notification.id, {
6640
+ element: el,
6641
+ notification
6642
+ });
6643
+ cont.appendChild(el);
6644
+ measureRowHeight(el);
6645
+ }
6646
+ for (const [id] of pills) if (!idsToKeep.has(id)) dismiss(id);
6647
+ requestAnimationFrame(() => applyLayout(notifications));
6648
+ }
6649
+ return {
6650
+ attach(newHooks) {
6651
+ hooks = newHooks;
6652
+ },
6653
+ detach() {
6654
+ for (const [id] of pills) dismiss(id);
6655
+ if (container) {
6656
+ container.remove();
6657
+ container = null;
6658
+ }
6659
+ },
6660
+ setNotifications
6661
+ };
6662
+ }
6663
+ //#endregion
6664
+ //#region src/notifier/manager.ts
6665
+ const options = {
6666
+ renderer: void 0,
6667
+ timeout: 4e3,
6668
+ timeoutError: 8e3,
6669
+ queue: true,
6670
+ queueSize: 3,
6671
+ tapBehaviour: `dismiss`,
6672
+ tapHandlerDefault: void 0
6673
+ };
6674
+ /** Rank-ordered, index 0 = oldest/first (next to be evicted or timed out). Length never exceeds queueSize (or 1 when queuing is disabled). */
6675
+ const visible = [];
6676
+ let renderer = null;
6677
+ let defaultRenderer = null;
6678
+ function getDefaultRenderer() {
6679
+ defaultRenderer ??= createPillRenderer({ position: `top-right` });
6680
+ return defaultRenderer;
6681
+ }
6682
+ function getRenderer() {
6683
+ return renderer ?? getDefaultRenderer();
6684
+ }
6685
+ function syncRenderer() {
6686
+ getRenderer().setNotifications([...visible], options.queueSize);
6687
+ }
6688
+ function clearTimer(managed) {
6689
+ if (managed.timer !== null) {
6690
+ clearTimeout(managed.timer);
6691
+ managed.timer = null;
6692
+ }
6693
+ }
5670
6694
  /**
5671
6695
  * A message shown alone takes the full timeout. A message that joined a
5672
6696
  * non-empty queue at arrival time is permanently "wasQueued" and gets
@@ -5763,7 +6787,7 @@ function dismiss(id) {
5763
6787
  //#endregion
5764
6788
  //#region src/panel/panel-group.ts
5765
6789
  /** Pointer movement threshold (px) before a press becomes a drag. */
5766
- const DRAG_THRESHOLD_PX = 5;
6790
+ const DRAG_THRESHOLD_PX$1 = 5;
5767
6791
  /**
5768
6792
  * Pure helper: nearest gap index for a pointer position along one axis.
5769
6793
  * `edges` are the [start, end] of each sibling (excluding the drag source),
@@ -5872,7 +6896,7 @@ let PanelGroupElement = class PanelGroupElement extends LitElement {
5872
6896
  if (!state || event.pointerId !== state.pointerId) return;
5873
6897
  const distance = Math.hypot(event.clientX - state.startX, event.clientY - state.startY);
5874
6898
  if (!state.active) {
5875
- if (distance < DRAG_THRESHOLD_PX) return;
6899
+ if (distance < DRAG_THRESHOLD_PX$1) return;
5876
6900
  this._beginDrag(event);
5877
6901
  }
5878
6902
  state.ghost?.move(event.clientX, event.clientY);
@@ -7791,9 +8815,13 @@ let ToolbarGroupElement = class ToolbarGroupElement extends LitElement {
7791
8815
  super(..._args);
7792
8816
  this.label = ``;
7793
8817
  this.grow = false;
8818
+ this.reorderable = false;
7794
8819
  }
7795
8820
  static {
7796
- this.styles = [themeFallbacks, css`
8821
+ this.styles = [
8822
+ themeFallbacks,
8823
+ handleStyles,
8824
+ css`
7797
8825
  :host {
7798
8826
  display: flex;
7799
8827
  flex-direction: column;
@@ -7805,11 +8833,43 @@ let ToolbarGroupElement = class ToolbarGroupElement extends LitElement {
7805
8833
  border-radius: var(--radius-s);
7806
8834
  background: var(--surface-3);
7807
8835
  min-width: 0;
7808
- }
7809
- :host(:not([label])) {
7810
- border: none;
7811
- background: transparent;
7812
- padding: 0;
8836
+ }
8837
+ :host([reorderable]) {
8838
+ flex-direction: row;
8839
+ align-items: center;
8840
+ }
8841
+ :host(:not([label])) {
8842
+ border: none;
8843
+ background: transparent;
8844
+ padding: 0;
8845
+ }
8846
+ .grab-handle {
8847
+ display: none;
8848
+ align-items: center;
8849
+ justify-content: center;
8850
+ align-self: stretch;
8851
+ padding-inline: var(--space-2xs, 2px);
8852
+ cursor: grab;
8853
+ touch-action: none;
8854
+ flex: 0 0 auto;
8855
+ }
8856
+ :host([reorderable]) .grab-handle { display: inline-flex; }
8857
+ :host([reorderable]) .grab-handle .ixfx-handle {
8858
+ --ixfx-handle-length: 18px;
8859
+ }
8860
+ :host([drag-source]) .grab-handle { cursor: grabbing; }
8861
+ :host([drag-source]) .grab-handle .ixfx-handle {
8862
+ color: var(--accent);
8863
+ opacity: 1;
8864
+ }
8865
+ .body {
8866
+ display: flex;
8867
+ flex-direction: column;
8868
+ align-items: var(--toolbar-group-align-items, center);
8869
+ justify-content: var(--toolbar-group-justify-content, flex-start);
8870
+ gap: var(--toolbar-gap, var(--space-s));
8871
+ min-width: 0;
8872
+ flex: 1 1 auto;
7813
8873
  }
7814
8874
  .label-row {
7815
8875
  display: none;
@@ -7827,13 +8887,19 @@ let ToolbarGroupElement = class ToolbarGroupElement extends LitElement {
7827
8887
  .components { display: flex; }
7828
8888
  ::slotted(*) { flex: 0 0 auto; }
7829
8889
  ::slotted([grow]) { flex: 1 1 auto; }
7830
- `];
8890
+ `
8891
+ ];
7831
8892
  }
7832
8893
  render() {
7833
8894
  return html`
7834
- <div class="label-row">${this.label}</div>
7835
- <div class="components">
7836
- <slot></slot>
8895
+ <div class="grab-handle" part="grab-handle" title="Drag to reorder" aria-hidden="true">
8896
+ <span class="ixfx-handle" data-handle="dotted" data-shape="vertical"></span>
8897
+ </div>
8898
+ <div class="body">
8899
+ <div class="label-row">${this.label}</div>
8900
+ <div class="components">
8901
+ <slot></slot>
8902
+ </div>
7837
8903
  </div>
7838
8904
  `;
7839
8905
  }
@@ -7843,10 +8909,68 @@ __decorate([property({
7843
8909
  type: Boolean,
7844
8910
  reflect: true
7845
8911
  })], ToolbarGroupElement.prototype, "grow", void 0);
8912
+ __decorate([property({
8913
+ type: Boolean,
8914
+ reflect: true
8915
+ })], ToolbarGroupElement.prototype, "reorderable", void 0);
7846
8916
  ToolbarGroupElement = __decorate([safeCustomElement(`ixfx-toolbar-group`)], ToolbarGroupElement);
7847
8917
  //#endregion
7848
8918
  //#region src/toolbar/toolbar.ts
7849
8919
  var _ToolbarElement;
8920
+ /** Pointer movement threshold (px) before a press becomes a drag. */
8921
+ const DRAG_THRESHOLD_PX = 5;
8922
+ /**
8923
+ * Pure helper: nearest gap index for a pointer position along one axis.
8924
+ * `edges` are the [start, end] of each sibling (excluding the drag source),
8925
+ * in visual order. Returns 0..edges.length.
8926
+ */
8927
+ function toolbarGapIndex(edges, pos) {
8928
+ let bestGap = 0;
8929
+ let bestDist = Number.POSITIVE_INFINITY;
8930
+ for (let i = 0; i < edges.length; i++) {
8931
+ const [start, end] = edges[i];
8932
+ if (pos < (start + end) / 2) {
8933
+ const d = Math.abs(pos - start);
8934
+ if (d < bestDist) {
8935
+ bestDist = d;
8936
+ bestGap = i;
8937
+ }
8938
+ } else {
8939
+ const d = Math.abs(pos - end);
8940
+ if (d < bestDist) {
8941
+ bestDist = d;
8942
+ bestGap = i + 1;
8943
+ }
8944
+ }
8945
+ }
8946
+ return bestGap;
8947
+ }
8948
+ let toolbarGhostStylesInjected = false;
8949
+ /**
8950
+ * Global ghost styling so the drag preview matches toolbar-group chrome.
8951
+ * Injected once; the ghost lives outside shadow DOM so component styles
8952
+ * cannot reach it.
8953
+ */
8954
+ function injectToolbarGhostStyles() {
8955
+ if (toolbarGhostStylesInjected) return;
8956
+ toolbarGhostStylesInjected = true;
8957
+ const style = document.createElement(`style`);
8958
+ style.textContent = `
8959
+ .ixfx-drag-ghost.ixfx-toolbar-group-ghost {
8960
+ padding: var(--space-s, 8px) var(--space-m, 16px);
8961
+ background: var(--surface-4);
8962
+ color: var(--surface-3-text);
8963
+ border: none;
8964
+ border-radius: var(--radius-s, 4px);
8965
+ font-family: var(--font-family);
8966
+ font-size: var(--text-m);
8967
+ font-weight: var(--font-weight-bold, 600);
8968
+ user-select: none;
8969
+ -webkit-user-select: none;
8970
+ }
8971
+ `;
8972
+ document.head.appendChild(style);
8973
+ }
7850
8974
  let ToolbarElement = class ToolbarElement extends LitElement {
7851
8975
  static {
7852
8976
  _ToolbarElement = this;
@@ -7859,11 +8983,76 @@ let ToolbarElement = class ToolbarElement extends LitElement {
7859
8983
  this.toolbarAlign = `near`;
7860
8984
  this.showGroupLabels = false;
7861
8985
  this.invisibleGroups = false;
8986
+ this.allowReorder = false;
7862
8987
  this._overflowCount = 0;
7863
8988
  this._ro = null;
7864
8989
  this._anchor = `--toolbar-${_ToolbarElement._cnt++}`;
7865
8990
  this._sizes = /* @__PURE__ */ new WeakMap();
7866
8991
  this._measureQueued = false;
8992
+ this._dragState = null;
8993
+ this._suppressNextClick = false;
8994
+ this._onClickCapture = (event) => {
8995
+ if (!this._suppressNextClick) return;
8996
+ this._suppressNextClick = false;
8997
+ event.preventDefault();
8998
+ event.stopPropagation();
8999
+ };
9000
+ this._onKeyDown = (event) => {
9001
+ const state = this._dragState;
9002
+ if (event.key !== `Escape` || !state?.active) return;
9003
+ event.preventDefault();
9004
+ const pointerId = state.pointerId;
9005
+ this._abortDrag();
9006
+ this._endDrag(pointerId);
9007
+ };
9008
+ this._onPointerDown = (event) => {
9009
+ if (!this.allowReorder || event.button !== 0) return;
9010
+ const target = this._findDragTarget(event);
9011
+ if (!target?.parentNode) return;
9012
+ if (Array.prototype.indexOf.call(target.parentNode.children, target) < 0) return;
9013
+ this._dragState = {
9014
+ pointerId: event.pointerId,
9015
+ source: target,
9016
+ startX: event.clientX,
9017
+ startY: event.clientY,
9018
+ active: false,
9019
+ ghost: null,
9020
+ gapIndex: void 0
9021
+ };
9022
+ window.addEventListener(`pointermove`, this._onPointerMove);
9023
+ window.addEventListener(`pointerup`, this._onPointerUp);
9024
+ window.addEventListener(`pointercancel`, this._onPointerCancel);
9025
+ };
9026
+ this._onPointerMove = (event) => {
9027
+ const state = this._dragState;
9028
+ if (!state || event.pointerId !== state.pointerId) return;
9029
+ const distance = Math.hypot(event.clientX - state.startX, event.clientY - state.startY);
9030
+ if (!state.active) {
9031
+ if (distance < DRAG_THRESHOLD_PX) return;
9032
+ this._beginDrag(event);
9033
+ }
9034
+ state.ghost?.move(event.clientX, event.clientY);
9035
+ const gap = this._computeGapIndex(event.clientX, event.clientY);
9036
+ if (gap !== state.gapIndex) {
9037
+ state.gapIndex = gap;
9038
+ this.requestUpdate();
9039
+ }
9040
+ };
9041
+ this._onPointerUp = (event) => {
9042
+ const state = this._dragState;
9043
+ if (!state || event.pointerId !== state.pointerId) return;
9044
+ if (state.active) {
9045
+ const gap = this._computeGapIndex(event.clientX, event.clientY);
9046
+ this._commitDrag(state.source, gap);
9047
+ }
9048
+ this._endDrag(event.pointerId);
9049
+ };
9050
+ this._onPointerCancel = (event) => {
9051
+ const state = this._dragState;
9052
+ if (!state || event.pointerId !== state.pointerId) return;
9053
+ this._abortDrag();
9054
+ this._endDrag(event.pointerId);
9055
+ };
7867
9056
  }
7868
9057
  static {
7869
9058
  this._cnt = 0;
@@ -7878,6 +9067,7 @@ let ToolbarElement = class ToolbarElement extends LitElement {
7878
9067
  --toolbar-group-gap: var(--space-m);
7879
9068
  --toolbar-overflow: auto;
7880
9069
  display: block;
9070
+ position: relative;
7881
9071
  container-type: inline-size;
7882
9072
  }
7883
9073
  :host([orientation="vertical-left"]),
@@ -7936,32 +9126,274 @@ let ToolbarElement = class ToolbarElement extends LitElement {
7936
9126
  }
7937
9127
  .overflow-popover:popover-open { display: flex; }
7938
9128
  .overflow-popover ::slotted(*) { writing-mode: horizontal-tb; text-orientation: mixed; }
9129
+
9130
+ :host([allow-reorder]) ::slotted(ixfx-toolbar-group[drag-source]) {
9131
+ opacity: 0.4;
9132
+ }
9133
+
9134
+ /* While dragging, suppress text selection inside the toolbar. */
9135
+ :host([dragging]),
9136
+ :host([dragging]) ::slotted(*) {
9137
+ user-select: none;
9138
+ -webkit-user-select: none;
9139
+ }
9140
+
9141
+ .drop-caret {
9142
+ position: absolute;
9143
+ top: 0;
9144
+ left: 0;
9145
+ background: var(--accent);
9146
+ border-radius: 1px;
9147
+ pointer-events: none;
9148
+ z-index: 1;
9149
+ }
7939
9150
  `
7940
9151
  ];
7941
9152
  }
7942
9153
  connectedCallback() {
7943
9154
  super.connectedCallback();
7944
9155
  this.style.setProperty(`--anchor-name`, this._anchor);
9156
+ this.addEventListener(`click`, this._onClickCapture, true);
7945
9157
  }
7946
9158
  firstUpdated() {
7947
- this._slot.addEventListener(`slotchange`, () => this._scheduleMeasure());
9159
+ this._slot.addEventListener(`slotchange`, () => {
9160
+ this._updateGroups();
9161
+ this._scheduleMeasure();
9162
+ });
7948
9163
  this._ro = new ResizeObserver(() => this._scheduleMeasure());
7949
9164
  this._ro.observe(this._inner);
7950
9165
  this._scheduleMeasure();
7951
- this._updateGroupLabels();
9166
+ this._updateGroups();
7952
9167
  }
7953
9168
  updated(changed) {
7954
- if (changed.has(`showGroupLabels`)) this._updateGroupLabels();
9169
+ if (changed.has(`showGroupLabels`) || changed.has(`allowReorder`)) this._updateGroups();
9170
+ }
9171
+ /** Groups in the main slot (not overflow) that can participate in reorder. */
9172
+ _mainGroups() {
9173
+ return Array.from(this.children).filter((el) => el.tagName === `IXFX-TOOLBAR-GROUP` && el.getAttribute(`slot`) !== `overflow`);
9174
+ }
9175
+ /**
9176
+ * Stable key identifying a group across save/restore: its `id`, falling
9177
+ * back to `label`. Groups with neither get an empty key, which still
9178
+ * round-trips as long as the set of groups is unchanged. Give groups a
9179
+ * unique `id` for reliable preference persistence.
9180
+ */
9181
+ _groupKey(group) {
9182
+ return group.id || group.label || ``;
9183
+ }
9184
+ /** Keys of the main-slot groups in current DOM order. */
9185
+ getGroupOrder() {
9186
+ return this._mainGroups().map((group) => this._groupKey(group));
9187
+ }
9188
+ /**
9189
+ * Reorder main-slot groups to match `order` (keys as returned by
9190
+ * `getGroupOrder`). Unknown keys are ignored; groups not mentioned keep
9191
+ * their relative order at the end. Each moved group fires a `reorder`
9192
+ * event, exactly as if dragged.
9193
+ */
9194
+ setGroupOrder(order) {
9195
+ const groups = this._mainGroups();
9196
+ if (groups.length === 0) return;
9197
+ const used = /* @__PURE__ */ new Set();
9198
+ const desired = [];
9199
+ for (const key of order) {
9200
+ const match = groups.find((candidate) => !used.has(candidate) && candidate.id === key && key !== ``) ?? groups.find((candidate) => !used.has(candidate) && this._groupKey(candidate) === key);
9201
+ if (match) {
9202
+ used.add(match);
9203
+ desired.push(match);
9204
+ }
9205
+ }
9206
+ const sequence = [...desired, ...groups.filter((group) => !used.has(group))];
9207
+ let guard = sequence.length * sequence.length + 1;
9208
+ let moved = true;
9209
+ while (moved && guard-- > 0) {
9210
+ moved = false;
9211
+ for (let i = 0; i + 1 < sequence.length; i++) {
9212
+ const before = sequence[i];
9213
+ const after = sequence[i + 1];
9214
+ if (before.compareDocumentPosition(after) & Node.DOCUMENT_POSITION_FOLLOWING) continue;
9215
+ if (this._insertAndEmit(before, after)) moved = true;
9216
+ }
9217
+ }
9218
+ }
9219
+ /**
9220
+ * Capture the current group order as a JSON-safe snapshot for preference
9221
+ * stores: `localStorage.setItem(key, JSON.stringify(toolbar.getLayoutSnapshot()))`.
9222
+ */
9223
+ getLayoutSnapshot() {
9224
+ return {
9225
+ version: 1,
9226
+ order: this.getGroupOrder()
9227
+ };
9228
+ }
9229
+ /**
9230
+ * Restore a snapshot from `getLayoutSnapshot` (e.g. parsed from
9231
+ * `localStorage`). Unknown keys are ignored. Returns `false` when the
9232
+ * value is not a valid snapshot, leaving the toolbar untouched.
9233
+ */
9234
+ applyLayoutSnapshot(snapshot) {
9235
+ if (typeof snapshot !== `object` || snapshot === null) return false;
9236
+ const { version, order } = snapshot;
9237
+ if (version !== 1 || !Array.isArray(order) || !order.every((key) => typeof key === `string`)) return false;
9238
+ this.setGroupOrder(order);
9239
+ return true;
9240
+ }
9241
+ /**
9242
+ * Move `source` before `ref` (or append when `ref` is null), firing a
9243
+ * `reorder` event when the position actually changed. Returns whether a
9244
+ * move happened. Indices are measured against all light-DOM children,
9245
+ * matching the `reorder` detail of drag-reorder.
9246
+ */
9247
+ _insertAndEmit(source, ref) {
9248
+ const parent = source.parentNode;
9249
+ if (!parent) return false;
9250
+ const fromIndex = Array.prototype.indexOf.call(parent.children, source);
9251
+ parent.insertBefore(source, ref);
9252
+ const toIndex = Array.prototype.indexOf.call(parent.children, source);
9253
+ if (toIndex === fromIndex) return false;
9254
+ this.dispatchEvent(new CustomEvent(`reorder`, {
9255
+ bubbles: true,
9256
+ composed: true,
9257
+ detail: {
9258
+ fromIndex,
9259
+ toIndex,
9260
+ group: source
9261
+ }
9262
+ }));
9263
+ this._updateGroups();
9264
+ this._scheduleMeasure();
9265
+ return true;
7955
9266
  }
7956
- _updateGroupLabels() {
7957
- for (const child of Array.from(this.children)) if (child.tagName === `IXFX-TOOLBAR-GROUP`) {
9267
+ _updateGroups() {
9268
+ for (const child of Array.from(this.children)) {
9269
+ if (child.tagName !== `IXFX-TOOLBAR-GROUP`) continue;
7958
9270
  if (this.showGroupLabels) child.setAttribute(`show-label`, ``);
7959
9271
  else child.removeAttribute(`show-label`);
9272
+ if (this.allowReorder && child.getAttribute(`slot`) !== `overflow`) child.setAttribute(`reorderable`, ``);
9273
+ else child.removeAttribute(`reorderable`);
7960
9274
  }
7961
9275
  }
7962
9276
  disconnectedCallback() {
7963
9277
  super.disconnectedCallback();
9278
+ this.removeEventListener(`click`, this._onClickCapture, true);
7964
9279
  this._ro?.disconnect();
9280
+ if (this._dragState) {
9281
+ this._abortDrag();
9282
+ this._endDrag(this._dragState.pointerId);
9283
+ }
9284
+ }
9285
+ /**
9286
+ * Walk composedPath for the nearest ixfx-toolbar-group, requiring the press
9287
+ * to have gone through its `.grab-handle`. Drag is handle-only so every
9288
+ * control inside the group (buttons, selects, inputs) keeps working.
9289
+ */
9290
+ _findDragTarget(event) {
9291
+ if (!this.allowReorder) return null;
9292
+ const path = event.composedPath();
9293
+ let group = null;
9294
+ let viaHandle = false;
9295
+ for (const node of path) {
9296
+ if (!(node instanceof HTMLElement)) continue;
9297
+ if (node.classList.contains(`grab-handle`)) viaHandle = true;
9298
+ if (!group && node.tagName === `IXFX-TOOLBAR-GROUP`) group = node;
9299
+ }
9300
+ if (!group || !viaHandle) return null;
9301
+ if (group.getAttribute(`slot`) === `overflow`) return null;
9302
+ return group;
9303
+ }
9304
+ _beginDrag(event) {
9305
+ const state = this._dragState;
9306
+ if (!state || state.active) return;
9307
+ try {
9308
+ this.setPointerCapture(event.pointerId);
9309
+ } catch {}
9310
+ state.active = true;
9311
+ this.setAttribute(`dragging`, ``);
9312
+ state.source.setAttribute(`drag-source`, ``);
9313
+ state.ghost = this._createGhost(state.source);
9314
+ state.ghost?.move(event.clientX, event.clientY);
9315
+ }
9316
+ _abortDrag() {
9317
+ const state = this._dragState;
9318
+ if (!state) return;
9319
+ state.source.removeAttribute(`drag-source`);
9320
+ if (state.gapIndex !== void 0) {
9321
+ state.gapIndex = void 0;
9322
+ this.requestUpdate();
9323
+ }
9324
+ }
9325
+ _endDrag(pointerId) {
9326
+ const state = this._dragState;
9327
+ if (!state) return;
9328
+ state.ghost?.destroy();
9329
+ state.ghost = null;
9330
+ this.removeAttribute(`dragging`);
9331
+ this._dragState = null;
9332
+ window.removeEventListener(`pointermove`, this._onPointerMove);
9333
+ window.removeEventListener(`pointerup`, this._onPointerUp);
9334
+ window.removeEventListener(`pointercancel`, this._onPointerCancel);
9335
+ try {
9336
+ this.releasePointerCapture(pointerId);
9337
+ } catch {}
9338
+ this.requestUpdate();
9339
+ }
9340
+ _commitDrag(source, gapIndex) {
9341
+ source.removeAttribute(`drag-source`);
9342
+ const domSiblings = this._mainGroups().filter((el) => el !== source);
9343
+ const n = domSiblings.length;
9344
+ const visualGap = gapIndex === void 0 ? n : Math.max(0, Math.min(gapIndex, n));
9345
+ const domIndex = this.orientation === `vertical-left` ? n - visualGap : visualGap;
9346
+ if (!source.parentNode) return;
9347
+ const ref = domSiblings[domIndex] ?? null;
9348
+ if (this._insertAndEmit(source, ref)) this._suppressNextClick = true;
9349
+ }
9350
+ _createGhost(source) {
9351
+ const label = document.createElement(`span`);
9352
+ label.textContent = (`label` in source && typeof source.label === `string` && source.label || (source.textContent ?? ``).trim()).slice(0, 60);
9353
+ label.style.overflow = `hidden`;
9354
+ label.style.textOverflow = `ellipsis`;
9355
+ label.style.whiteSpace = `nowrap`;
9356
+ try {
9357
+ injectToolbarGhostStyles();
9358
+ return createDragGhost({
9359
+ anchor: source,
9360
+ content: label,
9361
+ className: `ixfx-toolbar-group-ghost`
9362
+ });
9363
+ } catch {
9364
+ return null;
9365
+ }
9366
+ }
9367
+ /**
9368
+ * Nearest group gap to the pointer, ignoring the drag source.
9369
+ * The perpendicular axis is ignored — the pointer always resolves to a gap.
9370
+ * Gaps are in visual order (reversed for `vertical-left`/column-reverse).
9371
+ */
9372
+ _computeGapIndex(clientX, clientY) {
9373
+ const state = this._dragState;
9374
+ if (!state) return void 0;
9375
+ const domSiblings = this._mainGroups().filter((el) => el !== state.source);
9376
+ if (domSiblings.length === 0) return 0;
9377
+ const visual = this.orientation === `vertical-left` ? [...domSiblings].reverse() : domSiblings;
9378
+ const useX = this.orientation === `horizontal`;
9379
+ const pos = useX ? clientX : clientY;
9380
+ return toolbarGapIndex(visual.map((el) => {
9381
+ const rect = el.getBoundingClientRect();
9382
+ return [useX ? rect.left : rect.top, useX ? rect.right : rect.bottom];
9383
+ }), pos);
9384
+ }
9385
+ _computeCaretStyle() {
9386
+ const state = this._dragState;
9387
+ if (!state?.active || state.gapIndex === void 0) return void 0;
9388
+ const domSiblings = this._mainGroups().filter((el) => el !== state.source);
9389
+ if (domSiblings.length === 0) return void 0;
9390
+ const hostRect = this.getBoundingClientRect();
9391
+ if (!hostRect) return void 0;
9392
+ const visual = this.orientation === `vertical-left` ? [...domSiblings].reverse() : domSiblings;
9393
+ const gap = Math.max(0, Math.min(state.gapIndex, visual.length));
9394
+ const rect = gap < visual.length ? visual[gap].getBoundingClientRect() : visual[visual.length - 1].getBoundingClientRect();
9395
+ if (this.orientation === `horizontal`) return `transform: translate(${(gap < visual.length ? rect.left : rect.right) - hostRect.left - 1}px, 0); width: 2px; height: ${hostRect.height}px;`;
9396
+ return `transform: translate(0, ${(gap < visual.length ? rect.top : rect.bottom) - hostRect.top - 1}px); width: ${hostRect.width}px; height: 2px;`;
7965
9397
  }
7966
9398
  /** Coalesce measure passes into one per frame; prevents slotchange feedback loops. */
7967
9399
  _scheduleMeasure() {
@@ -8023,93 +9455,1372 @@ let ToolbarElement = class ToolbarElement extends LitElement {
8023
9455
  this._overflowCount = newCount;
8024
9456
  mutated = true;
8025
9457
  }
8026
- if (mutated) this.requestUpdate();
8027
- return;
9458
+ if (mutated) this.requestUpdate();
9459
+ return;
9460
+ }
9461
+ const btnAllowance = inOverflow.length > 0 ? gap + btnSize : 0;
9462
+ while (inMain.length > 0 && cum + btnAllowance > available) {
9463
+ const last = inMain[inMain.length - 1];
9464
+ const s = this._sizes.get(last) ?? 0;
9465
+ last.setAttribute(`slot`, `overflow`);
9466
+ inMain.pop();
9467
+ inOverflow.unshift(last);
9468
+ cum -= (inMain.length ? gap : 0) + s;
9469
+ mutated = true;
9470
+ }
9471
+ while (inOverflow.length > 0) {
9472
+ const first = inOverflow[0];
9473
+ const s = this._sizes.get(first);
9474
+ if (s === void 0) break;
9475
+ const addGap = inMain.length ? gap : 0;
9476
+ const nextBtnAllowance = inOverflow.length > 1 ? gap + btnSize : 0;
9477
+ if (cum + addGap + s + nextBtnAllowance > available) break;
9478
+ first.removeAttribute(`slot`);
9479
+ inOverflow.shift();
9480
+ inMain.push(first);
9481
+ cum += addGap + s;
9482
+ mutated = true;
9483
+ }
9484
+ const newCount = inOverflow.length;
9485
+ if (newCount !== this._overflowCount) {
9486
+ this._overflowCount = newCount;
9487
+ mutated = true;
9488
+ }
9489
+ if (mutated) this.requestUpdate();
9490
+ }
9491
+ _toggleOverflow() {
9492
+ try {
9493
+ this._popover.togglePopover();
9494
+ } catch {
9495
+ this._popover.showPopover();
9496
+ }
9497
+ }
9498
+ render() {
9499
+ const overflowMode = getComputedStyle(this).getPropertyValue(`--toolbar-overflow`).trim();
9500
+ const hasOverflow = this._overflowCount > 0 || overflowMode === `collapsed`;
9501
+ const caretStyle = this._computeCaretStyle();
9502
+ return html`
9503
+ <div class="inner" role="toolbar" aria-orientation=${this.orientation}>
9504
+ <slot @pointerdown=${this._onPointerDown} @keydown=${this._onKeyDown}></slot>
9505
+ <ixfx-button class="overflow-btn" ?hidden=${!hasOverflow} @click=${this._toggleOverflow} aria-label="More" aria-haspopup="true" aria-expanded=${this._popover?.matches(`:popover-open`) ?? false}>⋯</ixfx-button>
9506
+ </div>
9507
+ <div class="overflow-popover popup-surface" popover="auto" @click=${(e) => e.stopPropagation()}>
9508
+ <slot name="overflow"></slot>
9509
+ </div>
9510
+ ${caretStyle ? html`<div class="drop-caret" style=${caretStyle}></div>` : nothing}
9511
+ `;
9512
+ }
9513
+ };
9514
+ __decorate([property({
9515
+ type: String,
9516
+ reflect: true
9517
+ })], ToolbarElement.prototype, "orientation", void 0);
9518
+ __decorate([property({
9519
+ type: String,
9520
+ reflect: true
9521
+ })], ToolbarElement.prototype, "layout", void 0);
9522
+ __decorate([property({
9523
+ type: String,
9524
+ attribute: `vertical-writing`,
9525
+ reflect: true
9526
+ })], ToolbarElement.prototype, "verticalWriting", void 0);
9527
+ __decorate([property({
9528
+ type: String,
9529
+ attribute: `toolbar-align`,
9530
+ reflect: true
9531
+ })], ToolbarElement.prototype, "toolbarAlign", void 0);
9532
+ __decorate([property({
9533
+ type: Boolean,
9534
+ attribute: `show-group-labels`,
9535
+ reflect: true
9536
+ })], ToolbarElement.prototype, "showGroupLabels", void 0);
9537
+ __decorate([property({
9538
+ type: Boolean,
9539
+ attribute: `invisible-groups`,
9540
+ reflect: true
9541
+ })], ToolbarElement.prototype, "invisibleGroups", void 0);
9542
+ __decorate([property({
9543
+ type: Boolean,
9544
+ attribute: `allow-reorder`,
9545
+ reflect: true
9546
+ })], ToolbarElement.prototype, "allowReorder", void 0);
9547
+ __decorate([state()], ToolbarElement.prototype, "_overflowCount", void 0);
9548
+ __decorate([query(`.inner`)], ToolbarElement.prototype, "_inner", void 0);
9549
+ __decorate([query(`.overflow-popover`)], ToolbarElement.prototype, "_popover", void 0);
9550
+ __decorate([query(`slot`)], ToolbarElement.prototype, "_slot", void 0);
9551
+ ToolbarElement = _ToolbarElement = __decorate([safeCustomElement(`ixfx-toolbar`)], ToolbarElement);
9552
+ //#endregion
9553
+ //#region src/treemap/layout.ts
9554
+ /** Coerce a raw accessor result to a non-negative finite weight. */
9555
+ function normaliseValue$1(raw) {
9556
+ return typeof raw === `number` && Number.isFinite(raw) && raw > 0 ? raw : 0;
9557
+ }
9558
+ /** Depth-first search for the node with `key`. Returns `undefined` when absent. */
9559
+ function findTreemapNode(root, key) {
9560
+ if (!root) return void 0;
9561
+ if (root.item.key === key) return root;
9562
+ for (const child of root.children ?? []) {
9563
+ const hit = findTreemapNode(child, key);
9564
+ if (hit) return hit;
9565
+ }
9566
+ }
9567
+ /**
9568
+ * Chain of nodes from `root` down to (and including) the node with `key`.
9569
+ * Returns `undefined` when the key is absent. The first entry is always `root`.
9570
+ */
9571
+ function findTreemapPath(root, key) {
9572
+ if (!root) return void 0;
9573
+ if (root.item.key === key) return [root];
9574
+ for (const child of root.children ?? []) {
9575
+ const hit = findTreemapPath(child, key);
9576
+ if (hit) return [root, ...hit];
9577
+ }
9578
+ }
9579
+ /**
9580
+ * Worst aspect ratio of the row items if laid out together along a strip of
9581
+ * length `side` (the short side of the remaining container). Lower is better;
9582
+ * `1` is a perfect square. Follows Bruls et al.
9583
+ */
9584
+ function worstAspect(areas, total, side) {
9585
+ if (areas.length === 0 || total <= 0 || side <= 0) return Number.POSITIVE_INFINITY;
9586
+ let worst = 0;
9587
+ for (const area of areas) {
9588
+ if (area <= 0) return Number.POSITIVE_INFINITY;
9589
+ const ratio = side * side * area / (total * total);
9590
+ worst = Math.max(worst, ratio, 1 / ratio);
9591
+ }
9592
+ return worst;
9593
+ }
9594
+ /**
9595
+ * Squarified tiling of `items` (with pre-normalised positive areas summing to
9596
+ * `box.width * box.height`) into `box`. Returns absolute boxes in item order.
9597
+ * Sibling order is preserved — no descending sort — so output is deterministic.
9598
+ */
9599
+ function squarifyInto(items, box) {
9600
+ const out = Array.from({ length: items.length });
9601
+ let cx = box.x;
9602
+ let cy = box.y;
9603
+ let cw = box.width;
9604
+ let ch = box.height;
9605
+ let start = 0;
9606
+ while (start < items.length) {
9607
+ const side = Math.min(cw, ch);
9608
+ let end = start + 1;
9609
+ let rowAreas = items.slice(start, end).map((i) => i.area);
9610
+ let rowTotal = rowAreas.reduce((a, b) => a + b, 0);
9611
+ let worst = worstAspect(rowAreas, rowTotal, side);
9612
+ while (end < items.length) {
9613
+ const nextAreas = items.slice(start, end + 1).map((i) => i.area);
9614
+ const nextTotal = nextAreas.reduce((a, b) => a + b, 0);
9615
+ const nextWorst = worstAspect(nextAreas, nextTotal, side);
9616
+ if (nextWorst <= worst) {
9617
+ end++;
9618
+ rowAreas = nextAreas;
9619
+ rowTotal = nextTotal;
9620
+ worst = nextWorst;
9621
+ } else break;
9622
+ }
9623
+ if (cw <= ch) {
9624
+ const stripH = rowTotal / cw;
9625
+ let ix = cx;
9626
+ for (let i = start; i < end; i++) {
9627
+ const w = rowAreas[i - start] / stripH;
9628
+ out[i] = {
9629
+ x: ix,
9630
+ y: cy,
9631
+ width: w,
9632
+ height: stripH
9633
+ };
9634
+ ix += w;
9635
+ }
9636
+ cy += stripH;
9637
+ ch -= stripH;
9638
+ } else {
9639
+ const stripW = rowTotal / ch;
9640
+ let iy = cy;
9641
+ for (let i = start; i < end; i++) {
9642
+ const h = rowAreas[i - start] / stripW;
9643
+ out[i] = {
9644
+ x: cx,
9645
+ y: iy,
9646
+ width: stripW,
9647
+ height: h
9648
+ };
9649
+ iy += h;
9650
+ }
9651
+ cx += stripW;
9652
+ cw -= stripW;
9653
+ }
9654
+ start = end;
9655
+ }
9656
+ return out;
9657
+ }
9658
+ /**
9659
+ * Subdivide `box` among `weights` (same length as the child list) using
9660
+ * `method` at `relativeDepth`. Returns one absolute box per weight, in order.
9661
+ * Zero-share children get degenerate (zero-area) boxes at the box origin.
9662
+ */
9663
+ function subdivide(box, weights, method, relativeDepth) {
9664
+ const total = weights.reduce((a, b) => a + b, 0);
9665
+ const shares = total > 0 ? weights.map((w) => w / total) : weights.map(() => 1 / weights.length);
9666
+ const dice = method === `dice` || method === `slice-dice` && relativeDepth % 2 === 0;
9667
+ const slice = method === `slice` || method === `slice-dice` && relativeDepth % 2 === 1;
9668
+ if (method === `squarify`) {
9669
+ const positive = shares.map((share, i) => ({
9670
+ share,
9671
+ i
9672
+ })).filter((s) => s.share > 0);
9673
+ const boxes = weights.map(() => ({
9674
+ x: box.x,
9675
+ y: box.y,
9676
+ width: 0,
9677
+ height: 0
9678
+ }));
9679
+ if (positive.length > 0) {
9680
+ const containerArea = box.width * box.height;
9681
+ const laid = squarifyInto(positive.map((p) => ({ area: p.share * containerArea })), box);
9682
+ positive.forEach((p, k) => {
9683
+ boxes[p.i] = laid[k];
9684
+ });
9685
+ }
9686
+ return boxes;
9687
+ }
9688
+ const boxes = [];
9689
+ if (dice) {
9690
+ let cursor = box.x;
9691
+ shares.forEach((share) => {
9692
+ const w = box.width * share;
9693
+ boxes.push({
9694
+ x: cursor,
9695
+ y: box.y,
9696
+ width: w,
9697
+ height: box.height
9698
+ });
9699
+ cursor += w;
9700
+ });
9701
+ } else if (slice) {
9702
+ let cursor = box.y;
9703
+ shares.forEach((share) => {
9704
+ const h = box.height * share;
9705
+ boxes.push({
9706
+ x: box.x,
9707
+ y: cursor,
9708
+ width: box.width,
9709
+ height: h
9710
+ });
9711
+ cursor += h;
9712
+ });
9713
+ }
9714
+ return boxes;
9715
+ }
9716
+ /**
9717
+ * Compute the visible rectangles for a treemap view.
9718
+ *
9719
+ * The zoom node fills the whole viewport (`0,0,1,1`); its children subdivide
9720
+ * that rectangle by `valueAccessor` share, recursively, up to `maxDepth`
9721
+ * nested levels. The zoom node itself is never emitted as a rect (it is shown
9722
+ * in the breadcrumb strip instead — matching `TreeComponent.getNodes()`,
9723
+ * where the root is never listed). Siblings whose weights all normalise to
9724
+ * `0` split their parent's rectangle equally.
9725
+ *
9726
+ * @param root True tree root. `undefined` (or a root without children) yields `[]`.
9727
+ * @param options `valueAccessor` (default equal weights), `maxDepth` (nesting
9728
+ * levels, default unlimited), `zoomKey` (visual root, default true root;
9729
+ * unknown keys fall back to the true root), `tilingMethod` (default
9730
+ * `'squarify'`).
9731
+ */
9732
+ function computeTreemapLayout(root, options = {}) {
9733
+ if (!root?.children?.length) return [];
9734
+ const { maxDepth = Number.POSITIVE_INFINITY, valueAccessor = () => 1, zoomKey, tilingMethod = `squarify`, hideLeaves = false } = options;
9735
+ if (maxDepth <= 0) return [];
9736
+ const visibleChildren = (node) => {
9737
+ const children = node.children ?? [];
9738
+ return hideLeaves ? children.filter((n) => n.item.isLeaf !== true) : children;
9739
+ };
9740
+ const zoomNode = zoomKey === void 0 ? root : findTreemapNode(root, zoomKey) ?? root;
9741
+ const zoomPath = findTreemapPath(root, zoomNode.item.key);
9742
+ const zoomDepth = zoomPath ? zoomPath.length - 2 : -1;
9743
+ const siblings = visibleChildren(zoomNode);
9744
+ if (siblings.length === 0) return [];
9745
+ const rects = [];
9746
+ const layoutLevel = (nodes, box, depth, relativeDepth, parentKey) => {
9747
+ if (nodes.length === 0 || relativeDepth >= maxDepth) return;
9748
+ const boxes = subdivide(box, nodes.map((n) => normaliseValue$1(valueAccessor(n.item))), tilingMethod, relativeDepth);
9749
+ const setSize = nodes.length;
9750
+ nodes.forEach((node, index) => {
9751
+ const childBox = boxes[index];
9752
+ const children = visibleChildren(node);
9753
+ rects.push({
9754
+ node,
9755
+ depth,
9756
+ relativeDepth,
9757
+ x: childBox.x,
9758
+ y: childBox.y,
9759
+ width: childBox.width,
9760
+ height: childBox.height,
9761
+ parentKey,
9762
+ index,
9763
+ setSize,
9764
+ hasChildren: children.length > 0 || node.children === void 0 && node.item.isLeaf !== true
9765
+ });
9766
+ if (children.length > 0) layoutLevel(children, childBox, depth + 1, relativeDepth + 1, node.item.key);
9767
+ });
9768
+ };
9769
+ layoutLevel(siblings, {
9770
+ x: 0,
9771
+ y: 0,
9772
+ width: 1,
9773
+ height: 1
9774
+ }, zoomDepth + 1, 0, zoomNode.item.key);
9775
+ return rects;
9776
+ }
9777
+ //#endregion
9778
+ //#region src/treemap/treemap-list.ts
9779
+ let TreemapListElement = class TreemapListElement extends LitElement {
9780
+ constructor(..._args) {
9781
+ super(..._args);
9782
+ this.exclusivity = `none`;
9783
+ this.selectionFilter = `all`;
9784
+ this.interactionMode = `standard`;
9785
+ this.tilingMethod = `squarify`;
9786
+ this.maxDepth = 4;
9787
+ this.hideLeaves = false;
9788
+ this.minLabelArea = 1600;
9789
+ this.minTileWidth = 24;
9790
+ this.padding = 4;
9791
+ this.minChildArea = 400;
9792
+ this.valueAccessor = () => 1;
9793
+ this._selectedNodes = /* @__PURE__ */ new Set();
9794
+ this.disableKeyboardNavigation = false;
9795
+ this.registry = void 0;
9796
+ this._zoomKey = void 0;
9797
+ this.search = new ListSearchController(this, { adapter: createTreeListSearchAdapter(this, {
9798
+ labelSelector: `.treemap-label`,
9799
+ highlightKey: `incr-search-highlight`
9800
+ }) });
9801
+ this.tickled = new TickledController(this, {
9802
+ getItems: () => this.#rects().map((r) => ({
9803
+ id: r.node.item.key,
9804
+ enabled: true
9805
+ })),
9806
+ onStateChange: () => {
9807
+ this.setAttribute(`data-input-mode`, this.tickled.inputMode);
9808
+ },
9809
+ onTickle: (id) => {
9810
+ if (id === void 0) return;
9811
+ const rect = this.#rects().find((r) => r.node.item.key === id);
9812
+ if (!rect) return;
9813
+ this.dispatchEvent(new CustomEvent(`tickle`, {
9814
+ detail: {
9815
+ node: rect.node,
9816
+ depth: rect.depth
9817
+ },
9818
+ bubbles: true,
9819
+ composed: true
9820
+ }));
9821
+ }
9822
+ });
9823
+ this.#pendingKeys = /* @__PURE__ */ new Set();
9824
+ this.#aborters = /* @__PURE__ */ new Map();
9825
+ this.#wheelCooldownUntil = 0;
9826
+ }
9827
+ get selectedNodes() {
9828
+ return this._selectedNodes;
9829
+ }
9830
+ /** Current zoom root. `undefined` when showing the full tree. Pure view-state — `root` is never mutated. */
9831
+ get focusedNode() {
9832
+ if (this._zoomKey === void 0) return void 0;
9833
+ return findTreemapNode(this.#effectiveRoot, this._zoomKey);
9834
+ }
9835
+ #modelUnsubscribe;
9836
+ #pendingKeys;
9837
+ #aborters;
9838
+ #anchorIndex;
9839
+ #ownedRegistry;
9840
+ #unregisterSearchCommands;
9841
+ #resizeObserver;
9842
+ #pendingFlip;
9843
+ /** `performance.now()` timestamp before which further Cmd+wheel zoom steps are ignored. */
9844
+ #wheelCooldownUntil;
9845
+ get #effectiveRoot() {
9846
+ return this.model?.root ?? this.root;
9847
+ }
9848
+ #rects() {
9849
+ return this.#rectsForZoom(this._zoomKey);
9850
+ }
9851
+ #rectsForZoom(zoomKey) {
9852
+ return computeTreemapLayout(this.#effectiveRoot, {
9853
+ valueAccessor: this.valueAccessor,
9854
+ maxDepth: this.maxDepth,
9855
+ zoomKey,
9856
+ tilingMethod: this.tilingMethod,
9857
+ hideLeaves: this.hideLeaves
9858
+ });
9859
+ }
9860
+ connectedCallback() {
9861
+ super.connectedCallback();
9862
+ this.setAttribute(`data-input-mode`, `mouse`);
9863
+ const registry = this.#ownedRegistry = createCommandRegistry();
9864
+ registry.register({
9865
+ id: `select-all`,
9866
+ label: `Select All`,
9867
+ description: `Select all visible items`,
9868
+ execute: () => this.selectAll()
9869
+ });
9870
+ registry.register({
9871
+ id: `deselect-all`,
9872
+ label: `Deselect All`,
9873
+ description: `Clear all selection`,
9874
+ execute: () => this.deselectAll()
9875
+ });
9876
+ this.registry = registry;
9877
+ this.#syncSearchCommands();
9878
+ if (typeof ResizeObserver !== `undefined`) {
9879
+ this.#resizeObserver = new ResizeObserver(() => this.requestUpdate());
9880
+ this.#resizeObserver.observe(this);
9881
+ }
9882
+ }
9883
+ disconnectedCallback() {
9884
+ super.disconnectedCallback();
9885
+ this.#modelUnsubscribe?.();
9886
+ this.#modelUnsubscribe = void 0;
9887
+ for (const aborter of this.#aborters.values()) aborter.abort();
9888
+ this.#aborters.clear();
9889
+ this.#pendingKeys.clear();
9890
+ this.#resizeObserver?.disconnect();
9891
+ this.#resizeObserver = void 0;
9892
+ if (this.#ownedRegistry) {
9893
+ this.#ownedRegistry.unregister(`select-all`);
9894
+ this.#ownedRegistry.unregister(`deselect-all`);
9895
+ this.#ownedRegistry = void 0;
9896
+ this.registry = void 0;
9897
+ }
9898
+ }
9899
+ #effectiveSearchModes() {
9900
+ const raw = this.searchModes?.trim();
9901
+ if (raw === void 0) return [...LIST_SEARCH_MODES];
9902
+ const requested = new Set(raw.split(/\s+/));
9903
+ return LIST_SEARCH_MODES.filter((m) => requested.has(m));
9904
+ }
9905
+ #syncSearchCommands() {
9906
+ if (!this.#ownedRegistry) return;
9907
+ this.search.setModes(this.#effectiveSearchModes());
9908
+ this.#unregisterSearchCommands?.();
9909
+ this.#unregisterSearchCommands = registerListSearchCommands(this.#ownedRegistry, this.search);
9910
+ }
9911
+ updated(changedProps) {
9912
+ if (changedProps.has(`root`) && this.root) this.#resetViewState();
9913
+ if (changedProps.has(`model`)) {
9914
+ this.#modelUnsubscribe?.();
9915
+ this.#modelUnsubscribe = void 0;
9916
+ if (this.model) {
9917
+ this.#modelUnsubscribe = this.model._subscribe((event) => {
9918
+ if (event.type === `root`) {
9919
+ this.#resetViewState();
9920
+ this.requestUpdate();
9921
+ } else if (event.type === `invalidate`) this.requestUpdate();
9922
+ });
9923
+ this.#resetViewState();
9924
+ }
9925
+ }
9926
+ if (changedProps.has(`interactionMode`)) {
9927
+ this.#anchorIndex = void 0;
9928
+ this.clearSelection();
9929
+ }
9930
+ if (changedProps.has(`searchModes`)) this.#syncSearchCommands();
9931
+ if (this._zoomKey !== void 0 && !findTreemapNode(this.#effectiveRoot, this._zoomKey)) this._zoomKey = void 0;
9932
+ this.#ensureVisibleChildren();
9933
+ if (this.#pendingFlip) {
9934
+ const flip = this.#pendingFlip;
9935
+ this.#pendingFlip = void 0;
9936
+ this.#applyFlip(flip);
9937
+ }
9938
+ }
9939
+ #resetViewState() {
9940
+ this.#pendingFlip = void 0;
9941
+ this.#clearSelectionInternal();
9942
+ this.tickled.clearTickled();
9943
+ this._zoomKey = void 0;
9944
+ this.#anchorIndex = void 0;
9945
+ }
9946
+ #ensureVisibleChildren() {
9947
+ if (!this.model && !this.loadChildren) return;
9948
+ for (const rect of this.#rects()) {
9949
+ const { node } = rect;
9950
+ if (node.children !== void 0 || this.#pendingKeys.has(node.item.key)) continue;
9951
+ this.#loadChildren(node, rect.depth);
9952
+ }
9953
+ }
9954
+ async #loadChildren(node, depth) {
9955
+ const key = node.item.key;
9956
+ this.#pendingKeys.add(key);
9957
+ const controller = new AbortController();
9958
+ this.#aborters.set(key, controller);
9959
+ this.requestUpdate();
9960
+ try {
9961
+ let children;
9962
+ if (this.model) children = await this.model.getChildren(node, depth, controller.signal);
9963
+ else children = [...await collectDataResult(this.loadChildren({
9964
+ node,
9965
+ depth
9966
+ }, controller.signal), controller.signal)];
9967
+ if (controller.signal.aborted) return;
9968
+ node.children = children;
9969
+ } catch {
9970
+ if (controller.signal.aborted) return;
9971
+ node.children = [];
9972
+ } finally {
9973
+ this.#pendingKeys.delete(key);
9974
+ this.#aborters.delete(key);
9975
+ if (!controller.signal.aborted) this.requestUpdate();
9976
+ }
9977
+ }
9978
+ #canSelect(node) {
9979
+ if (this.selectionFilter === `none`) return false;
9980
+ if (this.selectionFilter === `leaf`) return node.item.isLeaf === true;
9981
+ if (this.selectionFilter === `branch`) return node.item.isLeaf === false || node.children !== void 0 && node.children.length > 0;
9982
+ return true;
9983
+ }
9984
+ #clearSelectionInternal() {
9985
+ this._selectedNodes.clear();
9986
+ this.selectedNode = void 0;
9987
+ }
9988
+ #dispatchSelect(previous) {
9989
+ this.dispatchEvent(new CustomEvent(`select`, {
9990
+ detail: {
9991
+ selected: new Set(this._selectedNodes),
9992
+ previous
9993
+ },
9994
+ bubbles: true,
9995
+ composed: true
9996
+ }));
9997
+ }
9998
+ select(node) {
9999
+ if (!this.#canSelect(node)) return;
10000
+ const prev = new Set(this._selectedNodes);
10001
+ this._selectedNodes.clear();
10002
+ this._selectedNodes.add(node);
10003
+ this.selectedNode = node;
10004
+ this.#dispatchSelect(prev);
10005
+ this.requestUpdate();
10006
+ }
10007
+ deselect(node) {
10008
+ if (!this._selectedNodes.has(node)) return;
10009
+ const prev = new Set(this._selectedNodes);
10010
+ this._selectedNodes.delete(node);
10011
+ if (this.selectedNode === node) this.selectedNode = [...this._selectedNodes].at(-1);
10012
+ this.#dispatchSelect(prev);
10013
+ this.requestUpdate();
10014
+ }
10015
+ clearSelection() {
10016
+ if (this._selectedNodes.size === 0) return;
10017
+ const prev = new Set(this._selectedNodes);
10018
+ this.#clearSelectionInternal();
10019
+ this.#dispatchSelect(prev);
10020
+ this.requestUpdate();
10021
+ }
10022
+ selectAll() {
10023
+ const prev = new Set(this._selectedNodes);
10024
+ for (const rect of this.#rects()) {
10025
+ if (!this.#canSelect(rect.node)) continue;
10026
+ this._selectedNodes.add(rect.node);
10027
+ }
10028
+ this.selectedNode = [...this._selectedNodes].at(-1);
10029
+ this.#dispatchSelect(prev);
10030
+ this.requestUpdate();
10031
+ }
10032
+ deselectAll() {
10033
+ const prev = new Set(this._selectedNodes);
10034
+ this.#clearSelectionInternal();
10035
+ this.#dispatchSelect(prev);
10036
+ this.requestUpdate();
10037
+ }
10038
+ /** Replace the selection with `nodes` (subject to `selectionFilter`) — search "select" mode. */
10039
+ selectMany(nodes) {
10040
+ const prev = new Set(this._selectedNodes);
10041
+ this._selectedNodes = new Set([...nodes].filter((n) => this.#canSelect(n)));
10042
+ this.selectedNode = [...this._selectedNodes].at(-1);
10043
+ this.#dispatchSelect(prev);
10044
+ this.requestUpdate();
10045
+ }
10046
+ /**
10047
+ * Drill down so `node` becomes the visual root (ancestors move to the
10048
+ * breadcrumb strip). Pure view-state — `root` is never mutated.
10049
+ * Fires `zoom` and the semantic `expand` event.
10050
+ */
10051
+ zoomTo(node) {
10052
+ if (this._zoomKey === node.item.key) return;
10053
+ this.#pendingFlip = this.#planFlip(node.item.key);
10054
+ this._zoomKey = node.item.key;
10055
+ this.tickled.clearTickled();
10056
+ const depth = findTreemapPath(this.#effectiveRoot, node.item.key)?.length;
10057
+ const absoluteDepth = depth === void 0 ? -1 : depth - 2;
10058
+ this.dispatchEvent(new CustomEvent(`zoom`, {
10059
+ detail: {
10060
+ node,
10061
+ depth: absoluteDepth
10062
+ },
10063
+ bubbles: true,
10064
+ composed: true
10065
+ }));
10066
+ this.dispatchEvent(new CustomEvent(`expand`, {
10067
+ detail: {
10068
+ node,
10069
+ depth: absoluteDepth
10070
+ },
10071
+ bubbles: true,
10072
+ composed: true
10073
+ }));
10074
+ this.requestUpdate();
10075
+ }
10076
+ /**
10077
+ * Zoom back out to the full tree. Fires `zoom` (with `node: undefined`) and
10078
+ * the semantic `collapse` event for the previous zoom root.
10079
+ */
10080
+ zoomOut() {
10081
+ if (this._zoomKey === void 0) return;
10082
+ this.#pendingFlip = this.#planFlip(void 0);
10083
+ const previous = this.focusedNode;
10084
+ const path = this._zoomKey === void 0 ? void 0 : findTreemapPath(this.#effectiveRoot, this._zoomKey);
10085
+ const absoluteDepth = path === void 0 ? -1 : path.length - 2;
10086
+ this._zoomKey = void 0;
10087
+ this.tickled.clearTickled();
10088
+ this.dispatchEvent(new CustomEvent(`zoom`, {
10089
+ detail: {
10090
+ node: void 0,
10091
+ depth: -1
10092
+ },
10093
+ bubbles: true,
10094
+ composed: true
10095
+ }));
10096
+ if (previous) this.dispatchEvent(new CustomEvent(`collapse`, {
10097
+ detail: {
10098
+ node: previous,
10099
+ depth: absoluteDepth
10100
+ },
10101
+ bubbles: true,
10102
+ composed: true
10103
+ }));
10104
+ this.requestUpdate();
10105
+ }
10106
+ /**
10107
+ * Work out (before `_zoomKey` changes) whether the upcoming zoom should
10108
+ * animate, and how — see `FlipPlan`. Returns `undefined` to skip the
10109
+ * animation: reduced-motion is requested, or the node needed to anchor it
10110
+ * isn't in either the old or new layout's visible window.
10111
+ */
10112
+ #planFlip(targetKey) {
10113
+ if (typeof matchMedia === `function` && matchMedia(`(prefers-reduced-motion: reduce)`).matches) return void 0;
10114
+ if (targetKey !== void 0) {
10115
+ const visible = this.#rects().find((r) => r.node.item.key === targetKey);
10116
+ if (visible) return {
10117
+ direction: `in`,
10118
+ rect: {
10119
+ x: visible.x,
10120
+ y: visible.y,
10121
+ width: visible.width,
10122
+ height: visible.height
10123
+ }
10124
+ };
10125
+ }
10126
+ const fromKey = this._zoomKey;
10127
+ if (fromKey === void 0) return void 0;
10128
+ const landing = this.#rectsForZoom(targetKey).find((r) => r.node.item.key === fromKey);
10129
+ if (!landing) return void 0;
10130
+ return {
10131
+ direction: `out`,
10132
+ rect: {
10133
+ x: landing.x,
10134
+ y: landing.y,
10135
+ width: landing.width,
10136
+ height: landing.height
10137
+ }
10138
+ };
10139
+ }
10140
+ /** Apply a planned FLIP animation once the new layout has rendered — see `FlipPlan`. */
10141
+ #applyFlip(flip) {
10142
+ const inner = this.renderRoot.querySelector(`.viewport-inner`);
10143
+ const viewport = this.renderRoot.querySelector(`.viewport`);
10144
+ if (!(inner instanceof HTMLElement) || !(viewport instanceof HTMLElement)) return;
10145
+ const vw = viewport.clientWidth;
10146
+ const vh = viewport.clientHeight;
10147
+ if (vw <= 0 || vh <= 0) return;
10148
+ const { rect, direction } = flip;
10149
+ const startTransform = direction === `in` ? `translate(${rect.x * vw}px, ${rect.y * vh}px) scale(${Math.max(rect.width, 1e-4)}, ${Math.max(rect.height, 1e-4)})` : (() => {
10150
+ const sx = rect.width > 0 ? 1 / rect.width : 1;
10151
+ const sy = rect.height > 0 ? 1 / rect.height : 1;
10152
+ return `translate(${-rect.x * sx * vw}px, ${-rect.y * sy * vh}px) scale(${sx}, ${sy})`;
10153
+ })();
10154
+ inner.classList.remove(`flipping`);
10155
+ inner.style.transform = startTransform;
10156
+ inner.offsetWidth;
10157
+ requestAnimationFrame(() => {
10158
+ inner.classList.add(`flipping`);
10159
+ inner.style.transform = `none`;
10160
+ });
10161
+ inner.addEventListener(`transitionend`, () => inner.classList.remove(`flipping`), { once: true });
10162
+ }
10163
+ #canZoom(node) {
10164
+ if (node.item.isLeaf === true) return false;
10165
+ if (node.children !== void 0) return this.hideLeaves ? node.children.some((n) => n.item.isLeaf !== true) : node.children.length > 0;
10166
+ return this.model !== void 0 || this.loadChildren !== void 0;
10167
+ }
10168
+ /**
10169
+ * Zoom so the node at `keys` (path from the root's children) is visible,
10170
+ * then select it. Returns `true` if the path was found.
10171
+ */
10172
+ navigateTo(keys) {
10173
+ const root = this.#effectiveRoot;
10174
+ if (!root?.children) return false;
10175
+ let current = root;
10176
+ const path = [];
10177
+ for (const key of keys) {
10178
+ const found = current.children?.find((n) => n.item.key === key);
10179
+ if (!found) return false;
10180
+ path.push(found);
10181
+ current = found;
10182
+ }
10183
+ const target = path.at(-1);
10184
+ if (path.length > this.maxDepth) this.zoomTo(path[path.length - this.maxDepth - 1]);
10185
+ else if (this._zoomKey !== void 0) this.zoomOut();
10186
+ this.select(target);
10187
+ this.tickled.setTickled(target.item.key);
10188
+ this.requestUpdate();
10189
+ return true;
10190
+ }
10191
+ /**
10192
+ * Zoom + select the first node for which `predicate` returns `true`.
10193
+ * Returns `true` if a matching node was found.
10194
+ */
10195
+ navigateToNode(predicate) {
10196
+ const root = this.#effectiveRoot;
10197
+ if (!root?.children) return false;
10198
+ const keyPath = this.#findKeyPath(root.children, predicate);
10199
+ if (!keyPath) return false;
10200
+ return this.navigateTo(keyPath);
10201
+ }
10202
+ /**
10203
+ * Zoom so the node with `key` is visible and move the keyboard cursor to it,
10204
+ * without changing the selection. Used by incremental-search "navigate" mode.
10205
+ */
10206
+ revealKey(key) {
10207
+ const root = this.#effectiveRoot;
10208
+ if (!root?.children) return false;
10209
+ const path = this.#findKeyPath(root.children, (n) => n.item.key === key);
10210
+ if (!path) return false;
10211
+ if (path.length > this.maxDepth) {
10212
+ const ancestor = this.#resolveKeyPath(path.slice(0, path.length - this.maxDepth));
10213
+ if (ancestor) this.zoomTo(ancestor);
10214
+ }
10215
+ this.tickled.setTickled(key);
10216
+ this.requestUpdate();
10217
+ return true;
10218
+ }
10219
+ #resolveKeyPath(keys) {
10220
+ let current = this.#effectiveRoot;
10221
+ for (const key of keys) {
10222
+ const found = key === current?.item.key ? current : current?.children?.find((n) => n.item.key === key);
10223
+ if (!found) return void 0;
10224
+ current = found;
10225
+ }
10226
+ return current;
10227
+ }
10228
+ #findKeyPath(nodes, predicate, prefix = []) {
10229
+ for (const node of nodes) {
10230
+ const path = [...prefix, node.item.key];
10231
+ if (predicate(node)) return path;
10232
+ if (node.children?.length) {
10233
+ const hit = this.#findKeyPath(node.children, predicate, path);
10234
+ if (hit) return hit;
10235
+ }
8028
10236
  }
8029
- const btnAllowance = inOverflow.length > 0 ? gap + btnSize : 0;
8030
- while (inMain.length > 0 && cum + btnAllowance > available) {
8031
- const last = inMain[inMain.length - 1];
8032
- const s = this._sizes.get(last) ?? 0;
8033
- last.setAttribute(`slot`, `overflow`);
8034
- inMain.pop();
8035
- inOverflow.unshift(last);
8036
- cum -= (inMain.length ? gap : 0) + s;
8037
- mutated = true;
10237
+ }
10238
+ /**
10239
+ * All nodes currently visible as tiles, in render (pre-order) order,
10240
+ * with absolute depth (root's children are depth 0).
10241
+ */
10242
+ getNodes() {
10243
+ return this.#rects().map((r) => ({
10244
+ node: r.node,
10245
+ depth: r.depth
10246
+ }));
10247
+ }
10248
+ #matchesFilter(node, depth) {
10249
+ if (!this.filterPredicate) return true;
10250
+ if (this.filterPredicate(node, depth)) return true;
10251
+ return node.children?.some((child) => this.#matchesFilter(child, depth + 1)) ?? false;
10252
+ }
10253
+ #rectCenter(rect) {
10254
+ return {
10255
+ x: rect.x + rect.width / 2,
10256
+ y: rect.y + rect.height / 2
10257
+ };
10258
+ }
10259
+ /**
10260
+ * Geometrically nearest sibling tile in `direction` from `current`
10261
+ * (nearest-neighbour by rect center). Falls back to the parent tile
10262
+ * (Up/Left) or first-child tile (Down/Right) when no sibling lies in that
10263
+ * direction, so every visible tile stays keyboard-reachable.
10264
+ */
10265
+ #nearestInDirection(current, direction) {
10266
+ const rects = this.#rects();
10267
+ const center = this.#rectCenter(current);
10268
+ const siblings = rects.filter((r) => r !== current && r.relativeDepth === current.relativeDepth && r.parentKey === current.parentKey);
10269
+ let best;
10270
+ let bestDistance = Number.POSITIVE_INFINITY;
10271
+ for (const candidate of siblings) {
10272
+ const c = this.#rectCenter(candidate);
10273
+ const dx = c.x - center.x;
10274
+ const dy = c.y - center.y;
10275
+ if (!(direction === `left` ? dx < 0 : direction === `right` ? dx > 0 : direction === `up` ? dy < 0 : dy > 0)) continue;
10276
+ const distance = Math.hypot(dx, dy);
10277
+ if (distance < bestDistance) {
10278
+ bestDistance = distance;
10279
+ best = candidate;
10280
+ }
8038
10281
  }
8039
- while (inOverflow.length > 0) {
8040
- const first = inOverflow[0];
8041
- const s = this._sizes.get(first);
8042
- if (s === void 0) break;
8043
- const addGap = inMain.length ? gap : 0;
8044
- const nextBtnAllowance = inOverflow.length > 1 ? gap + btnSize : 0;
8045
- if (cum + addGap + s + nextBtnAllowance > available) break;
8046
- first.removeAttribute(`slot`);
8047
- inOverflow.shift();
8048
- inMain.push(first);
8049
- cum += addGap + s;
8050
- mutated = true;
10282
+ if (best) return best;
10283
+ if (direction === `up` || direction === `left`) return current.parentKey === void 0 ? void 0 : rects.find((r) => r.node.item.key === current.parentKey);
10284
+ return rects.find((r) => r.parentKey === current.node.item.key);
10285
+ }
10286
+ #handleItemClick(rect, index, event) {
10287
+ this.dispatchEvent(new CustomEvent(`item-click`, {
10288
+ detail: {
10289
+ node: rect.node,
10290
+ depth: rect.depth
10291
+ },
10292
+ bubbles: true,
10293
+ composed: true
10294
+ }));
10295
+ if (this.interactionMode === `standard` && (event.ctrlKey || event.metaKey)) {
10296
+ if (this._selectedNodes.has(rect.node)) this.deselect(rect.node);
10297
+ else if (this.#canSelect(rect.node)) {
10298
+ const prev = new Set(this._selectedNodes);
10299
+ this._selectedNodes.add(rect.node);
10300
+ this.selectedNode = rect.node;
10301
+ this.#dispatchSelect(prev);
10302
+ this.requestUpdate();
10303
+ }
10304
+ this.#anchorIndex = index;
10305
+ } else if (this.interactionMode === `standard` && event.shiftKey && this.#anchorIndex !== void 0) {
10306
+ const rects = this.#rects();
10307
+ const from = Math.min(this.#anchorIndex, index);
10308
+ const to = Math.max(this.#anchorIndex, index);
10309
+ const prev = new Set(this._selectedNodes);
10310
+ this._selectedNodes = new Set(rects.slice(from, to + 1).map((r) => r.node).filter((n) => this.#canSelect(n)));
10311
+ this.selectedNode = [...this._selectedNodes].at(-1);
10312
+ this.#dispatchSelect(prev);
10313
+ this.requestUpdate();
10314
+ } else if (this.interactionMode !== `manual`) {
10315
+ this.select(rect.node);
10316
+ this.#anchorIndex = index;
8051
10317
  }
8052
- const newCount = inOverflow.length;
8053
- if (newCount !== this._overflowCount) {
8054
- this._overflowCount = newCount;
8055
- mutated = true;
10318
+ this.tickled.setTickled(rect.node.item.key);
10319
+ if (this.#canZoom(rect.node)) this.zoomTo(rect.node);
10320
+ }
10321
+ #handleItemDblClick(rect) {
10322
+ this.dispatchEvent(new CustomEvent(`item-dblclick`, {
10323
+ detail: {
10324
+ node: rect.node,
10325
+ depth: rect.depth
10326
+ },
10327
+ bubbles: true,
10328
+ composed: true
10329
+ }));
10330
+ this.dispatchEvent(new CustomEvent(`activate`, {
10331
+ detail: {
10332
+ node: rect.node,
10333
+ depth: rect.depth
10334
+ },
10335
+ bubbles: true,
10336
+ composed: true
10337
+ }));
10338
+ }
10339
+ #handleKeyDown(event) {
10340
+ if (this.disableKeyboardNavigation) return;
10341
+ const rects = this.#rects();
10342
+ if (rects.length === 0) return;
10343
+ const tickledKey = this.tickled.tickledId;
10344
+ const currentIndex = tickledKey === void 0 ? -1 : rects.findIndex((r) => r.node.item.key === tickledKey);
10345
+ const current = currentIndex >= 0 ? rects[currentIndex] : void 0;
10346
+ const moveTo = (rect) => {
10347
+ if (!rect) return;
10348
+ event.preventDefault();
10349
+ this.tickled.setTickled(rect.node.item.key);
10350
+ if (this.interactionMode === `implicit`) this.select(rect.node);
10351
+ };
10352
+ switch (event.key) {
10353
+ case `ArrowLeft`:
10354
+ moveTo(current ? this.#nearestInDirection(current, `left`) : rects[0]);
10355
+ return;
10356
+ case `ArrowRight`:
10357
+ moveTo(current ? this.#nearestInDirection(current, `right`) : rects[0]);
10358
+ return;
10359
+ case `ArrowUp`:
10360
+ moveTo(current ? this.#nearestInDirection(current, `up`) : rects[0]);
10361
+ return;
10362
+ case `ArrowDown`:
10363
+ moveTo(current ? this.#nearestInDirection(current, `down`) : rects[0]);
10364
+ return;
10365
+ case `Home`:
10366
+ moveTo(rects[0]);
10367
+ return;
10368
+ case `End`:
10369
+ moveTo(rects.at(-1));
10370
+ return;
10371
+ case `Enter`:
10372
+ if (!current) return;
10373
+ event.preventDefault();
10374
+ if (this.interactionMode !== `manual`) this.select(current.node);
10375
+ this.dispatchEvent(new CustomEvent(`activate`, {
10376
+ detail: {
10377
+ node: current.node,
10378
+ depth: current.depth
10379
+ },
10380
+ bubbles: true,
10381
+ composed: true
10382
+ }));
10383
+ if (this.#canZoom(current.node)) this.zoomTo(current.node);
10384
+ return;
10385
+ case `Backspace`:
10386
+ case `Escape`: if (this._zoomKey !== void 0) {
10387
+ event.preventDefault();
10388
+ this.zoomOut();
10389
+ }
8056
10390
  }
8057
- if (mutated) this.requestUpdate();
8058
10391
  }
8059
- _toggleOverflow() {
8060
- try {
8061
- this._popover.togglePopover();
8062
- } catch {
8063
- this._popover.showPopover();
10392
+ /**
10393
+ * Cmd+wheel zooms: scrolling backward (deltaY < 0, towards the user) zooms
10394
+ * into the tile under the pointer — the same as clicking it; scrolling
10395
+ * forward (deltaY > 0, away from the user) zooms out one level at a time
10396
+ * (see `#zoomOutOneStep`), not straight to the root. Plain wheel (no Cmd)
10397
+ * is left alone for normal page scroll. A short cooldown after each step
10398
+ * keeps a single trackpad gesture (which fires many small wheel events)
10399
+ * from firing a cascade of zoom steps.
10400
+ */
10401
+ #handleWheel(event) {
10402
+ if (!event.metaKey || event.deltaY === 0) return;
10403
+ event.preventDefault();
10404
+ if (performance.now() < this.#wheelCooldownUntil) return;
10405
+ if (event.deltaY > 0) {
10406
+ if (this._zoomKey === void 0) return;
10407
+ this.#wheelCooldownUntil = performance.now() + 260;
10408
+ this.#zoomOutOneStep();
10409
+ return;
8064
10410
  }
10411
+ const key = (event.target?.closest(`.tile`))?.getAttribute(`data-key`) ?? void 0;
10412
+ if (key === void 0) return;
10413
+ const rect = this.#rects().find((r) => r.node.item.key === key);
10414
+ if (!rect || !this.#canZoom(rect.node)) return;
10415
+ this.#wheelCooldownUntil = performance.now() + 260;
10416
+ this.zoomTo(rect.node);
10417
+ }
10418
+ /** Zoom to the current zoom node's parent, or fully out when it's a direct child of the root. */
10419
+ #zoomOutOneStep() {
10420
+ const path = this._zoomKey === void 0 ? void 0 : findTreemapPath(this.#effectiveRoot, this._zoomKey);
10421
+ const parent = path && path.length > 2 ? path[path.length - 2] : void 0;
10422
+ if (parent) this.zoomTo(parent);
10423
+ else this.zoomOut();
10424
+ }
10425
+ #renderBreadcrumb() {
10426
+ const zoomNode = this.focusedNode;
10427
+ if (zoomNode === void 0) return nothing;
10428
+ return html`
10429
+ <nav class="breadcrumb" part="breadcrumb" aria-label="Treemap zoom path">
10430
+ <ixfx-crumb-navigation
10431
+ with-up
10432
+ .root=${this.#effectiveRoot}
10433
+ .selectedNode=${zoomNode}
10434
+ @select=${this.#onCrumbSelect}
10435
+ @item-click=${(e) => e.stopPropagation()}
10436
+ @tickle=${(e) => e.stopPropagation()}
10437
+ @expand=${(e) => e.stopPropagation()}
10438
+ ></ixfx-crumb-navigation>
10439
+ </nav>
10440
+ `;
8065
10441
  }
8066
- render() {
8067
- const overflowMode = getComputedStyle(this).getPropertyValue(`--toolbar-overflow`).trim();
8068
- const hasOverflow = this._overflowCount > 0 || overflowMode === `collapsed`;
10442
+ /**
10443
+ * Translate crumb-trail selection (crumb clicks + the up-button) into zoom
10444
+ * state. The event is stopped: crumb clicks must not bubble as our `select`
10445
+ * (the selection set is unchanged by them).
10446
+ */
10447
+ #onCrumbSelect(event) {
10448
+ event.stopPropagation();
10449
+ const node = [...event.detail.selected].at(0);
10450
+ if (!node) return;
10451
+ if (node.item.key === this.#effectiveRoot?.item.key) this.zoomOut();
10452
+ else this.zoomTo(node);
10453
+ }
10454
+ /** On-screen size (px) of `rect`, or `undefined` before the host has been measured. */
10455
+ #pixelSize(rect) {
10456
+ const hostWidth = this.clientWidth || 0;
10457
+ const hostHeight = this.clientHeight || 0;
10458
+ if (hostWidth <= 0 || hostHeight <= 0) return void 0;
10459
+ return {
10460
+ width: rect.width * hostWidth,
10461
+ height: rect.height * hostHeight
10462
+ };
10463
+ }
10464
+ /** Hide when too small by area, or too narrow to fit any readable text even truncated. */
10465
+ #hideLabel(rect) {
10466
+ const size = this.#pixelSize(rect);
10467
+ if (size === void 0) return false;
10468
+ return size.width * size.height < this.minLabelArea || size.width < this.minTileWidth;
10469
+ }
10470
+ /** Whether `rect` is large enough on-screen for its own children to be subdivided and drawn. */
10471
+ #canDescend(rect) {
10472
+ const size = this.#pixelSize(rect);
10473
+ if (size === void 0) return true;
10474
+ return size.width * size.height >= this.minChildArea && size.width >= this.minTileWidth;
10475
+ }
10476
+ /** Slugify a category name into a CSS custom-property-safe token. */
10477
+ #categorySlug(category) {
10478
+ return category.replace(/[^\w-]/g, `-`);
10479
+ }
10480
+ /**
10481
+ * Inline colour overrides for `node`'s category, or `undefined` for none.
10482
+ * Set as real `background-color`/`color` (not custom properties) so they
10483
+ * simply win over `.tile`'s CSS-variable-driven defaults by specificity —
10484
+ * no risk of an unresolvable `var()` chain leaving the property invalid.
10485
+ */
10486
+ #categoryStyle(node) {
10487
+ const category = this.categoryAccessor?.(node.item);
10488
+ if (!category) return void 0;
10489
+ const slug = this.#categorySlug(category);
10490
+ return `background-color:var(--treemap-category-${slug}-bg, var(--treemap-tile-bg));color:var(--treemap-category-${slug}-text, var(--treemap-tile-text));`;
10491
+ }
10492
+ #renderTileBox(rect, positionStyle, hasVisibleChildren, childrenContent) {
10493
+ const { node } = rect;
10494
+ const selected = this._selectedNodes.has(node);
10495
+ const tickled = this.tickled.isTickled(node.item.key);
10496
+ const filtered = !this.#matchesFilter(node, rect.depth);
10497
+ const pending = this.#pendingKeys.has(node.item.key);
10498
+ const hideLabel = this.#hideLabel(rect);
10499
+ const showHeader = hasVisibleChildren && !hideLabel;
10500
+ const category = this.categoryAccessor?.(node.item);
10501
+ const categoryStyle = this.#categoryStyle(node);
8069
10502
  return html`
8070
- <div class="inner" role="toolbar" aria-orientation=${this.orientation}>
8071
- <slot></slot>
8072
- <ixfx-button class="overflow-btn" ?hidden=${!hasOverflow} @click=${this._toggleOverflow} aria-label="More" aria-haspopup="true" aria-expanded=${this._popover?.matches(`:popover-open`) ?? false}>⋯</ixfx-button>
10503
+ <div
10504
+ class="item tile ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${filtered ? `filtered` : ``} ${pending ? `loading` : ``} ${rect.width <= 0 || rect.height <= 0 ? `empty` : ``}"
10505
+ data-key=${node.item.key}
10506
+ data-depth=${rect.relativeDepth}
10507
+ data-category=${category ?? nothing}
10508
+ role="treeitem"
10509
+ tabindex="-1"
10510
+ aria-level=${rect.depth + 1}
10511
+ aria-selected=${selected}
10512
+ aria-setsize=${rect.setSize}
10513
+ aria-posinset=${rect.index + 1}
10514
+ aria-label=${node.item.label}
10515
+ title=${node.item.label}
10516
+ part="tile"
10517
+ style=${categoryStyle ? `${positionStyle};${categoryStyle}` : positionStyle}
10518
+ @click=${(e) => {
10519
+ e.stopPropagation();
10520
+ this.#handleItemClick(rect, this.#rects().findIndex((r) => r.node.item.key === node.item.key), e);
10521
+ }}
10522
+ @dblclick=${(e) => {
10523
+ e.stopPropagation();
10524
+ this.#handleItemDblClick(rect);
10525
+ }}
10526
+ @pointerenter=${() => this.tickled.handlePointerEnter(node.item.key)}
10527
+ >
10528
+ ${showHeader ? html`<div class="tile-header" part="tile-header"><span class="treemap-label" part="label">${node.item.label}</span></div>` : html`<span class="treemap-label ${hideLabel ? `hide-label` : ``}" part="label">${node.item.label}</span>`}
10529
+ ${childrenContent}
8073
10530
  </div>
8074
- <div class="overflow-popover popup-surface" popover="auto" @click=${(e) => e.stopPropagation()}>
8075
- <slot name="overflow"></slot>
10531
+ `;
10532
+ }
10533
+ /**
10534
+ * Render `rect` plus (if it's large enough to descend into — see
10535
+ * `#canDescend`) a `.tile-children` holding its own children, each
10536
+ * positioned relative to `rect` itself and recursing the same way.
10537
+ *
10538
+ * Each level gets its own real `.tile-header` + `.tile-children` box, so a
10539
+ * header reserves its own space via normal box-model layout (flex column:
10540
+ * header, then the remaining space for children) at *every* level — not
10541
+ * just the top one. That matters because layout rects are purely
10542
+ * geometric (no allowance for header height), so a folder whose one child
10543
+ * fills ~100% of its area gets a child rect nearly identical to its own;
10544
+ * positioning that child against its direct parent (not some flattened
10545
+ * shared ancestor) is what keeps the parent's header from being painted
10546
+ * over by an opaque, near-identically-sized child box.
10547
+ */
10548
+ #renderTile(rect, positionStyle, byParent) {
10549
+ const children = rect.hasChildren && this.#canDescend(rect) ? byParent.get(rect.node.item.key) ?? [] : [];
10550
+ const childrenContent = children.length > 0 ? html`
10551
+ <div class="tile-children" role="group">
10552
+ ${repeat(children, (r) => r.node.item.key, (r) => this.#renderNestedTile(r, rect, byParent))}
10553
+ </div>
10554
+ ` : nothing;
10555
+ return this.#renderTileBox(rect, positionStyle, children.length > 0, childrenContent);
10556
+ }
10557
+ /** A child tile, positioned as a percentage of `parent`'s own box. */
10558
+ #renderNestedTile(rect, parent, byParent) {
10559
+ const relX = parent.width > 0 ? (rect.x - parent.x) / parent.width : 0;
10560
+ const relY = parent.height > 0 ? (rect.y - parent.y) / parent.height : 0;
10561
+ const relW = parent.width > 0 ? rect.width / parent.width : 0;
10562
+ const relH = parent.height > 0 ? rect.height / parent.height : 0;
10563
+ const style = `left:${(relX * 100).toFixed(4)}%;top:${(relY * 100).toFixed(4)}%;width:${(relW * 100).toFixed(4)}%;height:${(relH * 100).toFixed(4)}%`;
10564
+ return this.#renderTile(rect, style, byParent);
10565
+ }
10566
+ #renderTopTile(rect, byParent) {
10567
+ const style = `left:${(rect.x * 100).toFixed(4)}%;top:${(rect.y * 100).toFixed(4)}%;width:${(rect.width * 100).toFixed(4)}%;height:${(rect.height * 100).toFixed(4)}%`;
10568
+ return this.#renderTile(rect, style, byParent);
10569
+ }
10570
+ render() {
10571
+ const rects = this.#rects();
10572
+ const byParent = /* @__PURE__ */ new Map();
10573
+ for (const rect of rects) {
10574
+ if (rect.parentKey === void 0) continue;
10575
+ const list = byParent.get(rect.parentKey) ?? [];
10576
+ list.push(rect);
10577
+ byParent.set(rect.parentKey, list);
10578
+ }
10579
+ const zoomKey = this._zoomKey ?? this.#effectiveRoot?.item.key;
10580
+ const topLevel = zoomKey === void 0 ? rects.filter((r) => r.relativeDepth === 0) : byParent.get(zoomKey) ?? rects.filter((r) => r.relativeDepth === 0);
10581
+ return html`
10582
+ <div
10583
+ class="treemap"
10584
+ role="tree"
10585
+ tabindex="0"
10586
+ data-input-mode=${this.tickled.inputMode}
10587
+ style=${`--treemap-padding:${this.padding}px`}
10588
+ @keydown=${(e) => this.#handleKeyDown(e)}
10589
+ @pointerleave=${() => this.tickled.clearTickled()}
10590
+ @wheel=${(e) => this.#handleWheel(e)}
10591
+ >
10592
+ ${this.#renderBreadcrumb()}
10593
+ ${rects.length === 0 ? html`<div class="empty-state">No data</div>` : html`
10594
+ <div class="viewport" part="viewport">
10595
+ <div class="viewport-inner">
10596
+ ${repeat(topLevel, (r) => r.node.item.key, (r) => this.#renderTopTile(r, byParent))}
10597
+ </div>
10598
+ </div>
10599
+ `}
8076
10600
  </div>
10601
+ ${this.search.renderField()}
8077
10602
  `;
8078
10603
  }
10604
+ static {
10605
+ this.styles = [
10606
+ themeFallbacks,
10607
+ tickledStyles,
10608
+ tickledItemStyles,
10609
+ listSearchStyles,
10610
+ css`
10611
+ :host {
10612
+ --treemap-height: 280px;
10613
+ --treemap-gap: 2px;
10614
+ --treemap-padding: 4px;
10615
+ --treemap-tile-bg: var(--surface-4);
10616
+ --treemap-tile-text: var(--surface-4-text);
10617
+ --treemap-tile-border: var(--border-subtle);
10618
+ --treemap-header-bg: rgb(0 0 0 / 0.12);
10619
+ --treemap-font-size: var(--text-s);
10620
+ --treemap-zoom-duration: 220ms;
10621
+
10622
+ display: block;
10623
+ overflow: hidden;
10624
+ }
10625
+
10626
+ /* Per-category tile colours (set categoryAccessor to opt in): define
10627
+ --treemap-category-SLUG-bg / -text at whatever scope suits (:host
10628
+ here, or globally) for each category your accessor can return —
10629
+ e.g. --treemap-category-urgent-bg: oklch(55% 0.18 25). A category
10630
+ with no matching pair falls back to --treemap-tile-bg/-text. */
10631
+
10632
+ .treemap {
10633
+ display: flex;
10634
+ flex-direction: column;
10635
+ gap: var(--space-xs);
10636
+ outline: none;
10637
+ height: 100%;
10638
+ }
10639
+
10640
+ .breadcrumb {
10641
+ display: flex;
10642
+ align-items: center;
10643
+ padding: var(--space-xs) var(--space-s);
10644
+ background: var(--surface-3);
10645
+ color: var(--surface-3-text);
10646
+ border: var(--border-subtle);
10647
+ border-radius: var(--radius-m);
10648
+ font-size: var(--text-s);
10649
+ flex-shrink: 0;
10650
+ }
10651
+
10652
+ .breadcrumb ixfx-crumb-navigation {
10653
+ flex: 1;
10654
+ min-width: 0;
10655
+ }
10656
+
10657
+ .viewport {
10658
+ position: relative;
10659
+ width: 100%;
10660
+ /* flex-basis of --treemap-height (its size when nothing forces a
10661
+ different one — the common case of an auto-height host) but
10662
+ allowed to grow/shrink like any flex item, so it shares the
10663
+ .treemap column's height with the breadcrumb instead of a plain
10664
+ fixed height always claiming that full amount regardless of what
10665
+ else needs space — which is what let the breadcrumb push the
10666
+ host past its own height and scroll once a zoom made it appear. */
10667
+ flex: 1 1 var(--treemap-height);
10668
+ min-height: 0;
10669
+ overflow: hidden;
10670
+ border-radius: var(--radius-m);
10671
+ }
10672
+
10673
+ /* Transform target for the zoom-in/zoom-out FLIP animation (see
10674
+ #planFlip/#applyFlip) — kept separate from .viewport so the
10675
+ transform never fights that element's own sizing/overflow. */
10676
+ .viewport-inner {
10677
+ position: absolute;
10678
+ inset: 0;
10679
+ transform-origin: 0 0;
10680
+ }
10681
+
10682
+ .viewport-inner.flipping {
10683
+ transition: transform var(--treemap-zoom-duration, 220ms) ease-out;
10684
+ }
10685
+
10686
+ .tile {
10687
+ position: absolute;
10688
+ box-sizing: border-box;
10689
+ overflow: hidden;
10690
+ display: flex;
10691
+ flex-direction: column;
10692
+ /* Override the shared .item class's align-items:center (built for
10693
+ icon+label rows) — tiles need their flex children to stretch to
10694
+ full width, or .tile-children collapses to its (empty) content
10695
+ width and every nested tile collapses with it. */
10696
+ align-items: stretch;
10697
+ white-space: normal;
10698
+ background: var(--treemap-tile-bg);
10699
+ color: var(--treemap-tile-text);
10700
+ border: var(--treemap-tile-border);
10701
+ font-size: var(--treemap-font-size);
10702
+ cursor: pointer;
10703
+ transition:
10704
+ opacity var(--transition),
10705
+ background-color var(--transition);
10706
+ }
10707
+
10708
+ .tile-children {
10709
+ position: relative;
10710
+ flex: 1;
10711
+ min-height: 0;
10712
+ margin: var(--treemap-padding);
10713
+ margin-top: 0;
10714
+ }
10715
+
10716
+ .tile-header {
10717
+ flex-shrink: 0;
10718
+ padding: 1px var(--space-xs);
10719
+ background: var(--treemap-header-bg);
10720
+ overflow: hidden;
10721
+ text-overflow: ellipsis;
10722
+ white-space: nowrap;
10723
+ font-weight: var(--font-weight-bold);
10724
+ }
10725
+
10726
+ .tile > .treemap-label {
10727
+ padding: var(--space-xs);
10728
+ overflow: hidden;
10729
+ text-overflow: ellipsis;
10730
+ white-space: nowrap;
10731
+ }
10732
+
10733
+ .tile-header .treemap-label {
10734
+ overflow: hidden;
10735
+ text-overflow: ellipsis;
10736
+ white-space: nowrap;
10737
+ }
10738
+
10739
+ .treemap-label.hide-label {
10740
+ visibility: hidden;
10741
+ }
10742
+
10743
+ .tile.filtered {
10744
+ opacity: 0.3;
10745
+ filter: saturate(0.4);
10746
+ }
10747
+
10748
+ .tile.empty {
10749
+ padding: 0;
10750
+ border: none;
10751
+ }
10752
+
10753
+ .tile.loading {
10754
+ cursor: progress;
10755
+ opacity: 0.6;
10756
+ }
10757
+
10758
+ .empty-state {
10759
+ padding: var(--space-l);
10760
+ color: var(--surface-muted-text);
10761
+ font-size: var(--text-s);
10762
+ }
10763
+ `
10764
+ ];
10765
+ }
8079
10766
  };
10767
+ __decorate([property({ type: Object })], TreemapListElement.prototype, "root", void 0);
10768
+ __decorate([property({ attribute: false })], TreemapListElement.prototype, "model", void 0);
10769
+ __decorate([property({ attribute: false })], TreemapListElement.prototype, "loadChildren", void 0);
8080
10770
  __decorate([property({
8081
- type: String,
8082
- reflect: true
8083
- })], ToolbarElement.prototype, "orientation", void 0);
10771
+ reflect: true,
10772
+ attribute: `exclusivity`
10773
+ })], TreemapListElement.prototype, "exclusivity", void 0);
8084
10774
  __decorate([property({
8085
- type: String,
8086
- reflect: true
8087
- })], ToolbarElement.prototype, "layout", void 0);
10775
+ reflect: true,
10776
+ attribute: `selection-filter`
10777
+ })], TreemapListElement.prototype, "selectionFilter", void 0);
8088
10778
  __decorate([property({
8089
10779
  type: String,
8090
- attribute: `vertical-writing`,
8091
- reflect: true
8092
- })], ToolbarElement.prototype, "verticalWriting", void 0);
10780
+ attribute: `interaction-mode`
10781
+ })], TreemapListElement.prototype, "interactionMode", void 0);
8093
10782
  __decorate([property({
8094
10783
  type: String,
8095
- attribute: `toolbar-align`,
8096
- reflect: true
8097
- })], ToolbarElement.prototype, "toolbarAlign", void 0);
10784
+ attribute: `tiling-method`
10785
+ })], TreemapListElement.prototype, "tilingMethod", void 0);
10786
+ __decorate([property({
10787
+ type: Number,
10788
+ attribute: `max-depth`
10789
+ })], TreemapListElement.prototype, "maxDepth", void 0);
8098
10790
  __decorate([property({
8099
10791
  type: Boolean,
8100
- attribute: `show-group-labels`,
8101
- reflect: true
8102
- })], ToolbarElement.prototype, "showGroupLabels", void 0);
10792
+ attribute: `hide-leaves`
10793
+ })], TreemapListElement.prototype, "hideLeaves", void 0);
10794
+ __decorate([property({
10795
+ type: Number,
10796
+ attribute: `min-label-area`
10797
+ })], TreemapListElement.prototype, "minLabelArea", void 0);
10798
+ __decorate([property({
10799
+ type: Number,
10800
+ attribute: `min-tile-width`
10801
+ })], TreemapListElement.prototype, "minTileWidth", void 0);
10802
+ __decorate([property({ type: Number })], TreemapListElement.prototype, "padding", void 0);
10803
+ __decorate([property({
10804
+ type: Number,
10805
+ attribute: `min-child-area`
10806
+ })], TreemapListElement.prototype, "minChildArea", void 0);
10807
+ __decorate([property({ attribute: false })], TreemapListElement.prototype, "valueAccessor", void 0);
10808
+ __decorate([property({ attribute: false })], TreemapListElement.prototype, "categoryAccessor", void 0);
10809
+ __decorate([property({ attribute: false })], TreemapListElement.prototype, "selectedNode", void 0);
10810
+ __decorate([state()], TreemapListElement.prototype, "_selectedNodes", void 0);
10811
+ __decorate([property({ attribute: false })], TreemapListElement.prototype, "filterPredicate", void 0);
8103
10812
  __decorate([property({
8104
10813
  type: Boolean,
8105
- attribute: `invisible-groups`,
10814
+ attribute: `disable-keyboard-navigation`
10815
+ })], TreemapListElement.prototype, "disableKeyboardNavigation", void 0);
10816
+ __decorate([property({ attribute: false })], TreemapListElement.prototype, "registry", void 0);
10817
+ __decorate([property({
10818
+ type: String,
10819
+ attribute: `search-modes`,
8106
10820
  reflect: true
8107
- })], ToolbarElement.prototype, "invisibleGroups", void 0);
8108
- __decorate([state()], ToolbarElement.prototype, "_overflowCount", void 0);
8109
- __decorate([query(`.inner`)], ToolbarElement.prototype, "_inner", void 0);
8110
- __decorate([query(`.overflow-popover`)], ToolbarElement.prototype, "_popover", void 0);
8111
- __decorate([query(`slot`)], ToolbarElement.prototype, "_slot", void 0);
8112
- ToolbarElement = _ToolbarElement = __decorate([safeCustomElement(`ixfx-toolbar`)], ToolbarElement);
10821
+ })], TreemapListElement.prototype, "searchModes", void 0);
10822
+ __decorate([state()], TreemapListElement.prototype, "_zoomKey", void 0);
10823
+ TreemapListElement = __decorate([safeCustomElement(`ixfx-treemap-list`)], TreemapListElement);
8113
10824
  //#endregion
8114
10825
  //#region src/twosplit-layout/split-layout.ts
8115
10826
  let TwoSplitLayoutElement = class TwoSplitLayoutElement extends LitElement {
@@ -8753,6 +11464,6 @@ function init() {
8753
11464
  console.log(`Init`);
8754
11465
  }
8755
11466
  //#endregion
8756
- export { AcTextElement, AcTokenElement, BottomDrawerElement, 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, ListDragSelectController, ListNativeDragController, 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, TreeDropController, TreeKeyboardNavController, TreeListElement, TwoSplitLayoutElement, VerticalListElement, XyPad, addPanelToStack, animateListItemIn, animateListItemOut, applyAgeModification, applyHighlights, applySnapshot, applyTreeDrop, attachTimelineInteractions, bottomDrawerCommandId, buildHighlightTargets, buildModel, captionForEntry, clearHighlights, collectContainerNames, collectDataResult, collectPanelIds, collectStackPaths, computeColour, computeLeafGeoms, createAcCompletionProvider, createDataListSearchAdapter, createDefaultListDragPreview, createElementIncrSearch, createElementListSearchAdapter, createEmptyLayout, createEmptyStack, createFuzzySearch, createIncrSearch, createPillRenderer, createSplit, createStack, createTreeListAdapter, createTreeListSearchAdapter, createVerticalListAdapter, distribute, dropTargetFor, 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, pruneDraggedDescendants, readBaseColour, registerFontIcon, registerIcon, registerListSearchCommands, removePanelFromStack, resolveCssVars, resolveGrid, resolveLeafPath, resolveTracks, serializeLayout, setActive, setIconFont, startListItemOutAnimation, svgToDataUri, swapLeaves, tickledItemStyles, tickledStyles, timelineStyles, toSnapshot, walk, wrapRoot };
11467
+ export { AcTextElement, AcTokenElement, BottomDrawerElement, 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, IcicleTreeListElement, IncrSearchTreeController as IncrSearchMillerController, IncrSearchTreeController, IxfxHexEditorElement, IxfxIconElement, IxfxTimelineElement, KeyedElementIndex, LIST_ITEM_MOTION_MS, LIST_SEARCH_COMMAND_IDS, LIST_SEARCH_MODES, LabelledRadialInput, LabelledRangeInput, LabelledSliderInput, LedElement, ListDragSelectController, ListNativeDragController, ListSearchController, ListSelectionController, menu_exports as Menu, MillerBaseElement, MillerListElement, MillerTreeController, 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, TreeDropController, TreeKeyboardNavController, TreeListElement, TreemapListElement, TwoSplitLayoutElement, VerticalListElement, XyPad, addPanelToStack, animateListItemIn, animateListItemOut, applyAgeModification, applyHighlights, applySnapshot, applyTreeDrop, attachTimelineInteractions, bottomDrawerCommandId, buildHighlightTargets, buildModel, captionForEntry, clearHighlights, collectContainerNames, collectDataResult, collectPanelIds, collectStackPaths, computeColour, computeIcicleLayout, computeLeafGeoms, computeTreemapLayout, createAcCompletionProvider, createDataListSearchAdapter, createDefaultListDragPreview, createElementIncrSearch, createElementListSearchAdapter, createEmptyLayout, createEmptyStack, createFuzzySearch, createIncrSearch, createPillRenderer, createSplit, createStack, createTreeListAdapter, createTreeListSearchAdapter, createVerticalListAdapter, distribute, dropTargetFor, extractStack, findIcicleNode, findIciclePath, findNode, findParentOf, findStackOf, findStackPathByName, findTreemapNode, findTreemapPath, getActiveDragPanelId, getIcon, getIconEntry, getIconFont, getLeafNeighbours, hasIcon, iconBus, init, insertBeside, isDragging, isMultiMode, isSplit, isStack, keyForElement, listItemFadeKeyframes, listSearchStyles, monitorIndeterminate, moveLeafNodeToEdge, moveLeafStep, normalise, normaliseValue, normalize, panelGapIndex, parseColourScaleAttr, parseLayout, pointerOverAnyHost, prefersReducedMotion, pruneDraggedDescendants, readBaseColour, registerFontIcon, registerIcon, registerListSearchCommands, removePanelFromStack, resolveCssVars, resolveGrid, resolveLeafPath, resolveTracks, serializeLayout, setActive, setIconFont, startListItemOutAnimation, svgToDataUri, swapLeaves, tickledItemStyles, tickledStyles, timelineStyles, toSnapshot, toolbarGapIndex, walk, wrapRoot };
8757
11468
 
8758
11469
  //# sourceMappingURL=index.js.map