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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/package.json +276 -3
  4. package/src/accordion/accordion.css +96 -0
  5. package/src/accordion/accordion.tokens.json +14 -0
  6. package/src/action-bar/action-bar.css +455 -0
  7. package/src/action-bar/action-bar.tokens.json +50 -0
  8. package/src/action-sheet/action-sheet.css +462 -0
  9. package/src/action-sheet/action-sheet.tokens.json +59 -0
  10. package/src/alert/alert.css +764 -0
  11. package/src/alert/alert.tokens.json +77 -0
  12. package/src/app-shell/app-shell.css +1899 -0
  13. package/src/app-shell/app-shell.tokens.json +88 -0
  14. package/src/aspect-ratio/aspect-ratio.css +131 -0
  15. package/src/aspect-ratio/aspect-ratio.tokens.json +1 -0
  16. package/src/attachment/attachment.css +906 -0
  17. package/src/attachment/attachment.tokens.json +85 -0
  18. package/src/avatar/avatar.css +598 -0
  19. package/src/avatar/avatar.tokens.json +64 -0
  20. package/src/avatar-group/avatar-group.css +268 -0
  21. package/src/avatar-group/avatar-group.tokens.json +16 -0
  22. package/src/badge/badge.css +462 -0
  23. package/src/badge/badge.tokens.json +58 -0
  24. package/src/breadcrumb/breadcrumb.css +441 -0
  25. package/src/breadcrumb/breadcrumb.tokens.json +50 -0
  26. package/src/button/button.css +754 -0
  27. package/src/button/button.tokens.json +72 -0
  28. package/src/button-group/button-group.css +455 -0
  29. package/src/button-group/button-group.tokens.json +11 -0
  30. package/src/calendar/calendar.css +846 -0
  31. package/src/calendar/calendar.tokens.json +67 -0
  32. package/src/card/card.css +602 -0
  33. package/src/card/card.tokens.json +68 -0
  34. package/src/carousel/carousel.css +860 -0
  35. package/src/carousel/carousel.tokens.json +62 -0
  36. package/src/cascader/cascader.css +1320 -0
  37. package/src/cascader/cascader.tokens.json +103 -0
  38. package/src/chart/chart.css +1473 -0
  39. package/src/chart/chart.tokens.json +107 -0
  40. package/src/checkbox/checkbox.css +1011 -0
  41. package/src/checkbox/checkbox.tokens.json +102 -0
  42. package/src/checkbox-group/checkbox-group.css +70 -0
  43. package/src/checkbox-group/checkbox-group.tokens.json +6 -0
  44. package/src/code-block/code-block.css +555 -0
  45. package/src/code-block/code-block.tokens.json +69 -0
  46. package/src/collapsible/collapsible.css +546 -0
  47. package/src/collapsible/collapsible.tokens.json +55 -0
  48. package/src/color-picker/color-picker.css +607 -0
  49. package/src/color-picker/color-picker.tokens.json +58 -0
  50. package/src/column-manager/column-manager.css +452 -0
  51. package/src/column-manager/column-manager.tokens.json +43 -0
  52. package/src/combobox/combobox.css +1048 -0
  53. package/src/combobox/combobox.tokens.json +112 -0
  54. package/src/command-palette/command-palette.css +1000 -0
  55. package/src/command-palette/command-palette.tokens.json +75 -0
  56. package/src/container/container.css +198 -0
  57. package/src/container/container.tokens.json +18 -0
  58. package/src/context-menu/context-menu.css +104 -0
  59. package/src/context-menu/context-menu.tokens.json +11 -0
  60. package/src/currency-field/currency-field.css +787 -0
  61. package/src/currency-field/currency-field.tokens.json +80 -0
  62. package/src/dashboard-grid/dashboard-grid.css +748 -0
  63. package/src/dashboard-grid/dashboard-grid.tokens.json +67 -0
  64. package/src/data-grid/data-grid.css +2484 -0
  65. package/src/data-grid/data-grid.tokens.json +100 -0
  66. package/src/data-list/data-list.css +455 -0
  67. package/src/data-list/data-list.tokens.json +35 -0
  68. package/src/date-field/date-field.css +560 -0
  69. package/src/date-field/date-field.tokens.json +80 -0
  70. package/src/date-picker/date-picker.css +420 -0
  71. package/src/date-picker/date-picker.tokens.json +49 -0
  72. package/src/date-range-picker/date-range-picker.css +983 -0
  73. package/src/date-range-picker/date-range-picker.tokens.json +109 -0
  74. package/src/datetime-field/datetime-field.css +850 -0
  75. package/src/datetime-field/datetime-field.tokens.json +91 -0
  76. package/src/datetime-picker/datetime-picker.css +843 -0
  77. package/src/datetime-picker/datetime-picker.tokens.json +78 -0
  78. package/src/datetime-range-picker/datetime-range-picker.css +1375 -0
  79. package/src/datetime-range-picker/datetime-range-picker.tokens.json +135 -0
  80. package/src/description-list/description-list.css +675 -0
  81. package/src/description-list/description-list.tokens.json +66 -0
  82. package/src/detail-panel/detail-panel.css +329 -0
  83. package/src/detail-panel/detail-panel.tokens.json +33 -0
  84. package/src/dialog/dialog.css +968 -0
  85. package/src/dialog/dialog.tokens.json +66 -0
  86. package/src/divider/divider.css +131 -0
  87. package/src/divider/divider.tokens.json +18 -0
  88. package/src/drawer/drawer.css +1303 -0
  89. package/src/drawer/drawer.tokens.json +75 -0
  90. package/src/entity-picker/entity-picker.css +1367 -0
  91. package/src/entity-picker/entity-picker.tokens.json +118 -0
  92. package/src/event-calendar/event-calendar.css +2148 -0
  93. package/src/event-calendar/event-calendar.tokens.json +87 -0
  94. package/src/field/field.css +707 -0
  95. package/src/field/field.tokens.json +58 -0
  96. package/src/field-group/field-group.css +226 -0
  97. package/src/field-group/field-group.tokens.json +33 -0
  98. package/src/filter-bar/filter-bar.css +1306 -0
  99. package/src/filter-bar/filter-bar.tokens.json +78 -0
  100. package/src/form/form.css +218 -0
  101. package/src/form/form.tokens.json +32 -0
  102. package/src/fullscreen/fullscreen.css +376 -0
  103. package/src/fullscreen/fullscreen.tokens.json +50 -0
  104. package/src/grid/grid.css +173 -0
  105. package/src/grid/grid.tokens.json +20 -0
  106. package/src/highlight/highlight.css +135 -0
  107. package/src/highlight/highlight.tokens.json +16 -0
  108. package/src/hover-card/hover-card.css +93 -0
  109. package/src/hover-card/hover-card.tokens.json +17 -0
  110. package/src/icon/icon.css +136 -0
  111. package/src/icon/icon.tokens.json +14 -0
  112. package/src/image/image.css +449 -0
  113. package/src/image/image.tokens.json +27 -0
  114. package/src/import-export-center/import-export-center.css +1175 -0
  115. package/src/import-export-center/import-export-center.tokens.json +84 -0
  116. package/src/inline/inline.css +149 -0
  117. package/src/inline/inline.tokens.json +14 -0
  118. package/src/inline-message/inline-message.css +396 -0
  119. package/src/inline-message/inline-message.tokens.json +19 -0
  120. package/src/kbd/kbd.css +490 -0
  121. package/src/kbd/kbd.tokens.json +40 -0
  122. package/src/link/link.css +237 -0
  123. package/src/link/link.tokens.json +25 -0
  124. package/src/list/list.css +240 -0
  125. package/src/list/list.tokens.json +42 -0
  126. package/src/list-item/list-item.css +937 -0
  127. package/src/list-item/list-item.tokens.json +114 -0
  128. package/src/listbox/listbox.css +674 -0
  129. package/src/listbox/listbox.tokens.json +72 -0
  130. package/src/loading-overlay/loading-overlay.css +730 -0
  131. package/src/loading-overlay/loading-overlay.tokens.json +48 -0
  132. package/src/markdown/markdown.css +168 -0
  133. package/src/markdown/markdown.tokens.json +18 -0
  134. package/src/menu/menu.css +817 -0
  135. package/src/menu/menu.tokens.json +78 -0
  136. package/src/menubar/menubar.css +170 -0
  137. package/src/menubar/menubar.tokens.json +32 -0
  138. package/src/message/message.css +1453 -0
  139. package/src/message/message.tokens.json +85 -0
  140. package/src/message-scroller/message-scroller.css +371 -0
  141. package/src/message-scroller/message-scroller.tokens.json +47 -0
  142. package/src/multi-select/multi-select.css +1427 -0
  143. package/src/multi-select/multi-select.tokens.json +114 -0
  144. package/src/navigation-menu/navigation-menu.css +1029 -0
  145. package/src/navigation-menu/navigation-menu.tokens.json +70 -0
  146. package/src/notification-center/notification-center.css +856 -0
  147. package/src/notification-center/notification-center.tokens.json +84 -0
  148. package/src/number-field/number-field.css +794 -0
  149. package/src/number-field/number-field.tokens.json +80 -0
  150. package/src/otp-input/otp-input.css +525 -0
  151. package/src/otp-input/otp-input.tokens.json +69 -0
  152. package/src/page/page.css +179 -0
  153. package/src/page/page.tokens.json +20 -0
  154. package/src/page-header/page-header.css +1181 -0
  155. package/src/page-header/page-header.tokens.json +71 -0
  156. package/src/pagination/pagination.css +500 -0
  157. package/src/pagination/pagination.tokens.json +56 -0
  158. package/src/popover/popover.css +526 -0
  159. package/src/popover/popover.tokens.json +60 -0
  160. package/src/progress-indicator/progress-indicator.css +1584 -0
  161. package/src/progress-indicator/progress-indicator.tokens.json +50 -0
  162. package/src/prompt-composer/prompt-composer.css +680 -0
  163. package/src/prompt-composer/prompt-composer.tokens.json +99 -0
  164. package/src/prose/prose.css +733 -0
  165. package/src/prose/prose.tokens.json +79 -0
  166. package/src/radio-group/radio-group.css +778 -0
  167. package/src/radio-group/radio-group.tokens.json +103 -0
  168. package/src/rating/rating.css +1071 -0
  169. package/src/rating/rating.tokens.json +88 -0
  170. package/src/result-state/result-state.css +980 -0
  171. package/src/result-state/result-state.tokens.json +63 -0
  172. package/src/runtime-properties.mjs +321 -0
  173. package/src/saved-views/saved-views.css +394 -0
  174. package/src/saved-views/saved-views.tokens.json +41 -0
  175. package/src/scroll-area/scroll-area.css +481 -0
  176. package/src/scroll-area/scroll-area.tokens.json +15 -0
  177. package/src/section/section.css +177 -0
  178. package/src/section/section.tokens.json +39 -0
  179. package/src/segmented-control/segmented-control.css +564 -0
  180. package/src/segmented-control/segmented-control.tokens.json +67 -0
  181. package/src/select/select.css +1593 -0
  182. package/src/select/select.tokens.json +110 -0
  183. package/src/side-navigation/side-navigation.css +1096 -0
  184. package/src/side-navigation/side-navigation.tokens.json +87 -0
  185. package/src/skeleton/skeleton.css +431 -0
  186. package/src/skeleton/skeleton.tokens.json +29 -0
  187. package/src/slider/slider.css +792 -0
  188. package/src/slider/slider.tokens.json +75 -0
  189. package/src/sparkline/sparkline.css +282 -0
  190. package/src/sparkline/sparkline.tokens.json +45 -0
  191. package/src/spinner/spinner.css +224 -0
  192. package/src/spinner/spinner.tokens.json +8 -0
  193. package/src/split-button/split-button.css +253 -0
  194. package/src/split-button/split-button.tokens.json +12 -0
  195. package/src/splitter/splitter.css +336 -0
  196. package/src/splitter/splitter.tokens.json +24 -0
  197. package/src/stack/stack.css +128 -0
  198. package/src/stack/stack.tokens.json +14 -0
  199. package/src/stat-card/stat-card.css +641 -0
  200. package/src/stat-card/stat-card.tokens.json +95 -0
  201. package/src/status-indicator/status-indicator.css +481 -0
  202. package/src/status-indicator/status-indicator.tokens.json +38 -0
  203. package/src/stepper/stepper.css +629 -0
  204. package/src/stepper/stepper.tokens.json +72 -0
  205. package/src/switch/switch.css +703 -0
  206. package/src/switch/switch.tokens.json +75 -0
  207. package/src/table/table.css +1482 -0
  208. package/src/table/table.tokens.json +69 -0
  209. package/src/table-of-contents/table-of-contents.css +533 -0
  210. package/src/table-of-contents/table-of-contents.tokens.json +53 -0
  211. package/src/tabs/tabs.css +998 -0
  212. package/src/tabs/tabs.tokens.json +82 -0
  213. package/src/tag/tag.css +768 -0
  214. package/src/tag/tag.tokens.json +85 -0
  215. package/src/tags-input/tags-input.css +757 -0
  216. package/src/tags-input/tags-input.tokens.json +84 -0
  217. package/src/text-field/text-field.css +900 -0
  218. package/src/text-field/text-field.tokens.json +87 -0
  219. package/src/textarea/textarea.css +365 -0
  220. package/src/textarea/textarea.tokens.json +59 -0
  221. package/src/time-field/time-field.css +719 -0
  222. package/src/time-field/time-field.tokens.json +86 -0
  223. package/src/time-picker/time-picker.css +665 -0
  224. package/src/time-picker/time-picker.tokens.json +66 -0
  225. package/src/time-range-picker/time-range-picker.css +1287 -0
  226. package/src/time-range-picker/time-range-picker.tokens.json +134 -0
  227. package/src/timeline/timeline.css +1596 -0
  228. package/src/timeline/timeline.tokens.json +96 -0
  229. package/src/toast/toast.css +943 -0
  230. package/src/toast/toast.tokens.json +90 -0
  231. package/src/toggle/toggle.css +290 -0
  232. package/src/toggle/toggle.tokens.json +54 -0
  233. package/src/toggle-group/toggle-group.css +463 -0
  234. package/src/toggle-group/toggle-group.tokens.json +54 -0
  235. package/src/toolbar/toolbar.css +434 -0
  236. package/src/toolbar/toolbar.tokens.json +49 -0
  237. package/src/tooltip/tooltip.css +368 -0
  238. package/src/tooltip/tooltip.tokens.json +39 -0
  239. package/src/top-navigation/top-navigation.css +1287 -0
  240. package/src/top-navigation/top-navigation.tokens.json +91 -0
  241. package/src/tour/tour.css +680 -0
  242. package/src/tour/tour.tokens.json +67 -0
  243. package/src/transfer/transfer.css +571 -0
  244. package/src/transfer/transfer.tokens.json +49 -0
  245. package/src/tree/tree.css +1000 -0
  246. package/src/tree/tree.tokens.json +80 -0
  247. package/src/typography/typography.css +211 -0
  248. package/src/typography/typography.tokens.json +31 -0
  249. package/src/upload/upload.css +1706 -0
  250. package/src/upload/upload.tokens.json +123 -0
  251. package/src/visually-hidden/visually-hidden.css +52 -0
  252. package/src/visually-hidden/visually-hidden.tokens.json +1 -0
  253. package/src/wizard/wizard.css +740 -0
  254. package/src/wizard/wizard.tokens.json +63 -0
@@ -0,0 +1,2484 @@
1
+ @layer slotted.components {
2
+ /**
3
+ * ── The frame and the widget ───────────────────────────────────────────
4
+ *
5
+ * The root is the frame, the `grid` role and the box that owns the height;
6
+ * the scroller is one element in. `table` divides the same way and for the
7
+ * same reason: `position: sticky` resolves against the nearest scrolling
8
+ * ancestor, and what the frame holds is everything that must not move when
9
+ * the grid scrolls — the border, the radius, the clip, the footer bar and
10
+ * every `aria-*` property the widget carries.
11
+ *
12
+ * Every token is declared here so both boxes and every cell read one set.
13
+ * The geometry is `table`'s under this family's prefix, so a listing screen
14
+ * that puts a table beside a grid sees one row height, one band and one
15
+ * rule; what the grid adds — the cursor, the range, the resizer, the drop
16
+ * indicator, the group and the filter row — is declared after the shared
17
+ * set.
18
+ */
19
+ [data-ui='data-grid'][data-part='root'] {
20
+ /* Geometry. Every value is the control scale's, so one theme decision
21
+ retunes the rows, the buttons and the fields together, and
22
+ `@media (pointer: coarse)` grows all three at once. */
23
+ /* The head, the filter row, the summary and the footer are the control's
24
+ height at every density; a body row is that too unless the consumer
25
+ states one — a row of avatars is taller than a row of text, and the
26
+ head over it is not. */
27
+ --_head-block: var(--slotted-data-grid-head-block-size, var(--slotted-control-height-md, 34px));
28
+ --_row-block: var(--slotted-data-grid-row-block-size, var(--_head-block));
29
+ --_row-max-block: var(--slotted-data-grid-row-max-block-size, calc(var(--_row-block) * 4));
30
+ --_font-size: var(--slotted-data-grid-font-size, var(--slotted-control-font-size-md, 13px));
31
+ --_icon-size: var(--slotted-data-grid-icon-size, var(--slotted-control-icon-size-md, 16px));
32
+ --_gap: var(--slotted-data-grid-gap, var(--slotted-control-gap-md, 8px));
33
+ --_pad-inline: var(
34
+ --slotted-data-grid-cell-padding-inline,
35
+ var(--slotted-control-padding-inline-md, 13px)
36
+ );
37
+ /* What is left of the control's height around one line of text, so a
38
+ one-line row is exactly the control height at every density. A floor of
39
+ one space step keeps a wrapped row from losing its inset. Derived from
40
+ the head's height, not the row's: a stated row height centres its cells
41
+ and pads nothing. */
42
+ --_pad-block: var(
43
+ --slotted-data-grid-cell-padding-block,
44
+ max(
45
+ var(--slotted-space-2, 0.25rem),
46
+ calc((var(--_head-block) - var(--_font-size) * var(--slotted-line-height-md, 1.5)) / 2)
47
+ )
48
+ );
49
+ --_line: calc(var(--_font-size) * var(--slotted-line-height-md, 1.5));
50
+ --_radius: var(--slotted-data-grid-radius, var(--slotted-radius-md, 6px));
51
+ --_rule: var(--slotted-data-grid-border-color, var(--slotted-border-subtle, ButtonBorder));
52
+ --_rule-strong: var(
53
+ --slotted-data-grid-border-color-strong,
54
+ var(--slotted-border-strong, ButtonBorder)
55
+ );
56
+ --_rule-width: var(--slotted-data-grid-border-width, var(--slotted-border-width-thin, 1px));
57
+ /* The shadow's ink is the scrim's — dark in every scheme, as a shadow is —
58
+ and not the text's, which is white on a dark ground: mixed from the
59
+ text, the shadow under a pinned column was a white haze at night. */
60
+ --_edge-shadow: var(
61
+ --slotted-data-grid-pin-shadow-color,
62
+ color-mix(in oklab, var(--slotted-scrim, rgb(0 0 0 / 0.5)) 22%, transparent)
63
+ );
64
+ --_edge-shadow-size: var(--slotted-data-grid-pin-shadow-size, var(--slotted-space-5, 0.75rem));
65
+ --_indent: var(--slotted-data-grid-indent, var(--slotted-space-6, 1rem));
66
+
67
+ /* Ink and ground. The row's ground is stated rather than transparent,
68
+ because a pinned cell inherits it to cover the cells sliding under it. */
69
+ --_text: var(--slotted-data-grid-color, var(--slotted-text, CanvasText));
70
+ --_muted: var(--slotted-data-grid-color-muted, var(--slotted-text-muted, GrayText));
71
+ /* The ground a raised container publishes (a card, a section on a surface),
72
+ else the page's surface. */
73
+ --_surface: var(
74
+ --slotted-data-grid-surface,
75
+ var(--slotted-ground, var(--slotted-surface, Canvas))
76
+ );
77
+ /* The header band, mixed from the ink at 8% as `table` mixes it: neutral,
78
+ darker than the page in light and lighter in dark, and one step above
79
+ the stripe at 5%. A group row and the filter row take the same mix at
80
+ 4%, so the three grounds of the family are one idea at three strengths. */
81
+ --_head-surface: var(
82
+ --slotted-data-grid-header-surface,
83
+ color-mix(in oklab, var(--slotted-text, CanvasText) 8%, var(--_surface))
84
+ );
85
+ --_head-surface-hover: var(
86
+ --slotted-data-grid-header-surface-hover,
87
+ color-mix(in oklab, var(--slotted-text, CanvasText) 7%, var(--_head-surface))
88
+ );
89
+ --_band-surface: var(
90
+ --slotted-data-grid-band-surface,
91
+ color-mix(in oklab, var(--slotted-text, CanvasText) 4%, var(--_surface))
92
+ );
93
+ --_stripe: var(
94
+ --slotted-data-grid-row-surface-stripe,
95
+ color-mix(in oklab, var(--slotted-text, CanvasText) 5%, var(--_surface))
96
+ );
97
+ --_hover: var(
98
+ --slotted-data-grid-row-surface-hover,
99
+ var(--slotted-tone-secondary-subtle-hover, ButtonFace)
100
+ );
101
+ --_selected: var(
102
+ --slotted-data-grid-row-surface-selected,
103
+ var(--slotted-tone-accent-surface, Canvas)
104
+ );
105
+ --_selected-hover: var(
106
+ --slotted-data-grid-row-surface-selected-hover,
107
+ var(--slotted-tone-accent-subtle-hover, ButtonFace)
108
+ );
109
+ --_selected-text: var(
110
+ --slotted-data-grid-row-color-selected,
111
+ var(--slotted-tone-accent-text, CanvasText)
112
+ );
113
+ /**
114
+ * The cursor and the range are one colour: the accent border, which holds
115
+ * the non-text ratio against the page and against the accent ground it
116
+ * encloses (`docs/token-foundation.md`, Semantic colour). The cursor is
117
+ * drawn at 60% while the grid has no focus — a reader back from a popover
118
+ * finds where they were — and whole under `:focus-visible`.
119
+ */
120
+ --_cursor: var(--slotted-data-grid-cursor-color, var(--slotted-tone-accent-border, Highlight));
121
+ /* The cursor at rest is the theme's own ring at 60%: the same trace the
122
+ cell draws whole under `:focus-visible`, so a reader sees one mark in
123
+ two strengths and not two marks. */
124
+ --_cursor-rest: color-mix(
125
+ in oklab,
126
+ var(--slotted-focus-ring-color, Highlight) 60%,
127
+ transparent
128
+ );
129
+ --_range: var(--slotted-data-grid-range-surface, var(--slotted-tone-accent-surface, Canvas));
130
+ /* The accent's solid ink at a quarter, over whatever the row is: the
131
+ accent's own surfaces are a step from the page in the dark scheme and
132
+ read as nothing on a row. */
133
+ --_changed: var(
134
+ --slotted-data-grid-changed-surface,
135
+ color-mix(in oklab, var(--slotted-tone-accent-solid, Highlight) 24%, transparent)
136
+ );
137
+ --_ring-width: var(--slotted-focus-ring-width, 2px);
138
+ /* A control drawn by the family — the column menu, the filter trigger, the
139
+ expander, the group toggle — is the target minimum where the row has
140
+ room and the row less the cursor's ring where it has not: at dense the
141
+ track is 26 pixels and a 24-pixel button stood over the two-pixel ring
142
+ drawn inward on both sides of it. */
143
+ --_control: min(
144
+ var(--slotted-target-min, 24px),
145
+ calc(
146
+ min(var(--_row-block), var(--_head-block)) - var(--_ring-width) * 2 -
147
+ var(--slotted-space-1, 0.125rem) * 2
148
+ )
149
+ );
150
+ /* Its glyph keeps two pixels of air inside the control's ground, so at
151
+ dense — a 17-pixel control in a 25-pixel track — the ground reads as a
152
+ button and not as a filled cell. */
153
+ --_control-icon: min(
154
+ var(--_icon-size),
155
+ calc(var(--_control) - var(--slotted-space-2, 0.25rem))
156
+ );
157
+ /* The track a body row lays its cells on: the row's height less the rule
158
+ under it, so the cells end where the rule begins. Stated as the whole
159
+ row, the cells overflowed the row's content box by the rule's width and
160
+ a pinned cell, painted above its row, covered the rule — read on the
161
+ pinning scene, where the pinned column had no lines between its rows. */
162
+ --_track: var(--_row-block);
163
+ --_transition: var(--slotted-control-transition-duration, var(--slotted-duration-short))
164
+ var(--slotted-control-transition-easing, var(--slotted-easing-standard));
165
+
166
+ background-color: var(--_surface);
167
+ /* The grid owns its height. `block-size` is the consumer's — a prop on
168
+ the root or the token — and without it the frame grows to its content:
169
+ correct, and windowing nothing, because the window is the whole set. */
170
+ block-size: var(--slotted-data-grid-block-size, auto);
171
+ box-sizing: border-box;
172
+ color: var(--_text);
173
+ display: flex;
174
+ flex-direction: column;
175
+ font-family: var(--slotted-data-grid-font-family, var(--slotted-control-font-family));
176
+ font-size: var(--_font-size);
177
+ /* Every step the sticky parts take is taken inside this box, so a pinned
178
+ column scrolling past an application's own bar does not paint over it. */
179
+ isolation: isolate;
180
+ line-height: var(--slotted-line-height-md, 1.5);
181
+ max-inline-size: 100%;
182
+ min-block-size: 0;
183
+ position: relative;
184
+ }
185
+
186
+ [data-ui='data-grid'][data-variant='framed'] {
187
+ border: var(--_rule-width) solid var(--_rule);
188
+ border-radius: var(--_radius);
189
+ /* A box standing on the page takes the surface step, as table's frame does. */
190
+ box-shadow: var(--slotted-data-grid-shadow, var(--slotted-shadow-xs, none));
191
+ /* `clip` and not `hidden`: `hidden` makes the box a scroll container, and
192
+ under `stickyTo="page"` the header would stick to the frame instead. */
193
+ overflow: clip;
194
+ }
195
+
196
+ /**
197
+ * ── The scroller ───────────────────────────────────────────────────────
198
+ *
199
+ * Both axes. It is the window the virtualiser measures, so it takes the room
200
+ * the frame leaves after the footer and scrolls what does not fit.
201
+ */
202
+ [data-ui='data-grid'] > [data-part='scroll'] {
203
+ /* The container a detail row, a group row and the empty state measure
204
+ their width against: each spans every column of a template that may
205
+ be wider than the frame, and `100cqi` is what holds it to the frame. */
206
+ container-name: slotted-data-grid;
207
+ container-type: inline-size;
208
+ flex: 1 1 auto;
209
+ min-block-size: 0;
210
+ overflow: auto;
211
+ /* Contained across, chained down. The scroller is a scroll container on
212
+ both axes whether or not it overflows on both, and a block axis held
213
+ back swallowed every vertical drag started over the grid: on a phone,
214
+ where the grid is as tall as its rows and the page is what scrolls,
215
+ a finger on the grid moved nothing. The inline axis stays contained,
216
+ so a sideways drag over the columns never reaches the browser's
217
+ back gesture. */
218
+ overscroll-behavior-block: auto;
219
+ overscroll-behavior-inline: contain;
220
+ /* The rows are as wide as the template and the template may be wider than
221
+ the frame; the scroller is what gives the header, the body and the
222
+ summary one width to share. */
223
+ position: relative;
224
+ }
225
+
226
+ [data-ui='data-grid'][data-sticky-to='page'] > [data-part='scroll'] {
227
+ overflow: visible;
228
+ }
229
+
230
+ /**
231
+ * ── Rows are grids ─────────────────────────────────────────────────────
232
+ *
233
+ * Every row — header, filter, body, group, detail, summary — is one CSS grid
234
+ * with the same template, written by the binding as
235
+ * `--slotted-data-grid-template` on the root: one track per visible column
236
+ * in the state's order, a pixel width or `minmax(min, flex fr)`. A cell
237
+ * writes `grid-column` explicitly through `--slotted-data-grid-column`,
238
+ * because with column virtualisation cells are missing from the DOM and
239
+ * auto-placement would move the cells that are there into the tracks of the
240
+ * cells that are not.
241
+ *
242
+ * `min-inline-size: 100%` is what lets a grid narrower than its frame fill
243
+ * it when a column takes `flex`, and `inline-size: fit-content` what lets a
244
+ * wider one scroll: a row stops at the frame unless the template's own
245
+ * minimum — the fixed widths and the flexible columns' floors — is wider.
246
+ * Sized to `max-content`, a flexible column grew with its longest value
247
+ * past the frame on a phone and the value never truncated; the cell's
248
+ * overflow rule only works on a track that has stopped growing.
249
+ */
250
+ /* The grid's own rows are the children of its four row groups and no
251
+ other: a widget inside a cell may hold rows of its own — a multi-select's
252
+ chips are a `row` of a grid pattern — and a descendant selector laid its
253
+ chips out on this grid's column template. */
254
+ [data-ui='data-grid']
255
+ > [data-part='scroll']
256
+ > :is(
257
+ [data-part='head'],
258
+ [data-part='body'],
259
+ [data-part='pinned-rows'],
260
+ [data-part='summary'],
261
+ [data-part='loader']
262
+ )
263
+ > [role='row'] {
264
+ box-sizing: border-box;
265
+ display: grid;
266
+ /* The template, then a filler track that takes whatever the frame has
267
+ left. A row is as wide as the frame or as the template, whichever is
268
+ wider, and with no filler a template narrower than the frame left a
269
+ white strip beside the header band — read on the grouping scene. The
270
+ filler holds no cell; the row's own ground and rule cover it, which is
271
+ why both are painted on the row and not on its cells. It is `auto`, not
272
+ `1fr`: a flexible track takes the free space before an auto track is
273
+ stretched, so a column with `flex` fills the frame and the filler is
274
+ nought; as `1fr` the filler took half the frame beside it. */
275
+ grid-template-columns: var(--slotted-data-grid-template, none) auto;
276
+ inline-size: fit-content;
277
+ min-inline-size: 100%;
278
+ }
279
+
280
+ /* The rules between rows are the rows' own: a row is a block box, unlike a
281
+ table row, so its border runs the whole width, filler included. */
282
+ [data-ui='data-grid'] [data-part='header-row'] {
283
+ background-color: var(--_head-surface);
284
+ border-block-end: var(--_rule-width) solid var(--_rule-strong);
285
+ }
286
+
287
+ /* A row of column groups stands above the row of names, and the rule
288
+ between the two is the lighter one; the strong rule is the last header
289
+ row's, over the values. */
290
+ [data-ui='data-grid'] [data-part='header-row']:not(:last-of-type) {
291
+ border-block-end-color: var(--_rule);
292
+ }
293
+
294
+ [data-ui='data-grid'][data-variant='plain'] [data-part='header-row'] {
295
+ background-color: transparent;
296
+ }
297
+
298
+ [data-ui='data-grid'] [data-part='filter-row'] {
299
+ background-color: var(--_band-surface);
300
+ border-block-end: var(--_rule-width) solid var(--_rule);
301
+ }
302
+
303
+ [data-ui='data-grid']
304
+ > [data-part='scroll']
305
+ > :is(
306
+ [data-part='head'],
307
+ [data-part='body'],
308
+ [data-part='pinned-rows'],
309
+ [data-part='summary'],
310
+ [data-part='loader']
311
+ )
312
+ > [role='row']
313
+ > * {
314
+ grid-column: var(--slotted-data-grid-column, auto);
315
+ }
316
+
317
+ /* A row group is as wide as its rows, never narrower: a block box the
318
+ scroller's width with wider rows inside it is a box its own rows overflow,
319
+ which the page audit reads as a defect and a reader never sees. Sized to
320
+ the rows, the only thing that scrolls is the scroller. */
321
+ [data-ui='data-grid']
322
+ > [data-part='scroll']
323
+ > :is(
324
+ [data-part='head'],
325
+ [data-part='body'],
326
+ [data-part='pinned-rows'],
327
+ [data-part='summary'],
328
+ [data-part='loader']
329
+ ) {
330
+ inline-size: fit-content;
331
+ min-inline-size: 100%;
332
+ }
333
+
334
+ /**
335
+ * ── The header ─────────────────────────────────────────────────────────
336
+ *
337
+ * A column header is what a screen reader repeats for every cell under it,
338
+ * so it is set in the page's own ink. It is a cell of the cursor, a sort
339
+ * control, a place for the column's own controls and a resizer, and every
340
+ * one of those is a box inside a flex row rather than a `button` wrapping
341
+ * the name: the pattern puts the cursor on the header cell itself, and a
342
+ * `button` inside a `columnheader` would be a widget the cursor has to
343
+ * enter.
344
+ */
345
+ [data-ui='data-grid'] > [data-part='scroll'] > [data-part='head'] {
346
+ inset-block-start: var(--slotted-data-grid-sticky-offset, 0);
347
+ position: sticky;
348
+ z-index: calc(var(--slotted-z-index-sticky, 100) + 1);
349
+ }
350
+
351
+ [data-ui='data-grid']:not([data-sticky-header]) > [data-part='scroll'] > [data-part='head'] {
352
+ position: static;
353
+ }
354
+
355
+ [data-ui='data-grid'] [data-part='column-header'],
356
+ [data-ui='data-grid'] [data-part='column-group'],
357
+ [data-ui='data-grid']
358
+ > [data-part='scroll']
359
+ > [data-part='head']
360
+ > [role='row']
361
+ > [data-part='selection'] {
362
+ align-items: center;
363
+ background-color: var(--_head-surface);
364
+ box-sizing: border-box;
365
+ color: var(--_text);
366
+ display: flex;
367
+ font-weight: var(--slotted-font-weight-semibold, 600);
368
+ gap: var(--slotted-space-2, 0.25rem);
369
+ min-block-size: var(--_head-block);
370
+ min-inline-size: 0;
371
+ padding-block: var(--_pad-block);
372
+ padding-inline: var(--_pad-inline);
373
+ /* The actions slot and the resizer are positioned against the cell. Stated
374
+ here, before the pinned rules, so a `position` written beside the slot
375
+ cannot land after `sticky` and unpin the corner. */
376
+ position: relative;
377
+ text-align: start;
378
+ transition: background-color var(--_transition);
379
+ }
380
+
381
+ [data-ui='data-grid'][data-variant='plain']
382
+ :is([data-part='column-header'], [data-part='column-group']) {
383
+ background-color: transparent;
384
+ }
385
+
386
+ /* A group header is centred over the columns it spans and separated from
387
+ the row of column names by the same rule that separates the names from
388
+ the values, one step lighter. */
389
+ [data-ui='data-grid'] [data-part='column-group'] {
390
+ justify-content: center;
391
+ }
392
+
393
+ /* The name takes what is left and truncates: a column name is two or three
394
+ words and its full text is the header's own accessible name. */
395
+ [data-ui='data-grid'] [data-part='column-header'] > span:first-child {
396
+ flex: 1 1 auto;
397
+ min-inline-size: 0;
398
+ overflow: hidden;
399
+ text-overflow: ellipsis;
400
+ white-space: nowrap;
401
+ }
402
+
403
+ /* An aligned column keeps its name over its values. */
404
+ [data-ui='data-grid'] [data-part='column-header'][data-align='center'] {
405
+ justify-content: center;
406
+ }
407
+
408
+ [data-ui='data-grid'] [data-part='column-header'][data-align='center'] > span:first-child {
409
+ flex: 0 1 auto;
410
+ margin-inline-start: auto;
411
+ }
412
+
413
+ [data-ui='data-grid']
414
+ [data-part='column-header']:is([data-align='end'], [data-numeric]:not([data-align]))
415
+ > span:first-child {
416
+ text-align: end;
417
+ }
418
+
419
+ /* A sortable header is a control and takes the hand. */
420
+ [data-ui='data-grid'] [data-part='column-header'][data-sortable] {
421
+ cursor: pointer;
422
+ }
423
+
424
+ /* A header, a filter cell and a summary cell are controls, not prose: a
425
+ Shift-press that appends a sort key must not also select its name. */
426
+ [data-ui='data-grid'] > [data-part='scroll'] > [data-part='head'] > [role='row'] > *,
427
+ [data-ui='data-grid'] > [data-part='scroll'] > [data-part='summary'] > [role='row'] > * {
428
+ user-select: none;
429
+ }
430
+
431
+ /* While rows are chosen with Shift or cells with a drag, the press is a
432
+ selection of the grid's own and not of text: the browser's selection is
433
+ put out of the body for the two modes that press across cells. Ctrl+C
434
+ copies the range; a grid with no selection keeps its text selectable. */
435
+ [data-ui='data-grid']:is([data-selection='multiple'], [data-selection='range'])
436
+ :is([data-part='body'], [data-part='pinned-rows']) {
437
+ user-select: none;
438
+ }
439
+
440
+ [data-ui='data-grid'] [data-part='column-header'][data-sortable]:not([data-disabled]):hover {
441
+ background-color: var(--_head-surface-hover);
442
+ }
443
+
444
+ /**
445
+ * The sort mark: the `chevron-down` `Glyph` the binding draws, turned a half
446
+ * turn for ascending, and the index beside it when the order holds more than
447
+ * one key. It is drawn only
448
+ * on a sorted column and takes no room otherwise: a mark reserved on every
449
+ * sortable header cost a 132-pixel column half its name, and a mark that
450
+ * appears under the pointer moves the name it stands beside. What says a
451
+ * header can be sorted is the hand, the darker band and the menu it
452
+ * reveals — the answer AG Grid gives.
453
+ */
454
+ [data-ui='data-grid'] [data-part='column-header'] > [data-part='sort'] {
455
+ --slotted-icon-size: var(--_icon-size);
456
+ align-items: center;
457
+ color: var(--_text);
458
+ display: none;
459
+ flex: none;
460
+ font-size: var(--slotted-font-size-xs, 0.75rem);
461
+ font-variant-numeric: tabular-nums;
462
+ gap: var(--slotted-space-1, 0.125rem);
463
+ line-height: 1;
464
+ }
465
+
466
+ [data-ui='data-grid'] [data-part='column-header'] > [data-part='sort'] > [data-ui='icon'] {
467
+ transition: rotate var(--_transition);
468
+ }
469
+
470
+ [data-ui='data-grid'] [data-part='column-header'][data-sorted] > [data-part='sort'] {
471
+ display: inline-flex;
472
+ }
473
+
474
+ [data-ui='data-grid']
475
+ [data-part='column-header'][data-sorted='ascending']
476
+ > [data-part='sort']
477
+ > [data-ui='icon'] {
478
+ rotate: 180deg;
479
+ }
480
+
481
+ /* The index of a key past the first, drawn only then: `aria-sort` cannot say
482
+ which key came first, and the number is what says it. */
483
+ [data-ui='data-grid'] [data-part='column-header'] > [data-part='sort']::before {
484
+ content: attr(data-index);
485
+ font-weight: var(--slotted-font-weight-medium, 500);
486
+ }
487
+
488
+ [data-ui='data-grid'] [data-part='column-header'] > [data-part='sort']:not([data-index])::before {
489
+ content: none;
490
+ }
491
+
492
+ /**
493
+ * ── The controls a column carries ──────────────────────────────────────
494
+ *
495
+ * Two, in flow after the name and the sort mark, drawn by this family
496
+ * rather than taken from `button`: a library button at its smallest step is
497
+ * thirty pixels tall against a line of nineteen and a half.
498
+ *
499
+ * The column menu keeps its room always and is revealed by the pointer and
500
+ * by the cursor, so the header row is twelve names and not twenty-four
501
+ * marks and nothing moves when the pointer arrives. The filter trigger is
502
+ * there only while the column is filtered — it is the mark that says so,
503
+ * in the accent ink, and the shortest way back to the panel — and a
504
+ * column that is not filtered reaches the panel through the menu or the
505
+ * filter row. Read on the first capture with both controls reserving room,
506
+ * a 120-pixel column had 34 pixels left for its name.
507
+ */
508
+ [data-ui='data-grid'] [data-part='column-actions'] {
509
+ align-items: center;
510
+ display: inline-flex;
511
+ flex: none;
512
+ gap: var(--slotted-space-1, 0.125rem);
513
+ /* Pulled back onto the line so the target does not grow the header. */
514
+ margin-block: calc((var(--_line) - var(--_control)) / 2);
515
+ /* The slot stands at the trailing edge whatever the column's alignment. */
516
+ margin-inline-start: auto;
517
+ }
518
+
519
+ [data-ui='data-grid'] [data-part='column-actions'] *:has([data-part='filter']) {
520
+ display: contents;
521
+ }
522
+
523
+ [data-ui='data-grid'] [data-part='column-help'],
524
+ [data-ui='data-grid'] [data-part='column-menu'],
525
+ [data-ui='data-grid'] [data-part='column-header'] [data-part='filter'],
526
+ [data-ui='data-grid'] [data-part='expander'],
527
+ [data-ui='data-grid'] [data-part='group-toggle'],
528
+ [data-ui='data-grid'] [data-part='tree-toggle'] {
529
+ /* The mark inside is the `Glyph` the binding draws, at the controls' own
530
+ icon step rather than the box's. */
531
+ --slotted-icon-size: var(--_control-icon);
532
+ align-items: center;
533
+ background-color: transparent;
534
+ block-size: var(--_control);
535
+ border: 0;
536
+ border-radius: var(--slotted-data-grid-control-radius, var(--slotted-radius-sm, 4px));
537
+ color: var(--_muted);
538
+ cursor: pointer;
539
+ display: inline-flex;
540
+ flex: none;
541
+ inline-size: var(--_control);
542
+ justify-content: center;
543
+ padding: 0;
544
+ transition:
545
+ background-color var(--_transition),
546
+ color var(--_transition),
547
+ opacity var(--_transition);
548
+ }
549
+
550
+ [data-ui='data-grid'] [data-part='column-menu'] {
551
+ opacity: 0;
552
+ }
553
+
554
+ [data-ui='data-grid']
555
+ [data-part='column-header']
556
+ [data-part='filter']:not([data-filtered], [data-open]) {
557
+ display: none;
558
+ }
559
+
560
+ [data-ui='data-grid']
561
+ [data-part='column-header']:not([data-disabled]):hover
562
+ [data-part='column-menu'],
563
+ [data-ui='data-grid'] [data-part='column-header']:focus-within [data-part='column-menu'],
564
+ [data-ui='data-grid'] [data-part='column-header'][data-current] [data-part='column-menu'],
565
+ [data-ui='data-grid'] [data-part='column-menu'][data-open] {
566
+ opacity: 1;
567
+ }
568
+
569
+ [data-ui='data-grid'] [data-part='column-help']:not(:disabled):hover,
570
+ [data-ui='data-grid'] [data-part='column-help'][data-open],
571
+ [data-ui='data-grid'] [data-part='column-menu']:not(:disabled):hover,
572
+ [data-ui='data-grid'] [data-part='column-header'] [data-part='filter']:not(:disabled):hover,
573
+ [data-ui='data-grid'] [data-part='group-toggle']:not(:disabled):hover {
574
+ background-color: var(--_head-surface-hover);
575
+ color: var(--_text);
576
+ }
577
+
578
+ [data-ui='data-grid'] [data-part='expander']:not(:disabled):hover {
579
+ background-color: var(--_hover);
580
+ color: var(--_text);
581
+ }
582
+
583
+ [data-ui='data-grid'] [data-part='column-help']:focus-visible,
584
+ [data-ui='data-grid'] [data-part='column-menu']:focus-visible,
585
+ [data-ui='data-grid'] [data-part='column-header'] [data-part='filter']:focus-visible,
586
+ [data-ui='data-grid'] [data-part='expander']:focus-visible,
587
+ [data-ui='data-grid'] [data-part='group-toggle']:focus-visible {
588
+ outline: var(--_ring-width) solid var(--slotted-focus-ring-color, Highlight);
589
+ outline-offset: calc(var(--slotted-focus-ring-offset, 2px) * -1);
590
+ }
591
+
592
+ /* The column menu is the ellipsis. The column help is the circled question
593
+ mark, and it is never hidden: it says the column has something to
594
+ explain, and a mark revealed by hover is one a coarse pointer never sees.
595
+ Both are the `Glyph` the binding draws. */
596
+
597
+ /* The description is the header's `aria-describedby`: read, never drawn,
598
+ and out of the header's flow so it asks for no room beside the name. */
599
+ [data-ui='data-grid'] [data-part='column-description'] {
600
+ block-size: 1px;
601
+ border: 0;
602
+ clip-path: inset(50%);
603
+ inline-size: 1px;
604
+ margin: -1px;
605
+ overflow: hidden;
606
+ padding: 0;
607
+ position: absolute;
608
+ white-space: nowrap;
609
+ }
610
+
611
+ /* The pop-in's column header names the pop-in for a reader: read, never
612
+ drawn, and out of the row's flow so it takes no track of the template. */
613
+ [data-ui='data-grid'] [data-part='popin-header'] {
614
+ block-size: 1px;
615
+ border: 0;
616
+ clip-path: inset(50%);
617
+ inline-size: 1px;
618
+ margin: -1px;
619
+ overflow: hidden;
620
+ padding: 0;
621
+ position: absolute;
622
+ white-space: nowrap;
623
+ }
624
+
625
+ /* The description again at the top of the column menu, for the eye: the
626
+ menu's layer is a portal, so the rule is scoped to the menu, and the text
627
+ is set as a caption over the items, in the muted ink, on the items' inset.
628
+ The screen reader hears it once, through the menu's `aria-describedby`.
629
+ The menu lays its items on a grid of tracks; the caption spans them all. */
630
+ [data-ui='menu'] [data-part='column-menu-description'] {
631
+ grid-column: 1 / -1;
632
+ border-block-end: var(--slotted-border-width-thin, 1px) solid
633
+ var(--slotted-border-subtle, GrayText);
634
+ color: var(--slotted-text-muted, GrayText);
635
+ font-size: var(--slotted-font-size-xs, 0.75rem);
636
+ line-height: var(--slotted-line-height-md, 1.5);
637
+ margin-block-end: var(--slotted-space-2, 0.25rem);
638
+ max-inline-size: 18rem;
639
+ padding-block: var(--slotted-space-2, 0.25rem) var(--slotted-space-3, 0.375rem);
640
+ padding-inline: var(--slotted-control-padding-inline-sm, 0.625rem);
641
+ }
642
+
643
+ /* The filter trigger is the funnel `Glyph` the binding draws, drawn only
644
+ while the column is filtered, in the accent ink. `funnel` joined the
645
+ glyph scale for this family: `table` takes the artwork from the consumer,
646
+ and a control this family draws itself owes its glyph to the scale. */
647
+
648
+ [data-ui='data-grid'] [data-part='column-header'] [data-part='filter'][data-filtered],
649
+ [data-ui='data-grid'] [data-part='column-header'] [data-part='filter'][data-open] {
650
+ color: var(--slotted-tone-accent-text, CanvasText);
651
+ }
652
+
653
+ [data-ui='data-grid']
654
+ [data-part='column-header']
655
+ [data-part='filter'][data-filtered]:not(:disabled):hover {
656
+ background-color: var(--slotted-tone-accent-surface, Canvas);
657
+ }
658
+
659
+ /* The expander and the group toggle are the `chevron-right` `Glyph` the
660
+ binding draws, turned a quarter turn when open and mirrored in a
661
+ right-to-left document. */
662
+ [data-ui='data-grid'] [data-part='expander'] > [data-ui='icon'],
663
+ [data-ui='data-grid'] [data-part='group-toggle'] > [data-ui='icon'],
664
+ [data-ui='data-grid'] button[data-part='tree-toggle'] > [data-ui='icon'] {
665
+ transition: rotate var(--_transition);
666
+ }
667
+
668
+ [data-ui='data-grid'] [data-part='expander'][aria-expanded='true'] > [data-ui='icon'],
669
+ [data-ui='data-grid'] button[data-part='tree-toggle'][aria-expanded='true'] > [data-ui='icon'],
670
+ [data-ui='data-grid']
671
+ [data-part='group-row'][aria-expanded='true']
672
+ [data-part='group-toggle']
673
+ > [data-ui='icon'] {
674
+ rotate: 90deg;
675
+ }
676
+
677
+ [data-ui='data-grid']:dir(rtl) [data-part='expander'] > [data-ui='icon'],
678
+ [data-ui='data-grid']:dir(rtl) [data-part='group-toggle'] > [data-ui='icon'],
679
+ [data-ui='data-grid']:dir(rtl) button[data-part='tree-toggle'] > [data-ui='icon'] {
680
+ rotate: 180deg;
681
+ }
682
+
683
+ [data-ui='data-grid']:dir(rtl) [data-part='expander'][aria-expanded='true'] > [data-ui='icon'],
684
+ [data-ui='data-grid']:dir(rtl)
685
+ button[data-part='tree-toggle'][aria-expanded='true']
686
+ > [data-ui='icon'],
687
+ [data-ui='data-grid']:dir(rtl)
688
+ [data-part='group-row'][aria-expanded='true']
689
+ [data-part='group-toggle']
690
+ > [data-ui='icon'] {
691
+ rotate: 90deg;
692
+ }
693
+
694
+ /**
695
+ * ── The tree ───────────────────────────────────────────────────────────
696
+ *
697
+ * The tree column's cell steps in by its level and leads with the toggle;
698
+ * a leaf keeps the toggle's room, empty, so the names of one level align.
699
+ * The toggle is not a tab stop: the cursor stands on the cell, and the
700
+ * horizontal arrows there open and close. A row waiting for its children
701
+ * turns the chevron into the loader's ring.
702
+ */
703
+ [data-ui='data-grid'] [data-part='cell'][data-tree],
704
+ [data-ui='data-grid'] [data-part='row-header'][data-tree] {
705
+ gap: var(--slotted-space-1, 0.125rem);
706
+ padding-inline-start: calc(
707
+ var(--_pad-inline) + var(--_indent) * (var(--slotted-data-grid-level, 1) - 1)
708
+ );
709
+ }
710
+
711
+ [data-ui='data-grid'] [data-part='tree-toggle'][data-empty] {
712
+ /* The room, and nothing in it. */
713
+ cursor: default;
714
+ }
715
+
716
+ [data-ui='data-grid'] button[data-part='tree-toggle']:not(:disabled):hover {
717
+ background-color: var(--_hover);
718
+ color: var(--_text);
719
+ }
720
+
721
+ /* The binding draws the loader's ring in place of the chevron. */
722
+ [data-ui='data-grid'] button[data-part='tree-toggle'][data-loading] > [data-ui='icon'] {
723
+ animation: slotted-data-grid-turn var(--slotted-duration-loop) linear infinite;
724
+ rotate: 0deg;
725
+ }
726
+
727
+ @keyframes slotted-data-grid-turn {
728
+ to {
729
+ rotate: 1turn;
730
+ }
731
+ }
732
+
733
+ /**
734
+ * ── Reordering rows ────────────────────────────────────────────────────
735
+ *
736
+ * The handle is the grip glyph in the muted ink, the row's whole gutter
737
+ * as its target, a grab cursor over it; disabled while the query is not at
738
+ * rest, it stays where it is at half its ink, so the column keeps its
739
+ * width and the reader hears why nothing moves. A row on its way is
740
+ * dimmed, as a column is.
741
+ */
742
+ [data-ui='data-grid'] [data-part='cell'][data-kind='drag'] {
743
+ justify-content: center;
744
+ padding-inline: 0;
745
+ }
746
+
747
+ [data-ui='data-grid'] [data-part='row-handle'] {
748
+ /* The grip is the `Glyph` the binding draws, at the controls' icon step. */
749
+ --slotted-icon-size: var(--_control-icon);
750
+ align-items: center;
751
+ background-color: transparent;
752
+ block-size: var(--_control);
753
+ border: 0;
754
+ border-radius: var(--slotted-data-grid-control-radius, var(--slotted-radius-sm, 4px));
755
+ color: var(--_muted);
756
+ cursor: grab;
757
+ display: inline-flex;
758
+ flex: none;
759
+ inline-size: var(--_control);
760
+ justify-content: center;
761
+ padding: 0;
762
+ touch-action: none;
763
+ transition:
764
+ background-color var(--_transition),
765
+ color var(--_transition),
766
+ opacity var(--_transition);
767
+ }
768
+
769
+ [data-ui='data-grid'] [data-part='row-handle']:not([data-disabled]):hover {
770
+ background-color: var(--_hover);
771
+ color: var(--_text);
772
+ }
773
+
774
+ [data-ui='data-grid'] [data-part='row-handle'][data-disabled] {
775
+ cursor: default;
776
+ opacity: 0.5;
777
+ }
778
+
779
+ [data-ui='data-grid'] [data-part='row'][data-dragging] {
780
+ opacity: 0.5;
781
+ }
782
+
783
+ [data-ui='data-grid'] [data-part='row'][data-dragging] [data-part='row-handle'] {
784
+ cursor: grabbing;
785
+ }
786
+
787
+ /**
788
+ * ── The resizer ────────────────────────────────────────────────────────
789
+ *
790
+ * Invisible at rest, a strong line under the pointer, the accent line while
791
+ * it is dragged. The target is `--slotted-target-min` wide and centred on
792
+ * the column's trailing edge, so half of it stands over the next column;
793
+ * it is pointer-only — `aria-hidden`, no tab stop — because the keyboard
794
+ * resizes from the header cell itself, and a focusable handle inside a
795
+ * composite widget would be a second tab stop.
796
+ */
797
+ [data-ui='data-grid'] [data-part='resizer'] {
798
+ cursor: col-resize;
799
+ inline-size: var(--slotted-target-min, 24px);
800
+ inset-block: 0;
801
+ inset-inline-end: calc(var(--slotted-target-min, 24px) / -2);
802
+ position: absolute;
803
+ /* Above the neighbour it overlaps, so the half that stands over the next
804
+ header still takes the press. */
805
+ touch-action: none;
806
+ z-index: 1;
807
+ }
808
+
809
+ /* The last column's handle stays inside the row: half a target hanging
810
+ past the last cell was twelve pixels of overflow on a row nothing could
811
+ scroll to. */
812
+ [data-ui='data-grid']
813
+ [data-part='header-row']
814
+ > [data-part='column-header']:last-child
815
+ > [data-part='resizer'] {
816
+ inline-size: calc(var(--slotted-target-min, 24px) / 2);
817
+ inset-inline-end: 0;
818
+ }
819
+
820
+ [data-ui='data-grid'] [data-part='resizer']::after {
821
+ background-color: transparent;
822
+ block-size: 100%;
823
+ content: '';
824
+ inline-size: var(--slotted-border-width-thick, 2px);
825
+ inset-inline-start: calc(50% - var(--slotted-border-width-thick, 2px) / 2);
826
+ position: absolute;
827
+ transition: background-color var(--_transition);
828
+ }
829
+
830
+ [data-ui='data-grid'] [data-part='resizer']:not([data-disabled]):hover::after {
831
+ background-color: var(--_rule-strong);
832
+ }
833
+
834
+ [data-ui='data-grid'] [data-part='resizer'][data-resizing]::after,
835
+ [data-ui='data-grid'] [data-part='column-header'][data-resizing] > [data-part='resizer']::after {
836
+ background-color: var(--_cursor);
837
+ }
838
+
839
+ /**
840
+ * ── Reordering ─────────────────────────────────────────────────────────
841
+ *
842
+ * The dragged header dims; the drop indicator is a line of the accent
843
+ * colour between two columns, positioned by the binding through
844
+ * `--slotted-data-grid-drop-offset` from the scroller's start edge.
845
+ */
846
+ [data-ui='data-grid'] [data-part='column-header'][data-dragging] {
847
+ opacity: 0.5;
848
+ }
849
+
850
+ [data-ui='data-grid'] [data-part='column-header'][data-drop-target] {
851
+ background-color: var(--_head-surface-hover);
852
+ }
853
+
854
+ [data-ui='data-grid'] [data-part='drop-indicator'] {
855
+ background-color: var(--_cursor);
856
+ /* Absolute in the scroller: its box is the content's, so the line is
857
+ moved down by the scrolled distance to span the viewport. */
858
+ block-size: 100%;
859
+ inline-size: var(--slotted-border-width-thick, 2px);
860
+ inset-block-start: var(--slotted-data-grid-drop-scroll, 0px);
861
+ inset-inline-start: calc(
862
+ var(--slotted-data-grid-drop-offset, 0px) - var(--slotted-border-width-thick, 2px) / 2
863
+ );
864
+ pointer-events: none;
865
+ position: absolute;
866
+ z-index: calc(var(--slotted-z-index-sticky, 100) + 3);
867
+ }
868
+
869
+ /* The same line for a row on its way: horizontal, in the gap the drop
870
+ lands in, as wide as the viewport and moved by the sideways scroll. */
871
+ [data-ui='data-grid'] [data-part='drop-indicator'][data-axis='row'] {
872
+ block-size: var(--slotted-border-width-thick, 2px);
873
+ inline-size: 100cqi;
874
+ inset-block-start: calc(
875
+ var(--slotted-data-grid-drop-offset, 0px) - var(--slotted-border-width-thick, 2px) / 2
876
+ );
877
+ inset-inline-start: var(--slotted-data-grid-drop-scroll, 0px);
878
+ }
879
+
880
+ /**
881
+ * ── The filter row ─────────────────────────────────────────────────────
882
+ *
883
+ * One operand per filterable column, `plain` and `sm`, filling the cell,
884
+ * with the operator as a symbol button at the field's start. The row sits
885
+ * on the band at 4%, one step under the header, and is part of the sticky
886
+ * head. It is a row of the grid — `role="row"`, one `gridcell` per column —
887
+ * so the cursor walks it.
888
+ */
889
+ [data-ui='data-grid'] [data-part='filter-row'] > [data-part='filter-cell'] {
890
+ /* The control inside draws no ring of its own: the cell draws the trace
891
+ around the whole of it while anything inside has the focus, so a field
892
+ in the row and the cell it fills are not two rings. The accent edge a
893
+ select takes while it is open is the same second frame, and is put out
894
+ for the same reason. */
895
+ --slotted-focus-ring-width: 0px;
896
+ --slotted-select-open-border-color: transparent;
897
+ --slotted-multi-select-open-border-color: transparent;
898
+
899
+ align-items: center;
900
+ background-color: var(--_band-surface);
901
+ box-sizing: border-box;
902
+ display: flex;
903
+ min-block-size: var(--_head-block);
904
+ min-inline-size: 0;
905
+ padding: 0;
906
+ position: relative;
907
+ }
908
+
909
+ [data-ui='data-grid'] [data-part='filter-row'] > [data-part='filter-cell']:focus-within {
910
+ box-shadow: none;
911
+ outline: var(--_ring-width) solid var(--slotted-focus-ring-color, Highlight);
912
+ outline-offset: calc(var(--_ring-width) * -1);
913
+ }
914
+
915
+ /* The operand fills what the operator leaves and never past the cell: a
916
+ field wider than its column is clipped at the column's edge, not drawn
917
+ into the next one. What a picker opens is in a layer, not in the cell.
918
+ The operand is the cell's child, or its grandchild through an element
919
+ with no part of its own: the Angular binding draws it inside a host
920
+ element (`display: contents`), and a plain child combinator held only
921
+ for React — read live, the Angular operand had no clip and no inset.
922
+ Two selectors, not `:is()`: a combinator inside `:is()` binds the parent
923
+ named there, and `A > :is(host > B)` asks B to be A's child and the
924
+ host's at once. `data-grid.styles.test.mjs` holds the rule for every
925
+ hosted part. */
926
+ [data-ui='data-grid'] [data-part='filter-cell'] > [data-part='filter-operand'],
927
+ [data-ui='data-grid']
928
+ [data-part='filter-cell']
929
+ > :not([data-part])
930
+ > [data-part='filter-operand'] {
931
+ align-items: center;
932
+ display: flex;
933
+ flex: 1 1 auto;
934
+ /* Inside the cell's trace, not under it: the trace is drawn inward by
935
+ the ring's width and a control the cell's full height stood over it,
936
+ and its hover ground covered it. The operand keeps the ring's width
937
+ back from every edge, at every density. */
938
+ margin: var(--_ring-width);
939
+ min-inline-size: 0;
940
+ overflow: clip;
941
+ white-space: nowrap;
942
+ }
943
+
944
+ /* The plain control inside takes what the trace leaves of the row's
945
+ height and a short inset after the operator, the rule every cell
946
+ holding a plain control applies. */
947
+ [data-ui='data-grid']
948
+ [data-part='filter-cell']
949
+ [data-part='filter-operand']
950
+ > [data-part='root'][data-variant='plain'],
951
+ [data-ui='data-grid']
952
+ [data-part='filter-cell']
953
+ [data-part='filter-operand']
954
+ > :not([data-part])
955
+ > [data-part='root'][data-variant='plain'] {
956
+ --slotted-control-padding-inline-sm: var(--slotted-space-3, 0.375rem);
957
+ --slotted-control-height-sm: calc(var(--_head-block) - var(--_ring-width) * 2);
958
+
959
+ flex: 1 1 auto;
960
+ min-inline-size: 0;
961
+ }
962
+
963
+ /* Both fields of a numeric `between` share the line, a dash between them;
964
+ a date, a time or a date-time `between` is one range picker and carries
965
+ no `data-range`. */
966
+ [data-ui='data-grid']
967
+ [data-part='filter-cell']
968
+ [data-part='filter-operand'][data-range]
969
+ > [data-part='root'],
970
+ [data-ui='data-grid']
971
+ [data-part='filter-cell']
972
+ [data-part='filter-operand'][data-range]
973
+ > :not([data-part])
974
+ > [data-part='root'] {
975
+ flex: 1 1 0;
976
+ inline-size: 0;
977
+ }
978
+
979
+ [data-ui='data-grid'] [data-part='filter-cell'] [data-part='filter-operand'][data-range]::before {
980
+ color: var(--_muted);
981
+ content: '–';
982
+ order: 2;
983
+ }
984
+
985
+ [data-ui='data-grid']
986
+ [data-part='filter-cell']
987
+ [data-part='filter-operand'][data-range]
988
+ > [data-part='root']:last-child,
989
+ [data-ui='data-grid']
990
+ [data-part='filter-cell']
991
+ [data-part='filter-operand'][data-range]
992
+ > :not([data-part]):last-child
993
+ > [data-part='root'] {
994
+ order: 3;
995
+ }
996
+
997
+ /**
998
+ * The symbol button. One character of the type, in the muted ink, on a
999
+ * square target the height of the inner control; it opens a `menu` naming
1000
+ * every operator in full, and its accessible name is the operator in full.
1001
+ */
1002
+ [data-ui='data-grid'] [data-part='filter-cell'] [data-part='filter-operator'] {
1003
+ display: inline-flex;
1004
+ flex: none;
1005
+ /* One space step off the cell's edge: glued to it, the symbol read as
1006
+ part of the column beside. */
1007
+ padding-inline-start: var(--slotted-space-3, 0.375rem);
1008
+ }
1009
+
1010
+ [data-ui='data-grid'] [data-part='filter-cell'] [data-part='filter-operator'] > button {
1011
+ align-items: center;
1012
+ background-color: transparent;
1013
+ block-size: var(--slotted-control-inner-size-sm, 22px);
1014
+ border: 0;
1015
+ border-radius: var(--slotted-data-grid-control-radius, var(--slotted-radius-sm, 4px));
1016
+ color: var(--_muted);
1017
+ cursor: pointer;
1018
+ display: inline-flex;
1019
+ font: inherit;
1020
+ font-size: var(--slotted-control-font-size-sm, 12px);
1021
+ font-weight: var(--slotted-font-weight-semibold, 600);
1022
+ inline-size: var(--slotted-control-inner-size-sm, 22px);
1023
+ justify-content: center;
1024
+ line-height: 1;
1025
+ padding: 0;
1026
+ transition:
1027
+ background-color var(--_transition),
1028
+ color var(--_transition);
1029
+ }
1030
+
1031
+ [data-ui='data-grid']
1032
+ [data-part='filter-cell']
1033
+ [data-part='filter-operator']
1034
+ > button:not(:disabled):hover {
1035
+ background-color: var(--_head-surface-hover);
1036
+ color: var(--_text);
1037
+ }
1038
+
1039
+ [data-ui='data-grid']
1040
+ [data-part='filter-cell']
1041
+ [data-part='filter-operator']
1042
+ > button:focus-visible {
1043
+ outline: var(--_ring-width) solid var(--slotted-focus-ring-color, Highlight);
1044
+ outline-offset: calc(var(--slotted-focus-ring-offset, 2px) * -1);
1045
+ }
1046
+
1047
+ [data-ui='data-grid']
1048
+ [data-part='filter-cell'][data-filtered]
1049
+ [data-part='filter-operator']
1050
+ > button {
1051
+ color: var(--slotted-tone-accent-text, CanvasText);
1052
+ }
1053
+
1054
+ /**
1055
+ * ── The filter panel ───────────────────────────────────────────────────
1056
+ *
1057
+ * Rendered in a popover outside the root, so the panel carries `data-ui`
1058
+ * itself. One condition is one line — `[operator] [operand]` in `sm` — the
1059
+ * join is two words in the first column of the second line, and the footer
1060
+ * is two words at the trailing edge. `table`'s panel stacked each control
1061
+ * at full width in `md`; this is the form the invocation asked for, and the
1062
+ * one `table` and `filter-bar` inherit.
1063
+ */
1064
+ /* The popover is `sm` for its type and its measure at that step is
1065
+ sixteen rem: the panel's twenty-four stood out of it on both sides. The
1066
+ layer that holds the panel is given the panel's measure plus the gap the
1067
+ layer keeps around its surface and the surface's own border. The panel
1068
+ is found anywhere under the content, not as its child: the Angular
1069
+ binding draws it inside a host element of its own (`display: contents`),
1070
+ and a child combinator here left that popover at sixteen rem with the
1071
+ panel standing out of it. */
1072
+ [data-ui='popover']:has(> [data-part='content'] [data-ui='data-grid'][data-part='filter-panel']) {
1073
+ --slotted-popover-measure-sm: calc(
1074
+ var(--slotted-data-grid-filter-panel-size, 24rem) + 2 *
1075
+ (var(--slotted-popover-gap, 0.5rem) + var(--slotted-border-width-thin, 1px))
1076
+ );
1077
+ }
1078
+
1079
+ /* A date's `between` is two dates in one frame and a date-time's two
1080
+ date-times: the panel widens for the kinds whose operand is long, so the
1081
+ range is read whole rather than clipped. */
1082
+ [data-ui='popover']:has(
1083
+ > [data-part='content'] [data-ui='data-grid'][data-part='filter-panel'][data-kind='date']
1084
+ ) {
1085
+ --slotted-data-grid-filter-panel-size: var(--slotted-data-grid-filter-panel-size-date, 28rem);
1086
+ }
1087
+
1088
+ [data-ui='popover']:has(
1089
+ > [data-part='content'] [data-ui='data-grid'][data-part='filter-panel'][data-kind='datetime']
1090
+ ) {
1091
+ --slotted-data-grid-filter-panel-size: var(
1092
+ --slotted-data-grid-filter-panel-size-datetime,
1093
+ 34rem
1094
+ );
1095
+ }
1096
+
1097
+ [data-ui='data-grid'][data-part='filter-panel'] {
1098
+ --_operator-size: var(--slotted-data-grid-filter-operator-size, 7rem);
1099
+ --_panel-gap: var(--slotted-space-3, 0.375rem);
1100
+
1101
+ box-sizing: border-box;
1102
+ color: var(--slotted-text, CanvasText);
1103
+ display: grid;
1104
+ font-family: var(--slotted-control-font-family);
1105
+ font-size: var(--slotted-control-font-size-sm, 12px);
1106
+ gap: var(--_panel-gap);
1107
+ /* The join column, the operator and the operand. The first line leaves the
1108
+ join column empty, so the two operators stand one under the other and
1109
+ the eye reads the join as the connector between two lines. */
1110
+ grid-template-columns: max-content var(--_operator-size) minmax(0, 1fr);
1111
+ /* The panel states its width: a popover sized to its content takes the
1112
+ operands' natural width, and two number fields side by side took a
1113
+ between past the popover's own edge. */
1114
+ inline-size: var(--slotted-data-grid-filter-panel-size, 24rem);
1115
+ max-inline-size: 100%;
1116
+ /* The popover pads its header and its body, not its surface: the panel
1117
+ stands in the surface directly and takes the `sm` inset itself. */
1118
+ padding: var(--slotted-popover-padding-block-sm, var(--slotted-space-4, 0.5rem))
1119
+ var(--slotted-popover-padding-inline-sm, var(--slotted-space-5, 0.75rem));
1120
+ }
1121
+
1122
+ [data-ui='data-grid'][data-part='filter-panel'] > [data-part='filter-condition'] {
1123
+ align-items: center;
1124
+ display: grid;
1125
+ gap: var(--_panel-gap);
1126
+ grid-column: 1 / -1;
1127
+ grid-template-columns: subgrid;
1128
+ }
1129
+
1130
+ /* The first condition has no join: its operator starts in the second
1131
+ column. A set filter has one condition and no join at all. */
1132
+ [data-ui='data-grid'][data-part='filter-panel']
1133
+ > [data-part='filter-condition']:first-child
1134
+ > [data-part='filter-operator'] {
1135
+ grid-column: 2;
1136
+ }
1137
+
1138
+ [data-ui='data-grid'][data-part='filter-panel'] [data-part='filter-join'] {
1139
+ grid-column: 1;
1140
+ }
1141
+
1142
+ [data-ui='data-grid'][data-part='filter-panel'] [data-part='filter-operator'] {
1143
+ grid-column: 2;
1144
+ min-inline-size: 0;
1145
+ }
1146
+
1147
+ [data-ui='data-grid'][data-part='filter-panel'] [data-part='filter-operand'] {
1148
+ align-items: center;
1149
+ display: flex;
1150
+ gap: var(--_panel-gap);
1151
+ grid-column: 3;
1152
+ min-inline-size: 0;
1153
+ }
1154
+
1155
+ /* An operator taking no operand leaves the operand column empty rather than
1156
+ drawing a disabled field. */
1157
+ [data-ui='data-grid'][data-part='filter-panel']
1158
+ [data-part='filter-condition'][data-operands='0']
1159
+ > [data-part='filter-operand'] {
1160
+ display: none;
1161
+ }
1162
+
1163
+ /* Both operands of a `between` stand on the line with a dash between them:
1164
+ the pair is one thought and one line says so. */
1165
+ [data-ui='data-grid'][data-part='filter-panel'] [data-part='filter-operand'][data-range] > * {
1166
+ flex: 1 1 0;
1167
+ inline-size: 0;
1168
+ min-inline-size: 0;
1169
+ }
1170
+
1171
+ [data-ui='data-grid'][data-part='filter-panel'] [data-part='filter-operand'][data-range]::before {
1172
+ color: var(--slotted-text-muted);
1173
+ content: '–';
1174
+ order: 1;
1175
+ }
1176
+
1177
+ [data-ui='data-grid'][data-part='filter-panel']
1178
+ [data-part='filter-operand'][data-range]
1179
+ > :last-child {
1180
+ order: 2;
1181
+ }
1182
+
1183
+ [data-ui='data-grid'][data-part='filter-panel'] [data-part='filter-operand'] > [data-part='root'],
1184
+ [data-ui='data-grid'][data-part='filter-panel']
1185
+ [data-part='filter-operand']
1186
+ > :not([data-part])
1187
+ > [data-part='root'] {
1188
+ flex: 1 1 auto;
1189
+ min-inline-size: 0;
1190
+ }
1191
+
1192
+ [data-ui='data-grid'][data-part='filter-panel'] [data-part='filter-operand'] [data-part='input'] {
1193
+ min-inline-size: 0;
1194
+ }
1195
+
1196
+ [data-ui='data-grid'][data-part='filter-panel'] [data-part='filter-actions'] {
1197
+ display: flex;
1198
+ gap: var(--_panel-gap);
1199
+ grid-column: 1 / -1;
1200
+ justify-content: end;
1201
+ margin-block-start: var(--slotted-space-2, 0.25rem);
1202
+ }
1203
+
1204
+ /**
1205
+ * ── Rows and cells ─────────────────────────────────────────────────────
1206
+ */
1207
+ [data-ui='data-grid'] > [data-part='scroll'] > [data-part='body'] {
1208
+ box-sizing: border-box;
1209
+ /* The virtual window: the body is as tall as every row and its padding is
1210
+ the offset of the first mounted one, so the mounted rows stand where the
1211
+ unmounted ones would. No transform, so a pinned column's `sticky`
1212
+ resolves against the scroller with nothing in between. */
1213
+ block-size: var(--slotted-data-grid-window-total, auto);
1214
+ padding-block-start: var(--slotted-data-grid-window-start, 0);
1215
+ }
1216
+
1217
+ [data-ui='data-grid'] [data-part='row'] {
1218
+ background-color: var(--_surface);
1219
+ transition: background-color var(--_transition);
1220
+ }
1221
+
1222
+ /**
1223
+ * The row's height is its grid track, not its box. Stated as `block-size`
1224
+ * on the row, the implicit track still sized itself to the tallest cell —
1225
+ * a 30-pixel button in a 34-pixel row made every track 41 — and each row
1226
+ * overflowed the seven pixels into the next, so a pinned cell painted over
1227
+ * the top of the row below it and every ring drawn inward landed on the
1228
+ * wrong row. Measured on the anatomy scene: cells 41 tall, rows 34 apart.
1229
+ *
1230
+ * `grid-auto-rows` fixes the track, and a cell taller than it overflows its
1231
+ * own box evenly without moving anything. An auto row is at least the row
1232
+ * height and grows to its tallest cell. The cap is the cell's and not the
1233
+ * track's: a row's height is its content, and a grid whose height is its
1234
+ * content grows a `minmax(row, cap)` track to the cap — read on the content
1235
+ * scene, every auto row was four rows tall, one line of text in each.
1236
+ */
1237
+ [data-ui='data-grid'][data-rows='fixed']
1238
+ > [data-part='scroll']
1239
+ > :is([data-part='body'], [data-part='pinned-rows'])
1240
+ > [data-part='row'] {
1241
+ block-size: var(--_row-block);
1242
+ grid-auto-rows: var(--_track);
1243
+ }
1244
+
1245
+ [data-ui='data-grid'][data-rows='auto']
1246
+ > [data-part='scroll']
1247
+ > :is([data-part='body'], [data-part='pinned-rows'])
1248
+ > [data-part='row'] {
1249
+ grid-auto-rows: minmax(var(--_track), auto);
1250
+ }
1251
+
1252
+ /* A value taller than the cap is clipped at it, on the block axis only: a
1253
+ pinned cell's edge shadow is drawn past its inline end. The pop-in is not
1254
+ a value of the row and has no cap. */
1255
+ [data-ui='data-grid'][data-rows='auto']
1256
+ :is([data-part='body'], [data-part='pinned-rows'])
1257
+ > [data-part='row']
1258
+ > :is([role='gridcell'], [role='rowheader']):not([data-part='popin']) {
1259
+ max-block-size: var(--_row-max-block);
1260
+ overflow-y: clip;
1261
+ }
1262
+
1263
+ /* A row that grows to its text aligns its cells to the top: a one-line
1264
+ value centred beside a four-line one floats in the middle of nothing,
1265
+ and the eye reads a row from its first line. Fixed rows stay centred. */
1266
+ [data-ui='data-grid'][data-rows='auto']
1267
+ :is([data-part='body'], [data-part='pinned-rows'])
1268
+ > [data-part='row']
1269
+ > [role='gridcell'],
1270
+ [data-ui='data-grid'][data-rows='auto']
1271
+ :is([data-part='body'], [data-part='pinned-rows'])
1272
+ > [data-part='row']
1273
+ > [role='rowheader'] {
1274
+ align-items: start;
1275
+ }
1276
+
1277
+ [data-ui='data-grid']:is([data-variant='ruled'], [data-variant='framed'])
1278
+ > [data-part='scroll']
1279
+ > :is([data-part='body'], [data-part='pinned-rows']) {
1280
+ --_track: calc(var(--_row-block) - var(--_rule-width));
1281
+ }
1282
+
1283
+ [data-ui='data-grid'] > [data-part='scroll'] > [data-part='head'] > [role='row'],
1284
+ [data-ui='data-grid'] > [data-part='scroll'] > [data-part='summary'] > [role='row'],
1285
+ [data-ui='data-grid'] > [data-part='scroll'] > [data-part='loader'] > [role='row'] {
1286
+ grid-auto-rows: minmax(var(--_head-block), auto);
1287
+ }
1288
+
1289
+ [data-ui='data-grid'] [data-part='cell'],
1290
+ [data-ui='data-grid'] [data-part='row-header'],
1291
+ [data-ui='data-grid'] [data-part='summary-cell'],
1292
+ [data-ui='data-grid']
1293
+ > [data-part='scroll']
1294
+ > :is([data-part='body'], [data-part='pinned-rows'])
1295
+ > [role='row']
1296
+ > [data-part='selection'] {
1297
+ align-items: center;
1298
+ box-sizing: border-box;
1299
+ display: flex;
1300
+ min-inline-size: 0;
1301
+ padding-block: var(--_pad-block);
1302
+ padding-inline: var(--_pad-inline);
1303
+ text-align: start;
1304
+ }
1305
+
1306
+ /* Every cell but a pinned one is a containing block, for the range edges
1307
+ and the invalid mark; a pinned cell is already positioned by `sticky`. */
1308
+ [data-ui='data-grid'] [data-part='cell']:not([data-pinned]) {
1309
+ position: relative;
1310
+ }
1311
+
1312
+ /* The value inside a cell is one box the text treatment applies to. It
1313
+ keeps its own direction: an identifier, an email or a price in a
1314
+ right-to-left document is left-to-right text, and read in `dir="rtl"`
1315
+ "Bloom & Co." had its full stop moved to the front. */
1316
+ [data-ui='data-grid'] [data-part='cell'] > span:not([data-ui], [data-part]),
1317
+ [data-ui='data-grid'] [data-part='row-header'] > span:not([data-ui], [data-part]),
1318
+ [data-ui='data-grid'] [data-part='summary-cell'] > span:not([data-ui], [data-part]) {
1319
+ flex: 1 1 auto;
1320
+ min-inline-size: 0;
1321
+ unicode-bidi: plaintext;
1322
+ }
1323
+
1324
+ /* A family drawn in a cell by a renderer — a badge, a tag — keeps its own
1325
+ size: read on the status column, a badge filled the cell's whole width
1326
+ because it was a span standing where the value stands. */
1327
+ [data-ui='data-grid'] [data-part='cell'] > [data-ui]:not([data-part]) {
1328
+ flex: 0 0 auto;
1329
+ max-inline-size: 100%;
1330
+ }
1331
+
1332
+ /* A row header names the record and carries one step of weight. */
1333
+ [data-ui='data-grid'] [data-part='row-header'] {
1334
+ font-weight: var(--slotted-font-weight-medium, 500);
1335
+ }
1336
+
1337
+ /* The rule between two rows, on the row; the last gives its rule up so a
1338
+ ruled grid does not end in a line with nothing under it. */
1339
+ [data-ui='data-grid']:is([data-variant='ruled'], [data-variant='framed'])
1340
+ :is([data-part='body'], [data-part='pinned-rows'])
1341
+ > [role='row'] {
1342
+ border-block-end: var(--_rule-width) solid var(--_rule);
1343
+ }
1344
+
1345
+ [data-ui='data-grid']:is([data-variant='ruled'], [data-variant='framed'])
1346
+ :is([data-part='body'], [data-part='pinned-rows'])
1347
+ > [role='row']:last-child {
1348
+ border-block-end-color: transparent;
1349
+ }
1350
+
1351
+ /* The stripe, declared before every state. */
1352
+ [data-ui='data-grid'][data-striped]
1353
+ > [data-part='scroll']
1354
+ > :is([data-part='body'], [data-part='pinned-rows'])
1355
+ > [data-part='row']:nth-child(even) {
1356
+ background-color: var(--_stripe);
1357
+ }
1358
+
1359
+ /**
1360
+ * ── The states of a row ────────────────────────────────────────────────
1361
+ *
1362
+ * In the order `STATE_PRECEDENCE` declares them, weakest last.
1363
+ */
1364
+ [data-ui='data-grid']
1365
+ > [data-part='scroll']
1366
+ > :is([data-part='body'], [data-part='pinned-rows'])
1367
+ > [data-part='row']:not([data-disabled]):hover {
1368
+ background-color: var(--_hover);
1369
+ }
1370
+
1371
+ [data-ui='data-grid']
1372
+ > [data-part='scroll']
1373
+ > :is([data-part='body'], [data-part='pinned-rows'])
1374
+ > [data-part='row'][data-selected] {
1375
+ background-color: var(--_selected);
1376
+ color: var(--_selected-text);
1377
+ }
1378
+
1379
+ [data-ui='data-grid']
1380
+ :is([data-part='body'], [data-part='pinned-rows'])
1381
+ > [data-part='row'][data-selected]:not([data-disabled]):hover {
1382
+ background-color: var(--_selected-hover);
1383
+ }
1384
+
1385
+ [data-ui='data-grid'] [data-part='row'][data-disabled] {
1386
+ color: var(--slotted-disabled-foreground, GrayText);
1387
+ }
1388
+
1389
+ /**
1390
+ * ── The cursor ─────────────────────────────────────────────────────────
1391
+ *
1392
+ * One trace for two states. `data-current` is where the reader is, drawn at
1393
+ * 60% so a grid that lost focus to a popover still shows the place;
1394
+ * `:focus-visible` is the same trace whole. It is drawn inward, because a
1395
+ * cell has no margin for an outer ring and the row below would cover it.
1396
+ * A cell in edit draws nothing: its control draws the inner ring.
1397
+ */
1398
+ [data-ui='data-grid'] [role='gridcell'][data-current]:not([data-editing]),
1399
+ [data-ui='data-grid'] [role='rowheader'][data-current]:not([data-editing]),
1400
+ [data-ui='data-grid'] [role='columnheader'][data-current] {
1401
+ /* An inset shadow rather than an outline: an outline is a focus ring by
1402
+ the library's rule and is drawn in the ring colour whole; this is the
1403
+ place the reader left, at rest. */
1404
+ box-shadow: inset 0 0 0 var(--_ring-width) var(--_cursor-rest);
1405
+ }
1406
+
1407
+ /* The grid's own cells, the children of its rows: a chip in a multi-select
1408
+ is a `gridcell` too, of the grid pattern the chips row is, and must keep
1409
+ its own ring rather than take this one. */
1410
+ [data-ui='data-grid'] [role='row'] > [role='gridcell']:focus-visible:not([data-editing]),
1411
+ [data-ui='data-grid'] [role='row'] > [role='rowheader']:focus-visible:not([data-editing]),
1412
+ [data-ui='data-grid'] [role='row'] > [role='columnheader']:focus-visible {
1413
+ box-shadow: none;
1414
+ outline: var(--_ring-width) solid var(--slotted-focus-ring-color, Highlight);
1415
+ outline-offset: calc(var(--_ring-width) * -1);
1416
+ }
1417
+
1418
+ /* A widget the cursor landed on draws its own ring; the cell around it
1419
+ draws none, so there are not two. A cell in edit and a filter cell are
1420
+ the other way round — the control inside is silenced and the cell draws
1421
+ the trace — so they are left out. */
1422
+ [data-ui='data-grid']
1423
+ [role='gridcell'][data-current]:not([data-editing], [data-part='filter-cell']):focus-within:not(
1424
+ :focus
1425
+ ) {
1426
+ outline: none;
1427
+ }
1428
+
1429
+ /**
1430
+ * ── The range ──────────────────────────────────────────────────────────
1431
+ *
1432
+ * Every cell in the rectangle takes the accent ground; the cells on its
1433
+ * edges draw the edge, one per side named in `data-range-edge`, so the
1434
+ * rectangle has one border and not a grid of them. The edge is a
1435
+ * pseudo-element rather than a border, because the cell's own bottom border
1436
+ * is the rule between rows.
1437
+ */
1438
+ [data-ui='data-grid'] [data-part='cell'][data-in-range] {
1439
+ background-color: var(--_range);
1440
+ }
1441
+
1442
+ [data-ui='data-grid'] [data-part='cell'][data-range-edge]::before {
1443
+ border: 0 solid var(--_cursor);
1444
+ content: '';
1445
+ /* Past the cell's own bottom border, so a vertical edge runs unbroken
1446
+ down the rectangle instead of stopping at every row rule. */
1447
+ inset: 0 0 calc(var(--_rule-width) * -1);
1448
+ pointer-events: none;
1449
+ position: absolute;
1450
+ }
1451
+
1452
+ [data-ui='data-grid'] [data-part='cell'][data-range-edge~='top']::before {
1453
+ border-block-start-width: var(--_ring-width);
1454
+ }
1455
+
1456
+ /* The bottom edge ends at the cell: run past it, over the row's rule, it
1457
+ read one pixel thicker than the other three. */
1458
+ [data-ui='data-grid'] [data-part='cell'][data-range-edge~='bottom']::before {
1459
+ border-block-end-width: var(--_ring-width);
1460
+ inset-block-end: 0;
1461
+ }
1462
+
1463
+ [data-ui='data-grid'] [data-part='cell'][data-range-edge~='start']::before {
1464
+ border-inline-start-width: var(--_ring-width);
1465
+ }
1466
+
1467
+ [data-ui='data-grid'] [data-part='cell'][data-range-edge~='end']::before {
1468
+ border-inline-end-width: var(--_ring-width);
1469
+ }
1470
+
1471
+ /**
1472
+ * ── Editing ────────────────────────────────────────────────────────────
1473
+ *
1474
+ * The cell gives its inset to the plain control, the rule `table`'s cell
1475
+ * already applies: the edited value stands where the read value stood.
1476
+ * The control is the row's height, so a row in edit is as tall as one that
1477
+ * is not.
1478
+ */
1479
+ [data-ui='data-grid'] [data-part='cell'][data-editing] {
1480
+ --slotted-control-padding-inline-sm: var(--_pad-inline);
1481
+ --slotted-control-padding-inline-md: var(--_pad-inline);
1482
+ --slotted-control-padding-inline-lg: var(--_pad-inline);
1483
+ /* The control stands inside the cell's trace: the track less the ring
1484
+ on both sides, and the editor keeps the ring's width back from every
1485
+ edge, so nothing the control draws — a hover ground, a stepper — lands
1486
+ on the trace. */
1487
+ --slotted-control-height-sm: calc(var(--_track) - var(--_ring-width) * 2);
1488
+ --slotted-control-height-md: calc(var(--_track) - var(--_ring-width) * 2);
1489
+ --slotted-control-height-lg: calc(var(--_track) - var(--_ring-width) * 2);
1490
+ /* The editor is set in the grid's own type: at `sm` a control is a pixel
1491
+ smaller than the value it replaces, and the cell in edit read as a
1492
+ different row. */
1493
+ --slotted-control-font-size-sm: var(--_font-size);
1494
+ /* The cell draws the cursor whole while it edits, and the control inside
1495
+ draws no ring of its own: two rings two pixels apart read as a defect.
1496
+ The token is zeroed for the descendants only; the cell's own trace
1497
+ reads `--_ring-width`, resolved on the root. */
1498
+ --slotted-focus-ring-width: 0px;
1499
+ --slotted-select-open-border-color: transparent;
1500
+ --slotted-multi-select-open-border-color: transparent;
1501
+
1502
+ /* Every cell in edit carries the cursor's resting trace — in row mode
1503
+ the whole row says it is being edited in one voice — and the cursor's
1504
+ own cell carries it whole. A one-pixel field edge on the others read as
1505
+ a different control, and the control's hover ground covered it. */
1506
+ box-shadow: inset 0 0 0 var(--_ring-width) var(--_cursor-rest);
1507
+ padding: 0;
1508
+ }
1509
+
1510
+ [data-ui='data-grid'] [data-part='cell'][data-editing][data-current] {
1511
+ box-shadow: none;
1512
+ outline: var(--_ring-width) solid var(--slotted-focus-ring-color, Highlight);
1513
+ outline-offset: calc(var(--_ring-width) * -1);
1514
+ }
1515
+
1516
+ /* An invalid draft keeps the cursor and recolours it the way every field
1517
+ does — by pointing the ring at the invalid step: `invalid` does not erase
1518
+ focus. The descendants' ring width is already zero, so the token moves
1519
+ only this cell's own trace. */
1520
+ [data-ui='data-grid'] [data-part='cell'][data-editing][data-invalid] {
1521
+ --slotted-focus-ring-color: var(--slotted-focus-ring-color-invalid);
1522
+ /* Width as well as colour (WCAG 1.4.1): in `high-contrast` the invalid ring
1523
+ is the valid ring's black, and the corner mark is withdrawn while the
1524
+ cell edits. The trace widens inward by the edge every invalid control
1525
+ adds, so the cell's box and its draft stay where they are. */
1526
+ --_invalid-ring: calc(var(--_ring-width) + var(--slotted-control-invalid-edge-width, 1px));
1527
+
1528
+ box-shadow: none;
1529
+ outline: var(--_invalid-ring) solid var(--slotted-focus-ring-color, Highlight);
1530
+ outline-offset: calc(var(--_invalid-ring) * -1);
1531
+ }
1532
+
1533
+ /* The editor of a numeric column keeps the digits at the end, where the
1534
+ read value stood. */
1535
+ [data-ui='data-grid'] [data-numeric][data-editing] [data-part='input'] {
1536
+ text-align: end;
1537
+ }
1538
+
1539
+ [data-ui='data-grid'] [data-part='cell'] > [data-part='editor'] {
1540
+ display: flex;
1541
+ flex: 1 1 auto;
1542
+ /* Inside the cell's trace, at every density. */
1543
+ margin: var(--_ring-width);
1544
+ min-inline-size: 0;
1545
+ }
1546
+
1547
+ [data-ui='data-grid'] [data-part='cell'] > [data-part='editor'] > [data-part='root'],
1548
+ [data-ui='data-grid']
1549
+ [data-part='cell']
1550
+ > [data-part='editor']
1551
+ > :not([data-part])
1552
+ > [data-part='root'] {
1553
+ flex: 1 1 auto;
1554
+ min-inline-size: 0;
1555
+ }
1556
+
1557
+ /**
1558
+ * A cell whose value just changed between two `rows`: the accent's subtle
1559
+ * ground, fading to nothing over the time the grid was given
1560
+ * (`--slotted-data-grid-changed-duration`, written by the binding from
1561
+ * `highlightChanges`). Two identical animations, one per mark, so a cell
1562
+ * changed again mid-fade restarts from full. The length is the grid's own,
1563
+ * outside the motion scale; under reduced motion the ground stays flat and
1564
+ * leaves when the mark does.
1565
+ */
1566
+ [data-ui='data-grid'] [data-part='cell'][data-changed='a'] {
1567
+ animation: slotted-data-grid-changed-a
1568
+ var(--slotted-data-grid-changed-duration, var(--slotted-duration-long))
1569
+ var(--slotted-easing-standard, ease-out) forwards;
1570
+ }
1571
+
1572
+ [data-ui='data-grid'] [data-part='cell'][data-changed='b'] {
1573
+ animation: slotted-data-grid-changed-b
1574
+ var(--slotted-data-grid-changed-duration, var(--slotted-duration-long))
1575
+ var(--slotted-easing-standard, ease-out) forwards;
1576
+ }
1577
+
1578
+ @keyframes slotted-data-grid-changed-a {
1579
+ /* Held for a third of the time, so the eye can land on it, then gone. */
1580
+ 0%,
1581
+ 35% {
1582
+ background-color: var(--_changed);
1583
+ }
1584
+
1585
+ 100% {
1586
+ background-color: transparent;
1587
+ }
1588
+ }
1589
+
1590
+ @keyframes slotted-data-grid-changed-b {
1591
+ /* Held for a third of the time, so the eye can land on it, then gone. */
1592
+ 0%,
1593
+ 35% {
1594
+ background-color: var(--_changed);
1595
+ }
1596
+
1597
+ 100% {
1598
+ background-color: transparent;
1599
+ }
1600
+ }
1601
+
1602
+ @media (prefers-reduced-motion: reduce) {
1603
+ [data-ui='data-grid'] [data-part='cell'][data-changed] {
1604
+ animation: none;
1605
+ background-color: var(--_changed);
1606
+ }
1607
+
1608
+ [data-ui='data-grid'] button[data-part='tree-toggle'][data-loading] > [data-ui='icon'] {
1609
+ animation: none;
1610
+ }
1611
+ }
1612
+
1613
+ /* A cell whose draft failed, or whose value changed under the reader. The
1614
+ mark is a corner in the danger ink, and the message is the control's own
1615
+ `aria-errormessage`; the cell keeps the draft. */
1616
+ [data-ui='data-grid'] [data-part='cell'][data-invalid]:not([data-editing])::after {
1617
+ border-block-start: var(--slotted-space-3, 0.375rem) solid var(--slotted-tone-danger-border);
1618
+ border-inline-start: var(--slotted-space-3, 0.375rem) solid transparent;
1619
+ content: '';
1620
+ inset-block-start: 0;
1621
+ inset-inline-end: 0;
1622
+ pointer-events: none;
1623
+ position: absolute;
1624
+ /* Above the editor, which is a positioned box of its own family. */
1625
+ z-index: 1;
1626
+ }
1627
+
1628
+ /* A cell whose commit is pending dims its value; nothing moves. */
1629
+ [data-ui='data-grid'] [data-part='cell'][data-loading] {
1630
+ color: var(--_muted);
1631
+ }
1632
+
1633
+ /* A cell that does not edit in a grid that does: no mark, no hand. It is a
1634
+ fact the reader hears, not one the eye needs. */
1635
+ [data-ui='data-grid'] [data-part='cell'][data-readonly] {
1636
+ cursor: default;
1637
+ }
1638
+
1639
+ /**
1640
+ * ── Alignment, numerals and the text treatment ─────────────────────────
1641
+ */
1642
+ [data-ui='data-grid'] [data-align='start'] {
1643
+ justify-content: start;
1644
+ text-align: start;
1645
+ }
1646
+
1647
+ [data-ui='data-grid'] [data-align='center'] {
1648
+ justify-content: center;
1649
+ text-align: center;
1650
+ }
1651
+
1652
+ [data-ui='data-grid'] [data-align='end'] {
1653
+ justify-content: end;
1654
+ text-align: end;
1655
+ }
1656
+
1657
+ [data-ui='data-grid'] [data-numeric] {
1658
+ font-variant-numeric: tabular-nums;
1659
+ }
1660
+
1661
+ [data-ui='data-grid'] [data-numeric]:not([data-align]) {
1662
+ justify-content: end;
1663
+ text-align: end;
1664
+ }
1665
+
1666
+ /* `truncate` is the default in a grid: a column has a width by definition,
1667
+ and the value stays whole in the DOM and in the cell's title. The value
1668
+ is the span with no part of its own — in a tree column the toggle comes
1669
+ first, and a rule on `:first-child` left "Barcelona" drawn over the next
1670
+ column once the column was narrower than the name. */
1671
+ [data-ui='data-grid'] [data-text='truncate'] > span:not([data-ui], [data-part]) {
1672
+ overflow: hidden;
1673
+ text-overflow: ellipsis;
1674
+ white-space: nowrap;
1675
+ }
1676
+
1677
+ /* A value that is never broken is clipped at its cell rather than drawn over
1678
+ the next one: a grid's columns are its layout, and a value crossing one is
1679
+ a value in the wrong column. */
1680
+ [data-ui='data-grid'] [data-text='nowrap'] > span:not([data-ui], [data-part]) {
1681
+ overflow: hidden;
1682
+ white-space: nowrap;
1683
+ }
1684
+
1685
+ [data-ui='data-grid'] [data-text='wrap'] > span:not([data-ui], [data-part]) {
1686
+ overflow-wrap: anywhere;
1687
+ }
1688
+
1689
+ /**
1690
+ * ── Pinned columns ─────────────────────────────────────────────────────
1691
+ *
1692
+ * A pinned cell is `sticky` at the summed width of the pinned cells before
1693
+ * it, written by the binding as `--slotted-data-grid-pin-offset`; it
1694
+ * inherits the row's ground, whatever state the row is in, to cover the
1695
+ * cells sliding under it. The edge is drawn as a pseudo-element crossing
1696
+ * the rules rather than as a border, for the reason `table` records: two
1697
+ * borders of one box are mitred at the corner.
1698
+ */
1699
+ [data-ui='data-grid'] [data-pinned='start'] {
1700
+ background-color: inherit;
1701
+ inset-inline-start: var(--slotted-data-grid-pin-offset, 0);
1702
+ position: sticky;
1703
+ z-index: var(--slotted-z-index-sticky, 100);
1704
+ }
1705
+
1706
+ [data-ui='data-grid'] [data-pinned='end'] {
1707
+ background-color: inherit;
1708
+ inset-inline-end: var(--slotted-data-grid-pin-offset, 0);
1709
+ position: sticky;
1710
+ z-index: var(--slotted-z-index-sticky, 100);
1711
+ }
1712
+
1713
+ /* The corner: a header cell that is also pinned takes the step above both. */
1714
+ [data-ui='data-grid'] > [data-part='scroll'] > [data-part='head'] [data-pinned] {
1715
+ background-color: var(--_head-surface);
1716
+ z-index: calc(var(--slotted-z-index-sticky, 100) + 2);
1717
+ }
1718
+
1719
+ /* A plain header has no band: a pinned header cell takes the page's ground,
1720
+ opaque over the names sliding under it, and not the band it would be the
1721
+ only piece of — read on the pop-in scene, a grey block behind one name. */
1722
+ [data-ui='data-grid'][data-variant='plain']
1723
+ > [data-part='scroll']
1724
+ > [data-part='head']
1725
+ [data-pinned],
1726
+ [data-ui='data-grid'][data-variant='plain']
1727
+ > [data-part='scroll']
1728
+ > [data-part='head']
1729
+ > [role='row']
1730
+ > [data-part='selection'] {
1731
+ background-color: var(--_surface);
1732
+ }
1733
+
1734
+ [data-ui='data-grid'] [data-part='filter-row'] > [data-pinned] {
1735
+ background-color: var(--_band-surface);
1736
+ }
1737
+
1738
+ [data-ui='data-grid']
1739
+ > [data-part='scroll']
1740
+ > :is([data-part='body'], [data-part='pinned-rows'])
1741
+ > [data-part='row'][data-selected]
1742
+ > [data-pinned] {
1743
+ background-color: var(--_selected);
1744
+ }
1745
+
1746
+ /* The last pinned cell of each zone draws the edge: the same hairline as
1747
+ the rules between rows, and no more. */
1748
+ [data-ui='data-grid'] [data-pinned][data-pin-edge]::after {
1749
+ background-color: var(--_rule);
1750
+ content: '';
1751
+ inline-size: var(--_rule-width);
1752
+ /* Past the row's own rule below, so the edge is one line down the body. */
1753
+ inset-block: 0 calc(var(--_rule-width) * -1);
1754
+ pointer-events: none;
1755
+ position: absolute;
1756
+ z-index: 1;
1757
+ }
1758
+
1759
+ [data-ui='data-grid']
1760
+ > [data-part='scroll']
1761
+ > :is([data-part='body'], [data-part='pinned-rows'])
1762
+ > [role='row']:last-child
1763
+ > [data-pin-edge]::after {
1764
+ inset-block-end: 0;
1765
+ }
1766
+
1767
+ [data-ui='data-grid'] [data-pinned='start'][data-pin-edge]::after {
1768
+ inset-inline-end: 0;
1769
+ }
1770
+
1771
+ [data-ui='data-grid'] [data-pinned='end'][data-pin-edge]::after {
1772
+ inset-inline-start: 0;
1773
+ }
1774
+
1775
+ /* A pop-in spans the row under the pinned cells, so the edge could never
1776
+ run down the body: it stood as a stub beside each values' line. At rest
1777
+ a grid with a pop-in draws no edge; the shadow below still marks what
1778
+ slides under a zone. */
1779
+ [data-ui='data-grid'][data-popin] [data-pinned][data-pin-edge]::after {
1780
+ background-color: transparent;
1781
+ }
1782
+
1783
+ /* While something is hidden under the zone the hairline casts a short
1784
+ shadow over what slides beneath it — the affordance a grid needs where a
1785
+ table did not, because a grid virtualises columns and a column cut
1786
+ mid-word is not always there to be seen. A shadow and not a stronger
1787
+ line: read on the pinning scene, a dark rule down the body read as a
1788
+ frame inside the frame. */
1789
+ [data-ui='data-grid']
1790
+ [data-part='scroll'][data-overflowing]:not([data-at-start])
1791
+ [data-pinned='start'][data-pin-edge]::after,
1792
+ [data-ui='data-grid']
1793
+ [data-part='scroll'][data-overflowing]:not([data-at-end])
1794
+ [data-pinned='end'][data-pin-edge]::after {
1795
+ /* The colour is the gradient's alone: with the hairline's colour still
1796
+ under the widened box, the shadow read as a solid grey band. */
1797
+ background-color: transparent;
1798
+ inline-size: calc(var(--_rule-width) + var(--_edge-shadow-size));
1799
+ }
1800
+
1801
+ [data-ui='data-grid']
1802
+ [data-part='scroll'][data-overflowing]:not([data-at-start])
1803
+ [data-pinned='start'][data-pin-edge]::after {
1804
+ background-image: linear-gradient(
1805
+ to right,
1806
+ var(--_rule) var(--_rule-width),
1807
+ var(--_edge-shadow) var(--_rule-width),
1808
+ transparent
1809
+ );
1810
+ inset-inline-end: calc(var(--_edge-shadow-size) * -1);
1811
+ }
1812
+
1813
+ [data-ui='data-grid']
1814
+ [data-part='scroll'][data-overflowing]:not([data-at-end])
1815
+ [data-pinned='end'][data-pin-edge]::after {
1816
+ background-image: linear-gradient(
1817
+ to left,
1818
+ var(--_rule) var(--_rule-width),
1819
+ var(--_edge-shadow) var(--_rule-width),
1820
+ transparent
1821
+ );
1822
+ inset-inline-start: calc(var(--_edge-shadow-size) * -1);
1823
+ }
1824
+
1825
+ [data-ui='data-grid']:dir(rtl)
1826
+ [data-part='scroll'][data-overflowing]:not([data-at-start])
1827
+ [data-pinned='start'][data-pin-edge]::after {
1828
+ background-image: linear-gradient(
1829
+ to left,
1830
+ var(--_rule) var(--_rule-width),
1831
+ var(--_edge-shadow) var(--_rule-width),
1832
+ transparent
1833
+ );
1834
+ }
1835
+
1836
+ [data-ui='data-grid']:dir(rtl)
1837
+ [data-part='scroll'][data-overflowing]:not([data-at-end])
1838
+ [data-pinned='end'][data-pin-edge]::after {
1839
+ background-image: linear-gradient(
1840
+ to right,
1841
+ var(--_rule) var(--_rule-width),
1842
+ var(--_edge-shadow) var(--_rule-width),
1843
+ transparent
1844
+ );
1845
+ }
1846
+
1847
+ /**
1848
+ * ── The selection column ───────────────────────────────────────────────
1849
+ *
1850
+ * The cell is the family's and the control is `checkbox`'s. The width is
1851
+ * stated so a column pinned beside it has a length to state.
1852
+ */
1853
+ [data-ui='data-grid'] [role='row'] > [data-part='selection'] {
1854
+ --slotted-selection-halo-opacity: 0;
1855
+
1856
+ justify-content: center;
1857
+ padding-inline: 0;
1858
+ }
1859
+
1860
+ /**
1861
+ * The chooser is always pinned to the start. It is the first column and its
1862
+ * offset is zero, so pinning it costs nothing when no other column is
1863
+ * pinned; and when one is, a chooser that scrolled away left a hole at the
1864
+ * start of every row where the identifier beside it stayed. AG Grid and MUI
1865
+ * X both pin it with the first pinned column; here it is pinned always, so a
1866
+ * consumer never has to say it.
1867
+ */
1868
+ [data-ui='data-grid'] [role='row'] > [data-part='selection'] {
1869
+ background-color: inherit;
1870
+ inset-inline-start: 0;
1871
+ position: sticky;
1872
+ z-index: var(--slotted-z-index-sticky, 100);
1873
+ }
1874
+
1875
+ /* Restated for the head's chooser, whose header rule above made it
1876
+ `relative` at a higher specificity: read scrolled, the select-all left
1877
+ with the columns while every row's chooser stayed. */
1878
+ [data-ui='data-grid']
1879
+ > [data-part='scroll']
1880
+ > [data-part='head']
1881
+ > [role='row']
1882
+ > [data-part='selection'] {
1883
+ background-color: var(--_head-surface);
1884
+ inset-inline-start: 0;
1885
+ position: sticky;
1886
+ z-index: calc(var(--slotted-z-index-sticky, 100) + 2);
1887
+ }
1888
+
1889
+ [data-ui='data-grid']
1890
+ :is([data-part='body'], [data-part='pinned-rows'])
1891
+ > [data-part='row'][data-selected]
1892
+ > [data-part='selection'] {
1893
+ background-color: var(--_selected);
1894
+ }
1895
+
1896
+ [data-ui='data-grid'] [role='row'] > [data-part='selection'] > [data-ui='checkbox'] {
1897
+ block-size: var(--_line);
1898
+ }
1899
+
1900
+ /* A cell holding nothing but the expander is a gutter. The binding names
1901
+ it rather than the stylesheet asking every cell what it holds: `:has()`
1902
+ on every cell of a windowed body is a question asked a thousand times per
1903
+ style pass. */
1904
+ [data-ui='data-grid'] [data-part='cell'][data-kind='expander'] {
1905
+ justify-content: center;
1906
+ padding-inline: 0;
1907
+ }
1908
+
1909
+ /**
1910
+ * ── The detail row ─────────────────────────────────────────────────────
1911
+ *
1912
+ * One cell spanning every column, on the sunken ground, with the record's
1913
+ * own inset so what is inside reads as belonging to the row above.
1914
+ */
1915
+ [data-ui='data-grid'] [data-part='detail'] {
1916
+ background-color: var(--slotted-surface-sunken, Canvas);
1917
+ }
1918
+
1919
+ [data-ui='data-grid'] [data-part='detail'] > * {
1920
+ background-color: var(--slotted-surface-sunken, Canvas);
1921
+ box-sizing: border-box;
1922
+ grid-column: 1 / -1;
1923
+ padding-block: var(--slotted-space-5, 0.75rem);
1924
+ padding-inline: var(--_pad-inline);
1925
+ /* Wide as the frame, whatever the template is: what is inside a detail is
1926
+ the consumer's own layout and not a column. */
1927
+ position: sticky;
1928
+ inset-inline-start: 0;
1929
+ max-inline-size: 100cqi;
1930
+ }
1931
+
1932
+ /**
1933
+ * ── The pop-in ─────────────────────────────────────────────────────────
1934
+ *
1935
+ * The columns a frame too narrow for the template took out, drawn at the
1936
+ * foot of their row as labelled values: one cell on the row's second line,
1937
+ * across every track, and a field per column in as many columns of at
1938
+ * least 9rem as the frame has room for — two on a phone without gutters.
1939
+ * It is as wide as the frame and stays put while the row scrolls sideways,
1940
+ * as the detail does. Outside a compact frame its fields start where the
1941
+ * first data column's text does — after the gutters, whose width the
1942
+ * binding writes as `--slotted-data-grid-leading`, and the cell's inline
1943
+ * padding — and end at
1944
+ * the cell's inline padding. The row paints the ground and the rule, so
1945
+ * hover, selection and the rule between rows cover both lines.
1946
+ */
1947
+ /* The values' line is the density's row, grown to its content; the
1948
+ pop-in's line is as tall as its fields. */
1949
+ [data-ui='data-grid'][data-popin]
1950
+ > [data-part='scroll']
1951
+ > :is([data-part='body'], [data-part='pinned-rows'])
1952
+ > [data-part='row'] {
1953
+ grid-template-rows: minmax(var(--_track), auto) auto;
1954
+ }
1955
+
1956
+ [data-ui='data-grid'][data-popin] > [data-part='scroll'] > [data-part='summary'] > [role='row'] {
1957
+ grid-template-rows: minmax(var(--_head-block), auto) auto;
1958
+ }
1959
+
1960
+ /* The chooser, the expander, the values and the actions stand on the
1961
+ values' line centred, as in a fixed row: the line is the row's own
1962
+ height, and a wrapped value is the only thing that grows it. */
1963
+ [data-ui='data-grid'][data-popin]
1964
+ :is([data-part='body'], [data-part='pinned-rows'])
1965
+ > [data-part='row']
1966
+ > :is([role='gridcell'], [role='rowheader']):not([data-part='popin']) {
1967
+ align-items: center;
1968
+ }
1969
+
1970
+ [data-ui='data-grid'] [data-part='popin'] {
1971
+ align-content: start;
1972
+ box-sizing: border-box;
1973
+ column-gap: var(--slotted-space-4, 0.5rem);
1974
+ display: grid;
1975
+ grid-column: 1 / -1;
1976
+ grid-row: 2;
1977
+ grid-template-columns: repeat(auto-fill, minmax(min(9rem, 100%), 1fr));
1978
+ inset-inline-start: 0;
1979
+ max-inline-size: 100cqi;
1980
+ min-inline-size: 0;
1981
+ padding-block: 0 var(--_pad-block);
1982
+ padding-inline: calc(var(--slotted-data-grid-leading, 0px) + var(--_pad-inline))
1983
+ var(--_pad-inline);
1984
+ position: sticky;
1985
+ row-gap: var(--slotted-space-2, 0.25rem);
1986
+ container: slotted-data-grid-popin / inline-size;
1987
+ }
1988
+
1989
+ /* A compact frame (`COMPACT_BELOW`) cannot spend the gutters' width on an
1990
+ indent: with the chooser and the expander, 88 of 360 pixels left one
1991
+ column of fields where the row's whole width holds two. The fields start
1992
+ at the row's own padding, under the chooser. */
1993
+ @container slotted-data-grid (width < 600px) {
1994
+ [data-ui='data-grid'] [data-part='popin'] {
1995
+ padding-inline-start: var(--_pad-inline);
1996
+ }
1997
+ }
1998
+
1999
+ /* The label above the value, as a form reads, so a narrow field keeps the
2000
+ value's whole width. */
2001
+ [data-ui='data-grid'] [data-part='popin-field'] {
2002
+ display: flex;
2003
+ flex-direction: column;
2004
+ min-inline-size: 0;
2005
+ }
2006
+
2007
+ /* Room for one column of fields only (two need 2 × 9rem and the gap): the
2008
+ label goes beside its value, so each field takes one line instead of two. */
2009
+ @container slotted-data-grid-popin (width < 19rem) {
2010
+ [data-ui='data-grid'] [data-part='popin-field'] {
2011
+ align-items: baseline;
2012
+ column-gap: var(--slotted-space-3, 0.375rem);
2013
+ display: grid;
2014
+ grid-template-columns: minmax(0, 7.5rem) minmax(0, 1fr);
2015
+ }
2016
+ }
2017
+
2018
+ [data-ui='data-grid'] [data-part='popin-label'] {
2019
+ color: var(--_muted);
2020
+ font-size: var(--slotted-font-size-xs, 0.75rem);
2021
+ font-weight: var(--slotted-font-weight-regular, 400);
2022
+ overflow-wrap: anywhere;
2023
+ }
2024
+
2025
+ /* At the start whatever the column's alignment: a number at the end of a
2026
+ field under a label at its start reads as the next field's. One line tall
2027
+ when empty, so an empty value keeps its place under its label, as the
2028
+ empty cell keeps its row. */
2029
+ [data-ui='data-grid'] [data-part='popin-value'] {
2030
+ align-items: center;
2031
+ display: flex;
2032
+ gap: var(--_gap);
2033
+ justify-content: start;
2034
+ min-block-size: var(--_line);
2035
+ min-inline-size: 0;
2036
+ text-align: start;
2037
+ }
2038
+
2039
+ /* Whole, whatever the column's text treatment: a truncated value in a cell
2040
+ keeps its title and its column; in a field it would be lost, and the
2041
+ field has the room to wrap. A wrapped value keeps its own direction and
2042
+ aligns its lines to the grid's start, under its label: read in `rtl`, a
2043
+ company name set by its own direction ran from the far edge of the field,
2044
+ and `match-parent` did not move it. */
2045
+ [data-ui='data-grid'] [data-part='popin-value'][data-text] > span:not([data-ui], [data-part]),
2046
+ [data-ui='data-grid'] [data-part='popin-value'] > span:not([data-ui], [data-part]) {
2047
+ flex: 0 1 auto;
2048
+ min-inline-size: 0;
2049
+ overflow: visible;
2050
+ overflow-wrap: anywhere;
2051
+ text-align: left;
2052
+ text-overflow: clip;
2053
+ unicode-bidi: plaintext;
2054
+ white-space: normal;
2055
+ }
2056
+
2057
+ /* Physical, because `start` under `plaintext` is the value's own start. */
2058
+ [data-ui='data-grid']:dir(rtl) [data-part='popin-value'] > span:not([data-ui], [data-part]) {
2059
+ text-align: right;
2060
+ }
2061
+
2062
+ [data-ui='data-grid'] [data-part='popin-value'] > [data-ui]:not([data-part]) {
2063
+ flex: 0 0 auto;
2064
+ max-inline-size: 100%;
2065
+ }
2066
+
2067
+ /**
2068
+ * ── The group row ──────────────────────────────────────────────────────
2069
+ *
2070
+ * A node of a tree: the toggle, the value, the count and the aggregates,
2071
+ * indented by its level. The ground is the band at 4%, and the text is
2072
+ * semibold, so a reader scanning for a group finds it without reading.
2073
+ */
2074
+ [data-ui='data-grid'] [data-part='group-row'] {
2075
+ background-color: var(--_band-surface);
2076
+ color: var(--_text);
2077
+ font-weight: var(--slotted-font-weight-semibold, 600);
2078
+ }
2079
+
2080
+ [data-ui='data-grid'] [data-part='group-row'] > [role='gridcell'] {
2081
+ align-items: center;
2082
+ box-sizing: border-box;
2083
+ display: flex;
2084
+ gap: var(--_gap);
2085
+ min-block-size: var(--_row-block);
2086
+ min-inline-size: 0;
2087
+ padding-block: var(--_pad-block);
2088
+ padding-inline: var(--_pad-inline);
2089
+ }
2090
+
2091
+ /* The first cell of a group row spans to the first aggregate, indented by
2092
+ its level; the aggregates stand in their own columns. */
2093
+ [data-ui='data-grid'] [data-part='group-row'] > [role='gridcell']:first-child {
2094
+ padding-inline-start: calc(
2095
+ var(--_pad-inline) + var(--_indent) * (var(--slotted-data-grid-level, 1) - 1)
2096
+ );
2097
+ position: sticky;
2098
+ inset-inline-start: 0;
2099
+ }
2100
+
2101
+ [data-ui='data-grid'] [data-part='group-row'] > [role='gridcell']:first-child > span {
2102
+ overflow: hidden;
2103
+ text-overflow: ellipsis;
2104
+ white-space: nowrap;
2105
+ }
2106
+
2107
+ /* The count, one step lighter than the value. */
2108
+ [data-ui='data-grid'] [data-part='group-row'] [data-count] {
2109
+ color: var(--_muted);
2110
+ font-weight: var(--slotted-font-weight-regular, 400);
2111
+ }
2112
+
2113
+ [data-ui='data-grid'] [data-part='group-row'] [data-aggregate] {
2114
+ color: var(--_muted);
2115
+ font-variant-numeric: tabular-nums;
2116
+ font-weight: var(--slotted-font-weight-medium, 500);
2117
+ }
2118
+
2119
+ [data-ui='data-grid'] [data-part='group-toggle'] {
2120
+ margin-block: calc((var(--_line) - var(--slotted-target-min, 24px)) / 2);
2121
+ margin-inline-start: calc(var(--slotted-space-2, 0.25rem) * -1);
2122
+ }
2123
+
2124
+ /**
2125
+ * ── The summary ────────────────────────────────────────────────────────
2126
+ *
2127
+ * The aggregates of the whole query, pinned to the end of the scroller, on
2128
+ * the band with a strong rule above — the header of the other end.
2129
+ */
2130
+ [data-ui='data-grid'] > [data-part='scroll'] > [data-part='summary'] {
2131
+ inset-block-end: var(--slotted-data-grid-sticky-offset-end, 0);
2132
+ position: sticky;
2133
+ z-index: calc(var(--slotted-z-index-sticky, 100) + 1);
2134
+ }
2135
+
2136
+ [data-ui='data-grid']:not([data-sticky-header]) > [data-part='scroll'] > [data-part='summary'] {
2137
+ position: static;
2138
+ }
2139
+
2140
+ /**
2141
+ * ── Pinned rows ────────────────────────────────────────────────────────
2142
+ *
2143
+ * Rows held at an edge: a rowgroup stuck under the head or over the
2144
+ * summary, at the height the binding measured for each, on the page's
2145
+ * surface so the body scrolls under it, with a rule on the side it faces
2146
+ * the body. Their rows are the body's rows.
2147
+ */
2148
+ [data-ui='data-grid'] > [data-part='scroll'] > [data-part='pinned-rows'] {
2149
+ background-color: var(--_surface);
2150
+ position: sticky;
2151
+ z-index: calc(var(--slotted-z-index-sticky, 100) + 1);
2152
+ }
2153
+
2154
+ [data-ui='data-grid'] > [data-part='scroll'] > [data-part='pinned-rows'][data-edge='top'] {
2155
+ border-block-end: var(--_rule-width) solid var(--_rule-strong);
2156
+ inset-block-start: calc(
2157
+ var(--slotted-data-grid-sticky-offset, 0px) + var(--slotted-data-grid-pinned-top-offset, 0px)
2158
+ );
2159
+ }
2160
+
2161
+ [data-ui='data-grid'] > [data-part='scroll'] > [data-part='pinned-rows'][data-edge='bottom'] {
2162
+ border-block-start: var(--_rule-width) solid var(--_rule-strong);
2163
+ inset-block-end: calc(
2164
+ var(--slotted-data-grid-sticky-offset-end, 0px) +
2165
+ var(--slotted-data-grid-pinned-bottom-offset, 0px)
2166
+ );
2167
+ }
2168
+
2169
+ [data-ui='data-grid']:not([data-sticky-header])
2170
+ > [data-part='scroll']
2171
+ > [data-part='pinned-rows'] {
2172
+ position: static;
2173
+ }
2174
+
2175
+ /* A pinned cell inside a held row stands over the row's own ground. */
2176
+ [data-ui='data-grid'] > [data-part='scroll'] > [data-part='pinned-rows'] [data-pinned] {
2177
+ background-color: var(--_surface);
2178
+ }
2179
+
2180
+ [data-ui='data-grid'] > [data-part='scroll'] > [data-part='summary'] > [role='row'] {
2181
+ background-color: var(--_head-surface);
2182
+ border-block-start: var(--_rule-width) solid var(--_rule-strong);
2183
+ }
2184
+
2185
+ [data-ui='data-grid'] [data-part='summary-cell'] {
2186
+ background-color: var(--_head-surface);
2187
+ font-weight: var(--slotted-font-weight-semibold, 600);
2188
+ min-block-size: var(--_head-block);
2189
+ }
2190
+
2191
+ [data-ui='data-grid'] > [data-part='scroll'] > [data-part='summary'] [data-pinned] {
2192
+ background-color: var(--_head-surface);
2193
+ z-index: calc(var(--slotted-z-index-sticky, 100) + 2);
2194
+ }
2195
+
2196
+ /* The label of an aggregate — "Total", "Average" — one step lighter than
2197
+ the number beside it. */
2198
+ [data-ui='data-grid'] [data-part='summary-cell'] [data-label] {
2199
+ color: var(--_muted);
2200
+ font-weight: var(--slotted-font-weight-regular, 400);
2201
+ margin-inline-end: var(--slotted-space-2, 0.25rem);
2202
+ }
2203
+
2204
+ /**
2205
+ * ── Row actions ────────────────────────────────────────────────────────
2206
+ *
2207
+ * Visible under the pointer, under focus and while the cursor is on the
2208
+ * row; always visible on a coarse pointer, where there is no hover to
2209
+ * reveal them.
2210
+ */
2211
+ /* Scoped to the cell's own child: `result-state` in the empty part has an
2212
+ `actions` part of the same name, and a rule naming the part alone hid
2213
+ its buttons. */
2214
+ [data-ui='data-grid'] [data-part='cell'] > [data-part='actions'] {
2215
+ align-items: center;
2216
+ /* One line tall, whatever stands inside: the buttons are taller than the
2217
+ line and overflow it evenly, so the row is still measured by its text
2218
+ and the buttons sit on the value's own centre. */
2219
+ block-size: var(--_line);
2220
+ display: inline-flex;
2221
+ gap: var(--slotted-space-1, 0.125rem);
2222
+ opacity: 0;
2223
+ transition: opacity var(--_transition);
2224
+ }
2225
+
2226
+ /**
2227
+ * The buttons are the family's, drawn to the header controls' geometry —
2228
+ * one WCAG target, the row's hover ground, the family's glyph size for the
2229
+ * artwork the consumer puts in. A library `button` at its smallest step is
2230
+ * thirty pixels against a line of nineteen and a half: read on the anatomy
2231
+ * scene, two of them stood five pixels proud of the row on both sides and
2232
+ * grew its track. The consumer keeps the behaviour and the artwork.
2233
+ */
2234
+ [data-ui='data-grid'] [data-part='cell'] > [data-part='actions'] > button {
2235
+ align-items: center;
2236
+ background-color: transparent;
2237
+ block-size: var(--_control);
2238
+ border: 0;
2239
+ border-radius: var(--slotted-data-grid-control-radius, var(--slotted-radius-sm, 4px));
2240
+ color: var(--_muted);
2241
+ cursor: pointer;
2242
+ display: inline-flex;
2243
+ flex: none;
2244
+ inline-size: var(--_control);
2245
+ justify-content: center;
2246
+ padding: 0;
2247
+ transition:
2248
+ background-color var(--_transition),
2249
+ color var(--_transition);
2250
+ }
2251
+
2252
+ [data-ui='data-grid'] [data-part='cell'] > [data-part='actions'] > button :is(svg, img) {
2253
+ block-size: var(--_control-icon);
2254
+ inline-size: var(--_control-icon);
2255
+ }
2256
+
2257
+ [data-ui='data-grid'] [data-part='cell'] > [data-part='actions'] > button:not(:disabled):hover {
2258
+ background-color: color-mix(in oklab, var(--slotted-text, CanvasText) 7%, transparent);
2259
+ color: var(--_text);
2260
+ }
2261
+
2262
+ [data-ui='data-grid'] [data-part='cell'] > [data-part='actions'] > button:focus-visible {
2263
+ outline: var(--_ring-width) solid var(--slotted-focus-ring-color, Highlight);
2264
+ outline-offset: calc(var(--slotted-focus-ring-offset, 2px) * -1);
2265
+ }
2266
+
2267
+ [data-ui='data-grid']
2268
+ [data-part='row']:not([data-disabled]):hover
2269
+ [data-part='cell']
2270
+ > [data-part='actions'],
2271
+ [data-ui='data-grid'] [data-part='row']:focus-within [data-part='cell'] > [data-part='actions'],
2272
+ [data-ui='data-grid'] [data-part='row'][data-current] [data-part='cell'] > [data-part='actions'],
2273
+ [data-ui='data-grid'] [data-part='row'][data-editing] [data-part='cell'] > [data-part='actions'] {
2274
+ opacity: 1;
2275
+ }
2276
+
2277
+ @media (pointer: coarse) {
2278
+ [data-ui='data-grid'] [data-part='cell'] > [data-part='actions'],
2279
+ [data-ui='data-grid'] [data-part='column-menu'],
2280
+ [data-ui='data-grid'] [data-part='column-header'] [data-part='filter'] {
2281
+ opacity: 1;
2282
+ }
2283
+
2284
+ /* The row grew to the comfortable target through the control scale; the
2285
+ family's own controls grow with it, to the row less the cursor's ring.
2286
+ A 24-pixel button in a 44-pixel row is a target a thumb misses. */
2287
+ [data-ui='data-grid'][data-part='root'] {
2288
+ --_control: min(
2289
+ var(--slotted-target-comfortable, 44px),
2290
+ calc(min(var(--_row-block), var(--_head-block)) - var(--_ring-width) * 2)
2291
+ );
2292
+ --_target: var(--slotted-target-comfortable, 44px);
2293
+ --_reach: max(0px, calc((var(--_target) - var(--_control)) / 2));
2294
+ }
2295
+
2296
+ /* The ground stays the row less the cursor's ring, so the ring still
2297
+ shows round it; the box reaches the full target by a padding the ground
2298
+ does not paint, and gives it back as a negative margin, so no row, no
2299
+ header and no column name moves. Neighbours overlap by the gap. */
2300
+ [data-ui='data-grid'] [data-part='column-help'],
2301
+ [data-ui='data-grid'] [data-part='column-menu'],
2302
+ [data-ui='data-grid'] [data-part='column-header'] [data-part='filter'],
2303
+ [data-ui='data-grid'] [data-part='expander'],
2304
+ [data-ui='data-grid'] [data-part='group-toggle'],
2305
+ [data-ui='data-grid'] [data-part='tree-toggle'],
2306
+ [data-ui='data-grid'] [data-part='row-handle'] {
2307
+ background-clip: content-box;
2308
+ box-sizing: content-box;
2309
+ margin: calc(var(--_reach) * -1);
2310
+ padding: var(--_reach);
2311
+ }
2312
+ }
2313
+
2314
+ /**
2315
+ * ── Empty, and loading ─────────────────────────────────────────────────
2316
+ */
2317
+ [data-ui='data-grid'] > [data-part='scroll'] > [data-part='empty'] {
2318
+ align-items: center;
2319
+ /* The padding stands inside the viewport's width. Without it the box was
2320
+ its own padding wider than the frame, and a grid whose columns all fit
2321
+ gained a sideways scroll the moment the filter emptied it. */
2322
+ box-sizing: border-box;
2323
+ display: flex;
2324
+ /* Wide as the frame's viewport and pinned to its start, so the message
2325
+ stands in the middle of what the reader sees whatever the columns'
2326
+ width and however far the grid is scrolled sideways: laid out over the
2327
+ columns, the middle of a wide template was off screen. */
2328
+ inline-size: 100cqi;
2329
+ inset-inline-start: 0;
2330
+ justify-content: center;
2331
+ min-block-size: calc(var(--_row-block) * 6);
2332
+ padding: var(--slotted-space-8, 1.5rem);
2333
+ position: sticky;
2334
+ }
2335
+
2336
+ [data-ui='data-grid'] > [data-part='scroll'] > [data-part='loader'] > [data-part='row'] > * {
2337
+ align-items: center;
2338
+ box-sizing: border-box;
2339
+ display: flex;
2340
+ /* The row the loader stands in for is a body row, not the header: a
2341
+ consumer who set a row height of its own had every box move when the
2342
+ rows arrived. It is as tall as the rows the grid has measured, because
2343
+ the binding counts skeleton rows by that height: drawn at the base
2344
+ height, a count made for taller rows left the frame half empty. */
2345
+ min-block-size: max(var(--_row-block), var(--slotted-data-grid-loader-row-size, 0px));
2346
+ min-inline-size: 0;
2347
+ padding-block: var(--_pad-block);
2348
+ padding-inline: var(--_pad-inline);
2349
+ }
2350
+
2351
+ [data-ui='data-grid'][aria-busy='true'] > [data-part='scroll'] > [data-part='body'] {
2352
+ opacity: 0.55;
2353
+ pointer-events: none;
2354
+ transition: opacity var(--_transition);
2355
+ }
2356
+
2357
+ /**
2358
+ * ── The footer ─────────────────────────────────────────────────────────
2359
+ *
2360
+ * The bar under the grid and inside the frame: pagination, the range, load
2361
+ * more. A sibling of the scroller, so a grid scrolled sideways does not take
2362
+ * its paging with it. The range reads at the start and the pager sits at
2363
+ * the end — the controls of a table close its inline end, the same edge the
2364
+ * numbers align to. The last item takes the room that is left and aligns
2365
+ * its own content to the end: `pagination` is an inline-size container and
2366
+ * contributes no intrinsic width, so it cannot be sized by its content.
2367
+ */
2368
+ [data-ui='data-grid'] > [data-part='footer'] {
2369
+ align-items: center;
2370
+ border-block-start: var(--_rule-width) solid var(--_rule);
2371
+ box-sizing: border-box;
2372
+ display: flex;
2373
+ flex: none;
2374
+ /* The range wraps onto its own line before the pager is squeezed into a
2375
+ column: read at 240 pixels, `pagination` stacked its three parts. */
2376
+ flex-wrap: wrap;
2377
+ gap: var(--_gap);
2378
+ min-block-size: calc(var(--_head-block) + var(--_pad-block) * 2);
2379
+ padding-block: var(--_pad-block);
2380
+ padding-inline: var(--_pad-inline);
2381
+ }
2382
+
2383
+ [data-ui='data-grid'] > [data-part='footer'] > :last-child {
2384
+ display: flex;
2385
+ flex: 1 1 auto;
2386
+ justify-content: flex-end;
2387
+ min-inline-size: 0;
2388
+ }
2389
+
2390
+ [data-ui='data-grid'] > [data-part='footer'] [data-range] {
2391
+ color: var(--_muted);
2392
+ flex: none;
2393
+ font-variant-numeric: tabular-nums;
2394
+ white-space: nowrap;
2395
+ }
2396
+
2397
+ /**
2398
+ * ── Scheme and platform ────────────────────────────────────────────────
2399
+ */
2400
+ @media (forced-colors: active) {
2401
+ [data-ui='data-grid'] [role='gridcell'][data-current],
2402
+ [data-ui='data-grid'] [role='columnheader'][data-current] {
2403
+ box-shadow: inset 0 0 0 var(--_ring-width) Highlight;
2404
+ }
2405
+
2406
+ [data-ui='data-grid'] [data-part='cell'][data-in-range] {
2407
+ background-color: Highlight;
2408
+ color: HighlightText;
2409
+ }
2410
+
2411
+ [data-ui='data-grid'] [data-part='drop-indicator'] {
2412
+ background-color: Highlight;
2413
+ }
2414
+
2415
+ [data-ui='data-grid'] [data-part='cell'][data-changed] {
2416
+ animation: none;
2417
+ background-color: Highlight;
2418
+ color: HighlightText;
2419
+ }
2420
+
2421
+ /* The resizer's line is a painted background, which the adjustment
2422
+ would otherwise replace, leaving the target with no line to show. */
2423
+ [data-ui='data-grid'] [data-part='resizer']::after {
2424
+ forced-color-adjust: none;
2425
+ }
2426
+ }
2427
+
2428
+ /**
2429
+ * ── Print ──────────────────────────────────────────────────────────────
2430
+ *
2431
+ * The frame grows to its rows, nothing scrolls, nothing sticks, and what
2432
+ * exists only for the pointer — the column's actions, the resize handle,
2433
+ * the drop guide, the cursor's ring, the row's hover ground — is not on
2434
+ * paper. A grid that windows its rows prints the rows it has mounted; a
2435
+ * consumer who prints ten thousand turns the window off first.
2436
+ */
2437
+ @media print {
2438
+ [data-ui='data-grid'][data-part='root'] {
2439
+ block-size: auto;
2440
+ break-inside: auto;
2441
+ }
2442
+
2443
+ [data-ui='data-grid'] > [data-part='scroll'] {
2444
+ overflow: visible;
2445
+ }
2446
+
2447
+ [data-ui='data-grid'] > [data-part='scroll'] > [data-part='head'],
2448
+ [data-ui='data-grid'] > [data-part='scroll'] > [data-part='summary'],
2449
+ [data-ui='data-grid'] > [data-part='scroll'] > [data-part='empty'],
2450
+ [data-ui='data-grid'] > [data-part='scroll'] > [data-part='pinned-rows'],
2451
+ [data-ui='data-grid'] [data-pinned] {
2452
+ position: static;
2453
+ }
2454
+
2455
+ [data-ui='data-grid'] [data-pinned] {
2456
+ box-shadow: none;
2457
+ }
2458
+
2459
+ [data-ui='data-grid'] [data-part='column-actions'],
2460
+ [data-ui='data-grid'] [data-part='row-handle'],
2461
+ [data-ui='data-grid'] [data-part='resizer'],
2462
+ [data-ui='data-grid'] [data-part='drop-indicator'],
2463
+ [data-ui='data-grid'] [data-part='loader'] {
2464
+ display: none;
2465
+ }
2466
+
2467
+ [data-ui='data-grid'] [role='gridcell'][data-current],
2468
+ [data-ui='data-grid'] [role='rowheader'][data-current],
2469
+ [data-ui='data-grid'] [role='columnheader'][data-current] {
2470
+ box-shadow: none;
2471
+ }
2472
+
2473
+ [data-ui='data-grid'] :is([data-part='body'], [data-part='pinned-rows']) > [data-part='row'] {
2474
+ break-inside: avoid;
2475
+ }
2476
+
2477
+ [data-ui='data-grid']
2478
+ > [data-part='scroll']
2479
+ > :is([data-part='body'], [data-part='pinned-rows'])
2480
+ > [data-part='row'] {
2481
+ --_hover: transparent;
2482
+ }
2483
+ }
2484
+ }