@ixfx/components 0.5.16 → 0.5.18

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 (51) hide show
  1. package/bundle/index.d.ts +52 -3
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +438 -160
  4. package/bundle/index.js.map +1 -1
  5. package/dist/ac-text.d.ts +1 -1
  6. package/dist/colour-picker.d.ts +1 -1
  7. package/dist/crumb-navigation-DTwJ8468.js +1123 -0
  8. package/dist/crumb-navigation-DTwJ8468.js.map +1 -0
  9. package/dist/crumbs.d.ts +4 -3
  10. package/dist/crumbs.d.ts.map +1 -1
  11. package/dist/crumbs.js +1 -1101
  12. package/dist/{icon-DpA9x2Ve.d.ts → icon-BUwP1CZ1.d.ts} +2 -2
  13. package/dist/icon-BUwP1CZ1.d.ts.map +1 -0
  14. package/dist/icons.d.ts +2 -2
  15. package/dist/{index-D_ftz_IT.d.ts → index-BKAlRgbq.d.ts} +2 -2
  16. package/dist/{index-D_ftz_IT.d.ts.map → index-BKAlRgbq.d.ts.map} +1 -1
  17. package/dist/{index-CoqtMSd9.d.ts → index-BUB5SICW.d.ts} +2 -2
  18. package/dist/{index-CoqtMSd9.d.ts.map → index-BUB5SICW.d.ts.map} +1 -1
  19. package/dist/{index-DfEkTGvT.d.ts → index-DkhBGHgF.d.ts} +3 -3
  20. package/dist/{index-DfEkTGvT.d.ts.map → index-DkhBGHgF.d.ts.map} +1 -1
  21. package/dist/{index-BtGFYfaK.d.ts → index-RqMh1i-O.d.ts} +2 -2
  22. package/dist/{index-BtGFYfaK.d.ts.map → index-RqMh1i-O.d.ts.map} +1 -1
  23. package/dist/index.d.ts +55 -9
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +259 -5
  26. package/dist/index.js.map +1 -1
  27. package/dist/{labelled-input-base-DiTme1JO.d.ts → labelled-input-base-Dp_9KP0G.d.ts} +2 -2
  28. package/dist/{labelled-input-base-DiTme1JO.d.ts.map → labelled-input-base-Dp_9KP0G.d.ts.map} +1 -1
  29. package/dist/labelled-radial-input.d.ts +2 -2
  30. package/dist/labelled-radial-input.d.ts.map +1 -1
  31. package/dist/labelled-range-input.d.ts +1 -1
  32. package/dist/{menu-B8gE2MSo.js → menu-CdwSe_XN.js} +2 -2
  33. package/dist/{menu-B8gE2MSo.js.map → menu-CdwSe_XN.js.map} +1 -1
  34. package/dist/{menu-item-DSyf9f_L.d.ts → menu-item-BG5w7fi4.d.ts} +4 -2
  35. package/dist/menu-item-BG5w7fi4.d.ts.map +1 -0
  36. package/dist/{menu-item-BYRvfyXK.js → menu-item-CrmUwIoJ.js} +7 -2
  37. package/dist/menu-item-CrmUwIoJ.js.map +1 -0
  38. package/dist/menu.d.ts +2 -2
  39. package/dist/menu.js +2 -2
  40. package/dist/panel.d.ts +1 -1
  41. package/dist/{tab-list-ih-0I0NE.d.ts → tab-list-CLqlPnX-.d.ts} +2 -2
  42. package/dist/{tab-list-ih-0I0NE.d.ts.map → tab-list-CLqlPnX-.d.ts.map} +1 -1
  43. package/dist/tabs.d.ts +1 -1
  44. package/docs-user/README.md +1 -0
  45. package/docs-user/crumbs.md +15 -1
  46. package/docs-user/select-horiz.md +114 -0
  47. package/package.json +1 -1
  48. package/dist/crumbs.js.map +0 -1
  49. package/dist/icon-DpA9x2Ve.d.ts.map +0 -1
  50. package/dist/menu-item-BYRvfyXK.js.map +0 -1
  51. package/dist/menu-item-DSyf9f_L.d.ts.map +0 -1
package/dist/crumbs.js CHANGED
@@ -1,1103 +1,3 @@
1
- import { n as tickledStyles, r as TickledController, t as tickledItemStyles } from "./tickled-styles-fNDdqf6l.js";
2
- import { t as themeFallbacks } from "./fallbacks-DPgPEqo8.js";
3
- import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
4
- import "./interaction-D6efrBLo.js";
5
- import "./menu-item-BYRvfyXK.js";
6
- import { t as collectDataResult } from "./data-provider-35TyWclX.js";
1
+ import { t as CrumbNavigationElement } from "./crumb-navigation-DTwJ8468.js";
7
2
  import "./narrowed-text.js";
8
- import { LitElement, css, html, nothing } from "lit";
9
- import { property, state } from "lit/decorators.js";
10
- //#region src/crumbs/crumb-navigation.ts
11
- let CrumbNavigationElement = class CrumbNavigationElement extends LitElement {
12
- constructor(..._args) {
13
- super(..._args);
14
- this._selectedNodes = /* @__PURE__ */ new Set();
15
- this.exclusivity = `none`;
16
- this.selectionFilter = `leaf`;
17
- this._openIdx = -1;
18
- this._menuOpen = false;
19
- this._focusOnNarrowedText = false;
20
- this.inputMode = ``;
21
- this.noTruncate = false;
22
- this.showSuggestions = false;
23
- this.leadingCaret = false;
24
- this.caretEmbedded = true;
25
- this.hideRoot = false;
26
- this.#previousSelectionKey = ``;
27
- this.#menuOpenedByKeyboard = false;
28
- this.#currentOpenTrigger = null;
29
- this.tickled = new TickledController(this, {
30
- getItems: () => this.#getTickledItems(),
31
- onTickle: (index) => {
32
- if (index !== void 0) this.#onTickled(index);
33
- }
34
- });
35
- this.#onHostClick = () => {
36
- this.focus();
37
- };
38
- this.#onMenuTriggerClose = () => {
39
- document.removeEventListener(`keydown`, this.#onMenuKeyNav, { capture: true });
40
- this.#currentOpenTrigger = null;
41
- this.#menuOpenedByKeyboard = false;
42
- requestAnimationFrame(() => {
43
- this.focus();
44
- });
45
- };
46
- this.#onMenuKeyNav = (e) => {
47
- if (!this.#currentOpenTrigger?.querySelector(`ixfx-menu-container`) || !this.#currentOpenTrigger?.isOpen) return;
48
- switch (e.key) {
49
- case `ArrowLeft`:
50
- case `ArrowRight`: {
51
- e.preventDefault();
52
- e.stopPropagation();
53
- this.#currentOpenTrigger.closeMenu();
54
- const r = this.root;
55
- const sel = this.selectedNode;
56
- if (!r || !sel) break;
57
- const totalItems = (this.#findPath(r, sel) ?? [r]).length + (this.#hasChildOptions() ? 1 : 0);
58
- const currentTickled = this.tickled.tickledId ?? -1;
59
- if (e.key === `ArrowLeft`) {
60
- if (currentTickled > 0) this.tickled.handleKeyboardNavigation(`previous`);
61
- } else if (currentTickled < totalItems - 1) this.tickled.handleKeyboardNavigation(`next`);
62
- break;
63
- }
64
- }
65
- };
66
- this.#onKeyDown = (event) => {
67
- const e = event;
68
- const r = this.root;
69
- const sel = this.selectedNode;
70
- if (!r || !sel) return;
71
- const totalItems = (this.#findPath(r, sel) ?? [r]).length + (this.#hasChildOptions() ? 1 : 0);
72
- const currentTickled = this.tickled.tickledId ?? -1;
73
- switch (e.key) {
74
- case `ArrowRight`:
75
- e.preventDefault();
76
- if (currentTickled < totalItems - 1) this.tickled.handleKeyboardNavigation(`next`);
77
- else if (currentTickled === totalItems - 1) this.#shiftFocusToNarrowedText();
78
- break;
79
- case `ArrowLeft`:
80
- e.preventDefault();
81
- if (currentTickled > 0) this.tickled.handleKeyboardNavigation(`previous`);
82
- break;
83
- case `ArrowDown`:
84
- case `ArrowUp`:
85
- if (this.tickled.tickledId !== void 0) {
86
- e.preventDefault();
87
- if (this.#currentOpenTrigger?.isOpen) {
88
- const popover = this.#currentOpenTrigger.querySelector(`ixfx-menu-container`)?.shadowRoot?.querySelector(`.popover`);
89
- if (popover) popover.dispatchEvent(new KeyboardEvent(`keydown`, {
90
- key: e.key,
91
- bubbles: true
92
- }));
93
- } else this.#handleOpenCaretMenu();
94
- }
95
- break;
96
- case `Enter`:
97
- e.preventDefault();
98
- this.#handleEnter();
99
- break;
100
- case `Escape`:
101
- e.preventDefault();
102
- this._menuOpen = false;
103
- this.tickled.clearTickled();
104
- this.blur();
105
- break;
106
- case `Tab`: if (e.shiftKey) {
107
- e.preventDefault();
108
- this.#shiftFocusToPath();
109
- } else if (currentTickled === totalItems - 1) {
110
- e.preventDefault();
111
- this._menuOpen = false;
112
- this._focusOnNarrowedText = true;
113
- const narrowedText = this.shadowRoot?.querySelector(`ixfx-narrowed-text`);
114
- if (narrowedText) narrowedText.focus();
115
- }
116
- }
117
- };
118
- this.#onNarrowedTextBlur = () => {
119
- this._focusOnNarrowedText = false;
120
- this.#shiftFocusToPath();
121
- };
122
- this.#onNarrowedTextShiftTab = () => {
123
- this._focusOnNarrowedText = false;
124
- this.#shiftFocusToPath();
125
- };
126
- this.#onMouseOut = (e) => {
127
- this.tickled.handlePointerLeave(e);
128
- };
129
- this.#isWheelScrolling = false;
130
- this.#onWheel = (e) => {
131
- const wheelEvent = e;
132
- if (wheelEvent.deltaX !== 0 || wheelEvent.deltaY !== 0) {
133
- e.preventDefault();
134
- if (!this.#isWheelScrolling) {
135
- this.#isWheelScrolling = true;
136
- this.tickled.clearTickled();
137
- }
138
- this.scrollLeft += wheelEvent.deltaX + wheelEvent.deltaY;
139
- clearTimeout(this.#wheelScrollTimeout);
140
- this.#wheelScrollTimeout = setTimeout(() => {
141
- this.#isWheelScrolling = false;
142
- }, 150);
143
- }
144
- };
145
- this.#onScroll = () => {
146
- this.#updateFade();
147
- };
148
- this.#currentLeadingSiblings = void 0;
149
- }
150
- get selectedNodes() {
151
- return this._selectedNodes;
152
- }
153
- #pendingNode;
154
- #pendingController;
155
- #modelUnsubscribe;
156
- #previousSelectionKey;
157
- #menuOpenedByKeyboard;
158
- #currentOpenTrigger;
159
- #getTickledItems() {
160
- const r = this.root;
161
- const sel = this.selectedNode;
162
- if (!r || !sel) return [];
163
- const pathNodes = this.#findPath(r, sel) ?? [r];
164
- const items = [];
165
- if (this.caretEmbedded) {
166
- pathNodes.forEach((_node, i) => {
167
- items.push({
168
- id: i,
169
- enabled: true
170
- });
171
- });
172
- if (this.#hasChildOptions()) items.push({
173
- id: pathNodes.length,
174
- enabled: true
175
- });
176
- } else {
177
- let segmentId = 0;
178
- pathNodes.forEach((node, i) => {
179
- const isLast = i === pathNodes.length - 1;
180
- const hasChildren = node.item.isLeaf !== true && (node.children === void 0 || node.children.length > 0);
181
- const showLeadingCaret = this.leadingCaret && (i > 0 || this.hideRoot) && hasChildren;
182
- const showTrailingCaret = !this.leadingCaret && hasChildren || this.leadingCaret && isLast && hasChildren;
183
- if (showLeadingCaret) items.push({
184
- id: segmentId++,
185
- enabled: true
186
- });
187
- items.push({
188
- id: segmentId++,
189
- enabled: true
190
- });
191
- if (showTrailingCaret) items.push({
192
- id: segmentId++,
193
- enabled: true
194
- });
195
- });
196
- }
197
- return items;
198
- }
199
- #onTickled(index) {
200
- if (this.tickled.inputMode === `keyboard`) this.#scrollTickledIntoView(index);
201
- }
202
- #scrollTickledIntoView(index) {
203
- const r = this.root;
204
- const sel = this.selectedNode;
205
- if (!r || !sel) return;
206
- const pathNodes = this.#findPath(r, sel) ?? [r];
207
- let el = null;
208
- if (index < pathNodes.length) el = this.shadowRoot?.querySelectorAll(`.crumb-segment`)[index] ?? null;
209
- else if (index === pathNodes.length) el = this.shadowRoot?.querySelector(`#child-options-trigger`) ?? null;
210
- el?.scrollIntoView({
211
- inline: `nearest`,
212
- block: `nearest`,
213
- behavior: `smooth`
214
- });
215
- }
216
- #hasChildOptions() {
217
- const s = this.selectedNode;
218
- if (s === void 0) return false;
219
- const isPending = this.#pendingNode === s;
220
- return (s.children ?? []).length > 0 || isPending;
221
- }
222
- #onHostClick;
223
- #handleOpenCaretMenu() {
224
- const currentTickled = this.tickled.tickledId;
225
- if (currentTickled === void 0) return;
226
- const r = this.root;
227
- const sel = this.selectedNode;
228
- if (!r || !sel) return;
229
- if (currentTickled < (this.#findPath(r, sel) ?? [r]).length - 1) {
230
- const trigger = (this.shadowRoot?.querySelectorAll(`.crumb-segment`))?.[currentTickled]?.querySelector(`.caret ixfx-menu-trigger`);
231
- if (trigger) {
232
- this.#menuOpenedByKeyboard = true;
233
- this.#currentOpenTrigger = trigger;
234
- document.addEventListener(`keydown`, this.#onMenuKeyNav, { capture: true });
235
- trigger.openMenu();
236
- this.#propagateInputModeToMenu();
237
- return;
238
- }
239
- }
240
- if (this.#hasChildOptions()) {
241
- const trigger = this.shadowRoot?.querySelector(`#child-options-trigger`);
242
- if (trigger) {
243
- this.#menuOpenedByKeyboard = true;
244
- this.#currentOpenTrigger = trigger;
245
- document.addEventListener(`keydown`, this.#onMenuKeyNav, { capture: true });
246
- trigger.openMenu();
247
- this.#propagateInputModeToMenu();
248
- }
249
- }
250
- }
251
- #propagateInputModeToMenu() {
252
- const inputMode = this.tickled.inputMode;
253
- requestAnimationFrame(() => {
254
- const container = this.#currentOpenTrigger?.querySelector(`ixfx-menu-container`);
255
- if (container) container.setAttribute(`data-input-mode`, inputMode);
256
- });
257
- }
258
- #onMenuTriggerClose;
259
- #onMenuKeyNav;
260
- #onKeyDown;
261
- #handleEnter() {
262
- const r = this.root;
263
- const sel = this.selectedNode;
264
- if (!r || !sel) return;
265
- const pathNodes = this.#findPath(r, sel) ?? [r];
266
- const hasChildOptions = this.#hasChildOptions();
267
- const currentTickled = this.tickled.tickledId ?? -1;
268
- if (currentTickled < pathNodes.length) {
269
- const node = pathNodes[currentTickled];
270
- this.#setSelected(node);
271
- this._menuOpen = false;
272
- this.dispatchEvent(new CustomEvent(`item-click`, {
273
- detail: {
274
- node,
275
- depth: currentTickled
276
- },
277
- bubbles: true,
278
- composed: true
279
- }));
280
- } else if (hasChildOptions && currentTickled === pathNodes.length) {
281
- this._menuOpen = true;
282
- this.#menuOpenedByKeyboard = true;
283
- const trigger = this.shadowRoot?.querySelector(`#child-options-trigger`);
284
- if (trigger) {
285
- this.#currentOpenTrigger = trigger;
286
- document.addEventListener(`keydown`, this.#onMenuKeyNav, { capture: true });
287
- trigger.openMenu();
288
- this.#propagateInputModeToMenu();
289
- }
290
- }
291
- }
292
- #shiftFocusToNarrowedText() {
293
- const narrowedText = this.shadowRoot?.querySelector(`ixfx-narrowed-text`);
294
- if (narrowedText) {
295
- this._focusOnNarrowedText = true;
296
- narrowedText.focus();
297
- }
298
- }
299
- #shiftFocusToPath() {
300
- this._focusOnNarrowedText = false;
301
- this.focus();
302
- }
303
- #onNarrowedTextBlur;
304
- #onNarrowedTextShiftTab;
305
- select(node) {
306
- this.#setSelected(node);
307
- }
308
- deselect(_node) {}
309
- clearSelection() {
310
- if (this.root) this.#setSelected(this.root);
311
- }
312
- /** Not implemented on crumb-navigation. */
313
- selectAll() {}
314
- /** Not implemented on crumb-navigation. */
315
- deselectAll() {}
316
- getNodes() {
317
- if (!this.root) return [];
318
- const result = [];
319
- const visit = (node, depth) => {
320
- result.push({
321
- node,
322
- depth
323
- });
324
- if (node.children) for (const child of node.children) visit(child, depth + 1);
325
- };
326
- for (const child of this.root.children ?? []) visit(child, 0);
327
- return result;
328
- }
329
- updated(changedProps) {
330
- if (changedProps.has(`model`)) {
331
- this.#modelUnsubscribe?.();
332
- this.#modelUnsubscribe = void 0;
333
- const model = this.model;
334
- if (model) {
335
- this.root = model.root;
336
- this.#modelUnsubscribe = model._subscribe((event) => {
337
- if (event.type === `root`) {
338
- this.root = event.root;
339
- this.requestUpdate();
340
- }
341
- });
342
- }
343
- }
344
- this.#runLayoutUpdate(changedProps);
345
- }
346
- connectedCallback() {
347
- super.connectedCallback();
348
- this.tabIndex = 0;
349
- this.addEventListener(`click`, this.#onHostClick);
350
- this.addEventListener(`wheel`, this.#onWheel, { passive: false });
351
- this.addEventListener(`scroll`, this.#onScroll);
352
- this.addEventListener(`keydown`, this.#onKeyDown);
353
- this.addEventListener(`menu-trigger-close`, this.#onMenuTriggerClose);
354
- this.addEventListener(`mouseout`, this.#onMouseOut);
355
- }
356
- disconnectedCallback() {
357
- super.disconnectedCallback();
358
- this.removeEventListener(`click`, this.#onHostClick);
359
- this.removeEventListener(`wheel`, this.#onWheel);
360
- this.removeEventListener(`scroll`, this.#onScroll);
361
- this.removeEventListener(`keydown`, this.#onKeyDown);
362
- this.removeEventListener(`menu-trigger-close`, this.#onMenuTriggerClose);
363
- this.removeEventListener(`mouseout`, this.#onMouseOut);
364
- this.#modelUnsubscribe?.();
365
- this.#modelUnsubscribe = void 0;
366
- document.removeEventListener(`keydown`, this.#onMenuKeyNav, { capture: true });
367
- this.#currentOpenTrigger = null;
368
- }
369
- firstUpdated() {
370
- this.scrollLeft = this.scrollWidth - this.clientWidth;
371
- this.#updateFade();
372
- }
373
- #onMouseOut;
374
- #isWheelScrolling;
375
- #onWheel;
376
- #wheelScrollTimeout;
377
- #onScroll;
378
- #updateFade() {
379
- const hasLeftFade = this.scrollLeft > 0;
380
- const hasRightFade = this.scrollLeft < this.scrollWidth - this.clientWidth - 1;
381
- this.classList.toggle(`has-left-fade`, hasLeftFade);
382
- this.classList.toggle(`has-right-fade`, hasRightFade);
383
- }
384
- #runLayoutUpdate(_changedProperties) {
385
- if (!this.shadowRoot) return;
386
- const hsiElement = this.shadowRoot.querySelector(`.horiz-select-items`);
387
- if (!hsiElement) return;
388
- const ulElement = hsiElement.querySelector(`ul`);
389
- if (!ulElement) return;
390
- const menuTrigger = this.shadowRoot.querySelector(`ixfx-menu-trigger`);
391
- if (!menuTrigger) return;
392
- if (ulElement.getBoundingClientRect().width > hsiElement.getBoundingClientRect().width) menuTrigger.style.display = `inline-flex`;
393
- else menuTrigger.style.display = `none`;
394
- for (const li of ulElement.querySelectorAll(`li`)) if ((li.getAttribute(`data-key`) ?? ``) === this.#previousSelectionKey) {
395
- this.#scrollToValue(li);
396
- break;
397
- }
398
- }
399
- #scrollToValue(li) {
400
- let l = li.offsetLeft;
401
- const ul = li.parentElement;
402
- if (!ul) return;
403
- l -= ul.offsetLeft;
404
- const horizSelectItems = ul.parentElement;
405
- if (!horizSelectItems) return;
406
- horizSelectItems.scroll({
407
- left: l,
408
- top: 0,
409
- behavior: `smooth`
410
- });
411
- const horizSelect = horizSelectItems.parentElement;
412
- if (!horizSelect) return;
413
- horizSelect.style.minWidth = `${li.getBoundingClientRect().width}px`;
414
- }
415
- #abortPending() {
416
- if (this.#pendingController) {
417
- this.#pendingController.abort();
418
- this.#pendingController = void 0;
419
- this.#pendingNode = void 0;
420
- }
421
- }
422
- #findPath(node, target, acc = []) {
423
- const path = [...acc, node];
424
- if (node === target) return path;
425
- if (!node.children) return void 0;
426
- for (const child of node.children) {
427
- const result = this.#findPath(child, target, path);
428
- if (result) return result;
429
- }
430
- }
431
- #scrollSegmentIntoView(depth) {
432
- if (!this.shadowRoot) return;
433
- this.shadowRoot.querySelectorAll(`.crumb-segment`)[depth]?.scrollIntoView({
434
- block: `nearest`,
435
- inline: `start`
436
- });
437
- }
438
- #setSelected(node) {
439
- const previous = this.selectedNode;
440
- if (previous) this.#previousSelectionKey = previous.item?.key ?? ``;
441
- else this.#previousSelectionKey = ``;
442
- const previousSet = previous ? /* @__PURE__ */ new Set([previous]) : /* @__PURE__ */ new Set();
443
- this.selectedNode = node;
444
- this._selectedNodes = /* @__PURE__ */ new Set([node]);
445
- if (this.root) {
446
- const path = this.#findPath(this.root, node);
447
- if (path) {
448
- const depth = this.hideRoot && path.length > 1 ? path.length - 2 : path.length - 1;
449
- requestAnimationFrame(() => {
450
- this.#scrollSegmentIntoView(depth);
451
- });
452
- }
453
- }
454
- this.dispatchEvent(new CustomEvent(`select`, {
455
- detail: {
456
- selected: /* @__PURE__ */ new Set([node]),
457
- previous: previousSet
458
- },
459
- bubbles: true,
460
- composed: true
461
- }));
462
- }
463
- #onLabelClick(node, depth) {
464
- this._openIdx = -1;
465
- this.#setSelected(node);
466
- if (this.leadingCaret) this.#scrollSegmentIntoView(depth);
467
- this.dispatchEvent(new CustomEvent(`item-click`, {
468
- detail: {
469
- node,
470
- depth
471
- },
472
- bubbles: true,
473
- composed: true
474
- }));
475
- }
476
- #onCaretClick(node, depth, e) {
477
- e.preventDefault();
478
- e.stopPropagation();
479
- if (this._openIdx === depth) {
480
- this._openIdx = -1;
481
- return;
482
- }
483
- this._openIdx = depth;
484
- if (node.children === void 0 && this.#pendingNode !== node) {
485
- this.#abortPending();
486
- this.#pendingNode = node;
487
- const controller = new AbortController();
488
- this.#pendingController = controller;
489
- if (this.loadChildren) this.#invokeLoadChildren(node, depth, controller).catch(() => {});
490
- }
491
- this.requestUpdate();
492
- }
493
- async #invokeLoadChildren(node, depth, controller) {
494
- try {
495
- const children = [...await collectDataResult(this.loadChildren({
496
- node,
497
- depth
498
- }, controller.signal), controller.signal)];
499
- if (controller.signal.aborted) return;
500
- node.children = children;
501
- this.#pendingNode = void 0;
502
- this.#pendingController = void 0;
503
- this.dispatchEvent(new CustomEvent(`expand`, {
504
- detail: {
505
- node,
506
- depth
507
- },
508
- bubbles: true,
509
- composed: true
510
- }));
511
- this.requestUpdate();
512
- } catch {
513
- if (controller.signal.aborted) return;
514
- node.children = [];
515
- this.#pendingNode = void 0;
516
- this.#pendingController = void 0;
517
- this.requestUpdate();
518
- }
519
- }
520
- #onSegmentMenuCommand(e, node, depth) {
521
- const key = e.detail.command;
522
- if (this.leadingCaret) {
523
- const child = node.children?.find((c) => c.item.key === key);
524
- if (child) {
525
- this.#onChildClick(child, depth + 1);
526
- return;
527
- }
528
- if (this.#currentLeadingSiblings) {
529
- const sibling = this.#currentLeadingSiblings.find((c) => c.item.key === key);
530
- if (sibling) this.#onChildClick(sibling, depth);
531
- this.#currentLeadingSiblings = void 0;
532
- return;
533
- }
534
- }
535
- const child = node.children?.find((c) => c.item.key === key);
536
- if (child) this.#onChildClick(child, depth + 1);
537
- }
538
- #currentLeadingSiblings;
539
- #getSiblingMenuItems(pathNodes, index) {
540
- if (index === 0) {
541
- if (!this.hideRoot || !this.root) return [];
542
- const siblings = [...this.root.children ?? []].sort((a, b) => (a.item.label ?? ``).localeCompare(b.item.label ?? ``));
543
- this.#currentLeadingSiblings = siblings;
544
- return siblings.map((sibling) => html`
545
- <ixfx-menu-item
546
- label="${sibling.item.label}"
547
- command="${sibling.item.key}"
548
- icon-name="${sibling.item.icon ?? nothing}"
549
- @click=${() => {
550
- this.#onChildClick(sibling, index);
551
- }}
552
- ></ixfx-menu-item>
553
- `);
554
- }
555
- const siblings = [...pathNodes[index - 1].children ?? []].sort((a, b) => (a.item.label ?? ``).localeCompare(b.item.label ?? ``));
556
- this.#currentLeadingSiblings = siblings;
557
- return siblings.map((sibling) => html`
558
- <ixfx-menu-item
559
- label="${sibling.item.label}"
560
- command="${sibling.item.key}"
561
- icon-name="${sibling.item.icon ?? nothing}"
562
- @click=${() => {
563
- this.#onChildClick(sibling, index);
564
- }}
565
- ></ixfx-menu-item>
566
- `);
567
- }
568
- #onChildClick(node, depth) {
569
- this._openIdx = -1;
570
- this.#setSelected(node);
571
- this.dispatchEvent(new CustomEvent(`item-click`, {
572
- detail: {
573
- node,
574
- depth
575
- },
576
- bubbles: true,
577
- composed: true
578
- }));
579
- }
580
- /** Navigate to a node by key path. Returns false if any key is not found in already-loaded children. */
581
- navigateTo(keys) {
582
- if (!this.root?.children) return false;
583
- let current = this.root;
584
- for (const key of keys) {
585
- const found = current?.children?.find((n) => n.item.key === key);
586
- if (!found) return false;
587
- current = found;
588
- }
589
- if (current && current !== this.root) this.#setSelected(current);
590
- return true;
591
- }
592
- /** Navigate to the first node matching the predicate. Returns false if not found. */
593
- navigateToNode(predicate) {
594
- if (!this.root) return false;
595
- const keyPath = this.#findNodePath(this.root.children ?? [], predicate);
596
- if (!keyPath) return false;
597
- return this.navigateTo(keyPath);
598
- }
599
- #findNodePath(nodes, predicate, prefix = []) {
600
- for (const node of nodes) {
601
- const path = [...prefix, node.item.key];
602
- if (predicate(node)) return path;
603
- if (node.children?.length) {
604
- const result = this.#findNodePath(node.children, predicate, path);
605
- if (result) return result;
606
- }
607
- }
608
- }
609
- #onMenuCommand(e) {
610
- const node = this.selectedNode;
611
- if (!node?.children) return;
612
- const key = e.detail.command;
613
- const child = node.children.find((c) => c.item.key === key);
614
- if (child) {
615
- const depth = this.root ? this.#findPath(this.root, node)?.length ?? 1 : 1;
616
- this.#onChildClick(child, depth);
617
- }
618
- }
619
- render() {
620
- const r = this.root;
621
- const sel = this.selectedNode;
622
- if (!r || !sel) return html``;
623
- this.inputMode = this.tickled.inputMode;
624
- let pathNodes = this.#findPath(r, sel) ?? [r];
625
- if (this.hideRoot && pathNodes.length > 1) pathNodes = pathNodes.slice(1);
626
- this.#hasChildOptions();
627
- const parts = [];
628
- let segmentId = 0;
629
- pathNodes.forEach((node, i) => {
630
- const isLast = i === pathNodes.length - 1;
631
- const isPending = this.#pendingNode === node;
632
- const isOpen = this._openIdx === i;
633
- const hasChildren = node.item.isLeaf !== true && (node.children === void 0 || node.children.length > 0);
634
- const isTickled = this.tickled.isTickled(i);
635
- const showLeadingCaret = this.leadingCaret && (i > 0 || this.hideRoot) && hasChildren;
636
- const showTrailingCaret = !this.leadingCaret && hasChildren || this.leadingCaret && isLast && hasChildren;
637
- const siblingMenuItems = showLeadingCaret ? this.#getSiblingMenuItems(pathNodes, i) : [];
638
- const childMenuItems = (node.children ?? []).map((c) => html`
639
- <ixfx-menu-item
640
- label="${c.item.label}"
641
- command="${c.item.key}"
642
- icon-name="${c.item.icon ?? nothing}"
643
- ></ixfx-menu-item>
644
- `);
645
- if (this.caretEmbedded) {
646
- const canTruncate = !this.noTruncate && !isLast && !isTickled;
647
- parts.push(html`
648
- <div class="item crumb-segment ${isTickled ? `tickled` : ``} ${canTruncate ? `truncatable` : ``} ${this.leadingCaret ? `leading-caret` : ``}" data-segment-id="${i}" data-path-index="${i}"
649
- @click=${(e) => {
650
- if (e.target.closest(`.caret`)) return;
651
- this.#onLabelClick(node, i);
652
- }}
653
- >
654
- ${showLeadingCaret ? html`
655
- <span class="caret ${isOpen ? `open` : ``} ${isPending ? `pending` : ``}">
656
- ${isPending ? html`<span class="loading-spinner"></span>` : html`
657
- <ixfx-menu-trigger mode="trigger" label="›" @menu-command=${(e) => this.#onSegmentMenuCommand(e, node, i)}>
658
- <ixfx-menu-container slot="menu">
659
- ${siblingMenuItems}
660
- </ixfx-menu-container>
661
- </ixfx-menu-trigger>
662
- `}
663
- </span>
664
- ` : nothing}
665
- <span class="label">${node.item.label}</span>
666
- ${showTrailingCaret ? html`
667
- <span class="caret ${isOpen ? `open` : ``} ${isPending ? `pending` : ``}">
668
- ${isPending ? html`<span class="loading-spinner"></span>` : html`
669
- <ixfx-menu-trigger mode="trigger" label="›" placement="right-start" @menu-command=${(e) => this.#onSegmentMenuCommand(e, node, i)}>
670
- <ixfx-menu-container slot="menu">
671
- ${childMenuItems}
672
- </ixfx-menu-container>
673
- </ixfx-menu-trigger>
674
- `}
675
- </span>
676
- ` : nothing}
677
- </div>
678
- `);
679
- } else {
680
- const leadingCaretTickled = showLeadingCaret ? this.tickled.isTickled(segmentId) : false;
681
- if (showLeadingCaret) {
682
- parts.push(html`
683
- <div class="item crumb-segment ${leadingCaretTickled ? `tickled` : ``}" data-segment-id="${segmentId}" data-path-index="${i}"
684
- @click=${(e) => {
685
- const trigger = e.target.closest(`.crumb-segment`)?.querySelector(`ixfx-menu-trigger`);
686
- if (trigger) trigger.openMenu();
687
- }}
688
- >
689
- <span class="caret ${isOpen ? `open` : ``} ${isPending ? `pending` : ``}">
690
- ${isPending ? html`<span class="loading-spinner"></span>` : html`
691
- <ixfx-menu-trigger mode="trigger" label="›" placement="right-start" @menu-command=${(e) => this.#onSegmentMenuCommand(e, node, i)}>
692
- <ixfx-menu-container slot="menu">
693
- ${siblingMenuItems}
694
- </ixfx-menu-container>
695
- </ixfx-menu-trigger>
696
- `}
697
- </span>
698
- </div>
699
- `);
700
- segmentId++;
701
- }
702
- const labelTickled = this.tickled.isTickled(segmentId);
703
- const canTruncate = !this.noTruncate && !isLast && !labelTickled;
704
- parts.push(html`
705
- <div class="item crumb-segment ${labelTickled ? `tickled` : ``} ${canTruncate ? `truncatable` : ``}" data-segment-id="${segmentId}" data-path-index="${i}"
706
- @click=${(e) => {
707
- if (e.target.closest(`.caret`)) return;
708
- this.#onLabelClick(node, i);
709
- }}
710
- >
711
- <span class="label">${node.item.label}</span>
712
- </div>
713
- `);
714
- segmentId++;
715
- if (showTrailingCaret) {
716
- const trailingCaretTickled = this.tickled.isTickled(segmentId);
717
- parts.push(html`
718
- <div class="item crumb-segment ${trailingCaretTickled ? `tickled` : ``}" data-segment-id="${segmentId}" data-path-index="${i}"
719
- @click=${(e) => {
720
- const trigger = e.target.closest(`.crumb-segment`)?.querySelector(`ixfx-menu-trigger`);
721
- if (trigger) trigger.openMenu();
722
- }}
723
- >
724
- <span class="caret ${isOpen ? `open` : ``} ${isPending ? `pending` : ``}">
725
- ${isPending ? html`<span class="loading-spinner"></span>` : html`
726
- <ixfx-menu-trigger mode="trigger" label="›" @menu-command=${(e) => this.#onSegmentMenuCommand(e, node, i)}>
727
- <ixfx-menu-container slot="menu">
728
- ${childMenuItems}
729
- </ixfx-menu-container>
730
- </ixfx-menu-trigger>
731
- `}
732
- </span>
733
- </div>
734
- `);
735
- segmentId++;
736
- }
737
- }
738
- });
739
- return html`
740
- <div class="parts"
741
- @mouseover=${(e) => {
742
- if (this.#isWheelScrolling) return;
743
- const target = e.target;
744
- if (!(target instanceof HTMLElement)) return;
745
- const segment = target.closest(`.crumb-segment`);
746
- if (!segment) return;
747
- const segmentId = parseInt(segment.dataset.segmentId ?? ``, 10);
748
- if (Number.isNaN(segmentId)) return;
749
- const pathIndex = parseInt(segment.dataset.pathIndex ?? ``, 10);
750
- const node = pathNodes[pathIndex];
751
- if (!node) return;
752
- this.tickled.handlePointerEnter(segmentId);
753
- this.dispatchEvent(new CustomEvent(`tickle`, {
754
- detail: {
755
- node,
756
- depth: pathIndex
757
- },
758
- bubbles: true,
759
- composed: true
760
- }));
761
- }}
762
- >${parts}<div class="trailing"></div></div>
763
- ${this.showSuggestions ? this.#renderChildOptions() : nothing}
764
- `;
765
- }
766
- #renderChildOptions() {
767
- const s = this.selectedNode;
768
- if (s === void 0) return nothing;
769
- const isPending = this.#pendingNode === s;
770
- const children = s.children ?? [];
771
- if (children.length === 0 && !isPending) return nothing;
772
- if (isPending) return html`
773
- <div class="horiz-select">
774
- <div class="horiz-select-items">
775
- <span class="loading-spinner"></span>
776
- </div>
777
- </div>
778
- `;
779
- return html`
780
- <ixfx-narrowed-text
781
- class="horiz-select"
782
- selection-mode="single"
783
- orientation="horizontal"
784
- nowrap
785
- width=${this.#getChildOptionsWidth()}
786
- @change=${this.#onNarrowedTextChange}
787
- @narrowed-text-blur=${this.#onNarrowedTextBlur}
788
- @narrowed-text-shift-tab=${this.#onNarrowedTextShiftTab}
789
- >
790
- ${children.map((c) => html`
791
- <span data-value="${c.item.key}" data-label="${c.item.label}"></span>
792
- `)}
793
- </ixfx-narrowed-text>
794
- `;
795
- }
796
- #onNarrowedTextChange(e) {
797
- e.stopPropagation();
798
- const detail = e.detail;
799
- const node = this.selectedNode;
800
- if (!node?.children) return;
801
- const child = node.children.find((c) => c.item.key === detail.item.value);
802
- if (child) {
803
- const depth = this.root ? this.#findPath(this.root, node)?.length ?? 1 : 1;
804
- this.#onChildClick(child, depth);
805
- }
806
- }
807
- #getChildOptionsWidth() {
808
- return `var(--child-options-width, 50ch)`;
809
- }
810
- static {
811
- this.styles = [
812
- themeFallbacks,
813
- tickledStyles,
814
- tickledItemStyles,
815
- css`
816
- :host {
817
- --crumb-item-bg: var(--item-bg, transparent);
818
- --crumb-item-bg-tickled: var(--item-bg-tickled);
819
- --crumb-item-text-tickled: var(--item-text-tickled);
820
- --crumb-item-border: var(--item-border);
821
- --crumb-item-border-tickled: var(--item-border-tickled);
822
- --crumb-item-padding: var(--space-xs);
823
- --crumb-item-border-radius: var(--item-border-radius);
824
- --crumb-item-gap: var(--space-xs);
825
- --crumb-separator: var(--surface-muted-text);
826
- --crumb-truncate-min-width: 10ch;
827
- --crumb-fade-width: 30px;
828
- --crumb-fade-color: var(--surface-3);
829
- position: relative;
830
- display: flex;
831
- overflow: scroll;
832
- width: 100%;
833
- scroll-snap-type: x mandatory;
834
- scrollbar-width: none;
835
- }
836
-
837
- :host(:focus) {
838
- outline: none;
839
- }
840
-
841
- :host(:focus) .parts {
842
- outline: 2px solid var(--accent);
843
- outline-offset: 2px;
844
- }
845
-
846
- :host::before,
847
- :host::after {
848
- content: '';
849
- position: absolute;
850
- top: 0;
851
- bottom: 0;
852
- width: var(--crumb-fade-width);
853
- pointer-events: none;
854
- opacity: 0;
855
- transition: opacity 0.2s ease;
856
- z-index: 1;
857
- }
858
-
859
- :host::before {
860
- left: 0;
861
- background: linear-gradient(to right, var(--crumb-fade-color), transparent);
862
- }
863
-
864
- :host::after {
865
- right: 0;
866
- background: linear-gradient(to left, var(--crumb-fade-color), transparent);
867
- }
868
-
869
- :host(.has-left-fade)::before,
870
- :host(.has-right-fade)::after {
871
- opacity: 1;
872
- }
873
-
874
- .parts {
875
- display: flex;
876
- gap: var(--space-s);
877
- }
878
-
879
- .trailing {
880
- flex: 1;
881
- min-width: 1em;
882
- cursor: pointer;
883
- }
884
-
885
- .crumb-segment {
886
- display: flex;
887
- align-items: center;
888
- flex-shrink: 0;
889
- background: var(--crumb-item-bg);
890
- border: var(--crumb-item-border);
891
- border-radius: var(--crumb-item-border-radius);
892
- }
893
-
894
- .crumb-segment.leading-caret {
895
- flex-direction: row;
896
- }
897
-
898
- .crumb-segment.leading-caret-only {
899
- flex-direction: row;
900
- padding: var(--crumb-item-padding);
901
- }
902
-
903
- .crumb-segment.trailing-caret-only {
904
- flex-direction: row;
905
- padding: var(--crumb-item-padding);
906
- }
907
-
908
- .crumb-segment.tickled {
909
- background: var(--crumb-item-bg-tickled);
910
- border-color: var(--crumb-item-border-tickled);
911
- color: var(--crumb-item-text-tickled);
912
- flex-shrink: 0;
913
- min-width: auto;
914
- }
915
-
916
- .crumb-segment.truncatable {
917
- flex-shrink: 1;
918
- min-width: min(var(--crumb-truncate-min-width), auto);
919
- overflow: hidden;
920
- }
921
-
922
- .crumb-segment.truncatable .label {
923
- overflow: hidden;
924
- text-overflow: ellipsis;
925
- white-space: nowrap;
926
- min-width: 0;
927
- }
928
-
929
- .label {
930
- cursor: pointer;
931
- white-space: nowrap;
932
- color: inherit;
933
- }
934
-
935
- .crumb-segment.tickled .label {
936
- color: var(--crumb-item-text-tickled);
937
- overflow: visible;
938
- text-overflow: clip;
939
- }
940
-
941
- .crumb-segment ixfx-menu-trigger {
942
- --ixfx-button-padding: var(--space-xs) 8px;
943
- }
944
-
945
- .caret {
946
- position: relative;
947
- cursor: pointer;
948
-
949
- color: var(--crumb-separator);
950
- display: inline-flex;
951
- align-items: center;
952
- justify-content: center;
953
- transition: all 0.2s ease-in-out;
954
- }
955
-
956
- .caret.open {
957
- background: color-mix(in srgb, var(--surface-5) 40%, transparent);
958
- }
959
-
960
- .caret.pending {
961
- display: inline-flex;
962
- }
963
-
964
- .caret > ixfx-menu-trigger {
965
- display: inline-flex;
966
- margin: 0;
967
- --_trigger-hover-bg: transparent;
968
- --_trigger-hover-opacity: 0.6;
969
- }
970
-
971
- .caret > ixfx-menu-trigger::part(base) {
972
- background: none;
973
- border: none;
974
- color: inherit;
975
- padding: 0;
976
- cursor: pointer;
977
- font-size: inherit;
978
- line-height: 1;
979
- }
980
-
981
- .caret > ixfx-menu-trigger::part(base):hover {
982
- background: none;
983
- opacity: 0.6;
984
- }
985
-
986
- .separator {
987
- margin-left: var(--crumb-item-gap);
988
- margin-right: var(--crumb-item-gap);
989
- color: var(--crumb-separator);
990
- }
991
-
992
- .separator-trigger {
993
- display: inline-flex;
994
- margin-left: var(--crumb-item-gap);
995
- margin-right: var(--crumb-item-gap);
996
- --_trigger-hover-bg: transparent;
997
- --_trigger-hover-opacity: 0.6;
998
- }
999
-
1000
- .separator-trigger::part(base) {
1001
- background: none;
1002
- border: none;
1003
- color: var(--crumb-separator);
1004
- padding: 0;
1005
- cursor: pointer;
1006
- font-size: inherit;
1007
- line-height: 1;
1008
- }
1009
-
1010
- .separator-trigger::part(base):hover {
1011
- background: none;
1012
- opacity: 0.6;
1013
- }
1014
-
1015
- .separator-trigger.tickled::part(base) {
1016
- }
1017
-
1018
- ixfx-menu-trigger {
1019
- display: none;
1020
- }
1021
-
1022
- .horiz-select > ixfx-menu-trigger {
1023
- display: inline-flex;
1024
- }
1025
-
1026
- .horiz-select {
1027
- display: flex;
1028
- min-width: 5ch;
1029
- }
1030
-
1031
- .loading-spinner {
1032
- display: inline-block;
1033
- width: 12px;
1034
- height: 12px;
1035
- border: 2px solid var(--surface-5);
1036
- border-top-color: var(--accent);
1037
- border-radius: 50%;
1038
- animation: crumb-spin 0.7s linear infinite;
1039
- }
1040
-
1041
- @keyframes crumb-spin {
1042
- to { transform: rotate(360deg); }
1043
- }
1044
-
1045
- ::slotted(*), option {
1046
- user-select: none;
1047
- scroll-snap-align: start;
1048
- }
1049
- `
1050
- ];
1051
- }
1052
- };
1053
- __decorate([property({ type: Object })], CrumbNavigationElement.prototype, "root", void 0);
1054
- __decorate([property({ attribute: false })], CrumbNavigationElement.prototype, "model", void 0);
1055
- __decorate([property({ type: Object })], CrumbNavigationElement.prototype, "selectedNode", void 0);
1056
- __decorate([state()], CrumbNavigationElement.prototype, "_selectedNodes", void 0);
1057
- __decorate([property({ attribute: false })], CrumbNavigationElement.prototype, "loadChildren", void 0);
1058
- __decorate([property({ attribute: false })], CrumbNavigationElement.prototype, "filterPredicate", void 0);
1059
- __decorate([property({
1060
- reflect: true,
1061
- attribute: `exclusivity`
1062
- })], CrumbNavigationElement.prototype, "exclusivity", void 0);
1063
- __decorate([property({
1064
- reflect: true,
1065
- attribute: `selection-filter`
1066
- })], CrumbNavigationElement.prototype, "selectionFilter", void 0);
1067
- __decorate([state()], CrumbNavigationElement.prototype, "_openIdx", void 0);
1068
- __decorate([state()], CrumbNavigationElement.prototype, "_menuOpen", void 0);
1069
- __decorate([state()], CrumbNavigationElement.prototype, "_focusOnNarrowedText", void 0);
1070
- __decorate([property({
1071
- reflect: true,
1072
- attribute: `input-mode`
1073
- })], CrumbNavigationElement.prototype, "inputMode", void 0);
1074
- __decorate([property({
1075
- type: Boolean,
1076
- reflect: true,
1077
- attribute: `no-truncate`
1078
- })], CrumbNavigationElement.prototype, "noTruncate", void 0);
1079
- __decorate([property({
1080
- type: Boolean,
1081
- reflect: true,
1082
- attribute: `show-suggestions`
1083
- })], CrumbNavigationElement.prototype, "showSuggestions", void 0);
1084
- __decorate([property({
1085
- type: Boolean,
1086
- reflect: true,
1087
- attribute: `leading-caret`
1088
- })], CrumbNavigationElement.prototype, "leadingCaret", void 0);
1089
- __decorate([property({
1090
- type: Boolean,
1091
- reflect: true,
1092
- attribute: `caret-embedded`
1093
- })], CrumbNavigationElement.prototype, "caretEmbedded", void 0);
1094
- __decorate([property({
1095
- type: Boolean,
1096
- reflect: true,
1097
- attribute: `hideroot`
1098
- })], CrumbNavigationElement.prototype, "hideRoot", void 0);
1099
- CrumbNavigationElement = __decorate([safeCustomElement(`ixfx-crumb-navigation`)], CrumbNavigationElement);
1100
- //#endregion
1101
3
  export { CrumbNavigationElement };
1102
-
1103
- //# sourceMappingURL=crumbs.js.map