@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/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;
@@ -4403,6 +4454,22 @@ let SplitButton = class SplitButton extends ButtonElement {
4403
4454
  this._selectedIcon = item.icon;
4404
4455
  return true;
4405
4456
  }
4457
+ /** Invokes the primary button action as if clicked by the user. */
4458
+ invoke() {
4459
+ if (this.disabled) return false;
4460
+ const primaryBtn = this.shadowRoot?.querySelector(`.split button.primary`);
4461
+ if (!primaryBtn) return false;
4462
+ if (this.mode === `switcher` && this._selectedCommand) {
4463
+ this.dispatchEvent(new CustomEvent(`menu-command`, {
4464
+ detail: { command: this._selectedCommand },
4465
+ bubbles: true,
4466
+ composed: true
4467
+ }));
4468
+ return true;
4469
+ }
4470
+ primaryBtn.dispatchEvent(new MouseEvent(`click`, { bubbles: true }));
4471
+ return true;
4472
+ }
4406
4473
  /** True when there is (or will be) popup content. Used to dim the caret visually. */
4407
4474
  get _caretHasContent() {
4408
4475
  if (this.itemsProvider) return true;
@@ -6958,10 +7025,14 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
6958
7025
  this.exclusivity = `none`;
6959
7026
  this.selectionFilter = `leaf`;
6960
7027
  this._openIdx = -1;
6961
- this._isFocused = false;
6962
7028
  this._menuOpen = false;
6963
7029
  this._focusOnNarrowedText = false;
6964
7030
  this.inputMode = ``;
7031
+ this.noTruncate = false;
7032
+ this.showSuggestions = false;
7033
+ this.leadingCaret = false;
7034
+ this.caretEmbedded = true;
7035
+ this.hideRoot = false;
6965
7036
  this.#previousSelectionKey = ``;
6966
7037
  this.#menuOpenedByKeyboard = false;
6967
7038
  this.#currentOpenTrigger = null;
@@ -6971,21 +7042,15 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
6971
7042
  if (index !== void 0) this.#onTickled(index);
6972
7043
  }
6973
7044
  });
6974
- this.#onFocus = () => {
6975
- this._isFocused = true;
6976
- };
6977
- this.#onBlur = () => {
6978
- this._isFocused = false;
6979
- };
6980
7045
  this.#onHostClick = () => {
6981
- this.shadowRoot?.querySelector(`.host-container`)?.focus();
7046
+ this.focus();
6982
7047
  };
6983
7048
  this.#onMenuTriggerClose = () => {
6984
7049
  document.removeEventListener(`keydown`, this.#onMenuKeyNav, { capture: true });
6985
7050
  this.#currentOpenTrigger = null;
6986
7051
  this.#menuOpenedByKeyboard = false;
6987
7052
  requestAnimationFrame(() => {
6988
- this.shadowRoot?.querySelector(`.host-container`)?.focus();
7053
+ this.focus();
6989
7054
  });
6990
7055
  };
6991
7056
  this.#onMenuKeyNav = (e) => {
@@ -7008,7 +7073,8 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7008
7073
  }
7009
7074
  }
7010
7075
  };
7011
- this.#onKeyDown = (e) => {
7076
+ this.#onKeyDown = (event) => {
7077
+ const e = event;
7012
7078
  const r = this.root;
7013
7079
  const sel = this.selectedNode;
7014
7080
  if (!r || !sel) return;
@@ -7067,6 +7133,29 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7067
7133
  this._focusOnNarrowedText = false;
7068
7134
  this.#shiftFocusToPath();
7069
7135
  };
7136
+ this.#onMouseOut = (e) => {
7137
+ this.tickled.handlePointerLeave(e);
7138
+ };
7139
+ this.#isWheelScrolling = false;
7140
+ this.#onWheel = (e) => {
7141
+ const wheelEvent = e;
7142
+ if (wheelEvent.deltaX !== 0 || wheelEvent.deltaY !== 0) {
7143
+ e.preventDefault();
7144
+ if (!this.#isWheelScrolling) {
7145
+ this.#isWheelScrolling = true;
7146
+ this.tickled.clearTickled();
7147
+ }
7148
+ this.scrollLeft += wheelEvent.deltaX + wheelEvent.deltaY;
7149
+ clearTimeout(this.#wheelScrollTimeout);
7150
+ this.#wheelScrollTimeout = setTimeout(() => {
7151
+ this.#isWheelScrolling = false;
7152
+ }, 150);
7153
+ }
7154
+ };
7155
+ this.#onScroll = () => {
7156
+ this.#updateFade();
7157
+ };
7158
+ this.#currentLeadingSiblings = void 0;
7070
7159
  }
7071
7160
  get selectedNodes() {
7072
7161
  return this._selectedNodes;
@@ -7083,26 +7172,41 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7083
7172
  if (!r || !sel) return [];
7084
7173
  const pathNodes = this.#findPath(r, sel) ?? [r];
7085
7174
  const items = [];
7086
- pathNodes.forEach((_node, i) => {
7087
- items.push({
7088
- id: i,
7175
+ if (this.caretEmbedded) {
7176
+ pathNodes.forEach((_node, i) => {
7177
+ items.push({
7178
+ id: i,
7179
+ enabled: true
7180
+ });
7181
+ });
7182
+ if (this.#hasChildOptions()) items.push({
7183
+ id: pathNodes.length,
7089
7184
  enabled: true
7090
7185
  });
7091
- });
7092
- if (this.#hasChildOptions()) items.push({
7093
- id: pathNodes.length,
7094
- enabled: true
7095
- });
7186
+ } else {
7187
+ let segmentId = 0;
7188
+ pathNodes.forEach((node, i) => {
7189
+ const isLast = i === pathNodes.length - 1;
7190
+ const hasChildren = node.item.isLeaf !== true && (node.children === void 0 || node.children.length > 0);
7191
+ const showLeadingCaret = this.leadingCaret && (i > 0 || this.hideRoot) && hasChildren;
7192
+ const showTrailingCaret = !this.leadingCaret && hasChildren || this.leadingCaret && isLast && hasChildren;
7193
+ if (showLeadingCaret) items.push({
7194
+ id: segmentId++,
7195
+ enabled: true
7196
+ });
7197
+ items.push({
7198
+ id: segmentId++,
7199
+ enabled: true
7200
+ });
7201
+ if (showTrailingCaret) items.push({
7202
+ id: segmentId++,
7203
+ enabled: true
7204
+ });
7205
+ });
7206
+ }
7096
7207
  return items;
7097
7208
  }
7098
7209
  #onTickled(index) {
7099
- const r = this.root;
7100
- const sel = this.selectedNode;
7101
- if (!r || !sel) return;
7102
- const pathNodes = this.#findPath(r, sel) ?? [r];
7103
- const hasChildOptions = this.#hasChildOptions();
7104
- if (index < pathNodes.length) this._openIdx = index;
7105
- else if (hasChildOptions && index === pathNodes.length) this._openIdx = pathNodes.length - 1;
7106
7210
  if (this.tickled.inputMode === `keyboard`) this.#scrollTickledIntoView(index);
7107
7211
  }
7108
7212
  #scrollTickledIntoView(index) {
@@ -7125,8 +7229,6 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7125
7229
  const isPending = this.#pendingNode === s;
7126
7230
  return (s.children ?? []).length > 0 || isPending;
7127
7231
  }
7128
- #onFocus;
7129
- #onBlur;
7130
7232
  #onHostClick;
7131
7233
  #handleOpenCaretMenu() {
7132
7234
  const currentTickled = this.tickled.tickledId;
@@ -7206,8 +7308,7 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7206
7308
  }
7207
7309
  #shiftFocusToPath() {
7208
7310
  this._focusOnNarrowedText = false;
7209
- const hostContainer = this.shadowRoot?.querySelector(`.host-container`);
7210
- if (hostContainer) hostContainer.focus();
7311
+ this.focus();
7211
7312
  }
7212
7313
  #onNarrowedTextBlur;
7213
7314
  #onNarrowedTextShiftTab;
@@ -7254,16 +7355,42 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7254
7355
  }
7255
7356
  connectedCallback() {
7256
7357
  super.connectedCallback();
7358
+ this.tabIndex = 0;
7257
7359
  this.addEventListener(`click`, this.#onHostClick);
7360
+ this.addEventListener(`wheel`, this.#onWheel, { passive: false });
7361
+ this.addEventListener(`scroll`, this.#onScroll);
7362
+ this.addEventListener(`keydown`, this.#onKeyDown);
7363
+ this.addEventListener(`menu-trigger-close`, this.#onMenuTriggerClose);
7364
+ this.addEventListener(`mouseout`, this.#onMouseOut);
7258
7365
  }
7259
7366
  disconnectedCallback() {
7260
7367
  super.disconnectedCallback();
7261
7368
  this.removeEventListener(`click`, this.#onHostClick);
7369
+ this.removeEventListener(`wheel`, this.#onWheel);
7370
+ this.removeEventListener(`scroll`, this.#onScroll);
7371
+ this.removeEventListener(`keydown`, this.#onKeyDown);
7372
+ this.removeEventListener(`menu-trigger-close`, this.#onMenuTriggerClose);
7373
+ this.removeEventListener(`mouseout`, this.#onMouseOut);
7262
7374
  this.#modelUnsubscribe?.();
7263
7375
  this.#modelUnsubscribe = void 0;
7264
7376
  document.removeEventListener(`keydown`, this.#onMenuKeyNav, { capture: true });
7265
7377
  this.#currentOpenTrigger = null;
7266
7378
  }
7379
+ firstUpdated() {
7380
+ this.scrollLeft = this.scrollWidth - this.clientWidth;
7381
+ this.#updateFade();
7382
+ }
7383
+ #onMouseOut;
7384
+ #isWheelScrolling;
7385
+ #onWheel;
7386
+ #wheelScrollTimeout;
7387
+ #onScroll;
7388
+ #updateFade() {
7389
+ const hasLeftFade = this.scrollLeft > 0;
7390
+ const hasRightFade = this.scrollLeft < this.scrollWidth - this.clientWidth - 1;
7391
+ this.classList.toggle(`has-left-fade`, hasLeftFade);
7392
+ this.classList.toggle(`has-right-fade`, hasRightFade);
7393
+ }
7267
7394
  #runLayoutUpdate(_changedProperties) {
7268
7395
  if (!this.shadowRoot) return;
7269
7396
  const hsiElement = this.shadowRoot.querySelector(`.horiz-select-items`);
@@ -7311,6 +7438,13 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7311
7438
  if (result) return result;
7312
7439
  }
7313
7440
  }
7441
+ #scrollSegmentIntoView(depth) {
7442
+ if (!this.shadowRoot) return;
7443
+ this.shadowRoot.querySelectorAll(`.crumb-segment`)[depth]?.scrollIntoView({
7444
+ block: `nearest`,
7445
+ inline: `start`
7446
+ });
7447
+ }
7314
7448
  #setSelected(node) {
7315
7449
  const previous = this.selectedNode;
7316
7450
  if (previous) this.#previousSelectionKey = previous.item?.key ?? ``;
@@ -7318,6 +7452,15 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7318
7452
  const previousSet = previous ? /* @__PURE__ */ new Set([previous]) : /* @__PURE__ */ new Set();
7319
7453
  this.selectedNode = node;
7320
7454
  this._selectedNodes = /* @__PURE__ */ new Set([node]);
7455
+ if (this.root) {
7456
+ const path = this.#findPath(this.root, node);
7457
+ if (path) {
7458
+ const depth = this.hideRoot && path.length > 1 ? path.length - 2 : path.length - 1;
7459
+ requestAnimationFrame(() => {
7460
+ this.#scrollSegmentIntoView(depth);
7461
+ });
7462
+ }
7463
+ }
7321
7464
  this.dispatchEvent(new CustomEvent(`select`, {
7322
7465
  detail: {
7323
7466
  selected: /* @__PURE__ */ new Set([node]),
@@ -7330,6 +7473,7 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7330
7473
  #onLabelClick(node, depth) {
7331
7474
  this._openIdx = -1;
7332
7475
  this.#setSelected(node);
7476
+ if (this.leadingCaret) this.#scrollSegmentIntoView(depth);
7333
7477
  this.dispatchEvent(new CustomEvent(`item-click`, {
7334
7478
  detail: {
7335
7479
  node,
@@ -7385,9 +7529,52 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7385
7529
  }
7386
7530
  #onSegmentMenuCommand(e, node, depth) {
7387
7531
  const key = e.detail.command;
7532
+ if (this.leadingCaret) {
7533
+ const child = node.children?.find((c) => c.item.key === key);
7534
+ if (child) {
7535
+ this.#onChildClick(child, depth + 1);
7536
+ return;
7537
+ }
7538
+ if (this.#currentLeadingSiblings) {
7539
+ const sibling = this.#currentLeadingSiblings.find((c) => c.item.key === key);
7540
+ if (sibling) this.#onChildClick(sibling, depth);
7541
+ this.#currentLeadingSiblings = void 0;
7542
+ return;
7543
+ }
7544
+ }
7388
7545
  const child = node.children?.find((c) => c.item.key === key);
7389
7546
  if (child) this.#onChildClick(child, depth + 1);
7390
7547
  }
7548
+ #currentLeadingSiblings;
7549
+ #getSiblingMenuItems(pathNodes, index) {
7550
+ if (index === 0) {
7551
+ if (!this.hideRoot || !this.root) return [];
7552
+ const siblings = [...this.root.children ?? []].sort((a, b) => (a.item.label ?? ``).localeCompare(b.item.label ?? ``));
7553
+ this.#currentLeadingSiblings = siblings;
7554
+ return siblings.map((sibling) => b`
7555
+ <ixfx-menu-item
7556
+ label="${sibling.item.label}"
7557
+ command="${sibling.item.key}"
7558
+ icon-name="${sibling.item.icon ?? A}"
7559
+ @click=${() => {
7560
+ this.#onChildClick(sibling, index);
7561
+ }}
7562
+ ></ixfx-menu-item>
7563
+ `);
7564
+ }
7565
+ const siblings = [...pathNodes[index - 1].children ?? []].sort((a, b) => (a.item.label ?? ``).localeCompare(b.item.label ?? ``));
7566
+ this.#currentLeadingSiblings = siblings;
7567
+ return siblings.map((sibling) => b`
7568
+ <ixfx-menu-item
7569
+ label="${sibling.item.label}"
7570
+ command="${sibling.item.key}"
7571
+ icon-name="${sibling.item.icon ?? A}"
7572
+ @click=${() => {
7573
+ this.#onChildClick(sibling, index);
7574
+ }}
7575
+ ></ixfx-menu-item>
7576
+ `);
7577
+ }
7391
7578
  #onChildClick(node, depth) {
7392
7579
  this._openIdx = -1;
7393
7580
  this.#setSelected(node);
@@ -7444,87 +7631,146 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7444
7631
  const sel = this.selectedNode;
7445
7632
  if (!r || !sel) return b``;
7446
7633
  this.inputMode = this.tickled.inputMode;
7447
- const pathNodes = this.#findPath(r, sel) ?? [r];
7634
+ let pathNodes = this.#findPath(r, sel) ?? [r];
7635
+ if (this.hideRoot && pathNodes.length > 1) pathNodes = pathNodes.slice(1);
7448
7636
  this.#hasChildOptions();
7449
- const parts = pathNodes.map((node, i) => {
7637
+ const parts = [];
7638
+ let segmentId = 0;
7639
+ pathNodes.forEach((node, i) => {
7450
7640
  const isLast = i === pathNodes.length - 1;
7451
7641
  const isPending = this.#pendingNode === node;
7452
7642
  const isOpen = this._openIdx === i;
7453
7643
  const hasChildren = node.item.isLeaf !== true && (node.children === void 0 || node.children.length > 0);
7454
- const showCaret = !isLast && hasChildren;
7455
7644
  const isTickled = this.tickled.isTickled(i);
7456
- const menuItems = (node.children ?? []).map((c) => b`
7457
- <ixfx-menu-item
7458
- label="${c.item.label}"
7645
+ const showLeadingCaret = this.leadingCaret && (i > 0 || this.hideRoot) && hasChildren;
7646
+ const showTrailingCaret = !this.leadingCaret && hasChildren || this.leadingCaret && isLast && hasChildren;
7647
+ const siblingMenuItems = showLeadingCaret ? this.#getSiblingMenuItems(pathNodes, i) : [];
7648
+ const childMenuItems = (node.children ?? []).map((c) => b`
7649
+ <ixfx-menu-item
7650
+ label="${c.item.label}"
7459
7651
  command="${c.item.key}"
7460
7652
  icon-name="${c.item.icon ?? A}"
7461
7653
  ></ixfx-menu-item>
7462
7654
  `);
7463
- return b`
7464
- <div class="item crumb-segment ${isTickled ? `tickled` : ``}"
7465
- @pointerenter=${() => {
7466
- this.tickled.handlePointerEnter(i);
7467
- this.dispatchEvent(new CustomEvent(`tickle`, {
7468
- detail: {
7469
- node,
7470
- depth: i
7471
- },
7472
- bubbles: true,
7473
- composed: true
7474
- }));
7475
- }}
7476
- >
7477
- <span class="label" @click=${() => this.#onLabelClick(node, i)}>${node.item.label}</span>
7478
- ${showCaret ? b`
7479
- <span class="caret ${isOpen ? `open` : ``} ${isPending ? `pending` : ``}">
7480
- ${isPending ? b`<span class="loading-spinner"></span>` : b`
7481
- <ixfx-menu-trigger mode="trigger" label="›" @menu-command=${(e) => this.#onSegmentMenuCommand(e, node, i)}>
7482
- <ixfx-menu-container slot="menu">
7483
- ${menuItems}
7484
- </ixfx-menu-container>
7485
- </ixfx-menu-trigger>
7486
- `}
7487
- </span>
7488
- ` : A}
7489
- </div>
7490
- `;
7655
+ if (this.caretEmbedded) {
7656
+ const canTruncate = !this.noTruncate && !isLast && !isTickled;
7657
+ parts.push(b`
7658
+ <div class="item crumb-segment ${isTickled ? `tickled` : ``} ${canTruncate ? `truncatable` : ``} ${this.leadingCaret ? `leading-caret` : ``}" data-segment-id="${i}" data-path-index="${i}"
7659
+ @click=${(e) => {
7660
+ if (e.target.closest(`.caret`)) return;
7661
+ this.#onLabelClick(node, i);
7662
+ }}
7663
+ >
7664
+ ${showLeadingCaret ? b`
7665
+ <span class="caret ${isOpen ? `open` : ``} ${isPending ? `pending` : ``}">
7666
+ ${isPending ? b`<span class="loading-spinner"></span>` : b`
7667
+ <ixfx-menu-trigger mode="trigger" label="›" @menu-command=${(e) => this.#onSegmentMenuCommand(e, node, i)}>
7668
+ <ixfx-menu-container slot="menu">
7669
+ ${siblingMenuItems}
7670
+ </ixfx-menu-container>
7671
+ </ixfx-menu-trigger>
7672
+ `}
7673
+ </span>
7674
+ ` : A}
7675
+ <span class="label">${node.item.label}</span>
7676
+ ${showTrailingCaret ? b`
7677
+ <span class="caret ${isOpen ? `open` : ``} ${isPending ? `pending` : ``}">
7678
+ ${isPending ? b`<span class="loading-spinner"></span>` : b`
7679
+ <ixfx-menu-trigger mode="trigger" label="›" placement="right-start" @menu-command=${(e) => this.#onSegmentMenuCommand(e, node, i)}>
7680
+ <ixfx-menu-container slot="menu">
7681
+ ${childMenuItems}
7682
+ </ixfx-menu-container>
7683
+ </ixfx-menu-trigger>
7684
+ `}
7685
+ </span>
7686
+ ` : A}
7687
+ </div>
7688
+ `);
7689
+ } else {
7690
+ const leadingCaretTickled = showLeadingCaret ? this.tickled.isTickled(segmentId) : false;
7691
+ if (showLeadingCaret) {
7692
+ parts.push(b`
7693
+ <div class="item crumb-segment ${leadingCaretTickled ? `tickled` : ``}" data-segment-id="${segmentId}" data-path-index="${i}"
7694
+ @click=${(e) => {
7695
+ const trigger = e.target.closest(`.crumb-segment`)?.querySelector(`ixfx-menu-trigger`);
7696
+ if (trigger) trigger.openMenu();
7697
+ }}
7698
+ >
7699
+ <span class="caret ${isOpen ? `open` : ``} ${isPending ? `pending` : ``}">
7700
+ ${isPending ? b`<span class="loading-spinner"></span>` : b`
7701
+ <ixfx-menu-trigger mode="trigger" label="›" placement="right-start" @menu-command=${(e) => this.#onSegmentMenuCommand(e, node, i)}>
7702
+ <ixfx-menu-container slot="menu">
7703
+ ${siblingMenuItems}
7704
+ </ixfx-menu-container>
7705
+ </ixfx-menu-trigger>
7706
+ `}
7707
+ </span>
7708
+ </div>
7709
+ `);
7710
+ segmentId++;
7711
+ }
7712
+ const labelTickled = this.tickled.isTickled(segmentId);
7713
+ const canTruncate = !this.noTruncate && !isLast && !labelTickled;
7714
+ parts.push(b`
7715
+ <div class="item crumb-segment ${labelTickled ? `tickled` : ``} ${canTruncate ? `truncatable` : ``}" data-segment-id="${segmentId}" data-path-index="${i}"
7716
+ @click=${(e) => {
7717
+ if (e.target.closest(`.caret`)) return;
7718
+ this.#onLabelClick(node, i);
7719
+ }}
7720
+ >
7721
+ <span class="label">${node.item.label}</span>
7722
+ </div>
7723
+ `);
7724
+ segmentId++;
7725
+ if (showTrailingCaret) {
7726
+ const trailingCaretTickled = this.tickled.isTickled(segmentId);
7727
+ parts.push(b`
7728
+ <div class="item crumb-segment ${trailingCaretTickled ? `tickled` : ``}" data-segment-id="${segmentId}" data-path-index="${i}"
7729
+ @click=${(e) => {
7730
+ const trigger = e.target.closest(`.crumb-segment`)?.querySelector(`ixfx-menu-trigger`);
7731
+ if (trigger) trigger.openMenu();
7732
+ }}
7733
+ >
7734
+ <span class="caret ${isOpen ? `open` : ``} ${isPending ? `pending` : ``}">
7735
+ ${isPending ? b`<span class="loading-spinner"></span>` : b`
7736
+ <ixfx-menu-trigger mode="trigger" label="›" @menu-command=${(e) => this.#onSegmentMenuCommand(e, node, i)}>
7737
+ <ixfx-menu-container slot="menu">
7738
+ ${childMenuItems}
7739
+ </ixfx-menu-container>
7740
+ </ixfx-menu-trigger>
7741
+ `}
7742
+ </span>
7743
+ </div>
7744
+ `);
7745
+ segmentId++;
7746
+ }
7747
+ }
7491
7748
  });
7492
- const childOptions = this.#renderChildOptions();
7493
- const isLastItemTickled = this.tickled.isTickled(pathNodes.length);
7494
7749
  return b`
7495
- <div
7496
- class="host-container"
7497
- tabindex="0"
7498
- @focus=${this.#onFocus}
7499
- @blur=${this.#onBlur}
7500
- @keydown=${this.#onKeyDown}
7501
- @click=${this.#onHostClick}
7502
- @menu-trigger-close=${this.#onMenuTriggerClose}
7503
- @pointerleave=${(e) => this.tickled.handlePointerLeave(e)}
7504
- >
7505
- <div class="parts">${parts}<div class="trailing"></div></div>
7506
- ${childOptions ? b`
7507
- <ixfx-menu-trigger
7508
- id="child-options-trigger"
7509
- class="item separator-trigger ${isLastItemTickled ? `tickled` : ``}"
7510
- mode="trigger"
7511
- label="›"
7512
- @menu-command=${this.#onMenuCommand}
7513
- @pointerenter=${() => this.tickled.handlePointerEnter(pathNodes.length)}
7514
- >
7515
- <ixfx-menu-container slot="menu">
7516
- ${(this.selectedNode?.children ?? []).map((c) => b`
7517
- <ixfx-menu-item
7518
- label="${c.item.label}"
7519
- command="${c.item.key}"
7520
- icon-name="${c.item.icon ?? A}"
7521
- ></ixfx-menu-item>
7522
- `)}
7523
- </ixfx-menu-container>
7524
- </ixfx-menu-trigger>
7525
- ` : A}
7526
- ${childOptions}
7527
- </div>
7750
+ <div class="parts"
7751
+ @mouseover=${(e) => {
7752
+ if (this.#isWheelScrolling) return;
7753
+ const target = e.target;
7754
+ if (!(target instanceof HTMLElement)) return;
7755
+ const segment = target.closest(`.crumb-segment`);
7756
+ if (!segment) return;
7757
+ const segmentId = parseInt(segment.dataset.segmentId ?? ``, 10);
7758
+ if (Number.isNaN(segmentId)) return;
7759
+ const pathIndex = parseInt(segment.dataset.pathIndex ?? ``, 10);
7760
+ const node = pathNodes[pathIndex];
7761
+ if (!node) return;
7762
+ this.tickled.handlePointerEnter(segmentId);
7763
+ this.dispatchEvent(new CustomEvent(`tickle`, {
7764
+ detail: {
7765
+ node,
7766
+ depth: pathIndex
7767
+ },
7768
+ bubbles: true,
7769
+ composed: true
7770
+ }));
7771
+ }}
7772
+ >${parts}<div class="trailing"></div></div>
7773
+ ${this.showSuggestions ? this.#renderChildOptions() : A}
7528
7774
  `;
7529
7775
  }
7530
7776
  #renderChildOptions() {
@@ -7578,36 +7824,66 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7578
7824
  tickledItemStyles,
7579
7825
  i$3`
7580
7826
  :host {
7581
- --text-highlight: var(--accent);
7582
- --separator: var(--surface-muted-text);
7583
- --padding-s: var(--space-xs);
7827
+ --crumb-item-bg: var(--item-bg, transparent);
7828
+ --crumb-item-bg-tickled: var(--item-bg-tickled);
7829
+ --crumb-item-text-tickled: var(--item-text-tickled);
7830
+ --crumb-item-border: var(--item-border);
7831
+ --crumb-item-border-tickled: var(--item-border-tickled);
7832
+ --crumb-item-padding: var(--space-xs);
7833
+ --crumb-item-border-radius: var(--item-border-radius);
7834
+ --crumb-item-gap: var(--space-xs);
7835
+ --crumb-separator: var(--surface-muted-text);
7836
+ --crumb-truncate-min-width: 10ch;
7837
+ --crumb-fade-width: 30px;
7838
+ --crumb-fade-color: var(--surface-3);
7839
+ position: relative;
7584
7840
  display: flex;
7585
7841
  overflow: scroll;
7586
7842
  width: 100%;
7587
7843
  scroll-snap-type: x mandatory;
7588
7844
  scrollbar-width: none;
7589
-
7590
- /* Breadcrumb tickled overrides — no layout shift: keep transparent border at rest */
7591
- --item-padding: 0;
7592
7845
  }
7593
7846
 
7594
- :host(.focused) {
7847
+ :host(:focus) {
7595
7848
  outline: none;
7596
7849
  }
7597
7850
 
7598
- :host(:focus-within) .host-container {
7851
+ :host(:focus) .parts {
7599
7852
  outline: 2px solid var(--accent);
7600
7853
  outline-offset: 2px;
7601
7854
  }
7602
7855
 
7603
- .host-container {
7604
- display: flex;
7605
- width: 100%;
7606
- outline: none;
7856
+ :host::before,
7857
+ :host::after {
7858
+ content: '';
7859
+ position: absolute;
7860
+ top: 0;
7861
+ bottom: 0;
7862
+ width: var(--crumb-fade-width);
7863
+ pointer-events: none;
7864
+ opacity: 0;
7865
+ transition: opacity 0.2s ease;
7866
+ z-index: 1;
7867
+ }
7868
+
7869
+ :host::before {
7870
+ left: 0;
7871
+ background: linear-gradient(to right, var(--crumb-fade-color), transparent);
7872
+ }
7873
+
7874
+ :host::after {
7875
+ right: 0;
7876
+ background: linear-gradient(to left, var(--crumb-fade-color), transparent);
7877
+ }
7878
+
7879
+ :host(.has-left-fade)::before,
7880
+ :host(.has-right-fade)::after {
7881
+ opacity: 1;
7607
7882
  }
7608
7883
 
7609
7884
  .parts {
7610
7885
  display: flex;
7886
+ gap: var(--space-s);
7611
7887
  }
7612
7888
 
7613
7889
  .trailing {
@@ -7619,21 +7895,68 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7619
7895
  .crumb-segment {
7620
7896
  display: flex;
7621
7897
  align-items: center;
7898
+ flex-shrink: 0;
7899
+ background: var(--crumb-item-bg);
7900
+ border: var(--crumb-item-border);
7901
+ border-radius: var(--crumb-item-border-radius);
7902
+ }
7903
+
7904
+ .crumb-segment.leading-caret {
7905
+ flex-direction: row;
7906
+ }
7907
+
7908
+ .crumb-segment.leading-caret-only {
7909
+ flex-direction: row;
7910
+ padding: var(--crumb-item-padding);
7911
+ }
7912
+
7913
+ .crumb-segment.trailing-caret-only {
7914
+ flex-direction: row;
7915
+ padding: var(--crumb-item-padding);
7916
+ }
7917
+
7918
+ .crumb-segment.tickled {
7919
+ background: var(--crumb-item-bg-tickled);
7920
+ border-color: var(--crumb-item-border-tickled);
7921
+ color: var(--crumb-item-text-tickled);
7922
+ flex-shrink: 0;
7923
+ min-width: auto;
7924
+ }
7925
+
7926
+ .crumb-segment.truncatable {
7927
+ flex-shrink: 1;
7928
+ min-width: min(var(--crumb-truncate-min-width), auto);
7929
+ overflow: hidden;
7930
+ }
7931
+
7932
+ .crumb-segment.truncatable .label {
7933
+ overflow: hidden;
7934
+ text-overflow: ellipsis;
7935
+ white-space: nowrap;
7936
+ min-width: 0;
7622
7937
  }
7623
7938
 
7624
7939
  .label {
7625
7940
  cursor: pointer;
7626
- margin-right: var(--padding-s);
7627
7941
  white-space: nowrap;
7942
+ color: inherit;
7943
+ }
7944
+
7945
+ .crumb-segment.tickled .label {
7946
+ color: var(--crumb-item-text-tickled);
7947
+ overflow: visible;
7948
+ text-overflow: clip;
7949
+ }
7950
+
7951
+ .crumb-segment ixfx-menu-trigger {
7952
+ --ixfx-button-padding: var(--space-xs) 8px;
7628
7953
  }
7629
7954
 
7630
7955
  .caret {
7631
7956
  position: relative;
7632
- cursor: pointer;
7633
- padding: var(--padding-m);
7634
- margin-left: var(--padding-s);
7635
- margin-right: var(--padding-s);
7636
- color: var(--separator);
7957
+ cursor: pointer;
7958
+
7959
+ color: var(--crumb-separator);
7637
7960
  display: inline-flex;
7638
7961
  align-items: center;
7639
7962
  justify-content: center;
@@ -7671,15 +7994,15 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7671
7994
  }
7672
7995
 
7673
7996
  .separator {
7674
- margin-left: var(--padding-s);
7675
- margin-right: var(--padding-s);
7676
- color: var(--separator);
7997
+ margin-left: var(--crumb-item-gap);
7998
+ margin-right: var(--crumb-item-gap);
7999
+ color: var(--crumb-separator);
7677
8000
  }
7678
8001
 
7679
8002
  .separator-trigger {
7680
8003
  display: inline-flex;
7681
- margin-left: var(--padding-s);
7682
- margin-right: var(--padding-s);
8004
+ margin-left: var(--crumb-item-gap);
8005
+ margin-right: var(--crumb-item-gap);
7683
8006
  --_trigger-hover-bg: transparent;
7684
8007
  --_trigger-hover-opacity: 0.6;
7685
8008
  }
@@ -7687,7 +8010,7 @@ let CrumbNavigationElement = class CrumbNavigationElement extends i {
7687
8010
  .separator-trigger::part(base) {
7688
8011
  background: none;
7689
8012
  border: none;
7690
- color: var(--separator);
8013
+ color: var(--crumb-separator);
7691
8014
  padding: 0;
7692
8015
  cursor: pointer;
7693
8016
  font-size: inherit;
@@ -7752,13 +8075,37 @@ __decorate([property({
7752
8075
  attribute: `selection-filter`
7753
8076
  })], CrumbNavigationElement.prototype, "selectionFilter", void 0);
7754
8077
  __decorate([state()], CrumbNavigationElement.prototype, "_openIdx", void 0);
7755
- __decorate([state()], CrumbNavigationElement.prototype, "_isFocused", void 0);
7756
8078
  __decorate([state()], CrumbNavigationElement.prototype, "_menuOpen", void 0);
7757
8079
  __decorate([state()], CrumbNavigationElement.prototype, "_focusOnNarrowedText", void 0);
7758
8080
  __decorate([property({
7759
8081
  reflect: true,
7760
8082
  attribute: `input-mode`
7761
8083
  })], CrumbNavigationElement.prototype, "inputMode", void 0);
8084
+ __decorate([property({
8085
+ type: Boolean,
8086
+ reflect: true,
8087
+ attribute: `no-truncate`
8088
+ })], CrumbNavigationElement.prototype, "noTruncate", void 0);
8089
+ __decorate([property({
8090
+ type: Boolean,
8091
+ reflect: true,
8092
+ attribute: `show-suggestions`
8093
+ })], CrumbNavigationElement.prototype, "showSuggestions", void 0);
8094
+ __decorate([property({
8095
+ type: Boolean,
8096
+ reflect: true,
8097
+ attribute: `leading-caret`
8098
+ })], CrumbNavigationElement.prototype, "leadingCaret", void 0);
8099
+ __decorate([property({
8100
+ type: Boolean,
8101
+ reflect: true,
8102
+ attribute: `caret-embedded`
8103
+ })], CrumbNavigationElement.prototype, "caretEmbedded", void 0);
8104
+ __decorate([property({
8105
+ type: Boolean,
8106
+ reflect: true,
8107
+ attribute: `hideroot`
8108
+ })], CrumbNavigationElement.prototype, "hideRoot", void 0);
7762
8109
  CrumbNavigationElement = __decorate([safeCustomElement(`ixfx-crumb-navigation`)], CrumbNavigationElement);
7763
8110
  //#endregion
7764
8111
  //#region src/crumbs/crumb-path-controller.ts
@@ -12334,6 +12681,7 @@ let VerticalListElement = class VerticalListElement extends i {
12334
12681
  overflow: hidden;
12335
12682
  box-sizing: border-box;
12336
12683
  outline: none;
12684
+ --ixfx-list-item-padding: var(--ixfx-list-item-padding, var(--list-item-padding, 4px var(--space-m, 8px)));
12337
12685
  }
12338
12686
 
12339
12687
  :host(:focus-visible) {
@@ -12370,7 +12718,7 @@ let VerticalListElement = class VerticalListElement extends i {
12370
12718
  ::slotted(li) {
12371
12719
  display: flex;
12372
12720
  align-items: center;
12373
- padding: var(--list-item-padding, 4px var(--space-m, 8px));
12721
+ padding: var(--ixfx-list-item-padding);
12374
12722
  min-height: var(--list-item-height, 28px);
12375
12723
  cursor: pointer;
12376
12724
  user-select: none;
@@ -24277,7 +24625,7 @@ let TabListItemElement = class TabListItemElement extends i {
24277
24625
  --selected-tab-border-width: var(--ixfx-selected-tab-border-width, 1px);
24278
24626
  --selected-tab-weight: var(--ixfx-selected-tab-weight, bold);
24279
24627
  --selected-background: var(--ixfx-tab-selected-background, var(--surface-5));
24280
- --padding: var(--space-m);
24628
+ --tab-padding: var(--ixfx-tab-padding, var(--space-m));
24281
24629
  display: inline-flex;
24282
24630
  min-width: 0;
24283
24631
  user-select: none;
@@ -24288,7 +24636,7 @@ let TabListItemElement = class TabListItemElement extends i {
24288
24636
  display: flex;
24289
24637
  align-items: center;
24290
24638
  gap: var(--space-s, 4px);
24291
- padding: var(--padding) calc(2 * var(--padding));
24639
+ padding: var(--tab-padding) calc(2 * var(--tab-padding));
24292
24640
  min-width: 0;
24293
24641
  width: 100%;
24294
24642
  font-weight: 500;