@slotted/styles 0.0.0-stage → 0.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,674 @@
|
|
|
1
|
+
@layer slotted.components {
|
|
2
|
+
/**
|
|
3
|
+
* A list of options the reader chooses from, and the primitive `select`,
|
|
4
|
+
* `combobox` and the pickers are built on.
|
|
5
|
+
*
|
|
6
|
+
* ┌─ root ─ role="listbox" ─ scrolls ─ one tab stop ──────────────┐
|
|
7
|
+
* │ ┌─ canvas ─ role="presentation" ─ only while windowed ───┐ │
|
|
8
|
+
* │ │ ┌─ group ─ role="group" ────────────────────────┐ │ │
|
|
9
|
+
* │ │ │ group-label │ │ │
|
|
10
|
+
* │ │ │ option ▸ indicator or icon │ label │ meta │ │ │
|
|
11
|
+
* │ │ │ description │ │ │
|
|
12
|
+
* │ │ └───────────────────────────────────────────────┘ │ │
|
|
13
|
+
* │ │ empty · loader │ │
|
|
14
|
+
* │ └────────────────────────────────────────────────────────┘ │
|
|
15
|
+
* └───────────────────────────────────────────────────────────────┘
|
|
16
|
+
*
|
|
17
|
+
* ── Why the surface is a grid and everything inside it is a subgrid ──
|
|
18
|
+
*
|
|
19
|
+
* The three columns — the mark, the label, the value — belong to the whole
|
|
20
|
+
* listbox rather than to one option, so `group`, `canvas` and `option` each
|
|
21
|
+
* pass through them with `grid-template-columns: subgrid`. Labels line up
|
|
22
|
+
* across every group, the value column is as wide as the widest value and
|
|
23
|
+
* never narrower, and the label column is the only one that gives way. A flex
|
|
24
|
+
* row per option — which is what every surveyed implementation draws — lets a
|
|
25
|
+
* long translated label eat the value beside it, and leaves two options'
|
|
26
|
+
* labels out of line whenever one has an icon and the other does not.
|
|
27
|
+
*
|
|
28
|
+
* The columns carry no `column-gap`: a gap would be drawn between two tracks
|
|
29
|
+
* even when the first is empty, so a listbox with no marks at all would open
|
|
30
|
+
* with a gutter down its start edge. Each optional cell carries its own
|
|
31
|
+
* margin instead, and a column nothing occupies collapses to nothing. That is
|
|
32
|
+
* `menu`'s rule, and the two families are the same row under two roles.
|
|
33
|
+
*
|
|
34
|
+
* ── Two layouts that are not the subgrid ────────────────────────────
|
|
35
|
+
*
|
|
36
|
+
* `data-layout="grid"` puts `columns` options across, so the root's tracks
|
|
37
|
+
* become the columns and an option owns its own three. `data-orientation
|
|
38
|
+
* ="horizontal"` lays the options along the inline axis and scrolls that way.
|
|
39
|
+
* Both are declared by the family from its own props; neither changes what an
|
|
40
|
+
* option is made of.
|
|
41
|
+
*
|
|
42
|
+
* ── Density ─────────────────────────────────────────────────────────
|
|
43
|
+
*
|
|
44
|
+
* An option is a control, so its height, inset, type, glyph and gap all read
|
|
45
|
+
* control geometry. A dense listbox holds more options and is the same
|
|
46
|
+
* component; only the surface's own inset, radius and shadow come off the
|
|
47
|
+
* space, radius and shadow scales.
|
|
48
|
+
*/
|
|
49
|
+
[data-ui='listbox'] {
|
|
50
|
+
--_surface: var(--slotted-listbox-surface, var(--slotted-surface, #ffffff));
|
|
51
|
+
--_text: var(--slotted-listbox-text, var(--slotted-text, #0f172a));
|
|
52
|
+
--_muted: var(--slotted-listbox-text-muted, var(--slotted-text-muted, #5b6b7f));
|
|
53
|
+
--_border-color: var(--slotted-listbox-border-color, var(--slotted-control-border-color));
|
|
54
|
+
--_border-width: var(--slotted-border-width-thin, 1px);
|
|
55
|
+
--_radius: var(--slotted-listbox-radius, var(--slotted-radius-md, 6px));
|
|
56
|
+
--_shadow: var(--slotted-listbox-shadow, var(--slotted-control-shadow));
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* One quiet ground for hover and for the option the keyboard is on, and the
|
|
60
|
+
* two are told apart by the ring rather than by two greys. A second, darker
|
|
61
|
+
* grey was tried first and read the wrong way round: the pointer's own
|
|
62
|
+
* feedback shouted louder than the keyboard's position. What separates them
|
|
63
|
+
* now is that the active option paints nothing at all until the keyboard is
|
|
64
|
+
* somewhere it can be — see the rule below.
|
|
65
|
+
*/
|
|
66
|
+
--_highlight: var(--slotted-listbox-highlight, var(--slotted-tone-secondary-subtle-hover));
|
|
67
|
+
--_highlight-active: var(
|
|
68
|
+
--slotted-listbox-highlight-active,
|
|
69
|
+
var(--slotted-tone-secondary-subtle-active)
|
|
70
|
+
);
|
|
71
|
+
--_selected-surface: var(
|
|
72
|
+
--slotted-listbox-selected-surface,
|
|
73
|
+
var(--slotted-tone-accent-surface)
|
|
74
|
+
);
|
|
75
|
+
--_selected-text: var(--slotted-listbox-selected-text, var(--slotted-tone-accent-text));
|
|
76
|
+
|
|
77
|
+
--_inset-block: var(--slotted-listbox-padding-block, var(--slotted-space-2, 0.25rem));
|
|
78
|
+
--_inset-inline: var(--slotted-listbox-padding-inline, var(--slotted-space-2, 0.25rem));
|
|
79
|
+
--_option-inset: var(--slotted-control-padding-inline-md, 0.8125rem);
|
|
80
|
+
--_option-height: var(--slotted-control-height-md, 34px);
|
|
81
|
+
--_option-radius: var(--slotted-listbox-option-radius, var(--slotted-control-radius));
|
|
82
|
+
--_font-size: var(--slotted-control-font-size-md, 13px);
|
|
83
|
+
--_secondary-font-size: var(--slotted-control-font-size-sm, 12px);
|
|
84
|
+
--_icon-size: var(--slotted-control-icon-size-md, 16px);
|
|
85
|
+
--_column-gap: var(--slotted-control-gap-md, 0.5rem);
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* How many options tall the box is, and how many across. `<select size>`
|
|
89
|
+
* under another name: a listbox with no bound is a listbox that pushes the
|
|
90
|
+
* page down as its data arrives, and a windowed one has no viewport at all.
|
|
91
|
+
* The family writes both per instance; the tokens are what a theme retunes.
|
|
92
|
+
*/
|
|
93
|
+
--_rows: var(--slotted-listbox-rows, 7);
|
|
94
|
+
--_columns: var(--slotted-listbox-columns, 1);
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The cap. Measure belongs to `container`, so the family offers no axis for
|
|
98
|
+
* it and still has to stop a `max-content` surface from setting one option
|
|
99
|
+
* the width of the page.
|
|
100
|
+
*/
|
|
101
|
+
--_measure: var(--slotted-listbox-measure, 24rem);
|
|
102
|
+
|
|
103
|
+
box-sizing: border-box;
|
|
104
|
+
color: var(--_text);
|
|
105
|
+
display: grid;
|
|
106
|
+
font-family: var(--slotted-font-family-sans);
|
|
107
|
+
font-size: var(--_font-size);
|
|
108
|
+
grid-auto-rows: min-content;
|
|
109
|
+
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
110
|
+
inline-size: 100%;
|
|
111
|
+
max-inline-size: var(--_measure);
|
|
112
|
+
/**
|
|
113
|
+
* The block cap is stated rather than the height: a listbox of two options
|
|
114
|
+
* is two options tall, and one of two hundred stops at `rows` and scrolls.
|
|
115
|
+
* The inset is added so the last option is not cut in half by the padding
|
|
116
|
+
* the surface reserved for itself.
|
|
117
|
+
*/
|
|
118
|
+
max-block-size: calc(var(--_rows) * var(--_option-height) + 2 * var(--_inset-block));
|
|
119
|
+
outline: none;
|
|
120
|
+
overflow-y: auto;
|
|
121
|
+
overscroll-behavior: contain;
|
|
122
|
+
padding-block: var(--_inset-block);
|
|
123
|
+
padding-inline: var(--_inset-inline);
|
|
124
|
+
/* The active option is scrolled to by script, and the browser must not
|
|
125
|
+
animate a second time over the top of it. */
|
|
126
|
+
scroll-behavior: auto;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
[data-ui='listbox'][data-size='sm'] {
|
|
130
|
+
--_option-inset: var(--slotted-control-padding-inline-sm, 0.625rem);
|
|
131
|
+
--_option-height: var(--slotted-control-height-sm, 30px);
|
|
132
|
+
--_font-size: var(--slotted-control-font-size-sm, 12px);
|
|
133
|
+
--_secondary-font-size: var(--slotted-font-size-xs, 0.75rem);
|
|
134
|
+
--_icon-size: var(--slotted-control-icon-size-sm, 14px);
|
|
135
|
+
--_column-gap: var(--slotted-control-gap-sm, 0.375rem);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
[data-ui='listbox'][data-size='lg'] {
|
|
139
|
+
--_option-inset: var(--slotted-control-padding-inline-lg, 1rem);
|
|
140
|
+
--_option-height: var(--slotted-control-height-lg, 38px);
|
|
141
|
+
--_font-size: var(--slotted-control-font-size-lg, 14px);
|
|
142
|
+
--_secondary-font-size: var(--slotted-control-font-size-sm, 12px);
|
|
143
|
+
--_icon-size: var(--slotted-control-icon-size-lg, 18px);
|
|
144
|
+
--_column-gap: var(--slotted-control-gap-lg, 0.5rem);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* ── The two variants ─────────────────────────────────────────────────── */
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* `framed` is the standalone control: a box with an edge, which is what
|
|
151
|
+
* `<select multiple>` looks like and what a listbox presented on its own has
|
|
152
|
+
* to look like to be recognised as a control at all. `plain` is what `select`
|
|
153
|
+
* and `combobox` render inside their own layer, where the layer already has
|
|
154
|
+
* the edge and a second one would draw a box inside a box.
|
|
155
|
+
*/
|
|
156
|
+
[data-ui='listbox'][data-variant='framed'] {
|
|
157
|
+
background: var(--_surface);
|
|
158
|
+
border: var(--_border-width) solid var(--_border-color);
|
|
159
|
+
border-radius: var(--_radius);
|
|
160
|
+
box-shadow: var(--_shadow);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
[data-ui='listbox'][data-variant='plain'] {
|
|
164
|
+
background: transparent;
|
|
165
|
+
border: 0;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The ring goes on the box only while the box itself is the focused element,
|
|
170
|
+
* which is the `activedescendant` model. Under `roving` the ring belongs to
|
|
171
|
+
* the option, and a second ring around the whole listbox would say the
|
|
172
|
+
* listbox is the thing being operated.
|
|
173
|
+
*/
|
|
174
|
+
[data-ui='listbox'][data-focus-mode='activedescendant']:focus-visible {
|
|
175
|
+
outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, #2563eb);
|
|
176
|
+
outline-offset: var(--slotted-focus-ring-offset, 2px);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
[data-ui='listbox'][data-focus-mode='roving']:focus {
|
|
180
|
+
outline: none;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
[data-ui='listbox'][data-invalid] {
|
|
184
|
+
--_border-color: var(--slotted-tone-danger-border);
|
|
185
|
+
--slotted-focus-ring-color: var(--slotted-focus-ring-color-invalid);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* Width as well as colour (WCAG 1.4.1): in `high-contrast` the danger border
|
|
189
|
+
is the resting edge's black. An edge inside the border, so nothing moves,
|
|
190
|
+
in place of the resting lift — the one `text-field` draws. Disabled keeps
|
|
191
|
+
its own edge, as it keeps its own border colour. */
|
|
192
|
+
[data-ui='listbox'][data-invalid]:not([data-disabled]) {
|
|
193
|
+
--_invalid-edge-width: var(--slotted-control-invalid-edge-width, 1px);
|
|
194
|
+
--_shadow: inset 0 0 0 var(--_invalid-edge-width) var(--slotted-tone-danger-border);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
[data-ui='listbox'][data-disabled] {
|
|
198
|
+
--_surface: var(--slotted-disabled-background, #e2e8f0);
|
|
199
|
+
--_text: var(--slotted-disabled-foreground, #64748b);
|
|
200
|
+
--_border-color: var(--slotted-disabled-border, #cbd5e1);
|
|
201
|
+
|
|
202
|
+
cursor: not-allowed;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
[data-ui='listbox'][data-loading] {
|
|
206
|
+
cursor: progress;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/* ── The two layouts that are not one column ──────────────────────────── */
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* `columns` options across. The root's tracks become the columns, so an
|
|
213
|
+
* option owns its own three and the subgrid stops at the row. React Aria
|
|
214
|
+
* reads the laid-out geometry to decide what "the option below" is, which is
|
|
215
|
+
* a layout read during a key press; here the number is stated, the arrows
|
|
216
|
+
* step by it, and the picture and the keyboard cannot disagree.
|
|
217
|
+
*/
|
|
218
|
+
[data-ui='listbox'][data-layout='grid'] {
|
|
219
|
+
column-gap: var(--_column-gap);
|
|
220
|
+
grid-template-columns: repeat(var(--_columns), minmax(0, 1fr));
|
|
221
|
+
max-block-size: none;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
[data-ui='listbox'][data-layout='grid'] [data-part='option'] {
|
|
225
|
+
grid-column: auto;
|
|
226
|
+
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
[data-ui='listbox'][data-layout='grid'] :is([data-part='group'], [data-part='canvas']) {
|
|
230
|
+
grid-template-columns: repeat(var(--_columns), minmax(0, 1fr));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Along the inline axis. The block cap does not apply — a horizontal listbox
|
|
235
|
+
* is one row tall — and the scroll turns with it, so `dir="rtl"` reverses the
|
|
236
|
+
* whole thing without a rule of its own.
|
|
237
|
+
*/
|
|
238
|
+
[data-ui='listbox'][data-orientation='horizontal'] {
|
|
239
|
+
display: flex;
|
|
240
|
+
flex-direction: row;
|
|
241
|
+
max-block-size: none;
|
|
242
|
+
overflow-x: auto;
|
|
243
|
+
overflow-y: hidden;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
[data-ui='listbox'][data-orientation='horizontal'] [data-part='option'] {
|
|
247
|
+
flex: 0 0 auto;
|
|
248
|
+
grid-template-columns: auto auto auto;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* ── The canvas, which exists only while the list is windowed ─────────── */
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* It carries the whole list's height so the scrollbar is the length of the
|
|
255
|
+
* data rather than of what is rendered, and it starts the rendered slice at
|
|
256
|
+
* the offset the engine returned. `padding-block-start` rather than a
|
|
257
|
+
* transform, so the options are still in flow and the subgrid survives:
|
|
258
|
+
* `box-sizing: border-box` makes the padding part of the stated height, and
|
|
259
|
+
* the rows below it overflow a box nothing clips.
|
|
260
|
+
*
|
|
261
|
+
* `role="presentation"` is what keeps the options owned by the listbox. A
|
|
262
|
+
* generic element between a `listbox` and its `option`s is an owned element
|
|
263
|
+
* with no permitted role, and the role is what makes it transparent.
|
|
264
|
+
*/
|
|
265
|
+
[data-ui='listbox'] > [data-part='canvas'] {
|
|
266
|
+
/**
|
|
267
|
+
* `align-content: start` and `grid-auto-rows: min-content` together, and
|
|
268
|
+
* neither is optional: a grid with a definite block size and automatic rows
|
|
269
|
+
* stretches those rows to fill it, so eight options inside a canvas six
|
|
270
|
+
* thousand pixels tall became eight rows of eight hundred and fifty and the
|
|
271
|
+
* window rendered nothing anybody could see.
|
|
272
|
+
*/
|
|
273
|
+
align-content: start;
|
|
274
|
+
block-size: var(--_canvas-size, auto);
|
|
275
|
+
box-sizing: border-box;
|
|
276
|
+
display: grid;
|
|
277
|
+
grid-auto-rows: min-content;
|
|
278
|
+
grid-column: 1 / -1;
|
|
279
|
+
grid-template-columns: subgrid;
|
|
280
|
+
padding-block-start: var(--_canvas-offset, 0);
|
|
281
|
+
position: relative;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* A row the window does not cover and the family renders anyway: the active
|
|
286
|
+
* option, and the anchor a range is measured from. Without it the element
|
|
287
|
+
* `aria-activedescendant` names leaves the document under a momentum scroll,
|
|
288
|
+
* and the reader is operating a widget with no focused option at all.
|
|
289
|
+
*
|
|
290
|
+
* It sits at its own place on the canvas rather than being pulled into view,
|
|
291
|
+
* because the reader scrolled it out and moving it back would take the
|
|
292
|
+
* scrollbar away from them.
|
|
293
|
+
*/
|
|
294
|
+
[data-ui='listbox'] [data-part='option'][data-pinned] {
|
|
295
|
+
/* Out of flow, so out of the subgrid: it states the three columns itself.
|
|
296
|
+
They are the option's own rather than the listbox's, which is the price
|
|
297
|
+
of rendering a row the window does not reach. */
|
|
298
|
+
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
299
|
+
inset-inline: 0;
|
|
300
|
+
position: absolute;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/* ── Groups ───────────────────────────────────────────────────────────── */
|
|
304
|
+
|
|
305
|
+
[data-ui='listbox'] [data-part='group'] {
|
|
306
|
+
display: grid;
|
|
307
|
+
grid-column: 1 / -1;
|
|
308
|
+
grid-template-columns: subgrid;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
[data-ui='listbox'] [data-part='group-label'] {
|
|
312
|
+
color: var(--_muted);
|
|
313
|
+
font-size: var(--_secondary-font-size);
|
|
314
|
+
font-weight: var(--slotted-font-weight-semibold, 600);
|
|
315
|
+
grid-column: 1 / -1;
|
|
316
|
+
line-height: var(--slotted-line-height-sm, 1.45);
|
|
317
|
+
padding-block: var(--slotted-space-2, 0.25rem);
|
|
318
|
+
padding-inline: var(--_option-inset);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* The rule between two groups is the group's own edge rather than a separator
|
|
323
|
+
* element: ARIA 1.2 lets a `listbox` own `option` and `group` and nothing
|
|
324
|
+
* else, so an `hr` inside one is an owned element with no permitted role.
|
|
325
|
+
*/
|
|
326
|
+
[data-ui='listbox'] [data-part='group'] + [data-part='group'] {
|
|
327
|
+
border-block-start: var(--_border-width) solid
|
|
328
|
+
var(--slotted-listbox-group-border-color, var(--slotted-border-subtle, #e2e8f0));
|
|
329
|
+
margin-block-start: var(--slotted-space-2, 0.25rem);
|
|
330
|
+
padding-block-start: var(--slotted-space-2, 0.25rem);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/* ── Options ──────────────────────────────────────────────────────────── */
|
|
334
|
+
|
|
335
|
+
[data-ui='listbox'] [data-part='option'] {
|
|
336
|
+
align-content: center;
|
|
337
|
+
background: transparent;
|
|
338
|
+
border-radius: var(--_option-radius);
|
|
339
|
+
box-sizing: border-box;
|
|
340
|
+
color: inherit;
|
|
341
|
+
cursor: default;
|
|
342
|
+
display: grid;
|
|
343
|
+
grid-column: 1 / -1;
|
|
344
|
+
grid-template-columns: subgrid;
|
|
345
|
+
/**
|
|
346
|
+
* The line box belongs to the option and every cell inherits it, so `1lh`
|
|
347
|
+
* on the mark resolves against the same line the label is set on. Stated
|
|
348
|
+
* anywhere else, a mark beside a label that wrapped sits two pixels above
|
|
349
|
+
* the text it annotates.
|
|
350
|
+
*/
|
|
351
|
+
line-height: var(--slotted-line-height-md, 1.5);
|
|
352
|
+
min-block-size: var(--_option-height);
|
|
353
|
+
padding-block: var(--slotted-space-2, 0.25rem);
|
|
354
|
+
padding-inline: var(--_option-inset);
|
|
355
|
+
position: relative;
|
|
356
|
+
/* The one gap inside an option, between a label and the line under it. */
|
|
357
|
+
row-gap: var(--slotted-space-1, 0.125rem);
|
|
358
|
+
text-align: start;
|
|
359
|
+
transition:
|
|
360
|
+
background-color var(--slotted-control-transition-duration, var(--slotted-duration-short))
|
|
361
|
+
var(--slotted-control-transition-easing, var(--slotted-easing-standard)),
|
|
362
|
+
color var(--slotted-control-transition-duration, var(--slotted-duration-short))
|
|
363
|
+
var(--slotted-control-transition-easing, var(--slotted-easing-standard));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Hover is a pointer's own feedback and nothing more. Unlike `menu`, it does
|
|
368
|
+
* not move the active option: a listbox holds a selection as well as a
|
|
369
|
+
* highlight, and a hover that moved the highlight would fight the arrow keys
|
|
370
|
+
* a reader is holding down.
|
|
371
|
+
*/
|
|
372
|
+
[data-ui='listbox']:not([data-disabled]) [data-part='option']:not([data-disabled]):hover {
|
|
373
|
+
background-color: var(--_highlight);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
[data-ui='listbox']:not([data-disabled]) [data-part='option']:not([data-disabled]):active {
|
|
377
|
+
background-color: var(--_highlight-active);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Where the keyboard is, and only while the keyboard is somewhere it can be.
|
|
382
|
+
*
|
|
383
|
+
* Under `roving` the ground is drawn while focus is inside the listbox: an
|
|
384
|
+
* option nobody is on is not the active one, and painting it anyway made a
|
|
385
|
+
* hovered option and the active option the same picture. Under
|
|
386
|
+
* `activedescendant` it is drawn unconditionally, because focus may
|
|
387
|
+
* legitimately be elsewhere — a `combobox` keeps it in its own text field and
|
|
388
|
+
* this ground is then the only thing that says which option is live.
|
|
389
|
+
*/
|
|
390
|
+
[data-ui='listbox'][data-focus-mode='roving']:focus-within [data-part='option'][data-current],
|
|
391
|
+
[data-ui='listbox'][data-focus-mode='activedescendant'] [data-part='option'][data-current] {
|
|
392
|
+
background-color: var(--_highlight);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* The ring, and it is drawn from the listbox rather than from the option.
|
|
397
|
+
* `:focus-within` is what makes one rule cover both focus models: under
|
|
398
|
+
* `roving` the focus is on the option and under `activedescendant` it is on
|
|
399
|
+
* the root, and in both the answer to "is the keyboard in this listbox" is
|
|
400
|
+
* the same. Keying on the option's own `:focus-visible` would have drawn
|
|
401
|
+
* nothing at all in the model where the option never holds focus.
|
|
402
|
+
*
|
|
403
|
+
* An inset `outline` rather than a `::after` border: an outline follows the
|
|
404
|
+
* option's own radius without a second radius to keep in step, and forced
|
|
405
|
+
* colours replace its paint on their own, where a pseudo-element's border
|
|
406
|
+
* would be decoration the theme cannot reach.
|
|
407
|
+
*/
|
|
408
|
+
[data-ui='listbox']:focus-within [data-part='option'][data-current] {
|
|
409
|
+
outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, #2563eb);
|
|
410
|
+
outline-offset: calc(-1 * var(--slotted-focus-ring-width, 2px));
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
[data-ui='listbox'] [data-part='option']:focus {
|
|
414
|
+
outline: none;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* What the reader chose. The ground says it at a glance, the ink says it
|
|
419
|
+
* again, and the mark says it where neither can: in the `high-contrast`
|
|
420
|
+
* scheme the accent surface *is* the plain surface, and every tone surface
|
|
421
|
+
* becomes `Canvas` under forced colours, so a selection painted with a fill
|
|
422
|
+
* alone disappears in the mode that needs it most — the same failure WCAG
|
|
423
|
+
* 2.2 SC 1.4.1 describes for colour on its own. A bar on the start edge said
|
|
424
|
+
* it once; a rail for state is out of the system (R1), and the `indicator`
|
|
425
|
+
* below is the mark that carries it instead.
|
|
426
|
+
*/
|
|
427
|
+
[data-ui='listbox'] [data-part='option'][data-selected] {
|
|
428
|
+
background-color: var(--_selected-surface);
|
|
429
|
+
color: var(--_selected-text);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Two chosen neighbours are one block (LB2). Every option keeps its own
|
|
434
|
+
* radius, so two selected siblings painted with the same ground met at a
|
|
435
|
+
* waist — four rounded corners inside the block — and a multiple selection
|
|
436
|
+
* read as a column of separate chips. The second gives up its top corners
|
|
437
|
+
* and the first its bottom ones, so the block is rounded only where the
|
|
438
|
+
* selection begins and ends. `select` and `combobox` draw the same ground
|
|
439
|
+
* and answer the same way; `selection-block.test.mjs` holds the three to it.
|
|
440
|
+
*/
|
|
441
|
+
[data-ui='listbox'] [data-part='option'][data-selected] + [data-part='option'][data-selected] {
|
|
442
|
+
border-start-end-radius: 0;
|
|
443
|
+
border-start-start-radius: 0;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
[data-ui='listbox']
|
|
447
|
+
[data-part='option'][data-selected]:has(+ [data-part='option'][data-selected]) {
|
|
448
|
+
border-end-end-radius: 0;
|
|
449
|
+
border-end-start-radius: 0;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
[data-ui='listbox']:not([data-disabled])
|
|
453
|
+
[data-part='option'][data-selected]:not([data-disabled]):hover {
|
|
454
|
+
background-color: var(--slotted-tone-accent-subtle-hover);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
[data-ui='listbox']:not([data-disabled])
|
|
458
|
+
[data-part='option'][data-selected]:not([data-disabled]):active {
|
|
459
|
+
background-color: var(--slotted-tone-accent-subtle-active);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* A disabled option keeps its place in the set and stays reachable when
|
|
464
|
+
* `navigateDisabled` says so, which the pattern allows and a native disabled
|
|
465
|
+
* control cannot do. It therefore has to answer the highlight with something,
|
|
466
|
+
* and answer it more quietly than an option that would do anything.
|
|
467
|
+
*/
|
|
468
|
+
[data-ui='listbox'] [data-part='option'][data-disabled] {
|
|
469
|
+
color: var(--slotted-disabled-foreground, #64748b);
|
|
470
|
+
cursor: not-allowed;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Disabled outranks selected, which is the order `STATE_PRECEDENCE` declares.
|
|
475
|
+
* The mark stays drawn — the reader still needs to know which option was
|
|
476
|
+
* chosen — and the ground stops claiming the option is theirs to change.
|
|
477
|
+
*/
|
|
478
|
+
[data-ui='listbox'] [data-part='option'][data-disabled]:is([data-selected], [data-current]) {
|
|
479
|
+
background-color: var(--slotted-disabled-background, #e2e8f0);
|
|
480
|
+
color: var(--slotted-disabled-foreground, #64748b);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
[data-ui='listbox'][data-readonly] [data-part='option'] {
|
|
484
|
+
cursor: default;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/* ── The cells ────────────────────────────────────────────────────────── */
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Column one, and the reason a chosen option and an unchosen one line their
|
|
491
|
+
* labels up: the mark's box exists whether or not the mark is drawn, so a
|
|
492
|
+
* list of options does not shift by sixteen pixels as one is chosen.
|
|
493
|
+
*/
|
|
494
|
+
[data-ui='listbox'] :is([data-part='indicator'], [data-part='icon']) {
|
|
495
|
+
/* One line box tall, so a mark beside a label that wrapped onto three lines
|
|
496
|
+
sits on the first of them rather than in the middle of the option. */
|
|
497
|
+
block-size: 1lh;
|
|
498
|
+
grid-column: 1;
|
|
499
|
+
grid-row: 1;
|
|
500
|
+
inline-size: var(--_icon-size);
|
|
501
|
+
justify-self: center;
|
|
502
|
+
margin-inline-end: var(--_column-gap);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* The tick is the library's own `Glyph`, an `<svg>` the binding draws inside
|
|
507
|
+
* an `icon` box, sized here to the column so the mark and a consumer's icon
|
|
508
|
+
* in the same column are one size.
|
|
509
|
+
*/
|
|
510
|
+
[data-ui='listbox'] [data-part='indicator'] {
|
|
511
|
+
--slotted-icon-size: var(--_icon-size);
|
|
512
|
+
|
|
513
|
+
align-items: center;
|
|
514
|
+
display: flex;
|
|
515
|
+
justify-content: center;
|
|
516
|
+
opacity: 0;
|
|
517
|
+
transition: opacity var(--slotted-duration-short) var(--slotted-easing-standard);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
[data-ui='listbox'] [data-part='option'][data-selected] > [data-part='indicator'] {
|
|
521
|
+
opacity: 1;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* The option's own artwork, in the mark column. The box holds content and
|
|
526
|
+
* paints none: a `Glyph` in an `Icon` box, or an element of the consumer's
|
|
527
|
+
* own, centred and sized from the column, as the tick is.
|
|
528
|
+
*/
|
|
529
|
+
[data-ui='listbox'] [data-part='icon'] {
|
|
530
|
+
--slotted-icon-size: var(--_icon-size);
|
|
531
|
+
|
|
532
|
+
align-items: center;
|
|
533
|
+
display: flex;
|
|
534
|
+
justify-content: center;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/* Column two, and the only column that gives way. */
|
|
538
|
+
[data-ui='listbox'] [data-part='label'] {
|
|
539
|
+
grid-column: 2;
|
|
540
|
+
grid-row: 1;
|
|
541
|
+
min-inline-size: 0;
|
|
542
|
+
overflow-wrap: anywhere;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
[data-ui='listbox'] [data-part='description'] {
|
|
546
|
+
color: var(--_muted);
|
|
547
|
+
font-size: var(--_secondary-font-size);
|
|
548
|
+
grid-column: 2;
|
|
549
|
+
grid-row: 2;
|
|
550
|
+
line-height: var(--slotted-line-height-sm, 1.45);
|
|
551
|
+
min-inline-size: 0;
|
|
552
|
+
overflow-wrap: anywhere;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
[data-ui='listbox']
|
|
556
|
+
[data-part='option']:is([data-disabled], [data-selected])
|
|
557
|
+
> [data-part='description'] {
|
|
558
|
+
color: inherit;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Column three: the value beside the name — a count, a price, a status word.
|
|
563
|
+
* `menu` calls the same column `shortcut` because a menu's third column holds
|
|
564
|
+
* a key binding; naming it that here would be a lie on every listbox in the
|
|
565
|
+
* library. Never narrower than its own content, so a long label wraps in
|
|
566
|
+
* column two rather than pushing the value off the surface.
|
|
567
|
+
*/
|
|
568
|
+
[data-ui='listbox'] [data-part='meta'] {
|
|
569
|
+
color: var(--_muted);
|
|
570
|
+
font-variant-numeric: tabular-nums;
|
|
571
|
+
grid-column: 3;
|
|
572
|
+
grid-row: 1;
|
|
573
|
+
justify-self: end;
|
|
574
|
+
margin-inline-start: var(--slotted-space-6, 1rem);
|
|
575
|
+
white-space: nowrap;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
[data-ui='listbox']
|
|
579
|
+
[data-part='option']:is([data-disabled], [data-selected])
|
|
580
|
+
> [data-part='meta'] {
|
|
581
|
+
color: inherit;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/* ── Empty, and the row that asks for more ────────────────────────────── */
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Both keep the surface's own measure rather than collapsing to a two-pixel
|
|
588
|
+
* box, which is what a consumer sees flash when the state is not painted at
|
|
589
|
+
* all. The message lives inside the listbox: outside it the reader hears
|
|
590
|
+
* "listbox, 0 items" and then nothing.
|
|
591
|
+
*/
|
|
592
|
+
[data-ui='listbox'] :is([data-part='empty'], [data-part='loader']) {
|
|
593
|
+
align-content: center;
|
|
594
|
+
color: var(--_muted);
|
|
595
|
+
display: grid;
|
|
596
|
+
grid-column: 1 / -1;
|
|
597
|
+
min-block-size: var(--_option-height);
|
|
598
|
+
padding-block: var(--slotted-space-5, 0.75rem);
|
|
599
|
+
padding-inline: var(--_option-inset);
|
|
600
|
+
text-align: center;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* The row at the end of a page that has not arrived. It is an `option` by
|
|
605
|
+
* role — a listbox owns nothing else — so it carries the same geometry and
|
|
606
|
+
* says what it is with a pulse rather than with a second element.
|
|
607
|
+
*/
|
|
608
|
+
[data-ui='listbox'] [data-part='loader'] {
|
|
609
|
+
animation: slotted-listbox-loader var(--slotted-duration-loop) ease-in-out infinite;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
@keyframes slotted-listbox-loader {
|
|
613
|
+
0%,
|
|
614
|
+
100% {
|
|
615
|
+
opacity: 0.45;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
50% {
|
|
619
|
+
opacity: 1;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* An animation that repeats forever has to stop rather than hurry: shortening
|
|
625
|
+
* it is insufficient, which is why the loop durations do not collapse with
|
|
626
|
+
* the rest of the motion scale.
|
|
627
|
+
*/
|
|
628
|
+
@media (prefers-reduced-motion: reduce) {
|
|
629
|
+
[data-ui='listbox'] [data-part='loader'] {
|
|
630
|
+
animation: none;
|
|
631
|
+
opacity: 0.7;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/* ── Forced colours ───────────────────────────────────────────────────── */
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* The two things the theme cannot reach. Every tone surface becomes `Canvas`
|
|
639
|
+
* under forced colours, so both the selection and the highlight — which are
|
|
640
|
+
* this family's whole state vocabulary, not decoration — would disappear and
|
|
641
|
+
* leave a reader unable to tell what they have chosen from what they are on.
|
|
642
|
+
*/
|
|
643
|
+
@media (forced-colors: active) {
|
|
644
|
+
[data-ui='listbox'] [data-part='option'][data-selected] {
|
|
645
|
+
background-color: Highlight;
|
|
646
|
+
color: HighlightText;
|
|
647
|
+
forced-color-adjust: none;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
[data-ui='listbox'] [data-part='option'][data-disabled] {
|
|
651
|
+
color: GrayText;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/*
|
|
656
|
+
* Forced colours remove every box shadow, and the edge with them. The width
|
|
657
|
+
* comes back as an outline inside the border, and where focus draws the ring
|
|
658
|
+
* on the same box an invalid ring is double, which a valid one is not.
|
|
659
|
+
*/
|
|
660
|
+
@media (forced-colors: active) {
|
|
661
|
+
[data-ui='listbox'][data-variant='framed'][data-invalid]:not([data-disabled]) {
|
|
662
|
+
outline: var(--_invalid-edge-width) solid CanvasText;
|
|
663
|
+
outline-offset: calc(-1 * (var(--_border-width) + var(--_invalid-edge-width)));
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
[data-ui='listbox'][data-variant='framed'][data-invalid]:not(
|
|
667
|
+
[data-disabled]
|
|
668
|
+
)[data-focus-mode='activedescendant']:focus-visible {
|
|
669
|
+
outline: calc(var(--slotted-focus-ring-width, 2px) + var(--_invalid-edge-width)) double
|
|
670
|
+
var(--slotted-focus-ring-color, Highlight);
|
|
671
|
+
outline-offset: var(--slotted-focus-ring-offset, 2px);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|