@slotted/styles 0.0.0-stage → 0.1.0-beta.0

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 (254) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/package.json +276 -3
  4. package/src/accordion/accordion.css +96 -0
  5. package/src/accordion/accordion.tokens.json +14 -0
  6. package/src/action-bar/action-bar.css +455 -0
  7. package/src/action-bar/action-bar.tokens.json +50 -0
  8. package/src/action-sheet/action-sheet.css +462 -0
  9. package/src/action-sheet/action-sheet.tokens.json +59 -0
  10. package/src/alert/alert.css +764 -0
  11. package/src/alert/alert.tokens.json +77 -0
  12. package/src/app-shell/app-shell.css +1899 -0
  13. package/src/app-shell/app-shell.tokens.json +88 -0
  14. package/src/aspect-ratio/aspect-ratio.css +131 -0
  15. package/src/aspect-ratio/aspect-ratio.tokens.json +1 -0
  16. package/src/attachment/attachment.css +906 -0
  17. package/src/attachment/attachment.tokens.json +85 -0
  18. package/src/avatar/avatar.css +598 -0
  19. package/src/avatar/avatar.tokens.json +64 -0
  20. package/src/avatar-group/avatar-group.css +268 -0
  21. package/src/avatar-group/avatar-group.tokens.json +16 -0
  22. package/src/badge/badge.css +462 -0
  23. package/src/badge/badge.tokens.json +58 -0
  24. package/src/breadcrumb/breadcrumb.css +441 -0
  25. package/src/breadcrumb/breadcrumb.tokens.json +50 -0
  26. package/src/button/button.css +754 -0
  27. package/src/button/button.tokens.json +72 -0
  28. package/src/button-group/button-group.css +455 -0
  29. package/src/button-group/button-group.tokens.json +11 -0
  30. package/src/calendar/calendar.css +846 -0
  31. package/src/calendar/calendar.tokens.json +67 -0
  32. package/src/card/card.css +602 -0
  33. package/src/card/card.tokens.json +68 -0
  34. package/src/carousel/carousel.css +860 -0
  35. package/src/carousel/carousel.tokens.json +62 -0
  36. package/src/cascader/cascader.css +1320 -0
  37. package/src/cascader/cascader.tokens.json +103 -0
  38. package/src/chart/chart.css +1473 -0
  39. package/src/chart/chart.tokens.json +107 -0
  40. package/src/checkbox/checkbox.css +1011 -0
  41. package/src/checkbox/checkbox.tokens.json +102 -0
  42. package/src/checkbox-group/checkbox-group.css +70 -0
  43. package/src/checkbox-group/checkbox-group.tokens.json +6 -0
  44. package/src/code-block/code-block.css +555 -0
  45. package/src/code-block/code-block.tokens.json +69 -0
  46. package/src/collapsible/collapsible.css +546 -0
  47. package/src/collapsible/collapsible.tokens.json +55 -0
  48. package/src/color-picker/color-picker.css +607 -0
  49. package/src/color-picker/color-picker.tokens.json +58 -0
  50. package/src/column-manager/column-manager.css +452 -0
  51. package/src/column-manager/column-manager.tokens.json +43 -0
  52. package/src/combobox/combobox.css +1048 -0
  53. package/src/combobox/combobox.tokens.json +112 -0
  54. package/src/command-palette/command-palette.css +1000 -0
  55. package/src/command-palette/command-palette.tokens.json +75 -0
  56. package/src/container/container.css +198 -0
  57. package/src/container/container.tokens.json +18 -0
  58. package/src/context-menu/context-menu.css +104 -0
  59. package/src/context-menu/context-menu.tokens.json +11 -0
  60. package/src/currency-field/currency-field.css +787 -0
  61. package/src/currency-field/currency-field.tokens.json +80 -0
  62. package/src/dashboard-grid/dashboard-grid.css +748 -0
  63. package/src/dashboard-grid/dashboard-grid.tokens.json +67 -0
  64. package/src/data-grid/data-grid.css +2484 -0
  65. package/src/data-grid/data-grid.tokens.json +100 -0
  66. package/src/data-list/data-list.css +455 -0
  67. package/src/data-list/data-list.tokens.json +35 -0
  68. package/src/date-field/date-field.css +560 -0
  69. package/src/date-field/date-field.tokens.json +80 -0
  70. package/src/date-picker/date-picker.css +420 -0
  71. package/src/date-picker/date-picker.tokens.json +49 -0
  72. package/src/date-range-picker/date-range-picker.css +983 -0
  73. package/src/date-range-picker/date-range-picker.tokens.json +109 -0
  74. package/src/datetime-field/datetime-field.css +850 -0
  75. package/src/datetime-field/datetime-field.tokens.json +91 -0
  76. package/src/datetime-picker/datetime-picker.css +843 -0
  77. package/src/datetime-picker/datetime-picker.tokens.json +78 -0
  78. package/src/datetime-range-picker/datetime-range-picker.css +1375 -0
  79. package/src/datetime-range-picker/datetime-range-picker.tokens.json +135 -0
  80. package/src/description-list/description-list.css +675 -0
  81. package/src/description-list/description-list.tokens.json +66 -0
  82. package/src/detail-panel/detail-panel.css +329 -0
  83. package/src/detail-panel/detail-panel.tokens.json +33 -0
  84. package/src/dialog/dialog.css +968 -0
  85. package/src/dialog/dialog.tokens.json +66 -0
  86. package/src/divider/divider.css +131 -0
  87. package/src/divider/divider.tokens.json +18 -0
  88. package/src/drawer/drawer.css +1303 -0
  89. package/src/drawer/drawer.tokens.json +75 -0
  90. package/src/entity-picker/entity-picker.css +1367 -0
  91. package/src/entity-picker/entity-picker.tokens.json +118 -0
  92. package/src/event-calendar/event-calendar.css +2148 -0
  93. package/src/event-calendar/event-calendar.tokens.json +87 -0
  94. package/src/field/field.css +707 -0
  95. package/src/field/field.tokens.json +58 -0
  96. package/src/field-group/field-group.css +226 -0
  97. package/src/field-group/field-group.tokens.json +33 -0
  98. package/src/filter-bar/filter-bar.css +1306 -0
  99. package/src/filter-bar/filter-bar.tokens.json +78 -0
  100. package/src/form/form.css +218 -0
  101. package/src/form/form.tokens.json +32 -0
  102. package/src/fullscreen/fullscreen.css +376 -0
  103. package/src/fullscreen/fullscreen.tokens.json +50 -0
  104. package/src/grid/grid.css +173 -0
  105. package/src/grid/grid.tokens.json +20 -0
  106. package/src/highlight/highlight.css +135 -0
  107. package/src/highlight/highlight.tokens.json +16 -0
  108. package/src/hover-card/hover-card.css +93 -0
  109. package/src/hover-card/hover-card.tokens.json +17 -0
  110. package/src/icon/icon.css +136 -0
  111. package/src/icon/icon.tokens.json +14 -0
  112. package/src/image/image.css +449 -0
  113. package/src/image/image.tokens.json +27 -0
  114. package/src/import-export-center/import-export-center.css +1175 -0
  115. package/src/import-export-center/import-export-center.tokens.json +84 -0
  116. package/src/inline/inline.css +149 -0
  117. package/src/inline/inline.tokens.json +14 -0
  118. package/src/inline-message/inline-message.css +396 -0
  119. package/src/inline-message/inline-message.tokens.json +19 -0
  120. package/src/kbd/kbd.css +490 -0
  121. package/src/kbd/kbd.tokens.json +40 -0
  122. package/src/link/link.css +237 -0
  123. package/src/link/link.tokens.json +25 -0
  124. package/src/list/list.css +240 -0
  125. package/src/list/list.tokens.json +42 -0
  126. package/src/list-item/list-item.css +937 -0
  127. package/src/list-item/list-item.tokens.json +114 -0
  128. package/src/listbox/listbox.css +674 -0
  129. package/src/listbox/listbox.tokens.json +72 -0
  130. package/src/loading-overlay/loading-overlay.css +730 -0
  131. package/src/loading-overlay/loading-overlay.tokens.json +48 -0
  132. package/src/markdown/markdown.css +168 -0
  133. package/src/markdown/markdown.tokens.json +18 -0
  134. package/src/menu/menu.css +817 -0
  135. package/src/menu/menu.tokens.json +78 -0
  136. package/src/menubar/menubar.css +170 -0
  137. package/src/menubar/menubar.tokens.json +32 -0
  138. package/src/message/message.css +1453 -0
  139. package/src/message/message.tokens.json +85 -0
  140. package/src/message-scroller/message-scroller.css +371 -0
  141. package/src/message-scroller/message-scroller.tokens.json +47 -0
  142. package/src/multi-select/multi-select.css +1427 -0
  143. package/src/multi-select/multi-select.tokens.json +114 -0
  144. package/src/navigation-menu/navigation-menu.css +1029 -0
  145. package/src/navigation-menu/navigation-menu.tokens.json +70 -0
  146. package/src/notification-center/notification-center.css +856 -0
  147. package/src/notification-center/notification-center.tokens.json +84 -0
  148. package/src/number-field/number-field.css +794 -0
  149. package/src/number-field/number-field.tokens.json +80 -0
  150. package/src/otp-input/otp-input.css +525 -0
  151. package/src/otp-input/otp-input.tokens.json +69 -0
  152. package/src/page/page.css +179 -0
  153. package/src/page/page.tokens.json +20 -0
  154. package/src/page-header/page-header.css +1181 -0
  155. package/src/page-header/page-header.tokens.json +71 -0
  156. package/src/pagination/pagination.css +500 -0
  157. package/src/pagination/pagination.tokens.json +56 -0
  158. package/src/popover/popover.css +526 -0
  159. package/src/popover/popover.tokens.json +60 -0
  160. package/src/progress-indicator/progress-indicator.css +1584 -0
  161. package/src/progress-indicator/progress-indicator.tokens.json +50 -0
  162. package/src/prompt-composer/prompt-composer.css +680 -0
  163. package/src/prompt-composer/prompt-composer.tokens.json +99 -0
  164. package/src/prose/prose.css +733 -0
  165. package/src/prose/prose.tokens.json +79 -0
  166. package/src/radio-group/radio-group.css +778 -0
  167. package/src/radio-group/radio-group.tokens.json +103 -0
  168. package/src/rating/rating.css +1071 -0
  169. package/src/rating/rating.tokens.json +88 -0
  170. package/src/result-state/result-state.css +980 -0
  171. package/src/result-state/result-state.tokens.json +63 -0
  172. package/src/runtime-properties.mjs +321 -0
  173. package/src/saved-views/saved-views.css +394 -0
  174. package/src/saved-views/saved-views.tokens.json +41 -0
  175. package/src/scroll-area/scroll-area.css +481 -0
  176. package/src/scroll-area/scroll-area.tokens.json +15 -0
  177. package/src/section/section.css +177 -0
  178. package/src/section/section.tokens.json +39 -0
  179. package/src/segmented-control/segmented-control.css +564 -0
  180. package/src/segmented-control/segmented-control.tokens.json +67 -0
  181. package/src/select/select.css +1593 -0
  182. package/src/select/select.tokens.json +110 -0
  183. package/src/side-navigation/side-navigation.css +1096 -0
  184. package/src/side-navigation/side-navigation.tokens.json +87 -0
  185. package/src/skeleton/skeleton.css +431 -0
  186. package/src/skeleton/skeleton.tokens.json +29 -0
  187. package/src/slider/slider.css +792 -0
  188. package/src/slider/slider.tokens.json +75 -0
  189. package/src/sparkline/sparkline.css +282 -0
  190. package/src/sparkline/sparkline.tokens.json +45 -0
  191. package/src/spinner/spinner.css +224 -0
  192. package/src/spinner/spinner.tokens.json +8 -0
  193. package/src/split-button/split-button.css +253 -0
  194. package/src/split-button/split-button.tokens.json +12 -0
  195. package/src/splitter/splitter.css +336 -0
  196. package/src/splitter/splitter.tokens.json +24 -0
  197. package/src/stack/stack.css +128 -0
  198. package/src/stack/stack.tokens.json +14 -0
  199. package/src/stat-card/stat-card.css +641 -0
  200. package/src/stat-card/stat-card.tokens.json +95 -0
  201. package/src/status-indicator/status-indicator.css +481 -0
  202. package/src/status-indicator/status-indicator.tokens.json +38 -0
  203. package/src/stepper/stepper.css +629 -0
  204. package/src/stepper/stepper.tokens.json +72 -0
  205. package/src/switch/switch.css +703 -0
  206. package/src/switch/switch.tokens.json +75 -0
  207. package/src/table/table.css +1482 -0
  208. package/src/table/table.tokens.json +69 -0
  209. package/src/table-of-contents/table-of-contents.css +533 -0
  210. package/src/table-of-contents/table-of-contents.tokens.json +53 -0
  211. package/src/tabs/tabs.css +998 -0
  212. package/src/tabs/tabs.tokens.json +82 -0
  213. package/src/tag/tag.css +768 -0
  214. package/src/tag/tag.tokens.json +85 -0
  215. package/src/tags-input/tags-input.css +757 -0
  216. package/src/tags-input/tags-input.tokens.json +84 -0
  217. package/src/text-field/text-field.css +900 -0
  218. package/src/text-field/text-field.tokens.json +87 -0
  219. package/src/textarea/textarea.css +365 -0
  220. package/src/textarea/textarea.tokens.json +59 -0
  221. package/src/time-field/time-field.css +719 -0
  222. package/src/time-field/time-field.tokens.json +86 -0
  223. package/src/time-picker/time-picker.css +665 -0
  224. package/src/time-picker/time-picker.tokens.json +66 -0
  225. package/src/time-range-picker/time-range-picker.css +1287 -0
  226. package/src/time-range-picker/time-range-picker.tokens.json +134 -0
  227. package/src/timeline/timeline.css +1596 -0
  228. package/src/timeline/timeline.tokens.json +96 -0
  229. package/src/toast/toast.css +943 -0
  230. package/src/toast/toast.tokens.json +90 -0
  231. package/src/toggle/toggle.css +290 -0
  232. package/src/toggle/toggle.tokens.json +54 -0
  233. package/src/toggle-group/toggle-group.css +463 -0
  234. package/src/toggle-group/toggle-group.tokens.json +54 -0
  235. package/src/toolbar/toolbar.css +434 -0
  236. package/src/toolbar/toolbar.tokens.json +49 -0
  237. package/src/tooltip/tooltip.css +368 -0
  238. package/src/tooltip/tooltip.tokens.json +39 -0
  239. package/src/top-navigation/top-navigation.css +1287 -0
  240. package/src/top-navigation/top-navigation.tokens.json +91 -0
  241. package/src/tour/tour.css +680 -0
  242. package/src/tour/tour.tokens.json +67 -0
  243. package/src/transfer/transfer.css +571 -0
  244. package/src/transfer/transfer.tokens.json +49 -0
  245. package/src/tree/tree.css +1000 -0
  246. package/src/tree/tree.tokens.json +80 -0
  247. package/src/typography/typography.css +211 -0
  248. package/src/typography/typography.tokens.json +31 -0
  249. package/src/upload/upload.css +1706 -0
  250. package/src/upload/upload.tokens.json +123 -0
  251. package/src/visually-hidden/visually-hidden.css +52 -0
  252. package/src/visually-hidden/visually-hidden.tokens.json +1 -0
  253. package/src/wizard/wizard.css +740 -0
  254. package/src/wizard/wizard.tokens.json +63 -0
@@ -0,0 +1,787 @@
1
+ @layer slotted.components {
2
+ /**
3
+ * ── One amount, in one currency ────────────────────────────────────────
4
+ *
5
+ * The frame `number-field` established, around a control that holds money.
6
+ * The shape is the same because the reason is: a `spinbutton`, like a
7
+ * `textbox`, may hold only non-interactive presentational children, so the
8
+ * two steppers are siblings of the control inside a frame with no role.
9
+ *
10
+ * What this family paints that the sibling does not is the alignment. An
11
+ * amount is read from its last digit — a column of them is compared on the
12
+ * cents — and under cents-first entry the caret lives at that edge, so the
13
+ * value is set against the reading end unless a consumer says otherwise.
14
+ *
15
+ * Every rule keys on `data-ui` and `data-part`. The field around the control
16
+ * mirrors its state onto the frame rather than styling through, so a control
17
+ * used outside a field looks the same as one inside it.
18
+ */
19
+ [data-ui='currency-field'][data-part='root'] {
20
+ /**
21
+ * Type, height, inline padding, gap and glyph size all come from the
22
+ * control scales — the only ones the theme restates per density — so an
23
+ * amount in a dense table shrinks whole rather than in parts.
24
+ */
25
+ --_font-size: var(--slotted-control-font-size-md, 13px);
26
+ --_height: var(--slotted-control-height-md, 34px);
27
+ --_padding-inline: var(--slotted-control-padding-inline-md, 13px);
28
+ --_gap: var(--slotted-control-gap-md, 8px);
29
+ /** The floor under the glyph scale at the dense step (SL3); a family token so a theme can retune it. */
30
+ --_icon-floor: var(--slotted-currency-field-icon-size-min, 14px);
31
+ --_icon-size: max(var(--_icon-floor), var(--slotted-control-icon-size-md, 16px));
32
+ --_inner-size: var(--slotted-control-inner-size-md, 24px);
33
+
34
+ /**
35
+ * The stepper's cross measurement — the column's width when the two halves
36
+ * are stacked, and each button's width when they are split. The glyph plus
37
+ * a step of air on each side — the box every inner control in the library
38
+ * draws (R2) — capped at the inner size of a control at this step
39
+ * (`--slotted-control-inner-size-*`) and floored at the WCAG 2.2 minimum.
40
+ */
41
+ --_stepper-size: var(
42
+ --slotted-currency-field-stepper-size,
43
+ max(
44
+ var(--slotted-target-min, 24px),
45
+ min(calc(var(--_icon-size) + 2 * var(--slotted-space-2, 0.25rem)), var(--_inner-size))
46
+ )
47
+ );
48
+
49
+ /**
50
+ * The split buttons are the inner controls `text-field` draws: the glyph
51
+ * plus a step of air, capped at the inner size, inset concentrically — the
52
+ * air beside a button equals the air above and below it, and its corner is
53
+ * the frame's less that air (docs/history/designs/2026-10-01-currency-field-reui-dossier.md § 3).
54
+ */
55
+ --_action-size: var(
56
+ --slotted-currency-field-action-size,
57
+ min(calc(var(--_icon-size) + 2 * var(--slotted-space-2, 0.25rem)), var(--_inner-size))
58
+ );
59
+ --_inset: max(
60
+ 0px,
61
+ calc(
62
+ (var(--_height) - 2 * var(--slotted-control-border-width, 1px) - var(--_action-size)) / 2
63
+ )
64
+ );
65
+ --_action-radius: max(
66
+ var(--slotted-radius-xs, 2px),
67
+ calc(var(--slotted-currency-field-radius, var(--slotted-control-radius, 4px)) - var(--_inset))
68
+ );
69
+
70
+ /**
71
+ * The stacked column is inset by the smallest step rather than by
72
+ * `--_inset`: two halves share the frame's height, and the larger air
73
+ * would leave each half 12 pixels tall at `md`.
74
+ */
75
+ --_column-inset: var(--slotted-space-1, 2px);
76
+ --_column-radius: max(
77
+ var(--slotted-radius-xs, 2px),
78
+ calc(
79
+ var(--slotted-currency-field-radius, var(--slotted-control-radius, 4px)) -
80
+ var(--_column-inset)
81
+ )
82
+ );
83
+
84
+ align-items: center;
85
+ background-color: var(
86
+ --slotted-currency-field-background,
87
+ var(--slotted-control-surface, Field)
88
+ );
89
+ /**
90
+ * The control step of the border scale rather than the strong one: a
91
+ * separator between two surfaces and the edge of a control are the same
92
+ * ratio and not the same weight.
93
+ */
94
+ border: var(--slotted-control-border-width, 1px) solid
95
+ var(--slotted-currency-field-border-color, var(--slotted-control-border-color, ButtonBorder));
96
+ border-radius: var(--slotted-currency-field-radius, var(--slotted-control-radius, 4px));
97
+ box-shadow: var(--slotted-currency-field-shadow, var(--slotted-control-shadow, none));
98
+ box-sizing: border-box;
99
+ color: var(--slotted-currency-field-color, var(--slotted-text, CanvasText));
100
+ display: flex;
101
+ font-family: var(--slotted-control-font-family, system-ui, sans-serif);
102
+ font-size: var(--_font-size);
103
+ gap: var(--_gap);
104
+ inline-size: 100%;
105
+ /* A control in a grid track narrower than its intrinsic width has to shrink. */
106
+ min-inline-size: 0;
107
+ /* A minimum rather than a height: the frame is the taller of the control
108
+ scale and whatever a consumer put in an adornment. */
109
+ min-block-size: var(--_height);
110
+ padding-inline: var(--_padding-inline);
111
+ transition:
112
+ background-color var(--slotted-control-transition-duration)
113
+ var(--slotted-control-transition-easing),
114
+ border-color var(--slotted-control-transition-duration)
115
+ var(--slotted-control-transition-easing),
116
+ box-shadow var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
117
+ }
118
+
119
+ /**
120
+ * The control. It gives up its own frame entirely and keeps everything that
121
+ * makes it a text control: the value, the caret, the selection and every
122
+ * native attribute a consumer wrote.
123
+ *
124
+ * The figures are tabular without an axis to ask for it. An amount whose
125
+ * digits change width as they are typed over is an amount that moves under
126
+ * the reader's eye, and under cents-first entry every keystroke rewrites the
127
+ * whole string — so the setting matters more here than anywhere else.
128
+ */
129
+ [data-ui='currency-field'] > [data-part='input'] {
130
+ background-color: transparent;
131
+ /* The frame's border box, not its content box: on a coarse pointer the frame
132
+ is the touch target's height and the control it holds is too. Stretched
133
+ rather than a percentage of the frame, which is indefinite wherever the
134
+ frame is not stretched itself — a search field in a bar measured 17
135
+ pixels. The control paints no ground, so the frame's edge still shows. */
136
+ align-self: stretch;
137
+ margin-block: calc(var(--slotted-control-border-width, 1px) * -1);
138
+ border: 0;
139
+ color: inherit;
140
+ flex: 1 1 auto;
141
+ font: inherit;
142
+ font-variant-numeric: tabular-nums;
143
+ /* The frame draws the ring for it, so two rings never nest. */
144
+ outline: none;
145
+ padding: 0;
146
+ /* The intrinsic width of an `input` is about twenty characters, and a flex
147
+ item refuses to shrink below it without this. */
148
+ min-inline-size: 0;
149
+ text-overflow: ellipsis;
150
+ /**
151
+ * The value is its own bidi run, ordered by its own content.
152
+ *
153
+ * A formatted amount has no strong character in it — digits, a sign, two
154
+ * separators and a symbol are all weak or neutral — so in a right-to-left
155
+ * document the whole run takes the paragraph's direction and `-R$ 1.480,00`
156
+ * prints with the sign after the amount. `plaintext` resolves a run with no
157
+ * strong character to left-to-right, which is the order every numbering
158
+ * system writes a number in.
159
+ */
160
+ unicode-bidi: plaintext;
161
+ }
162
+
163
+ [data-ui='currency-field'] > [data-part='input']::placeholder {
164
+ color: var(--slotted-currency-field-placeholder-color, var(--slotted-text-muted, GrayText));
165
+ text-overflow: ellipsis;
166
+ }
167
+
168
+ /* ── Alignment ────────────────────────────────────────────────────────
169
+ *
170
+ * The two rules below are the only place this family names a side, and they
171
+ * name it because they have to. `unicode-bidi: plaintext` pins the value's
172
+ * run to left-to-right, and a logical `text-align` resolves against that
173
+ * pinned direction rather than against the document's — so `end` in a
174
+ * right-to-left form would align the amount to the left of its own frame
175
+ * while the reader's eye is at the right. There is no logical value that
176
+ * means "the document's end edge, whatever the run inside is doing".
177
+ */
178
+
179
+ [data-ui='currency-field'][data-align='end'] > [data-part='input'] {
180
+ text-align: right;
181
+ }
182
+
183
+ [data-ui='currency-field'][data-align='end']:dir(rtl) > [data-part='input'] {
184
+ text-align: left;
185
+ }
186
+
187
+ [data-ui='currency-field'][data-align='start'] > [data-part='input'] {
188
+ text-align: left;
189
+ }
190
+
191
+ [data-ui='currency-field'][data-align='start']:dir(rtl) > [data-part='input'] {
192
+ text-align: right;
193
+ }
194
+
195
+ /* ── Size ─────────────────────────────────────────────────────────────
196
+ *
197
+ * Three steps, matching the field the control sits in and every sibling
198
+ * control beside it. One library, one scale.
199
+ */
200
+
201
+ [data-ui='currency-field'][data-part='root'][data-size='sm'] {
202
+ --_font-size: var(--slotted-control-font-size-sm, 12px);
203
+ --_height: var(--slotted-control-height-sm, 30px);
204
+ --_padding-inline: var(--slotted-control-padding-inline-sm, 10px);
205
+ --_gap: var(--slotted-control-gap-sm, 6px);
206
+ --_icon-size: max(var(--_icon-floor), var(--slotted-control-icon-size-sm, 14px));
207
+ --_inner-size: var(--slotted-control-inner-size-sm, 22px);
208
+ }
209
+
210
+ [data-ui='currency-field'][data-part='root'][data-size='md'] {
211
+ --_font-size: var(--slotted-control-font-size-md, 13px);
212
+ --_height: var(--slotted-control-height-md, 34px);
213
+ --_padding-inline: var(--slotted-control-padding-inline-md, 13px);
214
+ --_gap: var(--slotted-control-gap-md, 8px);
215
+ --_icon-size: max(var(--_icon-floor), var(--slotted-control-icon-size-md, 16px));
216
+ --_inner-size: var(--slotted-control-inner-size-md, 24px);
217
+ }
218
+
219
+ [data-ui='currency-field'][data-part='root'][data-size='lg'] {
220
+ --_font-size: var(--slotted-control-font-size-lg, 14px);
221
+ --_height: var(--slotted-control-height-lg, 38px);
222
+ --_padding-inline: var(--slotted-control-padding-inline-lg, 16px);
223
+ --_gap: var(--slotted-control-gap-lg, 8px);
224
+ --_icon-size: max(var(--_icon-floor), var(--slotted-control-icon-size-lg, 18px));
225
+ --_inner-size: var(--slotted-control-inner-size-lg, 28px);
226
+ }
227
+
228
+ /* ── Variant ──────────────────────────────────────────────────────────
229
+ *
230
+ * The three every framed control in this library ships: `outline` takes the
231
+ * ground it is placed on, `subtle` sinks into the page, and `plain` shows
232
+ * nothing at rest, which is what a dense table of amounts needs.
233
+ */
234
+
235
+ [data-ui='currency-field'][data-part='root'][data-variant='subtle'] {
236
+ background-color: var(
237
+ --slotted-currency-field-subtle-background,
238
+ var(--slotted-surface-sunken, Canvas)
239
+ );
240
+ border-color: var(
241
+ --slotted-currency-field-subtle-border-color,
242
+ var(--slotted-border-subtle, ButtonBorder)
243
+ );
244
+ }
245
+
246
+ /* No edge, no lift, no ground and no corner of its own: the cell it sits in
247
+ is the box, the row is the ground, a cell has no corner, and the resting
248
+ shadow would draw the cell where there is none. */
249
+ [data-ui='currency-field'][data-part='root'][data-variant='plain'] {
250
+ background-color: transparent;
251
+ border-color: transparent;
252
+ border-radius: var(--slotted-radius-none, 0px);
253
+ box-shadow: none;
254
+ }
255
+
256
+ /**
257
+ * The one rule the theme cannot write for the family. A transparent border
258
+ * has no system colour to be remapped to, so the `plain` control would lose
259
+ * its only edge exactly where an edge matters most.
260
+ */
261
+ @media (forced-colors: active) {
262
+ [data-ui='currency-field'][data-part='root'][data-variant='plain'] {
263
+ border-color: ButtonBorder;
264
+ }
265
+ }
266
+
267
+ /* ── Adornments ───────────────────────────────────────────────────────
268
+ *
269
+ * A symbol beside the amount rather than inside it. A symbol that belongs
270
+ * inside the value arrives through `Intl` as part of the string the control
271
+ * holds, is selected with it, is copied with it and is parsed back out of
272
+ * it. This one is none of those things, which is the whole distinction.
273
+ */
274
+ [data-ui='currency-field'] > [data-part='start'],
275
+ [data-ui='currency-field'] > [data-part='end'] {
276
+ align-items: center;
277
+ color: var(--slotted-currency-field-adornment-color, var(--slotted-text-muted, GrayText));
278
+ display: inline-flex;
279
+ /* Never squeezed: the value gives up width first, because the value is the
280
+ part that can scroll. */
281
+ flex: 0 0 auto;
282
+ gap: var(--slotted-space-2, 0.25rem);
283
+ line-height: 1;
284
+ /**
285
+ * Its own bidi run, ordered by its own content, so `R$` reads as itself in
286
+ * a right-to-left document instead of resolving to the paragraph.
287
+ */
288
+ unicode-bidi: plaintext;
289
+
290
+ --slotted-icon-size: var(--_icon-size);
291
+ }
292
+
293
+ [data-ui='currency-field'] > [data-part='start'] > svg,
294
+ [data-ui='currency-field'] > [data-part='end'] > svg {
295
+ block-size: var(--_icon-size);
296
+ inline-size: var(--_icon-size);
297
+ }
298
+
299
+ /**
300
+ * Optical alignment between the two kinds of adornment: a glyph fills the
301
+ * whole box and its artwork does not, while a character fills only its own
302
+ * advance width. An adornment that draws no glyph takes the margin the glyph
303
+ * box already has, on both sides — the measurement `text-field` derived.
304
+ */
305
+ [data-ui='currency-field'] > [data-part='start']:not(:has(svg, img, [data-ui='icon'])),
306
+ [data-ui='currency-field'] > [data-part='end']:not(:has(svg, img, [data-ui='icon'])) {
307
+ padding-inline: calc(var(--_icon-size) / 8);
308
+ }
309
+
310
+ /**
311
+ * A mark that moves the amount when it is dragged sideways — the sibling's
312
+ * gesture, drawn as the sibling draws it. The adornment holding it stands
313
+ * the frame's full height, so the target is the frame's height rather than
314
+ * the mark's.
315
+ *
316
+ * Hover lifts the mark from the adornments' quiet ink to the value's: the
317
+ * mark is not a button, it has no ground to change, and the cursor alone
318
+ * said nothing a reader would look at.
319
+ */
320
+ [data-ui='currency-field']
321
+ > :is([data-part='start'], [data-part='end']):has(> [data-part='scrub']) {
322
+ align-self: stretch;
323
+ }
324
+
325
+ [data-ui='currency-field'] [data-part='scrub'] {
326
+ align-items: center;
327
+ align-self: stretch;
328
+ cursor: ew-resize;
329
+ display: inline-flex;
330
+ gap: var(--slotted-space-2, 0.25rem);
331
+ /* The drag is the gesture; a pan or a text selection would take it. */
332
+ touch-action: none;
333
+ transition: color var(--slotted-control-transition-duration)
334
+ var(--slotted-control-transition-easing);
335
+ /* Its own bidi run, like the adornment around it: as a flex container its
336
+ text is a new block, and `R$` in a right-to-left document read `$R`. */
337
+ unicode-bidi: plaintext;
338
+ user-select: none;
339
+ -webkit-user-select: none;
340
+ }
341
+
342
+ [data-ui='currency-field']:not([data-disabled]):not([data-readonly])
343
+ [data-part='scrub']:is(:hover, [data-scrubbing]) {
344
+ color: var(--slotted-currency-field-scrub-active-color, var(--slotted-text, CanvasText));
345
+ }
346
+
347
+ [data-ui='currency-field']:is([data-disabled], [data-readonly]) [data-part='scrub'] {
348
+ cursor: default;
349
+ touch-action: auto;
350
+ }
351
+
352
+ /* ── The two steppers ─────────────────────────────────────────────────
353
+ *
354
+ * Withdrawn by default on this family — an invoice total is typed, not
355
+ * nudged — and drawn exactly as the sibling draws them when a product asks
356
+ * for them. Real buttons, out of the tab order, with real names and
357
+ * `aria-controls`. Their glyphs are the `Glyph` element the binding
358
+ * renders: a character is never an icon.
359
+ */
360
+ [data-ui='currency-field'] > [data-part='stepper'] > button,
361
+ [data-ui='currency-field'] > [data-part='decrement'],
362
+ [data-ui='currency-field'] > [data-part='increment'] {
363
+ align-items: center;
364
+ appearance: none;
365
+ background-color: transparent;
366
+ border: 0;
367
+ color: var(--slotted-currency-field-stepper-color, var(--slotted-text-muted, GrayText));
368
+ cursor: pointer;
369
+ display: inline-flex;
370
+ flex: 0 0 auto;
371
+ justify-content: center;
372
+ padding: 0;
373
+ /* The containing block of the hit area recovered below. */
374
+ position: relative;
375
+ /* No tap delay waiting for a second tap the button cannot use. */
376
+ touch-action: manipulation;
377
+ transition:
378
+ background-color var(--slotted-control-transition-duration)
379
+ var(--slotted-control-transition-easing),
380
+ color var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
381
+ -webkit-tap-highlight-color: transparent;
382
+ }
383
+
384
+ /* The `icon` box inside each button is drawn at the control's own icon
385
+ step rather than at the icon family's default. */
386
+ [data-ui='currency-field'] [data-part='decrement'],
387
+ [data-ui='currency-field'] [data-part='increment'] {
388
+ --slotted-icon-size: var(--_icon-size);
389
+ }
390
+
391
+ /**
392
+ * Stacked, the marks are chevrons: two halves of one column, pointing at the
393
+ * direction the value moves. The binding draws the down chevron in both, and
394
+ * the up one is that artwork turned half a turn.
395
+ *
396
+ * Split, they are the minus and the plus — one pair on one grid. Less and
397
+ * more do not change with the reading direction, where a chevron along the
398
+ * inline axis would read as "previous" and "next" and would have to mirror.
399
+ */
400
+ [data-ui='currency-field'][data-stepper='stacked'] [data-part='increment'] > [data-ui='icon'] {
401
+ rotate: 0.5turn;
402
+ }
403
+
404
+ /* ── Stacked: one column at the inline end ────────────────────────────
405
+ *
406
+ * The column stands `--_column-inset` inside the frame's edge, in the block
407
+ * axis and at the inline end, with no rule of its own: each half paints a
408
+ * ground concentric with the frame when it is hovered, pressed or at its
409
+ * bound, and that ground is the only edge a half needs. The value, the
410
+ * column and its halves are one surface. The paint gives the inset back to
411
+ * the pointer: each half's hit area reaches the frame.
412
+ */
413
+ [data-ui='currency-field'][data-part='root'][data-stepper='stacked'] {
414
+ padding-inline-end: 0;
415
+ }
416
+
417
+ [data-ui='currency-field'] > [data-part='stepper'] {
418
+ align-self: stretch;
419
+ box-sizing: content-box;
420
+ display: flex;
421
+ flex: 0 0 auto;
422
+ flex-direction: column;
423
+ inline-size: var(--_stepper-size);
424
+ padding-block: var(--_column-inset);
425
+ padding-inline-end: var(--_column-inset);
426
+ }
427
+
428
+ [data-ui='currency-field'] > [data-part='stepper'] > button {
429
+ /* Two halves of the column, whatever the frame's height turns out to be. */
430
+ block-size: 50%;
431
+ border-radius: var(--_column-radius);
432
+ inline-size: 100%;
433
+ }
434
+
435
+ [data-ui='currency-field']
436
+ > [data-part='stepper']
437
+ > :is([data-part='increment'], [data-part='decrement'])::after {
438
+ content: '';
439
+ inset-inline: 0 calc(var(--_column-inset) * -1);
440
+ position: absolute;
441
+ }
442
+
443
+ [data-ui='currency-field'] > [data-part='stepper'] > [data-part='increment']::after {
444
+ inset-block: calc(var(--_column-inset) * -1) 0;
445
+ }
446
+
447
+ [data-ui='currency-field'] > [data-part='stepper'] > [data-part='decrement']::after {
448
+ inset-block: 0 calc(var(--_column-inset) * -1);
449
+ }
450
+
451
+ /**
452
+ * The glyph box is the control's own, bounded by the half that holds it: a
453
+ * chevron's artwork occupies about a quarter of its box, so a box shrunk to
454
+ * leave visible air would leave a mark a few pixels tall beside a minus and a
455
+ * plus drawn at full size.
456
+ *
457
+ * Size containment keeps the drawing out of the column's intrinsic height.
458
+ * While the frame measures its content the half has no height yet, so the
459
+ * percentage falls back to the `<svg>`'s own square, and two of them plus the
460
+ * divider would stand taller than the value and grow the frame.
461
+ */
462
+ [data-ui='currency-field'] > [data-part='stepper'] > button > [data-ui='icon'] {
463
+ block-size: min(var(--_icon-size), 100%);
464
+ contain: size;
465
+ }
466
+
467
+ /* ── Split: one button at each end ────────────────────────────────────
468
+ *
469
+ * Each button is a square inner control inset concentrically, and the
470
+ * frame's padding is that inset on both sides. No rule divides a button from
471
+ * the value: the ground it paints is its edge.
472
+ */
473
+ [data-ui='currency-field'][data-part='root'][data-stepper='split'] {
474
+ gap: 0;
475
+ padding-inline: var(--_inset);
476
+ }
477
+
478
+ [data-ui='currency-field'][data-stepper='split'] > [data-part='decrement'],
479
+ [data-ui='currency-field'][data-stepper='split'] > [data-part='increment'] {
480
+ block-size: var(--_action-size);
481
+ border-radius: var(--_action-radius);
482
+ inline-size: var(--_action-size);
483
+ }
484
+
485
+ [data-ui='currency-field'][data-stepper='split'] > [data-part='start'] {
486
+ padding-inline-start: var(--_gap);
487
+ }
488
+
489
+ [data-ui='currency-field'][data-stepper='split'] > [data-part='end'] {
490
+ padding-inline-end: var(--_gap);
491
+ }
492
+
493
+ /**
494
+ * The value keeps its own alignment between the two buttons rather than
495
+ * being centred the way a quantity is: a quantity is a token between a minus
496
+ * and a plus, and an amount is still a figure to be compared down a column.
497
+ *
498
+ * Its inset is the frame's own padding rather than the gap the sibling
499
+ * family uses, so the digits sit the same distance from the edge in all
500
+ * three arrangements. Read down a column mixing them, an amount with a
501
+ * stepper beside it would otherwise be five pixels off every amount without
502
+ * one — which is the comparison `align="end"` exists to make.
503
+ */
504
+ [data-ui='currency-field'][data-stepper='split'] > [data-part='input'] {
505
+ padding-inline: var(--_padding-inline);
506
+ }
507
+
508
+ /**
509
+ * The hit area, recovered outside the paint. At `sm` with `dense` the whole
510
+ * control is 24 pixels tall, so a button bounded by the frame cannot also be
511
+ * 24 in both axes. This grows the target to the minimum and leaves a larger
512
+ * one alone — the correction `docs/token-foundation.md` leaves to the family.
513
+ */
514
+ [data-ui='currency-field'][data-stepper='split'] > [data-part='decrement']::after,
515
+ [data-ui='currency-field'][data-stepper='split'] > [data-part='increment']::after {
516
+ content: '';
517
+ inset: min(0px, calc((100% - var(--slotted-target-min, 24px)) / 2));
518
+ position: absolute;
519
+ }
520
+
521
+ /**
522
+ * On a finger the target is the comfortable size. The paint stays the inset
523
+ * square a mouse sees; only the hit area grows.
524
+ *
525
+ * Split, each button's hit area reaches 44 in both axes. Stacked, each half
526
+ * keeps its own half of the column in the block axis — reaching the frame's
527
+ * edge, as on a fine pointer — and reaches 44 in the inline axis, over the
528
+ * value's edge and past the frame's.
529
+ */
530
+ @media (pointer: coarse) {
531
+ [data-ui='currency-field'][data-stepper='split']
532
+ > :is([data-part='decrement'], [data-part='increment'])::after {
533
+ inset: min(0px, calc((100% - var(--slotted-target-comfortable, 44px)) / 2));
534
+ }
535
+
536
+ [data-ui='currency-field']
537
+ > [data-part='stepper']
538
+ > :is([data-part='increment'], [data-part='decrement'])::after {
539
+ inset-inline: min(0px, calc((100% - var(--slotted-target-comfortable, 44px)) / 2));
540
+ }
541
+ }
542
+
543
+ /* ── Stepper states ───────────────────────────────────────────────────
544
+ *
545
+ * Written strongest last, and each weaker one guarded against the stronger
546
+ * ones it can meet, which is the order `STATE_PRECEDENCE` sets.
547
+ */
548
+
549
+ [data-ui='currency-field'] [data-part='decrement']:hover:not(:disabled),
550
+ [data-ui='currency-field'] [data-part='increment']:hover:not(:disabled) {
551
+ background-color: var(
552
+ --slotted-currency-field-stepper-hover-background,
553
+ var(--slotted-tone-secondary-subtle-hover)
554
+ );
555
+ }
556
+
557
+ [data-ui='currency-field'] [data-part='decrement']:active:not(:disabled),
558
+ [data-ui='currency-field'] [data-part='increment']:active:not(:disabled) {
559
+ background-color: var(
560
+ --slotted-currency-field-stepper-active-background,
561
+ var(--slotted-tone-secondary-subtle-active)
562
+ );
563
+ }
564
+
565
+ /**
566
+ * A stepper at its bound is disabled rather than silently inert, so its name
567
+ * still announces and its appearance says why nothing happens. The ground
568
+ * says it as well as the ink: the disabled foreground and the resting
569
+ * stepper colour are two steps of one neutral.
570
+ */
571
+ [data-ui='currency-field'] [data-part='decrement']:disabled,
572
+ [data-ui='currency-field'] [data-part='increment']:disabled {
573
+ background-color: var(
574
+ --slotted-currency-field-stepper-disabled-background,
575
+ var(--slotted-disabled-background)
576
+ );
577
+ color: var(--slotted-currency-field-stepper-disabled-color, var(--slotted-disabled-foreground));
578
+ cursor: not-allowed;
579
+ }
580
+
581
+ /**
582
+ * The steppers are out of the tab order, so this answers the pointer path on
583
+ * touch and a screen-reader cursor activating one directly. It sits inside
584
+ * the frame's own edge, so the ring never doubles the border.
585
+ */
586
+ [data-ui='currency-field'] [data-part='decrement']:focus-visible,
587
+ [data-ui='currency-field'] [data-part='increment']:focus-visible {
588
+ outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
589
+ outline-offset: calc(var(--slotted-focus-ring-offset, 2px) * -1);
590
+ }
591
+
592
+ /* ── Frame states ─────────────────────────────────────────────────────── */
593
+
594
+ [data-ui='currency-field'][data-part='root']:hover:not([data-disabled]):not([data-readonly]):not(
595
+ [data-invalid]
596
+ ) {
597
+ border-color: var(
598
+ --slotted-currency-field-hover-border-color,
599
+ var(--slotted-tone-accent-border)
600
+ );
601
+ }
602
+
603
+ [data-ui='currency-field'][data-part='root'][data-variant='subtle']:hover:not(
604
+ [data-disabled]
605
+ ):not([data-readonly]):not([data-invalid]) {
606
+ background-color: var(
607
+ --slotted-currency-field-subtle-hover-background,
608
+ var(--slotted-tone-secondary-subtle-hover)
609
+ );
610
+ }
611
+
612
+ [data-ui='currency-field'][data-part='root'][data-variant='plain']:hover:not([data-disabled]):not(
613
+ [data-readonly]
614
+ ):not([data-invalid]) {
615
+ background-color: var(
616
+ --slotted-currency-field-plain-hover-background,
617
+ var(--slotted-tone-secondary-subtle-hover)
618
+ );
619
+ /* The accent edge the other variants take would draw the cell's outline. */
620
+ border-color: transparent;
621
+ }
622
+
623
+ /**
624
+ * The ring belongs to the frame and answers the control's focus: a browser
625
+ * matches `:focus-visible` on a text input for a pointer press as well as for
626
+ * a Tab, which is right for a control whose caret is the thing that moved.
627
+ */
628
+ [data-ui='currency-field'][data-part='root']:has(> [data-part='input']:focus-visible) {
629
+ /* The resting shadow, recoloured: the lift under the control and the ring
630
+ around it are one colour, and the state decides which colour that is. */
631
+ box-shadow: var(--slotted-control-shadow-focus-geometry, 0 1px 4px -1px)
632
+ var(--slotted-focus-ring-color, Highlight);
633
+ outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
634
+ outline-offset: var(--slotted-focus-ring-offset, 2px);
635
+ }
636
+
637
+ /**
638
+ * The ring of a control whose box is not its own is drawn inside that box,
639
+ * the way the inner controls draw theirs inside the frame: two pixels past
640
+ * the control is two pixels over the next row's rule. No lift either — a
641
+ * shadow that appears only under focus is a cell that changes size when it
642
+ * is used.
643
+ */
644
+ [data-ui='currency-field'][data-part='root'][data-variant='plain']:has(
645
+ > [data-part='input']:focus-visible
646
+ ) {
647
+ box-shadow: none;
648
+ outline-offset: calc(var(--slotted-focus-ring-offset, 2px) * -1);
649
+ }
650
+
651
+ /**
652
+ * Invalid outranks hover and is written after it for that reason. The ring is
653
+ * retargeted rather than repainted: every rule that draws focus reads
654
+ * `--slotted-focus-ring-color`, so pointing that one name at the invalid
655
+ * colour on the element moves the outline, the shadow and both steppers'
656
+ * rings at once.
657
+ */
658
+ [data-ui='currency-field'][data-part='root'][data-invalid] {
659
+ --slotted-focus-ring-color: var(
660
+ --slotted-currency-field-invalid-focus-ring-color,
661
+ var(--slotted-focus-ring-color-invalid, Highlight)
662
+ );
663
+ --_invalid-edge-width: var(
664
+ --slotted-currency-field-invalid-edge-width,
665
+ var(--slotted-control-invalid-edge-width, 1px)
666
+ );
667
+ --_invalid-edge-color: var(
668
+ --slotted-currency-field-invalid-border-color,
669
+ var(--slotted-tone-danger-border)
670
+ );
671
+ --_invalid-edge: inset 0 0 0 var(--_invalid-edge-width) var(--_invalid-edge-color);
672
+
673
+ border-color: var(
674
+ --slotted-currency-field-invalid-border-color,
675
+ var(--slotted-tone-danger-border)
676
+ );
677
+ /*
678
+ * Width as well as colour (WCAG 1.4.1), the edge `text-field` draws: in
679
+ * `high-contrast` the danger border is the resting edge's black. The line
680
+ * is a shadow inside the border, so neither box nor text moves, and it
681
+ * takes the resting lift's place.
682
+ */
683
+ box-shadow: var(--_invalid-edge);
684
+ }
685
+
686
+ [data-ui='currency-field'][data-part='root'][data-invalid]:has(
687
+ > [data-part='input']:focus-visible
688
+ ) {
689
+ box-shadow:
690
+ var(--_invalid-edge),
691
+ var(--slotted-control-shadow-focus-geometry, 0 1px 4px -1px)
692
+ var(--slotted-focus-ring-color, Highlight);
693
+ }
694
+
695
+ [data-ui='currency-field'][data-part='root'][data-variant='plain'][data-invalid]:has(
696
+ > [data-part='input']:focus-visible
697
+ ) {
698
+ /* The ring lies inside the box here, over the border and an edge as wide as
699
+ it; the edge reaches past the ring. */
700
+ box-shadow: inset 0 0 0
701
+ calc(
702
+ var(--slotted-focus-ring-offset, 2px) - var(--slotted-control-border-width, 1px) +
703
+ var(--_invalid-edge-width)
704
+ )
705
+ var(--_invalid-edge-color);
706
+ }
707
+
708
+ /*
709
+ * Forced colours remove every box shadow, and the edge with them. The
710
+ * width comes back as an outline inside the border, and a focused invalid
711
+ * control draws its ring double, which a valid ring is not.
712
+ */
713
+ @media (forced-colors: active) {
714
+ [data-ui='currency-field'][data-part='root'][data-invalid] {
715
+ outline: var(--_invalid-edge-width) solid CanvasText;
716
+ outline-offset: calc(
717
+ -1 * (var(--slotted-control-border-width, 1px) + var(--_invalid-edge-width))
718
+ );
719
+ }
720
+
721
+ [data-ui='currency-field'][data-part='root'][data-invalid]:has(
722
+ > [data-part='input']:focus-visible
723
+ ) {
724
+ outline: calc(var(--slotted-focus-ring-width, 2px) + var(--_invalid-edge-width)) double
725
+ var(--slotted-focus-ring-color, Highlight);
726
+ outline-offset: var(--slotted-focus-ring-offset, 2px);
727
+ }
728
+
729
+ [data-ui='currency-field'][data-part='root'][data-variant='plain'][data-invalid]:has(
730
+ > [data-part='input']:focus-visible
731
+ ) {
732
+ outline: calc(var(--slotted-focus-ring-width, 2px) + var(--_invalid-edge-width)) double
733
+ var(--slotted-focus-ring-color, Highlight);
734
+ outline-offset: calc(
735
+ -1 * (var(--slotted-focus-ring-width, 2px) + var(--_invalid-edge-width))
736
+ );
737
+ }
738
+ }
739
+
740
+ /**
741
+ * Read-only stays focusable, selectable and legible, and must not be
742
+ * mistakable for disabled: the amount is still real and still submitted. The
743
+ * ground says "not yours to change", the edge steps down to the separator
744
+ * weight, and the ink stays at full strength.
745
+ */
746
+ [data-ui='currency-field'][data-part='root'][data-readonly] {
747
+ background-color: var(
748
+ --slotted-currency-field-readonly-background,
749
+ var(--slotted-tone-secondary-subtle-hover)
750
+ );
751
+ border-color: var(
752
+ --slotted-currency-field-readonly-border-color,
753
+ var(--slotted-border-subtle, ButtonBorder)
754
+ );
755
+ cursor: default;
756
+ }
757
+
758
+ /**
759
+ * `:disabled` on the control is matched beside `data-disabled` on the frame,
760
+ * because a control can be disabled by something that never told it so: a
761
+ * disabled `fieldset` disables every control inside it.
762
+ */
763
+ [data-ui='currency-field'][data-part='root'][data-disabled],
764
+ [data-ui='currency-field'][data-part='root']:has(> [data-part='input']:disabled) {
765
+ background-color: var(
766
+ --slotted-currency-field-disabled-background,
767
+ var(--slotted-disabled-background)
768
+ );
769
+ border-color: var(
770
+ --slotted-currency-field-disabled-border-color,
771
+ var(--slotted-disabled-border)
772
+ );
773
+ /* Nothing a person cannot reach is lifted off the page. */
774
+ box-shadow: none;
775
+ color: var(--slotted-currency-field-disabled-color, var(--slotted-disabled-foreground));
776
+ cursor: not-allowed;
777
+ }
778
+
779
+ /**
780
+ * Muted to the colour the theme measures rather than faded by opacity: an
781
+ * amount nobody can read is a field nobody can check.
782
+ */
783
+ [data-ui='currency-field'][data-part='root'][data-disabled] > [data-part='start'],
784
+ [data-ui='currency-field'][data-part='root'][data-disabled] > [data-part='end'] {
785
+ color: var(--slotted-disabled-foreground);
786
+ }
787
+ }