@svgrid/grid 1.1.0 → 1.1.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 (117) hide show
  1. package/dist/GridMenus.svelte +205 -127
  2. package/dist/SvGrid.controller.svelte.d.ts +44 -1
  3. package/dist/SvGrid.controller.svelte.js +223 -38
  4. package/dist/SvGrid.css +2012 -1709
  5. package/dist/SvGrid.helpers.test.d.ts +1 -0
  6. package/dist/SvGrid.helpers.test.js +298 -0
  7. package/dist/SvGrid.svelte +2346 -2043
  8. package/dist/SvGrid.types.d.ts +91 -4
  9. package/dist/aligned-grids.d.ts +6 -0
  10. package/dist/aligned-grids.js +84 -0
  11. package/dist/aligned-grids.test.d.ts +1 -0
  12. package/dist/aligned-grids.test.js +75 -0
  13. package/dist/build-api.coverage.test.d.ts +20 -0
  14. package/dist/build-api.coverage.test.js +505 -0
  15. package/dist/build-api.js +59 -29
  16. package/dist/cell-render.test.d.ts +1 -0
  17. package/dist/cell-render.test.js +338 -0
  18. package/dist/chart-export.test.d.ts +1 -0
  19. package/dist/chart-export.test.js +302 -0
  20. package/dist/chart.coverage.test.d.ts +1 -0
  21. package/dist/chart.coverage.test.js +748 -0
  22. package/dist/clipboard.js +88 -24
  23. package/dist/clipboard.test.d.ts +1 -0
  24. package/dist/clipboard.test.js +700 -0
  25. package/dist/collaboration.coverage.test.d.ts +1 -0
  26. package/dist/collaboration.coverage.test.js +200 -0
  27. package/dist/column-groups.d.ts +19 -0
  28. package/dist/column-groups.js +62 -0
  29. package/dist/column-groups.test.d.ts +1 -0
  30. package/dist/column-groups.test.js +56 -0
  31. package/dist/column-types.d.ts +10 -0
  32. package/dist/column-types.js +63 -0
  33. package/dist/column-types.test.d.ts +1 -0
  34. package/dist/column-types.test.js +62 -0
  35. package/dist/columns.test.d.ts +1 -0
  36. package/dist/columns.test.js +625 -0
  37. package/dist/core.d.ts +85 -0
  38. package/dist/editing.d.ts +7 -0
  39. package/dist/editing.js +191 -5
  40. package/dist/editing.test.d.ts +1 -0
  41. package/dist/editing.test.js +732 -0
  42. package/dist/editors/cell-editors.coverage.test.d.ts +1 -0
  43. package/dist/editors/cell-editors.coverage.test.js +139 -0
  44. package/dist/facet-buckets.test.d.ts +1 -0
  45. package/dist/facet-buckets.test.js +296 -0
  46. package/dist/filter-operators.test.d.ts +1 -0
  47. package/dist/filter-operators.test.js +135 -0
  48. package/dist/hyperformula-adapter.test.d.ts +1 -0
  49. package/dist/hyperformula-adapter.test.js +205 -0
  50. package/dist/index.d.ts +5 -2
  51. package/dist/index.js +4 -1
  52. package/dist/keyboard-handlers.coverage.test.d.ts +1 -0
  53. package/dist/keyboard-handlers.coverage.test.js +495 -0
  54. package/dist/keyboard-handlers.js +7 -2
  55. package/dist/menus.js +1 -0
  56. package/dist/menus.test.d.ts +1 -0
  57. package/dist/menus.test.js +560 -0
  58. package/dist/named-views.coverage.test.d.ts +1 -0
  59. package/dist/named-views.coverage.test.js +180 -0
  60. package/dist/row-drag.d.ts +49 -0
  61. package/dist/row-drag.js +221 -0
  62. package/dist/row-drag.test.d.ts +1 -0
  63. package/dist/row-drag.test.js +142 -0
  64. package/dist/row-resize.test.d.ts +1 -0
  65. package/dist/row-resize.test.js +329 -0
  66. package/dist/scroll-sync.js +3 -0
  67. package/dist/scroll-sync.test.d.ts +1 -0
  68. package/dist/scroll-sync.test.js +290 -0
  69. package/dist/selection.d.ts +7 -1
  70. package/dist/selection.js +76 -36
  71. package/dist/selection.multi-range.test.d.ts +1 -0
  72. package/dist/selection.multi-range.test.js +55 -0
  73. package/dist/selection.test.d.ts +1 -0
  74. package/dist/selection.test.js +647 -0
  75. package/dist/server-data-source.coverage.test.d.ts +1 -0
  76. package/dist/server-data-source.coverage.test.js +154 -0
  77. package/dist/spreadsheet.d.ts +30 -0
  78. package/dist/spreadsheet.js +48 -0
  79. package/dist/spreadsheet.test.d.ts +1 -0
  80. package/dist/spreadsheet.test.js +446 -0
  81. package/dist/sv-grid-scrollbar.js +13 -1
  82. package/dist/svgrid-wrapper.types.d.ts +19 -0
  83. package/dist/svgrid.behavior.test.js +20 -0
  84. package/dist/svgrid.interaction.test.js +31 -0
  85. package/dist/svgrid.new-features.wrapper.test.js +34 -2
  86. package/dist/test-setup.js +9 -3
  87. package/dist/virtualization/scroll-scaling.d.ts +17 -0
  88. package/dist/virtualization/scroll-scaling.js +35 -0
  89. package/dist/virtualization/scroll-scaling.test.js +42 -1
  90. package/package.json +2 -1
  91. package/src/GridMenus.svelte +205 -127
  92. package/src/SvGrid.controller.svelte.ts +2352 -2195
  93. package/src/SvGrid.css +2012 -1747
  94. package/src/SvGrid.svelte +2346 -2047
  95. package/src/SvGrid.types.ts +537 -456
  96. package/src/aligned-grids.test.ts +80 -0
  97. package/src/aligned-grids.ts +87 -0
  98. package/src/build-api.ts +683 -663
  99. package/src/clipboard.test.ts +49 -0
  100. package/src/clipboard.ts +51 -23
  101. package/src/column-groups.test.ts +59 -0
  102. package/src/column-groups.ts +80 -0
  103. package/src/column-types.test.ts +68 -0
  104. package/src/column-types.ts +82 -0
  105. package/src/core.ts +78 -0
  106. package/src/editing.ts +669 -513
  107. package/src/index.ts +12 -0
  108. package/src/menus.ts +1 -0
  109. package/src/row-drag.test.ts +168 -0
  110. package/src/row-drag.ts +255 -0
  111. package/src/scroll-sync.ts +2 -0
  112. package/src/selection.multi-range.test.ts +61 -0
  113. package/src/selection.ts +71 -37
  114. package/src/spreadsheet.test.ts +489 -445
  115. package/src/spreadsheet.ts +304 -246
  116. package/src/svgrid-wrapper.types.ts +19 -0
  117. package/src/svgrid.new-features.wrapper.test.ts +2 -2
package/dist/SvGrid.css CHANGED
@@ -1,1709 +1,2012 @@
1
- /*
2
- * SvGrid component styles. Extracted from SvGrid.svelte's <style> block so the
3
- * component file stays focused on logic + markup. These are plain (global) CSS
4
- * rules - every selector is a unique .sv-grid-* class that maps to a real class
5
- * on the grid's elements, so behaviour is identical to the former scoped block.
6
- * Imported for its side effect by SvGrid.svelte; the WC build inlines it and
7
- * app bundlers include it via the import.
8
- */
9
- /* Root wrapper: holds the shell + pager + any overlays (find, tooltips
10
- * pinned to the root). Position-relative so absolutely-positioned
11
- * overlays (the find-in-grid toolbar) anchor to the grid, not the
12
- * viewport. */
13
- .sv-grid-root {
14
- position: relative;
15
- }
16
- .sv-grid-sr-only {
17
- position: absolute;
18
- width: 1px;
19
- height: 1px;
20
- overflow: hidden;
21
- clip: rect(0 0 0 0);
22
- white-space: nowrap;
23
- }
24
- .sv-grid-loading-overlay {
25
- position: absolute;
26
- inset: 0;
27
- z-index: 18;
28
- pointer-events: auto;
29
- background: color-mix(in srgb, var(--sg-bg, #fff) 35%, transparent);
30
- }
31
- /* Indeterminate top progress bar - the "something is happening" signal
32
- that keeps the current rows visible underneath (no flash). */
33
- .sv-grid-loading-bar {
34
- position: absolute;
35
- top: 0;
36
- left: 0;
37
- right: 0;
38
- height: 3px;
39
- overflow: hidden;
40
- background: color-mix(in srgb, var(--sg-accent, #2563eb) 18%, transparent);
41
- }
42
- .sv-grid-loading-bar::before {
43
- content: '';
44
- position: absolute;
45
- inset: 0;
46
- width: 40%;
47
- background: var(--sg-accent, #2563eb);
48
- animation: sv-grid-loading-slide 1.1s ease-in-out infinite;
49
- }
50
- @keyframes sv-grid-loading-slide {
51
- 0% { transform: translateX(-100%); }
52
- 100% { transform: translateX(350%); }
53
- }
54
- /* First-load skeleton: shimmer placeholder rows under the header. */
55
- .sv-grid-skeleton {
56
- position: absolute;
57
- top: var(--sg-thead-h, 40px);
58
- left: 0;
59
- right: 0;
60
- padding: 0;
61
- overflow: hidden;
62
- }
63
- .sv-grid-skeleton-row {
64
- display: flex;
65
- height: var(--sg-pinned-row-h, 36px);
66
- align-items: center;
67
- border-bottom: 1px solid var(--sg-border, #e2e8f0);
68
- }
69
- .sv-grid-skeleton-cell {
70
- flex-shrink: 0;
71
- padding: 0 12px;
72
- box-sizing: border-box;
73
- }
74
- .sv-grid-skeleton-bar {
75
- display: block;
76
- height: 10px;
77
- width: 70%;
78
- border-radius: 4px;
79
- background: linear-gradient(
80
- 90deg,
81
- color-mix(in srgb, var(--sg-muted, #94a3b8) 18%, transparent) 25%,
82
- color-mix(in srgb, var(--sg-muted, #94a3b8) 32%, transparent) 50%,
83
- color-mix(in srgb, var(--sg-muted, #94a3b8) 18%, transparent) 75%
84
- );
85
- background-size: 200% 100%;
86
- animation: sv-grid-shimmer 1.3s linear infinite;
87
- }
88
- @keyframes sv-grid-shimmer {
89
- 0% { background-position: 200% 0; }
90
- 100% { background-position: -200% 0; }
91
- }
92
-
93
- .sv-grid-tool-panel-toggle {
94
- position: absolute;
95
- top: 6px;
96
- right: 6px;
97
- z-index: 21;
98
- display: inline-flex;
99
- align-items: center;
100
- justify-content: center;
101
- width: 30px;
102
- height: 30px;
103
- border: 1px solid var(--sg-border, #e2e8f0);
104
- border-radius: var(--sg-radius, 6px);
105
- background: var(--sg-bg, #fff);
106
- color: var(--sg-muted, #64748b);
107
- cursor: pointer;
108
- }
109
- .sv-grid-tool-panel-toggle:hover,
110
- .sv-grid-tool-panel-toggle.is-open {
111
- color: var(--sg-accent, #2563eb);
112
- border-color: var(--sg-accent, #2563eb);
113
- }
114
- .sv-grid-tool-panel {
115
- position: absolute;
116
- top: 0;
117
- right: 0;
118
- bottom: 0;
119
- z-index: 20;
120
- width: 250px;
121
- display: flex;
122
- flex-direction: column;
123
- background: var(--sg-bg, #fff);
124
- border-left: 1px solid var(--sg-border, #e2e8f0);
125
- box-shadow: -8px 0 24px rgba(0, 0, 0, 0.12);
126
- }
127
- .sv-grid-tool-panel-head {
128
- display: flex;
129
- align-items: center;
130
- justify-content: space-between;
131
- padding: 8px 12px;
132
- font-size: 12px;
133
- font-weight: 700;
134
- text-transform: uppercase;
135
- letter-spacing: 0.05em;
136
- color: var(--sg-muted, #64748b);
137
- border-bottom: 1px solid var(--sg-border, #e2e8f0);
138
- }
139
- .sv-grid-tool-panel-close {
140
- border: 0;
141
- background: transparent;
142
- color: var(--sg-muted, #64748b);
143
- font-size: 14px;
144
- cursor: pointer;
145
- }
146
- .sv-grid-tool-panel-list {
147
- list-style: none;
148
- margin: 0;
149
- padding: 6px;
150
- overflow-y: auto;
151
- flex: 1;
152
- }
153
- .sv-grid-tool-panel-item {
154
- display: flex;
155
- align-items: center;
156
- justify-content: space-between;
157
- gap: 6px;
158
- padding: 3px 6px;
159
- border-radius: 5px;
160
- }
161
- .sv-grid-tool-panel-item:hover {
162
- background: var(--sg-row-hover-bg, rgba(148, 163, 184, 0.1));
163
- }
164
- .sv-grid-tool-panel-vis {
165
- display: flex;
166
- align-items: center;
167
- gap: 8px;
168
- flex: 1;
169
- min-width: 0;
170
- font-size: 13px;
171
- color: var(--sg-fg, #0f172a);
172
- cursor: pointer;
173
- }
174
- .sv-grid-tool-panel-vis input {
175
- accent-color: var(--sg-accent, #2563eb);
176
- }
177
- .sv-grid-tool-panel-name {
178
- overflow: hidden;
179
- text-overflow: ellipsis;
180
- white-space: nowrap;
181
- }
182
- .sv-grid-tool-panel-actions {
183
- display: inline-flex;
184
- gap: 2px;
185
- flex-shrink: 0;
186
- }
187
- .sv-grid-tool-panel-btn {
188
- width: 22px;
189
- height: 22px;
190
- border: 0;
191
- border-radius: 4px;
192
- background: transparent;
193
- color: var(--sg-muted, #64748b);
194
- font-size: 13px;
195
- cursor: pointer;
196
- }
197
- .sv-grid-tool-panel-btn:hover:not(:disabled) {
198
- background: var(--sg-row-hover-bg, rgba(148, 163, 184, 0.18));
199
- color: var(--sg-fg, #0f172a);
200
- }
201
- .sv-grid-tool-panel-btn.is-active {
202
- color: var(--sg-accent, #2563eb);
203
- }
204
- .sv-grid-tool-panel-btn:disabled {
205
- opacity: 0.35;
206
- cursor: default;
207
- }
208
- /* Root wrapper fill mode: only takes effect when consumer passes
209
- * containerHeight='100%'. The shell becomes a flex item that expands
210
- * while the pager keeps its natural height. */
211
- .sv-grid-root-fill {
212
- display: flex;
213
- flex-direction: column;
214
- height: 100%;
215
- min-height: 0;
216
- }
217
- .sv-grid-root-fill > .sv-grid-shell {
218
- flex: 1 1 0;
219
- min-height: 0;
220
- height: auto !important;
221
- }
222
-
223
- .sv-grid-shell {
224
- position: relative;
225
- border-bottom: 1px solid var(--sg-border, #cbd5e1);
226
- }
227
-
228
- .sv-grid-table {
229
- table-layout: fixed;
230
- border-collapse: separate;
231
- border-spacing: 0;
232
- }
233
-
234
- .sv-grid-table:focus {
235
- outline: none;
236
- }
237
-
238
- .sv-grid-column,
239
- .sv-grid-cell {
240
- box-sizing: border-box;
241
- overflow: hidden;
242
- text-overflow: ellipsis;
243
- white-space: nowrap;
244
- background: var(--sg-bg, #fff);
245
- color: var(--sg-fg, #0f172a);
246
- user-select: none;
247
- }
248
- /* Only the active cell + the cell hosting the fill handle become a
249
- positioning context (so the absolutely-positioned handle anchors
250
- to the cell box). Adding `position: relative` to EVERY cell paints
251
- each one as its own layout box and noticeably slows vertical
252
- scroll on dense grids - keep it scoped. Sticky / pinned cells are
253
- already positioning contexts via `position: sticky`. */
254
- .sv-grid-cell-active,
255
- .sv-grid-cell-has-fill-handle {
256
- position: relative;
257
- }
258
- /* The cell that owns the fill handle gets overflow: visible so the
259
- handle isn't clipped - chip cells in particular pack pills right up
260
- to the cell edges, and with overflow: hidden the handle's hover
261
- scale-up + the handle itself could be partially eaten by them. */
262
- .sv-grid-cell-has-fill-handle {
263
- overflow: visible;
264
- }
265
-
266
- /* Full-width expandable detail row (props.isDetailRow / renderDetailRow).
267
- Overrides the normal single-line cell clamps so the panel spans every
268
- column and grows to its natural height. Pair with virtualization={false}
269
- so the variable height isn't fought by the fixed-row-height virtualizer.
270
- These rules sit AFTER `.sv-grid-cell` so they win at equal specificity. */
271
- .sv-grid-detail-row {
272
- height: auto !important;
273
- }
274
- .sv-grid-detail-cell {
275
- overflow: visible;
276
- text-overflow: clip;
277
- white-space: normal;
278
- height: auto;
279
- padding: 0;
280
- background: var(--sg-bg, #fff);
281
- user-select: text;
282
- }
283
-
284
- /* Pinned columns read as "frozen" via three layered cues:
285
- 1. A distinct tint (slightly stronger than the header) so the
286
- pinned strip is visible even before you scroll.
287
- 2. A solid 1px divider on the inside edge (toward the scrollable
288
- middle) and a soft drop shadow extending into the scroll area.
289
- 3. A thin accent stripe on the outermost edge that turns on when
290
- the grid scrolls past the pin - a familiar Excel-style cue.
291
- The shadow direction flips based on which side is pinned so it
292
- always points toward the scrollable middle. */
293
- .sv-grid-cell[data-pinned="left"],
294
- .sv-grid-column[data-pinned="left"] {
295
- background: var(--sg-pinned-bg, color-mix(in oklab, var(--sg-header-bg, #f1f5f9) 70%, var(--sg-accent, #2563eb) 8%));
296
- box-shadow:
297
- inset -1px 0 0 var(--sg-pinned-divider, var(--sg-border, #cbd5e1)),
298
- 8px 0 12px -6px rgba(15, 23, 42, 0.22);
299
- }
300
- .sv-grid-cell[data-pinned="right"],
301
- .sv-grid-column[data-pinned="right"] {
302
- background: var(--sg-pinned-bg, color-mix(in oklab, var(--sg-header-bg, #f1f5f9) 70%, var(--sg-accent, #2563eb) 8%));
303
- box-shadow:
304
- inset 1px 0 0 var(--sg-pinned-divider, var(--sg-border, #cbd5e1)),
305
- -8px 0 12px -6px rgba(15, 23, 42, 0.22);
306
- }
307
- /* Header row pinned cells get a slightly darker variant + bolder font
308
- so the frozen header itself reads as part of the grid chrome. */
309
- .sv-grid-head .sv-grid-column[data-pinned="left"],
310
- .sv-grid-head .sv-grid-column[data-pinned="right"] {
311
- background: var(--sg-pinned-header-bg, color-mix(in oklab, var(--sg-header-bg, #f1f5f9) 60%, var(--sg-accent, #2563eb) 14%));
312
- font-weight: 600;
313
- }
314
- /* Zebra rows: keep the pinned tint visible (don't let the row-alt
315
- background bleed through) by re-painting the pinned cells. */
316
- .sv-grid-row-alt > .sv-grid-cell[data-pinned] {
317
- background: var(--sg-pinned-bg, color-mix(in oklab, var(--sg-header-bg, #f1f5f9) 70%, var(--sg-accent, #2563eb) 8%));
318
- }
319
- /* When the row is selected, keep the pinned tint but layer the
320
- selection color over it so the row still reads as selected. */
321
- .sv-grid-row-selected > .sv-grid-cell[data-pinned] {
322
- background:
323
- linear-gradient(
324
- color-mix(in srgb, var(--sg-selection-bg, #dbeafe) 65%, transparent),
325
- color-mix(in srgb, var(--sg-selection-bg, #dbeafe) 65%, transparent)
326
- ),
327
- var(--sg-pinned-bg, color-mix(in oklab, var(--sg-header-bg, #f1f5f9) 70%, var(--sg-accent, #2563eb) 8%));
328
- }
329
- /* Hovered row keeps the pinned tint distinguishable from the hover. */
330
- .sv-grid-row:hover > .sv-grid-cell[data-pinned] {
331
- background:
332
- linear-gradient(
333
- color-mix(in srgb, var(--sg-row-hover-bg, #eef2ff) 55%, transparent),
334
- color-mix(in srgb, var(--sg-row-hover-bg, #eef2ff) 55%, transparent)
335
- ),
336
- var(--sg-pinned-bg, color-mix(in oklab, var(--sg-header-bg, #f1f5f9) 70%, var(--sg-accent, #2563eb) 8%));
337
- }
338
-
339
- .sv-grid-column {
340
- position: relative;
341
- }
342
-
343
- /* Column alignment via data-align attribute (see getColumnAlign helper).
344
- * For body cells text-align is enough; for headers the label lives inside
345
- * a flex container so we adjust justify-content on the sort button.
346
- * Padding is biased to the aligned side: left-aligned text gets a 7px
347
- * left gap; right-aligned numbers/dates get a 7px right gap. */
348
- .sv-grid-cell[data-align="left"],
349
- .sv-grid-column[data-align="left"] {
350
- padding-left: var(--sg-cell-px, 7px);
351
- }
352
- .sv-grid-cell[data-align="right"] {
353
- text-align: right;
354
- padding-right: var(--sg-cell-px, 7px);
355
- }
356
- .sv-grid-cell[data-align="center"] {
357
- text-align: center;
358
- }
359
- .sv-grid-column[data-align="right"] {
360
- padding-right: var(--sg-cell-px, 7px);
361
- }
362
- .sv-grid-column[data-align="right"] .sv-grid-header-sort {
363
- justify-content: flex-end;
364
- }
365
- .sv-grid-column[data-align="center"] .sv-grid-header-sort {
366
- justify-content: center;
367
- }
368
-
369
- .sv-grid-resize-handle {
370
- position: absolute;
371
- top: 0;
372
- right: 0;
373
- width: 5px;
374
- height: 100%;
375
- cursor: col-resize;
376
- user-select: none;
377
- z-index: 2;
378
- transition: background-color 100ms ease;
379
- }
380
-
381
- .sv-grid-resize-handle:hover,
382
- .sv-grid-resize-handle.is-resizing {
383
- background: rgba(11, 99, 243, 0.3);
384
- }
385
-
386
- /* Row resize handle (counterpart of the column resize handle). Lives
387
- at the bottom of the row-header gutter cell; the consumer opts in
388
- by attaching the `rowResize` action and setting
389
- `cellClass: 'sv-row-gutter'` on the row-header column. */
390
- .sv-grid-row-resize-handle {
391
- /* Geometry + interactivity are also set inline by the rowResize
392
- action so the strip works even if this stylesheet hasn't been
393
- parsed yet. This rule only owns the hover/active tint. */
394
- transition: background-color 100ms ease;
395
- }
396
- .sv-grid-row-resize-handle:hover,
397
- .sv-grid-row-resize-handle.is-resizing {
398
- background: rgba(11, 99, 243, 0.3);
399
- }
400
-
401
- .sv-grid-head {
402
- position: sticky;
403
- top: 0;
404
- /* Above pinned body cells (z-index 30 via cellPinStyle). Without
405
- this, when a row scrolls partially behind the sticky header,
406
- its pinned cells (left + right) paint OVER the header while the
407
- middle non-pinned cells are correctly hidden behind it, leaving
408
- "floating" symbol / pnl cells at the top of the viewport. */
409
- z-index: 35;
410
- background: var(--sg-header-bg, #f5f7fb);
411
- }
412
-
413
- /* Header drag-to-reorder. The `draggable` attribute is only set
414
- * when `enableColumnReorder` is true, so the cursor and drop
415
- * indicators only show in that mode. */
416
- .sv-grid-column[draggable="true"] { cursor: grab; }
417
- .sv-grid-column[draggable="true"]:active { cursor: grabbing; }
418
- .sv-grid-column.is-dragging { opacity: 0.55; }
419
- .sv-grid-column.is-drag-target-before,
420
- .sv-grid-column.is-drag-target-after {
421
- position: relative;
422
- }
423
- .sv-grid-column.is-drag-target-before::before,
424
- .sv-grid-column.is-drag-target-after::after {
425
- content: "";
426
- position: absolute; top: 4px; bottom: 4px; width: 3px;
427
- background: linear-gradient(180deg, #6366f1, #8b5cf6);
428
- border-radius: 2px;
429
- box-shadow: 0 0 6px #6366f1;
430
- pointer-events: none;
431
- z-index: 7;
432
- animation: sg-drop-pulse 700ms ease-in-out infinite alternate;
433
- }
434
- .sv-grid-column.is-drag-target-before::before { left: -2px; }
435
- .sv-grid-column.is-drag-target-after::after { right: -2px; }
436
- @keyframes sg-drop-pulse { from { opacity: 0.55; } to { opacity: 1; } }
437
-
438
- /* Pinned rows (top / bottom). Each cell sticks individually so the
439
- * row tracks scroll on every browser. Top rows stick below the sticky
440
- * thead via --sg-thead-h; bottom rows stick to the bottom of the
441
- * scroll container. Tinted background distinguishes them from
442
- * regular rows. */
443
- .sv-grid-pinned-row td {
444
- position: sticky;
445
- z-index: 4;
446
- background: var(--sg-pinned-bg, color-mix(in oklab, #6366f1 4%, #ffffff));
447
- border-bottom: 1px solid var(--sg-pinned-border, color-mix(in oklab, #6366f1 24%, transparent));
448
- font-weight: 600;
449
- }
450
- .sv-grid-pinned-row-top td { top: var(--sg-thead-h, 36px); }
451
- .sv-grid-pinned-row-bottom td { bottom: 0; border-top: 1px solid var(--sg-pinned-border, color-mix(in oklab, #6366f1 24%, transparent)); }
452
- /* Stack multiple pinned-top rows: row 1 stops at thead, row 2 stops
453
- * at thead + 36px, etc. The component itself sets --sg-pinned-top-h
454
- * to the current pinned-top stack height via $effect. */
455
- .sv-grid-pinned-row-top[data-pinned-index="1"] td { top: calc(var(--sg-thead-h, 36px) + var(--sg-pinned-row-h, 32px)); }
456
- .sv-grid-pinned-row-top[data-pinned-index="2"] td { top: calc(var(--sg-thead-h, 36px) + var(--sg-pinned-row-h, 32px) * 2); }
457
- .sv-grid-pinned-row-top[data-pinned-index="3"] td { top: calc(var(--sg-thead-h, 36px) + var(--sg-pinned-row-h, 32px) * 3); }
458
- .sv-grid-pinned-row-bottom[data-pinned-index="1"] td { bottom: var(--sg-pinned-row-h, 32px); }
459
- .sv-grid-pinned-row-bottom[data-pinned-index="2"] td { bottom: calc(var(--sg-pinned-row-h, 32px) * 2); }
460
- .sv-grid-pinned-row-bottom[data-pinned-index="3"] td { bottom: calc(var(--sg-pinned-row-h, 32px) * 3); }
461
- /* Sticky-left columns keep their left position; just override their
462
- * z so they sit above non-pinned pinned-row cells. */
463
- .sv-grid-pinned-row td[data-pinned] { z-index: 5; }
464
-
465
- /* Group header rows for multi-level (pivot-style) headers. Sits above
466
- * the standard leaf header row when the column tree has nesting. */
467
- .sv-grid-group-header-row {
468
- background: var(--sg-header-bg, #eef2f8);
469
- }
470
- .sv-grid-group-header-cell {
471
- box-sizing: border-box;
472
- border-bottom: 1px solid var(--sg-border, #d8dee9);
473
- border-right: 1px solid var(--sg-border, #e2e8f0);
474
- text-align: center;
475
- padding: 4px 8px;
476
- font-weight: 700;
477
- font-size: 11.5px;
478
- color: var(--sg-header-fg, #475569);
479
- text-transform: uppercase;
480
- letter-spacing: 0.04em;
481
- }
482
- .sv-grid-group-header-placeholder {
483
- background: transparent;
484
- border-right-color: transparent;
485
- }
486
- .sv-grid-group-header-label {
487
- display: inline-block;
488
- max-width: 100%;
489
- overflow: hidden;
490
- text-overflow: ellipsis;
491
- white-space: nowrap;
492
- }
493
-
494
- .sv-grid-foot {
495
- position: sticky;
496
- bottom: 0;
497
- z-index: 6;
498
- background: var(--sg-header-bg, #f5f7fb);
499
- }
500
-
501
- .sv-grid-container {
502
- scrollbar-width: auto;
503
- -ms-overflow-style: auto;
504
- /* Trap wheel scroll inside the grid. Without this, hitting the top
505
- * or bottom edge of the grid lets the wheel event chain to the
506
- * outer page (or any scrollable ancestor), so what feels like one
507
- * wheel notch keeps scrolling something else - the "starts and
508
- * doesn't stop" symptom. `contain` cancels the chained scroll
509
- * but still bounces normally inside the grid. */
510
- overscroll-behavior: contain;
511
- /* Disable scroll anchoring. The virtualizer changes the top-spacer
512
- * height every time the visible row window slides - modern browsers
513
- * (default `overflow-anchor: auto`) react by quietly adjusting
514
- * `scrollTop` to keep the visible content stable. That adjustment
515
- * fires another `scroll` event, which fires `onBodyScroll`, which
516
- * re-runs the virtualizer, which shifts the spacer again, which
517
- * adjusts `scrollTop` again - a self-sustaining loop that
518
- * presents as "the grid keeps scrolling after the wheel stops."
519
- * Turning anchoring off lets the virtualizer own the scroll
520
- * position outright. */
521
- overflow-anchor: none;
522
- /* Tell the browser that the sticky thead occupies the top
523
- `--sg-thead-h` pixels of the scroll container. `scrollIntoView`
524
- and friends use this to avoid scrolling a row underneath the
525
- sticky header - critical for Excel-style keyboard nav. */
526
- scroll-padding-top: var(--sg-thead-h, 36px);
527
- }
528
-
529
- .sv-grid-container.sv-grid-container-custom-scrollbars {
530
- scrollbar-width: none;
531
- -ms-overflow-style: none;
532
- }
533
-
534
- .sv-grid-container.sv-grid-container-custom-scrollbars::-webkit-scrollbar {
535
- display: none;
536
- }
537
-
538
- .sv-grid-scrollbar {
539
- position: absolute;
540
- /* Above pinned columns (z-index 30 via cellPinStyle) so a frozen
541
- left/right column never paints over the custom scrollbar - that
542
- was the bug behind "vertical scrollbar hidden under the frozen
543
- column" in grids with a pinned column at the right edge. */
544
- z-index: 40;
545
- }
546
-
547
- /* Scrollbars anchor to the inline-end edge so RTL flips them to the
548
- * left automatically. `inset-inline-end` resolves to `right` in LTR
549
- * and `left` in RTL, no manual override needed. */
550
- .sv-grid-scrollbar-vertical {
551
- top: 0;
552
- inset-inline-end: 0;
553
- width: 16px;
554
- height: calc(100% - 16px);
555
- }
556
-
557
- .sv-grid-scrollbar-corner {
558
- position: absolute;
559
- top: 0;
560
- inset-inline-end: 0;
561
- width: 16px;
562
- background: var(--sg-header-bg, #f5f7fb);
563
- border-bottom: 1px solid rgba(15, 23, 42, 0.08);
564
- /* Above pinned header cells (z-index 30) so the gap above the
565
- vertical scrollbar stays clean over a frozen column. */
566
- z-index: 41;
567
- pointer-events: none;
568
- }
569
-
570
- .sv-grid-scrollbar-corner-br {
571
- position: absolute;
572
- inset-inline-end: 0;
573
- bottom: 0;
574
- width: 16px;
575
- height: 16px;
576
- background: var(--sg-header-bg, #eef2f8);
577
- box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.05);
578
- z-index: 42;
579
- pointer-events: none;
580
- }
581
-
582
- .sv-grid-scrollbar-horizontal {
583
- inset-inline-start: 0;
584
- bottom: 0;
585
- width: calc(100% - 16px);
586
- height: 16px;
587
- }
588
-
589
- .sv-grid-selection-column,
590
- .sv-grid-selection-cell {
591
- text-align: center;
592
- padding-inline: 6px;
593
- position: sticky;
594
- z-index: 4;
595
- }
596
-
597
- /* Row-number column: sticks to the left edge of the scroll viewport. */
598
- .sv-grid-row-number-column,
599
- .sv-grid-row-number-cell {
600
- text-align: right;
601
- padding-inline: 8px;
602
- position: sticky;
603
- left: 0;
604
- z-index: 4;
605
- color: var(--sg-muted, #64748b);
606
- font-variant-numeric: tabular-nums;
607
- }
608
- .sv-grid-row-number-head {
609
- color: inherit;
610
- }
611
-
612
- .sv-grid-filter-row-control {
613
- display: flex;
614
- flex-direction: row;
615
- align-items: center;
616
- gap: 4px;
617
- }
618
-
619
- .sv-grid-filter-operator {
620
- flex: 2 1 0;
621
- min-width: 0;
622
- box-sizing: border-box;
623
- }
624
-
625
- .sv-grid-filter-value {
626
- flex: 3 1 0;
627
- min-width: 0;
628
- box-sizing: border-box;
629
- }
630
-
631
- /* Brief glow used when the user clicks the funnel icon while the grid
632
- is in row-filter mode - draws attention to where the input is so
633
- the click isn't a no-op. */
634
- .sv-grid-filter-value.sv-grid-filter-value-pulse {
635
- animation: sv-grid-filter-pulse 700ms ease-out;
636
- }
637
- @keyframes sv-grid-filter-pulse {
638
- 0% { box-shadow: 0 0 0 0 rgba(11, 99, 243, 0.55); }
639
- 70% { box-shadow: 0 0 0 6px rgba(11, 99, 243, 0); }
640
- 100% { box-shadow: 0 0 0 0 rgba(11, 99, 243, 0); }
641
- }
642
-
643
- .sv-grid-checkbox {
644
- position: relative;
645
- display: inline-flex;
646
- align-items: center;
647
- justify-content: center;
648
- width: 16px;
649
- height: 16px;
650
- margin: 0 auto;
651
- border: 1px solid var(--sg-input-border, #8794a8);
652
- border-radius: 4px;
653
- background: var(--sg-input-bg, #fff);
654
- color: var(--sg-accent, #0b63f3);
655
- cursor: pointer;
656
- transition:
657
- border-color 120ms ease,
658
- background-color 120ms ease;
659
- }
660
-
661
- /* Color editor - native HTML color input filling the cell so the
662
- swatch is clickable across the whole cell area, not just the tiny
663
- OS default size. */
664
- .sv-grid-cell-editor-color {
665
- width: 100%;
666
- height: 100%;
667
- padding: 2px;
668
- border: 0;
669
- background: transparent;
670
- cursor: pointer;
671
- }
672
- .sv-grid-cell-editor-color::-webkit-color-swatch-wrapper { padding: 0; }
673
- .sv-grid-cell-editor-color::-webkit-color-swatch { border: 0; border-radius: 4px; }
674
- .sv-grid-cell-editor-color::-moz-color-swatch { border: 0; border-radius: 4px; }
675
-
676
- /* Rating editor - 5 clickable stars + clear button. Renders inline
677
- in the cell when editorType="rating"; the readonly view renders a
678
- filled-star count via the default text path (or a snippet cell). */
679
- .sv-grid-rating-editor {
680
- display: inline-flex;
681
- align-items: center;
682
- gap: 1px;
683
- height: 100%;
684
- }
685
- .sv-grid-rating-star {
686
- border: 0; background: transparent; cursor: pointer;
687
- color: var(--sg-rating-empty, #cbd5e1);
688
- font-size: 18px; line-height: 1;
689
- padding: 0 1px;
690
- transition: color 80ms ease, transform 80ms ease;
691
- }
692
- .sv-grid-rating-star-on { color: var(--sg-rating-on, #f59e0b); }
693
- .sv-grid-rating-star:hover { color: var(--sg-rating-hover, #fbbf24); transform: scale(1.12); }
694
- .sv-grid-rating-clear {
695
- margin-left: 4px;
696
- border: 0; background: transparent;
697
- color: var(--sg-muted, #94a3b8);
698
- cursor: pointer; font-size: 14px;
699
- }
700
- .sv-grid-rating-clear:hover { color: var(--sg-fg, #0f172a); }
701
-
702
- .sv-grid-checkbox[aria-checked="true"],
703
- .sv-grid-checkbox[aria-checked="mixed"] {
704
- border-color: var(--sg-accent, #0b63f3);
705
- background: var(--sg-selection-bg, #eaf2ff);
706
- }
707
-
708
- .sv-grid-checkbox[aria-checked="true"]::after {
709
- content: "";
710
- width: 5px;
711
- height: 9px;
712
- border-right: 2px solid currentColor;
713
- border-bottom: 2px solid currentColor;
714
- transform: rotate(40deg) translate(-1px, -1px);
715
- }
716
-
717
- .sv-grid-checkbox[aria-checked="mixed"]::after {
718
- content: "";
719
- width: 8px;
720
- height: 2px;
721
- border-radius: 1px;
722
- background: currentColor;
723
- }
724
-
725
- .sv-grid-checkbox:focus-visible {
726
- outline: 2px solid var(--sg-accent, #0b63f3);
727
- outline-offset: 1px;
728
- }
729
-
730
- .sv-grid-checkbox-readonly {
731
- cursor: default;
732
- pointer-events: none;
733
- }
734
-
735
- .sv-grid-cell-active,
736
- .sv-grid-cell-editing {
737
- position: relative;
738
- /* High enough to sit above pinned-column stacking contexts (z=4) so the
739
- list/chips popover overlays adjacent cells instead of being clipped. */
740
- z-index: 20;
741
- /* `box-shadow: inset` is bound to the cell's own box, so the ring
742
- clips cleanly with the cell when it's partially scrolled out of
743
- view (and shows up consistently at the right edge of a virtualized
744
- window). The previous `outline + outline-offset: -2px` rendered at
745
- the wrong position on clipped cells in some browsers - it's drawn
746
- outside the layout box, so the negative offset's effect was
747
- inconsistent at scroll boundaries. */
748
- box-shadow: inset 0 0 0 2px var(--sg-accent, #0b63f3);
749
- outline: none;
750
- /* Let list/chips popouts extend past the cell box (default cells have
751
- overflow: hidden so the chips picker would otherwise be clipped). */
752
- overflow: visible;
753
- }
754
-
755
- /* Excel-style fill handle: a 7×7 blue square anchored to the bottom-
756
- right corner of the active cell or selection range. The parent
757
- `.sv-grid-cell` is `position: relative` (sticky cells override
758
- that with sticky - which is also a positioning context), so the
759
- handle anchors to the cell box. */
760
- /* Excel-style per-cell note indicator. A small triangle in the top-
761
- * right corner that, when hovered, shows the note tooltip. The
762
- * triangle IS the hot-zone (12×12 square clipped to a triangle via
763
- * border tricks); a transparent invisible square on top widens the
764
- * hit target a little so it's not painful to hit. */
765
- .sv-grid-cell-note-corner {
766
- position: absolute;
767
- top: 0; right: 0;
768
- width: 12px; height: 12px;
769
- /* Triangle: 0,0 → 12,0 → 12,12 */
770
- background:
771
- linear-gradient(45deg, transparent 50%, var(--sg-note-corner, #f59e0b) 50%);
772
- cursor: help;
773
- z-index: 6;
774
- }
775
- /* Slightly larger invisible hit area so users don't have to land on
776
- a 6×6 pixel triangle. */
777
- .sv-grid-cell-note-corner::before {
778
- content: '';
779
- position: absolute;
780
- inset: -3px -3px 0 0;
781
- }
782
-
783
- .sv-grid-fill-handle {
784
- position: absolute;
785
- /* Sit fully INSIDE the cell box so `overflow: hidden` doesn't clip
786
- most of the handle. Previously the handle stuck 3px outside the
787
- cell and the visible 4×4 corner was nearly unclickable - especially
788
- on chip cells where pills filled the bottom edge. */
789
- bottom: 0;
790
- right: 0;
791
- width: 9px;
792
- height: 9px;
793
- background: var(--sg-accent, #0b63f3);
794
- border: 1px solid var(--sg-bg, #ffffff);
795
- box-sizing: border-box;
796
- cursor: crosshair;
797
- z-index: 15;
798
- touch-action: none;
799
- }
800
- .sv-grid-fill-handle:hover {
801
- transform: scale(1.3);
802
- transform-origin: bottom right;
803
- }
804
-
805
- /* Fill-preview overlay paints a dashed accent border on the cells the
806
- user is about to fill. Source cells (still in the selection range)
807
- keep their normal range styling. */
808
- .sv-grid-cell[data-fill-preview="true"] {
809
- box-shadow: inset 0 0 0 2px rgba(11, 99, 243, 0.5);
810
- background: rgba(11, 99, 243, 0.08);
811
- }
812
-
813
- .sv-grid-cell-editing {
814
- padding: 0;
815
- }
816
-
817
- /* The four CSS variables let us compose a single box-shadow that draws the
818
- selection rectangle outline only on the cells that sit on its edges. */
819
- .sv-grid-cell[data-selected-range="true"] {
820
- background: var(--sg-selection-bg, #eef4ff);
821
- --sv-range-top: 0 0 0 transparent;
822
- --sv-range-bottom: 0 0 0 transparent;
823
- --sv-range-left: 0 0 0 transparent;
824
- --sv-range-right: 0 0 0 transparent;
825
- box-shadow: var(--sv-range-top), var(--sv-range-bottom),
826
- var(--sv-range-left), var(--sv-range-right);
827
- }
828
-
829
- /* Selection-range edges as inset box-shadows. We tried `border-*: 2px
830
- solid` to dodge the 1px-notch artifact at cell boundaries, but
831
- border width changes force a full table reflow on every selection
832
- change - catastrophic during keyboard nav (every arrow key pressed
833
- re-flows ~30 visible rows + their neighbours under border-collapse).
834
- Box-shadow inset paints on the compositor only - no reflow. */
835
- .sv-grid-cell[data-range-top="true"] {
836
- --sv-range-top: inset 0 2px 0 var(--sg-accent, #0b63f3);
837
- }
838
- .sv-grid-cell[data-range-bottom="true"] {
839
- --sv-range-bottom: inset 0 -2px 0 var(--sg-accent, #0b63f3);
840
- }
841
- .sv-grid-cell[data-range-left="true"] {
842
- --sv-range-left: inset 2px 0 0 var(--sg-accent, #0b63f3);
843
- }
844
- .sv-grid-cell[data-range-right="true"] {
845
- --sv-range-right: inset -2px 0 0 var(--sg-accent, #0b63f3);
846
- }
847
-
848
- /* Lift edge cells above their neighbours so the inset shadow renders
849
- above adjacent cells' (collapsed) borders rather than under them. */
850
- .sv-grid-cell[data-range-top="true"],
851
- .sv-grid-cell[data-range-bottom="true"],
852
- .sv-grid-cell[data-range-left="true"],
853
- .sv-grid-cell[data-range-right="true"],
854
- .sv-grid-cell.sv-merge-edge-right,
855
- .sv-grid-cell.sv-merge-edge-bottom {
856
- position: relative;
857
- z-index: 3;
858
- }
859
-
860
- /* Inherited selection-range edges from `spreadsheetLayout` merges.
861
- Same inset-shadow technique - no layout impact when toggling. */
862
- .sv-grid-cell.sv-merge-edge-right {
863
- box-shadow: inset -2px 0 0 var(--sg-accent, #0b63f3);
864
- }
865
- .sv-grid-cell.sv-merge-edge-bottom {
866
- box-shadow: inset 0 -2px 0 var(--sg-accent, #0b63f3);
867
- }
868
- .sv-grid-cell.sv-merge-in-range {
869
- background: var(--sg-selection-bg, #eef4ff);
870
- }
871
-
872
- .sv-grid-cell-editor {
873
- position: absolute;
874
- inset: 0;
875
- width: 100%;
876
- height: 100%;
877
- border: 0;
878
- outline: none;
879
- background: var(--sg-input-bg, #fff);
880
- box-sizing: border-box;
881
- font: inherit;
882
- color: inherit;
883
- padding: 0 8px;
884
- }
885
-
886
- /* Filter-related inputs share the same focused-cell outline treatment. */
887
- .sv-grid-global-filter input:focus,
888
- .sv-grid-filter-value:focus,
889
- .sv-grid-column-filter:focus,
890
- .sv-grid-menu-search:focus,
891
- .sv-grid-menu-condition-value:focus,
892
- .sv-grid-menu-operator:focus {
893
- outline: 2px solid var(--sg-accent, #0b63f3);
894
- outline-offset: -2px;
895
- border-color: var(--sg-accent, #0b63f3);
896
- }
897
-
898
- .sv-grid-cell-editor-number,
899
- .sv-grid-cell-editor-date,
900
- .sv-grid-cell-editor-datetime {
901
- width: 100%;
902
- height: 100%;
903
- }
904
-
905
- /* List editor: native <select>. For multi-select it grows into a
906
- small popup-style listbox that hangs over the cell. */
907
- .sv-grid-cell-editor-list {
908
- width: 100%;
909
- height: 100%;
910
- appearance: auto;
911
- font: inherit;
912
- color: inherit;
913
- background: var(--sg-input-bg, #fff);
914
- border: 0;
915
- padding: 0 6px;
916
- box-sizing: border-box;
917
- }
918
- .sv-grid-cell-editor-list[multiple] {
919
- position: absolute;
920
- top: 0;
921
- left: 0;
922
- right: 0;
923
- height: auto;
924
- min-height: 100%;
925
- max-height: 220px;
926
- z-index: 5;
927
- padding: 4px;
928
- border: 1px solid var(--sg-accent, #0b63f3);
929
- background: var(--sg-input-bg, #fff);
930
- box-shadow: 0 8px 24px rgba(15, 23, 42, 0.18);
931
- }
932
-
933
- /* Free-form chips editor (multi-value cell with removable tokens).
934
- Absolutely positioned so it can grow downward past the row without
935
- pushing other rows. Theme-aware background + accent border + drop
936
- shadow so it reads as a popout above the next row, not as part of
937
- it. The cell's outer outline is suppressed for this editor - this
938
- popout already provides its own border. */
939
- .sv-grid-cell-editor-chips {
940
- position: absolute;
941
- top: -1px;
942
- left: -1px;
943
- right: -1px;
944
- min-height: calc(100% + 2px);
945
- background: var(--sg-header-bg, var(--sg-bg, #ffffff));
946
- color: var(--sg-fg, #0f172a);
947
- padding: 4px 6px;
948
- box-sizing: border-box;
949
- z-index: 1000;
950
- overflow: visible;
951
- display: flex;
952
- align-items: flex-start;
953
- border: 1px solid var(--sg-accent, #2563eb);
954
- border-radius: var(--sg-radius, 6px);
955
- box-shadow: 0 12px 28px rgba(0, 0, 0, 0.45);
956
- }
957
- /* When the chips editor is mounted, the parent .sv-grid-cell-editing
958
- no longer needs its own outline - the popout already shows one. */
959
- .sv-grid-cell-editing:has(.sv-grid-cell-editor-chips) {
960
- outline: none;
961
- }
962
- .sv-grid-chips-row {
963
- display: flex;
964
- flex-wrap: wrap;
965
- align-items: center;
966
- gap: 4px;
967
- width: 100%;
968
- }
969
- .sv-grid-chip-input {
970
- flex: 1 1 80px;
971
- min-width: 60px;
972
- border: 0;
973
- outline: none;
974
- background: transparent;
975
- font: inherit;
976
- color: inherit;
977
- padding: 2px 4px;
978
- }
979
- .sv-grid-chip-picker {
980
- flex: 1 1 100px;
981
- min-width: 80px;
982
- font: inherit;
983
- color: inherit;
984
- background: var(--sg-bg, #fff);
985
- border: 1px solid rgba(15, 23, 42, 0.15);
986
- border-radius: 4px;
987
- padding: 2px 4px;
988
- }
989
- .sv-grid-chip-commit {
990
- font-size: 11px;
991
- text-transform: uppercase;
992
- letter-spacing: 0.04em;
993
- color: #fff;
994
- background: var(--sg-accent, #0b63f3);
995
- border: 0;
996
- border-radius: 4px;
997
- padding: 3px 8px;
998
- cursor: pointer;
999
- }
1000
- .sv-grid-chip-commit:hover {
1001
- filter: brightness(1.1);
1002
- }
1003
-
1004
- /* Chip badge - used both in readonly cell display and inside the
1005
- chips editor. Subtle neutral chrome, scales to the row's font size.
1006
- Readonly chip rows stay on one line (the parent <td> has
1007
- overflow: hidden, so overflowing chips get clipped - widen the
1008
- column or open the editor to see all). */
1009
- .sv-grid-chips-display {
1010
- display: inline-flex;
1011
- flex-wrap: nowrap;
1012
- align-items: center;
1013
- gap: 4px;
1014
- max-width: 100%;
1015
- vertical-align: middle;
1016
- }
1017
- .sv-grid-sparkline {
1018
- display: inline-block;
1019
- vertical-align: middle;
1020
- overflow: visible;
1021
- }
1022
- /* Conditional formatting overlays - behind the cell text. */
1023
- .sv-grid-cf-bg {
1024
- position: absolute;
1025
- inset: 0;
1026
- z-index: 0;
1027
- pointer-events: none;
1028
- }
1029
- .sv-grid-cf-bar {
1030
- position: absolute;
1031
- left: 0;
1032
- top: 0;
1033
- bottom: 0;
1034
- z-index: 0;
1035
- opacity: 0.85;
1036
- border-radius: 0 2px 2px 0;
1037
- pointer-events: none;
1038
- }
1039
- .sv-grid-cell-cf {
1040
- position: relative;
1041
- }
1042
- .sv-grid-status-bar {
1043
- display: flex;
1044
- flex-wrap: wrap;
1045
- align-items: center;
1046
- gap: 18px;
1047
- padding: 6px 14px;
1048
- font-size: 12px;
1049
- color: var(--sg-fg);
1050
- background: var(--sg-header-bg, #f1f5f9);
1051
- border: 1px solid var(--sg-border, #e2e8f0);
1052
- border-top: 0;
1053
- flex-shrink: 0;
1054
- }
1055
- .sv-grid-status-item {
1056
- font-variant-numeric: tabular-nums;
1057
- font-weight: 600;
1058
- }
1059
- .sv-grid-status-label {
1060
- color: var(--sg-muted, #64748b);
1061
- font-weight: 400;
1062
- margin-right: 5px;
1063
- }
1064
- .sv-grid-cf-content {
1065
- position: relative;
1066
- z-index: 1;
1067
- }
1068
- .sv-grid-cf-icon {
1069
- margin-right: 5px;
1070
- font-size: 0.95em;
1071
- }
1072
- .sv-grid-chip {
1073
- display: inline-flex;
1074
- align-items: center;
1075
- gap: 4px;
1076
- padding: 2px 8px;
1077
- font-size: 0.85em;
1078
- line-height: 1.4;
1079
- background: color-mix(in srgb, var(--sg-accent, #2563eb) 18%, transparent);
1080
- color: var(--sg-fg, inherit);
1081
- border-radius: 999px;
1082
- border: 1px solid
1083
- color-mix(in srgb, var(--sg-accent, #2563eb) 35%, transparent);
1084
- white-space: nowrap;
1085
- }
1086
- .sv-grid-chip-removable {
1087
- padding-right: 2px;
1088
- }
1089
- .sv-grid-chip-remove {
1090
- display: inline-flex;
1091
- align-items: center;
1092
- justify-content: center;
1093
- width: 16px;
1094
- height: 16px;
1095
- border: 0;
1096
- border-radius: 999px;
1097
- background: color-mix(in srgb, var(--sg-fg, #0f172a) 18%, transparent);
1098
- color: inherit;
1099
- font-size: 12px;
1100
- line-height: 1;
1101
- cursor: pointer;
1102
- padding: 0;
1103
- }
1104
- .sv-grid-chip-remove:hover {
1105
- background: rgba(220, 38, 38, 0.7);
1106
- color: #fff;
1107
- }
1108
-
1109
- .sv-grid-icon {
1110
- width: 1em;
1111
- height: 1em;
1112
- display: block;
1113
- flex: none;
1114
- }
1115
-
1116
- .sv-grid-header-cell {
1117
- display: flex;
1118
- align-items: center;
1119
- gap: 2px;
1120
- width: 100%;
1121
- min-width: 0;
1122
- }
1123
-
1124
- .sv-grid-header-sort {
1125
- display: flex;
1126
- align-items: center;
1127
- gap: 4px;
1128
- flex: 1 1 auto;
1129
- min-width: 0;
1130
- padding: 2px;
1131
- border: 0;
1132
- background: transparent;
1133
- font: inherit;
1134
- color: inherit;
1135
- text-align: left;
1136
- cursor: pointer;
1137
- }
1138
-
1139
- /* Custom (function-valued) header rendering. Behaves like the sort
1140
- button but is a div so the consumer's snippet can contain its own
1141
- interactive elements (menu buttons, dropdowns, etc) - button-in-
1142
- button DOM is invalid. */
1143
- .sv-grid-header-custom {
1144
- display: flex;
1145
- align-items: center;
1146
- gap: 4px;
1147
- flex: 1 1 auto;
1148
- min-width: 0;
1149
- padding: 2px;
1150
- text-align: left;
1151
- cursor: pointer;
1152
- outline: none;
1153
- }
1154
-
1155
- .sv-grid-header-label {
1156
- /* min-width:0 lets this flex item shrink below its text width so a long
1157
- label truncates to a single line with an ellipsis instead of overflowing
1158
- or forcing the header taller. flex:0 1 auto keeps the sort icon snug to
1159
- the label rather than pushed to the far edge. */
1160
- flex: 0 1 auto;
1161
- min-width: 0;
1162
- overflow: hidden;
1163
- text-overflow: ellipsis;
1164
- white-space: nowrap;
1165
- }
1166
-
1167
- .sv-grid-header-icon {
1168
- display: inline-flex;
1169
- flex: none;
1170
- font-size: 13px;
1171
- color: var(--sg-muted, #5b6b85);
1172
- }
1173
-
1174
- .sv-grid-header-icon-hint {
1175
- opacity: 0;
1176
- transition: opacity 120ms ease;
1177
- }
1178
-
1179
- .sv-grid-column:hover .sv-grid-header-icon-hint {
1180
- opacity: 0.4;
1181
- }
1182
-
1183
- .sv-grid-header-icon-flag {
1184
- color: var(--sg-accent, #0b63f3);
1185
- }
1186
-
1187
- .sv-grid-col-menu-btn {
1188
- display: inline-flex;
1189
- align-items: center;
1190
- justify-content: center;
1191
- flex: none;
1192
- /* Collapse to zero width when invisible so a non-hovered column
1193
- has no dead space at the right; the funnel (when an active
1194
- filter is on) can then sit flush against the right edge.
1195
- Hover / focus / open expand it back to 22px. */
1196
- width: 0;
1197
- height: 22px;
1198
- padding: 0;
1199
- border: 0;
1200
- border-radius: 4px;
1201
- background: transparent;
1202
- color: var(--sg-muted, #5b6b85);
1203
- font-size: 15px;
1204
- cursor: pointer;
1205
- opacity: 0;
1206
- pointer-events: none;
1207
- overflow: hidden;
1208
- transition:
1209
- width 140ms ease,
1210
- opacity 140ms ease,
1211
- background-color 120ms ease;
1212
- }
1213
-
1214
- .sv-grid-column:hover .sv-grid-col-menu-btn,
1215
- .sv-grid-col-menu-btn.is-open,
1216
- .sv-grid-col-menu-btn:focus-visible {
1217
- width: 22px;
1218
- opacity: 1;
1219
- pointer-events: auto;
1220
- }
1221
-
1222
- /* The funnel filter button is a sibling .sv-grid-col-menu-btn that
1223
- ALSO carries .sv-grid-col-filter-btn. When the column has an
1224
- active filter (.is-active) the funnel stays visible at full size
1225
- even when the column isn't hovered - so users can see at a glance
1226
- which columns are filtered. Because the 3-dot menu still collapses
1227
- to width: 0 in that state, the funnel ends up flush against the
1228
- right edge. On hover, the menu expands back to 22px and pushes the
1229
- funnel left naturally. */
1230
- .sv-grid-col-filter-btn.is-active {
1231
- width: 22px;
1232
- opacity: 1;
1233
- pointer-events: auto;
1234
- }
1235
-
1236
- .sv-grid-col-menu-btn:hover,
1237
- .sv-grid-col-menu-btn.is-open {
1238
- background: var(--sg-row-hover-bg, #e2e8f3);
1239
- }
1240
-
1241
- .sv-grid-filter-operator-btn {
1242
- display: inline-flex;
1243
- align-items: center;
1244
- gap: 2px;
1245
- flex: none;
1246
- height: 24px;
1247
- padding: 0 4px;
1248
- border: 1px solid var(--sg-input-border, #cbd5e1);
1249
- border-radius: 4px;
1250
- background: var(--sg-input-bg, #fff);
1251
- color: var(--sg-fg, #334155);
1252
- font-size: 13px;
1253
- cursor: pointer;
1254
- }
1255
-
1256
- .sv-grid-filter-operator-btn.is-open {
1257
- border-color: var(--sg-accent, #0b63f3);
1258
- }
1259
-
1260
- .sv-grid-caret {
1261
- display: inline-flex;
1262
- font-size: 9px;
1263
- color: var(--sg-muted, #94a3b8);
1264
- }
1265
-
1266
- .sv-grid-menu-backdrop {
1267
- position: fixed;
1268
- inset: 0;
1269
- z-index: 900;
1270
- background: transparent;
1271
- }
1272
-
1273
- .sv-grid-menu {
1274
- position: fixed;
1275
- z-index: 901;
1276
- max-height: calc(100vh - 24px);
1277
- overflow: auto;
1278
- padding: 4px;
1279
- background: var(--sg-bg, #fff);
1280
- border: 1px solid var(--sg-border, #d6dee9);
1281
- border-radius: 8px;
1282
- box-shadow: 0 12px 28px rgba(15, 23, 42, 0.18);
1283
- font-size: 13px;
1284
- color: var(--sg-fg, #1f2937);
1285
- }
1286
-
1287
- .sv-grid-column-menu {
1288
- width: 260px;
1289
- }
1290
-
1291
- .sv-grid-operator-menu {
1292
- width: 184px;
1293
- }
1294
-
1295
- .sv-grid-context-menu {
1296
- min-width: 180px;
1297
- }
1298
-
1299
- .sv-grid-comment-editor {
1300
- width: 240px;
1301
- padding: 8px;
1302
- }
1303
- .sv-grid-comment-textarea {
1304
- width: 100%;
1305
- resize: vertical;
1306
- min-height: 60px;
1307
- padding: 6px 8px;
1308
- border: 1px solid var(--sg-input-border, #cbd5e1);
1309
- border-radius: 4px;
1310
- background: var(--sg-input-bg, #fff);
1311
- color: var(--sg-fg, #0f172a);
1312
- font: inherit;
1313
- font-size: 13px;
1314
- outline: none;
1315
- }
1316
- .sv-grid-comment-textarea:focus {
1317
- border-color: var(--sg-accent, #6366f1);
1318
- }
1319
- .sv-grid-comment-actions {
1320
- display: flex;
1321
- align-items: center;
1322
- gap: 6px;
1323
- margin-top: 8px;
1324
- }
1325
- .sv-grid-comment-spacer {
1326
- flex: 1 1 auto;
1327
- }
1328
- .sv-grid-comment-actions button {
1329
- padding: 4px 10px;
1330
- border-radius: 4px;
1331
- border: 1px solid var(--sg-border, #cbd5e1);
1332
- background: var(--sg-bg, #fff);
1333
- color: var(--sg-fg, #1f2937);
1334
- font: inherit;
1335
- font-size: 12px;
1336
- cursor: pointer;
1337
- }
1338
- .sv-grid-comment-save {
1339
- border-color: var(--sg-accent, #6366f1) !important;
1340
- background: var(--sg-accent, #6366f1) !important;
1341
- color: #fff !important;
1342
- }
1343
- .sv-grid-comment-remove {
1344
- color: var(--sg-danger, #dc2626) !important;
1345
- }
1346
-
1347
- .sv-grid-menu-item {
1348
- display: flex;
1349
- align-items: center;
1350
- gap: 8px;
1351
- width: 100%;
1352
- padding: 7px 10px;
1353
- border: 0;
1354
- border-radius: 5px;
1355
- background: transparent;
1356
- color: inherit;
1357
- font: inherit;
1358
- text-align: left;
1359
- cursor: pointer;
1360
- }
1361
-
1362
- .sv-grid-menu-item:hover {
1363
- background: var(--sg-row-hover-bg, #eef2f8);
1364
- }
1365
-
1366
- .sv-grid-menu-item[aria-checked="true"] {
1367
- background: var(--sg-selection-bg, #eaf2ff);
1368
- color: var(--sg-accent, #0b63f3);
1369
- }
1370
-
1371
- .sv-grid-menu-sep {
1372
- height: 1px;
1373
- margin: 4px 6px;
1374
- background: var(--sg-border, #e6ebf2);
1375
- }
1376
-
1377
- .sv-grid-menu-filter {
1378
- padding: 4px 6px 6px;
1379
- }
1380
-
1381
- .sv-grid-menu-filter-head {
1382
- display: flex;
1383
- align-items: center;
1384
- gap: 6px;
1385
- padding: 2px 0 6px;
1386
- font-weight: 600;
1387
- color: var(--sg-muted, #475569);
1388
- }
1389
-
1390
- .sv-grid-menu-search {
1391
- width: 100%;
1392
- height: 28px;
1393
- box-sizing: border-box;
1394
- margin-bottom: 6px;
1395
- padding: 0 8px;
1396
- border: 1px solid var(--sg-input-border, #cbd5e1);
1397
- border-radius: 5px;
1398
- font: inherit;
1399
- }
1400
-
1401
- .sv-grid-menu-filter-row {
1402
- display: flex;
1403
- gap: 4px;
1404
- margin-bottom: 6px;
1405
- }
1406
-
1407
- .sv-grid-menu-operator-group {
1408
- display: flex;
1409
- gap: 2px;
1410
- margin-bottom: 6px;
1411
- }
1412
-
1413
- .sv-grid-menu-operator-btn {
1414
- flex: 1 1 0;
1415
- display: inline-flex;
1416
- align-items: center;
1417
- justify-content: center;
1418
- height: 28px;
1419
- padding: 0;
1420
- border: 1px solid var(--sg-input-border, #cbd5e1);
1421
- border-radius: 4px;
1422
- background: var(--sg-input-bg, #fff);
1423
- color: var(--sg-fg, #475569);
1424
- cursor: pointer;
1425
- transition:
1426
- background-color 100ms ease,
1427
- color 100ms ease,
1428
- border-color 100ms ease;
1429
- }
1430
-
1431
- .sv-grid-menu-operator-btn:hover {
1432
- background: var(--sg-row-hover-bg, #eef2f8);
1433
- }
1434
-
1435
- .sv-grid-menu-operator-btn.is-active {
1436
- background: var(--sg-accent, #0b63f3);
1437
- border-color: var(--sg-accent, #0b63f3);
1438
- color: #fff;
1439
- }
1440
-
1441
- .sv-grid-menu-condition-value {
1442
- flex: 1 1 0;
1443
- min-width: 0;
1444
- height: 28px;
1445
- box-sizing: border-box;
1446
- padding: 0 8px;
1447
- border: 1px solid var(--sg-input-border, #cbd5e1);
1448
- border-radius: 5px;
1449
- font: inherit;
1450
- }
1451
-
1452
- .sv-grid-facet-list {
1453
- max-height: 200px;
1454
- overflow: auto;
1455
- border: 1px solid var(--sg-border, #eaeef4);
1456
- border-radius: 5px;
1457
- }
1458
-
1459
- .sv-grid-facet {
1460
- display: flex;
1461
- align-items: center;
1462
- gap: 8px;
1463
- padding: 4px 8px;
1464
- cursor: pointer;
1465
- }
1466
-
1467
- .sv-grid-facet:hover {
1468
- background: var(--sg-row-hover-bg, #f4f6fa);
1469
- }
1470
-
1471
- .sv-grid-facet-all {
1472
- border-bottom: 1px solid var(--sg-border, #eaeef4);
1473
- font-weight: 600;
1474
- }
1475
-
1476
- .sv-grid-facet input {
1477
- flex: none;
1478
- }
1479
-
1480
- .sv-grid-facet-label {
1481
- overflow: hidden;
1482
- text-overflow: ellipsis;
1483
- white-space: nowrap;
1484
- }
1485
-
1486
- .sv-grid-facet-empty,
1487
- .sv-grid-facet-note {
1488
- padding: 6px 8px;
1489
- color: var(--sg-muted, #94a3b8);
1490
- font-size: 12px;
1491
- }
1492
-
1493
- .sv-grid-menu-actions {
1494
- display: flex;
1495
- justify-content: flex-end;
1496
- gap: 6px;
1497
- padding-top: 8px;
1498
- }
1499
-
1500
- .sv-grid-menu-btn {
1501
- padding: 5px 10px;
1502
- border: 1px solid var(--sg-input-border, #cbd5e1);
1503
- border-radius: 5px;
1504
- background: var(--sg-input-bg, #fff);
1505
- color: var(--sg-fg, #334155);
1506
- font: inherit;
1507
- cursor: pointer;
1508
- }
1509
-
1510
- .sv-grid-menu-btn-primary {
1511
- background: var(--sg-accent, #0b63f3);
1512
- border-color: var(--sg-accent, #0b63f3);
1513
- color: #fff;
1514
- }
1515
-
1516
- .sv-grid-menu-item:disabled,
1517
- .sv-grid-menu-btn:disabled {
1518
- opacity: 0.4;
1519
- cursor: default;
1520
- }
1521
-
1522
- .sv-grid-menu-item:disabled:hover {
1523
- background: transparent;
1524
- }
1525
-
1526
- .sv-grid-group-row > .sv-grid-cell {
1527
- background: var(--sg-header-bg, #eef2f8);
1528
- font-weight: 600;
1529
- cursor: pointer;
1530
- }
1531
-
1532
- .sv-grid-row-selected > .sv-grid-cell {
1533
- background: var(--sg-selection-bg, #eaf2ff);
1534
- }
1535
-
1536
- .sv-grid-group-cell {
1537
- padding: 0 12px;
1538
- }
1539
-
1540
- .sv-grid-group-content {
1541
- display: inline-flex;
1542
- align-items: center;
1543
- gap: 6px;
1544
- }
1545
-
1546
- .sv-grid-group-toggle {
1547
- border: 0;
1548
- background: transparent;
1549
- padding: 0 4px 0 0;
1550
- font-size: 11px;
1551
- line-height: 1;
1552
- color: var(--sg-fg, #334155);
1553
- cursor: pointer;
1554
- }
1555
-
1556
- .sv-grid-group-count {
1557
- margin-left: 6px;
1558
- font-weight: 400;
1559
- font-size: 12px;
1560
- color: var(--sg-muted, #64748b);
1561
- }
1562
- .sv-grid-group-agg {
1563
- margin-left: 10px;
1564
- font-size: 12px;
1565
- font-weight: 600;
1566
- color: var(--sg-fg);
1567
- white-space: nowrap;
1568
- }
1569
- .sv-grid-group-agg-label {
1570
- font-weight: 400;
1571
- color: var(--sg-muted, #64748b);
1572
- margin-right: 4px;
1573
- }
1574
- .sv-grid-group-agg-label::after {
1575
- content: ":";
1576
- }
1577
-
1578
- .sv-grid-group-child-indent {
1579
- display: inline-block;
1580
- width: 18px;
1581
- }
1582
-
1583
- /* ---- Custom hover tooltip (column .tooltip + cell notes) ---- */
1584
- .sv-grid-tooltip {
1585
- position: fixed;
1586
- z-index: 10000;
1587
- max-width: 280px;
1588
- padding: 8px 10px;
1589
- background: #0f172a;
1590
- color: #f1f5f9;
1591
- border-radius: var(--sg-radius, 6px);
1592
- font-size: 12px;
1593
- line-height: 1.45;
1594
- box-shadow: 0 8px 24px rgba(15, 23, 42, 0.28);
1595
- pointer-events: none;
1596
- white-space: pre-wrap;
1597
- word-wrap: break-word;
1598
- }
1599
- [data-theme="dark"] .sv-grid-tooltip { background: #f1f5f9; color: #0f172a; }
1600
-
1601
- /* ---- Textarea editor ---- */
1602
- .sv-grid-cell-editor-textarea {
1603
- width: 100%;
1604
- min-height: 80px;
1605
- padding: 6px 8px;
1606
- border: 1px solid var(--sg-accent, #6366f1);
1607
- background: var(--sg-bg, #fff);
1608
- color: var(--sg-fg, #0f172a);
1609
- border-radius: 4px;
1610
- font-family: inherit;
1611
- font-size: 13px;
1612
- line-height: 1.4;
1613
- resize: vertical;
1614
- outline: none;
1615
- box-shadow: var(--sg-focus-ring, 0 0 0 2px rgba(99, 102, 241, 0.40));
1616
- }
1617
-
1618
- .sv-grid-autocomplete {
1619
- position: relative;
1620
- width: 100%;
1621
- }
1622
- .sv-grid-autocomplete-list {
1623
- position: absolute;
1624
- left: 0;
1625
- right: 0;
1626
- top: 100%;
1627
- z-index: 30;
1628
- max-height: 220px;
1629
- overflow: auto;
1630
- margin-top: 2px;
1631
- padding: 4px;
1632
- background: var(--sg-bg, #fff);
1633
- border: 1px solid var(--sg-border, #d6dee9);
1634
- border-radius: var(--sg-radius, 6px);
1635
- box-shadow: 0 12px 28px rgba(15, 23, 42, 0.18);
1636
- }
1637
- .sv-grid-autocomplete-option {
1638
- display: block;
1639
- width: 100%;
1640
- text-align: left;
1641
- padding: 5px 8px;
1642
- border: 0;
1643
- border-radius: 4px;
1644
- background: transparent;
1645
- color: var(--sg-fg, #1f2937);
1646
- font: inherit;
1647
- font-size: 13px;
1648
- cursor: pointer;
1649
- white-space: nowrap;
1650
- overflow: hidden;
1651
- text-overflow: ellipsis;
1652
- }
1653
- .sv-grid-autocomplete-option:hover,
1654
- .sv-grid-autocomplete-option[aria-selected="true"] {
1655
- background: var(--sg-row-hover-bg, rgba(99, 102, 241, 0.10));
1656
- }
1657
-
1658
- /* ---- Find-in-grid overlay ---- */
1659
- .sv-grid-find {
1660
- position: absolute;
1661
- top: 8px; right: 8px;
1662
- z-index: 60;
1663
- display: inline-flex; align-items: center; gap: 4px;
1664
- padding: 6px 8px;
1665
- background: var(--sg-bg, #fff);
1666
- color: var(--sg-fg, #0f172a);
1667
- border: 1px solid var(--sg-border, #cbd5e1);
1668
- border-radius: 8px;
1669
- box-shadow: 0 10px 25px rgba(15, 23, 42, 0.18);
1670
- min-width: 280px;
1671
- }
1672
- .sv-grid-find-icon {
1673
- width: 14px; height: 14px;
1674
- color: var(--sg-muted, #94a3b8);
1675
- flex: none;
1676
- margin-left: 2px;
1677
- }
1678
- .sv-grid-find-input {
1679
- flex: 1; min-width: 0;
1680
- border: 0; outline: none;
1681
- background: transparent;
1682
- color: inherit;
1683
- font-size: 13px;
1684
- padding: 2px 6px;
1685
- }
1686
- .sv-grid-find-input::placeholder { color: var(--sg-muted, #94a3b8); }
1687
- .sv-grid-find-count {
1688
- font-size: 11px;
1689
- color: var(--sg-muted, #64748b);
1690
- font-variant-numeric: tabular-nums;
1691
- padding: 0 4px;
1692
- white-space: nowrap;
1693
- }
1694
- .sv-grid-find-step, .sv-grid-find-close {
1695
- display: inline-flex; align-items: center; justify-content: center;
1696
- width: 22px; height: 22px;
1697
- background: transparent;
1698
- border: 0;
1699
- color: var(--sg-muted, #64748b);
1700
- border-radius: 4px;
1701
- cursor: pointer;
1702
- font-size: 12px;
1703
- }
1704
- .sv-grid-find-step:hover:not(:disabled),
1705
- .sv-grid-find-close:hover {
1706
- background: var(--sg-row-hover-bg, rgba(148, 163, 184, 0.12));
1707
- color: var(--sg-fg, #0f172a);
1708
- }
1709
- .sv-grid-find-step:disabled { opacity: 0.30; cursor: default; }
1
+ /*
2
+ * SvGrid component styles. Extracted from SvGrid.svelte's <style> block so the
3
+ * component file stays focused on logic + markup. These are plain (global) CSS
4
+ * rules - every selector is a unique .sv-grid-* class that maps to a real class
5
+ * on the grid's elements, so behaviour is identical to the former scoped block.
6
+ * Imported for its side effect by SvGrid.svelte; the WC build inlines it and
7
+ * app bundlers include it via the import.
8
+ */
9
+ /* Root wrapper: holds the shell + pager + any overlays (find, tooltips
10
+ * pinned to the root). Position-relative so absolutely-positioned
11
+ * overlays (the find-in-grid toolbar) anchor to the grid, not the
12
+ * viewport. */
13
+ .sv-grid-root {
14
+ position: relative;
15
+ }
16
+ .sv-grid-sr-only {
17
+ position: absolute;
18
+ width: 1px;
19
+ height: 1px;
20
+ overflow: hidden;
21
+ clip: rect(0 0 0 0);
22
+ white-space: nowrap;
23
+ }
24
+ .sv-grid-loading-overlay {
25
+ position: absolute;
26
+ inset: 0;
27
+ z-index: 18;
28
+ pointer-events: auto;
29
+ background: color-mix(in srgb, var(--sg-bg, #fff) 35%, transparent);
30
+ }
31
+ /* Indeterminate top progress bar - the "something is happening" signal
32
+ that keeps the current rows visible underneath (no flash). */
33
+ .sv-grid-loading-bar {
34
+ position: absolute;
35
+ top: 0;
36
+ left: 0;
37
+ right: 0;
38
+ height: 3px;
39
+ overflow: hidden;
40
+ background: color-mix(in srgb, var(--sg-accent, #2563eb) 18%, transparent);
41
+ }
42
+ .sv-grid-loading-bar::before {
43
+ content: '';
44
+ position: absolute;
45
+ inset: 0;
46
+ width: 40%;
47
+ background: var(--sg-accent, #2563eb);
48
+ animation: sv-grid-loading-slide 1.1s ease-in-out infinite;
49
+ }
50
+ @keyframes sv-grid-loading-slide {
51
+ 0% { transform: translateX(-100%); }
52
+ 100% { transform: translateX(350%); }
53
+ }
54
+ /* First-load skeleton: shimmer placeholder rows under the header. */
55
+ .sv-grid-skeleton {
56
+ position: absolute;
57
+ top: var(--sg-thead-h, 40px);
58
+ left: 0;
59
+ right: 0;
60
+ padding: 0;
61
+ overflow: hidden;
62
+ }
63
+ .sv-grid-skeleton-row {
64
+ display: flex;
65
+ height: var(--sg-pinned-row-h, 36px);
66
+ align-items: center;
67
+ border-bottom: 1px solid var(--sg-border, #e2e8f0);
68
+ }
69
+ .sv-grid-skeleton-cell {
70
+ flex-shrink: 0;
71
+ padding: 0 12px;
72
+ box-sizing: border-box;
73
+ }
74
+ .sv-grid-skeleton-bar {
75
+ display: block;
76
+ height: 10px;
77
+ width: 70%;
78
+ border-radius: 4px;
79
+ background: linear-gradient(
80
+ 90deg,
81
+ color-mix(in srgb, var(--sg-muted, #94a3b8) 18%, transparent) 25%,
82
+ color-mix(in srgb, var(--sg-muted, #94a3b8) 32%, transparent) 50%,
83
+ color-mix(in srgb, var(--sg-muted, #94a3b8) 18%, transparent) 75%
84
+ );
85
+ background-size: 200% 100%;
86
+ animation: sv-grid-shimmer 1.3s linear infinite;
87
+ }
88
+ @keyframes sv-grid-shimmer {
89
+ 0% { background-position: 200% 0; }
90
+ 100% { background-position: -200% 0; }
91
+ }
92
+
93
+ /* Grid toolbar strip above the shell - hosts the tool-panel trigger (and is a
94
+ natural home for future grid-level actions). */
95
+ .sv-grid-toolbar {
96
+ display: flex;
97
+ align-items: center;
98
+ gap: 8px;
99
+ padding: 6px 2px;
100
+ }
101
+ .sv-grid-toolbar-btn {
102
+ display: inline-flex;
103
+ align-items: center;
104
+ gap: 6px;
105
+ padding: 6px 12px;
106
+ border: 1px solid var(--sg-border, #e2e8f0);
107
+ border-radius: var(--sg-radius, 6px);
108
+ background: var(--sg-bg, #fff);
109
+ color: var(--sg-fg, #0f172a);
110
+ font-size: 12.5px;
111
+ font-weight: 600;
112
+ cursor: pointer;
113
+ transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
114
+ }
115
+ .sv-grid-toolbar-btn:hover {
116
+ border-color: var(--sg-accent, #2563eb);
117
+ color: var(--sg-accent, #2563eb);
118
+ }
119
+ .sv-grid-toolbar-btn.is-active {
120
+ background: var(--sg-accent, #2563eb);
121
+ border-color: var(--sg-accent, #2563eb);
122
+ color: #fff;
123
+ }
124
+ .sv-grid-tool-panel {
125
+ position: absolute;
126
+ top: 0;
127
+ right: 0;
128
+ bottom: 0;
129
+ /* Above the grid's sticky header (z-index 30) and custom scrollbars (z ~900)
130
+ so the docked panel fully overlays the grid instead of the header/right
131
+ columns bleeding through its top-right. Ties the menu layer (901) but the
132
+ menus render later in the DOM, so a column menu opened over the panel still
133
+ wins. */
134
+ z-index: 901;
135
+ width: 250px;
136
+ display: flex;
137
+ flex-direction: column;
138
+ background: var(--sg-bg, #fff);
139
+ border-left: 1px solid var(--sg-border, #e2e8f0);
140
+ box-shadow: -8px 0 24px rgba(0, 0, 0, 0.12);
141
+ }
142
+ .sv-grid-tool-panel-head {
143
+ display: flex;
144
+ align-items: center;
145
+ justify-content: space-between;
146
+ padding: 8px 12px;
147
+ font-size: 12px;
148
+ font-weight: 700;
149
+ text-transform: uppercase;
150
+ letter-spacing: 0.05em;
151
+ color: var(--sg-muted, #64748b);
152
+ border-bottom: 1px solid var(--sg-border, #e2e8f0);
153
+ }
154
+ .sv-grid-tool-panel-close {
155
+ border: 0;
156
+ background: transparent;
157
+ color: var(--sg-muted, #64748b);
158
+ font-size: 14px;
159
+ cursor: pointer;
160
+ }
161
+ .sv-grid-tool-panel-list {
162
+ list-style: none;
163
+ margin: 0;
164
+ padding: 6px;
165
+ overflow-y: auto;
166
+ flex: 1;
167
+ }
168
+ .sv-grid-tool-panel-item {
169
+ display: flex;
170
+ align-items: center;
171
+ justify-content: space-between;
172
+ gap: 6px;
173
+ padding: 3px 6px;
174
+ border-radius: 5px;
175
+ }
176
+ .sv-grid-tool-panel-item:hover {
177
+ background: var(--sg-row-hover-bg, rgba(148, 163, 184, 0.1));
178
+ }
179
+ .sv-grid-tool-panel-vis {
180
+ display: flex;
181
+ align-items: center;
182
+ gap: 8px;
183
+ flex: 1;
184
+ min-width: 0;
185
+ font-size: 13px;
186
+ color: var(--sg-fg, #0f172a);
187
+ cursor: pointer;
188
+ }
189
+ .sv-grid-tool-panel-vis input {
190
+ accent-color: var(--sg-accent, #2563eb);
191
+ }
192
+ .sv-grid-tool-panel-name {
193
+ overflow: hidden;
194
+ text-overflow: ellipsis;
195
+ white-space: nowrap;
196
+ }
197
+ .sv-grid-tool-panel-actions {
198
+ display: inline-flex;
199
+ gap: 2px;
200
+ flex-shrink: 0;
201
+ }
202
+ .sv-grid-tool-panel-btn {
203
+ width: 22px;
204
+ height: 22px;
205
+ border: 0;
206
+ border-radius: 4px;
207
+ background: transparent;
208
+ color: var(--sg-muted, #64748b);
209
+ font-size: 13px;
210
+ cursor: pointer;
211
+ }
212
+ .sv-grid-tool-panel-btn:hover:not(:disabled) {
213
+ background: var(--sg-row-hover-bg, rgba(148, 163, 184, 0.18));
214
+ color: var(--sg-fg, #0f172a);
215
+ }
216
+ .sv-grid-tool-panel-btn.is-active {
217
+ color: var(--sg-accent, #2563eb);
218
+ }
219
+ .sv-grid-tool-panel-btn:disabled {
220
+ opacity: 0.35;
221
+ cursor: default;
222
+ }
223
+ /* Tool-panel tab bar (Columns | Filters) */
224
+ .sv-grid-tool-panel-tabs {
225
+ display: flex;
226
+ gap: 2px;
227
+ padding: 6px 6px 0;
228
+ border-bottom: 1px solid var(--sg-border, #e2e8f0);
229
+ }
230
+ .sv-grid-tool-panel-tab {
231
+ flex: 1;
232
+ padding: 6px 8px;
233
+ border: 0;
234
+ border-bottom: 2px solid transparent;
235
+ background: transparent;
236
+ color: var(--sg-muted, #64748b);
237
+ font-size: 12px;
238
+ font-weight: 600;
239
+ cursor: pointer;
240
+ }
241
+ .sv-grid-tool-panel-tab:hover {
242
+ color: var(--sg-fg, #0f172a);
243
+ }
244
+ .sv-grid-tool-panel-tab.is-active {
245
+ color: var(--sg-accent, #2563eb);
246
+ border-bottom-color: var(--sg-accent, #2563eb);
247
+ }
248
+ /* Filters tab: one control block per filterable column */
249
+ .sv-grid-tool-panel-filters {
250
+ padding: 6px;
251
+ overflow-y: auto;
252
+ flex: 1;
253
+ display: flex;
254
+ flex-direction: column;
255
+ gap: 8px;
256
+ }
257
+ .sv-grid-tp-filter {
258
+ display: flex;
259
+ flex-direction: column;
260
+ gap: 4px;
261
+ padding: 6px;
262
+ border: 1px solid var(--sg-border, #e2e8f0);
263
+ border-radius: 6px;
264
+ }
265
+ .sv-grid-tp-filter.is-filtered {
266
+ border-color: var(--sg-accent, #2563eb);
267
+ }
268
+ .sv-grid-tp-filter-head {
269
+ display: flex;
270
+ align-items: center;
271
+ justify-content: space-between;
272
+ }
273
+ .sv-grid-tp-filter-name {
274
+ font-size: 12px;
275
+ font-weight: 600;
276
+ color: var(--sg-fg, #0f172a);
277
+ }
278
+ .sv-grid-tp-filter-clear {
279
+ border: 0;
280
+ background: transparent;
281
+ color: var(--sg-muted, #64748b);
282
+ font-size: 11px;
283
+ cursor: pointer;
284
+ padding: 0 2px;
285
+ }
286
+ .sv-grid-tp-filter-clear:hover {
287
+ color: var(--sg-accent, #2563eb);
288
+ }
289
+ .sv-grid-tp-filter-op,
290
+ .sv-grid-tp-filter-input {
291
+ width: 100%;
292
+ box-sizing: border-box;
293
+ padding: 4px 6px;
294
+ font-size: 12px;
295
+ border: 1px solid var(--sg-border, #e2e8f0);
296
+ border-radius: 5px;
297
+ background: var(--sg-bg, #fff);
298
+ color: var(--sg-fg, #0f172a);
299
+ }
300
+ /* Root wrapper fill mode: only takes effect when consumer passes
301
+ * containerHeight='100%'. The shell becomes a flex item that expands
302
+ * while the pager keeps its natural height. */
303
+ .sv-grid-root-fill {
304
+ display: flex;
305
+ flex-direction: column;
306
+ height: 100%;
307
+ min-height: 0;
308
+ }
309
+ .sv-grid-root-fill > .sv-grid-shell {
310
+ flex: 1 1 0;
311
+ min-height: 0;
312
+ height: auto !important;
313
+ }
314
+
315
+ .sv-grid-shell {
316
+ position: relative;
317
+ border-bottom: 1px solid var(--sg-border, #cbd5e1);
318
+ }
319
+
320
+ .sv-grid-table {
321
+ table-layout: fixed;
322
+ border-collapse: separate;
323
+ border-spacing: 0;
324
+ }
325
+
326
+ .sv-grid-table:focus {
327
+ outline: none;
328
+ }
329
+
330
+ .sv-grid-column,
331
+ .sv-grid-cell {
332
+ box-sizing: border-box;
333
+ overflow: hidden;
334
+ text-overflow: ellipsis;
335
+ white-space: nowrap;
336
+ background: var(--sg-bg, #fff);
337
+ color: var(--sg-fg, #0f172a);
338
+ user-select: none;
339
+ }
340
+ /* Only the active cell + the cell hosting the fill handle become a
341
+ positioning context (so the absolutely-positioned handle anchors
342
+ to the cell box). Adding `position: relative` to EVERY cell paints
343
+ each one as its own layout box and noticeably slows vertical
344
+ scroll on dense grids - keep it scoped. Sticky / pinned cells are
345
+ already positioning contexts via `position: sticky`. */
346
+ .sv-grid-cell-active,
347
+ .sv-grid-cell-has-fill-handle {
348
+ position: relative;
349
+ }
350
+ /* The cell that owns the fill handle gets overflow: visible so the
351
+ handle isn't clipped - chip cells in particular pack pills right up
352
+ to the cell edges, and with overflow: hidden the handle's hover
353
+ scale-up + the handle itself could be partially eaten by them. */
354
+ .sv-grid-cell-has-fill-handle {
355
+ overflow: visible;
356
+ }
357
+
358
+ /* Full-width expandable detail row (props.isDetailRow / renderDetailRow).
359
+ Overrides the normal single-line cell clamps so the panel spans every
360
+ column and grows to its natural height. Pair with virtualization={false}
361
+ so the variable height isn't fought by the fixed-row-height virtualizer.
362
+ These rules sit AFTER `.sv-grid-cell` so they win at equal specificity. */
363
+ .sv-grid-detail-row {
364
+ height: auto !important;
365
+ }
366
+ .sv-grid-detail-cell {
367
+ overflow: visible;
368
+ text-overflow: clip;
369
+ white-space: normal;
370
+ height: auto;
371
+ padding: 0;
372
+ background: var(--sg-bg, #fff);
373
+ user-select: text;
374
+ }
375
+
376
+ /* Pinned columns read as "frozen" via three layered cues:
377
+ 1. A distinct tint (slightly stronger than the header) so the
378
+ pinned strip is visible even before you scroll.
379
+ 2. A solid 1px divider on the inside edge (toward the scrollable
380
+ middle) and a soft drop shadow extending into the scroll area.
381
+ 3. A thin accent stripe on the outermost edge that turns on when
382
+ the grid scrolls past the pin - a familiar Excel-style cue.
383
+ The shadow direction flips based on which side is pinned so it
384
+ always points toward the scrollable middle. */
385
+ .sv-grid-cell[data-pinned="left"],
386
+ .sv-grid-column[data-pinned="left"] {
387
+ background: var(--sg-pinned-bg, color-mix(in oklab, var(--sg-header-bg, #f1f5f9) 70%, var(--sg-accent, #2563eb) 8%));
388
+ box-shadow:
389
+ inset -1px 0 0 var(--sg-pinned-divider, var(--sg-border, #cbd5e1)),
390
+ 8px 0 12px -6px rgba(15, 23, 42, 0.22);
391
+ }
392
+ .sv-grid-cell[data-pinned="right"],
393
+ .sv-grid-column[data-pinned="right"] {
394
+ background: var(--sg-pinned-bg, color-mix(in oklab, var(--sg-header-bg, #f1f5f9) 70%, var(--sg-accent, #2563eb) 8%));
395
+ box-shadow:
396
+ inset 1px 0 0 var(--sg-pinned-divider, var(--sg-border, #cbd5e1)),
397
+ -8px 0 12px -6px rgba(15, 23, 42, 0.22);
398
+ }
399
+ /* Header row pinned cells get a slightly darker variant + bolder font
400
+ so the frozen header itself reads as part of the grid chrome. */
401
+ .sv-grid-head .sv-grid-column[data-pinned="left"],
402
+ .sv-grid-head .sv-grid-column[data-pinned="right"] {
403
+ background: var(--sg-pinned-header-bg, color-mix(in oklab, var(--sg-header-bg, #f1f5f9) 60%, var(--sg-accent, #2563eb) 14%));
404
+ font-weight: 600;
405
+ }
406
+ /* Zebra striping, opt-in via the `zebraRows` prop (which adds
407
+ `.sv-grid-row-alt` to alternating DATA rows only - pinned, group,
408
+ detail, and summary rows never get the class, so they keep a single
409
+ background). This rule sits before `.sv-grid-row-selected` /
410
+ `:hover` so those states paint over the stripe. */
411
+ .sv-grid-row-alt > .sv-grid-cell {
412
+ background: var(--sg-row-alt-bg, #f8fafc);
413
+ }
414
+ /* Zebra rows: keep the pinned tint visible (don't let the row-alt
415
+ background bleed through) by re-painting the pinned cells. */
416
+ .sv-grid-row-alt > .sv-grid-cell[data-pinned] {
417
+ background: var(--sg-pinned-bg, color-mix(in oklab, var(--sg-header-bg, #f1f5f9) 70%, var(--sg-accent, #2563eb) 8%));
418
+ }
419
+ /* When the row is selected, keep the pinned tint but layer the
420
+ selection color over it so the row still reads as selected. */
421
+ .sv-grid-row-selected > .sv-grid-cell[data-pinned] {
422
+ background:
423
+ linear-gradient(
424
+ color-mix(in srgb, var(--sg-selection-bg, #dbeafe) 65%, transparent),
425
+ color-mix(in srgb, var(--sg-selection-bg, #dbeafe) 65%, transparent)
426
+ ),
427
+ var(--sg-pinned-bg, color-mix(in oklab, var(--sg-header-bg, #f1f5f9) 70%, var(--sg-accent, #2563eb) 8%));
428
+ }
429
+ /* Hovered row keeps the pinned tint distinguishable from the hover. */
430
+ .sv-grid-row:hover > .sv-grid-cell[data-pinned] {
431
+ background:
432
+ linear-gradient(
433
+ color-mix(in srgb, var(--sg-row-hover-bg, #eef2ff) 55%, transparent),
434
+ color-mix(in srgb, var(--sg-row-hover-bg, #eef2ff) 55%, transparent)
435
+ ),
436
+ var(--sg-pinned-bg, color-mix(in oklab, var(--sg-header-bg, #f1f5f9) 70%, var(--sg-accent, #2563eb) 8%));
437
+ }
438
+
439
+ .sv-grid-column {
440
+ position: relative;
441
+ }
442
+
443
+ /* Column alignment via data-align attribute (see getColumnAlign helper).
444
+ * For body cells text-align is enough; for headers the label lives inside
445
+ * a flex container so we adjust justify-content on the sort button.
446
+ * Padding is biased to the aligned side: left-aligned text gets a 7px
447
+ * left gap; right-aligned numbers/dates get a 7px right gap. */
448
+ .sv-grid-cell[data-align="left"],
449
+ .sv-grid-column[data-align="left"] {
450
+ padding-left: var(--sg-cell-px, 7px);
451
+ }
452
+ .sv-grid-cell[data-align="right"] {
453
+ text-align: right;
454
+ padding-right: var(--sg-cell-px, 7px);
455
+ }
456
+ .sv-grid-cell[data-align="center"] {
457
+ text-align: center;
458
+ }
459
+ .sv-grid-column[data-align="right"] {
460
+ padding-right: var(--sg-cell-px, 7px);
461
+ }
462
+ .sv-grid-column[data-align="right"] .sv-grid-header-sort {
463
+ justify-content: flex-end;
464
+ }
465
+ .sv-grid-column[data-align="center"] .sv-grid-header-sort {
466
+ justify-content: center;
467
+ }
468
+
469
+ .sv-grid-resize-handle {
470
+ position: absolute;
471
+ top: 0;
472
+ right: 0;
473
+ width: 5px;
474
+ height: 100%;
475
+ cursor: col-resize;
476
+ user-select: none;
477
+ z-index: 2;
478
+ transition: background-color 100ms ease;
479
+ }
480
+
481
+ .sv-grid-resize-handle:hover,
482
+ .sv-grid-resize-handle.is-resizing {
483
+ background: color-mix(in srgb, var(--sg-accent, #0b63f3) 30%, transparent);
484
+ }
485
+
486
+ /* Row resize handle (counterpart of the column resize handle). Lives
487
+ at the bottom of the row-header gutter cell; the consumer opts in
488
+ by attaching the `rowResize` action and setting
489
+ `cellClass: 'sv-row-gutter'` on the row-header column. */
490
+ .sv-grid-row-resize-handle {
491
+ /* Geometry + interactivity are also set inline by the rowResize
492
+ action so the strip works even if this stylesheet hasn't been
493
+ parsed yet. This rule only owns the hover/active tint. */
494
+ transition: background-color 100ms ease;
495
+ }
496
+ .sv-grid-row-resize-handle:hover,
497
+ .sv-grid-row-resize-handle.is-resizing {
498
+ background: color-mix(in srgb, var(--sg-accent, #0b63f3) 30%, transparent);
499
+ }
500
+
501
+ .sv-grid-head {
502
+ position: sticky;
503
+ top: 0;
504
+ /* Above pinned body cells (z-index 30 via cellPinStyle). Without
505
+ this, when a row scrolls partially behind the sticky header,
506
+ its pinned cells (left + right) paint OVER the header while the
507
+ middle non-pinned cells are correctly hidden behind it, leaving
508
+ "floating" symbol / pnl cells at the top of the viewport. */
509
+ z-index: 35;
510
+ background: var(--sg-header-bg, #f5f7fb);
511
+ }
512
+ /* Column-header cells take the header background (and header foreground)
513
+ so the header row paints as a solid band rather than body-colored
514
+ cells over the sticky header strip. Together with the row-number
515
+ gutter below - which uses the same --sg-header-bg - the column headers
516
+ and row headers read as one continuous header chrome. */
517
+ .sv-grid-head .sv-grid-column {
518
+ background: var(--sg-header-bg, #f5f7fb);
519
+ /* Header typography is themeable so each design-system preset can match
520
+ its real grid header (e.g. Excel bold, Atlassian uppercase + subtle,
521
+ shadcn medium + muted). The label/sort-button inherit these. */
522
+ color: var(--sg-header-label-color, var(--sg-header-fg, var(--sg-fg, #0f172a)));
523
+ font-weight: var(--sg-header-weight, 600);
524
+ font-size: var(--sg-header-size, inherit);
525
+ text-transform: var(--sg-header-transform, none);
526
+ letter-spacing: var(--sg-header-tracking, normal);
527
+ }
528
+
529
+ /* Header drag-to-reorder. The `draggable` attribute is only set
530
+ * when `enableColumnReorder` is true, so the cursor and drop
531
+ * indicators only show in that mode. */
532
+ .sv-grid-column[draggable="true"] { cursor: grab; }
533
+ .sv-grid-column[draggable="true"]:active { cursor: grabbing; }
534
+ .sv-grid-column.is-dragging { opacity: 0.55; }
535
+ .sv-grid-column.is-drag-target-before,
536
+ .sv-grid-column.is-drag-target-after {
537
+ position: relative;
538
+ }
539
+ .sv-grid-column.is-drag-target-before::before,
540
+ .sv-grid-column.is-drag-target-after::after {
541
+ content: "";
542
+ position: absolute; top: 4px; bottom: 4px; width: 3px;
543
+ background: linear-gradient(180deg, #6366f1, #8b5cf6);
544
+ border-radius: 2px;
545
+ box-shadow: 0 0 6px #6366f1;
546
+ pointer-events: none;
547
+ z-index: 7;
548
+ animation: sg-drop-pulse 700ms ease-in-out infinite alternate;
549
+ }
550
+ .sv-grid-column.is-drag-target-before::before { left: -2px; }
551
+ .sv-grid-column.is-drag-target-after::after { right: -2px; }
552
+ @keyframes sg-drop-pulse { from { opacity: 0.55; } to { opacity: 1; } }
553
+
554
+ /* Managed row dragging ----------------------------------------------------- */
555
+ /* The whole row is the drag source; the row-number cell shows a grip so the
556
+ * affordance is discoverable. Pair `rowDragManaged` with `showRowNumbers` for
557
+ * the grip; without row numbers the grab cursor still hints it. */
558
+ .sv-grid-row-draggable { cursor: grab; }
559
+ .sv-grid-row-draggable:active { cursor: grabbing; }
560
+ .sv-grid-row-draggable .sv-grid-row-number-cell {
561
+ position: relative;
562
+ padding-left: 14px;
563
+ }
564
+ .sv-grid-row-draggable .sv-grid-row-number-cell::before {
565
+ content: "\2807"; /* braille grip glyph */
566
+ position: absolute;
567
+ left: 3px;
568
+ top: 50%;
569
+ transform: translateY(-50%);
570
+ font-size: 13px;
571
+ line-height: 1;
572
+ color: var(--sg-muted, #94a3b8);
573
+ opacity: 0.4;
574
+ transition: opacity 120ms ease;
575
+ pointer-events: none;
576
+ }
577
+ .sv-grid-row-draggable:hover .sv-grid-row-number-cell::before { opacity: 1; }
578
+
579
+ /* Default highlight for an external drop zone (use:rowDropZone) while a
580
+ matching row is dragged over it. Consumers can override the class. */
581
+ .sv-grid-row-dropzone-over {
582
+ outline: 2px dashed var(--sg-accent, #6366f1);
583
+ outline-offset: -2px;
584
+ background: color-mix(in srgb, var(--sg-accent, #6366f1) 10%, transparent);
585
+ }
586
+
587
+ /* Drop indicator: a glowing line on the row's top (before) or bottom (after)
588
+ * edge. Painted on the cells so it spans the full row across table layouts. */
589
+ .sv-grid-row-drop-before > td {
590
+ box-shadow: inset 0 2px 0 0 var(--sg-accent, #6366f1);
591
+ }
592
+ .sv-grid-row-drop-after > td {
593
+ box-shadow: inset 0 -2px 0 0 var(--sg-accent, #6366f1);
594
+ }
595
+
596
+ /* Pinned rows (top / bottom). Each cell sticks individually so the
597
+ * row tracks scroll on every browser. Top rows stick below the sticky
598
+ * thead via --sg-thead-h; bottom rows stick to the bottom of the
599
+ * scroll container. Tinted background distinguishes them from
600
+ * regular rows. */
601
+ .sv-grid-pinned-row td {
602
+ position: sticky;
603
+ z-index: 4;
604
+ background: var(--sg-pinned-bg, color-mix(in oklab, #6366f1 4%, #ffffff));
605
+ border-bottom: 1px solid var(--sg-pinned-border, color-mix(in oklab, #6366f1 24%, transparent));
606
+ font-weight: 600;
607
+ }
608
+ .sv-grid-pinned-row-top td { top: var(--sg-thead-h, 36px); }
609
+ .sv-grid-pinned-row-bottom td { bottom: 0; border-top: 1px solid var(--sg-pinned-border, color-mix(in oklab, #6366f1 24%, transparent)); }
610
+ /* Stack multiple pinned-top rows: row 1 stops at thead, row 2 stops
611
+ * at thead + 36px, etc. The component itself sets --sg-pinned-top-h
612
+ * to the current pinned-top stack height via $effect. */
613
+ .sv-grid-pinned-row-top[data-pinned-index="1"] td { top: calc(var(--sg-thead-h, 36px) + var(--sg-pinned-row-h, 32px)); }
614
+ .sv-grid-pinned-row-top[data-pinned-index="2"] td { top: calc(var(--sg-thead-h, 36px) + var(--sg-pinned-row-h, 32px) * 2); }
615
+ .sv-grid-pinned-row-top[data-pinned-index="3"] td { top: calc(var(--sg-thead-h, 36px) + var(--sg-pinned-row-h, 32px) * 3); }
616
+ .sv-grid-pinned-row-bottom[data-pinned-index="1"] td { bottom: var(--sg-pinned-row-h, 32px); }
617
+ .sv-grid-pinned-row-bottom[data-pinned-index="2"] td { bottom: calc(var(--sg-pinned-row-h, 32px) * 2); }
618
+ .sv-grid-pinned-row-bottom[data-pinned-index="3"] td { bottom: calc(var(--sg-pinned-row-h, 32px) * 3); }
619
+ /* Sticky-left columns keep their left position; just override their
620
+ * z so they sit above non-pinned pinned-row cells. */
621
+ .sv-grid-pinned-row td[data-pinned] { z-index: 5; }
622
+
623
+ /* Group header rows for multi-level (pivot-style) headers. Sits above
624
+ * the standard leaf header row when the column tree has nesting. */
625
+ .sv-grid-group-header-row {
626
+ background: var(--sg-header-bg, #eef2f8);
627
+ }
628
+ .sv-grid-group-header-cell {
629
+ box-sizing: border-box;
630
+ border-bottom: 1px solid var(--sg-border, #d8dee9);
631
+ border-right: 1px solid var(--sg-border, #e2e8f0);
632
+ text-align: center;
633
+ padding: 4px 8px;
634
+ font-weight: 700;
635
+ font-size: 11.5px;
636
+ color: var(--sg-header-fg, #475569);
637
+ text-transform: uppercase;
638
+ letter-spacing: 0.04em;
639
+ }
640
+ .sv-grid-group-header-placeholder {
641
+ background: transparent;
642
+ border-right-color: transparent;
643
+ }
644
+ .sv-grid-group-header-label {
645
+ display: inline-block;
646
+ max-width: 100%;
647
+ overflow: hidden;
648
+ text-overflow: ellipsis;
649
+ white-space: nowrap;
650
+ }
651
+ /* Collapse toggle on a collapsible column group header. */
652
+ .sv-grid-group-toggle {
653
+ display: inline-flex;
654
+ align-items: center;
655
+ gap: 5px;
656
+ max-width: 100%;
657
+ border: 0;
658
+ background: transparent;
659
+ color: inherit;
660
+ font: inherit;
661
+ letter-spacing: inherit;
662
+ text-transform: inherit;
663
+ cursor: pointer;
664
+ padding: 2px 6px;
665
+ border-radius: 5px;
666
+ }
667
+ .sv-grid-group-toggle:hover {
668
+ background: color-mix(in srgb, var(--sg-accent, #2563eb) 12%, transparent);
669
+ color: var(--sg-accent, #2563eb);
670
+ }
671
+ .sv-grid-group-caret {
672
+ flex-shrink: 0;
673
+ transition: transform 140ms ease;
674
+ }
675
+ /* Collapsed: point the caret right (like a closed disclosure). */
676
+ .sv-grid-group-toggle.is-collapsed .sv-grid-group-caret {
677
+ transform: rotate(-90deg);
678
+ }
679
+
680
+ .sv-grid-foot {
681
+ position: sticky;
682
+ bottom: 0;
683
+ z-index: 6;
684
+ background: var(--sg-header-bg, #f5f7fb);
685
+ }
686
+
687
+ .sv-grid-container {
688
+ scrollbar-width: auto;
689
+ -ms-overflow-style: auto;
690
+ /* Trap wheel scroll inside the grid. Without this, hitting the top
691
+ * or bottom edge of the grid lets the wheel event chain to the
692
+ * outer page (or any scrollable ancestor), so what feels like one
693
+ * wheel notch keeps scrolling something else - the "starts and
694
+ * doesn't stop" symptom. `contain` cancels the chained scroll
695
+ * but still bounces normally inside the grid. */
696
+ overscroll-behavior: contain;
697
+ /* Disable scroll anchoring. The virtualizer changes the top-spacer
698
+ * height every time the visible row window slides - modern browsers
699
+ * (default `overflow-anchor: auto`) react by quietly adjusting
700
+ * `scrollTop` to keep the visible content stable. That adjustment
701
+ * fires another `scroll` event, which fires `onBodyScroll`, which
702
+ * re-runs the virtualizer, which shifts the spacer again, which
703
+ * adjusts `scrollTop` again - a self-sustaining loop that
704
+ * presents as "the grid keeps scrolling after the wheel stops."
705
+ * Turning anchoring off lets the virtualizer own the scroll
706
+ * position outright. */
707
+ overflow-anchor: none;
708
+ /* Tell the browser that the sticky thead occupies the top
709
+ `--sg-thead-h` pixels of the scroll container. `scrollIntoView`
710
+ and friends use this to avoid scrolling a row underneath the
711
+ sticky header - critical for Excel-style keyboard nav. */
712
+ scroll-padding-top: var(--sg-thead-h, 36px);
713
+ }
714
+
715
+ .sv-grid-container.sv-grid-container-custom-scrollbars {
716
+ scrollbar-width: none;
717
+ -ms-overflow-style: none;
718
+ }
719
+
720
+ .sv-grid-container.sv-grid-container-custom-scrollbars::-webkit-scrollbar {
721
+ display: none;
722
+ }
723
+
724
+ .sv-grid-scrollbar {
725
+ position: absolute;
726
+ /* Above pinned columns (z-index 30 via cellPinStyle) so a frozen
727
+ left/right column never paints over the custom scrollbar - that
728
+ was the bug behind "vertical scrollbar hidden under the frozen
729
+ column" in grids with a pinned column at the right edge. */
730
+ z-index: 40;
731
+ }
732
+
733
+ /* Scrollbars anchor to the inline-end edge so RTL flips them to the
734
+ * left automatically. `inset-inline-end` resolves to `right` in LTR
735
+ * and `left` in RTL, no manual override needed. */
736
+ .sv-grid-scrollbar-vertical {
737
+ top: 0;
738
+ inset-inline-end: 0;
739
+ width: 16px;
740
+ height: calc(100% - 16px);
741
+ }
742
+
743
+ .sv-grid-scrollbar-corner {
744
+ position: absolute;
745
+ top: 0;
746
+ inset-inline-end: 0;
747
+ width: 16px;
748
+ background: var(--sg-header-bg, #f5f7fb);
749
+ border-bottom: 1px solid rgba(15, 23, 42, 0.08);
750
+ /* Above pinned header cells (z-index 30) so the gap above the
751
+ vertical scrollbar stays clean over a frozen column. */
752
+ z-index: 41;
753
+ pointer-events: none;
754
+ }
755
+
756
+ .sv-grid-scrollbar-corner-br {
757
+ position: absolute;
758
+ inset-inline-end: 0;
759
+ bottom: 0;
760
+ width: 16px;
761
+ height: 16px;
762
+ background: var(--sg-header-bg, #eef2f8);
763
+ box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.05);
764
+ z-index: 42;
765
+ pointer-events: none;
766
+ }
767
+
768
+ .sv-grid-scrollbar-horizontal {
769
+ inset-inline-start: 0;
770
+ bottom: 0;
771
+ width: calc(100% - 16px);
772
+ height: 16px;
773
+ }
774
+
775
+ .sv-grid-selection-column,
776
+ .sv-grid-selection-cell {
777
+ text-align: center;
778
+ padding-inline: 6px;
779
+ position: sticky;
780
+ z-index: 4;
781
+ }
782
+
783
+ /* Row-number column: sticks to the left edge of the scroll viewport. */
784
+ .sv-grid-row-number-column,
785
+ .sv-grid-row-number-cell {
786
+ text-align: right;
787
+ padding-inline: 8px;
788
+ position: sticky;
789
+ left: 0;
790
+ z-index: 4;
791
+ /* Match the column-header band so the row-number gutter and the column
792
+ headers form one continuous header chrome. */
793
+ background: var(--sg-header-bg, #f5f7fb);
794
+ color: var(--sg-muted, #64748b);
795
+ font-variant-numeric: tabular-nums;
796
+ }
797
+ .sv-grid-row-number-head {
798
+ color: inherit;
799
+ }
800
+
801
+ .sv-grid-filter-row-control {
802
+ display: flex;
803
+ flex-direction: row;
804
+ align-items: center;
805
+ gap: 4px;
806
+ }
807
+
808
+ .sv-grid-filter-operator {
809
+ flex: 2 1 0;
810
+ min-width: 0;
811
+ box-sizing: border-box;
812
+ }
813
+
814
+ .sv-grid-filter-value {
815
+ flex: 3 1 0;
816
+ min-width: 0;
817
+ box-sizing: border-box;
818
+ }
819
+
820
+ /* Brief glow used when the user clicks the funnel icon while the grid
821
+ is in row-filter mode - draws attention to where the input is so
822
+ the click isn't a no-op. */
823
+ .sv-grid-filter-value.sv-grid-filter-value-pulse {
824
+ animation: sv-grid-filter-pulse 700ms ease-out;
825
+ }
826
+ @keyframes sv-grid-filter-pulse {
827
+ 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--sg-accent, #0b63f3) 55%, transparent); }
828
+ 70% { box-shadow: 0 0 0 6px color-mix(in srgb, var(--sg-accent, #0b63f3) 0%, transparent); }
829
+ 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--sg-accent, #0b63f3) 0%, transparent); }
830
+ }
831
+
832
+ .sv-grid-checkbox {
833
+ position: relative;
834
+ display: inline-flex;
835
+ align-items: center;
836
+ justify-content: center;
837
+ width: 16px;
838
+ height: 16px;
839
+ margin: 0 auto;
840
+ border: 1px solid var(--sg-input-border, #8794a8);
841
+ border-radius: 4px;
842
+ background: var(--sg-input-bg, #fff);
843
+ color: var(--sg-accent, #0b63f3);
844
+ cursor: pointer;
845
+ transition:
846
+ border-color 120ms ease,
847
+ background-color 120ms ease;
848
+ }
849
+
850
+ /* Full-row editor: inline editors shown for every editable cell of a row
851
+ in full-row edit. Fill the cell so the row reads as a form line. */
852
+ .sv-grid-fr-editor {
853
+ width: 100%;
854
+ box-sizing: border-box;
855
+ }
856
+ select.sv-grid-fr-editor {
857
+ height: 100%;
858
+ min-height: 26px;
859
+ }
860
+ .sv-grid-fr-checkbox {
861
+ width: 16px;
862
+ height: 16px;
863
+ accent-color: var(--sg-accent, #3b82f6);
864
+ cursor: pointer;
865
+ }
866
+
867
+ /* Color editor - native HTML color input filling the cell so the
868
+ swatch is clickable across the whole cell area, not just the tiny
869
+ OS default size. */
870
+ .sv-grid-cell-editor-color {
871
+ width: 100%;
872
+ height: 100%;
873
+ padding: 2px;
874
+ border: 0;
875
+ background: transparent;
876
+ cursor: pointer;
877
+ }
878
+ .sv-grid-cell-editor-color::-webkit-color-swatch-wrapper { padding: 0; }
879
+ .sv-grid-cell-editor-color::-webkit-color-swatch { border: 0; border-radius: 4px; }
880
+ .sv-grid-cell-editor-color::-moz-color-swatch { border: 0; border-radius: 4px; }
881
+
882
+ /* Rating editor - 5 clickable stars + clear button. Renders inline
883
+ in the cell when editorType="rating"; the readonly view renders a
884
+ filled-star count via the default text path (or a snippet cell). */
885
+ .sv-grid-rating-editor {
886
+ display: inline-flex;
887
+ align-items: center;
888
+ gap: 1px;
889
+ height: 100%;
890
+ }
891
+ .sv-grid-rating-star {
892
+ border: 0; background: transparent; cursor: pointer;
893
+ color: var(--sg-rating-empty, #cbd5e1);
894
+ font-size: 18px; line-height: 1;
895
+ padding: 0 1px;
896
+ transition: color 80ms ease, transform 80ms ease;
897
+ }
898
+ .sv-grid-rating-star-on { color: var(--sg-rating-on, #f59e0b); }
899
+ .sv-grid-rating-star:hover { color: var(--sg-rating-hover, #fbbf24); transform: scale(1.12); }
900
+ .sv-grid-rating-clear {
901
+ margin-left: 4px;
902
+ border: 0; background: transparent;
903
+ color: var(--sg-muted, #94a3b8);
904
+ cursor: pointer; font-size: 14px;
905
+ }
906
+ .sv-grid-rating-clear:hover { color: var(--sg-fg, #0f172a); }
907
+
908
+ .sv-grid-checkbox[aria-checked="true"],
909
+ .sv-grid-checkbox[aria-checked="mixed"] {
910
+ border-color: var(--sg-accent, #0b63f3);
911
+ background: var(--sg-selection-bg, #eaf2ff);
912
+ }
913
+
914
+ .sv-grid-checkbox[aria-checked="true"]::after {
915
+ content: "";
916
+ width: 5px;
917
+ height: 9px;
918
+ border-right: 2px solid currentColor;
919
+ border-bottom: 2px solid currentColor;
920
+ transform: rotate(40deg) translate(-1px, -1px);
921
+ }
922
+
923
+ .sv-grid-checkbox[aria-checked="mixed"]::after {
924
+ content: "";
925
+ width: 8px;
926
+ height: 2px;
927
+ border-radius: 1px;
928
+ background: currentColor;
929
+ }
930
+
931
+ .sv-grid-checkbox:focus-visible {
932
+ outline: 2px solid var(--sg-accent, #0b63f3);
933
+ outline-offset: 1px;
934
+ }
935
+
936
+ .sv-grid-checkbox-readonly {
937
+ cursor: default;
938
+ pointer-events: none;
939
+ }
940
+
941
+ /* Built-in cell flash (ColumnDef `cellFlash`). Theme-tinted fade; consumers
942
+ can pass their own class (e.g. up/down colouring) via `cellFlash.className`. */
943
+ @keyframes sv-grid-cell-flash-kf {
944
+ 0% { background-color: var(--sg-cell-flash, color-mix(in oklab, var(--sg-accent, #3b82f6) 42%, transparent)); }
945
+ 100% { background-color: transparent; }
946
+ }
947
+ .sv-grid-cell-flash {
948
+ animation: sv-grid-cell-flash-kf 0.7s ease-out;
949
+ }
950
+ @media (prefers-reduced-motion: reduce) {
951
+ .sv-grid-cell-flash { animation: none; }
952
+ }
953
+
954
+ .sv-grid-cell-active,
955
+ .sv-grid-cell-editing {
956
+ position: relative;
957
+ /* High enough to sit above pinned-column stacking contexts (z=4) so the
958
+ list/chips popover overlays adjacent cells instead of being clipped. */
959
+ z-index: 20;
960
+ /* `box-shadow: inset` is bound to the cell's own box, so the ring
961
+ clips cleanly with the cell when it's partially scrolled out of
962
+ view (and shows up consistently at the right edge of a virtualized
963
+ window). The previous `outline + outline-offset: -2px` rendered at
964
+ the wrong position on clipped cells in some browsers - it's drawn
965
+ outside the layout box, so the negative offset's effect was
966
+ inconsistent at scroll boundaries. */
967
+ box-shadow: inset 0 0 0 2px var(--sg-accent, #0b63f3);
968
+ outline: none;
969
+ /* Let list/chips popouts extend past the cell box (default cells have
970
+ overflow: hidden so the chips picker would otherwise be clipped). */
971
+ overflow: visible;
972
+ }
973
+
974
+ /* Excel-style fill handle: a 7×7 blue square anchored to the bottom-
975
+ right corner of the active cell or selection range. The parent
976
+ `.sv-grid-cell` is `position: relative` (sticky cells override
977
+ that with sticky - which is also a positioning context), so the
978
+ handle anchors to the cell box. */
979
+ /* Excel-style per-cell note indicator. A small triangle in the top-
980
+ * right corner that, when hovered, shows the note tooltip. The
981
+ * triangle IS the hot-zone (12×12 square clipped to a triangle via
982
+ * border tricks); a transparent invisible square on top widens the
983
+ * hit target a little so it's not painful to hit. */
984
+ .sv-grid-cell-note-corner {
985
+ position: absolute;
986
+ top: 0; right: 0;
987
+ width: 12px; height: 12px;
988
+ /* Triangle: 0,0 → 12,0 → 12,12 */
989
+ background:
990
+ linear-gradient(45deg, transparent 50%, var(--sg-note-corner, #f59e0b) 50%);
991
+ cursor: help;
992
+ z-index: 6;
993
+ }
994
+ /* Slightly larger invisible hit area so users don't have to land on
995
+ a 6×6 pixel triangle. */
996
+ .sv-grid-cell-note-corner::before {
997
+ content: '';
998
+ position: absolute;
999
+ inset: -3px -3px 0 0;
1000
+ }
1001
+
1002
+ .sv-grid-fill-handle {
1003
+ position: absolute;
1004
+ /* Sit fully INSIDE the cell box so `overflow: hidden` doesn't clip
1005
+ most of the handle. Previously the handle stuck 3px outside the
1006
+ cell and the visible 4×4 corner was nearly unclickable - especially
1007
+ on chip cells where pills filled the bottom edge. */
1008
+ bottom: 0;
1009
+ right: 0;
1010
+ width: 9px;
1011
+ height: 9px;
1012
+ background: var(--sg-accent, #0b63f3);
1013
+ border: 1px solid var(--sg-bg, #ffffff);
1014
+ box-sizing: border-box;
1015
+ cursor: crosshair;
1016
+ z-index: 15;
1017
+ touch-action: none;
1018
+ }
1019
+ .sv-grid-fill-handle:hover {
1020
+ transform: scale(1.3);
1021
+ transform-origin: bottom right;
1022
+ }
1023
+
1024
+ /* Fill-preview overlay paints an accent border on the cells the user is
1025
+ about to fill. Source cells (still in the selection range) keep their
1026
+ normal range styling. The border + tint derive from the theme accent
1027
+ (`--sg-accent`) so they match each theme instead of a fixed blue;
1028
+ `--sg-fill-preview-border` / `--sg-fill-preview-bg` let a theme override
1029
+ them directly. */
1030
+ .sv-grid-cell[data-fill-preview="true"] {
1031
+ box-shadow: inset 0 0 0 2px
1032
+ var(--sg-fill-preview-border,
1033
+ color-mix(in srgb, var(--sg-accent, #0b63f3) 50%, transparent));
1034
+ background: var(--sg-fill-preview-bg,
1035
+ color-mix(in srgb, var(--sg-accent, #0b63f3) 8%, transparent));
1036
+ }
1037
+
1038
+ .sv-grid-cell-editing {
1039
+ padding: 0;
1040
+ }
1041
+
1042
+ /* The four CSS variables let us compose a single box-shadow that draws the
1043
+ selection rectangle outline only on the cells that sit on its edges. */
1044
+ .sv-grid-cell[data-selected-range="true"] {
1045
+ background: var(--sg-selection-bg, #eef4ff);
1046
+ --sv-range-top: 0 0 0 transparent;
1047
+ --sv-range-bottom: 0 0 0 transparent;
1048
+ --sv-range-left: 0 0 0 transparent;
1049
+ --sv-range-right: 0 0 0 transparent;
1050
+ box-shadow: var(--sv-range-top), var(--sv-range-bottom),
1051
+ var(--sv-range-left), var(--sv-range-right);
1052
+ }
1053
+
1054
+ /* Selection-range edges as inset box-shadows. We tried `border-*: 2px
1055
+ solid` to dodge the 1px-notch artifact at cell boundaries, but
1056
+ border width changes force a full table reflow on every selection
1057
+ change - catastrophic during keyboard nav (every arrow key pressed
1058
+ re-flows ~30 visible rows + their neighbours under border-collapse).
1059
+ Box-shadow inset paints on the compositor only - no reflow. */
1060
+ .sv-grid-cell[data-range-top="true"] {
1061
+ --sv-range-top: inset 0 2px 0 var(--sg-accent, #0b63f3);
1062
+ }
1063
+ .sv-grid-cell[data-range-bottom="true"] {
1064
+ --sv-range-bottom: inset 0 -2px 0 var(--sg-accent, #0b63f3);
1065
+ }
1066
+ .sv-grid-cell[data-range-left="true"] {
1067
+ --sv-range-left: inset 2px 0 0 var(--sg-accent, #0b63f3);
1068
+ }
1069
+ .sv-grid-cell[data-range-right="true"] {
1070
+ --sv-range-right: inset -2px 0 0 var(--sg-accent, #0b63f3);
1071
+ }
1072
+
1073
+ /* Lift edge cells above their neighbours so the inset shadow renders
1074
+ above adjacent cells' (collapsed) borders rather than under them. */
1075
+ .sv-grid-cell[data-range-top="true"],
1076
+ .sv-grid-cell[data-range-bottom="true"],
1077
+ .sv-grid-cell[data-range-left="true"],
1078
+ .sv-grid-cell[data-range-right="true"],
1079
+ .sv-grid-cell.sv-merge-edge-right,
1080
+ .sv-grid-cell.sv-merge-edge-bottom {
1081
+ position: relative;
1082
+ z-index: 3;
1083
+ }
1084
+
1085
+ /* Inherited selection-range edges from `spreadsheetLayout` merges.
1086
+ Same inset-shadow technique - no layout impact when toggling. */
1087
+ .sv-grid-cell.sv-merge-edge-right {
1088
+ box-shadow: inset -2px 0 0 var(--sg-accent, #0b63f3);
1089
+ }
1090
+ .sv-grid-cell.sv-merge-edge-bottom {
1091
+ box-shadow: inset 0 -2px 0 var(--sg-accent, #0b63f3);
1092
+ }
1093
+ .sv-grid-cell.sv-merge-in-range {
1094
+ background: var(--sg-selection-bg, #eef4ff);
1095
+ }
1096
+
1097
+ .sv-grid-cell-editor {
1098
+ position: absolute;
1099
+ inset: 0;
1100
+ width: 100%;
1101
+ height: 100%;
1102
+ border: 0;
1103
+ outline: none;
1104
+ background: var(--sg-input-bg, #fff);
1105
+ box-sizing: border-box;
1106
+ font: inherit;
1107
+ color: inherit;
1108
+ padding: 0 8px;
1109
+ }
1110
+
1111
+ /* Filter-related inputs share the same focused-cell outline treatment. */
1112
+ .sv-grid-global-filter input:focus,
1113
+ .sv-grid-filter-value:focus,
1114
+ .sv-grid-column-filter:focus,
1115
+ .sv-grid-menu-search:focus,
1116
+ .sv-grid-menu-condition-value:focus,
1117
+ .sv-grid-menu-operator:focus {
1118
+ outline: 2px solid var(--sg-accent, #0b63f3);
1119
+ outline-offset: -2px;
1120
+ border-color: var(--sg-accent, #0b63f3);
1121
+ }
1122
+
1123
+ .sv-grid-cell-editor-number,
1124
+ .sv-grid-cell-editor-date,
1125
+ .sv-grid-cell-editor-datetime {
1126
+ width: 100%;
1127
+ height: 100%;
1128
+ }
1129
+
1130
+ /* List editor: native <select>. For multi-select it grows into a
1131
+ small popup-style listbox that hangs over the cell. */
1132
+ .sv-grid-cell-editor-list {
1133
+ width: 100%;
1134
+ height: 100%;
1135
+ appearance: auto;
1136
+ font: inherit;
1137
+ color: inherit;
1138
+ background: var(--sg-input-bg, #fff);
1139
+ border: 0;
1140
+ padding: 0 6px;
1141
+ box-sizing: border-box;
1142
+ }
1143
+ .sv-grid-cell-editor-list[multiple] {
1144
+ position: absolute;
1145
+ top: 0;
1146
+ left: 0;
1147
+ right: 0;
1148
+ height: auto;
1149
+ min-height: 100%;
1150
+ max-height: 220px;
1151
+ z-index: 5;
1152
+ padding: 4px;
1153
+ border: 1px solid var(--sg-accent, #0b63f3);
1154
+ background: var(--sg-input-bg, #fff);
1155
+ box-shadow: 0 8px 24px rgba(15, 23, 42, 0.18);
1156
+ }
1157
+
1158
+ /* Free-form chips editor (multi-value cell with removable tokens).
1159
+ Absolutely positioned so it can grow downward past the row without
1160
+ pushing other rows. Theme-aware background + accent border + drop
1161
+ shadow so it reads as a popout above the next row, not as part of
1162
+ it. The cell's outer outline is suppressed for this editor - this
1163
+ popout already provides its own border. */
1164
+ .sv-grid-cell-editor-chips {
1165
+ position: absolute;
1166
+ top: -1px;
1167
+ left: -1px;
1168
+ right: -1px;
1169
+ min-height: calc(100% + 2px);
1170
+ background: var(--sg-header-bg, var(--sg-bg, #ffffff));
1171
+ color: var(--sg-fg, #0f172a);
1172
+ padding: 4px 6px;
1173
+ box-sizing: border-box;
1174
+ z-index: 1000;
1175
+ overflow: visible;
1176
+ display: flex;
1177
+ align-items: flex-start;
1178
+ border: 1px solid var(--sg-accent, #2563eb);
1179
+ border-radius: var(--sg-radius, 6px);
1180
+ box-shadow: 0 12px 28px rgba(0, 0, 0, 0.45);
1181
+ }
1182
+ /* When the chips editor is mounted, the parent .sv-grid-cell-editing
1183
+ no longer needs its own outline - the popout already shows one. */
1184
+ .sv-grid-cell-editing:has(.sv-grid-cell-editor-chips) {
1185
+ outline: none;
1186
+ }
1187
+ .sv-grid-chips-row {
1188
+ display: flex;
1189
+ flex-wrap: wrap;
1190
+ align-items: center;
1191
+ gap: 4px;
1192
+ width: 100%;
1193
+ }
1194
+ .sv-grid-chip-input {
1195
+ flex: 1 1 80px;
1196
+ min-width: 60px;
1197
+ border: 0;
1198
+ outline: none;
1199
+ background: transparent;
1200
+ font: inherit;
1201
+ color: inherit;
1202
+ padding: 2px 4px;
1203
+ }
1204
+ .sv-grid-chip-picker {
1205
+ flex: 1 1 100px;
1206
+ min-width: 80px;
1207
+ font: inherit;
1208
+ color: inherit;
1209
+ background: var(--sg-bg, #fff);
1210
+ border: 1px solid rgba(15, 23, 42, 0.15);
1211
+ border-radius: 4px;
1212
+ padding: 2px 4px;
1213
+ }
1214
+ .sv-grid-chip-commit {
1215
+ font-size: 11px;
1216
+ text-transform: uppercase;
1217
+ letter-spacing: 0.04em;
1218
+ color: #fff;
1219
+ background: var(--sg-accent, #0b63f3);
1220
+ border: 0;
1221
+ border-radius: 4px;
1222
+ padding: 3px 8px;
1223
+ cursor: pointer;
1224
+ }
1225
+ .sv-grid-chip-commit:hover {
1226
+ filter: brightness(1.1);
1227
+ }
1228
+
1229
+ /* Chip badge - used both in readonly cell display and inside the
1230
+ chips editor. Subtle neutral chrome, scales to the row's font size.
1231
+ Readonly chip rows stay on one line (the parent <td> has
1232
+ overflow: hidden, so overflowing chips get clipped - widen the
1233
+ column or open the editor to see all). */
1234
+ .sv-grid-chips-display {
1235
+ display: inline-flex;
1236
+ flex-wrap: nowrap;
1237
+ align-items: center;
1238
+ gap: 4px;
1239
+ max-width: 100%;
1240
+ vertical-align: middle;
1241
+ }
1242
+ .sv-grid-sparkline {
1243
+ display: inline-block;
1244
+ vertical-align: middle;
1245
+ overflow: visible;
1246
+ }
1247
+ /* Conditional formatting overlays - behind the cell text. */
1248
+ .sv-grid-cf-bg {
1249
+ position: absolute;
1250
+ inset: 0;
1251
+ z-index: 0;
1252
+ pointer-events: none;
1253
+ }
1254
+ .sv-grid-cf-bar {
1255
+ position: absolute;
1256
+ left: 0;
1257
+ top: 0;
1258
+ bottom: 0;
1259
+ z-index: 0;
1260
+ opacity: 0.85;
1261
+ border-radius: 0 2px 2px 0;
1262
+ pointer-events: none;
1263
+ }
1264
+ .sv-grid-cell-cf {
1265
+ position: relative;
1266
+ }
1267
+ .sv-grid-status-bar {
1268
+ display: flex;
1269
+ flex-wrap: wrap;
1270
+ align-items: center;
1271
+ gap: 18px;
1272
+ padding: 6px 14px;
1273
+ font-size: 12px;
1274
+ color: var(--sg-fg);
1275
+ background: var(--sg-header-bg, #f1f5f9);
1276
+ border: 1px solid var(--sg-border, #e2e8f0);
1277
+ border-top: 0;
1278
+ flex-shrink: 0;
1279
+ }
1280
+ .sv-grid-status-item {
1281
+ font-variant-numeric: tabular-nums;
1282
+ font-weight: 600;
1283
+ }
1284
+ .sv-grid-status-label {
1285
+ color: var(--sg-muted, #64748b);
1286
+ font-weight: 400;
1287
+ margin-right: 5px;
1288
+ }
1289
+ .sv-grid-cf-content {
1290
+ position: relative;
1291
+ z-index: 1;
1292
+ }
1293
+ .sv-grid-cf-icon {
1294
+ margin-right: 5px;
1295
+ font-size: 0.95em;
1296
+ }
1297
+ .sv-grid-chip {
1298
+ display: inline-flex;
1299
+ align-items: center;
1300
+ gap: 4px;
1301
+ padding: 2px 8px;
1302
+ font-size: 0.85em;
1303
+ line-height: 1.4;
1304
+ background: color-mix(in srgb, var(--sg-accent, #2563eb) 18%, transparent);
1305
+ color: var(--sg-fg, inherit);
1306
+ border-radius: 999px;
1307
+ border: 1px solid
1308
+ color-mix(in srgb, var(--sg-accent, #2563eb) 35%, transparent);
1309
+ white-space: nowrap;
1310
+ }
1311
+ .sv-grid-chip-removable {
1312
+ padding-right: 2px;
1313
+ }
1314
+ .sv-grid-chip-remove {
1315
+ display: inline-flex;
1316
+ align-items: center;
1317
+ justify-content: center;
1318
+ width: 16px;
1319
+ height: 16px;
1320
+ border: 0;
1321
+ border-radius: 999px;
1322
+ background: color-mix(in srgb, var(--sg-fg, #0f172a) 18%, transparent);
1323
+ color: inherit;
1324
+ font-size: 12px;
1325
+ line-height: 1;
1326
+ cursor: pointer;
1327
+ padding: 0;
1328
+ }
1329
+ .sv-grid-chip-remove:hover {
1330
+ background: rgba(220, 38, 38, 0.7);
1331
+ color: #fff;
1332
+ }
1333
+
1334
+ .sv-grid-icon {
1335
+ width: 1em;
1336
+ height: 1em;
1337
+ display: block;
1338
+ flex: none;
1339
+ }
1340
+
1341
+ .sv-grid-header-cell {
1342
+ display: flex;
1343
+ align-items: center;
1344
+ gap: 2px;
1345
+ width: 100%;
1346
+ min-width: 0;
1347
+ /* Themeable header height. Defaults to auto (content-driven); presets
1348
+ can set a taller header (e.g. Material's roomy data-table header).
1349
+ The component measures the rendered header height for sticky offsets,
1350
+ so this propagates to --sg-thead-h automatically. */
1351
+ min-height: var(--sg-header-min-height, auto);
1352
+ }
1353
+
1354
+ .sv-grid-header-sort {
1355
+ display: flex;
1356
+ align-items: center;
1357
+ gap: 4px;
1358
+ flex: 1 1 auto;
1359
+ min-width: 0;
1360
+ padding: 2px;
1361
+ border: 0;
1362
+ background: transparent;
1363
+ font: inherit;
1364
+ color: inherit;
1365
+ text-align: left;
1366
+ cursor: pointer;
1367
+ }
1368
+
1369
+ /* Custom (function-valued) header rendering. Behaves like the sort
1370
+ button but is a div so the consumer's snippet can contain its own
1371
+ interactive elements (menu buttons, dropdowns, etc) - button-in-
1372
+ button DOM is invalid. */
1373
+ .sv-grid-header-custom {
1374
+ display: flex;
1375
+ align-items: center;
1376
+ gap: 4px;
1377
+ flex: 1 1 auto;
1378
+ min-width: 0;
1379
+ padding: 2px;
1380
+ text-align: left;
1381
+ cursor: pointer;
1382
+ outline: none;
1383
+ }
1384
+
1385
+ .sv-grid-header-label {
1386
+ /* min-width:0 lets this flex item shrink below its text width so a long
1387
+ label truncates to a single line with an ellipsis instead of overflowing
1388
+ or forcing the header taller. flex:0 1 auto keeps the sort icon snug to
1389
+ the label rather than pushed to the far edge. */
1390
+ flex: 0 1 auto;
1391
+ min-width: 0;
1392
+ overflow: hidden;
1393
+ text-overflow: ellipsis;
1394
+ white-space: nowrap;
1395
+ }
1396
+
1397
+ .sv-grid-header-icon {
1398
+ display: inline-flex;
1399
+ flex: none;
1400
+ font-size: 13px;
1401
+ color: var(--sg-muted, #5b6b85);
1402
+ }
1403
+
1404
+ .sv-grid-header-icon-hint {
1405
+ opacity: 0;
1406
+ transition: opacity 120ms ease;
1407
+ }
1408
+
1409
+ .sv-grid-column:hover .sv-grid-header-icon-hint {
1410
+ opacity: 0.4;
1411
+ }
1412
+
1413
+ .sv-grid-header-icon-flag {
1414
+ color: var(--sg-accent, #0b63f3);
1415
+ }
1416
+
1417
+ .sv-grid-col-menu-btn {
1418
+ display: inline-flex;
1419
+ align-items: center;
1420
+ justify-content: center;
1421
+ flex: none;
1422
+ /* Collapse to zero width when invisible so a non-hovered column
1423
+ has no dead space at the right; the funnel (when an active
1424
+ filter is on) can then sit flush against the right edge.
1425
+ Hover / focus / open expand it back to 22px. */
1426
+ width: 0;
1427
+ height: 22px;
1428
+ padding: 0;
1429
+ border: 0;
1430
+ border-radius: 4px;
1431
+ background: transparent;
1432
+ color: var(--sg-muted, #5b6b85);
1433
+ font-size: 15px;
1434
+ cursor: pointer;
1435
+ opacity: 0;
1436
+ pointer-events: none;
1437
+ overflow: hidden;
1438
+ transition:
1439
+ width 140ms ease,
1440
+ opacity 140ms ease,
1441
+ background-color 120ms ease;
1442
+ }
1443
+
1444
+ .sv-grid-column:hover .sv-grid-col-menu-btn,
1445
+ .sv-grid-col-menu-btn.is-open,
1446
+ .sv-grid-col-menu-btn:focus-visible {
1447
+ width: 22px;
1448
+ opacity: 1;
1449
+ pointer-events: auto;
1450
+ }
1451
+
1452
+ /* The funnel filter button is a sibling .sv-grid-col-menu-btn that
1453
+ ALSO carries .sv-grid-col-filter-btn. When the column has an
1454
+ active filter (.is-active) the funnel stays visible at full size
1455
+ even when the column isn't hovered - so users can see at a glance
1456
+ which columns are filtered. Because the 3-dot menu still collapses
1457
+ to width: 0 in that state, the funnel ends up flush against the
1458
+ right edge. On hover, the menu expands back to 22px and pushes the
1459
+ funnel left naturally. */
1460
+ .sv-grid-col-filter-btn.is-active {
1461
+ width: 22px;
1462
+ opacity: 1;
1463
+ pointer-events: auto;
1464
+ }
1465
+
1466
+ .sv-grid-col-menu-btn:hover,
1467
+ .sv-grid-col-menu-btn.is-open {
1468
+ background: var(--sg-row-hover-bg, #e2e8f3);
1469
+ }
1470
+
1471
+ .sv-grid-filter-operator-btn {
1472
+ display: inline-flex;
1473
+ align-items: center;
1474
+ gap: 2px;
1475
+ flex: none;
1476
+ height: 24px;
1477
+ padding: 0 4px;
1478
+ border: 1px solid var(--sg-input-border, #cbd5e1);
1479
+ border-radius: 4px;
1480
+ background: var(--sg-input-bg, #fff);
1481
+ color: var(--sg-fg, #334155);
1482
+ font-size: 13px;
1483
+ cursor: pointer;
1484
+ }
1485
+
1486
+ .sv-grid-filter-operator-btn.is-open {
1487
+ border-color: var(--sg-accent, #0b63f3);
1488
+ }
1489
+
1490
+ .sv-grid-caret {
1491
+ display: inline-flex;
1492
+ font-size: 9px;
1493
+ color: var(--sg-muted, #94a3b8);
1494
+ }
1495
+
1496
+ .sv-grid-menu-backdrop {
1497
+ position: fixed;
1498
+ inset: 0;
1499
+ z-index: 900;
1500
+ background: transparent;
1501
+ }
1502
+
1503
+ .sv-grid-menu {
1504
+ position: fixed;
1505
+ z-index: 901;
1506
+ max-height: calc(100vh - 24px);
1507
+ overflow: auto;
1508
+ padding: 4px;
1509
+ background: var(--sg-bg, #fff);
1510
+ border: 1px solid var(--sg-border, #d6dee9);
1511
+ border-radius: 8px;
1512
+ box-shadow: 0 12px 28px rgba(15, 23, 42, 0.18);
1513
+ font-size: 13px;
1514
+ color: var(--sg-fg, #1f2937);
1515
+ }
1516
+
1517
+ .sv-grid-column-menu {
1518
+ width: 260px;
1519
+ }
1520
+ /* Column-menu tab bar (General / Filter / Columns). */
1521
+ .sv-grid-menu-tabs {
1522
+ display: flex;
1523
+ gap: 2px;
1524
+ margin: -2px -2px 4px;
1525
+ padding: 2px 2px 0;
1526
+ border-bottom: 1px solid var(--sg-border, #e2e8f0);
1527
+ }
1528
+ .sv-grid-menu-tab {
1529
+ flex: 1;
1530
+ padding: 6px 6px;
1531
+ border: 0;
1532
+ border-bottom: 2px solid transparent;
1533
+ background: transparent;
1534
+ color: var(--sg-muted, #64748b);
1535
+ font-size: 12px;
1536
+ font-weight: 600;
1537
+ cursor: pointer;
1538
+ }
1539
+ .sv-grid-menu-tab:hover {
1540
+ color: var(--sg-fg, #0f172a);
1541
+ }
1542
+ .sv-grid-menu-tab.is-active {
1543
+ color: var(--sg-accent, #2563eb);
1544
+ border-bottom-color: var(--sg-accent, #2563eb);
1545
+ }
1546
+
1547
+ .sv-grid-operator-menu {
1548
+ width: 184px;
1549
+ }
1550
+
1551
+ .sv-grid-context-menu {
1552
+ min-width: 180px;
1553
+ }
1554
+
1555
+ .sv-grid-comment-editor {
1556
+ width: 240px;
1557
+ padding: 8px;
1558
+ }
1559
+ .sv-grid-comment-textarea {
1560
+ width: 100%;
1561
+ resize: vertical;
1562
+ min-height: 60px;
1563
+ padding: 6px 8px;
1564
+ border: 1px solid var(--sg-input-border, #cbd5e1);
1565
+ border-radius: 4px;
1566
+ background: var(--sg-input-bg, #fff);
1567
+ color: var(--sg-fg, #0f172a);
1568
+ font: inherit;
1569
+ font-size: 13px;
1570
+ outline: none;
1571
+ }
1572
+ .sv-grid-comment-textarea:focus {
1573
+ border-color: var(--sg-accent, #6366f1);
1574
+ }
1575
+ .sv-grid-comment-actions {
1576
+ display: flex;
1577
+ align-items: center;
1578
+ gap: 6px;
1579
+ margin-top: 8px;
1580
+ }
1581
+ .sv-grid-comment-spacer {
1582
+ flex: 1 1 auto;
1583
+ }
1584
+ .sv-grid-comment-actions button {
1585
+ padding: 4px 10px;
1586
+ border-radius: 4px;
1587
+ border: 1px solid var(--sg-border, #cbd5e1);
1588
+ background: var(--sg-bg, #fff);
1589
+ color: var(--sg-fg, #1f2937);
1590
+ font: inherit;
1591
+ font-size: 12px;
1592
+ cursor: pointer;
1593
+ }
1594
+ .sv-grid-comment-save {
1595
+ border-color: var(--sg-accent, #6366f1) !important;
1596
+ background: var(--sg-accent, #6366f1) !important;
1597
+ color: #fff !important;
1598
+ }
1599
+ .sv-grid-comment-remove {
1600
+ color: var(--sg-danger, #dc2626) !important;
1601
+ }
1602
+
1603
+ .sv-grid-menu-item {
1604
+ display: flex;
1605
+ align-items: center;
1606
+ gap: 8px;
1607
+ width: 100%;
1608
+ padding: 7px 10px;
1609
+ border: 0;
1610
+ border-radius: 5px;
1611
+ background: transparent;
1612
+ color: inherit;
1613
+ font: inherit;
1614
+ text-align: left;
1615
+ cursor: pointer;
1616
+ }
1617
+
1618
+ .sv-grid-menu-item:hover {
1619
+ background: var(--sg-row-hover-bg, #eef2f8);
1620
+ }
1621
+
1622
+ .sv-grid-menu-item[aria-checked="true"] {
1623
+ background: var(--sg-selection-bg, #eaf2ff);
1624
+ color: var(--sg-accent, #0b63f3);
1625
+ }
1626
+
1627
+ .sv-grid-menu-sep {
1628
+ height: 1px;
1629
+ margin: 4px 6px;
1630
+ background: var(--sg-border, #e6ebf2);
1631
+ }
1632
+
1633
+ .sv-grid-menu-filter {
1634
+ padding: 4px 6px 6px;
1635
+ }
1636
+
1637
+ .sv-grid-menu-filter-head {
1638
+ display: flex;
1639
+ align-items: center;
1640
+ gap: 6px;
1641
+ padding: 2px 0 6px;
1642
+ font-weight: 600;
1643
+ color: var(--sg-muted, #475569);
1644
+ }
1645
+
1646
+ .sv-grid-menu-search {
1647
+ width: 100%;
1648
+ height: 28px;
1649
+ box-sizing: border-box;
1650
+ margin-bottom: 6px;
1651
+ padding: 0 8px;
1652
+ border: 1px solid var(--sg-input-border, #cbd5e1);
1653
+ border-radius: 5px;
1654
+ font: inherit;
1655
+ }
1656
+
1657
+ .sv-grid-menu-filter-row {
1658
+ display: flex;
1659
+ gap: 4px;
1660
+ margin-bottom: 6px;
1661
+ }
1662
+
1663
+ .sv-grid-menu-operator-group {
1664
+ display: flex;
1665
+ gap: 2px;
1666
+ margin-bottom: 6px;
1667
+ }
1668
+
1669
+ .sv-grid-menu-operator-btn {
1670
+ flex: 1 1 0;
1671
+ display: inline-flex;
1672
+ align-items: center;
1673
+ justify-content: center;
1674
+ height: 28px;
1675
+ padding: 0;
1676
+ border: 1px solid var(--sg-input-border, #cbd5e1);
1677
+ border-radius: 4px;
1678
+ background: var(--sg-input-bg, #fff);
1679
+ color: var(--sg-fg, #475569);
1680
+ cursor: pointer;
1681
+ transition:
1682
+ background-color 100ms ease,
1683
+ color 100ms ease,
1684
+ border-color 100ms ease;
1685
+ }
1686
+
1687
+ .sv-grid-menu-operator-btn:hover {
1688
+ background: var(--sg-row-hover-bg, #eef2f8);
1689
+ }
1690
+
1691
+ .sv-grid-menu-operator-btn.is-active {
1692
+ background: var(--sg-accent, #0b63f3);
1693
+ border-color: var(--sg-accent, #0b63f3);
1694
+ color: #fff;
1695
+ }
1696
+
1697
+ .sv-grid-menu-condition-value {
1698
+ flex: 1 1 0;
1699
+ min-width: 0;
1700
+ height: 28px;
1701
+ box-sizing: border-box;
1702
+ padding: 0 8px;
1703
+ border: 1px solid var(--sg-input-border, #cbd5e1);
1704
+ border-radius: 5px;
1705
+ font: inherit;
1706
+ }
1707
+
1708
+ /* Multi-condition filter: AND/OR join toggle + add-condition button. */
1709
+ .sv-grid-menu-add-cond {
1710
+ align-self: flex-start;
1711
+ margin-top: 2px;
1712
+ padding: 3px 8px;
1713
+ font-size: 11.5px;
1714
+ font-weight: 600;
1715
+ color: var(--sg-accent, #3b82f6);
1716
+ background: transparent;
1717
+ border: 1px dashed var(--sg-border, #cbd5e1);
1718
+ border-radius: 5px;
1719
+ cursor: pointer;
1720
+ }
1721
+ .sv-grid-menu-add-cond:hover {
1722
+ background: color-mix(in oklab, var(--sg-accent, #3b82f6) 8%, transparent);
1723
+ border-style: solid;
1724
+ }
1725
+ .sv-grid-menu-join {
1726
+ display: inline-flex;
1727
+ align-items: center;
1728
+ gap: 2px;
1729
+ margin: 2px 0;
1730
+ }
1731
+ .sv-grid-menu-join button {
1732
+ padding: 2px 10px;
1733
+ font-size: 11px;
1734
+ font-weight: 700;
1735
+ color: var(--sg-muted, #64748b);
1736
+ background: transparent;
1737
+ border: 1px solid var(--sg-border, #cbd5e1);
1738
+ cursor: pointer;
1739
+ }
1740
+ .sv-grid-menu-join button:first-child { border-radius: 5px 0 0 5px; }
1741
+ .sv-grid-menu-join button:nth-child(2) { border-radius: 0; border-left: 0; }
1742
+ .sv-grid-menu-join button.is-on {
1743
+ background: var(--sg-accent, #3b82f6);
1744
+ color: #fff;
1745
+ border-color: var(--sg-accent, #3b82f6);
1746
+ }
1747
+ .sv-grid-menu-join-x {
1748
+ margin-left: auto;
1749
+ border-radius: 5px !important;
1750
+ font-size: 14px;
1751
+ line-height: 1;
1752
+ padding: 2px 7px !important;
1753
+ }
1754
+
1755
+ .sv-grid-facet-list {
1756
+ max-height: 200px;
1757
+ overflow: auto;
1758
+ border: 1px solid var(--sg-border, #eaeef4);
1759
+ border-radius: 5px;
1760
+ }
1761
+
1762
+ .sv-grid-facet {
1763
+ display: flex;
1764
+ align-items: center;
1765
+ gap: 8px;
1766
+ padding: 4px 8px;
1767
+ cursor: pointer;
1768
+ }
1769
+
1770
+ .sv-grid-facet:hover {
1771
+ background: var(--sg-row-hover-bg, #f4f6fa);
1772
+ }
1773
+
1774
+ .sv-grid-facet-all {
1775
+ border-bottom: 1px solid var(--sg-border, #eaeef4);
1776
+ font-weight: 600;
1777
+ }
1778
+
1779
+ .sv-grid-facet input {
1780
+ flex: none;
1781
+ }
1782
+
1783
+ .sv-grid-facet-label {
1784
+ overflow: hidden;
1785
+ text-overflow: ellipsis;
1786
+ white-space: nowrap;
1787
+ }
1788
+
1789
+ .sv-grid-facet-empty,
1790
+ .sv-grid-facet-note {
1791
+ padding: 6px 8px;
1792
+ color: var(--sg-muted, #94a3b8);
1793
+ font-size: 12px;
1794
+ }
1795
+
1796
+ .sv-grid-menu-actions {
1797
+ display: flex;
1798
+ justify-content: flex-end;
1799
+ gap: 6px;
1800
+ padding-top: 8px;
1801
+ }
1802
+
1803
+ .sv-grid-menu-btn {
1804
+ padding: 5px 10px;
1805
+ border: 1px solid var(--sg-input-border, #cbd5e1);
1806
+ border-radius: 5px;
1807
+ background: var(--sg-input-bg, #fff);
1808
+ color: var(--sg-fg, #334155);
1809
+ font: inherit;
1810
+ cursor: pointer;
1811
+ }
1812
+
1813
+ .sv-grid-menu-btn-primary {
1814
+ background: var(--sg-accent, #0b63f3);
1815
+ border-color: var(--sg-accent, #0b63f3);
1816
+ color: #fff;
1817
+ }
1818
+
1819
+ .sv-grid-menu-item:disabled,
1820
+ .sv-grid-menu-btn:disabled {
1821
+ opacity: 0.4;
1822
+ cursor: default;
1823
+ }
1824
+
1825
+ .sv-grid-menu-item:disabled:hover {
1826
+ background: transparent;
1827
+ }
1828
+
1829
+ .sv-grid-group-row > .sv-grid-cell {
1830
+ background: var(--sg-header-bg, #eef2f8);
1831
+ font-weight: 600;
1832
+ cursor: pointer;
1833
+ }
1834
+
1835
+ .sv-grid-row-selected > .sv-grid-cell {
1836
+ background: var(--sg-selection-bg, #eaf2ff);
1837
+ }
1838
+
1839
+ .sv-grid-group-cell {
1840
+ padding: 0 12px;
1841
+ }
1842
+
1843
+ .sv-grid-group-content {
1844
+ display: inline-flex;
1845
+ align-items: center;
1846
+ gap: 6px;
1847
+ }
1848
+
1849
+ .sv-grid-group-toggle {
1850
+ border: 0;
1851
+ background: transparent;
1852
+ padding: 0 4px 0 0;
1853
+ font-size: 11px;
1854
+ line-height: 1;
1855
+ color: var(--sg-fg, #334155);
1856
+ cursor: pointer;
1857
+ }
1858
+
1859
+ .sv-grid-group-count {
1860
+ margin-left: 6px;
1861
+ font-weight: 400;
1862
+ font-size: 12px;
1863
+ color: var(--sg-muted, #64748b);
1864
+ }
1865
+ .sv-grid-group-agg {
1866
+ margin-left: 10px;
1867
+ font-size: 12px;
1868
+ font-weight: 600;
1869
+ color: var(--sg-fg);
1870
+ white-space: nowrap;
1871
+ }
1872
+ .sv-grid-group-agg-label {
1873
+ font-weight: 400;
1874
+ color: var(--sg-muted, #64748b);
1875
+ margin-right: 4px;
1876
+ }
1877
+ .sv-grid-group-agg-label::after {
1878
+ content: ":";
1879
+ }
1880
+
1881
+ .sv-grid-group-child-indent {
1882
+ display: inline-block;
1883
+ width: 18px;
1884
+ }
1885
+
1886
+ /* ---- Custom hover tooltip (column .tooltip + cell notes) ---- */
1887
+ .sv-grid-tooltip {
1888
+ position: fixed;
1889
+ z-index: 10000;
1890
+ max-width: 280px;
1891
+ padding: 8px 10px;
1892
+ background: #0f172a;
1893
+ color: #f1f5f9;
1894
+ border-radius: var(--sg-radius, 6px);
1895
+ font-size: 12px;
1896
+ line-height: 1.45;
1897
+ box-shadow: 0 8px 24px rgba(15, 23, 42, 0.28);
1898
+ pointer-events: none;
1899
+ white-space: pre-wrap;
1900
+ word-wrap: break-word;
1901
+ }
1902
+ [data-theme="dark"] .sv-grid-tooltip { background: #f1f5f9; color: #0f172a; }
1903
+
1904
+ /* ---- Textarea editor ---- */
1905
+ .sv-grid-cell-editor-textarea {
1906
+ width: 100%;
1907
+ min-height: 80px;
1908
+ padding: 6px 8px;
1909
+ border: 1px solid var(--sg-accent, #6366f1);
1910
+ background: var(--sg-bg, #fff);
1911
+ color: var(--sg-fg, #0f172a);
1912
+ border-radius: 4px;
1913
+ font-family: inherit;
1914
+ font-size: 13px;
1915
+ line-height: 1.4;
1916
+ resize: vertical;
1917
+ outline: none;
1918
+ box-shadow: var(--sg-focus-ring, 0 0 0 2px rgba(99, 102, 241, 0.40));
1919
+ }
1920
+
1921
+ .sv-grid-autocomplete {
1922
+ position: relative;
1923
+ width: 100%;
1924
+ }
1925
+ .sv-grid-autocomplete-list {
1926
+ position: absolute;
1927
+ left: 0;
1928
+ right: 0;
1929
+ top: 100%;
1930
+ z-index: 30;
1931
+ max-height: 220px;
1932
+ overflow: auto;
1933
+ margin-top: 2px;
1934
+ padding: 4px;
1935
+ background: var(--sg-bg, #fff);
1936
+ border: 1px solid var(--sg-border, #d6dee9);
1937
+ border-radius: var(--sg-radius, 6px);
1938
+ box-shadow: 0 12px 28px rgba(15, 23, 42, 0.18);
1939
+ }
1940
+ .sv-grid-autocomplete-option {
1941
+ display: block;
1942
+ width: 100%;
1943
+ text-align: left;
1944
+ padding: 5px 8px;
1945
+ border: 0;
1946
+ border-radius: 4px;
1947
+ background: transparent;
1948
+ color: var(--sg-fg, #1f2937);
1949
+ font: inherit;
1950
+ font-size: 13px;
1951
+ cursor: pointer;
1952
+ white-space: nowrap;
1953
+ overflow: hidden;
1954
+ text-overflow: ellipsis;
1955
+ }
1956
+ .sv-grid-autocomplete-option:hover,
1957
+ .sv-grid-autocomplete-option[aria-selected="true"] {
1958
+ background: var(--sg-row-hover-bg, rgba(99, 102, 241, 0.10));
1959
+ }
1960
+
1961
+ /* ---- Find-in-grid overlay ---- */
1962
+ .sv-grid-find {
1963
+ position: absolute;
1964
+ top: 8px; right: 8px;
1965
+ z-index: 60;
1966
+ display: inline-flex; align-items: center; gap: 4px;
1967
+ padding: 6px 8px;
1968
+ background: var(--sg-bg, #fff);
1969
+ color: var(--sg-fg, #0f172a);
1970
+ border: 1px solid var(--sg-border, #cbd5e1);
1971
+ border-radius: 8px;
1972
+ box-shadow: 0 10px 25px rgba(15, 23, 42, 0.18);
1973
+ min-width: 280px;
1974
+ }
1975
+ .sv-grid-find-icon {
1976
+ width: 14px; height: 14px;
1977
+ color: var(--sg-muted, #94a3b8);
1978
+ flex: none;
1979
+ margin-left: 2px;
1980
+ }
1981
+ .sv-grid-find-input {
1982
+ flex: 1; min-width: 0;
1983
+ border: 0; outline: none;
1984
+ background: transparent;
1985
+ color: inherit;
1986
+ font-size: 13px;
1987
+ padding: 2px 6px;
1988
+ }
1989
+ .sv-grid-find-input::placeholder { color: var(--sg-muted, #94a3b8); }
1990
+ .sv-grid-find-count {
1991
+ font-size: 11px;
1992
+ color: var(--sg-muted, #64748b);
1993
+ font-variant-numeric: tabular-nums;
1994
+ padding: 0 4px;
1995
+ white-space: nowrap;
1996
+ }
1997
+ .sv-grid-find-step, .sv-grid-find-close {
1998
+ display: inline-flex; align-items: center; justify-content: center;
1999
+ width: 22px; height: 22px;
2000
+ background: transparent;
2001
+ border: 0;
2002
+ color: var(--sg-muted, #64748b);
2003
+ border-radius: 4px;
2004
+ cursor: pointer;
2005
+ font-size: 12px;
2006
+ }
2007
+ .sv-grid-find-step:hover:not(:disabled),
2008
+ .sv-grid-find-close:hover {
2009
+ background: var(--sg-row-hover-bg, rgba(148, 163, 184, 0.12));
2010
+ color: var(--sg-fg, #0f172a);
2011
+ }
2012
+ .sv-grid-find-step:disabled { opacity: 0.30; cursor: default; }