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