@kanunilabs/datagrid-react 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +55 -0
- package/README.md +246 -0
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +1761 -0
- package/dist/index.d.ts +1761 -0
- package/dist/index.js +2 -0
- package/dist/styles.css +3497 -0
- package/package.json +68 -0
package/dist/styles.css
ADDED
|
@@ -0,0 +1,3497 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────────────────────
|
|
2
|
+
KANUNILABS DataGrid — isolated theme system
|
|
3
|
+
─────────────────────────────────────────────────────────────
|
|
4
|
+
Every rule is scoped under .kanuni-datagrid-root so the host app's CSS
|
|
5
|
+
cannot bleed in and ours cannot bleed out. Tokens mirror the PivotGrid
|
|
6
|
+
vocabulary (--pivot-*) with a --dg-* prefix so the two products can be
|
|
7
|
+
themed side by side without collisions.
|
|
8
|
+
───────────────────────────────────────────────────────────── */
|
|
9
|
+
|
|
10
|
+
/* ── Design tokens ───────────────────────────────────────────
|
|
11
|
+
Declared on more than the grid root because two things render OUTSIDE it
|
|
12
|
+
and still have to look like the grid: the enterprise wrapper (which owns
|
|
13
|
+
the row/form editor, a SIBLING of the root) and any layer portalled to
|
|
14
|
+
<body> (the lookup list, tagged `.kanuni-datagrid-detached`).
|
|
15
|
+
|
|
16
|
+
Tokens defined only on the root left those resolving `var(--dg-surface)` to
|
|
17
|
+
nothing — which is not a fallback but an INVALID declaration, so the popup
|
|
18
|
+
editor rendered fully transparent and unreadable over the grid.
|
|
19
|
+
|
|
20
|
+
Only the custom properties live here. The grid's own box (border, radius,
|
|
21
|
+
overflow, flex) stays on the root below: giving it to the wrapper would draw
|
|
22
|
+
a second border around the whole component and clip the editor. */
|
|
23
|
+
.kanuni-datagrid-root,
|
|
24
|
+
.kanuni-datagrid-enterprise,
|
|
25
|
+
.kanuni-datagrid-detached {
|
|
26
|
+
/* ── Color tokens ── */
|
|
27
|
+
--dg-bg: #ffffff;
|
|
28
|
+
--dg-surface: #ffffff;
|
|
29
|
+
--dg-surface-header: #f8fafc; /* slate-50 */
|
|
30
|
+
--dg-surface-hover: #f1f5f9; /* slate-100 */
|
|
31
|
+
--dg-surface-stripe: #fafafa;
|
|
32
|
+
--dg-border: #e2e8f0; /* slate-200 */
|
|
33
|
+
--dg-border-strong: #cbd5e1; /* slate-300 */
|
|
34
|
+
--dg-text: #0f172a; /* slate-900 */
|
|
35
|
+
--dg-text-muted: #64748b; /* slate-500 */
|
|
36
|
+
/* sky-700, and the depth is the point rather than the hue.
|
|
37
|
+
This was sky-500 (#0ea5e9), which is the accent EVERY foreground in the
|
|
38
|
+
grid is drawn from: the sort caret over the header, the active funnel, the
|
|
39
|
+
editor underline, the detail rule, and the white label on a group chip or
|
|
40
|
+
a primary button. On a light surface sky-500 measures 2.77:1 under white
|
|
41
|
+
and 2.65:1 as a caret on the header — under the 4.5:1 that text needs and
|
|
42
|
+
under the 3:1 that an icon needs. sky-600 was not enough either (4.10:1
|
|
43
|
+
white-on). sky-700 clears both: 5.93:1 under white, 5.67:1 on the header.
|
|
44
|
+
The dark palette below keeps sky-400 — it is already at 8.33:1 there,
|
|
45
|
+
because the problem was never the hue, it was using a mid-tone accent
|
|
46
|
+
against a white background. */
|
|
47
|
+
--dg-primary: #0369a1; /* sky-700 */
|
|
48
|
+
--dg-primary-soft: #e0f2fe; /* sky-100 */
|
|
49
|
+
--dg-focus-ring: #0369a1; /* matches the accent */
|
|
50
|
+
/* Cell flash (P1.7). Translucent so the row's own background — striped,
|
|
51
|
+
selected, dirty — stays visible under the pulse rather than being
|
|
52
|
+
replaced by it. */
|
|
53
|
+
--dg-flash-bg: rgba(3, 105, 161, 0.35);
|
|
54
|
+
--dg-flash-duration: 700ms;
|
|
55
|
+
/* Cell note marker (P3.3). Red on purpose and NOT the accent: the accent
|
|
56
|
+
means "the grid did this" (focus, selection, sort), and a note means
|
|
57
|
+
"a person put something here" — two different kinds of signal. */
|
|
58
|
+
--dg-note-marker: #dc2626;
|
|
59
|
+
|
|
60
|
+
/* ── Semantic colours ──────────────────────────────────────
|
|
61
|
+
Every default below is the literal that used to be written inline at the
|
|
62
|
+
point of use, so a grid with no theme renders exactly as before. They are
|
|
63
|
+
tokens now because a theme has to be able to move them: `--dg-on-primary`
|
|
64
|
+
is the clearest case — the KanuniLabs accent is a GOLD, and the white that
|
|
65
|
+
reads fine on sky-500 measures about 2:1 on it. A theme that can recolour
|
|
66
|
+
the chip but not the text on the chip is a theme that ships unreadable
|
|
67
|
+
buttons. */
|
|
68
|
+
/* The header row is its own surface, separate from `--dg-surface-header`.
|
|
69
|
+
They default to the same value, and every theme here but one keeps them
|
|
70
|
+
that way — but a corporate theme with an INK header still wants the group
|
|
71
|
+
panel, the filter row and the footer to stay light. Without the split, the
|
|
72
|
+
one token dragged all four along and the panel hints (muted text) went
|
|
73
|
+
unreadable on the dark fill. */
|
|
74
|
+
--dg-header-bg: var(--dg-surface-header);
|
|
75
|
+
--dg-header-text: var(--dg-text);
|
|
76
|
+
--dg-header-text-muted: var(--dg-text-muted);
|
|
77
|
+
--dg-header-hover: var(--dg-surface-hover);
|
|
78
|
+
--dg-on-primary: #ffffff;
|
|
79
|
+
--dg-danger: #dc2626; /* red-600 — borders, solid fills */
|
|
80
|
+
--dg-danger-text: #b91c1c; /* red-700 — text on a light surface */
|
|
81
|
+
--dg-on-danger: #ffffff; /* text ON a solid --dg-danger fill */
|
|
82
|
+
--dg-warning: #b45309; /* amber-700 */
|
|
83
|
+
--dg-selected-bg: var(--dg-primary-soft);
|
|
84
|
+
/* Search highlight (P2.4). Amber rather than the accent: the accent already
|
|
85
|
+
means "selected" and "focused" in this grid, and a third meaning on the
|
|
86
|
+
same colour would make a highlighted cell read as a selected one. */
|
|
87
|
+
--dg-highlight-bg: #fde68a; /* amber-200 */
|
|
88
|
+
--dg-highlight-text: #713f12; /* amber-900 — 8.9:1 on the above */
|
|
89
|
+
--dg-group-bg: var(--dg-surface-header);
|
|
90
|
+
--dg-footer-bg: var(--dg-surface-header);
|
|
91
|
+
--dg-overlay: rgba(15, 23, 42, 0.55);
|
|
92
|
+
--dg-scrollbar-thumb: var(--dg-border-strong);
|
|
93
|
+
--dg-scrollbar-track: transparent;
|
|
94
|
+
|
|
95
|
+
/* ── Elevation ──
|
|
96
|
+
A flat theme sets these to `none`; Material raises them. */
|
|
97
|
+
--dg-shadow-popup: 0 10px 25px -5px rgba(15, 23, 42, 0.25);
|
|
98
|
+
--dg-shadow-popup-sm: 0 8px 24px rgba(15, 23, 42, 0.16);
|
|
99
|
+
--dg-shadow-menu: 0 12px 28px rgba(15, 23, 42, 0.22);
|
|
100
|
+
--dg-shadow-card: 0 12px 32px rgba(15, 23, 42, 0.18);
|
|
101
|
+
--dg-shadow-panel: 0 8px 20px rgba(0, 0, 0, 0.12);
|
|
102
|
+
--dg-shadow-dialog: 0 24px 48px rgba(0, 0, 0, 0.28);
|
|
103
|
+
|
|
104
|
+
/* ── Metric tokens ── */
|
|
105
|
+
--dg-row-height: 28px;
|
|
106
|
+
--dg-header-height: 32px;
|
|
107
|
+
--dg-cell-padding-x: 8px;
|
|
108
|
+
--dg-font-size: 13px;
|
|
109
|
+
--dg-header-font-size: 12px;
|
|
110
|
+
--dg-radius: 6px;
|
|
111
|
+
--dg-shadow-pinned: 2px 0 4px -2px rgba(15, 23, 42, 0.18);
|
|
112
|
+
--dg-shadow-pinned-right: -2px 0 4px -2px rgba(15, 23, 42, 0.18);
|
|
113
|
+
|
|
114
|
+
/* ── Line work ─────────────────────────────────────────────
|
|
115
|
+
Whole SHORTHANDS rather than a colour and a width, so a theme can drop a
|
|
116
|
+
line entirely (`none`) instead of only fading it. This is most of what
|
|
117
|
+
separates the presets: Balham rules every column and every row, Material
|
|
118
|
+
rules rows only, Quartz keeps the rows faint and skips the columns. */
|
|
119
|
+
--dg-cell-border: 1px solid var(--dg-border);
|
|
120
|
+
--dg-row-border: 1px solid var(--dg-border);
|
|
121
|
+
--dg-header-cell-border: 1px solid var(--dg-border);
|
|
122
|
+
--dg-header-border: 1px solid var(--dg-border-strong);
|
|
123
|
+
|
|
124
|
+
/* ── Header treatment ── */
|
|
125
|
+
--dg-header-weight: 600;
|
|
126
|
+
--dg-header-transform: none;
|
|
127
|
+
--dg-header-tracking: normal;
|
|
128
|
+
|
|
129
|
+
/* ── Chrome (toolbar, pager, panels, popups) ──
|
|
130
|
+
Sized separately from the data surface: a compact grid shrinks its rows
|
|
131
|
+
hard, but a 9px button is not usable at any density. */
|
|
132
|
+
--dg-chrome-font-size: 12px;
|
|
133
|
+
--dg-chrome-padding-y: 6px;
|
|
134
|
+
--dg-control-height: 26px;
|
|
135
|
+
--dg-control-font-size: 12px;
|
|
136
|
+
--dg-filter-input-height: 22px;
|
|
137
|
+
--dg-radius-control: 4px;
|
|
138
|
+
|
|
139
|
+
/* The line box a WRAPPED cell lays its text out on, and the height an editor
|
|
140
|
+
takes in one. Whole pixels — see the note on `--wrap` below. */
|
|
141
|
+
--dg-wrap-line-height: 18px;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* Detached surfaces carry the foreground as well as the tokens, for the reason
|
|
145
|
+
the enterprise wrapper does: a portalled layer is a child of <body>, so
|
|
146
|
+
anything inside it written as `color: inherit` would resolve against the host
|
|
147
|
+
page rather than against the grid it belongs to. The dialog and the row
|
|
148
|
+
editor set their own colour, but the box AROUND them is the safety net. */
|
|
149
|
+
.kanuni-datagrid-detached {
|
|
150
|
+
color: var(--dg-text);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* The grid's own box. */
|
|
154
|
+
.kanuni-datagrid-root {
|
|
155
|
+
box-sizing: border-box;
|
|
156
|
+
font-family: inherit;
|
|
157
|
+
-webkit-font-smoothing: antialiased;
|
|
158
|
+
-moz-osx-font-smoothing: grayscale;
|
|
159
|
+
position: relative;
|
|
160
|
+
display: flex;
|
|
161
|
+
flex-direction: column;
|
|
162
|
+
overflow: hidden;
|
|
163
|
+
border: 1px solid var(--dg-border);
|
|
164
|
+
border-radius: var(--dg-radius);
|
|
165
|
+
background: var(--dg-bg);
|
|
166
|
+
color: var(--dg-text);
|
|
167
|
+
font-size: var(--dg-font-size);
|
|
168
|
+
line-height: 1.4;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.kanuni-datagrid-root *,
|
|
172
|
+
.kanuni-datagrid-root *::before,
|
|
173
|
+
.kanuni-datagrid-root *::after {
|
|
174
|
+
box-sizing: border-box;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/* Dark theme: opt in with .kanuni-datagrid-root.kanuni-datagrid-dark */
|
|
178
|
+
.kanuni-datagrid-root.kanuni-datagrid-dark,
|
|
179
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-dark,
|
|
180
|
+
.kanuni-datagrid-detached.kanuni-datagrid-dark {
|
|
181
|
+
--dg-bg: #0b1120;
|
|
182
|
+
--dg-surface: #0f172a;
|
|
183
|
+
--dg-surface-header: #16233c;
|
|
184
|
+
--dg-surface-hover: #1e293b;
|
|
185
|
+
--dg-surface-stripe: #101a2e;
|
|
186
|
+
--dg-border: #24344d;
|
|
187
|
+
--dg-border-strong: #334155;
|
|
188
|
+
--dg-text: #e2e8f0;
|
|
189
|
+
--dg-text-muted: #94a3b8;
|
|
190
|
+
--dg-primary: #38bdf8;
|
|
191
|
+
--dg-primary-soft: #0c4a6e;
|
|
192
|
+
--dg-focus-ring: #7dd3fc;
|
|
193
|
+
/* Lifted for a dark surface: crimson on near-black reads as brown. */
|
|
194
|
+
--dg-note-marker: #f87171;
|
|
195
|
+
--dg-shadow-pinned: 2px 0 4px -2px rgba(0, 0, 0, 0.55);
|
|
196
|
+
--dg-shadow-pinned-right: -2px 0 4px -2px rgba(0, 0, 0, 0.55);
|
|
197
|
+
|
|
198
|
+
/* The semantic set, re-pointed for a dark surface. `--dg-on-primary` goes
|
|
199
|
+
DARK here because the dark accent is a light sky-400. */
|
|
200
|
+
--dg-on-primary: #082f49;
|
|
201
|
+
/* Dark: a muted amber fill with light text, so the mark reads as emphasis
|
|
202
|
+
instead of glowing off a dark surface the way amber-200 would. */
|
|
203
|
+
--dg-highlight-bg: #78350f; /* amber-900 */
|
|
204
|
+
--dg-highlight-text: #fef3c7; /* amber-100 — 9.6:1 on the above */
|
|
205
|
+
--dg-danger: #f87171;
|
|
206
|
+
--dg-danger-text: #fca5a5;
|
|
207
|
+
/* The dark danger is a LIGHT red, so what sits on it has to be dark. White
|
|
208
|
+
on #f87171 measures 2.4:1 — the delete button's own label. */
|
|
209
|
+
--dg-on-danger: #450a0a;
|
|
210
|
+
--dg-warning: #fbbf24;
|
|
211
|
+
--dg-overlay: rgba(2, 6, 23, 0.7);
|
|
212
|
+
--dg-scrollbar-thumb: #475569;
|
|
213
|
+
--dg-shadow-popup: 0 10px 25px -5px rgba(0, 0, 0, 0.6);
|
|
214
|
+
--dg-shadow-popup-sm: 0 8px 24px rgba(0, 0, 0, 0.5);
|
|
215
|
+
--dg-shadow-menu: 0 12px 28px rgba(0, 0, 0, 0.55);
|
|
216
|
+
--dg-shadow-card: 0 12px 32px rgba(0, 0, 0, 0.55);
|
|
217
|
+
--dg-shadow-panel: 0 8px 20px rgba(0, 0, 0, 0.45);
|
|
218
|
+
--dg-shadow-dialog: 0 24px 48px rgba(0, 0, 0, 0.65);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/* ── Single scroll container (§7.4: NO dual-table architecture) ────────
|
|
222
|
+
The header and the body live in ONE scroller, so horizontal alignment
|
|
223
|
+
cannot drift: there is no second element to keep in sync. */
|
|
224
|
+
.kanuni-datagrid-scroller {
|
|
225
|
+
position: relative;
|
|
226
|
+
flex: 1 1 auto;
|
|
227
|
+
overflow: auto;
|
|
228
|
+
overscroll-behavior: contain;
|
|
229
|
+
/* Scroll anchoring must be OFF in a virtualized scroller. Under scroll
|
|
230
|
+
compression the rows legitimately move relative to the canvas on every
|
|
231
|
+
scroll; Chrome's anchoring reads that as "content shifted under the user"
|
|
232
|
+
and walks scrollTop back to chase the anchor row. During a scrollbar drag
|
|
233
|
+
the correction is deferred and applied on mouse RELEASE — the reported
|
|
234
|
+
symptom (2026-08-14, 1M rows): the last rows peek out while dragging and
|
|
235
|
+
snap back when the button is let go, leaving the true bottom unreachable
|
|
236
|
+
by user scrolling while programmatic probes see nothing wrong. */
|
|
237
|
+
overflow-anchor: none;
|
|
238
|
+
background: var(--dg-surface);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/* Sized to the full virtual extent so the native scrollbar is accurate. */
|
|
242
|
+
.kanuni-datagrid-canvas {
|
|
243
|
+
position: relative;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/* ── Header ───────────────────────────────────────────────── */
|
|
247
|
+
/* ── Header bands (multi-level headers) ──────────────────────
|
|
248
|
+
Siblings of the header row, each sticky at its own offset so the whole
|
|
249
|
+
stack stays put while the rows scroll under it. */
|
|
250
|
+
.kanuni-datagrid-band-row {
|
|
251
|
+
position: sticky;
|
|
252
|
+
z-index: 4;
|
|
253
|
+
display: flex;
|
|
254
|
+
background: var(--dg-header-bg);
|
|
255
|
+
/* Deliberately a literal, not `--dg-header-cell-border`: that token is the
|
|
256
|
+
COLUMN separator, which some themes drop entirely. The line under a band
|
|
257
|
+
row is structural — without it a two-level header reads as one tall box. */
|
|
258
|
+
border-bottom: 1px solid var(--dg-border);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.kanuni-datagrid-band-cell {
|
|
262
|
+
box-sizing: border-box;
|
|
263
|
+
display: flex;
|
|
264
|
+
align-items: center;
|
|
265
|
+
justify-content: center;
|
|
266
|
+
min-width: 0;
|
|
267
|
+
padding: 0 8px;
|
|
268
|
+
border-inline-end: var(--dg-header-cell-border);
|
|
269
|
+
background: var(--dg-header-bg);
|
|
270
|
+
color: var(--dg-header-text);
|
|
271
|
+
font-size: calc(var(--dg-font-size) - 1px);
|
|
272
|
+
font-weight: var(--dg-header-weight);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/* No band at this level: the column's own header reaches up through here, so
|
|
276
|
+
the box must be INVISIBLE rather than an empty cell with borders. */
|
|
277
|
+
.kanuni-datagrid-band-cell--spacer {
|
|
278
|
+
border-inline-end: none;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.kanuni-datagrid-band-cell--pinned {
|
|
282
|
+
position: sticky;
|
|
283
|
+
z-index: 5;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.kanuni-datagrid-band-label {
|
|
287
|
+
overflow: hidden;
|
|
288
|
+
text-overflow: ellipsis;
|
|
289
|
+
white-space: nowrap;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.kanuni-datagrid-header {
|
|
293
|
+
position: sticky;
|
|
294
|
+
top: 0;
|
|
295
|
+
z-index: 3;
|
|
296
|
+
display: flex;
|
|
297
|
+
height: var(--dg-header-height);
|
|
298
|
+
background: var(--dg-header-bg);
|
|
299
|
+
border-bottom: var(--dg-header-border);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.kanuni-datagrid-header-cell {
|
|
303
|
+
/* Anchors the resize handle. Pinned headers override this with `sticky`,
|
|
304
|
+
which is also a positioned ancestor, so the handle works there too. */
|
|
305
|
+
position: relative;
|
|
306
|
+
display: flex;
|
|
307
|
+
align-items: center;
|
|
308
|
+
gap: 4px;
|
|
309
|
+
flex: none;
|
|
310
|
+
height: 100%;
|
|
311
|
+
padding: 0 var(--dg-cell-padding-x);
|
|
312
|
+
background: var(--dg-header-bg);
|
|
313
|
+
border-inline-end: var(--dg-header-cell-border);
|
|
314
|
+
font-size: var(--dg-header-font-size);
|
|
315
|
+
font-weight: var(--dg-header-weight);
|
|
316
|
+
letter-spacing: var(--dg-header-tracking);
|
|
317
|
+
text-transform: var(--dg-header-transform);
|
|
318
|
+
color: var(--dg-header-text);
|
|
319
|
+
white-space: nowrap;
|
|
320
|
+
overflow: hidden;
|
|
321
|
+
user-select: none;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.kanuni-datagrid-header-cell--sortable {
|
|
325
|
+
cursor: pointer;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.kanuni-datagrid-header-cell--sortable:hover {
|
|
329
|
+
background: var(--dg-header-hover);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.kanuni-datagrid-header-label {
|
|
333
|
+
overflow: hidden;
|
|
334
|
+
text-overflow: ellipsis;
|
|
335
|
+
white-space: nowrap;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.kanuni-datagrid-sort-indicator {
|
|
339
|
+
flex: none;
|
|
340
|
+
font-size: 10px;
|
|
341
|
+
line-height: 1;
|
|
342
|
+
color: var(--dg-primary);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.kanuni-datagrid-sort-order {
|
|
346
|
+
flex: none;
|
|
347
|
+
font-size: 9px;
|
|
348
|
+
color: var(--dg-header-text-muted);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/* ── Rows and cells ───────────────────────────────────────── */
|
|
352
|
+
.kanuni-datagrid-viewport {
|
|
353
|
+
position: absolute;
|
|
354
|
+
top: 0;
|
|
355
|
+
left: 0;
|
|
356
|
+
width: 100%;
|
|
357
|
+
will-change: transform;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.kanuni-datagrid-row {
|
|
361
|
+
display: flex;
|
|
362
|
+
height: var(--dg-row-height);
|
|
363
|
+
background: var(--dg-surface);
|
|
364
|
+
border-bottom: var(--dg-row-border);
|
|
365
|
+
/* Dragging a CELL RANGE must not also drag a text selection. The browser
|
|
366
|
+
starts one on any mousedown-and-move over text, so the user got two
|
|
367
|
+
overlapping highlights at once — the grid's range in pale blue and the
|
|
368
|
+
native selection in solid blue — with no way to tell which one the totals
|
|
369
|
+
strip was summing. The header and footer already do this; the data rows
|
|
370
|
+
were the gap. Copying is not lost: the range owns Ctrl+C. */
|
|
371
|
+
user-select: none;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/* ── Auto-height rows ──────────────────────────────────────
|
|
375
|
+
A grid with an `autoHeight` column lets its rows grow. The row's height is
|
|
376
|
+
NOT written inline here: it is measured after layout and handed to the window
|
|
377
|
+
engine, which owns the scroll canvas and every row offset. Setting it inline
|
|
378
|
+
as well would make the measurement read back whatever was set — the row would
|
|
379
|
+
agree with itself and never grow.
|
|
380
|
+
|
|
381
|
+
`min-height` keeps a one-line row exactly the height it was, so turning the
|
|
382
|
+
feature on does not resize the rows that did not need it. */
|
|
383
|
+
.kanuni-datagrid-root[data-auto-height] .kanuni-datagrid-row {
|
|
384
|
+
height: auto;
|
|
385
|
+
min-height: var(--dg-row-height);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/* Every OTHER cell of an autoHeight grid must stretch to its row.
|
|
389
|
+
The base rule's `height: 100%` cannot resolve against the row's
|
|
390
|
+
`height: auto`, so it computed as content height and every single-line cell
|
|
391
|
+
collapsed to its text — 18px inside a 28px row, worse inside a wrapped one.
|
|
392
|
+
Two user-visible symptoms (report, 2026-08-14): the focus rectangle hugged
|
|
393
|
+
the TEXT instead of the cell, and a click in the lower band of a cell landed
|
|
394
|
+
on the bare row — no column resolved, so nothing got focused. Stretching is
|
|
395
|
+
what the base rule always meant; `align-items: center` then keeps single-line
|
|
396
|
+
text centred exactly as in a fixed-height grid. The wrap cell is excluded:
|
|
397
|
+
its own block sizes it to its text on purpose — that is how the row learns
|
|
398
|
+
how tall to be. */
|
|
399
|
+
.kanuni-datagrid-root[data-auto-height] .kanuni-datagrid-row .kanuni-datagrid-cell:not(.kanuni-datagrid-cell--wrap) {
|
|
400
|
+
height: auto;
|
|
401
|
+
align-self: stretch;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/* Anything the user genuinely types into, or reads as prose, gets it back. */
|
|
405
|
+
.kanuni-datagrid-row input,
|
|
406
|
+
.kanuni-datagrid-row textarea,
|
|
407
|
+
.kanuni-datagrid-row select,
|
|
408
|
+
.kanuni-datagrid-detail-cell,
|
|
409
|
+
.kanuni-datagrid-editform-cell {
|
|
410
|
+
user-select: text;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.kanuni-datagrid-row--odd {
|
|
414
|
+
background: var(--dg-surface-stripe);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.kanuni-datagrid-row:hover,
|
|
418
|
+
.kanuni-datagrid-row:hover .kanuni-datagrid-cell--pinned {
|
|
419
|
+
background: var(--dg-surface-hover);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.kanuni-datagrid-cell {
|
|
423
|
+
display: flex;
|
|
424
|
+
align-items: center;
|
|
425
|
+
flex: none;
|
|
426
|
+
height: 100%;
|
|
427
|
+
padding: 0 var(--dg-cell-padding-x);
|
|
428
|
+
border-inline-end: var(--dg-cell-border);
|
|
429
|
+
white-space: nowrap;
|
|
430
|
+
overflow: hidden;
|
|
431
|
+
text-overflow: ellipsis;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/* ── autoHeight: the columns that wrap ─────────────────────
|
|
435
|
+
Only the columns that asked for it. Everything else keeps its single line and
|
|
436
|
+
its ellipsis — wrapping a whole grid because one column needed to is how a
|
|
437
|
+
dense table turns into a wall of text.
|
|
438
|
+
|
|
439
|
+
The doubled class is deliberate and load-bearing. `.kanuni-datagrid-cell--wrap`
|
|
440
|
+
alone has the same specificity as the base rule directly above, so whichever
|
|
441
|
+
came LAST in the file won; written earlier in the sheet it lost silently and
|
|
442
|
+
the cells kept `white-space: nowrap` while everything else about the feature
|
|
443
|
+
worked. Two classes settle it regardless of where this block sits. */
|
|
444
|
+
.kanuni-datagrid-cell.kanuni-datagrid-cell--wrap {
|
|
445
|
+
white-space: normal;
|
|
446
|
+
overflow: visible;
|
|
447
|
+
text-overflow: clip;
|
|
448
|
+
/* `anywhere` and not `break-word`: an unbroken 200-character token (a URL, a
|
|
449
|
+
serial number) otherwise overflows its column and lands on the next one. */
|
|
450
|
+
overflow-wrap: anywhere;
|
|
451
|
+
align-items: flex-start;
|
|
452
|
+
/* Not `height: 100%` — the cell has to be as tall as its own text, or the row
|
|
453
|
+
it is supposed to stretch never learns it needs to grow. */
|
|
454
|
+
height: auto;
|
|
455
|
+
align-self: stretch;
|
|
456
|
+
padding-block: 4px;
|
|
457
|
+
/* An INTEGER line box, not a ratio. `1.35` against the 13px body font lays
|
|
458
|
+
out at 17.55px, so every wrapped row landed on a fractional boundary and
|
|
459
|
+
its 1px border rendered a pixel thick on some rows and two on others.
|
|
460
|
+
A token now, because a theme moves the body font under it — 18px is right
|
|
461
|
+
for 13px text and cramped for Material's 14px. */
|
|
462
|
+
line-height: var(--dg-wrap-line-height);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
.kanuni-datagrid-cell--number {
|
|
466
|
+
justify-content: flex-end;
|
|
467
|
+
font-variant-numeric: tabular-nums;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.kanuni-datagrid-cell--center {
|
|
471
|
+
justify-content: center;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/* ── Pinned columns = sticky cells inside the same row (§7.4) ──────────
|
|
475
|
+
Compositor-friendly and layout-free; structurally immune to the
|
|
476
|
+
header/body desync bug class of a dual-table design. */
|
|
477
|
+
.kanuni-datagrid-cell--pinned,
|
|
478
|
+
.kanuni-datagrid-header-cell--pinned {
|
|
479
|
+
position: sticky;
|
|
480
|
+
z-index: 2;
|
|
481
|
+
background: inherit;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/* HARDENING: a pinned cell is sticky, whatever else it becomes.
|
|
485
|
+
State classes keep anchoring their markers with `position: relative` —
|
|
486
|
+
the dirty corner, the range overlay, whatever comes next — and on a pinned
|
|
487
|
+
cell that override turns `insetInlineStart` from a parking threshold into a
|
|
488
|
+
literal shift: the cell jumps sideways by its whole inset and parks over its
|
|
489
|
+
neighbour. It happened twice from two different classes in one day
|
|
490
|
+
(range-anchor on click, dirty on commit), so the guarantee lives HERE at
|
|
491
|
+
three-class specificity instead of as a `:not(--pinned)` chased onto every
|
|
492
|
+
state rule. Sticky is itself a positioned ancestor, so those markers lose
|
|
493
|
+
nothing. */
|
|
494
|
+
.kanuni-datagrid-root .kanuni-datagrid-cell.kanuni-datagrid-cell--pinned {
|
|
495
|
+
position: sticky;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/* A STICKY column sits in the scrolling order and parks itself only once it
|
|
499
|
+
would leave the viewport. It needs a lower z-index than a truly pinned
|
|
500
|
+
column: when the two meet, the pinned one is the fixed landmark and has to
|
|
501
|
+
win, or the sticky column would slide over the top of it. */
|
|
502
|
+
.kanuni-datagrid-cell--sticky {
|
|
503
|
+
z-index: 1;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.kanuni-datagrid-header-cell--pinned {
|
|
507
|
+
z-index: 4;
|
|
508
|
+
background: var(--dg-header-bg);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.kanuni-datagrid-cell--pinned-left:last-of-type,
|
|
512
|
+
.kanuni-datagrid-header-cell--pinned-left:last-of-type {
|
|
513
|
+
box-shadow: var(--dg-shadow-pinned);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
.kanuni-datagrid-pin-edge-left {
|
|
517
|
+
box-shadow: var(--dg-shadow-pinned);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
.kanuni-datagrid-pin-edge-right {
|
|
521
|
+
box-shadow: var(--dg-shadow-pinned-right);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/* Spacer that reserves the width of columns outside the horizontal window. */
|
|
525
|
+
.kanuni-datagrid-spacer {
|
|
526
|
+
flex: none;
|
|
527
|
+
height: 100%;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/* ── Toolbar ──────────────────────────────────────────────── */
|
|
531
|
+
.kanuni-datagrid-toolbar {
|
|
532
|
+
display: flex;
|
|
533
|
+
align-items: center;
|
|
534
|
+
gap: 6px;
|
|
535
|
+
flex: none;
|
|
536
|
+
padding: var(--dg-chrome-padding-y) 8px;
|
|
537
|
+
background: var(--dg-surface);
|
|
538
|
+
border-bottom: 1px solid var(--dg-border);
|
|
539
|
+
font-size: var(--dg-chrome-font-size);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
.kanuni-datagrid-toolbar-spacer {
|
|
543
|
+
flex: 1 1 auto;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.kanuni-datagrid-toolbar-search {
|
|
547
|
+
width: min(260px, 40%);
|
|
548
|
+
height: var(--dg-control-height);
|
|
549
|
+
padding: 0 8px;
|
|
550
|
+
border: 1px solid var(--dg-border-strong);
|
|
551
|
+
border-radius: var(--dg-radius-control);
|
|
552
|
+
background: var(--dg-surface);
|
|
553
|
+
color: var(--dg-text);
|
|
554
|
+
font: inherit;
|
|
555
|
+
font-size: var(--dg-control-font-size);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
.kanuni-datagrid-toolbar-search:focus {
|
|
559
|
+
outline: none;
|
|
560
|
+
border-color: var(--dg-primary);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
.kanuni-datagrid-toolbar > button {
|
|
564
|
+
padding: 3px 10px;
|
|
565
|
+
border: 1px solid var(--dg-border-strong);
|
|
566
|
+
border-radius: var(--dg-radius-control);
|
|
567
|
+
background: var(--dg-surface);
|
|
568
|
+
color: inherit;
|
|
569
|
+
font: inherit;
|
|
570
|
+
font-size: var(--dg-control-font-size);
|
|
571
|
+
cursor: pointer;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.kanuni-datagrid-toolbar > button:hover:not(:disabled) {
|
|
575
|
+
background: var(--dg-surface-hover);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
.kanuni-datagrid-toolbar > button:disabled {
|
|
579
|
+
opacity: 0.6;
|
|
580
|
+
cursor: default;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
.kanuni-datagrid-toolbar-error {
|
|
584
|
+
color: var(--dg-danger-text);
|
|
585
|
+
font-size: 11px;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/*
|
|
589
|
+
* The same message, but floating where a context menu just was — the cell
|
|
590
|
+
* menu's Export entries have nowhere else to report a missing optional peer.
|
|
591
|
+
* Sized and boxed like a menu so it reads as coming from one; it is a BUTTON
|
|
592
|
+
* because clicking it dismisses it, and a click target has to be one.
|
|
593
|
+
*/
|
|
594
|
+
.kanuni-datagrid-menu-error {
|
|
595
|
+
position: fixed;
|
|
596
|
+
/* Same layer as the context menu it replaces — see the rule further down. */
|
|
597
|
+
z-index: 1200;
|
|
598
|
+
max-width: 320px;
|
|
599
|
+
padding: 8px 10px;
|
|
600
|
+
text-align: left;
|
|
601
|
+
background: var(--dg-surface);
|
|
602
|
+
border: 1px solid var(--dg-danger-text);
|
|
603
|
+
border-radius: var(--dg-radius);
|
|
604
|
+
box-shadow: var(--dg-shadow-menu);
|
|
605
|
+
font-size: var(--dg-font-size);
|
|
606
|
+
cursor: pointer;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
.kanuni-datagrid-column-chooser .kanuni-datagrid-filter-item {
|
|
610
|
+
cursor: pointer;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/* ── Pager ────────────────────────────────────────────────── */
|
|
614
|
+
.kanuni-datagrid-pager {
|
|
615
|
+
display: flex;
|
|
616
|
+
align-items: center;
|
|
617
|
+
flex-wrap: wrap;
|
|
618
|
+
gap: 12px;
|
|
619
|
+
flex: none;
|
|
620
|
+
padding: 5px 8px;
|
|
621
|
+
background: var(--dg-surface);
|
|
622
|
+
border-top: 1px solid var(--dg-border);
|
|
623
|
+
font-size: var(--dg-chrome-font-size);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
.kanuni-datagrid-pager-size {
|
|
627
|
+
display: inline-flex;
|
|
628
|
+
align-items: center;
|
|
629
|
+
gap: 6px;
|
|
630
|
+
color: var(--dg-text-muted);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
.kanuni-datagrid-pager-size select {
|
|
634
|
+
padding: 2px 4px;
|
|
635
|
+
border: 1px solid var(--dg-border-strong);
|
|
636
|
+
border-radius: var(--dg-radius-control);
|
|
637
|
+
background: var(--dg-surface);
|
|
638
|
+
color: var(--dg-text);
|
|
639
|
+
font: inherit;
|
|
640
|
+
font-size: var(--dg-control-font-size);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
.kanuni-datagrid-pager-range {
|
|
644
|
+
color: var(--dg-text-muted);
|
|
645
|
+
font-variant-numeric: tabular-nums;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
.kanuni-datagrid-pager-buttons {
|
|
649
|
+
display: inline-flex;
|
|
650
|
+
align-items: center;
|
|
651
|
+
gap: 4px;
|
|
652
|
+
margin-left: auto;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
.kanuni-datagrid-pager-buttons button {
|
|
656
|
+
min-width: 26px;
|
|
657
|
+
height: calc(var(--dg-control-height) - 2px);
|
|
658
|
+
border: 1px solid var(--dg-border-strong);
|
|
659
|
+
border-radius: var(--dg-radius-control);
|
|
660
|
+
background: var(--dg-surface);
|
|
661
|
+
color: inherit;
|
|
662
|
+
font: inherit;
|
|
663
|
+
cursor: pointer;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
.kanuni-datagrid-pager-buttons button:hover:not(:disabled) {
|
|
667
|
+
background: var(--dg-surface-hover);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
.kanuni-datagrid-pager-buttons button:disabled {
|
|
671
|
+
opacity: 0.45;
|
|
672
|
+
cursor: default;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
.kanuni-datagrid-pager-current {
|
|
676
|
+
padding: 0 6px;
|
|
677
|
+
font-variant-numeric: tabular-nums;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/* ── Group panel ──────────────────────────────────────────── */
|
|
681
|
+
.kanuni-datagrid-group-panel {
|
|
682
|
+
display: flex;
|
|
683
|
+
align-items: flex-start;
|
|
684
|
+
flex-wrap: wrap;
|
|
685
|
+
gap: 8px;
|
|
686
|
+
flex: none;
|
|
687
|
+
padding: var(--dg-chrome-padding-y) 8px;
|
|
688
|
+
background: var(--dg-surface-header);
|
|
689
|
+
border-bottom: 1px solid var(--dg-border);
|
|
690
|
+
font-size: var(--dg-chrome-font-size);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/* ── Cascading levels ──────────────────────────────────────
|
|
694
|
+
One row per level, each indented past the one above and joined to it by an
|
|
695
|
+
elbow. Grouping is a hierarchy; a flat row of chips said nothing about which
|
|
696
|
+
column was the OUTER level, which is the only thing the order encodes. */
|
|
697
|
+
.kanuni-datagrid-group-levels {
|
|
698
|
+
display: flex;
|
|
699
|
+
flex-direction: column;
|
|
700
|
+
gap: 2px;
|
|
701
|
+
min-width: 0;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
.kanuni-datagrid-group-level {
|
|
705
|
+
position: relative;
|
|
706
|
+
display: flex;
|
|
707
|
+
align-items: center;
|
|
708
|
+
/* --dg-group-level is set per chip from its index. */
|
|
709
|
+
padding-inline-start: calc(var(--dg-group-level, 0) * 18px);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/* The elbow: down from the parent chip, then right into this one. */
|
|
713
|
+
.kanuni-datagrid-group-level:not(:first-child)::before {
|
|
714
|
+
content: '';
|
|
715
|
+
position: absolute;
|
|
716
|
+
inset-inline-start: calc((var(--dg-group-level, 1) - 1) * 18px + 9px);
|
|
717
|
+
top: -3px;
|
|
718
|
+
bottom: 50%;
|
|
719
|
+
width: 9px;
|
|
720
|
+
border-inline-start: 1px solid var(--dg-border-strong);
|
|
721
|
+
border-block-end: 1px solid var(--dg-border-strong);
|
|
722
|
+
border-end-start-radius: 4px;
|
|
723
|
+
pointer-events: none;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/* Drop indicator while a level is dragged over another. */
|
|
727
|
+
.kanuni-datagrid-group-level--drop-before::after,
|
|
728
|
+
.kanuni-datagrid-group-level--drop-after::after {
|
|
729
|
+
content: '';
|
|
730
|
+
position: absolute;
|
|
731
|
+
inset-inline: calc(var(--dg-group-level, 0) * 18px) 0;
|
|
732
|
+
height: 2px;
|
|
733
|
+
border-radius: 1px;
|
|
734
|
+
background: var(--dg-primary);
|
|
735
|
+
pointer-events: none;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
.kanuni-datagrid-group-level--drop-before::after {
|
|
739
|
+
top: -2px;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
.kanuni-datagrid-group-level--drop-after::after {
|
|
743
|
+
bottom: -2px;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
.kanuni-datagrid-group-panel-actions {
|
|
747
|
+
display: flex;
|
|
748
|
+
align-items: center;
|
|
749
|
+
gap: 8px;
|
|
750
|
+
margin-inline-start: auto;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
.kanuni-datagrid-group-panel--over {
|
|
754
|
+
background: var(--dg-primary-soft);
|
|
755
|
+
outline: 2px dashed var(--dg-primary);
|
|
756
|
+
outline-offset: -3px;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
.kanuni-datagrid-group-panel-hint {
|
|
760
|
+
color: var(--dg-text-muted);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
.kanuni-datagrid-group-chip {
|
|
764
|
+
display: inline-flex;
|
|
765
|
+
align-items: center;
|
|
766
|
+
gap: 6px;
|
|
767
|
+
padding: 2px 6px 2px 10px;
|
|
768
|
+
border-radius: 999px;
|
|
769
|
+
background: var(--dg-primary);
|
|
770
|
+
color: var(--dg-on-primary);
|
|
771
|
+
font-weight: 600;
|
|
772
|
+
white-space: nowrap;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
.kanuni-datagrid-group-chip[draggable='true'] {
|
|
776
|
+
cursor: grab;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
.kanuni-datagrid-group-chip:focus-visible {
|
|
780
|
+
outline: 2px solid var(--dg-focus-ring);
|
|
781
|
+
outline-offset: 2px;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
.kanuni-datagrid-group-chip button {
|
|
785
|
+
border: none;
|
|
786
|
+
background: transparent;
|
|
787
|
+
color: inherit;
|
|
788
|
+
font-size: 14px;
|
|
789
|
+
line-height: 1;
|
|
790
|
+
cursor: pointer;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
.kanuni-datagrid-group-panel-actions > button {
|
|
794
|
+
padding: 2px 8px;
|
|
795
|
+
border: 1px solid var(--dg-border-strong);
|
|
796
|
+
border-radius: var(--dg-radius-control);
|
|
797
|
+
background: var(--dg-surface);
|
|
798
|
+
color: inherit;
|
|
799
|
+
font: inherit;
|
|
800
|
+
font-size: 12px;
|
|
801
|
+
cursor: pointer;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
.kanuni-datagrid-group-select select {
|
|
805
|
+
padding: 2px 6px;
|
|
806
|
+
border: 1px solid var(--dg-border-strong);
|
|
807
|
+
border-radius: var(--dg-radius-control);
|
|
808
|
+
background: var(--dg-surface);
|
|
809
|
+
color: inherit;
|
|
810
|
+
font: inherit;
|
|
811
|
+
font-size: 12px;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/* ── Group rows ───────────────────────────────────────────── */
|
|
815
|
+
.kanuni-datagrid-group-row {
|
|
816
|
+
background: var(--dg-group-bg);
|
|
817
|
+
cursor: pointer;
|
|
818
|
+
font-weight: 600;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
.kanuni-datagrid-group-row:hover {
|
|
822
|
+
background: var(--dg-surface-hover);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
.kanuni-datagrid-group-cell {
|
|
826
|
+
/* Sticky so the label stays readable while scrolling horizontally. */
|
|
827
|
+
position: sticky;
|
|
828
|
+
inset-inline-start: 0;
|
|
829
|
+
display: flex;
|
|
830
|
+
align-items: center;
|
|
831
|
+
gap: 6px;
|
|
832
|
+
height: 100%;
|
|
833
|
+
padding: 0 var(--dg-cell-padding-x);
|
|
834
|
+
white-space: nowrap;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
.kanuni-datagrid-group-twisty {
|
|
838
|
+
display: inline-block;
|
|
839
|
+
width: 12px;
|
|
840
|
+
color: var(--dg-text-muted);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
.kanuni-datagrid-group-caption {
|
|
844
|
+
color: var(--dg-text-muted);
|
|
845
|
+
font-weight: 400;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
.kanuni-datagrid-group-count {
|
|
849
|
+
color: var(--dg-text-muted);
|
|
850
|
+
font-weight: 400;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/* Nested levels step back toward the body colour so depth reads at a glance
|
|
854
|
+
without needing to count the indent. */
|
|
855
|
+
.kanuni-datagrid-group-row--level-1 {
|
|
856
|
+
background: color-mix(in srgb, var(--dg-surface-header) 60%, var(--dg-surface));
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
.kanuni-datagrid-group-row--level-2 {
|
|
860
|
+
background: color-mix(in srgb, var(--dg-surface-header) 35%, var(--dg-surface));
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
.kanuni-datagrid-group-row--level-3 {
|
|
864
|
+
background: color-mix(in srgb, var(--dg-surface-header) 18%, var(--dg-surface));
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/* Aggregates trail the label; they must not push the count off-screen, so the
|
|
868
|
+
whole strip stays on one line and simply clips at the viewport edge. */
|
|
869
|
+
.kanuni-datagrid-group-aggregate {
|
|
870
|
+
margin-left: 12px;
|
|
871
|
+
font-weight: 600;
|
|
872
|
+
font-variant-numeric: tabular-nums;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
.kanuni-datagrid-group-aggregate-label {
|
|
876
|
+
color: var(--dg-text-muted);
|
|
877
|
+
font-weight: 400;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
.kanuni-datagrid-group-chip-level {
|
|
881
|
+
display: inline-flex;
|
|
882
|
+
align-items: center;
|
|
883
|
+
justify-content: center;
|
|
884
|
+
min-width: 15px;
|
|
885
|
+
height: 15px;
|
|
886
|
+
border-radius: 999px;
|
|
887
|
+
/* A wash of the chip's own FOREGROUND, not a fixed white: on a gold chip the
|
|
888
|
+
text is near-black, and a white pip on it disappeared. */
|
|
889
|
+
background: color-mix(in srgb, var(--dg-on-primary) 25%, transparent);
|
|
890
|
+
font-size: 10px;
|
|
891
|
+
font-weight: 700;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
.kanuni-datagrid-group-chip button[disabled] {
|
|
895
|
+
opacity: 0.4;
|
|
896
|
+
cursor: default;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
.kanuni-datagrid-group-row--focused {
|
|
900
|
+
outline: 2px solid var(--dg-focus-ring);
|
|
901
|
+
outline-offset: -2px;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/* ── Master-detail ────────────────────────────────────────── */
|
|
905
|
+
.kanuni-datagrid-detail-toggle-cell {
|
|
906
|
+
padding: 0;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
.kanuni-datagrid-detail-toggle {
|
|
910
|
+
width: 100%;
|
|
911
|
+
height: 100%;
|
|
912
|
+
border: none;
|
|
913
|
+
background: transparent;
|
|
914
|
+
color: var(--dg-text-muted);
|
|
915
|
+
font-size: 11px;
|
|
916
|
+
line-height: 1;
|
|
917
|
+
cursor: pointer;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
.kanuni-datagrid-detail-toggle:hover {
|
|
921
|
+
color: var(--dg-text);
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/* The panel is sized in whole rows by the component (span x rowHeight); this
|
|
925
|
+
rule only styles it. Overriding the height here would desync it from the
|
|
926
|
+
filler slots the virtualizer reserved. */
|
|
927
|
+
.kanuni-datagrid-detail-row {
|
|
928
|
+
background: var(--dg-surface);
|
|
929
|
+
border-bottom: 1px solid var(--dg-border);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
.kanuni-datagrid-detail-cell {
|
|
933
|
+
position: sticky;
|
|
934
|
+
inset-inline-start: 0;
|
|
935
|
+
box-sizing: border-box;
|
|
936
|
+
width: 100%;
|
|
937
|
+
height: 100%;
|
|
938
|
+
padding: 8px 12px;
|
|
939
|
+
overflow: auto;
|
|
940
|
+
border-inline-start: 3px solid var(--dg-primary);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/* ── In-grid edit form (mode: 'form') ─────────────────────── */
|
|
944
|
+
/* Sticky to the scroller's inline start so the form stays put while the
|
|
945
|
+
columns scroll under it — a form that slides off to the left with column 40
|
|
946
|
+
is a form the user has to chase. Width is the VIEWPORT's, not the canvas's,
|
|
947
|
+
for the same reason. */
|
|
948
|
+
.kanuni-datagrid-editform-row {
|
|
949
|
+
background: var(--dg-surface);
|
|
950
|
+
border-bottom: 1px solid var(--dg-border);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
.kanuni-datagrid-editform-cell {
|
|
954
|
+
position: sticky;
|
|
955
|
+
inset-inline-start: 0;
|
|
956
|
+
box-sizing: border-box;
|
|
957
|
+
/* The VIEWPORT's width, not the row's — see `--dg-viewport-width`. Falls
|
|
958
|
+
back to the row when nothing has measured yet (SSR, first paint). */
|
|
959
|
+
width: var(--dg-viewport-width, 100%);
|
|
960
|
+
height: 100%;
|
|
961
|
+
padding: 0;
|
|
962
|
+
border-inline-start: 3px solid var(--dg-primary);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
/* ── Summary bar ──────────────────────────────────────────── */
|
|
966
|
+
.kanuni-datagrid-summary-bar {
|
|
967
|
+
display: flex;
|
|
968
|
+
align-items: center;
|
|
969
|
+
flex-wrap: wrap;
|
|
970
|
+
gap: 16px;
|
|
971
|
+
flex: none;
|
|
972
|
+
padding: 5px 8px;
|
|
973
|
+
background: var(--dg-footer-bg);
|
|
974
|
+
border-top: var(--dg-header-border);
|
|
975
|
+
font-size: var(--dg-chrome-font-size);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
.kanuni-datagrid-summary-item {
|
|
979
|
+
display: inline-flex;
|
|
980
|
+
align-items: baseline;
|
|
981
|
+
gap: 4px;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
.kanuni-datagrid-summary-label {
|
|
985
|
+
color: var(--dg-text-muted);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/* ── Selection ────────────────────────────────────────────── */
|
|
989
|
+
.kanuni-datagrid-row--selected,
|
|
990
|
+
.kanuni-datagrid-row--selected .kanuni-datagrid-cell--pinned {
|
|
991
|
+
background: var(--dg-selected-bg);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
.kanuni-datagrid-row--selected:hover,
|
|
995
|
+
.kanuni-datagrid-row--selected:hover .kanuni-datagrid-cell--pinned {
|
|
996
|
+
background: var(--dg-selected-bg);
|
|
997
|
+
filter: brightness(0.97);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
.kanuni-datagrid-select-cell {
|
|
1001
|
+
justify-content: center;
|
|
1002
|
+
padding: 0;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
.kanuni-datagrid-select-cell input {
|
|
1006
|
+
accent-color: var(--dg-primary);
|
|
1007
|
+
cursor: pointer;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
/* ── Row numbers (gap analysis P3.5) ───────────────────────── */
|
|
1011
|
+
/* Muted and tabular on purpose. The number is an address, not data: at full
|
|
1012
|
+
text weight it competes with the first real column, and proportional digits
|
|
1013
|
+
make a column of numbers ragged down its right edge. */
|
|
1014
|
+
.kanuni-datagrid-rownum-cell {
|
|
1015
|
+
color: var(--dg-text-muted);
|
|
1016
|
+
font-variant-numeric: tabular-nums;
|
|
1017
|
+
background: var(--dg-header-bg);
|
|
1018
|
+
user-select: none;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
.kanuni-datagrid-row--selected .kanuni-datagrid-rownum-cell {
|
|
1022
|
+
background: var(--dg-selected-bg);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
/* ── Keyboard focus ───────────────────────────────────────── */
|
|
1026
|
+
/* Focus is virtual (aria-activedescendant), so it must be drawn explicitly —
|
|
1027
|
+
there is no browser focus ring on the cell itself. `inset` keeps the outline
|
|
1028
|
+
from being clipped by the neighbouring cell. */
|
|
1029
|
+
.kanuni-datagrid-cell--focused {
|
|
1030
|
+
outline: 2px solid var(--dg-focus-ring);
|
|
1031
|
+
outline-offset: -2px;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
.kanuni-datagrid-scroller:focus {
|
|
1035
|
+
outline: none;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
.kanuni-datagrid-scroller:focus-visible {
|
|
1039
|
+
outline: 2px solid var(--dg-focus-ring);
|
|
1040
|
+
outline-offset: -2px;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
/* Visually hidden but still read aloud. */
|
|
1044
|
+
.kanuni-datagrid-sr-only {
|
|
1045
|
+
position: absolute;
|
|
1046
|
+
width: 1px;
|
|
1047
|
+
height: 1px;
|
|
1048
|
+
margin: -1px;
|
|
1049
|
+
padding: 0;
|
|
1050
|
+
overflow: hidden;
|
|
1051
|
+
clip: rect(0, 0, 0, 0);
|
|
1052
|
+
white-space: nowrap;
|
|
1053
|
+
border: 0;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
/* ── Header filter button ──────────────────────────────────
|
|
1057
|
+
Hidden until the header is hovered or focused (`filterIconMode: 'hover'`,
|
|
1058
|
+
the default). `visibility` rather than `display` so the icon does not push
|
|
1059
|
+
the label around when it appears — the space is reserved either way. */
|
|
1060
|
+
.kanuni-datagrid-filter-button {
|
|
1061
|
+
display: inline-flex;
|
|
1062
|
+
align-items: center;
|
|
1063
|
+
justify-content: center;
|
|
1064
|
+
flex: none;
|
|
1065
|
+
margin-inline-start: auto;
|
|
1066
|
+
padding: 1px 3px;
|
|
1067
|
+
border: none;
|
|
1068
|
+
border-radius: 3px;
|
|
1069
|
+
background: transparent;
|
|
1070
|
+
/* Lives INSIDE the header cell, so it follows the header's foreground: on an
|
|
1071
|
+
ink header the body's muted slate is barely visible. */
|
|
1072
|
+
color: var(--dg-header-text-muted);
|
|
1073
|
+
line-height: 0;
|
|
1074
|
+
cursor: pointer;
|
|
1075
|
+
visibility: hidden;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
.kanuni-datagrid-header-cell:hover > .kanuni-datagrid-filter-button,
|
|
1079
|
+
.kanuni-datagrid-header-cell:focus-within > .kanuni-datagrid-filter-button,
|
|
1080
|
+
.kanuni-datagrid-filter-button--pinned,
|
|
1081
|
+
/* An ACTIVE filter is the only sign that rows are being withheld, so it stays
|
|
1082
|
+
visible in both modes — hiding it would make the grid look simply empty. */
|
|
1083
|
+
.kanuni-datagrid-filter-button--active {
|
|
1084
|
+
visibility: visible;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
.kanuni-datagrid-filter-button:hover {
|
|
1088
|
+
background: var(--dg-primary-soft);
|
|
1089
|
+
color: var(--dg-text);
|
|
1090
|
+
/* `--dg-primary-soft` is a LIGHT tint in every light theme, so the label on
|
|
1091
|
+
it is the body text, not the header's — this is the one spot in the header
|
|
1092
|
+
that deliberately does not follow `--dg-header-text`. */
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
.kanuni-datagrid-filter-button--active {
|
|
1096
|
+
color: var(--dg-primary);
|
|
1097
|
+
background: var(--dg-primary-soft);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
/* ── Column reorder (drag & drop) ──────────────────────────
|
|
1101
|
+
The indicator is an inset shadow, not a border: a border would change the
|
|
1102
|
+
cell's box and shift every column to its right by a pixel mid-drag. */
|
|
1103
|
+
.kanuni-datagrid-header-cell--drop-before {
|
|
1104
|
+
box-shadow: inset 3px 0 0 -1px var(--dg-primary);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
.kanuni-datagrid-header-cell--drop-after {
|
|
1108
|
+
box-shadow: inset -3px 0 0 -1px var(--dg-primary);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
[dir='rtl'] .kanuni-datagrid-header-cell--drop-before {
|
|
1112
|
+
box-shadow: inset -3px 0 0 -1px var(--dg-primary);
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
[dir='rtl'] .kanuni-datagrid-header-cell--drop-after {
|
|
1116
|
+
box-shadow: inset 3px 0 0 -1px var(--dg-primary);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
/* ── Column resize handle ─────────────────────────────────── */
|
|
1120
|
+
.kanuni-datagrid-resize-handle {
|
|
1121
|
+
position: absolute;
|
|
1122
|
+
inset-block: 0;
|
|
1123
|
+
inset-inline-end: -3px;
|
|
1124
|
+
width: 7px;
|
|
1125
|
+
z-index: 5;
|
|
1126
|
+
cursor: col-resize;
|
|
1127
|
+
/* Invisible by default; the line under the cursor is what marks the seam. */
|
|
1128
|
+
background: transparent;
|
|
1129
|
+
touch-action: none;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
.kanuni-datagrid-resize-handle:hover::after,
|
|
1133
|
+
.kanuni-datagrid-header-cell--resizing .kanuni-datagrid-resize-handle::after {
|
|
1134
|
+
content: '';
|
|
1135
|
+
position: absolute;
|
|
1136
|
+
inset-block: 4px;
|
|
1137
|
+
inset-inline-start: 3px;
|
|
1138
|
+
width: 2px;
|
|
1139
|
+
border-radius: 1px;
|
|
1140
|
+
background: var(--dg-primary);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/* ── Loading overlay (busy + percentage) ──────────────────── */
|
|
1144
|
+
.kanuni-datagrid-loading-overlay {
|
|
1145
|
+
position: absolute;
|
|
1146
|
+
inset: 0;
|
|
1147
|
+
z-index: 20;
|
|
1148
|
+
display: flex;
|
|
1149
|
+
align-items: center;
|
|
1150
|
+
justify-content: center;
|
|
1151
|
+
background: color-mix(in srgb, var(--dg-bg) 62%, transparent);
|
|
1152
|
+
/* Pointer-transparent: a recompute triggered by typing must not swallow the
|
|
1153
|
+
next keystroke aimed at the filter box. */
|
|
1154
|
+
pointer-events: none;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/* Exporting is the one phase that takes the grid away, so it looks like it.
|
|
1158
|
+
The sheet is nearly opaque instead of a hint, it swallows clicks instead of
|
|
1159
|
+
letting them fall through to a grid that cannot answer them, and the cursor
|
|
1160
|
+
says wait. See the comment on `data-blocking` in GridLoadingOverlay.tsx. */
|
|
1161
|
+
.kanuni-datagrid-loading-overlay[data-blocking='true'] {
|
|
1162
|
+
background: color-mix(in srgb, var(--dg-bg) 92%, transparent);
|
|
1163
|
+
pointer-events: auto;
|
|
1164
|
+
cursor: progress;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
.kanuni-datagrid-loading-card {
|
|
1168
|
+
display: flex;
|
|
1169
|
+
flex-direction: column;
|
|
1170
|
+
align-items: center;
|
|
1171
|
+
gap: 10px;
|
|
1172
|
+
padding: 18px 24px;
|
|
1173
|
+
border: 1px solid var(--dg-border);
|
|
1174
|
+
border-radius: calc(var(--dg-radius) + 4px);
|
|
1175
|
+
background: var(--dg-surface);
|
|
1176
|
+
box-shadow: var(--dg-shadow-card);
|
|
1177
|
+
color: var(--dg-text);
|
|
1178
|
+
font-size: var(--dg-chrome-font-size);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
.kanuni-datagrid-loading-spinner {
|
|
1182
|
+
width: 26px;
|
|
1183
|
+
height: 26px;
|
|
1184
|
+
border: 2px solid transparent;
|
|
1185
|
+
border-bottom-color: var(--dg-primary);
|
|
1186
|
+
border-radius: 50%;
|
|
1187
|
+
animation: kanuni-datagrid-spin 1s linear infinite;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
.kanuni-datagrid-loading-label {
|
|
1191
|
+
font-weight: 500;
|
|
1192
|
+
color: var(--dg-text-muted);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
.kanuni-datagrid-loading-progress {
|
|
1196
|
+
display: flex;
|
|
1197
|
+
flex-direction: column;
|
|
1198
|
+
align-items: center;
|
|
1199
|
+
gap: 6px;
|
|
1200
|
+
width: 220px;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
.kanuni-datagrid-loading-track {
|
|
1204
|
+
width: 100%;
|
|
1205
|
+
height: 6px;
|
|
1206
|
+
border-radius: 999px;
|
|
1207
|
+
overflow: hidden;
|
|
1208
|
+
background: color-mix(in srgb, var(--dg-border) 70%, transparent);
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
.kanuni-datagrid-loading-fill {
|
|
1212
|
+
height: 100%;
|
|
1213
|
+
border-radius: 999px;
|
|
1214
|
+
background: var(--dg-primary);
|
|
1215
|
+
transition: width 160ms ease-out;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
.kanuni-datagrid-loading-percent {
|
|
1219
|
+
font-size: 11px;
|
|
1220
|
+
font-weight: 600;
|
|
1221
|
+
font-variant-numeric: tabular-nums;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
/* ── Filter panel (cxGrid) ────────────────────────────────────
|
|
1225
|
+
Under the footer, outside the scroller: the conditions describe the whole
|
|
1226
|
+
result, so they must not scroll away with the rows. */
|
|
1227
|
+
.kanuni-datagrid-filter-panel {
|
|
1228
|
+
display: flex;
|
|
1229
|
+
align-items: center;
|
|
1230
|
+
gap: 6px;
|
|
1231
|
+
flex: none;
|
|
1232
|
+
padding: 4px 8px;
|
|
1233
|
+
background: var(--dg-surface-header);
|
|
1234
|
+
border-top: 1px solid var(--dg-border);
|
|
1235
|
+
font-size: var(--dg-chrome-font-size);
|
|
1236
|
+
color: var(--dg-text);
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
/* Suspended: the conditions are still there, just not being applied. Dimmed
|
|
1240
|
+
rather than hidden — there has to be something to switch back on. */
|
|
1241
|
+
.kanuni-datagrid-filter-panel--suspended .kanuni-datagrid-filter-panel-text {
|
|
1242
|
+
opacity: 0.5;
|
|
1243
|
+
text-decoration: line-through;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
.kanuni-datagrid-filter-panel-clear,
|
|
1247
|
+
.kanuni-datagrid-filter-panel-builder {
|
|
1248
|
+
display: inline-flex;
|
|
1249
|
+
align-items: center;
|
|
1250
|
+
justify-content: center;
|
|
1251
|
+
flex: none;
|
|
1252
|
+
width: 18px;
|
|
1253
|
+
height: 18px;
|
|
1254
|
+
padding: 0;
|
|
1255
|
+
border: 1px solid transparent;
|
|
1256
|
+
border-radius: var(--dg-radius-control);
|
|
1257
|
+
background: transparent;
|
|
1258
|
+
color: var(--dg-text-muted);
|
|
1259
|
+
font: inherit;
|
|
1260
|
+
line-height: 1;
|
|
1261
|
+
cursor: pointer;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
.kanuni-datagrid-filter-panel-clear:hover:not(:disabled),
|
|
1265
|
+
.kanuni-datagrid-filter-panel-builder:hover:not(:disabled) {
|
|
1266
|
+
background: var(--dg-surface-hover);
|
|
1267
|
+
border-color: var(--dg-border-strong);
|
|
1268
|
+
color: var(--dg-text);
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
.kanuni-datagrid-filter-panel-clear:disabled,
|
|
1272
|
+
.kanuni-datagrid-filter-panel-builder:disabled {
|
|
1273
|
+
opacity: 0.4;
|
|
1274
|
+
cursor: default;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
.kanuni-datagrid-filter-panel-toggle {
|
|
1278
|
+
flex: none;
|
|
1279
|
+
margin: 0;
|
|
1280
|
+
accent-color: var(--dg-primary);
|
|
1281
|
+
cursor: pointer;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
.kanuni-datagrid-filter-panel-text {
|
|
1285
|
+
flex: 1 1 auto;
|
|
1286
|
+
min-width: 0;
|
|
1287
|
+
overflow: hidden;
|
|
1288
|
+
text-overflow: ellipsis;
|
|
1289
|
+
white-space: nowrap;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
.kanuni-datagrid-filter-panel-builder {
|
|
1293
|
+
margin-inline-start: auto;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
/* Shown only for phases that can actually stop — see CANCELLABLE in the overlay. */
|
|
1297
|
+
.kanuni-datagrid-loading-cancel {
|
|
1298
|
+
margin-top: 8px;
|
|
1299
|
+
padding: 3px 12px;
|
|
1300
|
+
border: 1px solid var(--dg-border);
|
|
1301
|
+
border-radius: var(--dg-radius);
|
|
1302
|
+
background: var(--dg-surface);
|
|
1303
|
+
color: inherit;
|
|
1304
|
+
font: inherit;
|
|
1305
|
+
font-size: 11px;
|
|
1306
|
+
cursor: pointer;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
.kanuni-datagrid-loading-cancel:hover {
|
|
1310
|
+
background: var(--dg-surface-hover);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1314
|
+
.kanuni-datagrid-loading-spinner {
|
|
1315
|
+
animation-duration: 3s;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
.kanuni-datagrid-loading-fill {
|
|
1319
|
+
transition: none;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
/* ── Filter row (under the header, same sticky geometry) ──── */
|
|
1324
|
+
.kanuni-datagrid-filter-row {
|
|
1325
|
+
position: sticky;
|
|
1326
|
+
/* Clears the BANDS as well as the header. `--dg-band-offset` is published by
|
|
1327
|
+
the component (0 when there are no bands); the header height stays a
|
|
1328
|
+
variable here so density keeps working. Sticking at the header height alone
|
|
1329
|
+
parked this row inside the header on any banded grid, and the two drew over
|
|
1330
|
+
each other as soon as the user scrolled. */
|
|
1331
|
+
top: calc(var(--dg-band-offset, 0px) + var(--dg-header-height));
|
|
1332
|
+
z-index: 3;
|
|
1333
|
+
display: flex;
|
|
1334
|
+
height: var(--dg-header-height);
|
|
1335
|
+
background: var(--dg-surface-header);
|
|
1336
|
+
border-bottom: var(--dg-header-border);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
.kanuni-datagrid-filter-cell {
|
|
1340
|
+
display: flex;
|
|
1341
|
+
align-items: center;
|
|
1342
|
+
flex: none;
|
|
1343
|
+
height: 100%;
|
|
1344
|
+
padding: 0 4px;
|
|
1345
|
+
background: var(--dg-surface-header);
|
|
1346
|
+
border-inline-end: var(--dg-header-cell-border);
|
|
1347
|
+
overflow: hidden;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
.kanuni-datagrid-filter-cell--pinned {
|
|
1351
|
+
position: sticky;
|
|
1352
|
+
z-index: 4;
|
|
1353
|
+
background: var(--dg-surface-header);
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
.kanuni-datagrid-filter-input {
|
|
1357
|
+
width: 100%;
|
|
1358
|
+
min-width: 0;
|
|
1359
|
+
height: var(--dg-filter-input-height);
|
|
1360
|
+
padding: 0 6px;
|
|
1361
|
+
border: 1px solid var(--dg-border-strong);
|
|
1362
|
+
border-radius: var(--dg-radius-control);
|
|
1363
|
+
background: var(--dg-surface);
|
|
1364
|
+
color: var(--dg-text);
|
|
1365
|
+
font: inherit;
|
|
1366
|
+
font-size: var(--dg-control-font-size);
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
.kanuni-datagrid-filter-input:focus {
|
|
1370
|
+
outline: none;
|
|
1371
|
+
border-color: var(--dg-primary);
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
/* ── Header filter popup (portaled — see GridPortal) ──────── */
|
|
1375
|
+
.kanuni-datagrid-portal {
|
|
1376
|
+
/* The portal host must not paint or capture anything itself. */
|
|
1377
|
+
position: static;
|
|
1378
|
+
border: none;
|
|
1379
|
+
background: transparent;
|
|
1380
|
+
overflow: visible;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
/* ── Band fold toggle (P2.6) ──────────────────────────────── */
|
|
1384
|
+
.kanuni-datagrid-band-toggle {
|
|
1385
|
+
display: inline-flex;
|
|
1386
|
+
align-items: center;
|
|
1387
|
+
justify-content: center;
|
|
1388
|
+
flex: none;
|
|
1389
|
+
width: 16px;
|
|
1390
|
+
height: 16px;
|
|
1391
|
+
margin-inline-end: 4px;
|
|
1392
|
+
padding: 0;
|
|
1393
|
+
border: none;
|
|
1394
|
+
border-radius: 3px;
|
|
1395
|
+
background: transparent;
|
|
1396
|
+
color: var(--dg-text-muted);
|
|
1397
|
+
font-size: 10px;
|
|
1398
|
+
line-height: 1;
|
|
1399
|
+
cursor: pointer;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
.kanuni-datagrid-band-toggle:hover {
|
|
1403
|
+
background: var(--dg-primary-soft);
|
|
1404
|
+
color: var(--dg-text);
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
.kanuni-datagrid-band-toggle:focus-visible {
|
|
1408
|
+
outline: 2px solid var(--dg-focus-ring);
|
|
1409
|
+
outline-offset: -1px;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
/* ── Search highlight (P2.4) ──────────────────────────────── */
|
|
1413
|
+
/* A <mark>, so the match is in the accessibility tree as emphasis rather than
|
|
1414
|
+
as a colour only. The browser default is a hard yellow that fights every
|
|
1415
|
+
theme; these follow the accent instead. */
|
|
1416
|
+
.kanuni-datagrid-highlight {
|
|
1417
|
+
padding: 0;
|
|
1418
|
+
border-radius: 2px;
|
|
1419
|
+
background: var(--dg-highlight-bg);
|
|
1420
|
+
color: var(--dg-highlight-text);
|
|
1421
|
+
font-weight: 600;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
/* ── Cell / header tooltip (P2.1) ─────────────────────────── */
|
|
1425
|
+
.kanuni-datagrid-tooltip {
|
|
1426
|
+
position: fixed;
|
|
1427
|
+
/* Above every popup: a tooltip describes whatever is under the pointer, and
|
|
1428
|
+
the pointer can be over a popup. */
|
|
1429
|
+
z-index: 1100;
|
|
1430
|
+
/* The tooltip must never be the hover target — it appears UNDER the pointer's
|
|
1431
|
+
path, and taking the pointer would fire mouseout on the cell that asked for
|
|
1432
|
+
it, hiding the tooltip, which un-hovers it, which shows it again. */
|
|
1433
|
+
pointer-events: none;
|
|
1434
|
+
max-width: 420px;
|
|
1435
|
+
padding: 4px 8px;
|
|
1436
|
+
border: 1px solid var(--dg-border-strong);
|
|
1437
|
+
border-radius: var(--dg-radius);
|
|
1438
|
+
background: var(--dg-surface);
|
|
1439
|
+
color: var(--dg-text);
|
|
1440
|
+
box-shadow: var(--dg-shadow-popup);
|
|
1441
|
+
font-size: var(--dg-font-size);
|
|
1442
|
+
line-height: 1.45;
|
|
1443
|
+
/* Long values wrap instead of running off-screen; a pasted paragraph in a
|
|
1444
|
+
cell is exactly the case the tooltip exists for. */
|
|
1445
|
+
white-space: pre-wrap;
|
|
1446
|
+
overflow-wrap: anywhere;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
.kanuni-datagrid-filter-popup {
|
|
1450
|
+
position: fixed;
|
|
1451
|
+
z-index: 1000;
|
|
1452
|
+
display: flex;
|
|
1453
|
+
flex-direction: column;
|
|
1454
|
+
gap: 6px;
|
|
1455
|
+
width: 260px;
|
|
1456
|
+
max-height: 340px;
|
|
1457
|
+
padding: 8px;
|
|
1458
|
+
border: 1px solid var(--dg-border-strong);
|
|
1459
|
+
border-radius: var(--dg-radius);
|
|
1460
|
+
/* Explicit background: a transparent popup was the PivotGrid drill-down bug. */
|
|
1461
|
+
background: var(--dg-surface);
|
|
1462
|
+
color: var(--dg-text);
|
|
1463
|
+
box-shadow: var(--dg-shadow-popup);
|
|
1464
|
+
font-size: var(--dg-font-size);
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
.kanuni-datagrid-filter-search {
|
|
1468
|
+
width: 100%;
|
|
1469
|
+
height: var(--dg-control-height);
|
|
1470
|
+
padding: 0 8px;
|
|
1471
|
+
border: 1px solid var(--dg-border-strong);
|
|
1472
|
+
border-radius: var(--dg-radius-control);
|
|
1473
|
+
background: var(--dg-surface);
|
|
1474
|
+
color: var(--dg-text);
|
|
1475
|
+
font: inherit;
|
|
1476
|
+
font-size: var(--dg-control-font-size);
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
/* "There are more values than these." Sits directly under the search box
|
|
1480
|
+
because the search box is the answer to it — on a capped list, search scans
|
|
1481
|
+
the whole dataset rather than the values already on screen. */
|
|
1482
|
+
.kanuni-datagrid-filter-truncated {
|
|
1483
|
+
margin: 6px 0 0;
|
|
1484
|
+
font-size: 11px;
|
|
1485
|
+
line-height: 1.4;
|
|
1486
|
+
color: var(--dg-text-muted);
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
.kanuni-datagrid-filter-actions {
|
|
1490
|
+
display: flex;
|
|
1491
|
+
gap: 6px;
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
.kanuni-datagrid-filter-actions button,
|
|
1495
|
+
.kanuni-datagrid-filter-footer button {
|
|
1496
|
+
padding: 3px 8px;
|
|
1497
|
+
border: 1px solid var(--dg-border-strong);
|
|
1498
|
+
border-radius: var(--dg-radius-control);
|
|
1499
|
+
background: var(--dg-surface);
|
|
1500
|
+
color: var(--dg-text);
|
|
1501
|
+
font: inherit;
|
|
1502
|
+
font-size: var(--dg-control-font-size);
|
|
1503
|
+
cursor: pointer;
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
.kanuni-datagrid-filter-actions button:hover,
|
|
1507
|
+
.kanuni-datagrid-filter-footer button:hover {
|
|
1508
|
+
background: var(--dg-surface-hover);
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
.kanuni-datagrid-filter-primary {
|
|
1512
|
+
background: var(--dg-primary) !important;
|
|
1513
|
+
border-color: var(--dg-primary) !important;
|
|
1514
|
+
color: var(--dg-on-primary) !important;
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
.kanuni-datagrid-filter-list {
|
|
1518
|
+
flex: 1 1 auto;
|
|
1519
|
+
overflow-y: auto;
|
|
1520
|
+
/* Windowed list — scroll anchoring off for the same reason as the main
|
|
1521
|
+
scroller: rows repositioning during scroll read as content shifts and the
|
|
1522
|
+
browser fights the gesture. See `.kanuni-datagrid-scroller`. */
|
|
1523
|
+
overflow-anchor: none;
|
|
1524
|
+
/* Stop the wheel from CHAINING into the grid once the list hits its end.
|
|
1525
|
+
Without this, wheeling to the bottom of the list keeps going and scrolls
|
|
1526
|
+
the rows underneath — which moves the anchor and closes the popup, in the
|
|
1527
|
+
middle of a gesture the user meant for the list. */
|
|
1528
|
+
overscroll-behavior: contain;
|
|
1529
|
+
border: 1px solid var(--dg-border);
|
|
1530
|
+
border-radius: var(--dg-radius-control);
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
.kanuni-datagrid-filter-item {
|
|
1534
|
+
display: flex;
|
|
1535
|
+
align-items: center;
|
|
1536
|
+
gap: 6px;
|
|
1537
|
+
padding: 3px 6px;
|
|
1538
|
+
cursor: pointer;
|
|
1539
|
+
white-space: nowrap;
|
|
1540
|
+
overflow: hidden;
|
|
1541
|
+
text-overflow: ellipsis;
|
|
1542
|
+
/* FIXED, and the virtualizer depends on it: the list renders only the rows in
|
|
1543
|
+
view and positions them by `index x FILTER_ITEM_HEIGHT`. A label that
|
|
1544
|
+
wrapped to two lines would put every row below it out of place. Single-line
|
|
1545
|
+
is already enforced by `white-space: nowrap` above; this makes the height
|
|
1546
|
+
the popup computes with a fact rather than an assumption.
|
|
1547
|
+
Keep in sync with FILTER_ITEM_HEIGHT in GridHeaderFilterPopup.tsx. */
|
|
1548
|
+
height: 25px;
|
|
1549
|
+
box-sizing: border-box;
|
|
1550
|
+
flex: none;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
.kanuni-datagrid-filter-item:hover,
|
|
1554
|
+
.kanuni-datagrid-filter-item--active {
|
|
1555
|
+
background: var(--dg-surface-hover);
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
.kanuni-datagrid-filter-item input {
|
|
1559
|
+
flex: none;
|
|
1560
|
+
accent-color: var(--dg-primary);
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
.kanuni-datagrid-filter-empty {
|
|
1564
|
+
padding: 10px;
|
|
1565
|
+
color: var(--dg-text-muted);
|
|
1566
|
+
text-align: center;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
.kanuni-datagrid-filter-footer {
|
|
1570
|
+
display: flex;
|
|
1571
|
+
justify-content: flex-end;
|
|
1572
|
+
gap: 6px;
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
/* ── Empty state ──────────────────────────────────────────── */
|
|
1576
|
+
.kanuni-datagrid-empty {
|
|
1577
|
+
display: flex;
|
|
1578
|
+
align-items: center;
|
|
1579
|
+
justify-content: center;
|
|
1580
|
+
padding: 32px 16px;
|
|
1581
|
+
color: var(--dg-text-muted);
|
|
1582
|
+
font-size: var(--dg-font-size);
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
/* ── Accessibility ────────────────────────────────────────── */
|
|
1586
|
+
.kanuni-datagrid-root :focus-visible {
|
|
1587
|
+
outline: 2px solid var(--dg-focus-ring);
|
|
1588
|
+
outline-offset: -2px;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1592
|
+
.kanuni-datagrid-viewport {
|
|
1593
|
+
will-change: auto;
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
/* ── Remote loading rows (P10) ────────────────────────────── */
|
|
1598
|
+
/* A row that exists on the server but has not arrived. It keeps its slot so
|
|
1599
|
+
the scrollbar and every row index stay exact while the block is in flight. */
|
|
1600
|
+
.kanuni-datagrid-row--loading {
|
|
1601
|
+
align-items: center;
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
.kanuni-datagrid-loading-cell {
|
|
1605
|
+
position: sticky;
|
|
1606
|
+
inset-inline-start: 0;
|
|
1607
|
+
flex: 1;
|
|
1608
|
+
border-inline-end: none;
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
.kanuni-datagrid-loading-bar {
|
|
1612
|
+
display: block;
|
|
1613
|
+
width: 40%;
|
|
1614
|
+
max-width: 240px;
|
|
1615
|
+
height: 8px;
|
|
1616
|
+
border-radius: 4px;
|
|
1617
|
+
background: var(--dg-surface-hover);
|
|
1618
|
+
animation: kanuni-datagrid-pulse 1.2s ease-in-out infinite;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
@keyframes kanuni-datagrid-pulse {
|
|
1622
|
+
0%,
|
|
1623
|
+
100% {
|
|
1624
|
+
opacity: 0.45;
|
|
1625
|
+
}
|
|
1626
|
+
50% {
|
|
1627
|
+
opacity: 1;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
/* Respect a reduced-motion preference: the pulse is decoration, and a user who
|
|
1632
|
+
asked for stillness should get a static placeholder. */
|
|
1633
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1634
|
+
.kanuni-datagrid-loading-bar {
|
|
1635
|
+
animation: none;
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
|
|
1640
|
+
/* ── RTL ──────────────────────────────────────────────────────
|
|
1641
|
+
Insets are logical (inset-inline-*), so sticky columns flip on their own.
|
|
1642
|
+
What does NOT flip automatically is a directional SHADOW: the edge shadow
|
|
1643
|
+
that separates a left-pinned block from the scrolling body has to fall on
|
|
1644
|
+
the other side when the writing direction reverses. */
|
|
1645
|
+
.kanuni-datagrid-root[dir='rtl'] .kanuni-datagrid-pin-edge-left,
|
|
1646
|
+
[dir='rtl'] .kanuni-datagrid-root .kanuni-datagrid-pin-edge-left {
|
|
1647
|
+
box-shadow: var(--dg-shadow-pinned-right);
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
.kanuni-datagrid-root[dir='rtl'] .kanuni-datagrid-pin-edge-right,
|
|
1651
|
+
[dir='rtl'] .kanuni-datagrid-root .kanuni-datagrid-pin-edge-right {
|
|
1652
|
+
box-shadow: var(--dg-shadow-pinned);
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
/* The sort caret and the resize handle are positioned, not flowed. */
|
|
1656
|
+
.kanuni-datagrid-root[dir='rtl'] .kanuni-datagrid-resize-handle,
|
|
1657
|
+
[dir='rtl'] .kanuni-datagrid-root .kanuni-datagrid-resize-handle {
|
|
1658
|
+
right: auto;
|
|
1659
|
+
left: 0;
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
/* ── Density presets ──────────────────────────────────────────
|
|
1663
|
+
The ROW HEIGHT is not set here: it is a core value (the virtualizer runs on
|
|
1664
|
+
it), so the component resolves the preset into `rowHeight` and core feeds it
|
|
1665
|
+
back as --dg-row-height. What lives here is everything CSS owns — header
|
|
1666
|
+
height, padding and type scale — so the two can never disagree about the
|
|
1667
|
+
row geometry. */
|
|
1668
|
+
.kanuni-datagrid-root.kanuni-datagrid-density-compact {
|
|
1669
|
+
--dg-header-height: 28px;
|
|
1670
|
+
--dg-cell-padding-x: 6px;
|
|
1671
|
+
--dg-font-size: 12px;
|
|
1672
|
+
--dg-header-font-size: 11px;
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
.kanuni-datagrid-root.kanuni-datagrid-density-comfortable {
|
|
1676
|
+
--dg-header-height: 40px;
|
|
1677
|
+
--dg-cell-padding-x: 12px;
|
|
1678
|
+
--dg-font-size: 14px;
|
|
1679
|
+
--dg-header-font-size: 13px;
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
/* ── Row context menu ─────────────────────────────────────── */
|
|
1683
|
+
.kanuni-datagrid-context-menu {
|
|
1684
|
+
position: fixed;
|
|
1685
|
+
z-index: 1200;
|
|
1686
|
+
min-width: 180px;
|
|
1687
|
+
max-width: 320px;
|
|
1688
|
+
/*
|
|
1689
|
+
* A menu longer than the screen scrolls instead of running off it: the
|
|
1690
|
+
* clamp in `GridContextMenu` can only move a menu that FITS, and an app row
|
|
1691
|
+
* menu with a dozen entries plus the built-ins does not.
|
|
1692
|
+
*
|
|
1693
|
+
* This is safe only because the SUBMENU is portaled as a sibling. While it
|
|
1694
|
+
* was a child, any overflow value other than `visible` clipped it away
|
|
1695
|
+
* entirely — which is why this rule was removed once before.
|
|
1696
|
+
*/
|
|
1697
|
+
max-height: calc(100vh - 16px);
|
|
1698
|
+
overflow-y: auto;
|
|
1699
|
+
padding: 4px;
|
|
1700
|
+
border: 1px solid var(--dg-border-strong);
|
|
1701
|
+
border-radius: var(--dg-radius);
|
|
1702
|
+
background: var(--dg-surface);
|
|
1703
|
+
color: var(--dg-text);
|
|
1704
|
+
box-shadow: var(--dg-shadow-menu);
|
|
1705
|
+
font-size: var(--dg-font-size);
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
.kanuni-datagrid-context-menu:focus {
|
|
1709
|
+
outline: none;
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
.kanuni-datagrid-menu-row {
|
|
1713
|
+
position: relative;
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
.kanuni-datagrid-menu-item {
|
|
1717
|
+
display: flex;
|
|
1718
|
+
align-items: center;
|
|
1719
|
+
gap: 8px;
|
|
1720
|
+
width: 100%;
|
|
1721
|
+
padding: 5px 8px;
|
|
1722
|
+
border: none;
|
|
1723
|
+
border-radius: var(--dg-radius-control);
|
|
1724
|
+
background: transparent;
|
|
1725
|
+
color: inherit;
|
|
1726
|
+
font: inherit;
|
|
1727
|
+
text-align: start;
|
|
1728
|
+
cursor: pointer;
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
/* Hover and keyboard highlight share one class so the two navigation modes
|
|
1732
|
+
look identical — a menu where the arrow keys highlight differently from the
|
|
1733
|
+
mouse reads as two different menus. */
|
|
1734
|
+
.kanuni-datagrid-menu-item:hover:not(:disabled),
|
|
1735
|
+
.kanuni-datagrid-menu-item--active:not(:disabled) {
|
|
1736
|
+
background: var(--dg-surface-hover);
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
.kanuni-datagrid-menu-item:disabled {
|
|
1740
|
+
opacity: 0.45;
|
|
1741
|
+
cursor: default;
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
.kanuni-datagrid-menu-icon {
|
|
1745
|
+
display: inline-flex;
|
|
1746
|
+
align-items: center;
|
|
1747
|
+
flex: none;
|
|
1748
|
+
width: 16px;
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
.kanuni-datagrid-menu-label {
|
|
1752
|
+
flex: 1 1 auto;
|
|
1753
|
+
overflow: hidden;
|
|
1754
|
+
text-overflow: ellipsis;
|
|
1755
|
+
white-space: nowrap;
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
.kanuni-datagrid-menu-shortcut {
|
|
1759
|
+
flex: none;
|
|
1760
|
+
color: var(--dg-text-muted);
|
|
1761
|
+
font-size: 11px;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
.kanuni-datagrid-menu-caret {
|
|
1765
|
+
flex: none;
|
|
1766
|
+
color: var(--dg-text-muted);
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
.kanuni-datagrid-menu-separator {
|
|
1770
|
+
height: 1px;
|
|
1771
|
+
margin: 4px 6px;
|
|
1772
|
+
background: var(--dg-border);
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
/* Submenus open toward the inline end, so they flip in RTL for free. */
|
|
1776
|
+
/*
|
|
1777
|
+
* Positioned in VIEWPORT coordinates by `GridContextMenu`, because it is a
|
|
1778
|
+
* sibling of the menu rather than a child — see the comment there. It inherits
|
|
1779
|
+
* the menu's box from the shared class; only the positioning differs.
|
|
1780
|
+
*/
|
|
1781
|
+
.kanuni-datagrid-submenu {
|
|
1782
|
+
position: fixed;
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
/* ── Toolbar icon button + export menu ────────────────────── */
|
|
1786
|
+
.kanuni-datagrid-toolbar-icon-button {
|
|
1787
|
+
display: inline-flex;
|
|
1788
|
+
align-items: center;
|
|
1789
|
+
justify-content: center;
|
|
1790
|
+
width: 28px;
|
|
1791
|
+
height: 24px;
|
|
1792
|
+
padding: 0;
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
/* Spinner shown in place of the icon while an export runs, so the button does
|
|
1796
|
+
not change size and the toolbar does not reflow mid-export. */
|
|
1797
|
+
.kanuni-datagrid-toolbar-busy {
|
|
1798
|
+
width: 12px;
|
|
1799
|
+
height: 12px;
|
|
1800
|
+
border: 2px solid var(--dg-border-strong);
|
|
1801
|
+
border-top-color: var(--dg-primary);
|
|
1802
|
+
border-radius: 50%;
|
|
1803
|
+
animation: kanuni-datagrid-spin 0.7s linear infinite;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
@keyframes kanuni-datagrid-spin {
|
|
1807
|
+
to {
|
|
1808
|
+
transform: rotate(360deg);
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1813
|
+
.kanuni-datagrid-toolbar-busy {
|
|
1814
|
+
animation: none;
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
/* The export menu reuses the popup shell but is a plain list of actions. */
|
|
1819
|
+
.kanuni-datagrid-export-menu {
|
|
1820
|
+
width: 190px;
|
|
1821
|
+
gap: 2px;
|
|
1822
|
+
padding: 4px;
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
.kanuni-datagrid-chooser-empty {
|
|
1826
|
+
padding: 8px 4px;
|
|
1827
|
+
color: var(--dg-text-muted);
|
|
1828
|
+
font-style: italic;
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
/* ── Column-aligned footer row (punch #18) ─────────────────
|
|
1832
|
+
Sticky to the BOTTOM of the same scroller the header sticks to the top of, so
|
|
1833
|
+
it scrolls horizontally with the columns and stays put vertically. */
|
|
1834
|
+
/* ── Pinned top/bottom rows (P2.3) ────────────────────────── */
|
|
1835
|
+
/* Sticky in the same stack as the header and the footer. `--dg-pinned-gap`
|
|
1836
|
+
counts the header-height rows between this block and its edge (header +
|
|
1837
|
+
filter row above, footer below), so the offset follows the THEME's header
|
|
1838
|
+
height instead of a number measured in JS at the previous theme's size. */
|
|
1839
|
+
.kanuni-datagrid-pinned-rows {
|
|
1840
|
+
position: sticky;
|
|
1841
|
+
/* Above the data rows, below the header — a pinned row must not cover the
|
|
1842
|
+
header when the grid is scrolled to the very top. */
|
|
1843
|
+
z-index: 2;
|
|
1844
|
+
display: flex;
|
|
1845
|
+
flex-direction: column;
|
|
1846
|
+
background: var(--dg-surface-header);
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
.kanuni-datagrid-pinned-rows--top {
|
|
1850
|
+
top: calc(var(--dg-band-offset, 0px) + var(--dg-header-height) * var(--dg-pinned-gap, 1));
|
|
1851
|
+
border-bottom: var(--dg-header-border);
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
.kanuni-datagrid-pinned-rows--bottom {
|
|
1855
|
+
bottom: calc(var(--dg-header-height) * var(--dg-pinned-gap, 0));
|
|
1856
|
+
border-top: var(--dg-header-border);
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
/* The rows inside keep the grid's cell geometry but not its striping — these
|
|
1860
|
+
are not part of the alternating rhythm of the data. */
|
|
1861
|
+
.kanuni-datagrid-pinned-rows .kanuni-datagrid-row,
|
|
1862
|
+
.kanuni-datagrid-pinned-rows .kanuni-datagrid-cell {
|
|
1863
|
+
background: var(--dg-surface-header);
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
.kanuni-datagrid-footer-row {
|
|
1867
|
+
position: sticky;
|
|
1868
|
+
bottom: 0;
|
|
1869
|
+
z-index: 3;
|
|
1870
|
+
display: flex;
|
|
1871
|
+
height: var(--dg-header-height);
|
|
1872
|
+
background: var(--dg-footer-bg);
|
|
1873
|
+
border-top: var(--dg-header-border);
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
/* Totals at the TOP (P2.8) — same row, other edge. The border swaps sides so
|
|
1877
|
+
the rule always separates the totals from the DATA, not from the header. */
|
|
1878
|
+
.kanuni-datagrid-footer-row--top {
|
|
1879
|
+
top: calc(var(--dg-band-offset, 0px) + var(--dg-header-height) * var(--dg-pinned-gap, 1));
|
|
1880
|
+
bottom: auto;
|
|
1881
|
+
border-top: none;
|
|
1882
|
+
border-bottom: var(--dg-header-border);
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
.kanuni-datagrid-footer-cell {
|
|
1886
|
+
display: flex;
|
|
1887
|
+
align-items: center;
|
|
1888
|
+
gap: 5px;
|
|
1889
|
+
flex: none;
|
|
1890
|
+
height: 100%;
|
|
1891
|
+
padding: 0 var(--dg-cell-padding-x);
|
|
1892
|
+
background: var(--dg-footer-bg);
|
|
1893
|
+
border-inline-end: var(--dg-header-cell-border);
|
|
1894
|
+
font-size: var(--dg-header-font-size);
|
|
1895
|
+
white-space: nowrap;
|
|
1896
|
+
overflow: hidden;
|
|
1897
|
+
user-select: none;
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
.kanuni-datagrid-footer-cell--number {
|
|
1901
|
+
justify-content: flex-end;
|
|
1902
|
+
font-variant-numeric: tabular-nums;
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
.kanuni-datagrid-footer-cell--pinned {
|
|
1906
|
+
position: sticky;
|
|
1907
|
+
/* Above the unpinned footer cells, matching the header's pinned layer. */
|
|
1908
|
+
z-index: 4;
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
.kanuni-datagrid-footer-cell:hover {
|
|
1912
|
+
background: var(--dg-surface-hover);
|
|
1913
|
+
cursor: context-menu;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
.kanuni-datagrid-footer-value {
|
|
1917
|
+
overflow: hidden;
|
|
1918
|
+
text-overflow: ellipsis;
|
|
1919
|
+
font-weight: 600;
|
|
1920
|
+
color: var(--dg-text);
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
/* The affordance in an empty cell: barely there until hovered, so an untouched
|
|
1924
|
+
footer reads as blank rather than as a row of dots. */
|
|
1925
|
+
.kanuni-datagrid-footer-hint {
|
|
1926
|
+
margin-inline-start: auto;
|
|
1927
|
+
color: var(--dg-border-strong);
|
|
1928
|
+
opacity: 0;
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
.kanuni-datagrid-footer-cell:hover .kanuni-datagrid-footer-hint {
|
|
1932
|
+
opacity: 1;
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
/* ── Focused row (gap G6) ──────────────────────────────────
|
|
1936
|
+
Deliberately NOT the selection colour: focus is where the keyboard is,
|
|
1937
|
+
selection is what an action applies to, and a user who cannot tell them
|
|
1938
|
+
apart cannot tell what "delete selected" is about to do. An inset bar on the
|
|
1939
|
+
leading edge reads as "you are here" without competing with the row tint. */
|
|
1940
|
+
/* Mixed into the row's OWN colour, never into `transparent`.
|
|
1941
|
+
Pinned cells take `background: inherit`, so a see-through row tint made them
|
|
1942
|
+
see-through too — and a sticky cell you can see through shows the columns
|
|
1943
|
+
sliding along underneath it, which reads as two rows of text printed on top
|
|
1944
|
+
of each other. Both variants below are opaque, and keep the stripe. */
|
|
1945
|
+
.kanuni-datagrid-row--focused {
|
|
1946
|
+
background: color-mix(in srgb, var(--dg-primary) 7%, var(--dg-surface));
|
|
1947
|
+
box-shadow: inset 3px 0 0 0 var(--dg-primary);
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
.kanuni-datagrid-row--odd.kanuni-datagrid-row--focused {
|
|
1951
|
+
background: color-mix(in srgb, var(--dg-primary) 7%, var(--dg-surface-stripe));
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
/* Selected AND focused: the selection tint wins, the bar still marks the row. */
|
|
1955
|
+
.kanuni-datagrid-row--focused.kanuni-datagrid-row--selected {
|
|
1956
|
+
box-shadow: inset 3px 0 0 0 var(--dg-primary);
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
[dir='rtl'] .kanuni-datagrid-row--focused,
|
|
1960
|
+
[dir='rtl'] .kanuni-datagrid-row--focused.kanuni-datagrid-row--selected {
|
|
1961
|
+
box-shadow: inset -3px 0 0 0 var(--dg-primary);
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
/* ── Row drag & drop (G7) ─────────────────────────────────── */
|
|
1965
|
+
/*
|
|
1966
|
+
* A sticky GRIP overlaid at the row's inline start, not a lead column: a third
|
|
1967
|
+
* lead column would have to be threaded through `leadColumnWidth`, every
|
|
1968
|
+
* sticky offset and every aria-colindex across six components, and those
|
|
1969
|
+
* drifting apart is exactly what desynchronizes header and body.
|
|
1970
|
+
*/
|
|
1971
|
+
.kanuni-datagrid-row-grip {
|
|
1972
|
+
position: sticky;
|
|
1973
|
+
inset-inline-start: 0;
|
|
1974
|
+
z-index: 5;
|
|
1975
|
+
display: flex;
|
|
1976
|
+
align-items: center;
|
|
1977
|
+
justify-content: center;
|
|
1978
|
+
flex: none;
|
|
1979
|
+
width: 0;
|
|
1980
|
+
overflow: visible;
|
|
1981
|
+
color: var(--dg-text-muted, #94a3b8);
|
|
1982
|
+
cursor: grab;
|
|
1983
|
+
opacity: 0;
|
|
1984
|
+
transition: opacity 120ms ease;
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
/* Takes no horizontal space (width: 0) so column geometry is untouched; the
|
|
1988
|
+
glyph is painted into the row's own leading padding. */
|
|
1989
|
+
.kanuni-datagrid-row-grip > svg {
|
|
1990
|
+
position: absolute;
|
|
1991
|
+
/* Past the row-number column when one is showing (audit finding #4): the
|
|
1992
|
+
glyph used to sit at 1px unconditionally, which put it on top of the
|
|
1993
|
+
digits once P3.5 parked a number column at the inline start — visible at
|
|
1994
|
+
six-digit row counts, where the right-aligned number reaches the cell's
|
|
1995
|
+
left edge. The adapter sets the variable; without it nothing changes. */
|
|
1996
|
+
inset-inline-start: calc(var(--dg-grip-inset, 0px) + 1px);
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
.kanuni-datagrid-row:hover .kanuni-datagrid-row-grip {
|
|
2000
|
+
opacity: 1;
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
.kanuni-datagrid-row-grip:active {
|
|
2004
|
+
cursor: grabbing;
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
/* The drop line. An INSET box-shadow rather than a border: a border would add
|
|
2008
|
+
a pixel to the row and shift every row below it by one as the pointer moves,
|
|
2009
|
+
which reads as the whole grid twitching. */
|
|
2010
|
+
.kanuni-datagrid-row--drop-before {
|
|
2011
|
+
box-shadow: inset 0 2px 0 0 var(--dg-primary);
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
.kanuni-datagrid-row--drop-after {
|
|
2015
|
+
box-shadow: inset 0 -2px 0 0 var(--dg-primary);
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
/* ── Tree data (P1.4) ──────────────────────────────────────
|
|
2019
|
+
Indentation is inline on the cell (depth is data); everything visual lives
|
|
2020
|
+
here so all six themes and dark mode restyle the toggle for free. */
|
|
2021
|
+
.kanuni-datagrid-tree-cell {
|
|
2022
|
+
display: inline-flex;
|
|
2023
|
+
align-items: center;
|
|
2024
|
+
gap: 4px;
|
|
2025
|
+
min-width: 0;
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
.kanuni-datagrid-tree-toggle {
|
|
2029
|
+
flex: none;
|
|
2030
|
+
width: 16px;
|
|
2031
|
+
height: 16px;
|
|
2032
|
+
padding: 0;
|
|
2033
|
+
border: 0;
|
|
2034
|
+
background: transparent;
|
|
2035
|
+
color: var(--dg-text-muted, #64748b);
|
|
2036
|
+
font-size: 10px;
|
|
2037
|
+
line-height: 16px;
|
|
2038
|
+
cursor: pointer;
|
|
2039
|
+
border-radius: var(--dg-radius-control, 4px);
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
.kanuni-datagrid-tree-toggle:hover {
|
|
2043
|
+
color: var(--dg-text, #0f172a);
|
|
2044
|
+
background: var(--dg-surface-hover, #f1f5f9);
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
.kanuni-datagrid-tree-toggle--leaf {
|
|
2048
|
+
cursor: default;
|
|
2049
|
+
pointer-events: none;
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
/* Shown only because a descendant matched the filter — visibly the path,
|
|
2053
|
+
not a match. */
|
|
2054
|
+
.kanuni-datagrid-tree-cell--ancestor {
|
|
2055
|
+
color: var(--dg-text-muted, #64748b);
|
|
2056
|
+
}
|
|
2057
|
+
/* ── Cell flash (P1.7) ─────────────────────────────────────
|
|
2058
|
+
The "this number just changed" pulse. Driven by a class the adapter adds
|
|
2059
|
+
and removes; the colour is a token so all six themes and dark mode inherit
|
|
2060
|
+
it, and the animation is what fades — no JS frame loop. */
|
|
2061
|
+
.kanuni-datagrid-cell--flash {
|
|
2062
|
+
animation: kanuni-datagrid-flash var(--dg-flash-duration, 700ms) ease-out;
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
@keyframes kanuni-datagrid-flash {
|
|
2066
|
+
from {
|
|
2067
|
+
background-color: var(--dg-flash-bg, rgba(3, 105, 161, 0.35));
|
|
2068
|
+
}
|
|
2069
|
+
to {
|
|
2070
|
+
background-color: transparent;
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
/* A flashing grid is exactly the kind of motion that triggers vestibular
|
|
2075
|
+
symptoms. Reduced-motion keeps the SIGNAL — the cell still marks itself —
|
|
2076
|
+
and drops the animation. */
|
|
2077
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2078
|
+
.kanuni-datagrid-cell--flash {
|
|
2079
|
+
animation: none;
|
|
2080
|
+
background-color: var(--dg-flash-bg, rgba(3, 105, 161, 0.35));
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
/* ── Print layout (gap analysis P3.4) ──────────────────────────
|
|
2084
|
+
Everything that makes the grid fast on screen is what ruins it on paper. The
|
|
2085
|
+
scroller is a clipped, fixed-height box holding one screenful; the print
|
|
2086
|
+
engine gets exactly that and paginates twenty rows out of a hundred thousand.
|
|
2087
|
+
|
|
2088
|
+
This class undoes the five things that clip the content, in the order they
|
|
2089
|
+
clip it: the root's own box, the scroll container, the canvas that carries
|
|
2090
|
+
the virtual height, the absolutely-positioned viewport, and the block
|
|
2091
|
+
transform that parks the rendered window at its scroll offset.
|
|
2092
|
+
|
|
2093
|
+
The class is set by the ADAPTER, not by a bare `@media print`, because the
|
|
2094
|
+
rows themselves have to be re-rendered — CSS cannot un-virtualize a list that
|
|
2095
|
+
does not exist in the DOM. */
|
|
2096
|
+
.kanuni-datagrid-print {
|
|
2097
|
+
height: auto !important;
|
|
2098
|
+
max-height: none !important;
|
|
2099
|
+
overflow: visible !important;
|
|
2100
|
+
/* Apps mount the grid as a flex child that fills the page — the shape a
|
|
2101
|
+
screen layout wants. Flex sizing beats `height: auto`, so the box kept the
|
|
2102
|
+
viewport's height and the rows below the fold printed on top of whatever
|
|
2103
|
+
came next. `flex: none` hands the sizing back to the content.
|
|
2104
|
+
A host that constrains the grid from further out still has to release it;
|
|
2105
|
+
the grid can only unclip itself. */
|
|
2106
|
+
flex: none !important;
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
.kanuni-datagrid-print .kanuni-datagrid-scroller {
|
|
2110
|
+
height: auto !important;
|
|
2111
|
+
max-height: none !important;
|
|
2112
|
+
overflow: visible !important;
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
.kanuni-datagrid-print .kanuni-datagrid-canvas,
|
|
2116
|
+
.kanuni-datagrid-print .kanuni-datagrid-rows {
|
|
2117
|
+
/* The virtual extent, in a document that is no longer virtual.
|
|
2118
|
+
`.kanuni-datagrid-rows` is the one that matters and the one that was
|
|
2119
|
+
missed first: it carries the scroll canvas's height inline, and with the
|
|
2120
|
+
viewport put back in flow inside it, that inline height decided how much
|
|
2121
|
+
of the grid reached the page — five hundred rows printed as two hundred
|
|
2122
|
+
pixels of paper. The canvas is here for the same reason one level up. */
|
|
2123
|
+
height: auto !important;
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
.kanuni-datagrid-print .kanuni-datagrid-viewport {
|
|
2127
|
+
position: static;
|
|
2128
|
+
/* The block offset is a scroll position. On paper there is no scrolling, and
|
|
2129
|
+
leaving it in pushed the first row down by however far the user happened to
|
|
2130
|
+
have scrolled. */
|
|
2131
|
+
transform: none !important;
|
|
2132
|
+
will-change: auto;
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
/* Sticky is meaningless without a scrolling ancestor, but it still creates the
|
|
2136
|
+
stacking and offset behaviour — a pinned column would print on top of the
|
|
2137
|
+
cell it sits beside. In flow, each cell keeps its own width and order. */
|
|
2138
|
+
.kanuni-datagrid-print .kanuni-datagrid-header,
|
|
2139
|
+
.kanuni-datagrid-print .kanuni-datagrid-band-row,
|
|
2140
|
+
.kanuni-datagrid-print .kanuni-datagrid-filter-row,
|
|
2141
|
+
.kanuni-datagrid-print .kanuni-datagrid-pinned-rows,
|
|
2142
|
+
.kanuni-datagrid-print .kanuni-datagrid-footer-row {
|
|
2143
|
+
position: static !important;
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
/* Rules that only make sense once the ink is on paper. */
|
|
2147
|
+
@media print {
|
|
2148
|
+
/* Release the page around the grid.
|
|
2149
|
+
A grid is normally mounted as a flex child filling a viewport-height shell,
|
|
2150
|
+
and every ancestor in that chain holds it at one screen — the grid can
|
|
2151
|
+
unclip itself and still print one page. The adapter marks the ROOT ELEMENT
|
|
2152
|
+
while print layout is on, because CSS cannot reach an ancestor from a class
|
|
2153
|
+
on the grid, and only for the duration.
|
|
2154
|
+
|
|
2155
|
+
The grid's OWN wrappers are named; the host's are not, so an app with its
|
|
2156
|
+
own fixed-height shell needs the same two lines on it. */
|
|
2157
|
+
.kanuni-datagrid-printing,
|
|
2158
|
+
.kanuni-datagrid-printing body,
|
|
2159
|
+
.kanuni-datagrid-printing .kanuni-datagrid-enterprise,
|
|
2160
|
+
.kanuni-datagrid-printing .kanuni-datagrid-main {
|
|
2161
|
+
height: auto !important;
|
|
2162
|
+
max-height: none !important;
|
|
2163
|
+
overflow: visible !important;
|
|
2164
|
+
flex: none !important;
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
/* A row split across a page break is unreadable — the top half of the text on
|
|
2168
|
+
one sheet and the bottom half on the next. */
|
|
2169
|
+
.kanuni-datagrid-print .kanuni-datagrid-row,
|
|
2170
|
+
.kanuni-datagrid-print .kanuni-datagrid-group-row {
|
|
2171
|
+
break-inside: avoid;
|
|
2172
|
+
page-break-inside: avoid;
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
/* Chrome and Safari drop background colours by default, which turns the
|
|
2176
|
+
header, the group banners and the row striping into undifferentiated white.
|
|
2177
|
+
The structure IS the meaning here, so ask for them back. */
|
|
2178
|
+
.kanuni-datagrid-print {
|
|
2179
|
+
-webkit-print-color-adjust: exact;
|
|
2180
|
+
print-color-adjust: exact;
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
/* Controls that print as furniture: nothing on paper can be clicked, and each
|
|
2184
|
+
one costs a column of width that the data needs. */
|
|
2185
|
+
.kanuni-datagrid-print .kanuni-datagrid-toolbar,
|
|
2186
|
+
.kanuni-datagrid-print .kanuni-datagrid-filter-panel,
|
|
2187
|
+
.kanuni-datagrid-print .kanuni-datagrid-pager,
|
|
2188
|
+
.kanuni-datagrid-print .kanuni-datagrid-status-bar,
|
|
2189
|
+
.kanuni-datagrid-print .kanuni-datagrid-sidebar,
|
|
2190
|
+
.kanuni-datagrid-print .kanuni-datagrid-resize-handle,
|
|
2191
|
+
.kanuni-datagrid-print .kanuni-datagrid-filter-button {
|
|
2192
|
+
display: none !important;
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
/* ── Cell notes (gap analysis P3.3) ────────────────────────────
|
|
2197
|
+
A note is something a PERSON attached to this cell, and the whole value of
|
|
2198
|
+
the marker is that you can find those cells without hovering every one of
|
|
2199
|
+
them. Excel's corner triangle, for the same reason and in the same corner. */
|
|
2200
|
+
.kanuni-datagrid-cell--noted {
|
|
2201
|
+
position: relative;
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
.kanuni-datagrid-cell--noted::after {
|
|
2205
|
+
content: '';
|
|
2206
|
+
position: absolute;
|
|
2207
|
+
top: 0;
|
|
2208
|
+
/* Logical, not `right`: in an RTL grid the marker belongs in the corner the
|
|
2209
|
+
reader's eye ends at, which is the other one. */
|
|
2210
|
+
inset-inline-end: 0;
|
|
2211
|
+
width: 0;
|
|
2212
|
+
height: 0;
|
|
2213
|
+
/* A triangle from borders — no glyph, no font dependency, no extra element in
|
|
2214
|
+
a cell the row memo is built to keep cheap. */
|
|
2215
|
+
border-top: 6px solid var(--dg-note-marker);
|
|
2216
|
+
border-inline-start: 6px solid transparent;
|
|
2217
|
+
pointer-events: none;
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
.kanuni-datagrid-note-editor {
|
|
2221
|
+
display: flex;
|
|
2222
|
+
flex-direction: column;
|
|
2223
|
+
gap: 8px;
|
|
2224
|
+
padding: 8px;
|
|
2225
|
+
min-width: 260px;
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
.kanuni-datagrid-note-input {
|
|
2229
|
+
font: inherit;
|
|
2230
|
+
color: var(--dg-text);
|
|
2231
|
+
background: var(--dg-surface);
|
|
2232
|
+
border: 1px solid var(--dg-border);
|
|
2233
|
+
border-radius: var(--dg-radius);
|
|
2234
|
+
padding: 6px 8px;
|
|
2235
|
+
resize: vertical;
|
|
2236
|
+
min-height: 64px;
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
.kanuni-datagrid-note-input:focus-visible {
|
|
2240
|
+
outline: 2px solid var(--dg-focus-ring);
|
|
2241
|
+
outline-offset: -1px;
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
.kanuni-datagrid-note-actions {
|
|
2245
|
+
display: flex;
|
|
2246
|
+
justify-content: flex-end;
|
|
2247
|
+
gap: 6px;
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
/* ── Cell spans (gap analysis P3.1) ────────────────────────────
|
|
2251
|
+
The block is an absolutely-positioned child of the cell, not a taller cell.
|
|
2252
|
+
A taller cell would change the height of its ROW, and a grid with an
|
|
2253
|
+
`autoHeight` column measures rows and feeds the result back to the engine —
|
|
2254
|
+
block inflates row, row moves offsets, offsets resize block, React ends it
|
|
2255
|
+
with "Maximum update depth exceeded". Out of the flow, the cell stays the
|
|
2256
|
+
height it always was. */
|
|
2257
|
+
.kanuni-datagrid-cell--span {
|
|
2258
|
+
position: relative;
|
|
2259
|
+
/* The box reaches past the cell's own bottom edge on purpose. */
|
|
2260
|
+
overflow: visible;
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
.kanuni-datagrid-span-box {
|
|
2264
|
+
position: absolute;
|
|
2265
|
+
top: 0;
|
|
2266
|
+
inset-inline: 0;
|
|
2267
|
+
display: flex;
|
|
2268
|
+
/* Top-aligned, not centred: a two-row block and a fifty-row block should put
|
|
2269
|
+
the value in the same place, and a reader looks for it beside the first row
|
|
2270
|
+
of the group. */
|
|
2271
|
+
align-items: flex-start;
|
|
2272
|
+
/* Horizontal padding matches an ordinary cell so the value lines up with the
|
|
2273
|
+
column above and below. The top padding centres it within the block's FIRST
|
|
2274
|
+
row — the grid's line-height is 1.4, so half the leftover row height puts
|
|
2275
|
+
it exactly where an unmerged cell would sit. */
|
|
2276
|
+
padding: calc((var(--dg-row-height) - 1.4em) / 2) var(--dg-cell-padding-x);
|
|
2277
|
+
/* Its own background: the row underneath ends where the row does, and the
|
|
2278
|
+
rest of the block would show whatever is behind it. */
|
|
2279
|
+
background: var(--dg-surface);
|
|
2280
|
+
/* Above the STATIC center cells of later rows (which would otherwise paint
|
|
2281
|
+
their backgrounds over everything below the block's first row) — but below
|
|
2282
|
+
the pinned columns' 2. The audit caught 3 here: a center column's block
|
|
2283
|
+
painted OVER the left-pinned column during horizontal scroll, because the
|
|
2284
|
+
box competes in the viewport's stacking context while a box inside a
|
|
2285
|
+
pinned cell is scoped by its parent's own context. */
|
|
2286
|
+
z-index: 1;
|
|
2287
|
+
border-bottom: var(--dg-row-border);
|
|
2288
|
+
overflow: hidden;
|
|
2289
|
+
white-space: nowrap;
|
|
2290
|
+
text-overflow: ellipsis;
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
.kanuni-datagrid-cell--span-covered {
|
|
2294
|
+
/* No content, no rules. The block above owns this space. */
|
|
2295
|
+
border-bottom: none;
|
|
2296
|
+
background: transparent;
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
/* The FOCUSED covered cell lifts over the block and shows its own content —
|
|
2300
|
+
this is what makes an editor opened on it visible (audit finding #3), and
|
|
2301
|
+
arrowing through a merge gives per-row feedback instead of a void. */
|
|
2302
|
+
.kanuni-datagrid-cell--span-covered.kanuni-datagrid-cell--focused {
|
|
2303
|
+
position: relative;
|
|
2304
|
+
z-index: 2;
|
|
2305
|
+
background: var(--dg-surface);
|
|
2306
|
+
border-bottom: var(--dg-row-border);
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
/* A merged block sits over rows that stripe, get selected and get hovered.
|
|
2310
|
+
Their treatment must not show THROUGH the block — only beside it. */
|
|
2311
|
+
.kanuni-datagrid-row--odd .kanuni-datagrid-span-box,
|
|
2312
|
+
.kanuni-datagrid-row--selected .kanuni-datagrid-span-box {
|
|
2313
|
+
background: var(--dg-surface);
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
/* ── Filter popup resize handle (user request, 2026-08-14) ─────
|
|
2317
|
+
One corner, chosen by where there is room to grow: bottom-right normally,
|
|
2318
|
+
bottom-left when the popup is clamped against the viewport's right edge.
|
|
2319
|
+
Drawn as the familiar diagonal ridges so it reads as "drag me". */
|
|
2320
|
+
.kanuni-datagrid-popup-resize {
|
|
2321
|
+
position: absolute;
|
|
2322
|
+
bottom: 0;
|
|
2323
|
+
width: 14px;
|
|
2324
|
+
height: 14px;
|
|
2325
|
+
z-index: 2;
|
|
2326
|
+
background:
|
|
2327
|
+
repeating-linear-gradient(
|
|
2328
|
+
-45deg,
|
|
2329
|
+
transparent 0 3px,
|
|
2330
|
+
var(--dg-border-strong) 3px 4px
|
|
2331
|
+
);
|
|
2332
|
+
opacity: 0.7;
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
.kanuni-datagrid-popup-resize--right {
|
|
2336
|
+
right: 0;
|
|
2337
|
+
cursor: nwse-resize;
|
|
2338
|
+
clip-path: polygon(100% 0, 100% 100%, 0 100%);
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
.kanuni-datagrid-popup-resize--left {
|
|
2342
|
+
left: 0;
|
|
2343
|
+
cursor: nesw-resize;
|
|
2344
|
+
clip-path: polygon(0 0, 100% 100%, 0 100%);
|
|
2345
|
+
background:
|
|
2346
|
+
repeating-linear-gradient(
|
|
2347
|
+
45deg,
|
|
2348
|
+
transparent 0 3px,
|
|
2349
|
+
var(--dg-border-strong) 3px 4px
|
|
2350
|
+
);
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
/* ─────────────────────────────────────────────────────────────
|
|
2354
|
+
KANUNILABS DataGrid — packaged themes
|
|
2355
|
+
─────────────────────────────────────────────────────────────
|
|
2356
|
+
Six presets, each in a light and a dark palette, each at three densities.
|
|
2357
|
+
Nothing here is copied from a third-party stylesheet: every rule is a
|
|
2358
|
+
KanuniLabs class overriding a KanuniLabs --dg-* token. The names are the
|
|
2359
|
+
industry's shared vocabulary for a grid's VISUAL DENSITY AND CHROME — the
|
|
2360
|
+
same sense in which a typeface is called "grotesque" — and the palettes,
|
|
2361
|
+
metrics and line work below are our own.
|
|
2362
|
+
|
|
2363
|
+
── How a theme is expressed ───────────────────────────────
|
|
2364
|
+
A theme is a token override and (almost) nothing else. That is the whole
|
|
2365
|
+
design: the stylesheet that draws the grid already reads every colour,
|
|
2366
|
+
metric, shadow and border through a custom property, so a preset never
|
|
2367
|
+
restyles a component and can never fall behind one. It also costs the render
|
|
2368
|
+
path exactly nothing — the browser resolves the variables during style
|
|
2369
|
+
computation, and switching themes at runtime is one class swap on the root,
|
|
2370
|
+
not a re-render of a single cell.
|
|
2371
|
+
|
|
2372
|
+
── Why the selectors are written out three times ──────────
|
|
2373
|
+
Tokens have to reach three roots: the grid itself, the enterprise wrapper
|
|
2374
|
+
(a SIBLING owns the row/form editor), and anything portalled to <body>
|
|
2375
|
+
(`.kanuni-datagrid-detached`). That is the same trio the base sheet declares
|
|
2376
|
+
its defaults on. Writing all three also lands the block at specificity
|
|
2377
|
+
(0,2,0) — equal to the base `.kanuni-datagrid-root.kanuni-datagrid-dark`
|
|
2378
|
+
block and later in the file, so a theme wins; and the theme's own dark
|
|
2379
|
+
variant, at (0,3,0), wins over both. Order and specificity are load-bearing
|
|
2380
|
+
here: this file MUST be concatenated after DataGridTheme.css.
|
|
2381
|
+
───────────────────────────────────────────────────────────── */
|
|
2382
|
+
|
|
2383
|
+
/* ═══════════════════════════════════════════════════════════
|
|
2384
|
+
QUARTZ — modern, minimal chrome
|
|
2385
|
+
═══════════════════════════════════════════════════════════
|
|
2386
|
+
The least furniture that still reads as a table: no column separators at
|
|
2387
|
+
all, hairline row rules, and a header that is the same white as the body,
|
|
2388
|
+
separated only by one firm line. Generous radii. For product UIs where the
|
|
2389
|
+
grid sits among cards rather than inside a terminal. */
|
|
2390
|
+
.kanuni-datagrid-root.kanuni-datagrid-theme-quartz,
|
|
2391
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-theme-quartz,
|
|
2392
|
+
.kanuni-datagrid-detached.kanuni-datagrid-theme-quartz {
|
|
2393
|
+
--dg-bg: #ffffff;
|
|
2394
|
+
--dg-surface: #ffffff;
|
|
2395
|
+
--dg-surface-header: #ffffff;
|
|
2396
|
+
--dg-surface-hover: #f6f8fa;
|
|
2397
|
+
--dg-surface-stripe: #fbfcfd;
|
|
2398
|
+
--dg-border: #eaeef2;
|
|
2399
|
+
--dg-border-strong: #d5dbe2;
|
|
2400
|
+
--dg-text: #101828;
|
|
2401
|
+
--dg-text-muted: #667085;
|
|
2402
|
+
--dg-primary: #2563eb;
|
|
2403
|
+
--dg-primary-soft: #dbeafe;
|
|
2404
|
+
--dg-on-primary: #ffffff; /* 5.17:1 */
|
|
2405
|
+
--dg-focus-ring: #1d4ed8;
|
|
2406
|
+
--dg-selected-bg: #dbeafe;
|
|
2407
|
+
--dg-group-bg: #f6f8fa;
|
|
2408
|
+
--dg-footer-bg: #f9fafb;
|
|
2409
|
+
--dg-scrollbar-thumb: #d5dbe2;
|
|
2410
|
+
|
|
2411
|
+
/* No vertical rules — the whole point of the preset. */
|
|
2412
|
+
--dg-cell-border: none;
|
|
2413
|
+
--dg-header-cell-border: none;
|
|
2414
|
+
--dg-row-border: 1px solid var(--dg-border);
|
|
2415
|
+
--dg-header-border: 1px solid var(--dg-border-strong);
|
|
2416
|
+
|
|
2417
|
+
--dg-radius: 8px;
|
|
2418
|
+
--dg-radius-control: 6px;
|
|
2419
|
+
--dg-shadow-popup: 0 8px 24px -6px rgba(16, 24, 40, 0.16);
|
|
2420
|
+
--dg-shadow-popup-sm: 0 6px 18px -4px rgba(16, 24, 40, 0.12);
|
|
2421
|
+
--dg-shadow-menu: 0 10px 28px -8px rgba(16, 24, 40, 0.2);
|
|
2422
|
+
--dg-shadow-card: 0 12px 32px -10px rgba(16, 24, 40, 0.2);
|
|
2423
|
+
--dg-shadow-pinned: 4px 0 8px -6px rgba(16, 24, 40, 0.28);
|
|
2424
|
+
--dg-shadow-pinned-right: -4px 0 8px -6px rgba(16, 24, 40, 0.28);
|
|
2425
|
+
|
|
2426
|
+
--dg-row-height: 36px;
|
|
2427
|
+
--dg-header-height: 40px;
|
|
2428
|
+
--dg-cell-padding-x: 12px;
|
|
2429
|
+
--dg-font-size: 13px;
|
|
2430
|
+
--dg-wrap-line-height: 18px;
|
|
2431
|
+
--dg-header-font-size: 12px;
|
|
2432
|
+
--dg-control-height: 30px;
|
|
2433
|
+
--dg-filter-input-height: 26px;
|
|
2434
|
+
--dg-chrome-padding-y: 8px;
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
.kanuni-datagrid-root.kanuni-datagrid-theme-quartz.kanuni-datagrid-dark,
|
|
2438
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-theme-quartz.kanuni-datagrid-dark,
|
|
2439
|
+
.kanuni-datagrid-detached.kanuni-datagrid-theme-quartz.kanuni-datagrid-dark {
|
|
2440
|
+
--dg-bg: #0d1117;
|
|
2441
|
+
--dg-surface: #0d1117;
|
|
2442
|
+
--dg-surface-header: #0d1117;
|
|
2443
|
+
--dg-surface-hover: #161b22;
|
|
2444
|
+
--dg-surface-stripe: #10151c;
|
|
2445
|
+
--dg-border: #21262d;
|
|
2446
|
+
--dg-border-strong: #30363d;
|
|
2447
|
+
--dg-text: #e6edf3;
|
|
2448
|
+
--dg-text-muted: #8b949e;
|
|
2449
|
+
--dg-primary: #4493f8;
|
|
2450
|
+
--dg-primary-soft: #17325c;
|
|
2451
|
+
--dg-focus-ring: #79c0ff;
|
|
2452
|
+
--dg-selected-bg: #17325c;
|
|
2453
|
+
--dg-group-bg: #161b22;
|
|
2454
|
+
--dg-footer-bg: #12171e;
|
|
2455
|
+
--dg-on-primary: #05101f;
|
|
2456
|
+
--dg-scrollbar-thumb: #30363d;
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
/* ═══════════════════════════════════════════════════════════
|
|
2460
|
+
BALHAM — dense, ruled, financial
|
|
2461
|
+
═══════════════════════════════════════════════════════════
|
|
2462
|
+
Every column and every row is ruled, the type is small, the padding is
|
|
2463
|
+
tight and the corners are nearly square. Built for the case where the grid
|
|
2464
|
+
IS the application and the user is counting rows on screen. */
|
|
2465
|
+
.kanuni-datagrid-root.kanuni-datagrid-theme-balham,
|
|
2466
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-theme-balham,
|
|
2467
|
+
.kanuni-datagrid-detached.kanuni-datagrid-theme-balham {
|
|
2468
|
+
--dg-bg: #ffffff;
|
|
2469
|
+
--dg-surface: #ffffff;
|
|
2470
|
+
--dg-surface-header: #f2f4f6;
|
|
2471
|
+
--dg-surface-hover: #e6ecf2;
|
|
2472
|
+
--dg-surface-stripe: #f7f8fa;
|
|
2473
|
+
--dg-border: #c8ced4;
|
|
2474
|
+
--dg-border-strong: #98a2ac;
|
|
2475
|
+
--dg-text: #1c2126;
|
|
2476
|
+
--dg-text-muted: #5a6570;
|
|
2477
|
+
/* #0b7fc4 was the intended hue and it measured 4.33:1 against the white that
|
|
2478
|
+
sits on it — the group chip's own label, 12px semibold, so AA's large-text
|
|
2479
|
+
exemption does not apply. Two steps darker, same hue, 5.19:1. */
|
|
2480
|
+
--dg-primary: #0a72b0;
|
|
2481
|
+
--dg-primary-soft: #d3e7f5;
|
|
2482
|
+
--dg-on-primary: #ffffff; /* 5.19:1 */
|
|
2483
|
+
--dg-focus-ring: #0a72b0;
|
|
2484
|
+
--dg-selected-bg: #cfe4f3;
|
|
2485
|
+
--dg-group-bg: #e9edf1;
|
|
2486
|
+
--dg-footer-bg: #f2f4f6;
|
|
2487
|
+
--dg-scrollbar-thumb: #98a2ac;
|
|
2488
|
+
|
|
2489
|
+
/* Fully ruled: the grid character. */
|
|
2490
|
+
--dg-cell-border: 1px solid var(--dg-border);
|
|
2491
|
+
--dg-row-border: 1px solid var(--dg-border);
|
|
2492
|
+
--dg-header-cell-border: 1px solid var(--dg-border-strong);
|
|
2493
|
+
--dg-header-border: 1px solid var(--dg-border-strong);
|
|
2494
|
+
|
|
2495
|
+
--dg-radius: 2px;
|
|
2496
|
+
--dg-radius-control: 2px;
|
|
2497
|
+
/* Flat: a ruled grid does not need lift to separate its layers, and the
|
|
2498
|
+
shadow fought the lines. Popups keep a hard border instead. */
|
|
2499
|
+
--dg-shadow-popup: 0 3px 10px rgba(28, 33, 38, 0.22);
|
|
2500
|
+
--dg-shadow-popup-sm: 0 2px 8px rgba(28, 33, 38, 0.18);
|
|
2501
|
+
--dg-shadow-menu: 0 3px 10px rgba(28, 33, 38, 0.22);
|
|
2502
|
+
--dg-shadow-card: 0 4px 14px rgba(28, 33, 38, 0.22);
|
|
2503
|
+
--dg-shadow-panel: 0 3px 10px rgba(28, 33, 38, 0.18);
|
|
2504
|
+
--dg-shadow-dialog: 0 10px 32px rgba(28, 33, 38, 0.34);
|
|
2505
|
+
--dg-shadow-pinned: 2px 0 3px -2px rgba(28, 33, 38, 0.3);
|
|
2506
|
+
--dg-shadow-pinned-right: -2px 0 3px -2px rgba(28, 33, 38, 0.3);
|
|
2507
|
+
|
|
2508
|
+
--dg-row-height: 25px;
|
|
2509
|
+
--dg-header-height: 28px;
|
|
2510
|
+
--dg-cell-padding-x: 6px;
|
|
2511
|
+
--dg-font-size: 12px;
|
|
2512
|
+
--dg-wrap-line-height: 16px;
|
|
2513
|
+
--dg-header-font-size: 11px;
|
|
2514
|
+
--dg-header-weight: 700;
|
|
2515
|
+
--dg-control-height: 24px;
|
|
2516
|
+
--dg-control-font-size: 11px;
|
|
2517
|
+
--dg-chrome-font-size: 11px;
|
|
2518
|
+
--dg-filter-input-height: 20px;
|
|
2519
|
+
--dg-chrome-padding-y: 4px;
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
.kanuni-datagrid-root.kanuni-datagrid-theme-balham.kanuni-datagrid-dark,
|
|
2523
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-theme-balham.kanuni-datagrid-dark,
|
|
2524
|
+
.kanuni-datagrid-detached.kanuni-datagrid-theme-balham.kanuni-datagrid-dark {
|
|
2525
|
+
--dg-bg: #1a2029;
|
|
2526
|
+
--dg-surface: #1a2029;
|
|
2527
|
+
--dg-surface-header: #232b36;
|
|
2528
|
+
--dg-surface-hover: #2c3644;
|
|
2529
|
+
--dg-surface-stripe: #1e242e;
|
|
2530
|
+
--dg-border: #39434f;
|
|
2531
|
+
--dg-border-strong: #566373;
|
|
2532
|
+
--dg-text: #e4e9ef;
|
|
2533
|
+
--dg-text-muted: #97a3b2;
|
|
2534
|
+
--dg-primary: #3fa8e8;
|
|
2535
|
+
--dg-primary-soft: #143349;
|
|
2536
|
+
--dg-focus-ring: #7cc7f2;
|
|
2537
|
+
--dg-selected-bg: #17415e;
|
|
2538
|
+
--dg-group-bg: #262f3b;
|
|
2539
|
+
--dg-footer-bg: #232b36;
|
|
2540
|
+
--dg-on-primary: #06192a;
|
|
2541
|
+
--dg-scrollbar-thumb: #566373;
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
/* ═══════════════════════════════════════════════════════════
|
|
2545
|
+
MATERIAL — tall, quiet lines, real elevation
|
|
2546
|
+
═══════════════════════════════════════════════════════════
|
|
2547
|
+
Rows only, no column rules; larger rhythm; a header set in medium weight
|
|
2548
|
+
with open tracking rather than bold; pill-shaped controls; and shadows that
|
|
2549
|
+
actually lift. The one preset whose type scale grows rather than shrinks. */
|
|
2550
|
+
.kanuni-datagrid-root.kanuni-datagrid-theme-material,
|
|
2551
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-theme-material,
|
|
2552
|
+
.kanuni-datagrid-detached.kanuni-datagrid-theme-material {
|
|
2553
|
+
--dg-bg: #ffffff;
|
|
2554
|
+
--dg-surface: #ffffff;
|
|
2555
|
+
--dg-surface-header: #ffffff;
|
|
2556
|
+
--dg-surface-hover: #f4f0f7;
|
|
2557
|
+
--dg-surface-stripe: #faf8fc;
|
|
2558
|
+
--dg-border: #e7e0ec;
|
|
2559
|
+
--dg-border-strong: #cac4d0;
|
|
2560
|
+
--dg-text: #1d1b20;
|
|
2561
|
+
--dg-text-muted: #49454f;
|
|
2562
|
+
--dg-primary: #6750a4;
|
|
2563
|
+
--dg-primary-soft: #e8def8;
|
|
2564
|
+
--dg-on-primary: #ffffff; /* 6.44:1 */
|
|
2565
|
+
--dg-focus-ring: #6750a4;
|
|
2566
|
+
--dg-selected-bg: #e8def8;
|
|
2567
|
+
--dg-group-bg: #f5eefc;
|
|
2568
|
+
--dg-footer-bg: #faf8fc;
|
|
2569
|
+
--dg-scrollbar-thumb: #cac4d0;
|
|
2570
|
+
|
|
2571
|
+
--dg-cell-border: none;
|
|
2572
|
+
--dg-header-cell-border: none;
|
|
2573
|
+
--dg-row-border: 1px solid var(--dg-border);
|
|
2574
|
+
--dg-header-border: 1px solid var(--dg-border-strong);
|
|
2575
|
+
|
|
2576
|
+
/* A label, not a heading: medium weight, opened up. */
|
|
2577
|
+
--dg-header-weight: 500;
|
|
2578
|
+
--dg-header-tracking: 0.04em;
|
|
2579
|
+
|
|
2580
|
+
--dg-radius: 12px;
|
|
2581
|
+
--dg-radius-control: 20px;
|
|
2582
|
+
--dg-shadow-popup: 0 4px 8px 3px rgba(29, 27, 32, 0.15), 0 1px 3px rgba(29, 27, 32, 0.3);
|
|
2583
|
+
--dg-shadow-popup-sm: 0 2px 6px 2px rgba(29, 27, 32, 0.15), 0 1px 2px rgba(29, 27, 32, 0.3);
|
|
2584
|
+
--dg-shadow-menu: 0 4px 8px 3px rgba(29, 27, 32, 0.15), 0 1px 3px rgba(29, 27, 32, 0.3);
|
|
2585
|
+
--dg-shadow-card: 0 6px 10px 4px rgba(29, 27, 32, 0.15), 0 2px 3px rgba(29, 27, 32, 0.3);
|
|
2586
|
+
--dg-shadow-panel: 0 4px 8px 3px rgba(29, 27, 32, 0.15), 0 1px 3px rgba(29, 27, 32, 0.3);
|
|
2587
|
+
--dg-shadow-dialog: 0 8px 12px 6px rgba(29, 27, 32, 0.15), 0 4px 4px rgba(29, 27, 32, 0.3);
|
|
2588
|
+
--dg-shadow-pinned: 4px 0 8px -5px rgba(29, 27, 32, 0.35);
|
|
2589
|
+
--dg-shadow-pinned-right: -4px 0 8px -5px rgba(29, 27, 32, 0.35);
|
|
2590
|
+
|
|
2591
|
+
--dg-row-height: 40px;
|
|
2592
|
+
--dg-header-height: 48px;
|
|
2593
|
+
--dg-cell-padding-x: 16px;
|
|
2594
|
+
--dg-font-size: 14px;
|
|
2595
|
+
--dg-wrap-line-height: 20px;
|
|
2596
|
+
--dg-header-font-size: 12px;
|
|
2597
|
+
--dg-control-height: 34px;
|
|
2598
|
+
--dg-control-font-size: 13px;
|
|
2599
|
+
--dg-chrome-font-size: 13px;
|
|
2600
|
+
--dg-filter-input-height: 28px;
|
|
2601
|
+
--dg-chrome-padding-y: 10px;
|
|
2602
|
+
}
|
|
2603
|
+
|
|
2604
|
+
.kanuni-datagrid-root.kanuni-datagrid-theme-material.kanuni-datagrid-dark,
|
|
2605
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-theme-material.kanuni-datagrid-dark,
|
|
2606
|
+
.kanuni-datagrid-detached.kanuni-datagrid-theme-material.kanuni-datagrid-dark {
|
|
2607
|
+
--dg-bg: #141218;
|
|
2608
|
+
--dg-surface: #141218;
|
|
2609
|
+
--dg-surface-header: #141218;
|
|
2610
|
+
--dg-surface-hover: #211f26;
|
|
2611
|
+
--dg-surface-stripe: #1a171f;
|
|
2612
|
+
--dg-border: #2b2930;
|
|
2613
|
+
--dg-border-strong: #49454f;
|
|
2614
|
+
--dg-text: #e6e0e9;
|
|
2615
|
+
--dg-text-muted: #cac4d0;
|
|
2616
|
+
--dg-primary: #d0bcff;
|
|
2617
|
+
--dg-primary-soft: #4f378b;
|
|
2618
|
+
--dg-focus-ring: #d0bcff;
|
|
2619
|
+
--dg-selected-bg: #4f378b;
|
|
2620
|
+
--dg-group-bg: #241f2e;
|
|
2621
|
+
--dg-footer-bg: #1a171f;
|
|
2622
|
+
/* The dark accent is a pale lilac: what sits on it must be near-black. */
|
|
2623
|
+
--dg-on-primary: #381e72;
|
|
2624
|
+
--dg-scrollbar-thumb: #49454f;
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
/* ═══════════════════════════════════════════════════════════
|
|
2628
|
+
ALPINE — airy, fully framed, high-chroma accent
|
|
2629
|
+
═══════════════════════════════════════════════════════════
|
|
2630
|
+
The middle position: both sets of rules are drawn but kept light, the rhythm
|
|
2631
|
+
is roomy, and the header is anchored by a doubled rule and a heavy weight so
|
|
2632
|
+
it reads as a distinct band even though it shares the body's palette. */
|
|
2633
|
+
.kanuni-datagrid-root.kanuni-datagrid-theme-alpine,
|
|
2634
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-theme-alpine,
|
|
2635
|
+
.kanuni-datagrid-detached.kanuni-datagrid-theme-alpine {
|
|
2636
|
+
--dg-bg: #ffffff;
|
|
2637
|
+
--dg-surface: #ffffff;
|
|
2638
|
+
--dg-surface-header: #f8f9fa;
|
|
2639
|
+
--dg-surface-hover: #eef3f8;
|
|
2640
|
+
--dg-surface-stripe: #fcfdfe;
|
|
2641
|
+
--dg-border: #dde2e8;
|
|
2642
|
+
--dg-border-strong: #b9c2cc;
|
|
2643
|
+
--dg-text: #181d1f;
|
|
2644
|
+
--dg-text-muted: #626b74;
|
|
2645
|
+
--dg-primary: #1a73c7;
|
|
2646
|
+
--dg-primary-soft: #dceafa;
|
|
2647
|
+
--dg-on-primary: #ffffff; /* 4.86:1 */
|
|
2648
|
+
--dg-focus-ring: #1a73c7;
|
|
2649
|
+
--dg-selected-bg: #dceafa;
|
|
2650
|
+
--dg-group-bg: #eef1f4;
|
|
2651
|
+
--dg-footer-bg: #f8f9fa;
|
|
2652
|
+
--dg-scrollbar-thumb: #b9c2cc;
|
|
2653
|
+
|
|
2654
|
+
--dg-cell-border: 1px solid var(--dg-border);
|
|
2655
|
+
--dg-row-border: 1px solid var(--dg-border);
|
|
2656
|
+
--dg-header-cell-border: 1px solid var(--dg-border);
|
|
2657
|
+
/* Doubled rather than coloured: the header band is defined by weight of
|
|
2658
|
+
LINE, which survives every palette including the dark one. */
|
|
2659
|
+
--dg-header-border: 2px solid var(--dg-border-strong);
|
|
2660
|
+
--dg-header-weight: 700;
|
|
2661
|
+
|
|
2662
|
+
--dg-radius: 6px;
|
|
2663
|
+
--dg-radius-control: 4px;
|
|
2664
|
+
--dg-shadow-popup: 0 6px 20px -4px rgba(24, 29, 31, 0.22);
|
|
2665
|
+
--dg-shadow-popup-sm: 0 4px 14px -3px rgba(24, 29, 31, 0.18);
|
|
2666
|
+
--dg-shadow-menu: 0 8px 22px -6px rgba(24, 29, 31, 0.24);
|
|
2667
|
+
--dg-shadow-card: 0 10px 28px -8px rgba(24, 29, 31, 0.24);
|
|
2668
|
+
--dg-shadow-pinned: 3px 0 6px -4px rgba(24, 29, 31, 0.3);
|
|
2669
|
+
--dg-shadow-pinned-right: -3px 0 6px -4px rgba(24, 29, 31, 0.3);
|
|
2670
|
+
|
|
2671
|
+
--dg-row-height: 34px;
|
|
2672
|
+
--dg-header-height: 38px;
|
|
2673
|
+
--dg-cell-padding-x: 12px;
|
|
2674
|
+
--dg-font-size: 13px;
|
|
2675
|
+
--dg-wrap-line-height: 18px;
|
|
2676
|
+
--dg-header-font-size: 12px;
|
|
2677
|
+
--dg-control-height: 28px;
|
|
2678
|
+
--dg-filter-input-height: 24px;
|
|
2679
|
+
--dg-chrome-padding-y: 7px;
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
.kanuni-datagrid-root.kanuni-datagrid-theme-alpine.kanuni-datagrid-dark,
|
|
2683
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-theme-alpine.kanuni-datagrid-dark,
|
|
2684
|
+
.kanuni-datagrid-detached.kanuni-datagrid-theme-alpine.kanuni-datagrid-dark {
|
|
2685
|
+
--dg-bg: #181d1f;
|
|
2686
|
+
--dg-surface: #181d1f;
|
|
2687
|
+
--dg-surface-header: #222628;
|
|
2688
|
+
--dg-surface-hover: #2b3033;
|
|
2689
|
+
--dg-surface-stripe: #1c2123;
|
|
2690
|
+
--dg-border: #35393c;
|
|
2691
|
+
--dg-border-strong: #545a5f;
|
|
2692
|
+
--dg-text: #eceff1;
|
|
2693
|
+
--dg-text-muted: #9aa3aa;
|
|
2694
|
+
--dg-primary: #52a8f0;
|
|
2695
|
+
--dg-primary-soft: #123049;
|
|
2696
|
+
--dg-focus-ring: #8ec9f7;
|
|
2697
|
+
--dg-selected-bg: #16395a;
|
|
2698
|
+
--dg-group-bg: #252a2d;
|
|
2699
|
+
--dg-footer-bg: #222628;
|
|
2700
|
+
--dg-on-primary: #04182a;
|
|
2701
|
+
--dg-scrollbar-thumb: #545a5f;
|
|
2702
|
+
}
|
|
2703
|
+
|
|
2704
|
+
/* ═══════════════════════════════════════════════════════════
|
|
2705
|
+
KANUNI — the house theme, light
|
|
2706
|
+
═══════════════════════════════════════════════════════════
|
|
2707
|
+
The KanuniLabs identity: an ink header carrying the brand gold, over a warm
|
|
2708
|
+
near-white body. It is the theme that made `--dg-on-primary` necessary —
|
|
2709
|
+
the accent is #C5A55A, and white on it measures 1.9:1, so everything that
|
|
2710
|
+
sits ON the accent (group chips, primary buttons, the selected calendar day)
|
|
2711
|
+
takes near-black instead. `--dg-header-*` is likewise doing real work here:
|
|
2712
|
+
only the header row goes ink; the group panel, filter row and footer stay
|
|
2713
|
+
light, because their muted text would be unreadable on the dark fill. */
|
|
2714
|
+
.kanuni-datagrid-root.kanuni-datagrid-theme-kanuni,
|
|
2715
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-theme-kanuni,
|
|
2716
|
+
.kanuni-datagrid-detached.kanuni-datagrid-theme-kanuni {
|
|
2717
|
+
--dg-bg: #ffffff;
|
|
2718
|
+
--dg-surface: #ffffff;
|
|
2719
|
+
--dg-surface-header: #faf6ed; /* brand-50 — panels, filter row, footer */
|
|
2720
|
+
--dg-surface-hover: #f7f2e6;
|
|
2721
|
+
--dg-surface-stripe: #fdfbf6;
|
|
2722
|
+
--dg-border: #e7e2d6;
|
|
2723
|
+
--dg-border-strong: #cfc6b2;
|
|
2724
|
+
--dg-text: #1c1917;
|
|
2725
|
+
--dg-text-muted: #6b6154;
|
|
2726
|
+
--dg-primary: #c5a55a; /* brand-500 */
|
|
2727
|
+
--dg-primary-soft: #f3ead3; /* brand-100 */
|
|
2728
|
+
/* NOT the accent: gold on white is 1.9:1 and a focus ring has to be seen.
|
|
2729
|
+
brand-900 measures 7.0:1 on white. */
|
|
2730
|
+
--dg-focus-ring: #6b5119;
|
|
2731
|
+
--dg-on-primary: #1c1917;
|
|
2732
|
+
--dg-selected-bg: #f3ead3;
|
|
2733
|
+
--dg-group-bg: #faf6ed;
|
|
2734
|
+
--dg-footer-bg: #faf6ed;
|
|
2735
|
+
--dg-scrollbar-thumb: #cfc6b2;
|
|
2736
|
+
|
|
2737
|
+
/* The header band: ink, with brand foreground. */
|
|
2738
|
+
--dg-header-bg: #232b33;
|
|
2739
|
+
--dg-header-text: #f6efdd;
|
|
2740
|
+
--dg-header-text-muted: #b6ab92;
|
|
2741
|
+
--dg-header-hover: #2f3941;
|
|
2742
|
+
--dg-header-cell-border: 1px solid #3a444d;
|
|
2743
|
+
--dg-header-border: 2px solid #c5a55a;
|
|
2744
|
+
|
|
2745
|
+
--dg-cell-border: none;
|
|
2746
|
+
--dg-row-border: 1px solid var(--dg-border);
|
|
2747
|
+
|
|
2748
|
+
--dg-radius: 6px;
|
|
2749
|
+
--dg-radius-control: 4px;
|
|
2750
|
+
--dg-shadow-popup: 0 10px 26px -8px rgba(35, 43, 51, 0.3);
|
|
2751
|
+
--dg-shadow-popup-sm: 0 6px 18px -6px rgba(35, 43, 51, 0.24);
|
|
2752
|
+
--dg-shadow-menu: 0 10px 26px -8px rgba(35, 43, 51, 0.3);
|
|
2753
|
+
--dg-shadow-card: 0 12px 30px -10px rgba(35, 43, 51, 0.3);
|
|
2754
|
+
--dg-shadow-pinned: 3px 0 6px -4px rgba(35, 43, 51, 0.28);
|
|
2755
|
+
--dg-shadow-pinned-right: -3px 0 6px -4px rgba(35, 43, 51, 0.28);
|
|
2756
|
+
--dg-overlay: rgba(28, 25, 23, 0.6);
|
|
2757
|
+
|
|
2758
|
+
--dg-row-height: 32px;
|
|
2759
|
+
--dg-header-height: 38px;
|
|
2760
|
+
--dg-cell-padding-x: 10px;
|
|
2761
|
+
--dg-font-size: 13px;
|
|
2762
|
+
--dg-wrap-line-height: 18px;
|
|
2763
|
+
--dg-header-font-size: 12px;
|
|
2764
|
+
--dg-header-tracking: 0.02em;
|
|
2765
|
+
--dg-control-height: 28px;
|
|
2766
|
+
--dg-filter-input-height: 24px;
|
|
2767
|
+
--dg-chrome-padding-y: 7px;
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
/* `kanuni` in dark: the body follows the header down rather than the header
|
|
2771
|
+
coming up, so the gold stays the only warm thing on screen. */
|
|
2772
|
+
.kanuni-datagrid-root.kanuni-datagrid-theme-kanuni.kanuni-datagrid-dark,
|
|
2773
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-theme-kanuni.kanuni-datagrid-dark,
|
|
2774
|
+
.kanuni-datagrid-detached.kanuni-datagrid-theme-kanuni.kanuni-datagrid-dark,
|
|
2775
|
+
/* ═══════════════════════════════════════════════════════════
|
|
2776
|
+
KANUNI MIDNIGHT — the house theme, dark by default
|
|
2777
|
+
═══════════════════════════════════════════════════════════
|
|
2778
|
+
Shares the palette below, but applies WITHOUT the `dark` flag. Listed in the
|
|
2779
|
+
same block with and without `.kanuni-datagrid-dark`, so passing `dark` to a
|
|
2780
|
+
grid that is already midnight is a no-op rather than a reversion to the
|
|
2781
|
+
packaged slate palette. */
|
|
2782
|
+
.kanuni-datagrid-root.kanuni-datagrid-theme-kanuni-midnight,
|
|
2783
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-theme-kanuni-midnight,
|
|
2784
|
+
.kanuni-datagrid-detached.kanuni-datagrid-theme-kanuni-midnight,
|
|
2785
|
+
.kanuni-datagrid-root.kanuni-datagrid-theme-kanuni-midnight.kanuni-datagrid-dark,
|
|
2786
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-theme-kanuni-midnight.kanuni-datagrid-dark,
|
|
2787
|
+
.kanuni-datagrid-detached.kanuni-datagrid-theme-kanuni-midnight.kanuni-datagrid-dark {
|
|
2788
|
+
--dg-bg: #0c0a09;
|
|
2789
|
+
--dg-surface: #16130f;
|
|
2790
|
+
--dg-surface-header: #221d17;
|
|
2791
|
+
--dg-surface-hover: #2b241c;
|
|
2792
|
+
--dg-surface-stripe: #191511;
|
|
2793
|
+
--dg-border: #332b22;
|
|
2794
|
+
--dg-border-strong: #4d4235;
|
|
2795
|
+
--dg-text: #f5f1e8;
|
|
2796
|
+
--dg-text-muted: #a89b86;
|
|
2797
|
+
--dg-primary: #c5a55a;
|
|
2798
|
+
--dg-primary-soft: #3d3117;
|
|
2799
|
+
--dg-focus-ring: #d9bf7e;
|
|
2800
|
+
--dg-on-primary: #1c1917;
|
|
2801
|
+
--dg-selected-bg: #3d3117;
|
|
2802
|
+
--dg-group-bg: #221d17;
|
|
2803
|
+
--dg-footer-bg: #221d17;
|
|
2804
|
+
--dg-scrollbar-thumb: #4d4235;
|
|
2805
|
+
|
|
2806
|
+
--dg-header-bg: #1c1811;
|
|
2807
|
+
--dg-header-text: #f6efdd;
|
|
2808
|
+
--dg-header-text-muted: #a89b86;
|
|
2809
|
+
--dg-header-hover: #2b241c;
|
|
2810
|
+
--dg-header-cell-border: 1px solid #332b22;
|
|
2811
|
+
--dg-header-border: 2px solid #c5a55a;
|
|
2812
|
+
|
|
2813
|
+
--dg-cell-border: none;
|
|
2814
|
+
--dg-row-border: 1px solid var(--dg-border);
|
|
2815
|
+
|
|
2816
|
+
--dg-danger: #f87171;
|
|
2817
|
+
--dg-danger-text: #fca5a5;
|
|
2818
|
+
--dg-on-danger: #450a0a;
|
|
2819
|
+
--dg-warning: #fbbf24;
|
|
2820
|
+
--dg-overlay: rgba(12, 10, 9, 0.72);
|
|
2821
|
+
|
|
2822
|
+
--dg-radius: 6px;
|
|
2823
|
+
--dg-radius-control: 4px;
|
|
2824
|
+
--dg-shadow-popup: 0 10px 26px -6px rgba(0, 0, 0, 0.7);
|
|
2825
|
+
--dg-shadow-popup-sm: 0 6px 18px -4px rgba(0, 0, 0, 0.6);
|
|
2826
|
+
--dg-shadow-menu: 0 10px 26px -6px rgba(0, 0, 0, 0.7);
|
|
2827
|
+
--dg-shadow-card: 0 12px 30px -8px rgba(0, 0, 0, 0.7);
|
|
2828
|
+
--dg-shadow-panel: 0 8px 20px rgba(0, 0, 0, 0.55);
|
|
2829
|
+
--dg-shadow-dialog: 0 24px 48px rgba(0, 0, 0, 0.75);
|
|
2830
|
+
--dg-shadow-pinned: 3px 0 6px -3px rgba(0, 0, 0, 0.7);
|
|
2831
|
+
--dg-shadow-pinned-right: -3px 0 6px -3px rgba(0, 0, 0, 0.7);
|
|
2832
|
+
|
|
2833
|
+
--dg-row-height: 32px;
|
|
2834
|
+
--dg-header-height: 38px;
|
|
2835
|
+
--dg-cell-padding-x: 10px;
|
|
2836
|
+
--dg-font-size: 13px;
|
|
2837
|
+
--dg-wrap-line-height: 18px;
|
|
2838
|
+
--dg-header-font-size: 12px;
|
|
2839
|
+
--dg-header-tracking: 0.02em;
|
|
2840
|
+
--dg-control-height: 28px;
|
|
2841
|
+
--dg-filter-input-height: 24px;
|
|
2842
|
+
--dg-chrome-padding-y: 7px;
|
|
2843
|
+
}
|
|
2844
|
+
|
|
2845
|
+
/* ═══════════════════════════════════════════════════════════
|
|
2846
|
+
DENSITY
|
|
2847
|
+
═══════════════════════════════════════════════════════════
|
|
2848
|
+
Compact is not "the same grid in smaller type". Every vertical measure moves
|
|
2849
|
+
together — row, header, cell padding, control height, toolbar and pager
|
|
2850
|
+
padding, filter field — so the proportions hold and the grid stays usable
|
|
2851
|
+
rather than merely smaller. Each theme scales from its OWN normal, because a
|
|
2852
|
+
Material row starts at 40px and a Balham row at 25px; one shared compact
|
|
2853
|
+
value would have flattened the presets into each other at that density.
|
|
2854
|
+
|
|
2855
|
+
The ROW HEIGHT is deliberately absent here: it is a core value the
|
|
2856
|
+
virtualizer runs on, so the component resolves it in JS (THEME_ROW_HEIGHT)
|
|
2857
|
+
and core feeds it back as --dg-row-height. Everything CSS owns lives here,
|
|
2858
|
+
so the two can never disagree about the row geometry. Same split the base
|
|
2859
|
+
stylesheet already uses for the unthemed density presets.
|
|
2860
|
+
|
|
2861
|
+
All three roots are listed so a portalled popup gets the density too — the
|
|
2862
|
+
base sheet's presets are root-only, which left popup controls at their
|
|
2863
|
+
normal size whatever the grid was set to. */
|
|
2864
|
+
|
|
2865
|
+
/* ── Quartz ── */
|
|
2866
|
+
.kanuni-datagrid-root.kanuni-datagrid-theme-quartz.kanuni-datagrid-density-compact,
|
|
2867
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-theme-quartz.kanuni-datagrid-density-compact,
|
|
2868
|
+
.kanuni-datagrid-detached.kanuni-datagrid-theme-quartz.kanuni-datagrid-density-compact {
|
|
2869
|
+
--dg-header-height: 30px;
|
|
2870
|
+
--dg-cell-padding-x: 8px;
|
|
2871
|
+
--dg-font-size: 12px;
|
|
2872
|
+
--dg-wrap-line-height: 16px;
|
|
2873
|
+
--dg-header-font-size: 11px;
|
|
2874
|
+
--dg-control-height: 24px;
|
|
2875
|
+
--dg-control-font-size: 11px;
|
|
2876
|
+
--dg-chrome-font-size: 11px;
|
|
2877
|
+
--dg-filter-input-height: 20px;
|
|
2878
|
+
--dg-chrome-padding-y: 4px;
|
|
2879
|
+
}
|
|
2880
|
+
|
|
2881
|
+
.kanuni-datagrid-root.kanuni-datagrid-theme-quartz.kanuni-datagrid-density-comfortable,
|
|
2882
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-theme-quartz.kanuni-datagrid-density-comfortable,
|
|
2883
|
+
.kanuni-datagrid-detached.kanuni-datagrid-theme-quartz.kanuni-datagrid-density-comfortable {
|
|
2884
|
+
--dg-header-height: 48px;
|
|
2885
|
+
--dg-cell-padding-x: 16px;
|
|
2886
|
+
--dg-font-size: 14px;
|
|
2887
|
+
--dg-wrap-line-height: 20px;
|
|
2888
|
+
--dg-header-font-size: 13px;
|
|
2889
|
+
--dg-control-height: 34px;
|
|
2890
|
+
--dg-control-font-size: 13px;
|
|
2891
|
+
--dg-chrome-font-size: 13px;
|
|
2892
|
+
--dg-filter-input-height: 30px;
|
|
2893
|
+
--dg-chrome-padding-y: 10px;
|
|
2894
|
+
}
|
|
2895
|
+
|
|
2896
|
+
/* ── Balham ── */
|
|
2897
|
+
.kanuni-datagrid-root.kanuni-datagrid-theme-balham.kanuni-datagrid-density-compact,
|
|
2898
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-theme-balham.kanuni-datagrid-density-compact,
|
|
2899
|
+
.kanuni-datagrid-detached.kanuni-datagrid-theme-balham.kanuni-datagrid-density-compact {
|
|
2900
|
+
--dg-header-height: 22px;
|
|
2901
|
+
--dg-cell-padding-x: 4px;
|
|
2902
|
+
--dg-font-size: 11px;
|
|
2903
|
+
--dg-wrap-line-height: 16px;
|
|
2904
|
+
--dg-header-font-size: 10px;
|
|
2905
|
+
--dg-control-height: 20px;
|
|
2906
|
+
--dg-control-font-size: 10px;
|
|
2907
|
+
--dg-chrome-font-size: 10px;
|
|
2908
|
+
--dg-filter-input-height: 17px;
|
|
2909
|
+
--dg-chrome-padding-y: 2px;
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
.kanuni-datagrid-root.kanuni-datagrid-theme-balham.kanuni-datagrid-density-comfortable,
|
|
2913
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-theme-balham.kanuni-datagrid-density-comfortable,
|
|
2914
|
+
.kanuni-datagrid-detached.kanuni-datagrid-theme-balham.kanuni-datagrid-density-comfortable {
|
|
2915
|
+
--dg-header-height: 36px;
|
|
2916
|
+
--dg-cell-padding-x: 10px;
|
|
2917
|
+
--dg-font-size: 13px;
|
|
2918
|
+
--dg-wrap-line-height: 18px;
|
|
2919
|
+
--dg-header-font-size: 12px;
|
|
2920
|
+
--dg-control-height: 28px;
|
|
2921
|
+
--dg-control-font-size: 12px;
|
|
2922
|
+
--dg-chrome-font-size: 12px;
|
|
2923
|
+
--dg-filter-input-height: 24px;
|
|
2924
|
+
--dg-chrome-padding-y: 6px;
|
|
2925
|
+
}
|
|
2926
|
+
|
|
2927
|
+
/* ── Material ── */
|
|
2928
|
+
.kanuni-datagrid-root.kanuni-datagrid-theme-material.kanuni-datagrid-density-compact,
|
|
2929
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-theme-material.kanuni-datagrid-density-compact,
|
|
2930
|
+
.kanuni-datagrid-detached.kanuni-datagrid-theme-material.kanuni-datagrid-density-compact {
|
|
2931
|
+
--dg-header-height: 36px;
|
|
2932
|
+
--dg-cell-padding-x: 12px;
|
|
2933
|
+
--dg-font-size: 13px;
|
|
2934
|
+
--dg-wrap-line-height: 18px;
|
|
2935
|
+
--dg-header-font-size: 11px;
|
|
2936
|
+
--dg-control-height: 28px;
|
|
2937
|
+
--dg-control-font-size: 12px;
|
|
2938
|
+
--dg-chrome-font-size: 12px;
|
|
2939
|
+
--dg-filter-input-height: 24px;
|
|
2940
|
+
--dg-chrome-padding-y: 6px;
|
|
2941
|
+
}
|
|
2942
|
+
|
|
2943
|
+
.kanuni-datagrid-root.kanuni-datagrid-theme-material.kanuni-datagrid-density-comfortable,
|
|
2944
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-theme-material.kanuni-datagrid-density-comfortable,
|
|
2945
|
+
.kanuni-datagrid-detached.kanuni-datagrid-theme-material.kanuni-datagrid-density-comfortable {
|
|
2946
|
+
--dg-header-height: 56px;
|
|
2947
|
+
--dg-cell-padding-x: 20px;
|
|
2948
|
+
--dg-font-size: 15px;
|
|
2949
|
+
--dg-wrap-line-height: 20px;
|
|
2950
|
+
--dg-header-font-size: 13px;
|
|
2951
|
+
--dg-control-height: 40px;
|
|
2952
|
+
--dg-control-font-size: 14px;
|
|
2953
|
+
--dg-chrome-font-size: 14px;
|
|
2954
|
+
--dg-filter-input-height: 32px;
|
|
2955
|
+
--dg-chrome-padding-y: 12px;
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
/* ── Alpine ── */
|
|
2959
|
+
.kanuni-datagrid-root.kanuni-datagrid-theme-alpine.kanuni-datagrid-density-compact,
|
|
2960
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-theme-alpine.kanuni-datagrid-density-compact,
|
|
2961
|
+
.kanuni-datagrid-detached.kanuni-datagrid-theme-alpine.kanuni-datagrid-density-compact {
|
|
2962
|
+
--dg-header-height: 28px;
|
|
2963
|
+
--dg-cell-padding-x: 8px;
|
|
2964
|
+
--dg-font-size: 12px;
|
|
2965
|
+
--dg-wrap-line-height: 16px;
|
|
2966
|
+
--dg-header-font-size: 11px;
|
|
2967
|
+
--dg-control-height: 23px;
|
|
2968
|
+
--dg-control-font-size: 11px;
|
|
2969
|
+
--dg-chrome-font-size: 11px;
|
|
2970
|
+
--dg-filter-input-height: 19px;
|
|
2971
|
+
--dg-chrome-padding-y: 4px;
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
.kanuni-datagrid-root.kanuni-datagrid-theme-alpine.kanuni-datagrid-density-comfortable,
|
|
2975
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-theme-alpine.kanuni-datagrid-density-comfortable,
|
|
2976
|
+
.kanuni-datagrid-detached.kanuni-datagrid-theme-alpine.kanuni-datagrid-density-comfortable {
|
|
2977
|
+
--dg-header-height: 46px;
|
|
2978
|
+
--dg-cell-padding-x: 16px;
|
|
2979
|
+
--dg-font-size: 14px;
|
|
2980
|
+
--dg-wrap-line-height: 20px;
|
|
2981
|
+
--dg-header-font-size: 13px;
|
|
2982
|
+
--dg-control-height: 34px;
|
|
2983
|
+
--dg-control-font-size: 13px;
|
|
2984
|
+
--dg-chrome-font-size: 13px;
|
|
2985
|
+
--dg-filter-input-height: 28px;
|
|
2986
|
+
--dg-chrome-padding-y: 10px;
|
|
2987
|
+
}
|
|
2988
|
+
|
|
2989
|
+
/* ── Kanuni + Kanuni Midnight (one rhythm, two palettes) ── */
|
|
2990
|
+
.kanuni-datagrid-root.kanuni-datagrid-theme-kanuni.kanuni-datagrid-density-compact,
|
|
2991
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-theme-kanuni.kanuni-datagrid-density-compact,
|
|
2992
|
+
.kanuni-datagrid-detached.kanuni-datagrid-theme-kanuni.kanuni-datagrid-density-compact,
|
|
2993
|
+
.kanuni-datagrid-root.kanuni-datagrid-theme-kanuni-midnight.kanuni-datagrid-density-compact,
|
|
2994
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-theme-kanuni-midnight.kanuni-datagrid-density-compact,
|
|
2995
|
+
.kanuni-datagrid-detached.kanuni-datagrid-theme-kanuni-midnight.kanuni-datagrid-density-compact {
|
|
2996
|
+
--dg-header-height: 30px;
|
|
2997
|
+
--dg-cell-padding-x: 7px;
|
|
2998
|
+
--dg-font-size: 12px;
|
|
2999
|
+
--dg-wrap-line-height: 16px;
|
|
3000
|
+
--dg-header-font-size: 11px;
|
|
3001
|
+
--dg-control-height: 24px;
|
|
3002
|
+
--dg-control-font-size: 11px;
|
|
3003
|
+
--dg-chrome-font-size: 11px;
|
|
3004
|
+
--dg-filter-input-height: 20px;
|
|
3005
|
+
--dg-chrome-padding-y: 4px;
|
|
3006
|
+
}
|
|
3007
|
+
|
|
3008
|
+
.kanuni-datagrid-root.kanuni-datagrid-theme-kanuni.kanuni-datagrid-density-comfortable,
|
|
3009
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-theme-kanuni.kanuni-datagrid-density-comfortable,
|
|
3010
|
+
.kanuni-datagrid-detached.kanuni-datagrid-theme-kanuni.kanuni-datagrid-density-comfortable,
|
|
3011
|
+
.kanuni-datagrid-root.kanuni-datagrid-theme-kanuni-midnight.kanuni-datagrid-density-comfortable,
|
|
3012
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-theme-kanuni-midnight.kanuni-datagrid-density-comfortable,
|
|
3013
|
+
.kanuni-datagrid-detached.kanuni-datagrid-theme-kanuni-midnight.kanuni-datagrid-density-comfortable {
|
|
3014
|
+
--dg-header-height: 46px;
|
|
3015
|
+
--dg-cell-padding-x: 14px;
|
|
3016
|
+
--dg-font-size: 14px;
|
|
3017
|
+
--dg-wrap-line-height: 20px;
|
|
3018
|
+
--dg-header-font-size: 13px;
|
|
3019
|
+
--dg-control-height: 34px;
|
|
3020
|
+
--dg-control-font-size: 13px;
|
|
3021
|
+
--dg-chrome-font-size: 13px;
|
|
3022
|
+
--dg-filter-input-height: 28px;
|
|
3023
|
+
--dg-chrome-padding-y: 10px;
|
|
3024
|
+
}
|
|
3025
|
+
|
|
3026
|
+
/* ═══════════════════════════════════════════════════════════
|
|
3027
|
+
SCROLLBAR
|
|
3028
|
+
═══════════════════════════════════════════════════════════
|
|
3029
|
+
Only ever applied when a theme is chosen. Left to the platform otherwise:
|
|
3030
|
+
the unthemed grid must keep rendering exactly as it did, and a native
|
|
3031
|
+
scrollbar is a reasonable default in a component that is dropped into
|
|
3032
|
+
someone else's application.
|
|
3033
|
+
|
|
3034
|
+
`scrollbar-color` covers Firefox and current Chromium; the -webkit- rules
|
|
3035
|
+
cover Safari and older Chromium, which ignore the standard property. Both
|
|
3036
|
+
are pure paint — no extra element, nothing for the virtualizer to measure. */
|
|
3037
|
+
[class*='kanuni-datagrid-theme-'] .kanuni-datagrid-scroller,
|
|
3038
|
+
[class*='kanuni-datagrid-theme-'] .kanuni-datagrid-filter-list,
|
|
3039
|
+
[class*='kanuni-datagrid-theme-'] .kanuni-datagrid-detail-cell {
|
|
3040
|
+
scrollbar-color: var(--dg-scrollbar-thumb) var(--dg-scrollbar-track);
|
|
3041
|
+
scrollbar-width: thin;
|
|
3042
|
+
}
|
|
3043
|
+
|
|
3044
|
+
[class*='kanuni-datagrid-theme-'] .kanuni-datagrid-scroller::-webkit-scrollbar,
|
|
3045
|
+
[class*='kanuni-datagrid-theme-'] .kanuni-datagrid-filter-list::-webkit-scrollbar,
|
|
3046
|
+
[class*='kanuni-datagrid-theme-'] .kanuni-datagrid-detail-cell::-webkit-scrollbar {
|
|
3047
|
+
width: 11px;
|
|
3048
|
+
height: 11px;
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3051
|
+
[class*='kanuni-datagrid-theme-'] .kanuni-datagrid-scroller::-webkit-scrollbar-track,
|
|
3052
|
+
[class*='kanuni-datagrid-theme-'] .kanuni-datagrid-filter-list::-webkit-scrollbar-track,
|
|
3053
|
+
[class*='kanuni-datagrid-theme-'] .kanuni-datagrid-detail-cell::-webkit-scrollbar-track {
|
|
3054
|
+
background: var(--dg-scrollbar-track);
|
|
3055
|
+
}
|
|
3056
|
+
|
|
3057
|
+
[class*='kanuni-datagrid-theme-'] .kanuni-datagrid-scroller::-webkit-scrollbar-thumb,
|
|
3058
|
+
[class*='kanuni-datagrid-theme-'] .kanuni-datagrid-filter-list::-webkit-scrollbar-thumb,
|
|
3059
|
+
[class*='kanuni-datagrid-theme-'] .kanuni-datagrid-detail-cell::-webkit-scrollbar-thumb {
|
|
3060
|
+
border: 3px solid transparent;
|
|
3061
|
+
border-radius: 999px;
|
|
3062
|
+
/* Painted into the padding box so the 3px transparent border reads as a gap
|
|
3063
|
+
rather than as a thumb that touches both edges of the gutter. */
|
|
3064
|
+
background-clip: padding-box;
|
|
3065
|
+
background-color: var(--dg-scrollbar-thumb);
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
[class*='kanuni-datagrid-theme-'] .kanuni-datagrid-scroller::-webkit-scrollbar-corner {
|
|
3069
|
+
background: var(--dg-scrollbar-track);
|
|
3070
|
+
}
|
|
3071
|
+
|
|
3072
|
+
/* ═══════════════════════════════════════════════════════════
|
|
3073
|
+
THE ONE RULE A TOKEN CANNOT CARRY
|
|
3074
|
+
═══════════════════════════════════════════════════════════
|
|
3075
|
+
Four presets set `--dg-cell-border: none`, which also removes the only line
|
|
3076
|
+
separating a PINNED block from the columns sliding under it. The edge shadow
|
|
3077
|
+
is still there, but a shadow alone is a soft seam on a surface with no other
|
|
3078
|
+
vertical rules — the frozen columns stopped reading as frozen. A hairline on
|
|
3079
|
+
the edge element (there is exactly one per side, not one per cell) restores
|
|
3080
|
+
it without bringing the column rules back.
|
|
3081
|
+
|
|
3082
|
+
Every ruled preset — Balham, Alpine — needs nothing here: the seam is
|
|
3083
|
+
already drawn by the cell borders they keep. */
|
|
3084
|
+
.kanuni-datagrid-theme-quartz .kanuni-datagrid-pin-edge-left,
|
|
3085
|
+
.kanuni-datagrid-theme-material .kanuni-datagrid-pin-edge-left,
|
|
3086
|
+
.kanuni-datagrid-theme-kanuni .kanuni-datagrid-pin-edge-left,
|
|
3087
|
+
.kanuni-datagrid-theme-kanuni-midnight .kanuni-datagrid-pin-edge-left {
|
|
3088
|
+
border-inline-end: 1px solid var(--dg-border-strong);
|
|
3089
|
+
}
|
|
3090
|
+
|
|
3091
|
+
.kanuni-datagrid-theme-quartz .kanuni-datagrid-pin-edge-right,
|
|
3092
|
+
.kanuni-datagrid-theme-material .kanuni-datagrid-pin-edge-right,
|
|
3093
|
+
.kanuni-datagrid-theme-kanuni .kanuni-datagrid-pin-edge-right,
|
|
3094
|
+
.kanuni-datagrid-theme-kanuni-midnight .kanuni-datagrid-pin-edge-right {
|
|
3095
|
+
border-inline-start: 1px solid var(--dg-border-strong);
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
/* ─────────────────────────────────────────────────────────────
|
|
3099
|
+
KANUNILABS DataGrid — palettes
|
|
3100
|
+
─────────────────────────────────────────────────────────────
|
|
3101
|
+
A palette is a SECOND, independent axis: it changes the colours and nothing
|
|
3102
|
+
else. The theme keeps deciding the structure — where the rules are drawn, how
|
|
3103
|
+
tall a row is, how round a control is, how much the popups lift — so
|
|
3104
|
+
`theme="balham" palette="nord"` is Balham's dense ruled grid in Nord's arctic
|
|
3105
|
+
blues, and `theme="material" palette="nord"` is Material's tall airy one in
|
|
3106
|
+
the same blues. Four themes and six palettes is twenty-four looks out of ten
|
|
3107
|
+
class names, not twenty-four stylesheets.
|
|
3108
|
+
|
|
3109
|
+
That split is only possible because the token layer already separates the two
|
|
3110
|
+
— the theme blocks set metrics AND colours, and everything here overrides the
|
|
3111
|
+
colour half of that.
|
|
3112
|
+
|
|
3113
|
+
── Where these colours come from ──────────────────────────
|
|
3114
|
+
The six are the colour schemes people already recognise from their editors
|
|
3115
|
+
and terminals, and each is used the way its authors published it: the hues
|
|
3116
|
+
below are the scheme's own, mapped onto our token vocabulary. What is OURS is
|
|
3117
|
+
the mapping — which of a scheme's colours becomes the header, the stripe, the
|
|
3118
|
+
accent, the thing that has to stay readable on the accent — and every
|
|
3119
|
+
contrast decision that follows from it. Nothing here is imported, and no
|
|
3120
|
+
stylesheet from any of those projects is involved.
|
|
3121
|
+
|
|
3122
|
+
Several are dark-first schemes. Where the authors published a light
|
|
3123
|
+
counterpart it is used (Solarized Light, Catppuccin Latte, Gruvbox light,
|
|
3124
|
+
Tokyo Night Day); where they did not, the light block is our own reading of
|
|
3125
|
+
the same hue family, and says so.
|
|
3126
|
+
|
|
3127
|
+
── Ordering ───────────────────────────────────────────────
|
|
3128
|
+
Concatenated AFTER DataGridThemes.css. A palette block matches the theme
|
|
3129
|
+
blocks' specificity exactly — three roots, two classes — so source order is
|
|
3130
|
+
what makes the palette win, and the dark variants sit one class higher for
|
|
3131
|
+
the same reason. Getting this backwards would leave the palette silently
|
|
3132
|
+
ignored, which is why `themes.test.ts` checks the file order.
|
|
3133
|
+
───────────────────────────────────────────────────────────── */
|
|
3134
|
+
|
|
3135
|
+
/* ═══════════════════════════════════════════════════════════
|
|
3136
|
+
NORD — arctic, desaturated blue-grey
|
|
3137
|
+
═══════════════════════════════════════════════════════════
|
|
3138
|
+
Polar Night for the dark surfaces, Snow Storm for the light ones, Frost for
|
|
3139
|
+
the accent. The calmest of the six: low saturation everywhere, so a dense
|
|
3140
|
+
grid of numbers does not vibrate. */
|
|
3141
|
+
.kanuni-datagrid-root.kanuni-datagrid-palette-nord,
|
|
3142
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-palette-nord,
|
|
3143
|
+
.kanuni-datagrid-detached.kanuni-datagrid-palette-nord {
|
|
3144
|
+
--dg-bg: #ffffff;
|
|
3145
|
+
--dg-surface: #ffffff;
|
|
3146
|
+
--dg-surface-header: #eceff4; /* Snow Storm nord6 */
|
|
3147
|
+
--dg-surface-hover: #e5e9f0; /* nord5 */
|
|
3148
|
+
--dg-surface-stripe: #f7f9fb;
|
|
3149
|
+
--dg-border: #dde3ec;
|
|
3150
|
+
--dg-border-strong: #b8c2d0;
|
|
3151
|
+
--dg-text: #2e3440; /* Polar Night nord0 */
|
|
3152
|
+
--dg-text-muted: #55607a;
|
|
3153
|
+
/* Frost nord10 (#5e81ac) is the scheme's accent and measures 4.29:1 under
|
|
3154
|
+
white — just under the threshold for the chip label that sits on it. One
|
|
3155
|
+
step deeper, same Frost hue. */
|
|
3156
|
+
--dg-primary: #4c6d94;
|
|
3157
|
+
--dg-primary-soft: #dbe3ee;
|
|
3158
|
+
--dg-on-primary: #ffffff; /* 5.24:1 */
|
|
3159
|
+
--dg-focus-ring: #4c6d94;
|
|
3160
|
+
--dg-selected-bg: #dbe3ee;
|
|
3161
|
+
--dg-group-bg: #e9edf3;
|
|
3162
|
+
--dg-footer-bg: #eceff4;
|
|
3163
|
+
--dg-scrollbar-thumb: #b8c2d0;
|
|
3164
|
+
--dg-danger: #a2515a; /* Aurora nord11, deepened for white text */
|
|
3165
|
+
--dg-danger-text: #8f4149;
|
|
3166
|
+
--dg-on-danger: #ffffff;
|
|
3167
|
+
--dg-warning: #8a6220; /* nord13 family */
|
|
3168
|
+
}
|
|
3169
|
+
|
|
3170
|
+
.kanuni-datagrid-root.kanuni-datagrid-palette-nord.kanuni-datagrid-dark,
|
|
3171
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-palette-nord.kanuni-datagrid-dark,
|
|
3172
|
+
.kanuni-datagrid-detached.kanuni-datagrid-palette-nord.kanuni-datagrid-dark {
|
|
3173
|
+
--dg-bg: #2e3440; /* nord0 */
|
|
3174
|
+
--dg-surface: #2e3440;
|
|
3175
|
+
--dg-surface-header: #3b4252; /* nord1 */
|
|
3176
|
+
--dg-surface-hover: #434c5e; /* nord2 */
|
|
3177
|
+
--dg-surface-stripe: #333a48;
|
|
3178
|
+
--dg-border: #434c5e;
|
|
3179
|
+
--dg-border-strong: #4c566a; /* nord3 */
|
|
3180
|
+
--dg-text: #eceff4; /* nord6 */
|
|
3181
|
+
--dg-text-muted: #b3bccb;
|
|
3182
|
+
--dg-primary: #88c0d0; /* Frost nord8 */
|
|
3183
|
+
--dg-primary-soft: #3c5464;
|
|
3184
|
+
--dg-on-primary: #17222b; /* 9.11:1 */
|
|
3185
|
+
--dg-focus-ring: #8fbcbb;
|
|
3186
|
+
--dg-selected-bg: #3c5464;
|
|
3187
|
+
--dg-group-bg: #39404f;
|
|
3188
|
+
--dg-footer-bg: #3b4252;
|
|
3189
|
+
--dg-scrollbar-thumb: #4c566a;
|
|
3190
|
+
--dg-danger: #bf616a; /* nord11 */
|
|
3191
|
+
--dg-danger-text: #d9919a;
|
|
3192
|
+
/* nord11 is a muted red, so the label on it has to go further down than the
|
|
3193
|
+
usual near-black: #2b1113 measured 4.31:1 and this is 5.17:1. */
|
|
3194
|
+
--dg-on-danger: #170709;
|
|
3195
|
+
--dg-warning: #ebcb8b; /* nord13 */
|
|
3196
|
+
--dg-overlay: rgba(24, 28, 36, 0.72);
|
|
3197
|
+
}
|
|
3198
|
+
|
|
3199
|
+
/* ═══════════════════════════════════════════════════════════
|
|
3200
|
+
DRACULA — near-black violet with high-chroma accents
|
|
3201
|
+
═══════════════════════════════════════════════════════════
|
|
3202
|
+
The scheme is dark by design. Its light counterpart here is ours: the same
|
|
3203
|
+
purple/pink family lifted onto a white ground, so switching `dark` does not
|
|
3204
|
+
jump to an unrelated hue. */
|
|
3205
|
+
.kanuni-datagrid-root.kanuni-datagrid-palette-dracula,
|
|
3206
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-palette-dracula,
|
|
3207
|
+
.kanuni-datagrid-detached.kanuni-datagrid-palette-dracula {
|
|
3208
|
+
--dg-bg: #ffffff;
|
|
3209
|
+
--dg-surface: #ffffff;
|
|
3210
|
+
--dg-surface-header: #f3eefb;
|
|
3211
|
+
--dg-surface-hover: #ebe3f7;
|
|
3212
|
+
--dg-surface-stripe: #faf7fe;
|
|
3213
|
+
--dg-border: #e4dcf0;
|
|
3214
|
+
--dg-border-strong: #c3b5da;
|
|
3215
|
+
--dg-text: #282a36; /* Dracula background, used as ink here */
|
|
3216
|
+
--dg-text-muted: #5d5f73;
|
|
3217
|
+
--dg-primary: #7449a8; /* Purple #bd93f9, deepened for white text */
|
|
3218
|
+
--dg-primary-soft: #ece2fa;
|
|
3219
|
+
--dg-on-primary: #ffffff; /* 6.87:1 */
|
|
3220
|
+
--dg-focus-ring: #7449a8;
|
|
3221
|
+
--dg-selected-bg: #ece2fa;
|
|
3222
|
+
--dg-group-bg: #f1ebfa;
|
|
3223
|
+
--dg-footer-bg: #f3eefb;
|
|
3224
|
+
--dg-scrollbar-thumb: #c3b5da;
|
|
3225
|
+
--dg-danger: #b03a5b; /* Pink #ff79c6 / Red #ff5555 family */
|
|
3226
|
+
--dg-danger-text: #98304d;
|
|
3227
|
+
--dg-on-danger: #ffffff;
|
|
3228
|
+
--dg-warning: #8a5a1c;
|
|
3229
|
+
}
|
|
3230
|
+
|
|
3231
|
+
.kanuni-datagrid-root.kanuni-datagrid-palette-dracula.kanuni-datagrid-dark,
|
|
3232
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-palette-dracula.kanuni-datagrid-dark,
|
|
3233
|
+
.kanuni-datagrid-detached.kanuni-datagrid-palette-dracula.kanuni-datagrid-dark {
|
|
3234
|
+
--dg-bg: #282a36; /* Background */
|
|
3235
|
+
--dg-surface: #282a36;
|
|
3236
|
+
--dg-surface-header: #343746; /* Current Line, lightened */
|
|
3237
|
+
--dg-surface-hover: #44475a; /* Current Line */
|
|
3238
|
+
--dg-surface-stripe: #2d2f3d;
|
|
3239
|
+
--dg-border: #44475a;
|
|
3240
|
+
--dg-border-strong: #565a72;
|
|
3241
|
+
--dg-text: #f8f8f2; /* Foreground */
|
|
3242
|
+
--dg-text-muted: #a7abc4; /* Comment #6272a4, lifted to clear AA */
|
|
3243
|
+
--dg-primary: #bd93f9; /* Purple */
|
|
3244
|
+
--dg-primary-soft: #453860;
|
|
3245
|
+
--dg-on-primary: #241638; /* 8.66:1 */
|
|
3246
|
+
--dg-focus-ring: #ff79c6; /* Pink */
|
|
3247
|
+
--dg-selected-bg: #453860;
|
|
3248
|
+
--dg-group-bg: #32354a;
|
|
3249
|
+
--dg-footer-bg: #343746;
|
|
3250
|
+
--dg-scrollbar-thumb: #565a72;
|
|
3251
|
+
--dg-danger: #ff5555; /* Red */
|
|
3252
|
+
--dg-danger-text: #ff8a8a;
|
|
3253
|
+
--dg-on-danger: #3b0000;
|
|
3254
|
+
--dg-warning: #f1fa8c; /* Yellow */
|
|
3255
|
+
--dg-overlay: rgba(18, 19, 26, 0.74);
|
|
3256
|
+
}
|
|
3257
|
+
|
|
3258
|
+
/* ═══════════════════════════════════════════════════════════
|
|
3259
|
+
SOLARIZED — the fixed-lightness classic
|
|
3260
|
+
═══════════════════════════════════════════════════════════
|
|
3261
|
+
The one scheme designed as a matched light/dark PAIR, which is exactly what
|
|
3262
|
+
a grid with a `dark` switch wants: the accents are identical in both and only
|
|
3263
|
+
the eight base tones flip. */
|
|
3264
|
+
.kanuni-datagrid-root.kanuni-datagrid-palette-solarized,
|
|
3265
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-palette-solarized,
|
|
3266
|
+
.kanuni-datagrid-detached.kanuni-datagrid-palette-solarized {
|
|
3267
|
+
--dg-bg: #fdf6e3; /* base3 */
|
|
3268
|
+
--dg-surface: #fdf6e3;
|
|
3269
|
+
--dg-surface-header: #eee8d5; /* base2 */
|
|
3270
|
+
--dg-surface-hover: #e7e0cc;
|
|
3271
|
+
--dg-surface-stripe: #f7f0dd;
|
|
3272
|
+
--dg-border: #ddd6c1;
|
|
3273
|
+
--dg-border-strong: #bdb7a4;
|
|
3274
|
+
--dg-text: #073642; /* base02 */
|
|
3275
|
+
--dg-text-muted: #56707a; /* base00/base01 family */
|
|
3276
|
+
--dg-primary: #1f6f8b; /* blue #268bd2, deepened for white text */
|
|
3277
|
+
--dg-primary-soft: #dbe7ea;
|
|
3278
|
+
--dg-on-primary: #ffffff; /* 5.90:1 */
|
|
3279
|
+
--dg-focus-ring: #1f6f8b;
|
|
3280
|
+
--dg-selected-bg: #dbe7ea;
|
|
3281
|
+
--dg-group-bg: #eae3cf;
|
|
3282
|
+
--dg-footer-bg: #eee8d5;
|
|
3283
|
+
--dg-scrollbar-thumb: #bdb7a4;
|
|
3284
|
+
--dg-danger: #b3372c; /* red #dc322f, deepened */
|
|
3285
|
+
--dg-danger-text: #9c2f25;
|
|
3286
|
+
--dg-on-danger: #ffffff;
|
|
3287
|
+
--dg-warning: #8a5a00; /* yellow #b58900, deepened */
|
|
3288
|
+
}
|
|
3289
|
+
|
|
3290
|
+
.kanuni-datagrid-root.kanuni-datagrid-palette-solarized.kanuni-datagrid-dark,
|
|
3291
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-palette-solarized.kanuni-datagrid-dark,
|
|
3292
|
+
.kanuni-datagrid-detached.kanuni-datagrid-palette-solarized.kanuni-datagrid-dark {
|
|
3293
|
+
--dg-bg: #002b36; /* base03 */
|
|
3294
|
+
--dg-surface: #002b36;
|
|
3295
|
+
--dg-surface-header: #073642; /* base02 */
|
|
3296
|
+
--dg-surface-hover: #0a4351;
|
|
3297
|
+
--dg-surface-stripe: #01313d;
|
|
3298
|
+
--dg-border: #0d4a58;
|
|
3299
|
+
--dg-border-strong: #23606c;
|
|
3300
|
+
--dg-text: #eee8d5; /* base2 */
|
|
3301
|
+
--dg-text-muted: #a4b3b6;
|
|
3302
|
+
--dg-primary: #4fa3d9; /* blue, lifted for a dark ground */
|
|
3303
|
+
--dg-primary-soft: #0d4457;
|
|
3304
|
+
--dg-on-primary: #00212b; /* 7.62:1 */
|
|
3305
|
+
--dg-focus-ring: #6fc3df;
|
|
3306
|
+
--dg-selected-bg: #0d4457;
|
|
3307
|
+
--dg-group-bg: #05323d;
|
|
3308
|
+
--dg-footer-bg: #073642;
|
|
3309
|
+
--dg-scrollbar-thumb: #23606c;
|
|
3310
|
+
--dg-danger: #e07a72;
|
|
3311
|
+
--dg-danger-text: #f0a49e;
|
|
3312
|
+
--dg-on-danger: #34110d;
|
|
3313
|
+
--dg-warning: #d5a63b;
|
|
3314
|
+
--dg-overlay: rgba(0, 20, 26, 0.74);
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
/* ═══════════════════════════════════════════════════════════
|
|
3318
|
+
GRUVBOX — warm retro, high contrast
|
|
3319
|
+
═══════════════════════════════════════════════════════════
|
|
3320
|
+
The warmest of the six, and the only one whose light mode is genuinely warm
|
|
3321
|
+
rather than a cooled-off white. Both modes are the scheme's own. */
|
|
3322
|
+
.kanuni-datagrid-root.kanuni-datagrid-palette-gruvbox,
|
|
3323
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-palette-gruvbox,
|
|
3324
|
+
.kanuni-datagrid-detached.kanuni-datagrid-palette-gruvbox {
|
|
3325
|
+
--dg-bg: #fbf1c7; /* light bg0 */
|
|
3326
|
+
--dg-surface: #fbf1c7;
|
|
3327
|
+
--dg-surface-header: #ebdbb2; /* bg1 */
|
|
3328
|
+
--dg-surface-hover: #e3d3a8;
|
|
3329
|
+
--dg-surface-stripe: #f5ecc0;
|
|
3330
|
+
--dg-border: #ddcfa6;
|
|
3331
|
+
--dg-border-strong: #bdae8b;
|
|
3332
|
+
--dg-text: #3c3836; /* fg0 */
|
|
3333
|
+
--dg-text-muted: #665c54; /* fg3 */
|
|
3334
|
+
--dg-primary: #076678; /* light blue */
|
|
3335
|
+
--dg-primary-soft: #d9e5e6;
|
|
3336
|
+
--dg-on-primary: #ffffff; /* 7.71:1 */
|
|
3337
|
+
--dg-focus-ring: #076678;
|
|
3338
|
+
--dg-selected-bg: #d9e5e6;
|
|
3339
|
+
--dg-group-bg: #e8d9b0;
|
|
3340
|
+
--dg-footer-bg: #ebdbb2;
|
|
3341
|
+
--dg-scrollbar-thumb: #bdae8b;
|
|
3342
|
+
--dg-danger: #9d0006; /* light red */
|
|
3343
|
+
--dg-danger-text: #9d0006;
|
|
3344
|
+
--dg-on-danger: #ffffff;
|
|
3345
|
+
/* The scheme's light yellow (#b57614) is 3.33:1 on this cream ground — the
|
|
3346
|
+
warmth that makes Gruvbox work is also what makes its yellow weak here.
|
|
3347
|
+
Same hue, two steps down, 4.72:1. */
|
|
3348
|
+
--dg-warning: #8f5c0a;
|
|
3349
|
+
}
|
|
3350
|
+
|
|
3351
|
+
.kanuni-datagrid-root.kanuni-datagrid-palette-gruvbox.kanuni-datagrid-dark,
|
|
3352
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-palette-gruvbox.kanuni-datagrid-dark,
|
|
3353
|
+
.kanuni-datagrid-detached.kanuni-datagrid-palette-gruvbox.kanuni-datagrid-dark {
|
|
3354
|
+
--dg-bg: #282828; /* dark bg0 */
|
|
3355
|
+
--dg-surface: #282828;
|
|
3356
|
+
--dg-surface-header: #3c3836; /* bg1 */
|
|
3357
|
+
--dg-surface-hover: #504945; /* bg2 */
|
|
3358
|
+
--dg-surface-stripe: #2f2c2b;
|
|
3359
|
+
--dg-border: #504945;
|
|
3360
|
+
--dg-border-strong: #665c54; /* bg3 */
|
|
3361
|
+
--dg-text: #fbf1c7; /* fg0 */
|
|
3362
|
+
--dg-text-muted: #bdae93; /* fg3 */
|
|
3363
|
+
--dg-primary: #83a598; /* dark blue */
|
|
3364
|
+
--dg-primary-soft: #3b4a45;
|
|
3365
|
+
--dg-on-primary: #16211d; /* 8.24:1 */
|
|
3366
|
+
--dg-focus-ring: #8ec07c; /* aqua */
|
|
3367
|
+
--dg-selected-bg: #3b4a45;
|
|
3368
|
+
--dg-group-bg: #37332f;
|
|
3369
|
+
--dg-footer-bg: #3c3836;
|
|
3370
|
+
--dg-scrollbar-thumb: #665c54;
|
|
3371
|
+
--dg-danger: #fb4934; /* dark red */
|
|
3372
|
+
--dg-danger-text: #fb8b7f;
|
|
3373
|
+
--dg-on-danger: #2c0500;
|
|
3374
|
+
--dg-warning: #fabd2f; /* dark yellow */
|
|
3375
|
+
--dg-overlay: rgba(20, 20, 20, 0.74);
|
|
3376
|
+
}
|
|
3377
|
+
|
|
3378
|
+
/* ═══════════════════════════════════════════════════════════
|
|
3379
|
+
CATPPUCCIN — pastel, low contrast between surfaces
|
|
3380
|
+
═══════════════════════════════════════════════════════════
|
|
3381
|
+
Latte for light, Mocha for dark. The softest surfaces of the six: the
|
|
3382
|
+
stripe, hover and header sit very close together, which suits a grid the
|
|
3383
|
+
user reads for a long stretch. */
|
|
3384
|
+
.kanuni-datagrid-root.kanuni-datagrid-palette-catppuccin,
|
|
3385
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-palette-catppuccin,
|
|
3386
|
+
.kanuni-datagrid-detached.kanuni-datagrid-palette-catppuccin {
|
|
3387
|
+
--dg-bg: #eff1f5; /* Latte base */
|
|
3388
|
+
--dg-surface: #eff1f5;
|
|
3389
|
+
--dg-surface-header: #e6e9ef; /* mantle */
|
|
3390
|
+
--dg-surface-hover: #dce0e8; /* crust */
|
|
3391
|
+
--dg-surface-stripe: #eaecf1;
|
|
3392
|
+
--dg-border: #d6dae4;
|
|
3393
|
+
--dg-border-strong: #bcc0cc; /* surface1 */
|
|
3394
|
+
--dg-text: #4c4f69; /* text */
|
|
3395
|
+
--dg-text-muted: #5c5f77; /* subtext1 */
|
|
3396
|
+
--dg-primary: #1e66f5; /* blue */
|
|
3397
|
+
--dg-primary-soft: #dce4fb;
|
|
3398
|
+
--dg-on-primary: #ffffff; /* 4.88:1 */
|
|
3399
|
+
--dg-focus-ring: #1e66f5;
|
|
3400
|
+
--dg-selected-bg: #dce4fb;
|
|
3401
|
+
--dg-group-bg: #e4e7ee;
|
|
3402
|
+
--dg-footer-bg: #e6e9ef;
|
|
3403
|
+
--dg-scrollbar-thumb: #bcc0cc;
|
|
3404
|
+
--dg-danger: #c4304a; /* red #d20f39, deepened for white text */
|
|
3405
|
+
--dg-danger-text: #b02540;
|
|
3406
|
+
--dg-on-danger: #ffffff;
|
|
3407
|
+
--dg-warning: #995f14; /* peach, deepened */
|
|
3408
|
+
}
|
|
3409
|
+
|
|
3410
|
+
.kanuni-datagrid-root.kanuni-datagrid-palette-catppuccin.kanuni-datagrid-dark,
|
|
3411
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-palette-catppuccin.kanuni-datagrid-dark,
|
|
3412
|
+
.kanuni-datagrid-detached.kanuni-datagrid-palette-catppuccin.kanuni-datagrid-dark {
|
|
3413
|
+
--dg-bg: #1e1e2e; /* Mocha base */
|
|
3414
|
+
--dg-surface: #1e1e2e;
|
|
3415
|
+
--dg-surface-header: #313244; /* surface0 */
|
|
3416
|
+
--dg-surface-hover: #45475a; /* surface1 */
|
|
3417
|
+
--dg-surface-stripe: #242436;
|
|
3418
|
+
--dg-border: #45475a;
|
|
3419
|
+
--dg-border-strong: #585b70; /* surface2 */
|
|
3420
|
+
--dg-text: #cdd6f4; /* text */
|
|
3421
|
+
--dg-text-muted: #a6adc8; /* subtext0 */
|
|
3422
|
+
--dg-primary: #89b4fa; /* blue */
|
|
3423
|
+
--dg-primary-soft: #33405e;
|
|
3424
|
+
--dg-on-primary: #11182b; /* 8.83:1 */
|
|
3425
|
+
--dg-focus-ring: #b4befe; /* lavender */
|
|
3426
|
+
--dg-selected-bg: #33405e;
|
|
3427
|
+
--dg-group-bg: #2a2b3c;
|
|
3428
|
+
--dg-footer-bg: #313244;
|
|
3429
|
+
--dg-scrollbar-thumb: #585b70;
|
|
3430
|
+
--dg-danger: #f38ba8; /* red */
|
|
3431
|
+
--dg-danger-text: #f5a3ba;
|
|
3432
|
+
--dg-on-danger: #3d0b1a;
|
|
3433
|
+
--dg-warning: #fab387; /* peach */
|
|
3434
|
+
--dg-overlay: rgba(14, 14, 22, 0.74);
|
|
3435
|
+
}
|
|
3436
|
+
|
|
3437
|
+
/* ═══════════════════════════════════════════════════════════
|
|
3438
|
+
TOKYO NIGHT — deep navy with violet and cyan
|
|
3439
|
+
═══════════════════════════════════════════════════════════
|
|
3440
|
+
The most saturated of the dark six, and the one whose accent reads as a
|
|
3441
|
+
colour rather than a tint. Tokyo Night Day for the light mode. */
|
|
3442
|
+
.kanuni-datagrid-root.kanuni-datagrid-palette-tokyo-night,
|
|
3443
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-palette-tokyo-night,
|
|
3444
|
+
.kanuni-datagrid-detached.kanuni-datagrid-palette-tokyo-night {
|
|
3445
|
+
--dg-bg: #e1e2e7; /* Day base */
|
|
3446
|
+
--dg-surface: #e1e2e7;
|
|
3447
|
+
--dg-surface-header: #d4d6de;
|
|
3448
|
+
--dg-surface-hover: #c9cbd6;
|
|
3449
|
+
--dg-surface-stripe: #dcdde3;
|
|
3450
|
+
--dg-border: #c3c6d1;
|
|
3451
|
+
--dg-border-strong: #a3a7b8;
|
|
3452
|
+
/* Tokyo Night Day's #3760bf is its KEYWORD blue, not its body ink — using it
|
|
3453
|
+
as the text colour measured 4.03:1 on the header and 4.26:1 on a selected
|
|
3454
|
+
row. The scheme's actual foreground is this darker slate-blue, which keeps
|
|
3455
|
+
the character and clears every surface. */
|
|
3456
|
+
--dg-text: #343b58;
|
|
3457
|
+
--dg-text-muted: #4c5478;
|
|
3458
|
+
--dg-primary: #2e5cb8;
|
|
3459
|
+
--dg-primary-soft: #d2dcf3;
|
|
3460
|
+
--dg-on-primary: #ffffff; /* 5.64:1 */
|
|
3461
|
+
--dg-focus-ring: #2e5cb8;
|
|
3462
|
+
--dg-selected-bg: #d2dcf3;
|
|
3463
|
+
--dg-group-bg: #d8dae2;
|
|
3464
|
+
--dg-footer-bg: #d4d6de;
|
|
3465
|
+
--dg-scrollbar-thumb: #a3a7b8;
|
|
3466
|
+
--dg-danger: #b1364a;
|
|
3467
|
+
--dg-danger-text: #9d2c3f;
|
|
3468
|
+
--dg-on-danger: #ffffff;
|
|
3469
|
+
--dg-warning: #8a5a12;
|
|
3470
|
+
}
|
|
3471
|
+
|
|
3472
|
+
.kanuni-datagrid-root.kanuni-datagrid-palette-tokyo-night.kanuni-datagrid-dark,
|
|
3473
|
+
.kanuni-datagrid-enterprise.kanuni-datagrid-palette-tokyo-night.kanuni-datagrid-dark,
|
|
3474
|
+
.kanuni-datagrid-detached.kanuni-datagrid-palette-tokyo-night.kanuni-datagrid-dark {
|
|
3475
|
+
--dg-bg: #1a1b26; /* Night bg */
|
|
3476
|
+
--dg-surface: #1a1b26;
|
|
3477
|
+
--dg-surface-header: #24283b; /* Storm bg */
|
|
3478
|
+
--dg-surface-hover: #2f334d;
|
|
3479
|
+
--dg-surface-stripe: #1f2030;
|
|
3480
|
+
--dg-border: #2f334d;
|
|
3481
|
+
--dg-border-strong: #414868;
|
|
3482
|
+
--dg-text: #c0caf5; /* fg */
|
|
3483
|
+
--dg-text-muted: #9aa5ce;
|
|
3484
|
+
--dg-primary: #7aa2f7; /* blue */
|
|
3485
|
+
--dg-primary-soft: #2b3a5e;
|
|
3486
|
+
--dg-on-primary: #0d1220; /* 8.05:1 */
|
|
3487
|
+
--dg-focus-ring: #7dcfff; /* cyan */
|
|
3488
|
+
--dg-selected-bg: #2b3a5e;
|
|
3489
|
+
--dg-group-bg: #212335;
|
|
3490
|
+
--dg-footer-bg: #24283b;
|
|
3491
|
+
--dg-scrollbar-thumb: #414868;
|
|
3492
|
+
--dg-danger: #f7768e; /* red */
|
|
3493
|
+
--dg-danger-text: #f993a6;
|
|
3494
|
+
--dg-on-danger: #3a0713;
|
|
3495
|
+
--dg-warning: #e0af68; /* yellow */
|
|
3496
|
+
--dg-overlay: rgba(10, 11, 18, 0.76);
|
|
3497
|
+
}
|