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

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,850 @@
1
+ @layer slotted.components {
2
+ /**
3
+ * ── The segmented datetime control ──────────────────────────────────────
4
+ *
5
+ * One frame, one `role="group"` and one value, holding all seven fields the
6
+ * two halves have between them. It is not a `date-field` beside a
7
+ * `time-field`: two nested family roots would be two groups, two values and
8
+ * two segment states, and the order of the halves and the literal between
9
+ * them come from one combined `Intl.DateTimeFormat` that neither of them can
10
+ * see. The frame carries no role of its own — the APG's spin-button example
11
+ * names the group as what tells a screen-reader user that the buttons are
12
+ * one value.
13
+ *
14
+ * There is no `input[type=datetime-local]` under any of it, for the reason
15
+ * both siblings refuse their own native input: the browser's control cannot
16
+ * be styled past its closed state, its panel is drawn by the operating
17
+ * system and answers no scheme, no density and no direction, and its field
18
+ * order is the browser's locale rather than the application's. The value
19
+ * reaches a form through a hidden input carrying `YYYY-MM-DDTHH:mm:ss`.
20
+ */
21
+ [data-ui='datetime-field'][data-part='root'] {
22
+ /**
23
+ * Type, height, inline padding, gap and glyph size come from the control
24
+ * scales — the only ones the theme restates per density — so a datetime in
25
+ * a dense table shrinks whole rather than in parts.
26
+ */
27
+ --_font-size: var(--slotted-control-font-size-md, 13px);
28
+ --_height: var(--slotted-control-height-md, 34px);
29
+ --_padding-inline: var(--slotted-control-padding-inline-md, 13px);
30
+ --_gap: var(--slotted-control-gap-md, 8px);
31
+ --_icon-floor: var(--slotted-datetime-field-icon-size-min, 14px);
32
+ --_icon-size: max(var(--_icon-floor), var(--slotted-control-icon-size-md, 16px));
33
+
34
+ /**
35
+ * The distance between the two halves, and the only place in the row that
36
+ * is wider than a segment's own padding. Seven fields in one line read as
37
+ * one long number without it; with it they read as a date and a time, which
38
+ * is what the value is.
39
+ */
40
+ --_half-gap: var(--slotted-datetime-field-half-gap, var(--slotted-space-3, 0.375rem));
41
+
42
+ /* The same inner-control box every family in the library draws: the glyph,
43
+ and one step of air on each side, bounded by the frame's own height. */
44
+ --_action-size: var(
45
+ --slotted-datetime-field-action-size,
46
+ min(
47
+ calc(var(--_icon-size) + 2 * var(--slotted-space-2, 0.25rem)),
48
+ calc(
49
+ var(--_height) - var(--slotted-control-border-width, 1px) * 2 -
50
+ var(--slotted-space-2, 0.25rem)
51
+ )
52
+ )
53
+ );
54
+
55
+ align-items: center;
56
+ background-color: var(
57
+ --slotted-datetime-field-background,
58
+ var(--slotted-control-surface, Field)
59
+ );
60
+ border: var(--slotted-control-border-width, 1px) solid
61
+ var(--slotted-datetime-field-border-color, var(--slotted-control-border-color, ButtonBorder));
62
+ border-radius: var(--slotted-datetime-field-radius, var(--slotted-control-radius, 4px));
63
+ box-shadow: var(--slotted-datetime-field-shadow, var(--slotted-control-shadow, none));
64
+ box-sizing: border-box;
65
+ color: var(--slotted-datetime-field-color, var(--slotted-text, CanvasText));
66
+ display: flex;
67
+ font-family: var(--slotted-control-font-family, system-ui, sans-serif);
68
+ font-size: var(--_font-size);
69
+ gap: var(--_gap);
70
+ inline-size: 100%;
71
+ min-block-size: var(--_height);
72
+ min-inline-size: 0;
73
+ padding-inline: var(--_padding-inline);
74
+ /* The row inside can take two lines, so the frame's height is a floor and
75
+ not a fixed measure, and the padding block keeps the two lines off the
76
+ border rather than letting them sit against it. */
77
+ padding-block: var(--slotted-datetime-field-padding-block, var(--slotted-space-1, 0.125rem));
78
+ transition:
79
+ background-color var(--slotted-control-transition-duration)
80
+ var(--slotted-control-transition-easing),
81
+ border-color var(--slotted-control-transition-duration)
82
+ var(--slotted-control-transition-easing),
83
+ box-shadow var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
84
+ }
85
+
86
+ /**
87
+ * The button inside the frame is inset by its own gutter less than the value
88
+ * is, so the *glyph* ends the same distance from the border as the digits
89
+ * begin from it — the arithmetic `text-field` records, holding at every step
90
+ * and every density.
91
+ */
92
+ [data-ui='datetime-field'][data-part='root']:has(> [data-part='clear']:last-child) {
93
+ padding-inline-end: max(
94
+ 0px,
95
+ calc(var(--_padding-inline) - (var(--_action-size) - var(--_icon-size)) / 2)
96
+ );
97
+ }
98
+
99
+ /**
100
+ * The row of halves. It takes the whole of the space the adornments leave and
101
+ * puts the value at the reading edge, so an empty field and a full one start
102
+ * in the same place.
103
+ *
104
+ * **It is one line, at every width.** Seven fields do not fit a narrow
105
+ * container in every locale; the row used to wrap between its two halves,
106
+ * and a control two lines tall broke every row it stood in — a filter row,
107
+ * a table cell, a toolbar — and read as a different control from the field
108
+ * beside it. It is now clipped at the frame's edge, the way a text input
109
+ * clips a value longer than itself, and the half the reader steps into is
110
+ * scrolled into view, because a clipped box is still a scroll container to
111
+ * the focus. Each half stays one flex item, so the value never breaks
112
+ * inside a date or inside a time.
113
+ */
114
+ [data-ui='datetime-field'] [data-part='segments'] {
115
+ align-items: center;
116
+ column-gap: var(--_half-gap);
117
+ /* It says what it is before it is pressed. A datetime is typed, so the
118
+ pointer over it is a caret — and the row rather than only the segments,
119
+ because the gap between two fields is part of the control the reader
120
+ aims at. */
121
+ cursor: text;
122
+ display: flex;
123
+ flex: 1 1 auto;
124
+ min-inline-size: 0;
125
+ overflow: hidden;
126
+ }
127
+
128
+ /**
129
+ * One half, and the reason the row can only break in one place.
130
+ *
131
+ * The sequence inside runs left to right in every document, because a date
132
+ * and a clock time are both numeric runs and that is how the bidirectional
133
+ * algorithm renders one: `Intl.DateTimeFormat` writes `٠٢:٣٠ م` for `ar-EG`
134
+ * with the hour leftmost, and hands the parts back in that order for every
135
+ * locale it knows. A flex row that took the paragraph's direction reversed
136
+ * them, and the right-to-left capture read `30:14` — a time nobody wrote, in
137
+ * a field that had done nothing but inherit. The isolation keeps the decision
138
+ * inside the half, so the frame around it still reads with the document.
139
+ */
140
+ [data-ui='datetime-field'] [data-part='half'] {
141
+ align-items: center;
142
+ direction: ltr;
143
+ display: flex;
144
+ flex: 0 0 auto;
145
+ /* Digits that do not shift under one another as a value is typed over, and
146
+ seven fields that keep their columns while the reader steps. */
147
+ font-variant-numeric: tabular-nums;
148
+ unicode-bidi: isolate;
149
+ }
150
+
151
+ /**
152
+ * The halves are still placed with the document, though: a right-to-left form
153
+ * puts its values at the right edge, and a row whose halves are now
154
+ * left-to-right would otherwise start at the left of a full-width box.
155
+ *
156
+ * `:dir()` rather than an ancestor `[dir]` selector, because the halves' own
157
+ * `direction` above does not change what `:dir()` answers — that comes from
158
+ * the document — which is the reason every other family in this library
159
+ * reaches for it too.
160
+ */
161
+ [data-ui='datetime-field'] [data-part='segments']:dir(rtl) {
162
+ justify-content: flex-end;
163
+ }
164
+
165
+ /**
166
+ * One field of the value.
167
+ *
168
+ * Its width is stated rather than measured, so the row does not move when a
169
+ * single digit becomes two: `1ch` is the advance of a digit in a tabular
170
+ * face, and `--_digits` is how many the field can hold.
171
+ */
172
+ [data-ui='datetime-field'] [data-part='segment'] {
173
+ --_digits: 2;
174
+
175
+ background-color: transparent;
176
+ border-radius: var(--slotted-datetime-field-segment-radius, var(--slotted-radius-xs, 2px));
177
+ box-sizing: content-box;
178
+ /* The caret belongs to no segment: the whole field is replaced by typing,
179
+ and a caret between two digits would offer an edit the family refuses. */
180
+ caret-color: transparent;
181
+ color: inherit;
182
+ cursor: text;
183
+ font: inherit;
184
+ /* A field never shrinks under its digits: a row narrower than its value
185
+ overflows and is clipped at the frame, the way an input clips text. A
186
+ flex item shrunk to its minimum drew `mm` as `mr`. */
187
+ flex: 0 0 auto;
188
+ min-inline-size: var(
189
+ --slotted-datetime-field-segment-min-inline-size,
190
+ calc(var(--_digits) * 1ch)
191
+ );
192
+ padding-inline: var(
193
+ --slotted-datetime-field-segment-padding-inline,
194
+ var(--slotted-space-1, 0.125rem)
195
+ );
196
+ text-align: center;
197
+ transition:
198
+ background-color var(--slotted-control-transition-duration)
199
+ var(--slotted-control-transition-easing),
200
+ color var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
201
+ /* A spin button is operated by keys and by the pointer; nothing in it is
202
+ text a reader selects, and a drag across it would look like one. */
203
+ user-select: none;
204
+ -webkit-tap-highlight-color: transparent;
205
+ }
206
+
207
+ /* The one field with four digits rather than two. Stated here rather than in
208
+ the markup, so a year is the same width in both bindings and the harness. */
209
+ [data-ui='datetime-field'] [data-part='segment'][data-field='year'] {
210
+ --_digits: 4;
211
+ }
212
+
213
+ /**
214
+ * The day period holds a word rather than digits, so it is sized by what it
215
+ * holds with a floor under it. `es-MX` writes `a.m.`, `en-US` writes `AM`
216
+ * and a locale may write more; a box cut to two characters would clip the
217
+ * first of those, and a floor clips none of them. The digit face is dropped
218
+ * here for the same reason: tabular figures inside a word letter-space it.
219
+ */
220
+ [data-ui='datetime-field'] [data-part='segment'][data-field='dayPeriod'] {
221
+ font-variant-numeric: normal;
222
+ min-inline-size: var(--slotted-datetime-field-period-min-inline-size, 2ch);
223
+ /* An abbreviation, in whatever case the locale itself writes it. */
224
+ text-transform: none;
225
+ white-space: nowrap;
226
+ }
227
+
228
+ /**
229
+ * A field with no value yet. It is muted rather than hidden, because the
230
+ * placeholder is the format — `dd/mm/aaaa hh:mm` in the reader's own words
231
+ * rather than in English — and a value being typed has to keep saying what
232
+ * is missing.
233
+ */
234
+ [data-ui='datetime-field'] [data-part='segment'][data-placeholder] {
235
+ color: var(--slotted-datetime-field-placeholder-color, var(--slotted-text-muted, GrayText));
236
+ }
237
+
238
+ /**
239
+ * The separator the locale writes. It is decoration: the group's accessible
240
+ * name already carries the whole value in words, so a screen reader reading
241
+ * "slash" between two numbers would be reading punctuation aloud.
242
+ */
243
+ [data-ui='datetime-field'] [data-part='literal'] {
244
+ color: var(--slotted-datetime-field-literal-color, var(--slotted-text-muted, GrayText));
245
+ flex: 0 0 auto;
246
+ /* Its own bidi run: a colon between two numbers takes the paragraph's
247
+ direction, and in a right-to-left document the time read back to front. */
248
+ unicode-bidi: plaintext;
249
+ user-select: none;
250
+ }
251
+
252
+ /**
253
+ * The literal the locale writes *between* the halves — a comma in `pt-BR`,
254
+ * `en-US` and `de-DE`, a bare space in `fr-FR`, `ja-JP` and `zh-CN`. It sits
255
+ * at the end of the first half rather
256
+ * than between the two, so a wrap leaves it at the end of its line, which is
257
+ * where a comma in a sentence goes. A boundary the locale writes as
258
+ * whitespace is not drawn at all, and the row's column gap separates the
259
+ * halves instead.
260
+ */
261
+ [data-ui='datetime-field'] [data-part='literal'][data-boundary] {
262
+ margin-inline-end: 0;
263
+ }
264
+
265
+ /* ── The relative expression ──────────────────────────────────────────
266
+ *
267
+ * The entry no surveyed implementation has, widened here by two unit letters:
268
+ * `+2d` and `+1w` are a datetime written as a duration from now, resolved in
269
+ * view before it is accepted. It replaces the segments rather than sitting
270
+ * beside them, because both of them writing one value would be two controls
271
+ * disagreeing about which is the datetime.
272
+ *
273
+ * It is a real `input`, so a mobile keyboard opens and the platform supplies
274
+ * every text-editing key the reader already knows.
275
+ */
276
+ [data-ui='datetime-field'] [data-part='duration'] {
277
+ appearance: none;
278
+ background-color: transparent;
279
+ border: 0;
280
+ color: inherit;
281
+ flex: 1 1 auto;
282
+ font: inherit;
283
+ /* An expression is arithmetic, so its digits line up like the value's. */
284
+ font-variant-numeric: tabular-nums;
285
+ min-inline-size: 0;
286
+ outline: none;
287
+ padding: 0;
288
+
289
+ /**
290
+ * `+2d` is a left-to-right construct wherever it is typed: the sign leads,
291
+ * the unit follows the number, and the grammar is symbols rather than
292
+ * words. Left to the document it read `2d+` in the right-to-left capture.
293
+ */
294
+ direction: ltr;
295
+ unicode-bidi: isolate;
296
+ }
297
+
298
+ /* Left-to-right content in a right-to-left field still sits at the reading
299
+ edge, which is the treatment every form gives a phone number or an IBAN. */
300
+ [data-ui='datetime-field'] [data-part='duration']:dir(rtl) {
301
+ text-align: right;
302
+ }
303
+
304
+ [data-ui='datetime-field'] [data-part='duration']::placeholder {
305
+ color: var(--slotted-datetime-field-placeholder-color, var(--slotted-text-muted, GrayText));
306
+ }
307
+
308
+ /**
309
+ * What the expression resolves to, before anything commits.
310
+ *
311
+ * It is the answer rather than the question, so it takes the accent ink and
312
+ * the expression keeps the value's. The chevron between them is the mark the
313
+ * library already has for "and then"; it is direction-sensitive, so it turns
314
+ * with the document rather than pointing out of it.
315
+ */
316
+ [data-ui='datetime-field'] [data-part='preview'] {
317
+ /**
318
+ * One line box, not a flex row. The chevron sits *in* the line rather than
319
+ * beside it, which is the only way it can be aligned to the text at all —
320
+ * a flex box centres its children on the whole line box, and the letters
321
+ * do not live in the middle of that.
322
+ */
323
+ --_mark: 1cap;
324
+
325
+ color: var(--slotted-datetime-field-preview-color, var(--slotted-tone-accent-text));
326
+ display: block;
327
+ flex: 0 0 auto;
328
+ font-variant-numeric: tabular-nums;
329
+ white-space: nowrap;
330
+
331
+ /**
332
+ * The preview is a whole datetime in words and digits — `12 set, 14:40` —
333
+ * rather than the single numeric run `time-field` shows, so it is laid out
334
+ * by its own content and not by the paragraph around it. Left to the
335
+ * document, the right-to-left capture read `set, 14:40 12`: three runs
336
+ * reordered, and a date nobody wrote. `plaintext` gives an Arabic preview
337
+ * the same courtesy from the other side.
338
+ */
339
+ unicode-bidi: plaintext;
340
+ }
341
+
342
+ /**
343
+ * The mark, on the text's own axis. `1cap` is the size a mark inside a line
344
+ * of text takes, and the `vertical-align` is the formula
345
+ * `docs/build-briefing.md` records under _Decisions already taken_: half the
346
+ * difference between the band and the mark, which lands the mark's centre on
347
+ * the middle of the band. The band is the midpoint of cap and x-height,
348
+ * chosen after all three were read at four times the type size.
349
+ *
350
+ * The mark is the chevron the binding renders as the preview's first child,
351
+ * marked `directional`: it points along the reading direction, so the `icon`
352
+ * family turns it in a right-to-left context.
353
+ */
354
+ [data-ui='datetime-field'] [data-part='preview'] > [data-ui='icon'] {
355
+ --slotted-icon-size: var(--_mark);
356
+
357
+ /**
358
+ * The gap is on both sides rather than on the end alone. The preview's own
359
+ * direction is resolved from its content — `12 set, 14:40` reads
360
+ * left-to-right, `15:20` has no strong character and takes the paragraph's
361
+ * — so which side of the mark the words land on is not known here. A single
362
+ * end margin left the chevron flush against the digits in exactly the row
363
+ * that had no strong character to state a direction with.
364
+ */
365
+ margin-inline: var(--slotted-space-1, 0.125rem);
366
+ vertical-align: calc((((1cap + 1ex) / 2) - var(--_mark)) / 2);
367
+ }
368
+
369
+ /**
370
+ * A duration that landed on a different day.
371
+ *
372
+ * `time-field` paints this in the warning tone, because a `PlainTime` has no
373
+ * day and the crossing is a fact the reader has to be warned about. Here the
374
+ * day is part of the value: `+2d` is *supposed* to move it, and a warning
375
+ * every time the reader asks for one would be crying wolf. What the preview
376
+ * does instead is say the date out loud — the family writes it into the
377
+ * preview whenever it differs from today — and this rule only lifts the date
378
+ * out of the surrounding time so the eye lands on the part that moved.
379
+ */
380
+ [data-ui='datetime-field'] [data-part='preview'][data-rolls-day] > [data-part='preview-date'] {
381
+ color: var(--slotted-datetime-field-preview-day-color, var(--slotted-tone-accent-text));
382
+ font-weight: var(--slotted-font-weight-semibold, 600);
383
+ }
384
+
385
+ /**
386
+ * While an expression is open the frame says so, because the control now
387
+ * takes text where a moment ago it took digits one field at a time. The
388
+ * border is the accent the preview is written in, so the two marks are one
389
+ * statement rather than two.
390
+ */
391
+ [data-ui='datetime-field'][data-part='root'][data-relative] {
392
+ border-color: var(
393
+ --slotted-datetime-field-relative-border-color,
394
+ var(--slotted-tone-accent-border)
395
+ );
396
+ }
397
+
398
+ /* ── Size ─────────────────────────────────────────────────────────────
399
+ *
400
+ * Three steps, matching the field the control sits in. A `size="sm"` field
401
+ * holding an `md` control is a field and a control disagreeing about one row.
402
+ */
403
+
404
+ [data-ui='datetime-field'][data-part='root'][data-size='sm'] {
405
+ --_font-size: var(--slotted-control-font-size-sm, 12px);
406
+ --_height: var(--slotted-control-height-sm, 30px);
407
+ --_padding-inline: var(--slotted-control-padding-inline-sm, 10px);
408
+ --_gap: var(--slotted-control-gap-sm, 6px);
409
+ --_icon-size: max(var(--_icon-floor), var(--slotted-control-icon-size-sm, 14px));
410
+ }
411
+
412
+ [data-ui='datetime-field'][data-part='root'][data-size='md'] {
413
+ --_font-size: var(--slotted-control-font-size-md, 13px);
414
+ --_height: var(--slotted-control-height-md, 34px);
415
+ --_padding-inline: var(--slotted-control-padding-inline-md, 13px);
416
+ --_gap: var(--slotted-control-gap-md, 8px);
417
+ --_icon-size: max(var(--_icon-floor), var(--slotted-control-icon-size-md, 16px));
418
+ }
419
+
420
+ [data-ui='datetime-field'][data-part='root'][data-size='lg'] {
421
+ --_font-size: var(--slotted-control-font-size-lg, 14px);
422
+ --_height: var(--slotted-control-height-lg, 38px);
423
+ --_padding-inline: var(--slotted-control-padding-inline-lg, 16px);
424
+ --_gap: var(--slotted-control-gap-lg, 8px);
425
+ --_icon-size: max(var(--_icon-floor), var(--slotted-control-icon-size-lg, 18px));
426
+ }
427
+
428
+ /* ── Variant ──────────────────────────────────────────────────────────
429
+ *
430
+ * The three steps `text-field` set, so a form holding a name and a datetime
431
+ * shows one frame twice rather than two frames.
432
+ */
433
+
434
+ [data-ui='datetime-field'][data-part='root'][data-variant='subtle'] {
435
+ background-color: var(
436
+ --slotted-datetime-field-subtle-background,
437
+ var(--slotted-surface-sunken, Canvas)
438
+ );
439
+ border-color: var(
440
+ --slotted-datetime-field-subtle-border-color,
441
+ var(--slotted-border-subtle, ButtonBorder)
442
+ );
443
+ }
444
+
445
+ [data-ui='datetime-field'][data-part='root'][data-variant='plain'] {
446
+ background-color: transparent;
447
+ border-color: transparent;
448
+ border-radius: var(--slotted-radius-none, 0px);
449
+ box-shadow: none;
450
+ }
451
+
452
+ /* ── Adornments ───────────────────────────────────────────────────────
453
+ *
454
+ * Named for the reading direction rather than for a side, and quieter than
455
+ * the value: an adornment annotates, and a reader looking for the datetime
456
+ * should not have to step over it.
457
+ */
458
+ [data-ui='datetime-field'] > [data-part='start'],
459
+ [data-ui='datetime-field'] > [data-part='end'] {
460
+ --slotted-icon-size: var(--_icon-size);
461
+
462
+ align-items: center;
463
+ color: var(--slotted-datetime-field-adornment-color, var(--slotted-text-muted, GrayText));
464
+ display: inline-flex;
465
+ flex: 0 0 auto;
466
+ gap: var(--slotted-space-2, 0.25rem);
467
+ line-height: 1;
468
+ unicode-bidi: plaintext;
469
+ }
470
+
471
+ [data-ui='datetime-field'] > [data-part='start'] > svg,
472
+ [data-ui='datetime-field'] > [data-part='end'] > svg {
473
+ block-size: var(--_icon-size);
474
+ inline-size: var(--_icon-size);
475
+ }
476
+
477
+ /* ── The clear button ─────────────────────────────────────────────────
478
+ *
479
+ * A real button, with the `Glyph` element the binding renders rather than a
480
+ * character, and held to the target minimum by the family rather than by the
481
+ * page. It empties both halves: half a value is not a value this family
482
+ * holds.
483
+ */
484
+ [data-ui='datetime-field'] > [data-part='clear'] {
485
+ align-items: center;
486
+ appearance: none;
487
+ background-color: transparent;
488
+ block-size: var(--_action-size);
489
+ border: 0;
490
+ border-radius: var(--slotted-datetime-field-radius, var(--slotted-control-radius, 4px));
491
+ color: var(--slotted-datetime-field-action-color, var(--slotted-text-muted, GrayText));
492
+ cursor: pointer;
493
+ display: inline-flex;
494
+ flex: 0 0 auto;
495
+ inline-size: var(--_action-size);
496
+ justify-content: center;
497
+ padding: 0;
498
+ position: relative;
499
+ touch-action: manipulation;
500
+ transition:
501
+ background-color var(--slotted-control-transition-duration)
502
+ var(--slotted-control-transition-easing),
503
+ color var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
504
+ -webkit-tap-highlight-color: transparent;
505
+ }
506
+
507
+ /**
508
+ * The hit area, recovered outside the paint. At `sm` with `dense` the whole
509
+ * control is 24 pixels tall — the WCAG 2.2 minimum — so nothing drawn inside
510
+ * it can also be 24. This grows the target back in both axes and leaves a
511
+ * larger one alone, which is the correction `docs/token-foundation.md`
512
+ * leaves to the family rather than to the theme.
513
+ */
514
+ [data-ui='datetime-field'] > [data-part='clear']::after {
515
+ content: '';
516
+ inset: min(0px, calc((100% - var(--slotted-target-min, 24px)) / 2));
517
+ position: absolute;
518
+ }
519
+
520
+ /* On a finger the target is the comfortable size in both axes and the box
521
+ is the one a mouse sees: the ground and the focus ring stay square, and
522
+ the hit area above grows to 44 pixels around them. After an end
523
+ adornment — a picker's trigger — it does not reach back across it. */
524
+ @media (pointer: coarse) {
525
+ [data-ui='datetime-field'] > [data-part='clear']::after {
526
+ inset: min(0px, calc((100% - var(--slotted-target-comfortable, 44px)) / 2));
527
+ }
528
+
529
+ [data-ui='datetime-field'] > [data-part='end'] + [data-part='clear']::after {
530
+ inset-inline-start: 0;
531
+ }
532
+ }
533
+
534
+ /* The cross is drawn at the control's own icon step. */
535
+ [data-ui='datetime-field'] > [data-part='clear'] {
536
+ --slotted-icon-size: var(--_icon-size);
537
+ }
538
+
539
+ [data-ui='datetime-field'] > [data-part='clear']:hover:not(:disabled):not([data-disabled]) {
540
+ background-color: var(
541
+ --slotted-datetime-field-action-hover-background,
542
+ var(--slotted-tone-secondary-subtle-hover)
543
+ );
544
+ }
545
+
546
+ [data-ui='datetime-field'] > [data-part='clear']:active:not(:disabled):not([data-disabled]) {
547
+ background-color: var(
548
+ --slotted-datetime-field-action-active-background,
549
+ var(--slotted-tone-secondary-subtle-active)
550
+ );
551
+ }
552
+
553
+ /**
554
+ * The button carries its own ring rather than borrowing the frame's. Tabbing
555
+ * from the last segment to the clear button has to move something visible,
556
+ * and a ring around the whole frame would sit still while focus moved inside
557
+ * it.
558
+ */
559
+ [data-ui='datetime-field'] > [data-part='clear']:focus-visible {
560
+ outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
561
+ outline-offset: calc(var(--slotted-focus-ring-offset, 2px) * -1);
562
+ }
563
+
564
+ /* ── States ───────────────────────────────────────────────────────────
565
+ *
566
+ * Written strongest last, and each weaker one guarded against every stronger
567
+ * one it can meet, which is the order `STATE_PRECEDENCE` sets.
568
+ */
569
+
570
+ [data-ui='datetime-field'][data-part='root']:hover:not([data-disabled]):not([data-readonly]):not(
571
+ [data-invalid]
572
+ ):not([data-incomplete]) {
573
+ border-color: var(
574
+ --slotted-datetime-field-hover-border-color,
575
+ var(--slotted-tone-accent-border)
576
+ );
577
+ }
578
+
579
+ [data-ui='datetime-field'][data-part='root'][data-variant='subtle']:hover:not(
580
+ [data-disabled]
581
+ ):not([data-readonly]):not([data-invalid]) {
582
+ background-color: var(
583
+ --slotted-datetime-field-subtle-hover-background,
584
+ var(--slotted-tone-secondary-subtle-hover)
585
+ );
586
+ }
587
+
588
+ [data-ui='datetime-field'][data-part='root'][data-variant='plain']:hover:not([data-disabled]):not(
589
+ [data-readonly]
590
+ ):not([data-invalid]) {
591
+ background-color: var(
592
+ --slotted-datetime-field-plain-hover-background,
593
+ var(--slotted-tone-secondary-subtle-hover)
594
+ );
595
+ border-color: transparent;
596
+ }
597
+
598
+ /**
599
+ * One half written and the other empty.
600
+ *
601
+ * It is not `invalid`: nothing the reader wrote is wrong, and a form that
602
+ * shouted at a half-typed value would shout at every value on the way in. It
603
+ * is not silence either — a date sitting above an empty time is exactly the
604
+ * state a reader walks away from, and G3 of the inventory is that walk. So
605
+ * the frame takes the warning border, one tone short of the danger the
606
+ * refused value gets, and only once the reader has left the control: while a
607
+ * segment inside still has focus they are mid-entry and there is nothing to
608
+ * report yet.
609
+ *
610
+ * It sits under `invalid` and above `hover` in the precedence order, and each
611
+ * of the two is guarded by specificity rather than by source order.
612
+ *
613
+ * **The tone is the second signal here, not the first.** The high-contrast
614
+ * scheme collapses every tone border to black — the theme's decision, and the
615
+ * one `invalid` already lives with — so a state carried by colour alone would
616
+ * have nothing left to say there. It is not carried by colour: the missing
617
+ * half is drawing its placeholder, in the reader's own words, beside a half
618
+ * that is drawing digits. That contrast is shape and it survives every scheme,
619
+ * forced colours, and a reader who cannot tell amber from grey, which is what
620
+ * `docs/component-features.md` §2.5 asks for. The border is what makes the
621
+ * whole control legible at a glance from across a long form.
622
+ *
623
+ * A broken border was drawn here instead and read as an alarm rather than a
624
+ * reminder — louder at rest than `invalid` is, for a state that is weaker
625
+ * than it.
626
+ */
627
+ [data-ui='datetime-field'][data-part='root'][data-incomplete]:not([data-disabled]):not(
628
+ [data-readonly]
629
+ ):not([data-invalid]):not(:focus-within) {
630
+ border-color: var(
631
+ --slotted-datetime-field-incomplete-border-color,
632
+ var(--slotted-tone-warning-border)
633
+ );
634
+ }
635
+
636
+ /**
637
+ * Focus is two marks and they answer two different questions.
638
+ *
639
+ * The frame's ring says the control has the reader's attention, exactly as
640
+ * every other control in the library says it. The band on one segment says
641
+ * which field a key will move, and no ring can say that: a second outline
642
+ * two pixels inside the first would be two rings nested, and at `dense`
643
+ * there is not room for two.
644
+ *
645
+ * Both are `:focus` and not `:focus-visible`, which is the rule `date-field`
646
+ * paid for: a browser matches `:focus-visible` on a text input for a pointer
647
+ * press as well as for a Tab, and refuses it for a `div` a pointer focused.
648
+ * Every segment here is a `div`, so a clicked field would look exactly as it
649
+ * had before the press. The expression is an `input` and takes the frame's
650
+ * ring the same way from the other side.
651
+ */
652
+ [data-ui='datetime-field'][data-part='root']:has([data-part='segment']:focus),
653
+ [data-ui='datetime-field'][data-part='root']:has([data-part='duration']:focus) {
654
+ box-shadow: var(--slotted-control-shadow-focus-geometry, 0 1px 4px -1px)
655
+ var(--slotted-focus-ring-color, Highlight);
656
+ outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
657
+ outline-offset: var(--slotted-focus-ring-offset, 2px);
658
+ }
659
+
660
+ /**
661
+ * The ring of a control whose box is not its own is drawn inside that box,
662
+ * the way the inner controls draw theirs inside the frame: two pixels past
663
+ * the control is two pixels over the next row's rule. No lift either — a
664
+ * shadow that appears only under focus is a cell that changes size when it
665
+ * is used.
666
+ */
667
+ [data-ui='datetime-field'][data-part='root'][data-variant='plain']:has(
668
+ [data-part='segment']:focus
669
+ ),
670
+ [data-ui='datetime-field'][data-part='root'][data-variant='plain']:has(
671
+ [data-part='duration']:focus
672
+ ) {
673
+ box-shadow: none;
674
+ outline-offset: calc(var(--slotted-focus-ring-offset, 2px) * -1);
675
+ }
676
+
677
+ [data-ui='datetime-field'] [data-part='segment']:focus {
678
+ background-color: var(
679
+ --slotted-datetime-field-segment-focus-background,
680
+ var(--slotted-tone-accent-solid)
681
+ );
682
+ color: var(--slotted-datetime-field-segment-focus-color, var(--slotted-tone-accent-on-solid));
683
+ /* The frame already draws the ring, so the segment does not draw a second. */
684
+ outline: none;
685
+ }
686
+
687
+ /**
688
+ * Invalid outranks hover and incomplete and is written after both: a control
689
+ * reporting an error should not stop looking wrong because the pointer is
690
+ * over it, and a value a bound refuses is wrong whether or not it is whole.
691
+ * The ring is retargeted rather than repainted, so the frame's outline, the
692
+ * shadow under it and the ring on the clear button all move at once.
693
+ */
694
+ [data-ui='datetime-field'][data-part='root'][data-invalid] {
695
+ --slotted-focus-ring-color: var(
696
+ --slotted-datetime-field-invalid-focus-ring-color,
697
+ var(--slotted-focus-ring-color-invalid, Highlight)
698
+ );
699
+ --_invalid-edge-width: var(
700
+ --slotted-datetime-field-invalid-edge-width,
701
+ var(--slotted-control-invalid-edge-width, 1px)
702
+ );
703
+ --_invalid-edge-color: var(
704
+ --slotted-datetime-field-invalid-border-color,
705
+ var(--slotted-tone-danger-border)
706
+ );
707
+ --_invalid-edge: inset 0 0 0 var(--_invalid-edge-width) var(--_invalid-edge-color);
708
+
709
+ border-color: var(
710
+ --slotted-datetime-field-invalid-border-color,
711
+ var(--slotted-tone-danger-border)
712
+ );
713
+ /*
714
+ * Width as well as colour (WCAG 1.4.1), the edge `text-field` draws: in
715
+ * `high-contrast` the danger border is the resting edge's black. The line
716
+ * is a shadow inside the border, so neither box nor text moves, and it
717
+ * takes the resting lift's place.
718
+ */
719
+ box-shadow: var(--_invalid-edge);
720
+ }
721
+
722
+ [data-ui='datetime-field'][data-part='root'][data-invalid]:has([data-part='segment']:focus),
723
+ [data-ui='datetime-field'][data-part='root'][data-invalid]:has([data-part='duration']:focus) {
724
+ box-shadow:
725
+ var(--_invalid-edge),
726
+ var(--slotted-control-shadow-focus-geometry, 0 1px 4px -1px)
727
+ var(--slotted-focus-ring-color, Highlight);
728
+ }
729
+
730
+ [data-ui='datetime-field'][data-part='root'][data-variant='plain'][data-invalid]:has(
731
+ [data-part='segment']:focus
732
+ ),
733
+ [data-ui='datetime-field'][data-part='root'][data-variant='plain'][data-invalid]:has(
734
+ [data-part='duration']:focus
735
+ ) {
736
+ /* The ring lies inside the box here, over the border and an edge as wide as
737
+ it; the edge reaches past the ring. */
738
+ box-shadow: inset 0 0 0
739
+ calc(
740
+ var(--slotted-focus-ring-offset, 2px) - var(--slotted-control-border-width, 1px) +
741
+ var(--_invalid-edge-width)
742
+ )
743
+ var(--_invalid-edge-color);
744
+ }
745
+
746
+ /*
747
+ * Forced colours remove every box shadow, and the edge with them. The
748
+ * width comes back as an outline inside the border, and a focused invalid
749
+ * control draws its ring double, which a valid ring is not.
750
+ */
751
+ @media (forced-colors: active) {
752
+ [data-ui='datetime-field'][data-part='root'][data-invalid] {
753
+ outline: var(--_invalid-edge-width) solid CanvasText;
754
+ outline-offset: calc(
755
+ -1 * (var(--slotted-control-border-width, 1px) + var(--_invalid-edge-width))
756
+ );
757
+ }
758
+
759
+ [data-ui='datetime-field'][data-part='root'][data-invalid]:has([data-part='segment']:focus),
760
+ [data-ui='datetime-field'][data-part='root'][data-invalid]:has([data-part='duration']:focus) {
761
+ outline: calc(var(--slotted-focus-ring-width, 2px) + var(--_invalid-edge-width)) double
762
+ var(--slotted-focus-ring-color, Highlight);
763
+ outline-offset: var(--slotted-focus-ring-offset, 2px);
764
+ }
765
+
766
+ [data-ui='datetime-field'][data-part='root'][data-variant='plain'][data-invalid]:has(
767
+ [data-part='segment']:focus
768
+ ),
769
+ [data-ui='datetime-field'][data-part='root'][data-variant='plain'][data-invalid]:has(
770
+ [data-part='duration']:focus
771
+ ) {
772
+ outline: calc(var(--slotted-focus-ring-width, 2px) + var(--_invalid-edge-width)) double
773
+ var(--slotted-focus-ring-color, Highlight);
774
+ outline-offset: calc(
775
+ -1 * (var(--slotted-focus-ring-width, 2px) + var(--_invalid-edge-width))
776
+ );
777
+ }
778
+ }
779
+
780
+ /**
781
+ * Read-only stays focusable, readable and copyable, and must not be
782
+ * mistakable for disabled: the value is still real and still submitted. The
783
+ * ground says "not yours to change" and the ink says nothing at all.
784
+ */
785
+ [data-ui='datetime-field'][data-part='root'][data-readonly],
786
+ [data-ui='datetime-field'][data-part='root'][data-readonly] [data-part='segments'],
787
+ [data-ui='datetime-field'][data-part='root'][data-readonly] [data-part='segment'] {
788
+ cursor: default;
789
+ }
790
+
791
+ [data-ui='datetime-field'][data-part='root'][data-readonly] {
792
+ background-color: var(
793
+ --slotted-datetime-field-readonly-background,
794
+ var(--slotted-tone-secondary-subtle-hover)
795
+ );
796
+ }
797
+
798
+ /**
799
+ * Muted to the colour the theme measures rather than faded by opacity: a
800
+ * datetime nobody can read is a field nobody can check, and a disabled form
801
+ * still has to say what it holds.
802
+ */
803
+ [data-ui='datetime-field'][data-part='root'][data-disabled] {
804
+ background-color: var(
805
+ --slotted-datetime-field-disabled-background,
806
+ var(--slotted-disabled-background)
807
+ );
808
+ border-color: var(
809
+ --slotted-datetime-field-disabled-border-color,
810
+ var(--slotted-disabled-border)
811
+ );
812
+ /* Nothing a person cannot reach is lifted off the page. */
813
+ box-shadow: none;
814
+ color: var(--slotted-datetime-field-disabled-color, var(--slotted-disabled-foreground));
815
+ cursor: not-allowed;
816
+ }
817
+
818
+ [data-ui='datetime-field'][data-part='root'][data-disabled] [data-part='segments'],
819
+ [data-ui='datetime-field'][data-part='root'][data-disabled] [data-part='segment'] {
820
+ cursor: not-allowed;
821
+ }
822
+
823
+ [data-ui='datetime-field'][data-part='root'][data-disabled] > [data-part='start'],
824
+ [data-ui='datetime-field'][data-part='root'][data-disabled] > [data-part='end'],
825
+ [data-ui='datetime-field'][data-part='root'][data-disabled] [data-part='literal'],
826
+ [data-ui='datetime-field'][data-part='root'][data-disabled]
827
+ [data-part='segment'][data-placeholder] {
828
+ color: var(--slotted-disabled-foreground);
829
+ }
830
+
831
+ @media (forced-colors: active) {
832
+ /* A transparent border has no system colour to be remapped to, so the
833
+ `plain` control would lose its only edge where an edge matters most. */
834
+ [data-ui='datetime-field'][data-part='root'][data-variant='plain'] {
835
+ border-color: ButtonBorder;
836
+ }
837
+
838
+ /* Warning, accent and danger all map to the same system ink, so the two
839
+ modes that are told apart by tone need a second signal. The expression
840
+ borrows the highlight; the incomplete frame borrows the thicker border
841
+ the system reserves for something asking to be finished. */
842
+ [data-ui='datetime-field'][data-part='root'][data-relative] {
843
+ border-color: Highlight;
844
+ }
845
+
846
+ /* The broken line above is already shape rather than tone, so forced
847
+ colours need nothing added here; the rule is stated in the scheme block
848
+ and inherited. */
849
+ }
850
+ }