@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,1482 @@
|
|
|
1
|
+
@layer slotted.components {
|
|
2
|
+
/**
|
|
3
|
+
* ── The frame ──────────────────────────────────────────────────────────
|
|
4
|
+
*
|
|
5
|
+
* The root is the frame and the scroller is one element in. Both boxes are
|
|
6
|
+
* needed and each answers a different question: `position: sticky` resolves
|
|
7
|
+
* against the nearest scrolling ancestor, which is the scroller either way,
|
|
8
|
+
* while what the root holds is everything that must **not** move when the
|
|
9
|
+
* table is scrolled sideways — the border, the radius, the clip, and the
|
|
10
|
+
* footer where a paged listing puts its control. Read the other way round,
|
|
11
|
+
* with the root scrolling, a framed table and its pagination were two boxes
|
|
12
|
+
* with a seam between them.
|
|
13
|
+
*
|
|
14
|
+
* `prose` makes the same division for the same reason, and every token below
|
|
15
|
+
* is declared here so both boxes and every cell read one set.
|
|
16
|
+
*/
|
|
17
|
+
[data-ui='table'][data-part='root'] {
|
|
18
|
+
/* Geometry. Every value is the control scale's, because a row is a
|
|
19
|
+
control-sized thing: one theme decision retunes the rows, the buttons and
|
|
20
|
+
the fields together, and `@media (pointer: coarse)` grows all three at
|
|
21
|
+
once without this family writing a rule. `list-item` took the same
|
|
22
|
+
decision for the same reason, and `docs/product/prd.md` §13.1 names
|
|
23
|
+
`table.row.height` as a density-derived value — which this is. */
|
|
24
|
+
--_row-min-block: var(
|
|
25
|
+
--slotted-table-row-min-block-size,
|
|
26
|
+
var(--slotted-control-height-md, 34px)
|
|
27
|
+
);
|
|
28
|
+
--_font-size: var(--slotted-table-font-size, var(--slotted-control-font-size-md, 13px));
|
|
29
|
+
--_icon-size: var(--slotted-table-icon-size, var(--slotted-control-icon-size-md, 16px));
|
|
30
|
+
--_gap: var(--slotted-table-gap, var(--slotted-control-gap-md, 8px));
|
|
31
|
+
--_pad-inline: var(
|
|
32
|
+
--slotted-table-cell-padding-inline,
|
|
33
|
+
var(--slotted-control-padding-inline-md, 13px)
|
|
34
|
+
);
|
|
35
|
+
/**
|
|
36
|
+
* What is left of the row's height around one line of text, so a one-line
|
|
37
|
+
* row is exactly the control height at every density and a row of two lines
|
|
38
|
+
* grows by its second line and nothing else. A floor of one space step keeps
|
|
39
|
+
* a row whose text is taller than the control from losing its inset.
|
|
40
|
+
*/
|
|
41
|
+
--_pad-block: var(
|
|
42
|
+
--slotted-table-cell-padding-block,
|
|
43
|
+
max(
|
|
44
|
+
var(--slotted-space-2, 0.25rem),
|
|
45
|
+
calc((var(--_row-min-block) - var(--_font-size) * var(--slotted-line-height-md, 1.5)) / 2)
|
|
46
|
+
)
|
|
47
|
+
);
|
|
48
|
+
/* One line of the table's own text, which is what a mark beside a value is
|
|
49
|
+
aligned against — the row is taller than one line whenever a value wraps,
|
|
50
|
+
and a control centred in the cell would then float away from the line it
|
|
51
|
+
belongs to. */
|
|
52
|
+
--_line: calc(var(--_font-size) * var(--slotted-line-height-md, 1.5));
|
|
53
|
+
--_radius: var(--slotted-table-radius, var(--slotted-radius-md, 6px));
|
|
54
|
+
--_rule: var(--slotted-table-border-color, var(--slotted-border-subtle, ButtonBorder));
|
|
55
|
+
--_rule-strong: var(
|
|
56
|
+
--slotted-table-border-color-strong,
|
|
57
|
+
var(--slotted-border-strong, ButtonBorder)
|
|
58
|
+
);
|
|
59
|
+
--_rule-width: var(--slotted-table-border-width, var(--slotted-border-width-thin, 1px));
|
|
60
|
+
|
|
61
|
+
/* Ink and ground. The row's ground is stated rather than left transparent,
|
|
62
|
+
because a sticky cell inherits it to cover the rows sliding under it —
|
|
63
|
+
and a transparent sticky cell shows the table through itself. */
|
|
64
|
+
--_text: var(--slotted-table-color, var(--slotted-text, CanvasText));
|
|
65
|
+
--_muted: var(--slotted-table-color-muted, var(--slotted-text-muted, GrayText));
|
|
66
|
+
/* The ground a raised container publishes (a card, a section on a surface),
|
|
67
|
+
else the page's surface. */
|
|
68
|
+
--_surface: var(--slotted-table-surface, var(--slotted-ground, var(--slotted-surface, Canvas)));
|
|
69
|
+
/**
|
|
70
|
+
* The header band, mixed from the ink rather than taken from a tone. Read
|
|
71
|
+
* in light, `--slotted-tone-secondary-surface` was both too blue and too
|
|
72
|
+
* dark for a band that runs the width of the page: the tone is calibrated
|
|
73
|
+
* to enclose a chip, and a chip is a few characters wide.
|
|
74
|
+
*
|
|
75
|
+
* Mixing keeps the band neutral, keeps its direction right in every scheme —
|
|
76
|
+
* darker than the page in light, lighter in dark — and puts it one step
|
|
77
|
+
* above the stripe, which is mixed the same way at a smaller share. The
|
|
78
|
+
* stripe and the band are then one idea at two strengths instead of two
|
|
79
|
+
* values from two scales that happen to differ.
|
|
80
|
+
*/
|
|
81
|
+
--_head-surface: var(
|
|
82
|
+
--slotted-table-header-surface,
|
|
83
|
+
color-mix(in oklab, var(--slotted-text, CanvasText) 8%, var(--_surface))
|
|
84
|
+
);
|
|
85
|
+
/**
|
|
86
|
+
* The stripe, and it is mixed rather than taken from a scale. Read at 420
|
|
87
|
+
* and at 720 in both schemes, `--slotted-surface-sunken` was invisible under
|
|
88
|
+
* a row of text — a zebra stripe exists to carry the eye across a wide row,
|
|
89
|
+
* and one that cannot be seen at a glance is a rule the family pays for and
|
|
90
|
+
* nobody reads. The foundation's own answer where no token holds a ground is
|
|
91
|
+
* to mix one from an ink at a small share, which is what
|
|
92
|
+
* `--slotted-highlight-tint-opacity` and the selection halo do.
|
|
93
|
+
*
|
|
94
|
+
* It is mixed into the surface rather than into transparency, because a
|
|
95
|
+
* sticky cell copies the row's own ground with `background-color: inherit`
|
|
96
|
+
* and a translucent one would show the scrolled rows through the column
|
|
97
|
+
* that exists to cover them.
|
|
98
|
+
*/
|
|
99
|
+
--_stripe: var(
|
|
100
|
+
--slotted-table-row-surface-stripe,
|
|
101
|
+
color-mix(in oklab, var(--slotted-text, CanvasText) 5%, var(--_surface))
|
|
102
|
+
);
|
|
103
|
+
/* `docs/product/prd.md` §8 names `table.row.hover.background` as a decision an
|
|
104
|
+
application retunes on its own. It is one token, over the tone step every
|
|
105
|
+
neutral row in the library hovers with. */
|
|
106
|
+
--_hover: var(
|
|
107
|
+
--slotted-table-row-surface-hover,
|
|
108
|
+
var(--slotted-tone-secondary-subtle-hover, ButtonFace)
|
|
109
|
+
);
|
|
110
|
+
--_selected: var(
|
|
111
|
+
--slotted-table-row-surface-selected,
|
|
112
|
+
var(--slotted-tone-accent-surface, Canvas)
|
|
113
|
+
);
|
|
114
|
+
--_selected-hover: var(
|
|
115
|
+
--slotted-table-row-surface-selected-hover,
|
|
116
|
+
var(--slotted-tone-accent-subtle-hover, ButtonFace)
|
|
117
|
+
);
|
|
118
|
+
/* The ink of a selected row. A ground alone is a state `high-contrast`
|
|
119
|
+
cannot show — every tone ground is the page's own white there — and R1
|
|
120
|
+
forbids the rail two families reached for instead, so the pair that says
|
|
121
|
+
it is the ground and the ink, with the checkbox as the mark. */
|
|
122
|
+
--_selected-text: var(
|
|
123
|
+
--slotted-table-row-color-selected,
|
|
124
|
+
var(--slotted-tone-accent-text, CanvasText)
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
background-color: var(--_surface);
|
|
128
|
+
box-sizing: border-box;
|
|
129
|
+
color: var(--_text);
|
|
130
|
+
display: block;
|
|
131
|
+
/**
|
|
132
|
+
* Every step the sticky parts take is taken inside this box.
|
|
133
|
+
*
|
|
134
|
+
* A pinned column stands at `--slotted-z-index-sticky` and the header a
|
|
135
|
+
* step above it, and those steps are measured against each other — not
|
|
136
|
+
* against the page. Without a stacking context of its own the table hands
|
|
137
|
+
* them to whatever stacks outside it, and under `stickyTo="page"` a pinned
|
|
138
|
+
* column scrolling past an application's own bar painted straight over it.
|
|
139
|
+
* `isolation` contains the steps and creates no containing block, so
|
|
140
|
+
* nothing sticky changes what it resolves against.
|
|
141
|
+
*/
|
|
142
|
+
isolation: isolate;
|
|
143
|
+
font-family: var(--slotted-table-font-family, var(--slotted-control-font-family));
|
|
144
|
+
font-size: var(--_font-size);
|
|
145
|
+
line-height: var(--slotted-line-height-md, 1.5);
|
|
146
|
+
max-inline-size: 100%;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
[data-ui='table'][data-variant='framed'] {
|
|
150
|
+
border: var(--_rule-width) solid var(--_rule);
|
|
151
|
+
border-radius: var(--_radius);
|
|
152
|
+
/**
|
|
153
|
+
* A framed table is a box standing on the page, and it is separated from
|
|
154
|
+
* the page by the surface step, `--slotted-shadow-xs` — the same geometry
|
|
155
|
+
* a framed control carries at rest, at the ink the elevation scale gives a
|
|
156
|
+
* surface. `ruled` and `plain` draw no box and cast nothing.
|
|
157
|
+
*/
|
|
158
|
+
box-shadow: var(--slotted-table-shadow, var(--slotted-shadow-xs, none));
|
|
159
|
+
/**
|
|
160
|
+
* `clip` and not `hidden`, and the difference is the whole of `stickyTo`.
|
|
161
|
+
*
|
|
162
|
+
* Both clip what the scroller paints into the corners — a row's ground is
|
|
163
|
+
* square and would otherwise fill the radius — but `hidden` makes the box a
|
|
164
|
+
* **scroll container**, and a sticky element resolves against the nearest
|
|
165
|
+
* one of those. A framed table asking its header to stick to the page got a
|
|
166
|
+
* header that stuck to the frame instead, and a frame with no height holds
|
|
167
|
+
* nothing: the header simply left with the rows. `clip` clips without
|
|
168
|
+
* scrolling, so the nearest scroll container stays whatever the consumer
|
|
169
|
+
* meant it to be.
|
|
170
|
+
*/
|
|
171
|
+
overflow: clip;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* ── The scroller ───────────────────────────────────────────────────────
|
|
176
|
+
*
|
|
177
|
+
* The region wiring — `role="region"`, `tabindex="0"` and a name — is written
|
|
178
|
+
* by the binding and only while the content overflows, measured by
|
|
179
|
+
* `@slotted/core/overflow`. A tab stop on a table that fits makes a reader
|
|
180
|
+
* press Tab for nothing, and a wide table with no tab stop is unreachable
|
|
181
|
+
* without a pointer.
|
|
182
|
+
*/
|
|
183
|
+
[data-ui='table'] [data-part='scroll'] {
|
|
184
|
+
/* `overscroll-behavior-inline: contain` keeps a sideways flick inside the
|
|
185
|
+
table instead of walking the page back a step, which is the gesture a
|
|
186
|
+
wide table on a trackpad produces constantly. */
|
|
187
|
+
overflow: auto;
|
|
188
|
+
overscroll-behavior-inline: contain;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* The ring is drawn inside, because a scroller flush against the frame would
|
|
192
|
+
otherwise draw half its ring outside it. */
|
|
193
|
+
[data-ui='table'] [data-part='scroll']:focus-visible {
|
|
194
|
+
outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
|
|
195
|
+
outline-offset: calc(var(--slotted-focus-ring-offset, 2px) * -1);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* ── The table ──────────────────────────────────────────────────────────
|
|
200
|
+
*
|
|
201
|
+
* `border-collapse: separate` rather than `collapse`, and the two reasons are
|
|
202
|
+
* the two features above: a collapsed border is drawn on the table's own box
|
|
203
|
+
* and a sticky cell leaves its borders behind, and a collapsed table refuses
|
|
204
|
+
* a border radius. The rules are therefore drawn as one edge on each cell,
|
|
205
|
+
* never as a border on the row.
|
|
206
|
+
*/
|
|
207
|
+
[data-ui='table'] [data-part='table'] {
|
|
208
|
+
border-collapse: separate;
|
|
209
|
+
border-spacing: 0;
|
|
210
|
+
/* The table fills the scroller and grows past it when its columns need
|
|
211
|
+
more; `min-inline-size: 100%` is what makes both true at once. */
|
|
212
|
+
inline-size: max-content;
|
|
213
|
+
min-inline-size: 100%;
|
|
214
|
+
text-align: start;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
[data-ui='table'][data-layout='fixed'] [data-part='table'] {
|
|
218
|
+
inline-size: 100%;
|
|
219
|
+
table-layout: fixed;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* ── The caption ────────────────────────────────────────────────────────
|
|
224
|
+
*
|
|
225
|
+
* The table's name, and the element the platform already associates with it.
|
|
226
|
+
* A caption is the accessible name whether it is read or hidden, so a
|
|
227
|
+
* consumer who hides it with `visually-hidden` loses the picture and keeps
|
|
228
|
+
* the name.
|
|
229
|
+
*/
|
|
230
|
+
[data-ui='table'] [data-part='caption'] {
|
|
231
|
+
caption-side: block-start;
|
|
232
|
+
color: var(--_muted);
|
|
233
|
+
font-size: var(--slotted-font-size-xs, 0.75rem);
|
|
234
|
+
padding-block: var(--_pad-block);
|
|
235
|
+
padding-inline: var(--_pad-inline);
|
|
236
|
+
text-align: start;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* ── The header ─────────────────────────────────────────────────────────
|
|
241
|
+
*
|
|
242
|
+
* A column header is what a screen reader repeats for every cell under it, so
|
|
243
|
+
* it is set in the page's own ink rather than in the muted step: a header a
|
|
244
|
+
* reader has to look for twice is a header that has stopped naming anything.
|
|
245
|
+
*/
|
|
246
|
+
/* The chooser's header is a `selection` cell and not a `column-header`, so
|
|
247
|
+
every rule naming only the second stopped at it: the band's own line under
|
|
248
|
+
the head broke exactly over the checkbox. */
|
|
249
|
+
/* Two selectors rather than one `:is()`: `:is()` takes the specificity of
|
|
250
|
+
its strongest argument, and the selection's two-part selector lifted the
|
|
251
|
+
column header above the alignment rule that comes after it — a header
|
|
252
|
+
carrying data-align="end" computed text-align: start, so a numeric
|
|
253
|
+
column's digits ended at one edge and their name at the other. */
|
|
254
|
+
[data-ui='table'] [data-part='column-header'],
|
|
255
|
+
[data-ui='table'] [data-part='head'] [data-part='selection'] {
|
|
256
|
+
background-color: var(--_head-surface);
|
|
257
|
+
border-block-end: var(--_rule-width) solid var(--_rule-strong);
|
|
258
|
+
color: var(--_text);
|
|
259
|
+
font-weight: var(--slotted-font-weight-semibold, 600);
|
|
260
|
+
padding-block: var(--_pad-block);
|
|
261
|
+
padding-inline: var(--_pad-inline);
|
|
262
|
+
/* Positioned here and not beside the slot that needs it. The actions slot is
|
|
263
|
+
absolute inside the header, so the header has to be a containing block —
|
|
264
|
+
and a `position` written later, next to that slot, would land after the
|
|
265
|
+
sticky rules and quietly turn a sticky header back into a static one.
|
|
266
|
+
That is exactly what happened to the one header carrying a filter. */
|
|
267
|
+
position: relative;
|
|
268
|
+
text-align: start;
|
|
269
|
+
/* A header wraps rather than truncating: it is two or three words, and the
|
|
270
|
+
one thing a reader cannot recover is the name of the column they are
|
|
271
|
+
reading. */
|
|
272
|
+
vertical-align: bottom;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
[data-ui='table'][data-variant='plain'] [data-part='column-header'] {
|
|
276
|
+
background-color: transparent;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* ── Rows and cells ─────────────────────────────────────────────────────
|
|
281
|
+
*/
|
|
282
|
+
[data-ui='table'] [data-part='row'] {
|
|
283
|
+
background-color: var(--_surface);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* ── Where a cell's content sits in its row ─────────────────────────────
|
|
288
|
+
*
|
|
289
|
+
* The middle, because a row is as tall as its tallest cell: a one-line value
|
|
290
|
+
* beside a three-line one reads as belonging to the same record when both are
|
|
291
|
+
* centred, and reads as hanging from the ceiling when both are not. It is the
|
|
292
|
+
* answer Carbon, AG Grid and Material each arrive at, and the answer a table
|
|
293
|
+
* of one-line rows cannot tell apart.
|
|
294
|
+
*
|
|
295
|
+
* The other two are real, so the axis names all three rather than a token
|
|
296
|
+
* naming one. `start` is what a table of paragraphs wants — a cell of prose
|
|
297
|
+
* beside a date wants the date on the first line, not floating beside the
|
|
298
|
+
* middle of the paragraph. A table states it once and a cell overrides it,
|
|
299
|
+
* because the cell that wants the other answer is usually one: the actions
|
|
300
|
+
* pinned at the end of a tall row.
|
|
301
|
+
*/
|
|
302
|
+
[data-ui='table'] {
|
|
303
|
+
--_cell-align: middle;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
[data-ui='table'][data-cell-align='start'] {
|
|
307
|
+
--_cell-align: top;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
[data-ui='table'][data-cell-align='end'] {
|
|
311
|
+
--_cell-align: bottom;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
[data-ui='table'] [data-part='cell'],
|
|
315
|
+
[data-ui='table'] [data-part='row-header'] {
|
|
316
|
+
padding-block: var(--_pad-block);
|
|
317
|
+
padding-inline: var(--_pad-inline);
|
|
318
|
+
text-align: start;
|
|
319
|
+
vertical-align: var(--_cell-align);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
[data-ui='table'] [data-block-align='start'] {
|
|
323
|
+
vertical-align: top;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
[data-ui='table'] [data-block-align='center'] {
|
|
327
|
+
vertical-align: middle;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
[data-ui='table'] [data-block-align='end'] {
|
|
331
|
+
vertical-align: bottom;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* ── A control in a cell ────────────────────────────────────────────────
|
|
336
|
+
*
|
|
337
|
+
* A cell holding a `plain` control gives its inset to the control, the way
|
|
338
|
+
* the header gives its inset to `sort`: `padding-inline: 0` here, and the
|
|
339
|
+
* control's own padding tokens set to the cell's inset, so the edited value
|
|
340
|
+
* stands where the read value stood, under the column's name. With both
|
|
341
|
+
* insets kept, the value sat one inset in from the header — the picture the
|
|
342
|
+
* `in-a-row` scene of `text-field` showed. `--_pad-inline` resolves on the
|
|
343
|
+
* root and is inherited resolved, so redefining the token it reads does not
|
|
344
|
+
* feed back into it.
|
|
345
|
+
*
|
|
346
|
+
* The block inset moves too, and it moves into the control's height: the
|
|
347
|
+
* control is exactly the row's minimum block size, whatever size step the
|
|
348
|
+
* consumer wrote, so a row with a control in it is as tall as a row without
|
|
349
|
+
* one. Read with the block padding kept, a `sm` control stood in a
|
|
350
|
+
* comfortable row ten pixels taller than the rows around it — the reflow
|
|
351
|
+
* the `plain` variant exists to avoid. The control's width stays the
|
|
352
|
+
* control's own, as in a form.
|
|
353
|
+
*
|
|
354
|
+
* The selector names the fifteen `plain` field families
|
|
355
|
+
* `plain-variant.test.mjs` holds, and no other: a `kbd`, a `listbox`, a
|
|
356
|
+
* `timeline` or a nested table has a `plain` variant of its own and is not a
|
|
357
|
+
* control. `table.styles.test.mjs` holds the two lists against each other.
|
|
358
|
+
*/
|
|
359
|
+
[data-ui='table']
|
|
360
|
+
:is([data-part='cell'], [data-part='row-header']):has(
|
|
361
|
+
> [data-part='root'][data-variant='plain']:is(
|
|
362
|
+
[data-ui='select'],
|
|
363
|
+
[data-ui='combobox'],
|
|
364
|
+
[data-ui='multi-select'],
|
|
365
|
+
[data-ui='text-field'],
|
|
366
|
+
[data-ui='textarea'],
|
|
367
|
+
[data-ui='number-field'],
|
|
368
|
+
[data-ui='currency-field'],
|
|
369
|
+
[data-ui='date-field'],
|
|
370
|
+
[data-ui='time-field'],
|
|
371
|
+
[data-ui='datetime-field'],
|
|
372
|
+
[data-ui='tags-input'],
|
|
373
|
+
[data-ui='date-range-picker'],
|
|
374
|
+
[data-ui='time-range-picker'],
|
|
375
|
+
[data-ui='datetime-range-picker'],
|
|
376
|
+
[data-ui='entity-picker']
|
|
377
|
+
)
|
|
378
|
+
) {
|
|
379
|
+
--slotted-control-padding-inline-sm: var(--_pad-inline);
|
|
380
|
+
--slotted-control-padding-inline-md: var(--_pad-inline);
|
|
381
|
+
--slotted-control-padding-inline-lg: var(--_pad-inline);
|
|
382
|
+
--slotted-control-height-sm: var(--_row-min-block);
|
|
383
|
+
--slotted-control-height-md: var(--_row-min-block);
|
|
384
|
+
--slotted-control-height-lg: var(--_row-min-block);
|
|
385
|
+
|
|
386
|
+
padding: 0;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/* A row header names the record, so it carries the weight one step above the
|
|
390
|
+
values beside it and nothing else. It states no colour of its own: read
|
|
391
|
+
against the row's ink, a selected record's name stayed the page's black
|
|
392
|
+
while every value beside it turned accent, and a disabled row's name stayed
|
|
393
|
+
at full strength — the two states the row paints and this cell refused. */
|
|
394
|
+
[data-ui='table'] [data-part='row-header'] {
|
|
395
|
+
font-weight: var(--slotted-font-weight-medium, 500);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/* The rule between two rows, drawn on the cells because the row cannot carry
|
|
399
|
+
it under `border-collapse: separate`. The last row of the body gives its
|
|
400
|
+
rule up unless a footer follows, so a `ruled` table does not end in a line
|
|
401
|
+
with nothing under it. */
|
|
402
|
+
[data-ui='table']:is([data-variant='ruled'], [data-variant='framed'])
|
|
403
|
+
[data-part='body']
|
|
404
|
+
> [data-part='row']
|
|
405
|
+
> * {
|
|
406
|
+
border-block-end: var(--_rule-width) solid var(--_rule);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
[data-ui='table']:is([data-variant='ruled'], [data-variant='framed'])
|
|
410
|
+
[data-part='body']
|
|
411
|
+
> [data-part='row']:last-child
|
|
412
|
+
> * {
|
|
413
|
+
border-block-end-color: transparent;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/* The stripe is the weakest ground the family paints and is declared before
|
|
417
|
+
every state, so a selected or hovered stripe reads as selected or hovered.
|
|
418
|
+
`nth-child(even)` counts rows the family renders; a detail row is a row of
|
|
419
|
+
the same body, so the stripe follows the record it belongs to rather than
|
|
420
|
+
alternating around it — see the detail rule below. */
|
|
421
|
+
[data-ui='table'][data-striped] [data-part='body'] > [data-part='row']:nth-child(even) {
|
|
422
|
+
background-color: var(--_stripe);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* ── The states of a row ────────────────────────────────────────────────
|
|
427
|
+
*
|
|
428
|
+
* In the order `STATE_PRECEDENCE` declares them, weakest last, so a row that
|
|
429
|
+
* is two things at once shows the stronger one. `disabled` is the guard on
|
|
430
|
+
* every pointer state rather than a rule of its own: a row a consumer has
|
|
431
|
+
* disabled does not answer the pointer at all.
|
|
432
|
+
*/
|
|
433
|
+
[data-ui='table'] [data-part='body'] > [data-part='row']:not([data-disabled]):hover {
|
|
434
|
+
background-color: var(--_hover);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* A selected row is the accent ground with the accent ink, and it carries no
|
|
439
|
+
* rail: R1 is a decision of this repository — a band of a few pixels on the
|
|
440
|
+
* start edge may not say `selected`, and the state is said by the ground, the
|
|
441
|
+
* ink and the checkbox that owns it. `list-item` and `listbox` each drew one
|
|
442
|
+
* and each gave it up.
|
|
443
|
+
*
|
|
444
|
+
* The ink is what carries the state where the ground cannot: every tone
|
|
445
|
+
* ground is the page's own white in `high-contrast`, and the checkbox's mark
|
|
446
|
+
* is what survives forced colours, which is where WCAG 2.2 SC 1.4.1 puts the
|
|
447
|
+
* requirement anyway.
|
|
448
|
+
*/
|
|
449
|
+
[data-ui='table'] [data-part='body'] > [data-part='row'][data-selected] {
|
|
450
|
+
background-color: var(--_selected);
|
|
451
|
+
color: var(--_selected-text);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
[data-ui='table']
|
|
455
|
+
[data-part='body']
|
|
456
|
+
> [data-part='row'][data-selected]:not([data-disabled]):hover {
|
|
457
|
+
background-color: var(--_selected-hover);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
[data-ui='table'] [data-part='row'][data-disabled] {
|
|
461
|
+
color: var(--slotted-disabled-foreground, GrayText);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* ── Alignment and numerals ─────────────────────────────────────────────
|
|
466
|
+
*
|
|
467
|
+
* Two facts rather than one axis: a number is set in tabular figures
|
|
468
|
+
* whichever way it is aligned, and a right-aligned label is not a number.
|
|
469
|
+
* `start` and `end` are the logical values, so a column aligned to the end is
|
|
470
|
+
* on the right in English and on the left in Arabic.
|
|
471
|
+
*/
|
|
472
|
+
[data-ui='table'] [data-align='start'] {
|
|
473
|
+
text-align: start;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
[data-ui='table'] [data-align='center'] {
|
|
477
|
+
text-align: center;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
[data-ui='table'] [data-align='end'] {
|
|
481
|
+
text-align: end;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/* A ten under a nine is only a column if the digits are the same width. The
|
|
485
|
+
alignment is inferred once, and only where the consumer stated none. */
|
|
486
|
+
[data-ui='table'] [data-numeric] {
|
|
487
|
+
font-variant-numeric: tabular-nums;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
[data-ui='table'] [data-numeric]:not([data-align]) {
|
|
491
|
+
text-align: end;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* ── What a long value does ─────────────────────────────────────────────
|
|
496
|
+
*
|
|
497
|
+
* Three treatments, and none of them removes the value: the briefing's
|
|
498
|
+
* decision is that a truncated string stays readable to assistive technology
|
|
499
|
+
* and reachable in full.
|
|
500
|
+
*/
|
|
501
|
+
[data-ui='table'] [data-text='wrap'] {
|
|
502
|
+
/* An identifier, a hash or a URL is one unbroken token, and every surveyed
|
|
503
|
+
table lets it set the column's width and push the rest of the table
|
|
504
|
+
sideways. */
|
|
505
|
+
overflow-wrap: anywhere;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
[data-ui='table'] [data-text='nowrap'] {
|
|
509
|
+
white-space: nowrap;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
[data-ui='table'] [data-text='truncate'] {
|
|
513
|
+
overflow: hidden;
|
|
514
|
+
text-overflow: ellipsis;
|
|
515
|
+
white-space: nowrap;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* ── Sticky ─────────────────────────────────────────────────────────────
|
|
520
|
+
*
|
|
521
|
+
* The header sticks to the root, which is the scroller. Each sticky cell
|
|
522
|
+
* carries its own ground, because what slides under it is the rest of the
|
|
523
|
+
* table: a transparent sticky cell shows the rows through itself.
|
|
524
|
+
*
|
|
525
|
+
* The three stacking steps are one decision. A header cell that is also a
|
|
526
|
+
* sticky column is both at once, and with a single step the header slides
|
|
527
|
+
* under the pinned column or the pinned column over the header — the corner
|
|
528
|
+
* is where this always goes wrong.
|
|
529
|
+
*/
|
|
530
|
+
/**
|
|
531
|
+
* Where the header stops, and what it stops inside.
|
|
532
|
+
*
|
|
533
|
+
* `scroll` is the default: the header sticks to the family's own scroller,
|
|
534
|
+
* which is what keeps it right inside a drawer, a dialog or a split pane —
|
|
535
|
+
* the box the reader scrolls is the box the header stays in.
|
|
536
|
+
*
|
|
537
|
+
* `page` gives that box up. A sticky element resolves against its nearest
|
|
538
|
+
* scrolling ancestor, so for the header to stay while the *page* scrolls, the
|
|
539
|
+
* family must not be a scrolling ancestor at all — and a box that scrolls one
|
|
540
|
+
* axis scrolls both, because CSS computes a visible axis to `auto` the moment
|
|
541
|
+
* the other is `auto`. So the scroller releases its overflow, and the cost is
|
|
542
|
+
* written where the choice is made: a table wider than the page pushes the
|
|
543
|
+
* page sideways instead of scrolling inside its frame.
|
|
544
|
+
*
|
|
545
|
+
* The offset is where the header stops. An application with a bar of its own
|
|
546
|
+
* sets `--slotted-table-sticky-offset` once, and every table under it stops
|
|
547
|
+
* below the bar rather than under it.
|
|
548
|
+
*/
|
|
549
|
+
[data-ui='table'][data-sticky-header] [data-part='column-header'],
|
|
550
|
+
/* The chooser's own header is a `selection` cell and not a `column-header`,
|
|
551
|
+
and a rule naming only the second left the select-all scrolling away while
|
|
552
|
+
every column beside it stayed. */
|
|
553
|
+
[data-ui='table'][data-sticky-header] [data-part='head'] [data-part='selection'] {
|
|
554
|
+
inset-block-start: var(--slotted-table-sticky-offset, 0);
|
|
555
|
+
position: sticky;
|
|
556
|
+
z-index: calc(var(--slotted-z-index-sticky, 100) + 1);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
[data-ui='table'][data-sticky-to='page'] [data-part='scroll'] {
|
|
560
|
+
overflow: visible;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* The summary, pinned to the other end of the same box.
|
|
565
|
+
*
|
|
566
|
+
* A listing whose total scrolls out of view makes the reader scroll back for
|
|
567
|
+
* the number they came for, so the `tfoot` is held against the bottom the way
|
|
568
|
+
* the `thead` is held against the top — and it answers `stickyTo` the same
|
|
569
|
+
* way, through an offset of its own for a page with a bar across the bottom.
|
|
570
|
+
*
|
|
571
|
+
* It is the `tfoot` that is pinned and not the footer bar: the bar is a
|
|
572
|
+
* sibling of the scroller and never leaves it in the first place.
|
|
573
|
+
*/
|
|
574
|
+
[data-ui='table'][data-sticky-footer] [data-part='summary'] > [data-part='row'] > * {
|
|
575
|
+
inset-block-end: var(--slotted-table-sticky-offset-end, 0);
|
|
576
|
+
position: sticky;
|
|
577
|
+
z-index: calc(var(--slotted-z-index-sticky, 100) + 1);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/* The corner again, at the bottom: a summary cell that is also a pinned
|
|
581
|
+
column takes both insets and the step above both. */
|
|
582
|
+
[data-ui='table'][data-sticky-footer] [data-part='summary'] [data-sticky] {
|
|
583
|
+
z-index: calc(var(--slotted-z-index-sticky, 100) + 2);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
[data-ui='table'] [data-sticky='start'] {
|
|
587
|
+
/* The ground the row is painted with, whatever state it is in: `inherit`
|
|
588
|
+
takes the row's own computed value, so a hovered, striped or selected row
|
|
589
|
+
keeps one colour across the seam. */
|
|
590
|
+
background-color: inherit;
|
|
591
|
+
/**
|
|
592
|
+
* Where the column stops, and it is stated rather than measured. Two
|
|
593
|
+
* columns pinned to the same edge both resolve against the scroller, so
|
|
594
|
+
* without the offset the second is drawn over the first — a checkbox column
|
|
595
|
+
* and the identifier beside it landed on the same pixel. The consumer
|
|
596
|
+
* states how wide the first one is, because measuring it would mean reading
|
|
597
|
+
* the layout during render.
|
|
598
|
+
*/
|
|
599
|
+
inset-inline-start: var(--slotted-table-sticky-inset, 0);
|
|
600
|
+
position: sticky;
|
|
601
|
+
z-index: var(--slotted-z-index-sticky, 100);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
[data-ui='table'] [data-sticky='end'] {
|
|
605
|
+
background-color: inherit;
|
|
606
|
+
inset-inline-end: var(--slotted-table-sticky-inset, 0);
|
|
607
|
+
position: sticky;
|
|
608
|
+
z-index: var(--slotted-z-index-sticky, 100);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
[data-ui='table'][data-sticky-header] [data-part='column-header'][data-sticky],
|
|
612
|
+
[data-ui='table'][data-sticky-header] [data-part='head'] [data-part='selection'][data-sticky] {
|
|
613
|
+
background-color: var(--_head-surface);
|
|
614
|
+
z-index: calc(var(--slotted-z-index-sticky, 100) + 2);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* The edge that says a column is pinned rather than merely first, drawn as a
|
|
619
|
+
* line of its own rather than as a border on the cell.
|
|
620
|
+
*
|
|
621
|
+
* A border would be right if a cell had only one. This one has two — the
|
|
622
|
+
* pinned edge and, in the header, the rule under the band — and where two
|
|
623
|
+
* borders of a box meet the browser mitres them: the corner is split
|
|
624
|
+
* diagonally between the two, which left a visible notch in the header's
|
|
625
|
+
* horizontal rule exactly where the pinned column crossed it. Read at 420
|
|
626
|
+
* pixels on the scrolled scene.
|
|
627
|
+
*
|
|
628
|
+
* As a pseudo-element the line simply crosses the rule, which is what a
|
|
629
|
+
* spreadsheet does and what the eye reads as one continuous column boundary.
|
|
630
|
+
* It is stretched past the cell's own **top** border, so the line is unbroken
|
|
631
|
+
* down the body instead of stopping a pixel short at every row.
|
|
632
|
+
*
|
|
633
|
+
* Past the top and not past the bottom, and the difference is a pixel the
|
|
634
|
+
* reader can see. Overflow beyond the block-end edge is scrollable overflow,
|
|
635
|
+
* so a line hanging a pixel below the last row made the scroller one pixel
|
|
636
|
+
* taller than its table — and at the end of the scroll a pinned summary sat
|
|
637
|
+
* that pixel above the bottom, which reads as a row that shifts as it lands.
|
|
638
|
+
* Overflow beyond the block-start edge is not scrollable, so the same line
|
|
639
|
+
* drawn upwards bridges every row boundary and adds nothing to scroll.
|
|
640
|
+
*/
|
|
641
|
+
/* No `position` here. A pinned cell is already `position: sticky`, which is a
|
|
642
|
+
positioned box, so the line below resolves against it — and a `relative`
|
|
643
|
+
written at this point in the file would land after the sticky rules and
|
|
644
|
+
unpin every pinned column. */
|
|
645
|
+
[data-ui='table'] [data-sticky]::after {
|
|
646
|
+
background-color: var(--slotted-table-border-color, var(--slotted-border-subtle));
|
|
647
|
+
content: '';
|
|
648
|
+
inline-size: var(--_rule-width);
|
|
649
|
+
inset-block: calc(var(--_rule-width) * -1) 0;
|
|
650
|
+
position: absolute;
|
|
651
|
+
z-index: 1;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
[data-ui='table'] [data-sticky='start']::after {
|
|
655
|
+
inset-inline-end: 0;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
[data-ui='table'] [data-sticky='end']::after {
|
|
659
|
+
inset-inline-start: 0;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* The affordance, and it is the pinned column's own edge rather than a fade
|
|
664
|
+
* over the whole scroller. A gradient at the trailing edge would fade the
|
|
665
|
+
* pinned column with everything else, which is the one thing that must stay
|
|
666
|
+
* crisp; a shadow needs an ink no scale holds. The edge darkens exactly while
|
|
667
|
+
* something is hidden under it — `data-overflowing` and the two end
|
|
668
|
+
* attributes come from `@slotted/core/overflow`, which is the same
|
|
669
|
+
* measurement the region wiring is written from.
|
|
670
|
+
*
|
|
671
|
+
* A table with no pinned column keeps the three affordances the platform and
|
|
672
|
+
* the family already give it: a scrollbar, a column visibly cut mid-word at
|
|
673
|
+
* the edge, and a tab stop that scrolls with the arrow keys.
|
|
674
|
+
*/
|
|
675
|
+
/**
|
|
676
|
+
* The pinned column draws **one ordinary rule** and no more.
|
|
677
|
+
*
|
|
678
|
+
* It carried a darker line while something was hidden under it, which is what
|
|
679
|
+
* a spreadsheet does — and read on a real listing it was wrong twice. A table
|
|
680
|
+
* that pins a chooser and the identifier beside it has two seams and only one
|
|
681
|
+
* of them is a boundary, so the reader was told twice; and the boundary line
|
|
682
|
+
* itself, at the weight the affordance needed, read as a wall through a table
|
|
683
|
+
* whose other columns are separated by a hairline.
|
|
684
|
+
*
|
|
685
|
+
* The affordances the reader keeps are the ones the platform and the family
|
|
686
|
+
* already give: a scrollbar, a column visibly cut mid-word at the edge, and a
|
|
687
|
+
* named scrolling region with a tab stop that the arrow keys move.
|
|
688
|
+
*/
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* ── The sort control ───────────────────────────────────────────────────
|
|
692
|
+
*
|
|
693
|
+
* The APG's own sortable example wraps the header's text in a `button`, and
|
|
694
|
+
* so does this: the header cell gives up its inset and the control takes it,
|
|
695
|
+
* so the whole header is the target rather than the words inside it.
|
|
696
|
+
*/
|
|
697
|
+
[data-ui='table'] [data-part='column-header']:has(> [data-part='sort']) {
|
|
698
|
+
padding: 0;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* ── The controls a column carries ──────────────────────────────────────
|
|
703
|
+
*
|
|
704
|
+
* A listing screen puts a filter or a column menu in the header, and the
|
|
705
|
+
* control has to sit inside a cell this family lays out. With the sort
|
|
706
|
+
* control filling the header there was nowhere for a second target to go, so
|
|
707
|
+
* the header becomes a row: the sort control takes the space that is left and
|
|
708
|
+
* the slot holds its controls against the trailing edge. Two targets, never
|
|
709
|
+
* overlapping, and neither of them steals the other's press.
|
|
710
|
+
*
|
|
711
|
+
* The slot holds the control and none of its state. What a filter means and
|
|
712
|
+
* what it leaves is the consumer's query, and `filter-bar` owns the rules.
|
|
713
|
+
*/
|
|
714
|
+
/**
|
|
715
|
+
* One rule rather than three. The reservation was written twice — once on the
|
|
716
|
+
* sort control and once on a cell selected by
|
|
717
|
+
* `:has(> actions):not(:has(> sort))` — and reserving on the cell says it once
|
|
718
|
+
* for both: the sort control fills the cell's content box, so room taken from
|
|
719
|
+
* the cell is room the control never had.
|
|
720
|
+
*
|
|
721
|
+
* The slot is positioned against the cell, and the cell stays a table cell.
|
|
722
|
+
* `display: flex` on a `th` takes it out of the table's own formatting context
|
|
723
|
+
* and the browser wraps it in an anonymous cell: read on an earlier capture,
|
|
724
|
+
* two headers laid out as flex boxes stacked into one column and took the
|
|
725
|
+
* table's geometry with them.
|
|
726
|
+
*
|
|
727
|
+
* The room is stated rather than measured. The controls in it are the
|
|
728
|
+
* consumer's own, so their width is not a fact this family holds, and
|
|
729
|
+
* measuring it would mean reading the layout during render.
|
|
730
|
+
*/
|
|
731
|
+
[data-ui='table'] [data-part='column-header']:has(> [data-part='column-actions']) {
|
|
732
|
+
/**
|
|
733
|
+
* The room is what the slot actually occupies plus one gap: the slot stands
|
|
734
|
+
* `--_pad-inline` in from the cell's edge and its control is one target
|
|
735
|
+
* wide, so a reservation of the target alone is five pixels short of where
|
|
736
|
+
* the control starts. Read on a Status column whose name is nearly as wide
|
|
737
|
+
* as its values, the funnel sat three pixels from the last letter — the two
|
|
738
|
+
* numbers were describing different things and only the wide columns hid it.
|
|
739
|
+
*/
|
|
740
|
+
--_actions-room: var(
|
|
741
|
+
--slotted-table-column-actions-size,
|
|
742
|
+
calc(var(--_pad-inline) + var(--slotted-target-min, 24px) + var(--_gap))
|
|
743
|
+
);
|
|
744
|
+
|
|
745
|
+
padding-inline-end: var(--_actions-room);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
[data-ui='table'] [data-part='column-actions'] {
|
|
749
|
+
align-items: center;
|
|
750
|
+
display: inline-flex;
|
|
751
|
+
gap: var(--_gap);
|
|
752
|
+
inset-block: 0;
|
|
753
|
+
inset-inline-end: var(--_pad-inline);
|
|
754
|
+
position: absolute;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
[data-ui='table'] [data-part='sort'] {
|
|
758
|
+
/* The mark is centred on the text rather than dropped at the top of the
|
|
759
|
+
line: with the default `stretch` resolving to `flex-start` under a stated
|
|
760
|
+
block size, the chevron sat two pixels above the capitals and read as a
|
|
761
|
+
mark nobody had placed. */
|
|
762
|
+
align-items: center;
|
|
763
|
+
background-color: transparent;
|
|
764
|
+
border: 0;
|
|
765
|
+
color: inherit;
|
|
766
|
+
/* The hand, because the header is a control. `select` restored it to its
|
|
767
|
+
own trigger and its options for the same reason: a target that does not
|
|
768
|
+
say it is one is a target a pointer reader finds by accident. */
|
|
769
|
+
cursor: pointer;
|
|
770
|
+
/* The control fills the header cell, so a reader aiming at a column name
|
|
771
|
+
hits the button and not the two pixels beside it — less the room the
|
|
772
|
+
controls slot reserves, which is the rule above. */
|
|
773
|
+
display: flex;
|
|
774
|
+
font: inherit;
|
|
775
|
+
gap: var(--_gap);
|
|
776
|
+
inline-size: 100%;
|
|
777
|
+
/* The name at the leading edge and the mark at the trailing one, so every
|
|
778
|
+
column's controls stand in one line down the header row. With the mark
|
|
779
|
+
set immediately after the name it landed at a different distance in every
|
|
780
|
+
column — beside a short name, halfway across a wide one — and beside a
|
|
781
|
+
filter trigger pinned to the edge the pair read as two unrelated marks. */
|
|
782
|
+
justify-content: space-between;
|
|
783
|
+
min-block-size: var(--slotted-target-min, 24px);
|
|
784
|
+
min-inline-size: 0;
|
|
785
|
+
padding-block: var(--_pad-block);
|
|
786
|
+
padding-inline: var(--_pad-inline);
|
|
787
|
+
text-align: inherit;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/* An aligned column keeps its name over its values, and the mark follows the
|
|
791
|
+
name rather than the cell: a centred column reads as a centred pair, and a
|
|
792
|
+
numeric column's name and mark both stand at the edge its digits end on. */
|
|
793
|
+
[data-ui='table'] [data-align='center'] > [data-part='sort'] {
|
|
794
|
+
justify-content: center;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
[data-ui='table'] :is([data-align='end'], [data-numeric]:not([data-align])) > [data-part='sort'] {
|
|
798
|
+
justify-content: end;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Hover is painted on the **cell**, and only on a cell that sorts.
|
|
803
|
+
*
|
|
804
|
+
* Two things were wrong when the control painted it. The control's box stops
|
|
805
|
+
* where the controls slot begins — that is the room the cell reserves — so
|
|
806
|
+
* the fill ended in the middle of the header with the filter trigger sitting
|
|
807
|
+
* outside it, on the band. And a header that does not sort answered the
|
|
808
|
+
* pointer with a ground anyway, which offers an interaction it does not have.
|
|
809
|
+
*
|
|
810
|
+
* The ground goes **darker**, not lighter: the band is already a tinted
|
|
811
|
+
* ground, and the step every neutral row hovers with is lighter than it, so a
|
|
812
|
+
* hovered header was a pale hole in the band rather than a control answering
|
|
813
|
+
* the pointer. It is mixed from the ink for the reason the stripe is — no
|
|
814
|
+
* scale holds a ground one step under this one, and mixing keeps the
|
|
815
|
+
* direction right in every scheme.
|
|
816
|
+
*/
|
|
817
|
+
[data-ui='table'] [data-part='column-header']:has(> [data-part='sort']:not(:disabled)):hover {
|
|
818
|
+
background-color: var(
|
|
819
|
+
--slotted-table-header-surface-hover,
|
|
820
|
+
color-mix(in oklab, var(--slotted-text, CanvasText) 7%, var(--_head-surface))
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
[data-ui='table'] [data-part='column-header'] {
|
|
825
|
+
transition: background-color
|
|
826
|
+
var(--slotted-control-transition-duration, var(--slotted-duration-short))
|
|
827
|
+
var(--slotted-control-transition-easing, var(--slotted-easing-standard));
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
[data-ui='table'] [data-part='sort']:focus-visible {
|
|
831
|
+
outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
|
|
832
|
+
outline-offset: calc(var(--slotted-focus-ring-offset, 2px) * -1);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* The direction mark: the `Glyph` the binding draws after the name. One glyph
|
|
837
|
+
* rather than two: `chevron-down` turned a half turn is `chevron-up`, and a
|
|
838
|
+
* family that added an arrow pair to the foundation would be adding it for
|
|
839
|
+
* itself.
|
|
840
|
+
*
|
|
841
|
+
* An unsorted sortable column draws the mark muted and only under the
|
|
842
|
+
* pointer or under focus, so the column says it can be sorted without twelve
|
|
843
|
+
* marks competing with twelve names. The sorted column keeps its mark drawn.
|
|
844
|
+
*/
|
|
845
|
+
[data-ui='table'] [data-part='sort'] > [data-ui='icon'] {
|
|
846
|
+
--slotted-icon-size: var(--_icon-size);
|
|
847
|
+
opacity: 0;
|
|
848
|
+
transition:
|
|
849
|
+
opacity var(--slotted-control-transition-duration, var(--slotted-duration-short))
|
|
850
|
+
var(--slotted-control-transition-easing, var(--slotted-easing-standard)),
|
|
851
|
+
rotate var(--slotted-control-transition-duration, var(--slotted-duration-short))
|
|
852
|
+
var(--slotted-control-transition-easing, var(--slotted-easing-standard));
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
[data-ui='table'] [data-part='sort']:not(:disabled):hover > [data-ui='icon'],
|
|
856
|
+
[data-ui='table'] [data-part='sort']:focus-visible > [data-ui='icon'] {
|
|
857
|
+
opacity: 0.55;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
[data-ui='table'] [aria-sort] > [data-part='sort'] > [data-ui='icon'] {
|
|
861
|
+
opacity: 1;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/* Ascending is the same mark turned over. The rotation is animated because it
|
|
865
|
+
is the one movement in this family a reader has to follow: the column did
|
|
866
|
+
not change, its direction did. */
|
|
867
|
+
[data-ui='table'] [aria-sort='ascending'] > [data-part='sort'] > [data-ui='icon'] {
|
|
868
|
+
rotate: 180deg;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* ── The selection column ───────────────────────────────────────────────
|
|
873
|
+
*
|
|
874
|
+
* The cell is the family's and the control is not: `checkbox` owns the square,
|
|
875
|
+
* the check and the indeterminate bar, and a chooser drawn here would be a
|
|
876
|
+
* second answer to a question that family answers. The cell holds it to the
|
|
877
|
+
* width of the control and centres it on the row's first line.
|
|
878
|
+
*
|
|
879
|
+
* The width is **stated** rather than shrink-wrapped, and that is the whole
|
|
880
|
+
* reason it is a token. A listing pins the chooser and the identifier beside
|
|
881
|
+
* it, and the second one has to say where the first stops: shrink-wrapped, the
|
|
882
|
+
* answer was 42 pixels — a number nothing names and nobody can state — so a
|
|
883
|
+
* consumer stating the nearest round length pinned the identifier six pixels
|
|
884
|
+
* past the chooser and over the column behind it. Now one name answers both
|
|
885
|
+
* places: this width, and the `stickyOffset` beside it.
|
|
886
|
+
*/
|
|
887
|
+
[data-ui='table'] [data-part='selection'] {
|
|
888
|
+
inline-size: var(--slotted-table-selection-size, 3rem);
|
|
889
|
+
padding-block: var(--_pad-block);
|
|
890
|
+
padding-inline: var(--_pad-inline);
|
|
891
|
+
text-align: center;
|
|
892
|
+
/* The chooser follows the table's own answer: a table that centres its
|
|
893
|
+
values and hangs its checkboxes from the top is two decisions where the
|
|
894
|
+
reader sees one row. */
|
|
895
|
+
vertical-align: var(--_cell-align);
|
|
896
|
+
white-space: nowrap;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
/* The control is one line tall whatever the row is, so it sits on the line
|
|
900
|
+
the axis puts it on rather than stretching down a three-line cell. */
|
|
901
|
+
[data-ui='table'] [data-part='selection'] > [data-ui='checkbox'] {
|
|
902
|
+
block-size: var(--_line);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* The chooser keeps its box and gives up its halo.
|
|
907
|
+
*
|
|
908
|
+
* A checkbox standing on its own answers the pointer with a circle behind the
|
|
909
|
+
* box, which is what tells a reader the target is larger than the square. In
|
|
910
|
+
* a table the row is already answering the pointer with a ground the width of
|
|
911
|
+
* the page, and a second circle inside it read as a mark on the row rather
|
|
912
|
+
* than as a state of the control.
|
|
913
|
+
*
|
|
914
|
+
* It is turned off by the token the halo is painted at rather than by a rule
|
|
915
|
+
* that reaches into `checkbox`: a custom property set on this family's own
|
|
916
|
+
* cell is composition, where a selector naming another family's parts would
|
|
917
|
+
* be this stylesheet deciding how that one looks. The target is unaffected —
|
|
918
|
+
* it is a separate pseudo-element, and it stays at the WCAG minimum.
|
|
919
|
+
*/
|
|
920
|
+
[data-ui='table'] [data-part='selection'] {
|
|
921
|
+
--slotted-selection-halo-opacity: 0;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* A cell holding nothing but the expander is a gutter rather than a column:
|
|
926
|
+
* it takes the control's width and every row keeps it, so a set where three
|
|
927
|
+
* records in twenty have a detail does not indent those three and leave the
|
|
928
|
+
* other seventeen a step to the start of them. Read on the first capture, the
|
|
929
|
+
* expander inside the row header moved one identifier and left the column
|
|
930
|
+
* ragged.
|
|
931
|
+
*/
|
|
932
|
+
[data-ui='table'] [data-part='cell']:has(> [data-part='expander']) {
|
|
933
|
+
inline-size: 1%;
|
|
934
|
+
padding-inline-end: 0;
|
|
935
|
+
white-space: nowrap;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* The element a binding wraps its trigger in is not a box.
|
|
940
|
+
*
|
|
941
|
+
* React renders the trigger straight into the slot; Angular reaches its
|
|
942
|
+
* templates through elements, so the filter is a `span` holding a `span`
|
|
943
|
+
* holding the button. Left alone, the flex item is the outer wrapper and the
|
|
944
|
+
* geometry below lands on a button that is no longer the thing being laid
|
|
945
|
+
* out. `display: contents` removes both wrappers from layout and leaves the
|
|
946
|
+
* button as the slot's own child, which is the box tree React already has.
|
|
947
|
+
*/
|
|
948
|
+
[data-ui='table'] [data-part='column-actions'] *:has([data-part='filter']) {
|
|
949
|
+
display: contents;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/**
|
|
953
|
+
* ── The expander ───────────────────────────────────────────────────────
|
|
954
|
+
*
|
|
955
|
+
* The Disclosure pattern, borrowed whole: a `button` carrying `aria-expanded`
|
|
956
|
+
* and naming the row it opens. The mark is `chevron-right`, turned a quarter
|
|
957
|
+
* turn when the detail is open — and mirrored in a right-to-left document,
|
|
958
|
+
* where a closed disclosure points the other way.
|
|
959
|
+
*/
|
|
960
|
+
[data-ui='table'] [data-part='expander'],
|
|
961
|
+
[data-ui='table'] [data-part='column-actions'] > button,
|
|
962
|
+
[data-ui='table'] button[data-part='column-actions'],
|
|
963
|
+
[data-ui='table'] [data-part='filter'] {
|
|
964
|
+
background-color: transparent;
|
|
965
|
+
border: 0;
|
|
966
|
+
border-radius: var(--slotted-table-control-radius, var(--slotted-radius-sm, 4px));
|
|
967
|
+
color: var(--_muted);
|
|
968
|
+
cursor: pointer;
|
|
969
|
+
display: inline-flex;
|
|
970
|
+
/* An icon-only control is smaller than the WCAG 2.2 minimum in both axes
|
|
971
|
+
unless the family says otherwise, which is the family's job rather than
|
|
972
|
+
the theme's. */
|
|
973
|
+
block-size: var(--slotted-target-min, 24px);
|
|
974
|
+
inline-size: var(--slotted-target-min, 24px);
|
|
975
|
+
/**
|
|
976
|
+
* The target is taller than the line it stands beside, so the box is pulled
|
|
977
|
+
* back onto that line: without this the row grew by the difference and the
|
|
978
|
+
* chevron sat two pixels under the identifier it opens. The margin is
|
|
979
|
+
* negative by exactly the overshoot, the cell's own inset absorbs it, and
|
|
980
|
+
* the row is still measured by its text.
|
|
981
|
+
*/
|
|
982
|
+
margin-block: calc((var(--_line) - var(--slotted-target-min, 24px)) / 2);
|
|
983
|
+
padding: 0;
|
|
984
|
+
/* Both axes, stated separately, because `place-content` is not the property
|
|
985
|
+
that centres a single item: `align-content` aligns lines and a single-line
|
|
986
|
+
flex container has one, so the mark fell back to `align-items: stretch`,
|
|
987
|
+
which a stated block size resolves to `flex-start`. Read on the capture,
|
|
988
|
+
the chevron sat against the top edge of its own 24-pixel box. */
|
|
989
|
+
align-items: center;
|
|
990
|
+
justify-content: center;
|
|
991
|
+
transition: background-color
|
|
992
|
+
var(--slotted-control-transition-duration, var(--slotted-duration-short))
|
|
993
|
+
var(--slotted-control-transition-easing, var(--slotted-easing-standard));
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
/**
|
|
997
|
+
* On a finger the target is the comfortable size in both axes and the box
|
|
998
|
+
* is the one a mouse sees: a transparent `::after` centred on the control
|
|
999
|
+
* reaches 44 pixels, and the ground and the ring stay square. A header's
|
|
1000
|
+
* trigger stands after the sort button and its mark, so it does not reach
|
|
1001
|
+
* back across them.
|
|
1002
|
+
*/
|
|
1003
|
+
@media (pointer: coarse) {
|
|
1004
|
+
[data-ui='table'] [data-part='expander'],
|
|
1005
|
+
[data-ui='table'] [data-part='column-actions'] > button,
|
|
1006
|
+
[data-ui='table'] button[data-part='column-actions'],
|
|
1007
|
+
[data-ui='table'] [data-part='filter'] {
|
|
1008
|
+
position: relative;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
[data-ui='table'] [data-part='expander']::after,
|
|
1012
|
+
[data-ui='table'] [data-part='column-actions'] > button::after,
|
|
1013
|
+
[data-ui='table'] button[data-part='column-actions']::after,
|
|
1014
|
+
[data-ui='table'] [data-part='filter']::after {
|
|
1015
|
+
content: '';
|
|
1016
|
+
inset: min(0px, calc((100% - var(--slotted-target-comfortable, 44px)) / 2));
|
|
1017
|
+
position: absolute;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
[data-ui='table'] [data-part='column-actions'] > button::after,
|
|
1021
|
+
[data-ui='table'] button[data-part='column-actions']::after,
|
|
1022
|
+
[data-ui='table'] [data-part='filter']::after {
|
|
1023
|
+
inset-inline-start: 0;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
[data-ui='table'] [data-part='expander']:not(:disabled):hover {
|
|
1028
|
+
background-color: var(--_hover);
|
|
1029
|
+
color: var(--_text);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
/* On the band the hover darkens, for the reason the sort control's does: the
|
|
1033
|
+
step a row hovers with is lighter than the band and reads as a hole in it. */
|
|
1034
|
+
[data-ui='table'] [data-part='column-actions'] > button:not(:disabled):hover,
|
|
1035
|
+
[data-ui='table'] button[data-part='column-actions']:not(:disabled):hover,
|
|
1036
|
+
[data-ui='table'] [data-part='filter']:not(:disabled):hover {
|
|
1037
|
+
background-color: var(
|
|
1038
|
+
--slotted-table-header-surface-hover,
|
|
1039
|
+
color-mix(in oklab, var(--slotted-text, CanvasText) 7%, var(--_head-surface))
|
|
1040
|
+
);
|
|
1041
|
+
color: var(--_text);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/* The ring is flush with the control rather than drawn inside it. The two
|
|
1045
|
+
larger targets — the region and the sort button — draw theirs inward,
|
|
1046
|
+
because both stand against an edge the ring would otherwise cross; these
|
|
1047
|
+
are 24-pixel boxes inside a cell's own inset, and read at that size an
|
|
1048
|
+
inward ring closed on the chevron. */
|
|
1049
|
+
[data-ui='table'] [data-part='expander']:focus-visible,
|
|
1050
|
+
[data-ui='table'] [data-part='column-actions'] > button:focus-visible,
|
|
1051
|
+
[data-ui='table'] button[data-part='column-actions']:focus-visible,
|
|
1052
|
+
[data-ui='table'] [data-part='filter']:focus-visible {
|
|
1053
|
+
outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
|
|
1054
|
+
outline-offset: 0;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
/**
|
|
1058
|
+
* The artwork a consumer puts in the header trigger is drawn at the family's
|
|
1059
|
+
* own glyph size, so a filter mark and the sort mark beside it read as one
|
|
1060
|
+
* pair rather than as a control and a mark. Read at the gate, a `button` from
|
|
1061
|
+
* the library at its smallest step stood thirty pixels tall against a line of
|
|
1062
|
+
* nineteen and a half — the control scale is right for a control in a form
|
|
1063
|
+
* and wrong for a mark in a header.
|
|
1064
|
+
*/
|
|
1065
|
+
[data-ui='table'] :is([data-part='column-actions'], [data-part='filter']) :is(svg, img) {
|
|
1066
|
+
block-size: var(--_icon-size);
|
|
1067
|
+
inline-size: var(--_icon-size);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
/**
|
|
1071
|
+
* ── The filter ─────────────────────────────────────────────────────────
|
|
1072
|
+
*
|
|
1073
|
+
* The trigger takes the slot's geometry above; what is left is the one thing
|
|
1074
|
+
* it must say on its own — that the column is filtered. The mark turns to the
|
|
1075
|
+
* accent ink and holds it whether the pointer is on it or not, because a
|
|
1076
|
+
* filter a reader forgot they set is the defect this state exists to prevent.
|
|
1077
|
+
*/
|
|
1078
|
+
[data-ui='table'] [data-part='filter'][data-filtered],
|
|
1079
|
+
[data-ui='table'] [data-part='filter'][data-filtered]:not(:disabled):hover {
|
|
1080
|
+
color: var(--slotted-tone-accent-text, LinkText);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
/**
|
|
1084
|
+
* The panel, which is drawn wherever `popover` puts it — inside the header or
|
|
1085
|
+
* portalled to the end of the document. So it carries the family's identity
|
|
1086
|
+
* on itself rather than reading it from an ancestor: a rule that reached for
|
|
1087
|
+
* `[data-ui='table']` above would style the panel in one of the two.
|
|
1088
|
+
*
|
|
1089
|
+
* And it declares the values it uses rather than reading them from the root,
|
|
1090
|
+
* for the same reason. Every `--_` above is declared on `[data-part='root']`;
|
|
1091
|
+
* portalled to the end of the document the panel is not inside it, and a
|
|
1092
|
+
* custom property that resolves to nothing takes its whole declaration with
|
|
1093
|
+
* it — `gap: var(--_gap)` became no gap at all, silently, in the one
|
|
1094
|
+
* arrangement `popover` uses by default. The eight below are the panel's own,
|
|
1095
|
+
* from the same theme tokens the root reads.
|
|
1096
|
+
*/
|
|
1097
|
+
[data-ui='table'][data-part='filter-panel'] {
|
|
1098
|
+
--_gap: var(--slotted-table-gap, var(--slotted-control-gap-md, 8px));
|
|
1099
|
+
--_font-size: var(--slotted-table-font-size, var(--slotted-control-font-size-md, 13px));
|
|
1100
|
+
--_line: calc(var(--_font-size) * var(--slotted-line-height-md, 1.5));
|
|
1101
|
+
--_pad-block: var(--slotted-space-2, 4px);
|
|
1102
|
+
--_text: var(--slotted-table-color, var(--slotted-text, CanvasText));
|
|
1103
|
+
--_muted: var(--slotted-table-color-muted, var(--slotted-text-muted, GrayText));
|
|
1104
|
+
--_rule: var(--slotted-table-border-color, var(--slotted-border-subtle, ButtonBorder));
|
|
1105
|
+
--_rule-width: var(--slotted-table-border-width, var(--slotted-border-width-thin, 1px));
|
|
1106
|
+
--_hover: var(
|
|
1107
|
+
--slotted-table-row-surface-hover,
|
|
1108
|
+
var(--slotted-tone-secondary-subtle-hover, ButtonFace)
|
|
1109
|
+
);
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* The inset counts inside the width, and that is not a preference.
|
|
1113
|
+
* `min-inline-size` beats `max-inline-size`, and under the default
|
|
1114
|
+
* `content-box` both resolve against the content: `min(15rem, 100%)` gave
|
|
1115
|
+
* the panel the whole of the layer's width and then added twenty-four
|
|
1116
|
+
* pixels of padding to it, so a panel measured 229 pixels inside a
|
|
1117
|
+
* 205-pixel surface — the audit's number, on the first scene it read.
|
|
1118
|
+
*/
|
|
1119
|
+
box-sizing: border-box;
|
|
1120
|
+
display: grid;
|
|
1121
|
+
gap: var(--_gap);
|
|
1122
|
+
/* The panel is a surface of its own, so it states its type rather than
|
|
1123
|
+
inheriting it. Unportalled it is drawn inside the header cell, and a
|
|
1124
|
+
header is set in semibold: every value in the list came out bold, which
|
|
1125
|
+
reads as a set of headings rather than a set of values. */
|
|
1126
|
+
color: var(--_text);
|
|
1127
|
+
font-size: var(--_font-size);
|
|
1128
|
+
font-weight: var(--slotted-font-weight-regular, 400);
|
|
1129
|
+
line-height: var(--slotted-line-height-md, 1.5);
|
|
1130
|
+
/* The panel brings its own inset. `popover` puts the inset on its body and
|
|
1131
|
+
its footer, and this family draws neither: the whole surface is one part
|
|
1132
|
+
of its own, which is what keeps the condition, the values and the two
|
|
1133
|
+
actions on one rhythm. */
|
|
1134
|
+
padding: var(--slotted-space-5, 12px);
|
|
1135
|
+
/**
|
|
1136
|
+
* A floor rather than a fixed width. A set filter needs room for a value and
|
|
1137
|
+
* its count; a typed one needs room for an operator name, and "Does not
|
|
1138
|
+
* contain" and "Ist nicht leer" are wider than the panel a checkbox list
|
|
1139
|
+
* fits in. The chooser truncates its own button, so the floor holds the
|
|
1140
|
+
* common case and a wider panel is the consumer's to ask for.
|
|
1141
|
+
*
|
|
1142
|
+
* The floor gives way to the room there is, and the room is the positioner's
|
|
1143
|
+
* own number rather than a percentage. Two readings got this wrong before
|
|
1144
|
+
* it was measured: a bare `15rem` won against `max-inline-size: 100%` — a
|
|
1145
|
+
* minimum always does — and stood eight pixels outside the layer at a
|
|
1146
|
+
* 240-pixel viewport; `min(15rem, 100%)` then resolved its percentage
|
|
1147
|
+
* against a containing block that is itself shrink-to-fit, and the panel
|
|
1148
|
+
* came out at 217 pixels, twenty-three under the floor it was supposed to
|
|
1149
|
+
* hold. `--slotted-positioner-available-width` is the space the engine
|
|
1150
|
+
* measured, which is what "the room there is" actually means.
|
|
1151
|
+
*
|
|
1152
|
+
* Seventeen rem rather than fifteen, and the number is measured too: the
|
|
1153
|
+
* widest operator name is 146 pixels of text, the chooser adds its own
|
|
1154
|
+
* inset and chevron, and a range wants two seven-rem fields on one row.
|
|
1155
|
+
* Fifteen left 193 pixels of content and both a squeezed chooser and a
|
|
1156
|
+
* stacked range; seventeen leaves 248 and neither.
|
|
1157
|
+
*/
|
|
1158
|
+
min-inline-size: min(17rem, var(--slotted-positioner-available-width, 100%));
|
|
1159
|
+
max-inline-size: 100%;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
/**
|
|
1163
|
+
* One condition: the operator above the operands it takes. They are stacked
|
|
1164
|
+
* rather than set on a row, because an operator name is the widest thing in
|
|
1165
|
+
* the panel and a row would give the value what was left of 15rem.
|
|
1166
|
+
*/
|
|
1167
|
+
[data-ui='table'][data-part='filter-panel'] [data-part='filter-condition'] {
|
|
1168
|
+
display: grid;
|
|
1169
|
+
gap: calc(var(--_gap) / 2);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
[data-ui='table'][data-part='filter-panel'] [data-part='filter-operator'],
|
|
1173
|
+
[data-ui='table'][data-part='filter-panel'] [data-part='filter-search'] {
|
|
1174
|
+
display: block;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
/**
|
|
1178
|
+
* The operands, and the only place in the family where the number of controls
|
|
1179
|
+
* is not fixed: none for `is empty`, one for a comparison, two for a range.
|
|
1180
|
+
*
|
|
1181
|
+
* `auto-fit` answers all three without a query and without a second element.
|
|
1182
|
+
* Two fields need fourteen and a half rem between them and the panel's floor
|
|
1183
|
+
* leaves thirteen and a half, so a range stacks at the default width and sits
|
|
1184
|
+
* on one row the moment a consumer gives the panel more — which is the right
|
|
1185
|
+
* way round: two number fields side by side in a 15rem panel are two fields
|
|
1186
|
+
* nobody can read.
|
|
1187
|
+
*/
|
|
1188
|
+
[data-ui='table'][data-part='filter-panel'] [data-part='filter-operand'] {
|
|
1189
|
+
display: grid;
|
|
1190
|
+
gap: calc(var(--_gap) / 2);
|
|
1191
|
+
grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
/* A set's operand is a search and a list, which own their own stacking. */
|
|
1195
|
+
[data-ui='table'][data-part='filter-panel'] [data-part='filter-operand'][data-kind='set'] {
|
|
1196
|
+
grid-template-columns: 1fr;
|
|
1197
|
+
gap: var(--_gap);
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
/**
|
|
1201
|
+
* Three operands that are one control wide, whatever the operator's count.
|
|
1202
|
+
*
|
|
1203
|
+
* `data-range` is a `between` drawn as one range picker — `date-range-picker`
|
|
1204
|
+
* on a date, `time-range-picker` on a time, `datetime-range-picker` on a
|
|
1205
|
+
* date-time: the grid above would put the single child in the first of its
|
|
1206
|
+
* two columns and leave the second empty. A date-time field is seven
|
|
1207
|
+
* segments wide, so two of them side by side at the panel's floor are two
|
|
1208
|
+
* fields nobody can read; a range of date-times stacks, as a range of
|
|
1209
|
+
* numbers did at fifteen rem. The chips of a set typed as tags wrap inside
|
|
1210
|
+
* one field, and the field takes the panel's width so the wrapping has
|
|
1211
|
+
* somewhere to go.
|
|
1212
|
+
*/
|
|
1213
|
+
[data-ui='table'][data-part='filter-panel'] [data-part='filter-operand'][data-range],
|
|
1214
|
+
[data-ui='table'][data-part='filter-panel'] [data-part='filter-operand'][data-kind='datetime'],
|
|
1215
|
+
[data-ui='table'][data-part='filter-panel'] [data-part='filter-operand'][data-operand='tags'] {
|
|
1216
|
+
grid-template-columns: 1fr;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
/**
|
|
1220
|
+
* A time field is narrower than a date field — `09:00` against
|
|
1221
|
+
* `14/09/2026` — but the trigger beside it is the same width, so two of them
|
|
1222
|
+
* on one row want eight rem each rather than seven. At the floor the two
|
|
1223
|
+
* stack; a panel given two rem more puts them side by side. The rule is for
|
|
1224
|
+
* a consumer's own `renderOperand` drawing two time pickers: the family's
|
|
1225
|
+
* `between` is one range picker, marked `data-range`, and this rule stands
|
|
1226
|
+
* after the one above, so it steps aside for that mark.
|
|
1227
|
+
*/
|
|
1228
|
+
[data-ui='table'][data-part='filter-panel']
|
|
1229
|
+
[data-part='filter-operand'][data-kind='time']:not([data-range]) {
|
|
1230
|
+
grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
/**
|
|
1234
|
+
* The join between two conditions. It is a rule with a word on it: the two
|
|
1235
|
+
* conditions are separate boxes and the reader has to see where the first ends
|
|
1236
|
+
* before being asked how the second joins it.
|
|
1237
|
+
*/
|
|
1238
|
+
[data-ui='table'][data-part='filter-panel'] [data-part='filter-join'] {
|
|
1239
|
+
align-items: center;
|
|
1240
|
+
border-block-start: var(--_rule-width) solid var(--_rule);
|
|
1241
|
+
display: flex;
|
|
1242
|
+
flex-wrap: wrap;
|
|
1243
|
+
gap: var(--_gap);
|
|
1244
|
+
padding-block-start: var(--_gap);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
[data-ui='table'][data-part='filter-panel'] [data-part='filter-join'] > [data-label] {
|
|
1248
|
+
color: var(--_muted);
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
/**
|
|
1252
|
+
* The values, scrolling inside the panel rather than making the panel taller
|
|
1253
|
+
* than the screen. Eight rows is the height a set filter opens at in AG Grid
|
|
1254
|
+
* and about the height Excel's opens at; past that the reader searches.
|
|
1255
|
+
*/
|
|
1256
|
+
[data-ui='table'][data-part='filter-panel'] [data-part='filter-list'] {
|
|
1257
|
+
display: grid;
|
|
1258
|
+
gap: var(--_gap);
|
|
1259
|
+
max-block-size: calc(var(--_line) * 9);
|
|
1260
|
+
overflow-y: auto;
|
|
1261
|
+
/* The scrollbar sits outside the rows rather than over them. */
|
|
1262
|
+
padding-inline-end: var(--_gap);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
/**
|
|
1266
|
+
* One value, and the whole row is its target.
|
|
1267
|
+
*
|
|
1268
|
+
* The chooser gives up its halo here for the reason the selection column
|
|
1269
|
+
* gives up its own, and for a second: the list scrolls, so the circle drawn
|
|
1270
|
+
* outside the square was clipped against the panel's own edge — a mark that
|
|
1271
|
+
* only appears cut. The row answers the pointer with a ground instead, which
|
|
1272
|
+
* is what a value list in Ant, AG Grid and Excel does, and the ground is
|
|
1273
|
+
* pulled out to the panel's inset so it reads as a row rather than as a box
|
|
1274
|
+
* around the words.
|
|
1275
|
+
*/
|
|
1276
|
+
[data-ui='table'][data-part='filter-panel'] [data-part='filter-option'] {
|
|
1277
|
+
--slotted-selection-halo-opacity: 0;
|
|
1278
|
+
|
|
1279
|
+
align-items: center;
|
|
1280
|
+
border-radius: var(--slotted-table-control-radius, var(--slotted-radius-sm, 4px));
|
|
1281
|
+
display: flex;
|
|
1282
|
+
gap: var(--_gap);
|
|
1283
|
+
justify-content: space-between;
|
|
1284
|
+
margin-inline: calc(var(--_gap) * -1);
|
|
1285
|
+
padding-inline: var(--_gap);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
[data-ui='table'][data-part='filter-panel']
|
|
1289
|
+
[data-part='filter-option']:not([data-disabled]):hover {
|
|
1290
|
+
background-color: var(--_hover);
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
/* The count is the consumer's fact about their own query, so it is set apart
|
|
1294
|
+
from the value rather than beside it as a second label. */
|
|
1295
|
+
[data-ui='table'][data-part='filter-panel'] [data-part='filter-option'] > [data-count] {
|
|
1296
|
+
color: var(--_muted);
|
|
1297
|
+
font-variant-numeric: tabular-nums;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
/* The covering checkbox is a row of the same list and a boundary at once: it
|
|
1301
|
+
answers for everything under it, so the rule says where "everything" ends. */
|
|
1302
|
+
[data-ui='table'][data-part='filter-panel'] [data-part='filter-option'][data-covering] {
|
|
1303
|
+
border-block-end: var(--_rule-width) solid var(--_rule);
|
|
1304
|
+
padding-block-end: var(--_gap);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
[data-ui='table'][data-part='filter-panel'] [data-part='filter-empty'] {
|
|
1308
|
+
color: var(--_muted);
|
|
1309
|
+
padding-block: var(--_pad-block);
|
|
1310
|
+
text-align: center;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
/* Clear at the leading edge and Apply at the trailing one: the destructive
|
|
1314
|
+
answer is farthest from the one a reader presses without reading. */
|
|
1315
|
+
[data-ui='table'][data-part='filter-panel'] [data-part='filter-actions'] {
|
|
1316
|
+
border-block-start: var(--_rule-width) solid var(--_rule);
|
|
1317
|
+
display: flex;
|
|
1318
|
+
gap: var(--_gap);
|
|
1319
|
+
justify-content: space-between;
|
|
1320
|
+
padding-block-start: var(--_gap);
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
/* The chevron is the `Glyph` the binding draws inside the button. */
|
|
1324
|
+
[data-ui='table'] [data-part='expander'] > [data-ui='icon'] {
|
|
1325
|
+
--slotted-icon-size: var(--_icon-size);
|
|
1326
|
+
rotate: 0deg;
|
|
1327
|
+
transition: rotate var(--slotted-control-transition-duration, var(--slotted-duration-short))
|
|
1328
|
+
var(--slotted-control-transition-easing, var(--slotted-easing-standard));
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
[data-ui='table'] [data-part='expander'][aria-expanded='true'] > [data-ui='icon'] {
|
|
1332
|
+
rotate: 90deg;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
/* A chevron is direction-sensitive artwork, so it is mirrored rather than
|
|
1336
|
+
reused: a closed row in Arabic points to the start of the line, which is
|
|
1337
|
+
the right-hand side. */
|
|
1338
|
+
[data-ui='table']:dir(rtl) [data-part='expander'] > [data-ui='icon'] {
|
|
1339
|
+
scale: -1 1;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
[data-ui='table']:dir(rtl) [data-part='expander'][aria-expanded='true'] > [data-ui='icon'] {
|
|
1343
|
+
rotate: -90deg;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
/**
|
|
1347
|
+
* ── The detail ─────────────────────────────────────────────────────────
|
|
1348
|
+
*
|
|
1349
|
+
* One row holding one cell that spans the table. It is not rendered while it
|
|
1350
|
+
* is closed, so nothing is in the DOM that a reader is told about and cannot
|
|
1351
|
+
* reach, and it takes the sunken ground so the record it belongs to still
|
|
1352
|
+
* reads as one thing.
|
|
1353
|
+
*/
|
|
1354
|
+
[data-ui='table'] [data-part='detail'] {
|
|
1355
|
+
background-color: var(--_stripe);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
[data-ui='table'] [data-part='detail'] > [data-part='cell'] {
|
|
1359
|
+
padding-block: var(--_pad-block);
|
|
1360
|
+
padding-inline: var(--_pad-inline);
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
/* A detail row is not a record, so it takes no stripe of its own and never
|
|
1364
|
+
shifts the alternation of the rows around it. */
|
|
1365
|
+
[data-ui='table'][data-striped] [data-part='body'] > [data-part='detail'] {
|
|
1366
|
+
background-color: var(--_stripe);
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
/**
|
|
1370
|
+
* ── Empty and loading ──────────────────────────────────────────────────
|
|
1371
|
+
*/
|
|
1372
|
+
[data-ui='table'] [data-part='empty'] {
|
|
1373
|
+
color: var(--_muted);
|
|
1374
|
+
padding-block: var(--slotted-space-9, 2rem);
|
|
1375
|
+
padding-inline: var(--_pad-inline);
|
|
1376
|
+
text-align: center;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
/* The skeleton rows of a first load. The shapes are `skeleton`'s own; what
|
|
1380
|
+
this family supplies is the row that holds them, so the table's shape is
|
|
1381
|
+
visible before its content arrives. */
|
|
1382
|
+
[data-ui='table'] [data-part='loader'] > [data-part='row'] > * {
|
|
1383
|
+
padding-block: var(--_pad-block);
|
|
1384
|
+
padding-inline: var(--_pad-inline);
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
/**
|
|
1388
|
+
* A refresh over rows a reader is already looking at is not a first load. The
|
|
1389
|
+
* body dims and nothing moves: the column widths are the table's, the scroll
|
|
1390
|
+
* position is the reader's, and swapping rows for skeletons would take both.
|
|
1391
|
+
*/
|
|
1392
|
+
[data-ui='table'][data-loading] [data-part='body'] {
|
|
1393
|
+
opacity: 0.55;
|
|
1394
|
+
transition: opacity var(--slotted-control-transition-duration, var(--slotted-duration-short))
|
|
1395
|
+
var(--slotted-control-transition-easing, var(--slotted-easing-standard));
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
/**
|
|
1399
|
+
* ── The summary rows ───────────────────────────────────────────────────
|
|
1400
|
+
*
|
|
1401
|
+
* The `tfoot`, which the HTML Standard calls the column summaries of the
|
|
1402
|
+
* table: a total, a count, a unit. It carries the header's rule on its
|
|
1403
|
+
* leading edge rather than the body's, because what it separates is the same
|
|
1404
|
+
* kind of boundary — and it is not the `footer`, which is a bar outside the
|
|
1405
|
+
* table holding the consumer's own controls.
|
|
1406
|
+
*/
|
|
1407
|
+
[data-ui='table'] [data-part='summary'] > [data-part='row'] > * {
|
|
1408
|
+
background-color: var(--_head-surface);
|
|
1409
|
+
border-block-start: var(--_rule-width) solid var(--_rule-strong);
|
|
1410
|
+
font-weight: var(--slotted-font-weight-medium, 500);
|
|
1411
|
+
padding-block: var(--_pad-block);
|
|
1412
|
+
padding-inline: var(--_pad-inline);
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
[data-ui='table'][data-variant='plain'] [data-part='summary'] > [data-part='row'] > * {
|
|
1416
|
+
background-color: transparent;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
/**
|
|
1420
|
+
* ── The footer bar ─────────────────────────────────────────────────────
|
|
1421
|
+
*
|
|
1422
|
+
* Under the table and inside the frame, where a paged listing puts its
|
|
1423
|
+
* control. It is a sibling of the scroller rather than a child, so a table
|
|
1424
|
+
* scrolled sideways does not take its pagination with it — which is the whole
|
|
1425
|
+
* reason the frame and the scroller are two boxes.
|
|
1426
|
+
*
|
|
1427
|
+
* What stands in it is the consumer's own: `pagination`, a page-size select,
|
|
1428
|
+
* a count, a bulk-action bar. The family draws the rule above it and the
|
|
1429
|
+
* inset around it, and nothing else.
|
|
1430
|
+
*/
|
|
1431
|
+
[data-ui='table'] [data-part='footer'] {
|
|
1432
|
+
align-items: center;
|
|
1433
|
+
border-block-start: var(--_rule-width) solid var(--_rule);
|
|
1434
|
+
display: flex;
|
|
1435
|
+
flex-wrap: wrap;
|
|
1436
|
+
gap: var(--_gap);
|
|
1437
|
+
padding-block: var(--_pad-block);
|
|
1438
|
+
padding-inline: var(--_pad-inline);
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
/**
|
|
1442
|
+
* The last thing in the bar takes the room that is left, and that is not a
|
|
1443
|
+
* preference: `pagination` answers its own inline size, and an element with
|
|
1444
|
+
* `container-type: inline-size` contributes no intrinsic width — dropped into
|
|
1445
|
+
* a bar that shrink-wrapped its children it resolved to nothing, hid its own
|
|
1446
|
+
* page list under its 22rem query and left two chevrons in an empty row. The
|
|
1447
|
+
* same is true of every family that answers its own width, which
|
|
1448
|
+
* `references/stories.md` records for the story stages.
|
|
1449
|
+
*
|
|
1450
|
+
* So the bar states the room instead of asking for it, and a control that
|
|
1451
|
+
* wants to sit at the trailing edge justifies itself there.
|
|
1452
|
+
*/
|
|
1453
|
+
[data-ui='table'] [data-part='footer'] > :last-child {
|
|
1454
|
+
flex: 1 1 auto;
|
|
1455
|
+
/* And it aligns its own content to the trailing edge, which is where a
|
|
1456
|
+
listing screen puts its paging control. The rule is structural rather
|
|
1457
|
+
than a reach into another family: it names the last thing in the bar, not
|
|
1458
|
+
what that thing is. */
|
|
1459
|
+
justify-content: end;
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
/**
|
|
1463
|
+
* ── Print ──────────────────────────────────────────────────────────────
|
|
1464
|
+
*
|
|
1465
|
+
* A scroller, a fixed height and a sticky offset are all wrong on paper. Once
|
|
1466
|
+
* the family stops holding them, the platform's own rule applies: a `thead`
|
|
1467
|
+
* repeats at the top of every printed page.
|
|
1468
|
+
*/
|
|
1469
|
+
@media print {
|
|
1470
|
+
[data-ui='table'] [data-part='scroll'] {
|
|
1471
|
+
max-block-size: none;
|
|
1472
|
+
overflow: visible;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
[data-ui='table'][data-sticky-header] [data-part='column-header'],
|
|
1476
|
+
[data-ui='table'][data-sticky-header] [data-part='head'] [data-part='selection'],
|
|
1477
|
+
[data-ui='table'][data-sticky-footer] [data-part='summary'] > [data-part='row'] > *,
|
|
1478
|
+
[data-ui='table'] [data-sticky] {
|
|
1479
|
+
position: static;
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
}
|