@adia-ai/web-components 0.8.31 → 0.8.33

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 (164) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/MIGRATION.md +136 -0
  3. package/components/accordion/accordion-item.a2ui.json +8 -0
  4. package/components/accordion/accordion-item.yaml +6 -0
  5. package/components/accordion/accordion.css +3 -1
  6. package/components/accordion/accordion.d.ts +2 -2
  7. package/components/action-list/action-list.class.js +6 -2
  8. package/components/action-list/action-list.d.ts +1 -1
  9. package/components/agent-artifact/agent-artifact.d.ts +1 -1
  10. package/components/agent-feedback-bar/agent-feedback-bar.d.ts +2 -2
  11. package/components/agent-questions/agent-questions.d.ts +1 -1
  12. package/components/agent-reasoning/agent-reasoning.d.ts +3 -3
  13. package/components/agent-suggestions/agent-suggestions.d.ts +1 -1
  14. package/components/agent-trace/agent-trace.d.ts +1 -1
  15. package/components/alert/alert.a2ui.json +3 -5
  16. package/components/alert/alert.class.js +3 -3
  17. package/components/alert/alert.css +5 -12
  18. package/components/alert/alert.d.ts +4 -4
  19. package/components/alert/alert.yaml +6 -5
  20. package/components/anchor-bar/anchor-bar.d.ts +2 -2
  21. package/components/avatar/avatar.class.js +0 -1
  22. package/components/avatar/avatar.css +16 -2
  23. package/components/badge/badge.a2ui.json +6 -9
  24. package/components/badge/badge.class.js +4 -4
  25. package/components/badge/badge.css +134 -74
  26. package/components/badge/badge.d.ts +34 -12
  27. package/components/badge/badge.examples.md +1 -1
  28. package/components/badge/badge.yaml +37 -16
  29. package/components/breadcrumb/breadcrumb.css +15 -3
  30. package/components/button/button.a2ui.json +1 -1
  31. package/components/button/button.css +4 -2
  32. package/components/button/button.d.ts +3 -3
  33. package/components/button/button.yaml +1 -1
  34. package/components/calendar-grid/calendar-grid.d.ts +2 -2
  35. package/components/card/card.css +21 -0
  36. package/components/card/card.yaml +10 -1
  37. package/components/chart/chart.class.js +8 -4
  38. package/components/chart/chart.d.ts +4 -4
  39. package/components/chart-legend/chart-legend.d.ts +1 -1
  40. package/components/chat-thread/chat-input.css +1 -1
  41. package/components/chat-thread/chat-thread.d.ts +2 -2
  42. package/components/check/check.css +3 -3
  43. package/components/command/command.d.ts +2 -2
  44. package/components/context-menu/context-menu.d.ts +3 -3
  45. package/components/datetime-picker/datetime-picker.d.ts +5 -5
  46. package/components/demo-toggle/demo-toggle.d.ts +1 -1
  47. package/components/drawer/drawer.d.ts +2 -2
  48. package/components/heatmap/heatmap.d.ts +5 -5
  49. package/components/index.js +1 -0
  50. package/components/inline-edit/inline-edit.d.ts +4 -4
  51. package/components/input/input.a2ui.json +4 -1
  52. package/components/input/input.css +18 -0
  53. package/components/input/input.yaml +11 -1
  54. package/components/integration-card/integration-card.class.js +11 -2
  55. package/components/integration-card/integration-card.d.ts +4 -4
  56. package/components/link/link.d.ts +1 -1
  57. package/components/list/list.class.js +22 -7
  58. package/components/list/list.d.ts +1 -1
  59. package/components/list-window/list-window.d.ts +5 -5
  60. package/components/mark/mark.a2ui.json +6 -7
  61. package/components/mark/mark.class.js +1 -1
  62. package/components/mark/mark.css +7 -4
  63. package/components/mark/mark.d.ts +2 -2
  64. package/components/mark/mark.examples.md +0 -4
  65. package/components/mark/mark.yaml +16 -6
  66. package/components/menu/menu.css +2 -2
  67. package/components/menu/menu.d.ts +1 -1
  68. package/components/modal/modal.d.ts +1 -1
  69. package/components/nav/nav.css +8 -0
  70. package/components/nav/nav.d.ts +1 -1
  71. package/components/nav-group/nav-group.css +58 -9
  72. package/components/nav-group/nav-group.d.ts +1 -1
  73. package/components/nav-item/nav-item.css +7 -4
  74. package/components/nav-item/nav-item.d.ts +1 -1
  75. package/components/noodles/noodles.d.ts +3 -3
  76. package/components/option-card/option-card.css +1 -1
  77. package/components/option-card/option-card.examples.md +1 -1
  78. package/components/pagination/pagination.d.ts +1 -1
  79. package/components/pane/pane.d.ts +1 -1
  80. package/components/password-strength/password-strength.d.ts +1 -1
  81. package/components/popover/popover.a2ui.json +5 -0
  82. package/components/popover/popover.class.js +9 -4
  83. package/components/popover/popover.css +5 -2
  84. package/components/popover/popover.yaml +8 -0
  85. package/components/preview/preview.examples.md +1 -1
  86. package/components/radio/radio.a2ui.json +4 -3
  87. package/components/radio/radio.css +2 -2
  88. package/components/radio/radio.yaml +7 -14
  89. package/components/radio-group/radio-group.a2ui.json +118 -0
  90. package/components/radio-group/radio-group.class.js +75 -0
  91. package/components/radio-group/radio-group.css +31 -0
  92. package/components/radio-group/radio-group.d.ts +20 -0
  93. package/components/radio-group/radio-group.examples.md +37 -0
  94. package/components/radio-group/radio-group.js +17 -0
  95. package/components/radio-group/radio-group.yaml +114 -0
  96. package/components/stat/stat.css +12 -8
  97. package/components/stat/stat.js +66 -1
  98. package/components/stat/stat.yaml +3 -0
  99. package/components/stepper/stepper-item.a2ui.json +5 -0
  100. package/components/stepper/stepper-item.yaml +11 -0
  101. package/components/stepper/stepper.a2ui.json +38 -3
  102. package/components/stepper/stepper.class.js +199 -2
  103. package/components/stepper/stepper.css +30 -0
  104. package/components/stepper/stepper.d.ts +28 -1
  105. package/components/stepper/stepper.yaml +76 -2
  106. package/components/stream/stream.d.ts +3 -3
  107. package/components/swatch/swatch.d.ts +1 -1
  108. package/components/swiper/swiper.d.ts +3 -3
  109. package/components/table/cell-types.js +12 -1
  110. package/components/table/table.a2ui.json +2 -2
  111. package/components/table/table.d.ts +7 -7
  112. package/components/table/table.yaml +9 -1
  113. package/components/table-toolbar/table-toolbar.class.js +1 -1
  114. package/components/table-toolbar/table-toolbar.d.ts +4 -4
  115. package/components/tabs/tabs.d.ts +1 -1
  116. package/components/tag/tag.a2ui.json +5 -5
  117. package/components/tag/tag.class.js +1 -1
  118. package/components/tag/tag.css +149 -71
  119. package/components/tag/tag.d.ts +21 -11
  120. package/components/tag/tag.examples.md +4 -4
  121. package/components/tag/tag.yaml +35 -13
  122. package/components/tags-input/tags-input.d.ts +4 -4
  123. package/components/timeline/timeline.d.ts +1 -1
  124. package/components/toast/toast.a2ui.json +2 -4
  125. package/components/toast/toast.css +0 -12
  126. package/components/toast/toast.yaml +3 -4
  127. package/components/toc/toc.d.ts +1 -1
  128. package/components/toggle-group/toggle-group.d.ts +1 -1
  129. package/components/toggle-scheme/toggle-scheme.d.ts +1 -1
  130. package/components/tour/tour.d.ts +4 -4
  131. package/components/tree/tree.d.ts +1 -1
  132. package/core/icons-phosphor.js +129 -71
  133. package/core/provider.js +84 -23
  134. package/dist/host.min.css +1 -1
  135. package/dist/host.sheet.js +1 -1
  136. package/dist/theme-provider.min.js +1 -1
  137. package/dist/web-components.min.css +1 -1
  138. package/dist/web-components.min.js +90 -88
  139. package/dist/web-components.sheet.js +1 -1
  140. package/package.json +1 -1
  141. package/patterns/admin-keys-and-export/admin-keys-and-export.examples.html +1 -1
  142. package/patterns/agent-memory/agent-memory.examples.html +5 -5
  143. package/patterns/agent-prompt-library/agent-prompt-library.examples.html +2 -2
  144. package/patterns/audit-log/audit-log.examples.js +12 -5
  145. package/patterns/bulk-action-toolbar/bulk-action-toolbar.examples.js +4 -1
  146. package/patterns/changelog-feed/changelog-feed.examples.html +1 -1
  147. package/patterns/comments-and-collaboration/comments-and-collaboration.examples.html +1 -1
  148. package/patterns/conversion-funnel/conversion-funnel.examples.html +1 -1
  149. package/patterns/diff-review/diff-review.examples.html +9 -9
  150. package/patterns/filter-bar/filter-bar.examples.html +12 -8
  151. package/patterns/filter-bar/filter-bar.examples.js +4 -1
  152. package/patterns/marketing-engagement/marketing-engagement.examples.html +1 -1
  153. package/patterns/notifications-bell-and-digest/notifications-bell-and-digest.examples.html +1 -1
  154. package/patterns/permissions-matrix/permissions-matrix.examples.html +1 -1
  155. package/patterns/permissions-sharing/permissions-sharing.examples.html +1 -1
  156. package/patterns/profile-public-and-verification/profile-public-and-verification.examples.html +2 -2
  157. package/patterns/record-detail-drawer/record-detail-drawer.examples.html +1 -1
  158. package/patterns/retention-cohort/retention-cohort.examples.html +1 -1
  159. package/patterns/retention-cohort/retention-cohort.examples.js +4 -1
  160. package/patterns/system-banners/system-banners.examples.html +3 -3
  161. package/styles/api/sizing-fallback.css +32 -0
  162. package/styles/api/sizing.css +7 -3
  163. package/styles/components.css +1 -0
  164. package/styles/tokens.css +1 -0
@@ -51,13 +51,13 @@ export class UIIntegrationCard extends UIElement {
51
51
  /** Current connection state — drives the button label, variant, and visibility. `available` shows a primary `Connect`; `connected` shows an outline `Configure`; `error` shows an outline `Retry`; `pending` shows a non-interactive spinner; `coming-soon` hides the button. */
52
52
  status: string;
53
53
 
54
+ addEventListener(type: 'configure', listener: (ev: IntegrationCardConfigureEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
55
+ addEventListener(type: 'connect', listener: (ev: IntegrationCardConnectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
56
+ addEventListener(type: 'disconnect', listener: (ev: IntegrationCardDisconnectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
57
+ addEventListener(type: 'retry', listener: (ev: IntegrationCardRetryEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
54
58
  addEventListener<K extends keyof HTMLElementEventMap>(
55
59
  type: K,
56
60
  listener: (this: UIIntegrationCard, ev: HTMLElementEventMap[K]) => unknown,
57
61
  options?: boolean | AddEventListenerOptions,
58
62
  ): void;
59
- addEventListener(type: 'configure', listener: (ev: IntegrationCardConfigureEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
60
- addEventListener(type: 'connect', listener: (ev: IntegrationCardConnectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
61
- addEventListener(type: 'disconnect', listener: (ev: IntegrationCardDisconnectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
62
- addEventListener(type: 'retry', listener: (ev: IntegrationCardRetryEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
63
63
  }
@@ -63,10 +63,10 @@ export class UILink extends UIElement {
63
63
  /** Visual treatment. `default` underlines on rest + hover (standard link affordance). `subtle` underlines only on hover (for tighter designs where always-underlined would be noisy). `quiet` drops the link color and matches surrounding text color (used for footer-link rows where the link affordance is implied by context, not by color). */
64
64
  variant: 'default' | 'subtle' | 'quiet';
65
65
 
66
+ addEventListener(type: 'press', listener: (ev: LinkPressEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
66
67
  addEventListener<K extends keyof HTMLElementEventMap>(
67
68
  type: K,
68
69
  listener: (this: UILink, ev: HTMLElementEventMap[K]) => unknown,
69
70
  options?: boolean | AddEventListenerOptions,
70
71
  ): void;
71
- addEventListener(type: 'press', listener: (ev: LinkPressEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
72
72
  }
@@ -45,9 +45,12 @@ export class UIList extends UIElement {
45
45
  static template = () => null;
46
46
 
47
47
  #bound = false;
48
+ #stampedSelection = false;
48
49
 
49
50
  connected() {
50
- this.setAttribute('role', 'list');
51
+ // Adopt-or-stamp (gh#753): a consumer-authored role survives connect —
52
+ // only stamp when absent.
53
+ if (!this.hasAttribute('role')) this.setAttribute('role', 'list');
51
54
  if (!this.#bound) {
52
55
  this.#bound = true;
53
56
  this.addEventListener('click', this.#onClick);
@@ -63,13 +66,21 @@ export class UIList extends UIElement {
63
66
 
64
67
  render() {
65
68
  if (!this.selectable) {
66
- // Clear selection chrome from children
67
- for (const ch of this.#items()) {
68
- ch.removeAttribute('aria-selected');
69
- ch.removeAttribute('tabindex');
69
+ // Clear selection chrome from children — but ONLY chrome this list
70
+ // stamped (selectable toggled off). An unconditional strip clobbered
71
+ // consumer-managed focusability: a non-selectable list re-render
72
+ // silently removed tabindex the consumer set on its own rows
73
+ // (patient-visit rail's roving tabindex, gh#746).
74
+ if (this.#stampedSelection) {
75
+ for (const ch of this.#items()) {
76
+ ch.removeAttribute('aria-selected');
77
+ ch.removeAttribute('tabindex');
78
+ }
79
+ this.#stampedSelection = false;
70
80
  }
71
81
  return;
72
82
  }
83
+ this.#stampedSelection = true;
73
84
  const key = this.selectedKey || '';
74
85
  const items = this.#items();
75
86
  let focusTarget = null;
@@ -121,7 +132,8 @@ export class UIList extends UIElement {
121
132
 
122
133
  #onClick = (e) => {
123
134
  if (!this.selectable) return;
124
- const item = e.target.closest?.('[role="listitem"]');
135
+ // tag-or-role: adopt-or-stamp (gh#753) means a consumer role must not break click-select
136
+ const item = e.target.closest?.('list-item-ui, [role="listitem"]');
125
137
  if (!item || item.parentElement !== this) return;
126
138
  const key = this.#keyOf(item);
127
139
  if (key) this.#selectKey(key);
@@ -166,7 +178,10 @@ export class UIListItem extends UIElement {
166
178
  static template = () => null;
167
179
 
168
180
  connected() {
169
- this.setAttribute('role', 'listitem');
181
+ // Adopt-or-stamp (gh#753): a consumer-authored role (e.g. role="button"
182
+ // on a clickable row) survives connect AND reconnect — only stamp
183
+ // "listitem" when the host has no author-set role at all.
184
+ if (!this.hasAttribute('role')) this.setAttribute('role', 'listitem');
170
185
  this.#stamp();
171
186
  }
172
187
 
@@ -46,12 +46,12 @@ so content sits at the list-ui edges (canonical edge-to-edge style).
46
46
  /** Item spacing */
47
47
  spacing: string;
48
48
 
49
+ addEventListener(type: 'selection-change', listener: (ev: ListSelectionChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
49
50
  addEventListener<K extends keyof HTMLElementEventMap>(
50
51
  type: K,
51
52
  listener: (this: UIList, ev: HTMLElementEventMap[K]) => unknown,
52
53
  options?: boolean | AddEventListenerOptions,
53
54
  ): void;
54
- addEventListener(type: 'selection-change', listener: (ev: ListSelectionChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
55
55
  }
56
56
 
57
57
  export class UIListItem extends UIElement {
@@ -71,14 +71,14 @@ export class UIListWindow extends UIElement {
71
71
  /** Index to scroll to on mount. Useful for restoring scroll position on remount. */
72
72
  startIndex: number;
73
73
 
74
- addEventListener<K extends keyof HTMLElementEventMap>(
75
- type: K,
76
- listener: (this: UIListWindow, ev: HTMLElementEventMap[K]) => unknown,
77
- options?: boolean | AddEventListenerOptions,
78
- ): void;
79
74
  addEventListener(type: 'item-click', listener: (ev: ListWindowItemClickEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
80
75
  addEventListener(type: 'measure', listener: (ev: ListWindowMeasureEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
81
76
  addEventListener(type: 'range-change', listener: (ev: ListWindowRangeChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
82
77
  addEventListener(type: 'scroll-end', listener: (ev: ListWindowScrollEndEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
83
78
  addEventListener(type: 'scroll-start', listener: (ev: ListWindowScrollStartEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
79
+ addEventListener<K extends keyof HTMLElementEventMap>(
80
+ type: K,
81
+ listener: (this: UIListWindow, ev: HTMLElementEventMap[K]) => unknown,
82
+ options?: boolean | AddEventListenerOptions,
83
+ ): void;
84
84
  }
@@ -17,14 +17,13 @@
17
17
  "const": "Mark"
18
18
  },
19
19
  "variant": {
20
- "description": "Highlight color variant. Default `warning` is the conventional yellow-marker tone.",
20
+ "description": "Highlight color family — the only color axis mark exposes. Default `warning` is the conventional yellow-marker tone and IS mark's family-less identity, so there is no `default` member. For a neutral (non-semantic) highlight, override the public tokens instead of the variant: `--mark-bg: var(--a-bg-muted); --mark-fg: var(--md-sys-color-neutral-on-surface)` — the successor to the retired `variant=\"muted\"` (gh#881).",
21
21
  "type": "string",
22
22
  "enum": [
23
23
  "warning",
24
24
  "info",
25
25
  "success",
26
- "danger",
27
- "muted"
26
+ "danger"
28
27
  ],
29
28
  "default": "warning"
30
29
  }
@@ -87,12 +86,12 @@
87
86
  "tag": "mark-ui",
88
87
  "tokens": {
89
88
  "--mark-bg": {
90
- "description": "Background color of the highlight.",
91
- "default": "var(--a-warning-muted)"
89
+ "description": "Background color of the highlight. Override for a non-semantic (e.g. neutral) highlight — see [variant].",
90
+ "default": "var(--md-sys-color-warning-container)"
92
91
  },
93
92
  "--mark-fg": {
94
- "description": "Foreground (text) color inside the highlight.",
95
- "default": "var(--a-warning-text)"
93
+ "description": "Foreground (text) color inside the highlight. Pair every [--mark-bg] override with a matching fg so contrast holds in both schemes.",
94
+ "default": "var(--md-sys-color-warning-on-surface)"
96
95
  },
97
96
  "--mark-px": {
98
97
  "description": "Horizontal padding around the highlighted text.",
@@ -8,7 +8,7 @@ import { UIElement } from '../../core/element.js';
8
8
 
9
9
  export class UIMark extends UIElement {
10
10
  static properties = {
11
- variant: { type: String, default: 'warning', reflect: true }, // warning | info | success | danger | muted
11
+ variant: { type: String, default: 'warning', reflect: true }, // warning | info | success | danger
12
12
  };
13
13
 
14
14
  static template = () => null;
@@ -32,8 +32,11 @@
32
32
  --mark-bg: var(--md-sys-color-danger-container);
33
33
  --mark-fg: var(--md-sys-color-danger-on-surface);
34
34
  }
35
- :scope[variant="muted"] {
36
- --mark-bg: var(--a-bg-muted);
37
- --mark-fg: var(--md-sys-color-neutral-on-surface);
38
- }
35
+
36
+ /* NB `variant` is mark's ONLY color axis and it is family-only
37
+ (gh#881, the two-axis convention). The former `variant="muted"`
38
+ neutral highlight is retired; its successor is the public token
39
+ override — `--mark-bg: var(--a-bg-muted); --mark-fg:
40
+ var(--md-sys-color-neutral-on-surface)` — so a non-semantic
41
+ highlight no longer needs an enum member of its own. */
39
42
  }
@@ -22,6 +22,6 @@ background highlight behind the matched span.
22
22
  import { UIElement } from '../../core/element.js';
23
23
 
24
24
  export class UIMark extends UIElement {
25
- /** Highlight color variant. Default `warning` is the conventional yellow-marker tone. */
26
- variant: 'warning' | 'info' | 'success' | 'danger' | 'muted';
25
+ /** Highlight color family — the only color axis mark exposes. Default `warning` is the conventional yellow-marker tone and IS mark's family-less identity, so there is no `default` member. For a neutral (non-semantic) highlight, override the public tokens instead of the variant: `--mark-bg: var(--a-bg-muted); --mark-fg: var(--md-sys-color-neutral-on-surface)` — the successor to the retired `variant="muted"` (gh#881). */
26
+ variant: 'warning' | 'info' | 'success' | 'danger';
27
27
  }
@@ -42,10 +42,6 @@
42
42
  <mark-ui variant="danger">danger</mark-ui>
43
43
  — removed line, conflict.
44
44
  </text-ui>
45
- <text-ui>
46
- <mark-ui variant="muted">muted</mark-ui>
47
- — neutral emphasis.
48
- </text-ui>
49
45
  </col-ui>
50
46
  ```
51
47
 
@@ -17,7 +17,14 @@ description: |
17
17
  background highlight behind the matched span.
18
18
  props:
19
19
  variant:
20
- description: Highlight color variant. Default `warning` is the conventional yellow-marker tone.
20
+ description: >-
21
+ Highlight color family — the only color axis mark exposes. Default
22
+ `warning` is the conventional yellow-marker tone and IS mark's
23
+ family-less identity, so there is no `default` member. For a
24
+ neutral (non-semantic) highlight, override the public tokens
25
+ instead of the variant: `--mark-bg: var(--a-bg-muted);
26
+ --mark-fg: var(--md-sys-color-neutral-on-surface)` — the successor
27
+ to the retired `variant="muted"` (gh#881).
21
28
  type: string
22
29
  default: warning
23
30
  enum:
@@ -25,7 +32,6 @@ props:
25
32
  - info
26
33
  - success
27
34
  - danger
28
- - muted
29
35
  reflect: true
30
36
  events: {}
31
37
  slots:
@@ -37,11 +43,15 @@ states:
37
43
  traits: []
38
44
  tokens:
39
45
  --mark-bg:
40
- description: Background color of the highlight.
41
- default: var(--a-warning-muted)
46
+ description: >-
47
+ Background color of the highlight. Override for a non-semantic
48
+ (e.g. neutral) highlight — see [variant].
49
+ default: var(--md-sys-color-warning-container)
42
50
  --mark-fg:
43
- description: Foreground (text) color inside the highlight.
44
- default: var(--a-warning-text)
51
+ description: >-
52
+ Foreground (text) color inside the highlight. Pair every [--mark-bg]
53
+ override with a matching fg so contrast holds in both schemes.
54
+ default: var(--md-sys-color-warning-on-surface)
45
55
  --mark-px:
46
56
  description: Horizontal padding around the highlighted text.
47
57
  default: var(--a-space-0-5)
@@ -106,11 +106,11 @@ menu-item-ui:hover {
106
106
  menu-item-ui:hover [slot="icon"] {
107
107
  color: var(--menu-item-icon-fg-hover);
108
108
  }
109
- menu-item-ui[variant="danger"]:hover {
109
+ menu-item-ui[variant="danger"]:hover:not(:focus-visible) {
110
110
  --menu-item-bg: var(--menu-item-danger-bg);
111
111
  --menu-item-fg: var(--menu-item-danger-fg);
112
112
  }
113
- menu-item-ui[variant="danger"]:hover [slot="icon"] {
113
+ menu-item-ui[variant="danger"]:hover:not(:focus-visible) [slot="icon"] {
114
114
  color: var(--menu-item-danger-fg);
115
115
  }
116
116
 
@@ -29,12 +29,12 @@ export class UIMenu extends UIElement {
29
29
  /** Preferred position relative to the anchor element */
30
30
  placement: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
31
31
 
32
+ addEventListener(type: 'action', listener: (ev: MenuActionEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
32
33
  addEventListener<K extends keyof HTMLElementEventMap>(
33
34
  type: K,
34
35
  listener: (this: UIMenu, ev: HTMLElementEventMap[K]) => unknown,
35
36
  options?: boolean | AddEventListenerOptions,
36
37
  ): void;
37
- addEventListener(type: 'action', listener: (ev: MenuActionEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
38
38
  }
39
39
 
40
40
  export class UIMenuDivider extends UIElement {
@@ -33,10 +33,10 @@ export class UIModal extends UIElement {
33
33
  /** Aria label applied to the surface element for accessibility */
34
34
  text: string;
35
35
 
36
+ addEventListener(type: 'close', listener: (ev: ModalCloseEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
36
37
  addEventListener<K extends keyof HTMLElementEventMap>(
37
38
  type: K,
38
39
  listener: (this: UIModal, ev: HTMLElementEventMap[K]) => unknown,
39
40
  options?: boolean | AddEventListenerOptions,
40
41
  ): void;
41
- addEventListener(type: 'close', listener: (ev: ModalCloseEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
42
42
  }
@@ -20,6 +20,14 @@
20
20
  --nav-duration: var(--a-duration); /* §230-bundle (v0.5.9): canonical mid-tier duration (250ms). Was orphaned --a-duration-base. */
21
21
  --nav-easing: var(--a-easing, ease);
22
22
 
23
+ /* Collapsed-rail icon sizing — one shared source for nav-item-ui's
24
+ and nav-group-ui's [slot="header"] icons at the narrow (<=96px)
25
+ container-query breakpoint (gh#767). Was two independently
26
+ drifting values: nav-item pinned a literal 18px, nav-group
27
+ referenced an undefined --nav-group-icon-font-size (guaranteed-
28
+ invalid, silently riding icon-ui's ambient-font-size fallback). */
29
+ --nav-icon-size-collapsed: 18px;
30
+
23
31
  /* Group-heading kicker */
24
32
  --nav-label-fg: var(--a-fg-muted);
25
33
  --nav-label-font-size: var(--a-kicker-sm);
@@ -41,10 +41,10 @@ export class UINav extends UIElement {
41
41
  /** Visual treatment. primary = app sidebar; section = subnav rail. */
42
42
  variant: 'primary' | 'section';
43
43
 
44
+ addEventListener(type: 'nav-select', listener: (ev: NavSelectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
44
45
  addEventListener<K extends keyof HTMLElementEventMap>(
45
46
  type: K,
46
47
  listener: (this: UINav, ev: HTMLElementEventMap[K]) => unknown,
47
48
  options?: boolean | AddEventListenerOptions,
48
49
  ): void;
49
- addEventListener(type: 'nav-select', listener: (ev: NavSelectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
50
50
  }
@@ -16,6 +16,7 @@
16
16
  --nav-group-fg-selected: var(--a-fg-strong);
17
17
  --nav-group-icon-fg-selected: var(--md-sys-color-primary); /* one selection color with --nav-item-accent (gh#522) */
18
18
  --nav-group-bg-hover: var(--a-bg-muted);
19
+ --nav-group-bg-selected: var(--a-bg-selected); /* mirrors --nav-item-bg-selected — the missing container fill (gh#795) */
19
20
  --nav-group-icon-size: calc(var(--nav-group-row-height) - var(--a-space-2));
20
21
  --nav-group-badge-size: var(--a-ui-sm);
21
22
 
@@ -52,8 +53,22 @@
52
53
  }
53
54
  /* Selected-look header treatment keys on [selected] — the CSS half of
54
55
  gh issue 459 (PR 460 corrected the icon weight): the group CONTAINING
55
- the selected route reads strong; a merely-expanded group does not. */
56
- :scope[selected] > [slot="header"] {
56
+ the selected route reads strong; a merely-expanded group does not.
57
+ Text color applies in BOTH rail modes — subtle, no double-marking risk.
58
+ The BACKGROUND fill (gh#795) is collapsed-only (see the two
59
+ nav-ui[collapsed] / @container sidebar blocks below): [selected] and
60
+ [data-selected-within] both mean "CONTAINS the selected route", never
61
+ "IS the selected route" (nav-group.class.js's own doc comment on the
62
+ `selected` prop — consumers set it from active-route logic exactly
63
+ like the JS-derived data-selected-within case; there is no
64
+ directly-selected-leaf-group state distinct from it). In the expanded
65
+ rail the contained child already paints its own real selected pill
66
+ (nav-item.css), so a header background here double-marks the same
67
+ selection (gh#806 — operator screenshot, profile-security). Collapsed
68
+ hides the children, so the header becomes the ONLY selection signal
69
+ and needs the fill nav-item-ui gets. */
70
+ :scope[selected] > [slot="header"],
71
+ :scope[data-selected-within] > [slot="header"] {
57
72
  color: var(--nav-group-fg-selected);
58
73
  }
59
74
  /* Icon color = the SELECTION color, one language with nav-item's accent
@@ -111,9 +126,12 @@
111
126
  justify-content: center;
112
127
  }
113
128
 
114
- [slot="header"] [slot="icon"] icon-ui {
115
- --a-icon-size: calc(var(--nav-group-icon-font-size) + 2px);
116
- }
129
+ /* Default (non-collapsed) icon size: no explicit --a-icon-size here —
130
+ icon-ui rides its own ambient-font-size fallback
131
+ (calc(1em + 0.125rem), icon.css). Was a reference to
132
+ --nav-group-icon-font-size, a token defined nowhere — guaranteed-
133
+ invalid, so it silently resolved to the exact same fallback anyway;
134
+ removed as dead code (gh#767), same defect as nav-item's mirror rule. */
117
135
 
118
136
  [slot="header"] [slot="text"] {
119
137
  flex: 1;
@@ -175,10 +193,29 @@
175
193
  padding: 0;
176
194
  }
177
195
 
178
- nav-ui[collapsed] & {
179
- align-self: stretch;
196
+ /* gh#806: the header background fill lives HERE, collapsed-only
197
+ children are display:none in this mode (rule above), so the header
198
+ is the sole selection signal and needs the real container fill
199
+ nav-item-ui gets. Expanded mode never sets this background (the
200
+ :scope[selected]/[data-selected-within] rule above is color-only) —
201
+ the selected child already paints its own pill there. */
202
+ nav-ui[collapsed] &[selected] > [slot="header"],
203
+ nav-ui[collapsed] &[data-selected-within] > [slot="header"] {
204
+ background: var(--nav-group-bg-selected);
180
205
  }
181
206
 
207
+ /* No align-self override here (there was one — `align-self: stretch` —
208
+ until gh#767): in expanded mode :scope already stretches full-width
209
+ via nav-ui's default flex-column align-items:stretch, so the
210
+ explicit rule was a no-op there. In collapsed mode it silently
211
+ fought admin-shell.collapsed.css's square-footprint contract
212
+ (align-items:center at the nav-ui level, FEEDBACK-39) — stretching
213
+ the header to the full rail width and defeating the min-width floor
214
+ that pins nav-item-ui/nav-group-ui to a matching square. Falling
215
+ through to the ambient align-items (stretch normally, center under
216
+ admin-shell's collapsed rail) matches nav-item-ui, which never had
217
+ an override here. */
218
+
182
219
  nav-ui[collapsed] &::after {
183
220
  display: none;
184
221
  }
@@ -199,12 +236,24 @@
199
236
  invalid at the icon's var() site: a silent no-op. The caret is
200
237
  display:none in collapsed mode anyway; the icon keeps the
201
238
  inherited --a-icon-size.) */
202
- :scope {
203
- align-self: stretch;
239
+ /* No align-self override here either (gh#767) — same reasoning as
240
+ the nav-ui[collapsed] & block above; this duplicate fought the
241
+ same square-footprint contract inside the container-query path. */
242
+ [slot="header"] [slot="icon"] icon-ui {
243
+ --a-icon-size: var(--nav-icon-size-collapsed);
204
244
  }
205
245
  :scope::after {
206
246
  display: none;
207
247
  }
248
+ /* gh#806 — mirrors the nav-ui[collapsed] block above: this container
249
+ query is the OTHER mechanism that puts the rail in collapsed mode
250
+ (admin-sidebar's own collapse path), and children are hidden here
251
+ too ([slot="header"] nav-item-ui rule above), so the same
252
+ collapsed-only header background applies. */
253
+ :scope[selected] > [slot="header"],
254
+ :scope[data-selected-within] > [slot="header"] {
255
+ background: var(--nav-group-bg-selected);
256
+ }
208
257
  }
209
258
 
210
259
  /* ── Size variants ── */
@@ -45,10 +45,10 @@ export class UINavGroup extends UIElement {
45
45
  /** Visual treatment. Default ('') renders as a primary-rail group (icon row, caret, collapsible). 'section' renders the header as a static kicker label with always-visible children. When the parent <nav-ui> carries variant="section", this group inherits it via CSS cascade unless an explicit variant is set on the group. */
46
46
  variant: '' | 'section';
47
47
 
48
+ addEventListener(type: 'group-toggle', listener: (ev: NavGroupGroupToggleEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
48
49
  addEventListener<K extends keyof HTMLElementEventMap>(
49
50
  type: K,
50
51
  listener: (this: UINavGroup, ev: HTMLElementEventMap[K]) => unknown,
51
52
  options?: boolean | AddEventListenerOptions,
52
53
  ): void;
53
- addEventListener(type: 'group-toggle', listener: (ev: NavGroupGroupToggleEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
54
54
  }
@@ -106,9 +106,12 @@ nav-item-ui[selected] [slot="icon"]:empty::before {
106
106
  transition: color var(--nav-duration-fast) var(--nav-easing);
107
107
  }
108
108
 
109
- [slot="icon"] icon-ui {
110
- --a-icon-size: calc(var(--nav-item-icon-font-size) + 2px);
111
- }
109
+ /* Default (non-collapsed) icon size: no explicit --a-icon-size here —
110
+ icon-ui rides its own ambient-font-size fallback
111
+ (calc(1em + 0.125rem), icon.css). Was a reference to
112
+ --nav-item-icon-font-size, a token defined nowhere — guaranteed-
113
+ invalid, so it silently resolved to the exact same fallback anyway;
114
+ removed as dead code (gh#767). */
112
115
 
113
116
  /* Selected accent line on empty icon slot moved outside @scope — see Safari 17.x bug note at top. */
114
117
 
@@ -157,7 +160,7 @@ nav-item-ui[selected] [slot="icon"]:empty::before {
157
160
  display: none !important;
158
161
  }
159
162
  [slot="icon"] icon-ui {
160
- --a-icon-size: 18px;
163
+ --a-icon-size: var(--nav-icon-size-collapsed);
161
164
  }
162
165
  :scope {
163
166
  justify-content: center;
@@ -47,10 +47,10 @@ export class UINavItem extends UIElement {
47
47
  /** Visual treatment. Default ('') renders as a primary-rail item (reserved icon space, in-icon selected accent). 'section' renders flat — no icon space when absent, left-edge accent bar for selected. When the parent <nav-ui> carries variant="section", this item inherits it via CSS cascade unless an explicit variant is set. */
48
48
  variant: '' | 'section';
49
49
 
50
+ addEventListener(type: 'nav-select', listener: (ev: NavItemNavSelectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
50
51
  addEventListener<K extends keyof HTMLElementEventMap>(
51
52
  type: K,
52
53
  listener: (this: UINavItem, ev: HTMLElementEventMap[K]) => unknown,
53
54
  options?: boolean | AddEventListenerOptions,
54
55
  ): void;
55
- addEventListener(type: 'nav-select', listener: (ev: NavItemNavSelectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
56
56
  }
@@ -62,12 +62,12 @@ export class UINoodles extends UIElement {
62
62
  /** Bezier control point distance (0-1). Higher = wider curves */
63
63
  tension: number;
64
64
 
65
+ addEventListener(type: 'noodle-connect', listener: (ev: NoodlesNoodleConnectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
66
+ addEventListener(type: 'noodle-disconnect', listener: (ev: NoodlesNoodleDisconnectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
67
+ addEventListener(type: 'noodle-drag', listener: (ev: NoodlesNoodleDragEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
65
68
  addEventListener<K extends keyof HTMLElementEventMap>(
66
69
  type: K,
67
70
  listener: (this: UINoodles, ev: HTMLElementEventMap[K]) => unknown,
68
71
  options?: boolean | AddEventListenerOptions,
69
72
  ): void;
70
- addEventListener(type: 'noodle-connect', listener: (ev: NoodlesNoodleConnectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
71
- addEventListener(type: 'noodle-disconnect', listener: (ev: NoodlesNoodleDisconnectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
72
- addEventListener(type: 'noodle-drag', listener: (ev: NoodlesNoodleDragEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
73
73
  }
@@ -1,7 +1,7 @@
1
1
  /* Safari 17.x bug: `:scope:not(...):hover` (Flavor A) and `:scope[checked]`
2
2
  (Flavor B — attribute-removal restyle) both fail inside `@scope`.
3
3
  Selectors moved out. See docs/BROWSER-COMPAT.md §3a. */
4
- option-card-ui:not([checked]):not([disabled]):hover {
4
+ option-card-ui:not([checked]):not([disabled]):hover:not(:focus-visible) {
5
5
  background: var(--option-card-bg-hover);
6
6
  border-color: var(--option-card-border-hover);
7
7
  }
@@ -24,7 +24,7 @@
24
24
  <span slot="heading">
25
25
  Pro
26
26
  <text-ui color="subtle" size="sm">· $49/mo</text-ui>
27
- <badge-ui text="14-day trial" variant="accent"></badge-ui>
27
+ <badge-ui text="14-day trial" variant="primary"></badge-ui>
28
28
  </span>
29
29
  </option-card-ui>
30
30
  <option-card-ui name="demo-plan" value="team" description="Everything in Pro · SSO · audit log · 1 TB storage">
@@ -35,10 +35,10 @@ default; pass `size="sm"` for a denser numbered row.
35
35
  /** Visual variant — `default` (ghost buttons w/ hover bg) or `button` (1×1 bordered cells; active page filled). */
36
36
  variant: 'default' | 'button';
37
37
 
38
+ addEventListener(type: 'page-change', listener: (ev: PaginationPageChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
38
39
  addEventListener<K extends keyof HTMLElementEventMap>(
39
40
  type: K,
40
41
  listener: (this: UIPagination, ev: HTMLElementEventMap[K]) => unknown,
41
42
  options?: boolean | AddEventListenerOptions,
42
43
  ): void;
43
- addEventListener(type: 'page-change', listener: (ev: PaginationPageChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
44
44
  }
@@ -38,10 +38,10 @@ grabber when resizable).
38
38
  */
39
39
  side: '' | 'leading' | 'trailing';
40
40
 
41
+ addEventListener(type: 'toggle', listener: (ev: PaneToggleEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
41
42
  addEventListener<K extends keyof HTMLElementEventMap>(
42
43
  type: K,
43
44
  listener: (this: UIPane, ev: HTMLElementEventMap[K]) => unknown,
44
45
  options?: boolean | AddEventListenerOptions,
45
46
  ): void;
46
- addEventListener(type: 'toggle', listener: (ev: PaneToggleEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
47
47
  }
@@ -50,10 +50,10 @@ meter.value = e.target.value)`.
50
50
  */
51
51
  value: string;
52
52
 
53
+ addEventListener(type: 'score-change', listener: (ev: PasswordStrengthScoreChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
53
54
  addEventListener<K extends keyof HTMLElementEventMap>(
54
55
  type: K,
55
56
  listener: (this: UIPasswordStrength, ev: HTMLElementEventMap[K]) => unknown,
56
57
  options?: boolean | AddEventListenerOptions,
57
58
  ): void;
58
- addEventListener(type: 'score-change', listener: (ev: PasswordStrengthScoreChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
59
59
  }
@@ -21,6 +21,11 @@
21
21
  "type": "number",
22
22
  "default": 4
23
23
  },
24
+ "match-width": {
25
+ "description": "Size the content panel to the trigger's width instead of max-content. Opt-in — the default (unset) keeps every existing consumer's content-sized panel unchanged.",
26
+ "type": "boolean",
27
+ "default": false
28
+ },
24
29
  "open": {
25
30
  "description": "Controls visibility via showPopover()/hidePopover()",
26
31
  "type": "boolean",
@@ -24,6 +24,9 @@
24
24
  * (see @core/anchor.js).
25
25
  *
26
26
  * Triggers: click | hover | manual. ESC closes click/manual popovers.
27
+ *
28
+ * Set [match-width] to size the panel to the trigger's width instead of
29
+ * max-content (opt-in — see @core/anchor.js's `matchWidth` option).
27
30
  */
28
31
 
29
32
  import { UIElement } from '../../core/element.js';
@@ -33,10 +36,11 @@ const HOVER_CLOSE_DELAY = 120;
33
36
 
34
37
  export class UIPopover extends UIElement {
35
38
  static properties = {
36
- placement: { type: String, default: 'bottom', reflect: true },
37
- gap: { type: Number, default: 4, reflect: true },
38
- open: { type: Boolean, default: false, reflect: true },
39
- trigger: { type: String, default: 'click', reflect: true },
39
+ placement: { type: String, default: 'bottom', reflect: true },
40
+ gap: { type: Number, default: 4, reflect: true },
41
+ open: { type: Boolean, default: false, reflect: true },
42
+ trigger: { type: String, default: 'click', reflect: true },
43
+ matchWidth: { type: Boolean, default: false, reflect: true, attribute: 'match-width' },
40
44
  };
41
45
 
42
46
  static template = () => null;
@@ -104,6 +108,7 @@ export class UIPopover extends UIElement {
104
108
  this.#anchorCleanup = anchorPopover(trigger, content, {
105
109
  placement: this.placement,
106
110
  gap: this.gap,
111
+ matchWidth: this.matchWidth,
107
112
  });
108
113
 
109
114
  if (this.trigger === 'click' || this.trigger === 'manual') {
@@ -12,9 +12,12 @@
12
12
  mode (dimmer = neutral-925, darker than the neutral-875 page it floats
13
13
  over — the panel read near-black). Matches select-ui's own dropdown
14
14
  surface exactly, so the two overlays on a shell's topbar (a select
15
- menu and a popover panel) sit on one plane. */
15
+ menu and a popover panel) sit on one plane. Border matches too
16
+ (gh#857) — `--a-ui-border` (→ outline), same token select-ui's
17
+ `[slot="listbox"]` uses, not outline-variant (that's for internal
18
+ dividers — separators, group rules — a different role). */
16
19
  --popover-bg: var(--md-sys-color-neutral-surface-bright);
17
- --popover-border: var(--md-sys-color-neutral-outline-variant);
20
+ --popover-border: var(--a-ui-border);
18
21
  --popover-shadow: var(--a-shadow-lg);
19
22
  }
20
23