strata-css 1.1.0 → 1.2.7

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/CHANGELOG.md CHANGED
@@ -1,62 +1,158 @@
1
- # Changelog
2
-
3
- All notable changes to Strata CSS will be documented here.
4
-
5
- ## [1.0.0] — 2026-05-10
6
-
7
- ### Components
8
- - `btn-primary`, `btn-secondary`, `btn-success`, `btn-danger`, `btn-warning`, `btn-info`, `btn-light`, `btn-dark` full semantic button set with hover, focus, and active states baked in
9
- - `card`, `card-header`, `card-body`, `card-footer` — composable card component
10
- - `container`, `row`, `col-*` — Bootstrap-compatible responsive grid across all six breakpoints
11
- - `modal` — dialog component with `data-st-toggle`, `data-st-dismiss`, and `data-st-backdrop` attribute API
12
- - `navbar`, `navbar-brand`, `navbar-nav` — navigation bar component
13
- - Skeleton loader — animated loading placeholder with `Strata.skeleton` JavaScript API
14
-
15
- ### Utilities
16
- - Spacing: `mt-*`, `mb-*`, `ms-*`, `me-*`, `pt-*`, `pb-*`, `px-*`, `py-*`, `mx-auto`, `my-*`
17
- - Display: `d-flex`, `d-none`, `d-block`, `d-grid`, `d-inline`, `d-inline-flex`, `d-inline-block`
18
- - Colors: `text-*`, `bg-*` all semantic colors (primary, secondary, success, danger, warning, info, light, dark, muted)
19
- - Sizing: `w-25`, `w-50`, `w-75`, `w-100`, `h-25`, `h-50`, `h-75`, `h-100`, `mw-100`, `mh-100`
20
- - Flexbox: `justify-content-*`, `align-items-*`, `align-self-*`, `flex-wrap`, `flex-nowrap`, `flex-grow-*`, `flex-shrink-*`
21
- - Position: `position-static`, `position-relative`, `position-absolute`, `position-fixed`, `position-sticky`
22
- - Overflow: `overflow-auto`, `overflow-hidden`, `overflow-scroll`, `overflow-visible`
23
- - Opacity: `opacity-0`, `opacity-25`, `opacity-50`, `opacity-75`, `opacity-100`
24
- - Visibility: `visible`, `invisible`
25
- - Z-index: `z-0` through `z-3`
26
- - Cursor: `cursor-pointer`, `cursor-default`, `cursor-not-allowed`, `cursor-wait`
27
- - Shadows: `shadow-none`, `shadow-sm`, `shadow`, `shadow-lg`
28
- - Transitions: `transition`, `transition-fast`, `transition-slow`, `transition-none`
29
- - Easing: `ease-in`, `ease-out`, `ease-in-out`, `ease-linear`
30
- - Arbitrary values: `mt-[24px]`, `bg-[#ff0000]`, `w-[347px]`, `transition-[background-color_0.3s_ease]`
31
- - Important variants: `!mt-0`, `!d-none`, `!p-0`
32
- - Breakpoint variants on all utilities: `col-md-6`, `d-lg-none`, `mt-xl-4`, `px-xxl-5`
33
-
34
- ### Theming
35
- - Three built-in themes: `light` (default), `dark`, `dim` applied via `data-st-theme` on `<html>`
36
- - Automatic system preference detection via `prefers-color-scheme` — no configuration needed
37
- - Unlimited custom themes via CSS custom properties: `[data-st-theme="brand"] { --st-primary: #7c3aed }`
38
- - All `--st-*` custom properties fully overridable in `:root` or any selector
39
- - Smooth theme transitions — all elements animate when the theme attribute changes
40
-
41
- ### State Management
42
- - `data-st-visible="true|false"` — fade + translateY transition for show/hide
43
- - `data-st-collapsed="true|false"`smooth `max-height` expand/collapse
44
- - `data-st-loading="true|false"` opacity reduction + pointer-events disabled
45
- - `data-st-disabled="true|false"` — opacity reduction + `cursor: not-allowed`
46
- - `data-st-theme="light|dark|dim|custom"` — live theme switching
47
-
48
- ### Build System
49
- - PostCSS plugin with O(1) class registry 1065 pre-computed entries, zero linear scanning
50
- - Multi-layer caching: dirty flag, file mtime, glob hash, config hash, output string cache
51
- - CSS `@layer` hierarchy: `st-base` → `st-components` → `st-utilities` — breakpoint order guaranteed, HTML class order irrelevant
52
- - Bootstrap-style breakpoints: xs (0px), sm (576px), md (768px), lg (992px), xl (1200px), xxl (1400px)
53
- - Custom breakpoints via `strata.config.js` `theme.breakpoints`
54
- - `prefers-reduced-motion` respected automatically — no configuration needed
55
- - CLI: `strata init` (scaffold), `strata --watch` (development), `strata --build` (production), `strata --minify` (minified production)
56
-
57
- ### Performance (vs Tailwind CSS 3 in watch mode)
58
- - Cold build: 1.89ms avg vs 7.21ms 3.8× faster
59
- - Warm rebuild: 0.14ms avg vs 2.70ms 19× faster
60
- - Warm p95: 0.23ms vs 6.12ms 26× faster
61
-
62
- Reproduce via `npm run benchmark`.
1
+ # Changelog
2
+
3
+ All notable changes to Strata CSS will be documented here.
4
+
5
+ ## [1.2.7] — 2026-06-15
6
+
7
+ ### Fixed
8
+ - **Components bundle (`dist/strata.components.js`) now creates the `Strata` namespace.** Each component's UMD wrapper attaches to `Strata.*` only `if (root.Strata)` exists, but the bundle never initialised it — so every component fell back to its own global (`StrataChart`, `StrataModal`, …) and `window.Strata` was never defined, breaking all `Strata.Chart.create(...)` / `Strata.Modal.open(...)` usage (e.g. blank charts in `examples/chart.html`). The bundle banner (`bin/strata.js`) now initialises `Strata` before the wrappers run.
9
+
10
+ ---
11
+
12
+ ## [1.2.6] 2026-06-05
13
+
14
+ ### Docs
15
+ - Versioning rules clarified and expanded in `CONTRIBUTING.md`
16
+ - All package `CLAUDE.md` files updated with complete API references
17
+ - `README.md` files added to `@strata-packages/picker` and `@strata-packages/forms`
18
+ - `CHANGELOG.md` files added to `@strata-packages/picker` and `@strata-packages/forms`
19
+ - Root `README.md` updated: version badge, forms/picker added to standalone packages table
20
+
21
+ ---
22
+
23
+ ## [1.2.5] 2026-06-04
24
+
25
+ ### Added `@strata-packages/picker`
26
+ - `theme` option — per-instance inline CSS variable overrides (primary, bg, text, radius, shadow, cellSize, fontSize)
27
+ - `className` option — extra class on popup for targeted CSS overrides
28
+
29
+ ---
30
+
31
+ ## [1.2.4] 2026-06-03
32
+
33
+ ### Added
34
+ - `@strata-packages/picker` — new standalone package: date, time, and datetime picker
35
+ - Zero dependencies, works standalone or as `Strata.Picker` with Strata CSS
36
+ - Declarative init via `data-st-datepicker`, `data-st-timepicker`, `data-st-datetimepicker`
37
+ - Date range selection with two-input mode and range highlight
38
+ - Preset shortcuts (built-in and custom)
39
+ - Month/year grid navigation
40
+ - `--stp-*` CSS variable system; auto-inherits `--st-*` tokens when Strata CSS is present
41
+
42
+ ### Fixed
43
+ - Picker popup now appends to `<body>` with no CSS opacity transition appears immediately on open
44
+ - `position: fixed` popup no longer adds scroll offset to viewport coordinates
45
+ - Picker rewritten as unified `createPicker` — date / time / datetime all working correctly
46
+
47
+ ---
48
+
49
+ ## [1.2.3]2026-06-02
50
+
51
+ ### Fixed
52
+ - `@strata-packages/forms` auto-init now recognises all `data-st-*` select attributes at DOMContentLoaded
53
+
54
+ ---
55
+
56
+ ## [1.2.2] — 2026-06-01
57
+
58
+ ### Added`@strata-packages/forms`
59
+ - Checkbox select mode: dropdown stays open while ticking, Select All row, group-level checkboxes, `checkboxDisplay`: `chips` / `count` / `list`
60
+ - `maxDisplay` fixed-height chip trigger with `+N` overflow badge
61
+ - Search input always rendered inside the dropdown (not above it)
62
+
63
+ ---
64
+
65
+ ## [1.2.1] — 2026-05-30
66
+
67
+ ### Fixed
68
+ - `@strata-packages/forms` backend-friendly `required` validation — triggers visible error state on custom trigger
69
+
70
+ ---
71
+
72
+ ## [1.2.0] — 2026-05-28
73
+
74
+ ### Added — `@strata-packages/forms`
75
+ - New standalone package: fully accessible custom select replacement
76
+ - Multi-select with chips, `maxItems`, searchable, clearable, grouped `<optgroup>`, creatable, avatar/custom render, async `loadOptions`, auto-width with viewport edge detection
77
+ - Native `<select>` stays in DOM — form submission works with any backend
78
+ - Declarative init via `data-st-select` and `data-st-*` option attributes
79
+
80
+ ---
81
+
82
+ ## [1.1.0] — 2026-05-20
83
+
84
+ ### Added
85
+ - Transition CSS variables: `--st-duration-theme`, `--st-easing-theme` — all hardcoded transition values replaced
86
+ - Sizing utilities: `max-w-{xs/sm/md/lg/xl/xxl/full/none}`, `min-w-{0/full/screen}`, `max-h-{full/screen/none}`, `min-h-{0/full/screen}`
87
+ - Arbitrary sizing: `max-w-[440px]`, `min-h-[300px]`, `max-h-[500px]`, `min-w-[200px]`
88
+ - Responsive variants added to 15 utility groups: `flex-{bp}`, `fw-{bp}`, `fst-{bp}`, `text-{bp}-{transform}`, `rounded-{bp}`, `shadow-{bp}`, `w-{bp}`, `h-{bp}`, `opacity-{bp}`, `overflow-{bp}`, `position-{bp}`, `cursor-{bp}`, `lh-{bp}`, `visible-{bp}`, `invisible-{bp}`
89
+ - Component CSS variable tokens: all hardcoded color values replaced with local CSS variables on `.badge`, `.btn-*`, `.btn-outline-*`, `.nav-pills .active`, `.list-group-item.active`, `.page-item.active`, `.dropdown-item.active`, `.progress-bar`, `.tooltip-inner`, `.navbar-dark`, `.card-img-overlay`, `.carousel-*`, `.table-dark`
90
+ - List utilities: `list-unstyled`, `list-inline`, `list-inline-item`, `list-disc`, `list-decimal`, `list-circle`, `list-square`, `list-none`, `list-lower-alpha`, `list-upper-alpha`, `list-lower-roman`, `list-upper-roman`, `list-inside`, `list-outside`, `list-spaced`
91
+ - Outline utilities: `outline-none`, `outline-{color}`, `outline-{1-5}`
92
+ - Label component: `.label` and `.label-{color}` aliases to `.badge` / `.badge-{color}` for Bootstrap 3 compatibility
93
+
94
+ ### Fixed
95
+ - `text-[15px]` → `font-size: 15px` (length unit correctly detected)
96
+ - `text-[#f00]` → `color: #f00` (color value correctly detected)
97
+ - `#`, `(`, `)`, `,` in arbitrary values now correctly escaped in CSS class selectors
98
+
99
+ ---
100
+
101
+ ## [1.0.0] — 2026-05-10
102
+
103
+ ### Components
104
+ - `btn-primary`, `btn-secondary`, `btn-success`, `btn-danger`, `btn-warning`, `btn-info`, `btn-light`, `btn-dark` — full semantic button set with hover, focus, and active states baked in
105
+ - `card`, `card-header`, `card-body`, `card-footer` — composable card component
106
+ - `container`, `row`, `col-*` — Bootstrap-compatible responsive grid across all six breakpoints
107
+ - `modal` — dialog component with `data-st-toggle`, `data-st-dismiss`, and `data-st-backdrop` attribute API
108
+ - `navbar`, `navbar-brand`, `navbar-nav` — navigation bar component
109
+ - Skeleton loader — animated loading placeholder with `Strata.skeleton` JavaScript API
110
+
111
+ ### Utilities
112
+ - Spacing: `mt-*`, `mb-*`, `ms-*`, `me-*`, `pt-*`, `pb-*`, `px-*`, `py-*`, `mx-auto`, `my-*`
113
+ - Display: `d-flex`, `d-none`, `d-block`, `d-grid`, `d-inline`, `d-inline-flex`, `d-inline-block`
114
+ - Colors: `text-*`, `bg-*` — all semantic colors (primary, secondary, success, danger, warning, info, light, dark, muted)
115
+ - Sizing: `w-25`, `w-50`, `w-75`, `w-100`, `h-25`, `h-50`, `h-75`, `h-100`, `mw-100`, `mh-100`
116
+ - Flexbox: `justify-content-*`, `align-items-*`, `align-self-*`, `flex-wrap`, `flex-nowrap`, `flex-grow-*`, `flex-shrink-*`
117
+ - Position: `position-static`, `position-relative`, `position-absolute`, `position-fixed`, `position-sticky`
118
+ - Overflow: `overflow-auto`, `overflow-hidden`, `overflow-scroll`, `overflow-visible`
119
+ - Opacity: `opacity-0`, `opacity-25`, `opacity-50`, `opacity-75`, `opacity-100`
120
+ - Visibility: `visible`, `invisible`
121
+ - Z-index: `z-0` through `z-3`
122
+ - Cursor: `cursor-pointer`, `cursor-default`, `cursor-not-allowed`, `cursor-wait`
123
+ - Shadows: `shadow-none`, `shadow-sm`, `shadow`, `shadow-lg`
124
+ - Transitions: `transition`, `transition-fast`, `transition-slow`, `transition-none`
125
+ - Easing: `ease-in`, `ease-out`, `ease-in-out`, `ease-linear`
126
+ - Arbitrary values: `mt-[24px]`, `bg-[#ff0000]`, `w-[347px]`, `transition-[background-color_0.3s_ease]`
127
+ - Important variants: `!mt-0`, `!d-none`, `!p-0`
128
+ - Breakpoint variants on all utilities: `col-md-6`, `d-lg-none`, `mt-xl-4`, `px-xxl-5`
129
+
130
+ ### Theming
131
+ - Three built-in themes: `light` (default), `dark`, `dim` — applied via `data-st-theme` on `<html>`
132
+ - Automatic system preference detection via `prefers-color-scheme` — no configuration needed
133
+ - Unlimited custom themes via CSS custom properties: `[data-st-theme="brand"] { --st-primary: #7c3aed }`
134
+ - All `--st-*` custom properties fully overridable in `:root` or any selector
135
+ - Smooth theme transitions — all elements animate when the theme attribute changes
136
+
137
+ ### State Management
138
+ - `data-st-visible="true|false"` — fade + translateY transition for show/hide
139
+ - `data-st-collapsed="true|false"` — smooth `max-height` expand/collapse
140
+ - `data-st-loading="true|false"` — opacity reduction + pointer-events disabled
141
+ - `data-st-disabled="true|false"` — opacity reduction + `cursor: not-allowed`
142
+ - `data-st-theme="light|dark|dim|custom"` — live theme switching
143
+
144
+ ### Build System
145
+ - PostCSS plugin with O(1) class registry — 1065 pre-computed entries, zero linear scanning
146
+ - Multi-layer caching: dirty flag, file mtime, glob hash, config hash, output string cache
147
+ - CSS `@layer` hierarchy: `st-base` → `st-components` → `st-utilities` — breakpoint order guaranteed, HTML class order irrelevant
148
+ - Bootstrap-style breakpoints: xs (0px), sm (576px), md (768px), lg (992px), xl (1200px), xxl (1400px)
149
+ - Custom breakpoints via `strata.config.js` `theme.breakpoints`
150
+ - `prefers-reduced-motion` respected automatically — no configuration needed
151
+ - CLI: `strata init` (scaffold), `strata --watch` (development), `strata --build` (production), `strata --minify` (minified production)
152
+
153
+ ### Performance (vs Tailwind CSS 3 in watch mode)
154
+ - Cold build: 1.89ms avg vs 7.21ms — 3.8× faster
155
+ - Warm rebuild: 0.14ms avg vs 2.70ms — 19× faster
156
+ - Warm p95: 0.23ms vs 6.12ms — 26× faster
157
+
158
+ Reproduce via `npm run benchmark`.
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  **A modern CSS framework combining Bootstrap's component architecture with Tailwind's JIT processing.**
6
6
 
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
8
- [![Version](https://img.shields.io/badge/version-1.0.4-green.svg)]()
8
+ [![Version](https://img.shields.io/badge/version-1.2.6-green.svg)]()
9
9
  [![Node](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen.svg)]()
10
10
  [![npm](https://img.shields.io/badge/npm-strata--css-red.svg)](https://www.npmjs.com/package/strata-css)
11
11
  [![css-framework](https://img.shields.io/badge/css--framework-%E2%9C%93-blue.svg)]()
@@ -383,9 +383,11 @@ All Strata plugins are available as independent packages. Use them without Strat
383
383
 
384
384
  | Package | Standalone global | With Strata | Install |
385
385
  |---|---|---|---|
386
- | `@strata-css/skeleton-loader` | `SkeletonLoader` | `Strata.skeleton` | `npm i @strata-css/skeleton-loader` |
387
- | `@strata-css/modal` | `StrataModal` | `Strata.Modal` | `npm i @strata-css/modal` |
388
- | `@strata-css/chart` | `StrataChart` | `Strata.Chart` | `npm i @strata-css/chart` |
386
+ | `@strata-packages/forms` | `StrataForms` | `Strata.Forms` | `npm i @strata-packages/forms` |
387
+ | `@strata-packages/picker` | `StrataPicker` | `Strata.Picker` | `npm i @strata-packages/picker` |
388
+ | `@strata-packages/skeleton-loader` | `SkeletonLoader` | `Strata.skeleton` | `npm i @strata-packages/skeleton-loader` |
389
+ | `@strata-packages/modal` | `StrataModal` | `Strata.Modal` | `npm i @strata-packages/modal` |
390
+ | `@strata-packages/chart` | `StrataChart` | `Strata.Chart` | `npm i @strata-packages/chart` |
389
391
 
390
392
  ### How detection works
391
393
 
@@ -395,18 +397,18 @@ When `strata.components.js` is loaded it sets `data-strata` on `<html>`. Each pl
395
397
 
396
398
  ```html
397
399
  <!-- Skeleton -->
398
- <link rel="stylesheet" href="node_modules/@strata-css/skeleton-loader/skeleton-loader.css">
399
- <script src="node_modules/@strata-css/skeleton-loader/skeleton-loader.js"></script>
400
+ <link rel="stylesheet" href="node_modules/@strata-packages/skeleton-loader/skeleton-loader.css">
401
+ <script src="node_modules/@strata-packages/skeleton-loader/skeleton-loader.js"></script>
400
402
  <script>SkeletonLoader.init('.card')</script>
401
403
 
402
404
  <!-- Modal -->
403
- <link rel="stylesheet" href="node_modules/@strata-css/modal/modal.css">
404
- <script src="node_modules/@strata-css/modal/modal.js"></script>
405
+ <link rel="stylesheet" href="node_modules/@strata-packages/modal/modal.css">
406
+ <script src="node_modules/@strata-packages/modal/modal.js"></script>
405
407
  <script>StrataModal.open('#myModal')</script>
406
408
 
407
409
  <!-- Chart (requires Three.js) -->
408
410
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
409
- <script src="node_modules/@strata-css/chart/chart.js"></script>
411
+ <script src="node_modules/@strata-packages/chart/chart.js"></script>
410
412
  <script>StrataChart.create('#myChart', { type: 'bar', data: [...] })</script>
411
413
  ```
412
414
 
package/bin/strata.js CHANGED
@@ -90,7 +90,11 @@ async function build(cssMinify = false, jsMinify = true) {
90
90
  const jsDest = path.join(path.dirname(outputFile), 'strata.components.js')
91
91
  if (fs.existsSync(componentsDir)) {
92
92
  const files = fs.readdirSync(componentsDir).filter(f => f.endsWith('.js')).sort()
93
+ // Create the shared Strata namespace BEFORE the package UMD wrappers run, so
94
+ // each one attaches to Strata.* (their `if (root.Strata)` branch) instead of
95
+ // falling back to a separate StrataModal/StrataChart/etc. global.
93
96
  const banner = `/*! Strata Components — built ${new Date().toISOString().slice(0,10)} */\n`
97
+ + `;(function(g){g.Strata=g.Strata||{}})(typeof globalThis!=='undefined'?globalThis:this);\n`
94
98
  const parts = files.map(f => fs.readFileSync(path.join(componentsDir, f), 'utf8'))
95
99
  packageFiles.forEach(p => { if (fs.existsSync(p)) parts.push(fs.readFileSync(p, 'utf8')) })
96
100
  const raw = parts.join('\n')
@@ -178,9 +182,9 @@ function detectFramework(cwd) {
178
182
  // ─── Init helpers ─────────────────────────────────────────────────────
179
183
 
180
184
  const PACKAGES = [
181
- { name: '@strata-css/modal', label: 'modal — attribute-driven modal dialogs' },
182
- { name: '@strata-css/skeleton-loader',label: 'skeleton-loader — shimmer loading placeholders' },
183
- { name: '@strata-css/chart', label: 'chart — Three.js data visualisations' },
185
+ { name: '@strata-packages/modal', label: 'modal — attribute-driven modal dialogs' },
186
+ { name: '@strata-packages/skeleton-loader',label: 'skeleton-loader — shimmer loading placeholders' },
187
+ { name: '@strata-packages/chart', label: 'chart — Three.js data visualisations' },
184
188
  ]
185
189
 
186
190
  const FRAMEWORK_DEV = {
@@ -254,10 +258,10 @@ async function askCheckbox(rl, items) {
254
258
  }
255
259
 
256
260
  function packageUsageSnippet(pkgName) {
257
- if (pkgName === '@strata-css/modal') return `
261
+ if (pkgName === '@strata-packages/modal') return `
258
262
  <!-- Modal usage (standalone) -->
259
- <link rel="stylesheet" href="node_modules/@strata-css/modal/modal.css">
260
- <script src="node_modules/@strata-css/modal/modal.js"></script>
263
+ <link rel="stylesheet" href="node_modules/@strata-packages/modal/modal.css">
264
+ <script src="node_modules/@strata-packages/modal/modal.js"></script>
261
265
 
262
266
  <button data-st-toggle="modal" data-st-target="#myModal">Open</button>
263
267
  <div class="modal" id="myModal" aria-hidden="true">
@@ -270,10 +274,10 @@ function packageUsageSnippet(pkgName) {
270
274
  </div></div>
271
275
  </div>`
272
276
 
273
- if (pkgName === '@strata-css/skeleton-loader') return `
277
+ if (pkgName === '@strata-packages/skeleton-loader') return `
274
278
  <!-- Skeleton loader usage (standalone) -->
275
- <link rel="stylesheet" href="node_modules/@strata-css/skeleton-loader/skeleton-loader.css">
276
- <script src="node_modules/@strata-css/skeleton-loader/skeleton-loader.js"></script>
279
+ <link rel="stylesheet" href="node_modules/@strata-packages/skeleton-loader/skeleton-loader.css">
280
+ <script src="node_modules/@strata-packages/skeleton-loader/skeleton-loader.js"></script>
277
281
 
278
282
  <div class="card" data-st-skeleton="true">
279
283
  <div class="card-body"><p>Content loading...</p></div>
@@ -283,10 +287,10 @@ function packageUsageSnippet(pkgName) {
283
287
  fetchData().then(() => SkeletonLoader.reveal())
284
288
  </script>`
285
289
 
286
- if (pkgName === '@strata-css/chart') return `
290
+ if (pkgName === '@strata-packages/chart') return `
287
291
  <!-- Chart usage (standalone, requires Three.js) -->
288
292
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
289
- <script src="node_modules/@strata-css/chart/chart.js"></script>
293
+ <script src="node_modules/@strata-packages/chart/chart.js"></script>
290
294
 
291
295
  <canvas id="myChart"></canvas>
292
296
  <script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strata-css",
3
- "version": "1.1.0",
3
+ "version": "1.2.7",
4
4
  "_versioningNote": "Stable: 1.0.0 / 1.1.0 / 2.0.0 | Beta: 1.1.0-beta.1 / 1.1.0-beta.2",
5
5
  "description": "A modern CSS framework combining Bootstrap components with Tailwind JIT processing",
6
6
  "main": "src/index.js",
@@ -99,6 +99,10 @@ interface ChartOptions {
99
99
  onReady?: (chart: StrataChart) => void
100
100
  onChange?: (view: ChartView) => void
101
101
  onClick?: (point: { label: string; value: number; category: string; index: number }) => void
102
+
103
+ // Three.js is lazy-loaded from this URL if window.THREE is absent. Set to ''
104
+ // to require the host to pre-load it.
105
+ threeUrl?: string
102
106
  }
103
107
 
104
108
  interface OrbitControlsInstance {
@@ -115,7 +119,10 @@ interface OrbitControlsInstance {
115
119
  interface StrataNamespace { Chart: ChartPlugin }
116
120
 
117
121
  interface ChartPlugin {
118
- create(selector: string | Element, options: ChartOptions): StrataChart | null
122
+ // Synchronous when window.THREE is present; otherwise lazy-loads Three.js and
123
+ // resolves to the instance (or null on failure).
124
+ create(selector: string | Element, options: ChartOptions): StrataChart | null | Promise<StrataChart | null>
125
+ load(url?: string): Promise<unknown>
119
126
  destroyAll(): void
120
127
  }
121
128
 
@@ -157,6 +164,8 @@ const CAMERA_2D = { x: 0, y: 2, z: 22, fov: 18 }
157
164
  const DEFAULT_COLORS = ['#4a90e2', '#e25f4a', '#50c878', '#f5a623', '#9b59b6', '#1abc9c']
158
165
  const VALID_TYPES = ['bar', 'line', 'pie', 'scatter'] as ChartType[]
159
166
  const MAX_POINTS = 100_000
167
+ // Three.js is lazy-loaded on first chart creation if window.THREE is absent.
168
+ const DEFAULT_THREE_URL = 'https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js'
160
169
  const STRIP_HTML = /<[^>]*>/g
161
170
  const SCALE_STEPS = 5
162
171
  const GRID_COLOR_NORMAL = 0xd4d4d4
@@ -1097,29 +1106,54 @@ class StrataChart {
1097
1106
 
1098
1107
  // ─── Bootstrap IIFE ───────────────────────────────────────────────────────────
1099
1108
 
1100
- ;(function (win: Window & typeof globalThis & { Strata?: Partial<StrataNamespace>; StrataChart?: Partial<StrataNamespace['Chart']> }) {
1101
- if (!(win as unknown as Record<string, unknown>)['THREE']) {
1102
- console.error('[Strata Chart] Three.js (window.THREE) is required. Load it before this script.')
1103
- return
1109
+ ;(function (win: Window & typeof globalThis & { THREE?: unknown; Strata?: Partial<StrataNamespace>; StrataChart?: Partial<StrataNamespace['Chart']> }) {
1110
+ // Lazy Three.js loader fetched only on first chart creation if absent.
1111
+ let threeCache: Promise<unknown> | null = null
1112
+ function loadThree(url?: string): Promise<unknown> {
1113
+ if (win.THREE) return Promise.resolve(win.THREE)
1114
+ if (!url) return Promise.reject(new Error('[Strata Chart] Three.js not found and no threeUrl configured.'))
1115
+ if (threeCache) return threeCache
1116
+ threeCache = new Promise((resolve, reject) => {
1117
+ const s = document.createElement('script')
1118
+ s.src = url; s.async = true
1119
+ s.onload = () => { if (win.THREE) resolve(win.THREE); else { threeCache = null; reject(new Error('[Strata Chart] Three.js did not register after loading ' + url)) } }
1120
+ s.onerror = () => { threeCache = null; reject(new Error('[Strata Chart] Failed to load ' + url)) }
1121
+ document.head.appendChild(s)
1122
+ })
1123
+ return threeCache
1104
1124
  }
1105
1125
 
1106
- const api = {
1107
- create(selector: string | Element, options: ChartOptions): StrataChart | null {
1108
- const container = typeof selector === 'string' ? document.querySelector(selector) : selector as Element
1109
- if (!container) { console.error(`[Strata Chart] Element not found: ${String(selector)}`); return null }
1110
- if (registry.has(container)) {
1111
- console.warn('[Strata Chart] Chart already mounted here. Call .destroy() first.')
1112
- return registry.get(container)!
1113
- }
1114
- if (!Array.isArray(options?.data)) { console.error('[Strata Chart] options.data must be an array.'); return null }
1115
- if (options.type && !VALID_TYPES.includes(options.type)) {
1116
- console.error(`[Strata Chart] Invalid type "${options.type}". Use: ${VALID_TYPES.join(', ')}`)
1117
- return null
1118
- }
1119
- const instance = new StrataChart(container as HTMLElement, options)
1120
- registry.set(container, instance)
1121
- return instance
1126
+ // Validate + construct. Assumes Three.js is present.
1127
+ function build(selector: string | Element, options: ChartOptions): StrataChart | null {
1128
+ const container = typeof selector === 'string' ? document.querySelector(selector) : selector as Element
1129
+ if (!container) { console.error(`[Strata Chart] Element not found: ${String(selector)}`); return null }
1130
+ if (registry.has(container)) {
1131
+ console.warn('[Strata Chart] Chart already mounted here. Call .destroy() first.')
1132
+ return registry.get(container)!
1133
+ }
1134
+ if (!Array.isArray(options?.data)) { console.error('[Strata Chart] options.data must be an array.'); return null }
1135
+ if (options.type && !VALID_TYPES.includes(options.type)) {
1136
+ console.error(`[Strata Chart] Invalid type "${options.type}". Use: ${VALID_TYPES.join(', ')}`)
1137
+ return null
1138
+ }
1139
+ const instance = new StrataChart(container as HTMLElement, options)
1140
+ registry.set(container, instance)
1141
+ return instance
1142
+ }
1143
+
1144
+ const api: ChartPlugin = {
1145
+ // Synchronous + unchanged when Three.js is already present. If absent, it is
1146
+ // lazy-loaded and create() returns a Promise<instance> (override the source
1147
+ // with options.threeUrl, or '' to require pre-load).
1148
+ create(selector: string | Element, options: ChartOptions): StrataChart | null | Promise<StrataChart | null> {
1149
+ options = options || ({} as ChartOptions)
1150
+ if (win.THREE) return build(selector, options)
1151
+ const url = options.threeUrl === undefined ? DEFAULT_THREE_URL : options.threeUrl
1152
+ return loadThree(url)
1153
+ .then(() => build(selector, options))
1154
+ .catch((err: Error) => { console.error(err.message || err); return null })
1122
1155
  },
1156
+ load(url?: string): Promise<unknown> { return loadThree(url === undefined ? DEFAULT_THREE_URL : url) },
1123
1157
  destroyAll() { registry.forEach(inst => inst.destroy()) },
1124
1158
  }
1125
1159
 
@@ -253,6 +253,10 @@ const BASE_CSS = `
253
253
  font-family: inherit;
254
254
  }
255
255
 
256
+ label {
257
+ display: inline-block;
258
+ }
259
+
256
260
  input, button, select, textarea {
257
261
  font-family: inherit;
258
262
  font-size: inherit;
@@ -18,6 +18,10 @@ function escapeClass(cls) {
18
18
  .replace(/\//g, '\\/')
19
19
  .replace(/:/g, '\\:')
20
20
  .replace(/\./g, '\\.')
21
+ .replace(/#/g, '\\#')
22
+ .replace(/\(/g, '\\(')
23
+ .replace(/\)/g, '\\)')
24
+ .replace(/,/g, '\\,')
21
25
  }
22
26
 
23
27
  function parseArbitrary(value) {
@@ -510,12 +514,13 @@ reg('card-img', 'components', `.card-img {
510
514
  }`)
511
515
 
512
516
  reg('card-img-overlay', 'components', `.card-img-overlay {
517
+ --st-card-overlay-color: #fff;
513
518
  position: absolute;
514
519
  inset: 0;
515
520
  padding: 1rem;
516
521
  border-radius: calc(var(--st-border-radius) - 1px);
517
522
  background: rgba(0, 0, 0, 0.45);
518
- color: #fff;
523
+ color: var(--st-card-overlay-color);
519
524
  }`)
520
525
 
521
526
  reg('card-group', 'components', `.card-group {
@@ -598,12 +603,13 @@ reg('alert-link', 'components', `.alert-link {
598
603
  // ─── Components — Badge ──────────────────────────────────────────────
599
604
 
600
605
  reg('badge', 'components', `.badge {
606
+ --st-badge-color: #fff;
601
607
  display: inline-block;
602
608
  padding: 0.35em 0.65em;
603
609
  font-size: 0.75em;
604
610
  font-weight: 700;
605
611
  line-height: 1;
606
- color: #fff;
612
+ color: var(--st-badge-color);
607
613
  text-align: center;
608
614
  white-space: nowrap;
609
615
  vertical-align: baseline;
@@ -613,23 +619,61 @@ reg('badge', 'components', `.badge {
613
619
 
614
620
  const BADGE_COLORS = ['primary','secondary','success','danger','warning','info','light','dark']
615
621
  BADGE_COLORS.forEach(color => {
616
- const textColor = ['warning','info','light'].includes(color) ? 'var(--st-dark)' : '#fff'
622
+ const defaultFg = ['warning','info','light'].includes(color) ? 'var(--st-dark)' : '#fff'
617
623
  reg(`badge-${color}`, 'components', `.badge-${color} {
624
+ --st-badge-color: ${defaultFg};
618
625
  background-color: var(--st-${color});
619
- color: ${textColor};
626
+ color: var(--st-badge-color);
620
627
  }`)
621
628
  })
622
629
 
623
630
  reg('badge-pill', 'components', `.badge-pill { border-radius: 999px; }`)
624
631
  reg('rounded-pill', 'components', `.rounded-pill { border-radius: 999px; }`)
625
632
 
633
+ // ─── Components — Label (Bootstrap 3 aliases) ────────────────────────
634
+ // .label and .label-{color} are Bootstrap 3's label component.
635
+ // Bootstrap 4+ renamed them to .badge / .badge-{color}.
636
+ // These are aliases — identical output to their badge equivalents so
637
+ // Bootstrap 3 markup works without changes.
638
+
639
+ reg('label', 'components', `.label {
640
+ --st-badge-color: #fff;
641
+ display: inline-block;
642
+ padding: 0.35em 0.65em;
643
+ font-size: 0.75em;
644
+ font-weight: 700;
645
+ line-height: 1;
646
+ color: var(--st-badge-color);
647
+ text-align: center;
648
+ white-space: nowrap;
649
+ vertical-align: baseline;
650
+ border-radius: var(--st-border-radius);
651
+ background-color: var(--st-secondary);
652
+ }`)
653
+
654
+ const LABEL_COLORS = ['default','primary','secondary','success','info','warning','danger','light','dark']
655
+ LABEL_COLORS.forEach(color => {
656
+ const mappedColor = color === 'default' ? 'secondary' : color
657
+ const defaultFg = ['warning','info','light'].includes(mappedColor) ? 'var(--st-dark)' : '#fff'
658
+ reg(`label-${color}`, 'components', `.label-${color} {
659
+ --st-badge-color: ${defaultFg};
660
+ background-color: var(--st-${mappedColor});
661
+ color: var(--st-badge-color);
662
+ }`)
663
+ })
664
+
626
665
  // ─── Components — Buttons ────────────────────────────────────────────
627
666
 
628
667
  const BTN_COLORS = ['primary','secondary','success','danger','warning','info','light','dark']
629
668
 
630
669
  BTN_COLORS.forEach(color => {
631
- const textColor = ['warning','info','light'].includes(color) ? 'var(--st-dark, #212529)' : '#fff'
670
+ const defaultFg = ['warning','info','light'].includes(color) ? 'var(--st-dark)' : '#fff'
632
671
  reg(`btn-${color}`, 'components', `.btn-${color} {
672
+ --st-btn-color: ${defaultFg};
673
+ --st-btn-bg: var(--st-${color});
674
+ --st-btn-border: var(--st-${color});
675
+ --st-btn-hover-bg: var(--st-${color}-hover, color-mix(in srgb, var(--st-${color}) 85%, black));
676
+ --st-btn-hover-border:var(--st-${color}-hover, color-mix(in srgb, var(--st-${color}) 85%, black));
633
677
  display: inline-flex;
634
678
  align-items: center;
635
679
  justify-content: center;
@@ -637,9 +681,9 @@ BTN_COLORS.forEach(color => {
637
681
  font-size: 1rem;
638
682
  font-weight: 400;
639
683
  line-height: 1.5;
640
- color: ${textColor};
641
- background-color: var(--st-${color});
642
- border: 1px solid var(--st-${color});
684
+ color: var(--st-btn-color);
685
+ background-color: var(--st-btn-bg);
686
+ border: 1px solid var(--st-btn-border);
643
687
  border-radius: var(--st-border-radius);
644
688
  cursor: pointer;
645
689
  text-decoration: none;
@@ -653,9 +697,9 @@ BTN_COLORS.forEach(color => {
653
697
  }
654
698
 
655
699
  .btn-${color}:hover {
656
- background-color: var(--st-${color}-hover, color-mix(in srgb, var(--st-${color}) 85%, black));
657
- border-color: var(--st-${color}-hover, color-mix(in srgb, var(--st-${color}) 85%, black));
658
- color: ${textColor};
700
+ background-color: var(--st-btn-hover-bg);
701
+ border-color: var(--st-btn-hover-border);
702
+ color: var(--st-btn-color);
659
703
  }
660
704
 
661
705
  .btn-${color}:focus-visible {
@@ -668,6 +712,9 @@ BTN_COLORS.forEach(color => {
668
712
  }`)
669
713
 
670
714
  reg(`btn-outline-${color}`, 'components', `.btn-outline-${color} {
715
+ --st-btn-outline-color: var(--st-${color});
716
+ --st-btn-outline-hover-color:${defaultFg};
717
+ --st-btn-outline-hover-bg: var(--st-${color});
671
718
  display: inline-flex;
672
719
  align-items: center;
673
720
  justify-content: center;
@@ -675,7 +722,7 @@ BTN_COLORS.forEach(color => {
675
722
  font-size: 1rem;
676
723
  font-weight: 400;
677
724
  line-height: 1.5;
678
- color: var(--st-${color});
725
+ color: var(--st-btn-outline-color);
679
726
  background-color: transparent;
680
727
  border: 1px solid var(--st-${color});
681
728
  border-radius: var(--st-border-radius);
@@ -689,8 +736,8 @@ BTN_COLORS.forEach(color => {
689
736
  }
690
737
 
691
738
  .btn-outline-${color}:hover {
692
- background-color: var(--st-${color});
693
- color: ${textColor};
739
+ background-color: var(--st-btn-outline-hover-bg);
740
+ color: var(--st-btn-outline-hover-color);
694
741
  }
695
742
 
696
743
  .btn-outline-${color}:focus-visible {
@@ -949,8 +996,9 @@ reg('nav-pills', 'components', `.nav-pills .nav-link {
949
996
  }
950
997
 
951
998
  .nav-pills .nav-link.active {
999
+ --st-nav-pills-active-color: #fff;
952
1000
  background-color: var(--st-primary);
953
- color: #fff;
1001
+ color: var(--st-nav-pills-active-color);
954
1002
  }`)
955
1003
 
956
1004
  reg('nav-fill', 'components', `.nav-fill .nav-item { flex: 1 1 auto; text-align: center; }`)
@@ -1333,10 +1381,11 @@ reg('list-group-item', 'components', `.list-group-item {
1333
1381
  }
1334
1382
 
1335
1383
  .list-group-item.active {
1384
+ --st-list-group-active-color: #fff;
1336
1385
  z-index: 2;
1337
1386
  background-color: var(--st-primary);
1338
1387
  border-color: var(--st-primary);
1339
- color: #fff;
1388
+ color: var(--st-list-group-active-color);
1340
1389
  }
1341
1390
 
1342
1391
  .list-group-item.disabled {
@@ -1410,11 +1459,12 @@ reg('progress', 'components', `.progress {
1410
1459
  }`)
1411
1460
 
1412
1461
  reg('progress-bar', 'components', `.progress-bar {
1462
+ --st-progress-bar-color: #fff;
1413
1463
  display: flex;
1414
1464
  flex-direction: column;
1415
1465
  justify-content: center;
1416
1466
  overflow: hidden;
1417
- color: #fff;
1467
+ color: var(--st-progress-bar-color);
1418
1468
  text-align: center;
1419
1469
  white-space: nowrap;
1420
1470
  background-color: var(--st-primary);
@@ -1531,9 +1581,10 @@ reg('page-item', 'components', `.page-item.disabled .page-link {
1531
1581
  }
1532
1582
 
1533
1583
  .page-item.active .page-link {
1584
+ --st-pagination-active-color: #fff;
1534
1585
  background-color: var(--st-primary);
1535
1586
  border-color: var(--st-primary);
1536
- color: #fff;
1587
+ color: var(--st-pagination-active-color);
1537
1588
  }`)
1538
1589
 
1539
1590
  reg('page-link', 'components', `.page-link {
@@ -1926,8 +1977,9 @@ reg('dropdown-item', 'components', `.dropdown-item {
1926
1977
 
1927
1978
  .dropdown-item.active,
1928
1979
  .dropdown-item:active {
1980
+ --st-dropdown-active-color: #fff;
1929
1981
  background-color: var(--st-primary);
1930
- color: #fff;
1982
+ color: var(--st-dropdown-active-color);
1931
1983
  }
1932
1984
 
1933
1985
  .dropdown-item.disabled {
@@ -2103,12 +2155,17 @@ Object.entries(TABLE_COLORS).forEach(([color, bg]) => {
2103
2155
  .table-${color} > :not(caption) > * > * { background-color: var(--st-table-bg); }`)
2104
2156
  })
2105
2157
  reg('table-dark', 'components', `.table-dark {
2106
- --st-table-bg: #212529;
2107
- color: #dee2e6;
2108
- border-color: #373b3e;
2109
- }
2110
- .table-dark > :not(caption) > * > * { background-color: var(--st-table-bg); color: #dee2e6; }
2111
- .table-dark > thead > tr > th { background-color: #1a1d20; color: #adb5bd; }`)
2158
+ --st-table-dark-bg: #212529;
2159
+ --st-table-dark-color: #dee2e6;
2160
+ --st-table-dark-border: #373b3e;
2161
+ --st-table-dark-head-bg: #1a1d20;
2162
+ --st-table-dark-head-color: #adb5bd;
2163
+ --st-table-bg: var(--st-table-dark-bg);
2164
+ color: var(--st-table-dark-color);
2165
+ border-color: var(--st-table-dark-border);
2166
+ }
2167
+ .table-dark > :not(caption) > * > * { background-color: var(--st-table-bg); color: var(--st-table-dark-color); }
2168
+ .table-dark > thead > tr > th { background-color: var(--st-table-dark-head-bg); color: var(--st-table-dark-head-color); }`)
2112
2169
  reg('table-striped-columns', 'components', `.table-striped-columns > :not(caption) > tr > :nth-child(even) {
2113
2170
  background-color: var(--st-bg-secondary);
2114
2171
  }`)
@@ -2171,22 +2228,25 @@ reg('carousel-item', 'components', `.carousel-item {
2171
2228
  .carousel-item-next,
2172
2229
  .carousel-item-prev { display: block; }`)
2173
2230
  reg('carousel-control-prev', 'components', `.carousel-control-prev {
2231
+ --st-carousel-control-color: #fff;
2174
2232
  position: absolute; top: 0; bottom: 0; left: 0;
2175
2233
  display: flex; align-items: center; justify-content: center;
2176
- width: 15%; padding: 0; color: #fff; text-align: center;
2234
+ width: 15%; padding: 0; color: var(--st-carousel-control-color); text-align: center;
2177
2235
  background: rgba(0,0,0,0.2); border: 0; opacity: 0.5;
2178
2236
  cursor: pointer; transition: opacity var(--st-duration) var(--st-easing);
2179
2237
  }
2180
2238
  .carousel-control-prev:hover { opacity: 0.9; }`)
2181
2239
  reg('carousel-control-next', 'components', `.carousel-control-next {
2240
+ --st-carousel-control-color: #fff;
2182
2241
  position: absolute; top: 0; right: 0; bottom: 0;
2183
2242
  display: flex; align-items: center; justify-content: center;
2184
- width: 15%; padding: 0; color: #fff; text-align: center;
2243
+ width: 15%; padding: 0; color: var(--st-carousel-control-color); text-align: center;
2185
2244
  background: rgba(0,0,0,0.2); border: 0; opacity: 0.5;
2186
2245
  cursor: pointer; transition: opacity var(--st-duration) var(--st-easing);
2187
2246
  }
2188
2247
  .carousel-control-next:hover { opacity: 0.9; }`)
2189
2248
  reg('carousel-indicators', 'components', `.carousel-indicators {
2249
+ --st-carousel-indicator-bg: #fff;
2190
2250
  position: absolute; right: 0; bottom: 0; left: 0;
2191
2251
  display: flex; justify-content: center;
2192
2252
  padding: 0; margin: 0 15%; list-style: none;
@@ -2194,13 +2254,14 @@ reg('carousel-indicators', 'components', `.carousel-indicators {
2194
2254
  .carousel-indicators [data-bs-target],
2195
2255
  .carousel-indicators button {
2196
2256
  width: 30px; height: 3px; margin: 0 3px;
2197
- background-color: #fff; border: none; cursor: pointer;
2257
+ background-color: var(--st-carousel-indicator-bg); border: none; cursor: pointer;
2198
2258
  opacity: 0.5; transition: opacity var(--st-duration) var(--st-easing);
2199
2259
  }
2200
2260
  .carousel-indicators .active { opacity: 1; }`)
2201
2261
  reg('carousel-caption', 'components', `.carousel-caption {
2262
+ --st-carousel-caption-color: #fff;
2202
2263
  position: absolute; right: 15%; bottom: 1.25rem; left: 15%;
2203
- padding: 1.25rem; color: #fff; text-align: center;
2264
+ padding: 1.25rem; color: var(--st-carousel-caption-color); text-align: center;
2204
2265
  }`)
2205
2266
  reg('carousel-fade', 'components', `.carousel-fade .carousel-item { opacity: 0; transition: opacity var(--st-duration-slow) var(--st-easing); transform: none; }
2206
2267
  .carousel-fade .carousel-item.active { opacity: 1; }`)
@@ -2297,13 +2358,15 @@ reg('navbar-expand', 'components', `.navbar-expand {
2297
2358
  .navbar-expand .navbar-collapse { display: flex; flex-basis: auto; }
2298
2359
  .navbar-expand .navbar-toggler { display: none; }`)
2299
2360
  reg('navbar-dark', 'components', `.navbar-dark {
2300
- --st-navbar-color: rgba(255,255,255,0.75);
2361
+ --st-navbar-dark-color: rgba(255,255,255,0.75);
2362
+ --st-navbar-dark-color-hover: rgba(255,255,255,0.9);
2363
+ --st-navbar-dark-toggler-border: rgba(255,255,255,0.1);
2301
2364
  background-color: var(--st-dark);
2302
2365
  border-color: transparent;
2303
2366
  }
2304
2367
  .navbar-dark .navbar-brand,
2305
- .navbar-dark .nav-link { color: rgba(255,255,255,0.9); }
2306
- .navbar-dark .navbar-toggler { border-color: rgba(255,255,255,0.1); color: rgba(255,255,255,0.75); }`)
2368
+ .navbar-dark .nav-link { color: var(--st-navbar-dark-color-hover); }
2369
+ .navbar-dark .navbar-toggler { border-color: var(--st-navbar-dark-toggler-border); color: var(--st-navbar-dark-color); }`)
2307
2370
  reg('navbar-light', 'components', `.navbar-light {
2308
2371
  background-color: var(--st-bg);
2309
2372
  border-color: var(--st-border);
@@ -2428,14 +2491,16 @@ reg('tooltip', 'components', `.tooltip {
2428
2491
  }
2429
2492
  .tooltip.show { opacity: 0.9; }`)
2430
2493
  reg('tooltip-inner', 'components', `.tooltip-inner {
2494
+ --st-tooltip-color: #fff;
2495
+ --st-tooltip-bg: #000;
2431
2496
  max-width: 200px; padding: 0.25rem 0.5rem;
2432
- color: #fff; text-align: center;
2433
- background-color: #000; border-radius: var(--st-border-radius);
2497
+ color: var(--st-tooltip-color); text-align: center;
2498
+ background-color: var(--st-tooltip-bg); border-radius: var(--st-border-radius);
2434
2499
  }`)
2435
2500
  reg('bs-tooltip-top', 'components', `.bs-tooltip-top { padding: 4px 0; }
2436
2501
  .bs-tooltip-top .tooltip-arrow::before {
2437
2502
  top: -1px; border-width: 4px 4px 0;
2438
- border-top-color: #000;
2503
+ border-top-color: var(--st-tooltip-bg, #000);
2439
2504
  }`)
2440
2505
  reg('popover', 'components', `.popover {
2441
2506
  position: absolute; z-index: var(--st-z-popover, 1070);
@@ -2654,6 +2719,40 @@ reg('translate-middle-y', 'utilities', `.translate-middle-y { transform: transla
2654
2719
  // ─── Sizing extras ────────────────────────────────────────────────────
2655
2720
  reg('mw-100', 'utilities', `.mw-100 { max-width: 100%; }`)
2656
2721
  reg('mh-100', 'utilities', `.mh-100 { max-height: 100%; }`)
2722
+
2723
+ // ─── max-w-* named scale (aligned to Bootstrap container breakpoints) ─
2724
+ // max-w-xs → 320px small phone portrait
2725
+ // max-w-sm → 540px Bootstrap sm container
2726
+ // max-w-md → 720px Bootstrap md container
2727
+ // max-w-lg → 960px Bootstrap lg container
2728
+ // max-w-xl → 1140px Bootstrap xl container
2729
+ // max-w-xxl → 1320px Bootstrap xxl container
2730
+ const MAX_W_SCALE = {
2731
+ 'xs': '320px',
2732
+ 'sm': '540px',
2733
+ 'md': '720px',
2734
+ 'lg': '960px',
2735
+ 'xl': '1140px',
2736
+ 'xxl': '1320px',
2737
+ 'full': '100%',
2738
+ 'none': 'none',
2739
+ }
2740
+ Object.entries(MAX_W_SCALE).forEach(([k, v]) => {
2741
+ reg(`max-w-${k}`, 'utilities', `.max-w-${k} { max-width: ${v}; }`)
2742
+ })
2743
+
2744
+ // ─── min-w-* ──────────────────────────────────────────────────────────
2745
+ reg('min-w-0', 'utilities', `.min-w-0 { min-width: 0; }`)
2746
+ reg('min-w-full', 'utilities', `.min-w-full { min-width: 100%; }`)
2747
+ reg('min-w-screen', 'utilities', `.min-w-screen { min-width: 100vw; }`)
2748
+
2749
+ // max-h-* and min-h-* common values
2750
+ reg('max-h-full', 'utilities', `.max-h-full { max-height: 100%; }`)
2751
+ reg('max-h-screen', 'utilities', `.max-h-screen { max-height: 100vh; }`)
2752
+ reg('max-h-none', 'utilities', `.max-h-none { max-height: none; }`)
2753
+ reg('min-h-0', 'utilities', `.min-h-0 { min-height: 0; }`)
2754
+ reg('min-h-full', 'utilities', `.min-h-full { min-height: 100%; }`)
2755
+ reg('min-h-screen', 'utilities', `.min-h-screen { min-height: 100vh; }`)
2657
2756
  reg('vw-100', 'utilities', `.vw-100 { width: 100vw; }`)
2658
2757
  reg('vh-100', 'utilities', `.vh-100 { height: 100vh; }`)
2659
2758
  reg('min-vw-100','utilities', `.min-vw-100{ min-width: 100vw; }`)
@@ -2799,18 +2898,21 @@ reg('tooltip-arrow', 'components', TOOLTIP_ARROW)
2799
2898
  })
2800
2899
 
2801
2900
  // ─── List utilities ───────────────────────────────────────────────────
2901
+
2902
+ // Remove list styling entirely — resets padding, margin, and list-style
2802
2903
  reg('list-unstyled', 'utilities', `.list-unstyled {
2803
- padding-left: 0;
2804
- margin-top: 0;
2904
+ padding-left: 0;
2905
+ margin-top: 0;
2805
2906
  margin-bottom: 0;
2806
- list-style: none;
2907
+ list-style: none;
2807
2908
  }`)
2808
2909
 
2910
+ // Inline list — items sit side by side
2809
2911
  reg('list-inline', 'utilities', `.list-inline {
2810
- padding-left: 0;
2811
- margin-top: 0;
2912
+ padding-left: 0;
2913
+ margin-top: 0;
2812
2914
  margin-bottom: 0;
2813
- list-style: none;
2915
+ list-style: none;
2814
2916
  }`)
2815
2917
 
2816
2918
  reg('list-inline-item', 'utilities', `.list-inline-item {
@@ -2821,6 +2923,24 @@ reg('list-inline-item', 'utilities', `.list-inline-item {
2821
2923
  margin-right: 0.5rem;
2822
2924
  }`)
2823
2925
 
2926
+ // list-style-type variants
2927
+ reg('list-disc', 'utilities', `.list-disc { list-style-type: disc; }`)
2928
+ reg('list-decimal', 'utilities', `.list-decimal { list-style-type: decimal; }`)
2929
+ reg('list-circle', 'utilities', `.list-circle { list-style-type: circle; }`)
2930
+ reg('list-square', 'utilities', `.list-square { list-style-type: square; }`)
2931
+ reg('list-none', 'utilities', `.list-none { list-style-type: none; }`)
2932
+ reg('list-lower-alpha', 'utilities', `.list-lower-alpha { list-style-type: lower-alpha; }`)
2933
+ reg('list-upper-alpha', 'utilities', `.list-upper-alpha { list-style-type: upper-alpha; }`)
2934
+ reg('list-lower-roman', 'utilities', `.list-lower-roman { list-style-type: lower-roman; }`)
2935
+ reg('list-upper-roman', 'utilities', `.list-upper-roman { list-style-type: upper-roman; }`)
2936
+
2937
+ // list-style-position variants
2938
+ reg('list-inside', 'utilities', `.list-inside { list-style-position: inside; }`)
2939
+ reg('list-outside', 'utilities', `.list-outside { list-style-position: outside; }`)
2940
+
2941
+ // Spaced list — adds breathing room between items
2942
+ reg('list-spaced', 'utilities', `.list-spaced > li + li { margin-top: 0.5rem; }`)
2943
+
2824
2944
  // ─── Form group ───────────────────────────────────────────────────────
2825
2945
  reg('form-group', 'components', `.form-group {
2826
2946
  margin-bottom: 1rem;
@@ -2851,6 +2971,166 @@ Object.entries(OUTLINE_COLOR_MAP).forEach(([k, v]) => {
2851
2971
  reg(`outline-${n}`, 'utilities', `.outline-${n} { outline-width: ${n}px; }`)
2852
2972
  })
2853
2973
 
2974
+ // ─── Responsive variants ─────────────────────────────────────────────
2975
+ // Breakpoint-prefixed versions of utilities that were previously static.
2976
+ // Pattern: {utility}-{bp}-{value} e.g. flex-md-row, fw-lg-bold, rounded-md-3
2977
+
2978
+ const BP_KEYS = Object.keys(BP_VALUES) // sm md lg xl xxl
2979
+
2980
+ // Flex direction
2981
+ ;['row','column','wrap','nowrap','row-reverse','column-reverse'].forEach(v => {
2982
+ const prop = ['wrap','nowrap'].includes(v)
2983
+ ? `flex-wrap: ${v === 'nowrap' ? 'nowrap' : 'wrap'};`
2984
+ : `flex-direction: ${v};`
2985
+ BP_KEYS.forEach(bp => {
2986
+ reg(`flex-${bp}-${v}`, 'utilities',
2987
+ mq(bp, `.flex-${bp}-${v} { ${prop} }`))
2988
+ })
2989
+ })
2990
+
2991
+ // Font weight
2992
+ const FW_MAP = {
2993
+ light: '300', lighter: 'lighter', normal: '400',
2994
+ medium: '500', semibold: '600', bold: '700', bolder: 'bolder',
2995
+ }
2996
+ Object.entries(FW_MAP).forEach(([k, v]) => {
2997
+ BP_KEYS.forEach(bp => {
2998
+ reg(`fw-${bp}-${k}`, 'utilities',
2999
+ mq(bp, `.fw-${bp}-${k} { font-weight: ${v}; }`))
3000
+ })
3001
+ })
3002
+
3003
+ // Font style
3004
+ ;['italic','normal'].forEach(v => {
3005
+ BP_KEYS.forEach(bp => {
3006
+ reg(`fst-${bp}-${v}`, 'utilities',
3007
+ mq(bp, `.fst-${bp}-${v} { font-style: ${v}; }`))
3008
+ })
3009
+ })
3010
+
3011
+ // Text transform
3012
+ ;['uppercase','lowercase','capitalize','none'].forEach(v => {
3013
+ BP_KEYS.forEach(bp => {
3014
+ reg(`text-${bp}-${v}`, 'utilities',
3015
+ mq(bp, `.text-${bp}-${v} { text-transform: ${v}; }`))
3016
+ })
3017
+ })
3018
+
3019
+ // Text decoration
3020
+ ;['none','underline','line-through'].forEach(v => {
3021
+ BP_KEYS.forEach(bp => {
3022
+ reg(`text-${bp}-decoration-${v}`, 'utilities',
3023
+ mq(bp, `.text-${bp}-decoration-${v} { text-decoration: ${v}; }`))
3024
+ })
3025
+ })
3026
+
3027
+ // Text wrap
3028
+ ;['wrap','nowrap'].forEach(v => {
3029
+ BP_KEYS.forEach(bp => {
3030
+ reg(`text-${bp}-${v}`, 'utilities',
3031
+ mq(bp, `.text-${bp}-${v} { white-space: ${v === 'nowrap' ? 'nowrap' : 'normal'}; }`))
3032
+ })
3033
+ })
3034
+
3035
+ // Border radius
3036
+ const ROUNDED_SCALE = {
3037
+ '0':'0', '1':'0.25rem', '2':'0.375rem', '3':'0.5rem',
3038
+ '4':'0.75rem', '5':'1rem',
3039
+ 'pill':'999px', 'circle':'50%',
3040
+ }
3041
+ Object.entries(ROUNDED_SCALE).forEach(([k, v]) => {
3042
+ BP_KEYS.forEach(bp => {
3043
+ reg(`rounded-${bp}-${k}`, 'utilities',
3044
+ mq(bp, `.rounded-${bp}-${k} { border-radius: ${v}; }`))
3045
+ })
3046
+ })
3047
+ BP_KEYS.forEach(bp => {
3048
+ reg(`rounded-${bp}`, 'utilities',
3049
+ mq(bp, `.rounded-${bp} { border-radius: var(--st-border-radius); }`))
3050
+ })
3051
+
3052
+ // Shadow
3053
+ const SHADOW_MAP = {
3054
+ 'sm': 'var(--st-shadow-sm)', '': 'var(--st-shadow)',
3055
+ 'lg': 'var(--st-shadow-lg)', 'none': 'none',
3056
+ }
3057
+ Object.entries(SHADOW_MAP).forEach(([k, v]) => {
3058
+ const cls = k ? `shadow-${k}` : 'shadow'
3059
+ BP_KEYS.forEach(bp => {
3060
+ reg(`shadow-${bp}${k ? '-' + k : ''}`, 'utilities',
3061
+ mq(bp, `.shadow-${bp}${k ? '-' + k : ''} { box-shadow: ${v}; }`))
3062
+ })
3063
+ })
3064
+
3065
+ // Width
3066
+ Object.entries(SIZE_SCALE).forEach(([k, v]) => {
3067
+ BP_KEYS.forEach(bp => {
3068
+ reg(`w-${bp}-${k}`, 'utilities',
3069
+ mq(bp, `.w-${bp}-${k} { width: ${v}; }`))
3070
+ })
3071
+ })
3072
+
3073
+ // Height
3074
+ Object.entries(SIZE_SCALE).forEach(([k, v]) => {
3075
+ BP_KEYS.forEach(bp => {
3076
+ reg(`h-${bp}-${k}`, 'utilities',
3077
+ mq(bp, `.h-${bp}-${k} { height: ${v}; }`))
3078
+ })
3079
+ })
3080
+
3081
+ // Opacity
3082
+ Object.entries(OPACITY_SCALE).forEach(([k, v]) => {
3083
+ BP_KEYS.forEach(bp => {
3084
+ reg(`opacity-${bp}-${k}`, 'utilities',
3085
+ mq(bp, `.opacity-${bp}-${k} { opacity: ${v}; }`))
3086
+ })
3087
+ })
3088
+
3089
+ // Overflow
3090
+ ;['auto','hidden','visible','scroll'].forEach(v => {
3091
+ BP_KEYS.forEach(bp => {
3092
+ reg(`overflow-${bp}-${v}`, 'utilities',
3093
+ mq(bp, `.overflow-${bp}-${v} { overflow: ${v}; }`))
3094
+ reg(`overflow-x-${bp}-${v}`, 'utilities',
3095
+ mq(bp, `.overflow-x-${bp}-${v} { overflow-x: ${v}; }`))
3096
+ reg(`overflow-y-${bp}-${v}`, 'utilities',
3097
+ mq(bp, `.overflow-y-${bp}-${v} { overflow-y: ${v}; }`))
3098
+ })
3099
+ })
3100
+
3101
+ // Position
3102
+ ;['static','relative','absolute','fixed','sticky'].forEach(v => {
3103
+ BP_KEYS.forEach(bp => {
3104
+ reg(`position-${bp}-${v}`, 'utilities',
3105
+ mq(bp, `.position-${bp}-${v} { position: ${v}; }`))
3106
+ })
3107
+ })
3108
+
3109
+ // Cursor
3110
+ ;['auto','default','pointer','wait','text','move','not-allowed','grab'].forEach(v => {
3111
+ BP_KEYS.forEach(bp => {
3112
+ reg(`cursor-${bp}-${v}`, 'utilities',
3113
+ mq(bp, `.cursor-${bp}-${v} { cursor: ${v}; }`))
3114
+ })
3115
+ })
3116
+
3117
+ // Line height
3118
+ const LH_MAP = { '1':'1', 'sm':'1.25', 'base':'1.5', 'lg':'2' }
3119
+ Object.entries(LH_MAP).forEach(([k, v]) => {
3120
+ BP_KEYS.forEach(bp => {
3121
+ reg(`lh-${bp}-${k}`, 'utilities',
3122
+ mq(bp, `.lh-${bp}-${k} { line-height: ${v}; }`))
3123
+ })
3124
+ })
3125
+
3126
+ // Visibility
3127
+ BP_KEYS.forEach(bp => {
3128
+ reg(`visible-${bp}`, 'utilities',
3129
+ mq(bp, `.visible-${bp} { visibility: visible; }`))
3130
+ reg(`invisible-${bp}`, 'utilities',
3131
+ mq(bp, `.invisible-${bp} { visibility: hidden; }`))
3132
+ })
3133
+
2854
3134
  // ─── Arbitrary value patterns — regex fallback ────────────────────────
2855
3135
  // Only used when no exact match found in EXACT_MAP
2856
3136
 
@@ -2864,10 +3144,15 @@ const ARBITRARY_PATTERNS = [
2864
3144
  const decl = props.map(p => ` ${p}: ${val}${i};`).join('\n')
2865
3145
  return { layer: 'utilities', css: `.${escapeClass(m[0])} {\n${decl}\n}` }
2866
3146
  }},
2867
- // Text color arbitrary: text-[#ff0000]
3147
+ // Text arbitrary: text-[#ff0000] → color, text-[15px] → font-size
3148
+ // Values ending in a CSS length unit are font-size; everything else is color.
2868
3149
  { re: /^(!?)text-\[(.+)\]$/, fn: (m) => {
2869
- const i = m[1] ? ' !important' : ''
2870
- return { layer: 'utilities', css: `.${escapeClass(m[0])} { color: ${m[2]}${i}; }` }
3150
+ const i = m[1] ? ' !important' : ''
3151
+ const val = m[2]
3152
+ const prop = /^[\d.]+(px|rem|em|%|vw|vh|ch|ex|pt|cm|mm)$/.test(val)
3153
+ ? 'font-size'
3154
+ : 'color'
3155
+ return { layer: 'utilities', css: `.${escapeClass(m[0])} { ${prop}: ${val}${i}; }` }
2871
3156
  }},
2872
3157
  // BG arbitrary: bg-[#ff0000]
2873
3158
  { re: /^(!?)bg-\[(.+)\]$/, fn: (m) => {
@@ -2889,6 +3174,26 @@ const ARBITRARY_PATTERNS = [
2889
3174
  const i = m[1] ? ' !important' : ''
2890
3175
  return { layer: 'utilities', css: `.${escapeClass(m[0])} { height: ${m[2]}${i}; }` }
2891
3176
  }},
3177
+ // Max-width arbitrary: max-w-[440px]
3178
+ { re: /^(!?)max-w-\[(.+)\]$/, fn: (m) => {
3179
+ const i = m[1] ? ' !important' : ''
3180
+ return { layer: 'utilities', css: `.${escapeClass(m[0])} { max-width: ${m[2]}${i}; }` }
3181
+ }},
3182
+ // Min-width arbitrary: min-w-[200px]
3183
+ { re: /^(!?)min-w-\[(.+)\]$/, fn: (m) => {
3184
+ const i = m[1] ? ' !important' : ''
3185
+ return { layer: 'utilities', css: `.${escapeClass(m[0])} { min-width: ${m[2]}${i}; }` }
3186
+ }},
3187
+ // Max-height arbitrary: max-h-[500px]
3188
+ { re: /^(!?)max-h-\[(.+)\]$/, fn: (m) => {
3189
+ const i = m[1] ? ' !important' : ''
3190
+ return { layer: 'utilities', css: `.${escapeClass(m[0])} { max-height: ${m[2]}${i}; }` }
3191
+ }},
3192
+ // Min-height arbitrary: min-h-[300px]
3193
+ { re: /^(!?)min-h-\[(.+)\]$/, fn: (m) => {
3194
+ const i = m[1] ? ' !important' : ''
3195
+ return { layer: 'utilities', css: `.${escapeClass(m[0])} { min-height: ${m[2]}${i}; }` }
3196
+ }},
2892
3197
  // Opacity arbitrary: opacity-[0.3]
2893
3198
  { re: /^(!?)opacity-\[(.+)\]$/, fn: (m) => {
2894
3199
  const i = m[1] ? ' !important' : ''