@ngx-stoui/core 21.0.11 → 21.0.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngx-stoui/core",
3
- "version": "21.0.11",
3
+ "version": "21.0.12",
4
4
  "author": {
5
5
  "name": "Ronnie Laugen",
6
6
  "email": "rhenri@equinor.com"
@@ -1,15 +1,43 @@
1
1
  /**
2
2
  * EDS (Equinor Design System) Theme for @toolbox-web/grid
3
3
  *
4
- * Styles the toolbox grid to match Equinor's design system.
5
- * Supports light/dark modes via the light-dark() CSS function.
6
- * Uses EDS CSS variables (--eds_*) when available, with light-dark() fallbacks.
4
+ * Styles the toolbox grid to match Equinor's design system. Shared by
5
+ * Angular consumers (cargo-tracker-apps via `@ngx-stoui/core/toolbox-grid.css`)
6
+ * and React consumers (planning Roma app).
7
+ *
8
+ * Both `<tbw-grid>` (web component) and `<div data-tbw-grid>` are supported
9
+ * so React apps that don't import the web component can still opt in to the
10
+ * theme by stamping the attribute on a wrapper.
11
+ *
12
+ * Supports light/dark modes via the light-dark() CSS function. Consumers that
13
+ * don't support dark mode (e.g. Roma) can opt out with `color-scheme: light only`
14
+ * on their root.
15
+ *
16
+ * In-cell editor coexistence:
17
+ * - Angular: Material form fields (`.mat-mdc-*`) — chrome is stripped so the
18
+ * surrounding `.tbw-editor-host` paints the visual boundary.
19
+ * - React/EDS: native EDS `<DatePicker>` / `<Autocomplete>` wrapped with
20
+ * `.tbw-eds-date-editor` / `.tbw-eds-autocomplete-editor` marker classes.
7
21
  *
8
22
  * Reference: https://eds.equinor.com/
9
23
  * Grid Theming: https://toolboxjs.com/?path=/docs/grid-theming--docs
10
24
  * Requires @toolbox-web/grid v1.3.1+
11
25
  *
12
- * Usage: "@ngx-stoui/core/toolbox-grid.css" in project.json styles array
26
+ * Usage:
27
+ * Angular: "@ngx-stoui/core/toolbox-grid.css" in project.json styles array
28
+ * React: import the compiled css, or @use this partial in a Sass entry
29
+ *
30
+ * Sections:
31
+ * 1. Theme Variables
32
+ * 2. Grid Styles (cells, editors, Material stripping, anchors, Typography reset)
33
+ * 3. Pinned cells & sticky group rows
34
+ * 4. Filter Panel
35
+ * 5. Context Menu
36
+ * 6. Overlay Panel
37
+ * 7. EDS in-cell editors (DatePicker / Autocomplete)
38
+ * 8. Empty State Overlay
39
+ * 9. Opt-ins (compact mode, thin header)
40
+ * 10. Dark Mode
13
41
  */
14
42
 
15
43
  /* ================================================================
@@ -22,6 +50,7 @@
22
50
  * rendered in document.body, so these variables cascade properly.
23
51
  * ================================================================ */
24
52
  tbw-grid,
53
+ [data-tbw-grid],
25
54
  .tbw-filter-panel {
26
55
  /* --- EDS Token Aliases (cascade to all children) --- */
27
56
  --_bg: var(--eds_ui_background__default, light-dark(#ffffff, #132634));
@@ -52,6 +81,7 @@ tbw-grid,
52
81
  --tbw-font-size: var(--sto-base-font-size, 13px);
53
82
  --tbw-font-size-header: var(--sto-base-font-size, 13px);
54
83
  --tbw-font-weight-header: 700;
84
+ --tbw-aggregation-font-size: 0.9em;
55
85
 
56
86
  /* --- Colors --- */
57
87
  --tbw-color-bg: var(--_bg);
@@ -108,9 +138,16 @@ tbw-grid,
108
138
  --tbw-cell-padding-header: 0 var(--eds_spacing_medium, 8px);
109
139
  --tbw-button-padding: 0.375rem 0.625rem;
110
140
 
111
- /* --- Focus --- */
141
+ /* --- Focus ---
142
+ `--tbw-focus-outline` paints the focus ring; the grid applies
143
+ `--tbw-focus-background` as the cell tint when a cell has focus. Use an
144
+ alpha-blended accent so the grid's own `.cell-focus` rule produces the
145
+ soft tint we want — no element-targeting needed. Pinned cells need a
146
+ pre-composited override (see section 3) because alpha over a sticky
147
+ opaque panel-bg would bleed scrolling content through. */
112
148
  --tbw-focus-outline: 0.5px dotted var(--_focus);
113
149
  --tbw-focus-outline-offset: -1px;
150
+ --tbw-focus-background: rgba(from var(--_accent) r g b / 8%);
114
151
 
115
152
  /* --- Resize Handle --- */
116
153
  --tbw-resize-handle-color: transparent;
@@ -134,9 +171,12 @@ tbw-grid,
134
171
  rgba(0, 0, 0, 0.15),
135
172
  rgba(0, 0, 0, 0.4)
136
173
  );
137
- --tbw-filter-input-bg: var(--_bg);
138
- --tbw-filter-input-border: var(--_border);
139
- --tbw-filter-input-radius: var(--_radius);
174
+ /* Filter inputs: EDS underline style. Strip the grid's default border
175
+ and radius via variables; the underline box-shadow itself (no variable)
176
+ is applied in section 4. */
177
+ --tbw-filter-input-bg: var(--_surface);
178
+ --tbw-filter-input-border: none;
179
+ --tbw-filter-input-radius: 0;
140
180
  --tbw-filter-input-focus: var(--_focus);
141
181
  --tbw-filter-accent: var(--_accent);
142
182
  --tbw-filter-accent-fg: var(--_accent-fg);
@@ -161,16 +201,13 @@ tbw-grid,
161
201
  /* ================================================================
162
202
  * 2. Grid Styles
163
203
  *
164
- * All tbw-grid-scoped rules: compact mode, cell styling,
165
- * editor hosts, and Angular Material overrides.
204
+ * All grid-scoped rules: cell styling, editor hosts, anchors,
205
+ * Typography reset, and Angular Material overrides.
206
+ *
207
+ * Compact mode and thin-header opt-ins live in section 9.
166
208
  * ================================================================ */
167
- tbw-grid {
168
- /* --- Compact Mode --- */
169
- &.eds-compact {
170
- --tbw-row-height: calc(var(--sto-base-font-size, 13px) * 1.385);
171
- --tbw-header-height: calc(var(--sto-base-font-size, 13px) * 1.769);
172
- }
173
-
209
+ tbw-grid,
210
+ [data-tbw-grid] {
174
211
  /* --- Angular Material overrides for in-grid editors --- */
175
212
  --mat-checkbox-state-layer-size: 20px;
176
213
  --mdc-filled-text-field-container-color: transparent;
@@ -204,33 +241,78 @@ tbw-grid {
204
241
  line-height: 1em;
205
242
  }
206
243
 
244
+ /* Anchor tags rendered inside cells (e.g. deep-link cells) should read
245
+ in the same color as plain text cells. Browser UA / EDS `Typography`
246
+ would otherwise paint them blue/teal. Underline is preserved
247
+ (we only override `color`). */
248
+ & .cell a {
249
+ color: var(--_fg);
250
+ }
251
+
207
252
  /* --- Numeric alignment --- */
208
253
  & .cell[data-type='number'] {
209
254
  text-align: right;
210
255
  }
211
256
 
212
- /* --- Data cell styling & editor host --- */
257
+ /* --- Data cell styling & editor host ---
258
+ The grid already paints `height: var(--tbw-row-height)` and
259
+ `background: var(--tbw-focus-background)` on focused cells from its own
260
+ base layer, so we only add what the grid doesn't expose as a variable:
261
+ `font-weight` on cells, the sticky-cell focus override, and editor-host
262
+ chrome (which lives on our `.tbw-editor-host` wrapper, not on a built-in
263
+ grid class). */
213
264
  & [part='cell'],
214
265
  & .data-grid-row .cell {
215
266
  font-weight: 500;
216
- height: var(--tbw-row-height);
267
+
268
+ /* Neutralize EDS `<Typography>` when used inside custom cell renderers.
269
+ Typography defaults to its own color, weight, `<p>` margin, and
270
+ a `1.429em` line-height, all of which break the grid's uniform
271
+ row appearance.
272
+
273
+ `!important` is required: EDS ships a global rule with an ID
274
+ selector (specificity 1,1,0) that wins over any class-based
275
+ chain. The third-party rule is itself a hardcoded-ID hack;
276
+ overriding with `!important` is the correct counter and stays
277
+ scoped to elements inside a grid cell. */
278
+ & [class*='Typography__StyledTypography'] {
279
+ color: inherit !important;
280
+ font-family: inherit !important;
281
+ font-size: inherit !important;
282
+ font-weight: inherit !important;
283
+ line-height: inherit !important;
284
+ letter-spacing: inherit !important;
285
+ }
217
286
 
218
287
  &.editing {
219
288
  padding: 2px 1px;
220
289
  position: relative;
221
290
  }
222
291
 
223
- &.cell-focus {
224
- background: rgba(from var(--tbw-color-accent) r g b / 8%);
292
+ /* Pinned cell focus tint.
293
+
294
+ The grid's own `.cell-focus` rule already paints
295
+ `--tbw-focus-background` (set in section 1 to an alpha-blended
296
+ accent) on any focused cell. On a sticky cell the rows behind
297
+ would scroll through that translucent tint as the user pans
298
+ horizontally, so we pre-composite the same 8% accent onto the
299
+ pinned-cell `--tbw-color-panel-bg` to keep the result fully opaque.
300
+ This is the only place we still target `.cell-focus` directly
301
+ — `color-mix` needs the panel-bg as a second color, which a
302
+ single variable can't express. */
303
+ &.cell-focus.sticky-left,
304
+ &.cell-focus.sticky-right {
305
+ background: color-mix(
306
+ in srgb,
307
+ var(--_accent) 8%,
308
+ var(--tbw-color-panel-bg)
309
+ );
225
310
  }
226
311
 
227
312
  /* Editor host — visual boundary for all editor types */
228
313
  & .tbw-editor-host {
229
314
  border: 1px solid var(--_border);
230
- background: var(
231
- --eds_ui_background__default,
232
- light-dark(#ffffff, #243746)
233
- );
315
+ background: var(--tbw-color-bg);
234
316
  display: flex;
235
317
  align-items: center;
236
318
  width: 100%;
@@ -247,6 +329,14 @@ tbw-grid {
247
329
  outline: none;
248
330
  }
249
331
 
332
+ /* Editors that bring their own full-bleed input (e.g. EDS DatePicker /
333
+ Autocomplete) opt out of the host's padding so they can stretch
334
+ to fill the cell. See section 7. */
335
+ &:has(.tbw-eds-date-editor),
336
+ &:has(.tbw-eds-autocomplete-editor) {
337
+ padding: 0;
338
+ }
339
+
250
340
  /* Framework component hosts: transparent for flex layout.
251
341
  :where() for zero specificity so component :host styles can override.
252
342
  Excludes form elements and custom editor boxes. */
@@ -370,8 +460,13 @@ tbw-grid {
370
460
  }
371
461
 
372
462
  & input.mat-mdc-input-element {
463
+ /* `line-height: normal` is browser/font-dependent (≈ 1.2-1.5) which
464
+ inflates the `1lh + 2 * padding` floor used by `.cell.editing`
465
+ and pushes the editing row past `--tbw-row-height`. Locking it
466
+ to 1 keeps the input the height of its font-size so the cell can
467
+ collapse to `--tbw-row-height` in compact mode. */
373
468
  height: auto;
374
- line-height: normal;
469
+ line-height: 1;
375
470
  }
376
471
 
377
472
  & .mat-mdc-select {
@@ -410,30 +505,132 @@ tbw-grid {
410
505
  );
411
506
  }
412
507
  }
413
- }
414
508
 
415
- /* Compact mode when applied from a parent element */
416
- .eds-compact tbw-grid {
417
- --tbw-row-height: calc(var(--sto-base-font-size, 13px) * 1.385);
418
- --tbw-header-height: calc(var(--sto-base-font-size, 13px) * 1.769);
509
+ /* ================================================================
510
+ * 3. Pinned cells & sticky group rows
511
+ * ================================================================ */
512
+
513
+ /* Row hover on pinned cells.
514
+
515
+ Pinned cells (`.sticky-left` / `.sticky-right`) carry an opaque
516
+ `--tbw-color-panel-bg` so non-sticky content scrolling behind
517
+ them can't bleed through. That opaque fill also masks the row's
518
+ `:hover` tint, making pinned columns visually disconnected from
519
+ the rest of the row. Repaint them with the row-hover color to
520
+ restore continuity. Wrapped in `@media (hover: hover)` to mirror
521
+ the grid's own row-hover rule and avoid sticky tap-state flicker
522
+ on touch devices. */
523
+ @media (hover: hover) {
524
+ & .data-grid-row:hover > .cell.sticky-left,
525
+ & .data-grid-row:hover > .cell.sticky-right {
526
+ background: var(--tbw-color-row-hover);
527
+ }
528
+
529
+ /* Hover + focus on a pinned cell: keep the focus tint visible by
530
+ flattening the same 8% accent over the row-hover color. */
531
+ & .data-grid-row:hover > .cell.cell-focus.sticky-left,
532
+ & .data-grid-row:hover > .cell.cell-focus.sticky-right {
533
+ background: color-mix(
534
+ in srgb,
535
+ var(--tbw-color-accent) 8%,
536
+ var(--tbw-color-row-hover)
537
+ );
538
+ }
539
+ }
540
+
541
+ /* Sticky group-row chrome.
542
+
543
+ The grouping-rows feature renders a full-width banner row for each
544
+ group boundary: a `.data-grid-row.group-row` whose only child is a
545
+ single `.cell.group-full` (spanning `grid-column: 1 / -1`) containing
546
+ `.group-toggle`, `.group-label`, `.group-count`, and optional
547
+ `.group-aggregates`. On a horizontally-scrollable grid the cell is
548
+ as wide as the row, so when the user scrolls right the entire chrome
549
+ slides off the left edge.
550
+
551
+ Approach: shrink the cell to its content width and pin it to the left
552
+ with `position: sticky; left: 0`. The chrome children keep their
553
+ inline flow — we never touch them individually, so new chrome
554
+ elements in a future grid version "just work".
555
+
556
+ Two things move from the cell up to the row:
557
+ 1. Background — the cell no longer spans the row, so the row paints
558
+ the banner tint.
559
+ 2. Full-row span override — the cell still has `grid-column: 1 / -1`
560
+ from the grid runtime; `width: max-content !important` shrinks
561
+ the rendered box while leaving the grid-track placement intact.
562
+
563
+ Why we override `overflow: hidden` on the cell:
564
+ @toolbox-web/grid sets `overflow: hidden` on every `.cell` to clip
565
+ content. Per the CSS Position spec, that establishes a scroll
566
+ container, which traps the cell's own sticky positioning inside the
567
+ (non-scrolling) cell box. `overflow: visible` lets the sticky context
568
+ bubble up past the row, past `.rows-viewport` (`overflow: clip` —
569
+ non-scrollable), all the way to `.tbw-scroll-area` (the actual
570
+ horizontal scroller) where sticky engages. */
571
+ & .data-grid-row.group-row {
572
+ background: var(
573
+ --tbw-color-header-bg,
574
+ var(--tbw-color-panel-bg, transparent)
575
+ );
576
+
577
+ & > .cell.group-full {
578
+ overflow: visible;
579
+ position: sticky;
580
+ left: 0;
581
+ z-index: 2;
582
+ width: max-content !important;
583
+ max-width: 100%;
584
+ background: transparent;
585
+ }
586
+
587
+ & .group-count {
588
+ padding: 0 0.5rem;
589
+ }
590
+ }
591
+
592
+ /* Sticky aggregation (footer) row.
593
+
594
+ The bottom aggregation row (`.tbw-footer > .tbw-aggregation-rows >
595
+ .tbw-aggregation-row > .tbw-aggregation-cell.tbw-aggregation-cell-full`)
596
+ has the same shape as a group row: a full-width cell that spans the
597
+ entire scrollable area (`grid-column: 1 / -1`, width = scrollWidth).
598
+ When the user scrolls horizontally the totals/label slide off the
599
+ left edge.
600
+
601
+ Same fix as `.group-row > .cell.group-full`: shrink the cell to its
602
+ content width, pin it to the left with `position: sticky; left: 0`,
603
+ and override the grid's `overflow: hidden` so the sticky context can
604
+ bubble up past the row to the scroll container.
605
+
606
+ `.tbw-footer` itself is already `position: sticky; bottom: 0` from
607
+ the grid runtime — we only need to handle horizontal stickiness. */
608
+ & .tbw-aggregation-row > .tbw-aggregation-cell-full {
609
+ overflow: visible;
610
+ position: sticky;
611
+ left: 0;
612
+ z-index: 2;
613
+ width: max-content !important;
614
+ max-width: 100%;
615
+ background: transparent;
616
+ }
419
617
  }
420
618
 
421
619
  /* ================================================================
422
- * 3. Filter Panel Styles
620
+ * 4. Filter Panel Styles
423
621
  * ================================================================ */
424
622
  .tbw-filter-panel {
425
623
  max-width: none;
426
624
  max-height: none;
427
625
 
428
- /* EDS underline inputs */
626
+ /* EDS underline inputs — the bg/border/radius are stripped via
627
+ `--tbw-filter-input-*` variables in section 1. The underline
628
+ `box-shadow` and height have no variable equivalent. */
429
629
  & .tbw-filter-search-input,
430
630
  & .tbw-filter-range-input,
431
631
  & .tbw-filter-date-input {
432
632
  height: var(--tbw-row-height);
433
- border: none;
434
- border-radius: 0;
435
633
  box-shadow: inset 0px -1px 0px 0px var(--_fg-muted);
436
- background: var(--_surface);
437
634
  outline: 1px solid transparent;
438
635
  outline-offset: 0px;
439
636
  }
@@ -520,7 +717,7 @@ tbw-grid {
520
717
  }
521
718
 
522
719
  /* ================================================================
523
- * 4. Context Menu
720
+ * 5. Context Menu
524
721
  *
525
722
  * Appended to document.body (outside tbw-grid).
526
723
  * CSS variables are copied from tbw-grid at render time.
@@ -579,7 +776,7 @@ tbw-grid {
579
776
  }
580
777
 
581
778
  /* ================================================================
582
- * 5. Overlay Panel (BaseOverlayEditor)
779
+ * 6. Overlay Panel (BaseOverlayEditor)
583
780
  * ================================================================ */
584
781
  .tbw-overlay-panel {
585
782
  --tbw-overlay-bg: var(
@@ -596,10 +793,234 @@ tbw-grid {
596
793
  }
597
794
 
598
795
  /* ================================================================
599
- * 6. Dark Mode
796
+ * 7. EDS in-cell editors — DatePicker and Autocomplete
797
+ *
798
+ * Marker classes:
799
+ * `.tbw-eds-date-editor` — wrapper around EDS <DatePicker>
800
+ * `.tbw-eds-autocomplete-editor` — wrapper around EDS <Autocomplete>
801
+ *
802
+ * Used by React consumers that render EDS form components inside grid
803
+ * cell editors (typically inside `<EdsProvider density="compact">`).
804
+ * EDS wraps each field in an InputWrapper with a label and helper
805
+ * area — none of which we want inside a row-height cell. We strip the
806
+ * chrome, hide the label/helper for sighted users (kept for screen
807
+ * readers), and stretch the inner field stack to fill the cell.
808
+ *
809
+ * Popover positioning fix (applies to both):
810
+ * EDS uses the native popover API (`popover="manual"`) + floating-ui
811
+ * to place the calendar / options dropdown. Inline EDS sets
812
+ * `position: absolute` with viewport-anchored top/left from the
813
+ * trigger's getBoundingClientRect. That works in plain DOM, but
814
+ * `tbw-grid` virtualises rows with `transform` which creates a
815
+ * containing block. Once the popover enters the browser top-layer
816
+ * (`.showPopover()`), the spec says its containing block is the
817
+ * viewport — but floating-ui's inset values assume the regular
818
+ * containing-block cascade. The two coordinate systems mismatch and
819
+ * the popover renders off-screen.
820
+ *
821
+ * Forcing `position: fixed` aligns the popover's containing block
822
+ * with floating-ui's viewport math. The inset reset overrides the
823
+ * browser's default `[popover]:popover-open { inset: 0 }` rule that
824
+ * would otherwise stretch the popover full-viewport.
825
+ *
826
+ * Harmless when EDS is absent (Angular consumers don't ship these
827
+ * markers), so safe to include in the shared theme.
828
+ * ================================================================ */
829
+
830
+ /* Shared chrome-stripping + popover-positioning rules. Applied to both
831
+ editor wrappers via @extend so the rules stay co-located with their
832
+ shared docstring above. */
833
+ %tbw-eds-cell-editor {
834
+ width: 100%;
835
+ height: 100%;
836
+ display: flex;
837
+ align-items: stretch;
838
+
839
+ /* Hide the visually-rendered EDS label/helper, keep them for a11y. */
840
+ label,
841
+ [class*='HelperText'] {
842
+ position: absolute;
843
+ width: 1px;
844
+ height: 1px;
845
+ padding: 0;
846
+ margin: -1px;
847
+ overflow: hidden;
848
+ clip: rect(0, 0, 0, 0);
849
+ white-space: nowrap;
850
+ border: 0;
851
+ }
852
+
853
+ /* Calendar / options dropdown popover — see header for rationale. */
854
+ [popover] {
855
+ position: fixed !important;
856
+ right: auto !important;
857
+ bottom: auto !important;
858
+ width: max-content !important;
859
+ margin: 0 !important;
860
+ }
861
+ }
862
+
863
+ .tbw-eds-date-editor {
864
+ @extend %tbw-eds-cell-editor;
865
+
866
+ /* Stretch the EDS field stack to fill the cell vertically and strip
867
+ its own background/box-shadow underline since the surrounding
868
+ `.tbw-editor-host:focus-within` already provides the focus indicator.
869
+ `!important` beats EDS's ID-scoped rule (specificity 1,1,0).
870
+ NOTE: do not use `> div` — the calendar popover is also a direct
871
+ child once `popover="manual"` opens it, and forcing it to 100%
872
+ height stretches it to the full viewport and breaks auto-flip. */
873
+ [class*='InputWrapper__Container'],
874
+ [class*='FieldWrapper'] {
875
+ width: 100% !important;
876
+ height: 100% !important;
877
+ }
878
+
879
+ [class*='FieldWrapper'] {
880
+ background: transparent !important;
881
+ box-shadow: none !important;
882
+ border: none !important;
883
+ display: flex;
884
+ align-items: center;
885
+ }
886
+ }
887
+
888
+ .tbw-eds-autocomplete-editor {
889
+ @extend %tbw-eds-cell-editor;
890
+
891
+ /* Stretch EDS field stack to fill the cell vertically and strip its
892
+ own background/underline since the editor host owns the focus ring. */
893
+ [class*='InputWrapper__Container'],
894
+ [class*='Container'] {
895
+ width: 100% !important;
896
+ height: 100% !important;
897
+ }
898
+
899
+ [class*='Container'] {
900
+ background: transparent !important;
901
+ box-shadow: none !important;
902
+ border: none !important;
903
+ }
904
+ }
905
+
906
+ /* ================================================================
907
+ * 8. Empty State Overlay
908
+ *
909
+ * The grid's empty overlay (.tbw-empty-overlay) is absolutely
910
+ * positioned inside .rows-container. When the grid host is sized
911
+ * by content (no explicit height) and there are zero rows, the
912
+ * rows-container collapses to height 0 — leaving the overlay
913
+ * with no space and stacking the column header directly on top
914
+ * of the pinned/aggregation footer.
915
+ *
916
+ * Reserve a sensible minimum height for the rows area whenever
917
+ * the empty overlay is present, so the message has room to render
918
+ * and the header / footer separate visually.
919
+ * ================================================================ */
920
+ tbw-grid,
921
+ [data-tbw-grid] {
922
+ & .rows-container:has(> .tbw-empty-overlay) {
923
+ min-height: var(--tbw-empty-min-height, 120px);
924
+ }
925
+
926
+ & .tbw-empty-overlay {
927
+ min-height: var(--tbw-empty-min-height, 120px);
928
+ color: var(--_fg-muted);
929
+ }
930
+ }
931
+
932
+ /* ================================================================
933
+ * 9. Opt-ins (compact mode, thin header)
934
+ * ================================================================ */
935
+
936
+ /* Compact mode — triggers when `.eds-compact` is on the grid itself
937
+ (`<tbw-grid class="eds-compact">`) or on any ancestor (e.g. a wrapping
938
+ section that toggles density for everything inside). */
939
+ tbw-grid.eds-compact,
940
+ [data-tbw-grid].eds-compact,
941
+ .eds-compact tbw-grid,
942
+ .eds-compact [data-tbw-grid] {
943
+ /* Multipliers are tuned against the 13px EDS base:
944
+ row 13 * 1.111 ≈ 14.4px (works once in-cell mat-icons and chip
945
+ padding are constrained — see section 2)
946
+ header 13 * 1.4 ≈ 18.2px
947
+ Apps with a larger base (e.g. 18px) get proportionally bigger:
948
+ 18 * 1.111 ≈ 20px / 18 * 1.4 ≈ 25px. */
949
+ --tbw-row-height: calc(var(--sto-base-font-size, 13px) * 1.111);
950
+ --tbw-header-height: calc(var(--sto-base-font-size, 13px) * 1.4);
951
+
952
+ /* In-cell Material icons.
953
+ Angular Material renders `<mat-icon>` as `<span class="mat-icon">` at a
954
+ hardcoded 24x24. Since `.data-grid-row` is `display: grid` and the row
955
+ track grows to fit the tallest cell content, a 24px icon would inflate
956
+ every row past `--tbw-row-height`. Constrain in-cell icons to the cell
957
+ font-size so they fit the compact row track.
958
+
959
+ Sizing trick: `font-size: 1em` keeps the glyph at the cell font-size
960
+ (13px default), then `width/height: 1em` resolves against the icon's
961
+ own font-size — producing a 13x13 box. Only applied under `.eds-compact`
962
+ so non-compact grids keep Material's standard 24px icons. */
963
+ & .cell .mat-icon,
964
+ & .cell mat-icon {
965
+ font-size: 1em;
966
+ width: 1em;
967
+ height: 1em;
968
+ line-height: 1em;
969
+ }
970
+
971
+ /* In-cell editor density.
972
+ Active editors (`.cell.editing > .tbw-editor-host`) host Angular
973
+ Material form-fields. Even with all chrome stripped (see section 2),
974
+ a Material input renders at ~35px tall because of its intrinsic
975
+ padding/min-height — which then ratchets the grid's row-height
976
+ measurement upward and shrinks the visible-row count for every
977
+ other row.
978
+
979
+ In compact mode we constrain the editor to the compact row height
980
+ so editing a row no longer changes the row's painted height. */
981
+ & .cell.editing {
982
+ /* Eliminate the `1lh + 2 * padding` floor inherited from the base
983
+ `.cell.editing` rule — the active cell follows row-height only. */
984
+ min-height: var(--tbw-row-height);
985
+
986
+ & .tbw-editor-host,
987
+ & .tbw-editor-host .mat-mdc-form-field,
988
+ & .tbw-editor-host .mat-mdc-text-field-wrapper,
989
+ & .tbw-editor-host .mat-mdc-form-field-flex,
990
+ & .tbw-editor-host .mat-mdc-form-field-infix {
991
+ min-height: 0;
992
+ height: 100%;
993
+ }
994
+
995
+ & .tbw-editor-host input.mat-mdc-input-element,
996
+ & .tbw-editor-host .mat-mdc-select-trigger {
997
+ height: var(--tbw-row-height);
998
+ min-height: 0;
999
+ padding-top: 0;
1000
+ padding-bottom: 0;
1001
+ line-height: 1;
1002
+ }
1003
+ }
1004
+ }
1005
+
1006
+ /* Thin header — for grids that are sub-sections inside a larger page
1007
+ (e.g. a field-split table) where a full-height header competes
1008
+ visually with surrounding form headings.
1009
+
1010
+ Apply with: <div class="tbw-thin-header"><tbw-grid ... /></div> */
1011
+ .tbw-thin-header tbw-grid,
1012
+ .tbw-thin-header [data-tbw-grid] {
1013
+ --tbw-header-height: 28px;
1014
+ --tbw-font-weight-header: 500;
1015
+ --tbw-font-size-header: 0.75rem;
1016
+ }
1017
+
1018
+ /* ================================================================
1019
+ * 10. Dark Mode
600
1020
  * ================================================================ */
601
1021
  body.sto-dark-theme {
602
1022
  tbw-grid,
1023
+ [data-tbw-grid],
603
1024
  .tbw-filter-panel,
604
1025
  .tbw-context-menu,
605
1026
  .tbw-overlay-panel {