@livenetworks/ashlar 2.1.12 → 2.1.13

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.
@@ -1,191 +1,104 @@
1
- // ==========================================================================
2
- // Density — ln-ashlar v2.0
3
- // ==========================================================================
4
- // Inverted density system. The :root default is DENSE (DocuFlow scale:
5
- // 14px body, 52px header, 232px sidebar). Density goes UP via the
6
- // [data-density] attribute, mirroring how [data-mode] / [data-theme] works.
7
- //
8
- // Activation:
9
- // <html> ← default: dense (DocuFlow look)
10
- // <html data-density="compact"> ← explicit dense handle (same as base)
11
- // <html data-density="comfortable"> ← pre-v2 default (16px / roomier)
12
- // <html data-density="spacious"> ← large type, generous padding
13
- // <main data-density="compact"> ← scope dense within comfortable page
14
- //
15
- // Density is an explicit user choice (settings toggle).
16
- // It is NEVER auto-switched by viewport — viewport breakpoints handle
17
- // layout, density handles information density inside that layout.
18
- //
19
- // What reacts: everything reading --padding-y, --gap, --text-body-*,
20
- // --text-label-*, --text-title-*, --text-heading-*, --text-display-sm,
21
- // --lh-*, --app-header-height, --app-sidebar-width, --density-row-h,
22
- // --btn-padding-y, --input-padding-y.
23
- //
24
- // Interactive controls (buttons, inputs, pills): density-reactive UPWARD.
25
- // Base (~38px) = WCAG AA. Comfortable (≥46px) and Spacious (≥54px) = AAA.
26
- // Controls rebind --btn-padding-y and --input-padding-y in up-tier scopes.
27
- // See docs-mcp/css/density.md §wcag for the height math.
28
- //
29
- // No parallel --density-* token scale. Density overrides existing base
30
- // tokens at the scope root, exactly like theme color overrides.
31
- // ==========================================================================
32
-
33
- :root {
34
-
35
- // -----------------------------------------------------------------------
36
- // Density — Row height
37
- // Minimum row height for table rows and data lists. Kept density-named
38
- // because it has no analogue in the base token scale.
39
- // -----------------------------------------------------------------------
40
- --density-row-h: 2.25rem; // 36px — dense base
41
- }
42
-
43
- // ---------------------------------------------------------------------------
44
- // [data-density="compact"] — explicit handle for the dense base.
45
- //
46
- // Identical to :root. Exists so:
47
- // (a) a switcher can write a single attribute across all tiers including
48
- // returning to base (rather than removing the attribute)
49
- // (b) a region inside a comfortable page can be scoped dense:
50
- // <table data-density="compact">
51
- //
52
- // Rebinds --density-row-h so a compact-scoped region keeps the dense row
53
- // floor inside a comfortable/spacious page. NOTE: only row-h is restored —
54
- // full dense region-scoping (type, padding) would re-declare the base
55
- // surface; deferred. The global switcher returns to base via :root (dense).
56
- // ---------------------------------------------------------------------------
57
- [data-density="compact"] {
58
- --density-row-h: 2.25rem; // 36px — restore dense base in scoped context
59
- }
60
-
61
- // ---------------------------------------------------------------------------
62
- // [data-density="comfortable"] — pre-v2 default, now an explicit opt-in.
63
- //
64
- // Restores the roomy 16px / lh 1.6 scale that was the :root default before
65
- // design-v2. WCAG 2.5.5 AAA: buttons 46px, inputs/pills ≥44px.
66
- //
67
- // <html data-density="comfortable"> ← global comfortable
68
- // <aside data-density="comfortable"> ← scoped comfortable sidebar
69
- // ---------------------------------------------------------------------------
70
- [data-density="comfortable"] {
71
-
72
- // -- Logical rhythm --------------------------------------------------
73
- --padding-y: var(--size-sm); // 8px (was dense 6px)
74
- --gap: var(--size-sm); // 8px (was dense 6px)
75
-
76
- // -- Body text (restore 16px / 1.6 scale) ----------------------------
77
- --text-body-md: 1rem;
78
- --lh-body-md: 1.6;
79
-
80
- --text-body-sm: 0.875rem;
81
- --lh-body-sm: 1.5;
82
-
83
- // Explicit rebind — form inputs consume --font-size and don't always
84
- // cascade lazy var(--text-body-md) resolutions under a scope.
85
- --font-size: var(--text-body-md);
86
- --line-height: var(--lh-body-md);
87
-
88
- // -- Labels ----------------------------------------------------------
89
- --text-label-md: 0.875rem;
90
- --lh-label-md: 1.4;
91
-
92
- --text-label-sm: 0.8125rem; // 13px (was dense 12px) — form-label, table th
93
- --lh-label-sm: 1.4;
94
-
95
- // -- Caption ---------------------------------------------------------
96
- --text-caption: 0.8125rem; // 13px — modal subtitle, validation errors
97
- --lh-caption: 1.4;
98
-
99
- // -- Titles ----------------------------------------------------------
100
- --text-title-md: 1.125rem;
101
- --lh-title-md: 1.4;
102
-
103
- --text-title-sm: 1rem;
104
- --lh-title-sm: 1.4;
105
-
106
- // -- Headings --------------------------------------------------------
107
- --text-heading-sm: 1.25rem; --lh-heading-sm: 1.3;
108
- --text-heading-md: 1.5rem; --lh-heading-md: 1.25;
109
- --text-heading-lg: 1.875rem; --lh-heading-lg: 1.2;
110
-
111
- // -- Display ---------------------------------------------------------
112
- --text-display-sm: 2.25rem; --lh-display-sm: 1.15;
113
-
114
- // -- App shell -------------------------------------------------------
115
- --app-header-height: 3.5rem; // 56px (was dense 3.25rem)
116
- --app-sidebar-width: 16rem; // 256px (was dense 14.5rem)
117
-
118
- // -- Row height ------------------------------------------------------
119
- --density-row-h: 2.75rem; // 44px (was dense 2.25rem)
120
-
121
- // -- Interactive controls — Tailwind / shadcn standard (38px height) ---
122
- --btn-padding-y: var(--size-sm); // 8px → buttons 38px
123
- --btn-padding-x: var(--size-md); // 16px
124
- --input-padding-y: var(--size-sm); // 8px → inputs/selects 38px
125
- }
126
-
127
- // ---------------------------------------------------------------------------
128
- // [data-density="spacious"] — NEW tier above comfortable.
129
- //
130
- // For low-vision, touch-primary, or presentation contexts.
131
- // Body 18px, generous padding, larger shell. WCAG 2.5.5 AAA:
132
- // buttons 54px, inputs/pills ≥48px.
133
- //
134
- // <html data-density="spacious">
135
- // ---------------------------------------------------------------------------
136
- [data-density="spacious"] {
137
-
138
- // -- Logical rhythm --------------------------------------------------
139
- --padding-y: var(--size-md); // 16px
140
- --gap: var(--size-md); // 16px
141
-
142
- // -- Body text -------------------------------------------------------
143
- --text-body-md: 1.125rem; // 18px
144
- --lh-body-md: 1.75;
145
-
146
- --text-body-sm: 1rem;
147
- --lh-body-sm: 1.6;
148
-
149
- --font-size: var(--text-body-md);
150
- --line-height: var(--lh-body-md);
151
-
152
- // -- Labels ----------------------------------------------------------
153
- --text-label-md: 1rem;
154
- --lh-label-md: 1.5;
155
-
156
- --text-label-sm: 0.9375rem; // 15px (was dense 12px) — form-label, table th
157
- --lh-label-sm: 1.45;
158
-
159
- // -- Caption ---------------------------------------------------------
160
- --text-caption: 0.9375rem; // 15px — modal subtitle, validation errors
161
- --lh-caption: 1.45;
162
-
163
- // -- Titles ----------------------------------------------------------
164
- --text-title-md: 1.25rem;
165
- --lh-title-md: 1.5;
166
-
167
- --text-title-sm: 1.125rem;
168
- --lh-title-sm: 1.45;
169
-
170
- // -- Headings --------------------------------------------------------
171
- --text-heading-sm: 1.5rem; --lh-heading-sm: 1.35;
172
- --text-heading-md: 1.875rem; --lh-heading-md: 1.3;
173
- --text-heading-lg: 2.25rem; --lh-heading-lg: 1.25;
174
-
175
- // -- Display ---------------------------------------------------------
176
- --text-display-sm: 2.75rem; --lh-display-sm: 1.2;
177
-
178
- // -- App shell -------------------------------------------------------
179
- --app-header-height: 4rem; // 64px
180
- --app-sidebar-width: 17.5rem; // 280px
181
-
182
- // -- Row height ------------------------------------------------------
183
- --density-row-h: 3rem; // 48px
184
-
185
- // -- Interactive controls — WCAG 2.5.5 AAA ≥48px --------------------
186
- // btn-padding-y: 2×16px + 20px line-height + 2px borders = 54px
187
- // input-padding-y: indirection token read by form-input + pill-outline
188
- --btn-padding-y: var(--size-md); // 16px → buttons 54px
189
- --btn-padding-x: var(--size-lg); // 24px — spacious width
190
- --input-padding-y: var(--size-md); // 16px → inputs/pills ≥48px
191
- }
1
+ // ==========================================================================
2
+ // Density — ln-ashlar
3
+ // ==========================================================================
4
+ // Information density scaling via spatial compression.
5
+ //
6
+ // The :root default is dense base (DocuFlow scale: 36px row height floor,
7
+ // 6px padding-y, 52px header, 232px sidebar). Density scales spacing upward
8
+ // via the [data-density] attribute, mirroring how [data-mode] / [data-theme] works.
9
+ //
10
+ // Activation:
11
+ // <html> ← default: compact base
12
+ // <html data-density="compact"> ← explicit compact handle (same as base)
13
+ // <html data-density="comfortable"> ← roomy default (44px row floor, 8px padding)
14
+ // <html data-density="spacious"> ← generous touch/kiosk density (48px row floor, 16px padding)
15
+ // <main data-density="compact"> ← scope dense within comfortable page
16
+ //
17
+ // Density is an explicit user choice (settings toggle).
18
+ // It is NEVER auto-switched by viewport — viewport breakpoints handle
19
+ // layout, density handles information density inside that layout.
20
+ //
21
+ // What reacts: spatial rhythm and layout geometry:
22
+ // --padding-y, --gap, --app-header-height, --app-sidebar-width,
23
+ // --density-row-h, --btn-padding-y, --btn-padding-x, --input-padding-y.
24
+ //
25
+ // Typography (font-size, line-height) remains stable across density modes:
26
+ // (1) Prevents text disappearing/reappearing (glyph cache drop on variable fonts)
27
+ // (2) Eliminates layout thrashing during CSS transitions
28
+ // (3) Enables smooth, gentle CSS transitions on padding and row height
29
+ // ==========================================================================
30
+
31
+ :root {
32
+
33
+ // -----------------------------------------------------------------------
34
+ // Density — Row height
35
+ // Minimum row height for table rows and data lists. Kept density-named
36
+ // because it has no analogue in the base token scale.
37
+ // -----------------------------------------------------------------------
38
+ --density-row-h: 2.25rem; // 36px — dense base
39
+ }
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // [data-density="compact"] — explicit handle for the dense base.
43
+ //
44
+ // Identical to :root. Exists so:
45
+ // (a) a switcher can write a single attribute across all tiers including
46
+ // returning to base (rather than removing the attribute)
47
+ // (b) a region inside a comfortable page can be scoped dense:
48
+ // <table data-density="compact">
49
+ // ---------------------------------------------------------------------------
50
+ [data-density="compact"] {
51
+ --density-row-h: 2.25rem; // 36px — restore dense base in scoped context
52
+ --input-padding-y: var(--size-xs-up); // 6px — compact input padding
53
+ }
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // [data-density="comfortable"] — roomy spacing opt-in.
57
+ //
58
+ // WCAG 2.5.5 AAA: buttons 38-44px, inputs/pills ≥44px, table rows 44px.
59
+ //
60
+ // <html data-density="comfortable"> ← global comfortable
61
+ // <aside data-density="comfortable"> ← scoped comfortable sidebar
62
+ // ---------------------------------------------------------------------------
63
+ [data-density="comfortable"] {
64
+
65
+ // -- Logical rhythm --------------------------------------------------
66
+ --padding-y: var(--size-sm); // 8px (was dense 6px)
67
+ --gap: var(--size-sm); // 8px (was dense 6px)
68
+
69
+ // -- App shell -------------------------------------------------------
70
+ --app-header-height: 3.5rem; // 56px (was dense 3.25rem)
71
+ --app-sidebar-width: 16rem; // 256px (was dense 14.5rem)
72
+
73
+ // -- Row height ------------------------------------------------------
74
+ --density-row-h: 2.75rem; // 44px (was dense 2.25rem)
75
+
76
+ // -- Interactive controls — roomy 44px inputs ------------------------
77
+ --input-padding-y: var(--size-sm-up); // 12px → inputs/selects roomy
78
+ }
79
+
80
+ // ---------------------------------------------------------------------------
81
+ // [data-density="spacious"] — generous spacing tier above comfortable.
82
+ //
83
+ // For low-vision, touch-primary, or presentation contexts.
84
+ // Generous padding, larger shell. WCAG 2.5.5 AAA:
85
+ // inputs/pills ≥48px, table rows 48px.
86
+ //
87
+ // <html data-density="spacious">
88
+ // ---------------------------------------------------------------------------
89
+ [data-density="spacious"] {
90
+
91
+ // -- Logical rhythm --------------------------------------------------
92
+ --padding-y: var(--size-md); // 16px
93
+ --gap: var(--size-md); // 16px
94
+
95
+ // -- App shell -------------------------------------------------------
96
+ --app-header-height: 4rem; // 64px
97
+ --app-sidebar-width: 17.5rem; // 280px
98
+
99
+ // -- Row height ------------------------------------------------------
100
+ --density-row-h: 3rem; // 48px
101
+
102
+ // -- Interactive controls — generous touch targets -------------------
103
+ --input-padding-y: var(--size-md); // 16px → inputs/pills ≥48px
104
+ }
@@ -66,7 +66,7 @@
66
66
  }
67
67
 
68
68
  // Initials fallback — <abbr class="avatar">SO</abbr>
69
- &:not(:has(img)):not(:has(span)) {
69
+ &:not(:has(img, span)) {
70
70
  @include _avatar-media;
71
71
  @include flex-center;
72
72
  @include text-sm;
@@ -97,7 +97,7 @@
97
97
  // ─── Sizes ─────────────────────────────────────────────
98
98
  @mixin _avatar-size($size, $font-size, $line-height) {
99
99
  img { @include size($size); }
100
- &:not(:has(img)):not(:has(span)) {
100
+ &:not(:has(img, span)) {
101
101
  @include size($size);
102
102
  font-size: $font-size;
103
103
  line-height: $line-height;
@@ -165,8 +165,7 @@
165
165
  @mixin panel-body {
166
166
  padding: var(--padding-y) var(--padding-x);
167
167
 
168
- &:has(> table),
169
- &:has(> .table-container) {
168
+ &:has(> :is(table, .table-container)) {
170
169
  overflow-x: auto;
171
170
  max-width: 100%;
172
171
  -webkit-overflow-scrolling: touch;
@@ -192,9 +191,7 @@
192
191
  // If main has mixed content (intro <p>, demo-split, <h4>,
193
192
  // then <table>), :only-child doesn't match, padding stays,
194
193
  // and the table keeps its own inset.
195
- &:has(> table:only-child),
196
- &:has(> .table-container:only-child),
197
- &:has(> [data-ln-table]:only-child) {
194
+ &:has(> :is(table, .table-container, [data-ln-table]):only-child) {
198
195
  padding: 0;
199
196
 
200
197
  > table,
@@ -205,9 +205,7 @@
205
205
  }
206
206
 
207
207
  // Propagate disabled states to the parent visual shell
208
- &:has(> input:disabled),
209
- &:has(> select:disabled),
210
- &:has(> button:disabled) {
208
+ &:has(> :is(input, select, button):disabled) {
211
209
  @include opacity-50;
212
210
  @include cursor-not-allowed;
213
211
  --color-bg: var(--bg-sunken);
@@ -461,16 +459,16 @@
461
459
  // pill's `display: none` when pill-outline is applied as an override.
462
460
  > input { display: revert; }
463
461
 
464
- &:has(> input:checked) {
462
+ &:hover {
465
463
  border-color: var(--color-accent);
466
- color: var(--color-accent);
467
- // Reset pill's accent fill on checked — outline stays transparent.
468
- background: transparent;
469
464
  z-index: 1;
470
465
  }
471
466
 
472
- &:hover:not(:has(> input:checked)) {
467
+ &:has(> input:checked) {
473
468
  border-color: var(--color-accent);
469
+ color: var(--color-accent);
470
+ // Reset pill's accent fill on checked — outline stays transparent.
471
+ background: transparent;
474
472
  z-index: 1;
475
473
  }
476
474
  }
@@ -491,17 +489,17 @@
491
489
 
492
490
  > input { display: none; }
493
491
 
492
+ &:hover {
493
+ border-color: var(--color-border);
494
+ }
495
+
494
496
  &:has(> input:checked) {
495
497
  background-color: var(--color-accent);
496
498
  color: var(--color-accent-fg);
497
- }
498
499
 
499
- &:hover:not(:has(> input:checked)) {
500
- border-color: var(--color-border);
501
- }
502
-
503
- &:hover:has(> input:checked) {
504
- background-color: var(--color-accent-hover);
500
+ &:hover {
501
+ background-color: var(--color-accent-hover);
502
+ }
505
503
  }
506
504
  }
507
505
 
@@ -549,24 +547,20 @@
549
547
  @mixin pills {
550
548
  @include inline-flex;
551
549
 
552
- > li > label:has(> input[type="checkbox"]),
553
- > li > label:has(> input[type="radio"]) {
550
+ > li > label:has(> input:is([type="checkbox"], [type="radio"])) {
554
551
  @include pill;
555
552
  border-radius: 0;
556
553
  }
557
554
 
558
- > li:first-child > label:has(> input[type="checkbox"]),
559
- > li:first-child > label:has(> input[type="radio"]) {
555
+ > li:first-child > label:has(> input:is([type="checkbox"], [type="radio"])) {
560
556
  border-radius: var(--radius) 0 0 var(--radius);
561
557
  }
562
558
 
563
- > li:last-child > label:has(> input[type="checkbox"]),
564
- > li:last-child > label:has(> input[type="radio"]) {
559
+ > li:last-child > label:has(> input:is([type="checkbox"], [type="radio"])) {
565
560
  border-radius: 0 var(--radius) var(--radius) 0;
566
561
  }
567
562
 
568
- > li:only-child > label:has(> input[type="checkbox"]),
569
- > li:only-child > label:has(> input[type="radio"]) {
563
+ > li:only-child > label:has(> input:is([type="checkbox"], [type="radio"])) {
570
564
  border-radius: var(--radius);
571
565
  }
572
566
  }
@@ -587,24 +581,20 @@
587
581
  }
588
582
  }
589
583
 
590
- > li > label:has(> input[type="checkbox"]),
591
- > li > label:has(> input[type="radio"]) {
584
+ > li > label:has(> input:is([type="checkbox"], [type="radio"])) {
592
585
  @include pill-outline;
593
586
  border-radius: 0;
594
587
  }
595
588
 
596
- > li:first-child > label:has(> input[type="checkbox"]),
597
- > li:first-child > label:has(> input[type="radio"]) {
589
+ > li:first-child > label:has(> input:is([type="checkbox"], [type="radio"])) {
598
590
  border-radius: var(--radius) 0 0 var(--radius);
599
591
  }
600
592
 
601
- > li:last-child > label:has(> input[type="checkbox"]),
602
- > li:last-child > label:has(> input[type="radio"]) {
593
+ > li:last-child > label:has(> input:is([type="checkbox"], [type="radio"])) {
603
594
  border-radius: 0 var(--radius) var(--radius) 0;
604
595
  }
605
596
 
606
- > li:only-child > label:has(> input[type="checkbox"]),
607
- > li:only-child > label:has(> input[type="radio"]) {
597
+ > li:only-child > label:has(> input:is([type="checkbox"], [type="radio"])) {
608
598
  border-radius: var(--radius);
609
599
  }
610
600
  }
@@ -628,8 +618,7 @@
628
618
  display: block;
629
619
  }
630
620
 
631
- > li > label:has(> input[type="checkbox"]),
632
- > li > label:has(> input[type="radio"]) {
621
+ > li > label:has(> input:is([type="checkbox"], [type="radio"])) {
633
622
  @include flex;
634
623
  @include items-center;
635
624
  @include justify-center;
@@ -799,8 +788,7 @@
799
788
  --padding-y: var(--size-xs-up);
800
789
  padding: var(--padding-y) var(--padding-x);
801
790
 
802
- // Hover (unchecked) — subtle accent wash (nav hover).
803
- &:hover:not(:has(> input:checked)) {
791
+ &:hover {
804
792
  background-color: color-mix(in srgb, var(--color-accent) var(--tint-hover), transparent);
805
793
  }
806
794
 
@@ -809,11 +797,10 @@
809
797
  background-color: var(--color-accent-tint-strong);
810
798
  color: var(--color-accent);
811
799
  @include font-semibold;
812
- }
813
800
 
814
- // Selected + hover — keep the wash, don't darken (nav has no darken).
815
- &:hover:has(> input:checked) {
816
- background-color: var(--color-accent-tint-strong);
801
+ &:hover {
802
+ background-color: var(--color-accent-tint-strong);
803
+ }
817
804
  }
818
805
  }
819
806
  }
@@ -196,11 +196,11 @@
196
196
  }
197
197
  }
198
198
 
199
- &:has(a:hover)::before {
199
+ &:has(> a:hover)::before {
200
200
  opacity: 0.4;
201
201
  }
202
202
 
203
- &:has(a.active)::before {
203
+ &:has(> a.active)::before {
204
204
  transform: scaleY(1);
205
205
  opacity: 1;
206
206
  }
@@ -250,13 +250,13 @@
250
250
  }
251
251
  }
252
252
 
253
- &:has(a:hover)::before {
253
+ &:has(> a:hover)::before {
254
254
  left: 20%;
255
255
  right: 20%;
256
256
  opacity: 0.45;
257
257
  }
258
258
 
259
- &:has(a.active)::before {
259
+ &:has(> a.active)::before {
260
260
  left: 0;
261
261
  right: 0;
262
262
  opacity: 1;
@@ -1,16 +1,16 @@
1
1
  // Spacing — accepts CSS variable or value
2
2
 
3
3
  @mixin p($val) { padding: $val; }
4
- @mixin px($val) { padding-left: $val; padding-right: $val; }
5
- @mixin py($val) { padding-top: $val; padding-bottom: $val; }
4
+ @mixin px($val) { padding-inline: $val; }
5
+ @mixin py($val) { padding-block: $val; }
6
6
  @mixin pt($val) { padding-top: $val; }
7
7
  @mixin pb($val) { padding-bottom: $val; }
8
8
  @mixin pl($val) { padding-left: $val; }
9
9
  @mixin pr($val) { padding-right: $val; }
10
10
 
11
11
  @mixin m($val) { margin: $val; }
12
- @mixin mx($val) { margin-left: $val; margin-right: $val; }
13
- @mixin my($val) { margin-top: $val; margin-bottom: $val; }
12
+ @mixin mx($val) { margin-inline: $val; }
13
+ @mixin my($val) { margin-block: $val; }
14
14
  @mixin mt($val) { margin-top: $val; }
15
15
  @mixin mb($val) { margin-bottom: $val; }
16
16
  @mixin ml($val) { margin-left: $val; }
@@ -77,33 +77,12 @@
77
77
  overflow: hidden;
78
78
  text-overflow: ellipsis;
79
79
 
80
- // Shared header-button stack — keyed off the unprefixed structural filter button
81
- // class and the [data-ln-sort] JS-component selector, so table / ln-table /
82
- // ln-data-table all share one positioning rule.
83
- &:has([data-ln-sort]):has(.table-filter) {
84
- min-width: var(--th-double-action-min-width, 8.5rem);
85
- padding-inline-end: calc(var(--padding-x, var(--size-md)) + 3.75rem);
86
-
87
- .table-filter {
88
- position: absolute;
89
- top: 50%;
90
- transform: translateY(-50%);
91
- right: var(--padding-x, var(--size-md));
92
- margin: 0;
93
- }
94
- [data-ln-sort] {
95
- position: absolute;
96
- top: 50%;
97
- transform: translateY(-50%);
98
- right: calc(var(--padding-x, var(--size-md)) + 1.75rem);
99
- margin: 0;
100
- }
101
- }
102
-
103
- &:has([data-ln-sort]):not(:has(.table-filter)) {
80
+ // Shared header-button stack — cascading single vs double action
81
+ &:has(:is([data-ln-sort], .table-filter)) {
104
82
  min-width: var(--th-single-action-min-width, 6rem);
105
83
  padding-inline-end: calc(var(--padding-x, var(--size-md)) + 2rem);
106
84
 
85
+ .table-filter,
107
86
  [data-ln-sort] {
108
87
  position: absolute;
109
88
  top: 50%;
@@ -113,22 +92,19 @@
113
92
  }
114
93
  }
115
94
 
116
- &:has(.table-filter):not(:has([data-ln-sort])) {
117
- min-width: var(--th-single-action-min-width, 6rem);
118
- padding-inline-end: calc(var(--padding-x, var(--size-md)) + 2rem);
95
+ &:has([data-ln-sort]):has(.table-filter) {
96
+ min-width: var(--th-double-action-min-width, 8.5rem);
97
+ padding-inline-end: calc(var(--padding-x, var(--size-md)) + 3.75rem);
119
98
 
120
- .table-filter {
121
- position: absolute;
122
- top: 50%;
123
- transform: translateY(-50%);
124
- right: var(--padding-x, var(--size-md));
125
- margin: 0;
99
+ [data-ln-sort] {
100
+ right: calc(var(--padding-x, var(--size-md)) + 1.75rem);
126
101
  }
127
102
  }
128
103
  }
129
104
 
130
105
  tbody tr {
131
106
  @include transition;
107
+ height: var(--density-row-h);
132
108
  min-height: var(--density-row-h);
133
109
 
134
110
  &:hover {