@ixfx/components 0.2.5 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/bundle/index.d.ts +365 -143
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +2330 -826
  4. package/bundle/index.js.map +1 -1
  5. package/dist/ac-text.d.ts +2 -3
  6. package/dist/ac-text.d.ts.map +1 -1
  7. package/dist/ac-text.js +5 -5
  8. package/dist/ac-text.js.map +1 -1
  9. package/dist/{button-DgKUzE5o.js → button-DMt5XWUK.js} +6 -6
  10. package/dist/button-DMt5XWUK.js.map +1 -0
  11. package/dist/button.d.ts +3 -4
  12. package/dist/button.d.ts.map +1 -1
  13. package/dist/button.js +1 -1
  14. package/dist/checkbox.d.ts +3 -5
  15. package/dist/checkbox.d.ts.map +1 -1
  16. package/dist/checkbox.js +3 -3
  17. package/dist/checkbox.js.map +1 -1
  18. package/dist/{colour-C3MQIjFJ-BJV-QC-3.js → colour-C3MQIjFJ-BLXg8W3U.js} +2 -2
  19. package/dist/{colour-C3MQIjFJ-BJV-QC-3.js.map → colour-C3MQIjFJ-BLXg8W3U.js.map} +1 -1
  20. package/dist/{colour-picker-Bw-AZoWx.js → colour-picker-K6zu82lm.js} +8 -8
  21. package/dist/colour-picker-K6zu82lm.js.map +1 -0
  22. package/dist/colour-picker.d.ts +2 -2
  23. package/dist/colour-picker.js +1 -1
  24. package/dist/crumbs.d.ts +4 -6
  25. package/dist/crumbs.d.ts.map +1 -1
  26. package/dist/crumbs.js +12 -12
  27. package/dist/crumbs.js.map +1 -1
  28. package/dist/data-display.d.ts +1 -2
  29. package/dist/data-display.d.ts.map +1 -1
  30. package/dist/data-display.js +4 -5
  31. package/dist/data-display.js.map +1 -1
  32. package/dist/decorate-D7rC1gLP.js +27 -0
  33. package/dist/decorate-D7rC1gLP.js.map +1 -0
  34. package/dist/{defaults-FEZaGiYZ.js → defaults-C7elhuyJ.js} +2 -2
  35. package/dist/{defaults-FEZaGiYZ.js.map → defaults-C7elhuyJ.js.map} +1 -1
  36. package/dist/{dist-By5URG0Y.js → dist-BX0OVIm7.js} +2 -2
  37. package/dist/{dist-By5URG0Y.js.map → dist-BX0OVIm7.js.map} +1 -1
  38. package/dist/{dist-D7kJgnSE.js → dist-TAGYwaju.js} +1 -1
  39. package/dist/{dist-D7kJgnSE.js.map → dist-TAGYwaju.js.map} +1 -1
  40. package/dist/editable-label.d.ts +6 -1
  41. package/dist/editable-label.d.ts.map +1 -1
  42. package/dist/editable-label.js +51 -4
  43. package/dist/editable-label.js.map +1 -1
  44. package/dist/{hex-editor-Wd6ZtZA2.js → hex-editor-CsN_ySkH.js} +87 -87
  45. package/dist/hex-editor-CsN_ySkH.js.map +1 -0
  46. package/dist/{hex-editor-Gib1Grg2.d.ts → hex-editor-DlfoA65y.d.ts} +7 -8
  47. package/dist/hex-editor-DlfoA65y.d.ts.map +1 -0
  48. package/dist/hex.d.ts +1 -1
  49. package/dist/hex.js +1 -1
  50. package/dist/{highlight-CAjwQ4GE.js → highlight-TPbSHruI.js} +3 -3
  51. package/dist/highlight-TPbSHruI.js.map +1 -0
  52. package/dist/{icon-BR5aTIiP.js → icon-9NWTXtE1.js} +6 -7
  53. package/dist/icon-9NWTXtE1.js.map +1 -0
  54. package/dist/{icon-DQKdfR7U.d.ts → icon-CeTDJhC7.d.ts} +3 -4
  55. package/dist/icon-CeTDJhC7.d.ts.map +1 -0
  56. package/dist/icons.d.ts +2 -2
  57. package/dist/icons.js +3 -3
  58. package/dist/icons.js.map +1 -1
  59. package/dist/{incr-search-BjCNClnC.js → incr-search-DSKKsk6w.js} +119 -117
  60. package/dist/incr-search-DSKKsk6w.js.map +1 -0
  61. package/dist/incr-search.d.ts +2 -2
  62. package/dist/incr-search.js +2 -2
  63. package/dist/{index-CPVcTTXo.d.ts → index--hrxPBkQ.d.ts} +73 -74
  64. package/dist/index--hrxPBkQ.d.ts.map +1 -0
  65. package/dist/index-B1hNR7Z9.d.ts.map +1 -1
  66. package/dist/{index-CpbBI3n7.d.ts → index-CnL9krsH.d.ts} +3 -4
  67. package/dist/index-CnL9krsH.d.ts.map +1 -0
  68. package/dist/{index-CQxanqXF.d.ts → index-DSlPeWLK.d.ts} +28 -29
  69. package/dist/index-DSlPeWLK.d.ts.map +1 -0
  70. package/dist/{index-C5dK8RZa.d.ts → index-DrQjA8Gs.d.ts} +3 -4
  71. package/dist/index-DrQjA8Gs.d.ts.map +1 -0
  72. package/dist/{index-C9iBN908.d.ts → index-hXFn14tM.d.ts} +32 -32
  73. package/dist/index-hXFn14tM.d.ts.map +1 -0
  74. package/dist/index.d.ts +246 -32
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +1464 -32
  77. package/dist/index.js.map +1 -1
  78. package/dist/keyboard-De_vOHLg.js.map +1 -1
  79. package/dist/{labelled-input-base-DS6-6C0r.js → labelled-input-base-CqPL5rKm.js} +2 -2
  80. package/dist/labelled-input-base-CqPL5rKm.js.map +1 -0
  81. package/dist/{labelled-input-base-DnRh4reU.d.ts → labelled-input-base-D-Y7agky.d.ts} +2 -3
  82. package/dist/labelled-input-base-D-Y7agky.d.ts.map +1 -0
  83. package/dist/labelled-radial-input.d.ts +4 -5
  84. package/dist/labelled-radial-input.d.ts.map +1 -1
  85. package/dist/labelled-radial-input.js +5 -5
  86. package/dist/labelled-radial-input.js.map +1 -1
  87. package/dist/labelled-range-input.d.ts +1 -1
  88. package/dist/labelled-range-input.js +4 -4
  89. package/dist/labelled-range-input.js.map +1 -1
  90. package/dist/led.d.ts +2 -3
  91. package/dist/led.d.ts.map +1 -1
  92. package/dist/led.js +3 -3
  93. package/dist/led.js.map +1 -1
  94. package/dist/{menu-BV6UFx_z.js → menu-DJJuCm-5.js} +55 -55
  95. package/dist/menu-DJJuCm-5.js.map +1 -0
  96. package/dist/{menu-item-DEzL_Yjk.js → menu-item-CKRfP0pN.js} +14 -14
  97. package/dist/menu-item-CKRfP0pN.js.map +1 -0
  98. package/dist/{menu-item-Cikacp4F.d.ts → menu-item-ClQF2DKN.d.ts} +7 -8
  99. package/dist/menu-item-ClQF2DKN.d.ts.map +1 -0
  100. package/dist/menu.d.ts +3 -3
  101. package/dist/menu.js +2 -2
  102. package/dist/miller.d.ts +7 -10
  103. package/dist/miller.d.ts.map +1 -1
  104. package/dist/miller.js +17 -17
  105. package/dist/miller.js.map +1 -1
  106. package/dist/narrowed-text.d.ts +2 -3
  107. package/dist/narrowed-text.d.ts.map +1 -1
  108. package/dist/narrowed-text.js +6 -6
  109. package/dist/narrowed-text.js.map +1 -1
  110. package/dist/panel.d.ts +2 -3
  111. package/dist/panel.d.ts.map +1 -1
  112. package/dist/panel.js +4 -4
  113. package/dist/panel.js.map +1 -1
  114. package/dist/plots.d.ts +1 -1
  115. package/dist/plots.js +1 -1
  116. package/dist/polar-pad.d.ts +1 -2
  117. package/dist/polar-pad.d.ts.map +1 -1
  118. package/dist/polar-pad.js +3 -3
  119. package/dist/polar-pad.js.map +1 -1
  120. package/dist/{popup-Ddx9LCzY.js → popup-ImtS9KnS.js} +1 -1
  121. package/dist/{popup-Ddx9LCzY.js.map → popup-ImtS9KnS.js.map} +1 -1
  122. package/dist/{prominence-CreDBdCs.js → prominence--ic9uWBl.js} +1 -1
  123. package/dist/{prominence-CreDBdCs.js.map → prominence--ic9uWBl.js.map} +1 -1
  124. package/dist/{radial-input-CNJ8asY2.js → radial-input-DZA6WaFu.js} +16 -16
  125. package/dist/radial-input-DZA6WaFu.js.map +1 -0
  126. package/dist/{radial-input-xSTd4kEz.d.ts → radial-input-LUSBEycd.d.ts} +2 -3
  127. package/dist/radial-input-LUSBEycd.d.ts.map +1 -0
  128. package/dist/radial-input.d.ts +1 -1
  129. package/dist/radial-input.js +1 -1
  130. package/dist/range-input.d.ts +2 -3
  131. package/dist/range-input.d.ts.map +1 -1
  132. package/dist/range-input.js +3 -3
  133. package/dist/range-input.js.map +1 -1
  134. package/dist/range.d.ts +2 -3
  135. package/dist/range.d.ts.map +1 -1
  136. package/dist/range.js +4 -4
  137. package/dist/range.js.map +1 -1
  138. package/dist/{registry-CZEB1YI9.js → registry-Q2gYQCHM.js} +1 -1
  139. package/dist/{registry-CZEB1YI9.js.map → registry-Q2gYQCHM.js.map} +1 -1
  140. package/dist/selecthorizontal.d.ts +2 -3
  141. package/dist/selecthorizontal.d.ts.map +1 -1
  142. package/dist/selecthorizontal.js +3 -3
  143. package/dist/selecthorizontal.js.map +1 -1
  144. package/dist/snap-container.d.ts +1 -2
  145. package/dist/snap-container.d.ts.map +1 -1
  146. package/dist/snap-container.js +3 -3
  147. package/dist/snap-container.js.map +1 -1
  148. package/dist/split-layout.d.ts +2 -3
  149. package/dist/split-layout.d.ts.map +1 -1
  150. package/dist/split-layout.js +6 -6
  151. package/dist/split-layout.js.map +1 -1
  152. package/dist/swipe.d.ts +1 -2
  153. package/dist/swipe.d.ts.map +1 -1
  154. package/dist/swipe.js +4 -4
  155. package/dist/swipe.js.map +1 -1
  156. package/dist/{tab-list-CFF7uSYH.d.ts → tab-list-BnqISNvO.d.ts} +5 -6
  157. package/dist/tab-list-BnqISNvO.d.ts.map +1 -0
  158. package/dist/tabs.d.ts +1 -1
  159. package/dist/tabs.js +4 -4
  160. package/dist/tabs.js.map +1 -1
  161. package/dist/tickled-controller-Cq1Va0rR.d.ts.map +1 -1
  162. package/dist/tickled-styles-Bg3QbcrD.js.map +1 -1
  163. package/dist/{timeline-DBWJsSjd.js → timeline-Cv75Th2U.js} +123 -119
  164. package/dist/timeline-Cv75Th2U.js.map +1 -0
  165. package/dist/timeline.d.ts +2 -2
  166. package/dist/timeline.js +1 -1
  167. package/dist/{tooltip-kfUBWYVB.d.ts → tooltip-CwGfb4lp.d.ts} +2 -3
  168. package/dist/tooltip-CwGfb4lp.d.ts.map +1 -0
  169. package/dist/tree-CvvKbO-f.js +1330 -0
  170. package/dist/tree-CvvKbO-f.js.map +1 -0
  171. package/dist/{tree-component-CQpoPo4s.d.ts → tree-component-D8Bg48tt.d.ts} +12 -12
  172. package/dist/tree-component-D8Bg48tt.d.ts.map +1 -0
  173. package/dist/tree.d.ts +4 -5
  174. package/dist/tree.d.ts.map +1 -1
  175. package/dist/tree.js +1 -1143
  176. package/dist/types-BKaqnpwx.d.ts.map +1 -1
  177. package/dist/{xy-axis-CIFok7MF.d.ts → xy-axis-D9uwpGTT.d.ts} +5 -6
  178. package/dist/xy-axis-D9uwpGTT.d.ts.map +1 -0
  179. package/dist/{xy-axis-BANbgrI5.js → xy-axis-DL0tfJDr.js} +13 -13
  180. package/dist/xy-axis-DL0tfJDr.js.map +1 -0
  181. package/dist/xy-pad.d.ts +1 -2
  182. package/dist/xy-pad.d.ts.map +1 -1
  183. package/dist/xy-pad.js +4 -4
  184. package/dist/xy-pad.js.map +1 -1
  185. package/package.json +17 -13
  186. package/dist/button-DgKUzE5o.js.map +0 -1
  187. package/dist/colour-picker-Bw-AZoWx.js.map +0 -1
  188. package/dist/decorate-BZbkqIhA.js +0 -9
  189. package/dist/hex-editor-Gib1Grg2.d.ts.map +0 -1
  190. package/dist/hex-editor-Wd6ZtZA2.js.map +0 -1
  191. package/dist/highlight-CAjwQ4GE.js.map +0 -1
  192. package/dist/icon-BR5aTIiP.js.map +0 -1
  193. package/dist/icon-DQKdfR7U.d.ts.map +0 -1
  194. package/dist/incr-search-BjCNClnC.js.map +0 -1
  195. package/dist/index-C5dK8RZa.d.ts.map +0 -1
  196. package/dist/index-C9iBN908.d.ts.map +0 -1
  197. package/dist/index-CPVcTTXo.d.ts.map +0 -1
  198. package/dist/index-CQxanqXF.d.ts.map +0 -1
  199. package/dist/index-CpbBI3n7.d.ts.map +0 -1
  200. package/dist/labelled-input-base-DS6-6C0r.js.map +0 -1
  201. package/dist/labelled-input-base-DnRh4reU.d.ts.map +0 -1
  202. package/dist/menu-BV6UFx_z.js.map +0 -1
  203. package/dist/menu-item-Cikacp4F.d.ts.map +0 -1
  204. package/dist/menu-item-DEzL_Yjk.js.map +0 -1
  205. package/dist/radial-input-CNJ8asY2.js.map +0 -1
  206. package/dist/radial-input-xSTd4kEz.d.ts.map +0 -1
  207. package/dist/tab-list-CFF7uSYH.d.ts.map +0 -1
  208. package/dist/timeline-DBWJsSjd.js.map +0 -1
  209. package/dist/tooltip-kfUBWYVB.d.ts.map +0 -1
  210. package/dist/tree-component-CQpoPo4s.d.ts.map +0 -1
  211. package/dist/tree-data-model-DGvRVOFY.js +0 -190
  212. package/dist/tree-data-model-DGvRVOFY.js.map +0 -1
  213. package/dist/tree.js.map +0 -1
  214. package/dist/xy-axis-BANbgrI5.js.map +0 -1
  215. package/dist/xy-axis-CIFok7MF.d.ts.map +0 -1
  216. /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