@ixfx/components 0.5.14 → 0.5.16

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 (61) hide show
  1. package/bundle/index.d.ts +19 -5
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +475 -127
  4. package/bundle/index.js.map +1 -1
  5. package/dist/ac-text.js +2 -2
  6. package/dist/checkbox.js +1 -1
  7. package/dist/{colour-picker-DGso9ZOp.js → colour-picker-DbypcEp6.js} +2 -2
  8. package/dist/{colour-picker-DGso9ZOp.js.map → colour-picker-DbypcEp6.js.map} +1 -1
  9. package/dist/colour-picker.js +1 -1
  10. package/dist/crumbs.d.ts +16 -4
  11. package/dist/crumbs.d.ts.map +1 -1
  12. package/dist/crumbs.js +405 -125
  13. package/dist/crumbs.js.map +1 -1
  14. package/dist/data-display.js +1 -1
  15. package/dist/editable-label.js +1 -1
  16. package/dist/{fallbacks-pNMOMx38.js → fallbacks-DPgPEqo8.js} +51 -1
  17. package/dist/{fallbacks-pNMOMx38.js.map → fallbacks-DPgPEqo8.js.map} +1 -1
  18. package/dist/{icon-B22XVlCt.js → icon-DG9Lm3LT.js} +2 -2
  19. package/dist/{icon-B22XVlCt.js.map → icon-DG9Lm3LT.js.map} +1 -1
  20. package/dist/icons.js +1 -1
  21. package/dist/index.d.ts +3 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +29 -12
  24. package/dist/index.js.map +1 -1
  25. package/dist/labelled-radial-input.js +2 -2
  26. package/dist/{menu-nuOIUD9K.js → menu-B8gE2MSo.js} +4 -4
  27. package/dist/{menu-nuOIUD9K.js.map → menu-B8gE2MSo.js.map} +1 -1
  28. package/dist/{menu-item-BBTZnjAR.js → menu-item-BYRvfyXK.js} +5 -4
  29. package/dist/menu-item-BYRvfyXK.js.map +1 -0
  30. package/dist/menu-item-DSyf9f_L.d.ts.map +1 -1
  31. package/dist/menu.js +2 -2
  32. package/dist/miller.js +1 -1
  33. package/dist/narrowed-text.js +1 -1
  34. package/dist/panel.js +2 -2
  35. package/dist/polar-pad.js +1 -1
  36. package/dist/{radial-input-DxCrt_NE.js → radial-input-CVxGH-Cp.js} +2 -2
  37. package/dist/{radial-input-DxCrt_NE.js.map → radial-input-CVxGH-Cp.js.map} +1 -1
  38. package/dist/radial-input.js +1 -1
  39. package/dist/range-input.js +1 -1
  40. package/dist/range.js +1 -1
  41. package/dist/selecthorizontal.js +1 -1
  42. package/dist/{slider-input-DFpG23bS.js → slider-input-CzmSVgm7.js} +2 -2
  43. package/dist/{slider-input-DFpG23bS.js.map → slider-input-CzmSVgm7.js.map} +1 -1
  44. package/dist/slider-input.js +1 -1
  45. package/dist/split-layout.js +1 -1
  46. package/dist/swipe.js +1 -1
  47. package/dist/{tab-list-DhkVhRQc.js → tab-list-6W5CgXix.js} +2 -2
  48. package/dist/{tab-list-DhkVhRQc.js.map → tab-list-6W5CgXix.js.map} +1 -1
  49. package/dist/{tab-list-BohmbvqF.d.ts → tab-list-ih-0I0NE.d.ts} +2 -2
  50. package/dist/{tab-list-BohmbvqF.d.ts.map → tab-list-ih-0I0NE.d.ts.map} +1 -1
  51. package/dist/tabs.d.ts +1 -1
  52. package/dist/tabs.js +1 -1
  53. package/dist/{tree-CL-txefQ.js → tree-BtiiDX1a.js} +2 -2
  54. package/dist/{tree-CL-txefQ.js.map → tree-BtiiDX1a.js.map} +1 -1
  55. package/dist/tree.js +1 -1
  56. package/dist/xy-pad.js +1 -1
  57. package/docs-user/button.md +8 -0
  58. package/docs-user/crumbs.md +24 -9
  59. package/docs-user/tabs.md +19 -2
  60. package/package.json +1 -1
  61. package/dist/menu-item-BBTZnjAR.js.map +0 -1
package/dist/crumbs.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { n as tickledStyles, r as TickledController, t as tickledItemStyles } from "./tickled-styles-fNDdqf6l.js";
2
- import { t as themeFallbacks } from "./fallbacks-pNMOMx38.js";
2
+ import { t as themeFallbacks } from "./fallbacks-DPgPEqo8.js";
3
3
  import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
4
4
  import "./interaction-D6efrBLo.js";
5
- import "./menu-item-BBTZnjAR.js";
5
+ import "./menu-item-BYRvfyXK.js";
6
6
  import { t as collectDataResult } from "./data-provider-35TyWclX.js";
7
7
  import "./narrowed-text.js";
8
8
  import { LitElement, css, html, nothing } from "lit";
@@ -15,10 +15,14 @@ let CrumbNavigationElement = class CrumbNavigationElement extends LitElement {
15
15
  this.exclusivity = `none`;
16
16
  this.selectionFilter = `leaf`;
17
17
  this._openIdx = -1;
18
- this._isFocused = false;
19
18
  this._menuOpen = false;
20
19
  this._focusOnNarrowedText = false;
21
20
  this.inputMode = ``;
21
+ this.noTruncate = false;
22
+ this.showSuggestions = false;
23
+ this.leadingCaret = false;
24
+ this.caretEmbedded = true;
25
+ this.hideRoot = false;
22
26
  this.#previousSelectionKey = ``;
23
27
  this.#menuOpenedByKeyboard = false;
24
28
  this.#currentOpenTrigger = null;
@@ -28,21 +32,15 @@ let CrumbNavigationElement = class CrumbNavigationElement extends LitElement {
28
32
  if (index !== void 0) this.#onTickled(index);
29
33
  }
30
34
  });
31
- this.#onFocus = () => {
32
- this._isFocused = true;
33
- };
34
- this.#onBlur = () => {
35
- this._isFocused = false;
36
- };
37
35
  this.#onHostClick = () => {
38
- this.shadowRoot?.querySelector(`.host-container`)?.focus();
36
+ this.focus();
39
37
  };
40
38
  this.#onMenuTriggerClose = () => {
41
39
  document.removeEventListener(`keydown`, this.#onMenuKeyNav, { capture: true });
42
40
  this.#currentOpenTrigger = null;
43
41
  this.#menuOpenedByKeyboard = false;
44
42
  requestAnimationFrame(() => {
45
- this.shadowRoot?.querySelector(`.host-container`)?.focus();
43
+ this.focus();
46
44
  });
47
45
  };
48
46
  this.#onMenuKeyNav = (e) => {
@@ -65,7 +63,8 @@ let CrumbNavigationElement = class CrumbNavigationElement extends LitElement {
65
63
  }
66
64
  }
67
65
  };
68
- this.#onKeyDown = (e) => {
66
+ this.#onKeyDown = (event) => {
67
+ const e = event;
69
68
  const r = this.root;
70
69
  const sel = this.selectedNode;
71
70
  if (!r || !sel) return;
@@ -124,6 +123,29 @@ let CrumbNavigationElement = class CrumbNavigationElement extends LitElement {
124
123
  this._focusOnNarrowedText = false;
125
124
  this.#shiftFocusToPath();
126
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;
127
149
  }
128
150
  get selectedNodes() {
129
151
  return this._selectedNodes;
@@ -140,26 +162,41 @@ let CrumbNavigationElement = class CrumbNavigationElement extends LitElement {
140
162
  if (!r || !sel) return [];
141
163
  const pathNodes = this.#findPath(r, sel) ?? [r];
142
164
  const items = [];
143
- pathNodes.forEach((_node, i) => {
144
- items.push({
145
- id: i,
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,
146
174
  enabled: true
147
175
  });
148
- });
149
- if (this.#hasChildOptions()) items.push({
150
- id: pathNodes.length,
151
- enabled: true
152
- });
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
+ }
153
197
  return items;
154
198
  }
155
199
  #onTickled(index) {
156
- const r = this.root;
157
- const sel = this.selectedNode;
158
- if (!r || !sel) return;
159
- const pathNodes = this.#findPath(r, sel) ?? [r];
160
- const hasChildOptions = this.#hasChildOptions();
161
- if (index < pathNodes.length) this._openIdx = index;
162
- else if (hasChildOptions && index === pathNodes.length) this._openIdx = pathNodes.length - 1;
163
200
  if (this.tickled.inputMode === `keyboard`) this.#scrollTickledIntoView(index);
164
201
  }
165
202
  #scrollTickledIntoView(index) {
@@ -182,8 +219,6 @@ let CrumbNavigationElement = class CrumbNavigationElement extends LitElement {
182
219
  const isPending = this.#pendingNode === s;
183
220
  return (s.children ?? []).length > 0 || isPending;
184
221
  }
185
- #onFocus;
186
- #onBlur;
187
222
  #onHostClick;
188
223
  #handleOpenCaretMenu() {
189
224
  const currentTickled = this.tickled.tickledId;
@@ -263,8 +298,7 @@ let CrumbNavigationElement = class CrumbNavigationElement extends LitElement {
263
298
  }
264
299
  #shiftFocusToPath() {
265
300
  this._focusOnNarrowedText = false;
266
- const hostContainer = this.shadowRoot?.querySelector(`.host-container`);
267
- if (hostContainer) hostContainer.focus();
301
+ this.focus();
268
302
  }
269
303
  #onNarrowedTextBlur;
270
304
  #onNarrowedTextShiftTab;
@@ -311,16 +345,42 @@ let CrumbNavigationElement = class CrumbNavigationElement extends LitElement {
311
345
  }
312
346
  connectedCallback() {
313
347
  super.connectedCallback();
348
+ this.tabIndex = 0;
314
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);
315
355
  }
316
356
  disconnectedCallback() {
317
357
  super.disconnectedCallback();
318
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);
319
364
  this.#modelUnsubscribe?.();
320
365
  this.#modelUnsubscribe = void 0;
321
366
  document.removeEventListener(`keydown`, this.#onMenuKeyNav, { capture: true });
322
367
  this.#currentOpenTrigger = null;
323
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
+ }
324
384
  #runLayoutUpdate(_changedProperties) {
325
385
  if (!this.shadowRoot) return;
326
386
  const hsiElement = this.shadowRoot.querySelector(`.horiz-select-items`);
@@ -368,6 +428,13 @@ let CrumbNavigationElement = class CrumbNavigationElement extends LitElement {
368
428
  if (result) return result;
369
429
  }
370
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
+ }
371
438
  #setSelected(node) {
372
439
  const previous = this.selectedNode;
373
440
  if (previous) this.#previousSelectionKey = previous.item?.key ?? ``;
@@ -375,6 +442,15 @@ let CrumbNavigationElement = class CrumbNavigationElement extends LitElement {
375
442
  const previousSet = previous ? /* @__PURE__ */ new Set([previous]) : /* @__PURE__ */ new Set();
376
443
  this.selectedNode = node;
377
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
+ }
378
454
  this.dispatchEvent(new CustomEvent(`select`, {
379
455
  detail: {
380
456
  selected: /* @__PURE__ */ new Set([node]),
@@ -387,6 +463,7 @@ let CrumbNavigationElement = class CrumbNavigationElement extends LitElement {
387
463
  #onLabelClick(node, depth) {
388
464
  this._openIdx = -1;
389
465
  this.#setSelected(node);
466
+ if (this.leadingCaret) this.#scrollSegmentIntoView(depth);
390
467
  this.dispatchEvent(new CustomEvent(`item-click`, {
391
468
  detail: {
392
469
  node,
@@ -442,9 +519,52 @@ let CrumbNavigationElement = class CrumbNavigationElement extends LitElement {
442
519
  }
443
520
  #onSegmentMenuCommand(e, node, depth) {
444
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
+ }
445
535
  const child = node.children?.find((c) => c.item.key === key);
446
536
  if (child) this.#onChildClick(child, depth + 1);
447
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
+ }
448
568
  #onChildClick(node, depth) {
449
569
  this._openIdx = -1;
450
570
  this.#setSelected(node);
@@ -501,87 +621,146 @@ let CrumbNavigationElement = class CrumbNavigationElement extends LitElement {
501
621
  const sel = this.selectedNode;
502
622
  if (!r || !sel) return html``;
503
623
  this.inputMode = this.tickled.inputMode;
504
- const pathNodes = this.#findPath(r, sel) ?? [r];
624
+ let pathNodes = this.#findPath(r, sel) ?? [r];
625
+ if (this.hideRoot && pathNodes.length > 1) pathNodes = pathNodes.slice(1);
505
626
  this.#hasChildOptions();
506
- const parts = pathNodes.map((node, i) => {
627
+ const parts = [];
628
+ let segmentId = 0;
629
+ pathNodes.forEach((node, i) => {
507
630
  const isLast = i === pathNodes.length - 1;
508
631
  const isPending = this.#pendingNode === node;
509
632
  const isOpen = this._openIdx === i;
510
633
  const hasChildren = node.item.isLeaf !== true && (node.children === void 0 || node.children.length > 0);
511
- const showCaret = !isLast && hasChildren;
512
634
  const isTickled = this.tickled.isTickled(i);
513
- const menuItems = (node.children ?? []).map((c) => html`
514
- <ixfx-menu-item
515
- label="${c.item.label}"
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}"
516
641
  command="${c.item.key}"
517
642
  icon-name="${c.item.icon ?? nothing}"
518
643
  ></ixfx-menu-item>
519
644
  `);
520
- return html`
521
- <div class="item crumb-segment ${isTickled ? `tickled` : ``}"
522
- @pointerenter=${() => {
523
- this.tickled.handlePointerEnter(i);
524
- this.dispatchEvent(new CustomEvent(`tickle`, {
525
- detail: {
526
- node,
527
- depth: i
528
- },
529
- bubbles: true,
530
- composed: true
531
- }));
532
- }}
533
- >
534
- <span class="label" @click=${() => this.#onLabelClick(node, i)}>${node.item.label}</span>
535
- ${showCaret ? html`
536
- <span class="caret ${isOpen ? `open` : ``} ${isPending ? `pending` : ``}">
537
- ${isPending ? html`<span class="loading-spinner"></span>` : html`
538
- <ixfx-menu-trigger mode="trigger" label="›" @menu-command=${(e) => this.#onSegmentMenuCommand(e, node, i)}>
539
- <ixfx-menu-container slot="menu">
540
- ${menuItems}
541
- </ixfx-menu-container>
542
- </ixfx-menu-trigger>
543
- `}
544
- </span>
545
- ` : nothing}
546
- </div>
547
- `;
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
+ }
548
738
  });
549
- const childOptions = this.#renderChildOptions();
550
- const isLastItemTickled = this.tickled.isTickled(pathNodes.length);
551
739
  return html`
552
- <div
553
- class="host-container"
554
- tabindex="0"
555
- @focus=${this.#onFocus}
556
- @blur=${this.#onBlur}
557
- @keydown=${this.#onKeyDown}
558
- @click=${this.#onHostClick}
559
- @menu-trigger-close=${this.#onMenuTriggerClose}
560
- @pointerleave=${(e) => this.tickled.handlePointerLeave(e)}
561
- >
562
- <div class="parts">${parts}<div class="trailing"></div></div>
563
- ${childOptions ? html`
564
- <ixfx-menu-trigger
565
- id="child-options-trigger"
566
- class="item separator-trigger ${isLastItemTickled ? `tickled` : ``}"
567
- mode="trigger"
568
- label="›"
569
- @menu-command=${this.#onMenuCommand}
570
- @pointerenter=${() => this.tickled.handlePointerEnter(pathNodes.length)}
571
- >
572
- <ixfx-menu-container slot="menu">
573
- ${(this.selectedNode?.children ?? []).map((c) => html`
574
- <ixfx-menu-item
575
- label="${c.item.label}"
576
- command="${c.item.key}"
577
- icon-name="${c.item.icon ?? nothing}"
578
- ></ixfx-menu-item>
579
- `)}
580
- </ixfx-menu-container>
581
- </ixfx-menu-trigger>
582
- ` : nothing}
583
- ${childOptions}
584
- </div>
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}
585
764
  `;
586
765
  }
587
766
  #renderChildOptions() {
@@ -635,36 +814,66 @@ let CrumbNavigationElement = class CrumbNavigationElement extends LitElement {
635
814
  tickledItemStyles,
636
815
  css`
637
816
  :host {
638
- --text-highlight: var(--accent);
639
- --separator: var(--surface-muted-text);
640
- --padding-s: var(--space-xs);
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;
641
830
  display: flex;
642
831
  overflow: scroll;
643
832
  width: 100%;
644
833
  scroll-snap-type: x mandatory;
645
834
  scrollbar-width: none;
646
-
647
- /* Breadcrumb tickled overrides — no layout shift: keep transparent border at rest */
648
- --item-padding: 0;
649
835
  }
650
836
 
651
- :host(.focused) {
837
+ :host(:focus) {
652
838
  outline: none;
653
839
  }
654
840
 
655
- :host(:focus-within) .host-container {
841
+ :host(:focus) .parts {
656
842
  outline: 2px solid var(--accent);
657
843
  outline-offset: 2px;
658
844
  }
659
845
 
660
- .host-container {
661
- display: flex;
662
- width: 100%;
663
- outline: none;
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;
664
872
  }
665
873
 
666
874
  .parts {
667
875
  display: flex;
876
+ gap: var(--space-s);
668
877
  }
669
878
 
670
879
  .trailing {
@@ -676,21 +885,68 @@ let CrumbNavigationElement = class CrumbNavigationElement extends LitElement {
676
885
  .crumb-segment {
677
886
  display: flex;
678
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;
679
927
  }
680
928
 
681
929
  .label {
682
930
  cursor: pointer;
683
- margin-right: var(--padding-s);
684
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;
685
943
  }
686
944
 
687
945
  .caret {
688
946
  position: relative;
689
- cursor: pointer;
690
- padding: var(--padding-m);
691
- margin-left: var(--padding-s);
692
- margin-right: var(--padding-s);
693
- color: var(--separator);
947
+ cursor: pointer;
948
+
949
+ color: var(--crumb-separator);
694
950
  display: inline-flex;
695
951
  align-items: center;
696
952
  justify-content: center;
@@ -728,15 +984,15 @@ let CrumbNavigationElement = class CrumbNavigationElement extends LitElement {
728
984
  }
729
985
 
730
986
  .separator {
731
- margin-left: var(--padding-s);
732
- margin-right: var(--padding-s);
733
- color: var(--separator);
987
+ margin-left: var(--crumb-item-gap);
988
+ margin-right: var(--crumb-item-gap);
989
+ color: var(--crumb-separator);
734
990
  }
735
991
 
736
992
  .separator-trigger {
737
993
  display: inline-flex;
738
- margin-left: var(--padding-s);
739
- margin-right: var(--padding-s);
994
+ margin-left: var(--crumb-item-gap);
995
+ margin-right: var(--crumb-item-gap);
740
996
  --_trigger-hover-bg: transparent;
741
997
  --_trigger-hover-opacity: 0.6;
742
998
  }
@@ -744,7 +1000,7 @@ let CrumbNavigationElement = class CrumbNavigationElement extends LitElement {
744
1000
  .separator-trigger::part(base) {
745
1001
  background: none;
746
1002
  border: none;
747
- color: var(--separator);
1003
+ color: var(--crumb-separator);
748
1004
  padding: 0;
749
1005
  cursor: pointer;
750
1006
  font-size: inherit;
@@ -809,13 +1065,37 @@ __decorate([property({
809
1065
  attribute: `selection-filter`
810
1066
  })], CrumbNavigationElement.prototype, "selectionFilter", void 0);
811
1067
  __decorate([state()], CrumbNavigationElement.prototype, "_openIdx", void 0);
812
- __decorate([state()], CrumbNavigationElement.prototype, "_isFocused", void 0);
813
1068
  __decorate([state()], CrumbNavigationElement.prototype, "_menuOpen", void 0);
814
1069
  __decorate([state()], CrumbNavigationElement.prototype, "_focusOnNarrowedText", void 0);
815
1070
  __decorate([property({
816
1071
  reflect: true,
817
1072
  attribute: `input-mode`
818
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);
819
1099
  CrumbNavigationElement = __decorate([safeCustomElement(`ixfx-crumb-navigation`)], CrumbNavigationElement);
820
1100
  //#endregion
821
1101
  export { CrumbNavigationElement };