@soxa-ui/theme-base 0.0.0-stage → 0.3.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 (61) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +399 -2
  3. package/dist/setup/index.d.ts +31 -0
  4. package/dist/setup/index.js +4 -0
  5. package/dist/setup/plugins/index.d.ts +3 -0
  6. package/dist/setup/plugins/table-head.plugin.d.ts +44 -0
  7. package/dist/setup/plugins/table-head.plugin.js +86 -0
  8. package/dist/setup/plugins/tabs-view.plugin.d.ts +32 -0
  9. package/dist/setup/plugins/tabs-view.plugin.js +100 -0
  10. package/dist/setup/plugins/toasts-front.plugin.d.ts +63 -0
  11. package/dist/setup/plugins/toasts-front.plugin.js +131 -0
  12. package/dist/setup/plugins/transitions.d.ts +43 -0
  13. package/dist/setup/plugins/transitions.js +75 -0
  14. package/package.json +55 -4
  15. package/src/anchored.css +75 -0
  16. package/src/caption.css +71 -0
  17. package/src/components/accordion.css +96 -0
  18. package/src/components/button.css +139 -0
  19. package/src/components/calendar.css +594 -0
  20. package/src/components/check-box.css +121 -0
  21. package/src/components/date-input.css +224 -0
  22. package/src/components/date-picker.css +208 -0
  23. package/src/components/dialog.css +95 -0
  24. package/src/components/drag-and-drop.css +18 -0
  25. package/src/components/drawer.css +171 -0
  26. package/src/components/field.css +440 -0
  27. package/src/components/icon.css +61 -0
  28. package/src/components/input.css +189 -0
  29. package/src/components/label.css +160 -0
  30. package/src/components/list-box.css +165 -0
  31. package/src/components/menu.css +224 -0
  32. package/src/components/number-input.css +183 -0
  33. package/src/components/pagination.css +164 -0
  34. package/src/components/popover.css +164 -0
  35. package/src/components/progress-linear.css +88 -0
  36. package/src/components/progress-spinner.css +127 -0
  37. package/src/components/radio-group.css +130 -0
  38. package/src/components/rating.css +141 -0
  39. package/src/components/scroller.css +136 -0
  40. package/src/components/select.css +233 -0
  41. package/src/components/skeleton.css +33 -0
  42. package/src/components/slider.css +482 -0
  43. package/src/components/switch.css +142 -0
  44. package/src/components/table.css +1513 -0
  45. package/src/components/tabs.css +166 -0
  46. package/src/components/tags.css +371 -0
  47. package/src/components/textarea.css +179 -0
  48. package/src/components/toasts.css +447 -0
  49. package/src/components/tooltip.css +59 -0
  50. package/src/direction.css +30 -0
  51. package/src/disabled.css +26 -0
  52. package/src/group.css +90 -0
  53. package/src/index.css +97 -0
  54. package/src/modal.css +183 -0
  55. package/src/place.css +30 -0
  56. package/src/required.css +40 -0
  57. package/src/reset.css +172 -0
  58. package/src/swipe.css +71 -0
  59. package/src/target.css +46 -0
  60. package/theme-contract.json +3373 -0
  61. package/theme-variables.json +336 -0
@@ -0,0 +1,1513 @@
1
+ /* ─── Table ───────────────────────────────────────────────────
2
+ * A table after the APG Table and Sortable Table patterns.
3
+ *
4
+ * .so-table__viewport window around the table — a block: scrolls sideways
5
+ * .so-table root — `table`
6
+ * .so-table__head head — `thead`
7
+ * .so-table__head-row header row — `tr`
8
+ * .so-table__select header of the selection column — `th`, "select all" checkbox
9
+ * .so-table-column column header — Table.Column (`th`)
10
+ * __content content row: the label and the sort button
11
+ * __text label — on every column, the `header` slot
12
+ * __sort sort button (Button) of a sortable column
13
+ * __sort-icon arrow in its text area: icon of the `arrowUpward` role
14
+ * __resizer strip of the width handle at the header's end
15
+ * __input its field — `input type="range"`, transparent
16
+ * .so-table__body body — `tbody`
17
+ * .so-table-row row — Table.Row (`tr`)
18
+ * __select cell of the selection column — `td`, the row's checkbox
19
+ * __cell cell — `td`, `th` in the row header column
20
+ * .so-table__empty-row row of the empty state — `tr`
21
+ * .so-table__empty its full-width cell — `td`, the `empty` slot
22
+ * .so-table__filler-row window spacer — `tr` in place of the skipped rows
23
+ * .so-table__filler its full-width cell — `td`, height from the style
24
+ *
25
+ * From the core: on the root — `data-disabled`, the `--size-*` modifier,
26
+ * `data-overflow` (columns wider than the place; absent — the place isn't
27
+ * measured yet), `data-sized` (a width is known for every shown column — its
28
+ * own or the core's layout; absent — no columns, or one at least has no width),
29
+ * `data-sticky-head`, `data-virtual` (the window of a `Virtual` wrapper) and
30
+ * the gesture flags, absent at rest altogether: `data-resizing` — a width handle
31
+ * is dragged, `data-dragging` — a column is being moved, `data-reorder-preview`
32
+ * — a column of a `column` table is being moved; on a column header —
33
+ * `data-align`, `data-resizing` (its handle is dragged), `data-reorderable` and
34
+ * `data-dragging` (it is carried), and the width as `--so-table-column-width`;
35
+ * on a row — `data-disabled`; on a cell — the column's `data-align`. The markup
36
+ * writes the height of the skipped rows to the spacer (`--so-filler-height`,
37
+ * shared with other lists), and the layout plugin `TTableHeadPlugin` the height
38
+ * of the sticky head (`--so-table-head-height`, `setup/` of the shared layer;
39
+ * the theme installs it).
40
+ *
41
+ * Geometry follows Button of the same size (`button.css`): a body row is its
42
+ * height and a focus ring bleed above and below (`--so-table-bleed`), a cell's
43
+ * padding is its padding and the same bleed. A button the consumer puts into a
44
+ * cell or a header label fits the row without stretching it, and its focus ring
45
+ * stays in its cell: not on the next column and not past the table's edge,
46
+ * where a scrolling container would clip it. The head row is lower than a body
47
+ * row (`--so-table-head-trim`): column labels are denser than data. The
48
+ * selection column is as wide as a CheckBox box of the same size
49
+ * (`check-box.css`) and the cell padding on both sides. The cells have a font
50
+ * scale of their own (`--so-table-font-size-<size>`), a step smaller than
51
+ * Button's: a table is data, not button labels. The block puts the steps of its
52
+ * size into variables on the table root, and everything else counts from them;
53
+ * a nested table sets its own on its own root and doesn't inherit the outer
54
+ * ones. For the same reason the rules that depend on the root are written from
55
+ * it with child combinators — they don't reach the cells of a nested table.
56
+ *
57
+ * The core lays out the column widths — of a column with its own width and of
58
+ * one without — by the place of the window and the table's `columnFit`, within
59
+ * the column's bounds. The browser's fixed layout reads neither `min-width` nor
60
+ * `max-width` of cells and can't share the place between bounded columns, so
61
+ * the base computes no widths: it puts the column's variable into the header's
62
+ * width. The layout is fixed (`table-layout: fixed`): a column with a width
63
+ * gets exactly it, whatever the content. The content doesn't change the width:
64
+ * there may be thousands of rows, the layout needn't measure every cell, and the
65
+ * columns don't jump when the rows are sorted, filtered or replaced. A long word
66
+ * wraps at the column's width and doesn't spill past its edge.
67
+ *
68
+ * With every shown column's width known, the table is as wide as their sum, not
69
+ * as the whole place: otherwise the browser's layout would hand the excess out
70
+ * to all columns, the widths would drift from the core's, and the handle
71
+ * wouldn't hold the edge. The core has already summed them by the place: in the
72
+ * filling modes the sum is the place, and wider than it the window scrolls.
73
+ * Until the core lays the columns out by a measured place (no `data-overflow`
74
+ * yet — the first frame, the server), a column without a width takes its
75
+ * minimum (below, at the header), and the table is the sum of the widths too:
76
+ * as wide as the place, it would hand the excess out to every column, the
77
+ * selection column included, and the layout plugin, measuring that column with
78
+ * its share, would give the core a place short by it — the columns would be
79
+ * laid out twice. Measured, the table is as wide as the place while a width is
80
+ * missing — there are no columns.
81
+ *
82
+ * The window (`__viewport`) is a block as wide as the place, with the table in
83
+ * it. The base makes it a block, not the tag: the window is a `div` by default
84
+ * (`<div so-table>` in Angular, `<div is="so-table">` in Web Components), but in
85
+ * Angular the consumer picks the window's tag, and on an inline tag it would
86
+ * stand inline without the rule. Columns wider than the place
87
+ * (`data-overflow='true'` on the table) — the window scrolls sideways; they fit
88
+ * (`false`) — it clips horizontally (`overflow-x: clip`): a frame where the
89
+ * measure lags behind the window's narrowing doesn't flash a scrollbar, and
90
+ * `clip` doesn't make the window a scroll container. No flag — the window clips
91
+ * nothing. The sticky head is an exception (below, at it). Chromium paints a
92
+ * scrollable window whole, not the band at the screen, and the rows painted
93
+ * past that band pay for the sideways scroll (`packages/themes/base/AGENTS.md`,
94
+ * «Окно и закреплённая шапка Table»).
95
+ *
96
+ * Rules multiplied by every body cell stand only under a gesture flag that is
97
+ * absent at rest, and no rule has a `:has()` condition on the table root: the
98
+ * browser checks it by walking the whole body on every style recalc of the root
99
+ * (`packages/themes/base/AGENTS.md`, «Правило на каждую ячейку Table — под
100
+ * признаком, которого на покое нет»). So what a condition over the headers
101
+ * could tell, the core gives as a root flag: widths — `data-sized`, a gesture —
102
+ * `data-resizing` and `data-dragging`. Guarded by `table-reorder.spec.ts`.
103
+ *
104
+ * The table has no surface of its own — no background, no border. Only the
105
+ * sticky head has an opaque background — the surface of the place (below).
106
+ *
107
+ * The look — text color and tabular figures, the weight of headers and cells,
108
+ * the lines of the head and the rows, selection and hover veils, header
109
+ * corners, the grid's focus ring, the sort button and arrow — its visibility,
110
+ * turn and priority number, — the width handle's line, the column ghost, the
111
+ * carried header's look, the drop mark, the reorder motion — neighbours making
112
+ * room, landing, the whole column, — the line under the sticky head and forced
113
+ * colors mode — is the theme's. */
114
+
115
+ /* The bleed around a button in a cell — exactly the focus ring (the same number
116
+ * as in Calendar); how much lower the head row is than a body row; the cell
117
+ * font by size; the minimum of a column without a width; the gap between the
118
+ * label and the sort button and the button's padding around the arrow; the
119
+ * width of the handle's strip. The defaults are the numbers of the oren theme; a
120
+ * theme sets its own on the document root. */
121
+ :root {
122
+ --so-table-bleed: 0.25rem;
123
+ --so-table-head-trim: 2px;
124
+
125
+ /* The cells are a step smaller than the text next to them, and every step
126
+ * counts from the font of the place: 4px smaller at `sm`, 2px at `normal`,
127
+ * the font of the place at `lg`, 2px and 4px larger at `xl` and `2xl` — 12 /
128
+ * 14 / 16 / 18 / 20px at a 16px place, 10 / 12 / 14 / 16 / 18px at 14px.
129
+ * `em` resolves where the variable is read: on the root `table`, against its
130
+ * viewport, that is the place. The place of a nested table is its cell. */
131
+ --so-table-font-size-sm: calc(1em - 0.25rem);
132
+ --so-table-line-height-sm: calc(1 / 0.75);
133
+ --so-table-font-size-normal: calc(1em - 0.125rem);
134
+ --so-table-line-height-normal: calc(1.25 / 0.875);
135
+ --so-table-font-size-lg: 1em;
136
+ --so-table-line-height-lg: 1.5;
137
+ --so-table-font-size-xl: calc(1em + 0.125rem);
138
+ --so-table-line-height-xl: calc(1.75 / 1.125);
139
+ --so-table-font-size-2xl: calc(1em + 0.25rem);
140
+ --so-table-line-height-2xl: calc(1.75 / 1.25);
141
+
142
+ --so-table-column-min-width: 10em;
143
+ --so-table-sort-gap: 0.25rem;
144
+ --so-table-sort-padding: 0.125rem;
145
+ --so-table-resizer-width: 14px;
146
+ }
147
+
148
+ /* Without a modifier the size is `normal`: the core always writes `--size-*`,
149
+ * but the block must look like the default without it too.
150
+ *
151
+ * - `--so-table-button`, `--so-table-button-inline` — height and padding of
152
+ * Button of the same size;
153
+ * - `--so-table-row` — a body row's height: the button and the bleed above and
154
+ * below;
155
+ * - `--so-table-head-row` — the head row's height. The sort button is smaller
156
+ * than a button of the size, and its focus ring fits the head;
157
+ * - `--so-table-cell-inline` — a cell's padding: the button's padding and the
158
+ * same bleed. A header label stands in the same padding, right above the text
159
+ * of the cells;
160
+ * - `--so-table-check` — the side of the checkbox box.
161
+ *
162
+ * A stacking context of its own (`isolation`): the handle strips are raised
163
+ * over the next header and the sort buttons (`z-index`), but the raise doesn't
164
+ * reach over the table's neighbours on the page. */
165
+ .so-table {
166
+ --so-table-button: var(--so-button-height-normal);
167
+ --so-table-button-inline: var(--so-button-padding-normal);
168
+ --so-table-check: var(--so-check-box-size-normal);
169
+ --so-table-row: calc(var(--so-table-button) + 2 * var(--so-table-bleed));
170
+ --so-table-head-row: calc(var(--so-table-row) - var(--so-table-head-trim));
171
+ --so-table-cell-inline: calc(var(--so-table-button-inline) + var(--so-table-bleed));
172
+ width: 100%;
173
+ table-layout: fixed;
174
+ border-collapse: collapse;
175
+ isolation: isolate;
176
+ font-size: var(--so-table-font-size-normal);
177
+ line-height: var(--so-table-line-height-normal);
178
+ }
179
+
180
+ .so-table--size-sm {
181
+ --so-table-button: var(--so-button-height-sm);
182
+ --so-table-button-inline: var(--so-button-padding-sm);
183
+ --so-table-check: var(--so-check-box-size-sm);
184
+ font-size: var(--so-table-font-size-sm);
185
+ line-height: var(--so-table-line-height-sm);
186
+ }
187
+
188
+ .so-table--size-normal {
189
+ --so-table-button: var(--so-button-height-normal);
190
+ --so-table-button-inline: var(--so-button-padding-normal);
191
+ --so-table-check: var(--so-check-box-size-normal);
192
+ font-size: var(--so-table-font-size-normal);
193
+ line-height: var(--so-table-line-height-normal);
194
+ }
195
+
196
+ .so-table--size-lg {
197
+ --so-table-button: var(--so-button-height-lg);
198
+ --so-table-button-inline: var(--so-button-padding-lg);
199
+ --so-table-check: var(--so-check-box-size-lg);
200
+ font-size: var(--so-table-font-size-lg);
201
+ line-height: var(--so-table-line-height-lg);
202
+ }
203
+
204
+ .so-table--size-xl {
205
+ --so-table-button: var(--so-button-height-xl);
206
+ --so-table-button-inline: var(--so-button-padding-xl);
207
+ --so-table-check: var(--so-check-box-size-xl);
208
+ font-size: var(--so-table-font-size-xl);
209
+ line-height: var(--so-table-line-height-xl);
210
+ }
211
+
212
+ .so-table--size-2xl {
213
+ --so-table-button: var(--so-button-height-2xl);
214
+ --so-table-button-inline: var(--so-button-padding-2xl);
215
+ --so-table-check: var(--so-check-box-size-2xl);
216
+ font-size: var(--so-table-font-size-2xl);
217
+ line-height: var(--so-table-line-height-2xl);
218
+ }
219
+
220
+ /* A disabled table fades as a whole, by its root (`disabled.css`) — the head,
221
+ * the rows and the empty state. Its rows are disabled with it — their
222
+ * `disabled` spreads from the table, — the sort buttons and checkboxes too, and
223
+ * under the faded root they don't fade a second time. */
224
+ .so-table[data-disabled='true'] {
225
+ opacity: var(--so-disabled-opacity);
226
+ }
227
+
228
+ .so-table[data-disabled='true'] > * {
229
+ --so-disabled-opacity: 1;
230
+ }
231
+
232
+ /* The table is as wide as the sum of its column widths (see above) until the
233
+ * place is measured (no `data-overflow`) and once a width is known for every
234
+ * shown column (`data-sized`). A fixed table is never narrower than the sum of
235
+ * its column widths, whatever width it is given (CSS 2.1, 17.5.2.1), so at zero
236
+ * it is exactly the sum. A measured table without columns has no `data-sized` —
237
+ * an empty head would collapse it. */
238
+ .so-table:not([data-overflow]),
239
+ .so-table[data-sized='true'] {
240
+ inline-size: 0;
241
+ }
242
+
243
+ /* While a handle is dragged — the handle's cursor over the whole table and no
244
+ * text selection: the pointer leaves the strip, and the column's edge follows
245
+ * it. */
246
+ .so-table[data-resizing='true'] {
247
+ cursor: col-resize;
248
+ -webkit-user-select: none;
249
+ user-select: none;
250
+ }
251
+
252
+ /* The window — a block that scrolls sideways by the table's flag (see above).
253
+ * With a sticky head the window doesn't scroll (below, at it). The `:has()`
254
+ * condition here is on the window, not on the table root: there is one table
255
+ * under the window. */
256
+ .so-table__viewport {
257
+ display: block;
258
+ }
259
+
260
+ .so-table__viewport:has(> .so-table[data-overflow='false']) {
261
+ overflow-x: clip;
262
+ }
263
+
264
+ .so-table__viewport:has(> .so-table[data-overflow='true']:not([data-sticky-head='true'])) {
265
+ overflow-x: auto;
266
+ }
267
+
268
+ /* Cells — what all of them share, in the head and in the body: padding at the
269
+ * row's ends and the middle vertically. The padding above and below is the
270
+ * focus ring bleed, and the row itself holds the height: a button of the
271
+ * table's size fits the row exactly, and a line of text stands in the middle.
272
+ * Text of several lines stretches the table row — with the same padding from
273
+ * the line as a button has.
274
+ *
275
+ * Alignment follows `data-align`: the core writes it on a cell; the cells of
276
+ * the selection column have none and stand at the start. The sides are
277
+ * logical, in RTL `start` is the right edge. The browser centers `th` — the base
278
+ * sets the side itself.
279
+ *
280
+ * A long word (an address, an email, a number) wraps at the column's width
281
+ * instead of going under the next one: the layout is fixed, and the content
282
+ * doesn't widen the column. */
283
+ .so-table-column,
284
+ .so-table__select,
285
+ .so-table-row__cell,
286
+ .so-table-row__select,
287
+ .so-table__empty {
288
+ padding-inline: var(--so-table-cell-inline);
289
+ padding-block: var(--so-table-bleed);
290
+ text-align: start;
291
+ vertical-align: middle;
292
+ overflow-wrap: break-word;
293
+ }
294
+
295
+ .so-table-column[data-align='center'],
296
+ .so-table__select[data-align='center'],
297
+ .so-table-row__cell[data-align='center'],
298
+ .so-table-row__select[data-align='center'],
299
+ .so-table__empty[data-align='center'] {
300
+ text-align: center;
301
+ }
302
+
303
+ .so-table-column[data-align='end'],
304
+ .so-table__select[data-align='end'],
305
+ .so-table-row__cell[data-align='end'],
306
+ .so-table-row__select[data-align='end'],
307
+ .so-table__empty[data-align='end'] {
308
+ text-align: end;
309
+ }
310
+
311
+ /* The head row is `--so-table-head-trim` lower than a body row. */
312
+ .so-table__head-row {
313
+ block-size: var(--so-table-head-row);
314
+ }
315
+
316
+ /* The head is raised over the body (`z-index`) — sticky or in the flow: it is
317
+ * painted after everything positioned in the body — the field of a row's
318
+ * checkbox, the `required` mark of Switch, the checkbox itself. There are
319
+ * several such pieces per row, and without the raise the browser would paint
320
+ * them after the headers. Then a header that became a layer of its own — a
321
+ * neighbour making room with the theme's transition, the landing of a carried
322
+ * one, a sort arrow turning — would make it check every body piece against
323
+ * that layer on every layerization (`packages/themes/base/AGENTS.md`, «Правило
324
+ * на каждую ячейку Table — под признаком, которого на покое нет»). A raised
325
+ * head is painted after the body, and there is nothing to check against its
326
+ * layers. Under a sticky head (below) the raise also takes the presses: rows go
327
+ * under it, and a checkbox's field would catch the presses on it from under
328
+ * the head.
329
+ *
330
+ * The raise is `sticky` without an edge, not `relative`: with no inset the two
331
+ * are the same — the same stacking context and painting order, — but WebKit
332
+ * turns `relative` on a row group into `static` and keeps `sticky` (the sticky
333
+ * head stands on it). The sticky head only adds the edge and the backing.
334
+ *
335
+ * The body is not made a layer of its own: in its own layer the browser puts a
336
+ * row's veil next to its line rather than under it, and a light stripe shows
337
+ * between two selected rows. A raise in a consumer's cell higher than the
338
+ * head's goes over it. */
339
+ .so-table > .so-table__head {
340
+ position: sticky;
341
+ z-index: 30;
342
+ }
343
+
344
+ /* A column header. The width is the variable the core computes: the column's
345
+ * own width or the layout's by the place. Without it — until the core lays the
346
+ * columns out — the column takes its minimum: `--so-table-column-min-width` (in
347
+ * oren ten font sizes, about twenty characters of text) and the cell padding on
348
+ * both sides. The minimum is the width itself, not a strut in the header: fixed
349
+ * layout takes the column widths from the header cells, and WebKit doesn't
350
+ * look at their content at all. Positioned: the width handle's strip stands at
351
+ * its end (below). */
352
+ .so-table-column {
353
+ position: relative;
354
+ inline-size: var(
355
+ --so-table-column-width,
356
+ calc(var(--so-table-column-min-width) + 2 * var(--so-table-cell-inline))
357
+ );
358
+ }
359
+
360
+ /* The header's content is a row: the label, and on a sortable column the sort
361
+ * button after it, centered vertically. The label is the head cell's text: it
362
+ * stands in the header's padding right above the text of the cells below.
363
+ *
364
+ * The label shrinks below its longest word (`min-width: 0`): the column wraps
365
+ * such a word, as in a cell, instead of pushing the button past the edge. It
366
+ * doesn't grow: the button stands right after the label, like the arrow in
367
+ * ClickUp tables, instead of drifting to the other edge of the cell. The lines
368
+ * of a wrapped label go to the column's side: the label inherits `text-align`
369
+ * from the header.
370
+ *
371
+ * Between the label and the button — the `--so-table-sort-gap` gap: no narrower
372
+ * than half of what the button lacks to the minimum target, so the button's hit
373
+ * zone (below) doesn't reach over the label — the consumer's own control in the
374
+ * label is pressable whole. */
375
+ .so-table-column__content {
376
+ display: flex;
377
+ align-items: center;
378
+ gap: var(--so-table-sort-gap);
379
+ }
380
+
381
+ .so-table-column__text {
382
+ min-width: 0;
383
+ }
384
+
385
+ /* Centered — the label with the button in the middle of the cell. */
386
+ .so-table-column[data-align='center'] > .so-table-column__content {
387
+ justify-content: center;
388
+ }
389
+
390
+ /* At the end — at the cell's end, with the button before the label: the label
391
+ * stands over the end of the column's numbers, and the arrow on the outer
392
+ * side. The flex order is logical, and in RTL the button moves to the right of
393
+ * the label by itself; the reversed row starts at the cell's end. */
394
+ .so-table-column[data-align='end'] > .so-table-column__content {
395
+ flex-direction: row-reverse;
396
+ }
397
+
398
+ /* The sort button is a square around the arrow in the header's font (`1em`)
399
+ * with `--so-table-sort-padding` around it: the base drops Button's sized height
400
+ * and padding — the markup passes the button no size, the column doesn't know
401
+ * the table's size, and the font comes from the header. The path from the
402
+ * header to the button is child combinators: the header's rules don't reach a
403
+ * button in the label of a nested table. The weight is above Button's scale
404
+ * (`button.css`).
405
+ *
406
+ * The arrow with the priority number the theme appends stands centered in the
407
+ * button, whole: Button's text area would cut the number with an ellipsis.
408
+ *
409
+ * The hit zone is an invisible square centered on the button, its side the
410
+ * minimum pointer target (`--so-target-size`, `target.css`), as for a tag's
411
+ * cross: the button itself is smaller. The zone is the button's pseudo-element:
412
+ * a press on it is a press on the button, and the button looks the same. The
413
+ * height fits at any size, and the zone doesn't reach over the label: the row's
414
+ * gap keeps them apart (above). At the border between columns the width
415
+ * handle's strip lies over the button and its zone (below): the press there is
416
+ * the handle's. */
417
+ .so-table-column > .so-table-column__content > .so-table-column__sort {
418
+ flex-shrink: 0;
419
+ height: auto;
420
+ padding: var(--so-table-sort-padding);
421
+ font: inherit;
422
+ }
423
+
424
+ .so-table-column > .so-table-column__content > .so-table-column__sort > .so-button__text {
425
+ display: flex;
426
+ align-items: center;
427
+ justify-content: center;
428
+ overflow: visible;
429
+ }
430
+
431
+ .so-table-column > .so-table-column__content > .so-table-column__sort::before {
432
+ content: '';
433
+ position: absolute;
434
+ inset: calc(50% - var(--so-target-size) / 2);
435
+ }
436
+
437
+ /* The arrow's wrapper is an inline flex centered on the line. The theme turns
438
+ * the wrapper's content, not the wrapper: the priority number stands in it as
439
+ * well and stays upright. */
440
+ .so-table-column__sort-icon {
441
+ display: inline-flex;
442
+ flex-shrink: 0;
443
+ align-items: center;
444
+ }
445
+
446
+ .so-table-column__sort-icon > * {
447
+ flex-shrink: 0;
448
+ }
449
+
450
+ /* The width handle is a strip at the header's end, its full height. The strip
451
+ * itself is invisible; the theme draws the border line between the columns
452
+ * that shows when the pointer reaches for the handle. The cursor is the width
453
+ * handle's.
454
+ *
455
+ * The strip is a grab zone `--so-table-resizer-width` wide (14 px in oren).
456
+ * That is narrower than the minimum pointer target of WCAG 2.5.8 (24 px) — the
457
+ * owner's decision: a 24 px strip took 12 px off the head cell at each edge of
458
+ * a column, and a narrow column couldn't be sorted without touching the handle.
459
+ * Without a pointer the column's width changes with the keys on the handle's
460
+ * field. The strip stands centered on the border between columns: half on its
461
+ * own header, half on the next one — where one aims to take the edge. Over the
462
+ * next header and over the sort buttons (`z-index`): the button stands at the
463
+ * label, and with a label as wide as the column it reaches the border — there
464
+ * the press is the handle's, and past the strip the button's.
465
+ *
466
+ * The strip takes the gesture whole (`touch-action: none`): a finger dragging
467
+ * a column's edge must not scroll the page.
468
+ *
469
+ * On the last column the strip lies whole on its own header: past the table's
470
+ * edge it would widen the scroll of its container. */
471
+ .so-table-column__resizer {
472
+ position: absolute;
473
+ inset-block: 0;
474
+ inset-inline-end: calc(-1 * var(--so-table-resizer-width) / 2);
475
+ z-index: 1;
476
+ inline-size: var(--so-table-resizer-width);
477
+ cursor: col-resize;
478
+ -webkit-user-select: none;
479
+ user-select: none;
480
+ touch-action: none;
481
+ }
482
+
483
+ .so-table-column:last-child > .so-table-column__resizer {
484
+ inset-inline-end: 0;
485
+ }
486
+
487
+ /* The handle's field fills the strip and is transparent: it stays in the
488
+ * accessibility tree and the focus order, and a screen reader draws its cursor
489
+ * frame around the strip. It doesn't catch the pointer: the plugin drags, not
490
+ * the browser — otherwise two would move the edge. */
491
+ .so-table-column__input {
492
+ position: absolute;
493
+ inset: 0;
494
+ width: 100%;
495
+ height: 100%;
496
+ margin: 0;
497
+ padding: 0;
498
+ opacity: 0;
499
+ appearance: none;
500
+ pointer-events: none;
501
+ }
502
+
503
+ /* A column is moved by its header. The column extension writes the gesture's
504
+ * marks, the gesture plugin the shifts. A finger dragging a header along the
505
+ * row doesn't scroll the table: along the row the gesture is the reorder's,
506
+ * across it the page's scroll.
507
+ *
508
+ * The carried header (`data-dragging`) is raised over its neighbours and their
509
+ * handles: it follows the pointer — by the shift the gesture plugin writes on
510
+ * it (`--so-table-column-drag`), in any motion mode, — while its look, the
511
+ * released header's way to its place and the drop mark over it are the
512
+ * theme's. The shift is `translate`, not a margin: the head's layout doesn't
513
+ * change with it. The carried header has no handle — no one drags the column's
514
+ * edge now. */
515
+ .so-table-column[data-reorderable='true'] {
516
+ touch-action: pan-y;
517
+ }
518
+
519
+ .so-table-column[data-dragging='true'] {
520
+ z-index: 3;
521
+ translate: var(--so-table-column-drag, 0) 0;
522
+ }
523
+
524
+ .so-table-column[data-dragging='true'] > .so-table-column__resizer {
525
+ visibility: hidden;
526
+ }
527
+
528
+ /* The whole column (`reorderPreview: 'column'`): the cells of the drawn rows
529
+ * follow the headers — a neighbour's cells shift with its header, and the cells
530
+ * of the carried column are hidden in their place. The base ties a cell to its
531
+ * header by the column's number: the cell of the k-th column of a row
532
+ * (`:nth-child(k of .so-table-row__cell)` — the selection column doesn't count)
533
+ * follows the k-th header of the head. Rows and cells carry no marks — they
534
+ * would be repainted on every change of the place: the body learns of the
535
+ * gesture from the head, and rows the window draws in mid-gesture pick it up by
536
+ * themselves. Up to thirty numbers: `sibling-index()` would give the number
537
+ * without enumeration, but it is Baseline only since August 2026, and the
538
+ * themes' browser policy doesn't take it yet (the owner's decision).
539
+ *
540
+ * Everything here stands under the root flag `data-reorder-preview`, present
541
+ * only in a gesture: the column extension sets it when a column of a `column`
542
+ * table is taken and removes it with the same repaint that moves the column. A
543
+ * rule per column number is multiplied by every body cell, and the browser
544
+ * matches a selector right to left — from the cell, reaching the root last; it
545
+ * sees the flag's name on the ancestors before matching (the ancestor filter),
546
+ * but not its value. So at rest no ancestor of a cell has the name, and the
547
+ * rule costs a cell nothing. Don't lengthen the ancestor chain of a cell rule —
548
+ * root, body, row: the filter keeps only a few nearest names of a selector, and
549
+ * the root's flag would drop out of them.
550
+ *
551
+ * The body reads the head through a `:has()` selector on the head itself, and
552
+ * the body, the head's sibling, gets the variables — what to do with the cells
553
+ * of each column (`--so-table-cells-<k>-shift`, `--so-table-cells-<k>-hidden`);
554
+ * the cells only read them. The column's number isn't last in the name: the
555
+ * base's guard would read a name ending in a number as a step of the theme's
556
+ * palette. A condition on the root would walk the whole body to depth three,
557
+ * all its rows and cells, — on the head the walk is a score of its nodes. A
558
+ * condition on the cell costs more still: the browser rechecks it for every
559
+ * cell on every change of a mark.
560
+ *
561
+ * Shifting and hiding the cells is the theme's call — its motion: the
562
+ * variables it sets on the body — how far to shift the neighbours' cells
563
+ * (`--so-table-body-shift`: they all give way to one side) and what to do with
564
+ * the carried column's cells (`--so-table-body-dragged`); the cells'
565
+ * transition is the theme's too, under the same root flag. The rules per column
566
+ * number only hand the variables to the cells and change nothing without a
567
+ * theme: without the variable the declaration is invalid, and the cell stays
568
+ * as it was — unshifted, with the row's visibility. With more than thirty
569
+ * columns the theme sets no body variables, and the body stands whole rather
570
+ * than partly. */
571
+ .so-table[data-reorder-preview='column']
572
+ > .so-table__head:has(
573
+ > .so-table__head-row > .so-table-column:nth-child(1 of .so-table-column)[data-shift]
574
+ )
575
+ ~ .so-table__body {
576
+ --so-table-cells-1-shift: var(--so-table-body-shift);
577
+ }
578
+
579
+ .so-table[data-reorder-preview='column']
580
+ > .so-table__head:has(
581
+ > .so-table__head-row
582
+ > .so-table-column:nth-child(1 of .so-table-column)[data-dragging='true']
583
+ )
584
+ ~ .so-table__body {
585
+ --so-table-cells-1-hidden: var(--so-table-body-dragged);
586
+ }
587
+
588
+ .so-table[data-reorder-preview='column']
589
+ > .so-table__body
590
+ > .so-table-row
591
+ > .so-table-row__cell:nth-child(1 of .so-table-row__cell) {
592
+ translate: var(--so-table-cells-1-shift) 0;
593
+ visibility: var(--so-table-cells-1-hidden);
594
+ }
595
+
596
+ .so-table[data-reorder-preview='column']
597
+ > .so-table__head:has(
598
+ > .so-table__head-row > .so-table-column:nth-child(2 of .so-table-column)[data-shift]
599
+ )
600
+ ~ .so-table__body {
601
+ --so-table-cells-2-shift: var(--so-table-body-shift);
602
+ }
603
+
604
+ .so-table[data-reorder-preview='column']
605
+ > .so-table__head:has(
606
+ > .so-table__head-row
607
+ > .so-table-column:nth-child(2 of .so-table-column)[data-dragging='true']
608
+ )
609
+ ~ .so-table__body {
610
+ --so-table-cells-2-hidden: var(--so-table-body-dragged);
611
+ }
612
+
613
+ .so-table[data-reorder-preview='column']
614
+ > .so-table__body
615
+ > .so-table-row
616
+ > .so-table-row__cell:nth-child(2 of .so-table-row__cell) {
617
+ translate: var(--so-table-cells-2-shift) 0;
618
+ visibility: var(--so-table-cells-2-hidden);
619
+ }
620
+
621
+ .so-table[data-reorder-preview='column']
622
+ > .so-table__head:has(
623
+ > .so-table__head-row > .so-table-column:nth-child(3 of .so-table-column)[data-shift]
624
+ )
625
+ ~ .so-table__body {
626
+ --so-table-cells-3-shift: var(--so-table-body-shift);
627
+ }
628
+
629
+ .so-table[data-reorder-preview='column']
630
+ > .so-table__head:has(
631
+ > .so-table__head-row
632
+ > .so-table-column:nth-child(3 of .so-table-column)[data-dragging='true']
633
+ )
634
+ ~ .so-table__body {
635
+ --so-table-cells-3-hidden: var(--so-table-body-dragged);
636
+ }
637
+
638
+ .so-table[data-reorder-preview='column']
639
+ > .so-table__body
640
+ > .so-table-row
641
+ > .so-table-row__cell:nth-child(3 of .so-table-row__cell) {
642
+ translate: var(--so-table-cells-3-shift) 0;
643
+ visibility: var(--so-table-cells-3-hidden);
644
+ }
645
+
646
+ .so-table[data-reorder-preview='column']
647
+ > .so-table__head:has(
648
+ > .so-table__head-row > .so-table-column:nth-child(4 of .so-table-column)[data-shift]
649
+ )
650
+ ~ .so-table__body {
651
+ --so-table-cells-4-shift: var(--so-table-body-shift);
652
+ }
653
+
654
+ .so-table[data-reorder-preview='column']
655
+ > .so-table__head:has(
656
+ > .so-table__head-row
657
+ > .so-table-column:nth-child(4 of .so-table-column)[data-dragging='true']
658
+ )
659
+ ~ .so-table__body {
660
+ --so-table-cells-4-hidden: var(--so-table-body-dragged);
661
+ }
662
+
663
+ .so-table[data-reorder-preview='column']
664
+ > .so-table__body
665
+ > .so-table-row
666
+ > .so-table-row__cell:nth-child(4 of .so-table-row__cell) {
667
+ translate: var(--so-table-cells-4-shift) 0;
668
+ visibility: var(--so-table-cells-4-hidden);
669
+ }
670
+
671
+ .so-table[data-reorder-preview='column']
672
+ > .so-table__head:has(
673
+ > .so-table__head-row > .so-table-column:nth-child(5 of .so-table-column)[data-shift]
674
+ )
675
+ ~ .so-table__body {
676
+ --so-table-cells-5-shift: var(--so-table-body-shift);
677
+ }
678
+
679
+ .so-table[data-reorder-preview='column']
680
+ > .so-table__head:has(
681
+ > .so-table__head-row
682
+ > .so-table-column:nth-child(5 of .so-table-column)[data-dragging='true']
683
+ )
684
+ ~ .so-table__body {
685
+ --so-table-cells-5-hidden: var(--so-table-body-dragged);
686
+ }
687
+
688
+ .so-table[data-reorder-preview='column']
689
+ > .so-table__body
690
+ > .so-table-row
691
+ > .so-table-row__cell:nth-child(5 of .so-table-row__cell) {
692
+ translate: var(--so-table-cells-5-shift) 0;
693
+ visibility: var(--so-table-cells-5-hidden);
694
+ }
695
+
696
+ .so-table[data-reorder-preview='column']
697
+ > .so-table__head:has(
698
+ > .so-table__head-row > .so-table-column:nth-child(6 of .so-table-column)[data-shift]
699
+ )
700
+ ~ .so-table__body {
701
+ --so-table-cells-6-shift: var(--so-table-body-shift);
702
+ }
703
+
704
+ .so-table[data-reorder-preview='column']
705
+ > .so-table__head:has(
706
+ > .so-table__head-row
707
+ > .so-table-column:nth-child(6 of .so-table-column)[data-dragging='true']
708
+ )
709
+ ~ .so-table__body {
710
+ --so-table-cells-6-hidden: var(--so-table-body-dragged);
711
+ }
712
+
713
+ .so-table[data-reorder-preview='column']
714
+ > .so-table__body
715
+ > .so-table-row
716
+ > .so-table-row__cell:nth-child(6 of .so-table-row__cell) {
717
+ translate: var(--so-table-cells-6-shift) 0;
718
+ visibility: var(--so-table-cells-6-hidden);
719
+ }
720
+
721
+ .so-table[data-reorder-preview='column']
722
+ > .so-table__head:has(
723
+ > .so-table__head-row > .so-table-column:nth-child(7 of .so-table-column)[data-shift]
724
+ )
725
+ ~ .so-table__body {
726
+ --so-table-cells-7-shift: var(--so-table-body-shift);
727
+ }
728
+
729
+ .so-table[data-reorder-preview='column']
730
+ > .so-table__head:has(
731
+ > .so-table__head-row
732
+ > .so-table-column:nth-child(7 of .so-table-column)[data-dragging='true']
733
+ )
734
+ ~ .so-table__body {
735
+ --so-table-cells-7-hidden: var(--so-table-body-dragged);
736
+ }
737
+
738
+ .so-table[data-reorder-preview='column']
739
+ > .so-table__body
740
+ > .so-table-row
741
+ > .so-table-row__cell:nth-child(7 of .so-table-row__cell) {
742
+ translate: var(--so-table-cells-7-shift) 0;
743
+ visibility: var(--so-table-cells-7-hidden);
744
+ }
745
+
746
+ .so-table[data-reorder-preview='column']
747
+ > .so-table__head:has(
748
+ > .so-table__head-row > .so-table-column:nth-child(8 of .so-table-column)[data-shift]
749
+ )
750
+ ~ .so-table__body {
751
+ --so-table-cells-8-shift: var(--so-table-body-shift);
752
+ }
753
+
754
+ .so-table[data-reorder-preview='column']
755
+ > .so-table__head:has(
756
+ > .so-table__head-row
757
+ > .so-table-column:nth-child(8 of .so-table-column)[data-dragging='true']
758
+ )
759
+ ~ .so-table__body {
760
+ --so-table-cells-8-hidden: var(--so-table-body-dragged);
761
+ }
762
+
763
+ .so-table[data-reorder-preview='column']
764
+ > .so-table__body
765
+ > .so-table-row
766
+ > .so-table-row__cell:nth-child(8 of .so-table-row__cell) {
767
+ translate: var(--so-table-cells-8-shift) 0;
768
+ visibility: var(--so-table-cells-8-hidden);
769
+ }
770
+
771
+ .so-table[data-reorder-preview='column']
772
+ > .so-table__head:has(
773
+ > .so-table__head-row > .so-table-column:nth-child(9 of .so-table-column)[data-shift]
774
+ )
775
+ ~ .so-table__body {
776
+ --so-table-cells-9-shift: var(--so-table-body-shift);
777
+ }
778
+
779
+ .so-table[data-reorder-preview='column']
780
+ > .so-table__head:has(
781
+ > .so-table__head-row
782
+ > .so-table-column:nth-child(9 of .so-table-column)[data-dragging='true']
783
+ )
784
+ ~ .so-table__body {
785
+ --so-table-cells-9-hidden: var(--so-table-body-dragged);
786
+ }
787
+
788
+ .so-table[data-reorder-preview='column']
789
+ > .so-table__body
790
+ > .so-table-row
791
+ > .so-table-row__cell:nth-child(9 of .so-table-row__cell) {
792
+ translate: var(--so-table-cells-9-shift) 0;
793
+ visibility: var(--so-table-cells-9-hidden);
794
+ }
795
+
796
+ .so-table[data-reorder-preview='column']
797
+ > .so-table__head:has(
798
+ > .so-table__head-row > .so-table-column:nth-child(10 of .so-table-column)[data-shift]
799
+ )
800
+ ~ .so-table__body {
801
+ --so-table-cells-10-shift: var(--so-table-body-shift);
802
+ }
803
+
804
+ .so-table[data-reorder-preview='column']
805
+ > .so-table__head:has(
806
+ > .so-table__head-row
807
+ > .so-table-column:nth-child(10 of .so-table-column)[data-dragging='true']
808
+ )
809
+ ~ .so-table__body {
810
+ --so-table-cells-10-hidden: var(--so-table-body-dragged);
811
+ }
812
+
813
+ .so-table[data-reorder-preview='column']
814
+ > .so-table__body
815
+ > .so-table-row
816
+ > .so-table-row__cell:nth-child(10 of .so-table-row__cell) {
817
+ translate: var(--so-table-cells-10-shift) 0;
818
+ visibility: var(--so-table-cells-10-hidden);
819
+ }
820
+
821
+ .so-table[data-reorder-preview='column']
822
+ > .so-table__head:has(
823
+ > .so-table__head-row > .so-table-column:nth-child(11 of .so-table-column)[data-shift]
824
+ )
825
+ ~ .so-table__body {
826
+ --so-table-cells-11-shift: var(--so-table-body-shift);
827
+ }
828
+
829
+ .so-table[data-reorder-preview='column']
830
+ > .so-table__head:has(
831
+ > .so-table__head-row
832
+ > .so-table-column:nth-child(11 of .so-table-column)[data-dragging='true']
833
+ )
834
+ ~ .so-table__body {
835
+ --so-table-cells-11-hidden: var(--so-table-body-dragged);
836
+ }
837
+
838
+ .so-table[data-reorder-preview='column']
839
+ > .so-table__body
840
+ > .so-table-row
841
+ > .so-table-row__cell:nth-child(11 of .so-table-row__cell) {
842
+ translate: var(--so-table-cells-11-shift) 0;
843
+ visibility: var(--so-table-cells-11-hidden);
844
+ }
845
+
846
+ .so-table[data-reorder-preview='column']
847
+ > .so-table__head:has(
848
+ > .so-table__head-row > .so-table-column:nth-child(12 of .so-table-column)[data-shift]
849
+ )
850
+ ~ .so-table__body {
851
+ --so-table-cells-12-shift: var(--so-table-body-shift);
852
+ }
853
+
854
+ .so-table[data-reorder-preview='column']
855
+ > .so-table__head:has(
856
+ > .so-table__head-row
857
+ > .so-table-column:nth-child(12 of .so-table-column)[data-dragging='true']
858
+ )
859
+ ~ .so-table__body {
860
+ --so-table-cells-12-hidden: var(--so-table-body-dragged);
861
+ }
862
+
863
+ .so-table[data-reorder-preview='column']
864
+ > .so-table__body
865
+ > .so-table-row
866
+ > .so-table-row__cell:nth-child(12 of .so-table-row__cell) {
867
+ translate: var(--so-table-cells-12-shift) 0;
868
+ visibility: var(--so-table-cells-12-hidden);
869
+ }
870
+
871
+ .so-table[data-reorder-preview='column']
872
+ > .so-table__head:has(
873
+ > .so-table__head-row > .so-table-column:nth-child(13 of .so-table-column)[data-shift]
874
+ )
875
+ ~ .so-table__body {
876
+ --so-table-cells-13-shift: var(--so-table-body-shift);
877
+ }
878
+
879
+ .so-table[data-reorder-preview='column']
880
+ > .so-table__head:has(
881
+ > .so-table__head-row
882
+ > .so-table-column:nth-child(13 of .so-table-column)[data-dragging='true']
883
+ )
884
+ ~ .so-table__body {
885
+ --so-table-cells-13-hidden: var(--so-table-body-dragged);
886
+ }
887
+
888
+ .so-table[data-reorder-preview='column']
889
+ > .so-table__body
890
+ > .so-table-row
891
+ > .so-table-row__cell:nth-child(13 of .so-table-row__cell) {
892
+ translate: var(--so-table-cells-13-shift) 0;
893
+ visibility: var(--so-table-cells-13-hidden);
894
+ }
895
+
896
+ .so-table[data-reorder-preview='column']
897
+ > .so-table__head:has(
898
+ > .so-table__head-row > .so-table-column:nth-child(14 of .so-table-column)[data-shift]
899
+ )
900
+ ~ .so-table__body {
901
+ --so-table-cells-14-shift: var(--so-table-body-shift);
902
+ }
903
+
904
+ .so-table[data-reorder-preview='column']
905
+ > .so-table__head:has(
906
+ > .so-table__head-row
907
+ > .so-table-column:nth-child(14 of .so-table-column)[data-dragging='true']
908
+ )
909
+ ~ .so-table__body {
910
+ --so-table-cells-14-hidden: var(--so-table-body-dragged);
911
+ }
912
+
913
+ .so-table[data-reorder-preview='column']
914
+ > .so-table__body
915
+ > .so-table-row
916
+ > .so-table-row__cell:nth-child(14 of .so-table-row__cell) {
917
+ translate: var(--so-table-cells-14-shift) 0;
918
+ visibility: var(--so-table-cells-14-hidden);
919
+ }
920
+
921
+ .so-table[data-reorder-preview='column']
922
+ > .so-table__head:has(
923
+ > .so-table__head-row > .so-table-column:nth-child(15 of .so-table-column)[data-shift]
924
+ )
925
+ ~ .so-table__body {
926
+ --so-table-cells-15-shift: var(--so-table-body-shift);
927
+ }
928
+
929
+ .so-table[data-reorder-preview='column']
930
+ > .so-table__head:has(
931
+ > .so-table__head-row
932
+ > .so-table-column:nth-child(15 of .so-table-column)[data-dragging='true']
933
+ )
934
+ ~ .so-table__body {
935
+ --so-table-cells-15-hidden: var(--so-table-body-dragged);
936
+ }
937
+
938
+ .so-table[data-reorder-preview='column']
939
+ > .so-table__body
940
+ > .so-table-row
941
+ > .so-table-row__cell:nth-child(15 of .so-table-row__cell) {
942
+ translate: var(--so-table-cells-15-shift) 0;
943
+ visibility: var(--so-table-cells-15-hidden);
944
+ }
945
+
946
+ .so-table[data-reorder-preview='column']
947
+ > .so-table__head:has(
948
+ > .so-table__head-row > .so-table-column:nth-child(16 of .so-table-column)[data-shift]
949
+ )
950
+ ~ .so-table__body {
951
+ --so-table-cells-16-shift: var(--so-table-body-shift);
952
+ }
953
+
954
+ .so-table[data-reorder-preview='column']
955
+ > .so-table__head:has(
956
+ > .so-table__head-row
957
+ > .so-table-column:nth-child(16 of .so-table-column)[data-dragging='true']
958
+ )
959
+ ~ .so-table__body {
960
+ --so-table-cells-16-hidden: var(--so-table-body-dragged);
961
+ }
962
+
963
+ .so-table[data-reorder-preview='column']
964
+ > .so-table__body
965
+ > .so-table-row
966
+ > .so-table-row__cell:nth-child(16 of .so-table-row__cell) {
967
+ translate: var(--so-table-cells-16-shift) 0;
968
+ visibility: var(--so-table-cells-16-hidden);
969
+ }
970
+
971
+ .so-table[data-reorder-preview='column']
972
+ > .so-table__head:has(
973
+ > .so-table__head-row > .so-table-column:nth-child(17 of .so-table-column)[data-shift]
974
+ )
975
+ ~ .so-table__body {
976
+ --so-table-cells-17-shift: var(--so-table-body-shift);
977
+ }
978
+
979
+ .so-table[data-reorder-preview='column']
980
+ > .so-table__head:has(
981
+ > .so-table__head-row
982
+ > .so-table-column:nth-child(17 of .so-table-column)[data-dragging='true']
983
+ )
984
+ ~ .so-table__body {
985
+ --so-table-cells-17-hidden: var(--so-table-body-dragged);
986
+ }
987
+
988
+ .so-table[data-reorder-preview='column']
989
+ > .so-table__body
990
+ > .so-table-row
991
+ > .so-table-row__cell:nth-child(17 of .so-table-row__cell) {
992
+ translate: var(--so-table-cells-17-shift) 0;
993
+ visibility: var(--so-table-cells-17-hidden);
994
+ }
995
+
996
+ .so-table[data-reorder-preview='column']
997
+ > .so-table__head:has(
998
+ > .so-table__head-row > .so-table-column:nth-child(18 of .so-table-column)[data-shift]
999
+ )
1000
+ ~ .so-table__body {
1001
+ --so-table-cells-18-shift: var(--so-table-body-shift);
1002
+ }
1003
+
1004
+ .so-table[data-reorder-preview='column']
1005
+ > .so-table__head:has(
1006
+ > .so-table__head-row
1007
+ > .so-table-column:nth-child(18 of .so-table-column)[data-dragging='true']
1008
+ )
1009
+ ~ .so-table__body {
1010
+ --so-table-cells-18-hidden: var(--so-table-body-dragged);
1011
+ }
1012
+
1013
+ .so-table[data-reorder-preview='column']
1014
+ > .so-table__body
1015
+ > .so-table-row
1016
+ > .so-table-row__cell:nth-child(18 of .so-table-row__cell) {
1017
+ translate: var(--so-table-cells-18-shift) 0;
1018
+ visibility: var(--so-table-cells-18-hidden);
1019
+ }
1020
+
1021
+ .so-table[data-reorder-preview='column']
1022
+ > .so-table__head:has(
1023
+ > .so-table__head-row > .so-table-column:nth-child(19 of .so-table-column)[data-shift]
1024
+ )
1025
+ ~ .so-table__body {
1026
+ --so-table-cells-19-shift: var(--so-table-body-shift);
1027
+ }
1028
+
1029
+ .so-table[data-reorder-preview='column']
1030
+ > .so-table__head:has(
1031
+ > .so-table__head-row
1032
+ > .so-table-column:nth-child(19 of .so-table-column)[data-dragging='true']
1033
+ )
1034
+ ~ .so-table__body {
1035
+ --so-table-cells-19-hidden: var(--so-table-body-dragged);
1036
+ }
1037
+
1038
+ .so-table[data-reorder-preview='column']
1039
+ > .so-table__body
1040
+ > .so-table-row
1041
+ > .so-table-row__cell:nth-child(19 of .so-table-row__cell) {
1042
+ translate: var(--so-table-cells-19-shift) 0;
1043
+ visibility: var(--so-table-cells-19-hidden);
1044
+ }
1045
+
1046
+ .so-table[data-reorder-preview='column']
1047
+ > .so-table__head:has(
1048
+ > .so-table__head-row > .so-table-column:nth-child(20 of .so-table-column)[data-shift]
1049
+ )
1050
+ ~ .so-table__body {
1051
+ --so-table-cells-20-shift: var(--so-table-body-shift);
1052
+ }
1053
+
1054
+ .so-table[data-reorder-preview='column']
1055
+ > .so-table__head:has(
1056
+ > .so-table__head-row
1057
+ > .so-table-column:nth-child(20 of .so-table-column)[data-dragging='true']
1058
+ )
1059
+ ~ .so-table__body {
1060
+ --so-table-cells-20-hidden: var(--so-table-body-dragged);
1061
+ }
1062
+
1063
+ .so-table[data-reorder-preview='column']
1064
+ > .so-table__body
1065
+ > .so-table-row
1066
+ > .so-table-row__cell:nth-child(20 of .so-table-row__cell) {
1067
+ translate: var(--so-table-cells-20-shift) 0;
1068
+ visibility: var(--so-table-cells-20-hidden);
1069
+ }
1070
+
1071
+ .so-table[data-reorder-preview='column']
1072
+ > .so-table__head:has(
1073
+ > .so-table__head-row > .so-table-column:nth-child(21 of .so-table-column)[data-shift]
1074
+ )
1075
+ ~ .so-table__body {
1076
+ --so-table-cells-21-shift: var(--so-table-body-shift);
1077
+ }
1078
+
1079
+ .so-table[data-reorder-preview='column']
1080
+ > .so-table__head:has(
1081
+ > .so-table__head-row
1082
+ > .so-table-column:nth-child(21 of .so-table-column)[data-dragging='true']
1083
+ )
1084
+ ~ .so-table__body {
1085
+ --so-table-cells-21-hidden: var(--so-table-body-dragged);
1086
+ }
1087
+
1088
+ .so-table[data-reorder-preview='column']
1089
+ > .so-table__body
1090
+ > .so-table-row
1091
+ > .so-table-row__cell:nth-child(21 of .so-table-row__cell) {
1092
+ translate: var(--so-table-cells-21-shift) 0;
1093
+ visibility: var(--so-table-cells-21-hidden);
1094
+ }
1095
+
1096
+ .so-table[data-reorder-preview='column']
1097
+ > .so-table__head:has(
1098
+ > .so-table__head-row > .so-table-column:nth-child(22 of .so-table-column)[data-shift]
1099
+ )
1100
+ ~ .so-table__body {
1101
+ --so-table-cells-22-shift: var(--so-table-body-shift);
1102
+ }
1103
+
1104
+ .so-table[data-reorder-preview='column']
1105
+ > .so-table__head:has(
1106
+ > .so-table__head-row
1107
+ > .so-table-column:nth-child(22 of .so-table-column)[data-dragging='true']
1108
+ )
1109
+ ~ .so-table__body {
1110
+ --so-table-cells-22-hidden: var(--so-table-body-dragged);
1111
+ }
1112
+
1113
+ .so-table[data-reorder-preview='column']
1114
+ > .so-table__body
1115
+ > .so-table-row
1116
+ > .so-table-row__cell:nth-child(22 of .so-table-row__cell) {
1117
+ translate: var(--so-table-cells-22-shift) 0;
1118
+ visibility: var(--so-table-cells-22-hidden);
1119
+ }
1120
+
1121
+ .so-table[data-reorder-preview='column']
1122
+ > .so-table__head:has(
1123
+ > .so-table__head-row > .so-table-column:nth-child(23 of .so-table-column)[data-shift]
1124
+ )
1125
+ ~ .so-table__body {
1126
+ --so-table-cells-23-shift: var(--so-table-body-shift);
1127
+ }
1128
+
1129
+ .so-table[data-reorder-preview='column']
1130
+ > .so-table__head:has(
1131
+ > .so-table__head-row
1132
+ > .so-table-column:nth-child(23 of .so-table-column)[data-dragging='true']
1133
+ )
1134
+ ~ .so-table__body {
1135
+ --so-table-cells-23-hidden: var(--so-table-body-dragged);
1136
+ }
1137
+
1138
+ .so-table[data-reorder-preview='column']
1139
+ > .so-table__body
1140
+ > .so-table-row
1141
+ > .so-table-row__cell:nth-child(23 of .so-table-row__cell) {
1142
+ translate: var(--so-table-cells-23-shift) 0;
1143
+ visibility: var(--so-table-cells-23-hidden);
1144
+ }
1145
+
1146
+ .so-table[data-reorder-preview='column']
1147
+ > .so-table__head:has(
1148
+ > .so-table__head-row > .so-table-column:nth-child(24 of .so-table-column)[data-shift]
1149
+ )
1150
+ ~ .so-table__body {
1151
+ --so-table-cells-24-shift: var(--so-table-body-shift);
1152
+ }
1153
+
1154
+ .so-table[data-reorder-preview='column']
1155
+ > .so-table__head:has(
1156
+ > .so-table__head-row
1157
+ > .so-table-column:nth-child(24 of .so-table-column)[data-dragging='true']
1158
+ )
1159
+ ~ .so-table__body {
1160
+ --so-table-cells-24-hidden: var(--so-table-body-dragged);
1161
+ }
1162
+
1163
+ .so-table[data-reorder-preview='column']
1164
+ > .so-table__body
1165
+ > .so-table-row
1166
+ > .so-table-row__cell:nth-child(24 of .so-table-row__cell) {
1167
+ translate: var(--so-table-cells-24-shift) 0;
1168
+ visibility: var(--so-table-cells-24-hidden);
1169
+ }
1170
+
1171
+ .so-table[data-reorder-preview='column']
1172
+ > .so-table__head:has(
1173
+ > .so-table__head-row > .so-table-column:nth-child(25 of .so-table-column)[data-shift]
1174
+ )
1175
+ ~ .so-table__body {
1176
+ --so-table-cells-25-shift: var(--so-table-body-shift);
1177
+ }
1178
+
1179
+ .so-table[data-reorder-preview='column']
1180
+ > .so-table__head:has(
1181
+ > .so-table__head-row
1182
+ > .so-table-column:nth-child(25 of .so-table-column)[data-dragging='true']
1183
+ )
1184
+ ~ .so-table__body {
1185
+ --so-table-cells-25-hidden: var(--so-table-body-dragged);
1186
+ }
1187
+
1188
+ .so-table[data-reorder-preview='column']
1189
+ > .so-table__body
1190
+ > .so-table-row
1191
+ > .so-table-row__cell:nth-child(25 of .so-table-row__cell) {
1192
+ translate: var(--so-table-cells-25-shift) 0;
1193
+ visibility: var(--so-table-cells-25-hidden);
1194
+ }
1195
+
1196
+ .so-table[data-reorder-preview='column']
1197
+ > .so-table__head:has(
1198
+ > .so-table__head-row > .so-table-column:nth-child(26 of .so-table-column)[data-shift]
1199
+ )
1200
+ ~ .so-table__body {
1201
+ --so-table-cells-26-shift: var(--so-table-body-shift);
1202
+ }
1203
+
1204
+ .so-table[data-reorder-preview='column']
1205
+ > .so-table__head:has(
1206
+ > .so-table__head-row
1207
+ > .so-table-column:nth-child(26 of .so-table-column)[data-dragging='true']
1208
+ )
1209
+ ~ .so-table__body {
1210
+ --so-table-cells-26-hidden: var(--so-table-body-dragged);
1211
+ }
1212
+
1213
+ .so-table[data-reorder-preview='column']
1214
+ > .so-table__body
1215
+ > .so-table-row
1216
+ > .so-table-row__cell:nth-child(26 of .so-table-row__cell) {
1217
+ translate: var(--so-table-cells-26-shift) 0;
1218
+ visibility: var(--so-table-cells-26-hidden);
1219
+ }
1220
+
1221
+ .so-table[data-reorder-preview='column']
1222
+ > .so-table__head:has(
1223
+ > .so-table__head-row > .so-table-column:nth-child(27 of .so-table-column)[data-shift]
1224
+ )
1225
+ ~ .so-table__body {
1226
+ --so-table-cells-27-shift: var(--so-table-body-shift);
1227
+ }
1228
+
1229
+ .so-table[data-reorder-preview='column']
1230
+ > .so-table__head:has(
1231
+ > .so-table__head-row
1232
+ > .so-table-column:nth-child(27 of .so-table-column)[data-dragging='true']
1233
+ )
1234
+ ~ .so-table__body {
1235
+ --so-table-cells-27-hidden: var(--so-table-body-dragged);
1236
+ }
1237
+
1238
+ .so-table[data-reorder-preview='column']
1239
+ > .so-table__body
1240
+ > .so-table-row
1241
+ > .so-table-row__cell:nth-child(27 of .so-table-row__cell) {
1242
+ translate: var(--so-table-cells-27-shift) 0;
1243
+ visibility: var(--so-table-cells-27-hidden);
1244
+ }
1245
+
1246
+ .so-table[data-reorder-preview='column']
1247
+ > .so-table__head:has(
1248
+ > .so-table__head-row > .so-table-column:nth-child(28 of .so-table-column)[data-shift]
1249
+ )
1250
+ ~ .so-table__body {
1251
+ --so-table-cells-28-shift: var(--so-table-body-shift);
1252
+ }
1253
+
1254
+ .so-table[data-reorder-preview='column']
1255
+ > .so-table__head:has(
1256
+ > .so-table__head-row
1257
+ > .so-table-column:nth-child(28 of .so-table-column)[data-dragging='true']
1258
+ )
1259
+ ~ .so-table__body {
1260
+ --so-table-cells-28-hidden: var(--so-table-body-dragged);
1261
+ }
1262
+
1263
+ .so-table[data-reorder-preview='column']
1264
+ > .so-table__body
1265
+ > .so-table-row
1266
+ > .so-table-row__cell:nth-child(28 of .so-table-row__cell) {
1267
+ translate: var(--so-table-cells-28-shift) 0;
1268
+ visibility: var(--so-table-cells-28-hidden);
1269
+ }
1270
+
1271
+ .so-table[data-reorder-preview='column']
1272
+ > .so-table__head:has(
1273
+ > .so-table__head-row > .so-table-column:nth-child(29 of .so-table-column)[data-shift]
1274
+ )
1275
+ ~ .so-table__body {
1276
+ --so-table-cells-29-shift: var(--so-table-body-shift);
1277
+ }
1278
+
1279
+ .so-table[data-reorder-preview='column']
1280
+ > .so-table__head:has(
1281
+ > .so-table__head-row
1282
+ > .so-table-column:nth-child(29 of .so-table-column)[data-dragging='true']
1283
+ )
1284
+ ~ .so-table__body {
1285
+ --so-table-cells-29-hidden: var(--so-table-body-dragged);
1286
+ }
1287
+
1288
+ .so-table[data-reorder-preview='column']
1289
+ > .so-table__body
1290
+ > .so-table-row
1291
+ > .so-table-row__cell:nth-child(29 of .so-table-row__cell) {
1292
+ translate: var(--so-table-cells-29-shift) 0;
1293
+ visibility: var(--so-table-cells-29-hidden);
1294
+ }
1295
+
1296
+ .so-table[data-reorder-preview='column']
1297
+ > .so-table__head:has(
1298
+ > .so-table__head-row > .so-table-column:nth-child(30 of .so-table-column)[data-shift]
1299
+ )
1300
+ ~ .so-table__body {
1301
+ --so-table-cells-30-shift: var(--so-table-body-shift);
1302
+ }
1303
+
1304
+ .so-table[data-reorder-preview='column']
1305
+ > .so-table__head:has(
1306
+ > .so-table__head-row
1307
+ > .so-table-column:nth-child(30 of .so-table-column)[data-dragging='true']
1308
+ )
1309
+ ~ .so-table__body {
1310
+ --so-table-cells-30-hidden: var(--so-table-body-dragged);
1311
+ }
1312
+
1313
+ .so-table[data-reorder-preview='column']
1314
+ > .so-table__body
1315
+ > .so-table-row
1316
+ > .so-table-row__cell:nth-child(30 of .so-table-row__cell) {
1317
+ translate: var(--so-table-cells-30-shift) 0;
1318
+ visibility: var(--so-table-cells-30-hidden);
1319
+ }
1320
+
1321
+ /* While a header is carried — the "grabbing" cursor over the whole table and
1322
+ * no text selection: the pointer leaves its header. The core sets the root flag
1323
+ * (`data-dragging`) for the whole gesture, landing included — as long as the
1324
+ * carried header's mark. The sort button sets its own cursor, and in the
1325
+ * carried header it is overridden. */
1326
+ .so-table[data-dragging='true'] {
1327
+ cursor: grabbing;
1328
+ -webkit-user-select: none;
1329
+ user-select: none;
1330
+ }
1331
+
1332
+ .so-table-column[data-dragging='true'] .so-table-column__sort {
1333
+ cursor: grabbing;
1334
+ }
1335
+
1336
+ /* The selection column is as wide as the checkbox box and the cell padding on
1337
+ * both sides: from the table's edge to the box as far as to the text of the
1338
+ * first column without selection, and from the box to the next column's text as
1339
+ * far as between the texts of neighbouring columns. The box is centered in the
1340
+ * cell, also when its size differs from the table's. */
1341
+ .so-table__select,
1342
+ .so-table-row__select {
1343
+ inline-size: calc(var(--so-table-check) + 2 * var(--so-table-cell-inline));
1344
+ }
1345
+
1346
+ .so-table__select > .so-check-box,
1347
+ .so-table-row__select > .so-check-box {
1348
+ margin-inline: auto;
1349
+ }
1350
+
1351
+ /* A row is as high as the button and the bleeds: never lower, and higher when
1352
+ * a cell's content is.
1353
+ *
1354
+ * A disabled row fades its data cells. The checkbox in the selection cell is
1355
+ * disabled with the row and fades by itself. The line under the row, which the
1356
+ * theme draws, is the row's border, not the cells', and doesn't fade with
1357
+ * them. */
1358
+ .so-table-row {
1359
+ block-size: var(--so-table-row);
1360
+ }
1361
+
1362
+ .so-table-row[data-disabled='true'] > .so-table-row__cell {
1363
+ opacity: var(--so-disabled-opacity);
1364
+ }
1365
+
1366
+ .so-table-row[data-disabled='true'] > .so-table-row__cell > * {
1367
+ --so-disabled-opacity: 1;
1368
+ }
1369
+
1370
+ /* The empty state is a full-width cell with centered text. The padding above
1371
+ * and below is a table row each: a table without rows doesn't collapse into its
1372
+ * head, and the empty band reads as the place for data. */
1373
+ .so-table__empty {
1374
+ text-align: center;
1375
+ padding-block: var(--so-table-row);
1376
+ }
1377
+
1378
+ /* The window (`data-virtual`, a `Virtual` wrapper): the body draws only the
1379
+ * visible rows, and spacers stand in place of the skipped ones. All rows in the
1380
+ * window are of one height — the window counts a row's place from the step, and
1381
+ * a row higher than the step would shift the spacers. So a cell's text is one
1382
+ * line, and what doesn't fit is cut with an ellipsis: a long word doesn't wrap
1383
+ * here.
1384
+ *
1385
+ * A spacer is an empty band as high as the skipped rows: no padding and no
1386
+ * borders, under `aria-hidden`. */
1387
+ .so-table[data-virtual='true'] > .so-table__body > .so-table-row > .so-table-row__cell {
1388
+ overflow: hidden;
1389
+ text-overflow: ellipsis;
1390
+ white-space: nowrap;
1391
+ overflow-wrap: normal;
1392
+ }
1393
+
1394
+ .so-table__filler-row {
1395
+ border-width: 0;
1396
+ }
1397
+
1398
+ .so-table__filler {
1399
+ block-size: var(--so-filler-height, 0);
1400
+ padding: 0;
1401
+ border-width: 0;
1402
+ }
1403
+
1404
+ /* The sticky head (`data-sticky-head`) stands at the top edge of the scroll — a
1405
+ * container's or the page's — and the rows go under it. Without the attribute
1406
+ * the head leaves with the rows.
1407
+ *
1408
+ * The head sticks whole (`thead`), not its cells. A sticky element is a layer
1409
+ * of its own, and cells would make a layer per column: the width handle's
1410
+ * strip, half of which lies on the next header, and the drop mark over the
1411
+ * carried header would go under the neighbour. The head is one layer, and the
1412
+ * raises inside it are the same as without sticking. Its raise holds the head
1413
+ * over the body (above), and sticking only adds the edge to it.
1414
+ *
1415
+ * The offset from the scroll's top edge is the `--so-table-head-offset`
1416
+ * variable, zero without it. A plus is for the app's sticky bar: it is set on
1417
+ * the table or its wrapper that scrolls with the page, not on the document root
1418
+ * — or a nested scroll container would take the same offset. A minus is for a
1419
+ * container's top padding: the browser sticks below the padding, and the rows
1420
+ * leaving would show through the padding over the head. A table whose window
1421
+ * lies right in the body of Dialog and Drawer or in the content of Popover gets
1422
+ * it from them — a context on the window (`modal.css`,
1423
+ * `components/popover.css`) the table inherits.
1424
+ *
1425
+ * The backing is the surface of the place (`--so-place-surface`, `place.css`):
1426
+ * the head covers the rows, and the table has no surface of its own. The place
1427
+ * declares the surface — the theme's page, a panel, the app's card. The row
1428
+ * group's background alone isn't enough: the browser puts it into each cell's
1429
+ * area — a header's rounded corners cut it, in WebKit the rows showed through
1430
+ * under the head's outer corners, and the place a carried header left has none
1431
+ * of it. So the head's background lies once more under the whole head — the
1432
+ * head's `::after` with a negative `z-index`: it goes into the head's layer,
1433
+ * under the headers, and their veils lie over it. It travels with no header,
1434
+ * and the place a carried header (`data-dragging`) left stays covered, at the
1435
+ * head's outer corners too. Its color is the head's background itself
1436
+ * (`inherit`): a theme that takes the head out of forced colors and paints it
1437
+ * the system color of the page (oren does) gets the backing in that color too,
1438
+ * while the variable would keep the theme's color there.
1439
+ *
1440
+ * The backing's size is the head's height, not its insets: in WebKit a row
1441
+ * group is no containing block for an absolute box, the table is, and a backing
1442
+ * stretched by its insets covered the whole table, the body included. The
1443
+ * height is the one the layout plugin writes (`--so-table-head-height`, below at
1444
+ * focus); until there is one — the head row, and a head wrapped into several
1445
+ * rows is backed by its first one. The theme draws the line under the head in
1446
+ * the head's background, and the backing would cover it too — so it stops
1447
+ * short of the head's bottom by the line's thickness (`--so-table-head-border`;
1448
+ * zero without a theme). `::after`, not `::before`: the engine wraps a row
1449
+ * group's pseudo-element into an anonymous row, and before the head row it
1450
+ * becomes the first row of the fixed layout — Chromium then lays the table out
1451
+ * zero wide. The selection header is positioned, like the column headers:
1452
+ * WebKit paints a cell without a layer of its own together with the row group's
1453
+ * background, under the backing. A theme whose head background doesn't hold in
1454
+ * mid-transition of a header's color — Chromium loses it there — also paints
1455
+ * the backing with an inset shadow of the head (oren does, together with the
1456
+ * line under the head).
1457
+ *
1458
+ * Focus below the head: a grid cell focus went up to (↑, PageUp) and a field in
1459
+ * a cell after Shift+Tab stand below the head, not behind it. The scroll margin
1460
+ * to focus (`scroll-margin-block-start`) is on everything in the body: the
1461
+ * browser scrolls to the focused node itself, and it may lie deep in a cell.
1462
+ * The margin is the head's height and the offset when it is a plus: a minus
1463
+ * only brings the head back to the edge the browser measures from. The cell's
1464
+ * content adds the cell's bleed to it: a control's focus ring lies outside, and
1465
+ * a grid cell's inside. The layout plugin writes the height
1466
+ * (`--so-table-head-height`): labels wrap, a header slot may hold anything, and
1467
+ * the head may be higher than the head row. Until there is a height — the head
1468
+ * row.
1469
+ *
1470
+ * The window of a table with a sticky head doesn't scroll, even when the
1471
+ * columns are wider than the place (above, at the window). Sideways scrolling
1472
+ * (`overflow-x`) scrolls the height as well: the window would become the
1473
+ * nearest scroll for the head, with nothing to scroll in height — and the head
1474
+ * wouldn't stick. So the table goes past the window, and the consumer's
1475
+ * container or the page scrolls it sideways. The window may still clip a table
1476
+ * that fits (`clip`) with a sticky head: that doesn't make it a scroll
1477
+ * container. */
1478
+ .so-table[data-sticky-head='true'] > .so-table__head {
1479
+ inset-block-start: var(--so-table-head-offset, 0px);
1480
+ background-color: var(--so-place-surface);
1481
+ }
1482
+
1483
+ .so-table[data-sticky-head='true'] > .so-table__head > .so-table__head-row > .so-table__select {
1484
+ position: relative;
1485
+ }
1486
+
1487
+ .so-table[data-sticky-head='true'] > .so-table__head::after {
1488
+ content: '';
1489
+ position: absolute;
1490
+ inset-inline: 0;
1491
+ inset-block-start: 0;
1492
+ block-size: calc(
1493
+ var(--so-table-head-height, var(--so-table-head-row)) - var(--so-table-head-border, 0px)
1494
+ );
1495
+ z-index: -1;
1496
+ background-color: inherit;
1497
+ }
1498
+
1499
+ /* A cell — the grid's focus ring is inside it */
1500
+ .so-table[data-sticky-head='true'] > .so-table__body > * > * {
1501
+ scroll-margin-block-start: calc(
1502
+ max(0px, var(--so-table-head-offset, 0px)) +
1503
+ var(--so-table-head-height, var(--so-table-head-row))
1504
+ );
1505
+ }
1506
+
1507
+ /* A cell's content — the focus ring is outside, on the cell's bleed */
1508
+ .so-table[data-sticky-head='true'] > .so-table__body > * > * * {
1509
+ scroll-margin-block-start: calc(
1510
+ max(0px, var(--so-table-head-offset, 0px)) +
1511
+ var(--so-table-head-height, var(--so-table-head-row)) + var(--so-table-bleed)
1512
+ );
1513
+ }