@adia-ai/web-components 0.8.30 → 0.8.32

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 (120) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/components/accordion/accordion.d.ts +2 -2
  3. package/components/action-list/action-list.class.js +6 -2
  4. package/components/action-list/action-list.d.ts +1 -1
  5. package/components/agent-artifact/agent-artifact.d.ts +1 -1
  6. package/components/agent-feedback-bar/agent-feedback-bar.d.ts +2 -2
  7. package/components/agent-questions/agent-questions.d.ts +1 -1
  8. package/components/agent-reasoning/agent-reasoning.d.ts +3 -3
  9. package/components/agent-suggestions/agent-suggestions.d.ts +1 -1
  10. package/components/agent-trace/agent-trace.d.ts +1 -1
  11. package/components/alert/alert.d.ts +2 -2
  12. package/components/anchor-bar/anchor-bar.d.ts +2 -2
  13. package/components/avatar/avatar.class.js +0 -1
  14. package/components/avatar/avatar.css +16 -2
  15. package/components/badge/badge.css +30 -2
  16. package/components/breadcrumb/breadcrumb.css +15 -3
  17. package/components/button/button.d.ts +1 -1
  18. package/components/calendar-grid/calendar-grid.d.ts +2 -2
  19. package/components/card/card.css +21 -0
  20. package/components/card/card.yaml +9 -0
  21. package/components/chart/chart.d.ts +4 -4
  22. package/components/chart-legend/chart-legend.d.ts +1 -1
  23. package/components/chat-thread/chat-input.css +1 -1
  24. package/components/chat-thread/chat-input.js +9 -1
  25. package/components/chat-thread/chat-thread.class.js +6 -0
  26. package/components/chat-thread/chat-thread.d.ts +2 -2
  27. package/components/check/check.css +3 -3
  28. package/components/command/command.d.ts +2 -2
  29. package/components/context-menu/context-menu.d.ts +3 -3
  30. package/components/datetime-picker/datetime-picker.d.ts +5 -5
  31. package/components/demo-toggle/demo-toggle.d.ts +1 -1
  32. package/components/drawer/drawer.d.ts +2 -2
  33. package/components/heatmap/heatmap.d.ts +5 -5
  34. package/components/index.js +1 -0
  35. package/components/inline-edit/inline-edit.d.ts +4 -4
  36. package/components/input/input.a2ui.json +4 -1
  37. package/components/input/input.css +18 -0
  38. package/components/input/input.yaml +11 -1
  39. package/components/integration-card/integration-card.d.ts +4 -4
  40. package/components/link/link.d.ts +1 -1
  41. package/components/list/list-item.a2ui.json +6 -0
  42. package/components/list/list-item.yaml +13 -0
  43. package/components/list/list.class.js +28 -7
  44. package/components/list/list.d.ts +1 -1
  45. package/components/list-window/list-window.d.ts +5 -5
  46. package/components/menu/menu.css +2 -2
  47. package/components/menu/menu.d.ts +1 -1
  48. package/components/modal/modal.d.ts +1 -1
  49. package/components/nav/nav.css +8 -0
  50. package/components/nav/nav.d.ts +1 -1
  51. package/components/nav-group/nav-group.css +58 -9
  52. package/components/nav-group/nav-group.d.ts +1 -1
  53. package/components/nav-item/nav-item.css +7 -4
  54. package/components/nav-item/nav-item.d.ts +1 -1
  55. package/components/noodles/noodles.d.ts +3 -3
  56. package/components/option-card/option-card.css +1 -1
  57. package/components/pagination/pagination.d.ts +1 -1
  58. package/components/pane/pane.d.ts +1 -1
  59. package/components/password-strength/password-strength.d.ts +1 -1
  60. package/components/pipeline-status/pipeline-status.a2ui.json +14 -8
  61. package/components/pipeline-status/pipeline-status.d.ts +5 -4
  62. package/components/pipeline-status/pipeline-status.yaml +17 -11
  63. package/components/radio/radio.a2ui.json +4 -3
  64. package/components/radio/radio.css +2 -2
  65. package/components/radio/radio.yaml +7 -14
  66. package/components/radio-group/radio-group.a2ui.json +118 -0
  67. package/components/radio-group/radio-group.class.js +75 -0
  68. package/components/radio-group/radio-group.css +31 -0
  69. package/components/radio-group/radio-group.d.ts +20 -0
  70. package/components/radio-group/radio-group.examples.md +37 -0
  71. package/components/radio-group/radio-group.js +17 -0
  72. package/components/radio-group/radio-group.yaml +114 -0
  73. package/components/segmented/segmented.a2ui.json +5 -0
  74. package/components/segmented/segmented.class.js +20 -2
  75. package/components/segmented/segmented.yaml +5 -0
  76. package/components/stat/stat.css +12 -8
  77. package/components/stat/stat.js +66 -1
  78. package/components/stat/stat.yaml +3 -0
  79. package/components/stepper/stepper-item.a2ui.json +5 -0
  80. package/components/stepper/stepper-item.yaml +11 -0
  81. package/components/stepper/stepper.a2ui.json +38 -3
  82. package/components/stepper/stepper.class.js +199 -2
  83. package/components/stepper/stepper.css +30 -0
  84. package/components/stepper/stepper.d.ts +28 -1
  85. package/components/stepper/stepper.yaml +76 -2
  86. package/components/stream/stream.d.ts +3 -3
  87. package/components/swatch/swatch.class.js +1 -1
  88. package/components/swatch/swatch.d.ts +1 -1
  89. package/components/swiper/swiper.d.ts +3 -3
  90. package/components/table/table.a2ui.json +2 -2
  91. package/components/table/table.d.ts +7 -7
  92. package/components/table/table.yaml +9 -1
  93. package/components/table-toolbar/table-toolbar.d.ts +4 -4
  94. package/components/tabs/tabs.css +11 -3
  95. package/components/tabs/tabs.d.ts +1 -1
  96. package/components/tag/tag.a2ui.json +1 -1
  97. package/components/tag/tag.css +78 -13
  98. package/components/tag/tag.d.ts +12 -8
  99. package/components/tag/tag.examples.md +4 -4
  100. package/components/tag/tag.yaml +18 -7
  101. package/components/tags-input/tags-input.d.ts +4 -4
  102. package/components/timeline/timeline.d.ts +1 -1
  103. package/components/toc/toc.d.ts +1 -1
  104. package/components/toggle-group/toggle-group.d.ts +1 -1
  105. package/components/toggle-scheme/toggle-scheme.d.ts +1 -1
  106. package/components/tour/tour.d.ts +4 -4
  107. package/components/tree/tree.d.ts +1 -1
  108. package/core/icons-phosphor.js +129 -71
  109. package/core/provider.js +84 -23
  110. package/dist/host.min.css +1 -1
  111. package/dist/host.sheet.js +1 -1
  112. package/dist/theme-provider.min.js +1 -1
  113. package/dist/web-components.min.css +1 -1
  114. package/dist/web-components.min.js +89 -87
  115. package/dist/web-components.sheet.js +1 -1
  116. package/package.json +1 -1
  117. package/styles/api/sizing-fallback.css +32 -0
  118. package/styles/api/sizing.css +7 -3
  119. package/styles/components.css +1 -0
  120. package/styles/tokens.css +1 -0
@@ -3,12 +3,12 @@
3
3
  For [checked] / [indeterminate], when JS toggles the attribute off,
4
4
  Safari leaves the visual stuck — selectors moved out as plain
5
5
  `tagname[attr]` rules. See docs/BROWSER-COMPAT.md §3a. */
6
- check-ui:not([disabled]):hover [slot="box"] {
6
+ check-ui:not([disabled]):hover:not(:focus-visible) [slot="box"] {
7
7
  border-color: var(--check-border-hover);
8
8
  background: var(--check-bg-hover);
9
9
  }
10
- check-ui[checked]:not([disabled]):hover [slot="box"],
11
- check-ui[indeterminate]:not([disabled]):hover [slot="box"] {
10
+ check-ui[checked]:not([disabled]):hover:not(:focus-visible) [slot="box"],
11
+ check-ui[indeterminate]:not([disabled]):hover:not(:focus-visible) [slot="box"] {
12
12
  background: var(--check-bg-checked-hover);
13
13
  border-color: var(--check-bg-checked-hover);
14
14
  }
@@ -38,11 +38,11 @@ export class UICommand extends UIElement {
38
38
  /** Placeholder text for the search input */
39
39
  placeholder: string;
40
40
 
41
+ addEventListener(type: 'dismiss', listener: (ev: CommandDismissEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
42
+ addEventListener(type: 'select', listener: (ev: CommandSelectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
41
43
  addEventListener<K extends keyof HTMLElementEventMap>(
42
44
  type: K,
43
45
  listener: (this: UICommand, ev: HTMLElementEventMap[K]) => unknown,
44
46
  options?: boolean | AddEventListenerOptions,
45
47
  ): void;
46
- addEventListener(type: 'dismiss', listener: (ev: CommandDismissEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
47
- addEventListener(type: 'select', listener: (ev: CommandSelectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
48
48
  }
@@ -59,12 +59,12 @@ export class UIContextMenu extends UIElement {
59
59
  /** Programmatic open state. Set true to open at target center. */
60
60
  open: boolean;
61
61
 
62
+ addEventListener(type: 'context-menu-close', listener: (ev: ContextMenuCloseEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
63
+ addEventListener(type: 'context-menu-open', listener: (ev: ContextMenuOpenEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
64
+ addEventListener(type: 'context-menu-select', listener: (ev: ContextMenuSelectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
62
65
  addEventListener<K extends keyof HTMLElementEventMap>(
63
66
  type: K,
64
67
  listener: (this: UIContextMenu, ev: HTMLElementEventMap[K]) => unknown,
65
68
  options?: boolean | AddEventListenerOptions,
66
69
  ): void;
67
- addEventListener(type: 'context-menu-close', listener: (ev: ContextMenuCloseEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
68
- addEventListener(type: 'context-menu-open', listener: (ev: ContextMenuOpenEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
69
- addEventListener(type: 'context-menu-select', listener: (ev: ContextMenuSelectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
70
70
  }
@@ -75,14 +75,14 @@ document locale. `h12` forces a 12-hour cycle with an AM/PM
75
75
  /** Current value as ISO 8601 datetime ("YYYY-MM-DDTHH:mm" or "YYYY-MM-DDTHH:mm:ss"). Empty when no value selected. */
76
76
  value: string;
77
77
 
78
- addEventListener<K extends keyof HTMLElementEventMap>(
79
- type: K,
80
- listener: (this: UIDatetimePicker, ev: HTMLElementEventMap[K]) => unknown,
81
- options?: boolean | AddEventListenerOptions,
82
- ): void;
83
78
  addEventListener(type: 'change', listener: (ev: DatetimePickerChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
84
79
  addEventListener(type: 'close', listener: (ev: DatetimePickerCloseEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
85
80
  addEventListener(type: 'input', listener: (ev: DatetimePickerInputEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
86
81
  addEventListener(type: 'invalid', listener: (ev: DatetimePickerInvalidEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
87
82
  addEventListener(type: 'open', listener: (ev: DatetimePickerOpenEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
83
+ addEventListener<K extends keyof HTMLElementEventMap>(
84
+ type: K,
85
+ listener: (this: UIDatetimePicker, ev: HTMLElementEventMap[K]) => unknown,
86
+ options?: boolean | AddEventListenerOptions,
87
+ ): void;
88
88
  }
@@ -29,10 +29,10 @@ export class UIDemoToggle extends UIElement {
29
29
  /** Current toggle state ("on" | "off"). Reflected as data-state on the host. */
30
30
  state: '' | 'on' | 'off';
31
31
 
32
+ addEventListener(type: 'change', listener: (ev: DemoToggleChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
32
33
  addEventListener<K extends keyof HTMLElementEventMap>(
33
34
  type: K,
34
35
  listener: (this: UIDemoToggle, ev: HTMLElementEventMap[K]) => unknown,
35
36
  options?: boolean | AddEventListenerOptions,
36
37
  ): void;
37
- addEventListener(type: 'change', listener: (ev: DemoToggleChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
38
38
  }
@@ -32,11 +32,11 @@ export class UIDrawer extends UIElement {
32
32
  /** Title text displayed in the drawer header */
33
33
  text: string;
34
34
 
35
+ addEventListener(type: 'close', listener: (ev: DrawerCloseEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
36
+ addEventListener(type: 'opened', listener: (ev: DrawerOpenedEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
35
37
  addEventListener<K extends keyof HTMLElementEventMap>(
36
38
  type: K,
37
39
  listener: (this: UIDrawer, ev: HTMLElementEventMap[K]) => unknown,
38
40
  options?: boolean | AddEventListenerOptions,
39
41
  ): void;
40
- addEventListener(type: 'close', listener: (ev: DrawerCloseEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
41
- addEventListener(type: 'opened', listener: (ev: DrawerOpenedEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
42
42
  }
@@ -36,14 +36,14 @@ export class UIHeatmap extends UIElement {
36
36
  /** ISO start date; labels months in day-grid mode */
37
37
  startDate: string;
38
38
 
39
- addEventListener<K extends keyof HTMLElementEventMap>(
40
- type: K,
41
- listener: (this: UIHeatmap, ev: HTMLElementEventMap[K]) => unknown,
42
- options?: boolean | AddEventListenerOptions,
43
- ): void;
44
39
  addEventListener(type: 'cell-click', listener: (ev: HeatmapCellClickEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
45
40
  addEventListener(type: 'cell-hover', listener: (ev: HeatmapCellHoverEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
46
41
  addEventListener(type: 'chart-hover', listener: (ev: HeatmapChartHoverEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
47
42
  addEventListener(type: 'chart-leave', listener: (ev: HeatmapChartLeaveEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
48
43
  addEventListener(type: 'chart-select', listener: (ev: HeatmapChartSelectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
44
+ addEventListener<K extends keyof HTMLElementEventMap>(
45
+ type: K,
46
+ listener: (this: UIHeatmap, ev: HTMLElementEventMap[K]) => unknown,
47
+ options?: boolean | AddEventListenerOptions,
48
+ ): void;
49
49
  }
@@ -24,6 +24,7 @@ export { UIInput } from './input/input.js';
24
24
  export { UITextarea } from './textarea/textarea.js';
25
25
  export { UICheck } from './check/check.js';
26
26
  export { UIRadio } from './radio/radio.js';
27
+ export { UIRadioGroup } from './radio-group/radio-group.js';
27
28
  export { UIOptionCard } from './option-card/option-card.js';
28
29
  export { UIIntegrationCard } from './integration-card/integration-card.js';
29
30
  export { UISwitch } from './switch/switch.js';
@@ -40,13 +40,13 @@ by consumers. Listen to `edit-start` / `edit-end` events instead.
40
40
  /** Hint text shown when the value is empty (inline-edit reads this in the static state). */
41
41
  placeholder: string;
42
42
 
43
+ addEventListener(type: 'cancel', listener: (ev: InlineEditCancelEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
44
+ addEventListener(type: 'change', listener: (ev: InlineEditChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
45
+ addEventListener(type: 'edit-end', listener: (ev: InlineEditEditEndEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
46
+ addEventListener(type: 'edit-start', listener: (ev: InlineEditEditStartEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
43
47
  addEventListener<K extends keyof HTMLElementEventMap>(
44
48
  type: K,
45
49
  listener: (this: UIInlineEdit, ev: HTMLElementEventMap[K]) => unknown,
46
50
  options?: boolean | AddEventListenerOptions,
47
51
  ): void;
48
- addEventListener(type: 'cancel', listener: (ev: InlineEditCancelEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
49
- addEventListener(type: 'change', listener: (ev: InlineEditChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
50
- addEventListener(type: 'edit-end', listener: (ev: InlineEditEditEndEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
51
- addEventListener(type: 'edit-start', listener: (ev: InlineEditEditStartEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
52
52
  }
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://adiaui.dev/a2ui/v0_9/components/Input.json",
4
4
  "title": "Input",
5
- "description": "Text input field with contenteditable surface. Supports prefix/suffix icons, label, form participation, and a `type=\"number\"` mode that renders [+]/[-] stepper buttons, numeric input filtering, and ARIA spinbutton semantics — no native `<input type=\"number\">` under the hood. Password type uses a native `<input>` (only path that still wraps native, for `-webkit-text-security` disc masking).",
5
+ "description": "Text input field with contenteditable surface. Supports prefix/suffix icons, label, form participation, and a `type=\"number\"` mode that renders [+]/[-] stepper buttons, numeric input filtering, and ARIA spinbutton semantics — no native `<input type=\"number\">` under the hood. Password type uses a native `<input>` (only path that still wraps native, for `-webkit-text-security` disc masking). Has a 20ch minimum width (`--input-min-width`, mirrors the native `<input>` baseline) so a bare, placeholder-only instance stays usable inside a flex row (row-ui) with no explicit sizing — explicit `width`/`flex` or CSS can size the host above the floor; values below the floor remain clamped unless `--input-min-width` is lowered.",
6
6
  "type": "object",
7
7
  "allOf": [
8
8
  {
@@ -319,6 +319,9 @@
319
319
  "--input-line-height": {
320
320
  "description": "Override line height"
321
321
  },
322
+ "--input-min-width": {
323
+ "description": "Override the minimum-width floor (20ch, mirrors the native `<input>` baseline). Keeps an empty, placeholder-only instance legible when placed directly in a flex row (row-ui) with no explicit width/flex sizing — the placeholder renders via an out-of-flow ::before that contributes no intrinsic width of its own, so without this floor the host collapses to near-zero as a flex item. Explicit sizing (width/flex) can grow the host above the floor but does not bypass it — set to 0 or another lower value to permit a narrower explicit size."
324
+ },
322
325
  "--input-padding": {
323
326
  "description": "Override base padding (density-scaled)"
324
327
  },
@@ -32,6 +32,11 @@ input-ui:not([disabled]) [slot="field"]:hover [slot="suffix"] {
32
32
  --input-affix-fg: var(--a-ui-text-placeholder);
33
33
  --input-field-gap: var(--a-space-1);
34
34
  --input-label-min-width: 8ch;
35
+ /* Native-<input> baseline (historically ~20 characters). Floors the
36
+ host so an empty, placeholder-only instance stays legible as a
37
+ flex item — see the :scope min-width rule below for the collapse
38
+ mechanism this guards against. */
39
+ --input-min-width: 20ch;
35
40
 
36
41
  /* ── Transitions ── */
37
42
  --input-duration: var(--a-duration-fast);
@@ -53,6 +58,19 @@ input-ui:not([disabled]) [slot="field"]:hover [slot="suffix"] {
53
58
  /* ── Base ── */
54
59
  box-sizing: border-box;
55
60
  display: block;
61
+ /* `display: block` gives no width once this host becomes a flex item
62
+ (row-ui, or any display:flex parent) — flex items size from
63
+ content/flex-basis, not display type. The one child that could
64
+ drive content-based sizing, [slot="text"], is EMPTY when the
65
+ input has no value; its placeholder renders via an out-of-flow
66
+ ::before (deliberately, for caret positioning — see the pseudo
67
+ below) that contributes zero intrinsic width. Without a floor,
68
+ an empty placeholder-only input-ui collapses to near-zero in any
69
+ flex context that doesn't otherwise force a width (gh#781).
70
+ min-width, not width, so explicit width/flex can size the host
71
+ above the floor; values below the floor remain clamped. Override
72
+ via --input-min-width (e.g. 0 to opt back out) or size explicitly. */
73
+ min-width: var(--input-min-width);
56
74
  }
57
75
 
58
76
  /* Inline label — sits inside [slot="field"] as a leading caption,
@@ -8,7 +8,9 @@ version: 1
8
8
  description: Text input field with contenteditable surface. Supports prefix/suffix icons, label, form participation, and a
9
9
  `type="number"` mode that renders [+]/[-] stepper buttons, numeric input filtering, and ARIA spinbutton semantics — no native
10
10
  `<input type="number">` under the hood. Password type uses a native `<input>` (only path that still wraps native, for `-webkit-text-security`
11
- disc masking).
11
+ disc masking). Has a 20ch minimum width (`--input-min-width`, mirrors the native `<input>` baseline) so a bare,
12
+ placeholder-only instance stays usable inside a flex row (row-ui) with no explicit sizing — explicit `width`/`flex`
13
+ or CSS can size the host above the floor; values below the floor remain clamped unless `--input-min-width` is lowered.
12
14
  # Per ADR-0027 — primitives that programmatically create other primitives
13
15
  # do NOT auto-import them. Consumer (or demo shell) must explicitly import.
14
16
  composes:
@@ -230,6 +232,14 @@ tokens:
230
232
  description: Override hover state border color
231
233
  --input-line-height:
232
234
  description: Override line height
235
+ --input-min-width:
236
+ description: Override the minimum-width floor (20ch, mirrors the native `<input>`
237
+ baseline). Keeps an empty, placeholder-only instance legible when placed directly
238
+ in a flex row (row-ui) with no explicit width/flex sizing — the placeholder renders
239
+ via an out-of-flow ::before that contributes no intrinsic width of its own, so
240
+ without this floor the host collapses to near-zero as a flex item. Explicit sizing
241
+ (width/flex) can grow the host above the floor but does not bypass it — set to 0
242
+ or another lower value to permit a narrower explicit size.
233
243
  --input-padding:
234
244
  description: Override base padding (density-scaled)
235
245
  --input-placeholder-color:
@@ -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
  }
@@ -60,8 +60,14 @@
60
60
  "action": {
61
61
  "description": "Trailing action affordance on the row (typically a `<button-ui>`). Promotes the row's grid from 2-column (icon | content) to 3-column (icon | content | action), right-aligned + vertically centered across both content rows. Used by `<onboarding-checklist-ui>` item rows and any consumer that needs an end-aligned action button on a list-item."
62
62
  },
63
+ "content": {
64
+ "description": "Full-row escape hatch: when a [slot=\"content\"] child is present the component stamps NOTHING (no icon/text/description spans) and the child owns the entire row. Use for cards, rows, or any custom composition that the icon | content | action grid cannot express."
65
+ },
63
66
  "icon": {
64
67
  "description": "Override the [icon] glyph with a custom slotted element (e.g. a colored <icon-ui>, an image, or an avatar-ui). Mutually exclusive with the [icon] attribute — slot child wins if both are present."
68
+ },
69
+ "text": {
70
+ "description": "Override slot for richer primary-text markup than the plain [text] attribute string. Styled by `:scope [slot=\"text\"]` (list.css) and honored by UIListItem's stamping logic — a consumer-provided [slot=\"text\"] child is never overwritten (only component-stamped ones are). Undeclared until gh#653; the element source always supported it."
65
71
  }
66
72
  },
67
73
  "states": [],
@@ -30,6 +30,19 @@ props:
30
30
  type: string
31
31
 
32
32
  slots:
33
+ text:
34
+ description: >-
35
+ Override slot for richer primary-text markup than the plain [text]
36
+ attribute string. Styled by `:scope [slot="text"]` (list.css) and
37
+ honored by UIListItem's stamping logic — a consumer-provided
38
+ [slot="text"] child is never overwritten (only component-stamped ones
39
+ are). Undeclared until gh#653; the element source always supported it.
40
+ content:
41
+ description: >-
42
+ Full-row escape hatch: when a [slot="content"] child is present the
43
+ component stamps NOTHING (no icon/text/description spans) and the
44
+ child owns the entire row. Use for cards, rows, or any custom
45
+ composition that the icon | content | action grid cannot express.
33
46
  icon:
34
47
  description: >-
35
48
  Override the [icon] glyph with a custom slotted element (e.g. a colored
@@ -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
 
@@ -220,6 +235,12 @@ export class UIListItem extends UIElement {
220
235
  }
221
236
 
222
237
  render() {
238
+ // A [slot="content"] row owns its own markup entirely — same gate as
239
+ // #stamp() (:189). Without it, a post-connect icon/text/description
240
+ // prop change stamps icon/span elements into a content-slotted row,
241
+ // even though #stamp() never touched it at connect (gh#689).
242
+ if (this.#ownChild('[slot="content"]')) return;
243
+
223
244
  // Sync icon — only touch elements we stamped.
224
245
  const iconEl = this.#ownChild('[slot="icon"]');
225
246
  if (this.icon) {
@@ -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
  }
@@ -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;