@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.
- package/bundle/index.d.ts +807 -5
- package/bundle/index.d.ts.map +1 -1
- package/bundle/index.js +8928 -5526
- package/bundle/index.js.map +1 -1
- package/bundle/style.css +38 -85
- package/dist/crumbs.d.ts +1 -1
- package/dist/{data-grid-BUeZjnvj.d.ts → data-grid-BT1d_nlu.d.ts} +4 -3
- package/dist/{data-grid-BUeZjnvj.d.ts.map → data-grid-BT1d_nlu.d.ts.map} +1 -1
- package/dist/data-grid.d.ts +2 -2
- package/dist/data-grid.js +3 -1
- package/dist/data-grid.js.map +1 -1
- package/dist/{detail-list-CQguZg9h.d.ts → detail-list-BCaIN7nx.d.ts} +5 -4
- package/dist/detail-list-BCaIN7nx.d.ts.map +1 -0
- package/dist/detail-list.d.ts +3 -3
- package/dist/detail-list.js +3 -1
- package/dist/detail-list.js.map +1 -1
- package/dist/{grid-list-lTwg4lp7.d.ts → grid-list-CiwlNE4c.d.ts} +28 -4
- package/dist/{grid-list-lTwg4lp7.d.ts.map → grid-list-CiwlNE4c.d.ts.map} +1 -1
- package/dist/{grid-list-40blkeza.js → grid-list-DLfw5HI4.js} +68 -5
- package/dist/grid-list-DLfw5HI4.js.map +1 -0
- package/dist/grid-list.d.ts +2 -2
- package/dist/grid-list.js +1 -1
- package/dist/incr-search.d.ts +1 -1
- package/dist/{index--qnhBWZJ.d.ts → index-DPFrRhKL.d.ts} +2 -2
- package/dist/{index--qnhBWZJ.d.ts.map → index-DPFrRhKL.d.ts.map} +1 -1
- package/dist/index.d.ts +682 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2988 -277
- package/dist/index.js.map +1 -1
- package/dist/{list-native-drag-controller-B0hWdbtM.js → list-native-drag-controller-B9xO0XWn.js} +41 -8
- package/dist/list-native-drag-controller-B9xO0XWn.js.map +1 -0
- package/dist/{list-native-drag-controller-Cmpx7jnx.d.ts → list-native-drag-controller-BAuWAtyQ.d.ts} +3 -2
- package/dist/list-native-drag-controller-BAuWAtyQ.d.ts.map +1 -0
- package/dist/miller.d.ts +2 -2
- package/dist/style.css +38 -85
- package/dist/{tree-CVj7TIk2.js → tree-BuzWn18_.js} +4 -2
- package/dist/tree-BuzWn18_.js.map +1 -0
- package/dist/{tree-component-BW2iCFq9.d.ts → tree-component-DGmKBbpy.d.ts} +17 -2
- package/dist/tree-component-DGmKBbpy.d.ts.map +1 -0
- package/dist/tree.d.ts +4 -3
- package/dist/tree.d.ts.map +1 -1
- package/dist/tree.js +1 -1
- package/dist/{vertical-list-CHrXmmQ3.d.ts → vertical-list-BLvJ4-Zf.d.ts} +92 -4
- package/dist/vertical-list-BLvJ4-Zf.d.ts.map +1 -0
- package/dist/vertical-list.d.ts +2 -2
- package/dist/vertical-list.js +617 -28
- package/dist/vertical-list.js.map +1 -1
- package/docs-user/grid-list.md +14 -0
- package/docs-user/index.json +2 -2
- package/package.json +1 -1
- package/dist/detail-list-CQguZg9h.d.ts.map +0 -1
- package/dist/grid-list-40blkeza.js.map +0 -1
- package/dist/list-native-drag-controller-B0hWdbtM.js.map +0 -1
- package/dist/list-native-drag-controller-Cmpx7jnx.d.ts.map +0 -1
- package/dist/tree-CVj7TIk2.js.map +0 -1
- package/dist/tree-component-BW2iCFq9.d.ts.map +0 -1
- 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-
|
|
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-
|
|
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-
|
|
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
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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/
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
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
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
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
|
-
/**
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
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
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
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
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
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
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
5625
|
+
static {
|
|
5626
|
+
this.shadowRootOptions = {
|
|
5627
|
+
...LitElement.shadowRootOptions,
|
|
5628
|
+
delegatesFocus: true
|
|
5629
|
+
};
|
|
5527
5630
|
}
|
|
5528
|
-
|
|
5529
|
-
|
|
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
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
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
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
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
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
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
|
-
|
|
5605
|
-
|
|
5606
|
-
const
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
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
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
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 = [
|
|
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(
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
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="
|
|
7835
|
-
|
|
7836
|
-
|
|
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`, () =>
|
|
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.
|
|
9166
|
+
this._updateGroups();
|
|
7952
9167
|
}
|
|
7953
9168
|
updated(changed) {
|
|
7954
|
-
if (changed.has(`showGroupLabels`)) this.
|
|
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
|
-
|
|
7957
|
-
for (const child of Array.from(this.children))
|
|
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
|
-
|
|
8030
|
-
|
|
8031
|
-
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
|
|
8037
|
-
|
|
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
|
-
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
|
|
8043
|
-
|
|
8044
|
-
|
|
8045
|
-
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
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
|
-
|
|
8053
|
-
if (
|
|
8054
|
-
|
|
8055
|
-
|
|
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
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
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
|
-
|
|
8067
|
-
|
|
8068
|
-
|
|
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
|
|
8071
|
-
|
|
8072
|
-
|
|
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
|
-
|
|
8075
|
-
|
|
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
|
-
|
|
8082
|
-
|
|
8083
|
-
})],
|
|
10771
|
+
reflect: true,
|
|
10772
|
+
attribute: `exclusivity`
|
|
10773
|
+
})], TreemapListElement.prototype, "exclusivity", void 0);
|
|
8084
10774
|
__decorate([property({
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
})],
|
|
10775
|
+
reflect: true,
|
|
10776
|
+
attribute: `selection-filter`
|
|
10777
|
+
})], TreemapListElement.prototype, "selectionFilter", void 0);
|
|
8088
10778
|
__decorate([property({
|
|
8089
10779
|
type: String,
|
|
8090
|
-
attribute: `
|
|
8091
|
-
|
|
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: `
|
|
8096
|
-
|
|
8097
|
-
|
|
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: `
|
|
8101
|
-
|
|
8102
|
-
|
|
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: `
|
|
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
|
-
})],
|
|
8108
|
-
__decorate([state()],
|
|
8109
|
-
__decorate([
|
|
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
|