@tenphi/tasty 0.8.0 → 0.10.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 +47 -1
- package/dist/chunks/definitions.js +1 -2
- package/dist/chunks/definitions.js.map +1 -1
- package/dist/config.js +15 -1
- package/dist/config.js.map +1 -1
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +3 -3
- package/dist/hooks/useGlobalStyles.d.ts +3 -0
- package/dist/hooks/useGlobalStyles.js +28 -1
- package/dist/hooks/useGlobalStyles.js.map +1 -1
- package/dist/hooks/useKeyframes.js +18 -3
- package/dist/hooks/useKeyframes.js.map +1 -1
- package/dist/hooks/useProperty.js +36 -13
- package/dist/hooks/useProperty.js.map +1 -1
- package/dist/hooks/useRawCSS.js +13 -1
- package/dist/hooks/useRawCSS.js.map +1 -1
- package/dist/hooks/useStyles.d.ts +5 -0
- package/dist/hooks/useStyles.js +82 -3
- package/dist/hooks/useStyles.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/injector/index.d.ts +9 -1
- package/dist/injector/index.js +9 -1
- package/dist/injector/index.js.map +1 -1
- package/dist/injector/injector.d.ts +9 -0
- package/dist/injector/injector.js +20 -1
- package/dist/injector/injector.js.map +1 -1
- package/dist/injector/sheet-manager.d.ts +1 -0
- package/dist/injector/sheet-manager.js +1 -0
- package/dist/injector/sheet-manager.js.map +1 -1
- package/dist/parser/classify.js.map +1 -1
- package/dist/properties/index.js +20 -5
- package/dist/properties/index.js.map +1 -1
- package/dist/properties/property-type-resolver.js +4 -0
- package/dist/properties/property-type-resolver.js.map +1 -1
- package/dist/ssr/astro.d.ts +29 -0
- package/dist/ssr/astro.js +65 -0
- package/dist/ssr/astro.js.map +1 -0
- package/dist/ssr/async-storage.d.ts +17 -0
- package/dist/ssr/async-storage.js +35 -0
- package/dist/ssr/async-storage.js.map +1 -0
- package/dist/ssr/collect-auto-properties.js +40 -0
- package/dist/ssr/collect-auto-properties.js.map +1 -0
- package/dist/ssr/collector.d.ts +85 -0
- package/dist/ssr/collector.js +173 -0
- package/dist/ssr/collector.js.map +1 -0
- package/dist/ssr/context.d.ts +8 -0
- package/dist/ssr/context.js +14 -0
- package/dist/ssr/context.js.map +1 -0
- package/dist/ssr/format-global-rules.js +22 -0
- package/dist/ssr/format-global-rules.js.map +1 -0
- package/dist/ssr/format-keyframes.js +70 -0
- package/dist/ssr/format-keyframes.js.map +1 -0
- package/dist/ssr/format-property.js +48 -0
- package/dist/ssr/format-property.js.map +1 -0
- package/dist/ssr/format-rules.js +70 -0
- package/dist/ssr/format-rules.js.map +1 -0
- package/dist/ssr/hydrate.d.ts +22 -0
- package/dist/ssr/hydrate.js +50 -0
- package/dist/ssr/hydrate.js.map +1 -0
- package/dist/ssr/index.d.ts +5 -0
- package/dist/ssr/index.js +12 -0
- package/dist/ssr/index.js.map +1 -0
- package/dist/ssr/next.d.ts +45 -0
- package/dist/ssr/next.js +71 -0
- package/dist/ssr/next.js.map +1 -0
- package/dist/ssr/ssr-collector-ref.js +12 -0
- package/dist/ssr/ssr-collector-ref.js.map +1 -0
- package/dist/styles/predefined.d.ts +0 -2
- package/dist/styles/predefined.js +0 -3
- package/dist/styles/predefined.js.map +1 -1
- package/dist/styles/preset.js +1 -1
- package/dist/styles/preset.js.map +1 -1
- package/dist/styles/scrollbar.d.ts +9 -5
- package/dist/styles/scrollbar.js +25 -89
- package/dist/styles/scrollbar.js.map +1 -1
- package/dist/styles/transition.js +1 -1
- package/dist/styles/transition.js.map +1 -1
- package/dist/styles/types.d.ts +10 -13
- package/dist/tasty.d.ts +67 -68
- package/dist/zero/babel.d.ts +16 -2
- package/dist/zero/babel.js +32 -1
- package/dist/zero/babel.js.map +1 -1
- package/dist/zero/next.d.ts +29 -30
- package/dist/zero/next.js +49 -39
- package/dist/zero/next.js.map +1 -1
- package/docs/ssr.md +372 -0
- package/docs/styles.md +19 -12
- package/package.json +44 -28
- package/dist/styles/styledScrollbar.d.ts +0 -47
- package/dist/styles/styledScrollbar.js +0 -38
- package/dist/styles/styledScrollbar.js.map +0 -1
package/docs/ssr.md
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
# Server-Side Rendering (SSR)
|
|
2
|
+
|
|
3
|
+
Tasty supports server-side rendering with zero-cost client hydration. Your existing `tasty()` components work unchanged -- SSR is opt-in and requires no per-component modifications.
|
|
4
|
+
|
|
5
|
+
**Requires React 19+.**
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## How It Works
|
|
10
|
+
|
|
11
|
+
During server rendering, `useStyles()` detects a `ServerStyleCollector` and collects CSS into it instead of trying to access the DOM. The collector accumulates all styles, serializes them as `<style>` tags and a cache state script in the HTML. On the client, `hydrateTastyCache()` pre-populates the injector cache so that `useStyles()` skips the rendering pipeline entirely during hydration.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
Server Client
|
|
15
|
+
────── ──────
|
|
16
|
+
tasty() renders hydrateTastyCache() pre-populates cache
|
|
17
|
+
└─ useStyles() └─ cacheKey → className map ready
|
|
18
|
+
└─ collector.collect()
|
|
19
|
+
tasty() renders
|
|
20
|
+
After render: └─ useStyles()
|
|
21
|
+
<style data-tasty-ssr> └─ cache hit → skip pipeline
|
|
22
|
+
<script data-tasty-cache> └─ no CSS re-injection
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Next.js (App Router)
|
|
28
|
+
|
|
29
|
+
### 1. Create the registry
|
|
30
|
+
|
|
31
|
+
Create a client component that wraps your tree with `TastyRegistry`:
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
// app/tasty-registry.tsx
|
|
35
|
+
'use client';
|
|
36
|
+
|
|
37
|
+
import { TastyRegistry } from '@tenphi/tasty/ssr/next';
|
|
38
|
+
|
|
39
|
+
export default function TastyStyleRegistry({
|
|
40
|
+
children,
|
|
41
|
+
}: {
|
|
42
|
+
children: React.ReactNode;
|
|
43
|
+
}) {
|
|
44
|
+
return <TastyRegistry>{children}</TastyRegistry>;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 2. Add to root layout
|
|
49
|
+
|
|
50
|
+
Wrap your application in the registry:
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
// app/layout.tsx
|
|
54
|
+
import TastyStyleRegistry from './tasty-registry';
|
|
55
|
+
|
|
56
|
+
export default function RootLayout({
|
|
57
|
+
children,
|
|
58
|
+
}: {
|
|
59
|
+
children: React.ReactNode;
|
|
60
|
+
}) {
|
|
61
|
+
return (
|
|
62
|
+
<html>
|
|
63
|
+
<body>
|
|
64
|
+
<TastyStyleRegistry>{children}</TastyStyleRegistry>
|
|
65
|
+
</body>
|
|
66
|
+
</html>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
That's it. All `tasty()` components inside the tree automatically get SSR support. No per-component changes needed.
|
|
72
|
+
|
|
73
|
+
### How it works
|
|
74
|
+
|
|
75
|
+
- `TastyRegistry` is a `'use client'` component, but Next.js still server-renders it on initial page load.
|
|
76
|
+
- During SSR, `useStyles()` finds the collector via React context and pushes CSS rules to it.
|
|
77
|
+
- `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.
|
|
78
|
+
- A companion `<script>` tag transfers the `cacheKey → className` mapping to the client.
|
|
79
|
+
- When the module loads on the client, `hydrateTastyCache()` runs automatically and pre-populates the injector cache. During hydration, `useStyles()` hits the cache and skips the entire pipeline.
|
|
80
|
+
|
|
81
|
+
### Using tasty() in Server Components
|
|
82
|
+
|
|
83
|
+
`tasty()` components use React hooks internally, so they require `'use client'`. However, this does **not** prevent them from being used in Server Component pages. In Next.js, `'use client'` components are still server-rendered on initial load. Dynamic `styleProps` like `<Grid flow="column">` work normally when a `tasty()` component is imported into a Server Component page.
|
|
84
|
+
|
|
85
|
+
### Options
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
// Skip cache state transfer (saves payload size at the cost of hydration perf)
|
|
89
|
+
<TastyRegistry transferCache={false}>{children}</TastyRegistry>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### CSP nonce
|
|
93
|
+
|
|
94
|
+
If your app uses Content Security Policy with nonces, configure it before rendering:
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
// app/layout.tsx or a server-side init file
|
|
98
|
+
import { configure } from '@tenphi/tasty';
|
|
99
|
+
|
|
100
|
+
configure({ nonce: 'your-nonce-value' });
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The nonce is automatically applied to all `<style>` and `<script>` tags injected by `TastyRegistry`.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Astro
|
|
108
|
+
|
|
109
|
+
### 1. Add the middleware
|
|
110
|
+
|
|
111
|
+
Create or update your Astro middleware:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
// src/middleware.ts
|
|
115
|
+
import { tastyMiddleware } from '@tenphi/tasty/ssr/astro';
|
|
116
|
+
|
|
117
|
+
export const onRequest = tastyMiddleware();
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 2. Use tasty() components as normal
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
// src/components/Card.tsx
|
|
124
|
+
import { tasty } from '@tenphi/tasty';
|
|
125
|
+
|
|
126
|
+
const Card = tasty({
|
|
127
|
+
styles: {
|
|
128
|
+
padding: '4x',
|
|
129
|
+
fill: '#surface',
|
|
130
|
+
radius: '1r',
|
|
131
|
+
border: true,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
export default Card;
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
```astro
|
|
139
|
+
---
|
|
140
|
+
// src/pages/index.astro
|
|
141
|
+
import Card from '../components/Card.tsx';
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
<html>
|
|
145
|
+
<body>
|
|
146
|
+
<Card>Static card — styles collected by middleware</Card>
|
|
147
|
+
<Card client:load>Island card — styles hydrated on client</Card>
|
|
148
|
+
</body>
|
|
149
|
+
</html>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### How it works
|
|
153
|
+
|
|
154
|
+
Astro's `@astrojs/react` renderer calls `renderToString()` for each React component without wrapping the tree in a provider. The middleware uses `AsyncLocalStorage` to make the collector available to all `useStyles()` calls within the request.
|
|
155
|
+
|
|
156
|
+
- **Static components** (no `client:*`): Styles are collected during `renderToString` and injected into `</head>`. No JavaScript is shipped for these components.
|
|
157
|
+
- **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 `useStyles()` calls hit the cache during hydration.
|
|
158
|
+
|
|
159
|
+
### Client-side hydration for islands
|
|
160
|
+
|
|
161
|
+
The `@tenphi/tasty/ssr/astro` module auto-hydrates when imported on the client. To ensure the cache is warm before any island renders, import it in a shared entry point or in each island component:
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
// src/components/MyIsland.tsx
|
|
165
|
+
import '@tenphi/tasty/ssr/astro'; // auto-hydrates cache on import
|
|
166
|
+
import { tasty } from '@tenphi/tasty';
|
|
167
|
+
|
|
168
|
+
const MyIsland = tasty({
|
|
169
|
+
styles: { padding: '2x', fill: '#blue' },
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
export default MyIsland;
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Options
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
// Skip cache state transfer
|
|
179
|
+
export const onRequest = tastyMiddleware({ transferCache: false });
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### CSP nonce
|
|
183
|
+
|
|
184
|
+
Same as Next.js -- call `configure({ nonce: '...' })` before any rendering happens. The middleware reads the nonce and applies it to injected tags.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Generic Framework Integration
|
|
189
|
+
|
|
190
|
+
Any React-based framework can integrate using the core SSR API:
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
import {
|
|
194
|
+
ServerStyleCollector,
|
|
195
|
+
TastySSRContext,
|
|
196
|
+
hydrateTastyCache,
|
|
197
|
+
} from '@tenphi/tasty/ssr';
|
|
198
|
+
import { renderToString } from 'react-dom/server';
|
|
199
|
+
import { hydrateRoot } from 'react-dom/client';
|
|
200
|
+
|
|
201
|
+
// ── Server ──────────────────────────────────────────────
|
|
202
|
+
|
|
203
|
+
const collector = new ServerStyleCollector();
|
|
204
|
+
|
|
205
|
+
const html = renderToString(
|
|
206
|
+
<TastySSRContext.Provider value={collector}>
|
|
207
|
+
<App />
|
|
208
|
+
</TastySSRContext.Provider>
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
const css = collector.getCSS();
|
|
212
|
+
const cacheState = collector.getCacheState();
|
|
213
|
+
|
|
214
|
+
// Embed in your HTML template:
|
|
215
|
+
const fullHtml = `
|
|
216
|
+
<html>
|
|
217
|
+
<head>
|
|
218
|
+
<style data-tasty-ssr>${css}</style>
|
|
219
|
+
<script data-tasty-cache type="application/json">
|
|
220
|
+
${JSON.stringify(cacheState)}
|
|
221
|
+
</script>
|
|
222
|
+
</head>
|
|
223
|
+
<body>
|
|
224
|
+
<div id="root">${html}</div>
|
|
225
|
+
</body>
|
|
226
|
+
</html>
|
|
227
|
+
`;
|
|
228
|
+
|
|
229
|
+
// ── Client ──────────────────────────────────────────────
|
|
230
|
+
|
|
231
|
+
// Before hydration:
|
|
232
|
+
hydrateTastyCache(); // reads from <script data-tasty-cache>
|
|
233
|
+
|
|
234
|
+
hydrateRoot(document.getElementById('root'), <App />);
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Streaming SSR
|
|
238
|
+
|
|
239
|
+
For streaming with `renderToPipeableStream`, use `flushCSS()` instead of `getCSS()`:
|
|
240
|
+
|
|
241
|
+
```tsx
|
|
242
|
+
const collector = new ServerStyleCollector();
|
|
243
|
+
|
|
244
|
+
const stream = renderToPipeableStream(
|
|
245
|
+
<TastySSRContext.Provider value={collector}>
|
|
246
|
+
<App />
|
|
247
|
+
</TastySSRContext.Provider>,
|
|
248
|
+
{
|
|
249
|
+
onShellReady() {
|
|
250
|
+
// Flush styles collected so far
|
|
251
|
+
const css = collector.flushCSS();
|
|
252
|
+
res.write(`<style data-tasty-ssr>${css}</style>`);
|
|
253
|
+
stream.pipe(res);
|
|
254
|
+
},
|
|
255
|
+
onAllReady() {
|
|
256
|
+
// Flush any remaining styles + cache state
|
|
257
|
+
const css = collector.flushCSS();
|
|
258
|
+
if (css) res.write(`<style data-tasty-ssr>${css}</style>`);
|
|
259
|
+
|
|
260
|
+
const state = collector.getCacheState();
|
|
261
|
+
res.write(`<script data-tasty-cache type="application/json">${JSON.stringify(state)}</script>`);
|
|
262
|
+
},
|
|
263
|
+
}
|
|
264
|
+
);
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### AsyncLocalStorage (no React context)
|
|
268
|
+
|
|
269
|
+
If your framework doesn't support wrapping the React tree with a provider, use `runWithCollector`:
|
|
270
|
+
|
|
271
|
+
```tsx
|
|
272
|
+
import {
|
|
273
|
+
ServerStyleCollector,
|
|
274
|
+
runWithCollector,
|
|
275
|
+
hydrateTastyCache,
|
|
276
|
+
} from '@tenphi/tasty/ssr';
|
|
277
|
+
|
|
278
|
+
const collector = new ServerStyleCollector();
|
|
279
|
+
|
|
280
|
+
const html = await runWithCollector(collector, () =>
|
|
281
|
+
renderToString(<App />)
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
const css = collector.getCSS();
|
|
285
|
+
// ... inject into HTML as above
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## API Reference
|
|
291
|
+
|
|
292
|
+
### Entry points
|
|
293
|
+
|
|
294
|
+
| Import path | Description |
|
|
295
|
+
|---|---|
|
|
296
|
+
| `@tenphi/tasty/ssr` | Core SSR API: `ServerStyleCollector`, `TastySSRContext`, `runWithCollector`, `hydrateTastyCache` |
|
|
297
|
+
| `@tenphi/tasty/ssr/next` | Next.js App Router: `TastyRegistry` component |
|
|
298
|
+
| `@tenphi/tasty/ssr/astro` | Astro: `tastyMiddleware`, auto-hydration on import |
|
|
299
|
+
|
|
300
|
+
### `ServerStyleCollector`
|
|
301
|
+
|
|
302
|
+
Server-safe style collector. One instance per request.
|
|
303
|
+
|
|
304
|
+
| Method | Description |
|
|
305
|
+
|---|---|
|
|
306
|
+
| `allocateClassName(cacheKey)` | Allocate a sequential class name (`t0`, `t1`, ...) for a cache key. Returns `{ className, isNewAllocation }`. |
|
|
307
|
+
| `collectChunk(cacheKey, className, rules)` | Record CSS rules for a chunk. Deduplicated by `cacheKey`. |
|
|
308
|
+
| `collectKeyframes(name, css)` | Record a `@keyframes` rule. Deduplicated by name. |
|
|
309
|
+
| `collectProperty(name, css)` | Record a `@property` rule. Deduplicated by name. |
|
|
310
|
+
| `getCSS()` | Get all collected CSS as a single string. For non-streaming SSR. |
|
|
311
|
+
| `flushCSS()` | Get only CSS collected since the last flush. For streaming SSR. |
|
|
312
|
+
| `getCacheState()` | Serialize `{ entries: Record<cacheKey, className>, classCounter }` for client hydration. |
|
|
313
|
+
|
|
314
|
+
### `TastySSRContext`
|
|
315
|
+
|
|
316
|
+
React context (`createContext<ServerStyleCollector | null>(null)`). Used by `useStyles()` to find the collector during SSR.
|
|
317
|
+
|
|
318
|
+
### `TastyRegistry`
|
|
319
|
+
|
|
320
|
+
Next.js App Router component. Props:
|
|
321
|
+
|
|
322
|
+
| Prop | Type | Default | Description |
|
|
323
|
+
|---|---|---|---|
|
|
324
|
+
| `children` | `ReactNode` | required | Application tree |
|
|
325
|
+
| `transferCache` | `boolean` | `true` | Embed cache state script for zero-cost hydration |
|
|
326
|
+
|
|
327
|
+
### `tastyMiddleware(options?)`
|
|
328
|
+
|
|
329
|
+
Astro middleware factory. Options:
|
|
330
|
+
|
|
331
|
+
| Option | Type | Default | Description |
|
|
332
|
+
|---|---|---|---|
|
|
333
|
+
| `transferCache` | `boolean` | `true` | Embed cache state script for island hydration |
|
|
334
|
+
|
|
335
|
+
### `hydrateTastyCache(state?)`
|
|
336
|
+
|
|
337
|
+
Pre-populate the client injector cache. When called without arguments, reads from `window.__TASTY_SSR_CACHE__` (streaming) or `<script data-tasty-cache>` (non-streaming).
|
|
338
|
+
|
|
339
|
+
### `runWithCollector(collector, fn)`
|
|
340
|
+
|
|
341
|
+
Run a function with a `ServerStyleCollector` bound to the current async context via `AsyncLocalStorage`. All `useStyles()` calls within `fn` (and async continuations) will find this collector.
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## Troubleshooting
|
|
346
|
+
|
|
347
|
+
### Styles flash on page load (FOUC)
|
|
348
|
+
|
|
349
|
+
The `TastyRegistry` or `tastyMiddleware` is missing. Ensure your layout wraps the app with `TastyRegistry` (Next.js) or the middleware is registered (Astro).
|
|
350
|
+
|
|
351
|
+
### Hydration mismatch warnings
|
|
352
|
+
|
|
353
|
+
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()`.
|
|
354
|
+
|
|
355
|
+
### Styles duplicated after hydration
|
|
356
|
+
|
|
357
|
+
This is expected and harmless. 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, call `cleanupSSRStyles()` after hydration:
|
|
358
|
+
|
|
359
|
+
```tsx
|
|
360
|
+
import { hydrateTastyCache } from '@tenphi/tasty/ssr';
|
|
361
|
+
|
|
362
|
+
hydrateTastyCache();
|
|
363
|
+
hydrateRoot(root, <App />);
|
|
364
|
+
|
|
365
|
+
// Optional: remove SSR style tags after hydration
|
|
366
|
+
document.querySelectorAll('style[data-tasty-ssr]').forEach(el => el.remove());
|
|
367
|
+
document.querySelectorAll('script[data-tasty-cache]').forEach(el => el.remove());
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### `AsyncLocalStorage` not available
|
|
371
|
+
|
|
372
|
+
The `@tenphi/tasty/ssr` entry point imports from `node:async_hooks`. This is excluded from client bundles by the build configuration. If you see import errors on the client, ensure your bundler treats `node:async_hooks` as external or use the `@tenphi/tasty/ssr/next` entry point (which does not use ALS).
|
package/docs/styles.md
CHANGED
|
@@ -489,33 +489,40 @@ If the name is not a semantic name, it is used as a literal CSS property name.
|
|
|
489
489
|
|
|
490
490
|
### `scrollbar`
|
|
491
491
|
|
|
492
|
-
|
|
492
|
+
Scrollbar styling using CSS standard properties (`scrollbar-width`, `scrollbar-color`, `scrollbar-gutter`).
|
|
493
493
|
|
|
494
|
-
**Syntax:** `[
|
|
494
|
+
**Syntax:** `[width] [modifiers...] [thumb-color] [track-color]`
|
|
495
495
|
|
|
496
|
-
**
|
|
496
|
+
**Width values** (controls `scrollbar-width`, default is `thin`):
|
|
497
|
+
|
|
498
|
+
| Value | Effect |
|
|
499
|
+
|-------|--------|
|
|
500
|
+
| `thin` | Thin scrollbar (default) |
|
|
501
|
+
| `auto` | Browser-default scrollbar width |
|
|
502
|
+
| `none` | Hide scrollbar (still scrollable, no colors applied) |
|
|
503
|
+
|
|
504
|
+
**Modifiers** (controls gutter and overflow behavior):
|
|
497
505
|
|
|
498
506
|
| Modifier | Effect |
|
|
499
507
|
|----------|--------|
|
|
500
|
-
| `thin` | Thin scrollbar (`scrollbar-width: thin`) |
|
|
501
|
-
| `none` | Hide scrollbar (still scrollable) |
|
|
502
|
-
| `auto` | Browser-default scrollbar width |
|
|
503
508
|
| `stable` | Reserve space for scrollbar (`scrollbar-gutter: stable`) |
|
|
504
509
|
| `both-edges` | Reserve space on both sides (`scrollbar-gutter: stable both-edges`) |
|
|
505
510
|
| `always` | Force scrollbars visible (`overflow: scroll` + `scrollbar-gutter: stable`) |
|
|
506
|
-
| `styled` | Enhanced appearance with rounded thumb, transitions, and custom sizing |
|
|
507
511
|
|
|
508
|
-
**Colors:** Up to
|
|
512
|
+
**Colors:** Up to 2 color values for thumb and track (optional), applied via `scrollbar-color`.
|
|
513
|
+
|
|
514
|
+
**Defaults:** When no colors are specified, the thumb color comes from the `#scrollbar-thumb` token (`#text.5`) and the track color from the `#scrollbar-track` token (`#dark-bg`). These tokens are provided by the base token set. If the base tokens are not loaded, the track falls back to `transparent` via a CSS fallback, while the thumb has no CSS-level fallback — the browser treats the entire `scrollbar-color` declaration as invalid and reverts to the platform-default scrollbar appearance.
|
|
509
515
|
|
|
510
|
-
**
|
|
516
|
+
**Note:** `none` takes precedence over all other modifiers and colors. Combining `none` with other tokens (e.g., `"none always #red"`) produces a warning, and only `scrollbar-width: none` is applied.
|
|
511
517
|
|
|
512
518
|
| Value | Effect |
|
|
513
519
|
|-------|--------|
|
|
514
520
|
| `true` | Thin scrollbar with default thumb/track colors |
|
|
515
521
|
| `"none"` | Hidden scrollbar (still scrollable) |
|
|
516
|
-
| `"thin
|
|
517
|
-
| `"
|
|
518
|
-
| `"always
|
|
522
|
+
| `"thin #purple.40 #dark.04"` | Thin scrollbar with custom colors |
|
|
523
|
+
| `"auto #red #blue"` | Browser-default width with custom colors |
|
|
524
|
+
| `"always #primary.50 #white.02"` | Always visible with custom colors |
|
|
525
|
+
| `"thin stable #red #blue"` | Thin, gutter reserved, custom colors |
|
|
519
526
|
|
|
520
527
|
### `fade`
|
|
521
528
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tenphi/tasty",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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",
|
|
@@ -8,40 +8,49 @@
|
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
15
14
|
},
|
|
16
15
|
"./static": {
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
16
|
+
"types": "./dist/static/index.d.ts",
|
|
17
|
+
"import": "./dist/static/index.js",
|
|
18
|
+
"default": "./dist/static/index.js"
|
|
21
19
|
},
|
|
22
20
|
"./babel-plugin": {
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
21
|
+
"types": "./dist/zero/babel.d.ts",
|
|
22
|
+
"import": "./dist/zero/babel.js",
|
|
23
|
+
"default": "./dist/zero/babel.js"
|
|
27
24
|
},
|
|
28
25
|
"./zero": {
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
26
|
+
"types": "./dist/zero/index.d.ts",
|
|
27
|
+
"import": "./dist/zero/index.js",
|
|
28
|
+
"default": "./dist/zero/index.js"
|
|
33
29
|
},
|
|
34
30
|
"./next": {
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
31
|
+
"types": "./dist/zero/next.d.ts",
|
|
32
|
+
"import": "./dist/zero/next.js",
|
|
33
|
+
"default": "./dist/zero/next.js"
|
|
39
34
|
},
|
|
40
35
|
"./core": {
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
"types": "./dist/core/index.d.ts",
|
|
37
|
+
"import": "./dist/core/index.js",
|
|
38
|
+
"default": "./dist/core/index.js"
|
|
39
|
+
},
|
|
40
|
+
"./ssr": {
|
|
41
|
+
"types": "./dist/ssr/index.d.ts",
|
|
42
|
+
"import": "./dist/ssr/index.js",
|
|
43
|
+
"default": "./dist/ssr/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./ssr/next": {
|
|
46
|
+
"types": "./dist/ssr/next.d.ts",
|
|
47
|
+
"import": "./dist/ssr/next.js",
|
|
48
|
+
"default": "./dist/ssr/next.js"
|
|
49
|
+
},
|
|
50
|
+
"./ssr/astro": {
|
|
51
|
+
"types": "./dist/ssr/astro.d.ts",
|
|
52
|
+
"import": "./dist/ssr/astro.js",
|
|
53
|
+
"default": "./dist/ssr/astro.js"
|
|
45
54
|
},
|
|
46
55
|
"./tasty.config": "./tasty.config.ts"
|
|
47
56
|
},
|
|
@@ -50,7 +59,11 @@
|
|
|
50
59
|
"docs",
|
|
51
60
|
"tasty.config.ts"
|
|
52
61
|
],
|
|
53
|
-
"sideEffects":
|
|
62
|
+
"sideEffects": [
|
|
63
|
+
"./dist/ssr/index.js",
|
|
64
|
+
"./dist/ssr/next.js",
|
|
65
|
+
"./dist/ssr/astro.js"
|
|
66
|
+
],
|
|
54
67
|
"engines": {
|
|
55
68
|
"node": ">=20"
|
|
56
69
|
},
|
|
@@ -96,7 +109,8 @@
|
|
|
96
109
|
}
|
|
97
110
|
},
|
|
98
111
|
"dependencies": {
|
|
99
|
-
"csstype": "^3.1.0"
|
|
112
|
+
"csstype": "^3.1.0",
|
|
113
|
+
"jiti": "^2.6.1"
|
|
100
114
|
},
|
|
101
115
|
"devDependencies": {
|
|
102
116
|
"@babel/core": "^7.24.0",
|
|
@@ -178,6 +192,8 @@
|
|
|
178
192
|
"size": "size-limit",
|
|
179
193
|
"changeset": "changeset",
|
|
180
194
|
"version": "changeset version",
|
|
181
|
-
"release": "changeset publish"
|
|
195
|
+
"release": "changeset publish",
|
|
196
|
+
"hygiene": "pnpm lint && pnpm format:check && pnpm typecheck",
|
|
197
|
+
"hygiene:fix": "pnpm lint:fix && pnpm format && pnpm typecheck"
|
|
182
198
|
}
|
|
183
199
|
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
//#region src/styles/styledScrollbar.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* @deprecated `styledScrollbar` is deprecated. Use `scrollbar` instead.
|
|
4
|
-
*/
|
|
5
|
-
declare function styledScrollbarStyle({
|
|
6
|
-
styledScrollbar: val
|
|
7
|
-
}: {
|
|
8
|
-
styledScrollbar?: string | boolean | null;
|
|
9
|
-
}): ({
|
|
10
|
-
$: string;
|
|
11
|
-
display: string;
|
|
12
|
-
'scrollbar-width'?: undefined;
|
|
13
|
-
} | {
|
|
14
|
-
'scrollbar-width': string;
|
|
15
|
-
$?: undefined;
|
|
16
|
-
display?: undefined;
|
|
17
|
-
})[] | ({
|
|
18
|
-
$: string;
|
|
19
|
-
width: string;
|
|
20
|
-
height: string;
|
|
21
|
-
'background-color'?: undefined;
|
|
22
|
-
'border-radius'?: undefined;
|
|
23
|
-
border?: undefined;
|
|
24
|
-
'background-clip'?: undefined;
|
|
25
|
-
} | {
|
|
26
|
-
$: string;
|
|
27
|
-
'background-color': string;
|
|
28
|
-
width?: undefined;
|
|
29
|
-
height?: undefined;
|
|
30
|
-
'border-radius'?: undefined;
|
|
31
|
-
border?: undefined;
|
|
32
|
-
'background-clip'?: undefined;
|
|
33
|
-
} | {
|
|
34
|
-
$: string;
|
|
35
|
-
'background-color': string;
|
|
36
|
-
'border-radius': string;
|
|
37
|
-
border: string;
|
|
38
|
-
'background-clip': string;
|
|
39
|
-
width?: undefined;
|
|
40
|
-
height?: undefined;
|
|
41
|
-
})[] | null;
|
|
42
|
-
declare namespace styledScrollbarStyle {
|
|
43
|
-
var __lookupStyles: string[];
|
|
44
|
-
}
|
|
45
|
-
//#endregion
|
|
46
|
-
export { styledScrollbarStyle };
|
|
47
|
-
//# sourceMappingURL=styledScrollbar.d.ts.map
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
//#region src/styles/styledScrollbar.ts
|
|
2
|
-
/**
|
|
3
|
-
* @deprecated `styledScrollbar` is deprecated. Use `scrollbar` instead.
|
|
4
|
-
*/
|
|
5
|
-
function styledScrollbarStyle({ styledScrollbar: val }) {
|
|
6
|
-
if (val == null) return null;
|
|
7
|
-
if (!val) return [{
|
|
8
|
-
$: "::-webkit-scrollbar",
|
|
9
|
-
display: "none"
|
|
10
|
-
}, { "scrollbar-width": "none" }];
|
|
11
|
-
return [
|
|
12
|
-
{
|
|
13
|
-
$: "::-webkit-scrollbar",
|
|
14
|
-
width: "var(--scrollbar-width)",
|
|
15
|
-
height: "var(--scrollbar-width)"
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
$: "::-webkit-scrollbar-track",
|
|
19
|
-
"background-color": "var(--scrollbar-bg-color)"
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
$: "::-webkit-scrollbar-thumb",
|
|
23
|
-
"background-color": "var(--scrollbar-thumb-color)",
|
|
24
|
-
"border-radius": "var(--scrollbar-radius)",
|
|
25
|
-
border: "var(--scrollbar-outline-width) solid var(--scrollbar-outline-color)",
|
|
26
|
-
"background-clip": "padding-box"
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
$: "::-webkit-scrollbar-corner",
|
|
30
|
-
"background-color": "var(--scrollbar-corner-color)"
|
|
31
|
-
}
|
|
32
|
-
];
|
|
33
|
-
}
|
|
34
|
-
styledScrollbarStyle.__lookupStyles = ["styledScrollbar"];
|
|
35
|
-
|
|
36
|
-
//#endregion
|
|
37
|
-
export { styledScrollbarStyle };
|
|
38
|
-
//# sourceMappingURL=styledScrollbar.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"styledScrollbar.js","names":[],"sources":["../../src/styles/styledScrollbar.ts"],"sourcesContent":["/**\n * @deprecated `styledScrollbar` is deprecated. Use `scrollbar` instead.\n */\nexport function styledScrollbarStyle({\n styledScrollbar: val,\n}: {\n styledScrollbar?: string | boolean | null;\n}) {\n if (val == null) return null;\n\n if (!val) {\n return [\n {\n $: '::-webkit-scrollbar',\n display: 'none',\n },\n {\n 'scrollbar-width': 'none',\n },\n ];\n }\n\n return [\n {\n $: '::-webkit-scrollbar',\n width: 'var(--scrollbar-width)',\n height: 'var(--scrollbar-width)',\n },\n {\n $: '::-webkit-scrollbar-track',\n 'background-color': 'var(--scrollbar-bg-color)',\n },\n {\n $: '::-webkit-scrollbar-thumb',\n 'background-color': 'var(--scrollbar-thumb-color)',\n 'border-radius': 'var(--scrollbar-radius)',\n border:\n 'var(--scrollbar-outline-width) solid var(--scrollbar-outline-color)',\n 'background-clip': 'padding-box',\n },\n {\n $: '::-webkit-scrollbar-corner',\n 'background-color': 'var(--scrollbar-corner-color)',\n },\n // Breaks the scrollbar in the latest Chromium browsers\n // {\n // 'scrollbar-width': 'thin',\n // 'scrollbar-color':\n // 'var(--scrollbar-bg-color) var(--scrollbar-thumb-color)',\n // },\n ];\n}\n\nstyledScrollbarStyle.__lookupStyles = ['styledScrollbar'];\n"],"mappings":";;;;AAGA,SAAgB,qBAAqB,EACnC,iBAAiB,OAGhB;AACD,KAAI,OAAO,KAAM,QAAO;AAExB,KAAI,CAAC,IACH,QAAO,CACL;EACE,GAAG;EACH,SAAS;EACV,EACD,EACE,mBAAmB,QACpB,CACF;AAGH,QAAO;EACL;GACE,GAAG;GACH,OAAO;GACP,QAAQ;GACT;EACD;GACE,GAAG;GACH,oBAAoB;GACrB;EACD;GACE,GAAG;GACH,oBAAoB;GACpB,iBAAiB;GACjB,QACE;GACF,mBAAmB;GACpB;EACD;GACE,GAAG;GACH,oBAAoB;GACrB;EAOF;;AAGH,qBAAqB,iBAAiB,CAAC,kBAAkB"}
|