@tenphi/tasty 2.11.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -7
- package/dist/astro-B53trJyX.js +156 -0
- package/dist/astro-B53trJyX.js.map +1 -0
- package/dist/babel-HlEpSZrj.d.ts +83 -0
- package/dist/{collector-DYA5AOwr.d.ts → collector-BPzZezAG.d.ts} +44 -7
- package/dist/{collector-AHZaBSv8.js → collector-D14DcZx9.js} +75 -14
- package/dist/collector-D14DcZx9.js.map +1 -0
- package/dist/{config-YDAcLaVf.js → config-CCcE_tqx.js} +2312 -1131
- package/dist/config-CCcE_tqx.js.map +1 -0
- package/dist/{config-BtK9fUaz.d.ts → config-D2INrjr_.d.ts} +252 -25
- package/dist/core/index.d.ts +5 -5
- package/dist/core/index.js +6 -6
- package/dist/{core-CS4bzqGu.js → core-B5d0IPi2.js} +85 -133
- package/dist/core-B5d0IPi2.js.map +1 -0
- package/dist/{css-writer-BXSANVrq.js → css-writer-B5DBoxhf.js} +54 -15
- package/dist/css-writer-B5DBoxhf.js.map +1 -0
- package/dist/{format-rules-CYriCDwq.js → format-rules-BbKDjsfO.js} +2 -2
- package/dist/{format-rules-CYriCDwq.js.map → format-rules-BbKDjsfO.js.map} +1 -1
- package/dist/{hydrate-GVTorHpU.js → hydrate-CwALW3J9.js} +3 -11
- package/dist/{hydrate-GVTorHpU.js.map → hydrate-CwALW3J9.js.map} +1 -1
- package/dist/{index-BYtnj_gA.d.ts → index-BAAbdfRs.d.ts} +228 -153
- package/dist/{index-B9ih23uv.d.ts → index-l_NdJSUS.d.ts} +172 -61
- package/dist/index.d.ts +5 -5
- package/dist/index.js +191 -56
- package/dist/index.js.map +1 -1
- package/dist/{keyframes-CtcSlw4k.js → keyframes-B8M1FmtE.js} +10 -181
- package/dist/keyframes-B8M1FmtE.js.map +1 -0
- package/dist/{merge-styles-D4ITH4bc.js → merge-styles--1zOSFlS.js} +2 -2
- package/dist/{merge-styles-D4ITH4bc.js.map → merge-styles--1zOSFlS.js.map} +1 -1
- package/dist/{merge-styles-BZV-XAwX.d.ts → merge-styles-NEvqayL_.d.ts} +2 -2
- package/dist/{resolve-recipes-llr0COs8.js → resolve-recipes-Caix5NnH.js} +3 -3
- package/dist/{resolve-recipes-llr0COs8.js.map → resolve-recipes-Caix5NnH.js.map} +1 -1
- package/dist/ssr/astro-client.js +1 -1
- package/dist/ssr/astro-middleware.js +1 -1
- package/dist/ssr/astro-middleware.js.map +1 -1
- package/dist/ssr/astro.d.ts +1 -5
- package/dist/ssr/astro.js +2 -147
- package/dist/ssr/index.d.ts +2 -9
- package/dist/ssr/index.js +4 -4
- package/dist/ssr/index.js.map +1 -1
- package/dist/ssr/next.d.ts +1 -1
- package/dist/ssr/next.js +5 -6
- package/dist/ssr/next.js.map +1 -1
- package/dist/static/index.d.ts +2 -2
- package/dist/static/index.js +1 -1
- package/dist/zero/babel.d.ts +2 -80
- package/dist/zero/babel.js +27 -23
- package/dist/zero/babel.js.map +1 -1
- package/dist/zero/index.d.ts +16 -2
- package/dist/zero/index.js +2 -2
- package/dist/zero/next.d.ts +1 -1
- package/dist/zero/next.js +1 -1
- package/dist/zero/next.js.map +1 -1
- package/docs/README.md +3 -1
- package/docs/ai-agents.md +21 -7
- package/docs/configuration.md +220 -30
- package/docs/debug.md +7 -7
- package/docs/design-system.md +2 -2
- package/docs/dsl.md +68 -8
- package/docs/injector.md +46 -4
- package/docs/methodology.md +1 -1
- package/docs/migration-v3.md +285 -0
- package/docs/pipeline.md +3 -3
- package/docs/plugins.md +349 -0
- package/docs/react-api.md +82 -3
- package/docs/ssr.md +40 -33
- package/docs/styles.md +27 -9
- package/docs/tasty-static.md +8 -6
- package/package.json +7 -7
- package/tasty.config.ts +4 -0
- package/dist/collector-AHZaBSv8.js.map +0 -1
- package/dist/config-YDAcLaVf.js.map +0 -1
- package/dist/core-CS4bzqGu.js.map +0 -1
- package/dist/css-writer-BXSANVrq.js.map +0 -1
- package/dist/keyframes-CtcSlw4k.js.map +0 -1
- package/dist/ssr/astro.js.map +0 -1
package/docs/ssr.md
CHANGED
|
@@ -18,18 +18,18 @@ The Astro integration (`@tenphi/tasty/ssr/astro`) has no additional dependencies
|
|
|
18
18
|
|
|
19
19
|
## How It Works
|
|
20
20
|
|
|
21
|
-
`tasty()` components are hook-free and use `computeStyles()` internally — a synchronous, framework-agnostic function. On the server, `computeStyles()` discovers a `ServerStyleCollector` via a registered getter (module-level for Next.js, `globalThis` for Astro/generic frameworks using `AsyncLocalStorage`) and collects CSS into it instead of trying to access the DOM. On the client, CSS is injected synchronously into the DOM during render; the injector's content-based cache makes this idempotent. The collector accumulates all styles
|
|
21
|
+
`tasty()` components are hook-free and use `computeStyles()` internally — a synchronous, framework-agnostic function. On the server, `computeStyles()` discovers a `ServerStyleCollector` via a registered getter (module-level for Next.js, `globalThis` for Astro/generic frameworks using `AsyncLocalStorage`) and collects CSS into it instead of trying to access the DOM. On the client, CSS is injected synchronously into the DOM during render; the injector's content-based cache makes this idempotent. The collector accumulates all styles and serializes them as `<style>` tags plus a class-list script in the HTML. On the client, `hydrateTastyClasses()` pre-populates the injector's rules map with the rendered class names so that `computeStyles()` skips the rendering pipeline entirely during hydration.
|
|
22
22
|
|
|
23
23
|
```
|
|
24
24
|
Server Client
|
|
25
25
|
────── ──────
|
|
26
|
-
tasty() renders
|
|
27
|
-
└─ computeStyles() └─
|
|
26
|
+
tasty() renders hydrateTastyClasses() reads window.__TASTY__
|
|
27
|
+
└─ computeStyles() └─ marks rendered class names as already-in-DOM
|
|
28
28
|
└─ collector.collect()
|
|
29
29
|
tasty() renders
|
|
30
30
|
After render: └─ computeStyles()
|
|
31
|
-
<style data-tasty-ssr>
|
|
32
|
-
<script
|
|
31
|
+
<style data-tasty-ssr> └─ class name known → skip pipeline
|
|
32
|
+
<script> (pushes to __TASTY__) └─ no CSS re-injection
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
---
|
|
@@ -85,8 +85,8 @@ That's it. All `tasty()` components inside the tree automatically get SSR suppor
|
|
|
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
86
|
- During SSR, `TastyRegistry` creates a `ServerStyleCollector` and registers it via a module-level getter (not `globalThis` — this avoids leaking between Next.js's separate RSC and SSR module graphs). It also wraps children in a React context provider so that hooks inside the SSR tree can discover the collector. All style functions — `tasty()` components, `computeStyles()`, `useStyles()`, `useGlobalStyles()`, `useRawCSS()`, `useKeyframes()`, `useProperty()`, `useFontFace()`, and `useCounterStyle()` — discover the collector through the module-level getter or context provider.
|
|
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
|
-
- A companion inline `<script>` tag
|
|
89
|
-
- When the `@tenphi/tasty/ssr/next` module loads on the client, `
|
|
88
|
+
- A companion inline `<script>` tag pushes the rendered class names into `window.__TASTY__` for each flush. This streaming-friendly approach accumulates the class list incrementally as Suspense boundaries resolve.
|
|
89
|
+
- When the `@tenphi/tasty/ssr/next` module loads on the client, `hydrateTastyClasses()` runs automatically from `window.__TASTY__` and pre-populates the injector's rules map. During hydration, `computeStyles()` sees the class name is already registered and skips the entire pipeline.
|
|
90
90
|
|
|
91
91
|
### Using Tasty in Server Components
|
|
92
92
|
|
|
@@ -188,7 +188,7 @@ This gives you:
|
|
|
188
188
|
|
|
189
189
|
- A `ServerStyleCollector` per request via `AsyncLocalStorage`, deduplicating CSS across all React trees on the page
|
|
190
190
|
- A single consolidated `<style data-tasty-ssr>` injected into `</head>`
|
|
191
|
-
- A `<script
|
|
191
|
+
- A `<script>` tag pushing the rendered class names into `window.__TASTY__` for client hydration
|
|
192
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
|
|
193
193
|
|
|
194
194
|
All style functions (`useGlobalStyles`, `useRawCSS`, `useKeyframes`, `useProperty`, `useFontFace`, `useCounterStyle`) work on the server.
|
|
@@ -223,7 +223,7 @@ export default defineConfig({
|
|
|
223
223
|
});
|
|
224
224
|
```
|
|
225
225
|
|
|
226
|
-
This gives the same middleware deduplication and hook support, but ships zero client-side JavaScript. No `<script
|
|
226
|
+
This gives the same middleware deduplication and hook support, but ships zero client-side JavaScript. No class-list `<script>` is emitted.
|
|
227
227
|
|
|
228
228
|
### Manual middleware (advanced)
|
|
229
229
|
|
|
@@ -258,7 +258,7 @@ export const onRequest = tastyMiddleware({ transferCache: false });
|
|
|
258
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
259
|
|
|
260
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
|
|
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 class list from `window.__TASTY__` and pre-populates the injector's rules map. The island's `computeStyles()` calls see the class names as already registered and skip the pipeline during hydration.
|
|
262
262
|
- The middleware reads the full response body, then injects the collected CSS into `</head>` before sending the final HTML.
|
|
263
263
|
|
|
264
264
|
### CSP nonce
|
|
@@ -274,31 +274,30 @@ Any React-based framework can integrate using `runWithCollector`, which binds a
|
|
|
274
274
|
```tsx
|
|
275
275
|
import {
|
|
276
276
|
ServerStyleCollector,
|
|
277
|
+
createServerStyleCollector,
|
|
277
278
|
runWithCollector,
|
|
278
|
-
|
|
279
|
+
hydrateTastyClasses,
|
|
279
280
|
} from '@tenphi/tasty/ssr';
|
|
280
281
|
import { renderToString } from 'react-dom/server';
|
|
281
282
|
import { hydrateRoot } from 'react-dom/client';
|
|
282
283
|
|
|
283
284
|
// ── Server ──────────────────────────────────────────────
|
|
284
285
|
|
|
285
|
-
const collector =
|
|
286
|
+
const collector = createServerStyleCollector();
|
|
286
287
|
|
|
287
288
|
const html = await runWithCollector(collector, () =>
|
|
288
289
|
renderToString(<App />)
|
|
289
290
|
);
|
|
290
291
|
|
|
291
292
|
const css = collector.getCSS();
|
|
292
|
-
const
|
|
293
|
+
const classNames = collector.getRenderedClassNames();
|
|
293
294
|
|
|
294
295
|
// Embed in your HTML template:
|
|
295
296
|
const fullHtml = `
|
|
296
297
|
<html>
|
|
297
298
|
<head>
|
|
298
299
|
<style data-tasty-ssr>${css}</style>
|
|
299
|
-
<script
|
|
300
|
-
${JSON.stringify(cacheState)}
|
|
301
|
-
</script>
|
|
300
|
+
<script>(window.__TASTY__=window.__TASTY__||[]).push(${JSON.stringify(classNames)})</script>
|
|
302
301
|
</head>
|
|
303
302
|
<body>
|
|
304
303
|
<div id="root">${html}</div>
|
|
@@ -309,7 +308,7 @@ const fullHtml = `
|
|
|
309
308
|
// ── Client ──────────────────────────────────────────────
|
|
310
309
|
|
|
311
310
|
// Before hydration:
|
|
312
|
-
|
|
311
|
+
hydrateTastyClasses(); // reads from window.__TASTY__
|
|
313
312
|
|
|
314
313
|
hydrateRoot(document.getElementById('root'), <App />);
|
|
315
314
|
```
|
|
@@ -319,7 +318,7 @@ hydrateRoot(document.getElementById('root'), <App />);
|
|
|
319
318
|
For streaming with `renderToPipeableStream`, use `flushCSS()` instead of `getCSS()`:
|
|
320
319
|
|
|
321
320
|
```tsx
|
|
322
|
-
const collector =
|
|
321
|
+
const collector = createServerStyleCollector();
|
|
323
322
|
|
|
324
323
|
const stream = await runWithCollector(collector, () =>
|
|
325
324
|
renderToPipeableStream(<App />, {
|
|
@@ -330,12 +329,14 @@ const stream = await runWithCollector(collector, () =>
|
|
|
330
329
|
stream.pipe(res);
|
|
331
330
|
},
|
|
332
331
|
onAllReady() {
|
|
333
|
-
// Flush any remaining styles +
|
|
332
|
+
// Flush any remaining styles + class list
|
|
334
333
|
const css = collector.flushCSS();
|
|
335
334
|
if (css) res.write(`<style data-tasty-ssr>${css}</style>`);
|
|
336
335
|
|
|
337
|
-
const
|
|
338
|
-
res.write(
|
|
336
|
+
const classNames = collector.getRenderedClassNames();
|
|
337
|
+
res.write(
|
|
338
|
+
`<script>(window.__TASTY__=window.__TASTY__||[]).push(${JSON.stringify(classNames)})</script>`,
|
|
339
|
+
);
|
|
339
340
|
},
|
|
340
341
|
})
|
|
341
342
|
);
|
|
@@ -349,7 +350,7 @@ const stream = await runWithCollector(collector, () =>
|
|
|
349
350
|
|
|
350
351
|
| Import path | Description |
|
|
351
352
|
|---|---|
|
|
352
|
-
| `@tenphi/tasty/ssr` | Core SSR API: `ServerStyleCollector`, `runWithCollector`, `
|
|
353
|
+
| `@tenphi/tasty/ssr` | Core SSR API: `ServerStyleCollector`, `createServerStyleCollector`, `runWithCollector`, `hydrateTastyClasses` |
|
|
353
354
|
| `@tenphi/tasty/ssr/next` | Next.js App Router: `TastyRegistry` component |
|
|
354
355
|
| `@tenphi/tasty/ssr/astro` | Astro: `tastyIntegration`, `tastyMiddleware` |
|
|
355
356
|
| `@tenphi/tasty/ssr/astro-client` | Astro: client-side cache hydration (auto-injected by integration, or import manually) |
|
|
@@ -358,7 +359,7 @@ const stream = await runWithCollector(collector, () =>
|
|
|
358
359
|
|
|
359
360
|
Server-safe style collector. One instance per request.
|
|
360
361
|
|
|
361
|
-
Constructor: `new ServerStyleCollector(namePrefix?)
|
|
362
|
+
Constructor: `new ServerStyleCollector(namePrefix?)`, or use the `createServerStyleCollector(namePrefix?)` factory. The optional `namePrefix` overrides the value from `configure({ namePrefix })`; in normal usage you pass nothing and let the global config drive it. See [Configuration: Name prefix](configuration.md#name-prefix).
|
|
362
363
|
|
|
363
364
|
| Method | Description |
|
|
364
365
|
|---|---|
|
|
@@ -375,7 +376,7 @@ Constructor: `new ServerStyleCollector(namePrefix?)`. The optional `namePrefix`
|
|
|
375
376
|
| `collectInternals()` | Collect internal `@property` rules, `:root` token defaults, `@font-face`, and `@counter-style` rules from the global config. Called automatically on first chunk collection; idempotent. |
|
|
376
377
|
| `getCSS()` | Get all collected CSS as a single string. For non-streaming SSR. |
|
|
377
378
|
| `flushCSS()` | Get only CSS collected since the last flush. For streaming SSR. |
|
|
378
|
-
| `
|
|
379
|
+
| `getRenderedClassNames()` | Get the list of class names rendered so far. Serialized to `window.__TASTY__` for client hydration via `hydrateTastyClasses()`. |
|
|
379
380
|
|
|
380
381
|
### `TastyRegistry`
|
|
381
382
|
|
|
@@ -402,9 +403,9 @@ Astro middleware factory. Use for manual middleware composition.
|
|
|
402
403
|
|---|---|---|---|
|
|
403
404
|
| `transferCache` | `boolean` | `true` | Embed cache state script for island hydration |
|
|
404
405
|
|
|
405
|
-
### `
|
|
406
|
+
### `hydrateTastyClasses(classes?)`
|
|
406
407
|
|
|
407
|
-
Pre-populate the client injector
|
|
408
|
+
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).
|
|
408
409
|
|
|
409
410
|
### `runWithCollector(collector, fn)`
|
|
410
411
|
|
|
@@ -420,23 +421,29 @@ The `TastyRegistry` or `tastyIntegration` is missing. Ensure your layout wraps t
|
|
|
420
421
|
|
|
421
422
|
### Hydration mismatch warnings
|
|
422
423
|
|
|
423
|
-
Class names are deterministic for the same render order. If you see mismatches, ensure `
|
|
424
|
+
Class names are deterministic for the same render 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()` before `hydrateRoot()`.
|
|
425
|
+
|
|
426
|
+
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.
|
|
424
427
|
|
|
425
428
|
### Styles duplicated after hydration
|
|
426
429
|
|
|
427
430
|
**Global CSS** (`:root` tokens, `@property`, `globalStyles`, `@font-face`, `@counter-style`) configured via `configure()` is automatically deduplicated. When Tasty detects `<style data-tasty-ssr>` in the document, it skips client-side injection of globals that were already rendered by the SSR collector. This means `configure()` can be called with the full config on both server and client — no `typeof window === 'undefined'` guard is needed.
|
|
428
431
|
|
|
429
|
-
**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,
|
|
432
|
+
**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:
|
|
430
433
|
|
|
431
434
|
```tsx
|
|
432
|
-
import {
|
|
435
|
+
import { hydrateTastyClasses } from '@tenphi/tasty/ssr';
|
|
433
436
|
|
|
434
|
-
|
|
437
|
+
hydrateTastyClasses();
|
|
435
438
|
hydrateRoot(root, <App />);
|
|
436
439
|
|
|
437
|
-
// Optional: remove SSR style tags after hydration
|
|
438
|
-
document.querySelectorAll('style[data-tasty-ssr]').forEach(el => el.remove());
|
|
439
|
-
document
|
|
440
|
+
// Optional: remove SSR style tags and class-list scripts after hydration
|
|
441
|
+
document.querySelectorAll('style[data-tasty-ssr]').forEach((el) => el.remove());
|
|
442
|
+
document
|
|
443
|
+
.querySelectorAll('script')
|
|
444
|
+
.forEach((el) => {
|
|
445
|
+
if (el.textContent?.includes('__TASTY__')) el.remove();
|
|
446
|
+
});
|
|
440
447
|
```
|
|
441
448
|
|
|
442
449
|
### `AsyncLocalStorage` not available
|
package/docs/styles.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Style Properties Reference
|
|
2
2
|
|
|
3
|
-
All standard CSS properties are supported in Tasty and benefit from its syntax sugar: design tokens (`$name`, `#name`), custom units (`2x`, `1r`, `1bw`), color opacity (`#purple.5`), auto-calc (`(100% - 2x)`), and custom functions. Values are parsed through the Tasty engine automatically.
|
|
3
|
+
All standard CSS properties are supported in Tasty and benefit from its syntax sugar: design tokens (`$name`, `#name`), custom units (`2x`, `1r`, `1bw`), color opacity (`#purple.5`), auto-calc (`(100% - 2x)`), and [custom functions](dsl.md#functions-function) (`$$negative(10px)`). Values are parsed through the Tasty engine automatically.
|
|
4
4
|
|
|
5
5
|
The properties documented below have **custom handlers** with enhanced syntax. They should be **preferred** over their raw CSS equivalents because they:
|
|
6
6
|
|
|
@@ -100,18 +100,23 @@ For flex/grid layouts, outputs native `gap`. For block layouts, emulates gap usi
|
|
|
100
100
|
|
|
101
101
|
Element padding with directional modifiers and multi-group support. Use **comma-separated groups** to set a base value and then override specific directions.
|
|
102
102
|
|
|
103
|
-
**Syntax:** `[value]` | `[
|
|
103
|
+
**Syntax:** `[value]` | `[block inline]` | `[top right bottom left]` | `[value direction...]` — comma-separated for multiple groups
|
|
104
104
|
|
|
105
105
|
**Direction modifiers:** `top`, `right`, `bottom`, `left`
|
|
106
106
|
|
|
107
107
|
**Output modifier:** `longhand` — forces output as individual CSS longhand properties (`padding-top`, `padding-right`, `padding-bottom`, `padding-left`) instead of the `padding` shorthand. Useful when children need to selectively inherit individual directions.
|
|
108
108
|
|
|
109
|
+
**One value per directional group.** A group that names direction modifiers takes **exactly one** value, applied to every direction it names. For different values per side, use comma-separated groups: `padding: '2x top, 4x right'`. Writing `padding: '2x 4x top right'` ignores `4x` and warns in development — the parser keeps values and modifiers in separate buckets, so their interleaving in the source is not recoverable and `'2x 4x top right'`, `'2x top 4x right'` and `'top 2x right 4x'` are all the same input.
|
|
110
|
+
|
|
111
|
+
A group that names *no* direction keeps plain CSS shorthand order, which is unambiguous and unchanged.
|
|
112
|
+
|
|
109
113
|
| Value | Effect |
|
|
110
114
|
|-------|--------|
|
|
111
115
|
| `"2x"` | All sides `2x` |
|
|
112
116
|
| `"2x 1x"` | Top/bottom `2x`, left/right `1x` |
|
|
113
117
|
| `"2x top"` | Top `2x`, right/bottom/left `0` |
|
|
114
118
|
| `"1x left right"` | Left and right `1x`, top/bottom `0` |
|
|
119
|
+
| `"2x top, 4x right"` | Top `2x`, right `4x`, bottom/left `0` |
|
|
115
120
|
| `"1x, 2x top"` | All sides `1x`, then top overridden to `2x` |
|
|
116
121
|
| `"1x, 2x top bottom"` | Left/right `1x`, top/bottom `2x` |
|
|
117
122
|
| `"2x longhand"` | All sides `2x`, output as 4 individual `padding-*` properties |
|
|
@@ -128,17 +133,20 @@ Individual props `paddingTop`, `paddingRight`, `paddingBottom`, `paddingLeft`, `
|
|
|
128
133
|
|
|
129
134
|
Element margin. Same syntax, modifiers, and multi-group support as `padding`.
|
|
130
135
|
|
|
131
|
-
**Syntax:** `[value]` | `[
|
|
136
|
+
**Syntax:** `[value]` | `[block inline]` | `[top right bottom left]` | `[value direction...]` — comma-separated for multiple groups
|
|
132
137
|
|
|
133
138
|
**Direction modifiers:** `top`, `right`, `bottom`, `left`
|
|
134
139
|
|
|
135
140
|
**Output modifier:** `longhand` — forces output as individual CSS longhand properties (`margin-top`, etc.) instead of the `margin` shorthand.
|
|
136
141
|
|
|
142
|
+
The [one-value-per-directional-group rule](#padding) from `padding` applies here too.
|
|
143
|
+
|
|
137
144
|
| Value | Effect |
|
|
138
145
|
|-------|--------|
|
|
139
146
|
| `"2x"` | All sides `2x` |
|
|
140
147
|
| `"2x 1x"` | Top/bottom `2x`, left/right `1x` |
|
|
141
148
|
| `"2x top"` | Top `2x`, right/bottom/left `0` |
|
|
149
|
+
| `"2x top, 4x right"` | Top `2x`, right `4x`, bottom/left `0` |
|
|
142
150
|
| `"auto left right, 1x top bottom"` | Left/right `auto`, top/bottom `1x` |
|
|
143
151
|
| `true` | All sides `1x` |
|
|
144
152
|
| Number | Converted to `px` |
|
|
@@ -191,17 +199,22 @@ Separate `minHeight` and `maxHeight` props are supported and override values fro
|
|
|
191
199
|
|
|
192
200
|
Positioning offsets with directional modifiers and multi-group support. Same directional syntax as `padding`.
|
|
193
201
|
|
|
194
|
-
**Syntax:** `[value]` | `[
|
|
202
|
+
**Syntax:** `[value]` | `[block inline]` | `[top right bottom left]` | `[value direction...]` — comma-separated for multiple groups
|
|
195
203
|
|
|
196
204
|
**Direction modifiers:** `top`, `right`, `bottom`, `left`
|
|
197
205
|
|
|
206
|
+
The [one-value-per-directional-group rule](#padding) from `padding` applies here too, with `dock`
|
|
207
|
+
as the single exception.
|
|
208
|
+
|
|
198
209
|
**`dock` modifier:** pins the named edge and spans its full length, by also applying the value
|
|
199
210
|
to the two perpendicular sides. `inset: 'bottom dock'` gives `inset: auto 0 0 0` — anchored to
|
|
200
211
|
the bottom, full width. Without a direction, `dock` fills every side.
|
|
201
212
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
213
|
+
`dock` is the one place a directional group takes **two** values: the first insets the named
|
|
214
|
+
edge, the second the sides it spans. `inset: '2x 4x bottom dock'` pins the bottom at `2x` and
|
|
215
|
+
insets the sides by `4x`. A third value, or a second value without `dock`, is ignored and warns
|
|
216
|
+
in development. With several directions the first value applies to each named edge and the
|
|
217
|
+
second to every side they span.
|
|
205
218
|
|
|
206
219
|
**Output modifier:** `longhand` — forces output as individual CSS properties (`top`, `right`, `bottom`, `left`) instead of the `inset` shorthand.
|
|
207
220
|
|
|
@@ -572,15 +585,20 @@ Scrollbar styling using CSS standard properties (`scrollbar-width`, `scrollbar-c
|
|
|
572
585
|
|
|
573
586
|
Gradient-based edge fading using CSS masks. Use **comma-separated groups** to set different widths and colors per direction.
|
|
574
587
|
|
|
575
|
-
**Syntax:** `[width] [
|
|
588
|
+
**Syntax:** `[width] [direction...] [#from-color] [#to-color]` — comma-separated for multiple groups
|
|
576
589
|
|
|
577
590
|
**Direction modifiers:** `top`, `right`, `bottom`, `left`
|
|
578
591
|
|
|
592
|
+
A group that names edges takes a **single** width, applied to every edge it names — different
|
|
593
|
+
widths per edge come from comma groups. A group that names no edge covers all four and keeps
|
|
594
|
+
plain CSS shorthand order, like `padding`. See the [`padding` rule](#padding).
|
|
595
|
+
|
|
579
596
|
| Value | Effect |
|
|
580
597
|
|-------|--------|
|
|
581
598
|
| `"top"` | Fade top edge, default width (`2x`), all other edges unfaded |
|
|
582
599
|
| `"2x left right"` | Fade left and right edges with `2x` width, top/bottom unfaded |
|
|
583
|
-
| `"3x 1x
|
|
600
|
+
| `"3x 1x"` | All edges faded — top/bottom `3x`, left/right `1x` (CSS shorthand order) |
|
|
601
|
+
| `"3x top, 1x bottom"` | Fade top with `3x` width, bottom with `1x` width |
|
|
584
602
|
| `"2x #transparent #dark"` | All edges faded with `2x` width, from `#transparent` to `#dark` |
|
|
585
603
|
| `"top #a #b, bottom #c #d"` | Top fades from `#a` to `#b`, bottom fades from `#c` to `#d` |
|
|
586
604
|
|
package/docs/tasty-static.md
CHANGED
|
@@ -20,7 +20,7 @@ The zero-runtime mode is part of the main `@tenphi/tasty` package but requires a
|
|
|
20
20
|
| `@babel/core` | >= 7.24 | Babel plugin (`@tenphi/tasty/babel-plugin`) |
|
|
21
21
|
| `@babel/helper-plugin-utils` | >= 7.24 | Babel plugin |
|
|
22
22
|
| `@babel/types` | >= 7.24 | Babel plugin |
|
|
23
|
-
| `jiti` | >= 2.6 | Next.js wrapper (`@tenphi/tasty/next`) when using `configFile` option |
|
|
23
|
+
| `jiti` | >= 2.6 | Next.js wrapper (`@tenphi/tasty/zero/next`) when using `configFile` option |
|
|
24
24
|
|
|
25
25
|
All of these are declared as optional peer dependencies of `@tenphi/tasty`. Install only what your setup requires:
|
|
26
26
|
|
|
@@ -168,10 +168,11 @@ module.exports = {
|
|
|
168
168
|
| `config.replaceTokens` | `Record<string, string \| number>` | — | Parse-time token substitution. Keys use `$name` for custom properties and `#name` for color tokens. |
|
|
169
169
|
| `config.recipes` | `Record<string, RecipeStyles>` | `{}` | Predefined style recipes |
|
|
170
170
|
| `config.keyframes` | `Record<string, KeyframesSteps>` | — | Global `@keyframes` definitions available to all `tastyStatic` calls |
|
|
171
|
-
| `config.
|
|
172
|
-
| `config.
|
|
171
|
+
| `config.fontFaces` | `Record<string, FontFaceInput>` | — | Global `@font-face` definitions |
|
|
172
|
+
| `config.counterStyles` | `Record<string, CounterStyleDescriptors>` | — | Global `@counter-style` definitions |
|
|
173
173
|
| `config.units` | `Record<string, string \| UnitHandler>` | — | Custom units for the style parser (merged with built-ins). E.g. `{ em: 'em', vw: 'vw' }` |
|
|
174
|
-
| `config.
|
|
174
|
+
| `config.functions` | `Record<string, FunctionDefinition \| Function>` | — | Custom functions (merged). Bare keys → parse functions; `$$name` keys → declarative CSS `@function` definitions |
|
|
175
|
+
| `config.polyfills` | `{ functions?: boolean }` | `{}` | Opt-in polyfills. `functions: true` inlines `@function` calls into plain CSS at build time |
|
|
175
176
|
| `config.plugins` | `TastyPlugin[]` | — | Plugins that extend tasty with custom functions, units, states, and handlers |
|
|
176
177
|
| `config.handlers` | `Record<string, StyleHandlerDefinition>` | — | Custom style handlers that transform style properties into CSS declarations |
|
|
177
178
|
| `config.presets` | `Record<string, TypographyPreset>` | — | Typography presets — shorthand for `generateTypographyTokens()`. Generated tokens merge under explicit `tokens`. |
|
|
@@ -236,7 +237,7 @@ The `withTastyZero` wrapper configures both **webpack** and **Turbopack** automa
|
|
|
236
237
|
|
|
237
238
|
```typescript
|
|
238
239
|
// next.config.ts
|
|
239
|
-
import { withTastyZero } from '@tenphi/tasty/next';
|
|
240
|
+
import { withTastyZero } from '@tenphi/tasty/zero/next';
|
|
240
241
|
|
|
241
242
|
export default withTastyZero({
|
|
242
243
|
output: 'public/tasty.css',
|
|
@@ -395,7 +396,7 @@ With Next.js:
|
|
|
395
396
|
|
|
396
397
|
```typescript
|
|
397
398
|
// next.config.ts
|
|
398
|
-
import { withTastyZero } from '@tenphi/tasty/next';
|
|
399
|
+
import { withTastyZero } from '@tenphi/tasty/zero/next';
|
|
399
400
|
|
|
400
401
|
export default withTastyZero({
|
|
401
402
|
mode: 'inject',
|
|
@@ -545,3 +546,4 @@ const card = tastyStatic({
|
|
|
545
546
|
- [Style DSL](dsl.md) — State maps, tokens, units, extending semantics (shared by runtime and static)
|
|
546
547
|
- [React API](react-api.md) — Runtime styling: `tasty()` factory, component props, variants, sub-elements, style functions
|
|
547
548
|
- [Configuration](configuration.md) — Global configuration: tokens, recipes, custom units, and style handlers
|
|
549
|
+
- [Plugins & Extension Points](plugins.md) — Which extension points work at build time (`functions`, `units`, `states`, `handlers`, `recipes`) and which do not (`propHandlers` — `tastyStatic()` takes styles objects, not props)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tenphi/tasty",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "A design-system-integrated styling system and DSL for concise, state-aware UI styling",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"import": "./dist/zero/index.js",
|
|
33
33
|
"default": "./dist/zero/index.js"
|
|
34
34
|
},
|
|
35
|
-
"./next": {
|
|
35
|
+
"./zero/next": {
|
|
36
36
|
"types": "./dist/zero/next.d.ts",
|
|
37
37
|
"import": "./dist/zero/next.js",
|
|
38
38
|
"default": "./dist/zero/next.js"
|
|
@@ -166,19 +166,19 @@
|
|
|
166
166
|
"name": "main (import *)",
|
|
167
167
|
"path": "dist/index.js",
|
|
168
168
|
"import": "*",
|
|
169
|
-
"limit": "
|
|
169
|
+
"limit": "57 kB"
|
|
170
170
|
},
|
|
171
171
|
{
|
|
172
172
|
"name": "core (import *)",
|
|
173
173
|
"path": "dist/core/index.js",
|
|
174
174
|
"import": "*",
|
|
175
|
-
"limit": "
|
|
175
|
+
"limit": "54 kB"
|
|
176
176
|
},
|
|
177
177
|
{
|
|
178
178
|
"name": "static",
|
|
179
179
|
"path": "dist/static/index.js",
|
|
180
180
|
"import": "*",
|
|
181
|
-
"limit": "
|
|
181
|
+
"limit": "18.5 kB"
|
|
182
182
|
},
|
|
183
183
|
{
|
|
184
184
|
"name": "zero",
|
|
@@ -189,7 +189,7 @@
|
|
|
189
189
|
"path",
|
|
190
190
|
"crypto"
|
|
191
191
|
],
|
|
192
|
-
"limit": "
|
|
192
|
+
"limit": "33 kB"
|
|
193
193
|
},
|
|
194
194
|
{
|
|
195
195
|
"name": "babel-plugin",
|
|
@@ -200,7 +200,7 @@
|
|
|
200
200
|
"path",
|
|
201
201
|
"crypto"
|
|
202
202
|
],
|
|
203
|
-
"limit": "
|
|
203
|
+
"limit": "50.5 kB"
|
|
204
204
|
}
|
|
205
205
|
],
|
|
206
206
|
"scripts": {
|
package/tasty.config.ts
CHANGED
|
@@ -13,4 +13,8 @@ export default {
|
|
|
13
13
|
'$bold-font-weight',
|
|
14
14
|
],
|
|
15
15
|
styles: ['viewTransitionName'],
|
|
16
|
+
// This repo imports its own API relatively, so the plugin's default
|
|
17
|
+
// `@tenphi/tasty` import tracking never matches. List the local entry points so
|
|
18
|
+
// `tasty({ styles })` call sites are actually linted.
|
|
19
|
+
importSources: ['@tenphi/tasty', './tasty', '../tasty', './src/tasty'],
|
|
16
20
|
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"collector-AHZaBSv8.js","names":[],"sources":["../src/ssr/collector.ts"],"sourcesContent":["/**\n * ServerStyleCollector — server-safe style collector for SSR.\n *\n * Accumulates CSS rules and cache metadata during server rendering.\n * This is the server-side counterpart to StyleInjector: it allocates\n * hash-based class names using the configured `namePrefix` (defaults\n * to `'t'`), formats CSS rules into text, and tracks rendered class\n * names for lightweight client transfer.\n *\n * One instance is created per HTTP request. Concurrent requests\n * each get their own collector (via AsyncLocalStorage or React context).\n */\n\nimport {\n getEffectiveProperties,\n getGlobalStyles,\n getGlobalCounterStyle,\n getGlobalFontFace,\n getGlobalConfigTokens,\n getNamePrefix,\n} from '../config';\nimport { formatCounterStyleRule } from '../counter-style';\nimport { fontFaceContentHash, formatFontFaceRule } from '../font-face';\nimport { renderStyles } from '../pipeline';\nimport type { StyleResult } from '../pipeline';\nimport { hashString } from '../utils/hash';\nimport {\n makeClassName,\n makeCounterStyleName,\n makeKeyframeName,\n validateNamePrefix,\n} from '../utils/name-prefix';\nimport { formatPropertyCSS } from './format-property';\nimport { formatGlobalRules } from './format-global-rules';\nimport { formatRules } from './format-rules';\n\nexport class ServerStyleCollector {\n private chunks = new Map<string, string>();\n private cacheKeyToClassName = new Map<string, string>();\n private flushedKeys = new Set<string>();\n private propertyRules = new Map<string, string>();\n private flushedPropertyKeys = new Set<string>();\n private keyframeRules = new Map<string, string>();\n private flushedKeyframeKeys = new Set<string>();\n private globalStyles = new Map<string, string>();\n private flushedGlobalKeys = new Set<string>();\n private rawCSS = new Map<string, string>();\n private flushedRawKeys = new Set<string>();\n private fontFaceRules = new Map<string, string>();\n private flushedFontFaceKeys = new Set<string>();\n private counterStyleRules = new Map<string, string>();\n private flushedCounterStyleKeys = new Set<string>();\n private keyframesCounter = 0;\n private counterStyleCounter = 0;\n private internalsCollected = false;\n private namePrefix: string;\n\n /**\n * @param namePrefix - Optional override for the configured prefix.\n * Defaults to the value from `configure({ namePrefix })` (or `'t'`).\n * Pass an explicit prefix when constructing a collector outside the\n * normal configure() lifecycle (e.g. in tests). Validated eagerly\n * so misconfiguration fails before any CSS is collected.\n */\n constructor(namePrefix?: string) {\n if (namePrefix !== undefined) {\n validateNamePrefix(namePrefix);\n }\n this.namePrefix = namePrefix ?? getNamePrefix();\n }\n\n private generateClassName(cacheKey: string): string {\n return makeClassName(this.namePrefix, hashString(cacheKey));\n }\n\n /**\n * Collect internal @property rules and :root token defaults.\n * Mirrors markStylesGenerated() from the client-side injector.\n * Called automatically on first chunk collection; idempotent.\n *\n * Internals are always emitted here — the RSC path deliberately\n * defers to SSR so that tokens appear exactly once per page in\n * <style data-tasty-ssr> (avoiding duplication of large token sets).\n */\n collectInternals(): void {\n if (this.internalsCollected) return;\n this.internalsCollected = true;\n\n for (const [token, definition] of Object.entries(\n getEffectiveProperties(),\n )) {\n const css = formatPropertyCSS(token, definition);\n if (css) {\n this.collectProperty(`__prop:${token}`, css);\n }\n }\n\n const tokenStyles = getGlobalConfigTokens();\n if (tokenStyles && Object.keys(tokenStyles).length > 0) {\n const tokenRules = renderStyles(tokenStyles, ':root') as StyleResult[];\n if (tokenRules.length > 0) {\n const css = formatGlobalRules(tokenRules);\n if (css) {\n this.collectGlobalStyles('__global:tokens', css);\n }\n }\n }\n\n const globalFF = getGlobalFontFace();\n if (globalFF) {\n for (const [family, input] of Object.entries(globalFF)) {\n const descriptors = Array.isArray(input) ? input : [input];\n for (const desc of descriptors) {\n const hash = fontFaceContentHash(family, desc);\n const css = formatFontFaceRule(family, desc);\n this.collectFontFace(hash, css);\n }\n }\n }\n\n const globalCS = getGlobalCounterStyle();\n if (globalCS) {\n for (const [name, descriptors] of Object.entries(globalCS)) {\n const css = formatCounterStyleRule(name, descriptors);\n this.collectCounterStyle(name, css);\n }\n }\n\n const globalStyles = getGlobalStyles();\n if (globalStyles) {\n for (const [selector, styles] of Object.entries(globalStyles)) {\n if (Object.keys(styles).length > 0) {\n const rules = renderStyles(styles, selector) as StyleResult[];\n if (rules.length > 0) {\n const css = formatGlobalRules(rules);\n if (css) {\n this.collectGlobalStyles(`__global:styles:${selector}`, css);\n }\n }\n }\n }\n }\n }\n\n /**\n * Allocate a className for a cache key, server-side.\n * Mirrors StyleInjector.allocateClassName but without DOM access.\n */\n allocateClassName(cacheKey: string): {\n className: string;\n isNewAllocation: boolean;\n } {\n const existing = this.cacheKeyToClassName.get(cacheKey);\n if (existing) {\n return { className: existing, isNewAllocation: false };\n }\n\n const className = this.generateClassName(cacheKey);\n this.cacheKeyToClassName.set(cacheKey, className);\n\n return { className, isNewAllocation: true };\n }\n\n /**\n * Record CSS rules for a chunk.\n * Called by useStyles during server render.\n */\n collectChunk(\n cacheKey: string,\n className: string,\n rules: StyleResult[],\n ): void {\n if (this.chunks.has(cacheKey)) return;\n const css = formatRules(rules, className);\n if (css) {\n this.chunks.set(cacheKey, css);\n }\n }\n\n /**\n * Record a @property rule. Deduplicated by name.\n */\n collectProperty(name: string, css: string): void {\n if (!this.propertyRules.has(name)) {\n this.propertyRules.set(name, css);\n }\n }\n\n /**\n * Record a @keyframes rule. Deduplicated by name.\n */\n collectKeyframes(name: string, css: string): void {\n if (!this.keyframeRules.has(name)) {\n this.keyframeRules.set(name, css);\n }\n }\n\n /**\n * Allocate a keyframe name for SSR. Uses provided name or generates one.\n */\n allocateKeyframeName(providedName?: string): string {\n return (\n providedName ??\n makeKeyframeName(this.namePrefix, String(this.keyframesCounter++))\n );\n }\n\n /**\n * Record a @font-face rule. Deduplicated by key (content hash).\n */\n collectFontFace(key: string, css: string): void {\n if (!this.fontFaceRules.has(key)) {\n this.fontFaceRules.set(key, css);\n }\n }\n\n /**\n * Record a @counter-style rule. Deduplicated by name.\n */\n collectCounterStyle(name: string, css: string): void {\n if (!this.counterStyleRules.has(name)) {\n this.counterStyleRules.set(name, css);\n }\n }\n\n /**\n * Allocate a counter-style name for SSR. Uses provided name or generates one.\n */\n allocateCounterStyleName(providedName?: string): string {\n return (\n providedName ??\n makeCounterStyleName(this.namePrefix, String(this.counterStyleCounter++))\n );\n }\n\n /**\n * Record global styles (from useGlobalStyles). Deduplicated by key.\n */\n collectGlobalStyles(key: string, css: string): void {\n if (!this.globalStyles.has(key)) {\n this.globalStyles.set(key, css);\n }\n }\n\n /**\n * Record raw CSS text (from useRawCSS). Deduplicated by key.\n */\n collectRawCSS(key: string, css: string): void {\n if (!this.rawCSS.has(key)) {\n this.rawCSS.set(key, css);\n }\n }\n\n /**\n * Extract all CSS collected so far as a single string.\n * Includes @property and @keyframes rules.\n * Used for non-streaming SSR (renderToString).\n */\n getCSS(): string {\n const parts: string[] = [];\n\n for (const css of this.propertyRules.values()) {\n parts.push(css);\n }\n\n for (const css of this.fontFaceRules.values()) {\n parts.push(css);\n }\n\n for (const css of this.counterStyleRules.values()) {\n parts.push(css);\n }\n\n for (const css of this.rawCSS.values()) {\n parts.push(css);\n }\n\n for (const css of this.globalStyles.values()) {\n parts.push(css);\n }\n\n for (const css of this.chunks.values()) {\n parts.push(css);\n }\n\n for (const css of this.keyframeRules.values()) {\n parts.push(css);\n }\n\n return parts.join('\\n');\n }\n\n /**\n * Flush only newly collected CSS since the last flush.\n * Used for streaming SSR (renderToPipeableStream + useServerInsertedHTML).\n */\n flushCSS(): string {\n const parts: string[] = [];\n\n for (const [name, css] of this.propertyRules) {\n if (!this.flushedPropertyKeys.has(name)) {\n parts.push(css);\n this.flushedPropertyKeys.add(name);\n }\n }\n\n for (const [key, css] of this.fontFaceRules) {\n if (!this.flushedFontFaceKeys.has(key)) {\n parts.push(css);\n this.flushedFontFaceKeys.add(key);\n }\n }\n\n for (const [key, css] of this.counterStyleRules) {\n if (!this.flushedCounterStyleKeys.has(key)) {\n parts.push(css);\n this.flushedCounterStyleKeys.add(key);\n }\n }\n\n for (const [key, css] of this.rawCSS) {\n if (!this.flushedRawKeys.has(key)) {\n parts.push(css);\n this.flushedRawKeys.add(key);\n }\n }\n\n for (const [key, css] of this.globalStyles) {\n if (!this.flushedGlobalKeys.has(key)) {\n parts.push(css);\n this.flushedGlobalKeys.add(key);\n }\n }\n\n for (const [key, css] of this.chunks) {\n if (!this.flushedKeys.has(key)) {\n parts.push(css);\n this.flushedKeys.add(key);\n }\n }\n\n for (const [name, css] of this.keyframeRules) {\n if (!this.flushedKeyframeKeys.has(name)) {\n parts.push(css);\n this.flushedKeyframeKeys.add(name);\n }\n }\n\n return parts.join('\\n');\n }\n\n private flushedClassNames = new Set<string>();\n\n /**\n * Return class names rendered since the last call (for streaming).\n * Used to emit lightweight class-list scripts for client hydration.\n */\n getRenderedClassNames(): string[] {\n const names: string[] = [];\n for (const className of this.cacheKeyToClassName.values()) {\n if (!this.flushedClassNames.has(className)) {\n this.flushedClassNames.add(className);\n names.push(className);\n }\n }\n return names;\n }\n}\n"],"mappings":"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"}
|