@tenphi/tasty 0.0.0-snapshot.1d29633 → 0.0.0-snapshot.1f1c00a
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/README.md +152 -134
- package/dist/{babel-Cg-mJV_t.d.ts → babel-rZcEwPLM.d.ts} +2 -3
- package/dist/chunks/async-storage-CG5HnUT5.js +20 -0
- package/dist/chunks/async-storage-CG5HnUT5.js.map +1 -0
- package/dist/chunks/build-config-CL4eRu2z.js +42 -0
- package/dist/chunks/build-config-CL4eRu2z.js.map +1 -0
- package/dist/{collector-BMT05AmU.js → chunks/collector-D2ARGT2L.js} +21 -117
- package/dist/chunks/collector-D2ARGT2L.js.map +1 -0
- package/dist/chunks/config-engine-DEmmPPdp.js +272 -0
- package/dist/chunks/config-engine-DEmmPPdp.js.map +1 -0
- package/dist/chunks/css-definitions-DotDkIXt.js +876 -0
- package/dist/chunks/css-definitions-DotDkIXt.js.map +1 -0
- package/dist/chunks/css-resources-P7IBUUZs.js +142 -0
- package/dist/chunks/css-resources-P7IBUUZs.js.map +1 -0
- package/dist/chunks/debug-D8pg9RGI.js +502 -0
- package/dist/chunks/debug-D8pg9RGI.js.map +1 -0
- package/dist/chunks/dsl-DRhFWQzo.js +1378 -0
- package/dist/chunks/dsl-DRhFWQzo.js.map +1 -0
- package/dist/chunks/hydration-TRcz5vnk.js +41 -0
- package/dist/chunks/hydration-TRcz5vnk.js.map +1 -0
- package/dist/chunks/react-runtime-BpxlOTbO.js +562 -0
- package/dist/chunks/react-runtime-BpxlOTbO.js.map +1 -0
- package/dist/chunks/runtime-engine-CKFXUH-A.js +2416 -0
- package/dist/chunks/runtime-engine-CKFXUH-A.js.map +1 -0
- package/dist/chunks/runtime-state-BxaLhVdP.js +127 -0
- package/dist/chunks/runtime-state-BxaLhVdP.js.map +1 -0
- package/dist/{merge-styles-CkAcnd0Z.js → chunks/shared-utils-y-HjvPN4.js} +60 -55
- package/dist/chunks/shared-utils-y-HjvPN4.js.map +1 -0
- package/dist/chunks/style-engine-BBbBuSkT.js +5260 -0
- package/dist/chunks/style-engine-BBbBuSkT.js.map +1 -0
- package/dist/{css-writer-b02uBqNF.js → chunks/zero-engine-gJrKBRiw.js} +6 -121
- package/dist/chunks/zero-engine-gJrKBRiw.js.map +1 -0
- package/dist/{collector-BFuqdF2F.d.ts → collector-w4mBj5gd.d.ts} +23 -4
- package/dist/{config-DmFxnBxg.d.ts → config-CZWCzBr6.d.ts} +171 -72
- package/dist/core/index.d.ts +5 -5
- package/dist/core/index.js +9 -6
- package/dist/{index-D1nMkpUe.d.ts → index-G9JuwM9p.d.ts} +263 -247
- package/dist/index.d.ts +5 -5
- package/dist/index.js +11 -921
- package/dist/{merge-styles-Cx_6ySBv.d.ts → merge-styles-VXwJ0Frr.d.ts} +2 -3
- package/dist/ssr/astro-client.d.ts +1 -1
- package/dist/ssr/astro-client.js +1 -14
- package/dist/ssr/astro-client.js.map +1 -1
- package/dist/ssr/astro-middleware-extract-static.d.ts +10 -0
- package/dist/ssr/astro-middleware-extract-static.js +9 -0
- package/dist/ssr/astro-middleware-extract-static.js.map +1 -0
- package/dist/ssr/astro-middleware-extract.d.ts +10 -0
- package/dist/ssr/astro-middleware-extract.js +9 -0
- package/dist/ssr/astro-middleware-extract.js.map +1 -0
- package/dist/ssr/astro-middleware-static.d.ts +3 -2
- package/dist/ssr/astro-middleware-static.js +0 -11
- package/dist/ssr/astro-middleware-static.js.map +1 -1
- package/dist/ssr/astro-middleware.d.ts +3 -2
- package/dist/ssr/astro-middleware.js +0 -12
- package/dist/ssr/astro-middleware.js.map +1 -1
- package/dist/ssr/astro.d.ts +40 -6
- package/dist/ssr/astro.js +157 -93
- package/dist/ssr/astro.js.map +1 -1
- package/dist/ssr/index.d.ts +10 -11
- package/dist/ssr/index.js +4 -4
- package/dist/ssr/index.js.map +1 -1
- package/dist/ssr/next-config.d.ts +64 -0
- package/dist/ssr/next-config.js +128 -0
- package/dist/ssr/next-config.js.map +1 -0
- package/dist/ssr/next.d.ts +9 -8
- package/dist/ssr/next.js +29 -43
- package/dist/ssr/next.js.map +1 -1
- package/dist/static/index.d.ts +8 -9
- package/dist/static/index.js +4 -22
- package/dist/static/index.js.map +1 -1
- package/dist/static/inject.d.ts +1 -2
- package/dist/{index-LC3O3Jj4.d.ts → types-JEY8PVYP.d.ts} +862 -811
- package/dist/zero/babel.d.ts +1 -1
- package/dist/zero/babel.js +41 -107
- package/dist/zero/babel.js.map +1 -1
- package/dist/zero/index.d.ts +6 -7
- package/dist/zero/index.js +1 -1
- package/dist/zero/next.d.ts +3 -5
- package/dist/zero/next.js +0 -31
- package/dist/zero/next.js.map +1 -1
- package/docs/README.md +13 -8
- package/docs/adoption.md +56 -31
- package/docs/ai-agents.md +107 -80
- package/docs/comparison.md +69 -40
- package/docs/configuration.md +179 -114
- package/docs/debug.md +19 -7
- package/docs/design-system.md +41 -22
- package/docs/dsl.md +310 -201
- package/docs/getting-started.md +64 -41
- package/docs/injector.md +63 -26
- package/docs/methodology.md +44 -38
- package/docs/migration-v3.md +49 -49
- package/docs/pipeline.md +32 -8
- package/docs/plugins.md +38 -34
- package/docs/react-api.md +67 -44
- package/docs/runtime-benchmarks.md +388 -0
- package/docs/ssr.md +219 -61
- package/docs/styles.md +95 -18
- package/docs/tasty-static.md +138 -105
- package/package.json +104 -33
- package/dist/async-storage-DKK-wTD4.js +0 -44
- package/dist/async-storage-DKK-wTD4.js.map +0 -1
- package/dist/collector-BMT05AmU.js.map +0 -1
- package/dist/config-DF93rL3u.js +0 -12661
- package/dist/config-DF93rL3u.js.map +0 -1
- package/dist/context-CA8YKeMn.js +0 -24
- package/dist/context-CA8YKeMn.js.map +0 -1
- package/dist/core-B5ybnMP1.js +0 -1529
- package/dist/core-B5ybnMP1.js.map +0 -1
- package/dist/css-writer-b02uBqNF.js.map +0 -1
- package/dist/format-global-rules-DklyaXv-.js +0 -22
- package/dist/format-global-rules-DklyaXv-.js.map +0 -1
- package/dist/format-rules-Cs4e3M9m.js +0 -130
- package/dist/format-rules-Cs4e3M9m.js.map +0 -1
- package/dist/hydrate-aaUSNJZJ.js +0 -37
- package/dist/hydrate-aaUSNJZJ.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/keyframes-BedEzEXk.js +0 -424
- package/dist/keyframes-BedEzEXk.js.map +0 -1
- package/dist/merge-styles-CkAcnd0Z.js.map +0 -1
- package/dist/resolve-recipes-CiyLtJG9.js +0 -144
- package/dist/resolve-recipes-CiyLtJG9.js.map +0 -1
package/docs/migration-v3.md
CHANGED
|
@@ -21,30 +21,30 @@ Most of the work is mechanical renames — see the [search-and-replace cheat she
|
|
|
21
21
|
|
|
22
22
|
## At a glance
|
|
23
23
|
|
|
24
|
-
| v2.11
|
|
25
|
-
|
|
26
|
-
| `'@properties': { … }`
|
|
27
|
-
| `'@fontFace': { … }`
|
|
28
|
-
| `'@counterStyle': { … }`
|
|
29
|
-
| `padding: '2x 4x top right'`
|
|
30
|
-
| `fade: '3x 1x top bottom'`
|
|
31
|
-
| `configure({ funcs })`
|
|
32
|
-
| `configure({ fontFace })`
|
|
33
|
-
| `configure({ counterStyle })`
|
|
34
|
-
| `getCssText()`
|
|
35
|
-
| `getCssTextForNode()`
|
|
36
|
-
| `getCssTextForClasses()`
|
|
37
|
-
| `getGlobalFontFace()`
|
|
38
|
-
| `getGlobalCounterStyle()`
|
|
39
|
-
| `getGlobalFunction()`
|
|
40
|
-
| `okhslFunc` / `okhstFunc`
|
|
41
|
-
| `CssOptions`
|
|
42
|
-
| `ParserOptions.funcs`
|
|
43
|
-
| `StyleParser#setFuncs()`
|
|
44
|
-
| `ChunkInfo` (from `tastyDebug`) | `DebugChunkInfo`
|
|
45
|
-
| `@tenphi/tasty/next`
|
|
46
|
-
| `getIsTestEnvironment()`
|
|
47
|
-
| `hydrateTastyCache()`
|
|
24
|
+
| v2.11 | v3.0 | Find it with |
|
|
25
|
+
| ------------------------------- | --------------------------------- | ------------------------------ |
|
|
26
|
+
| `'@properties': { … }` | `'@property': { … }` | `rg "'@properties'"` |
|
|
27
|
+
| `'@fontFace': { … }` | `'@font-face': { … }` | `rg "'@fontFace'"` |
|
|
28
|
+
| `'@counterStyle': { … }` | `'@counter-style': { … }` | `rg "'@counterStyle'"` |
|
|
29
|
+
| `padding: '2x 4x top right'` | `padding: '2x top, 4x right'` | dev-mode console warning |
|
|
30
|
+
| `fade: '3x 1x top bottom'` | `fade: '3x top, 1x bottom'` | dev-mode console warning |
|
|
31
|
+
| `configure({ funcs })` | `configure({ functions })` | `rg "funcs"` |
|
|
32
|
+
| `configure({ fontFace })` | `configure({ fontFaces })` | `rg "fontFace:"` |
|
|
33
|
+
| `configure({ counterStyle })` | `configure({ counterStyles })` | `rg "counterStyle:"` |
|
|
34
|
+
| `getCssText()` | `getCSSText()` | `rg "getCssText"` |
|
|
35
|
+
| `getCssTextForNode()` | `getCSSTextForNode()` | `rg "getCssTextForNode"` |
|
|
36
|
+
| `getCssTextForClasses()` | `getCSSTextForClasses()` | `rg "getCssTextForClasses"` |
|
|
37
|
+
| `getGlobalFontFace()` | `getGlobalFontFaces()` | `rg "getGlobalFontFace\b"` |
|
|
38
|
+
| `getGlobalCounterStyle()` | `getGlobalCounterStyles()` | `rg "getGlobalCounterStyle\b"` |
|
|
39
|
+
| `getGlobalFunction()` | `getGlobalFunctions()` | `rg "getGlobalFunction\b"` |
|
|
40
|
+
| `okhslFunc` / `okhstFunc` | `okhslFunction` / `okhstFunction` | `rg "okhs[lt]Func\b"` |
|
|
41
|
+
| `CssOptions` | `CSSOptions` | `rg "CssOptions"` |
|
|
42
|
+
| `ParserOptions.funcs` | `ParserOptions.functions` | `rg "funcs:"` |
|
|
43
|
+
| `StyleParser#setFuncs()` | `StyleParser#setFunctions()` | `rg "setFuncs"` |
|
|
44
|
+
| `ChunkInfo` (from `tastyDebug`) | `DebugChunkInfo` | `rg "ChunkInfo"` |
|
|
45
|
+
| `@tenphi/tasty/next` | `@tenphi/tasty/zero/next` | `rg "tasty/next"` |
|
|
46
|
+
| `getIsTestEnvironment()` | `isTestEnvironment()` | `rg "getIsTestEnvironment"` |
|
|
47
|
+
| `hydrateTastyCache()` | `hydrateTastyClasses()` | `rg "hydrateTastyCache"` |
|
|
48
48
|
|
|
49
49
|
---
|
|
50
50
|
|
|
@@ -73,7 +73,7 @@ At-rule keys now match the real CSS at-rule names Tasty already emits, so they a
|
|
|
73
73
|
|
|
74
74
|
**This is the one change that can alter rendered CSS without a rename.** A style group that names direction modifiers now takes a **single** value, applied to every direction it names.
|
|
75
75
|
|
|
76
|
-
The old form looked positional but never was: the parser buckets values and modifiers into separate arrays per comma group, so `'2x 4x top right'`, `'2x top 4x right'`, and `'top 2x right 4x'` were all the same input, and the pairing was decided by the order the
|
|
76
|
+
The old form looked positional but never was: the parser buckets values and modifiers into separate arrays per comma group, so `'2x 4x top right'`, `'2x top 4x right'`, and `'top 2x right 4x'` were all the same input, and the pairing was decided by the order the _modifiers_ happened to appear in. `'1x 2x right top'` and `'1x 2x top right'` produced different CSS, and `'1x 2x top top'` silently assigned `top` twice.
|
|
77
77
|
|
|
78
78
|
Per-side values now come from comma-separated groups:
|
|
79
79
|
|
|
@@ -155,25 +155,25 @@ A key whose prefix doesn't match its value type is ignored with a development wa
|
|
|
155
155
|
|
|
156
156
|
## 5. Removed exports
|
|
157
157
|
|
|
158
|
-
| Removed
|
|
159
|
-
|
|
160
|
-
| `getIsTestEnvironment()`
|
|
161
|
-
| `hydrateTastyCache()`
|
|
162
|
-
| `customFunc()`
|
|
163
|
-
| `getGlobalFuncs()`
|
|
164
|
-
| `resetGlobalFuncs()`
|
|
165
|
-
| `setGlobalPredefinedTokens()`
|
|
166
|
-
| `resetGlobalPredefinedTokens()`
|
|
167
|
-
| `setMiddlewareTransferCache()` / `getMiddlewareTransferCache()` | internal; no replacement needed
|
|
168
|
-
| `clearWriterCache()`
|
|
169
|
-
| `DEFAULT_PLUGINS`
|
|
170
|
-
| `registerDefaultFunctions()`
|
|
171
|
-
| `warn()` / `deprecationWarning()`
|
|
172
|
-
| `Bucket` enum
|
|
173
|
-
| `Props` type
|
|
174
|
-
| `UseStylesOptions`
|
|
175
|
-
| `GlobalStyledProps`
|
|
176
|
-
| duplicate `PropertyOptions`
|
|
158
|
+
| Removed | Use instead |
|
|
159
|
+
| --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
160
|
+
| `getIsTestEnvironment()` | `isTestEnvironment()` |
|
|
161
|
+
| `hydrateTastyCache()` | `hydrateTastyClasses()` |
|
|
162
|
+
| `customFunc()` | `configure({ functions })` |
|
|
163
|
+
| `getGlobalFuncs()` | `getGlobalFunctions()` for `@function` definitions; the parse-function registry is internal |
|
|
164
|
+
| `resetGlobalFuncs()` | `resetConfig()` |
|
|
165
|
+
| `setGlobalPredefinedTokens()` | `configure({ replaceTokens })` |
|
|
166
|
+
| `resetGlobalPredefinedTokens()` | `resetConfig()` |
|
|
167
|
+
| `setMiddlewareTransferCache()` / `getMiddlewareTransferCache()` | internal; no replacement needed |
|
|
168
|
+
| `clearWriterCache()` | internal test utility |
|
|
169
|
+
| `DEFAULT_PLUGINS` | nothing — it was never consumed, and passing it to `configure({ plugins })` was a no-op. The default `okhsl`/`okhst` functions are registered automatically. |
|
|
170
|
+
| `registerDefaultFunctions()` | internal bootstrap helper |
|
|
171
|
+
| `warn()` / `deprecationWarning()` | internal helpers with no callers |
|
|
172
|
+
| `Bucket` enum | parser-internal |
|
|
173
|
+
| `Props` type | inline `Record<string, any>` |
|
|
174
|
+
| `UseStylesOptions` | `useStyles()` takes `Styles \| undefined` directly |
|
|
175
|
+
| `GlobalStyledProps` | its only member was the already-dead `breakpoints` prop |
|
|
176
|
+
| duplicate `PropertyOptions` | the single `PropertyOptions` from `injector/types` |
|
|
177
177
|
|
|
178
178
|
`getGlobalFuncs()` deserves a note: it returned the **live mutable internal registry**, and writing to it bypassed the parser's cache invalidation, so the write silently never took effect. If you were mutating it, move to `configure({ functions })`.
|
|
179
179
|
|
|
@@ -192,13 +192,13 @@ A key whose prefix doesn't match its value type is ignored with a development wa
|
|
|
192
192
|
+ import { withTasty } from '@tenphi/tasty/zero/next';
|
|
193
193
|
```
|
|
194
194
|
|
|
195
|
-
The
|
|
195
|
+
The build-time extraction Next.js wrapper already lived under the `zero` namespace internally; the top-level `/next` path collided confusingly with `@tenphi/tasty/ssr/next`, which is a different integration.
|
|
196
196
|
|
|
197
197
|
---
|
|
198
198
|
|
|
199
199
|
## 8. Behavior changes that are not renames
|
|
200
200
|
|
|
201
|
-
**Repeated `configure()` calls now merge.** `recipes`, `keyframes`, `properties`, `fontFaces`, `counterStyles`, and CSS `@function` definitions used to replace wholesale, so a design-system `configure()` followed by an application `configure()` silently dropped the design system's values. All of them now merge, with the later call winning on a key conflict — matching `tokens` and `globalStyles`, which already merged. If you were relying on a second call to
|
|
201
|
+
**Repeated `configure()` calls now merge.** `recipes`, `keyframes`, `properties`, `fontFaces`, `counterStyles`, and CSS `@function` definitions used to replace wholesale, so a design-system `configure()` followed by an application `configure()` silently dropped the design system's values. All of them now merge, with the later call winning on a key conflict — matching `tokens` and `globalStyles`, which already merged. If you were relying on a second call to _clear_ a collection, use `resetConfig()`.
|
|
202
202
|
|
|
203
203
|
`configure({ polyfills })` is now correctly rejected after the first render, like every sibling option, and shallow-merges so an unrelated later call cannot switch a polyfill off.
|
|
204
204
|
|
|
@@ -208,7 +208,7 @@ The zero-runtime Next.js wrapper already lived under the `zero` namespace intern
|
|
|
208
208
|
|
|
209
209
|
**Replacing a built-in style handler now warns.** Built-in handlers are shared across style names, so `configure({ handlers: { fill } })` also takes over `image` and the whole `background-*` family, and `configure({ handlers: { display } })` takes down `flow`, `gap`, `hide`, `overflow`, `whiteSpace`, and `textOverflow`. This was always true; what's new is that a development-mode warning now names exactly what was displaced. If you see it, either declare the whole group and delegate the rest to `styleHandlers.*`, or pick a narrower name. See [Plugins](plugins.md#custom-style-handlers).
|
|
210
210
|
|
|
211
|
-
**Multi-dependency custom handlers are fixed.** A handler whose dependencies mixed known and unknown style names was invoked once per chunk with only
|
|
211
|
+
**Multi-dependency custom handlers are fixed.** A handler whose dependencies mixed known and unknown style names was invoked once per chunk with only _part_ of its dependencies, and could emit stale CSS. Unknown names now join their handler's chunk, so it is invoked once with all of them. No built-in style moved, so no existing class name changes.
|
|
212
212
|
|
|
213
213
|
**`isChecked` added to the root prop types.** It already worked at runtime; it was just missing from `BaseProps` / `AllBaseProps`.
|
|
214
214
|
|
|
@@ -220,7 +220,7 @@ The zero-runtime Next.js wrapper already lived under the `zero` namespace intern
|
|
|
220
220
|
|
|
221
221
|
Nothing here is required to upgrade.
|
|
222
222
|
|
|
223
|
-
- **CSS `@function` support** — reusable, parameterized functions via the `'@function'` styles key, the `useFunction` style function, or `configure({ functions })`. Works across client, SSR/RSC, and
|
|
223
|
+
- **CSS `@function` support** — reusable, parameterized functions via the `'@function'` styles key, the `useFunction` style function, or `configure({ functions })`. Works across client rendering, SSR/RSC, and build-time extraction. `configure({ polyfills: { functions: true } })` inlines every call into plain CSS for browsers that don't ship the at-rule yet. See [Style DSL](dsl.md#functions-function).
|
|
224
224
|
- **Custom color plugins with no core special-casing** — `okhsl`/`okhst` are now ordinary one-line plugins, and a third-party color space can achieve identical integration. New `createColorFunc` and `resolveFunctionColor` helpers.
|
|
225
225
|
- **`configure({ propHandlers })`** — props middleware for every component: props in, props out. The extension point for props that aren't style values.
|
|
226
226
|
- **`configure({ baseStyleProps })`** — expose style properties as props on every component without listing them in each `styleProps`.
|
|
@@ -279,7 +279,7 @@ Worth stating explicitly, because it's easy to assume otherwise:
|
|
|
279
279
|
|
|
280
280
|
- **At-rule styles-object keys are CSS-spec-faithful, not JS-conventional.** `@keyframes`, `@property`, `@font-face`, `@counter-style`, `@function`, `@starting` — kebab-case, matching what Tasty emits.
|
|
281
281
|
- **The `func()` injector method stays abbreviated.** `function` is a reserved word.
|
|
282
|
-
- `injector.fontFace()` and `injector.counterStyle()` keep their singular names — only the
|
|
282
|
+
- `injector.fontFace()` and `injector.counterStyle()` keep their singular names — only the _config keys_ and _getters_ were pluralized.
|
|
283
283
|
- `useFontFace` and `useCounterStyle` are unchanged.
|
|
284
284
|
- `configure({ properties })` and `autoPropertyTypes` are unchanged; only the styles-object `@property` key was renamed.
|
|
285
285
|
- **The deprecated style aliases are retained deliberately.** `backgroundColor`, `background`, `backgroundImage`, `flex`, `grid`, and `flexDirection` still carry `@deprecated` tags and still work. They are the guardrail that discourages their use in projects that have not enabled `@tenphi/eslint-plugin-tasty` yet — read the tags as guidance, not as a removal notice.
|
package/docs/pipeline.md
CHANGED
|
@@ -86,18 +86,22 @@ Output: CSSRule[]
|
|
|
86
86
|
|
|
87
87
|
### What It Does
|
|
88
88
|
|
|
89
|
-
Runs on each style's value map **before** any parsing
|
|
89
|
+
Runs on each style's value map **before** any parsing, when the map has at least three keys and one of them uses `&`. An atom is a **don't-care** when flipping it never changes which value the map resolves to; every key that mentions a don't-care atom is dropped.
|
|
90
90
|
|
|
91
91
|
### How It Works
|
|
92
92
|
|
|
93
|
-
1.
|
|
94
|
-
2.
|
|
95
|
-
3.
|
|
96
|
-
4. Drop
|
|
93
|
+
1. Split each key into its top-level `&` atoms. Keys containing `|`, `^`, or `,` at top level are a single opaque atom. The `_` floor (and a misused `_` key) is kept verbatim and left out of the analysis.
|
|
94
|
+
2. Treat the map as the cascade it is: the last authored key whose atoms all hold wins, and `''` is always the lowest priority.
|
|
95
|
+
3. An atom is a don't-care when, in every state, the cascade resolves to the same value with the atom present as with it absent. Only the states `(K₁ minus the atom) ∪ K₀`, for every key `K₁` that contains the atom and every key `K₀` that does not (or none), can tell the two apart, so the check is quadratic in the number of keys rather than exponential in atoms.
|
|
96
|
+
4. Drop every key that contains a don't-care atom. With the atom forced absent those keys never match, and by definition the result is unchanged.
|
|
97
|
+
|
|
98
|
+
The check is over the cascade, not over pairs of keys. In `{ '': A, checked: B, 'invalid & checked': C, disabled: A }`, `disabled` has the default's value, but it outranks `checked`: a checked, disabled element gets `A`, so `disabled` is not a don't-care. (An earlier pairwise rule — "every key with the atom has a same-value partner without it" — folded `disabled` into `''` here and lost that priority.)
|
|
99
|
+
|
|
100
|
+
Atoms are compared as strings and treated as independent. Real atoms can be related (`theme=dark` vs `theme=light`, nested media ranges); that only rules states out, so a don't-care over independent atoms is one for every real state too. Two guards keep the rewrite safe: a map with more than 32 distinct atoms is left unreduced, and a reduction that would leave the `_` floor alone with `''` (which means "use the floor", see Stage 1) is skipped.
|
|
97
101
|
|
|
98
102
|
### Why
|
|
99
103
|
|
|
100
|
-
Removing don't-care dimensions before parsing prevents combinatorial blowup in later stages. `mergeEntriesByValue`, `buildExclusiveConditions`, and materialization all see fewer entries and fewer spurious conditions.
|
|
104
|
+
Removing don't-care dimensions before parsing prevents combinatorial blowup in later stages. `mergeEntriesByValue`, `buildExclusiveConditions`, and materialization all see fewer entries and fewer spurious conditions.
|
|
101
105
|
|
|
102
106
|
### Example
|
|
103
107
|
|
|
@@ -197,6 +201,8 @@ The merge is safe iff for every entry `e_m` strictly between them in priority wi
|
|
|
197
201
|
|
|
198
202
|
i.e. there is no scenario where the intermediate state could have matched, the lower same-value entry would also have matched, and the higher one would not. This is the only way the merge could leak through and shadow the intermediate state.
|
|
199
203
|
|
|
204
|
+
An intermediate whose condition always holds (a tautological key, or same-value keys that already merged into one, like `beta | !beta`) is checked like any other. Only the `''` default and the `_` floor are exempt from the check, and they are always the lowest priorities, so they are never intermediates.
|
|
205
|
+
|
|
200
206
|
### Why
|
|
201
207
|
|
|
202
208
|
Without this pass a value map like `{ '@dark': 'red', '@dark & @hc': 'red' }` would create two separate entries that later produce two CSS rules with identical output. Merging before exclusive building keeps the exclusive condition algebra small and avoids duplicate CSS. The safety check ensures we never break the cascade in service of this optimization.
|
|
@@ -293,7 +299,9 @@ B: B & !A (applies only when A doesn't)
|
|
|
293
299
|
C: C & !A & !B (applies only when neither A nor B)
|
|
294
300
|
```
|
|
295
301
|
|
|
296
|
-
Each exclusive condition is passed through `simplifyCondition`. Entries that simplify to `FALSE` (impossible) are filtered out.
|
|
302
|
+
Each exclusive condition is passed through `simplifyCondition`. Entries that simplify to `FALSE` (impossible) are filtered out.
|
|
303
|
+
|
|
304
|
+
Every entry is added to the "prior" list, including one whose condition is `TRUE`. That can be a tautological key, or same-value keys that merged into one in Stage 1b (`alpha: X, '!alpha': X` → `TRUE`). Such an entry always applies at its priority, so negating it correctly makes every lower entry `FALSE`. The bare `''` default is `TRUE` too, but it is always the lowest priority, so no entry comes after it.
|
|
297
305
|
|
|
298
306
|
### `_` fallback floor
|
|
299
307
|
|
|
@@ -383,7 +391,7 @@ The pre-build Stage 2a pass doesn't need this because user-authored ORs aren't p
|
|
|
383
391
|
|
|
384
392
|
### Example (conceptual)
|
|
385
393
|
|
|
386
|
-
See the comment block in `exclusive.ts
|
|
394
|
+
See the comment block on `expandExclusiveOrs` in `exclusive.ts`: a default value whose higher-priority sibling is `@supports(...) & :has(...)` gets an exclusive of `!@supports | !:has`. Expansion yields one branch under `@supports (not ...)` and another under `@supports (...) { :not(:has()) }` instead of a bare `:not(:has())` rule.
|
|
387
395
|
|
|
388
396
|
---
|
|
389
397
|
|
|
@@ -395,6 +403,8 @@ See the comment block in `exclusive.ts:500-523`: a default value whose higher-pr
|
|
|
395
403
|
|
|
396
404
|
Computes the Cartesian product of all style entries for a handler, creating snapshots of which value each style has for each possible state combination.
|
|
397
405
|
|
|
406
|
+
When every input is a direct value rather than a state map, the pipeline skips stages 0–4 and passes one unconditional snapshot directly to the handler. This avoids building exclusive entries and a Cartesian product for ordinary state-free styles.
|
|
407
|
+
|
|
398
408
|
### How It Works
|
|
399
409
|
|
|
400
410
|
1. Collect exclusive entries for each style the handler uses
|
|
@@ -454,6 +464,7 @@ Style handlers encapsulate the logic for translating design tokens (like `'2x'`)
|
|
|
454
464
|
### What It Does
|
|
455
465
|
|
|
456
466
|
Combines rules that have identical CSS output into a single rule with an OR condition.
|
|
467
|
+
Zero- and one-rule inputs return unchanged because no merge is possible.
|
|
457
468
|
|
|
458
469
|
### How It Works
|
|
459
470
|
|
|
@@ -739,3 +750,16 @@ Impossible combinations are detected at multiple levels (simplification, variant
|
|
|
739
750
|
### 4. Aggressive Caching
|
|
740
751
|
|
|
741
752
|
Parse, simplify, condition-to-CSS, and full-pipeline results are cached independently, enabling fast re-rendering when only parts of the style object change.
|
|
753
|
+
|
|
754
|
+
Cache keys must respect key order where it carries meaning. A state map resolves by key order, so `{ '': A, checked: B, disabled: C }` and `{ '': A, disabled: C, checked: B }` are different styles and must get different keys (and classes). Only the style keys of a styles object, including a sub-element's, are order-insensitive, so they may be sorted. `stringifyStyles` (pipeline cache) and `generateChunkCacheKey` (class allocation) both follow this rule.
|
|
755
|
+
|
|
756
|
+
---
|
|
757
|
+
|
|
758
|
+
## Testing Priority
|
|
759
|
+
|
|
760
|
+
Every stage above is an optimization that must leave one thing unchanged: in any state, the value that applies is the value of the last authored key whose condition holds. `src/test/state-oracle.ts` states that rule directly, with none of the pipeline's machinery, and two suites compare the pipeline against it:
|
|
761
|
+
|
|
762
|
+
- `src/pipeline/exclusive.test.ts` (Node) evaluates the conditions after each of Stages 0–3 on thousands of generated maps, and names the stage that disagrees.
|
|
763
|
+
- `src/pipeline/state-priority.test.ts` (browser) renders hand-written, permuted, and generated maps to CSS, applies every combination of states (modifiers, value modifiers, `:has()`, `@root`, `@parent`, `@own`, media, supports), and compares what the browser computes against the oracle. It also checks that rules stay mutually exclusive, which a correct computed value alone can hide. It runs through the pipeline, the runtime injector, and the SSR collector, with all maps mounted at once so a cache-key collision shows up.
|
|
764
|
+
|
|
765
|
+
A change to any stage should keep both green. When a generated map fails, the message includes the map, the failing states, and the CSS; the map is usually worth adding to the hand-written cases once it is reduced.
|
package/docs/plugins.md
CHANGED
|
@@ -43,23 +43,23 @@ configure({ plugins: [spacingPlugin()] });
|
|
|
43
43
|
|
|
44
44
|
## Choosing an extension point
|
|
45
45
|
|
|
46
|
-
| You want to add
|
|
47
|
-
|
|
48
|
-
| New value syntax — `okhsl(…)`, `double(2x)` | `functions` (bare key)
|
|
49
|
-
| A reusable CSS `@function`
|
|
50
|
-
| A new unit — `2gu`
|
|
51
|
-
| A new state alias — `@mobile`
|
|
52
|
-
| A style property → **CSS declarations**
|
|
53
|
-
| A style property on every component
|
|
54
|
-
| A **component prop** → anything
|
|
55
|
-
| A named bundle of styles
|
|
56
|
-
| Typography scale tokens
|
|
57
|
-
| `:root` variables
|
|
58
|
-
| Global selector styles
|
|
46
|
+
| You want to add | Use | Runs | Build-time extraction |
|
|
47
|
+
| ------------------------------------------- | -------------------------- | ---------------------------- | ----------------------- |
|
|
48
|
+
| New value syntax — `okhsl(…)`, `double(2x)` | `functions` (bare key) | parse time, per value | ✅ |
|
|
49
|
+
| A reusable CSS `@function` | `functions` (`$$name` key) | injected once, globally | ✅ |
|
|
50
|
+
| A new unit — `2gu` | `units` | parse time, per value | ✅ |
|
|
51
|
+
| A new state alias — `@mobile` | `states` | state-key parse | ✅ |
|
|
52
|
+
| A style property → **CSS declarations** | `handlers` | pipeline, per state snapshot | ✅ |
|
|
53
|
+
| A style property on every component | `baseStyleProps` | prop harvest | ✅ (styles only) |
|
|
54
|
+
| A **component prop** → anything | `propHandlers` | render, per component | ❌ (no props to run on) |
|
|
55
|
+
| A named bundle of styles | `recipes` | before chunking | ✅ |
|
|
56
|
+
| Typography scale tokens | `presets` | inject time | ✅ |
|
|
57
|
+
| `:root` variables | `tokens`, `replaceTokens` | inject time | ✅ |
|
|
58
|
+
| Global selector styles | `globalStyles` | inject time | ✅ |
|
|
59
59
|
|
|
60
60
|
Two distinctions do most of the work:
|
|
61
61
|
|
|
62
|
-
- **`handlers` vs `propHandlers`.** A handler turns a
|
|
62
|
+
- **`handlers` vs `propHandlers`.** A handler turns a _style property_ into CSS declarations and knows nothing about components. A prop handler turns a _component prop_ into other props — including `styles`, which then flow through the normal handlers. Reach for `propHandlers` when the input isn't a style value (an options object, a domain concept) or when it should affect more than styles.
|
|
63
63
|
- **`recipes` vs `propHandlers`.** A recipe is a static named bundle. A prop handler computes its styles from a value.
|
|
64
64
|
|
|
65
65
|
---
|
|
@@ -123,12 +123,12 @@ configure({
|
|
|
123
123
|
|
|
124
124
|
**The key is the trigger.** By default a handler runs only when a prop matching its key is present, so an absent prop costs one property check rather than a call. Override that with a tuple:
|
|
125
125
|
|
|
126
|
-
| Definition
|
|
127
|
-
|
|
128
|
-
| `fn`
|
|
129
|
-
| `['glaze', fn]`
|
|
130
|
-
| `[['glaze', 'tint'], fn]` | either is present
|
|
131
|
-
| `['*', fn]`
|
|
126
|
+
| Definition | Runs when |
|
|
127
|
+
| ------------------------- | ------------------------------------- |
|
|
128
|
+
| `fn` | a prop named after the key is present |
|
|
129
|
+
| `['glaze', fn]` | `glaze` is present |
|
|
130
|
+
| `[['glaze', 'tint'], fn]` | either is present |
|
|
131
|
+
| `['*', fn]` | always |
|
|
132
132
|
|
|
133
133
|
**Chaining.** Handlers run in registration order — plugins first, then direct `configure()` — and each receives the previous one's output. Registering the same key twice replaces the handler in place, keeping its position.
|
|
134
134
|
|
|
@@ -136,16 +136,16 @@ configure({
|
|
|
136
136
|
|
|
137
137
|
### Rules
|
|
138
138
|
|
|
139
|
-
- **Be pure. Never mutate the input.** Style values are cached by object identity, so mutating a value object in place yields a stale class name
|
|
139
|
+
- **Be pure. Never mutate the input.** Style values are cached by object identity, so mutating a value object in place yields a stale class name _and_ stale CSS. Return fresh objects.
|
|
140
140
|
- **Memoize the styles you build, per input value.** A reference-stable (ideally frozen) styles object lets the cache key reuse its serialization instead of recomputing it on every render. This is the single highest-leverage thing you can do for performance.
|
|
141
141
|
- **Precedence is fixed** and not adjustable from a handler. Styles are merged as `factory defaults → styles → harvested style props`, and injected styles occupy the `styles` slot: they beat a component's own default styles and lose to a style prop passed at the call site. This matches how `recipe` already behaves.
|
|
142
|
-
- **Prefer returning `styles` over bare style props.** Returning `{ fill: '#red' }` only works if `fill` is harvestable on
|
|
142
|
+
- **Prefer returning `styles` over bare style props.** Returning `{ fill: '#red' }` only works if `fill` is harvestable on _that_ component (in its `styleProps`, or promoted via `baseStyleProps`); otherwise it leaks to the DOM.
|
|
143
143
|
|
|
144
144
|
### What it cannot do
|
|
145
145
|
|
|
146
146
|
- **`ref`** — `forwardRef` separates it from props, so it is out of reach.
|
|
147
147
|
- **Sub-elements** (`Card.Title`) — they have no `styles` prop and no style-prop harvest, so middleware does not run on them.
|
|
148
|
-
- **
|
|
148
|
+
- **Build-time extraction** — see [Rendering modes](#rendering-modes-and-caveats).
|
|
149
149
|
|
|
150
150
|
---
|
|
151
151
|
|
|
@@ -161,7 +161,7 @@ configure({ baseStyleProps: ['radius', 'shadow'] });
|
|
|
161
161
|
|
|
162
162
|
Names must be real style properties, must start with a lowercase letter, and must not collide with a prop `tasty()` consumes itself (`as`, `styles`, `variant`, `mods`, `tokens`, …); invalid entries are dropped with a development warning.
|
|
163
163
|
|
|
164
|
-
`configure()` may run
|
|
164
|
+
`configure()` may run _after_ your components are defined — each factory resolves its prop list lazily and refreshes when the registry changes.
|
|
165
165
|
|
|
166
166
|
**Costs and caveats.** Each name adds one property check per render of every component, forever, so keep the list short. The effect is app-global and cannot be scoped to a subtree — use a factory's own `styleProps` for that. Be wary of names that collide with real DOM or component props (`width`, `size`, `color`): promoting one means every component swallows it as a style, including when rendering `as={SomeThirdPartyComponent}`.
|
|
167
167
|
|
|
@@ -211,7 +211,7 @@ export default {
|
|
|
211
211
|
};
|
|
212
212
|
```
|
|
213
213
|
|
|
214
|
-
`propHandlers` keys are JSX props rather than style keys, so they need no entry — but any
|
|
214
|
+
`propHandlers` keys are JSX props rather than style keys, so they need no entry — but any _style_ name or token a handler expands into does.
|
|
215
215
|
|
|
216
216
|
---
|
|
217
217
|
|
|
@@ -246,7 +246,7 @@ function glazeStyles(value: GlazeValue): Styles {
|
|
|
246
246
|
styles = Object.freeze({
|
|
247
247
|
'#glaze-bg': `#${tone}.${intensity}`,
|
|
248
248
|
fill: '#glaze-bg',
|
|
249
|
-
transition: 'fill
|
|
249
|
+
transition: 'fill',
|
|
250
250
|
}) as Styles;
|
|
251
251
|
cache.set(key, styles);
|
|
252
252
|
}
|
|
@@ -274,7 +274,11 @@ export const glazePlugin: TastyPluginFactory = () => ({
|
|
|
274
274
|
|
|
275
275
|
// Typed so `#glaze-bg` animates smoothly instead of snapping.
|
|
276
276
|
properties: {
|
|
277
|
-
'#glaze-bg': {
|
|
277
|
+
'#glaze-bg': {
|
|
278
|
+
syntax: '<color>',
|
|
279
|
+
inherits: false,
|
|
280
|
+
initialValue: 'transparent',
|
|
281
|
+
},
|
|
278
282
|
},
|
|
279
283
|
});
|
|
280
284
|
```
|
|
@@ -302,19 +306,19 @@ declare module '@tenphi/tasty' {
|
|
|
302
306
|
}
|
|
303
307
|
```
|
|
304
308
|
|
|
305
|
-
The object value is unambiguous here precisely because `glaze` is a **prop**. A style
|
|
309
|
+
The object value is unambiguous here precisely because `glaze` is a **prop**. A style _value_ of the same shape would be indistinguishable from a state map (`{ tone: … }` looks exactly like `{ hovered: … }`), which is why this belongs in `propHandlers` rather than `handlers`.
|
|
306
310
|
|
|
307
311
|
---
|
|
308
312
|
|
|
309
313
|
## Rendering modes and caveats
|
|
310
314
|
|
|
311
|
-
| Mode
|
|
312
|
-
|
|
313
|
-
| Client
|
|
314
|
-
| SSR / RSC
|
|
315
|
-
|
|
|
315
|
+
| Mode | `functions` / `units` / `states` / `handlers` / `recipes` / `tokens` | `propHandlers` |
|
|
316
|
+
| -------------------------- | -------------------------------------------------------------------- | -------------- |
|
|
317
|
+
| Client | ✅ | ✅ |
|
|
318
|
+
| SSR / RSC | ✅ | ✅ |
|
|
319
|
+
| Build-time (`tastyStatic`) | ✅ at build time | not applicable |
|
|
316
320
|
|
|
317
|
-
**
|
|
321
|
+
**Build-time extraction.** The Babel plugin transforms `tastyStatic()` calls, which take styles objects — there are no props in that pipeline, so there is nothing for props middleware to run on. Components rendered through `tasty()` are untouched by the plugin; `propHandlers` work whether those components render on the server or in the browser. Everything in the `tastyStatic()` path runs at build time, which is why handlers, functions, and units must be pure functions of their input.
|
|
318
322
|
|
|
319
323
|
**Server and client must configure identically.** Class names are derived from resolved styles, so a handler or promoted prop registered on one side and not the other produces a hydration mismatch. This is the same requirement as `namePrefix` — see [SSR → Hydration mismatch warnings](ssr.md#hydration-mismatch-warnings).
|
|
320
324
|
|