@slotted/styles 0.1.0-beta.1 → 0.1.0-beta.11

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 (180) hide show
  1. package/package.json +1 -1
  2. package/src/accordion/accordion.css +10 -10
  3. package/src/action-bar/action-bar.css +35 -42
  4. package/src/action-sheet/action-sheet.css +36 -36
  5. package/src/alert/alert.css +43 -35
  6. package/src/alert/alert.tokens.json +5 -0
  7. package/src/app-shell/app-shell.css +356 -301
  8. package/src/aspect-ratio/aspect-ratio.css +9 -9
  9. package/src/attachment/attachment.css +91 -99
  10. package/src/avatar/avatar.css +71 -41
  11. package/src/avatar/avatar.tokens.json +7 -0
  12. package/src/avatar-group/avatar-group.css +21 -22
  13. package/src/badge/badge.css +45 -37
  14. package/src/badge/badge.tokens.json +5 -0
  15. package/src/breadcrumb/breadcrumb.css +37 -37
  16. package/src/button/button.css +149 -68
  17. package/src/button/button.tokens.json +10 -0
  18. package/src/button-group/button-group.css +79 -74
  19. package/src/button-group/button-group.tokens.json +2 -0
  20. package/src/calendar/calendar.css +93 -103
  21. package/src/card/card.css +54 -61
  22. package/src/carousel/carousel.css +150 -161
  23. package/src/cascader/cascader.css +152 -153
  24. package/src/cascader/cascader.tokens.json +1 -0
  25. package/src/chart/chart.css +242 -239
  26. package/src/chart/chart.tokens.json +1 -0
  27. package/src/checkbox/checkbox.css +119 -124
  28. package/src/checkbox-group/checkbox-group.css +7 -7
  29. package/src/code-block/code-block.css +81 -85
  30. package/src/collapsible/collapsible.css +69 -76
  31. package/src/color-picker/color-picker.css +83 -91
  32. package/src/column-manager/column-manager.css +106 -114
  33. package/src/combobox/combobox.css +132 -107
  34. package/src/combobox/combobox.tokens.json +1 -0
  35. package/src/command-palette/command-palette.css +103 -90
  36. package/src/command-palette/command-palette.tokens.json +1 -0
  37. package/src/container/container.css +24 -24
  38. package/src/context-menu/context-menu.css +8 -8
  39. package/src/currency-field/currency-field.css +210 -88
  40. package/src/currency-field/currency-field.tokens.json +13 -0
  41. package/src/dashboard-grid/dashboard-grid.css +148 -116
  42. package/src/data-grid/data-grid.css +535 -470
  43. package/src/data-grid/data-grid.tokens.json +4 -0
  44. package/src/data-list/data-list.css +81 -70
  45. package/src/data-list/data-list.tokens.json +2 -1
  46. package/src/date-field/date-field.css +55 -52
  47. package/src/date-field/date-field.tokens.json +1 -0
  48. package/src/date-picker/date-picker.css +40 -42
  49. package/src/date-range-picker/date-range-picker.css +114 -118
  50. package/src/datetime-field/datetime-field.css +78 -80
  51. package/src/datetime-field/datetime-field.tokens.json +1 -0
  52. package/src/datetime-picker/datetime-picker.css +61 -63
  53. package/src/datetime-range-picker/datetime-range-picker.css +149 -156
  54. package/src/description-list/description-list.css +61 -64
  55. package/src/detail-panel/detail-panel.css +86 -71
  56. package/src/detail-panel/detail-panel.tokens.json +2 -0
  57. package/src/dialog/dialog.css +372 -153
  58. package/src/dialog/dialog.tokens.json +6 -0
  59. package/src/divider/divider.css +18 -18
  60. package/src/drawer/drawer.css +205 -210
  61. package/src/drawer/drawer.tokens.json +1 -0
  62. package/src/entity-picker/entity-picker.css +238 -241
  63. package/src/entity-picker/entity-picker.tokens.json +1 -0
  64. package/src/event-calendar/event-calendar.css +343 -385
  65. package/src/field/field.css +74 -78
  66. package/src/field-group/field-group.css +22 -25
  67. package/src/filter-bar/filter-bar.css +193 -209
  68. package/src/form/form.css +34 -24
  69. package/src/form/form.tokens.json +1 -0
  70. package/src/fullscreen/fullscreen.css +24 -25
  71. package/src/grid/grid.css +28 -28
  72. package/src/highlight/highlight.css +6 -6
  73. package/src/hover-card/hover-card.css +6 -6
  74. package/src/icon/icon.css +19 -15
  75. package/src/icon/icon.tokens.json +1 -0
  76. package/src/image/image.css +39 -39
  77. package/src/import-export-center/import-export-center.css +135 -156
  78. package/src/inline/inline.css +27 -27
  79. package/src/inline-message/inline-message.css +52 -28
  80. package/src/inline-message/inline-message.tokens.json +4 -0
  81. package/src/kbd/kbd.css +30 -26
  82. package/src/link/link.css +15 -15
  83. package/src/list/list.css +44 -17
  84. package/src/list/list.tokens.json +4 -0
  85. package/src/list-item/list-item.css +80 -66
  86. package/src/list-item/list-item.tokens.json +7 -0
  87. package/src/listbox/listbox.css +56 -59
  88. package/src/loading-overlay/loading-overlay.css +75 -92
  89. package/src/markdown/markdown.css +17 -17
  90. package/src/menu/menu.css +83 -63
  91. package/src/menubar/menubar.css +13 -13
  92. package/src/message/message.css +182 -202
  93. package/src/message/message.tokens.json +5 -0
  94. package/src/message-scroller/message-scroller.css +41 -42
  95. package/src/multi-select/multi-select.css +152 -145
  96. package/src/multi-select/multi-select.tokens.json +1 -0
  97. package/src/navigation-menu/navigation-menu.css +167 -188
  98. package/src/notification-center/notification-center.css +140 -148
  99. package/src/notification-center/notification-center.tokens.json +3 -0
  100. package/src/number-field/number-field.css +84 -83
  101. package/src/number-field/number-field.tokens.json +1 -0
  102. package/src/otp-input/otp-input.css +67 -57
  103. package/src/otp-input/otp-input.tokens.json +1 -0
  104. package/src/page/page.css +39 -23
  105. package/src/page/page.tokens.json +1 -0
  106. package/src/page-header/page-header.css +323 -283
  107. package/src/page-header/page-header.tokens.json +5 -0
  108. package/src/pagination/pagination.css +68 -72
  109. package/src/popover/popover.css +40 -49
  110. package/src/progress-indicator/progress-indicator.css +177 -120
  111. package/src/progress-indicator/progress-indicator.tokens.json +1 -1
  112. package/src/prompt-composer/prompt-composer.css +221 -102
  113. package/src/prompt-composer/prompt-composer.tokens.json +15 -0
  114. package/src/prose/prose.css +71 -75
  115. package/src/radio-group/radio-group.css +98 -103
  116. package/src/rating/rating.css +136 -132
  117. package/src/rating/rating.tokens.json +1 -0
  118. package/src/result-state/result-state.css +74 -77
  119. package/src/runtime-properties.mjs +2 -0
  120. package/src/saved-views/saved-views.css +41 -47
  121. package/src/scroll-area/scroll-area.css +39 -32
  122. package/src/section/section.css +161 -41
  123. package/src/section/section.tokens.json +7 -2
  124. package/src/segmented-control/segmented-control.css +254 -97
  125. package/src/segmented-control/segmented-control.tokens.json +15 -11
  126. package/src/select/select.css +118 -117
  127. package/src/select/select.tokens.json +1 -0
  128. package/src/side-navigation/side-navigation.css +179 -182
  129. package/src/side-navigation/side-navigation.tokens.json +1 -0
  130. package/src/skeleton/skeleton.css +47 -48
  131. package/src/slider/slider.css +86 -88
  132. package/src/sparkline/sparkline.css +62 -40
  133. package/src/sparkline/sparkline.tokens.json +2 -0
  134. package/src/spinner/spinner.css +83 -180
  135. package/src/spinner/spinner.tokens.json +0 -1
  136. package/src/split-button/split-button.css +30 -31
  137. package/src/split-button/split-button.tokens.json +1 -0
  138. package/src/splitter/splitter.css +35 -35
  139. package/src/stack/stack.css +24 -24
  140. package/src/stat-card/stat-card.css +341 -224
  141. package/src/stat-card/stat-card.tokens.json +3 -0
  142. package/src/status-indicator/status-indicator.css +28 -28
  143. package/src/stepper/stepper.css +58 -62
  144. package/src/switch/switch.css +86 -92
  145. package/src/table/table.css +310 -165
  146. package/src/table/table.tokens.json +6 -0
  147. package/src/table-of-contents/table-of-contents.css +51 -54
  148. package/src/tabs/tabs.css +337 -302
  149. package/src/tabs/tabs.tokens.json +8 -5
  150. package/src/tag/tag.css +79 -71
  151. package/src/tag/tag.tokens.json +9 -0
  152. package/src/tags-input/tags-input.css +145 -82
  153. package/src/tags-input/tags-input.tokens.json +7 -1
  154. package/src/text-field/text-field.css +132 -136
  155. package/src/text-field/text-field.tokens.json +1 -0
  156. package/src/textarea/textarea.css +31 -28
  157. package/src/textarea/textarea.tokens.json +1 -0
  158. package/src/time-field/time-field.css +72 -69
  159. package/src/time-field/time-field.tokens.json +1 -0
  160. package/src/time-picker/time-picker.css +56 -58
  161. package/src/time-range-picker/time-range-picker.css +126 -133
  162. package/src/timeline/timeline.css +254 -266
  163. package/src/timeline/timeline.tokens.json +5 -0
  164. package/src/toast/toast.css +108 -90
  165. package/src/toast/toast.tokens.json +6 -0
  166. package/src/toggle/toggle.css +62 -25
  167. package/src/toggle/toggle.tokens.json +8 -0
  168. package/src/toggle-group/toggle-group.css +106 -60
  169. package/src/toggle-group/toggle-group.tokens.json +10 -0
  170. package/src/toolbar/toolbar.css +30 -35
  171. package/src/tooltip/tooltip.css +26 -26
  172. package/src/top-navigation/top-navigation.css +243 -259
  173. package/src/tour/tour.css +51 -60
  174. package/src/transfer/transfer.css +160 -164
  175. package/src/tree/tree.css +103 -111
  176. package/src/typography/typography.css +40 -36
  177. package/src/typography/typography.tokens.json +1 -0
  178. package/src/upload/upload.css +224 -224
  179. package/src/visually-hidden/visually-hidden.css +1 -1
  180. package/src/wizard/wizard.css +185 -197
@@ -11,7 +11,7 @@
11
11
  * chooses its own width: it is a thing read beside a value, and the value's
12
12
  * container is what has the room. The height is the family's, by size step.
13
13
  */
14
- [data-ui='sparkline'] {
14
+ [data-ui-sparkline] {
15
15
  /* The height of the box, by size. Three steps of the space scale rather
16
16
  than of control geometry: a sparkline is content, not a control, and a
17
17
  dense table asks for `size="sm"` rather than for a shorter `md`. */
@@ -55,7 +55,7 @@
55
55
  * and wins; a series slot says which one it is; a sparkline that names
56
56
  * neither reads the colour its container states — a card's trend region
57
57
  * writes `--slotted-sparkline-color` from its own tone — or the first slot
58
- * of the data scale, which is the theme's own blue.
58
+ * of the data scale, which takes the accent seed's hue.
59
59
  */
60
60
  --_color: var(--slotted-sparkline-color, var(--slotted-data-1));
61
61
 
@@ -65,53 +65,57 @@
65
65
  inline-size: 100%;
66
66
  /* The box is the drawing; there is no line box to leave room under. */
67
67
  line-height: 0;
68
- min-inline-size: 0;
68
+ /* The nominal width of the grid. A percentage of a box sized by its
69
+ content — an auto table column — is the content's width, and a drawing
70
+ is no content: the sparkline started at zero and stretched once the
71
+ column settled. */
72
+ min-inline-size: var(--slotted-sparkline-min-inline-size, 6rem);
69
73
  vertical-align: middle;
70
74
  }
71
75
 
72
- [data-ui='sparkline'][data-size='sm'] {
76
+ [data-ui-sparkline][data-size='sm'] {
73
77
  --_size: var(--slotted-sparkline-block-size-sm, var(--slotted-space-7, 1.25rem));
74
78
  --_stroke: var(--slotted-sparkline-line-width-sm, var(--slotted-border-width-medium, 1.5px));
75
79
  --_marker: var(--slotted-sparkline-marker-size-sm, var(--slotted-space-2, 0.25rem));
76
80
  --_ring: var(--slotted-sparkline-ring-width-sm, var(--slotted-border-width-medium, 1.5px));
77
81
  }
78
82
 
79
- [data-ui='sparkline'][data-size='lg'] {
83
+ [data-ui-sparkline][data-size='lg'] {
80
84
  --_size: var(--slotted-sparkline-block-size-lg, var(--slotted-space-11, 3rem));
81
85
  --_marker: var(--slotted-sparkline-marker-size-lg, var(--slotted-space-4, 0.5rem));
82
86
  }
83
87
 
84
88
  /* ── Series: which one of eight this is ──────────────────────────────── */
85
89
 
86
- [data-ui='sparkline'][data-series='1'] {
90
+ [data-ui-sparkline][data-series='1'] {
87
91
  --_color: var(--slotted-data-1);
88
92
  }
89
93
 
90
- [data-ui='sparkline'][data-series='2'] {
94
+ [data-ui-sparkline][data-series='2'] {
91
95
  --_color: var(--slotted-data-2);
92
96
  }
93
97
 
94
- [data-ui='sparkline'][data-series='3'] {
98
+ [data-ui-sparkline][data-series='3'] {
95
99
  --_color: var(--slotted-data-3);
96
100
  }
97
101
 
98
- [data-ui='sparkline'][data-series='4'] {
102
+ [data-ui-sparkline][data-series='4'] {
99
103
  --_color: var(--slotted-data-4);
100
104
  }
101
105
 
102
- [data-ui='sparkline'][data-series='5'] {
106
+ [data-ui-sparkline][data-series='5'] {
103
107
  --_color: var(--slotted-data-5);
104
108
  }
105
109
 
106
- [data-ui='sparkline'][data-series='6'] {
110
+ [data-ui-sparkline][data-series='6'] {
107
111
  --_color: var(--slotted-data-6);
108
112
  }
109
113
 
110
- [data-ui='sparkline'][data-series='7'] {
114
+ [data-ui-sparkline][data-series='7'] {
111
115
  --_color: var(--slotted-data-7);
112
116
  }
113
117
 
114
- [data-ui='sparkline'][data-series='8'] {
118
+ [data-ui-sparkline][data-series='8'] {
115
119
  --_color: var(--slotted-data-8);
116
120
  }
117
121
 
@@ -123,33 +127,37 @@
123
127
  * shape rather than found. `neutral` is the comparison series and the aside,
124
128
  * in the step the page uses for one.
125
129
  */
126
- [data-ui='sparkline'][data-tone='neutral'] {
130
+ [data-ui-sparkline][data-tone='neutral'] {
127
131
  --_color: var(--slotted-text-muted, GrayText);
128
132
  }
129
133
 
130
- [data-ui='sparkline'][data-tone='accent'] {
134
+ [data-ui-sparkline][data-tone='accent'] {
131
135
  --_color: var(--slotted-tone-accent-text);
132
136
  }
133
137
 
134
- [data-ui='sparkline'][data-tone='secondary'] {
138
+ [data-ui-sparkline][data-tone='secondary'] {
135
139
  --_color: var(--slotted-tone-secondary-text);
136
140
  }
137
141
 
138
- [data-ui='sparkline'][data-tone='success'] {
142
+ [data-ui-sparkline][data-tone='success'] {
139
143
  --_color: var(--slotted-tone-success-text);
140
144
  }
141
145
 
142
- [data-ui='sparkline'][data-tone='warning'] {
146
+ [data-ui-sparkline][data-tone='info'] {
147
+ --_color: var(--slotted-tone-info-text);
148
+ }
149
+
150
+ [data-ui-sparkline][data-tone='warning'] {
143
151
  --_color: var(--slotted-tone-warning-text);
144
152
  }
145
153
 
146
- [data-ui='sparkline'][data-tone='danger'] {
154
+ [data-ui-sparkline][data-tone='danger'] {
147
155
  --_color: var(--slotted-tone-danger-text);
148
156
  }
149
157
 
150
158
  /* ── The drawing ───────────────────────────────────────────────────────── */
151
159
 
152
- [data-ui='sparkline'] [data-part='plot'] {
160
+ [data-ui-sparkline] [data-part-plot] {
153
161
  block-size: 100%;
154
162
  display: block;
155
163
  inline-size: 100%;
@@ -165,12 +173,11 @@
165
173
  * of forty sparklines does not mint forty gradients. `mask-image` applies to
166
174
  * an SVG child from CSS in every engine that ships `@layer`.
167
175
  */
168
- [data-ui='sparkline'] [data-part='area'] {
176
+ [data-ui-sparkline] [data-part-area] {
169
177
  fill: var(--_color);
170
178
  fill-opacity: var(--_area-opacity);
171
179
  mask-image: linear-gradient(to bottom, black, transparent);
172
180
  stroke: none;
173
- transition: d var(--slotted-duration-medium) var(--slotted-easing-standard);
174
181
  }
175
182
 
176
183
  /**
@@ -179,26 +186,23 @@
179
186
  * stretched to the box; once measured, the grid is the box and the effect
180
187
  * changes nothing.
181
188
  */
182
- [data-ui='sparkline'] [data-part='line'] {
189
+ [data-ui-sparkline] [data-part-line] {
183
190
  fill: none;
184
191
  stroke: var(--_color);
185
192
  stroke-linecap: round;
186
193
  stroke-linejoin: round;
187
194
  stroke-width: var(--_stroke);
188
- transition: d var(--slotted-duration-medium) var(--slotted-easing-standard);
189
195
  vector-effect: non-scaling-stroke;
190
196
  }
191
197
 
192
- [data-ui='sparkline'] [data-part='bar'] {
198
+ [data-ui-sparkline] [data-part-bar] {
193
199
  fill: var(--_bar-dim);
194
200
  stroke: none;
195
- transition:
196
- d var(--slotted-duration-medium) var(--slotted-easing-standard),
197
- fill var(--slotted-duration-short) var(--slotted-easing-standard);
201
+ transition: fill var(--slotted-duration-short) var(--slotted-easing-standard);
198
202
  }
199
203
 
200
204
  /* The current period is the one the value beside the chart is about. */
201
- [data-ui='sparkline'] [data-part='bar'][data-current] {
205
+ [data-ui-sparkline] [data-part-bar][data-current] {
202
206
  fill: var(--_color);
203
207
  }
204
208
 
@@ -207,7 +211,7 @@
207
211
  * that is not data, and the dash is what says so before the colour does.
208
212
  * The dash lengths are geometry of the stroke, not a scale step.
209
213
  */
210
- [data-ui='sparkline'] [data-part='reference'] {
214
+ [data-ui-sparkline] [data-part-reference] {
211
215
  fill: none;
212
216
  stroke: var(--_reference-color);
213
217
  stroke-dasharray: 2 3;
@@ -226,20 +230,38 @@
226
230
  * in the colour of the ground so the disc stays legible where it lands on
227
231
  * the line.
228
232
  */
229
- [data-ui='sparkline'] [data-part='ring'],
230
- [data-ui='sparkline'] [data-part='marker'] {
233
+ [data-ui-sparkline] [data-part-ring],
234
+ [data-ui-sparkline] [data-part-marker] {
231
235
  fill: none;
232
236
  stroke-linecap: round;
233
- transition: d var(--slotted-duration-medium) var(--slotted-easing-standard);
234
237
  vector-effect: non-scaling-stroke;
235
238
  }
236
239
 
237
- [data-ui='sparkline'] [data-part='ring'] {
240
+ /**
241
+ * A new series eases into place; a new box does not. The binding writes
242
+ * `data-animate` for the render whose `data` changed and not for one whose
243
+ * box changed: a path that eased to a column width settling after mount
244
+ * read as the series growing from nothing.
245
+ */
246
+ @media (prefers-reduced-motion: no-preference) {
247
+ [data-ui-sparkline][data-animate]
248
+ :is([data-part-area], [data-part-line], [data-part-ring], [data-part-marker]) {
249
+ transition: d var(--slotted-duration-medium) var(--slotted-easing-standard);
250
+ }
251
+
252
+ [data-ui-sparkline][data-animate] [data-part-bar] {
253
+ transition:
254
+ d var(--slotted-duration-medium) var(--slotted-easing-standard),
255
+ fill var(--slotted-duration-short) var(--slotted-easing-standard);
256
+ }
257
+ }
258
+
259
+ [data-ui-sparkline] [data-part-ring] {
238
260
  stroke: var(--_ring-color);
239
261
  stroke-width: calc(var(--_marker) + 2 * var(--_ring));
240
262
  }
241
263
 
242
- [data-ui='sparkline'] [data-part='marker'] {
264
+ [data-ui-sparkline] [data-part-marker] {
243
265
  stroke: var(--_color);
244
266
  stroke-width: var(--_marker);
245
267
  }
@@ -250,7 +272,7 @@
250
272
  * inline end. The grid is mirrored rather than recomputed, so both
251
273
  * bindings hand the engine the same box and the same order.
252
274
  */
253
- [data-ui='sparkline']:dir(rtl) [data-part='plot'] {
275
+ [data-ui-sparkline]:dir(rtl) [data-part-plot] {
254
276
  transform: scaleX(-1);
255
277
  }
256
278
 
@@ -263,19 +285,19 @@
263
285
  * one `CanvasText`, and the wash goes.
264
286
  */
265
287
  @media (forced-colors: active) {
266
- [data-ui='sparkline'] [data-part='area'] {
288
+ [data-ui-sparkline] [data-part-area] {
267
289
  display: none;
268
290
  }
269
291
 
270
- [data-ui='sparkline'] [data-part='bar'] {
292
+ [data-ui-sparkline] [data-part-bar] {
271
293
  fill: GrayText;
272
294
  }
273
295
 
274
- [data-ui='sparkline'] [data-part='bar'][data-current] {
296
+ [data-ui-sparkline] [data-part-bar][data-current] {
275
297
  fill: CanvasText;
276
298
  }
277
299
 
278
- [data-ui='sparkline'] [data-part='ring'] {
300
+ [data-ui-sparkline] [data-part-ring] {
279
301
  stroke: Canvas;
280
302
  }
281
303
  }
@@ -31,6 +31,7 @@
31
31
  "--slotted-sparkline-marker-size",
32
32
  "--slotted-sparkline-marker-size-lg",
33
33
  "--slotted-sparkline-marker-size-sm",
34
+ "--slotted-sparkline-min-inline-size",
34
35
  "--slotted-sparkline-reference-color",
35
36
  "--slotted-sparkline-ring-color",
36
37
  "--slotted-sparkline-ring-width",
@@ -39,6 +40,7 @@
39
40
  "--slotted-text-muted",
40
41
  "--slotted-tone-accent-text",
41
42
  "--slotted-tone-danger-text",
43
+ "--slotted-tone-info-text",
42
44
  "--slotted-tone-secondary-text",
43
45
  "--slotted-tone-success-text",
44
46
  "--slotted-tone-warning-text"
@@ -4,221 +4,124 @@
4
4
  * way — a button that is working, a select or combobox whose options are
5
5
  * still on their way — is drawn here, and a host names it and places it.
6
6
  *
7
- * It was a glyph before this: Lucide's `loader-circle`, a three-quarter arc
8
- * with two cut ends, masked over the ink and set turning. It read as what
9
- * it was, a static shape being rotated, and at the small step its stroke
10
- * was heavier than anything beside it. What is drawn now is the pattern
11
- * Material and the platform's own progress ring settle on: an arc that
7
+ * The pattern is Material's and the platform's progress ring: an arc that
12
8
  * chases itself around a faint track, growing from its head and closing
13
9
  * from its tail, so the eye reads work rather than a wheel.
14
10
  *
15
- * Three layers, one mask. The track is the whole circle in the host's ink
16
- * at a fifth of its strength — the same step every family uses for a
17
- * band or a rail on its own ground — and it is what stands when motion is
18
- * off. The arc is a conic gradient from the tail angle to the head angle,
19
- * in full ink. Two dots the width of the stroke sit at the two angles and
20
- * give the arc the round caps a conic gradient cannot draw. The annulus is
21
- * cut by a radial mask a pixel wide at the edge, so the stroke is drawn
22
- * rather than stepped. Everything is measured from the box, so the ring is
23
- * the same drawing at every step a host can ask for.
11
+ * The drawing is vector and inline: the host writes an `svg` of two circles
12
+ * inside the box, the track and the arc, stroked in the host's ink. No
13
+ * image is fetched. The ring was two SVG images used as masks, written as
14
+ * `data:` URLs, and a page whose policy allows images from its own origin
15
+ * alone refused both: the ring was absent.
24
16
  *
25
- * Three angles move it, and none is a rotation of the box. A turning box
26
- * is rasterised once and spun as a bitmap; a gradient repainted by angle is
27
- * as sharp on the last frame as on the first. In the first half of a cycle
28
- * the head runs ahead on an ease and the tail holds, so the arc opens from
29
- * a sliver to most of the circle; in the second half the head holds and
30
- * the tail runs the same distance, so the arc closes from behind. Neither
31
- * end ever moves backward. A linear turn under both keeps the whole
32
- * travelling through the eased ends, and it is a turn and a hundred
33
- * degrees per cycle because that is what the two resets add up to: the
34
- * last frame of a cycle is the first frame of the next, to the degree.
17
+ * The turn is the box's `rotate`, which the compositor runs: the arc used to
18
+ * turn inside the mask image, which is rasterised again on the main thread
19
+ * for every frame, and with the main thread busy for two seconds the ring
20
+ * drew no frame at all (`tools/harness/scenarios/spinner-busy.mjs`). The
21
+ * dash still opens and travels on the main thread; under load the arc keeps
22
+ * turning at the length it had.
35
23
  *
36
- * `currentColor` is what lets the ring take whatever ink its host is
37
- * already writing with — the label's colour on a filled button, the tone
38
- * on a bare one, the muted text of a field — and answer every scheme
39
- * without a colour of its own.
24
+ * It was gradients before the masks — a conic arc, two radial caps and a
25
+ * radial mask, three angles animated as custom properties. A conic
26
+ * gradient's hard stop has no antialiasing, so the arc stepped from frame to
27
+ * frame like a GIF, and the half-pixel stops of the caps and the mask
28
+ * blurred a ring of thirteen pixels. A stroke has round caps and is
29
+ * antialiased at every size.
40
30
  *
41
- * The box is the host's glyph box, read through `--slotted-spinner-size`:
42
- * a host that sizes its icons sizes the ring the same way, so the ring
43
- * that stands in for a glyph is the size of the glyph it replaces. Left
44
- * alone it is the medium control icon. The icon set keeps two units of
45
- * twenty-four clear inside that box, and the ring keeps the same inset, so
46
- * the swap is between two shapes of one measure.
31
+ * The geometry is the icon set's: in a box of twenty-four units the ring is
32
+ * twenty across and the stroke is two and a quarter, so the swap with a
33
+ * glyph is between two shapes of one measure.
47
34
  */
48
- [data-ui='spinner'] {
35
+ [data-ui-spinner] {
49
36
  --_size: var(--slotted-spinner-size, var(--slotted-control-icon-size-md, 16px));
50
- --_ring: calc(var(--_size) * 5 / 6);
51
- /**
52
- * The stroke of the icon set at this size — two units of twenty-four —
53
- * and never thinner than the medium hairline, which is where a 12px
54
- * ring would otherwise fall below a pixel.
55
- */
56
- --_stroke: max(var(--slotted-border-width-medium, 1.5px), calc(var(--_size) / 12));
57
- /** The radius the stroke is centred on: where the caps sit. */
58
- --_radius: calc((var(--_ring) - var(--_stroke)) / 2);
59
- --_track: color-mix(in oklab, currentColor 20%, transparent);
60
- --_start: calc(var(--_turn) + var(--_tail));
61
- --_end: calc(var(--_turn) + var(--_head));
62
- --_cap: calc(var(--_stroke) / 2);
63
37
 
64
- animation:
65
- slotted-spinner-turn var(--slotted-duration-loop-slow) linear infinite,
66
- slotted-spinner-head var(--slotted-duration-loop-slow) linear infinite,
67
- slotted-spinner-tail var(--slotted-duration-loop-slow) linear infinite;
68
- background-image:
69
- radial-gradient(
70
- circle at calc(50% + var(--_radius) * sin(var(--_end)))
71
- calc(50% - var(--_radius) * cos(var(--_end))),
72
- currentColor calc(var(--_cap) - 0.5px),
73
- transparent calc(var(--_cap) + 0.5px)
74
- ),
75
- radial-gradient(
76
- circle at calc(50% + var(--_radius) * sin(var(--_start)))
77
- calc(50% - var(--_radius) * cos(var(--_start))),
78
- currentColor calc(var(--_cap) - 0.5px),
79
- transparent calc(var(--_cap) + 0.5px)
80
- ),
81
- conic-gradient(
82
- from var(--_start),
83
- currentColor 0deg,
84
- currentColor calc(var(--_head) - var(--_tail)),
85
- transparent calc(var(--_head) - var(--_tail))
86
- ),
87
- conic-gradient(var(--_track) 0 100%);
88
- background-origin: content-box;
89
- background-clip: content-box;
38
+ /* A turn and a dash out of step, so the arc never comes back to the same place. */
39
+ animation: slotted-spinner-turn calc(var(--slotted-duration-loop-slow) * 1.25) linear infinite;
90
40
  block-size: var(--_size);
91
- box-sizing: border-box;
41
+ color: inherit;
92
42
  display: inline-block;
93
43
  /* A ring that shrinks is not a ring: it keeps its size in a flex row. */
94
44
  flex: none;
95
- inline-size: var(--_size);
96
45
  /*
97
- * Cut in lengths rather than in percentages of the ray: Chrome paints
98
- * `calc(100% - 2px)` as a stop in a radial gradient as a ramp several
99
- * pixels wide, and the ring came out as a soft donut. Two lengths a pixel
100
- * apart are a stroke with an anti-aliased edge.
46
+ * Not adjusting keeps `currentcolor`, the host's ink, which forced colours
47
+ * have already set, on a stroke the system would otherwise repaint.
101
48
  */
102
- mask-image: radial-gradient(
103
- circle,
104
- transparent calc(var(--_ring) / 2 - var(--_stroke) - 0.5px),
105
- #000 calc(var(--_ring) / 2 - var(--_stroke) + 0.5px),
106
- #000 calc(var(--_ring) / 2 - 0.5px),
107
- transparent calc(var(--_ring) / 2 + 0.5px)
108
- );
49
+ forced-color-adjust: none;
50
+ inline-size: var(--_size);
109
51
  opacity: 1;
110
- /* The inset the icon set keeps, so the ring is drawn where a glyph is. */
111
- padding: calc((var(--_size) - var(--_ring)) / 2);
112
52
  scale: 1;
113
- /**
114
- * A ring arrives rather than appears. A host inserts it on the frame the
115
- * wait begins, and the first frame is the start of a fade rather than a
116
- * cut, in the control's own transition step.
117
- */
118
53
  transition:
119
54
  opacity var(--slotted-control-transition-duration) var(--slotted-control-transition-easing),
120
55
  scale var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
121
56
  vertical-align: middle;
122
57
  }
123
58
 
124
- @starting-style {
125
- [data-ui='spinner'] {
126
- opacity: 0;
127
- scale: 0.6;
128
- }
59
+ [data-ui-spinner] > svg {
60
+ block-size: 100%;
61
+ display: block;
62
+ fill: none;
63
+ inline-size: 100%;
64
+ stroke: currentColor;
65
+ stroke-width: 2.25;
129
66
  }
130
67
 
131
- /**
132
- * A frozen ring still communicates waiting: the track, and an arc of most
133
- * of a turn resting on it. Stopping it entirely is less distracting than
134
- * replacing one continuous motion with a slower one.
135
- */
136
- @media (prefers-reduced-motion: reduce) {
137
- [data-ui='spinner'] {
138
- --_head: 270deg;
139
-
140
- animation: none;
141
- }
68
+ /* The track: the whole circle at a fifth of the ink's strength. */
69
+ [data-ui-spinner] > svg > circle:first-child {
70
+ stroke-opacity: 0.2;
142
71
  }
143
72
 
144
- @media (forced-colors: active) {
145
- /*
146
- * The ring is painted with a background, and forced colours replace a
147
- * background that is not one of the system's own colours: the ring goes
148
- * and the box it sat in stays. `forced-color-adjust: none` keeps
149
- * `currentcolor`, which is the host's ink, already forced to the right
150
- * value.
151
- */
152
- [data-ui='spinner'] {
153
- forced-color-adjust: none;
154
- }
73
+ /*
74
+ * The arc: a dash of one to seventy hundredths of the circle that opens in
75
+ * the first half of the cycle and travels on in the second, from the top.
76
+ * Each half eases on its own.
77
+ */
78
+ [data-ui-spinner] > svg > circle:last-child {
79
+ animation: slotted-spinner-arc var(--slotted-duration-loop-slow) infinite;
80
+ rotate: -90deg;
81
+ stroke-dasharray: 1 150;
82
+ stroke-linecap: round;
83
+ transform-origin: center;
155
84
  }
156
- }
157
-
158
- /**
159
- * Registered so the three angles interpolate: an unregistered custom property
160
- * flips between its keyframes instead of turning. Without the registration
161
- * the ring stands as it does under reduced motion rather than as nothing.
162
- */
163
- @property --_turn {
164
- inherits: false;
165
- initial-value: 0deg;
166
- syntax: '<angle>';
167
- }
168
85
 
169
- @property --_head {
170
- inherits: false;
171
- initial-value: 20deg;
172
- syntax: '<angle>';
173
- }
174
-
175
- @property --_tail {
176
- inherits: false;
177
- initial-value: 0deg;
178
- syntax: '<angle>';
179
- }
180
-
181
- /**
182
- * The linear turn under both ends. A full turn and the hundred degrees the
183
- * two resets below leave over — the tail ends 260 degrees on from where it
184
- * starts, the head likewise — so the angle a cycle ends on is the angle the
185
- * next begins on, and the loop has no seam.
186
- */
187
- @keyframes slotted-spinner-turn {
188
- to {
189
- --_turn: 460deg;
86
+ @keyframes slotted-spinner-turn {
87
+ to {
88
+ rotate: 360deg;
89
+ }
190
90
  }
191
- }
192
91
 
193
- /**
194
- * The head opens the arc in the first half of the cycle and holds in the
195
- * second; the tail holds in the first and closes it in the second. The ease
196
- * is written on the keyframe it leaves from, so each half eases on its own
197
- * and the hold between them is a hold. An arc of twenty degrees to two
198
- * hundred and eighty: never nothing, never a closed circle.
199
- */
200
- @keyframes slotted-spinner-head {
201
- 0% {
202
- --_head: 20deg;
203
-
204
- animation-timing-function: ease-in-out;
92
+ @keyframes slotted-spinner-arc {
93
+ 0% {
94
+ animation-timing-function: cubic-bezier(0.42, 0, 0.58, 1);
95
+ stroke-dasharray: 1 150;
96
+ stroke-dashoffset: 0;
97
+ }
98
+ 50% {
99
+ animation-timing-function: cubic-bezier(0.42, 0, 0.58, 1);
100
+ stroke-dasharray: 70 150;
101
+ stroke-dashoffset: -28;
102
+ }
103
+ 100% {
104
+ stroke-dasharray: 70 150;
105
+ stroke-dashoffset: -99;
106
+ }
205
107
  }
206
108
 
207
- 50%,
208
- 100% {
209
- --_head: 280deg;
109
+ @starting-style {
110
+ [data-ui-spinner] {
111
+ opacity: 0;
112
+ scale: 0.6;
113
+ }
210
114
  }
211
- }
212
-
213
- @keyframes slotted-spinner-tail {
214
- 0%,
215
- 50% {
216
- --_tail: 0deg;
217
115
 
218
- animation-timing-function: ease-in-out;
219
- }
116
+ /* Still: a track with an arc of three quarters resting on it. */
117
+ @media (prefers-reduced-motion: reduce) {
118
+ [data-ui-spinner],
119
+ [data-ui-spinner] > svg > circle:last-child {
120
+ animation: none;
121
+ }
220
122
 
221
- 100% {
222
- --_tail: 260deg;
123
+ [data-ui-spinner] > svg > circle:last-child {
124
+ stroke-dasharray: 75 150;
125
+ }
223
126
  }
224
127
  }
@@ -1,5 +1,4 @@
1
1
  [
2
- "--slotted-border-width-medium",
3
2
  "--slotted-control-icon-size-md",
4
3
  "--slotted-control-transition-duration",
5
4
  "--slotted-control-transition-easing",