@livenetworks/ashlar 2.1.12 → 2.2.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.
Files changed (108) hide show
  1. package/README.md +25 -2
  2. package/components/COMPONENTS.md +29 -25
  3. package/components/ln-api-connector/ln-api-connector.schema.json +105 -105
  4. package/components/ln-chart/ln-chart-dev.scss +0 -4
  5. package/components/ln-core/README.md +16 -60
  6. package/components/ln-core/index.js +1 -1
  7. package/components/ln-core/ln-core.schema.json +120 -120
  8. package/components/ln-core/query.js +113 -113
  9. package/components/ln-core/reactive.js +0 -59
  10. package/components/ln-data-coordinator/README.md +2 -0
  11. package/components/ln-data-coordinator/ln-data-coordinator.js +1 -1
  12. package/components/ln-data-coordinator/ln-data-coordinator.schema.json +196 -196
  13. package/components/ln-data-coordinator/src/ln-data-coordinator.js +457 -432
  14. package/components/ln-data-store/ln-data-store.schema.json +92 -92
  15. package/components/ln-date/ln-date.js +1 -1
  16. package/components/ln-date/src/ln-date.js +1 -1
  17. package/components/ln-debug/ln-debug.js +1 -1
  18. package/components/ln-debug/ln-debug.schema.json +28 -28
  19. package/components/ln-debug/src/generated-attributes.js +304 -302
  20. package/components/ln-dropdown/ln-dropdown.schema.json +90 -90
  21. package/components/ln-external-links/README.md +19 -19
  22. package/components/ln-external-links/ln-external-links.js +1 -1
  23. package/components/ln-external-links/ln-external-links.schema.json +9 -0
  24. package/components/ln-external-links/ln-external-links.scss +12 -0
  25. package/components/ln-external-links/src/ln-external-links.js +3 -2
  26. package/components/ln-filter/README.md +2 -1
  27. package/components/ln-filter/ln-filter.js +1 -1
  28. package/components/ln-filter/ln-filter.schema.json +157 -169
  29. package/components/ln-filter/src/ln-filter.js +4 -4
  30. package/components/ln-form/ln-form.schema.json +57 -57
  31. package/components/ln-key/ln-key.js +1 -1
  32. package/components/ln-key/ln-key.schema.json +8 -0
  33. package/components/ln-key/src/ln-key.js +8 -5
  34. package/components/ln-obfuscator/ln-obfuscator.js +1 -1
  35. package/components/ln-obfuscator/ln-obfuscator.schema.json +6 -0
  36. package/components/ln-obfuscator/src/ln-obfuscator.js +2 -2
  37. package/components/ln-options/ln-options.schema.json +47 -47
  38. package/components/ln-popover/ln-popover.schema.json +82 -82
  39. package/components/ln-progress/ln-progress.schema.json +0 -13
  40. package/components/ln-search/ln-search.schema.json +111 -111
  41. package/components/ln-sort/README.md +1 -0
  42. package/components/ln-sort/ln-sort.js +1 -1
  43. package/components/ln-sort/ln-sort.schema.json +100 -112
  44. package/components/ln-sort/src/sort-model.js +1 -6
  45. package/components/ln-sortable/ln-sortable.schema.json +38 -38
  46. package/components/ln-sortable/ln-sortable.scss +25 -25
  47. package/components/ln-table/README.md +8 -1
  48. package/components/ln-table/ln-table.js +1 -1
  49. package/components/ln-table/ln-table.schema.json +195 -195
  50. package/components/ln-table/src/ln-table.js +43 -7
  51. package/components/ln-table-coordinator/ln-table-coordinator.js +1 -1
  52. package/components/ln-table-coordinator/ln-table-coordinator.schema.json +18 -0
  53. package/components/ln-table-coordinator/src/ln-table-coordinator.js +104 -2
  54. package/components/ln-toast/README.md +1 -1
  55. package/components/ln-toast/ln-toast.js +1 -1
  56. package/components/ln-toast/ln-toast.schema.json +6 -0
  57. package/components/ln-toast/src/ln-toast.js +1 -1
  58. package/components/ln-validate/README.md +6 -6
  59. package/components/ln-validate/ln-validate-dev.scss +0 -5
  60. package/components/ln-validate/ln-validate.js +1 -1
  61. package/components/ln-validate/ln-validate.schema.json +0 -6
  62. package/components/ln-validate/src/ln-validate.js +48 -29
  63. package/ln-ashlar.html-data.json +1824 -1814
  64. package/package.json +15 -5
  65. package/theme/README.md +74 -0
  66. package/theme/base/_global.scss +2 -2
  67. package/theme/base/_reset.scss +11 -0
  68. package/theme/components/_app-shell.scss +0 -8
  69. package/theme/components/_form.scss +6 -6
  70. package/theme/components/_ln-table.scss +6 -3
  71. package/theme/components/_scrollbar.scss +5 -4
  72. package/theme/components/_sortable.scss +19 -19
  73. package/theme/components/_stat-card.scss +1 -0
  74. package/theme/components/_table.scss +11 -35
  75. package/theme/config/_density.scss +110 -65
  76. package/theme/config/_palette.scss +71 -49
  77. package/theme/config/_theme.scss +27 -31
  78. package/theme/config/_tokens.scss +28 -26
  79. package/theme/config/mixins/_app-shell.scss +17 -21
  80. package/theme/config/mixins/_avatar.scss +2 -2
  81. package/theme/config/mixins/_btn.scss +4 -2
  82. package/theme/config/mixins/_card.scss +88 -71
  83. package/theme/config/mixins/_colors.scss +2 -1
  84. package/theme/config/mixins/_display.scss +12 -0
  85. package/theme/config/mixins/_footer.scss +3 -1
  86. package/theme/config/mixins/_form.scss +74 -55
  87. package/theme/config/mixins/_layout.scss +9 -0
  88. package/theme/config/mixins/_ln-table.scss +22 -15
  89. package/theme/config/mixins/_modal.scss +3 -0
  90. package/theme/config/mixins/_nav.scss +31 -8
  91. package/theme/config/mixins/_sidebar.scss +2 -1
  92. package/theme/config/mixins/_spacing.scss +4 -4
  93. package/theme/config/mixins/_stat-card.scss +33 -2
  94. package/theme/config/mixins/_status-badge.scss +1 -0
  95. package/theme/config/mixins/_table.scss +87 -69
  96. package/theme/config/mixins/_tabs.scss +31 -0
  97. package/theme/config/mixins/_toast.scss +0 -1
  98. package/theme/designs/relaxed/_card.scss +12 -0
  99. package/theme/designs/relaxed/_layout.scss +24 -0
  100. package/theme/ln-ashlar-core.scss +1 -0
  101. package/theme/ln-ashlar-dev.scss +0 -1
  102. package/theme/ln-ashlar-relaxed.scss +67 -0
  103. package/theme/tailwind/components.css +2198 -0
  104. package/theme/tailwind/entry.css +3 -0
  105. package/theme/tailwind/theme.css +23 -0
  106. package/theme/utilities/_utilities.scss +1 -11
  107. package/web-types.json +2351 -2337
  108. package/components/ln-progress/ln-progress-dev.scss +0 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livenetworks/ashlar",
3
- "version": "2.1.12",
3
+ "version": "2.2.0",
4
4
  "description": "LiveNetworks unified frontend library - CSS framework and vanilla JS components",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "author": "Live Networks",
@@ -45,19 +45,21 @@
45
45
  "./dev.css": "./dist/ln-ashlar-dev.css",
46
46
  "./dev.js": "./dist/ln-ashlar-dev.js",
47
47
  "./theme/*": "./theme/*",
48
- "./components/*": "./components/*"
48
+ "./components/*": "./components/*",
49
+ "./tailwind.css": "./dist/ln-ashlar-tailwind.css"
49
50
  },
50
51
  "scripts": {
51
52
  "test": "npm run sync:ln-schemas:check && npm run sync:ln-events:check && npm run sync:css-tokens:check && node --test \"tests/*.test.js\"",
52
53
  "test:p0": "node --test tests/p0-data-integrity.test.js",
53
54
  "test:headless": "node tests/headless/run-all.mjs",
54
55
  "dev": "node scripts/build.mjs --watch",
55
- "build": "npm run sync:ln-schemas && npm run sync:ln-events && npm run sync:css-tokens && node scripts/build.mjs && npm run build:dev-css && npm run build:demos",
56
+ "build": "npm run sync:ln-schemas && npm run sync:ln-events && npm run sync:css-tokens && node scripts/build.mjs && npm run build:tailwind && npm run build:dev-css && npm run build:demos",
57
+ "build:tailwind": "node scripts/build-tailwind.mjs",
56
58
  "build:demos": "npm run build:demo-index && npm run build:demo-admin && npm run build:demo-docuflow && npm run build:demo-spa && npm run build:demo-landing && npm run build:demo-corporate",
57
59
  "build:dev-css": "sass theme/ln-ashlar-dev.scss demo/dist/ln-ashlar-dev.css --no-source-map --style=compressed",
58
60
  "generate:brand-assets": "powershell -ExecutionPolicy Bypass -File scripts/generate-brand-assets.ps1 && node scripts/generate-svg-favicon.mjs",
59
61
  "build:demo-index": "sass demo/index.scss demo/index.css --no-source-map --style=compressed",
60
- "build:demo-admin": "sass demo/admin/src/admin.scss demo/admin/dist/admin.css --no-source-map --style=compressed && node -e \"const fs = require('fs'); fs.copyFileSync('demo/admin/src/demo.js', 'demo/admin/dist/demo.js')\" && node demo/admin/src/build-pages.mjs",
62
+ "build:demo-admin": "sass demo/admin/src/admin.scss demo/admin/dist/admin.css --no-source-map --style=compressed && sass demo/admin/src/admin-relaxed.scss demo/admin/dist/admin-relaxed.css --no-source-map --style=compressed && node -e \"const fs = require('fs'); fs.copyFileSync('demo/admin/src/demo.js', 'demo/admin/dist/demo.js')\" && node demo/admin/src/build-pages.mjs",
61
63
  "build:demo-docuflow": "sass demo/docuflow/docuflow.scss demo/docuflow/dist/docuflow.css --no-source-map --style=compressed",
62
64
  "build:demo-spa": "node demo/spa/build.mjs",
63
65
  "build:demo-landing": "sass demo/landing/landing.scss demo/landing/landing.css --no-source-map --style=compressed",
@@ -72,14 +74,22 @@
72
74
  "manifest": "node scripts/extract-manifest.mjs",
73
75
  "manifest:file": "node scripts/extract-manifest.mjs --output=docs/manifest.txt",
74
76
  "generate:llms": "node scripts/generate-llms.mjs",
75
- "generate:llms:check": "node scripts/generate-llms.mjs --check"
77
+ "generate:llms:check": "node scripts/generate-llms.mjs --check",
78
+ "test:visual": "node tests/headless/visual-run.mjs",
79
+ "test:perf": "node tests/headless/perf-run.mjs",
80
+ "test:perf:history": "node tests/headless/perf-run.mjs --history",
81
+ "test:perf:compare": "node tests/headless/perf-run.mjs --compare",
82
+ "test:perf:density": "node tests/headless/perf-density.mjs",
83
+ "test:dual": "node tests/headless/dual-run.mjs"
76
84
  },
77
85
  "devDependencies": {
78
86
  "@tabler/icons": "^3.41.1",
87
+ "@tailwindcss/cli": "^4.3.3",
79
88
  "fake-indexeddb": "^6.2.4",
80
89
  "flag-icons": "^7.5.0",
81
90
  "puppeteer-core": "^25.1.0",
82
91
  "sass-embedded": "^1.83.0",
92
+ "tailwindcss": "^4.3.3",
83
93
  "vite": "^6.0.0"
84
94
  }
85
95
  }
@@ -0,0 +1,74 @@
1
+ # 🎨 ln-ashlar Theme & CSS Architecture
2
+
3
+ `ln-ashlar` provides a high-performance visual layer with **Dual CSS Engine Support**:
4
+ 1. **Ashlar SCSS (Native):** Semantic class-free, design token-driven stylesheet system optimized for minimal CSSOM invalidation and modern W3C standards.
5
+ 2. **Tailwind CSS v4:** Utility-first engine compiling into a standalone bundle (`dist/ln-ashlar-tailwind.css`) with comprehensive layout and interaction parity verified across all 67 demo pages.
6
+
7
+ ---
8
+
9
+ ## 🏛️ Directory Structure
10
+
11
+ ```
12
+ theme/
13
+ ├── base/ # Global HTML5 element resets, typography, and defaults
14
+ ├── components/ # Layer 2 component styling (card, table, form, modal, nav, etc.)
15
+ ├── config/ # Design tokens, density scales, color themes, and mixins
16
+ │ ├── mixins/ # Reusable SCSS mixins for semantic styling
17
+ │ ├── _tokens.scss # Master CSS custom properties (colors, spacing, radius, shadows)
18
+ │ ├── _theme.scss # Theme definitions (Default, Glass, Flat, Minimal)
19
+ │ └── _density.scss # Density scale rebinds (compact, default, spacious)
20
+ ├── tailwind/ # Tailwind CSS v4 source definitions
21
+ │ ├── theme.css # Design token mappings to Tailwind v4 @theme directive
22
+ │ ├── components.css # Component utility recipes
23
+ │ └── entry.css # Entrypoint bundle
24
+ ├── ln-ashlar.scss # Master bundle (Core + Theme)
25
+ ├── ln-ashlar-relaxed.scss # Relaxed design bundle (Core + Theme + designs/relaxed/*)
26
+ └── ln-ashlar-theme.scss # Theme-only visual design system bundle
27
+ ```
28
+
29
+ ---
30
+
31
+ ## ⚡ SCSS Performance & Selector Best Practices
32
+
33
+ To prevent browser style invalidation storms during interactive DOM mutations, all SCSS mixins and components adhere to the following rules:
34
+
35
+ 1. **Consolidate Compound `:has()` Queries via `:is(...)`:**
36
+ - Within structural mixins (such as `panel-body` in `_card.scss`, `pills` in `_form.scss`, and numeric table cells in `_table.scss`), collapse chained `:has()` checks into a single `:has(> :is(...))` selector rather than authoring sequential `:has()` rules.
37
+
38
+ 2. **Prefer Natural Cascade over `:not(:has(...))`:**
39
+ - Negative `:not(:has(...))` selectors force the browser layout engine into exhaustive negative ancestor traversals.
40
+ - Use CSS source-order cascade instead:
41
+ ```scss
42
+ // ✅ RECOMMENDED: Natural cascade override
43
+ &:hover { border-color: var(--color-accent); }
44
+ &:has(> input:checked) {
45
+ border-color: var(--color-accent);
46
+ color: var(--color-accent);
47
+ }
48
+ ```
49
+ - For multi-action stacking (e.g. table header sorting and filtering in `_table.scss`), declare base single-action rules with `:has(:is(...))` and override with double-action `:has(...):has(...)` rather than mutual `:not(:has(...))` exclusions.
50
+
51
+ 3. **Use Logical Properties for Sizing and Spacing:**
52
+ - Mixins for padding and margin use modern CSS logical properties (`padding-inline`, `padding-block`, `margin-inline`, `margin-block`), reducing generated rules by 50% compared to separate physical properties (`-left`, `-right`, `-top`, `-bottom`).
53
+
54
+ 4. **Scope Container State Checks to Direct Children:**
55
+ - For container-level state indicators (e.g. navigation item active/hover lines on `li::before`), scope state queries strictly to direct children using `>` (e.g. `li:has(> a:hover)::before`, `li:has(> a.active)::before`). This avoids recursive descendant tree searches while preserving full-item indicator positioning and animations.
56
+
57
+ ---
58
+
59
+ ## 💨 Tailwind CSS v4 Engine
60
+
61
+ To compile the Tailwind v4 bundle:
62
+ ```sh
63
+ npm run build:tailwind
64
+ ```
65
+
66
+ The output bundle is generated at:
67
+ - `dist/ln-ashlar-tailwind.css`
68
+ - `demo/admin/dist/admin-tailwind.css`
69
+ - `demo/dist/ln-ashlar-tailwind.css`
70
+
71
+ Consumers can toggle between engines dynamically in the browser or via:
72
+ ```javascript
73
+ localStorage.setItem('ln-demo-engine', 'tailwind'); // or 'ashlar'
74
+ ```
@@ -22,11 +22,11 @@ body {
22
22
 
23
23
  a {
24
24
  // Derive hover from --color-accent at this scope. Same hue/saturation,
25
- // lightness reduced by 8 (number form — see _btn.scss for the syntax
25
+ // lightness shifted by --accent-hover-shift (number form — see _btn.scss for the syntax
26
26
  // rationale). Local derivation means a semantic-color parent (.error,
27
27
  // .success, etc.) that rebinds --color-primary cascades to the hover
28
28
  // because var(--color-accent) re-resolves on the anchor element.
29
- --color-accent-hover: hsl(from var(--color-accent) h s calc(l - 8));
29
+ --color-accent-hover: hsl(from var(--color-accent) h s calc(l + var(--accent-hover-shift)));
30
30
 
31
31
  color: var(--color-accent);
32
32
  text-decoration: none;
@@ -15,3 +15,14 @@ ul, ol {
15
15
  html {
16
16
  font-size: 16px;
17
17
  }
18
+
19
+ // Form controls do not inherit typography by default — textarea falls back
20
+ // to the UA monospace face and buttons/inputs to the system UI font.
21
+ button,
22
+ input,
23
+ select,
24
+ textarea {
25
+ font: inherit;
26
+ letter-spacing: inherit;
27
+ color: inherit;
28
+ }
@@ -13,11 +13,3 @@
13
13
  .header-right { @include app-header-right; }
14
14
  .header-actions { @include app-header-actions; }
15
15
  .header-avatar { @include header-avatar; }
16
-
17
- // Lock root scroll exclusively for administrative shells
18
- html:has(.app-wrapper),
19
- body:has(.app-wrapper),
20
- .app-shell {
21
- height: 100%;
22
- overflow: hidden;
23
- }
@@ -71,6 +71,12 @@ $icon-group-test: ':has(> .ln-icon):has(> :is(input[type="text"], input[type="se
71
71
  @include form-input-icon-group-parts;
72
72
  }
73
73
 
74
+ // ─── Field wrapper ──────────────────────────────────────────────
75
+
76
+ .form-element {
77
+ @include form-element;
78
+ }
79
+
74
80
  // ─── Label & Actions ────────────────────────────────────────────
75
81
 
76
82
  // Non-wrapping labels only — the label sits above/beside its input, not
@@ -138,9 +144,3 @@ search:has(> input[type="search"]) {
138
144
  @include form-validate-errors;
139
145
  }
140
146
 
141
- // Any trailing feedback/metadata element inside .form-element automatically gets slot treatment with zero-specificity base styling
142
- :where(.form-element > :is(input, select, textarea, [data-ln-date-field], search, [class*="group"]) ~ :not(input)) {
143
- @include form-slot;
144
- color: var(--fg-muted);
145
- }
146
-
@@ -13,6 +13,12 @@
13
13
  @include ln-table-toolbar;
14
14
  }
15
15
 
16
+ // Toolbar sibling of the table: pin the thead under the toolbar.
17
+ :is(main, section, article):has(> .ln-table__toolbar + :is(table, [data-ln-table])),
18
+ [data-ln-table]:has(> header + table) {
19
+ @include ln-table-toolbar-stack;
20
+ }
21
+
16
22
  .ln-table__toolbar :is(label, search):has(> input[type="search"]) {
17
23
  @include ln-table-toolbar-search;
18
24
  }
@@ -21,9 +27,6 @@
21
27
  @include ln-table-count;
22
28
  }
23
29
 
24
- .table-container {
25
- @include ln-table-card-mode;
26
- }
27
30
  [data-ln-table] > footer,
28
31
  .ln-table__footer {
29
32
  @include ln-table-footer;
@@ -5,13 +5,14 @@
5
5
  height: 8px;
6
6
  }
7
7
 
8
+ // Overlay-style scrollbars: no gutter track; the thumb uses the strong
9
+ // border tone so it stays findable without competing with content.
8
10
  ::-webkit-scrollbar-track {
9
- --color-bg: var(--bg-sunken);
10
- background: var(--color-bg);
11
+ background: transparent;
11
12
  }
12
13
 
13
14
  ::-webkit-scrollbar-thumb {
14
- background-color: var(--color-border);
15
+ background-color: var(--border-strong);
15
16
  border-radius: 4px;
16
17
  border: 2px solid transparent;
17
18
  background-clip: content-box;
@@ -28,5 +29,5 @@
28
29
 
29
30
  * {
30
31
  scrollbar-width: thin;
31
- scrollbar-color: var(--color-border) var(--bg-sunken);
32
+ scrollbar-color: var(--border-strong) transparent;
32
33
  }
@@ -1,19 +1,19 @@
1
- @use '../config/mixins' as *;
2
-
3
- [data-ln-sortable] {
4
- list-style: none;
5
- padding: 0;
6
- margin: 0;
7
-
8
- > * {
9
- transition: box-shadow var(--transition-fast) var(--easing-standard), opacity var(--transition-fast) var(--easing-standard);
10
-
11
- &.ln-sortable--drop-before {
12
- box-shadow: inset 0 2px 0 0 var(--color-accent);
13
- }
14
-
15
- &.ln-sortable--drop-after {
16
- box-shadow: inset 0 -2px 0 0 var(--color-accent);
17
- }
18
- }
19
- }
1
+ @use '../config/mixins' as *;
2
+
3
+ [data-ln-sortable] {
4
+ list-style: none;
5
+ padding: 0;
6
+ margin: 0;
7
+
8
+ > * {
9
+ transition: box-shadow var(--transition-fast) var(--easing-standard), opacity var(--transition-fast) var(--easing-standard);
10
+
11
+ &.ln-sortable--drop-before {
12
+ box-shadow: inset 0 2px 0 0 var(--color-accent);
13
+ }
14
+
15
+ &.ln-sortable--drop-after {
16
+ box-shadow: inset 0 -2px 0 0 var(--color-accent);
17
+ }
18
+ }
19
+ }
@@ -2,4 +2,5 @@
2
2
 
3
3
  [data-ln-stat-card] {
4
4
  @include stat-card;
5
+ @include stat-card-tone;
5
6
  }
@@ -9,8 +9,7 @@ table {
9
9
 
10
10
  td.numeric,
11
11
  td.num,
12
- td:has(time),
13
- td:has([data-ln-number]) {
12
+ td:has(:is(time, [data-ln-number])) {
14
13
  @include font-tabular;
15
14
  }
16
15
 
@@ -26,52 +25,29 @@ table {
26
25
 
27
26
  .table-container {
28
27
  @include container(table);
29
- overflow-x: auto;
30
- overscroll-behavior-x: contain;
28
+ // `clip`, not `auto` — keeps the thead pinned to the page scroller;
29
+ // narrow containers stack records instead (below).
30
+ overflow-x: clip;
31
31
 
32
- @container table (max-width: 640px) {
32
+ // Narrow container (e.g. a side column on desktop) — same card mode.
33
+ @include cq-down(md, table) {
33
34
  table { @include table-responsive; }
34
-
35
- .table-row-actions {
36
- position: static !important;
37
- opacity: 1 !important;
38
- visibility: visible !important;
39
- pointer-events: auto !important;
40
- transform: none !important;
41
- background: transparent !important;
42
- padding-left: 0 !important;
43
- justify-content: flex-end;
44
- margin-top: var(--size-xs);
45
-
46
- &::before {
47
- display: none !important;
48
- }
49
- }
50
35
  }
51
36
  }
52
37
 
53
38
  // ─── Shared Column Filter Button ─────────────────────────────────
54
- // CSS targets the unprefixed structural helper class on the button, NOT the data-ln-* JS
55
- // hook. .table-filter is a thin binding that mirrors the mixin; a consumer may instead
56
- // @include table-filter on their own selector (the class is a convenience, not a mandate).
39
+ // Binding of @mixin table-filter to the column-filter hook; the th stack keys on the same attribute.
57
40
  // Sort lives in scss/components/_sort.scss — see [data-ln-sort].
58
41
 
59
- .table-filter { @include table-filter; }
60
- th:hover .table-filter { opacity: 0.7; }
61
-
62
- // Active filter — JS-state class set DIRECTLY on the button (self-selector), combined with
63
- // the structural helper class. Fixes the latent ln-data-table bug: JS toggles
64
- // .ln-filter-active on the button itself, but the old CSS used a descendant combinator
65
- // (.ln-filter-active [data-ln-col-filter]) that never matched, so the dot never rendered.
66
- // See D2.
67
- .table-filter.ln-filter-active { @include table-filter-active; }
42
+ [data-ln-table-col-filter] { @include table-filter; }
68
43
 
69
44
 
70
45
 
71
46
  // ─── Data Table JS-State Classes ───────────────────────────────
72
47
  // JS-added state classes only. Row chrome (clickable cursor/hover, action
73
- // buttons, sticky header) is a CONSUMER concern — applied via semantic
74
- // selectors in project SCSS, never keyed off the data-ln-* JS hooks.
48
+ // buttons) is a CONSUMER concern — applied via semantic selectors in project
49
+ // SCSS, never keyed off the data-ln-* JS hooks. The sticky header is library
50
+ // default (table-base).
75
51
 
76
52
  .ln-row-selected {
77
53
  @include table-row-selected;
@@ -1,33 +1,30 @@
1
+ @use 'mixins/breakpoints' as *;
2
+
1
3
  // ==========================================================================
2
- // Density — ln-ashlar v2.0
4
+ // Density — ln-ashlar
3
5
  // ==========================================================================
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.
6
+ // The :root default is COMFORTABLE (16px body, 44px controls, 56px header,
7
+ // 256px sidebar). Density goes down or up via [data-density]:
7
8
  //
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)
9
+ // <html> ← default: comfortable
10
+ // <html data-density="compact"> ← dense DocuFlow scale (14px / 36px)
11
+ // <html data-density="comfortable"> ← explicit handle (same as default)
12
12
  // <html data-density="spacious"> ← large type, generous padding
13
- // <main data-density="compact"> ← scope dense within comfortable page
13
+ // <table data-density="compact"> ← scope dense within a page
14
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.
15
+ // _tokens.scss declares the dense values as the raw base; the comfortable
16
+ // block re-binds them at :root, so they surface through
17
+ // [data-density="compact"].
18
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.
19
+ // Density is an explicit user choice (settings toggle). It is NEVER
20
+ // auto-switched by viewport; the mobile rhythm block at the end of this
21
+ // file tightens padding/gap at narrow widths independently.
23
22
  //
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.
23
+ // What reacts: the type roles, spatial rhythm, shell geometry, control
24
+ // sizing, and card/table padding. Each tier owns its own type scale.
28
25
  //
29
- // No parallel --density-* token scale. Density overrides existing base
30
- // tokens at the scope root, exactly like theme color overrides.
26
+ // Interactive controls: compact 36px = WCAG AA; comfortable 44px and
27
+ // spacious 54px = AAA. All controls share --control-line-height.
31
28
  // ==========================================================================
32
29
 
33
30
  :root {
@@ -41,39 +38,18 @@
41
38
  }
42
39
 
43
40
  // ---------------------------------------------------------------------------
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">
41
+ // :root / [data-density="comfortable"] — the default.
51
42
  //
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
43
+ // WCAG 2.5.5 AAA: buttons, inputs and pills 44px.
69
44
  // ---------------------------------------------------------------------------
45
+ :root,
70
46
  [data-density="comfortable"] {
71
47
 
72
48
  // -- Logical rhythm --------------------------------------------------
73
- --padding-y: var(--size-sm); // 8px (was dense 6px)
74
- --gap: var(--size-sm); // 8px (was dense 6px)
49
+ --padding-y: var(--size-sm); // 8px
50
+ --gap: var(--size-sm); // 8px
75
51
 
76
- // -- Body text (restore 16px / 1.6 scale) ----------------------------
52
+ // -- Body text -------------------------------------------------------
77
53
  --text-body-md: 1rem;
78
54
  --lh-body-md: 1.6;
79
55
 
@@ -89,7 +65,7 @@
89
65
  --text-label-md: 0.875rem;
90
66
  --lh-label-md: 1.4;
91
67
 
92
- --text-label-sm: 0.8125rem; // 13px (was dense 12px) — form-label, table th
68
+ --text-label-sm: 0.8125rem; // 13px — form-label, table th
93
69
  --lh-label-sm: 1.4;
94
70
 
95
71
  // -- Caption ---------------------------------------------------------
@@ -112,24 +88,76 @@
112
88
  --text-display-sm: 2.25rem; --lh-display-sm: 1.15;
113
89
 
114
90
  // -- App shell -------------------------------------------------------
115
- --app-header-height: 3.5rem; // 56px (was dense 3.25rem)
116
- --app-sidebar-width: 16rem; // 256px (was dense 14.5rem)
91
+ --app-header-height: 3.5rem; // 56px
92
+ --app-sidebar-width: 16rem; // 256px
117
93
 
118
94
  // -- Row height ------------------------------------------------------
119
- --density-row-h: 2.75rem; // 44px (was dense 2.25rem)
95
+ --density-row-h: 2.75rem; // 44px
120
96
 
121
- // -- Interactive controls — Tailwind / shadcn standard (38px height) ---
122
- --btn-padding-y: var(--size-sm); // 8px → buttons 38px
97
+ // -- Interactive controls — WCAG 2.5.5 AAA ≥44px --------------------
98
+ // Shared control contract: 26px line box + 2×8px + 2px borders = 44px
99
+ // for buttons, inputs, selects and pills alike.
100
+ --control-line-height: 1.625rem; // 26px
101
+ --btn-padding-y: var(--size-sm); // 8px → buttons 44px
123
102
  --btn-padding-x: var(--size-md); // 16px
124
- --input-padding-y: var(--size-sm); // 8px → inputs/selects 38px
103
+ --input-padding-y: var(--size-sm); // 8px → inputs/pills 44px
104
+
105
+ // -- Card and table padding ------------------------------------------
106
+ --card-padding-y: var(--size-md); // 16px
107
+ --card-padding-x: var(--size-lg); // 24px
108
+ --table-cell-padding-y: var(--size-sm-up); // 12px
109
+ --table-cell-padding-x: var(--size-lg); // 24px
110
+ }
111
+
112
+ // ---------------------------------------------------------------------------
113
+ // [data-density="compact"] — the dense DocuFlow scale.
114
+ //
115
+ // Declared after the :root default so it wins on <html>; also scopes a
116
+ // dense region inside a comfortable/spacious page.
117
+ // ---------------------------------------------------------------------------
118
+ [data-density="compact"] {
119
+ --padding-y: var(--size-xs-up); // 6px
120
+ --gap: var(--size-xs-up); // 6px
121
+
122
+ --text-body-md: 0.875rem; --lh-body-md: 1.5;
123
+ --text-body-sm: 0.8125rem; --lh-body-sm: 1.45;
124
+ --font-size: var(--text-body-md);
125
+ --line-height: var(--lh-body-md);
126
+
127
+ --text-label-md: 0.8125rem; --lh-label-md: 1.4;
128
+ --text-label-sm: 0.75rem; --lh-label-sm: 1.4;
129
+ --text-caption: 0.75rem; --lh-caption: 1.4;
130
+
131
+ --text-title-md: 1rem; --lh-title-md: 1.3;
132
+ --text-title-sm: 0.875rem; --lh-title-sm: 1.3;
133
+
134
+ --text-heading-sm: 1.125rem; --lh-heading-sm: 1.25;
135
+ --text-heading-md: 1.25rem; --lh-heading-md: 1.2;
136
+ --text-heading-lg: 1.75rem; --lh-heading-lg: 1.15;
137
+ --text-display-sm: 1.875rem; --lh-display-sm: 1.1;
138
+
139
+ --app-header-height: 3.25rem; // 52px
140
+ --app-sidebar-width: 14.5rem; // 232px
141
+ --density-row-h: 2.25rem; // 36px
142
+
143
+ // Control contract — 22px line box + 2×6px + 2px = 36px
144
+ --control-line-height: 1.375rem;
145
+ --btn-padding-y: var(--size-xs-up);
146
+ --btn-padding-x: var(--size-sm-up);
147
+ --input-padding-y: var(--size-xs-up);
148
+
149
+ --card-padding-y: var(--size-sm-up); // 12px
150
+ --card-padding-x: var(--size-md); // 16px
151
+ --table-cell-padding-y: var(--size-sm); // 8px
152
+ --table-cell-padding-x: var(--size-md); // 16px
125
153
  }
126
154
 
127
155
  // ---------------------------------------------------------------------------
128
- // [data-density="spacious"] — NEW tier above comfortable.
156
+ // [data-density="spacious"] — the tier above comfortable.
129
157
  //
130
158
  // For low-vision, touch-primary, or presentation contexts.
131
159
  // Body 18px, generous padding, larger shell. WCAG 2.5.5 AAA:
132
- // buttons 54px, inputs/pills ≥48px.
160
+ // buttons 54px, inputs/pills 54px.
133
161
  //
134
162
  // <html data-density="spacious">
135
163
  // ---------------------------------------------------------------------------
@@ -153,7 +181,7 @@
153
181
  --text-label-md: 1rem;
154
182
  --lh-label-md: 1.5;
155
183
 
156
- --text-label-sm: 0.9375rem; // 15px (was dense 12px) — form-label, table th
184
+ --text-label-sm: 0.9375rem; // 15px — form-label, table th
157
185
  --lh-label-sm: 1.45;
158
186
 
159
187
  // -- Caption ---------------------------------------------------------
@@ -182,10 +210,27 @@
182
210
  // -- Row height ------------------------------------------------------
183
211
  --density-row-h: 3rem; // 48px
184
212
 
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
213
+ // -- Interactive controls --------------------------------------------
214
+ // Shared control contract: 28px line box + 2×12px + 2px borders = 54px
215
+ --control-line-height: 1.75rem; // 28px
216
+ --btn-padding-y: var(--size-sm-up); // 12px → buttons 54px
217
+ --btn-padding-x: var(--size-lg); // 24px
218
+ --input-padding-y: var(--size-sm-up); // 12px → inputs/pills 54px
219
+
220
+ // -- Card and table padding ------------------------------------------
221
+ --card-padding-y: var(--size-lg); // 24px
222
+ --card-padding-x: var(--size-xl); // 32px
223
+ --table-cell-padding-y: var(--size-md); // 16px
224
+ --table-cell-padding-x: var(--size-xl); // 32px
225
+ }
226
+
227
+ // ---------------------------------------------------------------------------
228
+ // Mobile rhythm — logical tokens tighten at ≤ md viewport, whatever the
229
+ // density tier (declared last so it stacks on top of every tier).
230
+ // ---------------------------------------------------------------------------
231
+ @include mq-down(md) {
232
+ :root {
233
+ --padding-y: var(--size-xs);
234
+ --gap: var(--size-xs-up);
235
+ }
191
236
  }