@tenphi/tasty 1.4.2 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -7
- package/dist/compute-styles.js +6 -28
- package/dist/compute-styles.js.map +1 -1
- package/dist/config.d.ts +3 -2
- package/dist/config.js.map +1 -1
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +2 -2
- package/dist/hooks/useCounterStyle.d.ts +3 -17
- package/dist/hooks/useCounterStyle.js +54 -35
- package/dist/hooks/useCounterStyle.js.map +1 -1
- package/dist/hooks/useFontFace.d.ts +3 -1
- package/dist/hooks/useFontFace.js +21 -24
- package/dist/hooks/useFontFace.js.map +1 -1
- package/dist/hooks/useGlobalStyles.d.ts +18 -2
- package/dist/hooks/useGlobalStyles.js +51 -40
- package/dist/hooks/useGlobalStyles.js.map +1 -1
- package/dist/hooks/useKeyframes.d.ts +4 -2
- package/dist/hooks/useKeyframes.js +41 -50
- package/dist/hooks/useKeyframes.js.map +1 -1
- package/dist/hooks/useProperty.d.ts +4 -2
- package/dist/hooks/useProperty.js +29 -41
- package/dist/hooks/useProperty.js.map +1 -1
- package/dist/hooks/useRawCSS.d.ts +13 -44
- package/dist/hooks/useRawCSS.js +90 -21
- package/dist/hooks/useRawCSS.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/injector/index.d.ts +5 -10
- package/dist/injector/index.js +5 -12
- package/dist/injector/index.js.map +1 -1
- package/dist/injector/injector.d.ts +11 -13
- package/dist/injector/injector.js +50 -73
- package/dist/injector/injector.js.map +1 -1
- package/dist/injector/sheet-manager.js +2 -1
- package/dist/injector/sheet-manager.js.map +1 -1
- package/dist/injector/types.d.ts +21 -28
- package/dist/rsc-cache.js +81 -0
- package/dist/rsc-cache.js.map +1 -0
- package/dist/ssr/astro-client.d.ts +1 -0
- package/dist/ssr/astro-client.js +24 -0
- package/dist/ssr/astro-client.js.map +1 -0
- package/dist/ssr/astro-middleware.d.ts +15 -0
- package/dist/ssr/astro-middleware.js +19 -0
- package/dist/ssr/astro-middleware.js.map +1 -0
- package/dist/ssr/astro.d.ts +85 -8
- package/dist/ssr/astro.js +110 -20
- package/dist/ssr/astro.js.map +1 -1
- package/dist/ssr/async-storage.js +14 -4
- package/dist/ssr/async-storage.js.map +1 -1
- package/dist/ssr/collect-auto-properties.js +28 -9
- package/dist/ssr/collect-auto-properties.js.map +1 -1
- package/dist/ssr/index.d.ts +1 -1
- package/dist/ssr/ssr-collector-ref.js +4 -3
- package/dist/ssr/ssr-collector-ref.js.map +1 -1
- package/dist/tasty.d.ts +1 -1
- package/dist/utils/deps-equal.js +15 -0
- package/dist/utils/deps-equal.js.map +1 -0
- package/dist/utils/hash.js +14 -0
- package/dist/utils/hash.js.map +1 -0
- package/docs/adoption.md +1 -1
- package/docs/comparison.md +1 -1
- package/docs/configuration.md +1 -1
- package/docs/design-system.md +1 -1
- package/docs/dsl.md +21 -6
- package/docs/getting-started.md +1 -1
- package/docs/injector.md +25 -24
- package/docs/methodology.md +1 -1
- package/docs/runtime.md +12 -31
- package/docs/ssr.md +117 -36
- package/docs/tasty-static.md +1 -1
- package/package.json +8 -2
package/docs/design-system.md
CHANGED
|
@@ -418,7 +418,7 @@ The key principle: `config.ts` imports tokens and recipes, calls `configure()`,
|
|
|
418
418
|
- **[Methodology](methodology.md)** — The recommended patterns for structuring Tasty components
|
|
419
419
|
- **[Getting Started](getting-started.md)** — Installation, first component, tooling setup
|
|
420
420
|
- **[Style DSL](dsl.md)** — State maps, tokens, units, extending semantics, keyframes, @property
|
|
421
|
-
- **[Runtime API](runtime.md)** — `tasty()` factory, component props, variants, sub-elements,
|
|
421
|
+
- **[Runtime API](runtime.md)** — `tasty()` factory, component props, variants, sub-elements, style functions
|
|
422
422
|
- **[Configuration](configuration.md)** — Full `configure()` API: tokens, recipes, custom units, style handlers
|
|
423
423
|
- **[Adoption Guide](adoption.md)** — Who should adopt Tasty, incremental phases, what changes for product engineers
|
|
424
424
|
- **[Style Properties](styles.md)** — Complete reference for all enhanced style properties
|
package/docs/dsl.md
CHANGED
|
@@ -25,7 +25,21 @@ fill: { '': '#white', hovered: '#gray.05', 'theme=danger': '#red' }
|
|
|
25
25
|
| Pseudo-class | `:hover` | `:hover` |
|
|
26
26
|
| Class selector | `.active` | `.active` |
|
|
27
27
|
| Attribute selector | `[aria-expanded="true"]` | `[aria-expanded="true"]` |
|
|
28
|
-
| Combined | `hovered & .active` | `[data-hovered].active` |
|
|
28
|
+
| Combined (AND) | `hovered & .active` | `[data-hovered].active` |
|
|
29
|
+
| Combined (OR) | `hovered \| focused` | `[data-hovered], [data-focused]` |
|
|
30
|
+
| Negated (NOT) | `!disabled` | `:not([data-disabled])` |
|
|
31
|
+
| Exclusive (XOR) | `hovered ^ focused` | `[data-hovered]:not([data-focused]), :not([data-hovered])[data-focused]` |
|
|
32
|
+
|
|
33
|
+
Operator precedence (highest to lowest): `!` (NOT) > `^` (XOR) > `|` (OR) > `&` (AND). Use parentheses to override: `hovered & (pressed ^ focused)`.
|
|
34
|
+
|
|
35
|
+
`^` (XOR) means "exactly one of" — `A ^ B` expands to `(A & !B) | (!A & B)`. This is useful for mutually exclusive states where exactly one should be active:
|
|
36
|
+
|
|
37
|
+
```jsx
|
|
38
|
+
fill: {
|
|
39
|
+
'': '#surface',
|
|
40
|
+
'hovered ^ focused': '#accent', // active when hovered OR focused, but not both
|
|
41
|
+
}
|
|
42
|
+
```
|
|
29
43
|
|
|
30
44
|
### Sub-element
|
|
31
45
|
|
|
@@ -430,7 +444,7 @@ const FadeIn = tasty({
|
|
|
430
444
|
|
|
431
445
|
### `@parent(...)` — Parent Element States
|
|
432
446
|
|
|
433
|
-
Style based on ancestor element attributes. Uses `:is([selector] *)` / `:not([selector] *)` for symmetric, composable parent checks. Boolean logic (`&`, `|`,
|
|
447
|
+
Style based on ancestor element attributes. Uses `:is([selector] *)` / `:not([selector] *)` for symmetric, composable parent checks. Boolean logic (`&`, `|`, `!`, `^`) is supported inside `@parent()`.
|
|
434
448
|
|
|
435
449
|
```jsx
|
|
436
450
|
const Highlight = tasty({
|
|
@@ -508,12 +522,13 @@ const Card = tasty({
|
|
|
508
522
|
| `!:has(> Icon)` | `:not(:has(> [data-element="Icon"]))` | Negation (use `!`) |
|
|
509
523
|
| `!:is(Panel)` | `:not([data-element="Panel"])` | Negation (use `!:is`) |
|
|
510
524
|
|
|
511
|
-
Combine with other states using boolean logic:
|
|
525
|
+
Combine with other states using boolean logic (`&`, `|`, `!`, `^`):
|
|
512
526
|
|
|
513
527
|
```jsx
|
|
514
|
-
':has(> Icon) & hovered' // structural + data attribute
|
|
515
|
-
'@parent(hovered) & :has(> Icon)' // parent check + structural
|
|
528
|
+
':has(> Icon) & hovered' // AND: structural + data attribute
|
|
529
|
+
'@parent(hovered) & :has(> Icon)' // AND: parent check + structural
|
|
516
530
|
':has(> Icon) | :has(> Button)' // OR: either sub-element present
|
|
531
|
+
':has(> Icon) ^ :has(> Button)' // XOR: exactly one present
|
|
517
532
|
```
|
|
518
533
|
|
|
519
534
|
> **Nesting limit:** The state key parser supports up to 2 levels of nested parentheses inside `:is()`, `:has()`, `:not()`, and `:where()` — e.g. `:has(Input:not(:disabled))` works, but 3+ levels like `:has(:is(:not(:hover)))` will not be tokenized correctly. This covers virtually all practical use cases.
|
|
@@ -666,7 +681,7 @@ For a complete reference of all enhanced style properties — syntax, values, mo
|
|
|
666
681
|
|
|
667
682
|
## Learn more
|
|
668
683
|
|
|
669
|
-
- **[Runtime API](runtime.md)** — `tasty()` factory, component props, variants, sub-elements,
|
|
684
|
+
- **[Runtime API](runtime.md)** — `tasty()` factory, component props, variants, sub-elements, style functions
|
|
670
685
|
- **[Methodology](methodology.md)** — Recommended patterns: root + sub-elements, styleProps, tokens, wrapping
|
|
671
686
|
- **[Configuration](configuration.md)** — Tokens, recipes, custom units, style handlers, TypeScript extensions
|
|
672
687
|
- **[Style Properties](styles.md)** — Complete reference for all enhanced style properties
|
package/docs/getting-started.md
CHANGED
|
@@ -200,7 +200,7 @@ Both share the same DSL, tokens, units, and state mappings.
|
|
|
200
200
|
- **[Docs Hub](README.md)** — Pick the next guide by role, styling approach, or task
|
|
201
201
|
- **[Methodology](methodology.md)** — The recommended patterns for structuring Tasty components: sub-elements, styleProps, tokens, extension
|
|
202
202
|
- **[Style DSL](dsl.md)** — State maps, tokens, units, extending semantics, keyframes, @property
|
|
203
|
-
- **[Runtime API](runtime.md)** — `tasty()` factory, component props, variants, sub-elements,
|
|
203
|
+
- **[Runtime API](runtime.md)** — `tasty()` factory, component props, variants, sub-elements, style functions
|
|
204
204
|
- **[Building a Design System](design-system.md)** — Practical guide to building a DS layer with Tasty: tokens, recipes, primitives, compound components
|
|
205
205
|
- **[Adoption Guide](adoption.md)** — Roll out Tasty inside an existing design system or platform team
|
|
206
206
|
- **[Comparison](comparison.md)** — Evaluate Tasty against other styling systems
|
package/docs/injector.md
CHANGED
|
@@ -115,11 +115,13 @@ dispose();
|
|
|
115
115
|
|
|
116
116
|
### `useRawCSS(css, options?)` or `useRawCSS(factory, deps, options?)`
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
Inject raw CSS without parsing. Hook-free — works in client components, SSR, and React Server Components.
|
|
119
119
|
|
|
120
120
|
Supports two overloads:
|
|
121
|
-
- **Static CSS**: `useRawCSS(cssString, options?)`
|
|
122
|
-
- **Factory function**: `useRawCSS(() => cssString, deps, options?)`
|
|
121
|
+
- **Static CSS**: `useRawCSS(cssString, options?)` — content-based deduplication
|
|
122
|
+
- **Factory function**: `useRawCSS(() => cssString, deps, options?)` — factory called on every invocation, dedup handled internally
|
|
123
|
+
|
|
124
|
+
Use the `id` option for update tracking — when the CSS changes for the same id, the previous injection is replaced:
|
|
123
125
|
|
|
124
126
|
```tsx
|
|
125
127
|
import { useRawCSS } from '@tenphi/tasty';
|
|
@@ -132,7 +134,7 @@ function GlobalReset() {
|
|
|
132
134
|
return null;
|
|
133
135
|
}
|
|
134
136
|
|
|
135
|
-
// Dynamic CSS with factory function
|
|
137
|
+
// Dynamic CSS with factory function and update tracking
|
|
136
138
|
function ThemeStyles({ theme }: { theme: 'dark' | 'light' }) {
|
|
137
139
|
useRawCSS(() => `
|
|
138
140
|
body {
|
|
@@ -140,7 +142,7 @@ function ThemeStyles({ theme }: { theme: 'dark' | 'light' }) {
|
|
|
140
142
|
background: ${theme === 'dark' ? '#000' : '#fff'};
|
|
141
143
|
color: ${theme === 'dark' ? '#fff' : '#000'};
|
|
142
144
|
}
|
|
143
|
-
`, [theme]);
|
|
145
|
+
`, [theme], { id: 'theme-body' });
|
|
144
146
|
|
|
145
147
|
return null;
|
|
146
148
|
}
|
|
@@ -210,11 +212,8 @@ configure({
|
|
|
210
212
|
forceTextInjection: false, // Force textContent insertion (auto-detected for tests)
|
|
211
213
|
nonce: 'csp-nonce', // CSP nonce for security
|
|
212
214
|
gc: { // Garbage collection for unused styles
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
cooldown: 30000, // Minimum time between GC runs
|
|
216
|
-
autoInterval: 300000, // Background sweep interval (ms)
|
|
217
|
-
cacheCapacity: 5000, // Hard cap on cached styles (optional)
|
|
215
|
+
touchInterval: 1000, // Touch events between GC cycles (default: 1000)
|
|
216
|
+
capacity: 1000, // Max unused styles to retain (default: 1000)
|
|
218
217
|
},
|
|
219
218
|
states: { // Global predefined states for advanced state mapping
|
|
220
219
|
'@mobile': '@media(w < 768px)',
|
|
@@ -231,9 +230,8 @@ configure({
|
|
|
231
230
|
- Most options have sensible defaults and auto-detection
|
|
232
231
|
- `configure()` is optional - the injector works with defaults
|
|
233
232
|
- **Configuration is locked after styles are generated** - calling `configure()` after first render will emit a warning and be ignored
|
|
234
|
-
- `gc.
|
|
235
|
-
- `gc.
|
|
236
|
-
- `gc.cooldown`: Minimum time between GC runs to avoid thrashing.
|
|
233
|
+
- `gc.touchInterval`: Number of touch events between GC cycles. Each style render counts as a touch. When the counter reaches this value, GC is scheduled via `requestIdleCallback`.
|
|
234
|
+
- `gc.capacity`: Maximum number of unused styles (refCount = 0, not in DOM) to retain. When exceeded, the oldest are evicted first. Actively referenced styles don't count against this limit.
|
|
237
235
|
|
|
238
236
|
---
|
|
239
237
|
|
|
@@ -298,28 +296,31 @@ comp3.dispose(); // refCount: 1 → 0, eligible for bulk cleanup
|
|
|
298
296
|
### Garbage Collection
|
|
299
297
|
|
|
300
298
|
```typescript
|
|
301
|
-
import { configure, gc
|
|
299
|
+
import { configure, gc } from '@tenphi/tasty';
|
|
302
300
|
|
|
303
301
|
// Keyframes: Disposed immediately when refCount = 0 (safer for global scope)
|
|
304
|
-
// CSS rules: Tracked by
|
|
302
|
+
// CSS rules: Tracked by touch count and cleaned up via gc()
|
|
305
303
|
|
|
306
304
|
configure({
|
|
307
305
|
gc: {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
cooldown: 30000, // 30s between runs
|
|
306
|
+
touchInterval: 1000, // Schedule GC every 1000 touches
|
|
307
|
+
capacity: 1000, // Max unused styles to retain
|
|
311
308
|
},
|
|
312
309
|
});
|
|
313
310
|
|
|
314
311
|
// Manual GC (synchronous, returns number of swept styles):
|
|
315
312
|
gc();
|
|
316
313
|
|
|
317
|
-
//
|
|
318
|
-
|
|
314
|
+
// Force-remove ALL unused styles (e.g. on route change or test teardown):
|
|
315
|
+
gc({ force: true });
|
|
316
|
+
|
|
317
|
+
// GC is also triggered automatically by touch count during rendering.
|
|
318
|
+
// Every `touchInterval` touches, GC is scheduled via requestIdleCallback.
|
|
319
319
|
|
|
320
320
|
// Benefits:
|
|
321
|
-
// -
|
|
321
|
+
// - Activity-proportional: busy apps trigger GC more often
|
|
322
322
|
// - DOM-safe: styles currently in the DOM are never evicted
|
|
323
|
+
// - Oldest-first: least recently used styles are evicted first
|
|
323
324
|
// - Keyframes: Immediate cleanup prevents global namespace pollution
|
|
324
325
|
// - Unused styles can be instantly reactivated (just increment refCount)
|
|
325
326
|
```
|
|
@@ -481,8 +482,8 @@ import { configure } from '@tenphi/tasty';
|
|
|
481
482
|
|
|
482
483
|
configure({
|
|
483
484
|
gc: {
|
|
484
|
-
|
|
485
|
-
|
|
485
|
+
touchInterval: 1000, // Schedule GC every 1000 style touches
|
|
486
|
+
capacity: 1000, // Max unused styles to retain
|
|
486
487
|
},
|
|
487
488
|
});
|
|
488
489
|
```
|
|
@@ -495,7 +496,7 @@ configure({
|
|
|
495
496
|
// 1. Hash-based deduplication - same CSS = same className
|
|
496
497
|
// 2. Reference counting - styles stay alive while in use (refCount > 0)
|
|
497
498
|
// 3. Immediate keyframes cleanup - disposed instantly when refCount = 0
|
|
498
|
-
// 4.
|
|
499
|
+
// 4. Touch-count GC - unused CSS rules are evicted oldest-first when over capacity
|
|
499
500
|
// 5. DOM safety guard - styles visible in the DOM are never evicted
|
|
500
501
|
|
|
501
502
|
// Manual cleanup is rarely needed but available:
|
package/docs/methodology.md
CHANGED
|
@@ -610,7 +610,7 @@ The `elements` prop gives you typed sub-components with automatic `data-element`
|
|
|
610
610
|
- **[Getting Started](getting-started.md)** — Installation, first component, tooling setup
|
|
611
611
|
- **[Building a Design System](design-system.md)** — Practical guide to building a DS layer with Tasty
|
|
612
612
|
- **[Style DSL](dsl.md)** — State maps, tokens, units, extending semantics, keyframes, @property
|
|
613
|
-
- **[Runtime API](runtime.md)** — `tasty()` factory, component props, variants, sub-elements,
|
|
613
|
+
- **[Runtime API](runtime.md)** — `tasty()` factory, component props, variants, sub-elements, style functions
|
|
614
614
|
- **[Configuration](configuration.md)** — Full `configure()` API: tokens, recipes, custom units, style handlers
|
|
615
615
|
- **[Style Properties](styles.md)** — Complete reference for all enhanced style properties
|
|
616
616
|
- **[Adoption Guide](adoption.md)** — Who should adopt Tasty, incremental phases, what changes for product engineers
|
package/docs/runtime.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Runtime API
|
|
2
2
|
|
|
3
|
-
The React-specific `tasty()` component factory, component props, and
|
|
3
|
+
The React-specific `tasty()` component factory, component props, and style functions. All Tasty style functions — `tasty()` components, `useStyles()`, `useGlobalStyles()`, `useRawCSS()`, `useKeyframes()`, `useProperty()`, `useFontFace()`, and `useCounterStyle()` — are hook-free and compatible with React Server Components. No `'use client'` directive needed. For the shared style language (state maps, tokens, units, extending semantics), see [Style DSL](dsl.md). For global configuration, see [Configuration](configuration.md). For the broader docs map, see the [Docs Hub](README.md).
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -351,11 +351,13 @@ On the client, CSS is injected synchronously into the DOM (idempotent via the in
|
|
|
351
351
|
|
|
352
352
|
---
|
|
353
353
|
|
|
354
|
-
##
|
|
354
|
+
## Style Functions
|
|
355
|
+
|
|
356
|
+
All style functions below are plain functions (not React hooks) and can be used in any environment: client components, SSR with a `ServerStyleCollector`, and React Server Components. They retain their `use` prefix for backward compatibility, but do not use any React hooks internally.
|
|
355
357
|
|
|
356
358
|
### useStyles
|
|
357
359
|
|
|
358
|
-
Generate a className from a style object. Thin wrapper around `computeStyles()
|
|
360
|
+
Generate a className from a style object. Thin wrapper around `computeStyles()`:
|
|
359
361
|
|
|
360
362
|
```tsx
|
|
361
363
|
import { useStyles } from '@tenphi/tasty';
|
|
@@ -373,7 +375,7 @@ function MyComponent() {
|
|
|
373
375
|
|
|
374
376
|
### useGlobalStyles
|
|
375
377
|
|
|
376
|
-
Inject global styles for a CSS selector:
|
|
378
|
+
Inject global styles for a CSS selector. Accepts an optional third argument with an `id` for update tracking — when the styles change, the previous injection is disposed and the new one is injected:
|
|
377
379
|
|
|
378
380
|
```tsx
|
|
379
381
|
import { useGlobalStyles } from '@tenphi/tasty';
|
|
@@ -391,7 +393,7 @@ function ThemeStyles() {
|
|
|
391
393
|
|
|
392
394
|
### useRawCSS
|
|
393
395
|
|
|
394
|
-
Inject raw CSS strings:
|
|
396
|
+
Inject raw CSS strings. Accepts an optional `id` in the options for update tracking — when the CSS changes for the same id, the previous injection is replaced:
|
|
395
397
|
|
|
396
398
|
```tsx
|
|
397
399
|
import { useRawCSS } from '@tenphi/tasty';
|
|
@@ -425,7 +427,7 @@ function Spinner() {
|
|
|
425
427
|
}
|
|
426
428
|
```
|
|
427
429
|
|
|
428
|
-
`useKeyframes()` also supports a factory function
|
|
430
|
+
`useKeyframes()` also supports a factory function. The deps array is accepted for backward compatibility but the factory is called on every invocation — deduplication is handled internally by content hash:
|
|
429
431
|
|
|
430
432
|
```tsx
|
|
431
433
|
function Pulse({ scale }: { scale: number }) {
|
|
@@ -521,43 +523,22 @@ function EmojiList() {
|
|
|
521
523
|
}
|
|
522
524
|
```
|
|
523
525
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
```tsx
|
|
527
|
-
function DynamicList({ marker }: { marker: string }) {
|
|
528
|
-
const styleName = useCounterStyle(
|
|
529
|
-
() => ({
|
|
530
|
-
system: 'cyclic',
|
|
531
|
-
symbols: `"${marker}"`,
|
|
532
|
-
suffix: '" "',
|
|
533
|
-
}),
|
|
534
|
-
[marker],
|
|
535
|
-
);
|
|
536
|
-
|
|
537
|
-
return <ol style={{ listStyleType: styleName }}>...</ol>;
|
|
538
|
-
}
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
Signatures:
|
|
526
|
+
Signature:
|
|
542
527
|
|
|
543
528
|
```ts
|
|
544
529
|
function useCounterStyle(
|
|
545
530
|
descriptors: CounterStyleDescriptors,
|
|
546
531
|
options?: { name?: string; root?: Document | ShadowRoot },
|
|
547
532
|
): string;
|
|
548
|
-
|
|
549
|
-
function useCounterStyle(
|
|
550
|
-
factory: () => CounterStyleDescriptors,
|
|
551
|
-
deps: readonly unknown[],
|
|
552
|
-
options?: { name?: string; root?: Document | ShadowRoot },
|
|
553
|
-
): string;
|
|
554
533
|
```
|
|
555
534
|
|
|
556
535
|
### Troubleshooting
|
|
557
536
|
|
|
558
537
|
- 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).
|
|
559
|
-
- SSR output looks wrong: check the [SSR guide](ssr.md) for collector setup.
|
|
538
|
+
- 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`.
|
|
560
539
|
- Animation/custom property issues: prefer `useKeyframes()` and `useProperty()` over raw CSS when you want Tasty to manage injection and SSR collection for you.
|
|
540
|
+
- For dynamic styles that change over the component lifecycle, use the `id` option in `useGlobalStyles()` and `useRawCSS()` to enable update tracking.
|
|
541
|
+
- 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.
|
|
561
542
|
|
|
562
543
|
---
|
|
563
544
|
|
package/docs/ssr.md
CHANGED
|
@@ -83,14 +83,20 @@ That's it. All `tasty()` components inside the tree automatically get SSR suppor
|
|
|
83
83
|
### How it works
|
|
84
84
|
|
|
85
85
|
- `TastyRegistry` is a `'use client'` component, but Next.js still server-renders it on initial page load. The `'use client'` boundary is required solely to access `useServerInsertedHTML` — **not** because `tasty()` components need the client.
|
|
86
|
-
- During SSR, `TastyRegistry` creates a `ServerStyleCollector` and registers it via a module-level global getter. All style
|
|
86
|
+
- During SSR, `TastyRegistry` creates a `ServerStyleCollector` and registers it via a module-level global getter. All style functions — `tasty()` components, `computeStyles()`, `useStyles()`, `useGlobalStyles()`, `useRawCSS()`, `useKeyframes()`, `useProperty()`, `useFontFace()`, and `useCounterStyle()` — discover the collector through this single global getter. No React context is involved.
|
|
87
87
|
- `TastyRegistry` uses `useServerInsertedHTML` to flush collected CSS into the HTML stream as `<style data-tasty-ssr>` tags. This is fully streaming-compatible — styles are injected alongside each Suspense boundary as it resolves.
|
|
88
88
|
- A companion `<script>` tag transfers the `cacheKey → className` mapping to the client.
|
|
89
89
|
- When the module loads on the client, `hydrateTastyCache()` runs automatically and pre-populates the injector cache. During hydration, `computeStyles()` hits the cache and skips the entire pipeline.
|
|
90
90
|
|
|
91
|
-
### Using
|
|
91
|
+
### Using Tasty in Server Components
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
All Tasty style functions are hook-free and do not require `'use client'`. They can be used directly in React Server Components:
|
|
94
|
+
|
|
95
|
+
- `tasty()` components — dynamic `styleProps` like `<Grid flow="column">` work normally
|
|
96
|
+
- `useStyles()`, `useGlobalStyles()`, `useRawCSS()` — inject styles by class or selector
|
|
97
|
+
- `useKeyframes()`, `useProperty()`, `useFontFace()`, `useCounterStyle()` — inject ancillary CSS rules
|
|
98
|
+
|
|
99
|
+
During SSR, all functions discover the collector via the same global getter registered by `TastyRegistry` — no React context or client boundary needed. In RSC mode without a collector (e.g., Astro zero-setup), CSS is accumulated in a per-request cache and flushed into an inline `<style>` tag by the next `tasty()` component in the tree. Ensure at least one `tasty()` component is present in every RSC render tree — standalone style functions alone cannot emit their CSS without a `tasty()` component to trigger the flush.
|
|
94
100
|
|
|
95
101
|
### Options
|
|
96
102
|
|
|
@@ -116,18 +122,17 @@ The nonce is automatically applied to all `<style>` and `<script>` tags injected
|
|
|
116
122
|
|
|
117
123
|
## Astro
|
|
118
124
|
|
|
119
|
-
|
|
125
|
+
Tasty offers three levels of Astro integration. Choose the one that matches your needs:
|
|
120
126
|
|
|
121
|
-
|
|
127
|
+
| Setup | Config needed | Deduplication | Hooks work | Client JS |
|
|
128
|
+
|---|---|---|---|---|
|
|
129
|
+
| Zero setup | None | Per render tree | No | None |
|
|
130
|
+
| `tastyIntegration({ islands: false })` | One line | Cross-tree | Yes | None |
|
|
131
|
+
| `tastyIntegration()` | One line | Cross-tree | Yes | Auto-hydration |
|
|
122
132
|
|
|
123
|
-
|
|
124
|
-
// src/middleware.ts
|
|
125
|
-
import { tastyMiddleware } from '@tenphi/tasty/ssr/astro';
|
|
126
|
-
|
|
127
|
-
export const onRequest = tastyMiddleware();
|
|
128
|
-
```
|
|
133
|
+
### Zero setup (static pages)
|
|
129
134
|
|
|
130
|
-
|
|
135
|
+
`tasty()` components work in Astro with **no configuration**. Each component emits its own inline `<style>` tag during server rendering via the RSC inline path. Just import and use:
|
|
131
136
|
|
|
132
137
|
```tsx
|
|
133
138
|
// src/components/Card.tsx
|
|
@@ -153,51 +158,118 @@ import Card from '../components/Card.tsx';
|
|
|
153
158
|
|
|
154
159
|
<html>
|
|
155
160
|
<body>
|
|
156
|
-
<Card>
|
|
157
|
-
<Card client:load>Island card — styles hydrated on client</Card>
|
|
161
|
+
<Card>Styled with zero setup</Card>
|
|
158
162
|
</body>
|
|
159
163
|
</html>
|
|
160
164
|
```
|
|
161
165
|
|
|
162
|
-
|
|
166
|
+
**Trade-offs**: Styles are deduplicated within each React render tree, but Astro renders separate component trees independently, so shared CSS (tokens, `@property` rules) may appear more than once. All style functions (`useGlobalStyles`, `useRawCSS`, `useKeyframes`, `useProperty`, `useFontFace`, `useCounterStyle`) work in zero-setup mode — their CSS is accumulated in the RSC cache and flushed by the next `tasty()` component in the tree.
|
|
163
167
|
|
|
164
|
-
|
|
168
|
+
Best for quick prototyping, small static sites, or trying Tasty out in Astro.
|
|
165
169
|
|
|
166
|
-
|
|
167
|
-
- **Islands** (`client:load`, `client:visible`, etc.): Styles are collected during SSR the same way. On the client, importing `@tenphi/tasty/ssr/astro` auto-hydrates the cache from `<script data-tasty-cache>`. The island's `computeStyles()` calls hit the cache during hydration.
|
|
170
|
+
### Astro Integration (recommended)
|
|
168
171
|
|
|
169
|
-
|
|
172
|
+
For production use, add `tastyIntegration()` to your Astro config. This registers middleware automatically and, by default, injects client-side hydration for islands.
|
|
170
173
|
|
|
171
|
-
|
|
174
|
+
#### With islands (default)
|
|
172
175
|
|
|
173
|
-
```
|
|
174
|
-
//
|
|
175
|
-
import '
|
|
176
|
-
import
|
|
176
|
+
```ts
|
|
177
|
+
// astro.config.mjs
|
|
178
|
+
import { defineConfig } from 'astro/config';
|
|
179
|
+
import react from '@astrojs/react';
|
|
180
|
+
import { tastyIntegration } from '@tenphi/tasty/ssr/astro';
|
|
177
181
|
|
|
178
|
-
|
|
179
|
-
|
|
182
|
+
export default defineConfig({
|
|
183
|
+
integrations: [react(), tastyIntegration()],
|
|
180
184
|
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
This gives you:
|
|
188
|
+
|
|
189
|
+
- A `ServerStyleCollector` per request via `AsyncLocalStorage`, deduplicating CSS across all React trees on the page
|
|
190
|
+
- A single consolidated `<style data-tasty-ssr>` injected into `</head>`
|
|
191
|
+
- A `<script data-tasty-cache>` tag with the `cacheKey -> className` map for client hydration
|
|
192
|
+
- Auto-injected client hydration script (via `injectScript('before-hydration')`) so islands skip the style pipeline during hydration -- no need to import anything manually in each island component
|
|
181
193
|
|
|
182
|
-
|
|
194
|
+
All style functions (`useGlobalStyles`, `useRawCSS`, `useKeyframes`, `useProperty`, `useFontFace`, `useCounterStyle`) work on the server.
|
|
195
|
+
|
|
196
|
+
```astro
|
|
197
|
+
---
|
|
198
|
+
// src/pages/index.astro
|
|
199
|
+
import Card from '../components/Card.tsx';
|
|
200
|
+
import Interactive from '../components/Interactive.tsx';
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
<html>
|
|
204
|
+
<body>
|
|
205
|
+
<Card>Static -- styles in <style data-tasty-ssr></Card>
|
|
206
|
+
<Interactive client:load>Island -- cache hydrated automatically</Interactive>
|
|
207
|
+
</body>
|
|
208
|
+
</html>
|
|
183
209
|
```
|
|
184
210
|
|
|
185
|
-
|
|
211
|
+
#### Static only (no client JS)
|
|
212
|
+
|
|
213
|
+
If your site has no `client:*` islands, skip the hydration script and cache transfer:
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
// astro.config.mjs
|
|
217
|
+
import { defineConfig } from 'astro/config';
|
|
218
|
+
import react from '@astrojs/react';
|
|
219
|
+
import { tastyIntegration } from '@tenphi/tasty/ssr/astro';
|
|
220
|
+
|
|
221
|
+
export default defineConfig({
|
|
222
|
+
integrations: [react(), tastyIntegration({ islands: false })],
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
This gives the same middleware deduplication and hook support, but ships zero client-side JavaScript. No `<script data-tasty-cache>` is emitted.
|
|
227
|
+
|
|
228
|
+
### Manual middleware (advanced)
|
|
229
|
+
|
|
230
|
+
If you need to compose Tasty's middleware with other middleware (e.g., via `sequence()`), use `tastyMiddleware()` directly:
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
// src/middleware.ts
|
|
234
|
+
import { sequence } from 'astro:middleware';
|
|
235
|
+
import { tastyMiddleware } from '@tenphi/tasty/ssr/astro';
|
|
236
|
+
|
|
237
|
+
export const onRequest = sequence(
|
|
238
|
+
tastyMiddleware(),
|
|
239
|
+
myOtherMiddleware,
|
|
240
|
+
);
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
For island hydration with manual middleware, import the client module in a shared entry point or in each island:
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
import '@tenphi/tasty/ssr/astro-client';
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
#### Options
|
|
186
250
|
|
|
187
251
|
```ts
|
|
188
|
-
// Skip cache state transfer
|
|
252
|
+
// Skip cache state transfer (static-only, no islands)
|
|
189
253
|
export const onRequest = tastyMiddleware({ transferCache: false });
|
|
190
254
|
```
|
|
191
255
|
|
|
256
|
+
### How it works
|
|
257
|
+
|
|
258
|
+
Astro's `@astrojs/react` renderer calls `renderToString()` for each React component without wrapping the tree in a provider. The middleware creates a `ServerStyleCollector` and binds it via `AsyncLocalStorage`. All `computeStyles()` calls within the request discover this collector automatically.
|
|
259
|
+
|
|
260
|
+
- **Static components** (no `client:*`): Styles are collected during `renderToString` and injected into `</head>` as a single `<style>` tag. No JavaScript is shipped.
|
|
261
|
+
- **Islands** (`client:load`, `client:visible`, etc.): Styles are collected during SSR the same way. On the client, the hydration script (auto-injected by `tastyIntegration()` or manually via `@tenphi/tasty/ssr/astro-client`) reads the cache state from `<script data-tasty-cache>` and pre-populates the injector. The island's `computeStyles()` calls hit the cache during hydration.
|
|
262
|
+
- The middleware uses streaming-compatible `TransformStream` processing to inject CSS into the response without buffering the entire HTML.
|
|
263
|
+
|
|
192
264
|
### CSP nonce
|
|
193
265
|
|
|
194
|
-
|
|
266
|
+
Call `configure({ nonce: '...' })` before any rendering happens. The middleware reads the nonce and applies it to injected `<style>` and `<script>` tags.
|
|
195
267
|
|
|
196
268
|
---
|
|
197
269
|
|
|
198
270
|
## Generic Framework Integration
|
|
199
271
|
|
|
200
|
-
Any React-based framework can integrate using `runWithCollector`, which binds a `ServerStyleCollector` to the current async context via `AsyncLocalStorage`. All
|
|
272
|
+
Any React-based framework can integrate using `runWithCollector`, which binds a `ServerStyleCollector` to the current async context via `AsyncLocalStorage`. All style function calls within the render automatically discover the collector.
|
|
201
273
|
|
|
202
274
|
```tsx
|
|
203
275
|
import {
|
|
@@ -279,7 +351,8 @@ const stream = await runWithCollector(collector, () =>
|
|
|
279
351
|
|---|---|
|
|
280
352
|
| `@tenphi/tasty/ssr` | Core SSR API: `ServerStyleCollector`, `runWithCollector`, `hydrateTastyCache` |
|
|
281
353
|
| `@tenphi/tasty/ssr/next` | Next.js App Router: `TastyRegistry` component |
|
|
282
|
-
| `@tenphi/tasty/ssr/astro` | Astro: `
|
|
354
|
+
| `@tenphi/tasty/ssr/astro` | Astro: `tastyIntegration`, `tastyMiddleware` |
|
|
355
|
+
| `@tenphi/tasty/ssr/astro-client` | Astro: client-side cache hydration (auto-injected by integration, or import manually) |
|
|
283
356
|
|
|
284
357
|
### `ServerStyleCollector`
|
|
285
358
|
|
|
@@ -306,9 +379,17 @@ Next.js App Router component. Props:
|
|
|
306
379
|
| `children` | `ReactNode` | required | Application tree |
|
|
307
380
|
| `transferCache` | `boolean` | `true` | Embed cache state script for zero-cost hydration |
|
|
308
381
|
|
|
382
|
+
### `tastyIntegration(options?)`
|
|
383
|
+
|
|
384
|
+
Astro integration factory. Registers middleware and optionally injects client hydration.
|
|
385
|
+
|
|
386
|
+
| Option | Type | Default | Description |
|
|
387
|
+
|---|---|---|---|
|
|
388
|
+
| `islands` | `boolean` | `true` | When `true`, injects client hydration script and enables `transferCache`. When `false`, no client JS is shipped. |
|
|
389
|
+
|
|
309
390
|
### `tastyMiddleware(options?)`
|
|
310
391
|
|
|
311
|
-
Astro middleware factory.
|
|
392
|
+
Astro middleware factory. Use for manual middleware composition.
|
|
312
393
|
|
|
313
394
|
| Option | Type | Default | Description |
|
|
314
395
|
|---|---|---|---|
|
|
@@ -320,7 +401,7 @@ Pre-populate the client injector cache. When called without arguments, reads fro
|
|
|
320
401
|
|
|
321
402
|
### `runWithCollector(collector, fn)`
|
|
322
403
|
|
|
323
|
-
Run a function with a `ServerStyleCollector` bound to the current async context via `AsyncLocalStorage`. All `
|
|
404
|
+
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()`, and `useCounterStyle()` — will find this collector.
|
|
324
405
|
|
|
325
406
|
---
|
|
326
407
|
|
|
@@ -328,11 +409,11 @@ Run a function with a `ServerStyleCollector` bound to the current async context
|
|
|
328
409
|
|
|
329
410
|
### Styles flash on page load (FOUC)
|
|
330
411
|
|
|
331
|
-
The `TastyRegistry` or `
|
|
412
|
+
The `TastyRegistry` or `tastyIntegration` is missing. Ensure your layout wraps the app with `TastyRegistry` (Next.js) or that `tastyIntegration()` is in your Astro config (or `tastyMiddleware()` is registered manually).
|
|
332
413
|
|
|
333
414
|
### Hydration mismatch warnings
|
|
334
415
|
|
|
335
|
-
Class names are deterministic for the same render order. If you see mismatches, ensure `hydrateTastyCache()` runs before React hydration. For Next.js, this is automatic. For Astro, import `@tenphi/tasty/ssr/astro` in your island components. For custom setups, call `hydrateTastyCache()` before `hydrateRoot()`.
|
|
416
|
+
Class names are deterministic for the same render order. If you see mismatches, ensure `hydrateTastyCache()` 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 `hydrateTastyCache()` before `hydrateRoot()`.
|
|
336
417
|
|
|
337
418
|
### Styles duplicated after hydration
|
|
338
419
|
|
package/docs/tasty-static.md
CHANGED
|
@@ -516,5 +516,5 @@ const card = tastyStatic({
|
|
|
516
516
|
|
|
517
517
|
- [Docs Hub](README.md) — Choose the right guide by task or rendering mode
|
|
518
518
|
- [Style DSL](dsl.md) — State maps, tokens, units, extending semantics (shared by runtime and static)
|
|
519
|
-
- [Runtime API](runtime.md) — Runtime styling: `tasty()` factory, component props, variants, sub-elements,
|
|
519
|
+
- [Runtime API](runtime.md) — Runtime styling: `tasty()` factory, component props, variants, sub-elements, style functions
|
|
520
520
|
- [Configuration](configuration.md) — Global configuration: tokens, recipes, custom units, and style handlers
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tenphi/tasty",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
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",
|
|
@@ -57,6 +57,11 @@
|
|
|
57
57
|
"import": "./dist/ssr/astro.js",
|
|
58
58
|
"default": "./dist/ssr/astro.js"
|
|
59
59
|
},
|
|
60
|
+
"./ssr/astro-client": {
|
|
61
|
+
"types": "./dist/ssr/astro-client.d.ts",
|
|
62
|
+
"import": "./dist/ssr/astro-client.js",
|
|
63
|
+
"default": "./dist/ssr/astro-client.js"
|
|
64
|
+
},
|
|
60
65
|
"./tasty.config": "./tasty.config.ts"
|
|
61
66
|
},
|
|
62
67
|
"files": [
|
|
@@ -67,7 +72,8 @@
|
|
|
67
72
|
"sideEffects": [
|
|
68
73
|
"./dist/ssr/index.js",
|
|
69
74
|
"./dist/ssr/next.js",
|
|
70
|
-
"./dist/ssr/astro.js"
|
|
75
|
+
"./dist/ssr/astro.js",
|
|
76
|
+
"./dist/ssr/astro-client.js"
|
|
71
77
|
],
|
|
72
78
|
"engines": {
|
|
73
79
|
"node": ">=20"
|