@ixfx/components 0.4.4 → 0.5.0

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 (47) hide show
  1. package/bundle/index.d.ts +126 -3
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +560 -24
  4. package/bundle/index.js.map +1 -1
  5. package/bundle/style.css +1 -1
  6. package/dist/{colour-picker-BacNRabo.js → colour-picker-C3Ag4uh-.js} +2 -2
  7. package/dist/{colour-picker-BacNRabo.js.map → colour-picker-C3Ag4uh-.js.map} +1 -1
  8. package/dist/colour-picker.js +1 -1
  9. package/dist/{defaults-C3b9OWJD.js → defaults-DcE8RQJc.js} +19 -1
  10. package/dist/defaults-DcE8RQJc.js.map +1 -0
  11. package/dist/icons.js +1 -1
  12. package/dist/index.d.ts +127 -4
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +540 -27
  15. package/dist/index.js.map +1 -1
  16. package/dist/labelled-radial-input.d.ts +1 -1
  17. package/dist/polar-pad.d.ts +1 -1
  18. package/dist/{radial-input-DXMN55Nz.d.ts → radial-input-BhZeyyP-.d.ts} +2 -2
  19. package/dist/{radial-input-DXMN55Nz.d.ts.map → radial-input-BhZeyyP-.d.ts.map} +1 -1
  20. package/dist/radial-input.d.ts +1 -1
  21. package/dist/range-input.d.ts +1 -1
  22. package/dist/range.d.ts +1 -1
  23. package/dist/{slider-input-XppH3vcy.d.ts → slider-input-TidGhvXt.d.ts} +2 -2
  24. package/dist/{slider-input-XppH3vcy.d.ts.map → slider-input-TidGhvXt.d.ts.map} +1 -1
  25. package/dist/slider-input.d.ts +1 -1
  26. package/dist/style.css +1 -1
  27. package/dist/swipe.d.ts +1 -1
  28. package/dist/tab-list-BohmbvqF.d.ts.map +1 -1
  29. package/dist/{tab-list-DzknpdSo.js → tab-list-DhkVhRQc.js} +7 -2
  30. package/dist/{tab-list-DzknpdSo.js.map → tab-list-DhkVhRQc.js.map} +1 -1
  31. package/dist/tabs.js +1 -1
  32. package/dist/wheel-nudge-BlSCe_hD.js.map +1 -1
  33. package/dist/{wheel-nudge-DGrNzWuE.d.ts → wheel-nudge-DgMTRYlh.d.ts} +3 -3
  34. package/dist/wheel-nudge-DgMTRYlh.d.ts.map +1 -0
  35. package/dist/xy-pad.d.ts +1 -1
  36. package/docs-user/README.md +2 -0
  37. package/docs-user/button.md +48 -0
  38. package/docs-user/narrowed-text.md +33 -1
  39. package/docs-user/notifier.md +131 -0
  40. package/docs-user/plots.md +61 -1
  41. package/docs-user/slider-input.md +673 -0
  42. package/docs-user/tabs.md +12 -14
  43. package/docs-user/user-catalog.md +69 -4
  44. package/docs-user/vertical-list.md +75 -7
  45. package/package.json +3 -1
  46. package/dist/defaults-C3b9OWJD.js.map +0 -1
  47. package/dist/wheel-nudge-DGrNzWuE.d.ts.map +0 -1
package/bundle/index.js CHANGED
@@ -2858,6 +2858,21 @@ const ICON_CHECK = `<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="tr
2858
2858
  */
2859
2859
  const ICON_CLOSE = `<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>`;
2860
2860
  /**
2861
+ * Error / warning icon. Useful for error notifications.
2862
+ * fill="currentColor" so it inherits CSS color.
2863
+ */
2864
+ const ICON_ERROR = `<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>`;
2865
+ /**
2866
+ * Info icon. Useful for info notifications.
2867
+ * fill="currentColor" so it inherits CSS color.
2868
+ */
2869
+ const ICON_INFO = `<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg>`;
2870
+ /**
2871
+ * Warning icon. Useful for warning notifications.
2872
+ * fill="currentColor" so it inherits CSS color.
2873
+ */
2874
+ const ICON_WARNING = `<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>`;
2875
+ /**
2861
2876
  * Chevron pointing right. Used for split-button switcher toggle.
2862
2877
  * fill="currentColor" so it inherits CSS color.
2863
2878
  */
@@ -2868,6 +2883,9 @@ function registerDefaults() {
2868
2883
  registerIcon(`caret-right`, ICON_CARET_RIGHT);
2869
2884
  registerIcon(`check`, ICON_CHECK);
2870
2885
  registerIcon(`close`, ICON_CLOSE);
2886
+ registerIcon(`error`, ICON_ERROR);
2887
+ registerIcon(`info`, ICON_INFO);
2888
+ registerIcon(`warning`, ICON_WARNING);
2871
2889
  }
2872
2890
  //#endregion
2873
2891
  //#region src/icons/index.ts
@@ -3242,9 +3260,11 @@ let RadioGroup = class RadioGroup extends i {
3242
3260
  min-width: min-content;
3243
3261
  box-sizing: border-box;
3244
3262
  /* Default to looking like a cohesive control group */
3245
- background: var(--surface-3, transparent);
3263
+ background: var(--surface-4, transparent);
3264
+ background: oklch(from var(--surface-4) l calc(c*0.2) h / 0.4);
3265
+
3246
3266
  border: var(--border, none);
3247
- border-radius: var(--radius-m, 4px);
3267
+ border-radius: var(--radius-xl, 8px);
3248
3268
  }
3249
3269
 
3250
3270
  * {
@@ -3274,6 +3294,7 @@ let RadioGroup = class RadioGroup extends i {
3274
3294
  gap: var(--item-spacing, 0);
3275
3295
  overflow: hidden;
3276
3296
  border-radius: inherit;
3297
+ justify-content: center;
3277
3298
  }
3278
3299
 
3279
3300
  .radio-group.horizontal {
@@ -3315,13 +3336,13 @@ let RadioGroup = class RadioGroup extends i {
3315
3336
 
3316
3337
  .radio-item:hover:not([disabled]) {
3317
3338
  opacity: 0.9;
3318
- background: var(--surface-4, rgba(255,255,255,0.05));
3339
+ background: color-mix(in oklch, var(--surface-4) 80%, transparent);
3319
3340
  }
3320
3341
 
3321
3342
  .radio-item.selected {
3322
3343
  font-weight: bold;
3323
3344
  color: var(--color-fg-accent, var(--surface-2-text));
3324
- background: var(--surface-6, rgba(255,255,255,0.1));
3345
+ background: var(--surface-5, rgba(255,255,255,0.1));
3325
3346
  }
3326
3347
 
3327
3348
  .radio-item[disabled] {
@@ -11648,7 +11669,7 @@ let VerticalListElement = class VerticalListElement extends i {
11648
11669
  this._selectedItems = remapped;
11649
11670
  }
11650
11671
  this._syncItemStates();
11651
- }
11672
+ } else if (changed.has(`checkboxRenderer`) && this.interactionMode === `checked`) this._syncCheckboxes();
11652
11673
  }
11653
11674
  /** The most recently selected item, or `undefined` if nothing is selected. */
11654
11675
  get selectedItem() {
@@ -11844,6 +11865,7 @@ let VerticalListElement = class VerticalListElement extends i {
11844
11865
  }
11845
11866
  _syncCheckboxes() {
11846
11867
  const inCheckedMode = this.interactionMode === `checked`;
11868
+ const wantsCustom = typeof this.checkboxRenderer === `function`;
11847
11869
  if (inCheckedMode) for (const inner of Array.from(this.children)) {
11848
11870
  if (!inner.hasAttribute(`data-list-item`)) continue;
11849
11871
  if (inner.parentElement !== this) continue;
@@ -11868,24 +11890,70 @@ let VerticalListElement = class VerticalListElement extends i {
11868
11890
  this.#customWrappers.clear();
11869
11891
  }
11870
11892
  for (const el of this.querySelectorAll(`li`)) {
11871
- let cb = el.querySelector(`input.ixfx-list-checkbox`);
11872
- if (inCheckedMode) {
11873
- if (!cb) {
11874
- cb = document.createElement(`input`);
11875
- cb.type = `checkbox`;
11876
- cb.className = `ixfx-list-checkbox`;
11877
- cb.style.cssText = `flex-shrink:0;margin:0 6px 0 0;cursor:pointer;accent-color:var(--accent,#60a5fa);pointer-events:auto`;
11878
- cb.tabIndex = -1;
11879
- cb.addEventListener(`click`, (e) => {
11880
- e.stopPropagation();
11881
- this._toggleItem(el);
11882
- });
11883
- el.prepend(cb);
11884
- }
11885
- cb.checked = this._selectedItems.has(el);
11886
- } else if (cb) cb.remove();
11893
+ const existing = el.querySelector(`.ixfx-list-checkbox`);
11894
+ if (!inCheckedMode) {
11895
+ if (existing) existing.remove();
11896
+ continue;
11897
+ }
11898
+ const existingIsCustom = existing?.classList.contains(`ixfx-list-checkbox-custom`) ?? false;
11899
+ if (existing && existingIsCustom !== wantsCustom) existing.remove();
11900
+ let cb = el.querySelector(`.ixfx-list-checkbox`);
11901
+ if (!cb) {
11902
+ cb = wantsCustom ? this._createCustomCheckbox(el) : this._createNativeCheckboxCell(el);
11903
+ el.prepend(cb);
11904
+ }
11905
+ const checked = this._selectedItems.has(el);
11906
+ if (wantsCustom) {
11907
+ cb.setAttribute(`aria-checked`, String(checked));
11908
+ cb.toggleAttribute(`data-checked`, checked);
11909
+ if (this.checkboxRenderer) D(this.checkboxRenderer(checked), cb);
11910
+ } else {
11911
+ const input = cb.querySelector(`input.ixfx-list-checkbox`);
11912
+ if (input) input.checked = checked;
11913
+ }
11887
11914
  }
11888
11915
  }
11916
+ /**
11917
+ * Native checkbox cell: a span that stretches to the row's full cross-axis
11918
+ * (so the entire column height is a click target) with a real `<input>`
11919
+ * centred inside. The span owns the click; the input is purely visual and
11920
+ * has `pointer-events:none` so a single click produces one toggle.
11921
+ */
11922
+ _createNativeCheckboxCell(el) {
11923
+ const cell = document.createElement(`span`);
11924
+ cell.className = `ixfx-list-checkbox ixfx-list-checkbox-native`;
11925
+ cell.style.cssText = `display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;align-self:stretch;width:18px;margin:0 6px 0 0;cursor:pointer;pointer-events:auto`;
11926
+ cell.tabIndex = -1;
11927
+ cell.setAttribute(`role`, `checkbox`);
11928
+ cell.setAttribute(`part`, `checkbox`);
11929
+ cell.addEventListener(`click`, (e) => {
11930
+ e.stopPropagation();
11931
+ this._toggleItem(el);
11932
+ });
11933
+ const input = document.createElement(`input`);
11934
+ input.type = `checkbox`;
11935
+ input.className = `ixfx-list-checkbox`;
11936
+ input.style.cssText = `pointer-events:none;cursor:pointer;margin:0`;
11937
+ cell.appendChild(input);
11938
+ return cell;
11939
+ }
11940
+ /**
11941
+ * Custom checkbox cell: same shape as the native cell (a stretching span)
11942
+ * so behaviour is identical. The renderer's content goes inside.
11943
+ */
11944
+ _createCustomCheckbox(el) {
11945
+ const span = document.createElement(`span`);
11946
+ span.className = `ixfx-list-checkbox ixfx-list-checkbox-custom`;
11947
+ span.style.cssText = `display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;align-self:stretch;width:24px;margin:0 6px 0 0;cursor:pointer;pointer-events:auto`;
11948
+ span.tabIndex = -1;
11949
+ span.setAttribute(`role`, `checkbox`);
11950
+ span.setAttribute(`part`, `checkbox`);
11951
+ span.addEventListener(`click`, (e) => {
11952
+ e.stopPropagation();
11953
+ this._toggleItem(el);
11954
+ });
11955
+ return span;
11956
+ }
11889
11957
  _findListItem(path) {
11890
11958
  for (const target of path) {
11891
11959
  if (!(target instanceof Element)) continue;
@@ -11947,8 +12015,14 @@ let VerticalListElement = class VerticalListElement extends i {
11947
12015
  padding: 0;
11948
12016
  width: 100%;
11949
12017
  height: 100%;
12018
+ overflow-x: hidden;
11950
12019
  overflow-y: auto;
11951
12020
  box-sizing: border-box;
12021
+ /* Scrollbar theming: end users override these CSS variables to control
12022
+ the native scrollbar (scrollbar-width, scrollbar-gutter, scrollbar-color). */
12023
+ scrollbar-width: var(--list-scrollbar-width, auto);
12024
+ scrollbar-gutter: var(--list-scrollbar-gutter, auto);
12025
+ scrollbar-color: var(--list-scrollbar-color, auto);
11952
12026
  }
11953
12027
 
11954
12028
  ::slotted([hidden]) {
@@ -11978,6 +12052,25 @@ let VerticalListElement = class VerticalListElement extends i {
11978
12052
  outline: 1px solid transparent;
11979
12053
  }
11980
12054
 
12055
+ /* In checked mode, only the checkbox column is clickable; the row body
12056
+ gets a default cursor so it doesn't suggest otherwise. */
12057
+ :host([interaction-mode="checked"]) ::slotted(li),
12058
+ :host([interaction-mode="checked"]) ::slotted([data-list-item]) {
12059
+ cursor: default;
12060
+ }
12061
+
12062
+ /* Wrapper inserted around custom [data-list-item] elements in checked mode.
12063
+ Provides the flex layout so the injected checkbox can stretch to the
12064
+ row's full height via align-self:stretch. */
12065
+ .ixfx-custom-row {
12066
+ display: flex;
12067
+ align-items: center;
12068
+ min-height: var(--list-item-height, 28px);
12069
+ box-sizing: border-box;
12070
+ cursor: default;
12071
+ user-select: none;
12072
+ }
12073
+
11981
12074
  ::slotted([data-tickled]) {
11982
12075
  background: var(--item-bg-tickled);
11983
12076
  outline: var(--item-border-tickled);
@@ -12085,6 +12178,7 @@ __decorate([property({
12085
12178
  attribute: `interaction-mode`
12086
12179
  })], VerticalListElement.prototype, "interactionMode", void 0);
12087
12180
  __decorate([property({ attribute: false })], VerticalListElement.prototype, "filterPredicate", void 0);
12181
+ __decorate([property({ attribute: false })], VerticalListElement.prototype, "checkboxRenderer", void 0);
12088
12182
  __decorate([state()], VerticalListElement.prototype, "_selectedItems", void 0);
12089
12183
  __decorate([state()], VerticalListElement.prototype, "_searchVisible", void 0);
12090
12184
  __decorate([state()], VerticalListElement.prototype, "_searchQuery", void 0);
@@ -16643,6 +16737,441 @@ var MillerTreeController = class {
16643
16737
  }
16644
16738
  };
16645
16739
  //#endregion
16740
+ //#region src/notifier/notification-pill.ts
16741
+ let NotificationPillElement = class NotificationPillElement extends i {
16742
+ constructor(..._args) {
16743
+ super(..._args);
16744
+ this.notificationType = `info`;
16745
+ this.message = ``;
16746
+ this.dismissing = false;
16747
+ this.notificationId = ``;
16748
+ }
16749
+ #handleClick() {
16750
+ this.#emitTap();
16751
+ }
16752
+ #handleKeyDown(e) {
16753
+ if (e.key === `Enter` || e.key === ` `) {
16754
+ e.preventDefault();
16755
+ this.#emitTap();
16756
+ }
16757
+ }
16758
+ #emitTap() {
16759
+ this.dispatchEvent(new CustomEvent(`pill-tap`, {
16760
+ detail: { id: this.notificationId },
16761
+ bubbles: true,
16762
+ composed: true
16763
+ }));
16764
+ }
16765
+ #renderIcon() {
16766
+ if (!this.icon) return A;
16767
+ if (this.icon.startsWith(`<svg`)) return b`<span class="pill-icon">${unsafeHTML(this.icon)}</span>`;
16768
+ if (!getIcon(this.icon)) return A;
16769
+ return b`<ixfx-icon class="pill-icon" name="${this.icon}"></ixfx-icon>`;
16770
+ }
16771
+ render() {
16772
+ return b`
16773
+ <button
16774
+ type="button"
16775
+ class="pill"
16776
+ notification-type="${this.notificationType}"
16777
+ @click=${this.#handleClick}
16778
+ @keydown=${this.#handleKeyDown}
16779
+ aria-label="${this.message}"
16780
+ >
16781
+ ${this.#renderIcon()}
16782
+ <span class="pill-message">${this.message}</span>
16783
+ </button>
16784
+ `;
16785
+ }
16786
+ static {
16787
+ this.styles = [themeFallbacks, i$3`
16788
+ :host {
16789
+ display: block;
16790
+ }
16791
+
16792
+ .pill {
16793
+ appearance: none;
16794
+ cursor: pointer;
16795
+ font-family: var(--font-family, inherit);
16796
+ font-size: var(--text-m, 12px);
16797
+ padding: var(--space-s, 4px) var(--space-m, 8px);
16798
+ border-radius: var(--radius-l, 6px);
16799
+ border: 1px solid var(--surface-6, #666);
16800
+ display: inline-flex;
16801
+ align-items: center;
16802
+ gap: var(--space-s, 4px);
16803
+ max-width: 300px;
16804
+ box-shadow: var(--shadow-m);
16805
+ transition: opacity 0.2s ease-out, transform 0.2s ease-out;
16806
+ pointer-events: auto;
16807
+ }
16808
+
16809
+ :host([dismissing]) .pill {
16810
+ opacity: 0;
16811
+ transform: translateX(20px);
16812
+ }
16813
+
16814
+ .pill[notification-type="info"] {
16815
+ background: var(--surface-5, #555);
16816
+ color: var(--surface-5-text, #fff);
16817
+ }
16818
+
16819
+ .pill[notification-type="warning"] {
16820
+ background: color-mix(in oklch, oklch(65% 0.18 70) 30%, var(--surface-5));
16821
+ border-color: oklch(65% 0.18 70);
16822
+ color: var(--surface-5-text, #fff);
16823
+ }
16824
+
16825
+ .pill[notification-type="error"] {
16826
+ background: var(--error, #c00);
16827
+ border-color: var(--error, #c00);
16828
+ color: var(--error-text, #fff);
16829
+ }
16830
+
16831
+ .pill-icon {
16832
+ display: inline-flex;
16833
+ align-items: center;
16834
+ flex-shrink: 0;
16835
+ }
16836
+
16837
+ .pill-icon ixfx-icon {
16838
+ font-size: var(--text-m, 12px);
16839
+ }
16840
+
16841
+ .pill-message {
16842
+ white-space: nowrap;
16843
+ overflow: hidden;
16844
+ text-overflow: ellipsis;
16845
+ }
16846
+
16847
+ .pill:focus-visible {
16848
+ outline: 2px solid var(--accent, #4a9);
16849
+ outline-offset: 2px;
16850
+ }
16851
+
16852
+ @media (prefers-reduced-motion: reduce) {
16853
+ .pill {
16854
+ transition: opacity 0.1s ease-out;
16855
+ transform: none !important;
16856
+ }
16857
+ }
16858
+ `];
16859
+ }
16860
+ };
16861
+ __decorate([property({
16862
+ type: String,
16863
+ attribute: `notification-type`
16864
+ })], NotificationPillElement.prototype, "notificationType", void 0);
16865
+ __decorate([property({ type: String })], NotificationPillElement.prototype, "message", void 0);
16866
+ __decorate([property({ type: String })], NotificationPillElement.prototype, "icon", void 0);
16867
+ __decorate([property({
16868
+ type: Boolean,
16869
+ reflect: true
16870
+ })], NotificationPillElement.prototype, "dismissing", void 0);
16871
+ __decorate([property({
16872
+ type: String,
16873
+ attribute: `notification-id`
16874
+ })], NotificationPillElement.prototype, "notificationId", void 0);
16875
+ NotificationPillElement = __decorate([safeCustomElement(`ixfx-notification-pill`)], NotificationPillElement);
16876
+ //#endregion
16877
+ //#region src/notifier/pill-renderer.ts
16878
+ const ROW_SPACING = 8;
16879
+ const DEFAULT_ROW_HEIGHT = 40;
16880
+ const STACK_OFFSET_STEP = 4;
16881
+ const STACK_OPACITY_BASE = .7;
16882
+ const STACK_OPACITY_DECAY = .15;
16883
+ const STACK_OPACITY_MIN = .2;
16884
+ function edgesOf(position) {
16885
+ return {
16886
+ vertical: position.startsWith(`top`) ? `top` : `bottom`,
16887
+ horizontal: position.endsWith(`-left`) ? `left` : position.endsWith(`-right`) ? `right` : `middle`
16888
+ };
16889
+ }
16890
+ function anchorStyles(position, safeArea) {
16891
+ const { vertical, horizontal } = edgesOf(position);
16892
+ const vMargin = `calc(var(--space-l, 12px) + ${(vertical === `top` ? safeArea.top : safeArea.bottom) ?? 0}px)`;
16893
+ let css = `position: fixed; `;
16894
+ css += vertical === `top` ? `top: ${vMargin}; bottom: auto; ` : `bottom: ${vMargin}; top: auto; `;
16895
+ if (horizontal === `left`) {
16896
+ const hMargin = `calc(var(--space-l, 12px) + ${safeArea.left ?? 0}px)`;
16897
+ css += `left: ${hMargin}; right: auto;`;
16898
+ } else if (horizontal === `right`) {
16899
+ const hMargin = `calc(var(--space-l, 12px) + ${safeArea.right ?? 0}px)`;
16900
+ css += `right: ${hMargin}; left: auto;`;
16901
+ } else css += `left: 50%; right: auto;`;
16902
+ return css;
16903
+ }
16904
+ /** Entry-animation starting transform: pills slide in from the edge they're anchored to. */
16905
+ function entryTransform(edges) {
16906
+ const dy = edges.vertical === `top` ? -20 : 20;
16907
+ if (edges.horizontal === `middle`) return `translateX(-50%) translate(0, ${dy}px)`;
16908
+ return `translate(${edges.horizontal === `left` ? -20 : 20}px, ${dy}px)`;
16909
+ }
16910
+ function restingTransform(edges, dx, dy) {
16911
+ if (edges.horizontal === `middle`) return `translateX(-50%) translate(${dx}px, ${dy}px)`;
16912
+ return `translate(${dx}px, ${dy}px)`;
16913
+ }
16914
+ function createPillRenderer(options = {}) {
16915
+ const position = options.position ?? `top-right`;
16916
+ const queueDisplay = options.queueDisplay ?? `list`;
16917
+ const safeArea = options.safeArea ?? {};
16918
+ const edges = edgesOf(position);
16919
+ let container = null;
16920
+ let hooks = null;
16921
+ const pills = /* @__PURE__ */ new Map();
16922
+ let rowHeight = DEFAULT_ROW_HEIGHT;
16923
+ let measured = false;
16924
+ function ensureContainer() {
16925
+ if (container) return container;
16926
+ container = document.createElement(`div`);
16927
+ container.className = `ixfx-notification-container`;
16928
+ container.setAttribute(`role`, `region`);
16929
+ container.setAttribute(`aria-label`, `Notifications`);
16930
+ document.body.appendChild(container);
16931
+ return container;
16932
+ }
16933
+ function updateAttributes(el, notification) {
16934
+ el.setAttribute(`notification-type`, notification.type);
16935
+ el.setAttribute(`message`, notification.message);
16936
+ if (notification.icon) el.setAttribute(`icon`, notification.icon);
16937
+ else el.removeAttribute(`icon`);
16938
+ }
16939
+ function createPillElement(notification) {
16940
+ const el = document.createElement(`ixfx-notification-pill`);
16941
+ el.setAttribute(`notification-id`, notification.id);
16942
+ el.style.cssText = anchorStyles(position, safeArea);
16943
+ updateAttributes(el, notification);
16944
+ el.style.opacity = `0`;
16945
+ el.style.transform = entryTransform(edges);
16946
+ el.addEventListener(`pill-tap`, (e) => {
16947
+ const customEvent = e;
16948
+ hooks?.onTap(customEvent.detail.id);
16949
+ });
16950
+ return el;
16951
+ }
16952
+ function measureRowHeight(el) {
16953
+ if (measured) return;
16954
+ requestAnimationFrame(() => {
16955
+ const height = el.getBoundingClientRect().height;
16956
+ if (height > 0) {
16957
+ rowHeight = height;
16958
+ measured = true;
16959
+ }
16960
+ });
16961
+ }
16962
+ /** Positions every currently-visible pill for its rank in `order` (index 0 = front/oldest). */
16963
+ function applyLayout(order) {
16964
+ const total = order.length;
16965
+ order.forEach((notification, index) => {
16966
+ const entry = pills.get(notification.id);
16967
+ if (!entry) return;
16968
+ const el = entry.element;
16969
+ let dx = 0;
16970
+ let dy = 0;
16971
+ let opacity = 1;
16972
+ let pointerEvents = `auto`;
16973
+ let filter = `none`;
16974
+ if (queueDisplay === `list`) {
16975
+ const step = index * (rowHeight + ROW_SPACING);
16976
+ dy = edges.vertical === `top` ? step : -step;
16977
+ } else {
16978
+ const step = index * STACK_OFFSET_STEP;
16979
+ dy = edges.vertical === `top` ? step : -step;
16980
+ dx = edges.horizontal === `left` ? step : edges.horizontal === `right` ? -step : 0;
16981
+ if (index > 0) {
16982
+ const hintIndex = index - 1;
16983
+ opacity = Math.max(STACK_OPACITY_MIN, STACK_OPACITY_BASE - hintIndex * STACK_OPACITY_DECAY);
16984
+ pointerEvents = `none`;
16985
+ filter = `saturate(0.6)`;
16986
+ }
16987
+ }
16988
+ el.style.transition = `opacity 0.2s ease-out, transform 0.2s ease-out`;
16989
+ el.style.opacity = `${opacity}`;
16990
+ el.style.zIndex = `${total - index}`;
16991
+ el.style.pointerEvents = pointerEvents;
16992
+ el.style.filter = filter;
16993
+ el.style.transform = restingTransform(edges, dx, dy);
16994
+ });
16995
+ }
16996
+ function dismiss(id) {
16997
+ const entry = pills.get(id);
16998
+ if (!entry) return;
16999
+ const el = entry.element;
17000
+ el.setAttribute(`dismissing`, `true`);
17001
+ const cleanup = () => {
17002
+ el.removeEventListener(`transitionend`, cleanup);
17003
+ el.remove();
17004
+ pills.delete(id);
17005
+ if (container && pills.size === 0) {
17006
+ container.remove();
17007
+ container = null;
17008
+ measured = false;
17009
+ rowHeight = DEFAULT_ROW_HEIGHT;
17010
+ }
17011
+ };
17012
+ el.addEventListener(`transitionend`, cleanup);
17013
+ setTimeout(cleanup, 300);
17014
+ }
17015
+ function setNotifications(notifications, _queueSize) {
17016
+ const cont = ensureContainer();
17017
+ const idsToKeep = new Set(notifications.map((n) => n.id));
17018
+ for (const notification of notifications) {
17019
+ const existing = pills.get(notification.id);
17020
+ if (existing) {
17021
+ existing.notification = notification;
17022
+ updateAttributes(existing.element, notification);
17023
+ continue;
17024
+ }
17025
+ const el = createPillElement(notification);
17026
+ pills.set(notification.id, {
17027
+ element: el,
17028
+ notification
17029
+ });
17030
+ cont.appendChild(el);
17031
+ measureRowHeight(el);
17032
+ }
17033
+ for (const [id] of pills) if (!idsToKeep.has(id)) dismiss(id);
17034
+ requestAnimationFrame(() => applyLayout(notifications));
17035
+ }
17036
+ return {
17037
+ attach(newHooks) {
17038
+ hooks = newHooks;
17039
+ },
17040
+ detach() {
17041
+ for (const [id] of pills) dismiss(id);
17042
+ if (container) {
17043
+ container.remove();
17044
+ container = null;
17045
+ }
17046
+ },
17047
+ setNotifications
17048
+ };
17049
+ }
17050
+ //#endregion
17051
+ //#region src/notifier/manager.ts
17052
+ const options = {
17053
+ renderer: void 0,
17054
+ timeout: 4e3,
17055
+ timeoutError: 8e3,
17056
+ queue: true,
17057
+ queueSize: 3,
17058
+ tapBehaviour: `dismiss`,
17059
+ tapHandlerDefault: void 0
17060
+ };
17061
+ /** Rank-ordered, index 0 = oldest/first (next to be evicted or timed out). Length never exceeds queueSize (or 1 when queuing is disabled). */
17062
+ const visible = [];
17063
+ let renderer = null;
17064
+ let defaultRenderer = null;
17065
+ function getDefaultRenderer() {
17066
+ defaultRenderer ??= createPillRenderer({ position: `top-right` });
17067
+ return defaultRenderer;
17068
+ }
17069
+ function getRenderer() {
17070
+ return renderer ?? getDefaultRenderer();
17071
+ }
17072
+ function syncRenderer() {
17073
+ getRenderer().setNotifications([...visible], options.queueSize);
17074
+ }
17075
+ function clearTimer(managed) {
17076
+ if (managed.timer !== null) {
17077
+ clearTimeout(managed.timer);
17078
+ managed.timer = null;
17079
+ }
17080
+ }
17081
+ /**
17082
+ * A message shown alone takes the full timeout. A message that joined a
17083
+ * non-empty queue at arrival time is permanently "wasQueued" and gets
17084
+ * 1/queueSize of the timeout once it reaches the front. The timer for a
17085
+ * given message is only ever started once - when it first becomes front.
17086
+ */
17087
+ function startTimer(managed) {
17088
+ const base = managed.type === `error` ? options.timeoutError : options.timeout;
17089
+ const duration = managed.wasQueued ? base / options.queueSize : base;
17090
+ managed.timer = window.setTimeout(() => dismiss(managed.id), duration);
17091
+ }
17092
+ function ensureFrontTimer() {
17093
+ const front = visible[0];
17094
+ if (front && front.timer === null) startTimer(front);
17095
+ }
17096
+ const NotificationManager = {
17097
+ configure(opts) {
17098
+ if (opts.renderer !== void 0) {
17099
+ renderer?.detach?.();
17100
+ renderer = opts.renderer;
17101
+ renderer.attach({ onTap(id) {
17102
+ const notification = visible.find((n) => n.id === id);
17103
+ if (!notification) return;
17104
+ if (notification.onTap) notification.onTap(id);
17105
+ else options.tapHandlerDefault?.(notification);
17106
+ if (options.tapBehaviour === `dismiss`) dismiss(id);
17107
+ } });
17108
+ syncRenderer();
17109
+ }
17110
+ if (opts.timeout !== void 0) options.timeout = opts.timeout;
17111
+ if (opts.timeoutError !== void 0) options.timeoutError = opts.timeoutError;
17112
+ if (opts.queue !== void 0) options.queue = opts.queue;
17113
+ if (opts.queueSize !== void 0) {
17114
+ options.queueSize = Math.max(1, opts.queueSize);
17115
+ while (visible.length > options.queueSize) clearTimer(visible.shift());
17116
+ ensureFrontTimer();
17117
+ syncRenderer();
17118
+ }
17119
+ if (opts.tapBehaviour !== void 0) options.tapBehaviour = opts.tapBehaviour;
17120
+ if (opts.tapHandlerDefault !== void 0) options.tapHandlerDefault = opts.tapHandlerDefault;
17121
+ },
17122
+ notify(content) {
17123
+ const id = crypto.randomUUID();
17124
+ const now = Date.now();
17125
+ const icon = content.icon ?? (content.type === `error` ? `error` : void 0);
17126
+ const managed = {
17127
+ ...content,
17128
+ icon,
17129
+ id,
17130
+ createdAt: now,
17131
+ updatedAt: now,
17132
+ timer: null,
17133
+ wasQueued: false
17134
+ };
17135
+ if (options.queue) {
17136
+ if (visible.length >= options.queueSize) clearTimer(visible.shift());
17137
+ managed.wasQueued = visible.length > 0;
17138
+ visible.push(managed);
17139
+ } else {
17140
+ while (visible.length > 0) clearTimer(visible.pop());
17141
+ visible.push(managed);
17142
+ }
17143
+ ensureFrontTimer();
17144
+ syncRenderer();
17145
+ return id;
17146
+ },
17147
+ update(id, content) {
17148
+ const managed = visible.find((n) => n.id === id);
17149
+ if (!managed) return;
17150
+ Object.assign(managed, content);
17151
+ managed.updatedAt = Date.now();
17152
+ if (visible[0] === managed) {
17153
+ clearTimer(managed);
17154
+ startTimer(managed);
17155
+ }
17156
+ syncRenderer();
17157
+ },
17158
+ dismiss(id) {
17159
+ dismiss(id);
17160
+ },
17161
+ dismissAll() {
17162
+ for (const m of visible) clearTimer(m);
17163
+ visible.length = 0;
17164
+ syncRenderer();
17165
+ }
17166
+ };
17167
+ function dismiss(id) {
17168
+ const idx = visible.findIndex((m) => m.id === id);
17169
+ if (idx === -1) return;
17170
+ clearTimer(visible.splice(idx, 1)[0]);
17171
+ ensureFrontTimer();
17172
+ syncRenderer();
17173
+ }
17174
+ //#endregion
16646
17175
  //#region src/panel/panel-group.ts
16647
17176
  let PanelGroupElement = class PanelGroupElement extends i {
16648
17177
  constructor(..._args) {
@@ -23145,7 +23674,7 @@ let TabListItemElement = class TabListItemElement extends i {
23145
23674
  prominenceStyles,
23146
23675
  i$3`
23147
23676
  :host {
23148
- --selected-colour: var(--accent);
23677
+ --selected-colour: var(--surface-2-text);
23149
23678
  --selected-border-width: 1px;
23150
23679
  --padding: var(--space-m);
23151
23680
  display: inline-flex;
@@ -23171,6 +23700,8 @@ let TabListItemElement = class TabListItemElement extends i {
23171
23700
  border-bottom-color: var(--indicator-color-bottom, transparent);
23172
23701
  border-left-color: var(--indicator-color-left, transparent);
23173
23702
  border-right-color: var(--indicator-color-right, transparent);
23703
+ background: var(--surface-5);
23704
+ font-weight: bold;
23174
23705
  }
23175
23706
 
23176
23707
  .label {
@@ -23747,9 +24278,14 @@ let TabListElement = class TabListElement extends i {
23747
24278
  :host {
23748
24279
  display: inline-block;
23749
24280
  --activated-colour: var(--accent);
23750
- --colour: var(--surface-4);
24281
+ --colour: var(--surface-3-text);
23751
24282
  --scroll-max-height: 200px;
23752
24283
  --allow-overflow: hidden;
24284
+ color: var(--colour);
24285
+ border: var(--border, none);
24286
+ border-color: var(--surface-5);
24287
+ border-radius: var(--radius-l, 8px) var(--radius-l, 8px) 0 0;
24288
+
23753
24289
  }
23754
24290
  :host(.scrollable) {
23755
24291
  max-height: var(--scroll-max-height);
@@ -26523,6 +27059,6 @@ function init() {
26523
27059
  console.log(`Init`);
26524
27060
  }
26525
27061
  //#endregion
26526
- export { AcTextElement, AcTokenElement, ButtonElement, Checkbox, ColourPicker, ColourPickerPopup, CrumbNavigationElement, CrumbPathController, CrumbPathElement, DataController, DataDisplayComponent, EditableLabel, EditableNumber, GroupedItemListerElement, ICON_CARET_RIGHT, ICON_CHECK, ICON_CHEVRON_DOWN, ICON_CLOSE, IncrSearchTreeController as IncrSearchMillerController, IncrSearchTreeController, IxfxHexEditorElement, IxfxIconElement, IxfxTimelineElement, LabelledRadialInput, LabelledRangeInput, LedElement, menu_exports as Menu, MillerBaseElement, MillerListElement, MillerTreeController, NarrowedTextElement, PanelElement, PanelGroupElement, PlotDataSeries, PlotHistogram, PlotMultiAxis, PlotSingleAxis, PlotXyAxis, PolarPad, RadialInputElement, RadioGroup, RangeElement, RangeInput, RangeMultiElement, SelectHorizontalElement, SliderInputElement, SnapContainer, SplitButton, SplitLayoutElement, SwipeElement, TabListElement, TabListItemElement, TabPanelElement, TabPanelsElement, TimelineController, TimelineTrackTabElement, TooltipElement, TransitoryLabelElement, TreeBaseElement, TreeController, TreeDataModel, TreeListElement, VerticalListElement, XyPad, applyAgeModification, applyHighlights, attachTimelineInteractions, buildHighlightTargets, clearHighlights, collectDataResult, computeColour, createAcCompletionProvider, createElementIncrSearch, createFuzzySearch, createIncrSearch, createTreeListAdapter, createVerticalListAdapter, getIcon, hasIcon, iconBus, init, monitorIndeterminate, parseColourScaleAttr, readBaseColour, registerIcon, resolveCssVars, resolveGrid, svgToDataUri, timelineStyles };
27062
+ export { AcTextElement, AcTokenElement, ButtonElement, Checkbox, ColourPicker, ColourPickerPopup, CrumbNavigationElement, CrumbPathController, CrumbPathElement, DataController, DataDisplayComponent, EditableLabel, EditableNumber, GroupedItemListerElement, ICON_CARET_RIGHT, ICON_CHECK, ICON_CHEVRON_DOWN, ICON_CLOSE, IncrSearchTreeController as IncrSearchMillerController, IncrSearchTreeController, IxfxHexEditorElement, IxfxIconElement, IxfxTimelineElement, LabelledRadialInput, LabelledRangeInput, LedElement, menu_exports as Menu, MillerBaseElement, MillerListElement, MillerTreeController, NarrowedTextElement, NotificationManager, NotificationPillElement, PanelElement, PanelGroupElement, PlotDataSeries, PlotHistogram, PlotMultiAxis, PlotSingleAxis, PlotXyAxis, PolarPad, RadialInputElement, RadioGroup, RangeElement, RangeInput, RangeMultiElement, SelectHorizontalElement, SliderInputElement, SnapContainer, SplitButton, SplitLayoutElement, SwipeElement, TabListElement, TabListItemElement, TabPanelElement, TabPanelsElement, TimelineController, TimelineTrackTabElement, TooltipElement, TransitoryLabelElement, TreeBaseElement, TreeController, TreeDataModel, TreeListElement, VerticalListElement, XyPad, applyAgeModification, applyHighlights, attachTimelineInteractions, buildHighlightTargets, clearHighlights, collectDataResult, computeColour, createAcCompletionProvider, createElementIncrSearch, createFuzzySearch, createIncrSearch, createPillRenderer, createTreeListAdapter, createVerticalListAdapter, getIcon, hasIcon, iconBus, init, monitorIndeterminate, parseColourScaleAttr, readBaseColour, registerIcon, resolveCssVars, resolveGrid, svgToDataUri, timelineStyles };
26527
27063
 
26528
27064
  //# sourceMappingURL=index.js.map