@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,900 @@
|
|
|
1
|
+
@layer slotted.components {
|
|
2
|
+
/**
|
|
3
|
+
* ── The single-line text control ───────────────────────────────────────
|
|
4
|
+
*
|
|
5
|
+
* A frame around an `input`, rather than the `input` itself. The frame is
|
|
6
|
+
* what holds an adornment, a clear button and a reveal button inside one
|
|
7
|
+
* border, and no void element can hold a child. WAI-ARIA settles the rest:
|
|
8
|
+
* a `textbox` may hold only non-interactive, presentational children, so the
|
|
9
|
+
* two buttons are siblings of the control inside a frame with no role.
|
|
10
|
+
*
|
|
11
|
+
* Every rule keys on `data-ui` and `data-part`. The field around the control
|
|
12
|
+
* mirrors its state onto the frame rather than styling through, so a control
|
|
13
|
+
* used outside a field looks the same as one inside it and the field's DOM
|
|
14
|
+
* never becomes part of this stylesheet's contract.
|
|
15
|
+
*/
|
|
16
|
+
[data-ui='text-field'][data-part='root'] {
|
|
17
|
+
/**
|
|
18
|
+
* Type, height, inline padding, gap and glyph size all come from the
|
|
19
|
+
* control scales — the only ones the theme restates per density — so a
|
|
20
|
+
* field in a dense table shrinks whole rather than in parts.
|
|
21
|
+
*/
|
|
22
|
+
--_font-size: var(--slotted-control-font-size-md, 13px);
|
|
23
|
+
--_height: var(--slotted-control-height-md, 34px);
|
|
24
|
+
--_padding-inline: var(--slotted-control-padding-inline-md, 13px);
|
|
25
|
+
--_gap: var(--slotted-control-gap-md, 8px);
|
|
26
|
+
/**
|
|
27
|
+
* The glyph scale falls to 13px and 12px at the dense step, and a clear
|
|
28
|
+
* mark that small is a speck beside the text (SL3). The floor is the
|
|
29
|
+
* family's own token so a theme can retune it; 14px is the size the
|
|
30
|
+
* comfortable `sm` step draws, and the smallest a two-stroke mark reads at.
|
|
31
|
+
*/
|
|
32
|
+
--_icon-floor: var(--slotted-text-field-icon-size-min, 14px);
|
|
33
|
+
--_icon-size: max(var(--_icon-floor), var(--slotted-control-icon-size-md, 16px));
|
|
34
|
+
--_inner-size: var(--slotted-control-inner-size-md, 24px);
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The drawn button, and only the drawn button. It is sized by its glyph —
|
|
38
|
+
* a step of air on each side, the same box every inner control in the
|
|
39
|
+
* library draws (R2) — and capped at the inner size of a control at this
|
|
40
|
+
* step, `--slotted-control-inner-size-*`, the height a key or a chip
|
|
41
|
+
* already takes inside the same frame. Before the cap it was bounded by
|
|
42
|
+
* the frame's inner box less a step of air, which let it measure 22px in a
|
|
43
|
+
* 26px field at the compact step, where the inside of that field is 20px.
|
|
44
|
+
*
|
|
45
|
+
* The WCAG 2.2 minimum is not this measurement. At `sm` and `dense` the
|
|
46
|
+
* whole control is 24 pixels tall, so a button inset inside it cannot also
|
|
47
|
+
* be 24; the target is recovered outside the paint, on the pseudo-element
|
|
48
|
+
* further down.
|
|
49
|
+
*/
|
|
50
|
+
--_action-size: var(
|
|
51
|
+
--slotted-text-field-action-size,
|
|
52
|
+
min(calc(var(--_icon-size) + 2 * var(--slotted-space-2, 0.25rem)), var(--_inner-size))
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The air between a button's ground and the frame's inner edge, the same
|
|
57
|
+
* in both axes, and the corner that makes the two shapes concentric. The
|
|
58
|
+
* corner never falls below the smallest step of the radius scale: a
|
|
59
|
+
* square ground inside a rounded frame reads as a hole cut in it.
|
|
60
|
+
*/
|
|
61
|
+
--_inset: max(
|
|
62
|
+
0px,
|
|
63
|
+
calc(
|
|
64
|
+
(var(--_height) - 2 * var(--slotted-control-border-width, 1px) - var(--_action-size)) / 2
|
|
65
|
+
)
|
|
66
|
+
);
|
|
67
|
+
--_action-radius: max(
|
|
68
|
+
var(--slotted-radius-xs, 2px),
|
|
69
|
+
calc(var(--slotted-text-field-radius, var(--slotted-control-radius, 4px)) - var(--_inset))
|
|
70
|
+
);
|
|
71
|
+
/**
|
|
72
|
+
* What a button inside the frame animates, written once for the three
|
|
73
|
+
* built-in buttons and the consumer's `action`: its ground for hover and
|
|
74
|
+
* press, its ink for a copy that finished.
|
|
75
|
+
*/
|
|
76
|
+
--_action-transition:
|
|
77
|
+
background-color var(--slotted-control-transition-duration)
|
|
78
|
+
var(--slotted-control-transition-easing),
|
|
79
|
+
color var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
|
|
80
|
+
|
|
81
|
+
align-items: center;
|
|
82
|
+
background-color: var(--slotted-text-field-background, var(--slotted-control-surface, Field));
|
|
83
|
+
/**
|
|
84
|
+
* The control step of the border scale rather than the strong one. A
|
|
85
|
+
* separator between two surfaces and the edge of a control are the same
|
|
86
|
+
* ratio and not the same weight, and reading `--slotted-border-strong`
|
|
87
|
+
* here drew a frame that was the darkest line on the form.
|
|
88
|
+
*/
|
|
89
|
+
border: var(--slotted-control-border-width, 1px) solid
|
|
90
|
+
var(--slotted-text-field-border-color, var(--slotted-control-border-color, ButtonBorder));
|
|
91
|
+
border-radius: var(--slotted-text-field-radius, var(--slotted-control-radius, 4px));
|
|
92
|
+
/**
|
|
93
|
+
* The shadow every framed control carries. It is barely visible on purpose:
|
|
94
|
+
* it separates the control from the ground it sits on without adding a
|
|
95
|
+
* second edge, which is what lets the border soften.
|
|
96
|
+
*/
|
|
97
|
+
box-shadow: var(--slotted-text-field-shadow, var(--slotted-control-shadow, none));
|
|
98
|
+
box-sizing: border-box;
|
|
99
|
+
color: var(--slotted-text-field-color, var(--slotted-text, CanvasText));
|
|
100
|
+
display: flex;
|
|
101
|
+
font-family: var(--slotted-control-font-family, system-ui, sans-serif);
|
|
102
|
+
font-size: var(--_font-size);
|
|
103
|
+
gap: var(--_gap);
|
|
104
|
+
inline-size: 100%;
|
|
105
|
+
/* A control in a grid track narrower than its own intrinsic width would
|
|
106
|
+
otherwise refuse to shrink and take the drawer's edge with it. */
|
|
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
|
+
* A button at either end of the frame is inset concentrically: the air
|
|
122
|
+
* beside it equals the air above and below it, and its corner is the
|
|
123
|
+
* frame's corner less that air, so the ground a hover paints is a smaller
|
|
124
|
+
* copy of the frame rather than a box that drifted toward the value. The
|
|
125
|
+
* earlier rule aligned the glyph to the text's margin and left the hover
|
|
126
|
+
* ground nine pixels from the end and four from the top at `md`; ReUI's
|
|
127
|
+
* `c-input-group-11` measures four and four with a radius of seven inside
|
|
128
|
+
* ten (docs/history/designs/2026-09-30-text-field-reui-dossier.md § 3).
|
|
129
|
+
*
|
|
130
|
+
* The cost is written down: at `md` the glyph now ends four pixels nearer
|
|
131
|
+
* the border than the text begins from it.
|
|
132
|
+
*/
|
|
133
|
+
[data-ui='text-field'][data-part='root']:has(
|
|
134
|
+
> :is([data-part='clear'], [data-part='copy'], [data-part='reveal']):not([hidden]):last-child,
|
|
135
|
+
> [data-part='end']:last-child > [data-part='action']:last-child
|
|
136
|
+
) {
|
|
137
|
+
padding-inline-end: var(--_inset);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
[data-ui='text-field'][data-part='root']:has(
|
|
141
|
+
> [data-part='start']:first-child > [data-part='action']:first-child
|
|
142
|
+
) {
|
|
143
|
+
padding-inline-start: var(--_inset);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The control. It gives up its own frame entirely — border, ground, ring and
|
|
148
|
+
* padding all belong to the box around it — and keeps everything that makes
|
|
149
|
+
* it a text control: the value, the caret, the selection and every native
|
|
150
|
+
* attribute a consumer wrote.
|
|
151
|
+
*/
|
|
152
|
+
[data-ui='text-field'] > [data-part='input'] {
|
|
153
|
+
background-color: transparent;
|
|
154
|
+
/* The frame's border box, not its content box: on a coarse pointer the frame
|
|
155
|
+
is the touch target's height and the control it holds is too. Stretched
|
|
156
|
+
rather than a percentage of the frame, which is indefinite wherever the
|
|
157
|
+
frame is not stretched itself — a search field in a bar measured 17
|
|
158
|
+
pixels. The control paints no ground, so the frame's edge still shows. */
|
|
159
|
+
align-self: stretch;
|
|
160
|
+
margin-block: calc(var(--slotted-control-border-width, 1px) * -1);
|
|
161
|
+
border: 0;
|
|
162
|
+
color: inherit;
|
|
163
|
+
flex: 1 1 auto;
|
|
164
|
+
font: inherit;
|
|
165
|
+
/* The frame draws the ring for it, so two rings never nest. */
|
|
166
|
+
outline: none;
|
|
167
|
+
padding: 0;
|
|
168
|
+
/* The intrinsic width of an `input` is about twenty characters, and a flex
|
|
169
|
+
item refuses to shrink below it without this. */
|
|
170
|
+
min-inline-size: 0;
|
|
171
|
+
/* The user agent pins a text control to `start`. Read from the box around
|
|
172
|
+
it instead, so a `plain` control in a numeric cell sits where the
|
|
173
|
+
cell's numbers do. */
|
|
174
|
+
text-align: inherit;
|
|
175
|
+
text-overflow: ellipsis;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
[data-ui='text-field'] > [data-part='input']::placeholder {
|
|
179
|
+
color: var(--slotted-text-field-placeholder-color, var(--slotted-text-muted, GrayText));
|
|
180
|
+
/* A placeholder longer than the control is clipped at a word rather than
|
|
181
|
+
mid-glyph, which is the difference between a hint and a smear. */
|
|
182
|
+
text-overflow: ellipsis;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* The family draws its own clear button, so the one WebKit adds to a
|
|
187
|
+
* `type="search"` control would be the second. The native decorations are
|
|
188
|
+
* withdrawn rather than left to overlap.
|
|
189
|
+
*/
|
|
190
|
+
[data-ui='text-field'] > [data-part='input']::-webkit-search-cancel-button,
|
|
191
|
+
[data-ui='text-field'] > [data-part='input']::-webkit-search-decoration {
|
|
192
|
+
appearance: none;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Autofill. Chrome paints its own ground over the control's, and the classic
|
|
197
|
+
* repair — a hundred-pixel inset shadow — is the only way to reach it. The
|
|
198
|
+
* signal is kept rather than erased: an autofilled value is a fact worth
|
|
199
|
+
* seeing, so it is repainted in the accent surface the theme owns instead of
|
|
200
|
+
* in the browser's yellow.
|
|
201
|
+
*/
|
|
202
|
+
[data-ui='text-field'] > [data-part='input']:autofill {
|
|
203
|
+
box-shadow: inset 0 0 0 100px
|
|
204
|
+
var(--slotted-text-field-autofill-background, var(--slotted-tone-accent-surface));
|
|
205
|
+
-webkit-text-fill-color: var(--slotted-text-field-color, var(--slotted-text, CanvasText));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* ── Size ─────────────────────────────────────────────────────────────
|
|
209
|
+
*
|
|
210
|
+
* Three steps, matching the field the control sits in. The control reads the
|
|
211
|
+
* field's step rather than its own default when there is a field, because a
|
|
212
|
+
* `size="sm"` field holding an `md` control is a field and a control
|
|
213
|
+
* disagreeing about one row.
|
|
214
|
+
*/
|
|
215
|
+
|
|
216
|
+
[data-ui='text-field'][data-part='root'][data-size='sm'] {
|
|
217
|
+
--_font-size: var(--slotted-control-font-size-sm, 12px);
|
|
218
|
+
--_height: var(--slotted-control-height-sm, 30px);
|
|
219
|
+
--_padding-inline: var(--slotted-control-padding-inline-sm, 10px);
|
|
220
|
+
--_gap: var(--slotted-control-gap-sm, 6px);
|
|
221
|
+
--_icon-size: max(var(--_icon-floor), var(--slotted-control-icon-size-sm, 14px));
|
|
222
|
+
--_inner-size: var(--slotted-control-inner-size-sm, 22px);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
[data-ui='text-field'][data-part='root'][data-size='md'] {
|
|
226
|
+
--_font-size: var(--slotted-control-font-size-md, 13px);
|
|
227
|
+
--_height: var(--slotted-control-height-md, 34px);
|
|
228
|
+
--_padding-inline: var(--slotted-control-padding-inline-md, 13px);
|
|
229
|
+
--_gap: var(--slotted-control-gap-md, 8px);
|
|
230
|
+
--_icon-size: max(var(--_icon-floor), var(--slotted-control-icon-size-md, 16px));
|
|
231
|
+
--_inner-size: var(--slotted-control-inner-size-md, 24px);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
[data-ui='text-field'][data-part='root'][data-size='lg'] {
|
|
235
|
+
--_font-size: var(--slotted-control-font-size-lg, 14px);
|
|
236
|
+
--_height: var(--slotted-control-height-lg, 38px);
|
|
237
|
+
--_padding-inline: var(--slotted-control-padding-inline-lg, 16px);
|
|
238
|
+
--_gap: var(--slotted-control-gap-lg, 8px);
|
|
239
|
+
--_icon-size: max(var(--_icon-floor), var(--slotted-control-icon-size-lg, 18px));
|
|
240
|
+
--_inner-size: var(--slotted-control-inner-size-lg, 28px);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Joined into a `button-group` beside a button — "https://" and a check, a
|
|
245
|
+
* query and its submit — the control is the part of the bar that gives way:
|
|
246
|
+
* it takes the width the buttons leave rather than the whole row, so the
|
|
247
|
+
* button's label never wraps to make room for it.
|
|
248
|
+
*/
|
|
249
|
+
[data-ui='button-group'] > [data-ui='text-field'][data-part='root'] {
|
|
250
|
+
flex: 1 1 0;
|
|
251
|
+
inline-size: auto;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/* ── Variant: how much frame the control shows ────────────────────────
|
|
255
|
+
*
|
|
256
|
+
* `outline` carries `--slotted-control-surface`: the field is a plane of its
|
|
257
|
+
* own, a step off the ground in each scheme — white on the application's
|
|
258
|
+
* canvas in `light`, a step above the surface in `dark`. Transparent was
|
|
259
|
+
* tried first, on the rule that a form inside a card and the same form on
|
|
260
|
+
* the page both look right without a second token; what it produced was a
|
|
261
|
+
* field with no plane at all, found only by its edge.
|
|
262
|
+
* `subtle` sinks into the page. `plain` shows nothing at rest and is what a
|
|
263
|
+
* dense table needs: a cell that is only a control while it is being used,
|
|
264
|
+
* and that keeps exactly the same box so the row does not reflow when the
|
|
265
|
+
* pointer crosses it.
|
|
266
|
+
*/
|
|
267
|
+
|
|
268
|
+
[data-ui='text-field'][data-part='root'][data-variant='subtle'] {
|
|
269
|
+
background-color: var(
|
|
270
|
+
--slotted-text-field-subtle-background,
|
|
271
|
+
var(--slotted-surface-sunken, Canvas)
|
|
272
|
+
);
|
|
273
|
+
border-color: var(
|
|
274
|
+
--slotted-text-field-subtle-border-color,
|
|
275
|
+
var(--slotted-border-subtle, ButtonBorder)
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/* No edge, no lift, no ground and no corner of its own: the cell it sits in
|
|
280
|
+
is the box, the row is the ground, a cell has no corner, and the resting
|
|
281
|
+
shadow would draw the cell where there is none. */
|
|
282
|
+
[data-ui='text-field'][data-part='root'][data-variant='plain'] {
|
|
283
|
+
background-color: transparent;
|
|
284
|
+
border-color: transparent;
|
|
285
|
+
border-radius: var(--slotted-radius-none, 0px);
|
|
286
|
+
box-shadow: none;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* The one rule the theme cannot write for the family. A transparent border
|
|
291
|
+
* has no system colour to be remapped to, so the `plain` control would lose
|
|
292
|
+
* its only edge exactly where an edge matters most.
|
|
293
|
+
*/
|
|
294
|
+
@media (forced-colors: active) {
|
|
295
|
+
[data-ui='text-field'][data-part='root'][data-variant='plain'] {
|
|
296
|
+
border-color: ButtonBorder;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/* ── Monospace ────────────────────────────────────────────────────────
|
|
301
|
+
*
|
|
302
|
+
* A back-office form full of identifiers, hashes and keys, which is the
|
|
303
|
+
* audience `docs/product/prd.md` names first. Primer and Polaris both ship it.
|
|
304
|
+
*/
|
|
305
|
+
[data-ui='text-field'][data-part='root'][data-mono] {
|
|
306
|
+
font-family: var(--slotted-font-family-mono, monospace);
|
|
307
|
+
/* Digits that do not shift as a value is typed over. */
|
|
308
|
+
font-variant-numeric: tabular-nums;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/* ── Adornments ───────────────────────────────────────────────────────
|
|
312
|
+
*
|
|
313
|
+
* Named for the reading direction rather than for a side, so the same markup
|
|
314
|
+
* puts the currency before the amount in both directions. They are quieter
|
|
315
|
+
* than the value: an adornment annotates, and a reader looking for what they
|
|
316
|
+
* typed should not have to step over it.
|
|
317
|
+
*/
|
|
318
|
+
[data-ui='text-field'] > [data-part='start'],
|
|
319
|
+
[data-ui='text-field'] > [data-part='end'] {
|
|
320
|
+
align-items: center;
|
|
321
|
+
color: var(--slotted-text-field-adornment-color, var(--slotted-text-muted, GrayText));
|
|
322
|
+
display: inline-flex;
|
|
323
|
+
/* Never squeezed: the value gives up width first, because the value is the
|
|
324
|
+
part that can scroll. */
|
|
325
|
+
flex: 0 0 auto;
|
|
326
|
+
gap: var(--slotted-space-2, 0.25rem);
|
|
327
|
+
line-height: 1;
|
|
328
|
+
/**
|
|
329
|
+
* Its own bidi run, ordered by its own content. `R$` is a Latin letter and
|
|
330
|
+
* a currency symbol, and in a right-to-left document the symbol resolved to
|
|
331
|
+
* the paragraph's direction and printed `$R` — first as part of the value's
|
|
332
|
+
* run, then, with `isolate`, as an isolated run that was still ordered
|
|
333
|
+
* right-to-left. `plaintext` takes the direction from the adornment's own
|
|
334
|
+
* first strong character, so `R$` reads as itself and an Arabic unit reads
|
|
335
|
+
* as itself, in either document direction.
|
|
336
|
+
*/
|
|
337
|
+
unicode-bidi: plaintext;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* An `icon` placed in either adornment is sized by the control rather than
|
|
342
|
+
* by the page. The icon family reads `--slotted-icon-size` from whatever
|
|
343
|
+
* contains it, which is how a container sizes every glyph inside it without
|
|
344
|
+
* knowing anything else about that family — so a `sm` control in a dense
|
|
345
|
+
* table draws a 12-pixel glyph and nothing has to be written per instance.
|
|
346
|
+
* A bare `svg` a consumer passed instead is held to the same box.
|
|
347
|
+
*/
|
|
348
|
+
[data-ui='text-field'] > [data-part='start'],
|
|
349
|
+
[data-ui='text-field'] > [data-part='end'] {
|
|
350
|
+
--slotted-icon-size: var(--_icon-size);
|
|
351
|
+
/* And a key or a chip placed there is the height the inside of this
|
|
352
|
+
control has, from the foundation's scale for it. */
|
|
353
|
+
--slotted-control-inner-size: var(--_inner-size);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
[data-ui='text-field'] > [data-part='start'] > svg,
|
|
357
|
+
[data-ui='text-field'] > [data-part='end'] > svg {
|
|
358
|
+
block-size: var(--_icon-size);
|
|
359
|
+
inline-size: var(--_icon-size);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Optical alignment between the two kinds of adornment. A glyph fills the
|
|
364
|
+
* whole `--_icon-size` box, but its artwork is drawn inside a 24-unit grid
|
|
365
|
+
* that leaves two to three units of margin on each side; a character fills
|
|
366
|
+
* only its own advance width. Placed at the same padding edge the two boxes
|
|
367
|
+
* start together and the two *marks* do not, so `@` and `R$` hung two pixels
|
|
368
|
+
* outside the magnifier stacked above them.
|
|
369
|
+
*
|
|
370
|
+
* An adornment that draws no glyph therefore takes the margin the glyph box
|
|
371
|
+
* already has — an eighth of that box, the middle of the range the surveyed
|
|
372
|
+
* icon sets leave — on both sides. Both sides, because the same inset then
|
|
373
|
+
* keeps the gap to the value equal as well, and because a symmetric padding
|
|
374
|
+
* cannot disagree with `unicode-bidi: plaintext` about which side is which.
|
|
375
|
+
*/
|
|
376
|
+
[data-ui='text-field']
|
|
377
|
+
> [data-part='start']:not(:has(svg, img, [data-ui='icon'], [data-part='action'])),
|
|
378
|
+
[data-ui='text-field']
|
|
379
|
+
> [data-part='end']:not(:has(svg, img, [data-ui='icon'], [data-part='action'])) {
|
|
380
|
+
padding-inline: calc(var(--_icon-size) / 8);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/* ── The buttons inside the frame ─────────────────────────────────────
|
|
384
|
+
*
|
|
385
|
+
* Four the family draws — clear, copy, reveal, and nothing else — and the
|
|
386
|
+
* consumer's own `action`, placed in either adornment: a search submit, a
|
|
387
|
+
* menu trigger naming a country, a tooltip on an information mark. All five
|
|
388
|
+
* are real buttons, share one box, one ink, one hover and one press, and
|
|
389
|
+
* are held to the target minimum by the family rather than by the page.
|
|
390
|
+
* Their glyphs are the `Glyph` element the binding renders: a character is
|
|
391
|
+
* never an icon.
|
|
392
|
+
*/
|
|
393
|
+
[data-ui='text-field'] > :is([data-part='clear'], [data-part='copy'], [data-part='reveal']),
|
|
394
|
+
[data-ui='text-field'] > :is([data-part='start'], [data-part='end']) > [data-part='action'] {
|
|
395
|
+
--slotted-icon-size: var(--_icon-size);
|
|
396
|
+
|
|
397
|
+
align-items: center;
|
|
398
|
+
appearance: none;
|
|
399
|
+
background-color: transparent;
|
|
400
|
+
block-size: var(--_action-size);
|
|
401
|
+
border: 0;
|
|
402
|
+
border-radius: var(--_action-radius);
|
|
403
|
+
box-sizing: border-box;
|
|
404
|
+
color: var(--slotted-text-field-action-color, var(--slotted-text-muted, GrayText));
|
|
405
|
+
cursor: pointer;
|
|
406
|
+
display: inline-flex;
|
|
407
|
+
flex: 0 0 auto;
|
|
408
|
+
font: inherit;
|
|
409
|
+
gap: var(--slotted-space-2, 0.25rem);
|
|
410
|
+
justify-content: center;
|
|
411
|
+
line-height: 1;
|
|
412
|
+
min-inline-size: var(--_action-size);
|
|
413
|
+
/* The user agent pads a button, and inside a box sized to its glyph that
|
|
414
|
+
padding squeezed the glyph to twelve pixels. */
|
|
415
|
+
padding: 0;
|
|
416
|
+
/* The containing block of the hit area below. */
|
|
417
|
+
position: relative;
|
|
418
|
+
/* No tap delay waiting for a second tap the button cannot use. */
|
|
419
|
+
touch-action: manipulation;
|
|
420
|
+
transition: var(--_action-transition);
|
|
421
|
+
/* The adornments' rule, for the same reason: `+55` is its own run and
|
|
422
|
+
reads as itself in a right-to-left document. */
|
|
423
|
+
unicode-bidi: plaintext;
|
|
424
|
+
white-space: nowrap;
|
|
425
|
+
-webkit-tap-highlight-color: transparent;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* The consumer's button named by its own text — "Search", a country code
|
|
430
|
+
* over a chevron — takes a step of the space scale on each side, the six
|
|
431
|
+
* pixels ReUI's trigger measures, and the weight a control label carries.
|
|
432
|
+
* One named by `aria-label` or `aria-labelledby` is an icon, and stays the
|
|
433
|
+
* square every built-in button is: a button with no text has to carry one of
|
|
434
|
+
* the two, so the attribute that makes it accessible is the one that shapes
|
|
435
|
+
* it.
|
|
436
|
+
*/
|
|
437
|
+
[data-ui='text-field']
|
|
438
|
+
> :is([data-part='start'], [data-part='end'])
|
|
439
|
+
> [data-part='action']:not([aria-label], [aria-labelledby]) {
|
|
440
|
+
/* Words in the frame's quiet ink read as a suffix, not as something to
|
|
441
|
+
press: "Search" beside the value looked like a unit. A named command
|
|
442
|
+
or a chosen country code carries the value's ink; a lone mark stays
|
|
443
|
+
quiet, the way the built-in buttons are. */
|
|
444
|
+
color: var(--slotted-text-field-action-label-color, var(--slotted-text, CanvasText));
|
|
445
|
+
font-weight: var(--slotted-font-weight-medium, 500);
|
|
446
|
+
padding-inline: var(--slotted-space-3, 0.375rem);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Words and a mark in one button — a country code over its chevron, a model
|
|
451
|
+
* name, a notation. The mark is centred on the band the letters occupy, not
|
|
452
|
+
* on the line box: the line box keeps room under the baseline for
|
|
453
|
+
* descenders, so a box centred in it sits low beside the letters — every
|
|
454
|
+
* chevron in these buttons did. The rule is the briefing's (_A mark beside a
|
|
455
|
+
* line of text is aligned to the text_), here in a grid: items share the
|
|
456
|
+
* baseline, the mark's synthesized baseline is its bottom edge, a negative
|
|
457
|
+
* start margin takes back the height it would add above the letters, and a
|
|
458
|
+
* translate puts its centre at `(1cap + 1ex) / 4` above the baseline — the
|
|
459
|
+
* midpoint of the capitals' and the lowercase letters' own centres.
|
|
460
|
+
*/
|
|
461
|
+
[data-ui='text-field']
|
|
462
|
+
> :is([data-part='start'], [data-part='end'])
|
|
463
|
+
> [data-part='action']:not([aria-label], [aria-labelledby]) {
|
|
464
|
+
align-content: center;
|
|
465
|
+
align-items: baseline;
|
|
466
|
+
display: inline-grid;
|
|
467
|
+
grid-auto-flow: column;
|
|
468
|
+
justify-content: center;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
[data-ui='text-field']
|
|
472
|
+
> :is([data-part='start'], [data-part='end'])
|
|
473
|
+
> [data-part='action']:not([aria-label], [aria-labelledby])
|
|
474
|
+
> [data-ui='icon'] {
|
|
475
|
+
margin-block-start: calc((1cap + 1ex) / 4 - var(--_icon-size) / 2);
|
|
476
|
+
translate: 0 calc(var(--_icon-size) / 2 - (1cap + 1ex) / 4);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* An adornment holding a button is not quiet text any more: its gap is the
|
|
481
|
+
* one between two buttons, and it does not pad a glyph that is not there.
|
|
482
|
+
*/
|
|
483
|
+
[data-ui='text-field'] > :is([data-part='start'], [data-part='end']):has(> [data-part='action']) {
|
|
484
|
+
gap: 0;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* The hit area, recovered outside the paint. The drawn button is inset
|
|
489
|
+
* inside the frame so the frame keeps its vertical air, and at `sm` with
|
|
490
|
+
* `dense` the whole control is 24 pixels tall — the WCAG 2.2 minimum — so
|
|
491
|
+
* nothing drawn inside it can also be 24 pixels. This grows the target back
|
|
492
|
+
* to that minimum in both axes and leaves a larger one alone, which is the
|
|
493
|
+
* correction `docs/token-foundation.md` leaves to the family rather than to
|
|
494
|
+
* the theme.
|
|
495
|
+
*/
|
|
496
|
+
[data-ui='text-field']
|
|
497
|
+
> :is([data-part='clear'], [data-part='copy'], [data-part='reveal'])::after,
|
|
498
|
+
[data-ui='text-field']
|
|
499
|
+
> :is([data-part='start'], [data-part='end'])
|
|
500
|
+
> [data-part='action']::after {
|
|
501
|
+
content: '';
|
|
502
|
+
inset: min(0px, calc((100% - var(--slotted-target-min, 24px)) / 2));
|
|
503
|
+
position: absolute;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/* On a finger the target is the comfortable size in both axes and the box
|
|
507
|
+
is the one a mouse sees: the ground and the focus ring stay square, and
|
|
508
|
+
the hit area grows to 44 pixels around them. Side by side, no button
|
|
509
|
+
reaches across the edge it shares with the next. */
|
|
510
|
+
@media (pointer: coarse) {
|
|
511
|
+
[data-ui='text-field']
|
|
512
|
+
> :is([data-part='start'], [data-part='end'])
|
|
513
|
+
> [data-part='action']::after,
|
|
514
|
+
[data-ui='text-field']
|
|
515
|
+
> :is([data-part='clear'], [data-part='copy'], [data-part='reveal'])::after {
|
|
516
|
+
inset: min(0px, calc((100% - var(--slotted-target-comfortable, 44px)) / 2));
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
[data-ui='text-field']
|
|
520
|
+
> :is([data-part='clear'], [data-part='copy']):has(
|
|
521
|
+
~ :is([data-part='copy'], [data-part='reveal']):not([hidden])
|
|
522
|
+
)::after,
|
|
523
|
+
[data-ui='text-field']
|
|
524
|
+
> :is([data-part='start'], [data-part='end'])
|
|
525
|
+
> [data-part='action']:has(+ [data-part='action'])::after {
|
|
526
|
+
inset-inline-end: 0;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
[data-ui='text-field']
|
|
530
|
+
> :is([data-part='clear'], [data-part='copy']):not([hidden])
|
|
531
|
+
~ :is([data-part='copy'], [data-part='reveal'])::after,
|
|
532
|
+
[data-ui='text-field']
|
|
533
|
+
> :is([data-part='start'], [data-part='end'])
|
|
534
|
+
> [data-part='action']
|
|
535
|
+
+ [data-part='action']::after {
|
|
536
|
+
inset-inline-start: 0;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/* An open menu keeps its button pressed under the pointer as well, and a
|
|
541
|
+
finished copy keeps its success ink until the check goes. */
|
|
542
|
+
[data-ui='text-field']
|
|
543
|
+
> :is([data-part='clear'], [data-part='copy'], [data-part='reveal']):hover:not(
|
|
544
|
+
:disabled,
|
|
545
|
+
[data-complete]
|
|
546
|
+
),
|
|
547
|
+
[data-ui='text-field']
|
|
548
|
+
> :is([data-part='start'], [data-part='end'])
|
|
549
|
+
> [data-part='action']:hover:not(:disabled, [aria-expanded='true']) {
|
|
550
|
+
background-color: var(
|
|
551
|
+
--slotted-text-field-action-hover-background,
|
|
552
|
+
var(--slotted-tone-secondary-subtle-hover)
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Hover and press move the ground and nothing else, one step each (R2,
|
|
558
|
+
* `inner-control.test.mjs`): ReUI's inner button also brightens its ink and
|
|
559
|
+
* settles a pixel, and inside a 24-pixel box beside the value that reads as
|
|
560
|
+
* the value moving (dossier § 4, `recusar`).
|
|
561
|
+
*/
|
|
562
|
+
[data-ui='text-field']
|
|
563
|
+
> :is([data-part='clear'], [data-part='copy'], [data-part='reveal']):active:not(:disabled),
|
|
564
|
+
[data-ui='text-field']
|
|
565
|
+
> :is([data-part='start'], [data-part='end'])
|
|
566
|
+
> [data-part='action']:active:not(:disabled) {
|
|
567
|
+
background-color: var(
|
|
568
|
+
--slotted-text-field-action-active-background,
|
|
569
|
+
var(--slotted-tone-secondary-subtle-active)
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/* A menu or a popover the button opened keeps it pressed while it is open. */
|
|
574
|
+
[data-ui='text-field']
|
|
575
|
+
> :is([data-part='start'], [data-part='end'])
|
|
576
|
+
> [data-part='action'][aria-expanded='true'] {
|
|
577
|
+
background-color: var(
|
|
578
|
+
--slotted-text-field-action-active-background,
|
|
579
|
+
var(--slotted-tone-secondary-subtle-active)
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
[data-ui='text-field']
|
|
584
|
+
> :is([data-part='start'], [data-part='end'])
|
|
585
|
+
> [data-part='action']:disabled,
|
|
586
|
+
[data-ui='text-field'][data-part='root'][data-disabled]
|
|
587
|
+
> :is([data-part='start'], [data-part='end'])
|
|
588
|
+
> [data-part='action'] {
|
|
589
|
+
color: var(--slotted-disabled-foreground);
|
|
590
|
+
cursor: not-allowed;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* A button carries its own ring rather than borrowing the frame's. Tabbing
|
|
595
|
+
* from the value to a button has to move something visible, and a ring
|
|
596
|
+
* around the whole frame would sit still while focus moved inside it.
|
|
597
|
+
*/
|
|
598
|
+
[data-ui='text-field']
|
|
599
|
+
> :is([data-part='clear'], [data-part='copy'], [data-part='reveal']):focus-visible,
|
|
600
|
+
[data-ui='text-field']
|
|
601
|
+
> :is([data-part='start'], [data-part='end'])
|
|
602
|
+
> [data-part='action']:focus-visible {
|
|
603
|
+
outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
|
|
604
|
+
outline-offset: calc(var(--slotted-focus-ring-offset, 2px) * -1);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/** A copy that finished: the mark changes as well as the ink, and returns on its own. */
|
|
608
|
+
[data-ui='text-field'] > [data-part='copy'][data-complete] {
|
|
609
|
+
color: var(
|
|
610
|
+
--slotted-text-field-copy-complete-color,
|
|
611
|
+
var(--slotted-tone-success-text, CanvasText)
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* The mark a button swaps — copy for a check, an eye for a crossed eye — is
|
|
617
|
+
* a new element each time, and it arrives rather than replaces: from four
|
|
618
|
+
* fifths of its size and transparent, on the entering curve. Nothing leaves:
|
|
619
|
+
* the old mark is gone in the same frame, so there is no exit to time.
|
|
620
|
+
*/
|
|
621
|
+
[data-ui='text-field'] > :is([data-part='copy'], [data-part='reveal']) > [data-ui='icon'] {
|
|
622
|
+
transition:
|
|
623
|
+
opacity var(--slotted-duration-short) var(--slotted-easing-enter),
|
|
624
|
+
scale var(--slotted-duration-short) var(--slotted-easing-enter);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
@starting-style {
|
|
628
|
+
[data-ui='text-field'] > :is([data-part='copy'], [data-part='reveal']) > [data-ui='icon'] {
|
|
629
|
+
opacity: 0;
|
|
630
|
+
scale: 0.8;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/* ── What comes and goes inside the frame ───────────────────────────────
|
|
635
|
+
*
|
|
636
|
+
* The ring while a check runs, the Caps Lock mark, the clear button once
|
|
637
|
+
* there is something to clear, the copy button once there is something to
|
|
638
|
+
* copy. Each is rendered for as long as the capability is on and hidden
|
|
639
|
+
* with the `hidden` attribute rather than removed, so it can leave as well
|
|
640
|
+
* as arrive: it grows in from four fifths on the entering curve and shrinks
|
|
641
|
+
* out on the leaving one, in the same short step, and `display` is held
|
|
642
|
+
* until it has gone.
|
|
643
|
+
*/
|
|
644
|
+
[data-ui='text-field']
|
|
645
|
+
> :is([data-part='busy'], [data-part='caps-lock'], [data-part='clear'], [data-part='copy']) {
|
|
646
|
+
transition:
|
|
647
|
+
var(--_action-transition),
|
|
648
|
+
opacity var(--slotted-duration-short) var(--slotted-easing-enter),
|
|
649
|
+
scale var(--slotted-duration-short) var(--slotted-easing-enter),
|
|
650
|
+
display var(--slotted-duration-short) allow-discrete;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
[data-ui='text-field']
|
|
654
|
+
> :is(
|
|
655
|
+
[data-part='busy'],
|
|
656
|
+
[data-part='caps-lock'],
|
|
657
|
+
[data-part='clear'],
|
|
658
|
+
[data-part='copy']
|
|
659
|
+
)[hidden] {
|
|
660
|
+
display: none;
|
|
661
|
+
opacity: 0;
|
|
662
|
+
scale: 0.8;
|
|
663
|
+
transition:
|
|
664
|
+
opacity var(--slotted-duration-short) var(--slotted-easing-exit),
|
|
665
|
+
scale var(--slotted-duration-short) var(--slotted-easing-exit),
|
|
666
|
+
display var(--slotted-duration-short) allow-discrete;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
@starting-style {
|
|
670
|
+
[data-ui='text-field']
|
|
671
|
+
> :is([data-part='busy'], [data-part='caps-lock'], [data-part='clear'], [data-part='copy']) {
|
|
672
|
+
opacity: 0;
|
|
673
|
+
scale: 0.8;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* The ring is the one `spinner` draws for every family, at this control's
|
|
679
|
+
* glyph size, so it occupies the box a mark beside it would.
|
|
680
|
+
*/
|
|
681
|
+
[data-ui='text-field'] > [data-part='busy'] {
|
|
682
|
+
--slotted-spinner-size: var(--_icon-size);
|
|
683
|
+
|
|
684
|
+
color: var(--slotted-text-field-adornment-color, var(--slotted-text-muted, GrayText));
|
|
685
|
+
flex: 0 0 auto;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Caps Lock in a password: a warning about the next thing typed, not about
|
|
690
|
+
* the value, so it is the warning ink on the glyph alone and never the
|
|
691
|
+
* frame's edge — the frame's one alarming state is `invalid`.
|
|
692
|
+
*/
|
|
693
|
+
[data-ui='text-field'] > [data-part='caps-lock'] {
|
|
694
|
+
--slotted-icon-size: var(--_icon-size);
|
|
695
|
+
|
|
696
|
+
align-items: center;
|
|
697
|
+
color: var(--slotted-text-field-caps-lock-color, var(--slotted-tone-warning-text, CanvasText));
|
|
698
|
+
display: inline-flex;
|
|
699
|
+
flex: 0 0 auto;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/* ── States ───────────────────────────────────────────────────────────
|
|
703
|
+
*
|
|
704
|
+
* Written strongest last, and each weaker one guarded against every stronger
|
|
705
|
+
* one it can meet, which is the order `STATE_PRECEDENCE` sets.
|
|
706
|
+
*/
|
|
707
|
+
|
|
708
|
+
[data-ui='text-field'][data-part='root']:hover:not([data-disabled]):not([data-readonly]):not(
|
|
709
|
+
[data-invalid]
|
|
710
|
+
) {
|
|
711
|
+
border-color: var(--slotted-text-field-hover-border-color, var(--slotted-tone-accent-border));
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
[data-ui='text-field'][data-part='root'][data-variant='subtle']:hover:not([data-disabled]):not(
|
|
715
|
+
[data-readonly]
|
|
716
|
+
):not([data-invalid]) {
|
|
717
|
+
background-color: var(
|
|
718
|
+
--slotted-text-field-subtle-hover-background,
|
|
719
|
+
var(--slotted-tone-secondary-subtle-hover)
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
[data-ui='text-field'][data-part='root'][data-variant='plain']:hover:not([data-disabled]):not(
|
|
724
|
+
[data-readonly]
|
|
725
|
+
):not([data-invalid]) {
|
|
726
|
+
background-color: var(
|
|
727
|
+
--slotted-text-field-plain-hover-background,
|
|
728
|
+
var(--slotted-tone-secondary-subtle-hover)
|
|
729
|
+
);
|
|
730
|
+
/* The accent edge the other variants take would draw the cell's outline. */
|
|
731
|
+
border-color: transparent;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* The ring belongs to the frame, and it answers the control's focus rather
|
|
736
|
+
* than the frame's: a browser matches `:focus-visible` on a text input for a
|
|
737
|
+
* pointer press as well as for a Tab, which is exactly right for a control
|
|
738
|
+
* whose caret is the thing that moved.
|
|
739
|
+
*/
|
|
740
|
+
[data-ui='text-field'][data-part='root']:has(> [data-part='input']:focus-visible) {
|
|
741
|
+
/**
|
|
742
|
+
* The resting shadow, recoloured. The geometry is the foundation's and the
|
|
743
|
+
* ink is the focus ring's, so the lift under the control and the ring
|
|
744
|
+
* around it are one colour and the state decides which colour that is.
|
|
745
|
+
*/
|
|
746
|
+
box-shadow: var(--slotted-control-shadow-focus-geometry, 0 1px 4px -1px)
|
|
747
|
+
var(--slotted-focus-ring-color, Highlight);
|
|
748
|
+
outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
|
|
749
|
+
outline-offset: var(--slotted-focus-ring-offset, 2px);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* The ring of a control whose box is not its own is drawn inside that box,
|
|
754
|
+
* the way the clear button draws its ring inside the frame: two pixels past
|
|
755
|
+
* the control is two pixels over the next row's rule. No lift either — a
|
|
756
|
+
* shadow that appears only under focus is a cell that changes size when it
|
|
757
|
+
* is used.
|
|
758
|
+
*/
|
|
759
|
+
[data-ui='text-field'][data-part='root'][data-variant='plain']:has(
|
|
760
|
+
> [data-part='input']:focus-visible
|
|
761
|
+
) {
|
|
762
|
+
box-shadow: none;
|
|
763
|
+
outline-offset: calc(var(--slotted-focus-ring-offset, 2px) * -1);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Invalid outranks hover and is written after it for that reason: a control
|
|
768
|
+
* reporting an error should not stop looking wrong because the pointer is
|
|
769
|
+
* over it.
|
|
770
|
+
*
|
|
771
|
+
* The ring is retargeted rather than repainted. Every rule that draws focus —
|
|
772
|
+
* the frame's outline, the shadow under it, the ring on either button — reads
|
|
773
|
+
* `--slotted-focus-ring-color`, so pointing that one name at the invalid
|
|
774
|
+
* colour on the element moves all of them at once. That is the whole of the
|
|
775
|
+
* rule for a family, and it is the rule `checkbox`, `radio-group` and
|
|
776
|
+
* `switch` inherit rather than restate when they arrive.
|
|
777
|
+
*/
|
|
778
|
+
[data-ui='text-field'][data-part='root'][data-invalid] {
|
|
779
|
+
--slotted-focus-ring-color: var(
|
|
780
|
+
--slotted-text-field-invalid-focus-ring-color,
|
|
781
|
+
var(--slotted-focus-ring-color-invalid, Highlight)
|
|
782
|
+
);
|
|
783
|
+
--_invalid-edge-width: var(
|
|
784
|
+
--slotted-text-field-invalid-edge-width,
|
|
785
|
+
var(--slotted-control-invalid-edge-width, 1px)
|
|
786
|
+
);
|
|
787
|
+
--_invalid-edge-color: var(
|
|
788
|
+
--slotted-text-field-invalid-border-color,
|
|
789
|
+
var(--slotted-tone-danger-border)
|
|
790
|
+
);
|
|
791
|
+
--_invalid-edge: inset 0 0 0 var(--_invalid-edge-width) var(--_invalid-edge-color);
|
|
792
|
+
|
|
793
|
+
border-color: var(--slotted-text-field-invalid-border-color, var(--slotted-tone-danger-border));
|
|
794
|
+
/*
|
|
795
|
+
* Colour alone does not say the field is wrong (WCAG 1.4.1): in
|
|
796
|
+
* `high-contrast` the edge at rest, the danger border and both rings are
|
|
797
|
+
* one black. An edge inside the border makes the invalid line thicker in
|
|
798
|
+
* every scheme. It is a shadow rather than a wider border, so the box and
|
|
799
|
+
* the text do not move; it replaces the resting lift, which may be `none`
|
|
800
|
+
* and so cannot be listed beside it.
|
|
801
|
+
*/
|
|
802
|
+
box-shadow: var(--_invalid-edge);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
[data-ui='text-field'][data-part='root'][data-invalid]:has(> [data-part='input']:focus-visible) {
|
|
806
|
+
box-shadow:
|
|
807
|
+
var(--_invalid-edge),
|
|
808
|
+
var(--slotted-control-shadow-focus-geometry, 0 1px 4px -1px)
|
|
809
|
+
var(--slotted-focus-ring-color, Highlight);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
[data-ui='text-field'][data-part='root'][data-variant='plain'][data-invalid]:has(
|
|
813
|
+
> [data-part='input']:focus-visible
|
|
814
|
+
) {
|
|
815
|
+
/* The ring lies inside the box here, over the border and an edge as wide as
|
|
816
|
+
it; the edge reaches past the ring. */
|
|
817
|
+
box-shadow: inset 0 0 0
|
|
818
|
+
calc(
|
|
819
|
+
var(--slotted-focus-ring-offset, 2px) - var(--slotted-control-border-width, 1px) +
|
|
820
|
+
var(--_invalid-edge-width)
|
|
821
|
+
)
|
|
822
|
+
var(--_invalid-edge-color);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/*
|
|
826
|
+
* Forced colours remove every box shadow, and the edge with them. The
|
|
827
|
+
* width comes back as an outline inside the border, and a focused invalid
|
|
828
|
+
* control draws its ring double, which a valid ring is not.
|
|
829
|
+
*/
|
|
830
|
+
@media (forced-colors: active) {
|
|
831
|
+
[data-ui='text-field'][data-part='root'][data-invalid] {
|
|
832
|
+
outline: var(--_invalid-edge-width) solid CanvasText;
|
|
833
|
+
outline-offset: calc(
|
|
834
|
+
-1 * (var(--slotted-control-border-width, 1px) + var(--_invalid-edge-width))
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
[data-ui='text-field'][data-part='root'][data-invalid]:has(
|
|
839
|
+
> [data-part='input']:focus-visible
|
|
840
|
+
) {
|
|
841
|
+
outline: calc(var(--slotted-focus-ring-width, 2px) + var(--_invalid-edge-width)) double
|
|
842
|
+
var(--slotted-focus-ring-color, Highlight);
|
|
843
|
+
outline-offset: var(--slotted-focus-ring-offset, 2px);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
[data-ui='text-field'][data-part='root'][data-variant='plain'][data-invalid]:has(
|
|
847
|
+
> [data-part='input']:focus-visible
|
|
848
|
+
) {
|
|
849
|
+
outline: calc(var(--slotted-focus-ring-width, 2px) + var(--_invalid-edge-width)) double
|
|
850
|
+
var(--slotted-focus-ring-color, Highlight);
|
|
851
|
+
outline-offset: calc(
|
|
852
|
+
-1 * (var(--slotted-focus-ring-width, 2px) + var(--_invalid-edge-width))
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* Read-only stays focusable, selectable and legible, and it must not be
|
|
859
|
+
* mistakable for disabled: the value is still real and still submitted. The
|
|
860
|
+
* ground says "not yours to change" and the ink says nothing at all.
|
|
861
|
+
*/
|
|
862
|
+
[data-ui='text-field'][data-part='root'][data-readonly] {
|
|
863
|
+
background-color: var(
|
|
864
|
+
--slotted-text-field-readonly-background,
|
|
865
|
+
var(--slotted-tone-secondary-subtle-hover)
|
|
866
|
+
);
|
|
867
|
+
cursor: default;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* `:disabled` on the control is matched beside `data-disabled` on the frame,
|
|
872
|
+
* because a control can be disabled by something that never told it so: a
|
|
873
|
+
* disabled `fieldset` disables every control inside it, and the control has
|
|
874
|
+
* no way to know. The attribute alone left such a control looking fully
|
|
875
|
+
* operable and answering nothing — a defect the deleted `input` family
|
|
876
|
+
* recorded and fixed, and one no rebuild is allowed to reintroduce.
|
|
877
|
+
*/
|
|
878
|
+
[data-ui='text-field'][data-part='root'][data-disabled],
|
|
879
|
+
[data-ui='text-field'][data-part='root']:has(> [data-part='input']:disabled) {
|
|
880
|
+
background-color: var(
|
|
881
|
+
--slotted-text-field-disabled-background,
|
|
882
|
+
var(--slotted-disabled-background)
|
|
883
|
+
);
|
|
884
|
+
border-color: var(--slotted-text-field-disabled-border-color, var(--slotted-disabled-border));
|
|
885
|
+
/* Nothing a person cannot reach is lifted off the page. */
|
|
886
|
+
box-shadow: none;
|
|
887
|
+
color: var(--slotted-text-field-disabled-color, var(--slotted-disabled-foreground));
|
|
888
|
+
cursor: not-allowed;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* Muted to the colour the theme measures rather than faded by opacity: a
|
|
893
|
+
* value nobody can read is a field nobody can check, and a disabled form
|
|
894
|
+
* still has to say what it holds.
|
|
895
|
+
*/
|
|
896
|
+
[data-ui='text-field'][data-part='root'][data-disabled] > [data-part='start'],
|
|
897
|
+
[data-ui='text-field'][data-part='root'][data-disabled] > [data-part='end'] {
|
|
898
|
+
color: var(--slotted-disabled-foreground);
|
|
899
|
+
}
|
|
900
|
+
}
|