@tenphi/tasty 0.0.0-snapshot.06c8a41 → 0.0.0-snapshot.07ea4a0

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 (132) hide show
  1. package/README.md +408 -385
  2. package/dist/babel-types-BKDTT4--.d.ts +18 -0
  3. package/dist/chunks/async-storage-f9j9JRtq.js +37 -0
  4. package/dist/chunks/async-storage-f9j9JRtq.js.map +1 -0
  5. package/dist/chunks/build-config-CW4k04_V.js +60 -0
  6. package/dist/chunks/build-config-CW4k04_V.js.map +1 -0
  7. package/dist/{collector-B3OsM252.js → chunks/collector-DKWQXEwN.js} +10 -127
  8. package/dist/chunks/collector-DKWQXEwN.js.map +1 -0
  9. package/dist/chunks/config-engine-DTglOA78.js +272 -0
  10. package/dist/chunks/config-engine-DTglOA78.js.map +1 -0
  11. package/dist/chunks/config-file-DlZXJMj8.js +25 -0
  12. package/dist/chunks/config-file-DlZXJMj8.js.map +1 -0
  13. package/dist/chunks/css-definitions-_C7C-daI.js +910 -0
  14. package/dist/chunks/css-definitions-_C7C-daI.js.map +1 -0
  15. package/dist/{css-resources-Cyl_axbI.js → chunks/css-resources-P7IBUUZs.js} +2 -9
  16. package/dist/chunks/css-resources-P7IBUUZs.js.map +1 -0
  17. package/dist/chunks/debug-BapJwy9w.js +502 -0
  18. package/dist/chunks/debug-BapJwy9w.js.map +1 -0
  19. package/dist/chunks/dsl-DYSJc1-T.js +1383 -0
  20. package/dist/chunks/dsl-DYSJc1-T.js.map +1 -0
  21. package/dist/chunks/hydration-TRcz5vnk.js +41 -0
  22. package/dist/chunks/hydration-TRcz5vnk.js.map +1 -0
  23. package/dist/chunks/react-runtime-DayjnPuC.js +689 -0
  24. package/dist/chunks/react-runtime-DayjnPuC.js.map +1 -0
  25. package/dist/chunks/runtime-engine-IwGbSMb4.js +2432 -0
  26. package/dist/chunks/runtime-engine-IwGbSMb4.js.map +1 -0
  27. package/dist/chunks/runtime-state-CVrL92zX.js +152 -0
  28. package/dist/chunks/runtime-state-CVrL92zX.js.map +1 -0
  29. package/dist/{merge-styles-DuoZEsm9.js → chunks/shared-utils-y-HjvPN4.js} +60 -55
  30. package/dist/chunks/shared-utils-y-HjvPN4.js.map +1 -0
  31. package/dist/chunks/style-engine-CVmNIjow.js +5521 -0
  32. package/dist/chunks/style-engine-CVmNIjow.js.map +1 -0
  33. package/dist/{css-writer-Bgowobbx.js → chunks/zero-engine-igHhzYM6.js} +65 -145
  34. package/dist/chunks/zero-engine-igHhzYM6.js.map +1 -0
  35. package/dist/{collector-BTVXj2hS.d.ts → collector-CTU-nl0g.d.ts} +6 -5
  36. package/dist/{config-LfIDmVHx.d.ts → config-CcxqSZI2.d.ts} +45 -40
  37. package/dist/core/index.d.ts +5 -5
  38. package/dist/core/index.js +9 -6
  39. package/dist/hydrate-CRyjjAyl.d.ts +22 -0
  40. package/dist/{index-Bm9ltvdH.d.ts → index--GdqCPlp.d.ts} +271 -242
  41. package/dist/index.d.ts +5 -5
  42. package/dist/index.js +11 -922
  43. package/dist/{merge-styles-BcLl1u_d.d.ts → merge-styles-DSkVc6CD.d.ts} +2 -3
  44. package/dist/ssr/astro-client.d.ts +1 -1
  45. package/dist/ssr/astro-client.js +1 -14
  46. package/dist/ssr/astro-client.js.map +1 -1
  47. package/dist/ssr/astro-middleware-extract-static.d.ts +1 -2
  48. package/dist/ssr/astro-middleware-extract-static.js +1 -1
  49. package/dist/ssr/astro-middleware-extract.d.ts +1 -2
  50. package/dist/ssr/astro-middleware-extract.js +1 -1
  51. package/dist/ssr/astro-middleware-static.d.ts +1 -2
  52. package/dist/ssr/astro-middleware-static.js +1 -12
  53. package/dist/ssr/astro-middleware-static.js.map +1 -1
  54. package/dist/ssr/astro-middleware.d.ts +1 -2
  55. package/dist/ssr/astro-middleware.js +1 -13
  56. package/dist/ssr/astro-middleware.js.map +1 -1
  57. package/dist/ssr/astro.d.ts +8 -16
  58. package/dist/ssr/astro.js +220 -1
  59. package/dist/ssr/astro.js.map +1 -0
  60. package/dist/ssr/client.d.ts +2 -0
  61. package/dist/ssr/client.js +2 -0
  62. package/dist/ssr/index.d.ts +5 -25
  63. package/dist/ssr/index.js +5 -5
  64. package/dist/ssr/index.js.map +1 -1
  65. package/dist/ssr/next-config.d.ts +3 -5
  66. package/dist/ssr/next-config.js +43 -35
  67. package/dist/ssr/next-config.js.map +1 -1
  68. package/dist/ssr/next.d.ts +3 -9
  69. package/dist/ssr/next.js +7 -38
  70. package/dist/ssr/next.js.map +1 -1
  71. package/dist/static/index.d.ts +8 -9
  72. package/dist/static/index.js +4 -22
  73. package/dist/static/index.js.map +1 -1
  74. package/dist/static/inject.d.ts +1 -2
  75. package/dist/{index-BmogEzTo.d.ts → types-DJEM_BOJ.d.ts} +852 -860
  76. package/dist/zero/babel.d.ts +67 -2
  77. package/dist/zero/babel.js +77 -140
  78. package/dist/zero/babel.js.map +1 -1
  79. package/dist/zero/index.d.ts +62 -8
  80. package/dist/zero/index.js +24 -2
  81. package/dist/zero/index.js.map +1 -0
  82. package/dist/zero/next.d.ts +4 -5
  83. package/dist/zero/next.js +3 -38
  84. package/dist/zero/next.js.map +1 -1
  85. package/docs/README.md +4 -1
  86. package/docs/adoption.md +25 -17
  87. package/docs/ai-agents.md +66 -33
  88. package/docs/comparison.md +9 -8
  89. package/docs/configuration.md +192 -60
  90. package/docs/debug.md +5 -7
  91. package/docs/design-system.md +117 -15
  92. package/docs/dsl.md +204 -82
  93. package/docs/getting-started.md +81 -22
  94. package/docs/injector.md +41 -40
  95. package/docs/methodology.md +48 -23
  96. package/docs/migration-v3.md +3 -3
  97. package/docs/pipeline.md +54 -27
  98. package/docs/plugins.md +22 -42
  99. package/docs/react-api.md +72 -9
  100. package/docs/runtime-benchmarks.md +146 -91
  101. package/docs/ssr.md +92 -66
  102. package/docs/styles.md +117 -31
  103. package/docs/tasty-static.md +162 -58
  104. package/package.json +96 -38
  105. package/dist/astro-CeYENy2x.js +0 -301
  106. package/dist/astro-CeYENy2x.js.map +0 -1
  107. package/dist/async-storage-DKK-wTD4.js +0 -44
  108. package/dist/async-storage-DKK-wTD4.js.map +0 -1
  109. package/dist/babel-DWCsafYw.d.ts +0 -83
  110. package/dist/collector-B3OsM252.js.map +0 -1
  111. package/dist/config-B3gPdCqd.js +0 -12421
  112. package/dist/config-B3gPdCqd.js.map +0 -1
  113. package/dist/context-CA8YKeMn.js +0 -24
  114. package/dist/context-CA8YKeMn.js.map +0 -1
  115. package/dist/core-DGm0CFHP.js +0 -1732
  116. package/dist/core-DGm0CFHP.js.map +0 -1
  117. package/dist/css-resources-Cyl_axbI.js.map +0 -1
  118. package/dist/css-writer-Bgowobbx.js.map +0 -1
  119. package/dist/format-global-rules-DklyaXv-.js +0 -22
  120. package/dist/format-global-rules-DklyaXv-.js.map +0 -1
  121. package/dist/format-rules-XRw9u7d4.js +0 -104
  122. package/dist/format-rules-XRw9u7d4.js.map +0 -1
  123. package/dist/hydrate-CNOmZprz.js +0 -37
  124. package/dist/hydrate-CNOmZprz.js.map +0 -1
  125. package/dist/index.js.map +0 -1
  126. package/dist/keyframes-DE-OE76F.js +0 -535
  127. package/dist/keyframes-DE-OE76F.js.map +0 -1
  128. package/dist/merge-styles-DuoZEsm9.js.map +0 -1
  129. package/dist/resolve-recipes-H9NqOQuP.js +0 -144
  130. package/dist/resolve-recipes-H9NqOQuP.js.map +0 -1
  131. package/dist/ssr-collector-ref-COs_ioWl.js +0 -29
  132. package/dist/ssr-collector-ref-COs_ioWl.js.map +0 -1
@@ -8,30 +8,36 @@ Choose `tastyStatic` specifically when styles must be generated during the build
8
8
 
9
9
  ## When to Use
10
10
 
11
- - **Static site generation (SSG)** — Pre-render all styles at build time
12
- - **No-JavaScript websites** — CSS works without any JS runtime
13
- - **Performance-critical pages** — CSS extracted before the application renders
14
- - **Landing pages** — Minimal bundle size with pre-generated CSS
11
+ - **CSS that must exist before anything renders** — static sites, landing pages, and HTML served from a CDN get one stylesheet generated at build time
12
+ - **No React at runtime** — `tastyStatic` returns class names, so plain DOM code or any templating that sets `class` can use them; the [Programmatic API](#programmatic-api) covers frameworks Babel does not process
13
+ - **Class-name objects instead of components** — extend them with `tastyStatic(base, styles)` and pass them wherever a `className` goes
14
+ - **Self-contained packages** — [inject mode](#inject-mode) embeds the CSS in the JavaScript, so consumers have no stylesheet to wire up
15
+
16
+ If the styles depend on props, keep `tasty()`: rendered only on the server, it also ships no styling runtime (see [Server-Side Rendering](ssr.md)).
15
17
 
16
18
  ## Requirements
17
19
 
18
20
  Build-time extraction is part of the main `@tenphi/tasty` package but requires additional peer dependencies depending on your setup:
19
21
 
20
- | Dependency | Version | Required for |
21
- | ---------------------------- | ------- | -------------------------------------------------------------------------- |
22
- | `@babel/core` | >= 7.24 | Babel plugin (`@tenphi/tasty/babel-plugin`) |
23
- | `@babel/helper-plugin-utils` | >= 7.24 | Babel plugin |
24
- | `@babel/types` | >= 7.24 | Babel plugin |
25
- | `jiti` | >= 2.6 | Next.js wrapper (`@tenphi/tasty/zero/next`) when using `configFile` option |
22
+ | Dependency | Version | Required for |
23
+ | ---------------------------- | ------- | ---------------------------------------------------- |
24
+ | `@babel/core` | >= 7.24 | Babel plugin (`@tenphi/tasty/babel-plugin`) |
25
+ | `@babel/helper-plugin-utils` | >= 7.24 | Babel plugin |
26
+ | `@babel/types` | >= 7.24 | Babel plugin |
27
+ | `jiti` | >= 2.6 | `configFile` only (Babel plugin and Next.js wrapper) |
28
+ | `babel-loader` | any | Next.js wrapper (`@tenphi/tasty/zero/next`) |
26
29
 
27
- All of these are declared as optional peer dependencies of `@tenphi/tasty`. Install only what your setup requires:
30
+ The Babel packages and `jiti` are optional peer dependencies of `@tenphi/tasty`; `babel-loader` is resolved from your project. `jiti` is loaded only to read a `configFile`, so a setup that passes `config` directly does not need it; without it, `configFile` fails with an error that says to install it. Install what your setup requires:
28
31
 
29
32
  ```bash
30
- # For any Babel-based setup (Vite, custom Babel config, etc.)
33
+ # Any Babel-based setup (Vite, custom Babel config, etc.)
31
34
  pnpm add -D @babel/core @babel/helper-plugin-utils @babel/types
32
35
 
33
- # For Next.js with TypeScript config file
34
- pnpm add -D @babel/core @babel/helper-plugin-utils @babel/types jiti
36
+ # Next.js
37
+ pnpm add -D @babel/core @babel/helper-plugin-utils @babel/types babel-loader
38
+
39
+ # Either, with configFile
40
+ pnpm add -D jiti
35
41
  ```
36
42
 
37
43
  ---
@@ -52,13 +58,15 @@ const button = tastyStatic({
52
58
  radius: '1r',
53
59
  });
54
60
 
55
- // Use in JSX - works via toString() coercion
56
- <button className={button}>Click me</button>
61
+ // Use in JSX
62
+ <button className={button.className}>Click me</button>;
57
63
 
58
- // Or access className explicitly
59
- <button className={button.className}>Click me</button>
64
+ // toString() returns the class name, for template strings and plain JS
65
+ element.className = `${button} is-active`;
60
66
  ```
61
67
 
68
+ In TSX, pass `button.className`: React types `className` as `string`, so passing the `StaticStyle` object fails type-checking even though it works at runtime.
69
+
62
70
  ---
63
71
 
64
72
  ## API Reference
@@ -105,6 +113,8 @@ tastyStatic('body', {
105
113
  });
106
114
  ```
107
115
 
116
+ The selector can be a list (`'.uni-checkbox, uni-checkbox'`): every item gets the states and sub-elements. Local `@keyframes` and `@starting` states work as in `tastyStatic(styles)`.
117
+
108
118
  ---
109
119
 
110
120
  ## StaticStyle Object
@@ -166,7 +176,7 @@ module.exports = {
166
176
  | -------------------------- | ------------------------------------------------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
167
177
  | `output` | `string` | `'tasty.css'` | Path for generated CSS file |
168
178
  | `mode` | `'file' \| 'inject'` | `'file'` | `'file'` writes CSS to disk; `'inject'` embeds CSS inline in JS (see [Inject Mode](#inject-mode)) |
169
- | `configFile` | `string` | — | Absolute path to a TS/JS module that default-exports a `TastyZeroConfig` object. JSON-serializable alternative to `config` — required for Turbopack. |
179
+ | `configFile` | `string` | — | Absolute path to a TS/JS module that default-exports a `TastyZeroConfig` object. JSON-serializable alternative to `config` — required for Turbopack. Loaded with the optional `jiti` peer. |
170
180
  | `config` | `TastyZeroConfig \| () => TastyZeroConfig` | `{}` | Inline config object or factory function. Takes precedence over `configFile`. |
171
181
  | `configDeps` | `string[]` | `[]` | Absolute file paths that affect config (for cache invalidation) |
172
182
  | `injectImport` | `boolean` | `true` | Replace `@tenphi/tasty/static` imports with an import of the generated CSS file. Set to `false` to manage CSS imports manually. |
@@ -178,7 +188,8 @@ module.exports = {
178
188
  | `config.keyframes` | `Record<string, KeyframesSteps>` | — | Global `@keyframes` definitions available to all `tastyStatic` calls |
179
189
  | `config.fontFaces` | `Record<string, FontFaceInput>` | — | Global `@font-face` definitions |
180
190
  | `config.counterStyles` | `Record<string, CounterStyleDescriptors>` | — | Global `@counter-style` definitions |
181
- | `config.units` | `Record<string, string \| UnitHandler>` | — | Custom units for the style parser (merged with built-ins). E.g. `{ em: 'em', vw: 'vw' }` |
191
+ | `config.properties` | `Record<string, PropertyDefinition>` | — | Global `@property` definitions, written with Tasty's [default properties](dsl.md#default-properties) |
192
+ | `config.units` | `Record<string, string \| UnitHandler>` | — | Custom units for the style parser (merged with built-ins). E.g. `{ u: 'var(--u)' }` makes `2u` compile to `calc(2 * var(--u))`. Native CSS units need no entry |
182
193
  | `config.functions` | `Record<string, FunctionDefinition \| Function>` | — | Custom functions (merged). Bare keys → parse functions; `$$name` keys → declarative CSS `@function` definitions |
183
194
  | `config.polyfills` | `{ functions?: boolean }` | `{}` | Opt-in polyfills. `functions: true` inlines `@function` calls into plain CSS at build time |
184
195
  | `config.plugins` | `TastyPlugin[]` | — | Plugins that extend tasty with custom functions, units, states, and handlers |
@@ -242,7 +253,7 @@ const card = tastyStatic({
242
253
  color: '#text',
243
254
  });
244
255
 
245
- <div className={card}>Styled card</div>;
256
+ <div className={card.className}>Styled card</div>;
246
257
  ```
247
258
 
248
259
  ---
@@ -279,12 +290,12 @@ export default withTastyZero({
279
290
 
280
291
  Starting with Next.js 16, Turbopack is the default bundler. `withTastyZero` supports it out of the box by injecting `turbopack.rules` with `babel-loader` and JSON-serializable options.
281
292
 
282
- The `configFile` option is key for Turbopack — it passes a file path (JSON-serializable) instead of a function, and the Babel plugin loads the config internally via jiti.
293
+ The `configFile` option is key for Turbopack — it passes a file path (JSON-serializable) instead of a function, and the Babel plugin loads the config internally via `jiti`, which must be installed.
283
294
 
284
- **Requirements**: `babel-loader` must be installed in your project:
295
+ **Requirements**: `babel-loader` must be installed in your project (see [Requirements](#requirements)):
285
296
 
286
297
  ```bash
287
- pnpm add babel-loader
298
+ pnpm add -D babel-loader
288
299
  ```
289
300
 
290
301
  ### CSS Injection
@@ -299,34 +310,95 @@ public/tasty.css
299
310
 
300
311
  ---
301
312
 
313
+ ## Programmatic API
314
+
315
+ `@tenphi/tasty/zero` turns styles into CSS in Node without Babel or React — for build scripts, or CSS shipped to other frameworks:
316
+
317
+ ```ts
318
+ import {
319
+ configure,
320
+ extractStylesForSelector,
321
+ extractStylesWithChunks,
322
+ } from '@tenphi/tasty/zero';
323
+
324
+ configure({
325
+ units: { u: 'var(--unit)' },
326
+ states: { '@dark': '@root(schema=dark)' },
327
+ });
328
+
329
+ extractStylesForSelector('.card', { padding: '2u', fill: '#surface' }).css;
330
+ // → '.card { padding: calc(2 * var(--unit)); background-color: … }'
331
+
332
+ extractStylesWithChunks({ display: 'flex', gap: '1u' });
333
+ // → [{ className: 'ts…', css: '.ts….ts… { … }' }]
334
+ ```
335
+
336
+ `configure()` takes the same options as the Babel plugin's `config` (see [Plugin Options](#plugin-options)) and replaces the previous configuration on each call; `resetConfig()` returns to the defaults. Options that shape the extracted rules apply — `states`, `units`, parse `functions`, `handlers`, `replaceTokens`, `recipes`, `plugins`, `namePrefix`. Definitions that become at-rules or global CSS of their own are not emitted with the rules: `tokens`, `globalStyles`, `fontFaces`, `counterStyles`, `properties` and `$$` `@function` definitions. Render tokens and global styles with `extractStylesForSelector(':root', tokens)` and `extractStylesForSelector(selector, styles)`.
337
+
338
+ `extractConfigProperties()` returns the `@property` rules the Babel plugin writes at the top of its output: Tasty's [default set](dsl.md#default-properties) (`#border`, `#outline`, `$gap`, `$radius`, `$transition`, …) overlaid with the configured `properties`, or with the `properties` map you pass it. Emit them once per stylesheet. Style handlers read several of these tokens with no fallback of their own (`border: true` reads `#border`, `transition` reads `$transition`), so without them those declarations resolve to nothing.
339
+
340
+ The configuration is process-wide and shared with the Babel plugin and the runtime `configure()` from `@tenphi/tasty`. The extractors re-apply their own configuration when the Babel plugin replaced it, but not after a runtime `configure()` — don't mix the runtime and build-time configuration in one process.
341
+
342
+ `extractStylesForSelector()` output starts with the `@keyframes` its rules animate — local `@keyframes` and configured `keyframes` — under hashed names the `animation` declarations are rewritten to. `extractStylesWithChunks()` returns class rules only.
343
+
344
+ To write the results to a file the way the Babel plugin does, use `createCSSWriter()`. It deduplicates blocks by key and prepends the generated-file header:
345
+
346
+ ```ts
347
+ import {
348
+ createCSSWriter,
349
+ extractConfigProperties,
350
+ extractStylesWithChunks,
351
+ } from '@tenphi/tasty/zero';
352
+
353
+ const writer = createCSSWriter('dist/tasty.css', { devMode: true });
354
+
355
+ for (const prop of extractConfigProperties()) {
356
+ writer.add(`@property:${prop.name}`, prop.css);
357
+ }
358
+
359
+ for (const chunk of extractStylesWithChunks({ display: 'flex', gap: '1u' })) {
360
+ writer.add(chunk.className, chunk.css, 'src/card.ts'); // source is a devMode comment
361
+ }
362
+
363
+ writer.write(); // also: has(key), getCSS(), clear(), getOutputPath()
364
+ ```
365
+
366
+ ---
367
+
302
368
  ## Vite Integration
303
369
 
370
+ `@vitejs/plugin-react` 6 no longer runs Babel; add it with `@rolldown/plugin-babel` (`pnpm add -D @rolldown/plugin-babel`):
371
+
304
372
  ```typescript
305
373
  // vite.config.ts
306
374
  import { defineConfig } from 'vite';
307
375
  import react from '@vitejs/plugin-react';
376
+ import babel from '@rolldown/plugin-babel';
308
377
 
309
378
  export default defineConfig({
310
379
  plugins: [
311
- react({
312
- babel: {
313
- plugins: [
314
- [
315
- '@tenphi/tasty/babel-plugin',
316
- {
317
- output: 'public/tasty.css',
318
- config: {
319
- states: { '@mobile': '@media(w < 768px)' },
320
- },
380
+ react(),
381
+ babel({
382
+ plugins: [
383
+ [
384
+ '@tenphi/tasty/babel-plugin',
385
+ {
386
+ output: 'src/tasty.css',
387
+ config: {
388
+ states: { '@mobile': '@media(w < 768px)' },
321
389
  },
322
- ],
390
+ },
323
391
  ],
324
- },
392
+ ],
325
393
  }),
326
394
  ],
327
395
  });
328
396
  ```
329
397
 
398
+ With `@vitejs/plugin-react` 5 or older, pass the same `plugins` array to `react({ babel: { plugins } })` instead.
399
+
400
+ Keep `output` outside `public/`: the plugin replaces each `@tenphi/tasty/static` import with an import of the generated file, and Vite does not let JavaScript import files from `public/`. Vite then bundles the CSS like any other stylesheet.
401
+
330
402
  ---
331
403
 
332
404
  ## Build Transformation
@@ -344,44 +416,67 @@ const button = tastyStatic({
344
416
 
345
417
  tastyStatic('.heading', { preset: 'h1' });
346
418
 
347
- export const Button = () => <button className={button}>Click</button>;
419
+ export const Button = () => <button className={button.className}>Click</button>;
348
420
  ```
349
421
 
350
422
  ### After (Production Build)
351
423
 
352
424
  ```tsx
425
+ import './tasty.css';
426
+
353
427
  const button = {
354
- className: 'ts3f2a1b ts8c4d2e',
428
+ className: 'ts3f783b ts070801',
355
429
  styles: { padding: '2x 4x', fill: '#purple', color: '#white' },
356
430
  toString() {
357
431
  return this.className;
358
432
  },
359
433
  };
360
434
 
361
- export const Button = () => <button className={button}>Click</button>;
435
+ export const Button = () => <button className={button.className}>Click</button>;
362
436
  ```
363
437
 
438
+ The `@tenphi/tasty/static` import becomes an import of the generated file (relative to the source file), and the selector-mode call is removed.
439
+
364
440
  ### Generated CSS (tasty.css)
365
441
 
366
442
  ```css
367
443
  /* Generated by @tenphi/tasty/zero - DO NOT EDIT */
368
444
 
369
- .ts3f2a1b.ts3f2a1b {
370
- padding: 16px 32px;
445
+ @property --tasty-second-fill-color {
446
+ syntax: '<color>';
447
+ inherits: false;
448
+ initial-value: transparent;
371
449
  }
372
450
 
373
- .ts8c4d2e.ts8c4d2e {
374
- background: #9370db;
375
- color: #fff;
451
+ @property --current-color {
452
+ syntax: '<color>';
453
+ inherits: true;
454
+ initial-value: currentcolor;
376
455
  }
377
456
 
378
- .heading.heading {
379
- font-size: 2.5rem;
380
- font-weight: 700;
381
- line-height: 1.2;
457
+ /* …the rest of Tasty's default @property rules (--white-color, --border-color, --gap, --transition, …), then config.properties */
458
+
459
+ .ts3f783b.ts3f783b {
460
+ color: var(--white-color);
461
+ --current-color: var(--white-color);
462
+ }
463
+
464
+ .ts3f783b.ts3f783b {
465
+ background-color: var(--purple-color);
466
+ }
467
+
468
+ .ts070801.ts070801 {
469
+ padding: 16px 32px;
470
+ }
471
+
472
+ .heading {
473
+ font-size: var(--h1-font-size, var(--default-font-size));
474
+ /* …line-height, letter-spacing, font-weight, and the rest of the h1 preset */
382
475
  }
383
476
  ```
384
477
 
478
+ The file starts with Tasty's [default `@property` rules](dsl.md#default-properties) and your `config.properties`, written once. Styles are split into chunks (here appearance and spacing), each with its own class. Color tokens compile to custom properties: `#purple` becomes `var(--purple-color)`. Define it in `config.tokens` and the plugin writes it to `:root` at the top of the same file. Units are literal (`2x` is `16px`). Selector-mode rules use the selector as written, without the doubled specificity of class rules.
479
+
385
480
  ---
386
481
 
387
482
  ## Inject Mode
@@ -452,22 +547,29 @@ tastyStatic('.heading', { preset: 'h1' });
452
547
  ```tsx
453
548
  import { injectCSS as _$i } from '@tenphi/tasty/static/inject';
454
549
 
550
+ _$i(
551
+ '@property',
552
+ '@property --tasty-second-fill-color { … }\n@property --current-color { … }\n…',
553
+ );
554
+
455
555
  const button =
456
556
  (_$i(
457
- 'ts3f2a1b ts8c4d2e',
458
- '.ts3f2a1b.ts3f2a1b{padding:16px 32px}\n.ts8c4d2e.ts8c4d2e{background:#9370db}',
557
+ 'tsa903d6 ts070801',
558
+ '.tsa903d6.tsa903d6 { background-color: var(--purple-color); }\n.ts070801.ts070801 { padding: 16px 32px; }',
459
559
  ),
460
560
  {
461
- className: 'ts3f2a1b ts8c4d2e',
561
+ className: 'tsa903d6 ts070801',
462
562
  styles: { padding: '2x 4x', fill: '#purple' },
463
563
  toString() {
464
564
  return this.className;
465
565
  },
466
566
  });
467
567
 
468
- _$i('.heading', '.heading{font-size:2.5rem;font-weight:700;line-height:1.2}');
568
+ _$i('.heading', '.heading { font-size: var(--h1-font-size, …); … }');
469
569
  ```
470
570
 
571
+ Each file that calls `tastyStatic()` starts by injecting Tasty's default `@property` rules and your `config.properties` under the `@property` id. `injectCSS()` skips an id it has already injected, so the page gets them once.
572
+
471
573
  ### Dev Mode / HMR
472
574
 
473
575
  Class names are content-hashed (`ts` + MD5). When styles change, a new hash produces a new `_$i` call that injects fresh CSS. The injector deduplicates by id, so unchanged styles are skipped. Old CSS stays in the DOM but is harmless since no elements reference those class names.
@@ -489,7 +591,7 @@ const button = tastyStatic({
489
591
  radius: '1r',
490
592
  fill: '#gray.20',
491
593
  color: '#text',
492
- transition: 'fill 0.15s',
594
+ transition: 'fill',
493
595
  });
494
596
 
495
597
  // Variants
@@ -525,7 +627,7 @@ const card = tastyStatic({
525
627
 
526
628
  ## Extending Style Types (TypeScript)
527
629
 
528
- If you add custom style properties, use module augmentation so `tastyStatic` recognizes them too. See [Extending Style Types](configuration.md#extending-style-types-typescript) in the configuration docs.
630
+ If you add custom style properties, use module augmentation so `tastyStatic` recognizes them too. See [TypeScript](configuration.md#typescript) in the configuration docs.
529
631
 
530
632
  ---
531
633
 
@@ -547,10 +649,12 @@ const card = tastyStatic({
547
649
  });
548
650
 
549
651
  <div
550
- className={card}
551
- style={{
552
- '--card-bg': isActive ? 'var(--purple-color)' : 'var(--white-color)',
553
- }}
652
+ className={card.className}
653
+ style={
654
+ {
655
+ '--card-bg': isActive ? 'var(--purple-color)' : 'var(--white-color)',
656
+ } as React.CSSProperties
657
+ }
554
658
  />;
555
659
  ```
556
660
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenphi/tasty",
3
- "version": "0.0.0-snapshot.06c8a41",
3
+ "version": "0.0.0-snapshot.07ea4a0",
4
4
  "description": "A design-system-integrated styling system and DSL for concise, state-aware UI styling",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -62,6 +62,11 @@
62
62
  "import": "./dist/ssr/astro.js",
63
63
  "default": "./dist/ssr/astro.js"
64
64
  },
65
+ "./ssr/client": {
66
+ "types": "./dist/ssr/client.d.ts",
67
+ "import": "./dist/ssr/client.js",
68
+ "default": "./dist/ssr/client.js"
69
+ },
65
70
  "./ssr/astro-client": {
66
71
  "types": "./dist/ssr/astro-client.d.ts",
67
72
  "import": "./dist/ssr/astro-client.js",
@@ -126,12 +131,15 @@
126
131
  "bench:overhead": "vitest bench --config vitest.production-browser-bench.config.ts src/tasty-overhead.bench.tsx",
127
132
  "bench:injection": "vitest bench --config vitest.production-browser-bench.config.ts src/tasty-injection.bench.ts",
128
133
  "bench:interaction": "vitest bench --config vitest.production-browser-bench.config.ts src/tasty-interaction.bench.tsx",
134
+ "bench:tree": "vitest bench --config vitest.production-browser-bench.config.ts src/tasty-tree.bench.tsx",
129
135
  "bench:cold-start": "node scripts/cold-start/index.mjs",
130
136
  "prepublishOnly": "pnpm run build",
131
137
  "changeset": "changeset",
132
138
  "version": "changeset version",
133
139
  "release": "changeset publish",
134
140
  "knip": "knip",
141
+ "check:build": "node scripts/check-built-entries.mjs",
142
+ "check:react-compat": "node scripts/check-react-compat.mjs",
135
143
  "check:test-only": "node scripts/check-test-only-code.mjs",
136
144
  "hygiene": "pnpm lint && pnpm format:check && pnpm typecheck",
137
145
  "hygiene:fix": "pnpm lint:fix && pnpm format && pnpm typecheck"
@@ -189,60 +197,84 @@
189
197
  "csstype": "^3.1.0"
190
198
  },
191
199
  "devDependencies": {
192
- "@astrojs/react": "^4.4.2",
193
- "@babel/core": "^7.24.0",
194
- "@babel/helper-plugin-utils": "^7.24.0",
195
- "@babel/types": "^7.24.0",
196
- "@changesets/changelog-github": "^0.7.0",
197
- "@changesets/cli": "^2.29.8",
200
+ "@astrojs/react": "^7.0.0",
201
+ "@babel/core": "^7.29.7",
202
+ "@babel/helper-plugin-utils": "^7.29.7",
203
+ "@babel/types": "^7.29.8",
204
+ "@changesets/changelog-github": "^1.0.1",
205
+ "@changesets/cli": "^3.0.3",
198
206
  "@eslint/js": "^10.0.1",
199
- "@size-limit/esbuild": "^12.0.0",
200
- "@size-limit/file": "^12.0.0",
201
- "@tenphi/eslint-plugin-tasty": "^1.0.5",
202
- "@testing-library/jest-dom": "^6.9.1",
203
- "@testing-library/react": "^16.3.2",
207
+ "@size-limit/esbuild": "^14.0.0",
208
+ "@size-limit/file": "^14.0.0",
209
+ "@tenphi/eslint-plugin-tasty": "^1.5.1",
210
+ "@testing-library/jest-dom": "^7.0.1",
211
+ "@testing-library/react": "^16.3.3",
204
212
  "@types/babel__core": "^7.20.5",
205
213
  "@types/babel__helper-plugin-utils": "^7.10.3",
206
- "@types/node": "^24.13.3",
207
- "@types/react": "^19.0.0",
208
- "@types/react-dom": "^19.0.0",
209
- "@vitest/browser-playwright": "^4.1.10",
210
- "astro": "^5.18.2",
211
- "esbuild": "^0.27.7",
212
- "eslint": "^10.0.0",
214
+ "@types/node": "^24.13.6",
215
+ "@types/react": "^19.3.0",
216
+ "@types/react-dom": "^19.3.0",
217
+ "@vitest/browser-playwright": "^4.1.11",
218
+ "astro": "^7.3.4",
219
+ "esbuild": "^0.28.2",
220
+ "eslint": "^10.11.0",
213
221
  "eslint-config-prettier": "^10.1.8",
214
- "jiti": "^2.6.1",
215
- "knip": "^5.61.3",
216
- "next": "^16.0.0",
217
- "playwright": "^1.62.1",
218
- "prettier": "^3.9.6",
219
- "react": "^19.0.0",
220
- "react-dom": "^19.0.0",
221
- "size-limit": "^12.0.0",
222
- "tsdown": "^0.22.0",
223
- "typescript": "^6.0.2",
224
- "typescript-eslint": "^8.56.0",
225
- "vitest": "^4.1.10"
222
+ "jiti": "^2.7.0",
223
+ "knip": "^6.38.0",
224
+ "next": "^16.3.6",
225
+ "playwright": "^1.63.0",
226
+ "prettier": "^3.9.9",
227
+ "react": "^19.3.0",
228
+ "react-dom": "^19.3.0",
229
+ "size-limit": "^14.0.0",
230
+ "tsdown": "^0.23.0",
231
+ "typescript": "^6.0.3",
232
+ "typescript-eslint": "^8.70.1",
233
+ "vitest": "^4.1.11"
226
234
  },
227
- "packageManager": "pnpm@11.24.0",
235
+ "packageManager": "pnpm@11.27.1",
228
236
  "size-limit": [
229
237
  {
230
238
  "name": "main (import *)",
231
239
  "path": "dist/index.js",
232
240
  "import": "*",
233
- "limit": "57.5 kB"
241
+ "limit": "59.65 kB"
242
+ },
243
+ {
244
+ "name": "main (tasty)",
245
+ "path": "dist/index.js",
246
+ "import": "{ tasty }",
247
+ "limit": "52.67 kB"
248
+ },
249
+ {
250
+ "name": "main (StyleParser)",
251
+ "path": "dist/index.js",
252
+ "import": "{ StyleParser }",
253
+ "limit": "6.94 kB"
254
+ },
255
+ {
256
+ "name": "main (parseColor)",
257
+ "path": "dist/index.js",
258
+ "import": "{ parseColor }",
259
+ "limit": "7.59 kB"
260
+ },
261
+ {
262
+ "name": "main (useRawCSS)",
263
+ "path": "dist/index.js",
264
+ "import": "{ useRawCSS }",
265
+ "limit": "32.48 kB"
234
266
  },
235
267
  {
236
268
  "name": "core (import *)",
237
269
  "path": "dist/core/index.js",
238
270
  "import": "*",
239
- "limit": "54.5 kB"
271
+ "limit": "56.29 kB"
240
272
  },
241
273
  {
242
274
  "name": "static",
243
275
  "path": "dist/static/index.js",
244
276
  "import": "*",
245
- "limit": "16.8 kB"
277
+ "limit": "1.08 kB"
246
278
  },
247
279
  {
248
280
  "name": "zero",
@@ -253,7 +285,7 @@
253
285
  "path",
254
286
  "crypto"
255
287
  ],
256
- "limit": "31.25 kB"
288
+ "limit": "39.13 kB"
257
289
  },
258
290
  {
259
291
  "name": "babel-plugin",
@@ -262,9 +294,35 @@
262
294
  "ignore": [
263
295
  "fs",
264
296
  "path",
265
- "crypto"
297
+ "crypto",
298
+ "node:module",
299
+ "node:path"
300
+ ],
301
+ "limit": "43.55 kB"
302
+ },
303
+ {
304
+ "name": "astro client",
305
+ "path": "dist/ssr/astro-client.js",
306
+ "import": "*",
307
+ "limit": "0.5 kB"
308
+ },
309
+ {
310
+ "name": "ssr client",
311
+ "path": "dist/ssr/client.js",
312
+ "import": "{ hydrateTastyClasses }",
313
+ "limit": "0.5 kB"
314
+ },
315
+ {
316
+ "name": "Next.js config",
317
+ "path": "dist/ssr/next-config.js",
318
+ "import": "{ withTastyNext }",
319
+ "ignore": [
320
+ "node:crypto",
321
+ "node:fs",
322
+ "node:module",
323
+ "node:path"
266
324
  ],
267
- "limit": "49.6 kB"
325
+ "limit": "40.18 kB"
268
326
  }
269
327
  ]
270
328
  }