@aziontech/theme 4.2.0 → 4.3.1

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 (55) hide show
  1. package/README.md +110 -335
  2. package/dist/v4/globals.css +249 -72
  3. package/dist/v4/globals.scss +249 -72
  4. package/package.json +4 -13
  5. package/src/scripts/build-tokens.mjs +57 -7
  6. package/src/scripts/compile-primitives.js +9 -5
  7. package/src/scripts/compile-theme.js +12 -6
  8. package/src/scripts/refs.d.ts +1 -0
  9. package/src/scripts/refs.d.ts.map +1 -1
  10. package/src/scripts/refs.js +11 -0
  11. package/src/scripts/zero-unit.mjs +64 -0
  12. package/src/tokens/README.md +44 -29
  13. package/src/tokens/index.d.ts +0 -1
  14. package/src/tokens/index.js +0 -1
  15. package/src/tokens/primitives/animations/animate.d.ts +6 -0
  16. package/src/tokens/primitives/animations/animate.d.ts.map +1 -1
  17. package/src/tokens/primitives/animations/animate.js +14 -3
  18. package/src/tokens/primitives/animations/keyframes.d.ts +13 -1
  19. package/src/tokens/primitives/animations/keyframes.d.ts.map +1 -1
  20. package/src/tokens/primitives/animations/keyframes.js +23 -4
  21. package/src/tokens/primitives/colors/colors.d.ts +4 -0
  22. package/src/tokens/primitives/colors/colors.d.ts.map +1 -1
  23. package/src/tokens/primitives/colors/colors.js +9 -0
  24. package/src/tokens/primitives/shape/container.d.ts.map +1 -1
  25. package/src/tokens/primitives/shape/container.js +30 -13
  26. package/src/tokens/primitives/typography/leading.d.ts +1 -0
  27. package/src/tokens/primitives/typography/leading.js +1 -0
  28. package/src/tokens/primitives/typography/tracking.js +12 -6
  29. package/src/tokens/semantic/colors.js +5 -2
  30. package/src/tokens/semantic/layouts.data.d.ts +65 -0
  31. package/src/tokens/semantic/layouts.data.d.ts.map +1 -0
  32. package/src/tokens/semantic/layouts.data.js +282 -0
  33. package/src/tokens/semantic/texts.data.d.ts +28 -10
  34. package/src/tokens/semantic/texts.data.d.ts.map +1 -1
  35. package/src/tokens/semantic/texts.data.js +50 -30
  36. package/src/tokens/semantic/z-indices.data.d.ts +1 -1
  37. package/src/tokens/semantic/z-indices.data.js +1 -1
  38. package/src/tokens/theme/accent.js +18 -6
  39. package/src/tokens/theme/background.d.ts +12 -0
  40. package/src/tokens/theme/background.js +11 -2
  41. package/src/tokens/theme/border.js +26 -4
  42. package/src/tokens/theme/code-sintax.d.ts +6 -0
  43. package/src/tokens/theme/code-sintax.js +2 -0
  44. package/src/tokens/theme/feedback/info.js +2 -2
  45. package/src/tokens/theme/surfaces.d.ts +12 -0
  46. package/src/tokens/theme/surfaces.d.ts.map +1 -1
  47. package/src/tokens/theme/surfaces.js +4 -0
  48. package/src/tokens/theme/text.js +2 -2
  49. package/src/tokens/theme.d.ts +4 -0
  50. package/src/scripts/css-vars.d.ts +0 -7
  51. package/src/scripts/css-vars.d.ts.map +0 -1
  52. package/src/scripts/css-vars.js +0 -46
  53. package/src/scripts/resolve.d.ts +0 -5
  54. package/src/scripts/resolve.d.ts.map +0 -1
  55. package/src/scripts/resolve.js +0 -107
@@ -14,6 +14,10 @@
14
14
  * typography, breakpoints, animations, …)
15
15
  * - `tokens/semantic/{containers,spacings,texts}.data.js`
16
16
  * responsive-shaped semantic tokens
17
+ * - `tokens/semantic/layouts.data.js`
18
+ * layout tokens (all derived from --spacing-* /
19
+ * --container-*) + the `@utility` blocks that
20
+ * build the container system on them
17
21
  * - `tokens/primitives/animations/{animate,keyframes}.js`
18
22
  * animation utilities (`--animate-*`) + `@keyframes`
19
23
  * definitions + extra CSS (transform-origin) for
@@ -26,25 +30,25 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises';
26
30
  import { dirname, resolve } from 'node:path';
27
31
  import { fileURLToPath } from 'node:url';
28
32
 
29
- import { animate } from '../tokens/primitives/animations/animate.js';
33
+ import { animate, curve, duration } from '../tokens/primitives/animations/animate.js';
30
34
  import { animateExtras, keyframes } from '../tokens/primitives/animations/keyframes.js';
31
35
  import { breakpoints } from '../tokens/primitives/breakpoints.js';
32
- import { buildTrees, flatten } from './compile-primitives.js';
36
+ import { compilePrimitivesVars } from './compile-primitives.js';
33
37
  import { compileThemeCss, compileThemeVars } from './compile-theme.js';
34
38
  import { containersData } from '../tokens/semantic/containers.data.js';
39
+ import { layoutsData, layoutsUtilities } from '../tokens/semantic/layouts.data.js';
35
40
  import { spacingsData } from '../tokens/semantic/spacings.data.js';
36
41
  import { textsData } from '../tokens/semantic/texts.data.js';
37
42
  import { zIndicesData } from '../tokens/semantic/z-indices.data.js';
43
+ import { assertNoZeroWithUnit } from './zero-unit.mjs';
38
44
 
39
45
  const BREAKPOINT_ORDER = ['sm', 'md', 'lg', 'xl', '2xl'];
40
46
 
41
47
  const kebab = (s) => s.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
42
48
 
43
49
  // ─── 1. Primitives ──────────────────────────────────────────────────────────
44
- const flattenPrimitives = () => {
45
- const { refsTree, varsTree } = buildTrees();
46
- return flatten(varsTree, refsTree);
47
- };
50
+ // compilePrimitivesVars throws on any unresolved tokenRef (see refs.js).
51
+ const flattenPrimitives = () => compilePrimitivesVars();
48
52
 
49
53
  // ─── 2. Semantic flattening ─────────────────────────────────────────────────
50
54
  const splitResponsive = (value) => {
@@ -93,6 +97,7 @@ const buildFlatModel = () => ({
93
97
  containers: flattenSingleValue(containersData, (k) => `--container-${k}`),
94
98
  spacings: flattenSingleValue(spacingsData, (k) => `--${k}`),
95
99
  zIndices: flattenSingleValue(zIndicesData, (k) => `--${k}`),
100
+ layouts: flattenSingleValue(layoutsData, (k) => `--${k}`),
96
101
  texts: flattenBundle(textsData),
97
102
  });
98
103
 
@@ -235,6 +240,33 @@ const emitSemanticColorUtilities = () => {
235
240
  * interactive element loses the hand cursor unless a component overrides it.
236
241
  * Disabled controls keep the default cursor.
237
242
  */
243
+ /**
244
+ * Emit a map of `@utility` blocks. A string value is a plain declaration; an object
245
+ * value is a nested rule keyed by a literal selector (`&:first-child { … }`), the way
246
+ * the typography utilities nest their `&:hover`.
247
+ */
248
+ const emitUtilities = (utilities) =>
249
+ Object.entries(utilities)
250
+ .map(([name, decls]) => {
251
+ const body = Object.entries(decls)
252
+ .map(([prop, value]) =>
253
+ typeof value === 'object'
254
+ ? ` ${prop} { ${formatStateDecls(value)}; }`
255
+ : ` ${prop}: ${value};`,
256
+ )
257
+ .join('\n');
258
+ return `@utility ${name} {\n${body}\n}`;
259
+ })
260
+ .join('\n\n');
261
+
262
+ /**
263
+ * The container system as `@utility` blocks — same reason as the typography
264
+ * utilities: `@layer components` classes are opaque to the variant resolver, and a
265
+ * layout class is exactly the kind a consumer wants behind a variant
266
+ * (`md:layout-column`).
267
+ */
268
+ const emitLayoutUtilities = () => emitUtilities(layoutsUtilities);
269
+
238
270
  const emitBaseLayer = () =>
239
271
  [
240
272
  '@layer base {',
@@ -293,11 +325,25 @@ const emitCssV4 = () => {
293
325
  themeVars[`--animate-${name}`] = shorthand;
294
326
  }
295
327
 
328
+ // Named `duration-*` / `ease-*` utilities. Without these the design system's
329
+ // own `duration-moderate-02` / `ease-productive-entrance` classes compile to
330
+ // NOTHING (Tailwind v4 silently drops an unknown named step), and every
331
+ // component that declares one falls back to Tailwind's default 150ms `ease`.
332
+ // The namespaces are fixed by Tailwind: `--transition-duration-*` backs
333
+ // `duration-*`, `--ease-*` backs `ease-*`.
334
+ for (const [name, value] of Object.entries(duration)) {
335
+ themeVars[`--transition-duration-${name}`] = value;
336
+ }
337
+ for (const [name, value] of Object.entries(curve)) {
338
+ themeVars[`--ease-${name}`] = value;
339
+ }
340
+
296
341
  const semanticBase = {
297
342
  ...rootPrimitiveVars,
298
343
  ...(m.containers._ || {}),
299
344
  ...(m.spacings._ || {}),
300
345
  ...(m.zIndices._ || {}),
346
+ ...(m.layouts._ || {}),
301
347
  ...(m.texts._ || {}),
302
348
  };
303
349
 
@@ -307,6 +353,7 @@ const emitCssV4 = () => {
307
353
  ...(m.containers[bp] || {}),
308
354
  ...(m.spacings[bp] || {}),
309
355
  ...(m.zIndices[bp] || {}),
356
+ ...(m.layouts[bp] || {}),
310
357
  ...(m.texts[bp] || {}),
311
358
  };
312
359
  if (Object.keys(merged).length === 0) continue;
@@ -325,7 +372,7 @@ const emitCssV4 = () => {
325
372
  '@import "tailwindcss";',
326
373
  '',
327
374
  '/*',
328
- ' * `@aziontech/webkit` components use arbitrary values (`bg-[var(--x)]`) that',
375
+ ' * `@aziontech/webkit` components use arbitrary values (`bg-(--x)`) that',
329
376
  ' * Tailwind v4 only picks up when the source files are inside the scan.',
330
377
  ' * Auto-detect scans the CWD only, so we point `@source` at the webkit',
331
378
  ' * package via a monorepo-relative path (this file is under',
@@ -354,6 +401,8 @@ const emitCssV4 = () => {
354
401
  '',
355
402
  emitTextUtilities(),
356
403
  '',
404
+ emitLayoutUtilities(),
405
+ '',
357
406
  emitSemanticColorUtilities(),
358
407
  '',
359
408
  emitBaseLayer(),
@@ -382,6 +431,7 @@ const importIdx = rawCss.indexOf(IMPORT_LINE);
382
431
  if (importIdx === -1) throw new Error('emitCssV4 output is missing the tailwind import line');
383
432
  const afterImport = importIdx + IMPORT_LINE.length;
384
433
  const css = `${rawCss.slice(0, afterImport)}\n\n${fontsCss}${rawCss.slice(afterImport)}`;
434
+ assertNoZeroWithUnit(css, 'globals.css');
385
435
  await writeFile(resolve(dir, 'globals.css'), css, 'utf8');
386
436
  await writeFile(resolve(dir, 'globals.scss'), css, 'utf8');
387
437
  console.log(`✓ v4 → ${dir}`);
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Imports every primitive module (colors, shape, typography, effects,
5
5
  * breakpoints, border-widths, ring-offset), resolves `tokenRef(...)` aliases,
6
- * and produces a `:root { ... }` block. Mirrors the `injectCssVars` helper
6
+ * and produces a `:root { ... }` block. Mirrors `injectThemeCss` (compile-theme.js)
7
7
  * but for primitives only — no semantic light/dark split.
8
8
  */
9
9
 
@@ -33,7 +33,7 @@ import { fontWeight } from '../tokens/primitives/typography/font-weight.js'
33
33
  import { leading } from '../tokens/primitives/typography/leading.js'
34
34
  import { lineHeight } from '../tokens/primitives/typography/line-height.js'
35
35
  import { tracking } from '../tokens/primitives/typography/tracking.js'
36
- import { isTokenRef } from './refs.js'
36
+ import { assertResolvedRefs, isTokenRef } from './refs.js'
37
37
 
38
38
  /**
39
39
  * Returns two trees:
@@ -123,17 +123,18 @@ const varNameFor = (path) => {
123
123
  return `--${segments.join('-')}`
124
124
  }
125
125
 
126
- export const flatten = (obj, refsTree, prefix = []) => {
126
+ export const flatten = (obj, refsTree, prefix = [], unresolved = null) => {
127
127
  const result = {}
128
128
  Object.entries(obj).forEach(([key, value]) => {
129
129
  const nextPath = [...prefix, key]
130
130
  if (isTokenRef(value)) {
131
131
  const resolved = resolveRef(value.__ref, refsTree)
132
+ if (resolved == null) unresolved?.push(`${varNameFor(nextPath)} → ${value.__ref}`)
132
133
  result[varNameFor(nextPath)] = resolved ?? value.__ref
133
134
  return
134
135
  }
135
136
  if (value && typeof value === 'object' && !Array.isArray(value)) {
136
- Object.assign(result, flatten(value, refsTree, nextPath))
137
+ Object.assign(result, flatten(value, refsTree, nextPath, unresolved))
137
138
  return
138
139
  }
139
140
  if (typeof value === 'string' || typeof value === 'number') {
@@ -145,7 +146,10 @@ export const flatten = (obj, refsTree, prefix = []) => {
145
146
 
146
147
  export const compilePrimitivesVars = () => {
147
148
  const { refsTree, varsTree } = buildTrees()
148
- return flatten(varsTree, refsTree)
149
+ const unresolved = []
150
+ const vars = flatten(varsTree, refsTree, [], unresolved)
151
+ assertResolvedRefs('primitives', unresolved)
152
+ return vars
149
153
  }
150
154
 
151
155
  export const compilePrimitivesCss = () => {
@@ -24,7 +24,7 @@ import { ring } from '../tokens/theme/ring.js'
24
24
  import { secondary } from '../tokens/theme/secondary.js'
25
25
  import { surfaces } from '../tokens/theme/surfaces.js'
26
26
  import { text } from '../tokens/theme/text.js'
27
- import { isTokenRef } from './refs.js'
27
+ import { assertResolvedRefs, isTokenRef } from './refs.js'
28
28
 
29
29
  const VARIANTS = ['light', 'dark']
30
30
 
@@ -58,23 +58,25 @@ const resolveRef = (ref, surfacesResolved) => {
58
58
  return null
59
59
  }
60
60
 
61
- const resolveGroup = (group, surfacesResolved) => {
61
+ const resolveGroup = (group, surfacesResolved, variant, unresolved) => {
62
62
  const result = {}
63
63
  Object.entries(group).forEach(([key, value]) => {
64
64
  if (!isTokenRef(value)) return
65
65
  const resolved = resolveRef(value.__ref, surfacesResolved)
66
66
  if (resolved != null) result[`--${key}`] = String(resolved)
67
+ else unresolved.push(`[${variant}] --${key} → ${value.__ref}`)
67
68
  })
68
69
  return result
69
70
  }
70
71
 
71
- const compileVariant = (variant) => {
72
+ const compileVariant = (variant, unresolved) => {
72
73
  // Surfaces resolved first — other groups depend on them.
73
74
  const surfacesResolved = {}
74
75
  Object.entries(surfaces[variant]).forEach(([key, value]) => {
75
76
  if (!isTokenRef(value)) return
76
77
  const v = resolveRef(value.__ref, {})
77
78
  if (v != null) surfacesResolved[key] = String(v)
79
+ else unresolved.push(`[${variant}] --${key} → ${value.__ref}`)
78
80
  })
79
81
 
80
82
  const vars = {}
@@ -96,12 +98,16 @@ const compileVariant = (variant) => {
96
98
  danger[variant],
97
99
  info[variant]
98
100
  ]
99
- groups.forEach((g) => Object.assign(vars, resolveGroup(g, surfacesResolved)))
101
+ groups.forEach((g) => Object.assign(vars, resolveGroup(g, surfacesResolved, variant, unresolved)))
100
102
  return vars
101
103
  }
102
104
 
103
- export const compileThemeVars = () =>
104
- Object.fromEntries(VARIANTS.map((v) => [v, compileVariant(v)]))
105
+ export const compileThemeVars = () => {
106
+ const unresolved = []
107
+ const vars = Object.fromEntries(VARIANTS.map((v) => [v, compileVariant(v, unresolved)]))
108
+ assertResolvedRefs('semantic theme tokens', unresolved)
109
+ return vars
110
+ }
105
111
 
106
112
  export const compileThemeCss = () => {
107
113
  const { light, dark } = compileThemeVars()
@@ -2,4 +2,5 @@ export function tokenRef(path: any): {
2
2
  __ref: any;
3
3
  };
4
4
  export function isTokenRef(value: any): boolean;
5
+ export function assertResolvedRefs(context: any, unresolved: any): void;
5
6
  //# sourceMappingURL=refs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"refs.d.ts","sourceRoot":"","sources":["refs.js"],"names":[],"mappings":"AAIO;;EAA4C;AAE5C,gDAA6F"}
1
+ {"version":3,"file":"refs.d.ts","sourceRoot":"","sources":["refs.js"],"names":[],"mappings":"AAIO;;EAA4C;AAE5C,gDAA6F;AAO7F,wEAIN"}
@@ -5,3 +5,14 @@
5
5
  export const tokenRef = (path) => ({ __ref: path })
6
6
 
7
7
  export const isTokenRef = (value) => typeof value === 'object' && value !== null && '__ref' in value
8
+
9
+ /**
10
+ * Fail loudly on unresolved refs. A typo'd token must never silently vanish
11
+ * from the compiled CSS or ship as a raw `path.to.token` string — the compilers
12
+ * collect every miss and this throws once with the full list.
13
+ */
14
+ export const assertResolvedRefs = (context, unresolved) => {
15
+ if (unresolved.length === 0) return
16
+ const lines = unresolved.map((entry) => ` ${entry}`).join('\n')
17
+ throw new Error(`[theme] ${unresolved.length} unresolved tokenRef(s) in ${context}:\n${lines}`)
18
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Zero-with-unit gate for the compiled token stylesheet — the only check that sees token
3
+ * values (authored in JS, so no linter does), run on the final CSS before globals.css is
4
+ * written. A zero length is `0`; inside calc()/min()/max()/clamp() it must carry a unit, and
5
+ * that unit is `rem` (.claude/rules/styling.md). Mirror of the zero checks in packages/webkit
6
+ * token-checks.js, kept in sync — LENGTH_UNITS is the same string. Pure: node built-ins only.
7
+ */
8
+
9
+ // Opens a math function; balances parens one level deep (a zero nested deeper falls back to
10
+ // the bare-zero check).
11
+ const MATH_FN = '(?:calc|min|max|clamp)\\((?:[^()]|\\([^()]*\\))*';
12
+
13
+ // Complete CSS length-unit set — must be complete, since a missing unit silently passes.
14
+ export const LENGTH_UNITS =
15
+ 'px|rem|em|ex|rex|ch|rch|cap|rcap|ic|ric|lh|rlh|vw|vh|vi|vb|vmin|vmax|svw|svh|svi|svb|svmin|svmax|lvw|lvh|lvi|lvb|lvmin|lvmax|dvw|dvh|dvi|dvb|dvmin|dvmax|cqw|cqh|cqi|cqb|cqmin|cqmax|cm|mm|Q|in|pt|pc';
16
+
17
+ // Mutually exclusive, so a value is reported once. Non-global so `.test()` is stateless —
18
+ // findZeroMisuse adds `g` locally to enumerate matches.
19
+ export const ZERO_WITH_UNIT = new RegExp(`(?<![\\w.])(?<!${MATH_FN})0(?:${LENGTH_UNITS})(?![\\w%])`, 'i');
20
+ export const ZERO_UNIT_IN_MATH = new RegExp(
21
+ `(?<=${MATH_FN})(?<![\\w.])0(?!rem)(?:${LENGTH_UNITS})(?![\\w%])`,
22
+ 'i',
23
+ );
24
+
25
+ /**
26
+ * Matches of `regex` in `cssText` as `{ line, n }`. Scans the whole string, not line-by-line,
27
+ * so a calc() wrapped across newlines is still seen as in-math.
28
+ */
29
+ export const findZeroMisuse = (cssText, regex) => {
30
+ const scan = new RegExp(regex.source, `${regex.flags.replace('g', '')}g`);
31
+ const out = [];
32
+ for (let m; (m = scan.exec(cssText)); ) {
33
+ const lineStart = cssText.lastIndexOf('\n', m.index - 1) + 1;
34
+ const nlIdx = cssText.indexOf('\n', m.index);
35
+ const lineEnd = nlIdx === -1 ? cssText.length : nlIdx;
36
+ out.push({ line: cssText.slice(lineStart, lineEnd).trim(), n: cssText.slice(0, m.index).split('\n').length });
37
+ }
38
+ return out;
39
+ };
40
+
41
+ // The `--custom-property` a line declares, to name the token to fix; null off a `--token:` line.
42
+ const tokenOf = (line) => line.match(/(--[\w-]+)\s*:/)?.[1] ?? null;
43
+
44
+ /** Throw if `cssText` misuses a zero length. `source` names the artifact in the message. */
45
+ export const assertNoZeroWithUnit = (cssText, source = 'globals.css') => {
46
+ const bare = findZeroMisuse(cssText, ZERO_WITH_UNIT);
47
+ const inMath = findZeroMisuse(cssText, ZERO_UNIT_IN_MATH);
48
+ if (bare.length === 0 && inMath.length === 0) return;
49
+
50
+ const detail = (rows) => rows.map(({ line, n }) => ` ${source}:${n} ${line}`).join('\n');
51
+ const tokens = [...new Set([...bare, ...inMath].map(({ line }) => tokenOf(line)).filter(Boolean))];
52
+ const parts = [`build:tokens — ${bare.length + inMath.length} token value(s) misuse a zero.`];
53
+ if (bare.length > 0) {
54
+ parts.push(`A zero length takes no unit: write '0', not '0px' / '0rem' / '0em'.\n${detail(bare)}`);
55
+ }
56
+ if (inMath.length > 0) {
57
+ parts.push(
58
+ `Inside calc()/min()/max()/clamp() the zero needs a unit, and that unit is rem: write '0rem'.\n${detail(inMath)}`,
59
+ );
60
+ }
61
+ const grepHint = tokens.length > 0 ? ` — grep the offending token name (${tokens.join(', ')})` : '';
62
+ parts.push(`Fix the token at its source under src/tokens/**${grepHint}, not the generated ${source}.`);
63
+ throw new Error(parts.join('\n'));
64
+ };
@@ -39,8 +39,6 @@ tokens/theme/** ─┤ dist/v4/
39
39
  │
40
40
  scripts/{compile-primitives, ─┘
41
41
  compile-theme,
42
- resolve,
43
- css-vars,
44
42
  refs}.js (building blocks used by build-tokens.mjs)
45
43
  ```
46
44
 
@@ -115,17 +113,11 @@ src/
115
113
  │ │ └── animations.js
116
114
  │ ├── theme.js # Tailwind theme.extend (colors + semantic mappings)
117
115
  │ └── index.js # public re-exports
118
- ├── scripts/
119
- │ ├── refs.js # tokenRef helper + isTokenRef guard
120
- │ ├── resolve.js # resolves `tokenRef` paths to literal values
121
- │ ├── css-vars.js # builds light/dark CSS var maps from semantic refs
122
- │ ├── compile-primitives.js # flattens primitive trees into CSS vars
123
- │ ├── compile-theme.js # legacy theme compiler (used by the harness pages)
124
- │ └── build-tokens.mjs # main entrypoint: emits dist/v3 and dist/v4 bundles
125
- └── tests/
126
- ├── primitives.html # visual harness for all primitives
127
- ├── theme.html # semantic tokens with light/dark toggle
128
- └── tokens.html # combined view
116
+ └── scripts/
117
+ ├── refs.js # tokenRef helper + isTokenRef guard + assertResolvedRefs
118
+ ├── compile-primitives.js # flattens primitive trees into CSS vars
119
+ ├── compile-theme.js # semantic light/dark compiler (exported as ./theme-colors)
120
+ └── build-tokens.mjs # main entrypoint: emits the dist/v4 bundle
129
121
  ```
130
122
 
131
123
  ---
@@ -187,7 +179,31 @@ export const spacingsData = {
187
179
  }
188
180
  ```
189
181
 
190
- `_` is the base value emitted in `:root`; the breakpoint keys (`sm`, `md`, `lg`, `xl`, `2xl`) become media-query overrides.
182
+ `_` is the base value emitted in `:root`; the breakpoint keys (`sm`, `md`, `lg`, `xl`, `2xl`) become media-query overrides. A bare string is accepted as shorthand for `{ _: value }`.
183
+
184
+ ### Derived groups (`layouts.data.js`)
185
+
186
+ `semantic/layouts` is a **derived** group: every value is a `var()` reference to `--spacing-*` /
187
+ `--container-*`, never a literal length.
188
+
189
+ ```js
190
+ // tokens/semantic/layouts.data.js
191
+ export const layoutsData = {
192
+ 'layout-boundary-inline': 'var(--spacing-lg)',
193
+ 'layout-measure': 'var(--container-7xl)'
194
+ // …
195
+ }
196
+ ```
197
+
198
+ That indirection is the whole point, and it is why these carry no breakpoint map of their own: the
199
+ spacing scale is already responsive (`--spacing-lg` is `1rem`, then `1.5rem` from `sm`), and a `var()`
200
+ is substituted at **use** time on the element — so a layout token follows the breakpoint override for
201
+ free. Giving it its own map would duplicate the spacing scale into layout and let the two drift.
202
+
203
+ The group also ships `layoutsUtilities`, the container-system classes (`.layout-column`,
204
+ `.layout-boundary`, …) emitted as Tailwind v4 `@utility` blocks so they support variants
205
+ (`md:layout-column`). Utilities are shaped exactly like `illustrationsUtilities`: a string value is a
206
+ declaration, an object value is a nested rule keyed by a literal selector (`'&:first-child'`).
191
207
 
192
208
  ---
193
209
 
@@ -216,18 +232,18 @@ Utilities like `text-default`, `bg-surface`, `border-default`, `text-primary`, `
216
232
  ### As JS objects (Node / build steps)
217
233
 
218
234
  ```js
219
- import { createCssVars, cssVarsString } from '@aziontech/theme'
235
+ import { compileThemeVars, compileThemeCss } from '@aziontech/theme/theme-colors'
220
236
 
221
- createCssVars() // → { light: { '--text-default': '#1A1A1A', … }, dark: { … } }
222
- cssVarsString() // → ':root, [data-theme=light], .azion.azion-light { … } [data-theme=dark], … { … }'
237
+ compileThemeVars() // → { light: { '--primary': '…', … }, dark: { … } }
238
+ compileThemeCss() // → ':root, [data-theme=light], .azion.azion-light { … }\n\n[data-theme=dark], … { … }'
223
239
  ```
224
240
 
225
241
  ### Injecting at runtime (browser)
226
242
 
227
243
  ```js
228
- import { injectCssVars } from '@aziontech/theme'
244
+ import { injectThemeCss } from '@aziontech/theme/theme-colors'
229
245
 
230
- injectCssVars() // appends a <style data-azion-tokens> element to <head>
246
+ injectThemeCss() // appends a <style data-azion-theme> element to <head>
231
247
  ```
232
248
 
233
249
  Only useful if you can't precompile and import `globals.css` — production usage should prefer the static stylesheet.
@@ -306,18 +322,17 @@ Bundles with `fontSize: 'inherit'` are omitted from the Tailwind `fontSize` pres
306
322
 
307
323
  ## 🔗 Token references (`tokenRef`)
308
324
 
309
- `tokenRef(path)` returns a marker object `{ __ref: path }` that the compiler resolves at build time. Supported path prefixes (see `scripts/resolve.js`):
325
+ `tokenRef(path)` returns a marker object `{ __ref: path }` that the compilers resolve at build time. Primitive-tree refs (`scripts/compile-primitives.js`) resolve against the full primitives tree (e.g. `shape.container.3xs`). Semantic theme refs (`scripts/compile-theme.js`) support these prefixes:
310
326
 
311
- | Prefix | Looks up |
312
- | ----------------------------- | ------------------------------------------------------------------------------------------------------ |
313
- | `primitives.X.Y.Z` | `tokens/primitives/colors/colors.js` tree (e.g., `primitives.gray.900`, `primitives.alpha.orange.100`) |
314
- | `surfacePrimitives.surface.N` | the surface palette (used internally; usually referenced via `brand.surfaces.…`) |
315
- | `brand.surfaces.surface-N` | `surfacePrimitives.surface[N]` (e.g., `brand.surfaces.surface-100` → `#F5F5F5`) |
316
- | `brand.primary.primary-N` | `brandPrimitives.primary[N]` (e.g., `brand.primary.primary-500` → `#FE601F`) |
317
- | `brand.accent.accent-N` | `brandPrimitives.accent[N]` |
318
- | `brand.absolute.X` | `brandPrimitives.absolute.X` (`black` / `white`) |
327
+ | Prefix | Looks up |
328
+ | ------------------------- | ------------------------------------------------------------------------------------------------------ |
329
+ | `primitives.X.Y.Z` | `tokens/primitives/colors/colors.js` tree (e.g., `primitives.gray.900`, `primitives.alpha.orange.100`) |
330
+ | `brand.primary.primary-N` | `brandPrimitives.primary[N]` (e.g., `brand.primary.primary-500` → `#FE601F`) |
331
+ | `brand.accent.accent-N` | `brandPrimitives.accent[N]` |
332
+ | `brand.absolute.X` | `brandPrimitives.absolute.X` (`black` / `white`) |
333
+ | `theme.surfaces.X` | the variant's pre-resolved surfaces group (`tokens/theme/surfaces.js`) |
319
334
 
320
- Refs of unknown prefixes are left as the raw path string in the output — flag for "this needs resolver support".
335
+ A ref that does not resolve — unknown prefix or dead path — **fails the build**: the compilers collect every miss and throw one aggregated `--var → ref` list (`assertResolvedRefs` in `scripts/refs.js`).
321
336
 
322
337
  ---
323
338
 
@@ -2,5 +2,4 @@ export * from "./primitives/colors/colors.js";
2
2
  export * from "./semantic/colors.js";
3
3
  export * from "./theme.js";
4
4
  export { textsData } from "./semantic/texts.data.js";
5
- export { createCssVars, cssVarsString, injectCssVars } from "../scripts/css-vars.js";
6
5
  //# sourceMappingURL=index.d.ts.map
@@ -2,7 +2,6 @@
2
2
  * Tokens public exports.
3
3
  */
4
4
 
5
- export { createCssVars, cssVarsString, injectCssVars } from '../scripts/css-vars.js'
6
5
  export * from './primitives/colors/colors.js'
7
6
  export * from './semantic/colors.js'
8
7
  export { textsData } from './semantic/texts.data.js'
@@ -25,6 +25,8 @@ export const animate: {
25
25
  'fade-out': string;
26
26
  'slide-down': string;
27
27
  'highlight-fade': string;
28
+ 'page-enter': string;
29
+ 'content-enter': string;
28
30
  'popup-scale-in': string;
29
31
  'popup-scale-out': string;
30
32
  'slide-in-left': string;
@@ -33,8 +35,11 @@ export const animate: {
33
35
  'slide-out-right': string;
34
36
  'progress-indeterminate': string;
35
37
  'progress-indeterminate-short': string;
38
+ 'flow-dash': string;
36
39
  };
37
40
  export const useWhen: {
41
+ 'page-enter': string;
42
+ 'content-enter': string;
38
43
  spin: string;
39
44
  ping: string;
40
45
  pulse: string;
@@ -53,6 +58,7 @@ export const useWhen: {
53
58
  'slide-out-right': string;
54
59
  'progress-indeterminate': string;
55
60
  'progress-indeterminate-short': string;
61
+ 'flow-dash': string;
56
62
  };
57
63
  declare namespace _default {
58
64
  export { animate };
@@ -1 +1 @@
1
- {"version":3,"file":"animate.d.ts","sourceRoot":"","sources":["animate.js"],"names":[],"mappings":"AAAA;;;;;EAKC;AAED;;;;;;;;;EASC;AAED;;;;;;;;;;;;;;;;;;;EAmBC;AAED;;;;;;;;;;;;;;;;;;;EAsBC"}
1
+ {"version":3,"file":"animate.d.ts","sourceRoot":"","sources":["animate.js"],"names":[],"mappings":"AAAA;;;;;EAKC;AAED;;;;;;;;;EASC;AAED;;;;;;;;;;;;;;;;;;;;;;EAwBC;AAED;;;;;;;;;;;;;;;;;;;;;;EA4BC"}
@@ -26,7 +26,9 @@ export const animate = {
26
26
  'fade-in': 'fadeIn 220ms ease-in-out',
27
27
  'fade-out': 'fadeOut 220ms ease-in-out',
28
28
  'slide-down': 'slideDown 220ms ease-in-out',
29
- 'highlight-fade': 'highlight ease-in forwards',
29
+ 'highlight-fade': `highlight ${duration['slow-03']} ease-in forwards`,
30
+ 'page-enter': `pageEnter ${duration['moderate-02']} ${curve['productive-entrance']}`,
31
+ 'content-enter': `contentEnter ${duration['moderate-02']} ${curve['productive-entrance']} var(--content-enter-delay, 0s) backwards`,
30
32
  'popup-scale-in': `popupScaleIn ${duration['moderate-01']} ${curve['productive-entrance']}`,
31
33
  'popup-scale-out': `popupScaleOut ${duration['fast-02']} ${curve['productive-exit']}`,
32
34
  'slide-in-left': `slideInLeft ${duration['moderate-02']} ${curve['productive-entrance']}`,
@@ -34,10 +36,17 @@ export const animate = {
34
36
  'slide-in-right': `slideInRight ${duration['moderate-02']} ${curve['productive-entrance']}`,
35
37
  'slide-out-right': `slideOutRight ${duration['moderate-01']} ${curve['productive-exit']}`,
36
38
  'progress-indeterminate': `progressIndeterminate ${duration['slow-04']} ${curve['productive-entrance']} infinite`,
37
- 'progress-indeterminate-short': `progressIndeterminateShort ${duration['slow-04']} ${curve['expressive-entrance']} ${duration['slow-03']} infinite`
39
+ 'progress-indeterminate-short': `progressIndeterminateShort ${duration['slow-04']} ${curve['expressive-entrance']} ${duration['slow-03']} infinite`,
40
+ // `linear` (not a curve token) for the same reason as spin/shimmer: an endlessly
41
+ // looping animation must not accelerate, or the seam between repeats is visible.
42
+ 'flow-dash': `flowDash ${duration['slow-02']} linear infinite`
38
43
  }
39
44
 
40
45
  export const useWhen = {
46
+ 'page-enter':
47
+ 'A page arriving on a route change — on the CONTENT ZONE only, never the shell. The chrome is the same before and after, and sliding it announces a reload that did not happen. Key it on the route path so a component serving several paths still replays it. Nothing inside the page may animate on mount at the same time: the two run in lockstep and read as one element travelling on a diagonal.',
48
+ 'content-enter':
49
+ 'Content settling INSIDE a page that is already on screen — a loading window resolving, a filtered list swapping, a step changing. Never on first paint of a page whose own entrance is already running (use it after that entrance, or after a wire). Stagger a follower with --content-enter-delay.',
41
50
  spin: 'Indeterminate circular spinners (loading icons).',
42
51
  ping: 'One-off attention ring radiating from a small element (notification dot).',
43
52
  pulse: 'Skeleton/placeholder opacity pulse while content loads.',
@@ -58,7 +67,9 @@ export const useWhen = {
58
67
  'slide-in-right': 'Right-anchored panel entering (settings/detail drawer).',
59
68
  'slide-out-right': 'Right-anchored panel leaving (pair of slide-in-right).',
60
69
  'progress-indeterminate': 'Indeterminate linear progress bar (primary sweep).',
61
- 'progress-indeterminate-short': 'Indeterminate linear progress bar (secondary short sweep).'
70
+ 'progress-indeterminate-short': 'Indeterminate linear progress bar (secondary short sweep).',
71
+ 'flow-dash':
72
+ 'Flowing connection along an SVG connector stroke in a node-based / network diagram. Set a stroke-dasharray whose cycle divides 24 (e.g. 4 4) so the loop is seamless.'
62
73
  }
63
74
 
64
75
  export default { animate, curve, duration, useWhen }
@@ -16,13 +16,21 @@ export namespace keyframes {
16
16
  '50%': string;
17
17
  };
18
18
  let highlight: {
19
- '0%': string;
19
+ '0%, 40%': string;
20
20
  '100%': string;
21
21
  };
22
22
  let shimmer: {
23
23
  '0%': string;
24
24
  '100%': string;
25
25
  };
26
+ let pageEnter: {
27
+ '0%': string;
28
+ '100%': string;
29
+ };
30
+ let contentEnter: {
31
+ '0%': string;
32
+ '100%': string;
33
+ };
26
34
  let popupScaleIn: {
27
35
  '0%': string;
28
36
  '100%': string;
@@ -57,6 +65,10 @@ export namespace keyframes {
57
65
  '60%': string;
58
66
  '100%': string;
59
67
  };
68
+ let flowDash: {
69
+ '0%': string;
70
+ '100%': string;
71
+ };
60
72
  }
61
73
  /**
62
74
  * Utilities that need CSS properties beyond the `animation` shorthand
@@ -1 +1 @@
1
- {"version":3,"file":"keyframes.d.ts","sourceRoot":"","sources":["keyframes.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0EA;;;;GAIG;AACH;;;;;;;EAGC"}
1
+ {"version":3,"file":"keyframes.d.ts","sourceRoot":"","sources":["keyframes.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6FA;;;;GAIG;AACH;;;;;;;EAGC"}
@@ -28,21 +28,31 @@ export const keyframes = {
28
28
  '0%, 100%': 'opacity: 1',
29
29
  '50%': 'opacity: 0'
30
30
  },
31
+ // Holds the highlight for the first 40% of the run, then fades it back to
32
+ // normal over the remaining 60% — a row-flash, not a permanent tint.
31
33
  highlight: {
32
- '0%': 'background-color: var(--surface-hover); font-weight: 500',
33
- '100%': 'background-color: var(--surface-hover); font-weight: 500'
34
+ '0%, 40%': 'background-color: var(--surface-hover); font-weight: 500',
35
+ '100%': 'background-color: transparent; font-weight: 400'
34
36
  },
35
37
  shimmer: {
36
38
  '0%': 'background-position: 200% 0',
37
39
  '100%': 'background-position: -200% 0'
38
40
  },
41
+ pageEnter: {
42
+ '0%': 'opacity: 0; translate: calc(var(--page-enter-distance, var(--layout-boundary-inline)) * -1) 0',
43
+ '100%': 'opacity: 1; translate: 0 0'
44
+ },
45
+ contentEnter: {
46
+ '0%': 'opacity: 0; translate: 0 var(--spacing-xs)',
47
+ '100%': 'opacity: 1; translate: 0 0'
48
+ },
39
49
  popupScaleIn: {
40
- '0%': 'opacity: 0; transform: scale(0.95)',
50
+ '0%': 'opacity: 0; transform: scale(0.9)',
41
51
  '100%': 'opacity: 1; transform: scale(1)'
42
52
  },
43
53
  popupScaleOut: {
44
54
  '0%': 'opacity: 1; transform: scale(1)',
45
- '100%': 'opacity: 0; transform: scale(0.95)'
55
+ '100%': 'opacity: 0; transform: scale(0.9)'
46
56
  },
47
57
  slideInLeft: {
48
58
  '0%': 'transform: translateX(-100%)',
@@ -69,6 +79,15 @@ export const keyframes = {
69
79
  '0%': 'inset-inline-start: -200%; inset-inline-end: 100%',
70
80
  '60%': 'inset-inline-start: 107%; inset-inline-end: -8%',
71
81
  '100%': 'inset-inline-start: 107%; inset-inline-end: -8%'
82
+ },
83
+ // Marching dashes along an SVG stroke — the "flowing connection" of a node-based
84
+ // diagram. The travel distance is 24, so the loop is seamless for any
85
+ // `stroke-dasharray` whose cycle DIVIDES 24 (`4 4` = 8, `2 4` = 6, `8 4` = 12): the
86
+ // pattern lands exactly where it started. A cycle that does not divide 24 (`5 5` = 10)
87
+ // visibly jumps on repeat.
88
+ flowDash: {
89
+ '0%': 'stroke-dashoffset: 24',
90
+ '100%': 'stroke-dashoffset: 0'
72
91
  }
73
92
  }
74
93