@ixfx/components 0.5.15 → 0.5.17

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 (62) hide show
  1. package/bundle/index.d.ts +17 -4
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +482 -103
  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/crumb-navigation-DFqTzElw.js +1149 -0
  11. package/dist/crumb-navigation-DFqTzElw.js.map +1 -0
  12. package/dist/crumbs.d.ts +16 -3
  13. package/dist/crumbs.d.ts.map +1 -1
  14. package/dist/crumbs.js +1 -821
  15. package/dist/data-display.js +1 -1
  16. package/dist/editable-label.js +1 -1
  17. package/dist/{fallbacks-pNMOMx38.js → fallbacks-DPgPEqo8.js} +51 -1
  18. package/dist/{fallbacks-pNMOMx38.js.map → fallbacks-DPgPEqo8.js.map} +1 -1
  19. package/dist/{icon-B22XVlCt.js → icon-DG9Lm3LT.js} +2 -2
  20. package/dist/{icon-B22XVlCt.js.map → icon-DG9Lm3LT.js.map} +1 -1
  21. package/dist/icons.js +1 -1
  22. package/dist/index.d.ts +1 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +16 -14
  25. package/dist/index.js.map +1 -1
  26. package/dist/labelled-radial-input.js +2 -2
  27. package/dist/{menu-nuOIUD9K.js → menu-B8gE2MSo.js} +4 -4
  28. package/dist/{menu-nuOIUD9K.js.map → menu-B8gE2MSo.js.map} +1 -1
  29. package/dist/{menu-item-BBTZnjAR.js → menu-item-BYRvfyXK.js} +5 -4
  30. package/dist/menu-item-BYRvfyXK.js.map +1 -0
  31. package/dist/menu-item-DSyf9f_L.d.ts.map +1 -1
  32. package/dist/menu.js +2 -2
  33. package/dist/miller.js +1 -1
  34. package/dist/narrowed-text.js +1 -1
  35. package/dist/panel.js +2 -2
  36. package/dist/polar-pad.js +1 -1
  37. package/dist/{radial-input-DxCrt_NE.js → radial-input-CVxGH-Cp.js} +2 -2
  38. package/dist/{radial-input-DxCrt_NE.js.map → radial-input-CVxGH-Cp.js.map} +1 -1
  39. package/dist/radial-input.js +1 -1
  40. package/dist/range-input.js +1 -1
  41. package/dist/range.js +1 -1
  42. package/dist/selecthorizontal.js +1 -1
  43. package/dist/{slider-input-DFpG23bS.js → slider-input-CzmSVgm7.js} +2 -2
  44. package/dist/{slider-input-DFpG23bS.js.map → slider-input-CzmSVgm7.js.map} +1 -1
  45. package/dist/slider-input.js +1 -1
  46. package/dist/split-layout.js +1 -1
  47. package/dist/swipe.js +1 -1
  48. package/dist/{tab-list-DhkVhRQc.js → tab-list-6W5CgXix.js} +2 -2
  49. package/dist/{tab-list-DhkVhRQc.js.map → tab-list-6W5CgXix.js.map} +1 -1
  50. package/dist/{tab-list-BohmbvqF.d.ts → tab-list-ih-0I0NE.d.ts} +2 -2
  51. package/dist/{tab-list-BohmbvqF.d.ts.map → tab-list-ih-0I0NE.d.ts.map} +1 -1
  52. package/dist/tabs.d.ts +1 -1
  53. package/dist/tabs.js +1 -1
  54. package/dist/{tree-CL-txefQ.js → tree-BtiiDX1a.js} +2 -2
  55. package/dist/{tree-CL-txefQ.js.map → tree-BtiiDX1a.js.map} +1 -1
  56. package/dist/tree.js +1 -1
  57. package/dist/xy-pad.js +1 -1
  58. package/docs-user/crumbs.md +38 -9
  59. package/docs-user/tabs.md +19 -2
  60. package/package.json +1 -1
  61. package/dist/crumbs.js.map +0 -1
  62. package/dist/menu-item-BBTZnjAR.js.map +0 -1
package/bundle/index.js CHANGED
@@ -1119,6 +1119,56 @@ const themeFallbacks = i$3`
1119
1119
  inherits: true;
1120
1120
  initial-value: 0.1;
1121
1121
  }
1122
+ @property --crumb-item-bg {
1123
+ syntax: '<color>';
1124
+ inherits: true;
1125
+ initial-value: transparent;
1126
+ }
1127
+ @property --crumb-item-bg-tickled {
1128
+ syntax: '<color>';
1129
+ inherits: true;
1130
+ initial-value: var(--surface-5);
1131
+ }
1132
+ @property --crumb-item-text-tickled {
1133
+ syntax: '<color>';
1134
+ inherits: true;
1135
+ initial-value: inherit;
1136
+ }
1137
+ @property --crumb-item-border {
1138
+ syntax: '*';
1139
+ inherits: true;
1140
+ initial-value: 1px solid transparent;
1141
+ }
1142
+ @property --crumb-item-border-tickled {
1143
+ syntax: '<color>';
1144
+ inherits: true;
1145
+ initial-value: var(--accent);
1146
+ }
1147
+ @property --crumb-item-padding {
1148
+ syntax: '*';
1149
+ inherits: true;
1150
+ initial-value: var(--space-xs);
1151
+ }
1152
+ @property --crumb-item-border-radius {
1153
+ syntax: '<length>';
1154
+ inherits: true;
1155
+ initial-value: var(--radius-m);
1156
+ }
1157
+ @property --crumb-item-gap {
1158
+ syntax: '<length>';
1159
+ inherits: true;
1160
+ initial-value: var(--space-xs);
1161
+ }
1162
+ @property --crumb-separator {
1163
+ syntax: '<color>';
1164
+ inherits: true;
1165
+ initial-value: var(--surface-muted-text);
1166
+ }
1167
+ @property --crumb-truncate-min-width {
1168
+ syntax: '<length>';
1169
+ inherits: true;
1170
+ initial-value: 5ch;
1171
+ }
1122
1172
  `;
1123
1173
  //#endregion
1124
1174
  //#region src/styles/popup.ts
@@ -3985,6 +4035,7 @@ let MenuTrigger = class MenuTrigger extends i {
3985
4035
  :host {
3986
4036
  display: inline-block;
3987
4037
  position: relative;
4038
+ --ixfx-button-padding: var(--ixfx-button-padding, var(--space-s) var(--space-m));
3988
4039
  }
3989
4040
 
3990
4041
  .trigger-area {
@@ -3998,7 +4049,7 @@ let MenuTrigger = class MenuTrigger extends i {
3998
4049
  border: none;
3999
4050
  color: var(--surface-3-text);
4000
4051
  cursor: pointer;
4001
- padding: var(--space-s) var(--space-m);
4052
+ padding: var(--ixfx-button-padding);
4002
4053
  font-family: var(--font-family, inherit);
4003
4054
  font-size: var(--text-m);
4004
4055
  font-weight: 600;
@@ -6358,6 +6409,72 @@ var TreeDataModel = class {
6358
6409
  }
6359
6410
  };
6360
6411
  //#endregion
6412
+ //#region src/styles/scroll-fade.ts
6413
+ /**
6414
+ * Zero-JS scroll edge fade.
6415
+ *
6416
+ * A CSS alpha mask driven by a scroll-linked animation (`scroll-timeline`),
6417
+ * rather than a JS scroll/resize listener - so it can never go stale after
6418
+ * a resize (widening a scroller so content fully fits clears the fade
6419
+ * immediately, with no JS involved), and it fades the whole element
6420
+ * including its children, not just a background layer that an opaque
6421
+ * item background would hide.
6422
+ *
6423
+ * Apply the returned `.scroll-fade-x` / `.scroll-fade-y` class to
6424
+ * whichever element actually has `overflow` scrolling on that axis.
6425
+ *
6426
+ * CSS variables:
6427
+ * - `--scroll-fade-width` (default `30px`): how far the fade extends from each edge.
6428
+ *
6429
+ * ```ts
6430
+ * import { scrollFadeStyles } from '../styles/scroll-fade.js';
6431
+ *
6432
+ * static styles = [scrollFadeStyles(`x`), css`...`];
6433
+ * ```
6434
+ * ```html
6435
+ * <div class="scroll-fade-x" style="overflow-x: scroll">...</div>
6436
+ * ```
6437
+ */
6438
+ function scrollFadeStyles(axis) {
6439
+ const className = `scroll-fade-${axis}`;
6440
+ const start = `--${className}-start`;
6441
+ const end = `--${className}-end`;
6442
+ const timeline = `--${className}-timeline`;
6443
+ const keyframesName = `${className}-fade`;
6444
+ const gradientDirection = axis === `x` ? `to right` : `to bottom`;
6445
+ return i$3`
6446
+ @property ${r$2(start)} {
6447
+ syntax: '<length>';
6448
+ inherits: false;
6449
+ initial-value: 0px;
6450
+ }
6451
+
6452
+ @property ${r$2(end)} {
6453
+ syntax: '<length>';
6454
+ inherits: false;
6455
+ initial-value: 0px;
6456
+ }
6457
+
6458
+ @keyframes ${r$2(keyframesName)} {
6459
+ 0% { ${r$2(start)}: 0px; }
6460
+ 10%, 100% { ${r$2(start)}: var(--scroll-fade-width, 30px); }
6461
+ 0%, 90% { ${r$2(end)}: var(--scroll-fade-width, 30px); }
6462
+ 100% { ${r$2(end)}: 0px; }
6463
+ }
6464
+
6465
+ .${r$2(className)} {
6466
+ scroll-timeline: ${r$2(timeline)} ${r$2(axis)};
6467
+ animation: ${r$2(keyframesName)};
6468
+ animation-timeline: ${r$2(timeline)};
6469
+ mask-image: linear-gradient(${r$2(gradientDirection)},
6470
+ transparent,
6471
+ black var(${r$2(start)}, 0px) calc(100% - var(${r$2(end)}, 0px)),
6472
+ transparent
6473
+ );
6474
+ }
6475
+ `;
6476
+ }
6477
+ //#endregion
6361
6478
  //#region src/narrowed-text/narrowed-text.ts
6362
6479
  const HIGHLIGHT_KEY = `narrowed-text-highlight`;
6363
6480
  let NarrowedTextElement = class NarrowedTextElement extends i {
@@ -6978,6 +7095,11 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
6978
7095
  this._menuOpen = false;
6979
7096
  this._focusOnNarrowedText = false;
6980
7097
  this.inputMode = ``;
7098
+ this.noTruncate = false;
7099
+ this.showSuggestions = false;
7100
+ this.leadingCaret = false;
7101
+ this.caretEmbedded = true;
7102
+ this.hideRoot = false;
6981
7103
  this.#previousSelectionKey = ``;
6982
7104
  this.#menuOpenedByKeyboard = false;
6983
7105
  this.#currentOpenTrigger = null;
@@ -6994,14 +7116,14 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
6994
7116
  this._isFocused = false;
6995
7117
  };
6996
7118
  this.#onHostClick = () => {
6997
- this.shadowRoot?.querySelector(`.host-container`)?.focus();
7119
+ this.#getHostContainer()?.focus();
6998
7120
  };
6999
7121
  this.#onMenuTriggerClose = () => {
7000
7122
  document.removeEventListener(`keydown`, this.#onMenuKeyNav, { capture: true });
7001
7123
  this.#currentOpenTrigger = null;
7002
7124
  this.#menuOpenedByKeyboard = false;
7003
7125
  requestAnimationFrame(() => {
7004
- this.shadowRoot?.querySelector(`.host-container`)?.focus();
7126
+ this.#getHostContainer()?.focus();
7005
7127
  });
7006
7128
  };
7007
7129
  this.#onMenuKeyNav = (e) => {
@@ -7083,6 +7205,24 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7083
7205
  this._focusOnNarrowedText = false;
7084
7206
  this.#shiftFocusToPath();
7085
7207
  };
7208
+ this.#isWheelScrolling = false;
7209
+ this.#onWheel = (e) => {
7210
+ const wheelEvent = e;
7211
+ if (wheelEvent.deltaX !== 0 || wheelEvent.deltaY !== 0) {
7212
+ e.preventDefault();
7213
+ if (!this.#isWheelScrolling) {
7214
+ this.#isWheelScrolling = true;
7215
+ this.tickled.clearTickled();
7216
+ }
7217
+ const hostContainer = this.#getHostContainer();
7218
+ if (hostContainer) hostContainer.scrollLeft += wheelEvent.deltaX + wheelEvent.deltaY;
7219
+ clearTimeout(this.#wheelScrollTimeout);
7220
+ this.#wheelScrollTimeout = setTimeout(() => {
7221
+ this.#isWheelScrolling = false;
7222
+ }, 150);
7223
+ }
7224
+ };
7225
+ this.#currentLeadingSiblings = void 0;
7086
7226
  }
7087
7227
  get selectedNodes() {
7088
7228
  return this._selectedNodes;
@@ -7099,26 +7239,41 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7099
7239
  if (!r || !sel) return [];
7100
7240
  const pathNodes = this.#findPath(r, sel) ?? [r];
7101
7241
  const items = [];
7102
- pathNodes.forEach((_node, i) => {
7103
- items.push({
7104
- id: i,
7242
+ if (this.caretEmbedded) {
7243
+ pathNodes.forEach((_node, i) => {
7244
+ items.push({
7245
+ id: i,
7246
+ enabled: true
7247
+ });
7248
+ });
7249
+ if (this.#hasChildOptions()) items.push({
7250
+ id: pathNodes.length,
7105
7251
  enabled: true
7106
7252
  });
7107
- });
7108
- if (this.#hasChildOptions()) items.push({
7109
- id: pathNodes.length,
7110
- enabled: true
7111
- });
7253
+ } else {
7254
+ let segmentId = 0;
7255
+ pathNodes.forEach((node, i) => {
7256
+ const isLast = i === pathNodes.length - 1;
7257
+ const hasChildren = node.item.isLeaf !== true && (node.children === void 0 || node.children.length > 0);
7258
+ const showLeadingCaret = this.leadingCaret && (i > 0 || this.hideRoot) && hasChildren;
7259
+ const showTrailingCaret = !this.leadingCaret && hasChildren || this.leadingCaret && isLast && hasChildren;
7260
+ if (showLeadingCaret) items.push({
7261
+ id: segmentId++,
7262
+ enabled: true
7263
+ });
7264
+ items.push({
7265
+ id: segmentId++,
7266
+ enabled: true
7267
+ });
7268
+ if (showTrailingCaret) items.push({
7269
+ id: segmentId++,
7270
+ enabled: true
7271
+ });
7272
+ });
7273
+ }
7112
7274
  return items;
7113
7275
  }
7114
7276
  #onTickled(index) {
7115
- const r = this.root;
7116
- const sel = this.selectedNode;
7117
- if (!r || !sel) return;
7118
- const pathNodes = this.#findPath(r, sel) ?? [r];
7119
- const hasChildOptions = this.#hasChildOptions();
7120
- if (index < pathNodes.length) this._openIdx = index;
7121
- else if (hasChildOptions && index === pathNodes.length) this._openIdx = pathNodes.length - 1;
7122
7277
  if (this.tickled.inputMode === `keyboard`) this.#scrollTickledIntoView(index);
7123
7278
  }
7124
7279
  #scrollTickledIntoView(index) {
@@ -7143,6 +7298,9 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7143
7298
  }
7144
7299
  #onFocus;
7145
7300
  #onBlur;
7301
+ #getHostContainer() {
7302
+ return this.shadowRoot?.querySelector(`.host-container`) ?? null;
7303
+ }
7146
7304
  #onHostClick;
7147
7305
  #handleOpenCaretMenu() {
7148
7306
  const currentTickled = this.tickled.tickledId;
@@ -7222,7 +7380,7 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7222
7380
  }
7223
7381
  #shiftFocusToPath() {
7224
7382
  this._focusOnNarrowedText = false;
7225
- const hostContainer = this.shadowRoot?.querySelector(`.host-container`);
7383
+ const hostContainer = this.#getHostContainer();
7226
7384
  if (hostContainer) hostContainer.focus();
7227
7385
  }
7228
7386
  #onNarrowedTextBlur;
@@ -7271,15 +7429,24 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7271
7429
  connectedCallback() {
7272
7430
  super.connectedCallback();
7273
7431
  this.addEventListener(`click`, this.#onHostClick);
7432
+ this.addEventListener(`wheel`, this.#onWheel, { passive: false });
7274
7433
  }
7275
7434
  disconnectedCallback() {
7276
7435
  super.disconnectedCallback();
7277
7436
  this.removeEventListener(`click`, this.#onHostClick);
7437
+ this.removeEventListener(`wheel`, this.#onWheel);
7278
7438
  this.#modelUnsubscribe?.();
7279
7439
  this.#modelUnsubscribe = void 0;
7280
7440
  document.removeEventListener(`keydown`, this.#onMenuKeyNav, { capture: true });
7281
7441
  this.#currentOpenTrigger = null;
7282
7442
  }
7443
+ firstUpdated() {
7444
+ const hostContainer = this.#getHostContainer();
7445
+ if (hostContainer) hostContainer.scrollLeft = hostContainer.scrollWidth - hostContainer.clientWidth;
7446
+ }
7447
+ #isWheelScrolling;
7448
+ #onWheel;
7449
+ #wheelScrollTimeout;
7283
7450
  #runLayoutUpdate(_changedProperties) {
7284
7451
  if (!this.shadowRoot) return;
7285
7452
  const hsiElement = this.shadowRoot.querySelector(`.horiz-select-items`);
@@ -7327,6 +7494,13 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7327
7494
  if (result) return result;
7328
7495
  }
7329
7496
  }
7497
+ #scrollSegmentIntoView(depth) {
7498
+ if (!this.shadowRoot) return;
7499
+ this.shadowRoot.querySelectorAll(`.crumb-segment`)[depth]?.scrollIntoView({
7500
+ block: `nearest`,
7501
+ inline: `start`
7502
+ });
7503
+ }
7330
7504
  #setSelected(node) {
7331
7505
  const previous = this.selectedNode;
7332
7506
  if (previous) this.#previousSelectionKey = previous.item?.key ?? ``;
@@ -7334,6 +7508,15 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7334
7508
  const previousSet = previous ? /* @__PURE__ */ new Set([previous]) : /* @__PURE__ */ new Set();
7335
7509
  this.selectedNode = node;
7336
7510
  this._selectedNodes = /* @__PURE__ */ new Set([node]);
7511
+ if (this.leadingCaret && this.root) {
7512
+ const path = this.#findPath(this.root, node);
7513
+ if (path) {
7514
+ const depth = this.hideRoot && path.length > 1 ? path.length - 2 : path.length - 1;
7515
+ requestAnimationFrame(() => {
7516
+ this.#scrollSegmentIntoView(depth);
7517
+ });
7518
+ }
7519
+ }
7337
7520
  this.dispatchEvent(new CustomEvent(`select`, {
7338
7521
  detail: {
7339
7522
  selected: /* @__PURE__ */ new Set([node]),
@@ -7346,6 +7529,7 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7346
7529
  #onLabelClick(node, depth) {
7347
7530
  this._openIdx = -1;
7348
7531
  this.#setSelected(node);
7532
+ if (this.leadingCaret) this.#scrollSegmentIntoView(depth);
7349
7533
  this.dispatchEvent(new CustomEvent(`item-click`, {
7350
7534
  detail: {
7351
7535
  node,
@@ -7401,9 +7585,52 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7401
7585
  }
7402
7586
  #onSegmentMenuCommand(e, node, depth) {
7403
7587
  const key = e.detail.command;
7588
+ if (this.leadingCaret) {
7589
+ const child = node.children?.find((c) => c.item.key === key);
7590
+ if (child) {
7591
+ this.#onChildClick(child, depth + 1);
7592
+ return;
7593
+ }
7594
+ if (this.#currentLeadingSiblings) {
7595
+ const sibling = this.#currentLeadingSiblings.find((c) => c.item.key === key);
7596
+ if (sibling) this.#onChildClick(sibling, depth);
7597
+ this.#currentLeadingSiblings = void 0;
7598
+ return;
7599
+ }
7600
+ }
7404
7601
  const child = node.children?.find((c) => c.item.key === key);
7405
7602
  if (child) this.#onChildClick(child, depth + 1);
7406
7603
  }
7604
+ #currentLeadingSiblings;
7605
+ #getSiblingMenuItems(pathNodes, index) {
7606
+ if (index === 0) {
7607
+ if (!this.hideRoot || !this.root) return [];
7608
+ const siblings = [...this.root.children ?? []].sort((a, b) => (a.item.label ?? ``).localeCompare(b.item.label ?? ``));
7609
+ this.#currentLeadingSiblings = siblings;
7610
+ return siblings.map((sibling) => b`
7611
+ <ixfx-menu-item
7612
+ label="${sibling.item.label}"
7613
+ command="${sibling.item.key}"
7614
+ icon-name="${sibling.item.icon ?? A}"
7615
+ @click=${() => {
7616
+ this.#onChildClick(sibling, index);
7617
+ }}
7618
+ ></ixfx-menu-item>
7619
+ `);
7620
+ }
7621
+ const siblings = [...pathNodes[index - 1].children ?? []].sort((a, b) => (a.item.label ?? ``).localeCompare(b.item.label ?? ``));
7622
+ this.#currentLeadingSiblings = siblings;
7623
+ return siblings.map((sibling) => b`
7624
+ <ixfx-menu-item
7625
+ label="${sibling.item.label}"
7626
+ command="${sibling.item.key}"
7627
+ icon-name="${sibling.item.icon ?? A}"
7628
+ @click=${() => {
7629
+ this.#onChildClick(sibling, index);
7630
+ }}
7631
+ ></ixfx-menu-item>
7632
+ `);
7633
+ }
7407
7634
  #onChildClick(node, depth) {
7408
7635
  this._openIdx = -1;
7409
7636
  this.#setSelected(node);
@@ -7460,86 +7687,156 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7460
7687
  const sel = this.selectedNode;
7461
7688
  if (!r || !sel) return b``;
7462
7689
  this.inputMode = this.tickled.inputMode;
7463
- const pathNodes = this.#findPath(r, sel) ?? [r];
7690
+ let pathNodes = this.#findPath(r, sel) ?? [r];
7691
+ if (this.hideRoot && pathNodes.length > 1) pathNodes = pathNodes.slice(1);
7464
7692
  this.#hasChildOptions();
7465
- const parts = pathNodes.map((node, i) => {
7693
+ const parts = [];
7694
+ let segmentId = 0;
7695
+ pathNodes.forEach((node, i) => {
7466
7696
  const isLast = i === pathNodes.length - 1;
7467
7697
  const isPending = this.#pendingNode === node;
7468
7698
  const isOpen = this._openIdx === i;
7469
7699
  const hasChildren = node.item.isLeaf !== true && (node.children === void 0 || node.children.length > 0);
7470
- const showCaret = !isLast && hasChildren;
7471
7700
  const isTickled = this.tickled.isTickled(i);
7472
- const menuItems = (node.children ?? []).map((c) => b`
7473
- <ixfx-menu-item
7474
- label="${c.item.label}"
7701
+ const showLeadingCaret = this.leadingCaret && (i > 0 || this.hideRoot) && hasChildren;
7702
+ const showTrailingCaret = !this.leadingCaret && hasChildren || this.leadingCaret && isLast && hasChildren;
7703
+ const siblingMenuItems = showLeadingCaret ? this.#getSiblingMenuItems(pathNodes, i) : [];
7704
+ const childMenuItems = (node.children ?? []).map((c) => b`
7705
+ <ixfx-menu-item
7706
+ label="${c.item.label}"
7475
7707
  command="${c.item.key}"
7476
7708
  icon-name="${c.item.icon ?? A}"
7477
7709
  ></ixfx-menu-item>
7478
7710
  `);
7479
- return b`
7480
- <div class="item crumb-segment ${isTickled ? `tickled` : ``}"
7481
- @pointerenter=${() => {
7482
- this.tickled.handlePointerEnter(i);
7483
- this.dispatchEvent(new CustomEvent(`tickle`, {
7484
- detail: {
7485
- node,
7486
- depth: i
7487
- },
7488
- bubbles: true,
7489
- composed: true
7490
- }));
7491
- }}
7492
- >
7493
- <span class="label" @click=${() => this.#onLabelClick(node, i)}>${node.item.label}</span>
7494
- ${showCaret ? b`
7495
- <span class="caret ${isOpen ? `open` : ``} ${isPending ? `pending` : ``}">
7496
- ${isPending ? b`<span class="loading-spinner"></span>` : b`
7497
- <ixfx-menu-trigger mode="trigger" label="›" @menu-command=${(e) => this.#onSegmentMenuCommand(e, node, i)}>
7498
- <ixfx-menu-container slot="menu">
7499
- ${menuItems}
7500
- </ixfx-menu-container>
7501
- </ixfx-menu-trigger>
7502
- `}
7503
- </span>
7504
- ` : A}
7505
- </div>
7506
- `;
7711
+ if (this.caretEmbedded) {
7712
+ const canTruncate = !this.noTruncate && !isLast && !isTickled;
7713
+ parts.push(b`
7714
+ <div class="item crumb-segment ${isTickled ? `tickled` : ``} ${canTruncate ? `truncatable` : ``} ${this.leadingCaret ? `leading-caret` : ``} ${isLast ? `last-label` : ``}" data-segment-id="${i}" data-path-index="${i}"
7715
+ @click=${(e) => {
7716
+ if (e.target.closest(`.caret`)) return;
7717
+ this.#onLabelClick(node, i);
7718
+ }}
7719
+ >
7720
+ ${showLeadingCaret ? b`
7721
+ <span class="caret ${isOpen ? `open` : ``} ${isPending ? `pending` : ``}">
7722
+ ${isPending ? b`<span class="loading-spinner"></span>` : b`
7723
+ <ixfx-menu-trigger mode="trigger" label="›" @menu-command=${(e) => this.#onSegmentMenuCommand(e, node, i)}>
7724
+ <ixfx-menu-container slot="menu">
7725
+ ${siblingMenuItems}
7726
+ </ixfx-menu-container>
7727
+ </ixfx-menu-trigger>
7728
+ `}
7729
+ </span>
7730
+ ` : A}
7731
+ <span class="label">${node.item.label}</span>
7732
+ ${showTrailingCaret ? b`
7733
+ <span class="caret ${isOpen ? `open` : ``} ${isPending ? `pending` : ``}">
7734
+ ${isPending ? b`<span class="loading-spinner"></span>` : b`
7735
+ <ixfx-menu-trigger mode="trigger" label="›" placement="right-start" @menu-command=${(e) => this.#onSegmentMenuCommand(e, node, i)}>
7736
+ <ixfx-menu-container slot="menu">
7737
+ ${childMenuItems}
7738
+ </ixfx-menu-container>
7739
+ </ixfx-menu-trigger>
7740
+ `}
7741
+ </span>
7742
+ ` : A}
7743
+ </div>
7744
+ `);
7745
+ } else {
7746
+ const leadingCaretTickled = showLeadingCaret ? this.tickled.isTickled(segmentId) : false;
7747
+ if (showLeadingCaret) {
7748
+ parts.push(b`
7749
+ <div class="item crumb-segment ${leadingCaretTickled ? `tickled` : ``}" data-segment-id="${segmentId}" data-path-index="${i}"
7750
+ @click=${(e) => {
7751
+ const trigger = e.target.closest(`.crumb-segment`)?.querySelector(`ixfx-menu-trigger`);
7752
+ if (trigger) trigger.openMenu();
7753
+ }}
7754
+ >
7755
+ <span class="caret ${isOpen ? `open` : ``} ${isPending ? `pending` : ``}">
7756
+ ${isPending ? b`<span class="loading-spinner"></span>` : b`
7757
+ <ixfx-menu-trigger mode="trigger" label="›" placement="right-start" @menu-command=${(e) => this.#onSegmentMenuCommand(e, node, i)}>
7758
+ <ixfx-menu-container slot="menu">
7759
+ ${siblingMenuItems}
7760
+ </ixfx-menu-container>
7761
+ </ixfx-menu-trigger>
7762
+ `}
7763
+ </span>
7764
+ </div>
7765
+ `);
7766
+ segmentId++;
7767
+ }
7768
+ const labelTickled = this.tickled.isTickled(segmentId);
7769
+ const canTruncate = !this.noTruncate && !isLast && !labelTickled;
7770
+ parts.push(b`
7771
+ <div class="item crumb-segment ${labelTickled ? `tickled` : ``} ${canTruncate ? `truncatable` : ``} ${isLast ? `last-label` : ``}" data-segment-id="${segmentId}" data-path-index="${i}"
7772
+ @click=${(e) => {
7773
+ if (e.target.closest(`.caret`)) return;
7774
+ this.#onLabelClick(node, i);
7775
+ }}
7776
+ >
7777
+ <span class="label">${node.item.label}</span>
7778
+ </div>
7779
+ `);
7780
+ segmentId++;
7781
+ if (showTrailingCaret) {
7782
+ const trailingCaretTickled = this.tickled.isTickled(segmentId);
7783
+ parts.push(b`
7784
+ <div class="item crumb-segment ${trailingCaretTickled ? `tickled` : ``}" data-segment-id="${segmentId}" data-path-index="${i}"
7785
+ @click=${(e) => {
7786
+ const trigger = e.target.closest(`.crumb-segment`)?.querySelector(`ixfx-menu-trigger`);
7787
+ if (trigger) trigger.openMenu();
7788
+ }}
7789
+ >
7790
+ <span class="caret ${isOpen ? `open` : ``} ${isPending ? `pending` : ``}">
7791
+ ${isPending ? b`<span class="loading-spinner"></span>` : b`
7792
+ <ixfx-menu-trigger mode="trigger" label="›" @menu-command=${(e) => this.#onSegmentMenuCommand(e, node, i)}>
7793
+ <ixfx-menu-container slot="menu">
7794
+ ${childMenuItems}
7795
+ </ixfx-menu-container>
7796
+ </ixfx-menu-trigger>
7797
+ `}
7798
+ </span>
7799
+ </div>
7800
+ `);
7801
+ segmentId++;
7802
+ }
7803
+ }
7507
7804
  });
7508
- const childOptions = this.#renderChildOptions();
7509
- const isLastItemTickled = this.tickled.isTickled(pathNodes.length);
7510
7805
  return b`
7511
7806
  <div
7512
- class="host-container"
7807
+ class="host-container scroll-fade-x"
7513
7808
  tabindex="0"
7514
7809
  @focus=${this.#onFocus}
7515
7810
  @blur=${this.#onBlur}
7516
7811
  @keydown=${this.#onKeyDown}
7517
7812
  @click=${this.#onHostClick}
7518
7813
  @menu-trigger-close=${this.#onMenuTriggerClose}
7519
- @pointerleave=${(e) => this.tickled.handlePointerLeave(e)}
7814
+ @mouseout=${(e) => this.tickled.handlePointerLeave(e)}
7520
7815
  >
7521
- <div class="parts">${parts}<div class="trailing"></div></div>
7522
- ${childOptions ? b`
7523
- <ixfx-menu-trigger
7524
- id="child-options-trigger"
7525
- class="item separator-trigger ${isLastItemTickled ? `tickled` : ``}"
7526
- mode="trigger"
7527
- label="›"
7528
- @menu-command=${this.#onMenuCommand}
7529
- @pointerenter=${() => this.tickled.handlePointerEnter(pathNodes.length)}
7530
- >
7531
- <ixfx-menu-container slot="menu">
7532
- ${(this.selectedNode?.children ?? []).map((c) => b`
7533
- <ixfx-menu-item
7534
- label="${c.item.label}"
7535
- command="${c.item.key}"
7536
- icon-name="${c.item.icon ?? A}"
7537
- ></ixfx-menu-item>
7538
- `)}
7539
- </ixfx-menu-container>
7540
- </ixfx-menu-trigger>
7541
- ` : A}
7542
- ${childOptions}
7816
+ <div class="parts"
7817
+ @mouseover=${(e) => {
7818
+ if (this.#isWheelScrolling) return;
7819
+ const target = e.target;
7820
+ if (!(target instanceof HTMLElement)) return;
7821
+ const segment = target.closest(`.crumb-segment`);
7822
+ if (!segment) return;
7823
+ const segmentId = parseInt(segment.dataset.segmentId ?? ``, 10);
7824
+ if (Number.isNaN(segmentId)) return;
7825
+ const pathIndex = parseInt(segment.dataset.pathIndex ?? ``, 10);
7826
+ const node = pathNodes[pathIndex];
7827
+ if (!node) return;
7828
+ this.tickled.handlePointerEnter(segmentId);
7829
+ this.dispatchEvent(new CustomEvent(`tickle`, {
7830
+ detail: {
7831
+ node,
7832
+ depth: pathIndex
7833
+ },
7834
+ bubbles: true,
7835
+ composed: true
7836
+ }));
7837
+ }}
7838
+ >${parts}<div class="trailing"></div></div>
7839
+ ${this.showSuggestions ? this.#renderChildOptions() : A}
7543
7840
  </div>
7544
7841
  `;
7545
7842
  }
@@ -7592,19 +7889,21 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7592
7889
  themeFallbacks,
7593
7890
  tickledStyles,
7594
7891
  tickledItemStyles,
7892
+ scrollFadeStyles(`x`),
7595
7893
  i$3`
7596
7894
  :host {
7597
- --text-highlight: var(--accent);
7598
- --separator: var(--surface-muted-text);
7599
- --padding-s: var(--space-xs);
7895
+ --crumb-item-bg: var(--item-bg, transparent);
7896
+ --crumb-item-bg-tickled: var(--item-bg-tickled);
7897
+ --crumb-item-text-tickled: var(--item-text-tickled);
7898
+ --crumb-item-border: var(--item-border);
7899
+ --crumb-item-border-tickled: var(--item-border-tickled);
7900
+ --crumb-item-padding: var(--item-padding, var(--space-xs));
7901
+ --crumb-item-border-radius: var(--item-border-radius);
7902
+ --crumb-item-gap: var(--item-gap, var(--space-xs));
7903
+ --crumb-separator: var(--surface-muted-text);
7904
+ --crumb-truncate-min-width: 10ch;
7600
7905
  display: flex;
7601
- overflow: scroll;
7602
7906
  width: 100%;
7603
- scroll-snap-type: x mandatory;
7604
- scrollbar-width: none;
7605
-
7606
- /* Breadcrumb tickled overrides — no layout shift: keep transparent border at rest */
7607
- --item-padding: 0;
7608
7907
  }
7609
7908
 
7610
7909
  :host(.focused) {
@@ -7616,14 +7915,20 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7616
7915
  outline-offset: 2px;
7617
7916
  }
7618
7917
 
7918
+ /* Edge fade (shows/hides based on scroll position) comes from the
7919
+ * shared .scroll-fade-x class - see ../styles/scroll-fade.ts */
7619
7920
  .host-container {
7620
7921
  display: flex;
7621
7922
  width: 100%;
7622
7923
  outline: none;
7924
+ overflow: scroll;
7925
+ scroll-snap-type: x mandatory;
7926
+ scrollbar-width: none;
7623
7927
  }
7624
7928
 
7625
7929
  .parts {
7626
7930
  display: flex;
7931
+ gap: var(--space-s);
7627
7932
  }
7628
7933
 
7629
7934
  .trailing {
@@ -7635,21 +7940,68 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7635
7940
  .crumb-segment {
7636
7941
  display: flex;
7637
7942
  align-items: center;
7943
+ flex-shrink: 0;
7944
+ background: var(--crumb-item-bg);
7945
+ border: var(--crumb-item-border);
7946
+ border-radius: var(--crumb-item-border-radius);
7947
+ }
7948
+
7949
+ .crumb-segment.leading-caret {
7950
+ flex-direction: row;
7951
+ }
7952
+
7953
+ .crumb-segment.tickled {
7954
+ background: var(--crumb-item-bg-tickled);
7955
+ border-color: var(--crumb-item-border-tickled);
7956
+ color: var(--crumb-item-text-tickled);
7957
+ flex-shrink: 0;
7958
+ min-width: auto;
7959
+ }
7960
+
7961
+ .crumb-segment.truncatable {
7962
+ flex-shrink: 1;
7963
+ min-width: min(var(--crumb-truncate-min-width), auto);
7964
+ overflow: hidden;
7965
+ }
7966
+
7967
+ .crumb-segment.truncatable .label {
7968
+ overflow: hidden;
7969
+ text-overflow: ellipsis;
7970
+ white-space: nowrap;
7971
+ min-width: 0;
7638
7972
  }
7639
7973
 
7640
7974
  .label {
7641
7975
  cursor: pointer;
7642
- margin-right: var(--padding-s);
7643
7976
  white-space: nowrap;
7977
+ color: inherit;
7978
+ }
7979
+
7980
+ .crumb-segment.last-label .label {
7981
+ color: var(--crumb-last-label-color, inherit);
7982
+ font-style: var(--crumb-last-label-font-style, inherit);
7983
+ font-weight: var(--crumb-last-label-font-weight, inherit);
7984
+ text-decoration: var(--crumb-last-label-text-decoration, inherit);
7985
+ text-underline-offset: var(--crumb-last-label-text-underline-offset, auto);
7986
+ }
7987
+
7988
+ .crumb-segment.tickled .label {
7989
+ color: var(--crumb-item-text-tickled);
7990
+ overflow: visible;
7991
+ text-overflow: clip;
7992
+ }
7993
+
7994
+ .crumb-segment ixfx-menu-trigger {
7995
+ --ixfx-button-padding: var(--space-xs) 8px;
7644
7996
  }
7645
7997
 
7646
7998
  .caret {
7647
7999
  position: relative;
7648
8000
  cursor: pointer;
7649
- padding: var(--padding-m);
7650
- margin-left: var(--padding-s);
7651
- margin-right: var(--padding-s);
7652
- color: var(--separator);
8001
+ padding: var(--crumb-item-padding);
8002
+ margin-left: var(--crumb-item-gap);
8003
+ margin-right: var(--crumb-item-gap);
8004
+ color: var(--crumb-separator);
7653
8005
  display: inline-flex;
7654
8006
  align-items: center;
7655
8007
  justify-content: center;
@@ -7687,15 +8039,15 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7687
8039
  }
7688
8040
 
7689
8041
  .separator {
7690
- margin-left: var(--padding-s);
7691
- margin-right: var(--padding-s);
7692
- color: var(--separator);
8042
+ margin-left: var(--crumb-item-gap);
8043
+ margin-right: var(--crumb-item-gap);
8044
+ color: var(--crumb-separator);
7693
8045
  }
7694
8046
 
7695
8047
  .separator-trigger {
7696
8048
  display: inline-flex;
7697
- margin-left: var(--padding-s);
7698
- margin-right: var(--padding-s);
8049
+ margin-left: var(--crumb-item-gap);
8050
+ margin-right: var(--crumb-item-gap);
7699
8051
  --_trigger-hover-bg: transparent;
7700
8052
  --_trigger-hover-opacity: 0.6;
7701
8053
  }
@@ -7703,7 +8055,7 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7703
8055
  .separator-trigger::part(base) {
7704
8056
  background: none;
7705
8057
  border: none;
7706
- color: var(--separator);
8058
+ color: var(--crumb-separator);
7707
8059
  padding: 0;
7708
8060
  cursor: pointer;
7709
8061
  font-size: inherit;
@@ -7775,6 +8127,31 @@ __decorate([property({
7775
8127
  reflect: true,
7776
8128
  attribute: `input-mode`
7777
8129
  })], CrumbNavigationElement.prototype, "inputMode", void 0);
8130
+ __decorate([property({
8131
+ type: Boolean,
8132
+ reflect: true,
8133
+ attribute: `no-truncate`
8134
+ })], CrumbNavigationElement.prototype, "noTruncate", void 0);
8135
+ __decorate([property({
8136
+ type: Boolean,
8137
+ reflect: true,
8138
+ attribute: `show-suggestions`
8139
+ })], CrumbNavigationElement.prototype, "showSuggestions", void 0);
8140
+ __decorate([property({
8141
+ type: Boolean,
8142
+ reflect: true,
8143
+ attribute: `leading-caret`
8144
+ })], CrumbNavigationElement.prototype, "leadingCaret", void 0);
8145
+ __decorate([property({
8146
+ type: Boolean,
8147
+ reflect: true,
8148
+ attribute: `caret-embedded`
8149
+ })], CrumbNavigationElement.prototype, "caretEmbedded", void 0);
8150
+ __decorate([property({
8151
+ type: Boolean,
8152
+ reflect: true,
8153
+ attribute: `hide-root`
8154
+ })], CrumbNavigationElement.prototype, "hideRoot", void 0);
7778
8155
  CrumbNavigationElement = __decorate([safeCustomElement(`ixfx-crumb-navigation`)], CrumbNavigationElement);
7779
8156
  //#endregion
7780
8157
  //#region src/crumbs/crumb-path-controller.ts
@@ -8255,7 +8632,7 @@ let CrumbPathElement = class CrumbPathElement extends i {
8255
8632
  ${this.separator}
8256
8633
  </div>` : A;
8257
8634
  return b`
8258
- <div class="crumbs" tabindex="0" @keydown=${this.#onHostKeyDown}>
8635
+ <div class="crumbs scroll-fade-x" tabindex="0" @keydown=${this.#onHostKeyDown}>
8259
8636
  ${rootSegments.length > 0 ? rootSegments.map((label, i) => this.#renderRootSegment(label, i)) : A}
8260
8637
  ${leadingCaret}
8261
8638
  ${segments.map((label, i) => this.#renderSegment(label, i, i === segments.length - 1))}
@@ -8276,6 +8653,7 @@ let CrumbPathElement = class CrumbPathElement extends i {
8276
8653
  prominenceStyles,
8277
8654
  themeFallbacks,
8278
8655
  popupSurfaceStyles,
8656
+ scrollFadeStyles(`x`),
8279
8657
  i$3`
8280
8658
  :host {
8281
8659
  --text-highlight: var(--accent);
@@ -12350,6 +12728,7 @@ let VerticalListElement = class VerticalListElement extends i {
12350
12728
  overflow: hidden;
12351
12729
  box-sizing: border-box;
12352
12730
  outline: none;
12731
+ --ixfx-list-item-padding: var(--ixfx-list-item-padding, var(--list-item-padding, 4px var(--space-m, 8px)));
12353
12732
  }
12354
12733
 
12355
12734
  :host(:focus-visible) {
@@ -12386,7 +12765,7 @@ let VerticalListElement = class VerticalListElement extends i {
12386
12765
  ::slotted(li) {
12387
12766
  display: flex;
12388
12767
  align-items: center;
12389
- padding: var(--list-item-padding, 4px var(--space-m, 8px));
12768
+ padding: var(--ixfx-list-item-padding);
12390
12769
  min-height: var(--list-item-height, 28px);
12391
12770
  cursor: pointer;
12392
12771
  user-select: none;
@@ -24293,7 +24672,7 @@ let TabListItemElement = class TabListItemElement extends i {
24293
24672
  --selected-tab-border-width: var(--ixfx-selected-tab-border-width, 1px);
24294
24673
  --selected-tab-weight: var(--ixfx-selected-tab-weight, bold);
24295
24674
  --selected-background: var(--ixfx-tab-selected-background, var(--surface-5));
24296
- --padding: var(--space-m);
24675
+ --tab-padding: var(--ixfx-tab-padding, var(--space-m));
24297
24676
  display: inline-flex;
24298
24677
  min-width: 0;
24299
24678
  user-select: none;
@@ -24304,7 +24683,7 @@ let TabListItemElement = class TabListItemElement extends i {
24304
24683
  display: flex;
24305
24684
  align-items: center;
24306
24685
  gap: var(--space-s, 4px);
24307
- padding: var(--padding) calc(2 * var(--padding));
24686
+ padding: var(--tab-padding) calc(2 * var(--tab-padding));
24308
24687
  min-width: 0;
24309
24688
  width: 100%;
24310
24689
  font-weight: 500;