@ixfx/components 0.2.3 → 0.2.5
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/LICENSE +1 -1
- package/bundle/chunk-pbuEa-1d.js +13 -0
- package/bundle/index.d.ts +4702 -569
- package/bundle/index.d.ts.map +1 -1
- package/bundle/index.js +34929 -2800
- package/bundle/index.js.map +1 -1
- package/bundle/style.css +133 -0
- package/dist/ac-text.d.ts +97 -0
- package/dist/ac-text.d.ts.map +1 -0
- package/dist/ac-text.js +484 -0
- package/dist/ac-text.js.map +1 -0
- package/dist/button-DgKUzE5o.js +121 -0
- package/dist/button-DgKUzE5o.js.map +1 -0
- package/dist/button.d.ts +21 -0
- package/dist/button.d.ts.map +1 -0
- package/dist/button.js +3 -0
- package/dist/checkbox.d.ts +34 -0
- package/dist/checkbox.d.ts.map +1 -0
- package/dist/checkbox.js +251 -0
- package/dist/checkbox.js.map +1 -0
- package/dist/chunk-pbuEa-1d.js +13 -0
- package/dist/colour-C3MQIjFJ-BJV-QC-3.js +4138 -0
- package/dist/colour-C3MQIjFJ-BJV-QC-3.js.map +1 -0
- package/dist/colour-picker-Bw-AZoWx.js +922 -0
- package/dist/colour-picker-Bw-AZoWx.js.map +1 -0
- package/dist/colour-picker.d.ts +2 -0
- package/dist/colour-picker.js +2 -0
- package/dist/crumbs.d.ts +92 -0
- package/dist/crumbs.d.ts.map +1 -0
- package/dist/crumbs.js +789 -0
- package/dist/crumbs.js.map +1 -0
- package/dist/data-display.d.ts +24 -12
- package/dist/data-display.d.ts.map +1 -1
- package/dist/data-display.js +96 -105
- package/dist/data-display.js.map +1 -1
- package/dist/decorate-BZbkqIhA.js +9 -0
- package/dist/defaults-FEZaGiYZ.js +38 -0
- package/dist/defaults-FEZaGiYZ.js.map +1 -0
- package/dist/dist-By5URG0Y.js +1290 -0
- package/dist/dist-By5URG0Y.js.map +1 -0
- package/dist/dist-D7kJgnSE.js +1039 -0
- package/dist/dist-D7kJgnSE.js.map +1 -0
- package/dist/editable-label.d.ts +71 -0
- package/dist/editable-label.d.ts.map +1 -0
- package/dist/editable-label.js +279 -0
- package/dist/editable-label.js.map +1 -0
- package/dist/hex-editor-Gib1Grg2.d.ts +131 -0
- package/dist/hex-editor-Gib1Grg2.d.ts.map +1 -0
- package/dist/hex-editor-Wd6ZtZA2.js +1147 -0
- package/dist/hex-editor-Wd6ZtZA2.js.map +1 -0
- package/dist/hex.d.ts +2 -0
- package/dist/hex.js +2 -0
- package/dist/highlight-CAjwQ4GE.js +50 -0
- package/dist/highlight-CAjwQ4GE.js.map +1 -0
- package/dist/icon-BR5aTIiP.js +100 -0
- package/dist/icon-BR5aTIiP.js.map +1 -0
- package/dist/icon-DQKdfR7U.d.ts +36 -0
- package/dist/icon-DQKdfR7U.d.ts.map +1 -0
- package/dist/icons.d.ts +3 -0
- package/dist/icons.js +9 -0
- package/dist/icons.js.map +1 -0
- package/dist/incr-search-BjCNClnC.js +311 -0
- package/dist/incr-search-BjCNClnC.js.map +1 -0
- package/dist/incr-search.d.ts +2 -0
- package/dist/incr-search.js +3 -0
- package/dist/index-B1hNR7Z9.d.ts +36 -0
- package/dist/index-B1hNR7Z9.d.ts.map +1 -0
- package/dist/index-C5dK8RZa.d.ts +23 -0
- package/dist/index-C5dK8RZa.d.ts.map +1 -0
- package/dist/index-C9iBN908.d.ts +160 -0
- package/dist/index-C9iBN908.d.ts.map +1 -0
- package/dist/index-CPVcTTXo.d.ts +396 -0
- package/dist/index-CPVcTTXo.d.ts.map +1 -0
- package/dist/index-CQxanqXF.d.ts +183 -0
- package/dist/index-CQxanqXF.d.ts.map +1 -0
- package/dist/index-CpbBI3n7.d.ts +1239 -0
- package/dist/index-CpbBI3n7.d.ts.map +1 -0
- package/dist/index.d.ts +422 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5729 -14
- package/dist/index.js.map +1 -1
- package/dist/interaction-CQDQJf2Q.js +1 -0
- package/dist/keyboard-De_vOHLg.js +20 -0
- package/dist/keyboard-De_vOHLg.js.map +1 -0
- package/dist/labelled-input-base-DS6-6C0r.js +155 -0
- package/dist/labelled-input-base-DS6-6C0r.js.map +1 -0
- package/dist/labelled-input-base-DnRh4reU.d.ts +43 -0
- package/dist/labelled-input-base-DnRh4reU.d.ts.map +1 -0
- package/dist/labelled-radial-input.d.ts +34 -0
- package/dist/labelled-radial-input.d.ts.map +1 -0
- package/dist/labelled-radial-input.js +188 -0
- package/dist/labelled-radial-input.js.map +1 -0
- package/dist/labelled-range-input.d.ts +2 -0
- package/dist/labelled-range-input.js +132 -0
- package/dist/labelled-range-input.js.map +1 -0
- package/dist/led.d.ts +25 -0
- package/dist/led.d.ts.map +1 -0
- package/dist/led.js +92 -0
- package/dist/led.js.map +1 -0
- package/dist/menu-BV6UFx_z.js +813 -0
- package/dist/menu-BV6UFx_z.js.map +1 -0
- package/dist/menu-item-Cikacp4F.d.ts +128 -0
- package/dist/menu-item-Cikacp4F.d.ts.map +1 -0
- package/dist/menu-item-DEzL_Yjk.js +766 -0
- package/dist/menu-item-DEzL_Yjk.js.map +1 -0
- package/dist/menu.d.ts +3 -0
- package/dist/menu.js +3 -0
- package/dist/miller.d.ts +324 -0
- package/dist/miller.d.ts.map +1 -0
- package/dist/miller.js +1343 -0
- package/dist/miller.js.map +1 -0
- package/dist/narrowed-text.d.ts +72 -0
- package/dist/narrowed-text.d.ts.map +1 -0
- package/dist/narrowed-text.js +571 -0
- package/dist/narrowed-text.js.map +1 -0
- package/dist/panel.d.ts +43 -0
- package/dist/panel.d.ts.map +1 -0
- package/dist/panel.js +408 -0
- package/dist/panel.js.map +1 -0
- package/dist/plots.d.ts +2 -0
- package/dist/plots.js +2 -0
- package/dist/polar-pad.d.ts +67 -0
- package/dist/polar-pad.d.ts.map +1 -0
- package/dist/polar-pad.js +537 -0
- package/dist/polar-pad.js.map +1 -0
- package/dist/popup-Ddx9LCzY.js +24 -0
- package/dist/popup-Ddx9LCzY.js.map +1 -0
- package/dist/prominence-CreDBdCs.js +98 -0
- package/dist/prominence-CreDBdCs.js.map +1 -0
- package/dist/radial-input-CNJ8asY2.js +849 -0
- package/dist/radial-input-CNJ8asY2.js.map +1 -0
- package/dist/radial-input-xSTd4kEz.d.ts +70 -0
- package/dist/radial-input-xSTd4kEz.d.ts.map +1 -0
- package/dist/radial-input.d.ts +2 -0
- package/dist/radial-input.js +2 -0
- package/dist/range-input.d.ts +43 -0
- package/dist/range-input.d.ts.map +1 -0
- package/dist/range-input.js +459 -0
- package/dist/range-input.js.map +1 -0
- package/dist/range.d.ts +28 -0
- package/dist/range.d.ts.map +1 -0
- package/dist/range.js +136 -0
- package/dist/range.js.map +1 -0
- package/dist/registry-CZEB1YI9.js +21 -0
- package/dist/registry-CZEB1YI9.js.map +1 -0
- package/dist/selecthorizontal.d.ts +13 -8
- package/dist/selecthorizontal.d.ts.map +1 -1
- package/dist/selecthorizontal.js +21 -25
- package/dist/selecthorizontal.js.map +1 -1
- package/dist/snap-container.d.ts +50 -0
- package/dist/snap-container.d.ts.map +1 -0
- package/dist/snap-container.js +297 -0
- package/dist/snap-container.js.map +1 -0
- package/dist/split-layout.d.ts +56 -0
- package/dist/split-layout.d.ts.map +1 -0
- package/dist/split-layout.js +545 -0
- package/dist/split-layout.js.map +1 -0
- package/dist/style.css +133 -0
- package/dist/swipe.d.ts +48 -0
- package/dist/swipe.d.ts.map +1 -0
- package/dist/swipe.js +204 -0
- package/dist/swipe.js.map +1 -0
- package/dist/tab-list-CFF7uSYH.d.ts +66 -0
- package/dist/tab-list-CFF7uSYH.d.ts.map +1 -0
- package/dist/tabs.d.ts +2 -0
- package/dist/tabs.js +203 -0
- package/dist/tabs.js.map +1 -0
- package/dist/tickled-controller-Cq1Va0rR.d.ts +218 -0
- package/dist/tickled-controller-Cq1Va0rR.d.ts.map +1 -0
- package/dist/tickled-styles-Bg3QbcrD.js +327 -0
- package/dist/tickled-styles-Bg3QbcrD.js.map +1 -0
- package/dist/timeline-DBWJsSjd.js +1748 -0
- package/dist/timeline-DBWJsSjd.js.map +1 -0
- package/dist/timeline.d.ts +2 -0
- package/dist/timeline.js +2 -0
- package/dist/tooltip-DMxmBNky.js +104 -0
- package/dist/tooltip-DMxmBNky.js.map +1 -0
- package/dist/tooltip-kfUBWYVB.d.ts +26 -0
- package/dist/tooltip-kfUBWYVB.d.ts.map +1 -0
- package/dist/tree-component-CQpoPo4s.d.ts +326 -0
- package/dist/tree-component-CQpoPo4s.d.ts.map +1 -0
- package/dist/tree-data-model-DGvRVOFY.js +190 -0
- package/dist/tree-data-model-DGvRVOFY.js.map +1 -0
- package/dist/tree.d.ts +100 -0
- package/dist/tree.d.ts.map +1 -0
- package/dist/tree.js +1144 -0
- package/dist/tree.js.map +1 -0
- package/dist/types-BKaqnpwx.d.ts +8 -0
- package/dist/types-BKaqnpwx.d.ts.map +1 -0
- package/dist/xy-axis-BANbgrI5.js +6243 -0
- package/dist/xy-axis-BANbgrI5.js.map +1 -0
- package/dist/xy-axis-CIFok7MF.d.ts +141 -0
- package/dist/xy-axis-CIFok7MF.d.ts.map +1 -0
- package/dist/xy-pad.d.ts +81 -0
- package/dist/xy-pad.d.ts.map +1 -0
- package/dist/xy-pad.js +435 -0
- package/dist/xy-pad.js.map +1 -0
- package/package.json +48 -39
- package/README.md +0 -14
- package/dist/charts/frequency-histogram-plot.d.ts +0 -33
- package/dist/charts/frequency-histogram-plot.d.ts.map +0 -1
- package/dist/charts/frequency-histogram-plot.js +0 -54
- package/dist/charts/frequency-histogram-plot.js.map +0 -1
- package/dist/charts/histogram-vis.d.ts +0 -57
- package/dist/charts/histogram-vis.d.ts.map +0 -1
- package/dist/charts/histogram-vis.js +0 -222
- package/dist/charts/histogram-vis.js.map +0 -1
- package/dist/charts/plot.d.ts +0 -199
- package/dist/charts/plot.d.ts.map +0 -1
- package/dist/charts/plot.js +0 -914
- package/dist/charts/plot.js.map +0 -1
- package/dist/crumb-nav.d.ts +0 -41
- package/dist/crumb-nav.d.ts.map +0 -1
- package/dist/crumb-nav.js +0 -198
- package/dist/crumb-nav.js.map +0 -1
- package/dist/crumb-navigation.d.ts +0 -30
- package/dist/crumb-navigation.d.ts.map +0 -1
- package/dist/crumb-navigation.js +0 -307
- package/dist/crumb-navigation.js.map +0 -1
- package/dist/dial.d.ts +0 -89
- package/dist/dial.d.ts.map +0 -1
- package/dist/dial.js +0 -506
- package/dist/dial.js.map +0 -1
- package/dist/my-element.d.ts +0 -14
- package/dist/my-element.d.ts.map +0 -1
- package/dist/my-element.js +0 -44
- package/dist/my-element.js.map +0 -1
- package/dist/split-layout/split-layout.d.ts +0 -35
- package/dist/split-layout/split-layout.d.ts.map +0 -1
- package/dist/split-layout/split-layout.js +0 -220
- package/dist/split-layout/split-layout.js.map +0 -1
- package/dist/svg-edit-label.d.ts +0 -2
- package/dist/svg-edit-label.d.ts.map +0 -1
- package/dist/svg-edit-label.js +0 -110
- package/dist/svg-edit-label.js.map +0 -1
- package/dist/tabs/tab-layout-item.d.ts +0 -10
- package/dist/tabs/tab-layout-item.d.ts.map +0 -1
- package/dist/tabs/tab-layout-item.js +0 -20
- package/dist/tabs/tab-layout-item.js.map +0 -1
- package/dist/tabs/tab-layout.d.ts +0 -10
- package/dist/tabs/tab-layout.d.ts.map +0 -1
- package/dist/tabs/tab-layout.js +0 -25
- package/dist/tabs/tab-layout.js.map +0 -1
- package/dist/tabs/tab-list-item.d.ts +0 -18
- package/dist/tabs/tab-list-item.d.ts.map +0 -1
- package/dist/tabs/tab-list-item.js +0 -63
- package/dist/tabs/tab-list-item.js.map +0 -1
- package/dist/tabs/tab-list.d.ts +0 -21
- package/dist/tabs/tab-list.d.ts.map +0 -1
- package/dist/tabs/tab-list.js +0 -148
- package/dist/tabs/tab-list.js.map +0 -1
- package/dist/tabs/tab-panel.d.ts +0 -11
- package/dist/tabs/tab-panel.d.ts.map +0 -1
- package/dist/tabs/tab-panel.js +0 -30
- package/dist/tabs/tab-panel.js.map +0 -1
- package/dist/tabs/tab-panels.d.ts +0 -30
- package/dist/tabs/tab-panels.d.ts.map +0 -1
- package/dist/tabs/tab-panels.js +0 -80
- package/dist/tabs/tab-panels.js.map +0 -1
- package/dist/util/circle-arc.d.ts +0 -34
- package/dist/util/circle-arc.d.ts.map +0 -1
- package/dist/util/circle-arc.js +0 -77
- package/dist/util/circle-arc.js.map +0 -1
- package/dist/util/compare.d.ts +0 -53
- package/dist/util/compare.d.ts.map +0 -1
- package/dist/util/compare.js +0 -91
- package/dist/util/compare.js.map +0 -1
- package/dist/util/content-editable.d.ts +0 -2
- package/dist/util/content-editable.d.ts.map +0 -1
- package/dist/util/content-editable.js +0 -13
- package/dist/util/content-editable.js.map +0 -1
- package/dist/util/drag-controller.d.ts +0 -13
- package/dist/util/drag-controller.d.ts.map +0 -1
- package/dist/util/drag-controller.js +0 -21
- package/dist/util/drag-controller.js.map +0 -1
- package/dist/util/key-value.d.ts +0 -5
- package/dist/util/key-value.d.ts.map +0 -1
- package/dist/util/key-value.js +0 -30
- package/dist/util/key-value.js.map +0 -1
- package/dist/util/primitive-types.d.ts +0 -6
- package/dist/util/primitive-types.d.ts.map +0 -1
- package/dist/util/primitive-types.js +0 -2
- package/dist/util/primitive-types.js.map +0 -1
- package/dist/util/svg-editable-text.d.ts +0 -22
- package/dist/util/svg-editable-text.d.ts.map +0 -1
- package/dist/util/svg-editable-text.js +0 -121
- package/dist/util/svg-editable-text.js.map +0 -1
- package/dist/util/time.d.ts +0 -2
- package/dist/util/time.d.ts.map +0 -1
- package/dist/util/time.js +0 -2
- package/dist/util/time.js.map +0 -1
package/dist/miller.js
ADDED
|
@@ -0,0 +1,1343 @@
|
|
|
1
|
+
import { n as tickledStyles, r as TickledController, t as tickledItemStyles } from "./tickled-styles-Bg3QbcrD.js";
|
|
2
|
+
import { t as __decorate } from "./decorate-BZbkqIhA.js";
|
|
3
|
+
import "./interaction-CQDQJf2Q.js";
|
|
4
|
+
import { LitElement, css, html, nothing } from "lit";
|
|
5
|
+
import { customElement, property, state } from "lit/decorators.js";
|
|
6
|
+
//#region src/miller/miller-base.ts
|
|
7
|
+
const EXPAND_DEBOUNCE_MS = 150;
|
|
8
|
+
let MillerBaseElement = class MillerBaseElement extends LitElement {
|
|
9
|
+
constructor(..._args) {
|
|
10
|
+
super(..._args);
|
|
11
|
+
this.headers = false;
|
|
12
|
+
this.interactionMode = `standard`;
|
|
13
|
+
this.selectionFilter = "leaf";
|
|
14
|
+
this.exclusivity = "none";
|
|
15
|
+
this._columns = [];
|
|
16
|
+
this._selectedByDepth = /* @__PURE__ */ new Map();
|
|
17
|
+
this._selectedKeys = /* @__PURE__ */ new Set();
|
|
18
|
+
this._selectedNodes = /* @__PURE__ */ new Set();
|
|
19
|
+
this.#columnWidths = /* @__PURE__ */ new Map();
|
|
20
|
+
this.#resizingDepth = -1;
|
|
21
|
+
this.#resizeStartX = 0;
|
|
22
|
+
this.#resizeStartWidth = 0;
|
|
23
|
+
this.tickled = new TickledController(this, { getItems: () => {
|
|
24
|
+
const items = [];
|
|
25
|
+
for (let depth = 0; depth < this._columns.length; depth++) {
|
|
26
|
+
const col = this._columns[depth];
|
|
27
|
+
if (col) for (const node of col) items.push({ id: this.#buildTickledId(node, depth) });
|
|
28
|
+
}
|
|
29
|
+
return items;
|
|
30
|
+
} });
|
|
31
|
+
this.#onKeyDown = (event) => {
|
|
32
|
+
const currentDepth = this.#getTickledDepthOrDefault();
|
|
33
|
+
const col = this._columns[currentDepth];
|
|
34
|
+
if (!col || col.length === 0) {
|
|
35
|
+
const depth0Col = this._columns[0];
|
|
36
|
+
if (depth0Col && depth0Col.length > 0) {
|
|
37
|
+
this.tickled.setTickled(this.#buildTickledId(depth0Col[0], 0));
|
|
38
|
+
this.requestUpdate();
|
|
39
|
+
}
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
switch (event.key) {
|
|
43
|
+
case `ArrowDown`: {
|
|
44
|
+
event.preventDefault();
|
|
45
|
+
const current = this.#getTickledNodeAndDepth();
|
|
46
|
+
let targetIndex = 0;
|
|
47
|
+
if (current && current.depth === currentDepth) targetIndex = Math.min(current.index + 1, col.length - 1);
|
|
48
|
+
const targetNode = col[targetIndex];
|
|
49
|
+
if (!targetNode) break;
|
|
50
|
+
this.#setTickledNode(targetNode, currentDepth);
|
|
51
|
+
if (this.interactionMode === `standard` || this.interactionMode === `vscode`) {
|
|
52
|
+
const prevIndex = current?.index ?? 0;
|
|
53
|
+
if (event.shiftKey) {
|
|
54
|
+
if (!this.#anchor || this.#anchor.depth !== currentDepth) {
|
|
55
|
+
const anchorNode = col[prevIndex];
|
|
56
|
+
if (anchorNode) this.#anchor = {
|
|
57
|
+
key: anchorNode.item.key,
|
|
58
|
+
depth: currentDepth,
|
|
59
|
+
index: prevIndex,
|
|
60
|
+
selectionSnapshot: this.interactionMode === `vscode` ? new Set(this._selectedKeys) : void 0
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (this.#anchor) {
|
|
64
|
+
const from = Math.min(this.#anchor.index, targetIndex);
|
|
65
|
+
const to = Math.max(this.#anchor.index, targetIndex);
|
|
66
|
+
const rangeKeys = col.slice(from, to + 1).map((n) => n.item.key);
|
|
67
|
+
if (this.interactionMode === `vscode`) {
|
|
68
|
+
const snap = this.#anchor.selectionSnapshot;
|
|
69
|
+
for (const k of rangeKeys) if (snap.has(k)) this._selectedKeys.delete(k);
|
|
70
|
+
else this._selectedKeys.add(k);
|
|
71
|
+
} else this._selectedKeys = new Set(rangeKeys);
|
|
72
|
+
}
|
|
73
|
+
} else if (this.interactionMode === `standard`) this.#anchor = {
|
|
74
|
+
key: targetNode.item.key,
|
|
75
|
+
depth: currentDepth,
|
|
76
|
+
index: targetIndex
|
|
77
|
+
};
|
|
78
|
+
else this.#anchor = void 0;
|
|
79
|
+
}
|
|
80
|
+
this.requestUpdate();
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
case `ArrowUp`: {
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
const current = this.#getTickledNodeAndDepth();
|
|
86
|
+
let targetIndex = 0;
|
|
87
|
+
if (current && current.depth === currentDepth) targetIndex = Math.max(current.index - 1, 0);
|
|
88
|
+
const targetNode = col[targetIndex];
|
|
89
|
+
if (!targetNode) break;
|
|
90
|
+
this.#setTickledNode(targetNode, currentDepth);
|
|
91
|
+
if (this.interactionMode === `standard` || this.interactionMode === `vscode`) {
|
|
92
|
+
const prevIndex = current?.index ?? 0;
|
|
93
|
+
if (event.shiftKey) {
|
|
94
|
+
if (!this.#anchor || this.#anchor.depth !== currentDepth) {
|
|
95
|
+
const anchorNode = col[prevIndex];
|
|
96
|
+
if (anchorNode) this.#anchor = {
|
|
97
|
+
key: anchorNode.item.key,
|
|
98
|
+
depth: currentDepth,
|
|
99
|
+
index: prevIndex,
|
|
100
|
+
selectionSnapshot: this.interactionMode === `vscode` ? new Set(this._selectedKeys) : void 0
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (this.#anchor) {
|
|
104
|
+
const from = Math.min(this.#anchor.index, targetIndex);
|
|
105
|
+
const to = Math.max(this.#anchor.index, targetIndex);
|
|
106
|
+
const rangeKeys = col.slice(from, to + 1).map((n) => n.item.key);
|
|
107
|
+
if (this.interactionMode === `vscode`) {
|
|
108
|
+
const snap = this.#anchor.selectionSnapshot;
|
|
109
|
+
for (const k of rangeKeys) if (snap.has(k)) this._selectedKeys.delete(k);
|
|
110
|
+
else this._selectedKeys.add(k);
|
|
111
|
+
} else this._selectedKeys = new Set(rangeKeys);
|
|
112
|
+
}
|
|
113
|
+
} else if (this.interactionMode === `standard`) this.#anchor = {
|
|
114
|
+
key: targetNode.item.key,
|
|
115
|
+
depth: currentDepth,
|
|
116
|
+
index: targetIndex
|
|
117
|
+
};
|
|
118
|
+
else this.#anchor = void 0;
|
|
119
|
+
}
|
|
120
|
+
this.requestUpdate();
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case `ArrowRight`: {
|
|
124
|
+
event.preventDefault();
|
|
125
|
+
const tickled = this.#getTickledNodeAndDepth();
|
|
126
|
+
if (!tickled) {
|
|
127
|
+
if (col.length > 0) {
|
|
128
|
+
this.#setTickledNode(col[0], currentDepth);
|
|
129
|
+
this.requestUpdate();
|
|
130
|
+
}
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const { node, depth } = tickled;
|
|
134
|
+
if (this.interactionMode === `vscode` || this.interactionMode === `standard`) {
|
|
135
|
+
this._selectedByDepth.set(depth, node);
|
|
136
|
+
this.#expandNode(node, depth);
|
|
137
|
+
} else this.#onItemClick(node, depth);
|
|
138
|
+
if (this._columns.length > depth + 1) {
|
|
139
|
+
const nextCol = this._columns[depth + 1];
|
|
140
|
+
if (nextCol && nextCol.length > 0) this.#setTickledNode(nextCol[0], depth + 1);
|
|
141
|
+
}
|
|
142
|
+
this.requestUpdate();
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case `ArrowLeft`:
|
|
146
|
+
event.preventDefault();
|
|
147
|
+
if (currentDepth > 0) {
|
|
148
|
+
this.#abortPending();
|
|
149
|
+
this._columns = this._columns.slice(0, currentDepth);
|
|
150
|
+
const prevCol = this._columns[currentDepth - 1];
|
|
151
|
+
if (prevCol && prevCol.length > 0) {
|
|
152
|
+
const selectedNode = this._selectedByDepth.get(currentDepth - 1);
|
|
153
|
+
const targetNode = selectedNode && prevCol.includes(selectedNode) ? selectedNode : prevCol[0];
|
|
154
|
+
this.#setTickledNode(targetNode, currentDepth - 1);
|
|
155
|
+
}
|
|
156
|
+
this.requestUpdate();
|
|
157
|
+
}
|
|
158
|
+
break;
|
|
159
|
+
case `Enter`: {
|
|
160
|
+
event.preventDefault();
|
|
161
|
+
const tickled = this.#getTickledNodeAndDepth();
|
|
162
|
+
if (!tickled) return;
|
|
163
|
+
const { node, depth, index } = tickled;
|
|
164
|
+
if (this.interactionMode === `standard` || this.interactionMode === `vscode`) if (event.shiftKey && this.#anchor?.depth === depth) {
|
|
165
|
+
const from = Math.min(this.#anchor.index, index);
|
|
166
|
+
const to = Math.max(this.#anchor.index, index);
|
|
167
|
+
this._selectedKeys = new Set(col.slice(from, to + 1).map((n) => n.item.key));
|
|
168
|
+
} else {
|
|
169
|
+
this._selectedKeys.add(node.item.key);
|
|
170
|
+
this.#anchor = {
|
|
171
|
+
key: node.item.key,
|
|
172
|
+
depth,
|
|
173
|
+
index
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
else this._selectedByDepth.set(depth, node);
|
|
177
|
+
this.dispatchEvent(new CustomEvent(`activate`, {
|
|
178
|
+
detail: {
|
|
179
|
+
node,
|
|
180
|
+
depth
|
|
181
|
+
},
|
|
182
|
+
bubbles: true,
|
|
183
|
+
composed: true
|
|
184
|
+
}));
|
|
185
|
+
this.requestUpdate();
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
case `Escape`:
|
|
189
|
+
event.preventDefault();
|
|
190
|
+
this.#abortPending();
|
|
191
|
+
if (this._columns.length > 1) {
|
|
192
|
+
this._columns = this._columns.slice(0, -1);
|
|
193
|
+
const newDepth = Math.max(0, currentDepth - 1);
|
|
194
|
+
const prevCol = this._columns[newDepth];
|
|
195
|
+
if (prevCol && prevCol.length > 0) {
|
|
196
|
+
const selectedNode = this._selectedByDepth.get(newDepth);
|
|
197
|
+
const targetNode = selectedNode && prevCol.includes(selectedNode) ? selectedNode : prevCol[0];
|
|
198
|
+
this.#setTickledNode(targetNode, newDepth);
|
|
199
|
+
}
|
|
200
|
+
this.requestUpdate();
|
|
201
|
+
}
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
this.#onResizeStart = (event, depth) => {
|
|
206
|
+
event.preventDefault();
|
|
207
|
+
event.stopPropagation();
|
|
208
|
+
this.#resizingDepth = depth;
|
|
209
|
+
this.#resizeStartX = event.clientX;
|
|
210
|
+
const el = this.shadowRoot?.querySelectorAll(`.column`)[depth];
|
|
211
|
+
this.#resizeStartWidth = el ? el.getBoundingClientRect().width : 200;
|
|
212
|
+
window.addEventListener(`pointermove`, this.#onResizeMove);
|
|
213
|
+
window.addEventListener(`pointerup`, this.#onResizeEnd);
|
|
214
|
+
};
|
|
215
|
+
this.#onResizeMove = (event) => {
|
|
216
|
+
if (this.#resizingDepth < 0) return;
|
|
217
|
+
const delta = event.clientX - this.#resizeStartX;
|
|
218
|
+
const newWidth = Math.max(32, this.#resizeStartWidth + delta);
|
|
219
|
+
this.#columnWidths.set(this.#resizingDepth, newWidth);
|
|
220
|
+
this.requestUpdate();
|
|
221
|
+
};
|
|
222
|
+
this.#onResizeEnd = () => {
|
|
223
|
+
this.#resizingDepth = -1;
|
|
224
|
+
window.removeEventListener(`pointermove`, this.#onResizeMove);
|
|
225
|
+
window.removeEventListener(`pointerup`, this.#onResizeEnd);
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
#lastSelectedNode;
|
|
229
|
+
#anchor;
|
|
230
|
+
#columnWidths;
|
|
231
|
+
#pendingNode;
|
|
232
|
+
#pendingController;
|
|
233
|
+
#expandTimer;
|
|
234
|
+
#resizingDepth;
|
|
235
|
+
#resizeStartX;
|
|
236
|
+
#resizeStartWidth;
|
|
237
|
+
#modelUnsubscribe;
|
|
238
|
+
#buildTickledId(node, depth) {
|
|
239
|
+
return `${depth}:${node.item.key}`;
|
|
240
|
+
}
|
|
241
|
+
#getTickledDepth() {
|
|
242
|
+
const id = this.tickled.tickledId;
|
|
243
|
+
if (!id) return void 0;
|
|
244
|
+
const colonIndex = id.indexOf(":");
|
|
245
|
+
if (colonIndex < 0) return void 0;
|
|
246
|
+
return Number(id.substring(0, colonIndex));
|
|
247
|
+
}
|
|
248
|
+
updated(changedProps) {
|
|
249
|
+
if (changedProps.has(`root`) && this.root) {
|
|
250
|
+
this.#abortPending();
|
|
251
|
+
this._columns = this.root.children ? [this.root.children] : [];
|
|
252
|
+
this._selectedByDepth.clear();
|
|
253
|
+
this._selectedKeys = /* @__PURE__ */ new Set();
|
|
254
|
+
this._selectedNodes.clear();
|
|
255
|
+
this.#lastSelectedNode = void 0;
|
|
256
|
+
this.#anchor = void 0;
|
|
257
|
+
this.tickled.clearTickled();
|
|
258
|
+
}
|
|
259
|
+
if (changedProps.has(`model`)) {
|
|
260
|
+
this.#modelUnsubscribe?.();
|
|
261
|
+
this.#modelUnsubscribe = void 0;
|
|
262
|
+
const model = this.model;
|
|
263
|
+
if (model) {
|
|
264
|
+
this.root = model.root;
|
|
265
|
+
this.#modelUnsubscribe = model._subscribe((event) => {
|
|
266
|
+
if (event.type === "root") {
|
|
267
|
+
this.root = event.root;
|
|
268
|
+
this.requestUpdate();
|
|
269
|
+
} else if (event.type === "invalidate") {
|
|
270
|
+
for (let depth = 0; depth < this._columns.length - 1; depth++) if (this._columns[depth]?.some((n) => n.item.key === event.key)) {
|
|
271
|
+
this._columns = this._columns.slice(0, depth + 1);
|
|
272
|
+
this.requestUpdate();
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (this.#pendingNode?.children !== void 0 && this._loadingDepth !== void 0) {
|
|
280
|
+
const loadingDepth = this._loadingDepth;
|
|
281
|
+
const resolvedChildren = this.#pendingNode.children;
|
|
282
|
+
this.#pendingNode = void 0;
|
|
283
|
+
this.#pendingController = void 0;
|
|
284
|
+
this._loadingDepth = void 0;
|
|
285
|
+
const cols = this._columns.slice(0, loadingDepth);
|
|
286
|
+
if (resolvedChildren.length > 0) cols.push(resolvedChildren);
|
|
287
|
+
this._columns = cols;
|
|
288
|
+
}
|
|
289
|
+
const prevColumns = changedProps.get(`_columns`);
|
|
290
|
+
const columnAdded = changedProps.has(`_columns`) && (prevColumns?.length ?? 0) < this._columns.length;
|
|
291
|
+
const loadingAppeared = changedProps.has(`_loadingDepth`) && this._loadingDepth !== void 0;
|
|
292
|
+
if (columnAdded || loadingAppeared) {
|
|
293
|
+
const container = this.shadowRoot?.querySelector(`#container`);
|
|
294
|
+
container?.scrollTo({
|
|
295
|
+
left: container.scrollWidth,
|
|
296
|
+
behavior: `smooth`
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
disconnectedCallback() {
|
|
301
|
+
super.disconnectedCallback();
|
|
302
|
+
this.#modelUnsubscribe?.();
|
|
303
|
+
this.#modelUnsubscribe = void 0;
|
|
304
|
+
}
|
|
305
|
+
getConfig() {
|
|
306
|
+
const columnWidths = {};
|
|
307
|
+
for (const [depth, width] of this.#columnWidths) columnWidths[depth] = width;
|
|
308
|
+
return { columnWidths };
|
|
309
|
+
}
|
|
310
|
+
setConfig(config) {
|
|
311
|
+
this.#columnWidths.clear();
|
|
312
|
+
for (const [depth, width] of Object.entries(config.columnWidths)) this.#columnWidths.set(Number(depth), width);
|
|
313
|
+
this.requestUpdate();
|
|
314
|
+
}
|
|
315
|
+
/** Read current selection as an immutable set of item keys. */
|
|
316
|
+
get selectedKeys() {
|
|
317
|
+
return this._selectedKeys;
|
|
318
|
+
}
|
|
319
|
+
/** Replace entire selection and trigger a re-render. */
|
|
320
|
+
set selectedKeys(keys) {
|
|
321
|
+
this._selectedKeys = new Set(keys);
|
|
322
|
+
this.requestUpdate();
|
|
323
|
+
}
|
|
324
|
+
/** The most recently selected node. */
|
|
325
|
+
get selectedNode() {
|
|
326
|
+
return this.#lastSelectedNode;
|
|
327
|
+
}
|
|
328
|
+
set selectedNode(node) {
|
|
329
|
+
this.#lastSelectedNode = node;
|
|
330
|
+
if (node) {
|
|
331
|
+
if (this.interactionMode === `implicit` || this.interactionMode === `standard`) this._selectedKeys = new Set([node.item.key]);
|
|
332
|
+
}
|
|
333
|
+
this.requestUpdate();
|
|
334
|
+
}
|
|
335
|
+
/** Full selection set (implements TreeComponent interface). */
|
|
336
|
+
get selectedNodes() {
|
|
337
|
+
return this._selectedNodes;
|
|
338
|
+
}
|
|
339
|
+
/** Add `node` to the selection (respects selectionFilter). Dispatches `select` event. */
|
|
340
|
+
select(node) {
|
|
341
|
+
if (!this.#canSelect(node)) return;
|
|
342
|
+
const prev = new Set(this._selectedNodes);
|
|
343
|
+
this._selectedNodes.add(node);
|
|
344
|
+
this._selectedKeys.add(node.item.key);
|
|
345
|
+
this.#lastSelectedNode = node;
|
|
346
|
+
this.#dispatchSelectEvent(prev);
|
|
347
|
+
this.requestUpdate();
|
|
348
|
+
}
|
|
349
|
+
/** Remove `node` from the selection. Dispatches `select` event. */
|
|
350
|
+
deselect(node) {
|
|
351
|
+
if (!this._selectedNodes.has(node)) return;
|
|
352
|
+
const prev = new Set(this._selectedNodes);
|
|
353
|
+
this._selectedNodes.delete(node);
|
|
354
|
+
this._selectedKeys.delete(node.item.key);
|
|
355
|
+
if (this.#lastSelectedNode === node) this.#lastSelectedNode = [...this._selectedNodes].at(-1);
|
|
356
|
+
this.#dispatchSelectEvent(prev);
|
|
357
|
+
this.requestUpdate();
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Clear all selection and trigger a re-render.
|
|
361
|
+
*/
|
|
362
|
+
clearSelection() {
|
|
363
|
+
const prev = new Set(this._selectedNodes);
|
|
364
|
+
this._selectedKeys = /* @__PURE__ */ new Set();
|
|
365
|
+
this._selectedNodes.clear();
|
|
366
|
+
this.#lastSelectedNode = void 0;
|
|
367
|
+
if (prev.size > 0) this.#dispatchSelectEvent(prev);
|
|
368
|
+
this.requestUpdate();
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Select or deselect a single node by key.
|
|
372
|
+
* In `checked` interactionMode this also recursively selects/deselects all loaded descendants.
|
|
373
|
+
*/
|
|
374
|
+
setSelected(node, selected) {
|
|
375
|
+
if (this.interactionMode === `checked`) this.#applyCheckedToggle(node, selected);
|
|
376
|
+
else if (selected) {
|
|
377
|
+
this._selectedKeys.add(node.item.key);
|
|
378
|
+
this._selectedNodes.add(node);
|
|
379
|
+
} else {
|
|
380
|
+
this._selectedKeys.delete(node.item.key);
|
|
381
|
+
this._selectedNodes.delete(node);
|
|
382
|
+
}
|
|
383
|
+
this.requestUpdate();
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Return all MillerNodes whose key is in `_selectedKeys`, searching only
|
|
387
|
+
* the currently visible columns.
|
|
388
|
+
*/
|
|
389
|
+
getSelected() {
|
|
390
|
+
const results = [];
|
|
391
|
+
for (const col of this._columns) for (const node of col) if (this._selectedKeys.has(node.item.key)) results.push(node);
|
|
392
|
+
return results;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Programmatically navigate to a node by key path.
|
|
396
|
+
*/
|
|
397
|
+
navigateTo(keys) {
|
|
398
|
+
if (!this.root?.children) return false;
|
|
399
|
+
this.#abortPending();
|
|
400
|
+
this._selectedByDepth.clear();
|
|
401
|
+
this.tickled.clearTickled();
|
|
402
|
+
const newColumns = [this.root.children];
|
|
403
|
+
let currentColumn = this.root.children;
|
|
404
|
+
let success = true;
|
|
405
|
+
let lastNode;
|
|
406
|
+
let lastDepth = 0;
|
|
407
|
+
for (let depth = 0; depth < keys.length; depth++) {
|
|
408
|
+
const idx = currentColumn.findIndex((n) => n.item.key === keys[depth]);
|
|
409
|
+
if (idx < 0) {
|
|
410
|
+
success = false;
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
const node = currentColumn[idx];
|
|
414
|
+
this._selectedByDepth.set(depth, node);
|
|
415
|
+
lastNode = node;
|
|
416
|
+
lastDepth = depth;
|
|
417
|
+
if (node.item.isLeaf || !node.children || node.children.length === 0) {
|
|
418
|
+
if (depth < keys.length - 1) success = false;
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
newColumns.push(node.children);
|
|
422
|
+
currentColumn = node.children;
|
|
423
|
+
}
|
|
424
|
+
this._columns = newColumns;
|
|
425
|
+
if (lastNode) this.tickled.setTickled(this.#buildTickledId(lastNode, lastDepth));
|
|
426
|
+
if (this.interactionMode !== `implicit` && this.interactionMode !== `manual`) {
|
|
427
|
+
const leafKey = keys.at(-1);
|
|
428
|
+
if (leafKey) this._selectedKeys = new Set([leafKey]);
|
|
429
|
+
}
|
|
430
|
+
this.requestUpdate();
|
|
431
|
+
this.#scrollNavigatedItemIntoView();
|
|
432
|
+
return success;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Programmatically navigate to the first node (breadth-first) matching a predicate.
|
|
436
|
+
*/
|
|
437
|
+
navigateToNode(predicate) {
|
|
438
|
+
if (!this.root?.children) return false;
|
|
439
|
+
const keyPath = this.#findNodePath(this.root.children, predicate);
|
|
440
|
+
if (!keyPath) return false;
|
|
441
|
+
return this.navigateTo(keyPath);
|
|
442
|
+
}
|
|
443
|
+
getNodes() {
|
|
444
|
+
if (!this.root?.children) return [];
|
|
445
|
+
const result = [];
|
|
446
|
+
const visit = (node, depth) => {
|
|
447
|
+
result.push({
|
|
448
|
+
node,
|
|
449
|
+
depth
|
|
450
|
+
});
|
|
451
|
+
if (node.children) for (const child of node.children) visit(child, depth + 1);
|
|
452
|
+
};
|
|
453
|
+
for (const child of this.root.children) visit(child, 0);
|
|
454
|
+
return result;
|
|
455
|
+
}
|
|
456
|
+
#findNodePath(nodes, predicate, prefix = []) {
|
|
457
|
+
for (const node of nodes) {
|
|
458
|
+
const path = [...prefix, node.item.key];
|
|
459
|
+
if (predicate(node)) return path;
|
|
460
|
+
if (node.children?.length) {
|
|
461
|
+
const result = this.#findNodePath(node.children, predicate, path);
|
|
462
|
+
if (result) return result;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
renderColumnContent(nodes, depth) {
|
|
467
|
+
console.warn(`ixfx-miller-base: renderColumnContent not overridden. depth=${depth}, nodes=${nodes.length}`);
|
|
468
|
+
return nothing;
|
|
469
|
+
}
|
|
470
|
+
_handleItemClick(node, depth, event) {
|
|
471
|
+
this.#onItemClick(node, depth, event);
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Called by subclasses (in `checked` interactionMode) when a checkbox is clicked.
|
|
475
|
+
*/
|
|
476
|
+
_handleCheckboxClick(event, node, _depth) {
|
|
477
|
+
event.stopPropagation();
|
|
478
|
+
this.#onCheckboxClick(node);
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Called by subclasses when an item is double-clicked.
|
|
482
|
+
*/
|
|
483
|
+
_handleItemDblClick(node, depth, _event) {
|
|
484
|
+
this.dispatchEvent(new CustomEvent(`item-dblclick`, {
|
|
485
|
+
detail: {
|
|
486
|
+
node,
|
|
487
|
+
depth
|
|
488
|
+
},
|
|
489
|
+
bubbles: true,
|
|
490
|
+
composed: true
|
|
491
|
+
}));
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Called by subclasses when an item is right-clicked.
|
|
495
|
+
*/
|
|
496
|
+
_handleItemRightClick(node, depth, event) {
|
|
497
|
+
this.dispatchEvent(new CustomEvent(`item-rightclick`, {
|
|
498
|
+
detail: {
|
|
499
|
+
node,
|
|
500
|
+
depth,
|
|
501
|
+
mouseEvent: event
|
|
502
|
+
},
|
|
503
|
+
bubbles: true,
|
|
504
|
+
composed: true
|
|
505
|
+
}));
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Called by subclasses when a column header is right-clicked.
|
|
509
|
+
*/
|
|
510
|
+
_handleHeaderRightClick(event, depth) {
|
|
511
|
+
const label = this.#getColumnHeader(depth);
|
|
512
|
+
const path = this.#buildPath(depth);
|
|
513
|
+
this.dispatchEvent(new CustomEvent(`header-rightclick`, {
|
|
514
|
+
detail: {
|
|
515
|
+
depth,
|
|
516
|
+
label,
|
|
517
|
+
path,
|
|
518
|
+
mouseEvent: event
|
|
519
|
+
},
|
|
520
|
+
bubbles: true,
|
|
521
|
+
composed: true
|
|
522
|
+
}));
|
|
523
|
+
}
|
|
524
|
+
_isSelected(node, depth) {
|
|
525
|
+
if (this.interactionMode === `implicit`) return this._selectedByDepth.get(depth) === node;
|
|
526
|
+
return this._selectedKeys.has(node.item.key);
|
|
527
|
+
}
|
|
528
|
+
#isAncestor(node, depth) {
|
|
529
|
+
const nextCol = this._columns[depth + 1];
|
|
530
|
+
return nextCol !== void 0 && node.children === nextCol;
|
|
531
|
+
}
|
|
532
|
+
_isOpen(node, depth) {
|
|
533
|
+
return this.#isAncestor(node, depth);
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Return the display state for an item identified by key at a given depth.
|
|
537
|
+
*/
|
|
538
|
+
getItemState(key, depth) {
|
|
539
|
+
const node = this._columns[depth]?.find((n) => n.item.key === key);
|
|
540
|
+
if (!node) return void 0;
|
|
541
|
+
return {
|
|
542
|
+
selected: this._isSelected(node, depth),
|
|
543
|
+
open: this.#isAncestor(node, depth),
|
|
544
|
+
focused: this._isFocused(node, depth),
|
|
545
|
+
isLeaf: node.item.isLeaf ?? false
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
#canSelect(node) {
|
|
549
|
+
if (this.selectionFilter === "none") return false;
|
|
550
|
+
if (this.selectionFilter === "leaf") return node.item.isLeaf !== false;
|
|
551
|
+
if (this.selectionFilter === "branch") return node.item.isLeaf === false || node.children !== void 0 && node.children.length > 0;
|
|
552
|
+
return true;
|
|
553
|
+
}
|
|
554
|
+
#dispatchSelectEvent(previous) {
|
|
555
|
+
const detail = {
|
|
556
|
+
selected: new Set(this._selectedNodes),
|
|
557
|
+
previous
|
|
558
|
+
};
|
|
559
|
+
this.dispatchEvent(new CustomEvent(`select`, {
|
|
560
|
+
detail,
|
|
561
|
+
bubbles: true,
|
|
562
|
+
composed: true
|
|
563
|
+
}));
|
|
564
|
+
}
|
|
565
|
+
#abortPending() {
|
|
566
|
+
if (this.#expandTimer !== void 0) {
|
|
567
|
+
clearTimeout(this.#expandTimer);
|
|
568
|
+
this.#expandTimer = void 0;
|
|
569
|
+
}
|
|
570
|
+
if (this.#pendingController) {
|
|
571
|
+
const node = this.#pendingNode;
|
|
572
|
+
const depth = this._loadingDepth;
|
|
573
|
+
this.#pendingController.abort();
|
|
574
|
+
this.#pendingController = void 0;
|
|
575
|
+
this.#pendingNode = void 0;
|
|
576
|
+
this._loadingDepth = void 0;
|
|
577
|
+
if (node && depth !== void 0) this.dispatchEvent(new CustomEvent(`collapse`, {
|
|
578
|
+
detail: {
|
|
579
|
+
node,
|
|
580
|
+
depth
|
|
581
|
+
},
|
|
582
|
+
bubbles: true,
|
|
583
|
+
composed: true
|
|
584
|
+
}));
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Build the ancestry path for a given column depth.
|
|
589
|
+
*/
|
|
590
|
+
#buildPath(depth) {
|
|
591
|
+
const path = [];
|
|
592
|
+
if (this.root) path.push(this.root);
|
|
593
|
+
for (let d = 0; d < depth; d++) {
|
|
594
|
+
const sel = this._selectedByDepth.get(d);
|
|
595
|
+
if (sel) path.push(sel);
|
|
596
|
+
}
|
|
597
|
+
return path;
|
|
598
|
+
}
|
|
599
|
+
#onHeaderClick(depth) {
|
|
600
|
+
const detail = {
|
|
601
|
+
depth,
|
|
602
|
+
label: this.#getColumnHeader(depth),
|
|
603
|
+
path: this.#buildPath(depth)
|
|
604
|
+
};
|
|
605
|
+
this.dispatchEvent(new CustomEvent(`header-click`, {
|
|
606
|
+
detail,
|
|
607
|
+
bubbles: true,
|
|
608
|
+
composed: true
|
|
609
|
+
}));
|
|
610
|
+
}
|
|
611
|
+
#onItemClick(node, depth, event) {
|
|
612
|
+
if (this._loadingDepth !== void 0 && this._loadingDepth > depth) this.#abortPending();
|
|
613
|
+
this.tickled.setTickled(this.#buildTickledId(node, depth));
|
|
614
|
+
this.dispatchEvent(new CustomEvent(`item-click`, {
|
|
615
|
+
detail: {
|
|
616
|
+
node,
|
|
617
|
+
depth
|
|
618
|
+
},
|
|
619
|
+
bubbles: true,
|
|
620
|
+
composed: true
|
|
621
|
+
}));
|
|
622
|
+
switch (this.interactionMode) {
|
|
623
|
+
case `implicit`:
|
|
624
|
+
this.#handleImplicitClick(node, depth);
|
|
625
|
+
break;
|
|
626
|
+
case `standard`:
|
|
627
|
+
this.#handleStandardClick(node, depth, event);
|
|
628
|
+
break;
|
|
629
|
+
case `vscode`:
|
|
630
|
+
this.#handleStandardClick(node, depth, event);
|
|
631
|
+
break;
|
|
632
|
+
case `manual`: break;
|
|
633
|
+
case `checked`:
|
|
634
|
+
this.#expandNode(node, depth);
|
|
635
|
+
break;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
#handleImplicitClick(node, depth) {
|
|
639
|
+
const previous = new Set(this._selectedNodes);
|
|
640
|
+
this._selectedByDepth.set(depth, node);
|
|
641
|
+
this.#lastSelectedNode = node;
|
|
642
|
+
this._selectedKeys = new Set([node.item.key]);
|
|
643
|
+
this._selectedNodes.clear();
|
|
644
|
+
this._selectedNodes.add(node);
|
|
645
|
+
this.#expandNode(node, depth);
|
|
646
|
+
if (!previous.has(node)) this.#dispatchSelectEvent(previous);
|
|
647
|
+
}
|
|
648
|
+
#handleStandardClick(node, depth, event) {
|
|
649
|
+
const col = this._columns[depth];
|
|
650
|
+
const index = col ? col.indexOf(node) : -1;
|
|
651
|
+
const isMeta = event ? event.metaKey || event.ctrlKey : false;
|
|
652
|
+
const isShift = event ? event.shiftKey : false;
|
|
653
|
+
if (isMeta) {
|
|
654
|
+
if (this._selectedKeys.has(node.item.key)) {
|
|
655
|
+
this._selectedKeys.delete(node.item.key);
|
|
656
|
+
this._selectedNodes.delete(node);
|
|
657
|
+
} else {
|
|
658
|
+
this._selectedKeys.add(node.item.key);
|
|
659
|
+
this._selectedNodes.add(node);
|
|
660
|
+
}
|
|
661
|
+
this.#anchor = index >= 0 ? {
|
|
662
|
+
key: node.item.key,
|
|
663
|
+
depth,
|
|
664
|
+
index
|
|
665
|
+
} : this.#anchor;
|
|
666
|
+
this.requestUpdate();
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
if (isShift && this.#anchor && this.#anchor.depth === depth && col) {
|
|
670
|
+
const from = Math.min(this.#anchor.index, index);
|
|
671
|
+
const to = Math.max(this.#anchor.index, index);
|
|
672
|
+
const rangeNodes = col.slice(from, to + 1);
|
|
673
|
+
this._selectedKeys = new Set(rangeNodes.map((n) => n.item.key));
|
|
674
|
+
this._selectedNodes = new Set(rangeNodes);
|
|
675
|
+
this.requestUpdate();
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
const previous = new Set(this._selectedNodes);
|
|
679
|
+
this._selectedKeys = new Set([node.item.key]);
|
|
680
|
+
this._selectedNodes = new Set([node]);
|
|
681
|
+
this.#anchor = index >= 0 ? {
|
|
682
|
+
key: node.item.key,
|
|
683
|
+
depth,
|
|
684
|
+
index
|
|
685
|
+
} : void 0;
|
|
686
|
+
this._selectedByDepth.set(depth, node);
|
|
687
|
+
this.#lastSelectedNode = node;
|
|
688
|
+
this.#expandNode(node, depth);
|
|
689
|
+
if (!previous.has(node) || previous.size !== 1) this.#dispatchSelectEvent(previous);
|
|
690
|
+
}
|
|
691
|
+
#expandNode(node, depth) {
|
|
692
|
+
if (node.item.isLeaf) {
|
|
693
|
+
this._columns = this._columns.slice(0, depth + 1);
|
|
694
|
+
this.requestUpdate();
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
if (node.children === void 0) {
|
|
698
|
+
this._columns = this._columns.slice(0, depth + 1);
|
|
699
|
+
this._loadingDepth = depth + 1;
|
|
700
|
+
this.#pendingNode = node;
|
|
701
|
+
const controller = new AbortController();
|
|
702
|
+
this.#pendingController = controller;
|
|
703
|
+
this.dispatchEvent(new CustomEvent(`expand`, {
|
|
704
|
+
detail: {
|
|
705
|
+
node,
|
|
706
|
+
depth
|
|
707
|
+
},
|
|
708
|
+
bubbles: true,
|
|
709
|
+
composed: true
|
|
710
|
+
}));
|
|
711
|
+
const applyChildren = (children) => {
|
|
712
|
+
if (controller.signal.aborted) return;
|
|
713
|
+
node.children = children;
|
|
714
|
+
this.#pendingNode = void 0;
|
|
715
|
+
this.#pendingController = void 0;
|
|
716
|
+
this._loadingDepth = void 0;
|
|
717
|
+
const cols = this._columns.slice(0, depth + 1);
|
|
718
|
+
if (children.length > 0) cols.push(children);
|
|
719
|
+
this._columns = cols;
|
|
720
|
+
};
|
|
721
|
+
const handleError = (err) => {
|
|
722
|
+
if (err.name !== `AbortError`) console.error(err);
|
|
723
|
+
node.children = [];
|
|
724
|
+
this.#pendingNode = void 0;
|
|
725
|
+
this.#pendingController = void 0;
|
|
726
|
+
this._loadingDepth = void 0;
|
|
727
|
+
this.requestUpdate();
|
|
728
|
+
};
|
|
729
|
+
const loadFn = this.model ? (n, d, s) => this.model.getChildren(n, d, s) : this.loadChildren;
|
|
730
|
+
if (loadFn) {
|
|
731
|
+
if (this.#expandTimer !== void 0) clearTimeout(this.#expandTimer);
|
|
732
|
+
this.#expandTimer = setTimeout(() => {
|
|
733
|
+
this.#expandTimer = void 0;
|
|
734
|
+
if (this.#pendingNode !== node) return;
|
|
735
|
+
const result = loadFn(node, depth, controller.signal);
|
|
736
|
+
if (result instanceof Promise) result.then(applyChildren).catch(handleError);
|
|
737
|
+
else applyChildren(result);
|
|
738
|
+
}, EXPAND_DEBOUNCE_MS);
|
|
739
|
+
} else {
|
|
740
|
+
this._loadingDepth = void 0;
|
|
741
|
+
this.#pendingNode = void 0;
|
|
742
|
+
this.#pendingController = void 0;
|
|
743
|
+
const newColumns = this._columns.slice(0, depth + 1);
|
|
744
|
+
newColumns.push([]);
|
|
745
|
+
this._columns = newColumns;
|
|
746
|
+
}
|
|
747
|
+
} else {
|
|
748
|
+
const newColumns = this._columns.slice(0, depth + 1);
|
|
749
|
+
newColumns.push(node.children);
|
|
750
|
+
this._columns = newColumns;
|
|
751
|
+
this.dispatchEvent(new CustomEvent(`expand`, {
|
|
752
|
+
detail: {
|
|
753
|
+
node,
|
|
754
|
+
depth
|
|
755
|
+
},
|
|
756
|
+
bubbles: true,
|
|
757
|
+
composed: true
|
|
758
|
+
}));
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
#onCheckboxClick(node) {
|
|
762
|
+
const currentlySelected = this._selectedKeys.has(node.item.key);
|
|
763
|
+
if (!!node.children?.length ? this.#allDescendantsSelected(node) : currentlySelected) this.#applyCheckedToggle(node, false);
|
|
764
|
+
else this.#applyCheckedToggle(node, true);
|
|
765
|
+
this.requestUpdate();
|
|
766
|
+
}
|
|
767
|
+
#allDescendantsSelected(node) {
|
|
768
|
+
if (!this._selectedKeys.has(node.item.key)) return false;
|
|
769
|
+
if (!node.children?.length) return true;
|
|
770
|
+
return node.children.every((child) => this.#allDescendantsSelected(child));
|
|
771
|
+
}
|
|
772
|
+
#applyCheckedToggle(node, selected) {
|
|
773
|
+
if (selected) {
|
|
774
|
+
this._selectedKeys.add(node.item.key);
|
|
775
|
+
this._selectedNodes.add(node);
|
|
776
|
+
} else {
|
|
777
|
+
this._selectedKeys.delete(node.item.key);
|
|
778
|
+
this._selectedNodes.delete(node);
|
|
779
|
+
}
|
|
780
|
+
if (node.children) for (const child of node.children) this.#applyCheckedToggle(child, selected);
|
|
781
|
+
}
|
|
782
|
+
#getTickledDepthOrDefault() {
|
|
783
|
+
return this.#getTickledDepth() ?? 0;
|
|
784
|
+
}
|
|
785
|
+
#getTickledNodeAndDepth() {
|
|
786
|
+
const id = this.tickled.tickledId;
|
|
787
|
+
if (!id) return void 0;
|
|
788
|
+
const colonIndex = id.indexOf(":");
|
|
789
|
+
if (colonIndex < 0) return void 0;
|
|
790
|
+
const depth = Number(id.substring(0, colonIndex));
|
|
791
|
+
const key = id.substring(colonIndex + 1);
|
|
792
|
+
const col = this._columns[depth];
|
|
793
|
+
if (!col) return void 0;
|
|
794
|
+
const index = col.findIndex((n) => n.item.key === key);
|
|
795
|
+
if (index < 0) return void 0;
|
|
796
|
+
return {
|
|
797
|
+
node: col[index],
|
|
798
|
+
depth,
|
|
799
|
+
index
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
#scrollNavigatedItemIntoView() {
|
|
803
|
+
requestAnimationFrame(() => {
|
|
804
|
+
const tickled = this.#getTickledNodeAndDepth();
|
|
805
|
+
if (!tickled) return;
|
|
806
|
+
const container = this.shadowRoot?.querySelectorAll(`.column`)[tickled.depth]?.querySelector(`.column-content`);
|
|
807
|
+
if (!container) return;
|
|
808
|
+
container.querySelectorAll(`.item`)[tickled.index]?.scrollIntoView({
|
|
809
|
+
block: `nearest`,
|
|
810
|
+
behavior: `smooth`
|
|
811
|
+
});
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
#setTickledNode(node, depth) {
|
|
815
|
+
this.tickled.setTickled(this.#buildTickledId(node, depth));
|
|
816
|
+
this.dispatchEvent(new CustomEvent(`tickle`, {
|
|
817
|
+
detail: {
|
|
818
|
+
node,
|
|
819
|
+
depth
|
|
820
|
+
},
|
|
821
|
+
bubbles: true,
|
|
822
|
+
composed: true
|
|
823
|
+
}));
|
|
824
|
+
}
|
|
825
|
+
#onKeyDown;
|
|
826
|
+
#onResizeStart;
|
|
827
|
+
#onResizeMove;
|
|
828
|
+
#onResizeEnd;
|
|
829
|
+
#getColumnHeader(depth) {
|
|
830
|
+
if (depth === 0) return this.root?.item.label ?? ``;
|
|
831
|
+
const explicit = this._selectedByDepth.get(depth - 1);
|
|
832
|
+
if (explicit) return explicit.item.label;
|
|
833
|
+
const prevCol = this._columns[depth - 1];
|
|
834
|
+
const thisCol = this._columns[depth];
|
|
835
|
+
if (prevCol && thisCol) {
|
|
836
|
+
const parent = prevCol.find((n) => n.children === thisCol);
|
|
837
|
+
if (parent) return parent.item.label;
|
|
838
|
+
}
|
|
839
|
+
return ``;
|
|
840
|
+
}
|
|
841
|
+
#columnStyle(depth) {
|
|
842
|
+
const w = this.#columnWidths.get(depth);
|
|
843
|
+
return w !== void 0 ? `width:${w}px;flex-shrink:0` : `width:max-content`;
|
|
844
|
+
}
|
|
845
|
+
#isFocused(node, depth) {
|
|
846
|
+
return this.tickled.isTickled(this.#buildTickledId(node, depth));
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Override to provide a custom loading indicator.
|
|
850
|
+
*/
|
|
851
|
+
renderLoadingIndicator() {
|
|
852
|
+
return html`<div class="loading-spinner"></div>`;
|
|
853
|
+
}
|
|
854
|
+
#renderLoadingColumn() {
|
|
855
|
+
const depth = this._loadingDepth;
|
|
856
|
+
return html`
|
|
857
|
+
<div class="column loading-column" style=${this.#columnStyle(depth)}>
|
|
858
|
+
${this.headers ? html`
|
|
859
|
+
<div class="column-header">${this.#getColumnHeader(depth)}</div>
|
|
860
|
+
` : nothing}
|
|
861
|
+
<div class="column-content loading-content">
|
|
862
|
+
${this.renderLoadingIndicator()}
|
|
863
|
+
</div>
|
|
864
|
+
</div>
|
|
865
|
+
`;
|
|
866
|
+
}
|
|
867
|
+
render() {
|
|
868
|
+
return html`
|
|
869
|
+
<div id="container" @keydown=${this.#onKeyDown} tabindex="0">
|
|
870
|
+
${this._columns.map((nodes, depth) => {
|
|
871
|
+
const visibleNodes = this.filterPredicate ? nodes.filter((n) => this.filterPredicate(n, depth)) : nodes;
|
|
872
|
+
return html`
|
|
873
|
+
<div class="column" style=${this.#columnStyle(depth)}>
|
|
874
|
+
${this.headers ? html`
|
|
875
|
+
<div class="column-header"
|
|
876
|
+
@click=${() => this.#onHeaderClick(depth)}
|
|
877
|
+
@contextmenu=${(e) => this._handleHeaderRightClick(e, depth)}>
|
|
878
|
+
${this.#getColumnHeader(depth)}
|
|
879
|
+
</div>
|
|
880
|
+
` : nothing}
|
|
881
|
+
<div class="column-content">
|
|
882
|
+
${this.renderColumnContent(visibleNodes, depth)}
|
|
883
|
+
</div>
|
|
884
|
+
<div class="resize-handle"
|
|
885
|
+
@pointerdown=${(e) => this.#onResizeStart(e, depth)}>
|
|
886
|
+
</div>
|
|
887
|
+
</div>
|
|
888
|
+
`;
|
|
889
|
+
})}
|
|
890
|
+
${this._loadingDepth !== void 0 ? this.#renderLoadingColumn() : nothing}
|
|
891
|
+
</div>
|
|
892
|
+
`;
|
|
893
|
+
}
|
|
894
|
+
_getFocusedIndex(depth) {
|
|
895
|
+
const tickled = this.#getTickledNodeAndDepth();
|
|
896
|
+
if (tickled && tickled.depth === depth) return tickled.index;
|
|
897
|
+
return 0;
|
|
898
|
+
}
|
|
899
|
+
_isFocused(node, depth) {
|
|
900
|
+
return this.#isFocused(node, depth);
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Called by subclasses when pointer enters an item.
|
|
904
|
+
* Fires tickle event and switches to mouse mode.
|
|
905
|
+
*/
|
|
906
|
+
_handleItemPointerEnter(node, depth) {
|
|
907
|
+
this.tickled.handlePointerEnter(this.#buildTickledId(node, depth));
|
|
908
|
+
this.dispatchEvent(new CustomEvent(`tickle`, {
|
|
909
|
+
detail: {
|
|
910
|
+
node,
|
|
911
|
+
depth
|
|
912
|
+
},
|
|
913
|
+
bubbles: true,
|
|
914
|
+
composed: true
|
|
915
|
+
}));
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Called by subclasses when pointer leaves the column container.
|
|
919
|
+
*/
|
|
920
|
+
_handleColumnPointerLeave() {
|
|
921
|
+
this.tickled.handlePointerLeave(new PointerEvent("pointerleave"));
|
|
922
|
+
}
|
|
923
|
+
static {
|
|
924
|
+
this.styles = [tickledStyles, css`
|
|
925
|
+
:host {
|
|
926
|
+
--column-bg: var(--surface-3);
|
|
927
|
+
--column-header-bg: var(--surface-2);
|
|
928
|
+
--column-header-text: var(--surface-muted-text);
|
|
929
|
+
--column-separator: var(--surface-5);
|
|
930
|
+
--column-resize-handle: var(--surface-5);
|
|
931
|
+
--column-resize-handle-hover: var(--accent);
|
|
932
|
+
--column-resize-width: 4px;
|
|
933
|
+
--item-hover-bg: var(--item-bg-tickled);
|
|
934
|
+
--item-open-bg: color-mix(in oklch, var(--accent, oklch(60% 0.15 140)) 15%, transparent);
|
|
935
|
+
--item-selected-bg: var(--item-bg-selected);
|
|
936
|
+
--item-selected-text: var(--item-text-selected);
|
|
937
|
+
|
|
938
|
+
display: block;
|
|
939
|
+
overflow: hidden;
|
|
940
|
+
position: relative;
|
|
941
|
+
box-sizing: border-box;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
#container {
|
|
945
|
+
display: flex;
|
|
946
|
+
height: 100%;
|
|
947
|
+
overflow-x: auto;
|
|
948
|
+
outline: none;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
.column {
|
|
952
|
+
display: flex;
|
|
953
|
+
flex-direction: column;
|
|
954
|
+
position: relative;
|
|
955
|
+
background: var(--column-bg);
|
|
956
|
+
border-right: 1px solid var(--column-separator);
|
|
957
|
+
flex-shrink: 0;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
.column-header {
|
|
961
|
+
background: var(--column-header-bg);
|
|
962
|
+
color: var(--column-header-text);
|
|
963
|
+
font-size: var(--text-s, 11px);
|
|
964
|
+
font-family: var(--font-family, system-ui, sans-serif);
|
|
965
|
+
font-weight: var(--font-weight-bold, 600);
|
|
966
|
+
padding: var(--space-xs, 3px) var(--space-m, 8px);
|
|
967
|
+
border-bottom: 1px solid var(--column-separator);
|
|
968
|
+
white-space: nowrap;
|
|
969
|
+
overflow: hidden;
|
|
970
|
+
text-overflow: ellipsis;
|
|
971
|
+
flex-shrink: 0;
|
|
972
|
+
cursor: pointer;
|
|
973
|
+
user-select: none;
|
|
974
|
+
transition: background var(--transition, 0.15s ease-out),
|
|
975
|
+
color var(--transition, 0.15s ease-out);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
.column-header:hover {
|
|
979
|
+
background: var(--item-hover-bg);
|
|
980
|
+
color: var(--surface-2-text, #fff);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
.column-content {
|
|
984
|
+
flex: 1;
|
|
985
|
+
overflow-y: auto;
|
|
986
|
+
overflow-x: visible;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
.resize-handle {
|
|
990
|
+
position: absolute;
|
|
991
|
+
top: 0;
|
|
992
|
+
right: 0;
|
|
993
|
+
width: var(--column-resize-width);
|
|
994
|
+
height: 100%;
|
|
995
|
+
background: var(--column-resize-handle);
|
|
996
|
+
cursor: ew-resize;
|
|
997
|
+
z-index: 1;
|
|
998
|
+
transition: background var(--transition, 0.15s ease-out);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
.resize-handle:hover {
|
|
1002
|
+
background: var(--column-resize-handle-hover);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
.loading-column {
|
|
1006
|
+
min-width: 60px;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
.loading-content {
|
|
1010
|
+
display: flex;
|
|
1011
|
+
align-items: center;
|
|
1012
|
+
justify-content: center;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
.loading-spinner {
|
|
1016
|
+
width: 22px;
|
|
1017
|
+
height: 22px;
|
|
1018
|
+
border: 2px solid var(--column-separator);
|
|
1019
|
+
border-top-color: var(--accent, oklch(60% 0.15 140));
|
|
1020
|
+
border-radius: 50%;
|
|
1021
|
+
animation: miller-spin 0.7s linear infinite;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
@keyframes miller-spin {
|
|
1025
|
+
to { transform: rotate(360deg); }
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
::highlight(incr-search-highlight) {
|
|
1029
|
+
background: var(--search-highlight-bg, var(--accent));
|
|
1030
|
+
color: var(--search-highlight-color, var(--accent-text, #fff));
|
|
1031
|
+
border-radius: 2px;
|
|
1032
|
+
padding: 0 1px;
|
|
1033
|
+
}
|
|
1034
|
+
`];
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1037
|
+
__decorate([property({ type: Object })], MillerBaseElement.prototype, "root", void 0);
|
|
1038
|
+
__decorate([property({ attribute: false })], MillerBaseElement.prototype, "model", void 0);
|
|
1039
|
+
__decorate([property({ type: Boolean })], MillerBaseElement.prototype, "headers", void 0);
|
|
1040
|
+
__decorate([property({ type: String })], MillerBaseElement.prototype, "interactionMode", void 0);
|
|
1041
|
+
__decorate([property({
|
|
1042
|
+
reflect: true,
|
|
1043
|
+
attribute: "selection-filter"
|
|
1044
|
+
})], MillerBaseElement.prototype, "selectionFilter", void 0);
|
|
1045
|
+
__decorate([property({
|
|
1046
|
+
reflect: true,
|
|
1047
|
+
attribute: "exclusivity"
|
|
1048
|
+
})], MillerBaseElement.prototype, "exclusivity", void 0);
|
|
1049
|
+
__decorate([property({ type: Object })], MillerBaseElement.prototype, "loadChildren", void 0);
|
|
1050
|
+
__decorate([property({ attribute: false })], MillerBaseElement.prototype, "filterPredicate", void 0);
|
|
1051
|
+
__decorate([state()], MillerBaseElement.prototype, "_columns", void 0);
|
|
1052
|
+
__decorate([state()], MillerBaseElement.prototype, "_loadingDepth", void 0);
|
|
1053
|
+
__decorate([state()], MillerBaseElement.prototype, "_selectedNodes", void 0);
|
|
1054
|
+
MillerBaseElement = __decorate([customElement(`ixfx-miller-base`)], MillerBaseElement);
|
|
1055
|
+
//#endregion
|
|
1056
|
+
//#region src/miller/miller-list.ts
|
|
1057
|
+
/**
|
|
1058
|
+
* Recursively compute the checkbox state for a node based on `selectedKeys`.
|
|
1059
|
+
*
|
|
1060
|
+
* - Unloaded branch (`children === undefined`): treated as a leaf — we can only
|
|
1061
|
+
* report on what is known.
|
|
1062
|
+
* - Leaf or confirmed-empty branch: checked iff its key is in `selectedKeys`.
|
|
1063
|
+
* - Loaded branch: checked if ALL descendants are checked; unchecked if NONE are;
|
|
1064
|
+
* indeterminate otherwise.
|
|
1065
|
+
*/
|
|
1066
|
+
function computeCheckboxState(node, selectedKeys) {
|
|
1067
|
+
if (node.item.isLeaf || !node.children?.length) return selectedKeys.has(node.item.key) ? `checked` : `unchecked`;
|
|
1068
|
+
const childStates = node.children.map((c) => computeCheckboxState(c, selectedKeys));
|
|
1069
|
+
const allChecked = childStates.every((s) => s === `checked`);
|
|
1070
|
+
const allUnchecked = childStates.every((s) => s === `unchecked`);
|
|
1071
|
+
if (allChecked) return `checked`;
|
|
1072
|
+
if (allUnchecked) return `unchecked`;
|
|
1073
|
+
return `indeterminate`;
|
|
1074
|
+
}
|
|
1075
|
+
let MillerListElement = class MillerListElement extends MillerBaseElement {
|
|
1076
|
+
renderColumnContent(nodes, depth) {
|
|
1077
|
+
const hasIcons = nodes.some((node) => !!node.item.icon);
|
|
1078
|
+
const isCheckedMode = this.interactionMode === `checked`;
|
|
1079
|
+
return html`
|
|
1080
|
+
<ul
|
|
1081
|
+
class="item-list ${hasIcons ? `has-icons` : ``} ${isCheckedMode ? `checked-mode` : ``}"
|
|
1082
|
+
role="listbox"
|
|
1083
|
+
aria-label="Column ${depth}"
|
|
1084
|
+
@pointerleave=${() => this._handleColumnPointerLeave()}
|
|
1085
|
+
>
|
|
1086
|
+
${nodes.map((node) => this.#renderListItem(node, depth))}
|
|
1087
|
+
</ul>
|
|
1088
|
+
`;
|
|
1089
|
+
}
|
|
1090
|
+
#renderListItem(node, depth) {
|
|
1091
|
+
const selected = this._isSelected(node, depth);
|
|
1092
|
+
const tickled = this._isFocused(node, depth);
|
|
1093
|
+
const open = this._isOpen(node, depth);
|
|
1094
|
+
const isBranch = node.item.isLeaf !== true;
|
|
1095
|
+
const icon = node.item.icon;
|
|
1096
|
+
const tooltip = node.item.tooltip;
|
|
1097
|
+
const isCheckedMode = this.interactionMode === `checked`;
|
|
1098
|
+
let checkboxTpl = nothing;
|
|
1099
|
+
if (isCheckedMode) {
|
|
1100
|
+
const state = computeCheckboxState(node, this._selectedKeys);
|
|
1101
|
+
checkboxTpl = html`
|
|
1102
|
+
<input
|
|
1103
|
+
type="checkbox"
|
|
1104
|
+
class="item-checkbox"
|
|
1105
|
+
.checked=${state === `checked`}
|
|
1106
|
+
.indeterminate=${state === `indeterminate`}
|
|
1107
|
+
aria-label="Select ${node.item.label}"
|
|
1108
|
+
@click=${(e) => this._handleCheckboxClick(e, node, depth)}
|
|
1109
|
+
/>
|
|
1110
|
+
`;
|
|
1111
|
+
}
|
|
1112
|
+
return html`
|
|
1113
|
+
<li
|
|
1114
|
+
class="item ${selected ? `selected` : ``} ${open ? `open` : ``} ${tickled ? `tickled` : ``} ${isBranch ? `branch` : `leaf`}"
|
|
1115
|
+
role="option"
|
|
1116
|
+
aria-selected=${selected}
|
|
1117
|
+
@click=${(e) => this._handleItemClick(node, depth, e)}
|
|
1118
|
+
@dblclick=${(e) => this._handleItemDblClick(node, depth, e)}
|
|
1119
|
+
@contextmenu=${(e) => this._handleItemRightClick(node, depth, e)}
|
|
1120
|
+
@pointerenter=${() => this._handleItemPointerEnter(node, depth)}
|
|
1121
|
+
>
|
|
1122
|
+
${checkboxTpl}
|
|
1123
|
+
${icon ? html`<ixfx-icon class="item-icon" name="${icon}"></ixfx-icon>` : nothing}
|
|
1124
|
+
<span class="label">${node.item.label}</span>
|
|
1125
|
+
${tooltip ? html`<ixfx-tooltip class="item-tooltip" anchor="--tooltip-miller-${depth}-${node.item.key}" .value=${tooltip}></ixfx-tooltip>` : nothing}
|
|
1126
|
+
${isBranch ? html`<span class="caret" aria-hidden="true">›</span>` : nothing}
|
|
1127
|
+
</li>
|
|
1128
|
+
`;
|
|
1129
|
+
}
|
|
1130
|
+
static {
|
|
1131
|
+
this.styles = [
|
|
1132
|
+
...MillerBaseElement.styles,
|
|
1133
|
+
tickledItemStyles,
|
|
1134
|
+
css`
|
|
1135
|
+
.item-list {
|
|
1136
|
+
list-style: none;
|
|
1137
|
+
margin: 0;
|
|
1138
|
+
padding: var(--space-xs, 3px) 0;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
.item {
|
|
1142
|
+
display: flex;
|
|
1143
|
+
align-items: center;
|
|
1144
|
+
justify-content: space-between;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
.item.selected {
|
|
1148
|
+
background: var(--item-selected-bg);
|
|
1149
|
+
color: var(--item-selected-text, #fff);
|
|
1150
|
+
outline: 2px dashed var(--surface-3, #333);
|
|
1151
|
+
outline-offset: -2px;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
/* Item is on the currently open path (ancestor of deepest column) */
|
|
1155
|
+
.item.open:not(.selected) {
|
|
1156
|
+
background: var(--item-open-bg);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
.item-icon {
|
|
1160
|
+
flex-shrink: 0;
|
|
1161
|
+
width: var(--icon-size, 1em);
|
|
1162
|
+
height: var(--icon-size, 1em);
|
|
1163
|
+
margin-right: var(--icon-spacing, 0.3em);
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
/* Align text on icon-less rows with icon rows in a mixed list */
|
|
1167
|
+
.item-list.has-icons .item:not(:has(.item-icon)) .label {
|
|
1168
|
+
margin-left: calc(var(--icon-size, 1em) + var(--icon-spacing, 0.3em));
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
.label {
|
|
1172
|
+
flex: 1;
|
|
1173
|
+
overflow: hidden;
|
|
1174
|
+
text-overflow: ellipsis;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
.item-tooltip {
|
|
1178
|
+
position: absolute;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
.caret {
|
|
1182
|
+
flex-shrink: 0;
|
|
1183
|
+
margin-left: var(--space-s, 4px);
|
|
1184
|
+
color: var(--surface-muted-text);
|
|
1185
|
+
font-size: var(--text-l, 13px);
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
.item.selected .caret {
|
|
1189
|
+
color: var(--item-selected-text, #fff);
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
/* ---- checked mode ---- */
|
|
1193
|
+
|
|
1194
|
+
.item-checkbox {
|
|
1195
|
+
flex-shrink: 0;
|
|
1196
|
+
margin-right: var(--space-xs, 3px);
|
|
1197
|
+
cursor: pointer;
|
|
1198
|
+
accent-color: var(--accent);
|
|
1199
|
+
/* Prevent the checkbox click from also triggering item styles */
|
|
1200
|
+
pointer-events: auto;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
/* In checked mode the item row itself is not highlighted on selection —
|
|
1204
|
+
the checkbox communicates state instead. */
|
|
1205
|
+
.item-list.checked-mode .item.selected {
|
|
1206
|
+
background: transparent;
|
|
1207
|
+
color: var(--surface-3-text, #fff);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
|
|
1211
|
+
`
|
|
1212
|
+
];
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
1215
|
+
MillerListElement = __decorate([customElement(`ixfx-miller-list`)], MillerListElement);
|
|
1216
|
+
//#endregion
|
|
1217
|
+
//#region src/miller/miller-tree-controller.ts
|
|
1218
|
+
/**
|
|
1219
|
+
* Bridges an `ixfx` `TraversableTree<T>` with an `ixfx-miller-*` component.
|
|
1220
|
+
*
|
|
1221
|
+
* Usage:
|
|
1222
|
+
* ```ts
|
|
1223
|
+
* import { Trees } from '@ixfx/collections';
|
|
1224
|
+
* import { MillerTreeController } from 'ixfx-components/miller';
|
|
1225
|
+
*
|
|
1226
|
+
* const root = Trees.Mutable.createNode({ key: 'root', label: 'Root' });
|
|
1227
|
+
* Trees.Mutable.addValue({ key: 'a', label: 'Alpha' }, root);
|
|
1228
|
+
*
|
|
1229
|
+
* const ctrl = new MillerTreeController(
|
|
1230
|
+
* Trees.Mutable.asDynamicTraversable(root),
|
|
1231
|
+
* document.querySelector('ixfx-miller-list'),
|
|
1232
|
+
* { valueToItem: v => ({ key: v.key, label: v.label }) }
|
|
1233
|
+
* );
|
|
1234
|
+
* ctrl.connect();
|
|
1235
|
+
* ```
|
|
1236
|
+
*
|
|
1237
|
+
* Children are populated lazily: the controller provides a `loadChildren`
|
|
1238
|
+
* callback that synchronously reads from the underlying tree.
|
|
1239
|
+
*
|
|
1240
|
+
* Call `disconnect()` to remove all event listeners and release resources.
|
|
1241
|
+
*/
|
|
1242
|
+
var MillerTreeController = class {
|
|
1243
|
+
#root;
|
|
1244
|
+
#element;
|
|
1245
|
+
#options;
|
|
1246
|
+
#nodeToTree = /* @__PURE__ */ new WeakMap();
|
|
1247
|
+
#identityToMillerNode = /* @__PURE__ */ new Map();
|
|
1248
|
+
#connectSignal;
|
|
1249
|
+
constructor(root, element, options) {
|
|
1250
|
+
this.#root = root;
|
|
1251
|
+
this.#element = element;
|
|
1252
|
+
this.#options = options;
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* Wire up the controller: sets `element.root` and provides `loadChildren` callback.
|
|
1256
|
+
* Safe to call again after `disconnect()`.
|
|
1257
|
+
*/
|
|
1258
|
+
connect() {
|
|
1259
|
+
this.disconnect();
|
|
1260
|
+
this.#connectSignal = new AbortController();
|
|
1261
|
+
const rootValue = this.#root.getValue();
|
|
1262
|
+
const rootItem = this.#options.valueToItem(rootValue, this.#root);
|
|
1263
|
+
const rootChildren = this.#childrenOf(this.#root);
|
|
1264
|
+
this.#element.root = {
|
|
1265
|
+
item: rootItem,
|
|
1266
|
+
children: rootChildren
|
|
1267
|
+
};
|
|
1268
|
+
this.#element.loadChildren = (node, _depth, _signal) => {
|
|
1269
|
+
const treeNode = this.#nodeToTree.get(node);
|
|
1270
|
+
if (!treeNode) return [];
|
|
1271
|
+
return this.#childrenOf(treeNode);
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
/** Remove all event listeners and release internal state. */
|
|
1275
|
+
disconnect() {
|
|
1276
|
+
this.#connectSignal?.abort();
|
|
1277
|
+
this.#connectSignal = void 0;
|
|
1278
|
+
this.#element.loadChildren = void 0;
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Navigate the component to the first node (depth-first) in the ixfx tree
|
|
1282
|
+
* whose value satisfies `predicate`.
|
|
1283
|
+
*
|
|
1284
|
+
* Any ancestor `MillerNode`s with unloaded children are populated
|
|
1285
|
+
* synchronously before calling `element.navigateTo()`.
|
|
1286
|
+
*
|
|
1287
|
+
* Returns `false` if no matching node is found.
|
|
1288
|
+
*/
|
|
1289
|
+
navigateToValue(predicate) {
|
|
1290
|
+
const treePath = this.#findTreePath(this.#root, predicate);
|
|
1291
|
+
if (!treePath) return false;
|
|
1292
|
+
let currentColumn = this.#element.root?.children ?? [];
|
|
1293
|
+
const keys = [];
|
|
1294
|
+
for (let i = 1; i < treePath.length; i++) {
|
|
1295
|
+
const treeNode = treePath[i];
|
|
1296
|
+
const key = this.#options.valueToItem(treeNode.getValue(), treeNode).key;
|
|
1297
|
+
keys.push(key);
|
|
1298
|
+
if (i < treePath.length - 1) {
|
|
1299
|
+
const millerNode = currentColumn.find((n) => n.item.key === key);
|
|
1300
|
+
if (!millerNode) return false;
|
|
1301
|
+
if (millerNode.children === void 0) {
|
|
1302
|
+
const mapped = this.#nodeToTree.get(millerNode);
|
|
1303
|
+
if (mapped) millerNode.children = this.#childrenOf(mapped);
|
|
1304
|
+
}
|
|
1305
|
+
currentColumn = millerNode.children ?? [];
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
return this.#element.navigateTo(keys);
|
|
1309
|
+
}
|
|
1310
|
+
/** Convert a TraversableTree node's children to MillerNode[]. */
|
|
1311
|
+
#childrenOf(treeNode) {
|
|
1312
|
+
return Array.from(treeNode.children(), (child) => this.#toMillerNode(child));
|
|
1313
|
+
}
|
|
1314
|
+
#toMillerNode(treeNode) {
|
|
1315
|
+
const value = treeNode.getValue();
|
|
1316
|
+
const childList = [...treeNode.children()];
|
|
1317
|
+
const userItem = this.#options.valueToItem(value, treeNode);
|
|
1318
|
+
const isLeaf = userItem.isLeaf ?? childList.length === 0;
|
|
1319
|
+
const millerNode = {
|
|
1320
|
+
item: {
|
|
1321
|
+
...userItem,
|
|
1322
|
+
isLeaf
|
|
1323
|
+
},
|
|
1324
|
+
children: isLeaf ? [] : void 0
|
|
1325
|
+
};
|
|
1326
|
+
this.#nodeToTree.set(millerNode, treeNode);
|
|
1327
|
+
this.#identityToMillerNode.set(treeNode.getIdentity(), millerNode);
|
|
1328
|
+
return millerNode;
|
|
1329
|
+
}
|
|
1330
|
+
/** DFS through the ixfx tree returning the path of nodes to the first match. */
|
|
1331
|
+
#findTreePath(node, predicate, ancestors = []) {
|
|
1332
|
+
const path = [...ancestors, node];
|
|
1333
|
+
if (predicate(node.getValue())) return path;
|
|
1334
|
+
for (const child of node.children()) {
|
|
1335
|
+
const result = this.#findTreePath(child, predicate, path);
|
|
1336
|
+
if (result) return result;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
};
|
|
1340
|
+
//#endregion
|
|
1341
|
+
export { MillerBaseElement, MillerListElement, MillerTreeController };
|
|
1342
|
+
|
|
1343
|
+
//# sourceMappingURL=miller.js.map
|