@ixfx/components 0.2.6 → 0.3.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 +213 -117
- package/bundle/index.d.ts.map +1 -1
- package/bundle/index.js +1438 -686
- package/bundle/index.js.map +1 -1
- package/dist/ac-text.js +3 -3
- package/dist/ac-text.js.map +1 -1
- package/dist/{button-B9ztaKyl.js → button-DMt5XWUK.js} +4 -4
- package/dist/{button-B9ztaKyl.js.map → button-DMt5XWUK.js.map} +1 -1
- package/dist/button.js +1 -1
- package/dist/checkbox.d.ts +2 -3
- package/dist/checkbox.d.ts.map +1 -1
- package/dist/checkbox.js +1 -1
- package/dist/checkbox.js.map +1 -1
- package/dist/{colour-C3MQIjFJ-BJV-QC-3.js → colour-C3MQIjFJ-BLXg8W3U.js} +2 -2
- package/dist/{colour-C3MQIjFJ-BJV-QC-3.js.map → colour-C3MQIjFJ-BLXg8W3U.js.map} +1 -1
- package/dist/{colour-picker-BNqDz4wK.js → colour-picker-K6zu82lm.js} +5 -5
- package/dist/{colour-picker-BNqDz4wK.js.map → colour-picker-K6zu82lm.js.map} +1 -1
- package/dist/colour-picker.js +1 -1
- package/dist/crumbs.d.ts +2 -3
- package/dist/crumbs.d.ts.map +1 -1
- package/dist/crumbs.js +10 -10
- package/dist/crumbs.js.map +1 -1
- package/dist/data-display.js +3 -3
- package/dist/{decorate-O4m1U4l3.js → decorate-D7rC1gLP.js} +10 -8
- package/dist/decorate-D7rC1gLP.js.map +1 -0
- package/dist/{defaults-FEZaGiYZ.js → defaults-C7elhuyJ.js} +2 -2
- package/dist/{defaults-FEZaGiYZ.js.map → defaults-C7elhuyJ.js.map} +1 -1
- package/dist/{dist-By5URG0Y.js → dist-BX0OVIm7.js} +2 -2
- package/dist/{dist-By5URG0Y.js.map → dist-BX0OVIm7.js.map} +1 -1
- package/dist/{dist-D7kJgnSE.js → dist-TAGYwaju.js} +1 -1
- package/dist/{dist-D7kJgnSE.js.map → dist-TAGYwaju.js.map} +1 -1
- package/dist/editable-label.d.ts +6 -1
- package/dist/editable-label.d.ts.map +1 -1
- package/dist/editable-label.js +49 -2
- package/dist/editable-label.js.map +1 -1
- package/dist/{hex-editor-gH3mteMr.js → hex-editor-CsN_ySkH.js} +85 -85
- package/dist/hex-editor-CsN_ySkH.js.map +1 -0
- package/dist/{hex-editor-2_s0Ua5t.d.ts → hex-editor-DlfoA65y.d.ts} +5 -5
- package/dist/{hex-editor-2_s0Ua5t.d.ts.map → hex-editor-DlfoA65y.d.ts.map} +1 -1
- package/dist/hex.d.ts +1 -1
- package/dist/hex.js +1 -1
- package/dist/{highlight-CAjwQ4GE.js → highlight-TPbSHruI.js} +3 -3
- package/dist/highlight-TPbSHruI.js.map +1 -0
- package/dist/{icon-CYACf6o8.js → icon-9NWTXtE1.js} +4 -5
- package/dist/icon-9NWTXtE1.js.map +1 -0
- package/dist/icon-CeTDJhC7.d.ts.map +1 -1
- package/dist/icons.js +3 -3
- package/dist/{incr-search-BZawvrfl.js → incr-search-DSKKsk6w.js} +12 -10
- package/dist/incr-search-DSKKsk6w.js.map +1 -0
- package/dist/incr-search.d.ts +1 -1
- package/dist/incr-search.js +2 -2
- package/dist/{index-CwGkb9s1.d.ts → index--hrxPBkQ.d.ts} +69 -69
- package/dist/index--hrxPBkQ.d.ts.map +1 -0
- package/dist/{index-kY5gxbkI.d.ts → index-DSlPeWLK.d.ts} +15 -15
- package/dist/{index-kY5gxbkI.d.ts.map → index-DSlPeWLK.d.ts.map} +1 -1
- package/dist/{index-V2cJorlA.d.ts → index-hXFn14tM.d.ts} +4 -4
- package/dist/index-hXFn14tM.d.ts.map +1 -0
- package/dist/index.d.ts +109 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +748 -52
- package/dist/index.js.map +1 -1
- package/dist/keyboard-De_vOHLg.js.map +1 -1
- package/dist/{labelled-input-base-BvgH7ldm.js → labelled-input-base-CqPL5rKm.js} +2 -2
- package/dist/labelled-input-base-CqPL5rKm.js.map +1 -0
- package/dist/labelled-radial-input.js +3 -3
- package/dist/labelled-radial-input.js.map +1 -1
- package/dist/labelled-range-input.js +2 -2
- package/dist/led.js +1 -1
- package/dist/{menu-C3eANB5M.js → menu-DJJuCm-5.js} +48 -48
- package/dist/menu-DJJuCm-5.js.map +1 -0
- package/dist/{menu-item-6IU5weHi.js → menu-item-CKRfP0pN.js} +10 -10
- package/dist/menu-item-CKRfP0pN.js.map +1 -0
- package/dist/menu.d.ts +1 -1
- package/dist/menu.js +2 -2
- package/dist/miller.d.ts +5 -7
- package/dist/miller.d.ts.map +1 -1
- package/dist/miller.js +14 -14
- package/dist/miller.js.map +1 -1
- package/dist/narrowed-text.d.ts.map +1 -1
- package/dist/narrowed-text.js +4 -4
- package/dist/narrowed-text.js.map +1 -1
- package/dist/panel.js +2 -2
- package/dist/panel.js.map +1 -1
- package/dist/plots.js +1 -1
- package/dist/polar-pad.d.ts.map +1 -1
- package/dist/polar-pad.js +1 -1
- package/dist/polar-pad.js.map +1 -1
- package/dist/{popup-Ddx9LCzY.js → popup-ImtS9KnS.js} +1 -1
- package/dist/{popup-Ddx9LCzY.js.map → popup-ImtS9KnS.js.map} +1 -1
- package/dist/{prominence-CreDBdCs.js → prominence--ic9uWBl.js} +1 -1
- package/dist/{prominence-CreDBdCs.js.map → prominence--ic9uWBl.js.map} +1 -1
- package/dist/{radial-input-Dbc6fgCK.js → radial-input-DZA6WaFu.js} +14 -14
- package/dist/radial-input-DZA6WaFu.js.map +1 -0
- package/dist/radial-input.js +1 -1
- package/dist/range-input.js +1 -1
- package/dist/range.js +2 -2
- package/dist/{registry-CZEB1YI9.js → registry-Q2gYQCHM.js} +1 -1
- package/dist/{registry-CZEB1YI9.js.map → registry-Q2gYQCHM.js.map} +1 -1
- package/dist/selecthorizontal.js +1 -1
- package/dist/snap-container.js +1 -1
- package/dist/split-layout.js +4 -4
- package/dist/split-layout.js.map +1 -1
- package/dist/swipe.d.ts +1 -2
- package/dist/swipe.d.ts.map +1 -1
- package/dist/swipe.js +2 -2
- package/dist/swipe.js.map +1 -1
- package/dist/tab-list-BnqISNvO.d.ts.map +1 -1
- package/dist/tabs.js +2 -2
- package/dist/tabs.js.map +1 -1
- package/dist/{timeline-B62Sz6J_.js → timeline-Cv75Th2U.js} +120 -116
- package/dist/timeline-Cv75Th2U.js.map +1 -0
- package/dist/timeline.d.ts +1 -1
- package/dist/timeline.js +1 -1
- package/dist/tooltip-CwGfb4lp.d.ts.map +1 -1
- package/dist/tree-CvvKbO-f.js +1330 -0
- package/dist/tree-CvvKbO-f.js.map +1 -0
- package/dist/{tree-component-CQpoPo4s.d.ts → tree-component-D8Bg48tt.d.ts} +12 -12
- package/dist/{tree-component-CQpoPo4s.d.ts.map → tree-component-D8Bg48tt.d.ts.map} +1 -1
- package/dist/tree.d.ts +1 -1
- package/dist/tree.js +1 -1143
- package/dist/xy-axis-D9uwpGTT.d.ts.map +1 -1
- package/dist/{xy-axis-C7fNxDwY.js → xy-axis-DL0tfJDr.js} +10 -10
- package/dist/{xy-axis-C7fNxDwY.js.map → xy-axis-DL0tfJDr.js.map} +1 -1
- package/dist/xy-pad.js +2 -2
- package/package.json +3 -3
- package/dist/decorate-O4m1U4l3.js.map +0 -1
- package/dist/hex-editor-gH3mteMr.js.map +0 -1
- package/dist/highlight-CAjwQ4GE.js.map +0 -1
- package/dist/icon-CYACf6o8.js.map +0 -1
- package/dist/incr-search-BZawvrfl.js.map +0 -1
- package/dist/index-CwGkb9s1.d.ts.map +0 -1
- package/dist/index-V2cJorlA.d.ts.map +0 -1
- package/dist/labelled-input-base-BvgH7ldm.js.map +0 -1
- package/dist/menu-C3eANB5M.js.map +0 -1
- package/dist/menu-item-6IU5weHi.js.map +0 -1
- package/dist/radial-input-Dbc6fgCK.js.map +0 -1
- package/dist/timeline-B62Sz6J_.js.map +0 -1
- package/dist/tree-data-model-DGvRVOFY.js +0 -190
- package/dist/tree-data-model-DGvRVOFY.js.map +0 -1
- package/dist/tree.js.map +0 -1
- /package/dist/{interaction-CQDQJf2Q.js → interaction-DG7X1rhI.js} +0 -0
|
@@ -0,0 +1,1330 @@
|
|
|
1
|
+
import { n as tickledStyles, r as TickledController, t as tickledItemStyles } from "./tickled-styles-Bg3QbcrD.js";
|
|
2
|
+
import { n as safeCustomElement, t as __decorate } from "./decorate-D7rC1gLP.js";
|
|
3
|
+
import "./interaction-DG7X1rhI.js";
|
|
4
|
+
import { LitElement, css, html, nothing } from "lit";
|
|
5
|
+
import { property, state } from "lit/decorators.js";
|
|
6
|
+
//#region src/common/tree-data-model.ts
|
|
7
|
+
function findNode(root, key) {
|
|
8
|
+
if (root.item.key === key) return root;
|
|
9
|
+
if (!root.children) return void 0;
|
|
10
|
+
for (const child of root.children) {
|
|
11
|
+
const found = findNode(child, key);
|
|
12
|
+
if (found) return found;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** Return a new root with the node at `key` replaced by `replacer(node)`. */
|
|
16
|
+
function mapNode(root, key, replacer) {
|
|
17
|
+
if (root.item.key === key) return replacer(root);
|
|
18
|
+
if (!root.children) return root;
|
|
19
|
+
return {
|
|
20
|
+
item: root.item,
|
|
21
|
+
children: root.children.map((c) => mapNode(c, key, replacer))
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** Return a new root with `newChild` appended to the children of the node at `parentKey`. */
|
|
25
|
+
function addChildTo(root, parentKey, newChild) {
|
|
26
|
+
return mapNode(root, parentKey, (parent) => ({
|
|
27
|
+
item: parent.item,
|
|
28
|
+
children: [...parent.children ?? [], newChild]
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
/** Return a new root with the node at `key` removed (along with all its descendants). */
|
|
32
|
+
function removeNodeFrom(root, key) {
|
|
33
|
+
if (!root.children) return root;
|
|
34
|
+
return {
|
|
35
|
+
item: root.item,
|
|
36
|
+
children: root.children.filter((c) => c.item.key !== key).map((c) => removeNodeFrom(c, key))
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Return a new root with the node at `key` moved to become a child of `newParentKey`. */
|
|
40
|
+
function moveNodeTo(root, key, newParentKey) {
|
|
41
|
+
const target = findNode(root, key);
|
|
42
|
+
if (!target) return root;
|
|
43
|
+
return addChildTo(removeNodeFrom(root, key), newParentKey, target);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Manages tree data for tree-like components.
|
|
47
|
+
*
|
|
48
|
+
* Handles two concerns:
|
|
49
|
+
* 1. **Lazy loading** — caches children fetched via `loadChildren` and can invalidate
|
|
50
|
+
* specific branches so they are re-fetched on next expand.
|
|
51
|
+
* 2. **Structural mutations** — convenience methods to add, remove, update, or move
|
|
52
|
+
* nodes. Each mutation produces a new immutable root and notifies connected components.
|
|
53
|
+
*
|
|
54
|
+
* Assign an instance to a component's `model` property instead of setting `root`/`loadChildren`
|
|
55
|
+
* directly. The same model instance can be shared across multiple components.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* const model = new TreeDataModel({
|
|
60
|
+
* root: { item: { key: 'root', label: 'Root' }, children: [] },
|
|
61
|
+
* loadChildren: async (node, depth, signal) => fetchChildren(node.item.key),
|
|
62
|
+
* });
|
|
63
|
+
* myTreeList.model = model;
|
|
64
|
+
* myMillerList.model = model;
|
|
65
|
+
*
|
|
66
|
+
* // Later — invalidate a branch to force a re-fetch on next expand:
|
|
67
|
+
* model.invalidate('some-key');
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
var TreeDataModel = class {
|
|
71
|
+
#root;
|
|
72
|
+
#loadChildren;
|
|
73
|
+
#cache = /* @__PURE__ */ new Map();
|
|
74
|
+
#subscribers = /* @__PURE__ */ new Set();
|
|
75
|
+
constructor(options) {
|
|
76
|
+
this.#root = options.root;
|
|
77
|
+
this.#loadChildren = options.loadChildren;
|
|
78
|
+
}
|
|
79
|
+
/** The current immutable root snapshot. */
|
|
80
|
+
get root() {
|
|
81
|
+
return this.#root;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Replace the entire root with a new immutable snapshot.
|
|
85
|
+
* All cached children are cleared. Connected components re-render.
|
|
86
|
+
*/
|
|
87
|
+
setRoot(root) {
|
|
88
|
+
this.#root = root;
|
|
89
|
+
this.#cache.clear();
|
|
90
|
+
this.#notify({
|
|
91
|
+
type: `root`,
|
|
92
|
+
root
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Returns children for the given node.
|
|
97
|
+
* If children are cached, they are returned immediately (as a resolved Promise).
|
|
98
|
+
* Otherwise the `loadChildren` callback is invoked and the result is cached.
|
|
99
|
+
* If no `loadChildren` is set and no cache entry exists, returns `[]`.
|
|
100
|
+
*
|
|
101
|
+
* Called by components internally — not typically called by application code.
|
|
102
|
+
*/
|
|
103
|
+
async getChildren(node, depth, signal) {
|
|
104
|
+
const key = node.item.key;
|
|
105
|
+
if (this.#cache.has(key)) return this.#cache.get(key);
|
|
106
|
+
if (!this.#loadChildren) return [];
|
|
107
|
+
const result = await this.#loadChildren(node, depth, signal);
|
|
108
|
+
if (!signal.aborted) this.#cache.set(key, result);
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Clears the cached children for the node with the given key.
|
|
113
|
+
* Connected components will collapse that node (if currently expanded), so that
|
|
114
|
+
* the next expand triggers a fresh `loadChildren` call.
|
|
115
|
+
*/
|
|
116
|
+
invalidate(key) {
|
|
117
|
+
this.#cache.delete(key);
|
|
118
|
+
this.#notify({
|
|
119
|
+
type: `invalidate`,
|
|
120
|
+
key
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Adds `node` as a child of the node identified by `parentKey`.
|
|
125
|
+
* Returns the new root (connected components are notified automatically).
|
|
126
|
+
*/
|
|
127
|
+
addNode(parentKey, node) {
|
|
128
|
+
this.#root = addChildTo(this.#root, parentKey, node);
|
|
129
|
+
this.#notify({
|
|
130
|
+
type: `root`,
|
|
131
|
+
root: this.#root
|
|
132
|
+
});
|
|
133
|
+
return this.#root;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Removes the node with the given key and all its descendants.
|
|
137
|
+
* Returns the new root (connected components are notified automatically).
|
|
138
|
+
*/
|
|
139
|
+
removeNode(key) {
|
|
140
|
+
this.#cache.delete(key);
|
|
141
|
+
this.#root = removeNodeFrom(this.#root, key);
|
|
142
|
+
this.#notify({
|
|
143
|
+
type: `root`,
|
|
144
|
+
root: this.#root
|
|
145
|
+
});
|
|
146
|
+
return this.#root;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Applies a partial update to the `item` of the node with the given key.
|
|
150
|
+
* Returns the new root (connected components are notified automatically).
|
|
151
|
+
*/
|
|
152
|
+
updateItem(key, patch) {
|
|
153
|
+
this.#root = mapNode(this.#root, key, (n) => ({
|
|
154
|
+
item: {
|
|
155
|
+
...n.item,
|
|
156
|
+
...patch
|
|
157
|
+
},
|
|
158
|
+
children: n.children
|
|
159
|
+
}));
|
|
160
|
+
this.#notify({
|
|
161
|
+
type: `root`,
|
|
162
|
+
root: this.#root
|
|
163
|
+
});
|
|
164
|
+
return this.#root;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Moves the node with the given key to become a child of `newParentKey`.
|
|
168
|
+
* Returns the new root (connected components are notified automatically).
|
|
169
|
+
*/
|
|
170
|
+
moveNode(key, newParentKey) {
|
|
171
|
+
this.#root = moveNodeTo(this.#root, key, newParentKey);
|
|
172
|
+
this.#notify({
|
|
173
|
+
type: `root`,
|
|
174
|
+
root: this.#root
|
|
175
|
+
});
|
|
176
|
+
return this.#root;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Subscribe to model events. Returns an unsubscribe function.
|
|
180
|
+
* Components call this when the `model` property is set, and unsubscribe when it is unset.
|
|
181
|
+
*
|
|
182
|
+
* @internal
|
|
183
|
+
*/
|
|
184
|
+
_subscribe(callback) {
|
|
185
|
+
this.#subscribers.add(callback);
|
|
186
|
+
return () => this.#subscribers.delete(callback);
|
|
187
|
+
}
|
|
188
|
+
#notify(event) {
|
|
189
|
+
for (const sub of this.#subscribers) sub(event);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
//#endregion
|
|
193
|
+
//#region src/tree/tree-base.ts
|
|
194
|
+
let TreeBaseElement = class TreeBaseElement extends LitElement {
|
|
195
|
+
constructor(..._args) {
|
|
196
|
+
super(..._args);
|
|
197
|
+
this.exclusivity = `none`;
|
|
198
|
+
this.selectionFilter = `all`;
|
|
199
|
+
this.interactionMode = `standard`;
|
|
200
|
+
this._selectedNodes = /* @__PURE__ */ new Set();
|
|
201
|
+
this.disableKeyboardNavigation = false;
|
|
202
|
+
this._expandedKeys = /* @__PURE__ */ new Set();
|
|
203
|
+
this.tickled = new TickledController(this, {
|
|
204
|
+
getItems: () => {
|
|
205
|
+
return this.#getAllNodes().map(({ node }) => ({
|
|
206
|
+
id: node.item.key,
|
|
207
|
+
enabled: true
|
|
208
|
+
}));
|
|
209
|
+
},
|
|
210
|
+
onStateChange: () => {
|
|
211
|
+
this.setAttribute(`data-input-mode`, this.tickled.inputMode);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
this._focusedDepth = 0;
|
|
215
|
+
this.#config = { indent: 20 };
|
|
216
|
+
this.#onKeyDown = (event) => {
|
|
217
|
+
const nodes = this.#getAllNodes();
|
|
218
|
+
if (nodes.length === 0) return;
|
|
219
|
+
if ([
|
|
220
|
+
`ArrowDown`,
|
|
221
|
+
`ArrowUp`,
|
|
222
|
+
`ArrowLeft`,
|
|
223
|
+
`ArrowRight`,
|
|
224
|
+
`Enter`,
|
|
225
|
+
` `
|
|
226
|
+
].includes(event.key)) this.tickled.setInputMode(`keyboard`);
|
|
227
|
+
const currentIdx = this.#getFocusedIndex();
|
|
228
|
+
switch (event.key) {
|
|
229
|
+
case `ArrowDown`: {
|
|
230
|
+
event.preventDefault();
|
|
231
|
+
const nextIdx = Math.min(currentIdx + 1, nodes.length - 1);
|
|
232
|
+
if (this.interactionMode === `implicit`) this.select(nodes[nextIdx].node);
|
|
233
|
+
this.#setFocusedIndex(nextIdx);
|
|
234
|
+
if (event.shiftKey && (this.interactionMode === `standard` || this.interactionMode === `vscode`)) this.#extendRangeSelection(nodes, currentIdx, nextIdx);
|
|
235
|
+
else if (this.interactionMode === `vscode`) this.#anchor = void 0;
|
|
236
|
+
this.requestUpdate();
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
case `ArrowUp`: {
|
|
240
|
+
event.preventDefault();
|
|
241
|
+
const prevIdx = Math.max(currentIdx - 1, 0);
|
|
242
|
+
if (this.interactionMode === `implicit`) this.select(nodes[prevIdx].node);
|
|
243
|
+
this.#setFocusedIndex(prevIdx);
|
|
244
|
+
if (event.shiftKey && (this.interactionMode === `standard` || this.interactionMode === `vscode`)) this.#extendRangeSelection(nodes, currentIdx, prevIdx);
|
|
245
|
+
else if (this.interactionMode === `vscode`) this.#anchor = void 0;
|
|
246
|
+
this.requestUpdate();
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
case `ArrowRight`: {
|
|
250
|
+
event.preventDefault();
|
|
251
|
+
const current = nodes[currentIdx];
|
|
252
|
+
if (current && current.node.item.isLeaf !== true) {
|
|
253
|
+
if (!this._expandedKeys.has(current.node.item.key)) this.#toggleExpand(current.node, current.depth);
|
|
254
|
+
else if (currentIdx + 1 < nodes.length) this.#setFocusedIndex(currentIdx + 1);
|
|
255
|
+
}
|
|
256
|
+
this.requestUpdate();
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
case `ArrowLeft`: {
|
|
260
|
+
event.preventDefault();
|
|
261
|
+
const current = nodes[currentIdx];
|
|
262
|
+
if (current && this._expandedKeys.has(current.node.item.key)) {
|
|
263
|
+
this._expandedKeys.delete(current.node.item.key);
|
|
264
|
+
this.dispatchEvent(new CustomEvent(`collapse`, {
|
|
265
|
+
detail: {
|
|
266
|
+
node: current.node,
|
|
267
|
+
depth: current.depth
|
|
268
|
+
},
|
|
269
|
+
bubbles: true,
|
|
270
|
+
composed: true
|
|
271
|
+
}));
|
|
272
|
+
} else if (current.depth > 1) {
|
|
273
|
+
const parentDepth = current.depth - 1;
|
|
274
|
+
for (let i = currentIdx - 1; i >= 0; i--) if (nodes[i].depth === parentDepth) {
|
|
275
|
+
this.#setFocusedIndex(i);
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
this.requestUpdate();
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
case `Enter`: {
|
|
283
|
+
event.preventDefault();
|
|
284
|
+
const current = nodes[currentIdx];
|
|
285
|
+
if (current) {
|
|
286
|
+
if (this.interactionMode !== `manual` && this.interactionMode !== `checked`) this.select(current.node);
|
|
287
|
+
this.dispatchEvent(new CustomEvent(`activate`, {
|
|
288
|
+
detail: {
|
|
289
|
+
node: current.node,
|
|
290
|
+
depth: current.depth
|
|
291
|
+
},
|
|
292
|
+
bubbles: true,
|
|
293
|
+
composed: true
|
|
294
|
+
}));
|
|
295
|
+
this.requestUpdate();
|
|
296
|
+
}
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
case ` `: {
|
|
300
|
+
event.preventDefault();
|
|
301
|
+
const current = nodes[currentIdx];
|
|
302
|
+
if (!current) break;
|
|
303
|
+
if (this.interactionMode === `checked`) this._handleCheckboxClick(new MouseEvent(`click`), current.node, current.depth);
|
|
304
|
+
else if (this.interactionMode === `standard` || this.interactionMode === `vscode` || this.interactionMode === `manual`) this.#toggleSelection(current.node, current.depth);
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
case `Escape`:
|
|
308
|
+
event.preventDefault();
|
|
309
|
+
this.#abortPending();
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
this.#onDblClick = (e) => {
|
|
314
|
+
const li = e.target.closest(`li`);
|
|
315
|
+
if (!li) return;
|
|
316
|
+
const key = li.dataset.key;
|
|
317
|
+
if (!key) return;
|
|
318
|
+
const node = this.#getAllNodes().find((n) => n.node.item.key === key);
|
|
319
|
+
if (!node) return;
|
|
320
|
+
this.dispatchEvent(new CustomEvent(`activate`, {
|
|
321
|
+
detail: {
|
|
322
|
+
node: node.node,
|
|
323
|
+
depth: node.depth
|
|
324
|
+
},
|
|
325
|
+
bubbles: true,
|
|
326
|
+
composed: true
|
|
327
|
+
}));
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
get selectedNodes() {
|
|
331
|
+
return this._selectedNodes;
|
|
332
|
+
}
|
|
333
|
+
#pendingNode;
|
|
334
|
+
#pendingController;
|
|
335
|
+
#modelUnsubscribe;
|
|
336
|
+
#anchor;
|
|
337
|
+
#config;
|
|
338
|
+
updated(changedProps) {
|
|
339
|
+
if (changedProps.has(`root`) && this.root) {
|
|
340
|
+
this.#abortPending();
|
|
341
|
+
this._expandedKeys.clear();
|
|
342
|
+
this.#clearSelectionInternal();
|
|
343
|
+
this.tickled.clearTickled();
|
|
344
|
+
this._focusedDepth = 0;
|
|
345
|
+
}
|
|
346
|
+
if (changedProps.has(`model`)) {
|
|
347
|
+
this.#modelUnsubscribe?.();
|
|
348
|
+
this.#modelUnsubscribe = void 0;
|
|
349
|
+
const model = this.model;
|
|
350
|
+
if (model) {
|
|
351
|
+
this.root = model.root;
|
|
352
|
+
this.#modelUnsubscribe = model._subscribe((event) => {
|
|
353
|
+
if (event.type === `root`) {
|
|
354
|
+
this.root = event.root;
|
|
355
|
+
this.requestUpdate();
|
|
356
|
+
} else if (event.type === `invalidate`) {
|
|
357
|
+
if (this._expandedKeys.has(event.key)) {
|
|
358
|
+
this._expandedKeys.delete(event.key);
|
|
359
|
+
this.requestUpdate();
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (changedProps.has(`interactionMode`)) {
|
|
366
|
+
this.#anchor = void 0;
|
|
367
|
+
this.clearSelection();
|
|
368
|
+
}
|
|
369
|
+
if (changedProps.has(`selectedNode`) && this.selectedNode) {
|
|
370
|
+
if (changedProps.get(`selectedNode`) !== this.selectedNode) this.tickled.setTickled(this.selectedNode.item.key);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
connectedCallback() {
|
|
374
|
+
super.connectedCallback();
|
|
375
|
+
this.setAttribute(`data-input-mode`, `mouse`);
|
|
376
|
+
}
|
|
377
|
+
disconnectedCallback() {
|
|
378
|
+
super.disconnectedCallback();
|
|
379
|
+
this.#modelUnsubscribe?.();
|
|
380
|
+
this.#modelUnsubscribe = void 0;
|
|
381
|
+
}
|
|
382
|
+
getConfig() {
|
|
383
|
+
return { ...this.#config };
|
|
384
|
+
}
|
|
385
|
+
setConfig(config) {
|
|
386
|
+
this.#config = {
|
|
387
|
+
...this.#config,
|
|
388
|
+
...config
|
|
389
|
+
};
|
|
390
|
+
this.requestUpdate();
|
|
391
|
+
}
|
|
392
|
+
select(node) {
|
|
393
|
+
if (!this.#canSelect(node)) return;
|
|
394
|
+
const prev = new Set(this._selectedNodes);
|
|
395
|
+
this._selectedNodes.clear();
|
|
396
|
+
this._selectedNodes.add(node);
|
|
397
|
+
this.selectedNode = node;
|
|
398
|
+
this.#dispatchSelect(prev);
|
|
399
|
+
this.requestUpdate();
|
|
400
|
+
}
|
|
401
|
+
deselect(node) {
|
|
402
|
+
if (!this._selectedNodes.has(node)) return;
|
|
403
|
+
const prev = new Set(this._selectedNodes);
|
|
404
|
+
this._selectedNodes.delete(node);
|
|
405
|
+
if (this.selectedNode === node) this.selectedNode = [...this._selectedNodes].at(-1);
|
|
406
|
+
this.#dispatchSelect(prev);
|
|
407
|
+
this.requestUpdate();
|
|
408
|
+
}
|
|
409
|
+
clearSelection() {
|
|
410
|
+
if (this._selectedNodes.size === 0) return;
|
|
411
|
+
const prev = new Set(this._selectedNodes);
|
|
412
|
+
this.#clearSelectionInternal();
|
|
413
|
+
this.#dispatchSelect(prev);
|
|
414
|
+
this.requestUpdate();
|
|
415
|
+
}
|
|
416
|
+
navigateTo(keys) {
|
|
417
|
+
if (!this.root?.children) return false;
|
|
418
|
+
this.#abortPending();
|
|
419
|
+
this.#clearSelectionInternal();
|
|
420
|
+
this.tickled.clearTickled();
|
|
421
|
+
this._focusedDepth = 0;
|
|
422
|
+
let currentNode = this.root;
|
|
423
|
+
for (const key of keys) {
|
|
424
|
+
const children = currentNode?.children;
|
|
425
|
+
if (!children) return false;
|
|
426
|
+
const found = children.find((n) => n.item.key === key);
|
|
427
|
+
if (!found) return false;
|
|
428
|
+
this.#applyExclusivity(found.item.key);
|
|
429
|
+
this._expandedKeys.add(found.item.key);
|
|
430
|
+
if (key === keys.at(-1)) {
|
|
431
|
+
this.select(found);
|
|
432
|
+
this.tickled.setTickled(found.item.key);
|
|
433
|
+
}
|
|
434
|
+
currentNode = found;
|
|
435
|
+
}
|
|
436
|
+
this.requestUpdate();
|
|
437
|
+
this.#scrollKeyIntoView(keys.at(-1));
|
|
438
|
+
return true;
|
|
439
|
+
}
|
|
440
|
+
navigateToNode(predicate) {
|
|
441
|
+
if (!this.root?.children) return false;
|
|
442
|
+
const keyPath = this.#findNodePath(this.root.children, predicate);
|
|
443
|
+
if (!keyPath) return false;
|
|
444
|
+
return this.navigateTo(keyPath);
|
|
445
|
+
}
|
|
446
|
+
getNodes() {
|
|
447
|
+
if (!this.root?.children) return [];
|
|
448
|
+
const result = [];
|
|
449
|
+
const visit = (node, depth) => {
|
|
450
|
+
result.push({
|
|
451
|
+
node,
|
|
452
|
+
depth
|
|
453
|
+
});
|
|
454
|
+
if (node.children) for (const child of node.children) visit(child, depth + 1);
|
|
455
|
+
};
|
|
456
|
+
for (const child of this.root.children) visit(child, 0);
|
|
457
|
+
return result;
|
|
458
|
+
}
|
|
459
|
+
#canSelect(node) {
|
|
460
|
+
if (this.selectionFilter === `none`) return false;
|
|
461
|
+
if (this.selectionFilter === `leaf`) return node.item.isLeaf === true;
|
|
462
|
+
if (this.selectionFilter === `branch`) return node.item.isLeaf === false || node.children !== void 0 && node.children.length > 0;
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
465
|
+
#clearSelectionInternal() {
|
|
466
|
+
this._selectedNodes.clear();
|
|
467
|
+
this.selectedNode = void 0;
|
|
468
|
+
}
|
|
469
|
+
#dispatchSelect(previous) {
|
|
470
|
+
const detail = {
|
|
471
|
+
selected: new Set(this._selectedNodes),
|
|
472
|
+
previous
|
|
473
|
+
};
|
|
474
|
+
this.dispatchEvent(new CustomEvent(`select`, {
|
|
475
|
+
detail,
|
|
476
|
+
bubbles: true,
|
|
477
|
+
composed: true
|
|
478
|
+
}));
|
|
479
|
+
}
|
|
480
|
+
#findNodePath(nodes, predicate, prefix = []) {
|
|
481
|
+
for (const node of nodes) {
|
|
482
|
+
const path = [...prefix, node.item.key];
|
|
483
|
+
if (predicate(node)) return path;
|
|
484
|
+
if (node.children?.length) {
|
|
485
|
+
const result = this.#findNodePath(node.children, predicate, path);
|
|
486
|
+
if (result) return result;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
renderNodeContent(node, _depth) {
|
|
491
|
+
const isBranch = node.item.isLeaf !== true;
|
|
492
|
+
const isExpanded = this._isExpanded(node);
|
|
493
|
+
return html`
|
|
494
|
+
<span class="tree-caret">
|
|
495
|
+
${isBranch ? html`<ixfx-icon class="caret-icon" name="chevron-down" .rotate=${isExpanded ? nothing : `-90deg`}></ixfx-icon>` : ``}
|
|
496
|
+
</span>
|
|
497
|
+
<span class="tree-label">${node.item.label}</span>
|
|
498
|
+
`;
|
|
499
|
+
}
|
|
500
|
+
_handleItemClick(node, depth, event) {
|
|
501
|
+
if (this.#pendingNode && this._expandedKeys.has(this.#pendingNode.item.key)) this.#abortPending();
|
|
502
|
+
this.tickled.handlePointerEnter(node.item.key);
|
|
503
|
+
this._focusedDepth = depth;
|
|
504
|
+
this.dispatchEvent(new CustomEvent(`item-click`, {
|
|
505
|
+
detail: {
|
|
506
|
+
node,
|
|
507
|
+
depth
|
|
508
|
+
},
|
|
509
|
+
bubbles: true,
|
|
510
|
+
composed: true
|
|
511
|
+
}));
|
|
512
|
+
switch (this.interactionMode) {
|
|
513
|
+
case `implicit`:
|
|
514
|
+
this.#handleImplicitClick(node, depth);
|
|
515
|
+
break;
|
|
516
|
+
case `standard`:
|
|
517
|
+
this.#handleStandardClick(node, depth, event);
|
|
518
|
+
break;
|
|
519
|
+
case `vscode`:
|
|
520
|
+
this.#handleStandardClick(node, depth, event);
|
|
521
|
+
break;
|
|
522
|
+
case `manual`:
|
|
523
|
+
this.#handleManualClick(node, depth, event);
|
|
524
|
+
break;
|
|
525
|
+
case `checked`:
|
|
526
|
+
this.#handleExpandOnly(node, depth);
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
this.requestUpdate();
|
|
530
|
+
}
|
|
531
|
+
#handleImplicitClick(node, depth) {
|
|
532
|
+
if (this.#canSelect(node)) {
|
|
533
|
+
const prev = new Set(this._selectedNodes);
|
|
534
|
+
this._selectedNodes.clear();
|
|
535
|
+
this._selectedNodes.add(node);
|
|
536
|
+
this.selectedNode = node;
|
|
537
|
+
if (!prev.has(node) || prev.size !== 1) this.#dispatchSelect(prev);
|
|
538
|
+
}
|
|
539
|
+
this.#toggleExpand(node, depth);
|
|
540
|
+
}
|
|
541
|
+
#handleStandardClick(node, depth, event) {
|
|
542
|
+
const isMeta = event ? event.metaKey || event.ctrlKey : false;
|
|
543
|
+
const isShift = event ? event.shiftKey : false;
|
|
544
|
+
if (isMeta) {
|
|
545
|
+
if (this.#canSelect(node)) {
|
|
546
|
+
const prev = new Set(this._selectedNodes);
|
|
547
|
+
if (this._selectedNodes.has(node)) {
|
|
548
|
+
this._selectedNodes.delete(node);
|
|
549
|
+
if (this.selectedNode === node) this.selectedNode = [...this._selectedNodes].at(-1);
|
|
550
|
+
} else {
|
|
551
|
+
this._selectedNodes.add(node);
|
|
552
|
+
this.selectedNode = node;
|
|
553
|
+
this.#anchor = {
|
|
554
|
+
node,
|
|
555
|
+
flatIndex: this.#getFlatIndexOf(node)
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
this.#dispatchSelect(prev);
|
|
559
|
+
}
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
if (isShift && this.#anchor) {
|
|
563
|
+
const visible = this.#getAllNodes();
|
|
564
|
+
const anchorIdx = visible.findIndex((n) => n.node === this.#anchor.node);
|
|
565
|
+
const targetIdx = visible.findIndex((n) => n.node === node);
|
|
566
|
+
if (anchorIdx >= 0 && targetIdx >= 0) {
|
|
567
|
+
const prev = new Set(this._selectedNodes);
|
|
568
|
+
const from = Math.min(anchorIdx, targetIdx);
|
|
569
|
+
const to = Math.max(anchorIdx, targetIdx);
|
|
570
|
+
const rangeNodes = visible.slice(from, to + 1).map((n) => n.node).filter((n) => this.#canSelect(n));
|
|
571
|
+
this._selectedNodes = new Set(rangeNodes);
|
|
572
|
+
this.selectedNode = node;
|
|
573
|
+
this.#dispatchSelect(prev);
|
|
574
|
+
}
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
if (this.#canSelect(node)) {
|
|
578
|
+
const prev = new Set(this._selectedNodes);
|
|
579
|
+
this._selectedNodes.clear();
|
|
580
|
+
this._selectedNodes.add(node);
|
|
581
|
+
this.selectedNode = node;
|
|
582
|
+
this.#anchor = {
|
|
583
|
+
node,
|
|
584
|
+
flatIndex: this.#getFlatIndexOf(node)
|
|
585
|
+
};
|
|
586
|
+
if (!prev.has(node) || prev.size !== 1) this.#dispatchSelect(prev);
|
|
587
|
+
}
|
|
588
|
+
this.#toggleExpand(node, depth);
|
|
589
|
+
}
|
|
590
|
+
#handleExpandOnly(node, depth) {
|
|
591
|
+
this.#toggleExpand(node, depth);
|
|
592
|
+
}
|
|
593
|
+
#handleManualClick(node, depth, event) {
|
|
594
|
+
const isMeta = event ? event.metaKey || event.ctrlKey : false;
|
|
595
|
+
const isShift = event ? event.shiftKey : false;
|
|
596
|
+
if (isMeta) {
|
|
597
|
+
if (this.#canSelect(node)) {
|
|
598
|
+
const prev = new Set(this._selectedNodes);
|
|
599
|
+
if (this._selectedNodes.has(node)) {
|
|
600
|
+
this._selectedNodes.delete(node);
|
|
601
|
+
if (this.selectedNode === node) this.selectedNode = [...this._selectedNodes].at(-1);
|
|
602
|
+
} else {
|
|
603
|
+
this._selectedNodes.add(node);
|
|
604
|
+
this.selectedNode = node;
|
|
605
|
+
this.#anchor = {
|
|
606
|
+
node,
|
|
607
|
+
flatIndex: this.#getFlatIndexOf(node)
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
this.#dispatchSelect(prev);
|
|
611
|
+
}
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (isShift && this.#anchor) {
|
|
615
|
+
const visible = this.#getAllNodes();
|
|
616
|
+
const anchorIdx = visible.findIndex((n) => n.node === this.#anchor.node);
|
|
617
|
+
const targetIdx = visible.findIndex((n) => n.node === node);
|
|
618
|
+
if (anchorIdx >= 0 && targetIdx >= 0) {
|
|
619
|
+
const prev = new Set(this._selectedNodes);
|
|
620
|
+
const from = Math.min(anchorIdx, targetIdx);
|
|
621
|
+
const to = Math.max(anchorIdx, targetIdx);
|
|
622
|
+
const rangeNodes = visible.slice(from, to + 1).map((n) => n.node).filter((n) => this.#canSelect(n));
|
|
623
|
+
this._selectedNodes = new Set(rangeNodes);
|
|
624
|
+
this.selectedNode = node;
|
|
625
|
+
this.#dispatchSelect(prev);
|
|
626
|
+
}
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
this.#toggleExpand(node, depth);
|
|
630
|
+
}
|
|
631
|
+
#toggleSelection(node, _depth) {
|
|
632
|
+
if (!this.#canSelect(node)) return;
|
|
633
|
+
const prev = new Set(this._selectedNodes);
|
|
634
|
+
if (this._selectedNodes.has(node)) {
|
|
635
|
+
this._selectedNodes.delete(node);
|
|
636
|
+
if (this.selectedNode === node) this.selectedNode = [...this._selectedNodes].at(-1);
|
|
637
|
+
} else {
|
|
638
|
+
this._selectedNodes.add(node);
|
|
639
|
+
this.selectedNode = node;
|
|
640
|
+
this.#anchor = {
|
|
641
|
+
node,
|
|
642
|
+
flatIndex: this.#getFlatIndexOf(node)
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
this.#dispatchSelect(prev);
|
|
646
|
+
}
|
|
647
|
+
_handleCheckboxClick(event, node, _depth) {
|
|
648
|
+
event.stopPropagation();
|
|
649
|
+
const prev = new Set(this._selectedNodes);
|
|
650
|
+
const allSelected = this.#allDescendantsSelected(node);
|
|
651
|
+
this.#applyCheckedToggle(node, !allSelected);
|
|
652
|
+
if (this.selectedNode === void 0 || !this._selectedNodes.has(this.selectedNode)) this.selectedNode = [...this._selectedNodes].at(-1);
|
|
653
|
+
this.#dispatchSelect(prev);
|
|
654
|
+
this.requestUpdate();
|
|
655
|
+
}
|
|
656
|
+
#toggleExpand(node, depth) {
|
|
657
|
+
if (this._expandedKeys.has(node.item.key)) {
|
|
658
|
+
this._expandedKeys.delete(node.item.key);
|
|
659
|
+
this.dispatchEvent(new CustomEvent(`collapse`, {
|
|
660
|
+
detail: {
|
|
661
|
+
node,
|
|
662
|
+
depth
|
|
663
|
+
},
|
|
664
|
+
bubbles: true,
|
|
665
|
+
composed: true
|
|
666
|
+
}));
|
|
667
|
+
} else if (node.children === void 0) {
|
|
668
|
+
if (node.item.isLeaf !== false) return;
|
|
669
|
+
if (this.model ?? (this.loadChildren ? this : void 0)) {
|
|
670
|
+
this.#pendingNode = node;
|
|
671
|
+
const controller = new AbortController();
|
|
672
|
+
this.#pendingController = controller;
|
|
673
|
+
this.#invokeLoadChildren(node, depth, controller).catch(() => {});
|
|
674
|
+
}
|
|
675
|
+
} else {
|
|
676
|
+
this.#applyExclusivity(node.item.key);
|
|
677
|
+
this._expandedKeys.add(node.item.key);
|
|
678
|
+
this.dispatchEvent(new CustomEvent(`expand`, {
|
|
679
|
+
detail: {
|
|
680
|
+
node,
|
|
681
|
+
depth
|
|
682
|
+
},
|
|
683
|
+
bubbles: true,
|
|
684
|
+
composed: true
|
|
685
|
+
}));
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
#allDescendantsSelected(node) {
|
|
689
|
+
if (!this._selectedNodes.has(node)) return false;
|
|
690
|
+
if (!node.children?.length) return true;
|
|
691
|
+
return node.children.every((child) => this.#allDescendantsSelected(child));
|
|
692
|
+
}
|
|
693
|
+
#applyCheckedToggle(node, selected) {
|
|
694
|
+
if (this.#canSelect(node)) if (selected) this._selectedNodes.add(node);
|
|
695
|
+
else this._selectedNodes.delete(node);
|
|
696
|
+
if (node.children) for (const child of node.children) this.#applyCheckedToggle(child, selected);
|
|
697
|
+
}
|
|
698
|
+
#getFlatIndexOf(node) {
|
|
699
|
+
return this.#getAllNodes().findIndex((n) => n.node === node);
|
|
700
|
+
}
|
|
701
|
+
#extendRangeSelection(nodes, fromIdx, toIdx) {
|
|
702
|
+
if (!this.#anchor) {
|
|
703
|
+
const anchorNode = nodes[fromIdx]?.node;
|
|
704
|
+
if (anchorNode) this.#anchor = {
|
|
705
|
+
node: anchorNode,
|
|
706
|
+
flatIndex: fromIdx
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
if (!this.#anchor) return;
|
|
710
|
+
const anchorIdx = nodes.findIndex((n) => n.node === this.#anchor.node);
|
|
711
|
+
if (anchorIdx < 0) return;
|
|
712
|
+
const from = Math.min(anchorIdx, toIdx);
|
|
713
|
+
const to = Math.max(anchorIdx, toIdx);
|
|
714
|
+
if (this.interactionMode === `vscode`) {
|
|
715
|
+
if (!this.#anchor.selectionSnapshot) this.#anchor = {
|
|
716
|
+
...this.#anchor,
|
|
717
|
+
selectionSnapshot: new Set(this._selectedNodes)
|
|
718
|
+
};
|
|
719
|
+
const snap = this.#anchor.selectionSnapshot;
|
|
720
|
+
const rangeNodes = nodes.slice(from, to + 1).map((n) => n.node).filter((n) => this.#canSelect(n));
|
|
721
|
+
const next = new Set(snap);
|
|
722
|
+
for (const n of rangeNodes) if (snap.has(n)) next.delete(n);
|
|
723
|
+
else next.add(n);
|
|
724
|
+
const prev = new Set(this._selectedNodes);
|
|
725
|
+
this._selectedNodes = next;
|
|
726
|
+
this.selectedNode = nodes[toIdx]?.node ?? this.selectedNode;
|
|
727
|
+
this.#dispatchSelect(prev);
|
|
728
|
+
} else {
|
|
729
|
+
const prev = new Set(this._selectedNodes);
|
|
730
|
+
const rangeNodes = nodes.slice(from, to + 1).map((n) => n.node).filter((n) => this.#canSelect(n));
|
|
731
|
+
this._selectedNodes = new Set(rangeNodes);
|
|
732
|
+
this.selectedNode = nodes[toIdx]?.node ?? this.selectedNode;
|
|
733
|
+
this.#dispatchSelect(prev);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
async #invokeLoadChildren(node, depth, controller) {
|
|
737
|
+
try {
|
|
738
|
+
let children;
|
|
739
|
+
if (this.model) children = await this.model.getChildren(node, depth, controller.signal);
|
|
740
|
+
else {
|
|
741
|
+
const result = this.loadChildren(node, depth, controller.signal);
|
|
742
|
+
children = result instanceof Promise ? await result : result;
|
|
743
|
+
}
|
|
744
|
+
if (controller.signal.aborted) return;
|
|
745
|
+
node.children = children;
|
|
746
|
+
this.#applyExclusivity(node.item.key);
|
|
747
|
+
this._expandedKeys.add(node.item.key);
|
|
748
|
+
this.#pendingNode = void 0;
|
|
749
|
+
this.#pendingController = void 0;
|
|
750
|
+
this.dispatchEvent(new CustomEvent(`expand`, {
|
|
751
|
+
detail: {
|
|
752
|
+
node,
|
|
753
|
+
depth
|
|
754
|
+
},
|
|
755
|
+
bubbles: true,
|
|
756
|
+
composed: true
|
|
757
|
+
}));
|
|
758
|
+
this.requestUpdate();
|
|
759
|
+
} catch (err) {
|
|
760
|
+
if (controller.signal.aborted) return;
|
|
761
|
+
node.children = [];
|
|
762
|
+
this.#pendingNode = void 0;
|
|
763
|
+
this.#pendingController = void 0;
|
|
764
|
+
this.requestUpdate();
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
_isSelected(node) {
|
|
768
|
+
return this._selectedNodes.has(node);
|
|
769
|
+
}
|
|
770
|
+
_isFocused(node) {
|
|
771
|
+
return this.tickled.isTickled(node.item.key);
|
|
772
|
+
}
|
|
773
|
+
_isExpanded(node) {
|
|
774
|
+
if (this._expandedKeys.has(node.item.key)) return true;
|
|
775
|
+
if (this.filterPredicate && node.children) return node.children.some((child) => this.#matchesFilter(child, 0));
|
|
776
|
+
return false;
|
|
777
|
+
}
|
|
778
|
+
#matchesFilter(node, depth) {
|
|
779
|
+
if (!this.filterPredicate) return true;
|
|
780
|
+
if (this.filterPredicate(node, depth)) return true;
|
|
781
|
+
return node.children?.some((child) => this.#matchesFilter(child, depth + 1)) ?? false;
|
|
782
|
+
}
|
|
783
|
+
get _pendingNode() {
|
|
784
|
+
return this.#pendingNode;
|
|
785
|
+
}
|
|
786
|
+
#scrollKeyIntoView(key) {
|
|
787
|
+
requestAnimationFrame(() => {
|
|
788
|
+
const el = this.shadowRoot?.querySelector(`[data-key="${CSS.escape(key)}"]`);
|
|
789
|
+
if (!el) return;
|
|
790
|
+
const elRect = el.getBoundingClientRect();
|
|
791
|
+
const hostRect = this.getBoundingClientRect();
|
|
792
|
+
const relativeTop = elRect.top - hostRect.top;
|
|
793
|
+
const relativeBottom = relativeTop + elRect.height;
|
|
794
|
+
if (relativeTop < 0) this.scrollTop += relativeTop;
|
|
795
|
+
else if (relativeBottom > this.clientHeight) this.scrollTop += relativeBottom - this.clientHeight;
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
#abortPending() {
|
|
799
|
+
if (this.#pendingController) {
|
|
800
|
+
this.#pendingController.abort();
|
|
801
|
+
this.#pendingController = void 0;
|
|
802
|
+
this.#pendingNode = void 0;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
/** Apply exclusivity rules when a node is about to be expanded. */
|
|
806
|
+
#applyExclusivity(expandKey) {
|
|
807
|
+
if (this.exclusivity === `none`) return;
|
|
808
|
+
if (this.exclusivity === `depth`) this.#collapseSiblingsAtDepth(expandKey);
|
|
809
|
+
else if (this.exclusivity === `global`) {
|
|
810
|
+
const ancestorKeys = this.#findAncestorKeys(expandKey);
|
|
811
|
+
for (const key of [...this._expandedKeys]) if (!ancestorKeys.has(key) && key !== expandKey) this._expandedKeys.delete(key);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
#collapseSiblingsAtDepth(expandKey) {
|
|
815
|
+
if (!this.root) return;
|
|
816
|
+
const parent = this.#findParent(this.root, expandKey);
|
|
817
|
+
if (!parent?.children) return;
|
|
818
|
+
for (const child of parent.children) if (child.item.key !== expandKey && this._expandedKeys.has(child.item.key)) this._expandedKeys.delete(child.item.key);
|
|
819
|
+
}
|
|
820
|
+
#findAncestorKeys(targetKey) {
|
|
821
|
+
const keys = /* @__PURE__ */ new Set();
|
|
822
|
+
const walk = (node) => {
|
|
823
|
+
if (node.item.key === targetKey) return true;
|
|
824
|
+
if (!node.children) return false;
|
|
825
|
+
for (const child of node.children) if (walk(child)) {
|
|
826
|
+
keys.add(node.item.key);
|
|
827
|
+
return true;
|
|
828
|
+
}
|
|
829
|
+
return false;
|
|
830
|
+
};
|
|
831
|
+
if (this.root) walk(this.root);
|
|
832
|
+
return keys;
|
|
833
|
+
}
|
|
834
|
+
#findParent(node, targetKey, parent = void 0) {
|
|
835
|
+
if (!node.children) return void 0;
|
|
836
|
+
for (const child of node.children) {
|
|
837
|
+
if (child.item.key === targetKey) return parent ?? node;
|
|
838
|
+
const found = this.#findParent(child, targetKey, child);
|
|
839
|
+
if (found) return found;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
#getVisibleNodes(node, depth) {
|
|
843
|
+
const result = [];
|
|
844
|
+
const visit = (n, d) => {
|
|
845
|
+
result.push({
|
|
846
|
+
node: n,
|
|
847
|
+
depth: d
|
|
848
|
+
});
|
|
849
|
+
if (this._expandedKeys.has(n.item.key) && n.children) for (const child of n.children) visit(child, d + 1);
|
|
850
|
+
};
|
|
851
|
+
if (node.children) for (const child of node.children) visit(child, depth + 1);
|
|
852
|
+
return result;
|
|
853
|
+
}
|
|
854
|
+
#getAllNodes() {
|
|
855
|
+
if (!this.root) return [];
|
|
856
|
+
return this.#getVisibleNodes(this.root, 0);
|
|
857
|
+
}
|
|
858
|
+
#getFocusedIndex() {
|
|
859
|
+
const nodes = this.#getAllNodes();
|
|
860
|
+
const tickledKey = this.tickled.tickledId;
|
|
861
|
+
if (!tickledKey) return 0;
|
|
862
|
+
return nodes.findIndex((n) => n.node.item.key === tickledKey);
|
|
863
|
+
}
|
|
864
|
+
#setFocusedIndex(index) {
|
|
865
|
+
const nodes = this.#getAllNodes();
|
|
866
|
+
if (index < 0 || index >= nodes.length) return;
|
|
867
|
+
const { node, depth } = nodes[index];
|
|
868
|
+
this.tickled.setTickled(node.item.key);
|
|
869
|
+
this._focusedDepth = depth;
|
|
870
|
+
this.dispatchEvent(new CustomEvent(`tickle`, {
|
|
871
|
+
detail: {
|
|
872
|
+
node,
|
|
873
|
+
depth
|
|
874
|
+
},
|
|
875
|
+
bubbles: true,
|
|
876
|
+
composed: true
|
|
877
|
+
}));
|
|
878
|
+
this.requestUpdate();
|
|
879
|
+
this.updateComplete.then(() => this.#scrollTickledIntoView());
|
|
880
|
+
}
|
|
881
|
+
#scrollTickledIntoView() {
|
|
882
|
+
const tickledKey = this.tickled.tickledId;
|
|
883
|
+
if (tickledKey === void 0) return;
|
|
884
|
+
(this.shadowRoot?.querySelector(`[data-key="${tickledKey}"]`))?.scrollIntoView({
|
|
885
|
+
block: `nearest`,
|
|
886
|
+
behavior: `smooth`
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
#onKeyDown;
|
|
890
|
+
renderLoadingIndicator() {
|
|
891
|
+
return html`<span class="loading-spinner"></span>`;
|
|
892
|
+
}
|
|
893
|
+
render() {
|
|
894
|
+
const nodes = this.#getAllNodes();
|
|
895
|
+
return html`
|
|
896
|
+
<div
|
|
897
|
+
id="container"
|
|
898
|
+
data-input-mode=${this.tickled.inputMode}
|
|
899
|
+
@keydown=${this.#onKeyDown}
|
|
900
|
+
@dblclick=${this.#onDblClick}
|
|
901
|
+
@pointerleave=${(e) => this.tickled.handlePointerLeave(e)}
|
|
902
|
+
tabindex="0"
|
|
903
|
+
>
|
|
904
|
+
${this.root ? this.#renderNodes(nodes) : nothing}
|
|
905
|
+
</div>
|
|
906
|
+
`;
|
|
907
|
+
}
|
|
908
|
+
#onDblClick;
|
|
909
|
+
#renderNodes(nodes) {
|
|
910
|
+
if (nodes.length === 0 && this.#pendingNode) return html`
|
|
911
|
+
<div class="tree-loading">
|
|
912
|
+
${this.renderLoadingIndicator()}
|
|
913
|
+
</div>
|
|
914
|
+
`;
|
|
915
|
+
return html`
|
|
916
|
+
<ul class="tree-list" role="tree">
|
|
917
|
+
${nodes.map(({ node, depth }) => this._renderNode(node, depth))}
|
|
918
|
+
</ul>
|
|
919
|
+
`;
|
|
920
|
+
}
|
|
921
|
+
_renderNode(node, depth) {
|
|
922
|
+
const selected = this._isSelected(node);
|
|
923
|
+
const tickled = this._isFocused(node);
|
|
924
|
+
const isBranch = node.item.isLeaf !== true;
|
|
925
|
+
const isExpanded = this._isExpanded(node);
|
|
926
|
+
const isPending = this.#pendingNode === node;
|
|
927
|
+
const isFiltered = this.filterPredicate !== void 0 && !this.#matchesFilter(node, depth);
|
|
928
|
+
return html`
|
|
929
|
+
<li
|
|
930
|
+
class="item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${isBranch ? `branch` : `leaf`} ${isFiltered ? `filtered` : ``}"
|
|
931
|
+
role="treeitem"
|
|
932
|
+
aria-selected=${selected}
|
|
933
|
+
aria-expanded=${isBranch ? isExpanded : nothing}
|
|
934
|
+
aria-level=${depth + 1}
|
|
935
|
+
data-key=${node.item.key}
|
|
936
|
+
style=${`--depth: ${depth}`}
|
|
937
|
+
@click=${(e) => this._handleItemClick(node, depth, e)}
|
|
938
|
+
@pointerenter=${() => {
|
|
939
|
+
this.tickled.handlePointerEnter(node.item.key);
|
|
940
|
+
this.dispatchEvent(new CustomEvent(`tickle`, {
|
|
941
|
+
detail: {
|
|
942
|
+
node,
|
|
943
|
+
depth
|
|
944
|
+
},
|
|
945
|
+
bubbles: true,
|
|
946
|
+
composed: true
|
|
947
|
+
}));
|
|
948
|
+
}}
|
|
949
|
+
>
|
|
950
|
+
${this.renderNodeContent(node, depth)}
|
|
951
|
+
${isPending ? this.renderLoadingIndicator() : nothing}
|
|
952
|
+
</li>
|
|
953
|
+
`;
|
|
954
|
+
}
|
|
955
|
+
static {
|
|
956
|
+
this.styles = [
|
|
957
|
+
tickledStyles,
|
|
958
|
+
tickledItemStyles,
|
|
959
|
+
css`
|
|
960
|
+
:host {
|
|
961
|
+
--tree-bg: var(--surface-3);
|
|
962
|
+
--tree-text: var(--surface-3-text);
|
|
963
|
+
--tree-muted-text: var(--surface-muted-text);
|
|
964
|
+
--tree-indent: 1ch;
|
|
965
|
+
--tree-item-height: 28px;
|
|
966
|
+
--tree-caret-size: 10px;
|
|
967
|
+
--tree-radius: var(--radius-s);
|
|
968
|
+
|
|
969
|
+
display: block;
|
|
970
|
+
overflow: auto;
|
|
971
|
+
box-sizing: border-box;
|
|
972
|
+
background: var(--tree-bg);
|
|
973
|
+
color: var(--tree-text);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
#container {
|
|
977
|
+
outline: none;
|
|
978
|
+
min-height: 100%;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
.tree-list {
|
|
982
|
+
list-style: none;
|
|
983
|
+
margin: 0;
|
|
984
|
+
padding: var(--space-xs);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
.item {
|
|
988
|
+
display: flex;
|
|
989
|
+
align-items: center;
|
|
990
|
+
height: var(--tree-item-height);
|
|
991
|
+
padding-left: calc(var(--tree-indent) * var(--depth, 0));
|
|
992
|
+
cursor: pointer;
|
|
993
|
+
user-select: none;
|
|
994
|
+
font-family: var(--font-family);
|
|
995
|
+
font-size: var(--text-m);
|
|
996
|
+
border-radius: var(--tree-radius);
|
|
997
|
+
transition: background var(--transition, 0.15s ease-out);
|
|
998
|
+
margin: 1px 0;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
.tree-caret {
|
|
1002
|
+
display: inline-flex;
|
|
1003
|
+
align-items: center;
|
|
1004
|
+
justify-content: center;
|
|
1005
|
+
width: var(--tree-caret-size);
|
|
1006
|
+
height: var(--tree-caret-size);
|
|
1007
|
+
flex-shrink: 0;
|
|
1008
|
+
margin-right: var(--space-xs);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
.caret-icon {
|
|
1012
|
+
width: var(--tree-caret-size);
|
|
1013
|
+
height: var(--tree-caret-size);
|
|
1014
|
+
color: var(--tree-muted-text);
|
|
1015
|
+
transition: transform var(--transition, 0.15s ease-out);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
.item.selected .caret-icon {
|
|
1019
|
+
color: var(--accent-text);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
.tree-label {
|
|
1023
|
+
flex: 1;
|
|
1024
|
+
overflow: hidden;
|
|
1025
|
+
text-overflow: ellipsis;
|
|
1026
|
+
white-space: nowrap;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
.tree-loading {
|
|
1030
|
+
display: flex;
|
|
1031
|
+
align-items: center;
|
|
1032
|
+
justify-content: center;
|
|
1033
|
+
padding: var(--space-m);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
.loading-spinner {
|
|
1037
|
+
width: 16px;
|
|
1038
|
+
height: 16px;
|
|
1039
|
+
border: 2px solid var(--surface-5);
|
|
1040
|
+
border-top-color: var(--accent);
|
|
1041
|
+
border-radius: 50%;
|
|
1042
|
+
animation: tree-spin 0.7s linear infinite;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
@keyframes tree-spin {
|
|
1046
|
+
to { transform: rotate(360deg); }
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
.item.filtered {
|
|
1050
|
+
display: none;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
::highlight(incr-search-highlight) {
|
|
1054
|
+
background: var(--search-highlight-bg, var(--accent));
|
|
1055
|
+
color: var(--search-highlight-color, var(--accent-text, #fff));
|
|
1056
|
+
border-radius: 2px;
|
|
1057
|
+
padding: 0 1px;
|
|
1058
|
+
}
|
|
1059
|
+
`
|
|
1060
|
+
];
|
|
1061
|
+
}
|
|
1062
|
+
};
|
|
1063
|
+
__decorate([property({ type: Object })], TreeBaseElement.prototype, "root", void 0);
|
|
1064
|
+
__decorate([property({ attribute: false })], TreeBaseElement.prototype, "model", void 0);
|
|
1065
|
+
__decorate([property({ attribute: false })], TreeBaseElement.prototype, "loadChildren", void 0);
|
|
1066
|
+
__decorate([property({
|
|
1067
|
+
reflect: true,
|
|
1068
|
+
attribute: `exclusivity`
|
|
1069
|
+
})], TreeBaseElement.prototype, "exclusivity", void 0);
|
|
1070
|
+
__decorate([property({
|
|
1071
|
+
reflect: true,
|
|
1072
|
+
attribute: `selection-filter`
|
|
1073
|
+
})], TreeBaseElement.prototype, "selectionFilter", void 0);
|
|
1074
|
+
__decorate([property({
|
|
1075
|
+
type: String,
|
|
1076
|
+
attribute: `interaction-mode`
|
|
1077
|
+
})], TreeBaseElement.prototype, "interactionMode", void 0);
|
|
1078
|
+
__decorate([property({ attribute: false })], TreeBaseElement.prototype, "selectedNode", void 0);
|
|
1079
|
+
__decorate([state()], TreeBaseElement.prototype, "_selectedNodes", void 0);
|
|
1080
|
+
__decorate([property({ attribute: false })], TreeBaseElement.prototype, "filterPredicate", void 0);
|
|
1081
|
+
__decorate([property({
|
|
1082
|
+
type: Boolean,
|
|
1083
|
+
attribute: `disable-keyboard-navigation`
|
|
1084
|
+
})], TreeBaseElement.prototype, "disableKeyboardNavigation", void 0);
|
|
1085
|
+
__decorate([state()], TreeBaseElement.prototype, "_expandedKeys", void 0);
|
|
1086
|
+
__decorate([state()], TreeBaseElement.prototype, "_focusedDepth", void 0);
|
|
1087
|
+
TreeBaseElement = __decorate([safeCustomElement(`ixfx-tree-base`)], TreeBaseElement);
|
|
1088
|
+
//#endregion
|
|
1089
|
+
//#region src/tree/tree-controller.ts
|
|
1090
|
+
var TreeController = class {
|
|
1091
|
+
#root;
|
|
1092
|
+
#element;
|
|
1093
|
+
#options;
|
|
1094
|
+
#nodeToTree = /* @__PURE__ */ new WeakMap();
|
|
1095
|
+
#identityToTreeNode = /* @__PURE__ */ new Map();
|
|
1096
|
+
#connectSignal;
|
|
1097
|
+
constructor(root, element, options) {
|
|
1098
|
+
this.#root = root;
|
|
1099
|
+
this.#element = element;
|
|
1100
|
+
this.#options = options;
|
|
1101
|
+
}
|
|
1102
|
+
connect() {
|
|
1103
|
+
this.disconnect();
|
|
1104
|
+
this.#connectSignal = new AbortController();
|
|
1105
|
+
const { signal } = this.#connectSignal;
|
|
1106
|
+
const rootValue = this.#root.getValue();
|
|
1107
|
+
const rootItem = this.#options.valueToItem(rootValue, this.#root);
|
|
1108
|
+
const rootChildren = this.#childrenOf(this.#root);
|
|
1109
|
+
this.#element.root = {
|
|
1110
|
+
item: rootItem,
|
|
1111
|
+
children: rootChildren
|
|
1112
|
+
};
|
|
1113
|
+
this.#element.addEventListener(`expand`, (e) => {
|
|
1114
|
+
const { node } = e.detail;
|
|
1115
|
+
const treeNode = this.#nodeToTree.get(node);
|
|
1116
|
+
if (!treeNode) return;
|
|
1117
|
+
node.children = this.#childrenOf(treeNode);
|
|
1118
|
+
this.#element.requestUpdate();
|
|
1119
|
+
}, { signal });
|
|
1120
|
+
this.#element.addEventListener(`collapse`, (_e) => {}, { signal });
|
|
1121
|
+
}
|
|
1122
|
+
disconnect() {
|
|
1123
|
+
this.#connectSignal?.abort();
|
|
1124
|
+
this.#connectSignal = void 0;
|
|
1125
|
+
}
|
|
1126
|
+
navigateToValue(predicate) {
|
|
1127
|
+
const treePath = this.#findTreePath(this.#root, predicate);
|
|
1128
|
+
if (!treePath) return false;
|
|
1129
|
+
const keys = [];
|
|
1130
|
+
for (let i = 1; i < treePath.length; i++) {
|
|
1131
|
+
const treeNode = treePath[i];
|
|
1132
|
+
const key = this.#options.valueToItem(treeNode.getValue(), treeNode).key;
|
|
1133
|
+
keys.push(key);
|
|
1134
|
+
if (i < treePath.length - 1) {
|
|
1135
|
+
const currentChildren = i === 1 ? this.#element.root?.children ?? [] : this.#findInChildren(this.#element.root?.children ?? [], keys);
|
|
1136
|
+
const treeNodeMap = this.#nodeToTree;
|
|
1137
|
+
const node = currentChildren.find((n) => n.item.key === key);
|
|
1138
|
+
if (!node) return false;
|
|
1139
|
+
if (node.children === void 0) {
|
|
1140
|
+
const mapped = treeNodeMap.get(node);
|
|
1141
|
+
if (mapped) node.children = this.#childrenOf(mapped);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
return this.#element.navigateTo(keys);
|
|
1146
|
+
}
|
|
1147
|
+
#findInChildren(children, keys) {
|
|
1148
|
+
if (keys.length === 0) return children;
|
|
1149
|
+
const key = keys[0];
|
|
1150
|
+
return children.find((n) => n.item.key === key)?.children ?? [];
|
|
1151
|
+
}
|
|
1152
|
+
#childrenOf(treeNode) {
|
|
1153
|
+
return Array.from(treeNode.children(), (child) => this.#toTreeNode(child));
|
|
1154
|
+
}
|
|
1155
|
+
#toTreeNode(treeNode) {
|
|
1156
|
+
const value = treeNode.getValue();
|
|
1157
|
+
const childList = [...treeNode.children()];
|
|
1158
|
+
const userItem = this.#options.valueToItem(value, treeNode);
|
|
1159
|
+
const isLeaf = userItem.isLeaf ?? childList.length === 0;
|
|
1160
|
+
const treeNodeMap = { item: {
|
|
1161
|
+
...userItem,
|
|
1162
|
+
isLeaf
|
|
1163
|
+
} };
|
|
1164
|
+
this.#nodeToTree.set(treeNodeMap, treeNode);
|
|
1165
|
+
this.#identityToTreeNode.set(treeNode.getIdentity(), treeNode);
|
|
1166
|
+
return treeNodeMap;
|
|
1167
|
+
}
|
|
1168
|
+
#findTreePath(node, predicate, ancestors = []) {
|
|
1169
|
+
const path = [...ancestors, node];
|
|
1170
|
+
if (predicate(node.getValue())) return path;
|
|
1171
|
+
for (const child of node.children()) {
|
|
1172
|
+
const result = this.#findTreePath(child, predicate, path);
|
|
1173
|
+
if (result) return result;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
};
|
|
1177
|
+
//#endregion
|
|
1178
|
+
//#region src/tree/tree-list.ts
|
|
1179
|
+
function computeCheckboxState(node, selectedNodes) {
|
|
1180
|
+
if (node.item.isLeaf || !node.children?.length) return selectedNodes.has(node) ? `checked` : `unchecked`;
|
|
1181
|
+
const childStates = node.children.map((c) => computeCheckboxState(c, selectedNodes));
|
|
1182
|
+
const allChecked = childStates.every((s) => s === `checked`);
|
|
1183
|
+
const allUnchecked = childStates.every((s) => s === `unchecked`);
|
|
1184
|
+
if (allChecked) return `checked`;
|
|
1185
|
+
if (allUnchecked) return `unchecked`;
|
|
1186
|
+
return `indeterminate`;
|
|
1187
|
+
}
|
|
1188
|
+
let TreeListElement = class TreeListElement extends TreeBaseElement {
|
|
1189
|
+
#depthsWithIcons = /* @__PURE__ */ new Set();
|
|
1190
|
+
render() {
|
|
1191
|
+
this.#detectIcons();
|
|
1192
|
+
return super.render();
|
|
1193
|
+
}
|
|
1194
|
+
#detectIcons() {
|
|
1195
|
+
this.#depthsWithIcons.clear();
|
|
1196
|
+
const visit = (node, depth) => {
|
|
1197
|
+
if (node.item.icon) this.#depthsWithIcons.add(depth);
|
|
1198
|
+
if (node.children) for (const child of node.children) visit(child, depth + 1);
|
|
1199
|
+
};
|
|
1200
|
+
if (this.root) visit(this.root, 0);
|
|
1201
|
+
}
|
|
1202
|
+
renderNodeContent(node, depth) {
|
|
1203
|
+
const isBranch = node.item.isLeaf !== true;
|
|
1204
|
+
const isExpanded = this._isExpanded(node);
|
|
1205
|
+
const icon = node.item.icon;
|
|
1206
|
+
const tooltip = node.item.tooltip;
|
|
1207
|
+
const caretAndLabel = html`
|
|
1208
|
+
<span class="tree-caret">
|
|
1209
|
+
${isBranch ? html`<ixfx-icon class="caret-icon" name="chevron-down" .rotate=${isExpanded ? nothing : `-90deg`}></ixfx-icon>` : ``}
|
|
1210
|
+
</span>
|
|
1211
|
+
${icon ? html`<ixfx-icon class="tree-icon" name="${icon}"></ixfx-icon>` : nothing}
|
|
1212
|
+
<span class="tree-label">
|
|
1213
|
+
${node.item.label}
|
|
1214
|
+
${tooltip ? html`<ixfx-tooltip class="tree-tooltip" anchor="--tooltip-tree-${node.item.key}" .value=${tooltip}></ixfx-tooltip>` : nothing}
|
|
1215
|
+
</span>
|
|
1216
|
+
`;
|
|
1217
|
+
if (this.interactionMode === `checked`) {
|
|
1218
|
+
const state = computeCheckboxState(node, this._selectedNodes);
|
|
1219
|
+
return html`
|
|
1220
|
+
<input
|
|
1221
|
+
type="checkbox"
|
|
1222
|
+
class="tree-checkbox"
|
|
1223
|
+
.checked=${state === `checked`}
|
|
1224
|
+
.indeterminate=${state === `indeterminate`}
|
|
1225
|
+
aria-label="Select ${node.item.label}"
|
|
1226
|
+
@click=${(e) => this._handleCheckboxClick(e, node, depth)}
|
|
1227
|
+
/>${caretAndLabel}`;
|
|
1228
|
+
}
|
|
1229
|
+
return caretAndLabel;
|
|
1230
|
+
}
|
|
1231
|
+
_renderNode(node, depth) {
|
|
1232
|
+
const selected = this._isSelected(node);
|
|
1233
|
+
const tickled = this._isFocused(node);
|
|
1234
|
+
const isBranch = node.item.isLeaf !== true;
|
|
1235
|
+
const isExpanded = this._isExpanded(node);
|
|
1236
|
+
const isPending = this._pendingNode === node;
|
|
1237
|
+
const hasIconAtDepth = this.#depthsWithIcons.has(depth);
|
|
1238
|
+
return html`
|
|
1239
|
+
<li
|
|
1240
|
+
class="item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${isBranch ? `branch` : `leaf`} ${hasIconAtDepth ? `has-icon-at-depth` : ``}"
|
|
1241
|
+
data-key=${node.item.key}
|
|
1242
|
+
role="treeitem"
|
|
1243
|
+
aria-selected=${selected}
|
|
1244
|
+
aria-expanded=${isBranch ? isExpanded : nothing}
|
|
1245
|
+
aria-level=${depth + 1}
|
|
1246
|
+
style=${`--depth: ${depth}`}
|
|
1247
|
+
@click=${(e) => this._handleItemClick(node, depth, e)}
|
|
1248
|
+
@pointerenter=${() => this.tickled.handlePointerEnter(node.item.key)}
|
|
1249
|
+
>
|
|
1250
|
+
${this.renderNodeContent(node, depth)}
|
|
1251
|
+
${isPending ? this.renderLoadingIndicator() : nothing}
|
|
1252
|
+
</li>
|
|
1253
|
+
`;
|
|
1254
|
+
}
|
|
1255
|
+
static {
|
|
1256
|
+
this.styles = [...TreeBaseElement.styles, css`
|
|
1257
|
+
.tree-caret {
|
|
1258
|
+
display: inline-flex;
|
|
1259
|
+
align-items: center;
|
|
1260
|
+
justify-content: center;
|
|
1261
|
+
width: var(--tree-caret-size);
|
|
1262
|
+
height: var(--tree-caret-size);
|
|
1263
|
+
flex-shrink: 0;
|
|
1264
|
+
margin-right: var(--space-xs);
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
.caret-icon {
|
|
1268
|
+
width: var(--tree-caret-size);
|
|
1269
|
+
height: var(--tree-caret-size);
|
|
1270
|
+
color: var(--tree-muted-text);
|
|
1271
|
+
transition: transform var(--transition, 0.15s ease-out);
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
.item.selected .caret-icon {
|
|
1275
|
+
color: var(--accent-text);
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
.tree-icon {
|
|
1279
|
+
flex-shrink: 0;
|
|
1280
|
+
width: var(--icon-size, 1em);
|
|
1281
|
+
height: var(--icon-size, 1em);
|
|
1282
|
+
margin-right: var(--icon-spacing, 0.3em);
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
.item:not(.has-icon-at-depth) .tree-icon {
|
|
1286
|
+
display: none;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
.item.has-icon-at-depth .tree-label {
|
|
1290
|
+
margin-left: var(--icon-size, 1em);
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
.tree-tooltip {
|
|
1294
|
+
position: absolute;
|
|
1295
|
+
anchor-name: --tooltip-tree;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
.tree-checkbox {
|
|
1299
|
+
flex-shrink: 0;
|
|
1300
|
+
margin-right: var(--space-xs, 3px);
|
|
1301
|
+
cursor: pointer;
|
|
1302
|
+
accent-color: var(--accent);
|
|
1303
|
+
pointer-events: auto;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
/* In checked mode the checkbox communicates selection state —
|
|
1307
|
+
suppress the row highlight so it doesn't double-signal */
|
|
1308
|
+
:host([interaction-mode="checked"]) .item.selected {
|
|
1309
|
+
background: transparent;
|
|
1310
|
+
color: inherit;
|
|
1311
|
+
outline: none;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
|
|
1315
|
+
|
|
1316
|
+
.tree-label {
|
|
1317
|
+
flex: 1;
|
|
1318
|
+
overflow: hidden;
|
|
1319
|
+
text-overflow: ellipsis;
|
|
1320
|
+
white-space: nowrap;
|
|
1321
|
+
position: relative;
|
|
1322
|
+
}
|
|
1323
|
+
`];
|
|
1324
|
+
}
|
|
1325
|
+
};
|
|
1326
|
+
TreeListElement = __decorate([safeCustomElement(`ixfx-tree-list`)], TreeListElement);
|
|
1327
|
+
//#endregion
|
|
1328
|
+
export { TreeDataModel as i, TreeController as n, TreeBaseElement as r, TreeListElement as t };
|
|
1329
|
+
|
|
1330
|
+
//# sourceMappingURL=tree-CvvKbO-f.js.map
|