@ixfx/components 0.15.5 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/bundle/index.d.ts +986 -49
  2. package/bundle/index.js +765 -729
  3. package/dist/ac-token.js +1 -1
  4. package/dist/crumb-navigation-BbqfmkYs.d.ts +147 -0
  5. package/dist/crumb-navigation-YnFVFqrx.js +1290 -0
  6. package/dist/crumbs.d.ts +2 -115
  7. package/dist/crumbs.js +1 -1179
  8. package/dist/{data-grid-C1sZX6n9.js → data-grid-Cqxuwgbb.js} +615 -210
  9. package/dist/{data-grid-D7qzmMMp.d.ts → data-grid-Csh5EJW9.d.ts} +55 -4
  10. package/dist/data-grid.d.ts +3 -3
  11. package/dist/data-grid.js +1 -1
  12. package/dist/{detail-list-q8bs67BM.js → detail-list-Bo5ze3SN.js} +234 -9
  13. package/dist/{detail-list-CKIKlszv.d.ts → detail-list-DSZ1DDor.d.ts} +48 -4
  14. package/dist/detail-list.d.ts +4 -4
  15. package/dist/detail-list.js +1 -1
  16. package/dist/{flat-list-DXMyDiSH.d.ts → flat-list-CT9kX3TQ.d.ts} +27 -4
  17. package/dist/flat-list.d.ts +2 -2
  18. package/dist/flat-list.js +123 -4
  19. package/dist/{grid-list-y6dWJYu3.js → grid-list-D0dJNVEF.js} +278 -76
  20. package/dist/{grid-list-0e7snW6t.d.ts → grid-list-DDLrWRxm.d.ts} +128 -22
  21. package/dist/grid-list.d.ts +3 -3
  22. package/dist/grid-list.js +1 -1
  23. package/dist/{incr-search-JIhwB49y.js → incr-search-CgbL-OlS.js} +140 -10
  24. package/dist/incr-search.d.ts +1 -1
  25. package/dist/incr-search.js +1 -1
  26. package/dist/index-9ttcdwlL.d.ts +71 -0
  27. package/dist/index-Bnm-kKGC.d.ts +475 -0
  28. package/dist/{index-KN2MW_xD.d.ts → index-CUErGhCg.d.ts} +31 -4
  29. package/dist/{index-DEVyVIn8.d.ts → index-D-lZNlHa.d.ts} +52 -6
  30. package/dist/index.d.ts +341 -21
  31. package/dist/index.js +2286 -884
  32. package/dist/{list-native-drag-controller-O2Nruqpi.d.ts → list-native-drag-controller-D_UrXipS.d.ts} +1 -1
  33. package/dist/{list-selection-controller-BeE70lbB.js → list-selection-controller-5L0Nj9tR.js} +87 -2
  34. package/dist/miller-DUqiUAWQ.js +2230 -0
  35. package/dist/miller.d.ts +4 -437
  36. package/dist/miller.js +1 -1815
  37. package/dist/narrowed-text.js +1 -1
  38. package/dist/pending-BGidHppL.js +91 -0
  39. package/dist/scroll-hover-guard-Cjw6179Q.js +35 -0
  40. package/dist/{split-layout-JK3xWVX2.d.ts → split-layout-CophkNeU.d.ts} +41 -3
  41. package/dist/{split-layout-Deoxy47d.js → split-layout-DTY4PJRp.js} +251 -19
  42. package/dist/split-layout.d.ts +1 -1
  43. package/dist/split-layout.js +1 -1
  44. package/dist/sunburst-tree.d.ts +2 -2
  45. package/dist/sunburst-tree.js +1 -1
  46. package/dist/{sunbursttree-list-yXwS-uEI.js → sunbursttree-list-B_M0YTEA.js} +299 -80
  47. package/dist/{sunbursttree-list--Z0uyh8O.d.ts → sunbursttree-list-DePuFYlU.d.ts} +40 -3
  48. package/dist/tree-CftHwtEb.js +146 -0
  49. package/dist/{tree-D8VxvR9u.js → tree-DAlgvaHB.js} +276 -48
  50. package/dist/{tree-component-C6UHO-w9.d.ts → tree-component-DgfkxmVY.d.ts} +19 -8
  51. package/dist/tree.d.ts +3 -3
  52. package/dist/tree.js +1 -1
  53. package/dist/types-LU1u0CB8.d.ts +87 -0
  54. package/dist/vertical-list-DyKPKGxK.js +1993 -0
  55. package/dist/{vertical-list-D8MksoTp.d.ts → vertical-list-SAx56U-l.d.ts} +31 -3
  56. package/dist/vertical-list.d.ts +3 -3
  57. package/dist/vertical-list.js +1 -1789
  58. package/dist/view-state-controller-BdyXWUUM.js +305 -0
  59. package/docs-user/README.md +2 -0
  60. package/docs-user/crumbs.md +27 -0
  61. package/docs-user/data-grid.md +79 -5
  62. package/docs-user/detail-list.md +69 -0
  63. package/docs-user/dock.md +63 -12
  64. package/docs-user/flat-list.md +27 -0
  65. package/docs-user/grid-list.md +69 -13
  66. package/docs-user/hyperbolic-tree.md +31 -4
  67. package/docs-user/icicle-tree.md +36 -0
  68. package/docs-user/index.json +12 -2
  69. package/docs-user/llms.txt +2 -0
  70. package/docs-user/miller.md +52 -21
  71. package/docs-user/multicol-list.md +50 -8
  72. package/docs-user/split-layout.md +45 -17
  73. package/docs-user/sunburst-tree.md +32 -2
  74. package/docs-user/tree.md +45 -6
  75. package/docs-user/treemap.md +32 -0
  76. package/docs-user/twosplit-layout.md +22 -0
  77. package/docs-user/user-catalog.md +1 -1
  78. package/docs-user/vertical-list.md +55 -7
  79. package/llms.txt +2 -0
  80. package/package.json +2 -2
@@ -0,0 +1,1290 @@
1
+ import { t as TickledController } from "./tickled-controller-D-HZymaZ.js";
2
+ import { n as tickledStyles, t as tickledItemStyles } from "./tickled-styles-BrAHEj1q.js";
3
+ import { t as themeFallbacks } from "./fallbacks-DQ_5OsmR.js";
4
+ import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
5
+ import "./interaction-E-uMGHkJ.js";
6
+ import "./menu-item-BUv8An2k.js";
7
+ import { t as collectDataResult } from "./data-provider-35TyWclX.js";
8
+ import { t as ViewStateController } from "./view-state-controller-BdyXWUUM.js";
9
+ import { t as scrollFadeStyles } from "./scroll-fade-Xx28D7mH.js";
10
+ import "./narrowed-text.js";
11
+ import { LitElement, css, html, nothing } from "lit";
12
+ import { property, state } from "lit/decorators.js";
13
+ //#region src/crumbs/crumb-view-state.ts
14
+ /** Walk `keys` down from `root` as far as the loaded tree allows. */
15
+ function resolveKeyPath(root, keys) {
16
+ const nodes = [root];
17
+ for (const key of keys) {
18
+ const parent = nodes.at(-1);
19
+ if (parent.children === void 0) {
20
+ const leaf = parent.item.isLeaf === true;
21
+ return {
22
+ nodes,
23
+ blocked: leaf ? void 0 : parent,
24
+ done: leaf
25
+ };
26
+ }
27
+ const next = parent.children.find((n) => n.item.key === key);
28
+ if (!next) return {
29
+ nodes,
30
+ blocked: void 0,
31
+ done: false
32
+ };
33
+ nodes.push(next);
34
+ }
35
+ return {
36
+ nodes,
37
+ blocked: void 0,
38
+ done: true
39
+ };
40
+ }
41
+ /** Key path (root excluded) to `target`, or `undefined` when it isn't in the loaded tree. */
42
+ function keyPathTo(root, target) {
43
+ if (root === target) return [];
44
+ for (const child of root.children ?? []) {
45
+ const sub = keyPathTo(child, target);
46
+ if (sub) return [child.item.key, ...sub];
47
+ }
48
+ }
49
+ //#endregion
50
+ //#region src/crumbs/crumb-navigation.ts
51
+ /** Menu / suggestion order: by label, without mutating the tree's own order. */
52
+ function sortNodesByLabel(nodes) {
53
+ return [...nodes].sort((a, b) => (a.item.label ?? ``).localeCompare(b.item.label ?? ``));
54
+ }
55
+ let CrumbNavigationElement = class CrumbNavigationElement extends LitElement {
56
+ constructor(..._args) {
57
+ super(..._args);
58
+ this._selectedNodes = /* @__PURE__ */ new Set();
59
+ this.exclusivity = `none`;
60
+ this.selectionFilter = `leaf`;
61
+ this._openIdx = -1;
62
+ this._isFocused = false;
63
+ this._menuOpen = false;
64
+ this._focusOnNarrowedText = false;
65
+ this.inputMode = ``;
66
+ this.noTruncate = false;
67
+ this.showSuggestions = false;
68
+ this.leadingCaret = false;
69
+ this.caretEmbedded = true;
70
+ this.hideRoot = false;
71
+ this.padEnding = false;
72
+ this.withUp = false;
73
+ this.restoreLoads = true;
74
+ this.#viewState = new ViewStateController(this, {
75
+ kind: `crumb-navigation`,
76
+ capture: () => this.getViewState()
77
+ });
78
+ this.#previousSelectionKey = ``;
79
+ this.#menuOpenedByKeyboard = false;
80
+ this.#currentOpenTrigger = null;
81
+ this.tickled = new TickledController(this, {
82
+ getItems: () => this.#getTickledItems(),
83
+ onTickle: (index) => {
84
+ if (index !== void 0) this.#onTickled(index);
85
+ }
86
+ });
87
+ this.#onFocus = () => {
88
+ this._isFocused = true;
89
+ };
90
+ this.#onBlur = () => {
91
+ this._isFocused = false;
92
+ };
93
+ this.#onHostClick = () => {
94
+ this.#getHostContainer()?.focus();
95
+ };
96
+ this.#onMenuTriggerClose = () => {
97
+ document.removeEventListener(`keydown`, this.#onMenuKeyNav, { capture: true });
98
+ this.#currentOpenTrigger = null;
99
+ this.#menuOpenedByKeyboard = false;
100
+ requestAnimationFrame(() => {
101
+ this.#getHostContainer()?.focus();
102
+ });
103
+ };
104
+ this.#onMenuKeyNav = (e) => {
105
+ if (!this.#currentOpenTrigger?.querySelector(`ixfx-menu-container`) || !this.#currentOpenTrigger?.isOpen) return;
106
+ switch (e.key) {
107
+ case `ArrowLeft`:
108
+ case `ArrowRight`: {
109
+ e.preventDefault();
110
+ e.stopPropagation();
111
+ this.#currentOpenTrigger.closeMenu();
112
+ const r = this.root;
113
+ const sel = this.selectedNode;
114
+ if (!r || !sel) break;
115
+ const totalItems = this.#getTickledItems().length;
116
+ const currentTickled = this.tickled.tickledId ?? -1;
117
+ if (e.key === `ArrowLeft`) {
118
+ if (currentTickled > 0) this.tickled.handleKeyboardNavigation(`previous`);
119
+ } else if (currentTickled < totalItems - 1) this.tickled.handleKeyboardNavigation(`next`);
120
+ break;
121
+ }
122
+ }
123
+ };
124
+ this.#onKeyDown = (e) => {
125
+ const r = this.root;
126
+ const sel = this.selectedNode;
127
+ if (!r || !sel) return;
128
+ const totalItems = this.#getTickledItems().length;
129
+ const currentTickled = this.tickled.tickledId ?? -1;
130
+ switch (e.key) {
131
+ case `ArrowRight`:
132
+ e.preventDefault();
133
+ if (currentTickled < totalItems - 1) this.tickled.handleKeyboardNavigation(`next`);
134
+ else if (currentTickled === totalItems - 1 && this.#canFocusSuggestions()) this.#shiftFocusToNarrowedText();
135
+ break;
136
+ case `ArrowLeft`:
137
+ e.preventDefault();
138
+ if (currentTickled > 0) this.tickled.handleKeyboardNavigation(`previous`);
139
+ break;
140
+ case `ArrowDown`:
141
+ case `ArrowUp`:
142
+ if (this.tickled.tickledId !== void 0) {
143
+ e.preventDefault();
144
+ if (this.#currentOpenTrigger?.isOpen) {
145
+ const popover = this.#currentOpenTrigger.querySelector(`ixfx-menu-container`)?.shadowRoot?.querySelector(`.popover`);
146
+ if (popover) popover.dispatchEvent(new KeyboardEvent(`keydown`, {
147
+ key: e.key,
148
+ bubbles: true
149
+ }));
150
+ } else this.#handleOpenCaretMenu();
151
+ }
152
+ break;
153
+ case `Enter`:
154
+ case ` `:
155
+ e.preventDefault();
156
+ this.#activateTickled();
157
+ break;
158
+ case `Escape`:
159
+ e.preventDefault();
160
+ this._menuOpen = false;
161
+ this.tickled.clearTickled();
162
+ this.blur();
163
+ break;
164
+ case `Tab`:
165
+ if (e.shiftKey) break;
166
+ if (currentTickled === totalItems - 1 && this.#canFocusSuggestions()) {
167
+ e.preventDefault();
168
+ this.#shiftFocusToNarrowedText();
169
+ }
170
+ }
171
+ };
172
+ this.#onNarrowedTextBlur = () => {
173
+ this._focusOnNarrowedText = false;
174
+ this.#shiftFocusToPath();
175
+ };
176
+ this.#onNarrowedTextShiftTab = () => {
177
+ this._focusOnNarrowedText = false;
178
+ this.#shiftFocusToPath();
179
+ };
180
+ this.#isWheelScrolling = false;
181
+ this.#onWheel = (e) => {
182
+ const wheelEvent = e;
183
+ if (wheelEvent.deltaX !== 0 || wheelEvent.deltaY !== 0) {
184
+ e.preventDefault();
185
+ if (!this.#isWheelScrolling) {
186
+ this.#isWheelScrolling = true;
187
+ this.tickled.clearTickled();
188
+ }
189
+ const hostContainer = this.#getHostContainer();
190
+ if (hostContainer) hostContainer.scrollLeft += wheelEvent.deltaX + wheelEvent.deltaY;
191
+ clearTimeout(this.#wheelScrollTimeout);
192
+ this.#wheelScrollTimeout = setTimeout(() => {
193
+ this.#isWheelScrolling = false;
194
+ }, 150);
195
+ }
196
+ };
197
+ this.#currentLeadingSiblings = void 0;
198
+ }
199
+ get selectedNodes() {
200
+ return this._selectedNodes;
201
+ }
202
+ #pendingNode;
203
+ #pendingController;
204
+ #modelUnsubscribe;
205
+ #viewState;
206
+ /** Saved location still being reached (keys not loaded yet). */
207
+ #pendingPath;
208
+ /** The last `selectedNode` this component set itself — anything else came from the host. */
209
+ #ownSelection;
210
+ #previousSelectionKey;
211
+ #menuOpenedByKeyboard;
212
+ #currentOpenTrigger;
213
+ #getTickledItems() {
214
+ const r = this.root;
215
+ const sel = this.selectedNode;
216
+ if (!r || !sel) return [];
217
+ const pathNodes = this.#findPath(r, sel) ?? [r];
218
+ const items = [];
219
+ if (this.caretEmbedded) {
220
+ pathNodes.forEach((_node, i) => {
221
+ items.push({
222
+ id: i,
223
+ enabled: true
224
+ });
225
+ });
226
+ if (this.#canFocusSuggestions()) items.push({
227
+ id: pathNodes.length,
228
+ enabled: true
229
+ });
230
+ } else {
231
+ let segmentId = 0;
232
+ pathNodes.forEach((node, i) => {
233
+ const isLast = i === pathNodes.length - 1;
234
+ const hasChildren = node.item.isLeaf !== true && (node.children === void 0 || node.children.length > 0);
235
+ const showLeadingCaret = this.leadingCaret && (i > 0 || this.hideRoot) && hasChildren;
236
+ const showTrailingCaret = !this.leadingCaret && hasChildren || this.leadingCaret && isLast && hasChildren;
237
+ if (showLeadingCaret) items.push({
238
+ id: segmentId++,
239
+ enabled: true
240
+ });
241
+ items.push({
242
+ id: segmentId++,
243
+ enabled: true
244
+ });
245
+ if (showTrailingCaret) items.push({
246
+ id: segmentId++,
247
+ enabled: true
248
+ });
249
+ });
250
+ }
251
+ return items;
252
+ }
253
+ #onTickled(index) {
254
+ if (this.tickled.inputMode === `keyboard`) this.#scrollTickledIntoView(index);
255
+ }
256
+ #scrollTickledIntoView(index) {
257
+ const r = this.root;
258
+ const sel = this.selectedNode;
259
+ if (!r || !sel) return;
260
+ const pathNodes = this.#findPath(r, sel) ?? [r];
261
+ let el = null;
262
+ if (this.caretEmbedded && this.#canFocusSuggestions() && index === pathNodes.length) el = this.shadowRoot?.querySelector(`ixfx-narrowed-text`) ?? null;
263
+ else el = this.shadowRoot?.querySelectorAll(`.crumb-segment`)[index] ?? null;
264
+ el?.scrollIntoView({
265
+ inline: `nearest`,
266
+ block: `nearest`,
267
+ behavior: `smooth`
268
+ });
269
+ }
270
+ #hasChildOptions() {
271
+ const s = this.selectedNode;
272
+ if (s === void 0) return false;
273
+ const isPending = this.#pendingNode === s;
274
+ return (s.children ?? []).length > 0 || isPending;
275
+ }
276
+ /** True when the suggestions (narrowed-text) sub-part is renderable and focusable. */
277
+ #canFocusSuggestions() {
278
+ return this.showSuggestions && this.#hasChildOptions();
279
+ }
280
+ #onFocus;
281
+ #onBlur;
282
+ #getHostContainer() {
283
+ return this.shadowRoot?.querySelector(`.host-container`) ?? null;
284
+ }
285
+ #onHostClick;
286
+ #handleOpenCaretMenu() {
287
+ const currentTickled = this.tickled.tickledId;
288
+ if (currentTickled === void 0) return;
289
+ const r = this.root;
290
+ const sel = this.selectedNode;
291
+ if (!r || !sel) return;
292
+ const pathNodes = this.#findPath(r, sel) ?? [r];
293
+ if (this.caretEmbedded && this.#canFocusSuggestions() && currentTickled === pathNodes.length) {
294
+ this.#shiftFocusToNarrowedText();
295
+ return;
296
+ }
297
+ const trigger = (this.shadowRoot?.querySelectorAll(`.crumb-segment`))?.[currentTickled]?.querySelector(`.caret ixfx-menu-trigger`);
298
+ if (trigger) {
299
+ this.#menuOpenedByKeyboard = true;
300
+ this.#currentOpenTrigger = trigger;
301
+ document.addEventListener(`keydown`, this.#onMenuKeyNav, { capture: true });
302
+ trigger.openMenu();
303
+ this.#propagateInputModeToMenu();
304
+ }
305
+ }
306
+ #propagateInputModeToMenu() {
307
+ const inputMode = this.tickled.inputMode;
308
+ requestAnimationFrame(() => {
309
+ const container = this.#currentOpenTrigger?.querySelector(`ixfx-menu-container`);
310
+ if (container) container.setAttribute(`data-input-mode`, inputMode);
311
+ });
312
+ }
313
+ #onMenuTriggerClose;
314
+ #onMenuKeyNav;
315
+ #onKeyDown;
316
+ /** Activates the currently tickled item, as if it had been clicked. */
317
+ #activateTickled() {
318
+ const currentTickled = this.tickled.tickledId;
319
+ if (currentTickled === void 0) return;
320
+ const r = this.root;
321
+ const sel = this.selectedNode;
322
+ if (!r || !sel) return;
323
+ const pathNodes = this.#findPath(r, sel) ?? [r];
324
+ if (this.caretEmbedded && this.#canFocusSuggestions() && currentTickled === pathNodes.length) {
325
+ this.#shiftFocusToNarrowedText();
326
+ return;
327
+ }
328
+ (this.shadowRoot?.querySelector(`.crumb-segment[data-segment-id="${currentTickled}"]`))?.click();
329
+ }
330
+ #shiftFocusToNarrowedText() {
331
+ const narrowedText = this.shadowRoot?.querySelector(`ixfx-narrowed-text`);
332
+ if (narrowedText) {
333
+ this._menuOpen = false;
334
+ this._focusOnNarrowedText = true;
335
+ narrowedText.focus();
336
+ }
337
+ }
338
+ #shiftFocusToPath() {
339
+ this._focusOnNarrowedText = false;
340
+ const hostContainer = this.#getHostContainer();
341
+ if (hostContainer) hostContainer.focus();
342
+ }
343
+ #onNarrowedTextBlur;
344
+ #onNarrowedTextShiftTab;
345
+ select(node) {
346
+ this.#setSelected(node);
347
+ }
348
+ deselect(_node) {}
349
+ clearSelection() {
350
+ if (this.root) this.#setSelected(this.root);
351
+ }
352
+ /** Not implemented on crumb-navigation. */
353
+ selectAll() {}
354
+ /** Not implemented on crumb-navigation. */
355
+ deselectAll() {}
356
+ getNodes() {
357
+ if (!this.root) return [];
358
+ const result = [];
359
+ const visit = (node, depth) => {
360
+ result.push({
361
+ node,
362
+ depth
363
+ });
364
+ if (node.children) for (const child of node.children) visit(child, depth + 1);
365
+ };
366
+ for (const child of this.root.children ?? []) visit(child, 0);
367
+ return result;
368
+ }
369
+ /** Snapshot the location as a key path. Includes a saved location not reached yet. */
370
+ getViewState(options) {
371
+ const withPending = options?.includePending ?? true;
372
+ const live = this.root && this.selectedNode ? keyPathTo(this.root, this.selectedNode) : void 0;
373
+ return this.#viewState.shape({ path: (withPending ? this.#pendingPath : void 0) ?? live ?? [] }, options);
374
+ }
375
+ /**
376
+ * Go to a saved location. Keys not loaded yet are reached as they appear —
377
+ * via `loadChildren` when `restoreLoads` — moving through each loaded ancestor.
378
+ */
379
+ setViewState(state) {
380
+ if (!this.#viewState.accepts(state) || !state.path) return;
381
+ this.#pendingPath = [...state.path];
382
+ this.#reconcilePath(false);
383
+ this.requestUpdate();
384
+ }
385
+ clearPendingViewState() {
386
+ this.#pendingPath = void 0;
387
+ }
388
+ /** Move to the deepest loaded node of the pending path; load the next branch when allowed. */
389
+ #reconcilePath(silent) {
390
+ const keys = this.#pendingPath;
391
+ const root = this.root;
392
+ if (!keys || !root) return;
393
+ const r = resolveKeyPath(root, keys);
394
+ if (r.done) this.#pendingPath = void 0;
395
+ const target = r.nodes.at(-1);
396
+ if (target !== this.selectedNode) this.#viewState.restoring(() => {
397
+ if (silent) {
398
+ this.#ownSelection = target;
399
+ this.selectedNode = target;
400
+ this._selectedNodes = /* @__PURE__ */ new Set([target]);
401
+ this.#viewState.notify();
402
+ } else this.#setSelected(target);
403
+ });
404
+ if (r.blocked && this.restoreLoads && this.loadChildren && !this.#pendingNode) {
405
+ this.#pendingNode = r.blocked;
406
+ const controller = new AbortController();
407
+ this.#pendingController = controller;
408
+ this.#invokeLoadChildren(r.blocked, r.nodes.length - 1, controller).catch(() => {});
409
+ }
410
+ }
411
+ willUpdate(changedProps) {
412
+ if (changedProps.has(`selectedNode`) && this.selectedNode !== this.#ownSelection) {
413
+ this.#ownSelection = this.selectedNode;
414
+ this.#pendingPath = void 0;
415
+ this.#viewState.notify();
416
+ } else if (changedProps.has(`root`) && !this.#pendingPath) {
417
+ const oldRoot = changedProps.get(`root`);
418
+ const sel = this.selectedNode;
419
+ if (oldRoot && sel && this.root && !keyPathTo(this.root, sel)) {
420
+ this.#pendingPath = keyPathTo(oldRoot, sel);
421
+ this.#reconcilePath(true);
422
+ return;
423
+ }
424
+ }
425
+ this.#reconcilePath(false);
426
+ }
427
+ updated(changedProps) {
428
+ if (changedProps.has(`model`)) {
429
+ this.#modelUnsubscribe?.();
430
+ this.#modelUnsubscribe = void 0;
431
+ const model = this.model;
432
+ if (model) {
433
+ this.root = model.root;
434
+ this.#modelUnsubscribe = model._subscribe((event) => {
435
+ if (event.type === `root`) {
436
+ this.root = event.root;
437
+ this.requestUpdate();
438
+ }
439
+ });
440
+ }
441
+ }
442
+ this.#runLayoutUpdate(changedProps);
443
+ }
444
+ connectedCallback() {
445
+ super.connectedCallback();
446
+ this.addEventListener(`click`, this.#onHostClick);
447
+ this.addEventListener(`wheel`, this.#onWheel, { passive: false });
448
+ }
449
+ disconnectedCallback() {
450
+ super.disconnectedCallback();
451
+ this.removeEventListener(`click`, this.#onHostClick);
452
+ this.removeEventListener(`wheel`, this.#onWheel);
453
+ this.#modelUnsubscribe?.();
454
+ this.#modelUnsubscribe = void 0;
455
+ document.removeEventListener(`keydown`, this.#onMenuKeyNav, { capture: true });
456
+ this.#currentOpenTrigger = null;
457
+ }
458
+ firstUpdated() {
459
+ const hostContainer = this.#getHostContainer();
460
+ if (hostContainer) hostContainer.scrollLeft = hostContainer.scrollWidth - hostContainer.clientWidth;
461
+ }
462
+ #isWheelScrolling;
463
+ #onWheel;
464
+ #wheelScrollTimeout;
465
+ #runLayoutUpdate(_changedProperties) {
466
+ if (!this.shadowRoot) return;
467
+ const hsiElement = this.shadowRoot.querySelector(`.horiz-select-items`);
468
+ if (!hsiElement) return;
469
+ const ulElement = hsiElement.querySelector(`ul`);
470
+ if (!ulElement) return;
471
+ const menuTrigger = this.shadowRoot.querySelector(`ixfx-menu-trigger`);
472
+ if (!menuTrigger) return;
473
+ if (ulElement.getBoundingClientRect().width > hsiElement.getBoundingClientRect().width) menuTrigger.style.display = `inline-flex`;
474
+ else menuTrigger.style.display = `none`;
475
+ for (const li of ulElement.querySelectorAll(`li`)) if ((li.getAttribute(`data-key`) ?? ``) === this.#previousSelectionKey) {
476
+ this.#scrollToValue(li);
477
+ break;
478
+ }
479
+ }
480
+ #scrollToValue(li) {
481
+ let l = li.offsetLeft;
482
+ const ul = li.parentElement;
483
+ if (!ul) return;
484
+ l -= ul.offsetLeft;
485
+ const horizSelectItems = ul.parentElement;
486
+ if (!horizSelectItems) return;
487
+ horizSelectItems.scroll({
488
+ left: l,
489
+ top: 0,
490
+ behavior: `smooth`
491
+ });
492
+ const horizSelect = horizSelectItems.parentElement;
493
+ if (!horizSelect) return;
494
+ horizSelect.style.minWidth = `${li.getBoundingClientRect().width}px`;
495
+ }
496
+ #abortPending() {
497
+ if (this.#pendingController) {
498
+ this.#pendingController.abort();
499
+ this.#pendingController = void 0;
500
+ this.#pendingNode = void 0;
501
+ }
502
+ }
503
+ #findPath(node, target, acc = []) {
504
+ const path = [...acc, node];
505
+ if (node === target) return path;
506
+ if (!node.children) return void 0;
507
+ for (const child of node.children) {
508
+ const result = this.#findPath(child, target, path);
509
+ if (result) return result;
510
+ }
511
+ }
512
+ #scrollSegmentIntoView(depth) {
513
+ if (!this.shadowRoot) return;
514
+ this.shadowRoot.querySelectorAll(`.crumb-segment`)[depth]?.scrollIntoView({
515
+ block: `nearest`,
516
+ inline: `start`
517
+ });
518
+ }
519
+ #updateTrailingWidth() {
520
+ if (!this.padEnding || !this.shadowRoot) return;
521
+ const hostContainer = this.shadowRoot.querySelector(`.host-container`);
522
+ const trailing = this.shadowRoot.querySelector(`.trailing`);
523
+ if (!hostContainer || !trailing) return;
524
+ const segments = this.shadowRoot.querySelectorAll(`.crumb-segment`);
525
+ const lastSegment = segments[segments.length - 1];
526
+ if (!lastSegment) return;
527
+ const containerWidth = hostContainer.getBoundingClientRect().width;
528
+ const itemWidth = lastSegment.getBoundingClientRect().width;
529
+ if (itemWidth < containerWidth) {
530
+ trailing.style.width = `${containerWidth - itemWidth - 5}px`;
531
+ requestAnimationFrame(() => {
532
+ if (hostContainer.scrollWidth <= hostContainer.clientWidth) hostContainer.style.setProperty(`--scroll-fade-x-start`, `0px`, `important`);
533
+ else hostContainer.style.removeProperty(`--scroll-fade-x-start`);
534
+ });
535
+ } else trailing.style.width = ``;
536
+ }
537
+ #setSelected(node) {
538
+ const previous = this.selectedNode;
539
+ if (previous) this.#previousSelectionKey = previous.item?.key ?? ``;
540
+ else this.#previousSelectionKey = ``;
541
+ const previousSet = previous ? /* @__PURE__ */ new Set([previous]) : /* @__PURE__ */ new Set();
542
+ this.#ownSelection = node;
543
+ this.selectedNode = node;
544
+ this._selectedNodes = /* @__PURE__ */ new Set([node]);
545
+ if (!this.#viewState.isRestoring) this.#pendingPath = void 0;
546
+ this.#viewState.notify();
547
+ if (this.root) {
548
+ const path = this.#findPath(this.root, node);
549
+ if (path) {
550
+ const depth = this.hideRoot && path.length > 1 ? path.length - 2 : path.length - 1;
551
+ requestAnimationFrame(() => {
552
+ this.#updateTrailingWidth();
553
+ const hostContainer = this.#getHostContainer();
554
+ if (hostContainer) hostContainer.scrollLeft = 0;
555
+ this.#scrollSegmentIntoView(depth);
556
+ });
557
+ }
558
+ }
559
+ this.dispatchEvent(new CustomEvent(`select`, {
560
+ detail: {
561
+ selected: /* @__PURE__ */ new Set([node]),
562
+ previous: previousSet
563
+ },
564
+ bubbles: true,
565
+ composed: true
566
+ }));
567
+ }
568
+ #onLabelClick(node, depth) {
569
+ this._openIdx = -1;
570
+ this.#setSelected(node);
571
+ this.#scrollSegmentIntoView(depth);
572
+ this.dispatchEvent(new CustomEvent(`item-click`, {
573
+ detail: {
574
+ node,
575
+ depth
576
+ },
577
+ bubbles: true,
578
+ composed: true
579
+ }));
580
+ }
581
+ #onCaretClick(node, depth, e) {
582
+ e.preventDefault();
583
+ e.stopPropagation();
584
+ if (this._openIdx === depth) {
585
+ this._openIdx = -1;
586
+ return;
587
+ }
588
+ this._openIdx = depth;
589
+ if (node.children === void 0 && this.#pendingNode !== node) {
590
+ this.#abortPending();
591
+ this.#pendingNode = node;
592
+ const controller = new AbortController();
593
+ this.#pendingController = controller;
594
+ if (this.loadChildren) this.#invokeLoadChildren(node, depth, controller).catch(() => {});
595
+ }
596
+ this.requestUpdate();
597
+ }
598
+ async #invokeLoadChildren(node, depth, controller) {
599
+ try {
600
+ const children = [...await collectDataResult(this.loadChildren({
601
+ node,
602
+ depth
603
+ }, controller.signal), controller.signal)];
604
+ if (controller.signal.aborted) return;
605
+ node.children = children;
606
+ this.#pendingNode = void 0;
607
+ this.#pendingController = void 0;
608
+ this.dispatchEvent(new CustomEvent(`expand`, {
609
+ detail: {
610
+ node,
611
+ depth
612
+ },
613
+ bubbles: true,
614
+ composed: true
615
+ }));
616
+ this.requestUpdate();
617
+ } catch {
618
+ if (controller.signal.aborted) return;
619
+ node.children = [];
620
+ this.#pendingNode = void 0;
621
+ this.#pendingController = void 0;
622
+ this.requestUpdate();
623
+ }
624
+ }
625
+ #onSegmentMenuCommand(e, node, depth) {
626
+ const key = e.detail.command;
627
+ if (this.leadingCaret) {
628
+ const child = node.children?.find((c) => c.item.key === key);
629
+ if (child) {
630
+ this.#onChildClick(child, depth + 1);
631
+ return;
632
+ }
633
+ if (this.#currentLeadingSiblings) {
634
+ const sibling = this.#currentLeadingSiblings.find((c) => c.item.key === key);
635
+ if (sibling) this.#onChildClick(sibling, depth);
636
+ this.#currentLeadingSiblings = void 0;
637
+ return;
638
+ }
639
+ }
640
+ const child = node.children?.find((c) => c.item.key === key);
641
+ if (child) this.#onChildClick(child, depth + 1);
642
+ }
643
+ #currentLeadingSiblings;
644
+ #getSiblingMenuItems(pathNodes, index) {
645
+ if (index === 0) {
646
+ if (!this.hideRoot || !this.root) return [];
647
+ const siblings = sortNodesByLabel(this.root.children ?? []);
648
+ this.#currentLeadingSiblings = siblings;
649
+ return siblings.map((sibling) => html`
650
+ <ixfx-menu-item
651
+ label="${sibling.item.label}"
652
+ command="${sibling.item.key}"
653
+ icon-name="${sibling.item.icon ?? nothing}"
654
+ @click=${() => {
655
+ this.#onChildClick(sibling, index);
656
+ }}
657
+ ></ixfx-menu-item>
658
+ `);
659
+ }
660
+ const parent = pathNodes[index - 1];
661
+ const siblings = sortNodesByLabel(parent.children ?? []);
662
+ this.#currentLeadingSiblings = siblings;
663
+ return siblings.map((sibling) => html`
664
+ <ixfx-menu-item
665
+ label="${sibling.item.label}"
666
+ command="${sibling.item.key}"
667
+ icon-name="${sibling.item.icon ?? nothing}"
668
+ @click=${() => {
669
+ this.#onChildClick(sibling, index);
670
+ }}
671
+ ></ixfx-menu-item>
672
+ `);
673
+ }
674
+ #onChildClick(node, depth) {
675
+ this._openIdx = -1;
676
+ this.#setSelected(node);
677
+ this.dispatchEvent(new CustomEvent(`item-click`, {
678
+ detail: {
679
+ node,
680
+ depth
681
+ },
682
+ bubbles: true,
683
+ composed: true
684
+ }));
685
+ }
686
+ #onUpClick() {
687
+ const r = this.root;
688
+ const sel = this.selectedNode;
689
+ if (!r || !sel) return;
690
+ const path = this.#findPath(r, sel);
691
+ if (!path || path.length <= 1) return;
692
+ const parent = path[path.length - 2];
693
+ this.#setSelected(parent);
694
+ }
695
+ /** Navigate to a node by key path. Returns false if any key is not found in already-loaded children. */
696
+ navigateTo(keys) {
697
+ if (!this.root?.children) return false;
698
+ let current = this.root;
699
+ for (const key of keys) {
700
+ const found = current?.children?.find((n) => n.item.key === key);
701
+ if (!found) return false;
702
+ current = found;
703
+ }
704
+ if (current && current !== this.root) this.#setSelected(current);
705
+ return true;
706
+ }
707
+ /** Navigate to the first node matching the predicate. Returns false if not found. */
708
+ navigateToNode(predicate) {
709
+ if (!this.root) return false;
710
+ const keyPath = this.#findNodePath(this.root.children ?? [], predicate);
711
+ if (!keyPath) return false;
712
+ return this.navigateTo(keyPath);
713
+ }
714
+ /** `TreeComponent` parity — a crumb path always follows the selection, so this just navigates to `key`. */
715
+ revealKey(key) {
716
+ return this.navigateToNode((n) => n.item.key === key);
717
+ }
718
+ /** `TreeComponent` parity — a crumb path is a single selection, so only the last node is kept. */
719
+ selectMany(nodes) {
720
+ const last = [...nodes].at(-1);
721
+ if (last) this.select(last);
722
+ else this.clearSelection();
723
+ }
724
+ #findNodePath(nodes, predicate, prefix = []) {
725
+ for (const node of nodes) {
726
+ const path = [...prefix, node.item.key];
727
+ if (predicate(node)) return path;
728
+ if (node.children?.length) {
729
+ const result = this.#findNodePath(node.children, predicate, path);
730
+ if (result) return result;
731
+ }
732
+ }
733
+ }
734
+ #onMenuCommand(e) {
735
+ const node = this.selectedNode;
736
+ if (!node?.children) return;
737
+ const key = e.detail.command;
738
+ const child = node.children.find((c) => c.item.key === key);
739
+ if (child) {
740
+ const depth = this.root ? this.#findPath(this.root, node)?.length ?? 1 : 1;
741
+ this.#onChildClick(child, depth);
742
+ }
743
+ }
744
+ render() {
745
+ const r = this.root;
746
+ const sel = this.selectedNode;
747
+ if (!r || !sel) return html``;
748
+ this.inputMode = this.tickled.inputMode;
749
+ let pathNodes = this.#findPath(r, sel) ?? [r];
750
+ if (this.hideRoot && pathNodes.length > 1) pathNodes = pathNodes.slice(1);
751
+ this.#hasChildOptions();
752
+ const isAtRoot = (() => {
753
+ return (this.#findPath(r, sel) ?? [r]).length <= 1;
754
+ })();
755
+ const parts = [];
756
+ let segmentId = 0;
757
+ pathNodes.forEach((node, i) => {
758
+ const isLast = i === pathNodes.length - 1;
759
+ const isPending = this.#pendingNode === node;
760
+ const isOpen = this._openIdx === i;
761
+ const hasChildren = node.item.isLeaf !== true && (node.children === void 0 || node.children.length > 0);
762
+ const isTickled = this.tickled.isTickled(i);
763
+ const showLeadingCaret = this.leadingCaret && (i > 0 || this.hideRoot) && hasChildren;
764
+ const showTrailingCaret = !this.leadingCaret && hasChildren || this.leadingCaret && isLast && hasChildren;
765
+ const siblingMenuItems = showLeadingCaret ? this.#getSiblingMenuItems(pathNodes, i) : [];
766
+ const childMenuItems = sortNodesByLabel(node.children ?? []).map((c) => html`
767
+ <ixfx-menu-item
768
+ label="${c.item.label}"
769
+ command="${c.item.key}"
770
+ icon-name="${c.item.icon ?? nothing}"
771
+ ></ixfx-menu-item>
772
+ `);
773
+ if (this.caretEmbedded) {
774
+ const canTruncate = !this.noTruncate && !isLast && !isTickled;
775
+ parts.push(html`
776
+ <div class="item crumb-segment ${isTickled ? `tickled` : ``} ${canTruncate ? `truncatable` : ``} ${this.leadingCaret ? `leading-caret` : ``} ${isLast ? `last-label` : ``}" data-segment-id="${i}" data-path-index="${i}"
777
+ @click=${(e) => {
778
+ if (e.target.closest(`.caret`)) return;
779
+ this.#onLabelClick(node, i);
780
+ }}
781
+ >
782
+ ${showLeadingCaret ? html`
783
+ <span class="caret ${isOpen ? `open` : ``} ${isPending ? `pending` : ``}">
784
+ ${isPending ? html`<span class="loading-spinner"></span>` : html`
785
+ <ixfx-menu-trigger mode="trigger" label="›" .tabStop=${false} @menu-command=${(e) => this.#onSegmentMenuCommand(e, node, i)}>
786
+ <ixfx-menu-container slot="menu">
787
+ ${siblingMenuItems}
788
+ </ixfx-menu-container>
789
+ </ixfx-menu-trigger>
790
+ `}
791
+ </span>
792
+ ` : nothing}
793
+ <span class="label">${node.item.label}</span>
794
+ ${showTrailingCaret ? html`
795
+ <span class="caret ${isOpen ? `open` : ``} ${isPending ? `pending` : ``}">
796
+ ${isPending ? html`<span class="loading-spinner"></span>` : html`
797
+ <ixfx-menu-trigger mode="trigger" label="›" .tabStop=${false} placement="right-start" @menu-command=${(e) => this.#onSegmentMenuCommand(e, node, i)}>
798
+ <ixfx-menu-container slot="menu">
799
+ ${childMenuItems}
800
+ </ixfx-menu-container>
801
+ </ixfx-menu-trigger>
802
+ `}
803
+ </span>
804
+ ` : nothing}
805
+ </div>
806
+ `);
807
+ } else {
808
+ const leadingCaretTickled = showLeadingCaret ? this.tickled.isTickled(segmentId) : false;
809
+ if (showLeadingCaret) {
810
+ parts.push(html`
811
+ <div class="item crumb-segment ${leadingCaretTickled ? `tickled` : ``}" data-segment-id="${segmentId}" data-path-index="${i}"
812
+ @click=${(e) => {
813
+ const trigger = e.target.closest(`.crumb-segment`)?.querySelector(`ixfx-menu-trigger`);
814
+ if (trigger) trigger.openMenu();
815
+ }}
816
+ >
817
+ <span class="caret ${isOpen ? `open` : ``} ${isPending ? `pending` : ``}">
818
+ ${isPending ? html`<span class="loading-spinner"></span>` : html`
819
+ <ixfx-menu-trigger mode="trigger" label="›" .tabStop=${false} placement="right-start" @menu-command=${(e) => this.#onSegmentMenuCommand(e, node, i)}>
820
+ <ixfx-menu-container slot="menu">
821
+ ${siblingMenuItems}
822
+ </ixfx-menu-container>
823
+ </ixfx-menu-trigger>
824
+ `}
825
+ </span>
826
+ </div>
827
+ `);
828
+ segmentId++;
829
+ }
830
+ const labelTickled = this.tickled.isTickled(segmentId);
831
+ const canTruncate = !this.noTruncate && !isLast && !labelTickled;
832
+ parts.push(html`
833
+ <div class="item crumb-segment ${labelTickled ? `tickled` : ``} ${canTruncate ? `truncatable` : ``} ${isLast ? `last-label` : ``}" data-segment-id="${segmentId}" data-path-index="${i}"
834
+ @click=${(e) => {
835
+ if (e.target.closest(`.caret`)) return;
836
+ this.#onLabelClick(node, i);
837
+ }}
838
+ >
839
+ <span class="label">${node.item.label}</span>
840
+ </div>
841
+ `);
842
+ segmentId++;
843
+ if (showTrailingCaret) {
844
+ const trailingCaretTickled = this.tickled.isTickled(segmentId);
845
+ parts.push(html`
846
+ <div class="item crumb-segment ${trailingCaretTickled ? `tickled` : ``}" data-segment-id="${segmentId}" data-path-index="${i}"
847
+ @click=${(e) => {
848
+ const trigger = e.target.closest(`.crumb-segment`)?.querySelector(`ixfx-menu-trigger`);
849
+ if (trigger) trigger.openMenu();
850
+ }}
851
+ >
852
+ <span class="caret ${isOpen ? `open` : ``} ${isPending ? `pending` : ``}">
853
+ ${isPending ? html`<span class="loading-spinner"></span>` : html`
854
+ <ixfx-menu-trigger mode="trigger" label="›" .tabStop=${false} @menu-command=${(e) => this.#onSegmentMenuCommand(e, node, i)}>
855
+ <ixfx-menu-container slot="menu">
856
+ ${childMenuItems}
857
+ </ixfx-menu-container>
858
+ </ixfx-menu-trigger>
859
+ `}
860
+ </span>
861
+ </div>
862
+ `);
863
+ segmentId++;
864
+ }
865
+ }
866
+ });
867
+ return html`
868
+ <div class="crumb-wrapper">
869
+ ${this.withUp ? html`
870
+ <button
871
+ class="up-button"
872
+ ?disabled=${isAtRoot}
873
+ aria-label="Go up one level"
874
+ @click=${this.#onUpClick}
875
+ >
876
+ <span class="label"><ixfx-icon name="folder-up"></ixfx-icon></span>
877
+ </button>
878
+ ` : nothing}
879
+ <div
880
+ class="host-container scroll-fade-x"
881
+ tabindex="0"
882
+ @focus=${this.#onFocus}
883
+ @blur=${this.#onBlur}
884
+ @keydown=${this.#onKeyDown}
885
+ @click=${this.#onHostClick}
886
+ @menu-trigger-close=${this.#onMenuTriggerClose}
887
+ @mouseout=${(e) => this.tickled.handlePointerLeave(e)}
888
+ >
889
+ <div class="parts"
890
+ @mouseover=${(e) => {
891
+ if (this.#isWheelScrolling) return;
892
+ const target = e.target;
893
+ if (!(target instanceof HTMLElement)) return;
894
+ const segment = target.closest(`.crumb-segment`);
895
+ if (!segment) return;
896
+ const segmentId = parseInt(segment.dataset.segmentId ?? ``, 10);
897
+ if (Number.isNaN(segmentId)) return;
898
+ const pathIndex = parseInt(segment.dataset.pathIndex ?? ``, 10);
899
+ const node = pathNodes[pathIndex];
900
+ if (!node) return;
901
+ this.tickled.handlePointerEnter(segmentId);
902
+ this.dispatchEvent(new CustomEvent(`tickle`, {
903
+ detail: {
904
+ node,
905
+ depth: pathIndex
906
+ },
907
+ bubbles: true,
908
+ composed: true
909
+ }));
910
+ }}
911
+ >${parts}${this.padEnding ? html`<div class="trailing"></div>` : nothing}</div>
912
+ ${this.showSuggestions ? this.#renderChildOptions() : nothing}
913
+ </div>
914
+ </div>
915
+ `;
916
+ }
917
+ #renderChildOptions() {
918
+ const s = this.selectedNode;
919
+ if (s === void 0) return nothing;
920
+ const isPending = this.#pendingNode === s;
921
+ const children = s.children ?? [];
922
+ if (children.length === 0 && !isPending) return nothing;
923
+ if (isPending) return html`
924
+ <div class="horiz-select">
925
+ <div class="horiz-select-items">
926
+ <span class="loading-spinner"></span>
927
+ </div>
928
+ </div>
929
+ `;
930
+ return html`
931
+ <ixfx-narrowed-text
932
+ class="horiz-select"
933
+ selection-mode="single"
934
+ orientation="horizontal"
935
+ nowrap
936
+ width=${this.#getChildOptionsWidth()}
937
+ @change=${this.#onNarrowedTextChange}
938
+ @narrowed-text-blur=${this.#onNarrowedTextBlur}
939
+ @narrowed-text-shift-tab=${this.#onNarrowedTextShiftTab}
940
+ >
941
+ ${sortNodesByLabel(children).map((c) => html`
942
+ <span data-value="${c.item.key}" data-label="${c.item.label}"></span>
943
+ `)}
944
+ </ixfx-narrowed-text>
945
+ `;
946
+ }
947
+ #onNarrowedTextChange(e) {
948
+ e.stopPropagation();
949
+ const detail = e.detail;
950
+ const node = this.selectedNode;
951
+ if (!node?.children) return;
952
+ const child = node.children.find((c) => c.item.key === detail.item.value);
953
+ if (child) {
954
+ const depth = this.root ? this.#findPath(this.root, node)?.length ?? 1 : 1;
955
+ this.#onChildClick(child, depth);
956
+ }
957
+ }
958
+ #getChildOptionsWidth() {
959
+ return `var(--child-options-width, 50ch)`;
960
+ }
961
+ static {
962
+ this.styles = [
963
+ themeFallbacks,
964
+ tickledStyles,
965
+ tickledItemStyles,
966
+ scrollFadeStyles(`x`),
967
+ css`
968
+ :host {
969
+ --crumb-item-bg: var(--item-bg, transparent);
970
+ --crumb-item-bg-tickled: var(--item-bg-tickled);
971
+ --crumb-item-text-tickled: var(--item-text-tickled);
972
+ --crumb-item-border: var(--item-border);
973
+ --crumb-item-border-tickled: var(--item-border-tickled);
974
+ --crumb-item-padding: var(--space-s, 2px);
975
+ --crumb-item-border-radius: var(--item-border-radius);
976
+ --crumb-item-gap: var(--item-gap, var(--space-xs));
977
+ --crumb-separator: var(--surface-muted-text);
978
+ --crumb-truncate-min-width: 10ch;
979
+ display: flex;
980
+ width: 100%;
981
+ }
982
+
983
+ /* Outline goes on :host, not .host-container: .host-container carries the
984
+ * scroll-fade mask-image, and CSS masks clip everything painted outside
985
+ * the masked box - including an outline, which paints outside the border edge. */
986
+ :host(:focus-within) {
987
+ outline: 2px solid var(--accent);
988
+ outline-offset: 2px;
989
+ }
990
+
991
+ .crumb-wrapper {
992
+ display: flex;
993
+ align-items: center;
994
+ gap: var(--crumb-item-gap);
995
+ width: 100%;
996
+ }
997
+
998
+ .up-button {
999
+ box-sizing: border-box;
1000
+ display: inline-flex;
1001
+ align-items: center;
1002
+ justify-content: center;
1003
+ flex-shrink: 0;
1004
+ background: var(--crumb-item-bg);
1005
+ border: var(--crumb-item-border);
1006
+ border-radius: var(--crumb-item-border-radius);
1007
+ padding: 0;
1008
+ margin: 0;
1009
+ cursor: pointer;
1010
+ color: inherit;
1011
+ font: inherit;
1012
+ line-height: normal;
1013
+ }
1014
+
1015
+ .up-button:disabled {
1016
+ opacity: 0.5;
1017
+ cursor: not-allowed;
1018
+ }
1019
+
1020
+ .up-button:hover:not(:disabled) {
1021
+ background: var(--crumb-item-bg-tickled);
1022
+ border-color: var(--crumb-item-border-tickled);
1023
+ }
1024
+
1025
+ .up-button .label {
1026
+ display: flex;
1027
+ align-items: center;
1028
+ padding: var(--crumb-item-padding);
1029
+ }
1030
+
1031
+ .up-button .label ixfx-icon {
1032
+ display: block;
1033
+ width: 1em;
1034
+ height: 1em;
1035
+ pointer-events: none;
1036
+ }
1037
+
1038
+ /* Edge fade (shows/hides based on scroll position) comes from the
1039
+ * shared .scroll-fade-x class - see ../styles/scroll-fade.ts */
1040
+ .host-container {
1041
+ display: flex;
1042
+ flex: 1;
1043
+ min-width: 0;
1044
+ outline: none;
1045
+ overflow: scroll;
1046
+ scroll-snap-type: x mandatory;
1047
+ scrollbar-width: none;
1048
+ overscroll-behavior: none;
1049
+ }
1050
+
1051
+ .host-container::-webkit-scrollbar {
1052
+ display: none;
1053
+ }
1054
+
1055
+ .parts {
1056
+ display: flex;
1057
+ gap: var(--space-s);
1058
+ }
1059
+
1060
+ .trailing {
1061
+ flex-shrink: 0;
1062
+ }
1063
+
1064
+ .crumb-segment {
1065
+ display: flex;
1066
+ align-items: center;
1067
+ flex-shrink: 0;
1068
+ background: var(--crumb-item-bg);
1069
+ border: var(--crumb-item-border);
1070
+ border-radius: var(--crumb-item-border-radius);
1071
+ padding:0;
1072
+ }
1073
+
1074
+ .crumb-segment.leading-caret {
1075
+ flex-direction: row;
1076
+ }
1077
+
1078
+ .crumb-segment.tickled {
1079
+ background: var(--crumb-item-bg-tickled);
1080
+ border-color: var(--crumb-item-border-tickled);
1081
+ color: var(--crumb-item-text-tickled);
1082
+ flex-shrink: 0;
1083
+ min-width: auto;
1084
+ }
1085
+
1086
+ .crumb-segment.truncatable {
1087
+ flex-shrink: 1;
1088
+ min-width: min(var(--crumb-truncate-min-width), auto);
1089
+ overflow: hidden;
1090
+ }
1091
+
1092
+ .crumb-segment.truncatable .label {
1093
+ overflow: hidden;
1094
+ text-overflow: ellipsis;
1095
+ white-space: nowrap;
1096
+ min-width: 0;
1097
+ }
1098
+
1099
+ .label {
1100
+ padding: var(--crumb-item-padding);
1101
+ cursor: pointer;
1102
+ white-space: nowrap;
1103
+ color: inherit;
1104
+ }
1105
+
1106
+ .crumb-segment.last-label .label {
1107
+ color: var(--crumb-last-label-color, inherit);
1108
+ font-style: var(--crumb-last-label-font-style, inherit);
1109
+ font-weight: var(--crumb-last-label-font-weight, inherit);
1110
+ text-decoration: var(--crumb-last-label-text-decoration, inherit);
1111
+ text-underline-offset: var(--crumb-last-label-text-underline-offset, auto);
1112
+ }
1113
+
1114
+ .crumb-segment.tickled .label {
1115
+ color: var(--crumb-item-text-tickled);
1116
+ overflow: visible;
1117
+ text-overflow: clip;
1118
+ }
1119
+
1120
+ .crumb-segment ixfx-menu-trigger {
1121
+ --ixfx-button-padding: var(--space-s, 2px);
1122
+ }
1123
+
1124
+ .caret {
1125
+ position: relative;
1126
+ cursor: pointer;
1127
+ color: var(--crumb-separator);
1128
+ display: inline-flex;
1129
+ align-items: center;
1130
+ justify-content: center;
1131
+ transition: all 0.2s ease-in-out;
1132
+ }
1133
+
1134
+ .caret.open {
1135
+ background: color-mix(in srgb, var(--surface-5) 40%, transparent);
1136
+ }
1137
+
1138
+ .caret.pending {
1139
+ display: inline-flex;
1140
+ }
1141
+
1142
+ .caret > ixfx-menu-trigger {
1143
+ display: inline-flex;
1144
+ margin: 0;
1145
+ --_trigger-hover-bg: transparent;
1146
+ --_trigger-hover-opacity: 0.6;
1147
+ }
1148
+
1149
+ .caret > ixfx-menu-trigger::part(base) {
1150
+ background: none;
1151
+ border: none;
1152
+ color: inherit;
1153
+ cursor: pointer;
1154
+ font-size: inherit;
1155
+ line-height: 1;
1156
+ }
1157
+
1158
+ .caret > ixfx-menu-trigger::part(base):hover {
1159
+ background: none;
1160
+ opacity: 0.6;
1161
+ }
1162
+
1163
+ .separator {
1164
+ margin-left: var(--crumb-item-gap);
1165
+ margin-right: var(--crumb-item-gap);
1166
+ color: var(--crumb-separator);
1167
+ }
1168
+
1169
+ .separator-trigger {
1170
+ display: inline-flex;
1171
+ margin-left: var(--crumb-item-gap);
1172
+ margin-right: var(--crumb-item-gap);
1173
+ --_trigger-hover-bg: transparent;
1174
+ --_trigger-hover-opacity: 0.6;
1175
+ }
1176
+
1177
+ .separator-trigger::part(base) {
1178
+ background: none;
1179
+ border: none;
1180
+ color: var(--crumb-separator);
1181
+ padding: 0;
1182
+ cursor: pointer;
1183
+ font-size: inherit;
1184
+ line-height: 1;
1185
+ }
1186
+
1187
+ .separator-trigger::part(base):hover {
1188
+ background: none;
1189
+ opacity: 0.6;
1190
+ }
1191
+
1192
+ .separator-trigger.tickled::part(base) {
1193
+ }
1194
+
1195
+ ixfx-menu-trigger {
1196
+ display: none;
1197
+ }
1198
+
1199
+ .horiz-select > ixfx-menu-trigger {
1200
+ display: inline-flex;
1201
+ }
1202
+
1203
+ .horiz-select {
1204
+ display: flex;
1205
+ min-width: 5ch;
1206
+ }
1207
+
1208
+ .loading-spinner {
1209
+ display: inline-block;
1210
+ width: 12px;
1211
+ height: 12px;
1212
+ border: 2px solid var(--surface-5);
1213
+ border-top-color: var(--accent);
1214
+ border-radius: 50%;
1215
+ animation: crumb-spin 0.7s linear infinite;
1216
+ }
1217
+
1218
+ @keyframes crumb-spin {
1219
+ to { transform: rotate(360deg); }
1220
+ }
1221
+
1222
+ ::slotted(*), option {
1223
+ user-select: none;
1224
+ scroll-snap-align: start;
1225
+ }
1226
+ `
1227
+ ];
1228
+ }
1229
+ };
1230
+ __decorate([property({ type: Object })], CrumbNavigationElement.prototype, "root", void 0);
1231
+ __decorate([property({ attribute: false })], CrumbNavigationElement.prototype, "model", void 0);
1232
+ __decorate([property({ type: Object })], CrumbNavigationElement.prototype, "selectedNode", void 0);
1233
+ __decorate([state()], CrumbNavigationElement.prototype, "_selectedNodes", void 0);
1234
+ __decorate([property({ attribute: false })], CrumbNavigationElement.prototype, "loadChildren", void 0);
1235
+ __decorate([property({ attribute: false })], CrumbNavigationElement.prototype, "filterPredicate", void 0);
1236
+ __decorate([property({
1237
+ reflect: true,
1238
+ attribute: `exclusivity`
1239
+ })], CrumbNavigationElement.prototype, "exclusivity", void 0);
1240
+ __decorate([property({
1241
+ reflect: true,
1242
+ attribute: `selection-filter`
1243
+ })], CrumbNavigationElement.prototype, "selectionFilter", void 0);
1244
+ __decorate([state()], CrumbNavigationElement.prototype, "_openIdx", void 0);
1245
+ __decorate([state()], CrumbNavigationElement.prototype, "_isFocused", void 0);
1246
+ __decorate([state()], CrumbNavigationElement.prototype, "_menuOpen", void 0);
1247
+ __decorate([state()], CrumbNavigationElement.prototype, "_focusOnNarrowedText", void 0);
1248
+ __decorate([property({
1249
+ reflect: true,
1250
+ attribute: `input-mode`
1251
+ })], CrumbNavigationElement.prototype, "inputMode", void 0);
1252
+ __decorate([property({
1253
+ type: Boolean,
1254
+ reflect: true,
1255
+ attribute: `no-truncate`
1256
+ })], CrumbNavigationElement.prototype, "noTruncate", void 0);
1257
+ __decorate([property({
1258
+ type: Boolean,
1259
+ reflect: true,
1260
+ attribute: `show-suggestions`
1261
+ })], CrumbNavigationElement.prototype, "showSuggestions", void 0);
1262
+ __decorate([property({
1263
+ type: Boolean,
1264
+ reflect: true,
1265
+ attribute: `leading-caret`
1266
+ })], CrumbNavigationElement.prototype, "leadingCaret", void 0);
1267
+ __decorate([property({
1268
+ type: Boolean,
1269
+ reflect: true,
1270
+ attribute: `caret-embedded`
1271
+ })], CrumbNavigationElement.prototype, "caretEmbedded", void 0);
1272
+ __decorate([property({
1273
+ type: Boolean,
1274
+ reflect: true,
1275
+ attribute: `hide-root`
1276
+ })], CrumbNavigationElement.prototype, "hideRoot", void 0);
1277
+ __decorate([property({
1278
+ type: Boolean,
1279
+ reflect: true,
1280
+ attribute: `pad-ending`
1281
+ })], CrumbNavigationElement.prototype, "padEnding", void 0);
1282
+ __decorate([property({
1283
+ type: Boolean,
1284
+ reflect: true,
1285
+ attribute: `with-up`
1286
+ })], CrumbNavigationElement.prototype, "withUp", void 0);
1287
+ __decorate([property({ attribute: false })], CrumbNavigationElement.prototype, "restoreLoads", void 0);
1288
+ CrumbNavigationElement = __decorate([safeCustomElement(`ixfx-crumb-navigation`)], CrumbNavigationElement);
1289
+ //#endregion
1290
+ export { resolveKeyPath as n, CrumbNavigationElement as t };