@gridkitjs/theme-css 0.0.0-stage → 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/styles.css ADDED
@@ -0,0 +1,2226 @@
1
+ /*
2
+ * GridKit plain-CSS theme.
3
+ *
4
+ * Generated from packages/theme-tailwind/src/styles.css in the GridKit
5
+ * repository by this package's scripts/write-stylesheet.mjs. Don't edit it
6
+ * by hand: change the source and run the package's build.
7
+ *
8
+ * The theme tokens are the `--gridkit-*` custom properties declared on
9
+ * `:root` below. A theme overrides them on `:root`, in a stylesheet loaded
10
+ * after this one.
11
+ *
12
+ * Each colour is a raw `-light`/`-dark` pair and a live name the rules
13
+ * read, pointed at one pair by whichever of these applies:
14
+ *
15
+ * - `:root`, by default: light.
16
+ * - The `prefers-color-scheme: dark` query: dark, following the OS.
17
+ * - `.gridkit-light` / `.gridkit-dark`: one palette, whatever the OS
18
+ * prefers, for an app with its own theme toggle. Put either on `<html>`
19
+ * or on any ancestor of a grid.
20
+ */
21
+
22
+ :root {
23
+ /*
24
+ * A default of `inherit` is the CSS-wide keyword even here: it leaves the
25
+ * token with no value, so a declaration reading it falls back to `unset`,
26
+ * and the property, being one that inherits, takes the page's value.
27
+ */
28
+ --gridkit-font-family: inherit;
29
+ --gridkit-font-size: 1em;
30
+ --gridkit-menu-font-size: 0.875rem;
31
+ --gridkit-bar-font-size: 0.8125rem;
32
+ --gridkit-hint-font-size: 0.75rem;
33
+ --gridkit-header-font-weight: 700;
34
+ --gridkit-header-text-transform: inherit;
35
+ --gridkit-header-letter-spacing: inherit;
36
+ --gridkit-group-font-weight: 600;
37
+ --gridkit-font-variant-numeric: inherit;
38
+
39
+ /*
40
+ * Every padding, gap and control height in the rules is a multiple of the
41
+ * spacing unit, unless a token below sets it for one part.
42
+ */
43
+ --gridkit-spacing: 0.25rem;
44
+ --gridkit-cell-padding-x: calc(var(--gridkit-spacing) * 2);
45
+ --gridkit-cell-padding-y: calc(var(--gridkit-spacing) * 2);
46
+ --gridkit-header-padding-x: var(--gridkit-cell-padding-x);
47
+ --gridkit-header-padding-y: var(--gridkit-cell-padding-y);
48
+ --gridkit-toolbar-padding: calc(var(--gridkit-spacing) * 2);
49
+ --gridkit-group-by-bar-padding: calc(var(--gridkit-spacing) * 2);
50
+ --gridkit-pager-padding: calc(var(--gridkit-spacing) * 2);
51
+ --gridkit-menu-item-padding-x: calc(var(--gridkit-spacing) * 3);
52
+ --gridkit-menu-item-padding-y: calc(var(--gridkit-spacing) * 1.5);
53
+
54
+ /* A line width of 0 turns that kind of line off. */
55
+ --gridkit-border-width: 1px;
56
+ --gridkit-border-style: solid;
57
+ --gridkit-row-line-width: var(--gridkit-border-width);
58
+ --gridkit-column-line-width: var(--gridkit-border-width);
59
+ --gridkit-outer-border-width: 0;
60
+
61
+ --gridkit-radius: 0.25rem;
62
+ --gridkit-menu-radius: 0.375rem;
63
+ --gridkit-control-radius: 0.25rem;
64
+
65
+ --gridkit-icon-size: 1rem;
66
+ --gridkit-header-icon-size: 14px;
67
+ --gridkit-focus-ring-width: 2px;
68
+ --gridkit-indicator-width: 2px;
69
+ /*
70
+ * The shadow's offset and blur. Its colour is the palette pair
71
+ * `--gridkit-overlay-shadow-color`, since a value declared here can't
72
+ * follow the palette.
73
+ */
74
+ --gridkit-overlay-shadow: 0 4px 12px;
75
+ --gridkit-menu-min-width: 12rem;
76
+ --gridkit-column-chooser-width: 16rem;
77
+ --gridkit-filter-menu-width: 18rem;
78
+ --gridkit-column-chooser-max-height: 17.5rem;
79
+ --gridkit-filter-checklist-height: 12rem;
80
+ --gridkit-transition-duration: 0.1s;
81
+
82
+ --gridkit-surface-light: oklch(1 0 0);
83
+ --gridkit-surface-dark: oklch(0.21 0.01 250);
84
+ --gridkit-surface-muted-light: oklch(0.97 0.003 250);
85
+ --gridkit-surface-muted-dark: oklch(0.26 0.012 250);
86
+ --gridkit-line-light: oklch(0.92 0.005 250);
87
+ --gridkit-line-dark: oklch(0.33 0.014 250);
88
+ --gridkit-hover-line-light: oklch(0.65 0.09 255);
89
+ --gridkit-hover-line-dark: oklch(0.75 0.09 255);
90
+ --gridkit-fg-light: oklch(0.21 0.01 250);
91
+ --gridkit-fg-dark: oklch(0.97 0.003 250);
92
+ /*
93
+ * Secondary text sits on `surface-muted` and `selected` too (a group row
94
+ * and its hover), so each `fg-muted` keeps WCAG AA's 4.5:1 against those
95
+ * as well as against `surface`.
96
+ */
97
+ --gridkit-fg-muted-light: oklch(0.5 0.015 250);
98
+ --gridkit-fg-muted-dark: oklch(0.7 0.015 250);
99
+ --gridkit-accent-light: oklch(0.6 0.17 255);
100
+ --gridkit-accent-dark: oklch(0.7 0.15 255);
101
+ --gridkit-selected-light: oklch(0.94 0.03 255);
102
+ --gridkit-selected-dark: oklch(0.32 0.05 255);
103
+ --gridkit-selected-strong-light: oklch(0.9 0.05 255);
104
+ --gridkit-selected-strong-dark: oklch(0.38 0.07 255);
105
+
106
+ /*
107
+ * The colours of single parts. Most default to a palette colour, so a
108
+ * change to the palette carries over to them; the header's text keeps the
109
+ * page's colour, as the cells' does, and rows are not striped.
110
+ */
111
+ --gridkit-header-bg-light: var(--gridkit-surface-light);
112
+ --gridkit-header-bg-dark: var(--gridkit-surface-dark);
113
+ --gridkit-header-fg-light: currentColor;
114
+ --gridkit-header-fg-dark: currentColor;
115
+ --gridkit-group-row-bg-light: var(--gridkit-surface-muted-light);
116
+ --gridkit-group-row-bg-dark: var(--gridkit-surface-muted-dark);
117
+ --gridkit-row-hover-bg-light: var(--gridkit-surface-muted-light);
118
+ --gridkit-row-hover-bg-dark: var(--gridkit-surface-muted-dark);
119
+ --gridkit-row-stripe-bg-light: transparent;
120
+ --gridkit-row-stripe-bg-dark: transparent;
121
+ --gridkit-focus-ring-light: var(--gridkit-accent-light);
122
+ --gridkit-focus-ring-dark: var(--gridkit-accent-dark);
123
+ --gridkit-accent-fg-light: var(--gridkit-surface-light);
124
+ --gridkit-accent-fg-dark: var(--gridkit-surface-dark);
125
+ /* The accent turned red, as light or dark as the accent. */
126
+ --gridkit-error-light: oklch(from var(--gridkit-accent-light) l c 25);
127
+ --gridkit-error-dark: oklch(from var(--gridkit-accent-dark) l c 25);
128
+ --gridkit-scrollbar-thumb-light: color-mix(
129
+ in oklch,
130
+ var(--gridkit-fg-muted-light) 45%,
131
+ transparent
132
+ );
133
+ --gridkit-scrollbar-thumb-dark: color-mix(
134
+ in oklch,
135
+ var(--gridkit-fg-muted-dark) 45%,
136
+ transparent
137
+ );
138
+ --gridkit-overlay-shadow-color-light: oklch(0 0 0 / 0.15);
139
+ --gridkit-overlay-shadow-color-dark: oklch(0 0 0 / 0.15);
140
+
141
+ color-scheme: light;
142
+ --gridkit-surface: var(--gridkit-surface-light);
143
+ --gridkit-surface-muted: var(--gridkit-surface-muted-light);
144
+ --gridkit-line: var(--gridkit-line-light);
145
+ --gridkit-hover-line: var(--gridkit-hover-line-light);
146
+ --gridkit-fg: var(--gridkit-fg-light);
147
+ --gridkit-fg-muted: var(--gridkit-fg-muted-light);
148
+ --gridkit-accent: var(--gridkit-accent-light);
149
+ --gridkit-selected: var(--gridkit-selected-light);
150
+ --gridkit-selected-strong: var(--gridkit-selected-strong-light);
151
+ --gridkit-header-bg: var(--gridkit-header-bg-light);
152
+ --gridkit-header-fg: var(--gridkit-header-fg-light);
153
+ --gridkit-group-row-bg: var(--gridkit-group-row-bg-light);
154
+ --gridkit-row-hover-bg: var(--gridkit-row-hover-bg-light);
155
+ --gridkit-row-stripe-bg: var(--gridkit-row-stripe-bg-light);
156
+ --gridkit-focus-ring: var(--gridkit-focus-ring-light);
157
+ --gridkit-accent-fg: var(--gridkit-accent-fg-light);
158
+ --gridkit-error: var(--gridkit-error-light);
159
+ --gridkit-scrollbar-thumb: var(--gridkit-scrollbar-thumb-light);
160
+ --gridkit-overlay-shadow-color: var(--gridkit-overlay-shadow-color-light);
161
+ }
162
+
163
+ @media (prefers-color-scheme: dark) {
164
+ :root {
165
+ color-scheme: dark;
166
+ --gridkit-surface: var(--gridkit-surface-dark);
167
+ --gridkit-surface-muted: var(--gridkit-surface-muted-dark);
168
+ --gridkit-line: var(--gridkit-line-dark);
169
+ --gridkit-hover-line: var(--gridkit-hover-line-dark);
170
+ --gridkit-fg: var(--gridkit-fg-dark);
171
+ --gridkit-fg-muted: var(--gridkit-fg-muted-dark);
172
+ --gridkit-accent: var(--gridkit-accent-dark);
173
+ --gridkit-selected: var(--gridkit-selected-dark);
174
+ --gridkit-selected-strong: var(--gridkit-selected-strong-dark);
175
+ --gridkit-header-bg: var(--gridkit-header-bg-dark);
176
+ --gridkit-header-fg: var(--gridkit-header-fg-dark);
177
+ --gridkit-group-row-bg: var(--gridkit-group-row-bg-dark);
178
+ --gridkit-row-hover-bg: var(--gridkit-row-hover-bg-dark);
179
+ --gridkit-row-stripe-bg: var(--gridkit-row-stripe-bg-dark);
180
+ --gridkit-focus-ring: var(--gridkit-focus-ring-dark);
181
+ --gridkit-accent-fg: var(--gridkit-accent-fg-dark);
182
+ --gridkit-error: var(--gridkit-error-dark);
183
+ --gridkit-scrollbar-thumb: var(--gridkit-scrollbar-thumb-dark);
184
+ --gridkit-overlay-shadow-color: var(--gridkit-overlay-shadow-color-dark);
185
+ }
186
+ }
187
+
188
+ /*
189
+ * Without relative colour syntax the error default above is invalid, and a
190
+ * declaration reading it would draw nothing at all, so there it is the text
191
+ * colour instead. A theme's own error colour, set on `:root` after this
192
+ * stylesheet, still wins.
193
+ */
194
+ @supports not (color: oklch(from red l c h)) {
195
+ :root {
196
+ --gridkit-error-light: var(--gridkit-fg-light);
197
+ --gridkit-error-dark: var(--gridkit-fg-dark);
198
+ }
199
+ }
200
+
201
+ .gridkit-light {
202
+ color-scheme: light;
203
+ --gridkit-surface: var(--gridkit-surface-light);
204
+ --gridkit-surface-muted: var(--gridkit-surface-muted-light);
205
+ --gridkit-line: var(--gridkit-line-light);
206
+ --gridkit-hover-line: var(--gridkit-hover-line-light);
207
+ --gridkit-fg: var(--gridkit-fg-light);
208
+ --gridkit-fg-muted: var(--gridkit-fg-muted-light);
209
+ --gridkit-accent: var(--gridkit-accent-light);
210
+ --gridkit-selected: var(--gridkit-selected-light);
211
+ --gridkit-selected-strong: var(--gridkit-selected-strong-light);
212
+ --gridkit-header-bg: var(--gridkit-header-bg-light);
213
+ --gridkit-header-fg: var(--gridkit-header-fg-light);
214
+ --gridkit-group-row-bg: var(--gridkit-group-row-bg-light);
215
+ --gridkit-row-hover-bg: var(--gridkit-row-hover-bg-light);
216
+ --gridkit-row-stripe-bg: var(--gridkit-row-stripe-bg-light);
217
+ --gridkit-focus-ring: var(--gridkit-focus-ring-light);
218
+ --gridkit-accent-fg: var(--gridkit-accent-fg-light);
219
+ --gridkit-error: var(--gridkit-error-light);
220
+ --gridkit-scrollbar-thumb: var(--gridkit-scrollbar-thumb-light);
221
+ --gridkit-overlay-shadow-color: var(--gridkit-overlay-shadow-color-light);
222
+ }
223
+
224
+ .gridkit-dark {
225
+ color-scheme: dark;
226
+ --gridkit-surface: var(--gridkit-surface-dark);
227
+ --gridkit-surface-muted: var(--gridkit-surface-muted-dark);
228
+ --gridkit-line: var(--gridkit-line-dark);
229
+ --gridkit-hover-line: var(--gridkit-hover-line-dark);
230
+ --gridkit-fg: var(--gridkit-fg-dark);
231
+ --gridkit-fg-muted: var(--gridkit-fg-muted-dark);
232
+ --gridkit-accent: var(--gridkit-accent-dark);
233
+ --gridkit-selected: var(--gridkit-selected-dark);
234
+ --gridkit-selected-strong: var(--gridkit-selected-strong-dark);
235
+ --gridkit-header-bg: var(--gridkit-header-bg-dark);
236
+ --gridkit-header-fg: var(--gridkit-header-fg-dark);
237
+ --gridkit-group-row-bg: var(--gridkit-group-row-bg-dark);
238
+ --gridkit-row-hover-bg: var(--gridkit-row-hover-bg-dark);
239
+ --gridkit-row-stripe-bg: var(--gridkit-row-stripe-bg-dark);
240
+ --gridkit-focus-ring: var(--gridkit-focus-ring-dark);
241
+ --gridkit-accent-fg: var(--gridkit-accent-fg-dark);
242
+ --gridkit-error: var(--gridkit-error-dark);
243
+ --gridkit-scrollbar-thumb: var(--gridkit-scrollbar-thumb-dark);
244
+ --gridkit-overlay-shadow-color: var(--gridkit-overlay-shadow-color-dark);
245
+ }
246
+
247
+ /*
248
+ * Grid styles.
249
+ */
250
+
251
+ @layer base {
252
+ /*
253
+ * What the rules below take for granted from Tailwind's Preflight, set on
254
+ * the grid's root and on what it renders into `document.body`, so they
255
+ * draw the same in an app without Preflight, and in theme-css's plain
256
+ * CSS. `:where()` keeps each at no specificity, as Preflight's are.
257
+ */
258
+ :where(.gridkit-data-grid-viewport, .drag-ghost, .gridkit-overlay),
259
+ :where(.gridkit-data-grid-viewport, .drag-ghost, .gridkit-overlay) *,
260
+ :where(.gridkit-data-grid-viewport, .drag-ghost, .gridkit-overlay) ::before,
261
+ :where(.gridkit-data-grid-viewport, .drag-ghost, .gridkit-overlay) ::after {
262
+ box-sizing: border-box;
263
+ }
264
+
265
+ :where(.gridkit-data-grid-viewport, .gridkit-overlay) :where(button, input) {
266
+ font: inherit;
267
+ letter-spacing: inherit;
268
+ color: inherit;
269
+ }
270
+
271
+ :where(.gridkit-data-grid-viewport, .gridkit-overlay) :where(svg) {
272
+ vertical-align: middle;
273
+ }
274
+
275
+ /*
276
+ * The table is exactly as wide as its columns, so anything wider than the
277
+ * space available scrolls here rather than squashing the columns. Scrolls
278
+ * the header, body, and footer tables together, since all three are
279
+ * descendants of this one viewport.
280
+ */
281
+ .gridkit-data-grid-viewport {
282
+ overflow-x: auto;
283
+ font-size: var(--gridkit-font-size);
284
+ border-radius: var(--gridkit-radius);
285
+ border: var(--gridkit-outer-border-width) var(--gridkit-border-style)
286
+ var(--gridkit-line);
287
+ }
288
+
289
+ /*
290
+ * The grid's text, set on its root and again on what it renders into
291
+ * `document.body`, which sits outside that root.
292
+ */
293
+ .gridkit-data-grid-viewport,
294
+ .gridkit-data-grid.drag-ghost,
295
+ .group-by-chip.drag-ghost,
296
+ .gridkit-overlay {
297
+ font-family: var(--gridkit-font-family);
298
+ font-variant-numeric: var(--gridkit-font-variant-numeric);
299
+ }
300
+
301
+ /*
302
+ * The row area alone. `height` unset leaves this `auto` — exactly the
303
+ * content-driven height the single-table layout had before the
304
+ * header/body/footer split — and a consumer-set `height` bounds only this
305
+ * div, so the group-by bar, header, footer, and pager stay outside it and
306
+ * always visible while the rows scroll independently.
307
+ *
308
+ * No `position: sticky` here or anywhere else in this file for the header/
309
+ * footer: they don't need it. Splitting header, body, and footer into
310
+ * sibling `<table>`s already keeps the header and footer out of the region
311
+ * that scrolls — there's nothing to "stick," since they were never going
312
+ * to scroll away. A consumer wanting a divider or shadow at the seam adds
313
+ * their own CSS targeting `.gridkit-data-grid-header`/
314
+ * `.gridkit-data-grid-footer`.
315
+ */
316
+ .gridkit-data-grid-body {
317
+ overflow-y: auto;
318
+ height: var(--gridkit-body-height, auto);
319
+ }
320
+
321
+ /*
322
+ * Slim scrollbars on the grid's own scroll regions, never on the page: the
323
+ * viewport, the row area, the menus, and the column chooser and filter
324
+ * checklist lists. Chromium and Firefox honour
325
+ * `scrollbar-width`/`scrollbar-color`; the `::-webkit-scrollbar` rules are
326
+ * only for engines that do not, since a browser that supports the standard
327
+ * properties ignores them anyway.
328
+ */
329
+ .gridkit-data-grid-viewport,
330
+ .gridkit-data-grid-body,
331
+ .gridkit-menu,
332
+ .column-chooser-list,
333
+ .filter-menu-checklist {
334
+ scrollbar-width: thin;
335
+ scrollbar-color: var(--gridkit-scrollbar-thumb) transparent;
336
+ }
337
+
338
+ @supports not (scrollbar-color: auto) {
339
+ .gridkit-data-grid-viewport::-webkit-scrollbar,
340
+ .gridkit-data-grid-body::-webkit-scrollbar,
341
+ .gridkit-menu::-webkit-scrollbar,
342
+ .column-chooser-list::-webkit-scrollbar,
343
+ .filter-menu-checklist::-webkit-scrollbar {
344
+ width: 8px;
345
+ height: 8px;
346
+ }
347
+
348
+ .gridkit-data-grid-viewport::-webkit-scrollbar-track,
349
+ .gridkit-data-grid-body::-webkit-scrollbar-track,
350
+ .gridkit-menu::-webkit-scrollbar-track,
351
+ .column-chooser-list::-webkit-scrollbar-track,
352
+ .filter-menu-checklist::-webkit-scrollbar-track {
353
+ background: transparent;
354
+ }
355
+
356
+ .gridkit-data-grid-viewport::-webkit-scrollbar-thumb,
357
+ .gridkit-data-grid-body::-webkit-scrollbar-thumb,
358
+ .gridkit-menu::-webkit-scrollbar-thumb,
359
+ .column-chooser-list::-webkit-scrollbar-thumb,
360
+ .filter-menu-checklist::-webkit-scrollbar-thumb {
361
+ background-color: var(--gridkit-scrollbar-thumb);
362
+ border-radius: 4px;
363
+ }
364
+ }
365
+
366
+ /*
367
+ * Read but not seen — the grid's live region, which reports a reorder or a
368
+ * resize that only a sighted user could otherwise notice.
369
+ *
370
+ * Clipped to nothing rather than hidden with `display: none` or
371
+ * `visibility: hidden`, either of which would take it out of the
372
+ * accessibility tree along with the announcement.
373
+ */
374
+ .gridkit-sr-only {
375
+ position: absolute;
376
+ width: 1px;
377
+ height: 1px;
378
+ margin: -1px;
379
+ padding: 0;
380
+ overflow: hidden;
381
+ clip-path: inset(50%);
382
+ white-space: nowrap;
383
+ border-width: 0;
384
+ }
385
+
386
+ /*
387
+ * `fixed` is what makes a width a width. Under the default `auto` layout the
388
+ * browser treats the colgroup as a hint and re-apportions space by content,
389
+ * so dragging one column visibly shifts its neighbours.
390
+ */
391
+ .gridkit-data-grid {
392
+ border-collapse: collapse;
393
+ table-layout: fixed;
394
+ }
395
+
396
+ /*
397
+ * Fixed layout does not grow a cell to fit, so content has to be told what to
398
+ * do when it does not. Keeping it on one line is also what lets a column be
399
+ * measured against its content.
400
+ */
401
+ .gridkit-data-grid .header-cell,
402
+ .gridkit-data-grid .grid-cell {
403
+ overflow: hidden;
404
+ text-overflow: ellipsis;
405
+ white-space: nowrap;
406
+ }
407
+
408
+ /*
409
+ * A column that opts into `wrap` needs all three of the rule above undone —
410
+ * `overflow: hidden` alone would still clip a second line, and
411
+ * `text-overflow: ellipsis` only matters once `overflow` stops being
412
+ * `visible`. Fixed layout still holds the column's width; only its height
413
+ * stops being one line's worth.
414
+ *
415
+ * `white-space: normal` alone only breaks at spaces — a single word longer
416
+ * than the column (a long identifier, a URL) has nowhere to break and
417
+ * overflows the fixed width instead, now that `overflow: visible` no longer
418
+ * clips it. `overflow-wrap: anywhere` lets it break mid-word as a last
419
+ * resort, after normal word breaks are exhausted.
420
+ */
421
+ .gridkit-data-grid .header-cell.is-wrapped,
422
+ .gridkit-data-grid .grid-cell.is-wrapped {
423
+ overflow: visible;
424
+ text-overflow: clip;
425
+ white-space: normal;
426
+ overflow-wrap: anywhere;
427
+ }
428
+
429
+ .gridkit-data-grid .grid-header {
430
+ color: var(--gridkit-header-fg);
431
+ background-color: var(--gridkit-header-bg);
432
+ }
433
+
434
+ .gridkit-data-grid .grid-header .header-cell {
435
+ padding: var(--gridkit-header-padding-y) var(--gridkit-header-padding-x);
436
+ position: relative;
437
+ font-weight: var(--gridkit-header-font-weight);
438
+ letter-spacing: var(--gridkit-header-letter-spacing);
439
+ text-transform: var(--gridkit-header-text-transform);
440
+ }
441
+
442
+ .gridkit-data-grid .header-resize-handle {
443
+ position: absolute;
444
+ top: 0;
445
+ right: 0;
446
+ bottom: 0;
447
+ width: 6px;
448
+ cursor: col-resize;
449
+ /* Without this a touch drag scrolls the page instead of resizing. */
450
+ touch-action: none;
451
+ user-select: none;
452
+ }
453
+
454
+ /*
455
+ * A resize colours the edge being dragged on every cell in the column, so it
456
+ * reads as one line down the grid rather than a stack of outlined cells.
457
+ * `box-shadow` rather than `border-right`: under `borders="none"` there is no
458
+ * border to recolour, and adding one would shift the cell's content by a
459
+ * pixel mid-drag.
460
+ */
461
+ .gridkit-data-grid .header-cell.is-resizing,
462
+ .gridkit-data-grid .grid-cell.is-resizing {
463
+ box-shadow: inset calc(var(--gridkit-indicator-width) * -1) 0 0 0
464
+ var(--gridkit-hover-line);
465
+ }
466
+
467
+ /*
468
+ * The grid is one tab stop and the arrow keys move within it, so the focused
469
+ * cell is the only sign of where that stop currently is — without this ring a
470
+ * keyboard user is navigating blind.
471
+ *
472
+ * Inset because the viewport clips its overflow: an outline drawn outwards
473
+ * would be cut off on the first and last columns.
474
+ */
475
+ .gridkit-data-grid .header-cell:focus-visible,
476
+ .gridkit-data-grid .grid-cell:focus-visible {
477
+ outline: var(--gridkit-focus-ring-width) solid var(--gridkit-focus-ring);
478
+ outline-offset: calc(var(--gridkit-focus-ring-width) * -1);
479
+ }
480
+
481
+ /*
482
+ * A reorderable header is the grab zone for its own drag, so a press on it
483
+ * must not select the label instead. Asked for by name: every header is
484
+ * focusable now that the grid roves one tab stop, so a `tabindex` no longer
485
+ * distinguishes the ones that drag.
486
+ */
487
+ .gridkit-data-grid .header-cell.is-reorderable {
488
+ cursor: grab;
489
+ touch-action: none;
490
+ user-select: none;
491
+ }
492
+
493
+ .gridkit-data-grid .header-cell.is-dragging {
494
+ cursor: grabbing;
495
+ opacity: 0.5;
496
+ }
497
+
498
+ /*
499
+ * A header, cell or group row that opens the grid's context menu — its
500
+ * `aria-keyshortcuts` names Shift+F10 then — takes a long-press for that
501
+ * menu, so iOS must not show its own callout, or start selecting the text
502
+ * under the finger, on the same press.
503
+ */
504
+ .gridkit-data-grid [aria-keyshortcuts~="Shift+F10"] {
505
+ -webkit-touch-callout: none;
506
+ }
507
+
508
+ @media (pointer: coarse) {
509
+ .gridkit-data-grid [aria-keyshortcuts~="Shift+F10"] {
510
+ -webkit-user-select: none;
511
+ user-select: none;
512
+ }
513
+ }
514
+
515
+ /*
516
+ * The header trailing the pointer. It sits on the body rather than in the
517
+ * table, so it restates what a header cell gets from its place in the grid.
518
+ *
519
+ * `pointer-events: none` is load-bearing: sitting under the cursor, it would
520
+ * otherwise mask every header it passes over and there would be no drop
521
+ * target at all.
522
+ */
523
+ .gridkit-data-grid.drag-ghost {
524
+ position: fixed;
525
+ top: 0;
526
+ left: 0;
527
+ z-index: 50;
528
+ padding: var(--gridkit-header-padding-y) var(--gridkit-header-padding-x);
529
+ font-size: var(--gridkit-font-size);
530
+ font-weight: var(--gridkit-header-font-weight);
531
+ letter-spacing: var(--gridkit-header-letter-spacing);
532
+ text-transform: var(--gridkit-header-text-transform);
533
+ text-align: center;
534
+ color: var(--gridkit-header-fg);
535
+ background-color: var(--gridkit-header-bg);
536
+ border: var(--gridkit-border-width) dashed var(--gridkit-accent);
537
+ opacity: 0.75;
538
+ pointer-events: none;
539
+ /*
540
+ * Restated because the rule that clips a header's text is scoped to a
541
+ * `.header-cell` inside the grid, and the ghost carries that class while
542
+ * being the grid element itself.
543
+ */
544
+ overflow: hidden;
545
+ text-overflow: ellipsis;
546
+ white-space: nowrap;
547
+ /* Placed by a transform written straight to the node each pointer move. */
548
+ will-change: transform;
549
+ }
550
+
551
+ /*
552
+ * Where the column would land, drawn on the gap's own edge so the one gap
553
+ * between two columns reads as one line.
554
+ *
555
+ * `box-shadow` for the reason the resize line uses it, and because a
556
+ * recoloured `borders-vertical` border would land on the neighbour and read
557
+ * as a column out.
558
+ */
559
+ .gridkit-data-grid .header-cell.is-drop-before {
560
+ box-shadow: inset var(--gridkit-indicator-width) 0 0 0 var(--gridkit-accent);
561
+ }
562
+
563
+ .gridkit-data-grid .header-cell.is-drop-after {
564
+ box-shadow: inset calc(var(--gridkit-indicator-width) * -1) 0 0 0
565
+ var(--gridkit-accent);
566
+ }
567
+
568
+ /*
569
+ * Docked to the header's own right edge, absolutely — like the resize
570
+ * handle, and for the same reason: sitting in the label's normal flow, a
571
+ * long label under `text-overflow: ellipsis` pushes the toggle out past
572
+ * where `overflow: hidden` clips it, leaving it unclickable (a real click
573
+ * there lands on the header cell instead and selects the column). Docked
574
+ * to a fixed offset, it stays inside the clipped area — and clear of the
575
+ * resize handle's own 6px strip at `right: 0`, so a resizable, sortable
576
+ * column keeps two independent hit targets rather than one shadowing the
577
+ * other.
578
+ */
579
+ .gridkit-data-grid .header-sort-toggle {
580
+ display: inline-flex;
581
+ position: absolute;
582
+ top: 50%;
583
+ right: calc(var(--gridkit-spacing) * 3);
584
+ transform: translateY(-50%);
585
+ align-items: center;
586
+ gap: calc(var(--gridkit-spacing) * 0.5);
587
+ cursor: pointer;
588
+ color: var(--gridkit-fg-muted);
589
+ }
590
+
591
+ .gridkit-data-grid .header-sort-toggle svg {
592
+ width: var(--gridkit-header-icon-size);
593
+ height: var(--gridkit-header-icon-size);
594
+ flex-shrink: 0;
595
+ }
596
+
597
+ /*
598
+ * Hidden until the column has a direction or the header is hovered or
599
+ * focused — an unsorted, un-hovered header stays quiet.
600
+ */
601
+ .gridkit-data-grid
602
+ .header-sort-toggle:not(.is-sorted-asc):not(.is-sorted-desc) {
603
+ opacity: 0;
604
+ }
605
+
606
+ .gridkit-data-grid .grid-header .header-cell:hover .header-sort-toggle,
607
+ .gridkit-data-grid .header-cell:focus-visible .header-sort-toggle {
608
+ opacity: 1;
609
+ }
610
+
611
+ .gridkit-data-grid .header-sort-toggle.is-sorted-asc,
612
+ .gridkit-data-grid .header-sort-toggle.is-sorted-desc {
613
+ opacity: 1;
614
+ color: var(--gridkit-accent);
615
+ }
616
+
617
+ /* One chevron covers both directions; `desc` is `asc` turned upside down. */
618
+ .gridkit-data-grid .header-sort-toggle.is-sorted-desc svg {
619
+ transform: rotate(180deg);
620
+ }
621
+
622
+ .gridkit-data-grid .header-sort-priority {
623
+ font-size: calc(var(--gridkit-hint-font-size) * 5 / 6);
624
+ line-height: 1;
625
+ }
626
+
627
+ .gridkit-data-grid:not(.no-hover-columns) .grid-header .header-cell:hover {
628
+ background-color: var(--gridkit-surface-muted);
629
+ }
630
+
631
+ /*
632
+ * Docked before the label, in normal flow rather than absolutely
633
+ * positioned like the sort toggle and resize handle — see the component's
634
+ * own note on why. Faint until grouped, hovered, or focused, the same
635
+ * restraint `.header-sort-toggle` uses for an unsorted header.
636
+ */
637
+ .gridkit-data-grid .header-group-toggle {
638
+ display: inline-flex;
639
+ align-items: center;
640
+ vertical-align: middle;
641
+ margin-right: calc(var(--gridkit-spacing) * 1.5);
642
+ cursor: pointer;
643
+ color: var(--gridkit-fg-muted);
644
+ }
645
+
646
+ .gridkit-data-grid .header-group-toggle svg {
647
+ width: var(--gridkit-header-icon-size);
648
+ height: var(--gridkit-header-icon-size);
649
+ flex-shrink: 0;
650
+ }
651
+
652
+ .gridkit-data-grid .header-group-toggle:not(.is-grouped) {
653
+ opacity: 0.35;
654
+ }
655
+
656
+ .gridkit-data-grid .grid-header .header-cell:hover .header-group-toggle,
657
+ .gridkit-data-grid .header-cell:focus-visible .header-group-toggle {
658
+ opacity: 1;
659
+ }
660
+
661
+ .gridkit-data-grid .header-group-toggle.is-grouped {
662
+ opacity: 1;
663
+ color: var(--gridkit-accent);
664
+ }
665
+
666
+ /*
667
+ * Docked to the header's end, absolutely, for the sort toggle's reasons —
668
+ * at the very end, where a spreadsheet keeps its filter button, with a
669
+ * sortable column's sort toggle moved over beside it. Hidden until the
670
+ * header is hovered or focused, as an unsorted sort toggle is, and shown,
671
+ * filled in accent, while the column is filtered: the one sign of it on
672
+ * screen.
673
+ */
674
+ .gridkit-data-grid .header-filter-toggle {
675
+ display: inline-flex;
676
+ position: absolute;
677
+ top: 50%;
678
+ right: calc(var(--gridkit-spacing) * 3);
679
+ transform: translateY(-50%);
680
+ align-items: center;
681
+ cursor: pointer;
682
+ color: var(--gridkit-fg-muted);
683
+ }
684
+
685
+ .gridkit-data-grid .header-filter-toggle svg {
686
+ width: var(--gridkit-header-icon-size);
687
+ height: var(--gridkit-header-icon-size);
688
+ flex-shrink: 0;
689
+ }
690
+
691
+ .gridkit-data-grid .header-filter-toggle:not(.is-filtered) {
692
+ opacity: 0;
693
+ }
694
+
695
+ .gridkit-data-grid .grid-header .header-cell:hover .header-filter-toggle,
696
+ .gridkit-data-grid .header-cell:focus-visible .header-filter-toggle {
697
+ opacity: 1;
698
+ }
699
+
700
+ .gridkit-data-grid .header-filter-toggle.is-filtered {
701
+ color: var(--gridkit-accent);
702
+ }
703
+
704
+ .gridkit-data-grid .header-filter-toggle.is-filtered svg {
705
+ fill: currentColor;
706
+ }
707
+
708
+ .gridkit-data-grid
709
+ .header-cell:has(> .header-filter-toggle)
710
+ .header-sort-toggle {
711
+ /* The filter button's offset and width, then a gap. */
712
+ right: calc(var(--gridkit-spacing) * 4.5 + var(--gridkit-header-icon-size));
713
+ }
714
+
715
+ .gridkit-data-grid .grid-cell {
716
+ padding: var(--gridkit-cell-padding-y) var(--gridkit-cell-padding-x);
717
+ }
718
+
719
+ .gridkit-data-grid:not(.no-hover-cells) .grid-cell:hover {
720
+ outline: var(--gridkit-border-width) solid var(--gridkit-hover-line);
721
+ background-color: var(--gridkit-surface-muted);
722
+ }
723
+
724
+ /*
725
+ * Every second row, told by its `data-gridkit-row-index` ending in an odd
726
+ * digit. Not `:nth-child`: a virtualized grid mounts only the rows near the
727
+ * scroll position, after a spacer row, so a row's place among its siblings
728
+ * changes as it scrolls while its index does not. Ahead of the hover and
729
+ * selection rules, which win over it.
730
+ */
731
+ .gridkit-data-grid
732
+ .grid-row:where(
733
+ [data-gridkit-row-index$="1"],
734
+ [data-gridkit-row-index$="3"],
735
+ [data-gridkit-row-index$="5"],
736
+ [data-gridkit-row-index$="7"],
737
+ [data-gridkit-row-index$="9"]
738
+ ) {
739
+ background-color: var(--gridkit-row-stripe-bg);
740
+ }
741
+
742
+ .gridkit-data-grid:not(.no-hover-rows) .grid-row:hover {
743
+ background-color: var(--gridkit-row-hover-bg);
744
+ }
745
+
746
+ /*
747
+ * A group header — one cell spanning every column, not a `.grid-cell` — so
748
+ * it gets its own rules rather than sharing the data-cell ones above.
749
+ */
750
+ .gridkit-data-grid .grid-group-row {
751
+ cursor: pointer;
752
+ background-color: var(--gridkit-group-row-bg);
753
+ }
754
+
755
+ .gridkit-data-grid .grid-group-row:hover {
756
+ background-color: var(--gridkit-selected);
757
+ }
758
+
759
+ /*
760
+ * Padding lives on `.grid-group-cell-content` (the inner `div`), not here:
761
+ * see that component's own note on why the flex layout is one level in
762
+ * rather than on the `<td>` itself.
763
+ */
764
+ .gridkit-data-grid .grid-group-cell {
765
+ padding: 0;
766
+ }
767
+
768
+ .gridkit-data-grid .grid-group-cell:focus-visible {
769
+ outline: var(--gridkit-focus-ring-width) solid var(--gridkit-focus-ring);
770
+ outline-offset: calc(var(--gridkit-focus-ring-width) * -1);
771
+ }
772
+
773
+ .gridkit-data-grid .grid-group-cell-content {
774
+ display: flex;
775
+ align-items: center;
776
+ gap: calc(var(--gridkit-spacing) * 2);
777
+ padding: var(--gridkit-cell-padding-y) var(--gridkit-cell-padding-x);
778
+ font-weight: var(--gridkit-group-font-weight);
779
+ }
780
+
781
+ /* A right-pointing triangle that rotates open on expand. */
782
+ .gridkit-data-grid .grid-group-row .group-toggle {
783
+ display: inline-block;
784
+ flex-shrink: 0;
785
+ width: 0;
786
+ height: 0;
787
+ border-style: solid;
788
+ border-width: calc(var(--gridkit-icon-size) / 4) 0
789
+ calc(var(--gridkit-icon-size) / 4) calc(var(--gridkit-icon-size) * 0.4);
790
+ border-color: transparent transparent transparent var(--gridkit-fg-muted);
791
+ transition: transform var(--gridkit-transition-duration) ease;
792
+ }
793
+
794
+ .gridkit-data-grid .grid-group-row .group-toggle.is-expanded {
795
+ transform: rotate(90deg);
796
+ }
797
+
798
+ /*
799
+ * Set on the rule rather than on `--gridkit-transition-duration`, so a
800
+ * theme's own duration can't bring the motion back.
801
+ */
802
+ @media (prefers-reduced-motion: reduce) {
803
+ .gridkit-data-grid .grid-group-row .group-toggle {
804
+ transition: none;
805
+ }
806
+ }
807
+
808
+ .gridkit-data-grid .grid-group-row .group-count {
809
+ font-weight: 400;
810
+ color: var(--gridkit-fg-muted);
811
+ }
812
+
813
+ /*
814
+ * A group's own subtotals, inline after its count — `gap` on the parent
815
+ * `.grid-group-cell-content` already spaces every child, this just keeps
816
+ * multiple aggregates readable as a list rather than running together.
817
+ */
818
+ .gridkit-data-grid .grid-group-row .group-aggregates {
819
+ display: flex;
820
+ gap: calc(var(--gridkit-spacing) * 3);
821
+ margin-inline-start: calc(var(--gridkit-spacing) * 2);
822
+ font-weight: 400;
823
+ }
824
+
825
+ .gridkit-data-grid .grid-group-row .group-aggregate {
826
+ color: var(--gridkit-fg-muted);
827
+ }
828
+
829
+ /*
830
+ * The grand-total footer, styled as the header's counterpart: the
831
+ * surface the header has by default, the same cell padding, and the weight
832
+ * of the group rows' totals. No border by default, matching the
833
+ * header's own `.header-cell` — see the `borders-*` variants below for
834
+ * the opt-in top border that echoes the header's bottom one.
835
+ */
836
+ .gridkit-data-grid .grid-footer {
837
+ background-color: var(--gridkit-surface);
838
+ }
839
+
840
+ .gridkit-data-grid .grid-footer-cell {
841
+ padding: var(--gridkit-cell-padding-y) var(--gridkit-cell-padding-x);
842
+ font-weight: var(--gridkit-group-font-weight);
843
+ }
844
+
845
+ /*
846
+ * A group's own summary row (`groupAggregateDisplay: "row"`) — the
847
+ * per-column-cell alternative to `.group-aggregates`' inline text.
848
+ * Shares the group header's background so the pair read as one unit,
849
+ * but is presentational: no hover state, no pointer cursor, matching
850
+ * `GridGroupSummaryRow`'s own lack of any click/focus handling.
851
+ */
852
+ .gridkit-data-grid .grid-group-summary-row {
853
+ background-color: var(--gridkit-group-row-bg);
854
+ }
855
+
856
+ .gridkit-data-grid .grid-group-summary-cell {
857
+ padding: var(--gridkit-cell-padding-y) var(--gridkit-cell-padding-x);
858
+ font-weight: var(--gridkit-group-font-weight);
859
+ }
860
+
861
+ /*
862
+ * `infiniteScroll`'s built-in loading row — aria-hidden and outside
863
+ * `aria-rowcount` (see `GridBody`), so this is a purely visual cue, not an
864
+ * announced one.
865
+ */
866
+ .gridkit-data-grid .grid-loading-cell {
867
+ padding: var(--gridkit-cell-padding-y) var(--gridkit-cell-padding-x);
868
+ color: var(--gridkit-fg-muted);
869
+ text-align: center;
870
+ }
871
+
872
+ /*
873
+ * Indents only the first cell, matching how `GridGroupRow`'s own
874
+ * `paddingInlineStart` indents just the one spanning cell it has — an
875
+ * empty, `aria-hidden` inline spacer rather than padding on the `<td>`
876
+ * itself, so the cell's own content still aligns flush with the column
877
+ * above and below it once the indent's width is accounted for.
878
+ */
879
+ .gridkit-data-grid .grid-group-summary-indent {
880
+ display: inline-block;
881
+ }
882
+
883
+ /*
884
+ * Selection, after the hover rules so that a selected row hovered stays
885
+ * selected — the two would otherwise be the same colour, since hover already
886
+ * claims `--gridkit-surface-muted`.
887
+ *
888
+ * A selected cell wins over its row and its column: it is the more specific
889
+ * thing the user pointed at, and the rule below it says so by ordering.
890
+ */
891
+ .gridkit-data-grid .grid-row.is-selected,
892
+ .gridkit-data-grid .header-cell.is-selected,
893
+ .gridkit-data-grid .grid-cell.is-selected {
894
+ background-color: var(--gridkit-selected);
895
+ }
896
+
897
+ .gridkit-data-grid:not(.no-hover-rows) .grid-row.is-selected:hover,
898
+ .gridkit-data-grid:not(.no-hover-cells) .grid-cell.is-selected:hover,
899
+ .gridkit-data-grid:not(.no-hover-columns)
900
+ .grid-header
901
+ .header-cell.is-selected:hover {
902
+ background-color: var(--gridkit-selected-strong);
903
+ }
904
+
905
+ /*
906
+ * Only where a press does something. A grid that merely displays data keeps
907
+ * the default cursor, which is what says it is not interactive.
908
+ *
909
+ * A reorderable header keeps its `grab`: it is the rarer affordance and the
910
+ * one a user would not otherwise guess, and its header still selects.
911
+ */
912
+ .gridkit-data-grid.selectable-rows .grid-row,
913
+ .gridkit-data-grid.selectable-cells .grid-cell,
914
+ .gridkit-data-grid.selectable-columns
915
+ .grid-header
916
+ .header-cell:not(.is-reorderable) {
917
+ cursor: pointer;
918
+ }
919
+
920
+ /*
921
+ * A Shift-click takes a range of rows, and would otherwise take a range of
922
+ * text along with it — leaving the browser's own highlight smeared across
923
+ * every cell it crossed.
924
+ *
925
+ * The cost is that the text no longer drags out to be copied, which is why
926
+ * this is scoped to a grid that asked for selection rather than applied to
927
+ * all of them.
928
+ */
929
+ .gridkit-data-grid.selectable-rows .grid-row,
930
+ .gridkit-data-grid.selectable-cells .grid-cell {
931
+ user-select: none;
932
+ }
933
+
934
+ /*
935
+ * Horizontal borders.
936
+ *
937
+ * Header, body, and footer are three separate `<table>`s, so
938
+ * `border-collapse` only ever merges borders within one of them — it
939
+ * cannot merge a header border against a body border the way it would
940
+ * merge two adjacent body rows' borders in one shared `<tbody>`. Each seam
941
+ * below is therefore deliberately defined from one side only, never both,
942
+ * or the two tables' independent borders would render as a doubled line
943
+ * instead of collapsing into one:
944
+ * - header/body seam: the header's own `border-bottom` here. The body's
945
+ * first row never sets a `border-top` of its own.
946
+ * - body/footer seam: the footer's own `border-top` (below, alongside
947
+ * `.grid-footer-cell`). The body's last row has its `border-bottom`
948
+ * stripped (below, `.grid-row:last-child`) specifically so it never
949
+ * supplies the other half of this seam.
950
+ */
951
+ .gridkit-data-grid.borders-horizontal .grid-header .header-cell,
952
+ .gridkit-data-grid.borders-horizontal .grid-cell,
953
+ .gridkit-data-grid.borders-horizontal .grid-group-cell,
954
+ .gridkit-data-grid.borders-horizontal .grid-group-summary-cell {
955
+ border-bottom: var(--gridkit-row-line-width) var(--gridkit-border-style)
956
+ var(--gridkit-line);
957
+ }
958
+
959
+ /*
960
+ * The footer's own top border, echoing the header's bottom-anchored one —
961
+ * a `border-top` rather than a `border-bottom` on the last body row, since
962
+ * the footer sits in its own `<table>` entirely, sibling to the body's.
963
+ */
964
+ .gridkit-data-grid.borders-horizontal .grid-footer-cell,
965
+ .gridkit-data-grid.borders-all .grid-footer-cell {
966
+ border-top: var(--gridkit-row-line-width) var(--gridkit-border-style)
967
+ var(--gridkit-line);
968
+ }
969
+
970
+ /*
971
+ * The last row's bottom edge has no row below it to collapse with, so
972
+ * unlike every internal boundary it is a genuinely extra line — the same
973
+ * reason `borders-vertical` drops its `:last-child` border-right below.
974
+ * Matched against both row kinds: the last rendered row can just as well
975
+ * be a collapsed group header as a data row.
976
+ */
977
+ .gridkit-data-grid.borders-horizontal .grid-row:last-child .grid-cell,
978
+ .gridkit-data-grid.borders-horizontal
979
+ .grid-group-row:last-child
980
+ .grid-group-cell,
981
+ .gridkit-data-grid.borders-horizontal
982
+ .grid-group-summary-row:last-child
983
+ .grid-group-summary-cell {
984
+ border-bottom: none;
985
+ }
986
+
987
+ /* Vertical borders */
988
+ .gridkit-data-grid.borders-vertical .grid-header .header-cell,
989
+ .gridkit-data-grid.borders-vertical .grid-cell,
990
+ .gridkit-data-grid.borders-vertical .grid-footer-cell,
991
+ .gridkit-data-grid.borders-vertical .grid-group-summary-cell {
992
+ border-right: var(--gridkit-column-line-width) var(--gridkit-border-style)
993
+ var(--gridkit-line);
994
+ }
995
+
996
+ .gridkit-data-grid.borders-vertical .grid-header .header-cell:last-child,
997
+ .gridkit-data-grid.borders-vertical .grid-cell:last-child,
998
+ .gridkit-data-grid.borders-vertical .grid-footer-cell:last-child,
999
+ .gridkit-data-grid.borders-vertical .grid-group-summary-cell:last-child {
1000
+ border-right: none;
1001
+ }
1002
+
1003
+ /* Borders on all sides */
1004
+ .gridkit-data-grid.borders-all .grid-header .header-cell,
1005
+ .gridkit-data-grid.borders-all .grid-cell,
1006
+ .gridkit-data-grid.borders-all .grid-group-cell,
1007
+ .gridkit-data-grid.borders-all .grid-group-summary-cell {
1008
+ border-bottom: var(--gridkit-row-line-width) var(--gridkit-border-style)
1009
+ var(--gridkit-line);
1010
+ border-right: var(--gridkit-column-line-width) var(--gridkit-border-style)
1011
+ var(--gridkit-line);
1012
+ }
1013
+
1014
+ .gridkit-data-grid.borders-all .grid-footer-cell {
1015
+ border-right: var(--gridkit-column-line-width) var(--gridkit-border-style)
1016
+ var(--gridkit-line);
1017
+ }
1018
+
1019
+ /*
1020
+ * The outer left/right edges have no neighbouring cell to collapse with, so
1021
+ * unlike every internal boundary they are a genuinely extra line — without
1022
+ * this the table would render two lines wider than the sum of its columns. A
1023
+ * group cell is both its row's first and last child at once (it is the
1024
+ * only cell), so it takes both exceptions.
1025
+ */
1026
+ .gridkit-data-grid.borders-all .grid-header .header-cell:first-child,
1027
+ .gridkit-data-grid.borders-all .grid-cell:first-child,
1028
+ .gridkit-data-grid.borders-all .grid-group-cell,
1029
+ .gridkit-data-grid.borders-all .grid-footer-cell:first-child,
1030
+ .gridkit-data-grid.borders-all .grid-group-summary-cell:first-child {
1031
+ border-left: none;
1032
+ }
1033
+
1034
+ .gridkit-data-grid.borders-all .grid-header .header-cell:last-child,
1035
+ .gridkit-data-grid.borders-all .grid-cell:last-child,
1036
+ .gridkit-data-grid.borders-all .grid-group-cell,
1037
+ .gridkit-data-grid.borders-all .grid-footer-cell:last-child,
1038
+ .gridkit-data-grid.borders-all .grid-group-summary-cell:last-child {
1039
+ border-right: none;
1040
+ }
1041
+
1042
+ .gridkit-data-grid.borders-all .grid-row:last-child .grid-cell,
1043
+ .gridkit-data-grid.borders-all .grid-group-row:last-child .grid-group-cell,
1044
+ .gridkit-data-grid.borders-all
1045
+ .grid-group-summary-row:last-child
1046
+ .grid-group-summary-cell {
1047
+ border-bottom: none;
1048
+ }
1049
+
1050
+ /*
1051
+ * The toolbar: the grid's first child, above the group-by bar, and unscoped
1052
+ * by `.gridkit-data-grid` for the same reason that bar is. The grid scrolls
1053
+ * sideways with everything in it, so the toolbar sticks to its start edge
1054
+ * and stays in view while the tables scroll on under it.
1055
+ *
1056
+ * Its entries keep to one row at their own widths, which is what the
1057
+ * overflow measures: what does not fit goes into "More", and `overflow:
1058
+ * hidden` keeps the frame before that from spilling out of the toolbar.
1059
+ */
1060
+ .gridkit-toolbar {
1061
+ position: sticky;
1062
+ inset-inline-start: 0;
1063
+ display: flex;
1064
+ align-items: center;
1065
+ gap: calc(var(--gridkit-spacing) * 2);
1066
+ padding: var(--gridkit-toolbar-padding);
1067
+ overflow: hidden;
1068
+ font-size: var(--gridkit-bar-font-size);
1069
+ color: var(--gridkit-fg);
1070
+ background-color: var(--gridkit-surface);
1071
+ border-bottom: var(--gridkit-border-width) var(--gridkit-border-style)
1072
+ var(--gridkit-line);
1073
+ }
1074
+
1075
+ .gridkit-toolbar > * {
1076
+ flex-shrink: 0;
1077
+ }
1078
+
1079
+ /*
1080
+ * The search box and custom entries never move into "More", so together
1081
+ * they can be wider than the toolbar. "More" then sticks to its end edge,
1082
+ * over what is clipped there, rather than being clipped itself.
1083
+ */
1084
+ .gridkit-toolbar > .toolbar-more {
1085
+ position: sticky;
1086
+ /* Counted from the toolbar's padding edge, so its padding again. */
1087
+ inset-inline-end: var(--gridkit-toolbar-padding);
1088
+ }
1089
+
1090
+ /* A command, a dropdown, or "More". */
1091
+ .toolbar-button {
1092
+ display: inline-flex;
1093
+ align-items: center;
1094
+ gap: calc(var(--gridkit-spacing) * 1.5);
1095
+ padding: var(--gridkit-spacing) calc(var(--gridkit-spacing) * 2.5);
1096
+ font: inherit;
1097
+ color: var(--gridkit-fg);
1098
+ white-space: nowrap;
1099
+ cursor: pointer;
1100
+ background-color: var(--gridkit-surface);
1101
+ border: var(--gridkit-border-width) solid var(--gridkit-line);
1102
+ border-radius: var(--gridkit-control-radius);
1103
+ }
1104
+
1105
+ .toolbar-button:hover:not([aria-disabled="true"]),
1106
+ .toolbar-button[aria-expanded="true"] {
1107
+ background-color: var(--gridkit-surface-muted);
1108
+ }
1109
+
1110
+ .toolbar-button:focus-visible {
1111
+ outline: var(--gridkit-focus-ring-width) solid var(--gridkit-focus-ring);
1112
+ outline-offset: 1px;
1113
+ }
1114
+
1115
+ /* Still focusable, as a menu's disabled item is, so a screen reader finds it. */
1116
+ .toolbar-button[aria-disabled="true"] {
1117
+ color: var(--gridkit-fg-muted);
1118
+ cursor: default;
1119
+ opacity: 0.5;
1120
+ }
1121
+
1122
+ /* A toggle that is on. */
1123
+ .toolbar-button[aria-pressed="true"],
1124
+ .toolbar-button[aria-pressed="true"]:hover {
1125
+ background-color: var(--gridkit-selected-strong);
1126
+ border-color: var(--gridkit-accent);
1127
+ }
1128
+
1129
+ .toolbar-button-icon,
1130
+ .toolbar-button-indicator {
1131
+ display: inline-flex;
1132
+ color: var(--gridkit-fg-muted);
1133
+ }
1134
+
1135
+ /* An icon key the grid has no glyph for draws nothing, and takes no room. */
1136
+ .toolbar-button-icon:empty {
1137
+ display: none;
1138
+ }
1139
+
1140
+ .toolbar-button-icon svg,
1141
+ .toolbar-button-indicator svg {
1142
+ width: var(--gridkit-icon-size);
1143
+ height: var(--gridkit-icon-size);
1144
+ }
1145
+
1146
+ .toolbar-button-indicator {
1147
+ margin-inline-end: calc(var(--gridkit-spacing) * -1);
1148
+ }
1149
+
1150
+ /*
1151
+ * Narrower in a narrow toolbar, by the toolbar's width alone — never by
1152
+ * what else is in it, which the overflow would measure as a width of its
1153
+ * own — so it leaves room for "More" beside it.
1154
+ */
1155
+ .toolbar-search {
1156
+ width: 12rem;
1157
+ max-width: 50%;
1158
+ padding: var(--gridkit-spacing) calc(var(--gridkit-spacing) * 2);
1159
+ font: inherit;
1160
+ color: var(--gridkit-fg);
1161
+ background-color: var(--gridkit-surface);
1162
+ border: var(--gridkit-border-width) solid var(--gridkit-line);
1163
+ border-radius: var(--gridkit-control-radius);
1164
+ }
1165
+
1166
+ .toolbar-search::placeholder {
1167
+ color: var(--gridkit-fg-muted);
1168
+ }
1169
+
1170
+ .toolbar-search:focus-visible {
1171
+ outline: var(--gridkit-focus-ring-width) solid var(--gridkit-focus-ring);
1172
+ outline-offset: 1px;
1173
+ }
1174
+
1175
+ .toolbar-separator {
1176
+ align-self: stretch;
1177
+ width: var(--gridkit-border-width);
1178
+ margin: calc(var(--gridkit-spacing) * 0.5) 0;
1179
+ background-color: var(--gridkit-line);
1180
+ }
1181
+
1182
+ /* Takes the free space, pushing what follows it to the far end. */
1183
+ .gridkit-toolbar > .toolbar-spacer {
1184
+ flex: 1 1 0;
1185
+ }
1186
+
1187
+ /* Holds what `renderToolbarItem` drew. */
1188
+ .toolbar-custom {
1189
+ display: inline-flex;
1190
+ align-items: center;
1191
+ }
1192
+
1193
+ /*
1194
+ * The group-by bar. A sibling of the table, not a row inside it — see
1195
+ * `GroupByBar.tsx`'s own note on why — so it's unscoped by `.gridkit-data-grid`
1196
+ * the way `.gridkit-sr-only` is.
1197
+ */
1198
+ .gridkit-group-by-bar {
1199
+ display: flex;
1200
+ flex-wrap: wrap;
1201
+ align-items: center;
1202
+ gap: calc(var(--gridkit-spacing) * 2);
1203
+ padding: var(--gridkit-group-by-bar-padding);
1204
+ background-color: var(--gridkit-surface);
1205
+ border-bottom: var(--gridkit-border-width) var(--gridkit-border-style)
1206
+ var(--gridkit-line);
1207
+ }
1208
+
1209
+ /*
1210
+ * Drawn while a chip or an eligible header is being dragged — a dashed
1211
+ * outline marking the bar itself as a valid drop zone, load-bearing when
1212
+ * it's otherwise empty, with no chip of its own to show a drop indicator
1213
+ * on. Where exactly the drop would land is the chips' own
1214
+ * `is-drop-before`/`is-drop-after` marks; this outline doesn't also wash
1215
+ * the bar's background once hovered — the marks are enough on their own.
1216
+ */
1217
+ .gridkit-group-by-bar.is-drag-target {
1218
+ outline: var(--gridkit-indicator-width) dashed var(--gridkit-accent);
1219
+ outline-offset: calc(var(--gridkit-indicator-width) * -1);
1220
+ }
1221
+
1222
+ /*
1223
+ * A header already in the group-by stack, dragged back toward it — never a
1224
+ * valid drop, so no accent outline invites it: a muted dashed line in the
1225
+ * same neutral tone as the grid's own borders, plus a `not-allowed` cursor
1226
+ * for the pointer icon itself.
1227
+ */
1228
+ .gridkit-group-by-bar.is-drag-blocked {
1229
+ cursor: not-allowed;
1230
+ outline: var(--gridkit-indicator-width) dashed var(--gridkit-line);
1231
+ outline-offset: calc(var(--gridkit-indicator-width) * -1);
1232
+ }
1233
+
1234
+ .group-by-bar-placeholder {
1235
+ font-size: var(--gridkit-bar-font-size);
1236
+ color: var(--gridkit-fg-muted);
1237
+ }
1238
+
1239
+ .group-by-chip {
1240
+ position: relative;
1241
+ display: inline-flex;
1242
+ align-items: center;
1243
+ gap: calc(var(--gridkit-spacing) * 1.5);
1244
+ padding: var(--gridkit-spacing) calc(var(--gridkit-spacing) * 2);
1245
+ font-size: var(--gridkit-bar-font-size);
1246
+ color: var(--gridkit-fg);
1247
+ background-color: var(--gridkit-selected);
1248
+ border-radius: 9999px;
1249
+ cursor: grab;
1250
+ touch-action: none;
1251
+ user-select: none;
1252
+ }
1253
+
1254
+ .group-by-chip:focus-visible {
1255
+ outline: var(--gridkit-focus-ring-width) solid var(--gridkit-focus-ring);
1256
+ outline-offset: 1px;
1257
+ }
1258
+
1259
+ .group-by-chip.is-dragging {
1260
+ cursor: grabbing;
1261
+ opacity: 0.5;
1262
+ }
1263
+
1264
+ /*
1265
+ * Where the chip would land. `.header-cell.is-drop-before`/`.is-drop-after`
1266
+ * draw this as an inset line, but an inset line on a pill this round would
1267
+ * be clipped by the chip's own `border-radius` into a curved sliver rather
1268
+ * than a clean mark — so this is a `::before`/`::after` mark of its own
1269
+ * instead, centred in the bar's gap beside the chip. Absolutely positioned and
1270
+ * so out of flow, it never shifts the chip or its neighbors the way a
1271
+ * change to border or padding would.
1272
+ */
1273
+ .group-by-chip.is-drop-before::before,
1274
+ .group-by-chip.is-drop-after::after {
1275
+ content: "";
1276
+ position: absolute;
1277
+ top: 0;
1278
+ bottom: 0;
1279
+ width: var(--gridkit-indicator-width);
1280
+ border-radius: calc(var(--gridkit-indicator-width) / 2);
1281
+ background-color: var(--gridkit-accent);
1282
+ }
1283
+
1284
+ .group-by-chip.is-drop-before::before {
1285
+ left: calc(
1286
+ var(--gridkit-spacing) * -1 - var(--gridkit-indicator-width) / 2
1287
+ );
1288
+ }
1289
+
1290
+ .group-by-chip.is-drop-after::after {
1291
+ right: calc(
1292
+ var(--gridkit-spacing) * -1 - var(--gridkit-indicator-width) / 2
1293
+ );
1294
+ }
1295
+
1296
+ /*
1297
+ * The chip trailing the pointer mid-drag. Unlike the header's own
1298
+ * `.gridkit-data-grid.drag-ghost` this can't share that compound selector
1299
+ * — a chip ghost isn't part of the grid's own table — so it restates the
1300
+ * positioning in full rather than only the parts that differ.
1301
+ */
1302
+ .group-by-chip.drag-ghost {
1303
+ position: fixed;
1304
+ top: 0;
1305
+ left: 0;
1306
+ z-index: 50;
1307
+ border: var(--gridkit-border-width) dashed var(--gridkit-accent);
1308
+ opacity: 0.75;
1309
+ pointer-events: none;
1310
+ will-change: transform;
1311
+ }
1312
+
1313
+ .group-by-chip-remove {
1314
+ display: inline-flex;
1315
+ align-items: center;
1316
+ justify-content: center;
1317
+ width: var(--gridkit-icon-size);
1318
+ height: var(--gridkit-icon-size);
1319
+ padding: 0;
1320
+ line-height: 1;
1321
+ color: inherit;
1322
+ cursor: pointer;
1323
+ background: none;
1324
+ border: none;
1325
+ }
1326
+
1327
+ .group-by-chip-remove:hover {
1328
+ color: var(--gridkit-accent);
1329
+ }
1330
+
1331
+ .group-by-chip-remove:focus-visible {
1332
+ outline: var(--gridkit-focus-ring-width) solid var(--gridkit-focus-ring);
1333
+ outline-offset: 1px;
1334
+ }
1335
+
1336
+ /*
1337
+ * The pager. A sibling of the table, not a row inside it — the same
1338
+ * reasoning `.gridkit-group-by-bar` gives for its own placement — so it's
1339
+ * unscoped by `.gridkit-data-grid` the way that bar is.
1340
+ */
1341
+ .gridkit-grid-pager {
1342
+ display: flex;
1343
+ flex-wrap: wrap;
1344
+ align-items: center;
1345
+ gap: calc(var(--gridkit-spacing) * 2) calc(var(--gridkit-spacing) * 4);
1346
+ padding: var(--gridkit-pager-padding);
1347
+ font-size: var(--gridkit-bar-font-size);
1348
+ color: var(--gridkit-fg);
1349
+ background-color: var(--gridkit-surface);
1350
+ border-top: var(--gridkit-border-width) var(--gridkit-border-style)
1351
+ var(--gridkit-line);
1352
+ }
1353
+
1354
+ /*
1355
+ * First, previous, the page numbers (or the compact status), next and
1356
+ * last as adjoining segments of one bar: an outer hairline, a shared one
1357
+ * between segments, and the radius on the bar's outer corners only. Every
1358
+ * number slot is the same width, sized by `--gridkit-pager-digits` (the
1359
+ * page count's digits, which the pager sets) in tabular numerals, so with
1360
+ * `paginationWindow`'s constant slot count the arrows never move.
1361
+ */
1362
+ .grid-pager-bar {
1363
+ --gridkit-pager-segment: calc(var(--gridkit-spacing) * 8);
1364
+ display: inline-flex;
1365
+ align-items: stretch;
1366
+ height: var(--gridkit-pager-segment);
1367
+ font-variant-numeric: tabular-nums;
1368
+ background-color: var(--gridkit-surface);
1369
+ border: var(--gridkit-border-width) solid var(--gridkit-line);
1370
+ border-radius: var(--gridkit-radius);
1371
+ }
1372
+
1373
+ .grid-pager-button,
1374
+ .grid-pager-ellipsis,
1375
+ .grid-pager-status-slot {
1376
+ position: relative;
1377
+ box-sizing: border-box;
1378
+ display: inline-flex;
1379
+ align-items: center;
1380
+ justify-content: center;
1381
+ min-width: var(--gridkit-pager-segment);
1382
+ margin: 0;
1383
+ padding: 0;
1384
+ font: inherit;
1385
+ line-height: 1;
1386
+ color: var(--gridkit-fg);
1387
+ background-color: transparent;
1388
+ border: 0;
1389
+ border-inline-start: var(--gridkit-border-width) solid var(--gridkit-line);
1390
+ border-radius: 0;
1391
+ }
1392
+
1393
+ .grid-pager-bar > :first-child {
1394
+ border-inline-start: 0;
1395
+ border-start-start-radius: calc(
1396
+ var(--gridkit-radius) - var(--gridkit-border-width)
1397
+ );
1398
+ border-end-start-radius: calc(
1399
+ var(--gridkit-radius) - var(--gridkit-border-width)
1400
+ );
1401
+ }
1402
+
1403
+ .grid-pager-bar > :last-child {
1404
+ border-start-end-radius: calc(
1405
+ var(--gridkit-radius) - var(--gridkit-border-width)
1406
+ );
1407
+ border-end-end-radius: calc(
1408
+ var(--gridkit-radius) - var(--gridkit-border-width)
1409
+ );
1410
+ }
1411
+
1412
+ .grid-pager-button {
1413
+ cursor: pointer;
1414
+ }
1415
+
1416
+ .grid-pager-page,
1417
+ .grid-pager-ellipsis {
1418
+ width: max(
1419
+ var(--gridkit-pager-segment),
1420
+ calc(var(--gridkit-pager-digits, 1) * 1ch + var(--gridkit-spacing) * 4)
1421
+ );
1422
+ }
1423
+
1424
+ .grid-pager-nav svg {
1425
+ width: var(--gridkit-icon-size);
1426
+ height: var(--gridkit-icon-size);
1427
+ }
1428
+
1429
+ .grid-pager-nav:dir(rtl) svg {
1430
+ transform: scaleX(-1);
1431
+ }
1432
+
1433
+ .grid-pager-button:hover:not([aria-disabled="true"]) {
1434
+ background-color: var(--gridkit-surface-muted);
1435
+ }
1436
+
1437
+ .grid-pager-button:focus-visible {
1438
+ z-index: 2;
1439
+ outline: var(--gridkit-focus-ring-width) solid var(--gridkit-focus-ring);
1440
+ outline-offset: calc(var(--gridkit-focus-ring-width) * -1);
1441
+ }
1442
+
1443
+ .grid-pager-button[aria-disabled="true"] {
1444
+ color: var(--gridkit-fg-muted);
1445
+ cursor: default;
1446
+ }
1447
+
1448
+ /*
1449
+ * The icon fades rather than the whole button: a disabled arrow keeps
1450
+ * focus once it has paged to the end, and its focus ring should not fade
1451
+ * with it.
1452
+ */
1453
+ .grid-pager-button[aria-disabled="true"] svg {
1454
+ opacity: 0.45;
1455
+ }
1456
+
1457
+ .grid-pager-button[aria-current="page"],
1458
+ .grid-pager-button[aria-current="page"]:hover {
1459
+ z-index: 1;
1460
+ color: var(--gridkit-accent);
1461
+ background-color: var(--gridkit-selected);
1462
+ box-shadow: inset 0 0 0 var(--gridkit-border-width) var(--gridkit-accent);
1463
+ }
1464
+
1465
+ .grid-pager-ellipsis {
1466
+ color: var(--gridkit-fg-muted);
1467
+ cursor: default;
1468
+ user-select: none;
1469
+ }
1470
+
1471
+ /*
1472
+ * The compact status, stacked on an invisible copy of its widest text so
1473
+ * its segment keeps one width from the first page to the last.
1474
+ */
1475
+ .grid-pager-status-slot {
1476
+ display: inline-grid;
1477
+ padding: 0 calc(var(--gridkit-spacing) * 3);
1478
+ white-space: nowrap;
1479
+ }
1480
+
1481
+ .grid-pager-status,
1482
+ .grid-pager-status-sizer {
1483
+ grid-area: 1 / 1;
1484
+ text-align: center;
1485
+ }
1486
+
1487
+ .grid-pager-status {
1488
+ color: var(--gridkit-fg-muted);
1489
+ }
1490
+
1491
+ .grid-pager-status-sizer {
1492
+ visibility: hidden;
1493
+ }
1494
+
1495
+ .grid-pager-page-size {
1496
+ display: inline-flex;
1497
+ align-items: center;
1498
+ gap: calc(var(--gridkit-spacing) * 2);
1499
+ color: var(--gridkit-fg-muted);
1500
+ }
1501
+
1502
+ .grid-pager-page-size select {
1503
+ box-sizing: border-box;
1504
+ /* As tall as the pager bar beside it. */
1505
+ height: calc(var(--gridkit-spacing) * 8);
1506
+ padding: 0 calc(var(--gridkit-spacing) * 1.5);
1507
+ font: inherit;
1508
+ font-variant-numeric: tabular-nums;
1509
+ color: var(--gridkit-fg);
1510
+ background-color: var(--gridkit-surface);
1511
+ border: var(--gridkit-border-width) solid var(--gridkit-line);
1512
+ border-radius: var(--gridkit-control-radius);
1513
+ }
1514
+
1515
+ .grid-pager-page-size select:focus-visible {
1516
+ outline: var(--gridkit-focus-ring-width) solid var(--gridkit-focus-ring);
1517
+ outline-offset: 1px;
1518
+ }
1519
+
1520
+ /*
1521
+ * Overlays — the grid's menus and panels — render into `document.body`, outside the
1522
+ * grid and any scrolling ancestor that would clip them, so every selector
1523
+ * from here on is top-level rather than under `.gridkit-data-grid`. The
1524
+ * grid copies the `.gridkit-light`/`.gridkit-dark` it sits under onto the
1525
+ * overlay, since the portal leaves that ancestor's token scope.
1526
+ *
1527
+ * Fixed from the start, so the overlay never takes room at the end of the
1528
+ * page before it is placed; above the grid's own drag ghosts.
1529
+ */
1530
+ .gridkit-overlay {
1531
+ position: fixed;
1532
+ top: 0;
1533
+ left: 0;
1534
+ z-index: 60;
1535
+ color: var(--gridkit-fg);
1536
+ }
1537
+
1538
+ /*
1539
+ * A menu too tall for the viewport scrolls inside itself — a scroll there
1540
+ * does not dismiss it, as one outside would.
1541
+ */
1542
+ .gridkit-menu {
1543
+ min-width: var(--gridkit-menu-min-width);
1544
+ max-height: 100vh;
1545
+ overflow-y: auto;
1546
+ padding: var(--gridkit-spacing) 0;
1547
+ font-size: var(--gridkit-menu-font-size);
1548
+ background-color: var(--gridkit-surface);
1549
+ border: var(--gridkit-border-width) solid var(--gridkit-line);
1550
+ border-radius: var(--gridkit-menu-radius);
1551
+ box-shadow: var(--gridkit-overlay-shadow)
1552
+ var(--gridkit-overlay-shadow-color);
1553
+ }
1554
+
1555
+ .menu-item {
1556
+ display: flex;
1557
+ align-items: center;
1558
+ gap: calc(var(--gridkit-spacing) * 2);
1559
+ padding: var(--gridkit-menu-item-padding-y)
1560
+ var(--gridkit-menu-item-padding-x);
1561
+ white-space: nowrap;
1562
+ cursor: pointer;
1563
+ user-select: none;
1564
+ }
1565
+
1566
+ /*
1567
+ * `:focus` rather than `:focus-visible`: the pointer moves focus to the
1568
+ * item under it, so focus is the hover state too, and has to show
1569
+ * whichever moved it.
1570
+ */
1571
+ .menu-item:focus {
1572
+ outline: none;
1573
+ background-color: var(--gridkit-selected);
1574
+ }
1575
+
1576
+ /*
1577
+ * The item whose submenu is open, while focus is in that submenu — the
1578
+ * trail back to it.
1579
+ */
1580
+ .menu-item[aria-expanded="true"]:not(:focus) {
1581
+ background-color: var(--gridkit-surface-muted);
1582
+ }
1583
+
1584
+ /*
1585
+ * Still focusable, so a screen reader finds it: muted, and marked only
1586
+ * faintly when focused, since choosing it does nothing.
1587
+ */
1588
+ .menu-item[aria-disabled="true"] {
1589
+ color: var(--gridkit-fg-muted);
1590
+ cursor: default;
1591
+ }
1592
+
1593
+ .menu-item[aria-disabled="true"]:focus {
1594
+ background-color: var(--gridkit-surface-muted);
1595
+ }
1596
+
1597
+ /*
1598
+ * Always there, empty for an item without an icon, so every label in a
1599
+ * menu starts at the same place. A checked item shows its check here.
1600
+ */
1601
+ .menu-item-icon {
1602
+ display: inline-flex;
1603
+ flex-shrink: 0;
1604
+ align-items: center;
1605
+ justify-content: center;
1606
+ width: var(--gridkit-icon-size);
1607
+ height: var(--gridkit-icon-size);
1608
+ color: var(--gridkit-fg-muted);
1609
+ }
1610
+
1611
+ .menu-item-icon svg,
1612
+ .menu-submenu-indicator svg {
1613
+ width: var(--gridkit-icon-size);
1614
+ height: var(--gridkit-icon-size);
1615
+ }
1616
+
1617
+ .menu-item[aria-checked="true"] .menu-item-icon {
1618
+ color: var(--gridkit-accent);
1619
+ }
1620
+
1621
+ .menu-item-label {
1622
+ flex: 1;
1623
+ }
1624
+
1625
+ .menu-item-shortcut {
1626
+ margin-inline-start: calc(var(--gridkit-spacing) * 6);
1627
+ font-size: var(--gridkit-hint-font-size);
1628
+ color: var(--gridkit-fg-muted);
1629
+ }
1630
+
1631
+ .menu-submenu-indicator {
1632
+ display: inline-flex;
1633
+ margin-inline-end: calc(var(--gridkit-spacing) * -1);
1634
+ color: var(--gridkit-fg-muted);
1635
+ }
1636
+
1637
+ .menu-separator {
1638
+ height: var(--gridkit-border-width);
1639
+ margin: var(--gridkit-spacing) 0;
1640
+ background-color: var(--gridkit-line);
1641
+ }
1642
+
1643
+ /*
1644
+ * The column chooser, an overlay like the menus: a search box, "Show all",
1645
+ * then a checkbox per column. A fixed width, so the panel does not jump
1646
+ * sideways as a search narrows the list.
1647
+ */
1648
+ .gridkit-column-chooser {
1649
+ display: flex;
1650
+ flex-direction: column;
1651
+ gap: calc(var(--gridkit-spacing) * 1.5);
1652
+ width: var(--gridkit-column-chooser-width);
1653
+ padding: calc(var(--gridkit-spacing) * 2);
1654
+ font-size: var(--gridkit-menu-font-size);
1655
+ background-color: var(--gridkit-surface);
1656
+ border: var(--gridkit-border-width) solid var(--gridkit-line);
1657
+ border-radius: var(--gridkit-menu-radius);
1658
+ box-shadow: var(--gridkit-overlay-shadow)
1659
+ var(--gridkit-overlay-shadow-color);
1660
+ }
1661
+
1662
+ .column-chooser-search {
1663
+ box-sizing: border-box;
1664
+ width: 100%;
1665
+ padding: var(--gridkit-spacing) calc(var(--gridkit-spacing) * 2);
1666
+ font: inherit;
1667
+ color: var(--gridkit-fg);
1668
+ background-color: var(--gridkit-surface);
1669
+ border: var(--gridkit-border-width) solid var(--gridkit-line);
1670
+ border-radius: var(--gridkit-control-radius);
1671
+ }
1672
+
1673
+ .column-chooser-search::placeholder {
1674
+ color: var(--gridkit-fg-muted);
1675
+ }
1676
+
1677
+ .column-chooser-search:focus-visible {
1678
+ outline: var(--gridkit-focus-ring-width) solid var(--gridkit-focus-ring);
1679
+ outline-offset: 1px;
1680
+ }
1681
+
1682
+ /*
1683
+ * Past about ten columns the list scrolls inside itself — a scroll there
1684
+ * does not dismiss the chooser, as one outside would — while the search
1685
+ * box and "Show all" stay put above it.
1686
+ */
1687
+ .column-chooser-list {
1688
+ max-height: var(--gridkit-column-chooser-max-height);
1689
+ margin: 0;
1690
+ padding: 0;
1691
+ overflow-y: auto;
1692
+ list-style: none;
1693
+ }
1694
+
1695
+ /* One checkbox and its label: "Show all", or a column. */
1696
+ .column-chooser-option {
1697
+ display: flex;
1698
+ align-items: center;
1699
+ gap: calc(var(--gridkit-spacing) * 2);
1700
+ padding: var(--gridkit-spacing);
1701
+ border-radius: var(--gridkit-control-radius);
1702
+ cursor: pointer;
1703
+ user-select: none;
1704
+ }
1705
+
1706
+ .column-chooser-option:hover {
1707
+ background-color: var(--gridkit-surface-muted);
1708
+ }
1709
+
1710
+ /* "Show all", set apart from the columns it acts on. */
1711
+ .column-chooser-all {
1712
+ padding-bottom: calc(var(--gridkit-spacing) * 1.5);
1713
+ border-bottom: var(--gridkit-border-width) solid var(--gridkit-line);
1714
+ border-radius: 0;
1715
+ }
1716
+
1717
+ .column-chooser-option input {
1718
+ flex-shrink: 0;
1719
+ margin: 0;
1720
+ accent-color: var(--gridkit-accent);
1721
+ }
1722
+
1723
+ .column-chooser-option input:focus-visible {
1724
+ outline: var(--gridkit-focus-ring-width) solid var(--gridkit-focus-ring);
1725
+ outline-offset: 1px;
1726
+ }
1727
+
1728
+ /* A column that cannot be hidden, or the last one shown. */
1729
+ .column-chooser-option:has(> input:disabled) {
1730
+ color: var(--gridkit-fg-muted);
1731
+ cursor: default;
1732
+ }
1733
+
1734
+ .column-chooser-option:has(> input:disabled):hover {
1735
+ background-color: transparent;
1736
+ }
1737
+
1738
+ .column-chooser-empty {
1739
+ margin: 0;
1740
+ padding: var(--gridkit-spacing);
1741
+ color: var(--gridkit-fg-muted);
1742
+ }
1743
+
1744
+ /*
1745
+ * A column's filter menu, an overlay like the chooser: the column's sort
1746
+ * entries, its editor, then its buttons. A fixed width, so the panel does
1747
+ * not jump sideways as its editor changes.
1748
+ */
1749
+ .gridkit-filter-menu {
1750
+ box-sizing: border-box;
1751
+ width: var(--gridkit-filter-menu-width);
1752
+ padding: calc(var(--gridkit-spacing) * 2);
1753
+ font-size: var(--gridkit-menu-font-size);
1754
+ background-color: var(--gridkit-surface);
1755
+ border: var(--gridkit-border-width) solid var(--gridkit-line);
1756
+ border-radius: var(--gridkit-menu-radius);
1757
+ box-shadow: var(--gridkit-overlay-shadow)
1758
+ var(--gridkit-overlay-shadow-color);
1759
+ }
1760
+
1761
+ .filter-menu-form {
1762
+ display: flex;
1763
+ flex-direction: column;
1764
+ gap: calc(var(--gridkit-spacing) * 2);
1765
+ margin: 0;
1766
+ }
1767
+
1768
+ /* The sort entries, drawn as a menu's items and set apart from the editor. */
1769
+ .filter-menu-sort {
1770
+ display: flex;
1771
+ flex-direction: column;
1772
+ padding-bottom: calc(var(--gridkit-spacing) * 1.5);
1773
+ border-bottom: var(--gridkit-border-width) solid var(--gridkit-line);
1774
+ }
1775
+
1776
+ .filter-menu-sort-item {
1777
+ display: flex;
1778
+ align-items: center;
1779
+ gap: calc(var(--gridkit-spacing) * 2);
1780
+ padding: var(--gridkit-menu-item-padding-y) calc(var(--gridkit-spacing) * 2);
1781
+ font: inherit;
1782
+ color: inherit;
1783
+ text-align: start;
1784
+ background: none;
1785
+ border: 0;
1786
+ border-radius: var(--gridkit-control-radius);
1787
+ cursor: pointer;
1788
+ }
1789
+
1790
+ .filter-menu-sort-item:hover {
1791
+ background-color: var(--gridkit-surface-muted);
1792
+ }
1793
+
1794
+ .filter-menu-sort-item:focus-visible {
1795
+ outline: var(--gridkit-focus-ring-width) solid var(--gridkit-focus-ring);
1796
+ outline-offset: calc(var(--gridkit-focus-ring-width) * -1);
1797
+ }
1798
+
1799
+ .filter-menu-sort-item:disabled {
1800
+ color: var(--gridkit-fg-muted);
1801
+ background: none;
1802
+ cursor: default;
1803
+ }
1804
+
1805
+ /* Always there, empty for "Clear sort", so every label starts in line. */
1806
+ .filter-menu-sort-icon {
1807
+ display: inline-flex;
1808
+ flex-shrink: 0;
1809
+ align-items: center;
1810
+ justify-content: center;
1811
+ width: var(--gridkit-icon-size);
1812
+ height: var(--gridkit-icon-size);
1813
+ color: var(--gridkit-fg-muted);
1814
+ }
1815
+
1816
+ .filter-menu-sort-icon svg {
1817
+ width: var(--gridkit-icon-size);
1818
+ height: var(--gridkit-icon-size);
1819
+ }
1820
+
1821
+ /* The direction the column sorts in now. */
1822
+ .filter-menu-sort-item[aria-pressed="true"] .filter-menu-sort-icon {
1823
+ color: var(--gridkit-accent);
1824
+ }
1825
+
1826
+ /*
1827
+ * The simple editor: its conditions in a column, And/Or between them, and
1828
+ * the message under them while one is marked invalid.
1829
+ */
1830
+ .filter-menu-conditions {
1831
+ display: flex;
1832
+ flex-direction: column;
1833
+ gap: calc(var(--gridkit-spacing) * 2);
1834
+ }
1835
+
1836
+ /*
1837
+ * "Custom filter", or "Filtered by condition" above a checklist: a filter
1838
+ * the editor can't show, until it applies one in its place.
1839
+ */
1840
+ .filter-menu-custom {
1841
+ margin: 0;
1842
+ padding: var(--gridkit-spacing) calc(var(--gridkit-spacing) * 2);
1843
+ color: var(--gridkit-fg-muted);
1844
+ background-color: var(--gridkit-surface-muted);
1845
+ border-radius: var(--gridkit-control-radius);
1846
+ }
1847
+
1848
+ /*
1849
+ * The operator picker on a line of its own, the value boxes sharing the
1850
+ * next: one fills it, and a "Between"'s two halve it.
1851
+ */
1852
+ .filter-menu-condition {
1853
+ display: flex;
1854
+ flex-wrap: wrap;
1855
+ gap: calc(var(--gridkit-spacing) * 1.5);
1856
+ }
1857
+
1858
+ .filter-menu-operator,
1859
+ .filter-menu-value {
1860
+ box-sizing: border-box;
1861
+ min-width: 0;
1862
+ padding: var(--gridkit-spacing) calc(var(--gridkit-spacing) * 2);
1863
+ font: inherit;
1864
+ color: var(--gridkit-fg);
1865
+ background-color: var(--gridkit-surface);
1866
+ border: var(--gridkit-border-width) solid var(--gridkit-line);
1867
+ border-radius: var(--gridkit-control-radius);
1868
+ }
1869
+
1870
+ .filter-menu-operator {
1871
+ flex-basis: 100%;
1872
+ }
1873
+
1874
+ .filter-menu-value {
1875
+ flex: 1 1 0;
1876
+ }
1877
+
1878
+ .filter-menu-operator:focus-visible,
1879
+ .filter-menu-value:focus-visible {
1880
+ outline: var(--gridkit-focus-ring-width) solid var(--gridkit-focus-ring);
1881
+ outline-offset: 1px;
1882
+ }
1883
+
1884
+ .filter-menu-value[aria-invalid="true"] {
1885
+ border-color: var(--gridkit-error);
1886
+ }
1887
+
1888
+ .filter-menu-invalid {
1889
+ margin: 0;
1890
+ color: var(--gridkit-error);
1891
+ }
1892
+
1893
+ /* And/Or, native radio buttons between the two conditions. */
1894
+ .filter-menu-combinator {
1895
+ display: flex;
1896
+ gap: calc(var(--gridkit-spacing) * 4);
1897
+ }
1898
+
1899
+ .filter-menu-combinator-option {
1900
+ display: inline-flex;
1901
+ align-items: center;
1902
+ gap: calc(var(--gridkit-spacing) * 1.5);
1903
+ cursor: pointer;
1904
+ user-select: none;
1905
+ }
1906
+
1907
+ .filter-menu-combinator-option input {
1908
+ margin: 0;
1909
+ accent-color: var(--gridkit-accent);
1910
+ }
1911
+
1912
+ .filter-menu-combinator-option input:focus-visible {
1913
+ outline: var(--gridkit-focus-ring-width) solid var(--gridkit-focus-ring);
1914
+ outline-offset: 1px;
1915
+ }
1916
+
1917
+ /*
1918
+ * The excel editor: a search box, "Select all", then the checklist of the
1919
+ * column's values, with a note above or below it when one applies.
1920
+ */
1921
+ .filter-menu-values {
1922
+ display: flex;
1923
+ flex-direction: column;
1924
+ gap: calc(var(--gridkit-spacing) * 1.5);
1925
+ }
1926
+
1927
+ /*
1928
+ * The excel editor's "Number filters" button, drawn as a menu item whose
1929
+ * submenu opens beside it, and "Back to values" above a condition it
1930
+ * chose: the sort entries' look, an icon before the label in line with
1931
+ * theirs.
1932
+ */
1933
+ .filter-menu-submenu,
1934
+ .filter-menu-back {
1935
+ display: flex;
1936
+ align-items: center;
1937
+ gap: calc(var(--gridkit-spacing) * 2);
1938
+ padding: var(--gridkit-menu-item-padding-y) calc(var(--gridkit-spacing) * 2);
1939
+ font: inherit;
1940
+ color: inherit;
1941
+ text-align: start;
1942
+ background: none;
1943
+ border: 0;
1944
+ border-radius: var(--gridkit-control-radius);
1945
+ cursor: pointer;
1946
+ }
1947
+
1948
+ .filter-menu-submenu:hover,
1949
+ .filter-menu-submenu[aria-expanded="true"],
1950
+ .filter-menu-back:hover {
1951
+ background-color: var(--gridkit-surface-muted);
1952
+ }
1953
+
1954
+ .filter-menu-submenu:focus-visible,
1955
+ .filter-menu-back:focus-visible {
1956
+ outline: var(--gridkit-focus-ring-width) solid var(--gridkit-focus-ring);
1957
+ outline-offset: calc(var(--gridkit-focus-ring-width) * -1);
1958
+ }
1959
+
1960
+ .filter-menu-submenu-icon,
1961
+ .filter-menu-submenu-indicator,
1962
+ .filter-menu-back-icon {
1963
+ display: inline-flex;
1964
+ flex-shrink: 0;
1965
+ align-items: center;
1966
+ justify-content: center;
1967
+ width: var(--gridkit-icon-size);
1968
+ height: var(--gridkit-icon-size);
1969
+ color: var(--gridkit-fg-muted);
1970
+ }
1971
+
1972
+ .filter-menu-submenu svg,
1973
+ .filter-menu-back svg {
1974
+ width: var(--gridkit-icon-size);
1975
+ height: var(--gridkit-icon-size);
1976
+ }
1977
+
1978
+ /* Ticked while the column is filtered by conditions, as Excel's is. */
1979
+ .filter-menu-submenu-icon {
1980
+ color: var(--gridkit-accent);
1981
+ }
1982
+
1983
+ /* The submenu's chevron, at the end of the line. */
1984
+ .filter-menu-submenu-indicator {
1985
+ margin-inline-start: auto;
1986
+ }
1987
+
1988
+ .filter-menu-search {
1989
+ box-sizing: border-box;
1990
+ width: 100%;
1991
+ padding: var(--gridkit-spacing) calc(var(--gridkit-spacing) * 2);
1992
+ font: inherit;
1993
+ color: var(--gridkit-fg);
1994
+ background-color: var(--gridkit-surface);
1995
+ border: var(--gridkit-border-width) solid var(--gridkit-line);
1996
+ border-radius: var(--gridkit-control-radius);
1997
+ }
1998
+
1999
+ .filter-menu-search::placeholder {
2000
+ color: var(--gridkit-fg-muted);
2001
+ }
2002
+
2003
+ .filter-menu-search:focus-visible {
2004
+ outline: var(--gridkit-focus-ring-width) solid var(--gridkit-focus-ring);
2005
+ outline-offset: 1px;
2006
+ }
2007
+
2008
+ /* A native checkbox, in line with the drawn ones in the list below. */
2009
+ .filter-menu-select-all {
2010
+ display: flex;
2011
+ align-items: center;
2012
+ gap: calc(var(--gridkit-spacing) * 2);
2013
+ padding: var(--gridkit-spacing)
2014
+ calc(var(--gridkit-spacing) * 2 + var(--gridkit-border-width));
2015
+ cursor: pointer;
2016
+ user-select: none;
2017
+ }
2018
+
2019
+ .filter-menu-select-all input {
2020
+ flex-shrink: 0;
2021
+ width: var(--gridkit-icon-size);
2022
+ height: var(--gridkit-icon-size);
2023
+ margin: 0;
2024
+ accent-color: var(--gridkit-accent);
2025
+ }
2026
+
2027
+ .filter-menu-select-all input:focus-visible {
2028
+ outline: var(--gridkit-focus-ring-width) solid var(--gridkit-focus-ring);
2029
+ outline-offset: 1px;
2030
+ }
2031
+
2032
+ /* Nothing to select: the search matches no value. */
2033
+ .filter-menu-select-all:has(> input:disabled) {
2034
+ color: var(--gridkit-fg-muted);
2035
+ cursor: default;
2036
+ }
2037
+
2038
+ /*
2039
+ * The list scrolls inside itself, at a fixed height: the menu keeps its
2040
+ * size as a search narrows the list, and the virtualizer windowing it
2041
+ * knows from the start how many values it shows. "Type to search …"
2042
+ * takes the same box while the list waits for a search.
2043
+ */
2044
+ .filter-menu-checklist,
2045
+ .filter-menu-search-first {
2046
+ box-sizing: border-box;
2047
+ height: var(--gridkit-filter-checklist-height);
2048
+ margin: 0;
2049
+ border: var(--gridkit-border-width) solid var(--gridkit-line);
2050
+ border-radius: var(--gridkit-control-radius);
2051
+ }
2052
+
2053
+ .filter-menu-checklist {
2054
+ overflow-y: auto;
2055
+ }
2056
+
2057
+ .filter-menu-search-first {
2058
+ padding: calc(var(--gridkit-spacing) * 2);
2059
+ color: var(--gridkit-fg-muted);
2060
+ }
2061
+
2062
+ /* One value: a drawn checkbox, its label, and its count when shown. */
2063
+ .filter-menu-option {
2064
+ display: flex;
2065
+ align-items: center;
2066
+ gap: calc(var(--gridkit-spacing) * 2);
2067
+ padding: var(--gridkit-spacing) calc(var(--gridkit-spacing) * 2);
2068
+ line-height: 1.25rem;
2069
+ cursor: pointer;
2070
+ user-select: none;
2071
+ }
2072
+
2073
+ .filter-menu-option:hover {
2074
+ background-color: var(--gridkit-surface-muted);
2075
+ }
2076
+
2077
+ .filter-menu-option:focus-visible {
2078
+ outline: var(--gridkit-focus-ring-width) solid var(--gridkit-focus-ring);
2079
+ outline-offset: calc(var(--gridkit-focus-ring-width) * -1);
2080
+ }
2081
+
2082
+ /*
2083
+ * An outline, filled with the accent and ticked in `--gridkit-accent-fg`
2084
+ * while the option is checked, as the native "Select all" above it is.
2085
+ */
2086
+ .filter-menu-option-check {
2087
+ display: inline-flex;
2088
+ flex-shrink: 0;
2089
+ align-items: center;
2090
+ justify-content: center;
2091
+ box-sizing: border-box;
2092
+ width: var(--gridkit-icon-size);
2093
+ height: var(--gridkit-icon-size);
2094
+ color: var(--gridkit-accent-fg);
2095
+ border: var(--gridkit-border-width) solid var(--gridkit-fg-muted);
2096
+ border-radius: calc(var(--gridkit-control-radius) * 0.75);
2097
+ }
2098
+
2099
+ .filter-menu-option-check svg {
2100
+ width: calc(var(--gridkit-icon-size) * 0.75);
2101
+ height: calc(var(--gridkit-icon-size) * 0.75);
2102
+ visibility: hidden;
2103
+ }
2104
+
2105
+ .filter-menu-option[aria-checked="true"] .filter-menu-option-check {
2106
+ background-color: var(--gridkit-accent);
2107
+ border-color: var(--gridkit-accent);
2108
+ }
2109
+
2110
+ .filter-menu-option[aria-checked="true"] .filter-menu-option-check svg {
2111
+ visibility: visible;
2112
+ }
2113
+
2114
+ /* A label too long for the menu ends in an ellipsis. */
2115
+ .filter-menu-option-label {
2116
+ flex: 1;
2117
+ min-width: 0;
2118
+ overflow: hidden;
2119
+ text-overflow: ellipsis;
2120
+ white-space: nowrap;
2121
+ }
2122
+
2123
+ /* How many rows hold the value, muted at the end of its line. */
2124
+ .filter-menu-option-count {
2125
+ flex-shrink: 0;
2126
+ font-size: var(--gridkit-hint-font-size);
2127
+ font-variant-numeric: tabular-nums;
2128
+ color: var(--gridkit-fg-muted);
2129
+ }
2130
+
2131
+ /* "250 more, refine the search", under a list the value limit cut short. */
2132
+ .filter-menu-more {
2133
+ margin: 0;
2134
+ padding: 0 calc(var(--gridkit-spacing) * 2);
2135
+ color: var(--gridkit-fg-muted);
2136
+ }
2137
+
2138
+ /* "Clear filter" at the start; Cancel and OK at the end. */
2139
+ .filter-menu-actions {
2140
+ display: flex;
2141
+ align-items: center;
2142
+ gap: calc(var(--gridkit-spacing) * 2);
2143
+ }
2144
+
2145
+ .filter-menu-actions button {
2146
+ padding: var(--gridkit-spacing) calc(var(--gridkit-spacing) * 3);
2147
+ font: inherit;
2148
+ color: var(--gridkit-fg);
2149
+ background-color: var(--gridkit-surface);
2150
+ border: var(--gridkit-border-width) solid var(--gridkit-line);
2151
+ border-radius: var(--gridkit-control-radius);
2152
+ cursor: pointer;
2153
+ }
2154
+
2155
+ .filter-menu-actions button:hover {
2156
+ background-color: var(--gridkit-surface-muted);
2157
+ }
2158
+
2159
+ .filter-menu-actions button:focus-visible {
2160
+ outline: var(--gridkit-focus-ring-width) solid var(--gridkit-focus-ring);
2161
+ outline-offset: 1px;
2162
+ }
2163
+
2164
+ .filter-menu-actions button:disabled {
2165
+ color: var(--gridkit-fg-muted);
2166
+ background-color: var(--gridkit-surface);
2167
+ cursor: default;
2168
+ }
2169
+
2170
+ .filter-menu-actions .filter-menu-clear {
2171
+ margin-inline-end: auto;
2172
+ }
2173
+
2174
+ /*
2175
+ * OK, the button Enter presses: tinted rather than filled with the accent,
2176
+ * which the text on it would not stand out from in the light palette.
2177
+ */
2178
+ .filter-menu-actions .filter-menu-apply {
2179
+ background-color: var(--gridkit-selected);
2180
+ border-color: var(--gridkit-accent);
2181
+ }
2182
+
2183
+ .filter-menu-actions .filter-menu-apply:hover {
2184
+ background-color: var(--gridkit-selected-strong);
2185
+ }
2186
+ }
2187
+
2188
+ /*
2189
+ * The browser's own Ctrl+P of a page holding a grid: the grid as a plain
2190
+ * table, every row it has drawn flowing onto as many pages as it takes. Only
2191
+ * those rows — not one a virtualised grid has not mounted, nor another
2192
+ * page's — which is why printing through the grid's own Print, building a
2193
+ * document of every row in a hidden frame, is the supported path.
2194
+ */
2195
+ @layer base {
2196
+ @media print {
2197
+ /* What is for using the grid, not reading it. */
2198
+ .gridkit-data-grid-viewport .gridkit-toolbar,
2199
+ .gridkit-data-grid-viewport .gridkit-group-by-bar,
2200
+ .gridkit-data-grid-viewport .gridkit-grid-pager,
2201
+ .gridkit-data-grid-viewport .header-resize-handle {
2202
+ display: none;
2203
+ }
2204
+
2205
+ /* No height bound, and nothing scrolls, so no row is cut off. */
2206
+ .gridkit-data-grid-viewport,
2207
+ .gridkit-data-grid-viewport .gridkit-data-grid-body {
2208
+ height: auto;
2209
+ overflow: visible;
2210
+ }
2211
+
2212
+ .gridkit-data-grid-viewport :focus-visible {
2213
+ outline: none;
2214
+ }
2215
+
2216
+ /* Shading printed as it is, not dropped to save ink. */
2217
+ .gridkit-data-grid {
2218
+ -webkit-print-color-adjust: exact;
2219
+ print-color-adjust: exact;
2220
+ }
2221
+
2222
+ .gridkit-data-grid tr {
2223
+ break-inside: avoid;
2224
+ }
2225
+ }
2226
+ }