@adia-ai/web-components 0.0.22 → 0.0.24

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.
@@ -36,7 +36,6 @@ class AdiaDrawer extends AdiaElement {
36
36
  #closing = false;
37
37
  #previousFocus = null;
38
38
  #closeTimer = null;
39
- #openRaf = null;
40
39
  #dialogRef = null;
41
40
 
42
41
  static properties = {
@@ -60,6 +59,28 @@ class AdiaDrawer extends AdiaElement {
60
59
  // html`` result would trigger stamp() → replaceChildren(), wiping authored
61
60
  // [slot=header|body|footer] before render() can migrate them into the panel.
62
61
 
62
+ constructor() {
63
+ super();
64
+ // Safari requires <dialog>.showModal() to be invoked synchronously inside
65
+ // the click handler. The reactive system schedules render() in a microtask
66
+ // after the property change, which Safari treats as outside the user-gesture
67
+ // window and silently no-ops the showModal. Wrap the auto-installed `open`
68
+ // setter so dialog state syncs in the same synchronous frame as the
69
+ // assignment. See docs/BROWSER-COMPAT.md §3a (Flavor C).
70
+ const desc = Object.getOwnPropertyDescriptor(this, 'open');
71
+ if (desc?.set) {
72
+ const origSet = desc.set;
73
+ Object.defineProperty(this, 'open', {
74
+ get: desc.get,
75
+ set: (v) => {
76
+ origSet.call(this, v);
77
+ this.#syncDialog();
78
+ },
79
+ configurable: true,
80
+ });
81
+ }
82
+ }
83
+
63
84
  #onPress = (e) => {
64
85
  if (e.target.closest('[slot="close"]')) this.open = false;
65
86
  };
@@ -101,10 +122,6 @@ class AdiaDrawer extends AdiaElement {
101
122
  this.#dialogRef.removeEventListener('close', this.#onDialogClose);
102
123
  this.#dialogRef.removeEventListener('click', this.#onDialogClick);
103
124
  }
104
- if (this.#openRaf != null) {
105
- cancelAnimationFrame(this.#openRaf);
106
- this.#openRaf = null;
107
- }
108
125
  if (this.#closeTimer != null) {
109
126
  clearTimeout(this.#closeTimer);
110
127
  this.#closeTimer = null;
@@ -228,15 +245,26 @@ class AdiaDrawer extends AdiaElement {
228
245
  if (userFooter.parentElement !== panel) panel.appendChild(userFooter);
229
246
  }
230
247
 
231
- // Sync open state
248
+ // Sync open state — also syncs synchronously from the `open` setter
249
+ // (see constructor) so showModal() runs in the click handler's gesture
250
+ // frame on Safari.
251
+ this.#syncDialog();
252
+ }
253
+
254
+ #syncDialog() {
255
+ const dialog = this.#dialogRef;
256
+ if (!dialog) return;
232
257
  if (this.open && !dialog.open) {
233
258
  this.#closing = false;
234
259
  this.#previousFocus = document.activeElement;
235
260
  dialog.showModal();
236
- this.#openRaf = requestAnimationFrame(() => {
237
- this.#openRaf = null;
238
- dialog.setAttribute('data-open', '');
239
- });
261
+ // Synchronous reflow instead of rAF — Safari throttles
262
+ // requestAnimationFrame when a top-layer dialog is open, sometimes
263
+ // delaying [data-open] (and the slide-in transition) by tens of
264
+ // seconds. Forcing a reflow keeps the animation start in the same
265
+ // synchronous frame. See docs/BROWSER-COMPAT.md §3a (Flavor C).
266
+ void dialog.offsetHeight;
267
+ dialog.setAttribute('data-open', '');
240
268
  } else if (!this.open && dialog.open && !this.#closing) {
241
269
  this.#animateClose(dialog);
242
270
  }
@@ -244,8 +272,13 @@ class AdiaDrawer extends AdiaElement {
244
272
 
245
273
  #animateClose(dialog) {
246
274
  this.#closing = true;
247
- dialog.removeAttribute('data-open');
275
+ // Set [data-closing] FIRST (carries the transition spec), force a
276
+ // reflow, THEN remove [data-open]. If both attribute changes batch
277
+ // into a single style update, Safari can skip the slide-out animation
278
+ // entirely.
248
279
  dialog.setAttribute('data-closing', '');
280
+ void dialog.offsetHeight;
281
+ dialog.removeAttribute('data-open');
249
282
 
250
283
  this.#closeTimer = setTimeout(() => {
251
284
  this.#closeTimer = null;
@@ -1,3 +1,19 @@
1
+ /* Safari 17.x bug: `:scope[attr]:hover` and `:scope:not(...) [descendant]:hover`
2
+ inside `@scope` don't match the scope root. Plain selectors outside work.
3
+ See docs/BROWSER-COMPAT.md §3a. */
4
+ input-ui[variant="ghost"]:hover {
5
+ --input-bg: var(--a-bg-muted);
6
+ }
7
+ input-ui:not([disabled]) [slot="field"]:hover {
8
+ background: var(--input-bg-hover);
9
+ border-color: var(--input-border-hover);
10
+ color: var(--input-fg-hover);
11
+ }
12
+ input-ui:not([disabled]) [slot="field"]:hover [slot="prefix"],
13
+ input-ui:not([disabled]) [slot="field"]:hover [slot="suffix"] {
14
+ color: var(--input-affix-fg-hover);
15
+ }
16
+
1
17
  @scope (input-ui) {
2
18
  :where(:scope) {
3
19
  /* ── Tokens (wired to --a-ui-*) ── */
@@ -77,15 +93,7 @@
77
93
  color var(--input-duration) var(--input-easing),
78
94
  box-shadow var(--input-duration) var(--input-easing);
79
95
  }
80
- :scope:not([disabled]) [slot="field"]:hover {
81
- background: var(--input-bg-hover);
82
- border-color: var(--input-border-hover);
83
- color: var(--input-fg-hover);
84
- }
85
- :scope:not([disabled]) [slot="field"]:hover [slot="prefix"],
86
- :scope:not([disabled]) [slot="field"]:hover [slot="suffix"] {
87
- color: var(--input-affix-fg-hover);
88
- }
96
+ /* hover rules moved outside @scope — see Safari 17.x bug note at top. */
89
97
  :scope:not([disabled]):focus-within [slot="field"] {
90
98
  /* Canonical ring — consumes the L3 --input-focus-ring token
91
99
  which aliases --a-focus-ring. Border stays stable; the ring
@@ -176,7 +184,6 @@
176
184
  --input-border: transparent;
177
185
  --input-border-hover: transparent;
178
186
  }
179
- :scope[variant="ghost"]:hover {
180
- --input-bg: var(--a-bg-muted);
181
- }
187
+ /* :scope[variant="ghost"]:hover moved outside @scope — see Safari
188
+ 17.x bug note at top of file. */
182
189
  }
@@ -8,7 +8,7 @@
8
8
  --kbd-font: var(--a-font-family-code);
9
9
 
10
10
  /* Size — defaults to md */
11
- --kbd-font-size: var(--a-ui-sm);
11
+ --kbd-font-size: var(--a-ui-tiny);
12
12
  --kbd-height: 1.25rem;
13
13
  --kbd-min-width: 1.25rem;
14
14
  --kbd-px: var(--a-space-1);
@@ -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] {
@@ -36,7 +36,6 @@ class AdiaModal extends AdiaElement {
36
36
  #closing = false;
37
37
  #previousFocus = null;
38
38
  #closeTimer = null;
39
- #openRaf = null;
40
39
  #dialogRef = null;
41
40
 
42
41
  static properties = {
@@ -101,10 +100,6 @@ class AdiaModal extends AdiaElement {
101
100
  this.#dialogRef.removeEventListener('close', this.#onDialogClose);
102
101
  this.#dialogRef.removeEventListener('click', this.#onDialogClick);
103
102
  }
104
- if (this.#openRaf != null) {
105
- cancelAnimationFrame(this.#openRaf);
106
- this.#openRaf = null;
107
- }
108
103
  if (this.#closeTimer != null) {
109
104
  clearTimeout(this.#closeTimer);
110
105
  this.#closeTimer = null;
@@ -185,15 +180,16 @@ class AdiaModal extends AdiaElement {
185
180
  if (footer.parentElement !== panel) panel.appendChild(footer);
186
181
  }
187
182
 
188
- // Sync open state
183
+ // Sync open state. Synchronous reflow instead of rAF — Safari throttles
184
+ // requestAnimationFrame when a top-layer dialog is open, sometimes
185
+ // delaying [data-open] (and the scale-in transition) by tens of seconds.
186
+ // See docs/BROWSER-COMPAT.md §3a (Flavor C).
189
187
  if (this.open && !dialog.open) {
190
188
  this.#closing = false;
191
189
  this.#previousFocus = document.activeElement;
192
190
  dialog.showModal();
193
- this.#openRaf = requestAnimationFrame(() => {
194
- this.#openRaf = null;
195
- dialog.setAttribute('data-open', '');
196
- });
191
+ void dialog.offsetHeight;
192
+ dialog.setAttribute('data-open', '');
197
193
  } else if (!this.open && dialog.open && !this.#closing) {
198
194
  this.#animateClose(dialog);
199
195
  }
@@ -201,8 +197,13 @@ class AdiaModal extends AdiaElement {
201
197
 
202
198
  #animateClose(dialog) {
203
199
  this.#closing = true;
204
- dialog.removeAttribute('data-open');
200
+ // Set [data-closing] FIRST (carries the transition spec), force a
201
+ // reflow, THEN remove [data-open]. If both attribute changes batch
202
+ // into a single style update, Safari can skip the fade-out animation
203
+ // entirely.
205
204
  dialog.setAttribute('data-closing', '');
205
+ void dialog.offsetHeight;
206
+ dialog.removeAttribute('data-open');
206
207
 
207
208
  this.#closeTimer = setTimeout(() => {
208
209
  this.#closeTimer = null;
@@ -1,3 +1,33 @@
1
+ /* Safari 17.x bug: `:scope:not(...):hover` (Flavor A) and `:scope[checked]`
2
+ (Flavor B — attribute-removal restyle) both fail inside `@scope`.
3
+ Selectors moved out. See 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
+ option-card-ui[checked] > :not([slot]) {
9
+ display: block;
10
+ }
11
+ option-card-ui[checked] {
12
+ background: var(--option-card-bg-checked);
13
+ border-color: var(--option-card-border-checked);
14
+ }
15
+ option-card-ui[checked]::before {
16
+ border-color: var(--option-card-radio-fill);
17
+ background:
18
+ radial-gradient(
19
+ circle,
20
+ var(--option-card-radio-dot) 0 30%,
21
+ var(--option-card-radio-fill) 30% 100%
22
+ );
23
+ }
24
+ option-card-ui[checked] > [slot="heading"] {
25
+ color: var(--option-card-heading-color-checked);
26
+ }
27
+ option-card-ui[checked] > [slot="icon"] {
28
+ color: var(--option-card-icon-color-checked);
29
+ }
30
+
1
31
  @scope (option-card-ui) {
2
32
  :where(:scope) {
3
33
  /* ── Container ── */
@@ -134,43 +164,9 @@
134
164
  :scope:has(> [slot="icon"]) > :not([slot]) {
135
165
  grid-column: 3 / -1;
136
166
  }
137
- :scope[checked] > :not([slot]) {
138
- display: block;
139
- }
140
-
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
- }
146
-
147
- /* ── State: checked — accent border + tinted bg + filled radio.
148
- The indicator becomes an accent disc with a centered dot of
149
- --option-card-radio-dot at 60% of the size, mirroring
150
- radio-ui's recipe (radio.css:75-78). Done with a radial
151
- gradient so a single pseudo-element carries both layers. */
152
- :scope[checked] {
153
- background: var(--option-card-bg-checked);
154
- border-color: var(--option-card-border-checked);
155
- }
156
- :scope[checked]::before {
157
- border-color: var(--option-card-radio-fill);
158
- background:
159
- radial-gradient(
160
- circle,
161
- var(--option-card-radio-dot) 0 30%,
162
- var(--option-card-radio-fill) 30% 100%
163
- );
164
- }
165
- /* Heading + icon shift to a strong color when checked — gives the
166
- selected card a clear text-level emphasis on top of the bg/border
167
- state, so picking is unambiguous beyond the radio dot alone. */
168
- :scope[checked] > [slot="heading"] {
169
- color: var(--option-card-heading-color-checked);
170
- }
171
- :scope[checked] > [slot="icon"] {
172
- color: var(--option-card-icon-color-checked);
173
- }
167
+ /* hover + [checked] state rules moved outside @scope — see Safari 17.x bug note at top.
168
+ The :scope[checked]::before recipe lives at top-of-file: an accent disc
169
+ with a centered dot via radial-gradient, mirroring radio-ui's recipe. */
174
170
 
175
171
  /* ── Layout: tile — icon top-left, indicator top-right, heading +
176
172
  description below, all left-aligned. Used for hero pickers
@@ -1,3 +1,24 @@
1
+ /* Safari 17.x bug: `:scope*:hover [descendant]` (Flavor A) and
2
+ `:scope[checked] [descendant]` (Flavor B — attribute-removal restyle)
3
+ both fail inside `@scope`. Selectors moved out. See
4
+ docs/BROWSER-COMPAT.md §3a. */
5
+ radio-ui:not([disabled]):hover [slot="dot"] {
6
+ border-color: var(--radio-border-hover);
7
+ background: var(--radio-bg-hover);
8
+ }
9
+ radio-ui[checked]:not([disabled]):hover [slot="dot"] {
10
+ background: var(--radio-bg-checked-hover);
11
+ border-color: var(--radio-bg-checked-hover);
12
+ }
13
+ radio-ui[checked] [slot="dot"] {
14
+ background: var(--radio-bg-checked);
15
+ border-color: var(--radio-border-checked);
16
+ }
17
+ radio-ui[checked] [slot="dot"]::after {
18
+ width: calc(var(--radio-size) * 0.6);
19
+ height: calc(var(--radio-size) * 0.6);
20
+ }
21
+
1
22
  @scope (radio-ui) {
2
23
  :where(:scope) {
3
24
  /* ── Layout ── (size scales with universal [size] attribute via --a-toggle-size) */
@@ -72,25 +93,7 @@
72
93
  height var(--radio-duration) var(--radio-easing);
73
94
  }
74
95
 
75
- :scope[checked] [slot="dot"]::after {
76
- width: calc(var(--radio-size) * 0.6);
77
- height: calc(var(--radio-size) * 0.6);
78
- }
79
-
80
- :scope:not([disabled]):hover [slot="dot"] {
81
- border-color: var(--radio-border-hover);
82
- background: var(--radio-bg-hover);
83
- }
84
-
85
- :scope[checked] [slot="dot"] {
86
- background: var(--radio-bg-checked);
87
- border-color: var(--radio-border-checked);
88
- }
89
-
90
- :scope[checked]:not([disabled]):hover [slot="dot"] {
91
- background: var(--radio-bg-checked-hover);
92
- border-color: var(--radio-bg-checked-hover);
93
- }
96
+ /* hover + [checked] rules moved outside @scope — see Safari 17.x bug note at top. */
94
97
 
95
98
  /* Label */
96
99
  :scope[label]::after { content: attr(label); }
@@ -1,3 +1,10 @@
1
+ /* Safari 17.x bug: `:scope:not(...) [descendant]:hover` inside `@scope`
2
+ doesn't match the scope root. Plain selector outside works. See
3
+ docs/BROWSER-COMPAT.md §3a. */
4
+ range-ui:not([disabled]) [slot="field"]:hover [data-layer="fill"] {
5
+ background: var(--range-fill-bg-hover);
6
+ }
7
+
1
8
  @scope (range-ui) {
2
9
  :where(:scope) {
3
10
  /* ── Tokens (wired to --a-ui-*) ── */
@@ -127,10 +134,7 @@
127
134
  color: var(--range-label-fg-hover);
128
135
  }
129
136
 
130
- /* Hover: brighten the fill */
131
- :scope:not([disabled]) [slot="field"]:hover [data-layer="fill"] {
132
- background: var(--range-fill-bg-hover);
133
- }
137
+ /* Hover-fill brighten moved outside @scope — see Safari 17.x bug note at top. */
134
138
 
135
139
  /* Dragging: deepest fill, sharper border, instant (no transition lag on the clip) */
136
140
  :scope[data-dragging] [slot="field"] {
@@ -1,3 +1,10 @@
1
+ /* Safari 17.x bug: `:scope:hover` inside `@scope` doesn't match the scope
2
+ root. Plain selector outside the @scope works. See
3
+ docs/BROWSER-COMPAT.md §3a. */
4
+ rating-ui:hover [data-rating-symbol] {
5
+ color: var(--rating-fg-hover);
6
+ }
7
+
1
8
  @scope (rating-ui) {
2
9
  :where(:scope) {
3
10
  /* ── Layout ── */
@@ -72,10 +79,6 @@
72
79
  clip-path: inset(0 0 0 0);
73
80
  }
74
81
 
75
- :scope:hover [data-rating-symbol] {
76
- color: var(--rating-fg-hover);
77
- }
78
-
79
82
  /* ── Variants override TOKENS only ── */
80
83
  :scope[variant="accent"] {
81
84
  --rating-fg-filled: var(--a-accent-bg);
@@ -1,3 +1,13 @@
1
+ /* Safari 17.x bug: `:scope:not(...):hover` (Flavor A) and `:scope[selected]`
2
+ (Flavor B — attribute-removal restyle) both fail inside `@scope`.
3
+ Selectors moved out. See docs/BROWSER-COMPAT.md §3a. */
4
+ segment-ui:not([disabled]):not([selected]):hover {
5
+ color: var(--segment-fg-hover);
6
+ }
7
+ segment-ui[selected] {
8
+ color: var(--segment-fg-selected);
9
+ }
10
+
1
11
  @scope (segment-ui) {
2
12
  :where(:scope) {
3
13
  /* ── Layout ── */
@@ -73,14 +83,7 @@
73
83
  white-space: nowrap;
74
84
  }
75
85
 
76
- /* States */
77
- :scope:not([disabled]):not([selected]):hover {
78
- color: var(--segment-fg-hover);
79
- }
80
-
81
- :scope[selected] {
82
- color: var(--segment-fg-selected);
83
- }
86
+ /* States — hover + [selected] rules moved outside @scope; see Safari 17.x bug note at top. */
84
87
 
85
88
  :scope:focus-visible {
86
89
  outline: none;
@@ -1,3 +1,23 @@
1
+ /* Safari 17.x bug: `:scope[attr]:hover` (Flavor A) and `:scope[checked]`
2
+ token-block (Flavor B — attribute-removal restyle) both fail inside
3
+ `@scope`. Selectors moved out. See docs/BROWSER-COMPAT.md §3a. */
4
+ switch-ui:not([disabled]):hover {
5
+ --switch-track-bg: var(--switch-track-bg-hover);
6
+ --switch-track-border: var(--switch-track-border-hover);
7
+ }
8
+ switch-ui[checked]:not([disabled]):hover {
9
+ --switch-track-bg: var(--switch-track-bg-checked-hover);
10
+ --switch-track-border: var(--switch-track-border-checked-hover);
11
+ }
12
+ switch-ui[checked] {
13
+ --switch-track-bg: var(--switch-track-bg-checked);
14
+ --switch-track-border: var(--switch-track-border-checked);
15
+ --switch-thumb-bg: var(--switch-thumb-bg-checked);
16
+ }
17
+ switch-ui[checked] [slot="thumb"] {
18
+ transform: translateX(var(--switch-thumb-travel));
19
+ }
20
+
1
21
  @scope (switch-ui) {
2
22
  :where(:scope) {
3
23
  /* ── Layout ── */
@@ -66,19 +86,7 @@
66
86
  background var(--switch-duration) var(--switch-easing),
67
87
  border-color var(--switch-duration) var(--switch-easing);
68
88
  }
69
- :scope:not([disabled]):hover {
70
- --switch-track-bg: var(--switch-track-bg-hover);
71
- --switch-track-border: var(--switch-track-border-hover);
72
- }
73
- :scope[checked] {
74
- --switch-track-bg: var(--switch-track-bg-checked);
75
- --switch-track-border: var(--switch-track-border-checked);
76
- --switch-thumb-bg: var(--switch-thumb-bg-checked);
77
- }
78
- :scope[checked]:not([disabled]):hover {
79
- --switch-track-bg: var(--switch-track-bg-checked-hover);
80
- --switch-track-border: var(--switch-track-border-checked-hover);
81
- }
89
+ /* hover + [checked] rules moved outside @scope — see Safari 17.x bug note at top. */
82
90
 
83
91
  [slot="thumb"] {
84
92
  position: absolute;
@@ -93,9 +101,7 @@
93
101
  transform var(--switch-duration) var(--switch-easing),
94
102
  background var(--switch-duration) var(--switch-easing);
95
103
  }
96
- :scope[checked] [slot="thumb"] {
97
- transform: translateX(var(--switch-thumb-travel));
98
- }
104
+ /* :scope[checked] [slot="thumb"] moved outside @scope — see Safari 17.x bug note at top. */
99
105
 
100
106
  [slot="label"] { font-size: var(--switch-font-size); }
101
107
 
@@ -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
+ tag-ui[removable]:not([disabled]):hover {
4
+ --tag-bg: var(--tag-bg-hover);
5
+ }
6
+
1
7
  @scope (tag-ui) {
2
8
  :where(:scope) {
3
9
  /* ── Tokens ── */
@@ -75,10 +81,7 @@
75
81
 
76
82
  /* Size handled by universal [size] attribute system. */
77
83
 
78
- /* ── Hover (only interactive/removable tags) ── */
79
- :scope[removable]:not([disabled]):hover {
80
- --tag-bg: var(--tag-bg-hover);
81
- }
84
+ /* hover rule moved outside @scope see Safari 17.x bug note at top. */
82
85
 
83
86
  /* ── Focus ── */
84
87
  :scope:focus-visible {
@@ -1,3 +1,12 @@
1
+ /* Safari 17.x bug: `:scope:not(...) [descendant]:hover` inside `@scope`
2
+ doesn't match the scope root. Plain selector outside works. See
3
+ docs/BROWSER-COMPAT.md §3a. */
4
+ textarea-ui:not([disabled]) [slot="text"]:hover {
5
+ background: var(--textarea-bg-hover);
6
+ border-color: var(--textarea-border-hover);
7
+ color: var(--textarea-fg-hover);
8
+ }
9
+
1
10
  @scope (textarea-ui) {
2
11
  :where(:scope) {
3
12
  /* ── Tokens (wired to --a-ui-*) ── */
@@ -66,11 +75,7 @@
66
75
  outline: none;
67
76
  transition: border-color var(--textarea-duration) var(--textarea-easing);
68
77
  }
69
- :scope:not([disabled]) [slot="text"]:hover {
70
- background: var(--textarea-bg-hover);
71
- border-color: var(--textarea-border-hover);
72
- color: var(--textarea-fg-hover);
73
- }
78
+ /* hover rule moved outside @scope — see Safari 17.x bug note at top. */
74
79
  :scope:not([disabled]) [slot="text"]:focus {
75
80
  /* Canonical ring via L3 token (see semantics.css FOCUS block).
76
81
  `:focus` (not :focus-visible) is deliberate — the caret lives
@@ -2,6 +2,31 @@
2
2
  TOAST-N — Notification popup with auto-dismiss.
3
3
  ═══════════════════════════════════════════════════════════════ */
4
4
 
5
+ /* Safari 17.x bug: `:scope[data-open]` and `:scope[data-closing]`
6
+ (Flavor B — attribute-removal restyle) don't reliably restyle on
7
+ attribute toggling inside `@scope`. Selectors moved out as plain
8
+ `toast-ui[data-…]` rules. See docs/BROWSER-COMPAT.md §3a. */
9
+ toast-ui[data-open] {
10
+ transition: transform var(--toast-duration) var(--toast-easing),
11
+ opacity var(--toast-duration) var(--toast-easing);
12
+ transform: translateY(0);
13
+ opacity: 1;
14
+ }
15
+ toast-ui[data-closing] {
16
+ transition: transform var(--toast-duration) var(--toast-easing),
17
+ opacity var(--toast-duration) var(--toast-easing);
18
+ opacity: 0;
19
+ }
20
+ toast-ui[data-closing],
21
+ toast-ui[data-closing][position="bottom-right"],
22
+ toast-ui[data-closing][position="bottom-left"] {
23
+ transform: translateY(1rem);
24
+ }
25
+ toast-ui[data-closing][position="top-right"],
26
+ toast-ui[data-closing][position="top-left"] {
27
+ transform: translateY(-1rem);
28
+ }
29
+
5
30
  @scope (toast-ui) {
6
31
  :where(:scope) {
7
32
  --toast-bg: var(--a-bg-subtle);
@@ -78,32 +103,8 @@
78
103
  transform: translateY(-1rem);
79
104
  }
80
105
 
81
- /* ── Enter animation ── */
82
-
83
- :scope[data-open] {
84
- transition: transform var(--toast-duration) var(--toast-easing),
85
- opacity var(--toast-duration) var(--toast-easing);
86
- transform: translateY(0);
87
- opacity: 1;
88
- }
89
-
90
- /* ── Exit animation ── */
91
-
92
- :scope[data-closing] {
93
- transition: transform var(--toast-duration) var(--toast-easing),
94
- opacity var(--toast-duration) var(--toast-easing);
95
- opacity: 0;
96
- }
97
-
98
- :where(:scope[data-closing]),
99
- :where(:scope[data-closing][position="bottom-right"]),
100
- :where(:scope[data-closing][position="bottom-left"]) {
101
- transform: translateY(1rem);
102
- }
103
- :where(:scope[data-closing][position="top-right"]),
104
- :where(:scope[data-closing][position="top-left"]) {
105
- transform: translateY(-1rem);
106
- }
106
+ /* Enter / exit animation rules ([data-open] / [data-closing]) moved
107
+ outside @scope — see Safari 17.x bug note at top of file. */
107
108
 
108
109
  /* ── Variant: info (default) ── */
109
110
 
@@ -1,3 +1,12 @@
1
+ /* Safari 17.x bug: `:scope:not(...):hover` inside `@scope` doesn't match
2
+ the scope root. Plain selector outside works. The @scope is
3
+ `(toggle-option-ui)` — NOT `(toggle-group-ui)` — so the moved-out
4
+ selector targets `toggle-option-ui:hover`. See docs/BROWSER-COMPAT.md §3a. */
5
+ toggle-option-ui:not([disabled]):hover {
6
+ --toggle-option-bg: var(--toggle-option-bg-hover);
7
+ --toggle-option-fg: var(--toggle-option-fg-hover);
8
+ }
9
+
1
10
  @scope (toggle-group-ui) {
2
11
  :where(:scope) {
3
12
  /* ── Tokens ── */
@@ -69,10 +78,7 @@
69
78
  border-inline-start: var(--toggle-option-border-width) solid var(--toggle-option-border-color);
70
79
  }
71
80
 
72
- :scope:not([disabled]):hover {
73
- --toggle-option-bg: var(--toggle-option-bg-hover);
74
- --toggle-option-fg: var(--toggle-option-fg-hover);
75
- }
81
+ /* hover rule moved outside @scope — see Safari 17.x bug note at top. */
76
82
 
77
83
  :scope:focus-visible {
78
84
  outline: none;