@kanunilabs/datagrid 1.9.0 → 1.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/datagrid.umd.js +8 -8
  2. package/dist/index.cjs +2 -2
  3. package/dist/index.d.cts +123 -11
  4. package/dist/index.d.ts +123 -11
  5. package/dist/index.js +2 -2
  6. package/dist/styles.css +541 -1
  7. package/dist/themes/all.css +108 -0
  8. package/dist/themes/amber.css +3 -0
  9. package/dist/themes/aubergine.css +3 -0
  10. package/dist/themes/azure.css +3 -0
  11. package/dist/themes/burgundy.css +3 -0
  12. package/dist/themes/charcoal.css +3 -0
  13. package/dist/themes/contrast-dark.css +3 -0
  14. package/dist/themes/contrast-light.css +3 -0
  15. package/dist/themes/coral.css +3 -0
  16. package/dist/themes/corporate.css +3 -0
  17. package/dist/themes/deep-sea.css +3 -0
  18. package/dist/themes/editorial.css +3 -0
  19. package/dist/themes/ember.css +3 -0
  20. package/dist/themes/emerald.css +3 -0
  21. package/dist/themes/forest.css +3 -0
  22. package/dist/themes/glacier.css +3 -0
  23. package/dist/themes/graphite.css +3 -0
  24. package/dist/themes/indigo-night.css +3 -0
  25. package/dist/themes/indigo.css +3 -0
  26. package/dist/themes/ivory.css +3 -0
  27. package/dist/themes/lagoon.css +3 -0
  28. package/dist/themes/linen.css +3 -0
  29. package/dist/themes/midnight.css +3 -0
  30. package/dist/themes/mist.css +3 -0
  31. package/dist/themes/monochrome.css +3 -0
  32. package/dist/themes/onyx.css +3 -0
  33. package/dist/themes/orchid.css +3 -0
  34. package/dist/themes/porcelain.css +3 -0
  35. package/dist/themes/rose.css +3 -0
  36. package/dist/themes/sandstone.css +3 -0
  37. package/dist/themes/slate-night.css +3 -0
  38. package/dist/themes/slate.css +3 -0
  39. package/dist/themes/steel.css +3 -0
  40. package/dist/themes/stone.css +3 -0
  41. package/dist/themes/taupe.css +3 -0
  42. package/dist/themes/teal.css +3 -0
  43. package/dist/themes/terminal.css +3 -0
  44. package/package.json +4 -4
package/dist/styles.css CHANGED
@@ -84,6 +84,9 @@
84
84
  --dg-danger: #dc2626; /* red-600 — borders, solid fills */
85
85
  --dg-danger-text: #b91c1c; /* red-700 — text on a light surface */
86
86
  --dg-on-danger: #ffffff; /* text ON a solid --dg-danger fill */
87
+ --dg-success: #15803d; /* green-700 — fills ("saved" marks) */
88
+ --dg-success-text: #166534; /* success as text on a light surface */
89
+ --dg-on-success: #ffffff; /* text ON a solid --dg-success fill */
87
90
  --dg-warning: #b45309; /* amber-700 */
88
91
  --dg-selected-bg: var(--dg-primary-soft);
89
92
  /* Search highlight (P2.4). Amber rather than the accent: the accent already
@@ -171,10 +174,20 @@
171
174
  color: var(--dg-text);
172
175
  }
173
176
 
177
+ /* Typeface. Inherited from the page unless --dg-font-family names one — set
178
+ it once (on :root, or in a palette class) and the grid, the Enterprise
179
+ chrome around it and every popup portalled to <body> use it. Without the
180
+ variable, a font set only on the grid's container never reached the menus
181
+ and dialogs, which inherit from <body> (2026-10-16, customer request). */
182
+ .kanuni-datagrid-enterprise,
183
+ .kanuni-datagrid-detached {
184
+ font-family: var(--dg-font-family, inherit);
185
+ }
186
+
174
187
  /* The grid's own box. */
175
188
  .kanuni-datagrid-root {
176
189
  box-sizing: border-box;
177
- font-family: inherit;
190
+ font-family: var(--dg-font-family, inherit);
178
191
  -webkit-font-smoothing: antialiased;
179
192
  -moz-osx-font-smoothing: grayscale;
180
193
  position: relative;
@@ -229,6 +242,9 @@
229
242
  /* The dark danger is a LIGHT red, so what sits on it has to be dark. White
230
243
  on #f87171 measures 2.4:1 — the delete button's own label. */
231
244
  --dg-on-danger: #450a0a;
245
+ --dg-success: #4ade80;
246
+ --dg-success-text: #86efac;
247
+ --dg-on-success: #0d2817;
232
248
  --dg-warning: #fbbf24;
233
249
  --dg-overlay: rgba(2, 6, 23, 0.7);
234
250
  --dg-scrollbar-thumb: #475569;
@@ -3181,6 +3197,491 @@
3181
3197
  }
3182
3198
  }
3183
3199
 
3200
+ /* ─────────────────────────────────────────────────────────────
3201
+ Editing modes: batch, row, form, popup.
3202
+ ─────────────────────────────────────────────────────────────
3203
+ Moved here from the Enterprise sheet when these modes became Community
3204
+ (owner decision, 2026-10-16). The Enterprise sheet loads after this one,
3205
+ so the Enterprise grid looks exactly as before; the plain-JS grid reads
3206
+ this file too. The command column's rules stay in the Enterprise sheet. */
3207
+
3208
+ /* ── Pending (unsaved) edits ──────────────────────────────── */
3209
+ /* A corner triangle rather than a background tint: the row may already be
3210
+ striped, selected or in-range, and those must stay distinguishable. */
3211
+ .kanuni-datagrid-root .kanuni-datagrid-cell--dirty {
3212
+ position: relative;
3213
+ }
3214
+
3215
+ .kanuni-datagrid-root .kanuni-datagrid-cell--dirty::before {
3216
+ content: '';
3217
+ position: absolute;
3218
+ top: 0;
3219
+ left: 0;
3220
+ border-width: 5px 5px 0 0;
3221
+ border-style: solid;
3222
+ border-color: var(--dg-primary) transparent transparent transparent;
3223
+ pointer-events: none;
3224
+ }
3225
+
3226
+ /* Marked for deletion, not yet saved (batch mode). Still readable, clearly on
3227
+ its way out, and still right-clickable so the delete can be undone. */
3228
+ .kanuni-datagrid-root .kanuni-datagrid-cell--removed {
3229
+ text-decoration: line-through;
3230
+ opacity: 0.55;
3231
+ background: color-mix(in srgb, var(--dg-danger) 10%, transparent);
3232
+ }
3233
+
3234
+ /* ── The Save / Cancel strip (and the Enterprise filter strip) ── */
3235
+ .kanuni-datagrid-filter-bar {
3236
+ display: flex;
3237
+ align-items: center;
3238
+ gap: 8px;
3239
+ padding: 6px 8px;
3240
+ border: 1px solid var(--dg-border);
3241
+ border-bottom: none;
3242
+ border-radius: var(--dg-radius) var(--dg-radius) 0 0;
3243
+ background: var(--dg-surface-header);
3244
+ font-size: var(--dg-chrome-font-size);
3245
+ }
3246
+
3247
+ .kanuni-datagrid-filter-bar button {
3248
+ padding: 2px 8px;
3249
+ border: 1px solid var(--dg-border-strong);
3250
+ border-radius: var(--dg-radius-control);
3251
+ background: var(--dg-surface);
3252
+ color: inherit;
3253
+ font: inherit;
3254
+ font-size: var(--dg-chrome-font-size);
3255
+ cursor: pointer;
3256
+ }
3257
+
3258
+ .kanuni-datagrid-filter-active {
3259
+ color: var(--dg-primary);
3260
+ font-weight: 600;
3261
+ }
3262
+
3263
+ /* Inside the free grid's root, whose own border is the frame: the strip is
3264
+ a band across the top rather than a tab sitting on the grid. */
3265
+ .kanuni-datagrid-root > .kanuni-datagrid-edit-bar {
3266
+ flex: none;
3267
+ border: 0;
3268
+ border-bottom: 1px solid var(--dg-border);
3269
+ border-radius: 0;
3270
+ }
3271
+
3272
+ /* The filter, in words. Truncated rather than wrapped: the strip is one line
3273
+ tall, and a growing bar would push the grid around as conditions are added.
3274
+ The full sentence is on the element's title. */
3275
+ .kanuni-datagrid-filter-active {
3276
+ overflow: hidden;
3277
+ text-overflow: ellipsis;
3278
+ white-space: nowrap;
3279
+ max-width: min(60ch, 50%);
3280
+ }
3281
+
3282
+ /* ── Row / form / popup editing (G2) ──────────────────────── */
3283
+ /*
3284
+ * One panel, two placements: in the row flow (`form`, the `--inline` variant
3285
+ * below, inside a row slot the virtualizer sized) and in a dialog over the
3286
+ * grid (`popup`).
3287
+ */
3288
+ .kanuni-datagrid-edit-form {
3289
+ flex: none;
3290
+ display: flex;
3291
+ flex-direction: column;
3292
+ max-height: 60vh;
3293
+ margin-bottom: 8px;
3294
+ border: 1px solid var(--dg-border-strong);
3295
+ border-radius: var(--dg-radius);
3296
+ background: var(--dg-surface);
3297
+ color: var(--dg-text);
3298
+ box-shadow: var(--dg-shadow-panel);
3299
+ font-size: var(--dg-font-size);
3300
+ }
3301
+
3302
+ /* Drawn inside a row slot: the row is already the card, so the panel drops the
3303
+ chrome that exists to lift a FLOATING panel off the page. `max-height: 100%`
3304
+ rather than 60vh — the slot's height is the budget here, and overflowing it
3305
+ would spill over the rows below, which the virtualizer has already placed. */
3306
+ .kanuni-datagrid-edit-form--inline {
3307
+ height: 100%;
3308
+ max-height: 100%;
3309
+ margin-bottom: 0;
3310
+ border: 0;
3311
+ border-radius: 0;
3312
+ box-shadow: none;
3313
+ background: var(--dg-surface-header);
3314
+ }
3315
+
3316
+ .kanuni-datagrid-edit-form-header {
3317
+ padding: 10px 14px;
3318
+ border-bottom: 1px solid var(--dg-border);
3319
+ background: var(--dg-surface-header);
3320
+ border-radius: var(--dg-radius) var(--dg-radius) 0 0;
3321
+ }
3322
+
3323
+ .kanuni-datagrid-edit-form-fields {
3324
+ flex: 1;
3325
+ overflow: auto;
3326
+ display: grid;
3327
+ /* Two columns on a wide grid, one on a narrow one — without a media query,
3328
+ which would key off the VIEWPORT when what matters is the grid's width. */
3329
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
3330
+ gap: 10px 16px;
3331
+ padding: 14px;
3332
+ }
3333
+
3334
+ .kanuni-datagrid-edit-form-field {
3335
+ display: flex;
3336
+ flex-direction: column;
3337
+ gap: 3px;
3338
+ min-width: 0;
3339
+ }
3340
+
3341
+ .kanuni-datagrid-edit-form-label {
3342
+ font-size: 11px;
3343
+ font-weight: 600;
3344
+ opacity: 0.75;
3345
+ }
3346
+
3347
+ .kanuni-datagrid-edit-form-input {
3348
+ width: 100%;
3349
+ min-width: 0;
3350
+ padding: 4px 6px;
3351
+ border: 1px solid var(--dg-border-strong);
3352
+ border-radius: var(--dg-radius-control);
3353
+ background: var(--dg-surface);
3354
+ color: inherit;
3355
+ font: inherit;
3356
+ font-size: var(--dg-font-size);
3357
+ }
3358
+
3359
+ input[type='checkbox'].kanuni-datagrid-edit-form-input {
3360
+ width: auto;
3361
+ accent-color: var(--dg-primary);
3362
+ }
3363
+
3364
+ .kanuni-datagrid-edit-form-input:focus {
3365
+ outline: 2px solid var(--dg-primary);
3366
+ outline-offset: -1px;
3367
+ }
3368
+
3369
+ .kanuni-datagrid-edit-form-field[data-invalid] .kanuni-datagrid-edit-form-input {
3370
+ border-color: var(--dg-danger);
3371
+ }
3372
+
3373
+ .kanuni-datagrid-edit-form-message {
3374
+ color: var(--dg-danger-text);
3375
+ font-size: 11px;
3376
+ }
3377
+
3378
+ /* A rejected save, above the fields — where the eye already is after pressing
3379
+ Save, rather than beside the button that is about to be pressed again. */
3380
+ .kanuni-datagrid-edit-form-error {
3381
+ margin: 10px 14px 0;
3382
+ padding: 6px 10px;
3383
+ border-radius: var(--dg-radius-control);
3384
+ background: color-mix(in srgb, var(--dg-danger) 12%, transparent);
3385
+ color: var(--dg-danger-text);
3386
+ font-size: var(--dg-chrome-font-size);
3387
+ }
3388
+
3389
+ .kanuni-datagrid-edit-form-footer {
3390
+ display: flex;
3391
+ align-items: center;
3392
+ gap: 8px;
3393
+ padding: 10px 14px;
3394
+ border-top: 1px solid var(--dg-border);
3395
+ background: var(--dg-surface-header);
3396
+ border-radius: 0 0 var(--dg-radius) var(--dg-radius);
3397
+ }
3398
+
3399
+ .kanuni-datagrid-edit-form-hint {
3400
+ margin-inline-end: auto;
3401
+ font-size: 11px;
3402
+ opacity: 0.65;
3403
+ }
3404
+
3405
+ .kanuni-datagrid-edit-form-footer button {
3406
+ padding: 4px 12px;
3407
+ border: 1px solid var(--dg-border-strong);
3408
+ border-radius: var(--dg-radius-control);
3409
+ background: var(--dg-surface);
3410
+ color: inherit;
3411
+ font: inherit;
3412
+ font-size: var(--dg-chrome-font-size);
3413
+ cursor: pointer;
3414
+ }
3415
+
3416
+ .kanuni-datagrid-edit-form-footer button:disabled {
3417
+ opacity: 0.55;
3418
+ cursor: default;
3419
+ }
3420
+
3421
+ .kanuni-datagrid-edit-form-save {
3422
+ border-color: var(--dg-primary) !important;
3423
+ background: var(--dg-primary) !important;
3424
+ color: var(--dg-on-primary) !important;
3425
+ }
3426
+
3427
+ /* ── Popup mode ───────────────────────────────────────────── */
3428
+ .kanuni-datagrid-edit-popup {
3429
+ position: fixed;
3430
+ inset: 0;
3431
+ z-index: 1000;
3432
+ display: flex;
3433
+ align-items: center;
3434
+ justify-content: center;
3435
+ }
3436
+
3437
+ .kanuni-datagrid-edit-popup-overlay {
3438
+ position: absolute;
3439
+ inset: 0;
3440
+ /* Opaque enough to read the form against a dense grid — the see-through
3441
+ overlay was a real bug in PivotGrid's drill-down modal. */
3442
+ background: var(--dg-overlay);
3443
+ }
3444
+
3445
+ .kanuni-datagrid-edit-popup > .kanuni-datagrid-edit-form {
3446
+ position: relative;
3447
+ width: min(720px, 94vw);
3448
+ max-height: 86vh;
3449
+ margin-bottom: 0;
3450
+ box-shadow: var(--dg-shadow-dialog);
3451
+ }
3452
+
3453
+ /* While a row/form/popup session owns the record, the grid behind it must not
3454
+ accept edits: a click that lands on a cell would change a value the user
3455
+ cannot see is changing, in a record they believe is open in the dialog. */
3456
+ /* A modal covers the grid, so nothing under it should react at all. */
3457
+ .kanuni-datagrid-enterprise[data-row-session='popup'] .kanuni-datagrid-scroller,
3458
+ .kanuni-datagrid-root[data-row-session='popup'] .kanuni-datagrid-scroller {
3459
+ pointer-events: none;
3460
+ }
3461
+
3462
+ /* The in-grid form is INSIDE the scroller, so the lock is per row rather than
3463
+ over the whole scroll area. Locking the scroller would take the form's own
3464
+ fields and buttons with it — and the wheel, leaving a form taller than the
3465
+ viewport with a Save nobody could scroll to. */
3466
+ .kanuni-datagrid-enterprise[data-row-session='form']
3467
+ .kanuni-datagrid-row:not(.kanuni-datagrid-editform-row),
3468
+ .kanuni-datagrid-root[data-row-session='form']
3469
+ .kanuni-datagrid-row:not(.kanuni-datagrid-editform-row) {
3470
+ pointer-events: none;
3471
+ }
3472
+
3473
+ /* ─────────────────────────────────────────────────────────────
3474
+ Row save status (`rowStatus`, Community, 2026-10-16).
3475
+ ─────────────────────────────────────────────────────────────
3476
+ A customer whose grid saves every edit at once asked to see each row's
3477
+ save: unsaved, in flight, saved, refused. Both renderers put
3478
+ `data-row-status` and `kanuni-datagrid-row--status-<status>` on the ROW
3479
+ element — one element per row, pinned cells included — so the marker is
3480
+ drawn once per row and cannot repeat per pinned block.
3481
+
3482
+ The marker is the row's own `::before`: a sticky flex item at the inline
3483
+ start that gives its width back with a negative margin. Sticky, because
3484
+ the row is as wide as all its columns and scrolls sideways — a plain left
3485
+ border or inset shadow on the row would scroll out of view with the first
3486
+ column, and under a pinned-left column it would be painted over. It sits
3487
+ above pinned cells (z-index) and moves no cell content. `inset-inline-start`
3488
+ puts it on the right edge under `dir="rtl"`. The focused row keeps its own
3489
+ inset bar; at the leading edge the status marker covers it, which is the
3490
+ more urgent of the two. */
3491
+ .kanuni-datagrid-root,
3492
+ .kanuni-datagrid-enterprise,
3493
+ .kanuni-datagrid-detached {
3494
+ --dg-row-status-width: 3px;
3495
+ --dg-row-status-changed: var(--dg-warning);
3496
+ --dg-row-status-saving: var(--dg-primary);
3497
+ /* The success fill: every palette and scheme sets it, dark ones included. */
3498
+ --dg-row-status-saved: var(--dg-success);
3499
+ --dg-row-status-error: var(--dg-danger);
3500
+ /* How strongly `tint: true` washes the row. */
3501
+ --dg-row-status-tint: 8%;
3502
+ }
3503
+
3504
+ .kanuni-datagrid-row--status-changed {
3505
+ --dg-row-status-color: var(--dg-row-status-changed);
3506
+ }
3507
+
3508
+ .kanuni-datagrid-row--status-saving {
3509
+ --dg-row-status-color: var(--dg-row-status-saving);
3510
+ }
3511
+
3512
+ .kanuni-datagrid-row--status-saved {
3513
+ --dg-row-status-color: var(--dg-row-status-saved);
3514
+ }
3515
+
3516
+ .kanuni-datagrid-row--status-error {
3517
+ --dg-row-status-color: var(--dg-row-status-error);
3518
+ }
3519
+
3520
+ .kanuni-datagrid-row[data-row-status]::before {
3521
+ content: '';
3522
+ position: sticky;
3523
+ inset-inline-start: 0;
3524
+ /* Above pinned cells (2), below the row being edited (4): at 6 the next
3525
+ row's marker cut through the editor's error bubble, which hangs one row
3526
+ down (pre-release review, 2026-10-16). */
3527
+ z-index: 3;
3528
+ flex: none;
3529
+ align-self: stretch;
3530
+ width: var(--dg-row-status-width);
3531
+ margin-inline-end: calc(-1 * var(--dg-row-status-width));
3532
+ background: var(--dg-row-status-color);
3533
+ pointer-events: none;
3534
+ }
3535
+
3536
+ /* The tint is a translucent IMAGE over whatever colour the row already has —
3537
+ stripe, hover, selection or focus — so those still read through it. Pinned
3538
+ cells inherit the row's background but the hover rule resets theirs, so
3539
+ they are named here too (three classes + an attribute: above the hover and
3540
+ selection rules, which are two classes). */
3541
+ .kanuni-datagrid-row.kanuni-datagrid-row--status-tint[data-row-status],
3542
+ .kanuni-datagrid-row.kanuni-datagrid-row--status-tint[data-row-status] .kanuni-datagrid-cell--pinned {
3543
+ background-image: linear-gradient(
3544
+ color-mix(in srgb, var(--dg-row-status-color) var(--dg-row-status-tint), transparent),
3545
+ color-mix(in srgb, var(--dg-row-status-color) var(--dg-row-status-tint), transparent)
3546
+ );
3547
+ }
3548
+
3549
+ /* High contrast drops background colours; the marker is the whole signal,
3550
+ so it keeps its own. */
3551
+ @media (forced-colors: active) {
3552
+ .kanuni-datagrid-row[data-row-status]::before {
3553
+ forced-color-adjust: none;
3554
+ }
3555
+ }
3556
+
3557
+ /* ── Message rows (`rowStatus.messages`, 2026-10-16) ─────────
3558
+ A refused save's reason in a row under it, with Retry and Delete row. The
3559
+ in-grid edit form's shape: sticky to the scroller's inline start and as wide
3560
+ as the VIEWPORT, so it stays in view while the columns scroll. The ground
3561
+ stays the surface — the text is the danger TEXT colour, measured at 4.5:1
3562
+ on it — and the row's own marker colour runs down its leading edge. */
3563
+ .kanuni-datagrid-message-row {
3564
+ background: var(--dg-surface);
3565
+ border-bottom: 1px solid var(--dg-border);
3566
+ }
3567
+
3568
+ .kanuni-datagrid-message-cell {
3569
+ position: sticky;
3570
+ inset-inline-start: 0;
3571
+ box-sizing: border-box;
3572
+ width: var(--dg-viewport-width, 100%);
3573
+ height: 100%;
3574
+ border-inline-start: var(--dg-row-status-width) solid var(--dg-row-status-error);
3575
+ user-select: text;
3576
+ }
3577
+
3578
+ .kanuni-datagrid-row-message {
3579
+ display: flex;
3580
+ align-items: center;
3581
+ gap: 8px;
3582
+ height: 100%;
3583
+ padding: 0 var(--dg-cell-padding-x);
3584
+ color: var(--dg-danger-text);
3585
+ font-size: var(--dg-chrome-font-size);
3586
+ }
3587
+
3588
+ .kanuni-datagrid-row-message-icon {
3589
+ flex: none;
3590
+ }
3591
+
3592
+ .kanuni-datagrid-row-message-text {
3593
+ flex: 1 1 auto;
3594
+ min-width: 0;
3595
+ overflow: hidden;
3596
+ text-overflow: ellipsis;
3597
+ white-space: nowrap;
3598
+ }
3599
+
3600
+ .kanuni-datagrid-root .kanuni-datagrid-row-message-button {
3601
+ flex: none;
3602
+ height: min(var(--dg-control-height), calc(100% - 6px));
3603
+ padding: 0 8px;
3604
+ border: 1px solid var(--dg-border-strong);
3605
+ border-radius: var(--dg-radius-control);
3606
+ background: var(--dg-surface);
3607
+ color: var(--dg-text);
3608
+ font: inherit;
3609
+ cursor: pointer;
3610
+ }
3611
+
3612
+ .kanuni-datagrid-root .kanuni-datagrid-row-message-button:hover {
3613
+ background: var(--dg-surface-hover);
3614
+ }
3615
+
3616
+ .kanuni-datagrid-root .kanuni-datagrid-row-message-button:focus-visible {
3617
+ outline: 2px solid var(--dg-focus-ring);
3618
+ outline-offset: 1px;
3619
+ }
3620
+
3621
+ /* ── Status column (`rowStatus.icons`, 2026-10-16) ───────────
3622
+ A glyph per status so it does not rest on colour alone: a dot while
3623
+ changed, a turning ring while saving, a tick when saved, a triangle when
3624
+ refused. Drawn as a MASK filled with the status colour, so a palette or a
3625
+ scheme recolours it like the marker. The cell exists on every row (an empty
3626
+ one where there is no status) so the columns beside it never step. */
3627
+ .kanuni-datagrid-status-cell {
3628
+ display: flex;
3629
+ align-items: center;
3630
+ justify-content: center;
3631
+ padding: 0;
3632
+ }
3633
+
3634
+ .kanuni-datagrid-status-icon {
3635
+ display: none;
3636
+ width: 12px;
3637
+ height: 12px;
3638
+ background: var(--dg-row-status-color);
3639
+ -webkit-mask: var(--dg-row-status-glyph) center / contain no-repeat;
3640
+ mask: var(--dg-row-status-glyph) center / contain no-repeat;
3641
+ }
3642
+
3643
+ .kanuni-datagrid-row[data-row-status] .kanuni-datagrid-status-icon {
3644
+ display: block;
3645
+ }
3646
+
3647
+ .kanuni-datagrid-row--status-changed {
3648
+ --dg-row-status-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='3.5'/%3E%3C/svg%3E");
3649
+ }
3650
+
3651
+ .kanuni-datagrid-row--status-saving {
3652
+ --dg-row-status-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 2a6 6 0 1 1-6 6' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round'/%3E%3C/svg%3E");
3653
+ }
3654
+
3655
+ .kanuni-datagrid-row--status-saved {
3656
+ --dg-row-status-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2.5 8.5 6.5 12.5 13.5 3.5' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
3657
+ }
3658
+
3659
+ .kanuni-datagrid-row--status-error {
3660
+ --dg-row-status-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 1 15.5 14.5H.5z M7 6v4.5h2V6z M7 11.5v2h2v-2z' fill-rule='evenodd'/%3E%3C/svg%3E");
3661
+ }
3662
+
3663
+ .kanuni-datagrid-row--status-saving .kanuni-datagrid-status-icon {
3664
+ animation: kanuni-datagrid-status-spin 0.9s linear infinite;
3665
+ }
3666
+
3667
+ @keyframes kanuni-datagrid-status-spin {
3668
+ to {
3669
+ transform: rotate(360deg);
3670
+ }
3671
+ }
3672
+
3673
+ @media (prefers-reduced-motion: reduce) {
3674
+ .kanuni-datagrid-row--status-saving .kanuni-datagrid-status-icon {
3675
+ animation: none;
3676
+ }
3677
+ }
3678
+
3679
+ @media (forced-colors: active) {
3680
+ .kanuni-datagrid-status-icon {
3681
+ background: CanvasText;
3682
+ }
3683
+ }
3684
+
3184
3685
  /* ─────────────────────────────────────────────────────────────
3185
3686
  KANUNILABS DataGrid — packaged themes
3186
3687
  ─────────────────────────────────────────────────────────────
@@ -3647,6 +4148,9 @@
3647
4148
  --dg-danger: #f87171;
3648
4149
  --dg-danger-text: #fca5a5;
3649
4150
  --dg-on-danger: #450a0a;
4151
+ --dg-success: #4ade80;
4152
+ --dg-success-text: #86efac;
4153
+ --dg-on-success: #0d2817;
3650
4154
  --dg-warning: #fbbf24;
3651
4155
  --dg-overlay: rgba(12, 10, 9, 0.72);
3652
4156
 
@@ -3995,6 +4499,9 @@
3995
4499
  --dg-danger: #a2515a; /* Aurora nord11, deepened for white text */
3996
4500
  --dg-danger-text: #8f4149;
3997
4501
  --dg-on-danger: #ffffff;
4502
+ --dg-success: #687a5a;
4503
+ --dg-success-text: #5d6c50;
4504
+ --dg-on-success: #ffffff;
3998
4505
  --dg-warning: #8a6220; /* nord13 family */
3999
4506
  }
4000
4507
 
@@ -4023,6 +4530,9 @@
4023
4530
  /* nord11 is a muted red, so the label on it has to go further down than the
4024
4531
  usual near-black: #2b1113 measured 4.31:1 and this is 5.17:1. */
4025
4532
  --dg-on-danger: #170709;
4533
+ --dg-success: #a3be8c;
4534
+ --dg-success-text: #adc599;
4535
+ --dg-on-success: #1d2219;
4026
4536
  --dg-warning: #ebcb8b; /* nord13 */
4027
4537
  --dg-overlay: rgba(24, 28, 36, 0.72);
4028
4538
  }
@@ -4056,6 +4566,9 @@
4056
4566
  --dg-danger: #b03a5b; /* Pink #ff79c6 / Red #ff5555 family */
4057
4567
  --dg-danger-text: #98304d;
4058
4568
  --dg-on-danger: #ffffff;
4569
+ --dg-success: #2a8441;
4570
+ --dg-success-text: #257339;
4571
+ --dg-on-success: #ffffff;
4059
4572
  --dg-warning: #8a5a1c;
4060
4573
  }
4061
4574
 
@@ -4082,6 +4595,9 @@
4082
4595
  --dg-danger: #ff5555; /* Red */
4083
4596
  --dg-danger-text: #ff8a8a;
4084
4597
  --dg-on-danger: #3b0000;
4598
+ --dg-success: #50fa7b;
4599
+ --dg-success-text: #50fa7b;
4600
+ --dg-on-success: #0e2d16;
4085
4601
  --dg-warning: #f1fa8c; /* Yellow */
4086
4602
  --dg-overlay: rgba(18, 19, 26, 0.74);
4087
4603
  }
@@ -4115,6 +4631,9 @@
4115
4631
  --dg-danger: #b3372c; /* red #dc322f, deepened */
4116
4632
  --dg-danger-text: #9c2f25;
4117
4633
  --dg-on-danger: #ffffff;
4634
+ --dg-success: #6c7c00;
4635
+ --dg-success-text: #5a6800;
4636
+ --dg-on-success: #ffffff;
4118
4637
  --dg-warning: #8a5a00; /* yellow #b58900, deepened */
4119
4638
  }
4120
4639
 
@@ -4141,6 +4660,9 @@
4141
4660
  --dg-danger: #e07a72;
4142
4661
  --dg-danger-text: #f0a49e;
4143
4662
  --dg-on-danger: #34110d;
4663
+ --dg-success: #859900;
4664
+ --dg-success-text: #a4b240;
4665
+ --dg-on-success: #181c00;
4144
4666
  --dg-warning: #d5a63b;
4145
4667
  --dg-overlay: rgba(0, 20, 26, 0.74);
4146
4668
  }
@@ -4173,6 +4695,9 @@
4173
4695
  --dg-danger: #9d0006; /* light red */
4174
4696
  --dg-danger-text: #9d0006;
4175
4697
  --dg-on-danger: #ffffff;
4698
+ --dg-success: #79740e;
4699
+ --dg-success-text: #605c0b;
4700
+ --dg-on-success: #ffffff;
4176
4701
  /* The scheme's light yellow (#b57614) is 3.33:1 on this cream ground — the
4177
4702
  warmth that makes Gruvbox work is also what makes its yellow weak here.
4178
4703
  Same hue, two steps down, 4.72:1. */
@@ -4202,6 +4727,9 @@
4202
4727
  --dg-danger: #fb4934; /* dark red */
4203
4728
  --dg-danger-text: #fb8b7f;
4204
4729
  --dg-on-danger: #2c0500;
4730
+ --dg-success: #b8bb26;
4731
+ --dg-success-text: #bfc23c;
4732
+ --dg-on-success: #212207;
4205
4733
  --dg-warning: #fabd2f; /* dark yellow */
4206
4734
  --dg-overlay: rgba(20, 20, 20, 0.74);
4207
4735
  }
@@ -4235,6 +4763,9 @@
4235
4763
  --dg-danger: #c4304a; /* red #d20f39, deepened for white text */
4236
4764
  --dg-danger-text: #b02540;
4237
4765
  --dg-on-danger: #ffffff;
4766
+ --dg-success: #358524;
4767
+ --dg-success-text: #2d701e;
4768
+ --dg-on-success: #ffffff;
4238
4769
  --dg-warning: #995f14; /* peach, deepened */
4239
4770
  }
4240
4771
 
@@ -4261,6 +4792,9 @@
4261
4792
  --dg-danger: #f38ba8; /* red */
4262
4793
  --dg-danger-text: #f5a3ba;
4263
4794
  --dg-on-danger: #3d0b1a;
4795
+ --dg-success: #a6e3a1;
4796
+ --dg-success-text: #a6e3a1;
4797
+ --dg-on-success: #1e291d;
4264
4798
  --dg-warning: #fab387; /* peach */
4265
4799
  --dg-overlay: rgba(14, 14, 22, 0.74);
4266
4800
  }
@@ -4297,6 +4831,9 @@
4297
4831
  --dg-danger: #b1364a;
4298
4832
  --dg-danger-text: #9d2c3f;
4299
4833
  --dg-on-danger: #ffffff;
4834
+ --dg-success: #587539;
4835
+ --dg-success-text: #455b2c;
4836
+ --dg-on-success: #ffffff;
4300
4837
  --dg-warning: #8a5a12;
4301
4838
  }
4302
4839
 
@@ -4323,6 +4860,9 @@
4323
4860
  --dg-danger: #f7768e; /* red */
4324
4861
  --dg-danger-text: #f993a6;
4325
4862
  --dg-on-danger: #3a0713;
4863
+ --dg-success: #9ece6a;
4864
+ --dg-success-text: #9ece6a;
4865
+ --dg-on-success: #1c2513;
4326
4866
  --dg-warning: #e0af68; /* yellow */
4327
4867
  --dg-overlay: rgba(10, 11, 18, 0.76);
4328
4868
  }