@tenphi/tasty 3.0.1 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +16 -16
  2. package/dist/{babel-HlEpSZrj.d.ts → babel-R2qT7yLN.d.ts} +2 -2
  3. package/dist/{collector-D14DcZx9.js → collector-D7yzmG5G.js} +3 -3
  4. package/dist/{collector-D14DcZx9.js.map → collector-D7yzmG5G.js.map} +1 -1
  5. package/dist/{collector-BPzZezAG.d.ts → collector-D8o4Wdq-.d.ts} +2 -2
  6. package/dist/{config-CCcE_tqx.js → config-CwQ-fAsp.js} +674 -330
  7. package/dist/config-CwQ-fAsp.js.map +1 -0
  8. package/dist/{config-D2INrjr_.d.ts → config-De8L9NWM.d.ts} +2 -2
  9. package/dist/core/index.d.ts +4 -4
  10. package/dist/core/index.js +5 -5
  11. package/dist/{core-B5d0IPi2.js → core-CqSh853z.js} +11 -12
  12. package/dist/core-CqSh853z.js.map +1 -0
  13. package/dist/{css-writer-B5DBoxhf.js → css-writer-dS1srTkS.js} +3 -3
  14. package/dist/{css-writer-B5DBoxhf.js.map → css-writer-dS1srTkS.js.map} +1 -1
  15. package/dist/{format-rules-BbKDjsfO.js → format-rules-CaW4lJGg.js} +11 -9
  16. package/dist/format-rules-CaW4lJGg.js.map +1 -0
  17. package/dist/{hydrate-CwALW3J9.js → hydrate-uFv9kx7G.js} +2 -2
  18. package/dist/{hydrate-CwALW3J9.js.map → hydrate-uFv9kx7G.js.map} +1 -1
  19. package/dist/{index-BAAbdfRs.d.ts → index-DzGxoeyN.d.ts} +5 -5
  20. package/dist/{index-l_NdJSUS.d.ts → index-KUYya3x7.d.ts} +11 -1
  21. package/dist/index.d.ts +4 -4
  22. package/dist/index.js +6 -6
  23. package/dist/{keyframes-B8M1FmtE.js → keyframes-DaZhjqkd.js} +2 -2
  24. package/dist/{keyframes-B8M1FmtE.js.map → keyframes-DaZhjqkd.js.map} +1 -1
  25. package/dist/{merge-styles-NEvqayL_.d.ts → merge-styles-DLm4wdnb.d.ts} +2 -2
  26. package/dist/{merge-styles--1zOSFlS.js → merge-styles-H8LFJ5HY.js} +2 -2
  27. package/dist/{merge-styles--1zOSFlS.js.map → merge-styles-H8LFJ5HY.js.map} +1 -1
  28. package/dist/{resolve-recipes-Caix5NnH.js → resolve-recipes-aN94fjqS.js} +3 -3
  29. package/dist/{resolve-recipes-Caix5NnH.js.map → resolve-recipes-aN94fjqS.js.map} +1 -1
  30. package/dist/ssr/astro-client.js +1 -1
  31. package/dist/ssr/astro.js +3 -3
  32. package/dist/ssr/index.d.ts +1 -1
  33. package/dist/ssr/index.js +3 -3
  34. package/dist/ssr/next.d.ts +1 -1
  35. package/dist/ssr/next.js +4 -4
  36. package/dist/static/index.d.ts +2 -2
  37. package/dist/static/index.js +1 -1
  38. package/dist/zero/babel.d.ts +1 -1
  39. package/dist/zero/babel.js +4 -4
  40. package/dist/zero/index.d.ts +1 -1
  41. package/dist/zero/index.js +1 -1
  42. package/dist/zero/next.d.ts +1 -1
  43. package/docs/ai-agents.md +3 -0
  44. package/docs/configuration.md +11 -6
  45. package/docs/dsl.md +99 -2
  46. package/docs/styles.md +3 -1
  47. package/package.json +14 -10
  48. package/dist/config-CCcE_tqx.js.map +0 -1
  49. package/dist/core-B5d0IPi2.js.map +0 -1
  50. package/dist/format-rules-BbKDjsfO.js.map +0 -1
package/dist/ssr/next.js CHANGED
@@ -1,9 +1,9 @@
1
1
  "use client";
2
- import { n as getConfig } from "../config-CCcE_tqx.js";
3
- import { i as registerSSRCollectorGetter } from "../format-rules-BbKDjsfO.js";
2
+ import { n as getConfig } from "../config-CwQ-fAsp.js";
3
+ import { i as registerSSRCollectorGetter } from "../format-rules-CaW4lJGg.js";
4
4
  import { t as getTastySSRContext } from "../context-CA8YKeMn.js";
5
- import { t as ServerStyleCollector } from "../collector-D14DcZx9.js";
6
- import { t as hydrateTastyClasses } from "../hydrate-CwALW3J9.js";
5
+ import { t as ServerStyleCollector } from "../collector-D7yzmG5G.js";
6
+ import { t as hydrateTastyClasses } from "../hydrate-uFv9kx7G.js";
7
7
  import { Fragment, createElement, useState } from "react";
8
8
  import { useServerInsertedHTML } from "next/navigation";
9
9
  //#region src/ssr/next.ts
@@ -1,5 +1,5 @@
1
- import { b as Styles } from "../index-l_NdJSUS.js";
2
- import { t as mergeStyles } from "../merge-styles-NEvqayL_.js";
1
+ import { b as Styles } from "../index-KUYya3x7.js";
2
+ import { t as mergeStyles } from "../merge-styles-DLm4wdnb.js";
3
3
 
4
4
  //#region src/static/types.d.ts
5
5
  /**
@@ -1,4 +1,4 @@
1
- import { t as mergeStyles } from "../merge-styles--1zOSFlS.js";
1
+ import { t as mergeStyles } from "../merge-styles-H8LFJ5HY.js";
2
2
  //#region src/static/types.ts
3
3
  /**
4
4
  * Create a StaticStyle object.
@@ -1,2 +1,2 @@
1
- import { n as _default, r as TastyZeroConfig, t as TastyZeroBabelOptions } from "../babel-HlEpSZrj.js";
1
+ import { n as _default, r as TastyZeroConfig, t as TastyZeroBabelOptions } from "../babel-R2qT7yLN.js";
2
2
  export { TastyZeroBabelOptions, type TastyZeroConfig, _default as default };
@@ -1,7 +1,7 @@
1
- import { b as resetConfig, d as getGlobalStyles, i as getGlobalConfigTokens, s as getGlobalFunctions, t as configure } from "../config-CCcE_tqx.js";
2
- import { t as mergeStyles } from "../merge-styles--1zOSFlS.js";
3
- import { t as resolveRecipes } from "../resolve-recipes-Caix5NnH.js";
4
- import { a as extractFunctionsFromStyles, c as extractStylesForSelector, i as extractFontFaceFromStyles, l as extractStylesWithChunks, o as extractKeyframesFromStyles, r as extractCounterStyleFromStyles, s as extractPropertiesFromStyles, t as CSSWriter, u as setExtractorNamePrefix } from "../css-writer-B5DBoxhf.js";
1
+ import { b as resetConfig, d as getGlobalStyles, i as getGlobalConfigTokens, s as getGlobalFunctions, t as configure } from "../config-CwQ-fAsp.js";
2
+ import { t as mergeStyles } from "../merge-styles-H8LFJ5HY.js";
3
+ import { t as resolveRecipes } from "../resolve-recipes-aN94fjqS.js";
4
+ import { a as extractFunctionsFromStyles, c as extractStylesForSelector, i as extractFontFaceFromStyles, l as extractStylesWithChunks, o as extractKeyframesFromStyles, r as extractCounterStyleFromStyles, s as extractPropertiesFromStyles, t as CSSWriter, u as setExtractorNamePrefix } from "../css-writer-dS1srTkS.js";
5
5
  import * as fs from "fs";
6
6
  import * as path from "path";
7
7
  import { declare } from "@babel/helper-plugin-utils";
@@ -1,4 +1,4 @@
1
- import { b as Styles } from "../index-l_NdJSUS.js";
1
+ import { b as Styles } from "../index-KUYya3x7.js";
2
2
 
3
3
  //#region src/zero/extractor.d.ts
4
4
  interface ExtractedChunk {
@@ -1,2 +1,2 @@
1
- import { c as extractStylesForSelector, l as extractStylesWithChunks, n as createCSSWriter, t as CSSWriter } from "../css-writer-B5DBoxhf.js";
1
+ import { c as extractStylesForSelector, l as extractStylesWithChunks, n as createCSSWriter, t as CSSWriter } from "../css-writer-dS1srTkS.js";
2
2
  export { CSSWriter, createCSSWriter, extractStylesForSelector, extractStylesWithChunks };
@@ -1,4 +1,4 @@
1
- import { r as TastyZeroConfig } from "../babel-HlEpSZrj.js";
1
+ import { r as TastyZeroConfig } from "../babel-R2qT7yLN.js";
2
2
 
3
3
  //#region src/zero/next.d.ts
4
4
  interface WebpackConfigContext {
package/docs/ai-agents.md CHANGED
@@ -70,6 +70,9 @@ The last row goes the other way — longhands over the shorthand — because `fl
70
70
  - ❌ `#f5f5f5`, `rgb(0 0 0)`, `oklch(…)`, `okhsl(…)`, `red` → ✅ `#surface` (add the token to the config if it doesn't exist).
71
71
  - ❌ `var(--gap)` → ✅ `$gap` · ❌ `$accent-color` → ✅ `#accent` · ❌ `transparent` → ✅ `#clear` · ❌ `currentColor` → ✅ `#current`.
72
72
  - A `$name` must be declared as a `'$name': value` key in the same styles object, or in the config.
73
+ - Names are case-sensitive and start lowercase — `$myVar` → `--myVar`; a leading capital folds (`$Foo` → `--foo`). Prefer kebab-case.
74
+ - A `$name` reference is untyped: it fills the first free slot in a shorthand (`border: '1bw $my-style'` → the line style). Write colors as `#name`; `$name-color` is a fallback for pointing at a raw CSS custom property that holds a color.
75
+ - `preset` and `transition` take token *names*, not values, so a `$name` cannot stand in for one (it warns in dev and is ignored).
73
76
 
74
77
  ### Units
75
78
 
@@ -78,7 +78,7 @@ These docs use `data-schema="dark"` in examples. If your app already standardize
78
78
 
79
79
  ## Color Space
80
80
 
81
- Controls the CSS color space used for decomposed color token companion variables. When you define `#name` color tokens, tasty generates both `--name-color` (full color) and `--name-color-{suffix}` (decomposed components for alpha composition).
81
+ Controls the CSS color space used for decomposed color token companion variables. When you define `#name` color tokens, tasty generates both `--name-color` (the color) and `--name-color-{suffix}` (its channels, decomposed).
82
82
 
83
83
  ```jsx
84
84
  configure({
@@ -86,13 +86,17 @@ configure({
86
86
  });
87
87
  ```
88
88
 
89
- | Color Space | Suffix | Components Format | Alpha Syntax |
89
+ | Color Space | Suffix | Components Format | Channel Reference |
90
90
  |---|---|---|---|
91
- | `rgb` | `-rgb` | `255 128 0` | `rgb(var(--name-color-rgb) / .5)` |
92
- | `hsl` | `-hsl` | `300 100% 25%` | `hsl(var(--name-color-hsl) / .5)` |
93
- | `oklch` | `-oklch` | `0.42 0.16 328` | `oklch(var(--name-color-oklch) / .5)` |
91
+ | `rgb` | `-rgb` | `255 128 0` | `rgb(var(--name-color-rgb))` |
92
+ | `hsl` | `-hsl` | `300 100% 25%` | `hsl(var(--name-color-hsl))` |
93
+ | `oklch` | `-oklch` | `0.42 0.16 328` | `oklch(var(--name-color-oklch))` |
94
94
 
95
- The `oklch` color space is the default because it provides perceptually uniform color manipulation — alpha fading and color mixing produce more natural-looking results.
95
+ The `oklch` color space is the default because it provides perceptually uniform color manipulation — reaching for a channel to shift lightness or hue produces more natural-looking results.
96
+
97
+ The companion is there so you can *address the channels*: shift a lightness, animate a hue, build a derived color. It is not how the [opacity suffix](dsl.md#color-tokens--opacity) works — that sets the alpha on `--name-color` itself with relative color syntax, so it needs nothing from the companion and works on any color.
98
+
99
+ A color the engine cannot evaluate at build time — a `color-mix()`, a `light-dark()`, a `color()` in a space it has no conversion for — has no channels to decompose, so its companion is expressed *by reference* using CSS relative color syntax (`--name-color-oklch: from color-mix(…) l c h`). Reading a channel off it still works; the browser resolves them.
96
100
 
97
101
  ---
98
102
 
@@ -164,6 +168,7 @@ configure({
164
168
 
165
169
  - `$name` keys become `--name` CSS custom properties
166
170
  - `#name` keys become `--name-color` and `--name-color-{colorSpace}` properties (suffix depends on `colorSpace`, default `oklch`)
171
+ - Names keep their case, since CSS custom properties are case-sensitive: `$myVar` is `--myVar` and is referenced as `$myVar`. A leading capital is not supported and folds — `$Foo` and `#Purple` become `--foo` and `--purple-color` — so start names lowercase. Kebab-case (`$my-var`) remains the convention.
167
172
 
168
173
  Tokens are automatically emitted in all rendering modes: runtime (client), SSR, and zero-runtime (Babel plugin).
169
174
 
package/docs/dsl.md CHANGED
@@ -111,7 +111,7 @@ const List = tasty({
111
111
  Named color prefixed with `#` that maps to CSS custom properties. Supports opacity with `.N` suffix:
112
112
 
113
113
  ```jsx
114
- fill: '#purple.5' // → var(--purple-color) with 50% opacity
114
+ fill: '#purple.5' // → oklch(from var(--purple-color) l c h / .5)
115
115
  ```
116
116
 
117
117
  ### Modifier
@@ -133,11 +133,108 @@ Modifiers can also be exposed as top-level component props via `modProps` — se
133
133
  color: '#purple', // Full opacity
134
134
  color: '#purple.5', // 50% opacity
135
135
  color: '#purple.05', // 5% opacity
136
+ color: '#purple.$fade', // Opacity from a custom property
136
137
  fill: '#current', // → currentcolor
137
- fill: '#current.5', // → color-mix(in oklab, currentcolor 50%, transparent)
138
138
  color: '(#primary, #secondary)', // Fallback syntax
139
139
  ```
140
140
 
141
+ The suffix sets the alpha on the token's color with CSS relative color syntax:
142
+
143
+ ```jsx
144
+ fill: '#purple.5';
145
+ // → oklch(from var(--purple-color) l c h / .5)
146
+ ```
147
+
148
+ The channels are copied over and the alpha slot is written, which asks nothing of
149
+ the color beyond *being* a color. That means the suffix works on every one of
150
+ these:
151
+
152
+ - a token holding a `color-mix()`, a `light-dark()`, or a `color()` in a space
153
+ Tasty cannot convert — none of which have channels to decompose
154
+ - `#current`, which resolves to `currentcolor`
155
+ - a `--name-color` variable declared in your own CSS, with no Tasty token
156
+ definition and no companion variable behind it
157
+
158
+ Two properties follow from writing the alpha slot rather than compositing:
159
+
160
+ - **Alpha is replaced, not multiplied.** A token holding `rgb(255 0 0 / .8)`
161
+ faded to `.5` is alpha `.5`, not `.4`.
162
+ - **The alpha may be a number or a percentage.** `#purple.$fade` emits
163
+ `/ var(--fade)` unchanged, so it works whether `$fade` holds `.5` or `50%` —
164
+ which is what `--*-opacity` properties are registered to accept.
165
+
166
+ ### `#current` composes instead
167
+
168
+ `#current` is the one exception, and the difference is deliberate. A token *names*
169
+ a color, so fading it sets its alpha. `currentcolor` is the color an element
170
+ **inherits**, which an ancestor may already have faded — `#current.4` means "40%
171
+ of what reaches me":
172
+
173
+ ```jsx
174
+ fill: '#current.4';
175
+ // → color-mix(in oklab, currentcolor 40%, transparent)
176
+ ```
177
+
178
+ So a `#current` fade nested inside another one composes: a label at `#current.4`
179
+ with a fill of `#current.18` under it lands at `.072`. Color ramps built on
180
+ `#current` depend on that — replacing would double the opacity of every nested
181
+ step. Because a `color-mix()` percentage cannot be a `<number>`, an opacity
182
+ custom property used as `#current.$fade` must hold a unitless number; a token
183
+ accepts either form.
184
+
185
+ The space is always `oklch`, whatever [`colorSpace`](configuration.md#color-space)
186
+ is set to: it is unbounded, so a wide-gamut color survives a round trip that a
187
+ gamut-limited space would clamp.
188
+
189
+ ---
190
+
191
+ ## CSS Color Functions
192
+
193
+ Every CSS color function is recognized as a color, so it lands in the color slot
194
+ of whichever style property you use it in — and tokens inside it are expanded:
195
+
196
+ ```jsx
197
+ fill: 'color-mix(in oklab, #primary 50%, #surface)',
198
+ color: 'light-dark(#dark, #light)',
199
+ color: 'contrast-color(#primary)',
200
+ fill: 'color(display-p3 1 .5 0)',
201
+ border: '1bw solid oklch(from #primary l c h / 50%)', // relative color syntax
202
+ shadow: '0 0 1x color-mix(in oklab, #dark 20%, transparent)',
203
+ ```
204
+
205
+ `light-dark()` is the one exception to "always a color": CSS lets it pick between
206
+ values of any type, so it is treated as a color only when its arguments are
207
+ colors. `padding: 'light-dark(1x, 2x)'` still reaches the padding slot.
208
+
209
+ A color token defined as one of these functions takes the
210
+ [opacity suffix](#color-tokens--opacity) like any other:
211
+
212
+ ```jsx
213
+ const Card = tasty({
214
+ styles: {
215
+ '#brand': 'color-mix(in oklab, #primary 50%, #surface)',
216
+ fill: '#brand.5',
217
+ },
218
+ });
219
+ ```
220
+
221
+ A [replace token](configuration.md#replace-tokens-parse-time-substitution) is
222
+ substituted while parsing, so its color is right there to fade in place. A
223
+ channel function takes the alpha after a slash; a derived function has no alpha
224
+ channel, so it gets wrapped:
225
+
226
+ ```jsx
227
+ configure({
228
+ replaceTokens: {
229
+ '#solid': 'hsl(220 90% 50%)',
230
+ '#adaptive': 'light-dark(#dark, #light)',
231
+ },
232
+ });
233
+
234
+ fill: '#solid.5'; // → hsl(220 90% 50% / .5)
235
+ fill: '#adaptive.5'; // → oklch(from light-dark(…) l c h / .5)
236
+ ```
237
+
141
238
  ---
142
239
 
143
240
  ## Built-in Units
package/docs/styles.md CHANGED
@@ -254,6 +254,7 @@ Background color with design token support. Preferred over `backgroundColor` and
254
254
  | `"#purple.10"` | Token color at 10% opacity |
255
255
  | `"#surface #primary.10"` | Background `#surface` with `#primary.10` overlay (two colors enable smooth transitions between both) |
256
256
  | `"rgb(255 128 0)"` | CSS color value |
257
+ | `"color-mix(in oklab, #primary 50%, #surface)"` | CSS color function — see [CSS Color Functions](dsl.md#css-color-functions) |
257
258
  | `true` | Default fill color |
258
259
 
259
260
  When two colors are provided, the first sets the background color and the second is applied as an overlay gradient layer. This enables independent CSS transitions on each color. The overlay is only applied when no explicit `image` or `backgroundImage` is set.
@@ -286,9 +287,10 @@ Text color with design token support.
286
287
  | `"#current"` | Current inherited color |
287
288
  | `"#current.5"` | Current inherited color at 50% opacity |
288
289
  | `"(#primary, #secondary)"` | Fallback: use `#primary`, fall back to `#secondary` |
290
+ | `"light-dark(#dark, #light)"` | CSS color function — see [CSS Color Functions](dsl.md#css-color-functions) |
289
291
  | `true` | `currentColor` |
290
292
 
291
- When set to a named color token, also sets `$current-color` and `$current-color-{colorSpace}` custom properties for downstream use (suffix depends on the configured `colorSpace`, default `oklch`).
293
+ When set to a named color token, also sets `$current-color` and `$current-color-{colorSpace}` custom properties for downstream use (suffix depends on the configured `colorSpace`, default `oklch`). An opacity suffix does not move the companion: `color="#purple.5"` still reports `#purple`'s channels, since components carry no alpha.
292
294
 
293
295
  ### `svgFill`
294
296
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenphi/tasty",
3
- "version": "3.0.1",
3
+ "version": "3.1.0",
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",
@@ -158,39 +158,39 @@
158
158
  "@types/babel__helper-plugin-utils": "^7.10.3",
159
159
  "@types/node": "^25.3.2",
160
160
  "@types/react": "^19.0.0",
161
+ "@vitest/browser-playwright": "^4.1.10",
161
162
  "eslint": "^10.0.0",
162
163
  "eslint-config-prettier": "^10.1.8",
163
- "happy-dom": "^20.9.0",
164
164
  "jiti": "^2.6.1",
165
- "jsdom": "^29.0.2",
166
165
  "knip": "^5.61.3",
167
166
  "next": "^16.0.0",
167
+ "playwright": "^1.62.1",
168
168
  "prettier": "^3.8.1",
169
169
  "react": "^19.0.0",
170
170
  "size-limit": "^12.0.0",
171
171
  "tsdown": "^0.22.0",
172
172
  "typescript": "^6.0.2",
173
173
  "typescript-eslint": "^8.56.0",
174
- "vitest": "^4.0.18"
174
+ "vitest": "^4.1.10"
175
175
  },
176
176
  "size-limit": [
177
177
  {
178
178
  "name": "main (import *)",
179
179
  "path": "dist/index.js",
180
180
  "import": "*",
181
- "limit": "57 kB"
181
+ "limit": "58 kB"
182
182
  },
183
183
  {
184
184
  "name": "core (import *)",
185
185
  "path": "dist/core/index.js",
186
186
  "import": "*",
187
- "limit": "54 kB"
187
+ "limit": "55 kB"
188
188
  },
189
189
  {
190
190
  "name": "static",
191
191
  "path": "dist/static/index.js",
192
192
  "import": "*",
193
- "limit": "18.5 kB"
193
+ "limit": "19.25 kB"
194
194
  },
195
195
  {
196
196
  "name": "zero",
@@ -201,7 +201,7 @@
201
201
  "path",
202
202
  "crypto"
203
203
  ],
204
- "limit": "33 kB"
204
+ "limit": "33.75 kB"
205
205
  },
206
206
  {
207
207
  "name": "babel-plugin",
@@ -212,12 +212,15 @@
212
212
  "path",
213
213
  "crypto"
214
214
  ],
215
- "limit": "50.5 kB"
215
+ "limit": "51.25 kB"
216
216
  }
217
217
  ],
218
218
  "scripts": {
219
219
  "build": "tsdown",
220
220
  "test": "vitest run",
221
+ "test:node": "vitest run --project node",
222
+ "test:browser": "vitest run --project browser",
223
+ "test:setup": "playwright install chromium",
221
224
  "test:watch": "vitest",
222
225
  "test:coverage": "vitest run --coverage",
223
226
  "typecheck": "tsc --noEmit",
@@ -226,7 +229,8 @@
226
229
  "format": "prettier --write \"src/**/*.{ts,tsx}\"",
227
230
  "format:check": "prettier --check \"src/**/*.{ts,tsx}\"",
228
231
  "size": "size-limit",
229
- "bench": "vitest bench",
232
+ "bench": "vitest bench --project node",
233
+ "bench:browser": "vitest bench --project browser",
230
234
  "changeset": "changeset",
231
235
  "version": "changeset version",
232
236
  "release": "changeset publish",