@adia-ai/web-components 0.0.21 → 0.0.23

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 (40) hide show
  1. package/README.md +1 -1
  2. package/components/action-list/action-list.css +13 -4
  3. package/components/agent-reasoning/agent-reasoning.js +6 -3
  4. package/components/alert/alert.css +19 -15
  5. package/components/alert/alert.js +8 -13
  6. package/components/avatar/avatar.css +8 -9
  7. package/components/button/button.css +27 -24
  8. package/components/card/card.css +1 -0
  9. package/components/check/check.css +15 -10
  10. package/components/col/col.css +3 -0
  11. package/components/description-list/description-list.css +9 -9
  12. package/components/grid/grid.css +6 -1
  13. package/components/input/input.css +15 -4
  14. package/components/list/list.css +8 -0
  15. package/components/menu/menu.css +22 -11
  16. package/components/modal/modal.js +40 -4
  17. package/components/option-card/option-card.css +9 -5
  18. package/components/progress/progress.css +8 -0
  19. package/components/progress-row/progress-row.js +9 -0
  20. package/components/radio/radio.css +13 -9
  21. package/components/rating/rating.css +7 -4
  22. package/components/richtext/richtext.css +138 -0
  23. package/components/row/row.css +7 -1
  24. package/components/segment/segment.css +8 -5
  25. package/components/select/select.css +4 -1
  26. package/components/stack/stack.css +3 -0
  27. package/components/stepper/stepper.css +12 -4
  28. package/components/stepper/stepper.js +7 -7
  29. package/components/swiper/swiper.css +10 -0
  30. package/components/swiper/swiper.js +77 -0
  31. package/components/switch/switch.css +15 -9
  32. package/components/tag/tag.css +7 -4
  33. package/components/toggle-group/toggle-group.css +9 -4
  34. package/package.json +1 -1
  35. package/patterns/app-nav/app-nav.js +13 -0
  36. package/patterns/app-nav-item/app-nav-item.css +15 -9
  37. package/patterns/section-nav-item/section-nav-item.css +13 -2
  38. package/styles/colors/semantics.css +5 -5
  39. package/styles/prose.css +5 -5
  40. package/styles/tokens.css +57 -0
package/README.md CHANGED
@@ -199,7 +199,7 @@ the `streams` registry export from `core/data-stream.js`.
199
199
 
200
200
  Implementation: `core/data-stream.js` (~360 lines). Full
201
201
  attribute table + live demos:
202
- [`/site/components/chart#data-stream`](./site/pages/components/chart/index.html).
202
+ [`/site/components/chart#data-stream`](../../site/pages/components/chart/index.html).
203
203
 
204
204
  ## Build
205
205
 
@@ -14,6 +14,19 @@
14
14
  }
15
15
  }
16
16
 
17
+ /* Safari 17.x bug: `:scope:hover` inside `@scope` doesn't match the scope
18
+ root. Plain selector outside the @scope works. `:focus-visible` matches
19
+ correctly inside @scope, so the combined `:hover, :focus-visible` rules
20
+ are split — `:hover` moves out, `:focus-visible` stays. See
21
+ docs/BROWSER-COMPAT.md §3a. */
22
+ action-item-ui:hover {
23
+ background: var(--action-item-bg-hover);
24
+ color: var(--action-item-fg-hover);
25
+ }
26
+ action-item-ui:hover [slot="icon"] {
27
+ color: var(--action-item-icon-fg-hover);
28
+ }
29
+
17
30
  @scope (action-item-ui) {
18
31
  :where(:scope) {
19
32
  /* Reuse menu-item visual language for consistency */
@@ -59,12 +72,9 @@
59
72
  color var(--action-item-duration) var(--action-item-easing);
60
73
  }
61
74
 
62
- :scope:hover,
63
75
  :scope:focus-visible {
64
76
  background: var(--action-item-bg-hover);
65
77
  color: var(--action-item-fg-hover);
66
- }
67
- :scope:focus-visible {
68
78
  box-shadow: var(--action-item-focus-ring);
69
79
  }
70
80
 
@@ -75,7 +85,6 @@
75
85
  color: var(--action-item-icon-fg);
76
86
  transition: color var(--action-item-duration) var(--action-item-easing);
77
87
  }
78
- :scope:hover [slot="icon"],
79
88
  :scope:focus-visible [slot="icon"] {
80
89
  color: var(--action-item-icon-fg-hover);
81
90
  }
@@ -371,9 +371,12 @@ class AdiaAgentReasoning extends AdiaElement {
371
371
  #makeStepItem(step) {
372
372
  const item = document.createElement('timeline-item-ui');
373
373
  item.setAttribute('text', step.label || '');
374
- if (step.status === 'done') item.setAttribute('completed', '');
375
- if (step.status === 'error') item.setAttribute('error', '');
376
- if (step.status === 'active') { item.setAttribute('active', ''); item.setAttribute('spinner', ''); }
374
+ // Map step.status canonical timeline-item-ui [status] enum.
375
+ // The legacy [completed]/[active]/[error] Booleans were removed
376
+ // in the Phase 6 cut (2026-04-27); only [status] is honoured.
377
+ if (step.status === 'done') item.setAttribute('status', 'completed');
378
+ if (step.status === 'error') item.setAttribute('status', 'error');
379
+ if (step.status === 'active') { item.setAttribute('status', 'active'); item.setAttribute('spinner', ''); }
377
380
 
378
381
  const duration = step.durationLabel
379
382
  || (step.duration != null ? formatDuration(step.duration) : '');
@@ -5,8 +5,6 @@
5
5
  --alert-fg: var(--a-fg);
6
6
  --alert-border: var(--a-border-subtle);
7
7
  --alert-icon-fg: var(--a-fg-muted);
8
- --alert-close-fg: var(--a-fg-muted);
9
- --alert-close-fg-hover: var(--a-fg);
10
8
 
11
9
  /* ── Layout ── */
12
10
  --alert-radius: var(--a-radius-md);
@@ -15,19 +13,25 @@
15
13
  --alert-gap: var(--a-space-2);
16
14
 
17
15
  /* ── Typography ── */
18
- --alert-font: var(--a-ui-size);
16
+ --alert-font: var(--a-ui-size);
17
+ --alert-line-height: var(--a-ui-line-height, 1.5);
19
18
  }
20
19
 
21
20
  :scope {
22
21
  /* ── Base ── */
23
22
  box-sizing: border-box;
24
23
  display: flex;
25
- align-items: center;
24
+ /* `align-items: start` so the leading icon and close button anchor
25
+ to the top — keeps the icon visually paired with the first line of
26
+ text on multi-line alerts. The leading slot's own height = body
27
+ line-height (below) means the icon optically centers on line 1. */
28
+ align-items: start;
26
29
  gap: var(--alert-gap);
27
30
  padding: var(--alert-py) var(--alert-px);
28
31
  border: 1px solid var(--alert-border);
29
32
  border-radius: var(--alert-radius);
30
33
  font-size: var(--alert-font);
34
+ line-height: var(--alert-line-height);
31
35
  color: var(--alert-fg);
32
36
  background: var(--alert-bg);
33
37
  }
@@ -65,6 +69,11 @@
65
69
  :scope [slot="leading"] {
66
70
  flex-shrink: 0;
67
71
  color: var(--alert-icon-fg);
72
+ /* Box height = one line of body so the icon optical-centers on the
73
+ first line under `align-items: start`. */
74
+ display: inline-flex;
75
+ align-items: center;
76
+ min-height: calc(var(--alert-font) * var(--alert-line-height));
68
77
  /* `ensure()` appends the leading-slot icon to the host, which
69
78
  puts it after any consumer-provided content in DOM order.
70
79
  Force it to the visual lead via flex `order` so the icon
@@ -77,17 +86,12 @@
77
86
  min-width: 0;
78
87
  }
79
88
 
89
+ /* Close button is a `<button-ui icon="x" variant="ghost" size="sm">`
90
+ stamped by alert.js — it brings its own focus ring, hover state,
91
+ and transitions. Box height matches the leading slot so X and icon
92
+ sit on the same first-line baseline. */
80
93
  :scope [slot="close"] {
81
- background: none;
82
- border: none;
83
- cursor: pointer;
84
- color: var(--alert-close-fg);
85
- padding: 0;
86
- line-height: 1;
87
- font-size: 1rem;
88
- }
89
-
90
- :scope [slot="close"]:hover {
91
- color: var(--alert-close-fg-hover);
94
+ flex-shrink: 0;
95
+ min-height: calc(var(--alert-font) * var(--alert-line-height));
92
96
  }
93
97
  }
@@ -24,22 +24,19 @@ class AdiaAlert extends AdiaElement {
24
24
  static parts = {
25
25
  leading: '<icon-ui slot="leading"></icon-ui>',
26
26
  content: '<span slot="content"></span>',
27
- close: '<button slot="close" type="button" aria-label="Close">\u00d7</button>',
27
+ /* Close affordance is a real button-ui \u2014 inherits the system focus
28
+ ring, hover transition, and icon sizing automatically. Listen for
29
+ the canonical `press` event (button-ui dispatches on click + Enter
30
+ + Space, no extra keydown wiring needed). */
31
+ close: '<button-ui slot="close" icon="x" variant="ghost" size="sm" aria-label="Close"></button-ui>',
28
32
  };
29
33
 
30
34
  static template = () => null;
31
35
 
32
- #onClick = (e) => {
36
+ #onPress = (e) => {
33
37
  if (e.target.closest('[slot="close"]')) this.#close();
34
38
  };
35
39
 
36
- #onKeydown = (e) => {
37
- if (e.target.closest('[slot="close"]') && (e.key === 'Enter' || e.key === ' ')) {
38
- e.preventDefault();
39
- this.#close();
40
- }
41
- };
42
-
43
40
  connected() {
44
41
  this.#updateRole();
45
42
 
@@ -48,13 +45,11 @@ class AdiaAlert extends AdiaElement {
48
45
  this.ensure('content');
49
46
  if (this.closable) this.ensure('close');
50
47
 
51
- this.addEventListener('click', this.#onClick);
52
- this.addEventListener('keydown', this.#onKeydown);
48
+ this.addEventListener('press', this.#onPress);
53
49
  }
54
50
 
55
51
  disconnected() {
56
- this.removeEventListener('click', this.#onClick);
57
- this.removeEventListener('keydown', this.#onKeydown);
52
+ this.removeEventListener('press', this.#onPress);
58
53
  }
59
54
 
60
55
  render() {
@@ -74,6 +74,14 @@
74
74
  AVATAR-GROUP-N — Overlapping avatar stack with +N overflow
75
75
  ═══════════════════════════════════════════════════════════════ */
76
76
 
77
+ /* Safari 17.x bug: `:scope:hover` inside `@scope` doesn't match the scope
78
+ root. Plain selector outside the @scope works. The two `:scope:hover`
79
+ rules combine into one rule list outside. See docs/BROWSER-COMPAT.md §3a. */
80
+ avatar-group-ui:hover > avatar-ui:not(:first-child),
81
+ avatar-group-ui:hover > [slot="overflow"] {
82
+ margin-inline-start: var(--avatar-group-spread);
83
+ }
84
+
77
85
  @scope (avatar-group-ui) {
78
86
  :where(:scope) {
79
87
  /* ── Layout ── */
@@ -123,11 +131,6 @@
123
131
  z-index: 99;
124
132
  }
125
133
 
126
- /* Group hover: spread apart */
127
- :scope:hover > avatar-ui:not(:first-child) {
128
- margin-inline-start: var(--avatar-group-spread);
129
- }
130
-
131
134
  /* Overflow counter */
132
135
  [slot="overflow"] {
133
136
  display: inline-flex;
@@ -147,10 +150,6 @@
147
150
  transition: margin var(--avatar-group-duration) var(--avatar-group-easing);
148
151
  }
149
152
 
150
- :scope:hover > [slot="overflow"] {
151
- margin-inline-start: var(--avatar-group-spread);
152
- }
153
-
154
153
  /* Size variants — adjust counter + overlap */
155
154
  :scope[size="xs"] { --avatar-group-counter-size: 1.25rem; --avatar-group-overlap: -0.3rem; }
156
155
  :scope[size="sm"] { --avatar-group-counter-size: 1.75rem; --avatar-group-overlap: -0.4rem; }
@@ -1,3 +1,28 @@
1
+ /* Safari 17.x bug: `:scope[attr]:hover` inside `@scope` doesn't match the
2
+ scope root. Plain selectors outside the @scope work — custom-property
3
+ tokens still resolve via inheritance. Specificity (0,1,1 / 0,2,1) is
4
+ preserved. See docs/BROWSER-COMPAT.md §3a. */
5
+ button-ui:not([disabled]):hover,
6
+ button-ui[variant="primary"]:not([disabled]):hover {
7
+ --button-bg: var(--button-bg-hover);
8
+ --button-fg: var(--button-fg-hover);
9
+ --button-border: var(--button-border-hover);
10
+ }
11
+ button-ui[variant="ghost"]:not([disabled]):hover {
12
+ --button-bg: var(--button-bg-ghost-hover);
13
+ --button-fg: var(--button-fg-ghost-hover);
14
+ --button-border: var(--button-border-ghost-hover);
15
+ }
16
+ button-ui[variant="outline"]:not([disabled]):hover {
17
+ --button-fg: var(--button-fg-hover);
18
+ --button-border: var(--button-border-hover);
19
+ }
20
+ button-ui[color="danger"]:not([disabled]):hover {
21
+ --button-bg: var(--button-bg-hover);
22
+ --button-fg: var(--button-fg-hover);
23
+ --button-border: var(--button-border-hover);
24
+ }
25
+
1
26
  @scope (button-ui) {
2
27
  :where(:scope) {
3
28
  --button-bg: var(--a-ui-bg);
@@ -136,30 +161,8 @@
136
161
  --button-fg: var(--a-warning-fg);
137
162
  }
138
163
 
139
- /* ── Hover (after variants so it wins on specificity via :hover) ── */
140
- :scope:not([disabled]):hover,
141
- :scope[variant="primary"]:not([disabled]):hover {
142
- --button-bg: var(--button-bg-hover);
143
- --button-fg: var(--button-fg-hover);
144
- --button-border: var(--button-border-hover);
145
- }
146
-
147
- :scope[variant="ghost"]:not([disabled]):hover {
148
- --button-bg: var(--button-bg-ghost-hover);
149
- --button-fg: var(--button-fg-ghost-hover);
150
- --button-border: var(--button-border-ghost-hover);
151
- }
152
-
153
- :scope[variant="outline"]:not([disabled]):hover {
154
- --button-fg: var(--button-fg-hover);
155
- --button-border: var(--button-border-hover);
156
- }
157
-
158
- :scope[color="danger"]:not([disabled]):hover {
159
- --button-bg: var(--button-bg-hover);
160
- --button-fg: var(--button-fg-hover);
161
- --button-border: var(--button-border-hover);
162
- }
164
+ /* Hover rules moved outside @scope see Safari 17.x bug note at
165
+ top of file. */
163
166
 
164
167
  /* Size is handled by the universal [size] attribute system
165
168
  which sets --a-size, --a-ui-px, --a-ui-size.
@@ -61,6 +61,7 @@
61
61
  box-shadow: var(--card-shadow);
62
62
  overflow: hidden;
63
63
  corner-shape: superellipse(1.1);
64
+ width: stretch;
64
65
  }
65
66
 
66
67
  /* ═══════ Variants — token-only overrides ═══════ */
@@ -1,3 +1,17 @@
1
+ /* Safari 17.x bug: `:scope*:hover` inside `@scope` doesn't match the
2
+ scope root. The `:scope:hover [slot="box"]` shape — `:hover` on the
3
+ scope root, descendant selector — is also affected; the entire selector
4
+ moves out together. See docs/BROWSER-COMPAT.md §3a. */
5
+ check-ui:not([disabled]):hover [slot="box"] {
6
+ border-color: var(--check-border-hover);
7
+ background: var(--check-bg-hover);
8
+ }
9
+ check-ui[checked]:not([disabled]):hover [slot="box"],
10
+ check-ui[indeterminate]:not([disabled]):hover [slot="box"] {
11
+ background: var(--check-bg-checked-hover);
12
+ border-color: var(--check-bg-checked-hover);
13
+ }
14
+
1
15
  @scope (check-ui) {
2
16
  :where(:scope) {
3
17
  /* ── Tokens ── (size scales with universal [size] attribute via --a-toggle-size) */
@@ -83,10 +97,7 @@
83
97
  background: var(--_dash-bg);
84
98
  }
85
99
 
86
- :scope:not([disabled]):hover [slot="box"] {
87
- border-color: var(--check-border-hover);
88
- background: var(--check-bg-hover);
89
- }
100
+ /* hover rules moved outside @scope — see Safari 17.x bug note at top. */
90
101
 
91
102
  /* Checked — CSS border checkmark */
92
103
  :scope[checked] [slot="box"],
@@ -110,12 +121,6 @@
110
121
  --_icon-h: calc(var(--check-size) * 0.12);
111
122
  }
112
123
 
113
- :scope[checked]:not([disabled]):hover [slot="box"],
114
- :scope[indeterminate]:not([disabled]):hover [slot="box"] {
115
- background: var(--check-bg-checked-hover);
116
- border-color: var(--check-bg-checked-hover);
117
- }
118
-
119
124
  /* Label */
120
125
  :scope[label]::after { content: attr(label); }
121
126
 
@@ -12,6 +12,9 @@
12
12
  gap: var(--col-gap);
13
13
  justify-content: var(--col-justify);
14
14
  align-items: var(--col-align);
15
+ /* Universal [padding] / [margin] opt-in — see tokens.css for scale. */
16
+ padding: var(--a-padding, 0);
17
+ margin: var(--a-margin, 0);
15
18
  /* Neutralize legacy UA rule that maps HTML `align=` attr to `text-align`.
16
19
  Our [align="center"] sets flex cross-axis alignment, not text alignment. */
17
20
  text-align: inherit;
@@ -17,19 +17,15 @@
17
17
  }
18
18
 
19
19
  :scope {
20
- /* ── Base ── */
20
+ /* ── Base ── stacked layout (term above desc, single column). */
21
21
  box-sizing: border-box;
22
22
  display: grid;
23
+ grid-template-columns: 1fr;
23
24
  gap: var(--description-list-gap-row) var(--description-list-gap-column);
24
25
  margin: 0;
25
26
  padding: 0;
26
27
  }
27
28
 
28
- /* Default stacked: term above desc. Each pair in its own implicit row. */
29
- :scope {
30
- grid-template-columns: 1fr;
31
- }
32
-
33
29
  [data-dl-term],
34
30
  dt {
35
31
  margin: 0;
@@ -48,9 +44,13 @@
48
44
  line-height: 1.4;
49
45
  }
50
46
 
51
- /* Separate pairs with extra gap when stacked */
52
- :scope[layout="stacked"] [data-dl-desc]:not(:last-child),
53
- :scope[layout="stacked"] dd:not(:last-child) {
47
+ /* Pair separator: extra margin under each non-last dd so DD→DT (between
48
+ pairs) reads as a wider gap than DT→DD (within a pair). Match anything
49
+ that isn't `[layout="inline"]` so the default case (no attribute set —
50
+ `layout` defaults to "stacked" but only reflects when explicitly set)
51
+ and the explicit `[layout="stacked"]` both get the same rhythm. */
52
+ :scope:not([layout="inline"]) [data-dl-desc]:not(:last-child),
53
+ :scope:not([layout="inline"]) dd:not(:last-child) {
54
54
  margin-bottom: var(--description-list-gap-row);
55
55
  }
56
56
 
@@ -1,6 +1,8 @@
1
1
  @scope (grid-ui) {
2
2
  :where(:scope) {
3
- --grid-gap: var(--a-gap-md);
3
+ /* Read `--a-gap` so the universal `[gap="N"]` rules (tokens.css)
4
+ reach the grid; defaults to `--a-gap-md` at :root. */
5
+ --grid-gap: var(--a-gap);
4
6
  --grid-column-gap: var(--grid-gap);
5
7
  --grid-row-gap: var(--grid-gap);
6
8
  }
@@ -13,6 +15,9 @@
13
15
  grid-auto-columns: 1fr;
14
16
  column-gap: var(--grid-column-gap);
15
17
  row-gap: var(--grid-row-gap);
18
+ /* Universal [padding] / [margin] opt-in — see tokens.css for scale. */
19
+ padding: var(--a-padding, 0);
20
+ margin: var(--a-margin, 0);
16
21
  }
17
22
 
18
23
  /* Explicit column count — switches to template mode */
@@ -1,3 +1,9 @@
1
+ /* Safari 17.x bug: `:scope[attr]:hover` inside `@scope` doesn't match the
2
+ scope root. Plain selector outside works. See docs/BROWSER-COMPAT.md §3a. */
3
+ input-ui[variant="ghost"]:hover {
4
+ --input-bg: var(--a-bg-muted);
5
+ }
6
+
1
7
  @scope (input-ui) {
2
8
  :where(:scope) {
3
9
  /* ── Tokens (wired to --a-ui-*) ── */
@@ -69,7 +75,13 @@
69
75
  baseline centered regardless of line-height. */
70
76
  line-height: 1.4;
71
77
  cursor: text;
72
- transition: border-color var(--input-duration) var(--input-easing);
78
+ /* Cover every property the hover / focus / invalid states change so
79
+ the field doesn't half-snap (border slides, but bg/colour/ring snap). */
80
+ transition:
81
+ background var(--input-duration) var(--input-easing),
82
+ border-color var(--input-duration) var(--input-easing),
83
+ color var(--input-duration) var(--input-easing),
84
+ box-shadow var(--input-duration) var(--input-easing);
73
85
  }
74
86
  :scope:not([disabled]) [slot="field"]:hover {
75
87
  background: var(--input-bg-hover);
@@ -170,7 +182,6 @@
170
182
  --input-border: transparent;
171
183
  --input-border-hover: transparent;
172
184
  }
173
- :scope[variant="ghost"]:hover {
174
- --input-bg: var(--a-bg-muted);
175
- }
185
+ /* :scope[variant="ghost"]:hover moved outside @scope — see Safari
186
+ 17.x bug note at top of file. */
176
187
  }
@@ -57,6 +57,14 @@
57
57
  border-inline-start: 2px solid transparent;
58
58
  padding-inline-start: var(--a-space-2);
59
59
  outline: none;
60
+ transition:
61
+ background var(--a-duration-fast) var(--a-easing),
62
+ color var(--a-duration-fast) var(--a-easing),
63
+ border-color var(--a-duration-fast) var(--a-easing);
64
+ }
65
+
66
+ :scope[selectable] > [role="listitem"]:hover {
67
+ background: var(--a-bg-hover);
60
68
  }
61
69
 
62
70
  :scope[selectable] > [role="listitem"][aria-selected="true"] {
@@ -40,6 +40,28 @@ menu-ui [data-menu-popover] {
40
40
  color: var(--a-fg);
41
41
  }
42
42
 
43
+ /* Safari 17.x bug: `:scope:hover` inside `@scope` doesn't match the scope
44
+ root. Plain selectors outside the @scope work. `:focus-visible` matches
45
+ correctly inside @scope, so the combined `:hover, :focus-visible` rule
46
+ is split — `:hover` moves out, `:focus-visible` stays. The
47
+ `:scope[variant="danger"]:hover` rules also move (same bug shape). See
48
+ docs/BROWSER-COMPAT.md §3a. */
49
+ menu-item-ui:hover {
50
+ --menu-item-bg: var(--menu-item-bg-hover);
51
+ --menu-item-fg: var(--menu-item-fg-hover);
52
+ outline: none;
53
+ }
54
+ menu-item-ui:hover [slot="icon"] {
55
+ color: var(--menu-item-icon-fg-hover);
56
+ }
57
+ menu-item-ui[variant="danger"]:hover {
58
+ --menu-item-bg: var(--menu-item-danger-bg);
59
+ --menu-item-fg: var(--menu-item-danger-fg);
60
+ }
61
+ menu-item-ui[variant="danger"]:hover [slot="icon"] {
62
+ color: var(--menu-item-danger-fg);
63
+ }
64
+
43
65
  @scope (menu-item-ui) {
44
66
  :where(:scope) {
45
67
  /* ── Layout ── */
@@ -84,7 +106,6 @@ menu-ui [data-menu-popover] {
84
106
  color var(--menu-item-duration) var(--menu-item-easing);
85
107
  }
86
108
 
87
- :scope:hover,
88
109
  :scope:focus-visible {
89
110
  --menu-item-bg: var(--menu-item-bg-hover);
90
111
  --menu-item-fg: var(--menu-item-fg-hover);
@@ -98,9 +119,6 @@ menu-ui [data-menu-popover] {
98
119
  color: var(--menu-item-icon-fg);
99
120
  transition: color var(--menu-item-duration) var(--menu-item-easing);
100
121
  }
101
- :scope:hover [slot="icon"] {
102
- color: var(--menu-item-icon-fg-hover);
103
- }
104
122
 
105
123
  /* Text */
106
124
  [slot="text"] {
@@ -112,16 +130,9 @@ menu-ui [data-menu-popover] {
112
130
  :scope[variant="danger"] {
113
131
  --menu-item-fg: var(--a-danger-bg);
114
132
  }
115
- :scope[variant="danger"]:hover {
116
- --menu-item-bg: var(--menu-item-danger-bg);
117
- --menu-item-fg: var(--menu-item-danger-fg);
118
- }
119
133
  :scope[variant="danger"] [slot="icon"] {
120
134
  color: var(--menu-item-danger-fg);
121
135
  }
122
- :scope[variant="danger"]:hover [slot="icon"] {
123
- color: var(--menu-item-danger-fg);
124
- }
125
136
 
126
137
  /* ── Disabled ── */
127
138
  :scope[disabled] {
@@ -143,11 +143,47 @@ class AdiaModal extends AdiaElement {
143
143
  this.drop('close');
144
144
  }
145
145
 
146
- const body = this.ensure('body');
147
- if (body.parentElement !== panel) panel.appendChild(body);
146
+ // Body — accept bare `<section>` tags, explicit `[slot="body"]`, OR
147
+ // unslotted light-DOM children (so `<modal-ui><p>…</p></modal-ui>` works
148
+ // with no markup ceremony). Mirrors drawer-ui's authoring contract.
149
+ const authorBody = [...this.children].find(c =>
150
+ c.getAttribute('slot') === 'body' || (!c.getAttribute('slot') && c.localName === 'section')
151
+ );
152
+ const looseChildren = [...this.children].filter(c =>
153
+ !c.getAttribute('slot') && c.localName !== 'section' && c.localName !== 'dialog' && c.localName !== 'footer'
154
+ );
155
+
156
+ let body;
157
+ if (authorBody) {
158
+ if (authorBody.getAttribute('slot') !== 'body') authorBody.setAttribute('slot', 'body');
159
+ if (authorBody.parentElement !== panel) panel.appendChild(authorBody);
160
+ panel.querySelector(':scope > [slot="body"][data-stamped]')?.remove();
161
+ body = authorBody;
162
+ } else {
163
+ body = this.ensure('body');
164
+ body.setAttribute('data-stamped', '');
165
+ if (body.parentElement !== panel) panel.appendChild(body);
166
+ }
167
+
168
+ // Migrate any remaining unslotted light-DOM children into the body so
169
+ // `<modal-ui><p>…</p></modal-ui>` lands in the visible region.
170
+ for (const child of looseChildren) {
171
+ if (child !== body) body.appendChild(child);
172
+ }
148
173
 
149
- const footer = this.ensure('footer');
150
- if (footer.parentElement !== panel) panel.appendChild(footer);
174
+ // Footer — opt-in via [slot="footer"] or bare <footer> tag (card-ui style).
175
+ const userFooter = [...this.children].find(c =>
176
+ c.getAttribute('slot') === 'footer' || (!c.getAttribute('slot') && c.localName === 'footer')
177
+ );
178
+ if (userFooter) {
179
+ if (userFooter.getAttribute('slot') !== 'footer') userFooter.setAttribute('slot', 'footer');
180
+ if (userFooter.parentElement !== panel) panel.appendChild(userFooter);
181
+ panel.querySelector(':scope > [slot="footer"][data-stamped]')?.remove();
182
+ } else {
183
+ const footer = this.ensure('footer');
184
+ footer.setAttribute('data-stamped', '');
185
+ if (footer.parentElement !== panel) panel.appendChild(footer);
186
+ }
151
187
 
152
188
  // Sync open state
153
189
  if (this.open && !dialog.open) {
@@ -1,3 +1,11 @@
1
+ /* Safari 17.x bug: `:scope:not(...):hover` inside `@scope` doesn't match
2
+ the scope root. Plain selector outside works. See
3
+ docs/BROWSER-COMPAT.md §3a. */
4
+ option-card-ui:not([checked]):not([disabled]):hover {
5
+ background: var(--option-card-bg-hover);
6
+ border-color: var(--option-card-border-hover);
7
+ }
8
+
1
9
  @scope (option-card-ui) {
2
10
  :where(:scope) {
3
11
  /* ── Container ── */
@@ -138,11 +146,7 @@
138
146
  display: block;
139
147
  }
140
148
 
141
- /* ── State: hover (not checked, not disabled) ── */
142
- :scope:not([checked]):not([disabled]):hover {
143
- background: var(--option-card-bg-hover);
144
- border-color: var(--option-card-border-hover);
145
- }
149
+ /* hover rule moved outside @scope see Safari 17.x bug note at top. */
146
150
 
147
151
  /* ── State: checked — accent border + tinted bg + filled radio.
148
152
  The indicator becomes an accent disc with a centered dot of
@@ -37,6 +37,14 @@
37
37
  border-radius: var(--progress-radius);
38
38
  background: var(--progress-fill);
39
39
  transition: width var(--progress-duration) var(--progress-easing);
40
+
41
+ /* Enter animation: paint the fill at 0 on first frame, then transition
42
+ to the inline width set by JS — so a freshly-mounted determinate bar
43
+ animates 0 → value rather than snapping in (or, in some cascades,
44
+ briefly painting at the indeterminate width:100% before settling). */
45
+ @starting-style {
46
+ width: 0;
47
+ }
40
48
  }
41
49
 
42
50
  /* Indeterminate bar shimmer */
@@ -53,6 +53,15 @@ class AdiaProgressRow extends AdiaElement {
53
53
  this.#progressEl = this.querySelector(':scope > progress-ui');
54
54
  if (!this.#progressEl) {
55
55
  this.#progressEl = document.createElement('progress-ui');
56
+ // Pre-set value BEFORE append so progress-ui's first paint is
57
+ // determinate at the target value — `@starting-style { width: 0 }`
58
+ // in progress.css then transitions 0 → value on mount. Without this,
59
+ // the bar mounts indeterminate (width: 100% from the shimmer rule),
60
+ // and `render()` setting value: N triggers a backwards 100% → N
61
+ // animation, which reads as the bar shrinking right-to-left.
62
+ if (this.value != null && this.value >= 0) {
63
+ this.#progressEl.setAttribute('value', String(this.value));
64
+ }
56
65
  this.appendChild(this.#progressEl);
57
66
  }
58
67
  }