@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/dist/index.js CHANGED
@@ -6,14 +6,14 @@ import { a as svgToDataUri, i as registerIcon, n as hasIcon, r as iconBus, t as
6
6
  import { t as IxfxIconElement } from "./icon-B22XVlCt.js";
7
7
  import { AcTextElement } from "./ac-text.js";
8
8
  import { a as createFuzzySearch, i as createAcCompletionProvider, n as IncrSearchTreeController, r as createIncrSearch, t as createElementIncrSearch } from "./incr-search-2pz21xtR.js";
9
- import { i as ICON_CLOSE, n as ICON_CHECK, r as ICON_CHEVRON_DOWN, t as ICON_CARET_RIGHT } from "./defaults-C3b9OWJD.js";
9
+ import { i as ICON_CLOSE, n as ICON_CHECK, r as ICON_CHEVRON_DOWN, t as ICON_CARET_RIGHT } from "./defaults-DcE8RQJc.js";
10
10
  import "./icons.js";
11
11
  import { t as prominenceStyles } from "./prominence-DlFShic3.js";
12
12
  import { n as buttonStyles, t as ButtonElement } from "./button-DxYee1ZH.js";
13
13
  import { t as popupSurfaceStyles } from "./popup-DiWMxsWj.js";
14
14
  import "./menu-item-NUKwJ4s5.js";
15
15
  import { Checkbox } from "./checkbox.js";
16
- import { n as ColourPicker, t as ColourPickerPopup } from "./colour-picker-BacNRabo.js";
16
+ import { n as ColourPicker, t as ColourPickerPopup } from "./colour-picker-C3Ag4uh-.js";
17
17
  import { LabelledRangeInput } from "./labelled-range-input.js";
18
18
  import { t as collectDataResult } from "./data-provider-35TyWclX.js";
19
19
  import { i as TreeDataModel, n as TreeListElement, r as TreeBaseElement, t as TreeController } from "./tree-BnvzhsAw.js";
@@ -40,10 +40,10 @@ import { t as SliderInputElement } from "./slider-input-DFpG23bS.js";
40
40
  import { SnapContainer } from "./snap-container.js";
41
41
  import { SplitLayoutElement } from "./split-layout.js";
42
42
  import { SwipeElement } from "./swipe.js";
43
- import { t as TabListElement } from "./tab-list-DzknpdSo.js";
43
+ import { t as TabListElement } from "./tab-list-DhkVhRQc.js";
44
44
  import { a as resolveCssVars, i as attachTimelineInteractions, n as TimelineTrackTabElement, o as resolveGrid, r as timelineStyles, s as TimelineController, t as IxfxTimelineElement } from "./timeline-B2fgDL4U.js";
45
45
  import { XyPad } from "./xy-pad.js";
46
- import { LitElement, css, html, nothing } from "lit";
46
+ import { LitElement, css, html, nothing, render } from "lit";
47
47
  import { eventOptions, property, query, queryAssignedElements, state } from "lit/decorators.js";
48
48
  import { unsafeHTML } from "lit/directives/unsafe-html.js";
49
49
  import { Trees } from "@ixfx/collections";
@@ -1001,9 +1001,11 @@ let RadioGroup = class RadioGroup extends LitElement {
1001
1001
  min-width: min-content;
1002
1002
  box-sizing: border-box;
1003
1003
  /* Default to looking like a cohesive control group */
1004
- background: var(--surface-3, transparent);
1004
+ background: var(--surface-4, transparent);
1005
+ background: oklch(from var(--surface-4) l calc(c*0.2) h / 0.4);
1006
+
1005
1007
  border: var(--border, none);
1006
- border-radius: var(--radius-m, 4px);
1008
+ border-radius: var(--radius-xl, 8px);
1007
1009
  }
1008
1010
 
1009
1011
  * {
@@ -1033,6 +1035,7 @@ let RadioGroup = class RadioGroup extends LitElement {
1033
1035
  gap: var(--item-spacing, 0);
1034
1036
  overflow: hidden;
1035
1037
  border-radius: inherit;
1038
+ justify-content: center;
1036
1039
  }
1037
1040
 
1038
1041
  .radio-group.horizontal {
@@ -1074,13 +1077,13 @@ let RadioGroup = class RadioGroup extends LitElement {
1074
1077
 
1075
1078
  .radio-item:hover:not([disabled]) {
1076
1079
  opacity: 0.9;
1077
- background: var(--surface-4, rgba(255,255,255,0.05));
1080
+ background: color-mix(in oklch, var(--surface-4) 80%, transparent);
1078
1081
  }
1079
1082
 
1080
1083
  .radio-item.selected {
1081
1084
  font-weight: bold;
1082
1085
  color: var(--color-fg-accent, var(--surface-2-text));
1083
- background: var(--surface-6, rgba(255,255,255,0.1));
1086
+ background: var(--surface-5, rgba(255,255,255,0.1));
1084
1087
  }
1085
1088
 
1086
1089
  .radio-item[disabled] {
@@ -3356,7 +3359,7 @@ let VerticalListElement = class VerticalListElement extends LitElement {
3356
3359
  this._selectedItems = remapped;
3357
3360
  }
3358
3361
  this._syncItemStates();
3359
- }
3362
+ } else if (changed.has(`checkboxRenderer`) && this.interactionMode === `checked`) this._syncCheckboxes();
3360
3363
  }
3361
3364
  /** The most recently selected item, or `undefined` if nothing is selected. */
3362
3365
  get selectedItem() {
@@ -3552,6 +3555,7 @@ let VerticalListElement = class VerticalListElement extends LitElement {
3552
3555
  }
3553
3556
  _syncCheckboxes() {
3554
3557
  const inCheckedMode = this.interactionMode === `checked`;
3558
+ const wantsCustom = typeof this.checkboxRenderer === `function`;
3555
3559
  if (inCheckedMode) for (const inner of Array.from(this.children)) {
3556
3560
  if (!inner.hasAttribute(`data-list-item`)) continue;
3557
3561
  if (inner.parentElement !== this) continue;
@@ -3576,24 +3580,70 @@ let VerticalListElement = class VerticalListElement extends LitElement {
3576
3580
  this.#customWrappers.clear();
3577
3581
  }
3578
3582
  for (const el of this.querySelectorAll(`li`)) {
3579
- let cb = el.querySelector(`input.ixfx-list-checkbox`);
3580
- if (inCheckedMode) {
3581
- if (!cb) {
3582
- cb = document.createElement(`input`);
3583
- cb.type = `checkbox`;
3584
- cb.className = `ixfx-list-checkbox`;
3585
- cb.style.cssText = `flex-shrink:0;margin:0 6px 0 0;cursor:pointer;accent-color:var(--accent,#60a5fa);pointer-events:auto`;
3586
- cb.tabIndex = -1;
3587
- cb.addEventListener(`click`, (e) => {
3588
- e.stopPropagation();
3589
- this._toggleItem(el);
3590
- });
3591
- el.prepend(cb);
3592
- }
3593
- cb.checked = this._selectedItems.has(el);
3594
- } else if (cb) cb.remove();
3583
+ const existing = el.querySelector(`.ixfx-list-checkbox`);
3584
+ if (!inCheckedMode) {
3585
+ if (existing) existing.remove();
3586
+ continue;
3587
+ }
3588
+ const existingIsCustom = existing?.classList.contains(`ixfx-list-checkbox-custom`) ?? false;
3589
+ if (existing && existingIsCustom !== wantsCustom) existing.remove();
3590
+ let cb = el.querySelector(`.ixfx-list-checkbox`);
3591
+ if (!cb) {
3592
+ cb = wantsCustom ? this._createCustomCheckbox(el) : this._createNativeCheckboxCell(el);
3593
+ el.prepend(cb);
3594
+ }
3595
+ const checked = this._selectedItems.has(el);
3596
+ if (wantsCustom) {
3597
+ cb.setAttribute(`aria-checked`, String(checked));
3598
+ cb.toggleAttribute(`data-checked`, checked);
3599
+ if (this.checkboxRenderer) render(this.checkboxRenderer(checked), cb);
3600
+ } else {
3601
+ const input = cb.querySelector(`input.ixfx-list-checkbox`);
3602
+ if (input) input.checked = checked;
3603
+ }
3595
3604
  }
3596
3605
  }
3606
+ /**
3607
+ * Native checkbox cell: a span that stretches to the row's full cross-axis
3608
+ * (so the entire column height is a click target) with a real `<input>`
3609
+ * centred inside. The span owns the click; the input is purely visual and
3610
+ * has `pointer-events:none` so a single click produces one toggle.
3611
+ */
3612
+ _createNativeCheckboxCell(el) {
3613
+ const cell = document.createElement(`span`);
3614
+ cell.className = `ixfx-list-checkbox ixfx-list-checkbox-native`;
3615
+ 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`;
3616
+ cell.tabIndex = -1;
3617
+ cell.setAttribute(`role`, `checkbox`);
3618
+ cell.setAttribute(`part`, `checkbox`);
3619
+ cell.addEventListener(`click`, (e) => {
3620
+ e.stopPropagation();
3621
+ this._toggleItem(el);
3622
+ });
3623
+ const input = document.createElement(`input`);
3624
+ input.type = `checkbox`;
3625
+ input.className = `ixfx-list-checkbox`;
3626
+ input.style.cssText = `pointer-events:none;cursor:pointer;margin:0`;
3627
+ cell.appendChild(input);
3628
+ return cell;
3629
+ }
3630
+ /**
3631
+ * Custom checkbox cell: same shape as the native cell (a stretching span)
3632
+ * so behaviour is identical. The renderer's content goes inside.
3633
+ */
3634
+ _createCustomCheckbox(el) {
3635
+ const span = document.createElement(`span`);
3636
+ span.className = `ixfx-list-checkbox ixfx-list-checkbox-custom`;
3637
+ 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`;
3638
+ span.tabIndex = -1;
3639
+ span.setAttribute(`role`, `checkbox`);
3640
+ span.setAttribute(`part`, `checkbox`);
3641
+ span.addEventListener(`click`, (e) => {
3642
+ e.stopPropagation();
3643
+ this._toggleItem(el);
3644
+ });
3645
+ return span;
3646
+ }
3597
3647
  _findListItem(path) {
3598
3648
  for (const target of path) {
3599
3649
  if (!(target instanceof Element)) continue;
@@ -3655,8 +3705,14 @@ let VerticalListElement = class VerticalListElement extends LitElement {
3655
3705
  padding: 0;
3656
3706
  width: 100%;
3657
3707
  height: 100%;
3708
+ overflow-x: hidden;
3658
3709
  overflow-y: auto;
3659
3710
  box-sizing: border-box;
3711
+ /* Scrollbar theming: end users override these CSS variables to control
3712
+ the native scrollbar (scrollbar-width, scrollbar-gutter, scrollbar-color). */
3713
+ scrollbar-width: var(--list-scrollbar-width, auto);
3714
+ scrollbar-gutter: var(--list-scrollbar-gutter, auto);
3715
+ scrollbar-color: var(--list-scrollbar-color, auto);
3660
3716
  }
3661
3717
 
3662
3718
  ::slotted([hidden]) {
@@ -3686,6 +3742,25 @@ let VerticalListElement = class VerticalListElement extends LitElement {
3686
3742
  outline: 1px solid transparent;
3687
3743
  }
3688
3744
 
3745
+ /* In checked mode, only the checkbox column is clickable; the row body
3746
+ gets a default cursor so it doesn't suggest otherwise. */
3747
+ :host([interaction-mode="checked"]) ::slotted(li),
3748
+ :host([interaction-mode="checked"]) ::slotted([data-list-item]) {
3749
+ cursor: default;
3750
+ }
3751
+
3752
+ /* Wrapper inserted around custom [data-list-item] elements in checked mode.
3753
+ Provides the flex layout so the injected checkbox can stretch to the
3754
+ row's full height via align-self:stretch. */
3755
+ .ixfx-custom-row {
3756
+ display: flex;
3757
+ align-items: center;
3758
+ min-height: var(--list-item-height, 28px);
3759
+ box-sizing: border-box;
3760
+ cursor: default;
3761
+ user-select: none;
3762
+ }
3763
+
3689
3764
  ::slotted([data-tickled]) {
3690
3765
  background: var(--item-bg-tickled);
3691
3766
  outline: var(--item-border-tickled);
@@ -3793,6 +3868,7 @@ __decorate([property({
3793
3868
  attribute: `interaction-mode`
3794
3869
  })], VerticalListElement.prototype, "interactionMode", void 0);
3795
3870
  __decorate([property({ attribute: false })], VerticalListElement.prototype, "filterPredicate", void 0);
3871
+ __decorate([property({ attribute: false })], VerticalListElement.prototype, "checkboxRenderer", void 0);
3796
3872
  __decorate([state()], VerticalListElement.prototype, "_selectedItems", void 0);
3797
3873
  __decorate([state()], VerticalListElement.prototype, "_searchVisible", void 0);
3798
3874
  __decorate([state()], VerticalListElement.prototype, "_searchQuery", void 0);
@@ -3928,6 +4004,441 @@ function createVerticalListAdapter(renderItem, options) {
3928
4004
  return adapter;
3929
4005
  }
3930
4006
  //#endregion
4007
+ //#region src/notifier/notification-pill.ts
4008
+ let NotificationPillElement = class NotificationPillElement extends LitElement {
4009
+ constructor(..._args) {
4010
+ super(..._args);
4011
+ this.notificationType = `info`;
4012
+ this.message = ``;
4013
+ this.dismissing = false;
4014
+ this.notificationId = ``;
4015
+ }
4016
+ #handleClick() {
4017
+ this.#emitTap();
4018
+ }
4019
+ #handleKeyDown(e) {
4020
+ if (e.key === `Enter` || e.key === ` `) {
4021
+ e.preventDefault();
4022
+ this.#emitTap();
4023
+ }
4024
+ }
4025
+ #emitTap() {
4026
+ this.dispatchEvent(new CustomEvent(`pill-tap`, {
4027
+ detail: { id: this.notificationId },
4028
+ bubbles: true,
4029
+ composed: true
4030
+ }));
4031
+ }
4032
+ #renderIcon() {
4033
+ if (!this.icon) return nothing;
4034
+ if (this.icon.startsWith(`<svg`)) return html`<span class="pill-icon">${unsafeHTML(this.icon)}</span>`;
4035
+ if (!getIcon(this.icon)) return nothing;
4036
+ return html`<ixfx-icon class="pill-icon" name="${this.icon}"></ixfx-icon>`;
4037
+ }
4038
+ render() {
4039
+ return html`
4040
+ <button
4041
+ type="button"
4042
+ class="pill"
4043
+ notification-type="${this.notificationType}"
4044
+ @click=${this.#handleClick}
4045
+ @keydown=${this.#handleKeyDown}
4046
+ aria-label="${this.message}"
4047
+ >
4048
+ ${this.#renderIcon()}
4049
+ <span class="pill-message">${this.message}</span>
4050
+ </button>
4051
+ `;
4052
+ }
4053
+ static {
4054
+ this.styles = [themeFallbacks, css`
4055
+ :host {
4056
+ display: block;
4057
+ }
4058
+
4059
+ .pill {
4060
+ appearance: none;
4061
+ cursor: pointer;
4062
+ font-family: var(--font-family, inherit);
4063
+ font-size: var(--text-m, 12px);
4064
+ padding: var(--space-s, 4px) var(--space-m, 8px);
4065
+ border-radius: var(--radius-l, 6px);
4066
+ border: 1px solid var(--surface-6, #666);
4067
+ display: inline-flex;
4068
+ align-items: center;
4069
+ gap: var(--space-s, 4px);
4070
+ max-width: 300px;
4071
+ box-shadow: var(--shadow-m);
4072
+ transition: opacity 0.2s ease-out, transform 0.2s ease-out;
4073
+ pointer-events: auto;
4074
+ }
4075
+
4076
+ :host([dismissing]) .pill {
4077
+ opacity: 0;
4078
+ transform: translateX(20px);
4079
+ }
4080
+
4081
+ .pill[notification-type="info"] {
4082
+ background: var(--surface-5, #555);
4083
+ color: var(--surface-5-text, #fff);
4084
+ }
4085
+
4086
+ .pill[notification-type="warning"] {
4087
+ background: color-mix(in oklch, oklch(65% 0.18 70) 30%, var(--surface-5));
4088
+ border-color: oklch(65% 0.18 70);
4089
+ color: var(--surface-5-text, #fff);
4090
+ }
4091
+
4092
+ .pill[notification-type="error"] {
4093
+ background: var(--error, #c00);
4094
+ border-color: var(--error, #c00);
4095
+ color: var(--error-text, #fff);
4096
+ }
4097
+
4098
+ .pill-icon {
4099
+ display: inline-flex;
4100
+ align-items: center;
4101
+ flex-shrink: 0;
4102
+ }
4103
+
4104
+ .pill-icon ixfx-icon {
4105
+ font-size: var(--text-m, 12px);
4106
+ }
4107
+
4108
+ .pill-message {
4109
+ white-space: nowrap;
4110
+ overflow: hidden;
4111
+ text-overflow: ellipsis;
4112
+ }
4113
+
4114
+ .pill:focus-visible {
4115
+ outline: 2px solid var(--accent, #4a9);
4116
+ outline-offset: 2px;
4117
+ }
4118
+
4119
+ @media (prefers-reduced-motion: reduce) {
4120
+ .pill {
4121
+ transition: opacity 0.1s ease-out;
4122
+ transform: none !important;
4123
+ }
4124
+ }
4125
+ `];
4126
+ }
4127
+ };
4128
+ __decorate([property({
4129
+ type: String,
4130
+ attribute: `notification-type`
4131
+ })], NotificationPillElement.prototype, "notificationType", void 0);
4132
+ __decorate([property({ type: String })], NotificationPillElement.prototype, "message", void 0);
4133
+ __decorate([property({ type: String })], NotificationPillElement.prototype, "icon", void 0);
4134
+ __decorate([property({
4135
+ type: Boolean,
4136
+ reflect: true
4137
+ })], NotificationPillElement.prototype, "dismissing", void 0);
4138
+ __decorate([property({
4139
+ type: String,
4140
+ attribute: `notification-id`
4141
+ })], NotificationPillElement.prototype, "notificationId", void 0);
4142
+ NotificationPillElement = __decorate([safeCustomElement(`ixfx-notification-pill`)], NotificationPillElement);
4143
+ //#endregion
4144
+ //#region src/notifier/pill-renderer.ts
4145
+ const ROW_SPACING = 8;
4146
+ const DEFAULT_ROW_HEIGHT = 40;
4147
+ const STACK_OFFSET_STEP = 4;
4148
+ const STACK_OPACITY_BASE = .7;
4149
+ const STACK_OPACITY_DECAY = .15;
4150
+ const STACK_OPACITY_MIN = .2;
4151
+ function edgesOf(position) {
4152
+ return {
4153
+ vertical: position.startsWith(`top`) ? `top` : `bottom`,
4154
+ horizontal: position.endsWith(`-left`) ? `left` : position.endsWith(`-right`) ? `right` : `middle`
4155
+ };
4156
+ }
4157
+ function anchorStyles(position, safeArea) {
4158
+ const { vertical, horizontal } = edgesOf(position);
4159
+ const vMargin = `calc(var(--space-l, 12px) + ${(vertical === `top` ? safeArea.top : safeArea.bottom) ?? 0}px)`;
4160
+ let css = `position: fixed; `;
4161
+ css += vertical === `top` ? `top: ${vMargin}; bottom: auto; ` : `bottom: ${vMargin}; top: auto; `;
4162
+ if (horizontal === `left`) {
4163
+ const hMargin = `calc(var(--space-l, 12px) + ${safeArea.left ?? 0}px)`;
4164
+ css += `left: ${hMargin}; right: auto;`;
4165
+ } else if (horizontal === `right`) {
4166
+ const hMargin = `calc(var(--space-l, 12px) + ${safeArea.right ?? 0}px)`;
4167
+ css += `right: ${hMargin}; left: auto;`;
4168
+ } else css += `left: 50%; right: auto;`;
4169
+ return css;
4170
+ }
4171
+ /** Entry-animation starting transform: pills slide in from the edge they're anchored to. */
4172
+ function entryTransform(edges) {
4173
+ const dy = edges.vertical === `top` ? -20 : 20;
4174
+ if (edges.horizontal === `middle`) return `translateX(-50%) translate(0, ${dy}px)`;
4175
+ return `translate(${edges.horizontal === `left` ? -20 : 20}px, ${dy}px)`;
4176
+ }
4177
+ function restingTransform(edges, dx, dy) {
4178
+ if (edges.horizontal === `middle`) return `translateX(-50%) translate(${dx}px, ${dy}px)`;
4179
+ return `translate(${dx}px, ${dy}px)`;
4180
+ }
4181
+ function createPillRenderer(options = {}) {
4182
+ const position = options.position ?? `top-right`;
4183
+ const queueDisplay = options.queueDisplay ?? `list`;
4184
+ const safeArea = options.safeArea ?? {};
4185
+ const edges = edgesOf(position);
4186
+ let container = null;
4187
+ let hooks = null;
4188
+ const pills = /* @__PURE__ */ new Map();
4189
+ let rowHeight = DEFAULT_ROW_HEIGHT;
4190
+ let measured = false;
4191
+ function ensureContainer() {
4192
+ if (container) return container;
4193
+ container = document.createElement(`div`);
4194
+ container.className = `ixfx-notification-container`;
4195
+ container.setAttribute(`role`, `region`);
4196
+ container.setAttribute(`aria-label`, `Notifications`);
4197
+ document.body.appendChild(container);
4198
+ return container;
4199
+ }
4200
+ function updateAttributes(el, notification) {
4201
+ el.setAttribute(`notification-type`, notification.type);
4202
+ el.setAttribute(`message`, notification.message);
4203
+ if (notification.icon) el.setAttribute(`icon`, notification.icon);
4204
+ else el.removeAttribute(`icon`);
4205
+ }
4206
+ function createPillElement(notification) {
4207
+ const el = document.createElement(`ixfx-notification-pill`);
4208
+ el.setAttribute(`notification-id`, notification.id);
4209
+ el.style.cssText = anchorStyles(position, safeArea);
4210
+ updateAttributes(el, notification);
4211
+ el.style.opacity = `0`;
4212
+ el.style.transform = entryTransform(edges);
4213
+ el.addEventListener(`pill-tap`, (e) => {
4214
+ const customEvent = e;
4215
+ hooks?.onTap(customEvent.detail.id);
4216
+ });
4217
+ return el;
4218
+ }
4219
+ function measureRowHeight(el) {
4220
+ if (measured) return;
4221
+ requestAnimationFrame(() => {
4222
+ const height = el.getBoundingClientRect().height;
4223
+ if (height > 0) {
4224
+ rowHeight = height;
4225
+ measured = true;
4226
+ }
4227
+ });
4228
+ }
4229
+ /** Positions every currently-visible pill for its rank in `order` (index 0 = front/oldest). */
4230
+ function applyLayout(order) {
4231
+ const total = order.length;
4232
+ order.forEach((notification, index) => {
4233
+ const entry = pills.get(notification.id);
4234
+ if (!entry) return;
4235
+ const el = entry.element;
4236
+ let dx = 0;
4237
+ let dy = 0;
4238
+ let opacity = 1;
4239
+ let pointerEvents = `auto`;
4240
+ let filter = `none`;
4241
+ if (queueDisplay === `list`) {
4242
+ const step = index * (rowHeight + ROW_SPACING);
4243
+ dy = edges.vertical === `top` ? step : -step;
4244
+ } else {
4245
+ const step = index * STACK_OFFSET_STEP;
4246
+ dy = edges.vertical === `top` ? step : -step;
4247
+ dx = edges.horizontal === `left` ? step : edges.horizontal === `right` ? -step : 0;
4248
+ if (index > 0) {
4249
+ const hintIndex = index - 1;
4250
+ opacity = Math.max(STACK_OPACITY_MIN, STACK_OPACITY_BASE - hintIndex * STACK_OPACITY_DECAY);
4251
+ pointerEvents = `none`;
4252
+ filter = `saturate(0.6)`;
4253
+ }
4254
+ }
4255
+ el.style.transition = `opacity 0.2s ease-out, transform 0.2s ease-out`;
4256
+ el.style.opacity = `${opacity}`;
4257
+ el.style.zIndex = `${total - index}`;
4258
+ el.style.pointerEvents = pointerEvents;
4259
+ el.style.filter = filter;
4260
+ el.style.transform = restingTransform(edges, dx, dy);
4261
+ });
4262
+ }
4263
+ function dismiss(id) {
4264
+ const entry = pills.get(id);
4265
+ if (!entry) return;
4266
+ const el = entry.element;
4267
+ el.setAttribute(`dismissing`, `true`);
4268
+ const cleanup = () => {
4269
+ el.removeEventListener(`transitionend`, cleanup);
4270
+ el.remove();
4271
+ pills.delete(id);
4272
+ if (container && pills.size === 0) {
4273
+ container.remove();
4274
+ container = null;
4275
+ measured = false;
4276
+ rowHeight = DEFAULT_ROW_HEIGHT;
4277
+ }
4278
+ };
4279
+ el.addEventListener(`transitionend`, cleanup);
4280
+ setTimeout(cleanup, 300);
4281
+ }
4282
+ function setNotifications(notifications, _queueSize) {
4283
+ const cont = ensureContainer();
4284
+ const idsToKeep = new Set(notifications.map((n) => n.id));
4285
+ for (const notification of notifications) {
4286
+ const existing = pills.get(notification.id);
4287
+ if (existing) {
4288
+ existing.notification = notification;
4289
+ updateAttributes(existing.element, notification);
4290
+ continue;
4291
+ }
4292
+ const el = createPillElement(notification);
4293
+ pills.set(notification.id, {
4294
+ element: el,
4295
+ notification
4296
+ });
4297
+ cont.appendChild(el);
4298
+ measureRowHeight(el);
4299
+ }
4300
+ for (const [id] of pills) if (!idsToKeep.has(id)) dismiss(id);
4301
+ requestAnimationFrame(() => applyLayout(notifications));
4302
+ }
4303
+ return {
4304
+ attach(newHooks) {
4305
+ hooks = newHooks;
4306
+ },
4307
+ detach() {
4308
+ for (const [id] of pills) dismiss(id);
4309
+ if (container) {
4310
+ container.remove();
4311
+ container = null;
4312
+ }
4313
+ },
4314
+ setNotifications
4315
+ };
4316
+ }
4317
+ //#endregion
4318
+ //#region src/notifier/manager.ts
4319
+ const options = {
4320
+ renderer: void 0,
4321
+ timeout: 4e3,
4322
+ timeoutError: 8e3,
4323
+ queue: true,
4324
+ queueSize: 3,
4325
+ tapBehaviour: `dismiss`,
4326
+ tapHandlerDefault: void 0
4327
+ };
4328
+ /** Rank-ordered, index 0 = oldest/first (next to be evicted or timed out). Length never exceeds queueSize (or 1 when queuing is disabled). */
4329
+ const visible = [];
4330
+ let renderer = null;
4331
+ let defaultRenderer = null;
4332
+ function getDefaultRenderer() {
4333
+ defaultRenderer ??= createPillRenderer({ position: `top-right` });
4334
+ return defaultRenderer;
4335
+ }
4336
+ function getRenderer() {
4337
+ return renderer ?? getDefaultRenderer();
4338
+ }
4339
+ function syncRenderer() {
4340
+ getRenderer().setNotifications([...visible], options.queueSize);
4341
+ }
4342
+ function clearTimer(managed) {
4343
+ if (managed.timer !== null) {
4344
+ clearTimeout(managed.timer);
4345
+ managed.timer = null;
4346
+ }
4347
+ }
4348
+ /**
4349
+ * A message shown alone takes the full timeout. A message that joined a
4350
+ * non-empty queue at arrival time is permanently "wasQueued" and gets
4351
+ * 1/queueSize of the timeout once it reaches the front. The timer for a
4352
+ * given message is only ever started once - when it first becomes front.
4353
+ */
4354
+ function startTimer(managed) {
4355
+ const base = managed.type === `error` ? options.timeoutError : options.timeout;
4356
+ const duration = managed.wasQueued ? base / options.queueSize : base;
4357
+ managed.timer = window.setTimeout(() => dismiss(managed.id), duration);
4358
+ }
4359
+ function ensureFrontTimer() {
4360
+ const front = visible[0];
4361
+ if (front && front.timer === null) startTimer(front);
4362
+ }
4363
+ const NotificationManager = {
4364
+ configure(opts) {
4365
+ if (opts.renderer !== void 0) {
4366
+ renderer?.detach?.();
4367
+ renderer = opts.renderer;
4368
+ renderer.attach({ onTap(id) {
4369
+ const notification = visible.find((n) => n.id === id);
4370
+ if (!notification) return;
4371
+ if (notification.onTap) notification.onTap(id);
4372
+ else options.tapHandlerDefault?.(notification);
4373
+ if (options.tapBehaviour === `dismiss`) dismiss(id);
4374
+ } });
4375
+ syncRenderer();
4376
+ }
4377
+ if (opts.timeout !== void 0) options.timeout = opts.timeout;
4378
+ if (opts.timeoutError !== void 0) options.timeoutError = opts.timeoutError;
4379
+ if (opts.queue !== void 0) options.queue = opts.queue;
4380
+ if (opts.queueSize !== void 0) {
4381
+ options.queueSize = Math.max(1, opts.queueSize);
4382
+ while (visible.length > options.queueSize) clearTimer(visible.shift());
4383
+ ensureFrontTimer();
4384
+ syncRenderer();
4385
+ }
4386
+ if (opts.tapBehaviour !== void 0) options.tapBehaviour = opts.tapBehaviour;
4387
+ if (opts.tapHandlerDefault !== void 0) options.tapHandlerDefault = opts.tapHandlerDefault;
4388
+ },
4389
+ notify(content) {
4390
+ const id = crypto.randomUUID();
4391
+ const now = Date.now();
4392
+ const icon = content.icon ?? (content.type === `error` ? `error` : void 0);
4393
+ const managed = {
4394
+ ...content,
4395
+ icon,
4396
+ id,
4397
+ createdAt: now,
4398
+ updatedAt: now,
4399
+ timer: null,
4400
+ wasQueued: false
4401
+ };
4402
+ if (options.queue) {
4403
+ if (visible.length >= options.queueSize) clearTimer(visible.shift());
4404
+ managed.wasQueued = visible.length > 0;
4405
+ visible.push(managed);
4406
+ } else {
4407
+ while (visible.length > 0) clearTimer(visible.pop());
4408
+ visible.push(managed);
4409
+ }
4410
+ ensureFrontTimer();
4411
+ syncRenderer();
4412
+ return id;
4413
+ },
4414
+ update(id, content) {
4415
+ const managed = visible.find((n) => n.id === id);
4416
+ if (!managed) return;
4417
+ Object.assign(managed, content);
4418
+ managed.updatedAt = Date.now();
4419
+ if (visible[0] === managed) {
4420
+ clearTimer(managed);
4421
+ startTimer(managed);
4422
+ }
4423
+ syncRenderer();
4424
+ },
4425
+ dismiss(id) {
4426
+ dismiss(id);
4427
+ },
4428
+ dismissAll() {
4429
+ for (const m of visible) clearTimer(m);
4430
+ visible.length = 0;
4431
+ syncRenderer();
4432
+ }
4433
+ };
4434
+ function dismiss(id) {
4435
+ const idx = visible.findIndex((m) => m.id === id);
4436
+ if (idx === -1) return;
4437
+ clearTimer(visible.splice(idx, 1)[0]);
4438
+ ensureFrontTimer();
4439
+ syncRenderer();
4440
+ }
4441
+ //#endregion
3931
4442
  //#region src/panel/panel-group.ts
3932
4443
  let PanelGroupElement = class PanelGroupElement extends LitElement {
3933
4444
  constructor(..._args) {
@@ -4616,7 +5127,7 @@ let TabListItemElement = class TabListItemElement extends LitElement {
4616
5127
  prominenceStyles,
4617
5128
  css`
4618
5129
  :host {
4619
- --selected-colour: var(--accent);
5130
+ --selected-colour: var(--surface-2-text);
4620
5131
  --selected-border-width: 1px;
4621
5132
  --padding: var(--space-m);
4622
5133
  display: inline-flex;
@@ -4642,6 +5153,8 @@ let TabListItemElement = class TabListItemElement extends LitElement {
4642
5153
  border-bottom-color: var(--indicator-color-bottom, transparent);
4643
5154
  border-left-color: var(--indicator-color-left, transparent);
4644
5155
  border-right-color: var(--indicator-color-right, transparent);
5156
+ background: var(--surface-5);
5157
+ font-weight: bold;
4645
5158
  }
4646
5159
 
4647
5160
  .label {
@@ -5104,6 +5617,6 @@ function init() {
5104
5617
  console.log(`Init`);
5105
5618
  }
5106
5619
  //#endregion
5107
- 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 };
5620
+ 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 };
5108
5621
 
5109
5622
  //# sourceMappingURL=index.js.map