@tenphi/tasty 3.11.0 → 3.11.2
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 +13 -9
- package/dist/chunks/{build-config-5d8WlQbq.js → build-config-DlxpZKk_.js} +4 -4
- package/dist/chunks/{build-config-5d8WlQbq.js.map → build-config-DlxpZKk_.js.map} +1 -1
- package/dist/chunks/{collector-CjCak4iE.js → collector-B4-yqFEs.js} +4 -4
- package/dist/chunks/{collector-CjCak4iE.js.map → collector-B4-yqFEs.js.map} +1 -1
- package/dist/chunks/{config-engine-uOFYZidB.js → config-engine-DJsb5WYu.js} +3 -3
- package/dist/chunks/{config-engine-uOFYZidB.js.map → config-engine-DJsb5WYu.js.map} +1 -1
- package/dist/chunks/{css-definitions-ubrHP97m.js → css-definitions-DW1TGGiy.js} +2 -2
- package/dist/chunks/{css-definitions-ubrHP97m.js.map → css-definitions-DW1TGGiy.js.map} +1 -1
- package/dist/chunks/{debug-CJ31U_WE.js → debug-DMMwe1rY.js} +4 -4
- package/dist/chunks/{debug-CJ31U_WE.js.map → debug-DMMwe1rY.js.map} +1 -1
- package/dist/chunks/{react-runtime-CyyCI02N.js → react-runtime-DhPtFlZs.js} +5 -5
- package/dist/chunks/react-runtime-DhPtFlZs.js.map +1 -0
- package/dist/chunks/{runtime-engine-BQmOu5-W.js → runtime-engine-BlrVXv8l.js} +5 -5
- package/dist/chunks/{runtime-engine-BQmOu5-W.js.map → runtime-engine-BlrVXv8l.js.map} +1 -1
- package/dist/chunks/{runtime-state-DEsyUMU-.js → runtime-state-BLyMRYFF.js} +2 -2
- package/dist/chunks/{runtime-state-DEsyUMU-.js.map → runtime-state-BLyMRYFF.js.map} +1 -1
- package/dist/chunks/{style-engine-BX2LshDZ.js → style-engine-of5Xbc27.js} +139 -34
- package/dist/chunks/style-engine-of5Xbc27.js.map +1 -0
- package/dist/chunks/{zero-engine-Dw0P4yGM.js → zero-engine-D2jPSpWA.js} +5 -5
- package/dist/chunks/{zero-engine-Dw0P4yGM.js.map → zero-engine-D2jPSpWA.js.map} +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +6 -6
- package/dist/{index-xtey-KDu.d.ts → index--GdqCPlp.d.ts} +16 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +7 -7
- package/dist/ssr/astro.js +2 -2
- package/dist/ssr/index.js +2 -2
- package/dist/ssr/next-config.js +3 -3
- package/dist/ssr/next.js +3 -3
- package/dist/zero/babel.js +4 -4
- package/dist/zero/index.js +3 -3
- package/docs/ai-agents.md +5 -1
- package/docs/configuration.md +2 -1
- package/docs/design-system.md +1 -1
- package/docs/dsl.md +42 -3
- package/docs/injector.md +3 -1
- package/docs/methodology.md +7 -7
- package/docs/migration-v3.md +1 -1
- package/docs/pipeline.md +1 -1
- package/docs/plugins.md +1 -1
- package/docs/react-api.md +51 -8
- package/docs/ssr.md +29 -23
- package/docs/styles.md +6 -0
- package/docs/tasty-static.md +33 -16
- package/package.json +5 -5
- package/dist/chunks/react-runtime-CyyCI02N.js.map +0 -1
- package/dist/chunks/style-engine-BX2LshDZ.js.map +0 -1
package/docs/injector.md
CHANGED
|
@@ -269,6 +269,8 @@ Dispose, ref-counted cleanup and GC therefore behave identically in every mode.
|
|
|
269
269
|
|
|
270
270
|
`gc({ force: true })` and `cleanup()` take bands 4 and 5 together, ignoring capacity. Band 3 is spared even then: an explicit cleanup is still no reason to take rules from a render that has not committed yet.
|
|
271
271
|
|
|
272
|
+
`tasty()` components cache the class names they computed, so a re-render with the same styles skips the pipeline. Whenever `gc()`, `cleanup()` or `destroy()` deletes rules, those caches are dropped too, so a component whose CSS was collected injects it again on its next render.
|
|
273
|
+
|
|
272
274
|
> **Why band 3 exists.** Rendering is not commit-aware: a render can resolve a class and commit it a little later, and in between nothing on the page carries it. From outside React that is indistinguishable from a class that is finished, so collection does not try to tell them apart — it leaves alone anything only just noticed to be cold. A render would have to stay pending for the whole window to lose its rules, and it gets them back on its next render.
|
|
273
275
|
>
|
|
274
276
|
> The clock starts when a sweep *notices*, not when the element actually left — nothing observes that moment, so starting it at the sighting is what gives every class the same full window however long ago it went.
|
|
@@ -353,7 +355,7 @@ const button2 = inject([rule], { cacheKey: 'button' });
|
|
|
353
355
|
console.log(button1.className === button2.className); // true
|
|
354
356
|
```
|
|
355
357
|
|
|
356
|
-
Without `cacheKey` the class name
|
|
358
|
+
Without `cacheKey` the class name is a hash of the rules, so injecting the same rules again reuses the class the same way: the rules are written once, and each call adds a pin that its own `dispose()` releases.
|
|
357
359
|
|
|
358
360
|
### Pinning
|
|
359
361
|
|
package/docs/methodology.md
CHANGED
|
@@ -242,7 +242,7 @@ const Button = tasty({
|
|
|
242
242
|
<Button variant="danger" size="sm" isLoading />;
|
|
243
243
|
```
|
|
244
244
|
|
|
245
|
-
`variant` is typed from the variant keys, the `default` variant applies when no `variant` prop is passed, and only variants that render emit CSS. See [React API — Variants](react-api.md#variants).
|
|
245
|
+
`variant` is typed from the variant keys, the `default` variant applies when no `variant` prop is passed (set the `variant` option, as in `variant: 'danger'`, to fall back to another one), and only variants that render emit CSS. See [React API — Variants](react-api.md#variants).
|
|
246
246
|
|
|
247
247
|
---
|
|
248
248
|
|
|
@@ -359,9 +359,9 @@ const LargeCard = tasty(Card, {
|
|
|
359
359
|
|
|
360
360
|
Why? Styled wrappers are:
|
|
361
361
|
|
|
362
|
-
- **Stable** — the style object is defined once at module level,
|
|
362
|
+
- **Stable** — the style object is defined once at module level, so Tasty merges and computes it once instead of on every render
|
|
363
363
|
- **Composable** — another engineer can extend `LargeCard` further
|
|
364
|
-
- **Inspectable** — give it a `qa` (`tasty(Card, { qa: 'LargeCard', styles })`) and it appears as `
|
|
364
|
+
- **Inspectable** — give it a `qa` (`tasty(Card, { qa: 'LargeCard', styles })`) and it appears as `data-qa="LargeCard"` in the DOM; React DevTools shows it as `TastyWrappedComponent(…)` around the base's name
|
|
365
365
|
- **Lint-friendly** — the ESLint plugin's `no-styles-prop` rule flags direct usage
|
|
366
366
|
|
|
367
367
|
The `styles` prop exists as an escape hatch — for prototyping, one-off overrides during development, or cases where wrapping is impractical. It should not be the default way product engineers customize components.
|
|
@@ -382,7 +382,7 @@ See [Best practices](#best-practices) below for the full list of do's and don'ts
|
|
|
382
382
|
|
|
383
383
|
## Wrapping and extension
|
|
384
384
|
|
|
385
|
-
`tasty(Base, { styles })` is the primary extension mechanism. It creates a new component whose styles are merged with the base component's styles.
|
|
385
|
+
`tasty(Base, { styles })` is the primary extension mechanism. It creates a new component whose styles are merged with the base component's styles. The extension can also declare its own `styleProps`, `modProps`, `tokenProps`, `variants`, and `elements`, and it keeps the base's sub-elements — see [React API — Extend an existing component](react-api.md#extend-an-existing-component).
|
|
386
386
|
|
|
387
387
|
```tsx
|
|
388
388
|
import { Button } from 'my-ds';
|
|
@@ -478,10 +478,10 @@ This is preferred over `<Card styles={{ border: '1bw solid #danger' }}>` because
|
|
|
478
478
|
|
|
479
479
|
1. The styles live in one module-level definition instead of being rebuilt inside render
|
|
480
480
|
2. `DangerCard` can be extended further by others
|
|
481
|
-
3. It is searchable in code, and
|
|
481
|
+
3. It is searchable in code, and carries `data-qa` in the DOM once you give it a `qa`
|
|
482
482
|
4. The ESLint `no-styles-prop` rule encourages this pattern
|
|
483
483
|
|
|
484
|
-
A wrapper
|
|
484
|
+
A wrapper keeps the base's sub-element components, typed: `DangerCard.Title` is `Card.Title`, so `<DangerCard><DangerCard.Title>…</DangerCard.Title></DangerCard>` works. The wrapper's `Title` styles apply to it, because they target `[data-element="Title"]`.
|
|
485
485
|
|
|
486
486
|
---
|
|
487
487
|
|
|
@@ -618,7 +618,7 @@ const Card = tasty({
|
|
|
618
618
|
});
|
|
619
619
|
```
|
|
620
620
|
|
|
621
|
-
Modifiers are compiled into exclusive selectors once.
|
|
621
|
+
Modifiers are compiled into exclusive selectors once. A `styles` object that stays the same between renders (a module-level constant or a memoized object) is merged and computed once, but a new object on every render is merged and computed on every render, and each new value generates and injects new CSS. `tasty/no-styles-prop` and `tasty/no-runtime-styles-mutation` flag these patterns.
|
|
622
622
|
|
|
623
623
|
### Overusing style prop
|
|
624
624
|
|
package/docs/migration-v3.md
CHANGED
|
@@ -204,7 +204,7 @@ The build-time extraction Next.js wrapper already lived under the `zero` namespa
|
|
|
204
204
|
|
|
205
205
|
**The `theme` prop is now implemented.** It maps to a `data-theme` attribute on the rendered element; previously it fell through and was spread onto the DOM raw. Augment `TastyThemeNames` for autocomplete.
|
|
206
206
|
|
|
207
|
-
**`tasty(Component, options)` no longer leaks factory-only options.** `variants`, `elements`, `styleProps`, `modProps`, and `tokenProps` are stripped instead of being passed to the wrapped component as runtime props, matching the element factory.
|
|
207
|
+
**`tasty(Component, options)` no longer leaks factory-only options.** `variants`, `elements`, `styleProps`, `modProps`, and `tokenProps` are stripped instead of being passed to the wrapped component as runtime props, matching the element factory. Since 3.11 the extension form applies them as the element factory does; see [Extend an existing component](react-api.md#extend-an-existing-component).
|
|
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
|
|
package/docs/pipeline.md
CHANGED
|
@@ -522,7 +522,7 @@ Converts condition trees into actual CSS selectors and at-rules.
|
|
|
522
522
|
|
|
523
523
|
5. **`:where()` specificity equalization**: Every stateful part of the selector is wrapped in `:where(...)` so it contributes **zero specificity**:
|
|
524
524
|
- the flat root modifiers + pseudos and the root element's `:is()`/`:not()` groups are combined into one `:where(...)` (`buildSelectorFromVariant`);
|
|
525
|
-
- the sub-element (`@own`) groups are wrapped in a **separate** `:where(...)` so the sub-element's structural `[data-element="X"]` specificity is preserved while its states are zeroed;
|
|
525
|
+
- the sub-element (`@own`) groups are wrapped in a **separate** `:where(...)` so the sub-element's structural `[data-element="X"]` specificity is preserved while its states are zeroed. When the suffix ends in a pseudo-element, these are the pseudo-element's own states and stay after it (`a::before:where(:hover)`). States a pseudo-element can't have (attributes, modifiers, classes, tree-structural pseudo-classes) are dropped with a `PSEUDO_ELEMENT_STATE_IGNORED` warning before Stage 0, using the CSS language alone, never per-browser support (`pseudo-element-states.ts`). A direct selector that ends in a pseudo-element (`.card::before`) is rendered separately: its root states are checked the same way and go after the pseudo-element, its `@parent` segments before it, at the `pseudoElementAt` each `CSSRule` records;
|
|
526
526
|
- `@parent` ancestors (`parentGroupsToCSS`) become `:where(... *)` / `:where(:not(... *))`;
|
|
527
527
|
- the `@root` context prefix (`rootStateToPrefix`) becomes `:where(:root...)` (zeroing the `:root` pseudo too). When a direct selector can only match the root itself (`:root`, `html`, `:where(:root)`), the state segment joins it instead — `:root:where([data-schema="dark"])` — since an ancestor prefix would ask for a `:root` inside the root.
|
|
528
528
|
|
package/docs/plugins.md
CHANGED
|
@@ -69,7 +69,7 @@ Two distinctions do most of the work:
|
|
|
69
69
|
A handler receives a map of the style properties it declared and returns CSS declarations. See [Configuration → Custom Style Handlers](configuration.md#custom-style-handlers) for the definition forms, return shape, the shared-handler groups you must be careful of, and chunk membership. The essentials:
|
|
70
70
|
|
|
71
71
|
- Values arrive **state-resolved but unparsed** — the raw authored DSL string (`'2x'`, `'#purple.5'`, `true`, `4`). Call the exported `parseStyle()` / `parseColor()` yourself.
|
|
72
|
-
- Return a `CSSMap` with **kebab-case** keys, an array of them, or nothing. `$` is a selector suffix, not a property.
|
|
72
|
+
- Return a `CSSMap` with **kebab-case** keys, an array of them, or nothing. `$` is a selector suffix, not a property. A property whose value is an array gets one declaration per entry, for fallback chains.
|
|
73
73
|
- Handlers must be pure and synchronous. They are called once per state combination and the results are cached.
|
|
74
74
|
- Use `defineHandler(deps, fn)` for multi-dependency handlers so the dependency types are inferred.
|
|
75
75
|
|
package/docs/react-api.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Use `tasty()` to turn state-aware style definitions into normal React components, extend existing Tasty components, and expose typed styling APIs to consumers.
|
|
4
4
|
|
|
5
|
-
Every Tasty style function
|
|
5
|
+
Every Tasty style function — `tasty()` components, `computeStyles()`, `useStyles()`, `useGlobalStyles()`, `useRawCSS()`, `useKeyframes()`, `useProperty()`, `useFontFace()`, `useCounterStyle()`, and `useFunction()` — works in React Server Components with no `'use client'` directive. All of them except `useStyles()` are hook-free. `useStyles()` reads the `TastyRegistry` collector from React context on the client, so it is a real hook there; outside React, call `computeStyles()` instead. For state maps, tokens, units, and extension semantics, see [Style DSL](dsl.md). For global configuration, see [Configuration](configuration.md). For the broader docs map, see the [Docs Hub](README.md).
|
|
6
6
|
|
|
7
7
|
> **Note:** This file was previously named `runtime.md`. All functionality documented here works in both server and client contexts — "runtime" referred to style computation during React rendering, not to client-side JavaScript.
|
|
8
8
|
|
|
@@ -44,7 +44,29 @@ const PrimaryButton = tasty(Button, {
|
|
|
44
44
|
|
|
45
45
|
Style maps merge intelligently — see [Style DSL — Extending vs. Replacing State Maps](dsl.md#extending-vs-replacing-state-maps) for extend mode, replace mode, `@inherit`, `null`, and `false` tombstones.
|
|
46
46
|
|
|
47
|
-
The extension form
|
|
47
|
+
The extension form takes the same component options as `tasty({ … })`:
|
|
48
|
+
|
|
49
|
+
```jsx
|
|
50
|
+
const ActionCard = tasty(Card, {
|
|
51
|
+
styles: { fill: { '': '#surface', isRaised: '#surface-raised' } },
|
|
52
|
+
styleProps: ['gap'] as const, // gap="2x" becomes a style
|
|
53
|
+
modProps: ['isRaised'] as const, // isRaised becomes a mod (data-is-raised)
|
|
54
|
+
tokenProps: ['accentColor'] as const, // accentColor sets the #accent token
|
|
55
|
+
variants: {
|
|
56
|
+
plain: { border: 0 },
|
|
57
|
+
framed: { border: '2bw #accent' },
|
|
58
|
+
},
|
|
59
|
+
variant: 'framed', // applied when no variant prop is passed
|
|
60
|
+
elements: { Footer: 'footer' },
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
<ActionCard gap="2x" isRaised accentColor="#purple">
|
|
64
|
+
<ActionCard.Title>Kept from Card</ActionCard.Title>
|
|
65
|
+
<ActionCard.Footer>Added here</ActionCard.Footer>
|
|
66
|
+
</ActionCard>;
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The extension turns its own `styleProps`, `modProps`, and `tokenProps` into the `styles`, `mods`, and `tokens` it passes to the base, which renders them as usual: mods as `data-*` attributes, tokens as inline custom properties. Its `variants` merge over its `styles`, and the `variant` prop is still forwarded, so it selects the base's own variants too and accepts either set's names. Both sets share that one prop, so a name only one of them defines leaves the other at its fallback: if `Card` had an `accent` variant, `variant="accent"` would apply it together with `framed`, and `variant="plain"` would leave `Card` on its default. For two independent choices, give one of them its own prop, such as a `modProps` value list. `elements` adds sub-components, and the base's are kept: `ActionCard.Title` is `Card.Title`. The returned component is typed with all of these props and sub-components.
|
|
48
70
|
|
|
49
71
|
`tasty(Component, ...)` always wraps `Component` and forwards every prop to it, so `Component` must be Tasty-aware (i.e. it accepts `styles`, `mods`, `qa`, etc. and renders them through its own Tasty pipeline). To apply styles to a third-party component or a string DOM tag via `className`, use the options-only form with `as`:
|
|
50
72
|
|
|
@@ -303,6 +325,22 @@ const Button = tasty({
|
|
|
303
325
|
|
|
304
326
|
With `variant` omitted, or set to a key `variants` does not define, the `default` variant applies; without a `default` key only the base `styles` apply. `variant` is typed from the variant keys.
|
|
305
327
|
|
|
328
|
+
To fall back to another variant instead, name it in the `variant` option:
|
|
329
|
+
|
|
330
|
+
```jsx
|
|
331
|
+
const Button = tasty({
|
|
332
|
+
styles: { padding: '2x 4x' },
|
|
333
|
+
variants: {
|
|
334
|
+
primary: { fill: '#blue', color: '#white' },
|
|
335
|
+
danger: { fill: '#red', color: '#white' },
|
|
336
|
+
},
|
|
337
|
+
variant: 'primary',
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
<Button>Save</Button>; // primary
|
|
341
|
+
<Button variant="danger">Delete</Button>;
|
|
342
|
+
```
|
|
343
|
+
|
|
306
344
|
### Extending Variants with Base State Maps
|
|
307
345
|
|
|
308
346
|
When base `styles` contain an extend-mode state map (an object **without** a `''` key), it is applied **after** the variant merge. This lets you add or override states across all variants without repeating yourself:
|
|
@@ -413,13 +451,13 @@ On the client, CSS is injected synchronously into the DOM (idempotent via the in
|
|
|
413
451
|
|
|
414
452
|
## Style Functions
|
|
415
453
|
|
|
416
|
-
All style functions below
|
|
454
|
+
All style functions below work in client components, SSR with a `ServerStyleCollector`, and React Server Components. All except `useStyles()` are plain functions, not React hooks; they keep the `use` prefix for backward compatibility.
|
|
417
455
|
|
|
418
456
|
In server-only contexts, components that use only Tasty style functions ship no Tasty styling runtime. Astro without `client:*` directives produces no client JavaScript; server-only Next.js RSC follows the same Tasty architecture, while final output depends on the application deployment. Tasty never forces the `'use client'` boundary — that decision belongs to your component when it needs React interactivity (state, effects, event handlers).
|
|
419
457
|
|
|
420
458
|
### useStyles
|
|
421
459
|
|
|
422
|
-
Generate a className from a style object. A thin wrapper around `computeStyles()` that also picks up the `TastyRegistry` collector through React context. That makes it a hook: call it inside a component, and
|
|
460
|
+
Generate a className from a style object. A thin wrapper around `computeStyles()` that also picks up the `TastyRegistry` collector through React context. That makes it a hook on the client: call it inside a component, and use `computeStyles()` outside React. It also works in a Server Component, where it skips the context lookup; like the other style functions there, it cannot emit CSS itself, so the next `tasty()` element in the tree emits it.
|
|
423
461
|
|
|
424
462
|
```tsx
|
|
425
463
|
import { useStyles } from '@tenphi/tasty';
|
|
@@ -456,9 +494,13 @@ function ThemeStyles() {
|
|
|
456
494
|
The selector can be a list (`'.uni-checkbox, uni-checkbox'`) — every item gets
|
|
457
495
|
the states and sub-elements, with no `:is()` wrapping needed. Root states
|
|
458
496
|
(`@root(...)`) on a selector that is the root itself (`:root`, `html`) apply to
|
|
459
|
-
that element.
|
|
460
|
-
|
|
461
|
-
`@
|
|
497
|
+
that element. A selector that ends in a pseudo-element takes its states on the
|
|
498
|
+
pseudo-element (`'.card::before'` with `:hover` gives
|
|
499
|
+
`.card::before:where(:hover)`) and `@parent()` on the element before it.
|
|
500
|
+
Attribute and modifier states, which a pseudo-element can't have, are ignored
|
|
501
|
+
with a warning. Local `@keyframes` are injected with the rules,
|
|
502
|
+
under the same resolved names `tasty()` components use, and `@starting` states
|
|
503
|
+
emit `@starting-style`.
|
|
462
504
|
|
|
463
505
|
A slot — the `id`, or the selector when no `id` is given — holds exactly one
|
|
464
506
|
injection **per `root`**. So:
|
|
@@ -685,9 +727,10 @@ See the [Functions section of the DSL reference](dsl.md#functions-function) for
|
|
|
685
727
|
|
|
686
728
|
- Styles are not updating: make sure `configure()` runs before first render, and verify the generated class name or global rule with [Debug Utilities](debug.md).
|
|
687
729
|
- SSR output looks wrong: check the [SSR guide](ssr.md) for collector setup. All style functions discover the SSR collector via `AsyncLocalStorage` or the global getter registered by `TastyRegistry`.
|
|
730
|
+
- "Server rendering without a style collector" in development: the server render has no request scope, so each `tasty()` element inlines its own CSS and configured globals are not emitted. Add `tastyIntegration()` (Astro), `TastyRegistry` (Next.js), or `runWithCollector()` — see the [SSR guide](ssr.md).
|
|
688
731
|
- Animation/custom property issues: prefer `useKeyframes()` and `useProperty()` over raw CSS when you want Tasty to manage injection and SSR collection for you.
|
|
689
732
|
- For dynamic styles that change over the component lifecycle, use the `id` option in `useGlobalStyles()` and `useRawCSS()` to enable update tracking.
|
|
690
|
-
- RSC inline mode: CSS accumulated by standalone style functions (`useGlobalStyles`, `useRawCSS`, etc.) is flushed into inline `<style>` tags by the next `tasty()` component in the render tree. If your page uses only standalone style functions without any `tasty()` component, the CSS will not be emitted. Ensure at least one `tasty()` component is present in each RSC render tree.
|
|
733
|
+
- RSC inline mode: CSS accumulated by standalone style functions (`useStyles`, `useGlobalStyles`, `useRawCSS`, etc.) is flushed into inline `<style>` tags by the next `tasty()` component in the render tree. If your page uses only standalone style functions without any `tasty()` component, the CSS will not be emitted. Ensure at least one `tasty()` component is present in each RSC render tree.
|
|
691
734
|
|
|
692
735
|
---
|
|
693
736
|
|
package/docs/ssr.md
CHANGED
|
@@ -187,14 +187,14 @@ meaning; use root-relative, absolute, or data URLs.
|
|
|
187
187
|
|
|
188
188
|
### Using Tasty in Server Components
|
|
189
189
|
|
|
190
|
-
Every Tasty style function
|
|
190
|
+
Every Tasty style function needs no `'use client'`, so it can be used directly in React Server Components:
|
|
191
191
|
|
|
192
192
|
- `tasty()` components — dynamic `styleProps` like `<Grid flow="column">` work normally
|
|
193
|
-
- `
|
|
193
|
+
- `useStyles()` and its hook-free equivalent `computeStyles()` — a class name for any element
|
|
194
194
|
- `useGlobalStyles()`, `useRawCSS()` — inject styles by selector
|
|
195
195
|
- `useKeyframes()`, `useProperty()`, `useFontFace()`, `useCounterStyle()`, `useFunction()` — inject ancillary CSS rules
|
|
196
196
|
|
|
197
|
-
Next.js renders Server Components in a separate module graph that cannot see the collector `TastyRegistry` registers, so their CSS takes the RSC inline path: it is accumulated in a per-request `React.cache` and emitted as an inline `<style>` tag by the next `tasty()` component in the tree. Standalone style functions cannot emit CSS on their own, so make sure a `tasty()` component renders after them in every RSC tree. The RSC path does not emit configured globals (tokens, `@property`, `globalStyles`); those come from `TastyRegistry`'s collector or the `withTastyNext()` shared stylesheet. For the same reason, import the module that calls `configure()` from `app/layout.tsx` as well as from the registry module, so the RSC graph sees the same states, recipes, and units.
|
|
197
|
+
Next.js renders Server Components in a separate module graph that cannot see the collector `TastyRegistry` registers, so their CSS takes the RSC inline path: it is accumulated in a per-request `React.cache` and emitted as an inline `<style>` tag by the next `tasty()` component in the tree. Standalone style functions, `useStyles()` included, cannot emit CSS on their own, so make sure a `tasty()` component renders after them in every RSC tree. The RSC path does not emit configured globals (tokens, `@property`, `globalStyles`); those come from `TastyRegistry`'s collector or the `withTastyNext()` shared stylesheet. For the same reason, import the module that calls `configure()` from `app/layout.tsx` as well as from the registry module, so the RSC graph sees the same states, recipes, and units.
|
|
198
198
|
|
|
199
199
|
### Options
|
|
200
200
|
|
|
@@ -241,9 +241,9 @@ import { tasty } from '@tenphi/tasty';
|
|
|
241
241
|
const Card = tasty({
|
|
242
242
|
styles: {
|
|
243
243
|
padding: '4x',
|
|
244
|
-
fill: '#
|
|
244
|
+
fill: '#fff',
|
|
245
245
|
radius: '1r',
|
|
246
|
-
border:
|
|
246
|
+
border: '1bw solid #ddd',
|
|
247
247
|
},
|
|
248
248
|
});
|
|
249
249
|
|
|
@@ -263,9 +263,9 @@ import Card from '../components/Card.tsx';
|
|
|
263
263
|
</html>
|
|
264
264
|
```
|
|
265
265
|
|
|
266
|
-
**Trade-offs**: Astro renders React with the standard `react` build, where `React.cache` does nothing, so there is no per-request style cache. Every `tasty()` element inlines the full CSS for its classes, even when the same component renders twice. Configured globals (`:root` tokens, `@property` defaults, `globalStyles`, `@font-face`) are never emitted, because only the SSR collector emits them, and CSS from `useGlobalStyles`, `useRawCSS`, `useKeyframes`, `useProperty`, `useFontFace`, `useCounterStyle`, and `useFunction` is dropped.
|
|
266
|
+
**Trade-offs**: Astro renders React with the standard `react` build, where `React.cache` does nothing, so there is no per-request style cache. Every `tasty()` element inlines the full CSS for its classes, even when the same component renders twice. Configured globals (`:root` tokens, `@property` defaults, `globalStyles`, `@font-face`) are never emitted, because only the SSR collector emits them, and CSS from `useGlobalStyles`, `useRawCSS`, `useKeyframes`, `useProperty`, `useFontFace`, `useCounterStyle`, and `useFunction` is dropped. Without the [default properties](dsl.md#default-properties), styles that read them resolve to nothing: `border: true` and `outline: true` draw nothing, and `transition` with no duration does not animate. In development, Tasty warns once that it is rendering without a style collector.
|
|
267
267
|
|
|
268
|
-
Best for trying Tasty out with
|
|
268
|
+
Best for trying Tasty out with plain values, as above. Use `tastyIntegration()` for anything that relies on `configure()` or on Tasty's default tokens.
|
|
269
269
|
|
|
270
270
|
### Astro Integration (recommended)
|
|
271
271
|
|
|
@@ -463,14 +463,14 @@ const fullHtml = `
|
|
|
463
463
|
|
|
464
464
|
```tsx
|
|
465
465
|
// ── Client ──────────────────────────────────────────────
|
|
466
|
-
|
|
467
|
-
import '@tenphi/tasty/ssr/astro-client';
|
|
466
|
+
import { hydrateTastyClasses } from '@tenphi/tasty/ssr/client';
|
|
468
467
|
import { hydrateRoot } from 'react-dom/client';
|
|
469
468
|
|
|
469
|
+
hydrateTastyClasses(); // reads window.__TASTY__
|
|
470
470
|
hydrateRoot(document.getElementById('root'), <App />);
|
|
471
471
|
```
|
|
472
472
|
|
|
473
|
-
`@tenphi/tasty/ssr` is
|
|
473
|
+
`@tenphi/tasty/ssr/client` is the browser half: it exports `hydrateTastyClasses()` and has no import side effects. A module shared by the server and the client can import `@tenphi/tasty/ssr` too. It is safe in a browser bundle, where there is no server collector to find, and only `runWithCollector()` throws there.
|
|
474
474
|
|
|
475
475
|
### Streaming SSR
|
|
476
476
|
|
|
@@ -517,14 +517,15 @@ runWithCollector(collector, () => {
|
|
|
517
517
|
|
|
518
518
|
### Entry points
|
|
519
519
|
|
|
520
|
-
| Import path | Description
|
|
521
|
-
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
522
|
-
| `@tenphi/tasty/ssr` | Core SSR API
|
|
523
|
-
| `@tenphi/tasty/ssr/
|
|
524
|
-
| `@tenphi/tasty/ssr/next
|
|
525
|
-
| `@tenphi/tasty/ssr/
|
|
526
|
-
| `@tenphi/tasty/ssr/astro
|
|
527
|
-
| `@tenphi/tasty/ssr/astro-
|
|
520
|
+
| Import path | Description |
|
|
521
|
+
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
522
|
+
| `@tenphi/tasty/ssr` | Core SSR API: `ServerStyleCollector`, `createServerStyleCollector`, `runWithCollector`, `getSSRCollector`, `hydrateTastyClasses`; types `ServerStyleArtifact`, `ServerStyleArtifactKind`. Safe to import in a browser bundle; `runWithCollector()` needs the server |
|
|
523
|
+
| `@tenphi/tasty/ssr/client` | Browser-safe client hydration for any framework: `hydrateTastyClasses`, no import side effects |
|
|
524
|
+
| `@tenphi/tasty/ssr/next` | Next.js App Router: `TastyRegistry` component |
|
|
525
|
+
| `@tenphi/tasty/ssr/next-config` | Next.js config wrapper: shared, content-hashed global stylesheet |
|
|
526
|
+
| `@tenphi/tasty/ssr/astro` | Astro: `tastyIntegration`, `tastyMiddleware` |
|
|
527
|
+
| `@tenphi/tasty/ssr/astro-client` | Astro: client-side cache hydration (auto-injected by integration, or import manually) |
|
|
528
|
+
| `@tenphi/tasty/ssr/astro-middleware`<br />`@tenphi/tasty/ssr/astro-middleware-static`<br />`@tenphi/tasty/ssr/astro-middleware-extract`<br />`@tenphi/tasty/ssr/astro-middleware-extract-static` | Astro: the middleware entrypoints `tastyIntegration()` registers via `addMiddleware()`. Exported so Astro can resolve them by specifier; you should not import them. For manual setups use `tastyMiddleware()`. |
|
|
528
529
|
|
|
529
530
|
### `ServerStyleCollector`
|
|
530
531
|
|
|
@@ -587,11 +588,11 @@ Astro middleware factory. Use for manual middleware composition.
|
|
|
587
588
|
|
|
588
589
|
### `hydrateTastyClasses(classes?)`
|
|
589
590
|
|
|
590
|
-
Pre-populate the client injector's rules map with class names rendered on the server, marking them as already present in the DOM so `computeStyles()` skips re-injection during hydration. When called without arguments, reads the class list from `window.__TASTY__` (populated by the streaming `<script>` tags emitted during SSR).
|
|
591
|
+
Exported from `@tenphi/tasty/ssr/client` and `@tenphi/tasty/ssr`. Pre-populate the client injector's rules map with class names rendered on the server, marking them as already present in the DOM so `computeStyles()` skips re-injection during hydration. When called without arguments, reads the class list from `window.__TASTY__` (populated by the streaming `<script>` tags emitted during SSR).
|
|
591
592
|
|
|
592
593
|
### `runWithCollector(collector, fn)`
|
|
593
594
|
|
|
594
|
-
Run a function with a `ServerStyleCollector` bound to the current async context via `AsyncLocalStorage`. All style function calls within `fn` (and async continuations) — including `computeStyles()`, `useStyles()`, `useGlobalStyles()`, `useRawCSS()`, `useKeyframes()`, `useProperty()`, `useFontFace()`, `useCounterStyle()`, and `useFunction()` — will find this collector. Returns `fn`'s return value, synchronously unless `fn` returns a promise.
|
|
595
|
+
Run a function with a `ServerStyleCollector` bound to the current async context via `AsyncLocalStorage`. All style function calls within `fn` (and async continuations) — including `computeStyles()`, `useStyles()`, `useGlobalStyles()`, `useRawCSS()`, `useKeyframes()`, `useProperty()`, `useFontFace()`, `useCounterStyle()`, and `useFunction()` — will find this collector. Returns `fn`'s return value, synchronously unless `fn` returns a promise. Throws where `AsyncLocalStorage` is unavailable, such as in a browser.
|
|
595
596
|
|
|
596
597
|
---
|
|
597
598
|
|
|
@@ -603,7 +604,7 @@ The `TastyRegistry` or `tastyIntegration` is missing. Ensure your layout wraps t
|
|
|
603
604
|
|
|
604
605
|
### Hydration mismatch warnings
|
|
605
606
|
|
|
606
|
-
Class names are content hashes of the resolved styles, so render order does not affect them. Unnamed `useKeyframes()` and `useCounterStyle()` names are counters (`tk0`, `tc0`) on both server and client; pass `name` if the two sides can call them in a different order. If you see mismatches, ensure `hydrateTastyClasses()` runs before React hydration. For Next.js, this is automatic. For Astro with `tastyIntegration()`, this is also automatic. For manual Astro middleware setups, import `@tenphi/tasty/ssr/astro-client` in your island components. For custom setups,
|
|
607
|
+
Class names are content hashes of the resolved styles, so render order does not affect them. Unnamed `useKeyframes()` and `useCounterStyle()` names are counters (`tk0`, `tc0`) on both server and client; pass `name` if the two sides can call them in a different order. If you see mismatches, ensure `hydrateTastyClasses()` runs before React hydration. For Next.js, this is automatic. For Astro with `tastyIntegration()`, this is also automatic. For manual Astro middleware setups, import `@tenphi/tasty/ssr/astro-client` in your island components. For custom setups, call `hydrateTastyClasses()` from `@tenphi/tasty/ssr/client` before `hydrateRoot()`.
|
|
607
608
|
|
|
608
609
|
Class names are also derived from the _resolved_ styles, so the server and the client must configure Tasty identically. Anything that changes what a component's styles resolve to will produce a mismatch if it is registered on only one side — `namePrefix`, `recipes`, `handlers`, and the `propHandlers` / `baseStyleProps` extension points described in [Plugins](plugins.md). Call the same `configure()` on both; global CSS is deduplicated automatically, so no `typeof window` guard is needed.
|
|
609
610
|
|
|
@@ -614,8 +615,9 @@ Class names are also derived from the _resolved_ styles, so the server and the c
|
|
|
614
615
|
**Component CSS**: SSR `<style data-tasty-ssr>` tags remain in the DOM. The client injector creates separate `<style>` elements for any new styles. SSR styles are never modified or removed by the client. If this is a concern for very large apps, you can remove the SSR style tags and hydration scripts manually after hydration:
|
|
615
616
|
|
|
616
617
|
```tsx
|
|
617
|
-
import '@tenphi/tasty/ssr/
|
|
618
|
+
import { hydrateTastyClasses } from '@tenphi/tasty/ssr/client';
|
|
618
619
|
|
|
620
|
+
hydrateTastyClasses();
|
|
619
621
|
hydrateRoot(root, <App />);
|
|
620
622
|
|
|
621
623
|
// Optional: remove SSR style tags and class-list scripts after hydration
|
|
@@ -627,4 +629,8 @@ document.querySelectorAll('script').forEach((el) => {
|
|
|
627
629
|
|
|
628
630
|
### `AsyncLocalStorage` not available
|
|
629
631
|
|
|
630
|
-
|
|
632
|
+
`runWithCollector()` throws `runWithCollector() needs AsyncLocalStorage from node:async_hooks` where the runtime has no `AsyncLocalStorage`, such as in a browser bundle. Call it on the server. The rest of `@tenphi/tasty/ssr` is safe to import in a browser bundle; on the client, call `hydrateTastyClasses()` from `@tenphi/tasty/ssr/client`, or use `@tenphi/tasty/ssr/next`, which hydrates automatically.
|
|
633
|
+
|
|
634
|
+
### "Server rendering without a style collector" warning
|
|
635
|
+
|
|
636
|
+
Tasty rendered on the server with no collector and no React Server Components request scope: Astro without `tastyIntegration()`, a client component server-rendered without `TastyRegistry`, or a custom setup that skips `runWithCollector()`. Each `tasty()` element then inlines all of its CSS, and configured globals (tokens, the default `@property` set, `globalStyles`, `@font-face`) and the CSS of the standalone style functions are not emitted. Add the integration for your framework. The warning is shown once, in development only.
|
package/docs/styles.md
CHANGED
|
@@ -197,6 +197,9 @@ Element width with min/max control. One, two, or three values set `min-width`, `
|
|
|
197
197
|
| `"min 2x"` | `2x` | auto | initial |
|
|
198
198
|
| `"max 100%"` | initial | auto | `100%` |
|
|
199
199
|
| `"fixed 200px"` | `200px` | `200px` | `200px` |
|
|
200
|
+
| `"min"` | `1x` | auto | initial |
|
|
201
|
+
| `"max"` | initial | auto | `100%` |
|
|
202
|
+
| `"fixed"` | `max-content` | `max-content` | `max-content` |
|
|
200
203
|
|
|
201
204
|
**Keywords:** `stretch`, `max-content`, `min-content`, `fit-content`
|
|
202
205
|
|
|
@@ -312,6 +315,8 @@ Background color with design token support. Preferred over `backgroundColor` and
|
|
|
312
315
|
|
|
313
316
|
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. With `image` or `backgroundImage` on the same element, the image is layered over the overlay color in one `background-image`.
|
|
314
317
|
|
|
318
|
+
There is no default fill color, so `fill: true` is ignored with a development warning. Use `false` for no fill.
|
|
319
|
+
|
|
315
320
|
**Related deprecated props:** `backgroundColor` (use `fill`), `background` (overrides all background props when set — avoid).
|
|
316
321
|
|
|
317
322
|
### `image`
|
|
@@ -341,6 +346,7 @@ Text color with design token support.
|
|
|
341
346
|
| `"#current.5"` | Current inherited color at 50% opacity |
|
|
342
347
|
| `"(#primary, #secondary)"` | Fallback: use `#primary`, fall back to `#secondary` |
|
|
343
348
|
| `"light-dark(#dark, #light)"` | CSS color function — see [CSS Color Functions](dsl.md#css-color-functions) |
|
|
349
|
+
| `"#ddd"` | Hex literal, read as the `#ddd` token with the literal as fallback: `var(--ddd-color, #ddd)`, as in every color style |
|
|
344
350
|
| `true` | `currentColor` |
|
|
345
351
|
|
|
346
352
|
Also sets `$current-color` to the same color, for anything that needs the
|
package/docs/tasty-static.md
CHANGED
|
@@ -19,22 +19,25 @@ If the styles depend on props, keep `tasty()`: rendered only on the server, it a
|
|
|
19
19
|
|
|
20
20
|
Build-time extraction is part of the main `@tenphi/tasty` package but requires additional peer dependencies depending on your setup:
|
|
21
21
|
|
|
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 | Babel plugin and Next.js wrapper
|
|
28
|
-
| `babel-loader` | any | Next.js wrapper (`@tenphi/tasty/zero/next`)
|
|
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`) |
|
|
29
29
|
|
|
30
|
-
The Babel packages and `jiti` are optional peer dependencies of `@tenphi/tasty`; `babel-loader` is resolved from your project. Install 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:
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
33
|
# Any Babel-based setup (Vite, custom Babel config, etc.)
|
|
34
|
-
pnpm add -D @babel/core @babel/helper-plugin-utils @babel/types
|
|
34
|
+
pnpm add -D @babel/core @babel/helper-plugin-utils @babel/types
|
|
35
35
|
|
|
36
36
|
# Next.js
|
|
37
|
-
pnpm add -D @babel/core @babel/helper-plugin-utils @babel/types
|
|
37
|
+
pnpm add -D @babel/core @babel/helper-plugin-utils @babel/types babel-loader
|
|
38
|
+
|
|
39
|
+
# Either, with configFile
|
|
40
|
+
pnpm add -D jiti
|
|
38
41
|
```
|
|
39
42
|
|
|
40
43
|
---
|
|
@@ -173,7 +176,7 @@ module.exports = {
|
|
|
173
176
|
| -------------------------- | ------------------------------------------------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
174
177
|
| `output` | `string` | `'tasty.css'` | Path for generated CSS file |
|
|
175
178
|
| `mode` | `'file' \| 'inject'` | `'file'` | `'file'` writes CSS to disk; `'inject'` embeds CSS inline in JS (see [Inject Mode](#inject-mode)) |
|
|
176
|
-
| `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. |
|
|
177
180
|
| `config` | `TastyZeroConfig \| () => TastyZeroConfig` | `{}` | Inline config object or factory function. Takes precedence over `configFile`. |
|
|
178
181
|
| `configDeps` | `string[]` | `[]` | Absolute file paths that affect config (for cache invalidation) |
|
|
179
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. |
|
|
@@ -185,7 +188,8 @@ module.exports = {
|
|
|
185
188
|
| `config.keyframes` | `Record<string, KeyframesSteps>` | — | Global `@keyframes` definitions available to all `tastyStatic` calls |
|
|
186
189
|
| `config.fontFaces` | `Record<string, FontFaceInput>` | — | Global `@font-face` definitions |
|
|
187
190
|
| `config.counterStyles` | `Record<string, CounterStyleDescriptors>` | — | Global `@counter-style` definitions |
|
|
188
|
-
| `config.
|
|
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 |
|
|
189
193
|
| `config.functions` | `Record<string, FunctionDefinition \| Function>` | — | Custom functions (merged). Bare keys → parse functions; `$$name` keys → declarative CSS `@function` definitions |
|
|
190
194
|
| `config.polyfills` | `{ functions?: boolean }` | `{}` | Opt-in polyfills. `functions: true` inlines `@function` calls into plain CSS at build time |
|
|
191
195
|
| `config.plugins` | `TastyPlugin[]` | — | Plugins that extend tasty with custom functions, units, states, and handlers |
|
|
@@ -286,7 +290,7 @@ export default withTastyZero({
|
|
|
286
290
|
|
|
287
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.
|
|
288
292
|
|
|
289
|
-
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.
|
|
290
294
|
|
|
291
295
|
**Requirements**: `babel-loader` must be installed in your project (see [Requirements](#requirements)):
|
|
292
296
|
|
|
@@ -331,7 +335,7 @@ extractStylesWithChunks({ display: 'flex', gap: '1u' });
|
|
|
331
335
|
|
|
332
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)`.
|
|
333
337
|
|
|
334
|
-
`extractConfigProperties()` returns the `@property` rules the Babel plugin writes at the top of its output: Tasty's default set (`#border`, `#outline`, `$gap`, `$radius`, `$transition`, …) overlaid with the configured `properties
|
|
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.
|
|
335
339
|
|
|
336
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.
|
|
337
341
|
|
|
@@ -438,13 +442,19 @@ The `@tenphi/tasty/static` import becomes an import of the generated file (relat
|
|
|
438
442
|
```css
|
|
439
443
|
/* Generated by @tenphi/tasty/zero - DO NOT EDIT */
|
|
440
444
|
|
|
445
|
+
@property --tasty-second-fill-color {
|
|
446
|
+
syntax: '<color>';
|
|
447
|
+
inherits: false;
|
|
448
|
+
initial-value: transparent;
|
|
449
|
+
}
|
|
450
|
+
|
|
441
451
|
@property --current-color {
|
|
442
452
|
syntax: '<color>';
|
|
443
453
|
inherits: true;
|
|
444
454
|
initial-value: currentcolor;
|
|
445
455
|
}
|
|
446
456
|
|
|
447
|
-
/* …the rest of Tasty's default @property rules (--
|
|
457
|
+
/* …the rest of Tasty's default @property rules (--white-color, --border-color, --gap, --transition, …), then config.properties */
|
|
448
458
|
|
|
449
459
|
.ts3f783b.ts3f783b {
|
|
450
460
|
color: var(--white-color);
|
|
@@ -465,7 +475,7 @@ The `@tenphi/tasty/static` import becomes an import of the generated file (relat
|
|
|
465
475
|
}
|
|
466
476
|
```
|
|
467
477
|
|
|
468
|
-
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.
|
|
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.
|
|
469
479
|
|
|
470
480
|
---
|
|
471
481
|
|
|
@@ -537,6 +547,11 @@ tastyStatic('.heading', { preset: 'h1' });
|
|
|
537
547
|
```tsx
|
|
538
548
|
import { injectCSS as _$i } from '@tenphi/tasty/static/inject';
|
|
539
549
|
|
|
550
|
+
_$i(
|
|
551
|
+
'@property',
|
|
552
|
+
'@property --tasty-second-fill-color { … }\n@property --current-color { … }\n…',
|
|
553
|
+
);
|
|
554
|
+
|
|
540
555
|
const button =
|
|
541
556
|
(_$i(
|
|
542
557
|
'tsa903d6 ts070801',
|
|
@@ -553,6 +568,8 @@ const button =
|
|
|
553
568
|
_$i('.heading', '.heading { font-size: var(--h1-font-size, …); … }');
|
|
554
569
|
```
|
|
555
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
|
+
|
|
556
573
|
### Dev Mode / HMR
|
|
557
574
|
|
|
558
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.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tenphi/tasty",
|
|
3
|
-
"version": "3.11.
|
|
3
|
+
"version": "3.11.2",
|
|
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",
|
|
@@ -211,7 +211,7 @@
|
|
|
211
211
|
"name": "main (tasty)",
|
|
212
212
|
"path": "dist/index.js",
|
|
213
213
|
"import": "{ tasty }",
|
|
214
|
-
"limit": "
|
|
214
|
+
"limit": "53.84 kB"
|
|
215
215
|
},
|
|
216
216
|
{
|
|
217
217
|
"name": "main (StyleParser)",
|
|
@@ -235,7 +235,7 @@
|
|
|
235
235
|
"name": "core (import *)",
|
|
236
236
|
"path": "dist/core/index.js",
|
|
237
237
|
"import": "*",
|
|
238
|
-
"limit": "
|
|
238
|
+
"limit": "56.29 kB"
|
|
239
239
|
},
|
|
240
240
|
{
|
|
241
241
|
"name": "static",
|
|
@@ -252,7 +252,7 @@
|
|
|
252
252
|
"path",
|
|
253
253
|
"crypto"
|
|
254
254
|
],
|
|
255
|
-
"limit": "
|
|
255
|
+
"limit": "40.16 kB"
|
|
256
256
|
},
|
|
257
257
|
{
|
|
258
258
|
"name": "babel-plugin",
|
|
@@ -265,7 +265,7 @@
|
|
|
265
265
|
"node:module",
|
|
266
266
|
"node:path"
|
|
267
267
|
],
|
|
268
|
-
"limit": "
|
|
268
|
+
"limit": "44.79 kB"
|
|
269
269
|
},
|
|
270
270
|
{
|
|
271
271
|
"name": "astro client",
|