@tenphi/tasty 3.10.0 → 3.11.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 +399 -380
- package/dist/{babel-types-C_7kRP38.d.ts → babel-types-BKDTT4--.d.ts} +2 -2
- package/dist/chunks/async-storage-f9j9JRtq.js +37 -0
- package/dist/chunks/async-storage-f9j9JRtq.js.map +1 -0
- package/dist/chunks/{build-config-l2Ys-AJr.js → build-config-5d8WlQbq.js} +5 -5
- package/dist/chunks/{build-config-l2Ys-AJr.js.map → build-config-5d8WlQbq.js.map} +1 -1
- package/dist/chunks/{collector-CaNtsRtn.js → collector-CjCak4iE.js} +4 -4
- package/dist/chunks/{collector-CaNtsRtn.js.map → collector-CjCak4iE.js.map} +1 -1
- package/dist/chunks/{config-engine-CpExZO_T.js → config-engine-uOFYZidB.js} +4 -4
- package/dist/chunks/{config-engine-CpExZO_T.js.map → config-engine-uOFYZidB.js.map} +1 -1
- package/dist/chunks/config-file-DlZXJMj8.js +25 -0
- package/dist/chunks/config-file-DlZXJMj8.js.map +1 -0
- package/dist/chunks/{css-definitions-DAIJxzFY.js → css-definitions-ubrHP97m.js} +88 -77
- package/dist/chunks/css-definitions-ubrHP97m.js.map +1 -0
- package/dist/chunks/{debug-CKy49GEr.js → debug-CJ31U_WE.js} +6 -6
- package/dist/chunks/{debug-CKy49GEr.js.map → debug-CJ31U_WE.js.map} +1 -1
- package/dist/chunks/{dsl-DRhFWQzo.js → dsl-DYSJc1-T.js} +11 -6
- package/dist/chunks/dsl-DYSJc1-T.js.map +1 -0
- package/dist/chunks/{react-runtime-oi6i3ELR.js → react-runtime-CyyCI02N.js} +168 -61
- package/dist/chunks/react-runtime-CyyCI02N.js.map +1 -0
- package/dist/chunks/{runtime-engine-DoULyhPt.js → runtime-engine-BQmOu5-W.js} +53 -36
- package/dist/chunks/runtime-engine-BQmOu5-W.js.map +1 -0
- package/dist/chunks/{runtime-state-CZ2J5QiZ.js → runtime-state-DEsyUMU-.js} +14 -3
- package/dist/chunks/runtime-state-DEsyUMU-.js.map +1 -0
- package/dist/chunks/{style-engine-CeKsjtIr.js → style-engine-BX2LshDZ.js} +116 -56
- package/dist/chunks/style-engine-BX2LshDZ.js.map +1 -0
- package/dist/chunks/{zero-engine-fxKu8Jbn.js → zero-engine-Dw0P4yGM.js} +22 -7
- package/dist/chunks/zero-engine-Dw0P4yGM.js.map +1 -0
- package/dist/{collector-w4mBj5gd.d.ts → collector-CTU-nl0g.d.ts} +2 -2
- package/dist/{config-CrZOfNJ5.d.ts → config-CcxqSZI2.d.ts} +28 -9
- package/dist/core/index.d.ts +4 -4
- package/dist/core/index.js +7 -7
- package/dist/hydrate-CRyjjAyl.d.ts +22 -0
- package/dist/{index-CYJtAI9a.d.ts → index-xtey-KDu.d.ts} +43 -13
- package/dist/index.d.ts +4 -4
- package/dist/index.js +8 -8
- package/dist/{merge-styles-VXwJ0Frr.d.ts → merge-styles-DSkVc6CD.d.ts} +2 -2
- package/dist/ssr/astro.js +4 -4
- package/dist/ssr/client.d.ts +2 -0
- package/dist/ssr/client.js +2 -0
- package/dist/ssr/index.d.ts +3 -22
- package/dist/ssr/index.js +4 -4
- package/dist/ssr/index.js.map +1 -1
- package/dist/ssr/next-config.d.ts +1 -1
- package/dist/ssr/next-config.js +6 -17
- package/dist/ssr/next-config.js.map +1 -1
- package/dist/ssr/next.d.ts +1 -1
- package/dist/ssr/next.js +3 -3
- package/dist/static/index.d.ts +2 -2
- package/dist/{types-JEY8PVYP.d.ts → types-DJEM_BOJ.d.ts} +6 -6
- package/dist/zero/babel.d.ts +1 -1
- package/dist/zero/babel.js +30 -19
- package/dist/zero/babel.js.map +1 -1
- package/dist/zero/index.d.ts +24 -6
- package/dist/zero/index.js +4 -4
- package/dist/zero/index.js.map +1 -1
- package/dist/zero/next.d.ts +1 -1
- package/dist/zero/next.js +3 -7
- package/dist/zero/next.js.map +1 -1
- package/docs/README.md +4 -1
- package/docs/adoption.md +25 -17
- package/docs/ai-agents.md +37 -34
- package/docs/comparison.md +9 -8
- package/docs/configuration.md +173 -58
- package/docs/debug.md +3 -2
- package/docs/design-system.md +117 -15
- package/docs/dsl.md +120 -81
- package/docs/getting-started.md +81 -24
- package/docs/injector.md +39 -40
- package/docs/methodology.md +45 -20
- package/docs/migration-v3.md +2 -2
- package/docs/pipeline.md +19 -16
- package/docs/plugins.md +20 -40
- package/docs/react-api.md +21 -8
- package/docs/runtime-benchmarks.md +4 -4
- package/docs/ssr.md +83 -63
- package/docs/styles.md +27 -17
- package/docs/tasty-static.md +110 -56
- package/package.json +23 -9
- package/dist/chunks/async-storage-CG5HnUT5.js +0 -20
- package/dist/chunks/async-storage-CG5HnUT5.js.map +0 -1
- package/dist/chunks/css-definitions-DAIJxzFY.js.map +0 -1
- package/dist/chunks/dsl-DRhFWQzo.js.map +0 -1
- package/dist/chunks/react-runtime-oi6i3ELR.js.map +0 -1
- package/dist/chunks/runtime-engine-DoULyhPt.js.map +0 -1
- package/dist/chunks/runtime-state-CZ2J5QiZ.js.map +0 -1
- package/dist/chunks/style-engine-CeKsjtIr.js.map +0 -1
- package/dist/chunks/zero-engine-fxKu8Jbn.js.map +0 -1
package/docs/getting-started.md
CHANGED
|
@@ -64,7 +64,7 @@ export default function App() {
|
|
|
64
64
|
|
|
65
65
|
`tasty()` returns a normal React component. The keys in each state map define priority: later branches win over earlier ones. Because `disabled` is last, a disabled button keeps its disabled styles even while the pointer is over it.
|
|
66
66
|
|
|
67
|
-
`disabled` and `
|
|
67
|
+
`disabled`, `checked`, and `hidden` are built-in automatic states. Tasty sets `data-disabled`, `data-checked`, and `data-hidden` from the matching native prop, so `disabled` in a state map is the concise form of `[disabled]` (it compiles to `[data-disabled]`).
|
|
68
68
|
|
|
69
69
|
Tasty compiles that priority into selectors that exclude one another. The hover, active, and disabled background rules cannot all match and ask the CSS cascade to decide the winner.
|
|
70
70
|
|
|
@@ -101,9 +101,9 @@ import App from './App';
|
|
|
101
101
|
createRoot(document.getElementById('root')!).render(<App />);
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
### Define shared tokens and
|
|
104
|
+
### Define shared tokens and units
|
|
105
105
|
|
|
106
|
-
Color tokens like `#primary` resolve to CSS custom properties
|
|
106
|
+
Color tokens like `#primary` and value tokens like `$sidebar-width` resolve to CSS custom properties (`var(--primary-color)`, `var(--sidebar-width)`). Define their values with `configure({ tokens })`:
|
|
107
107
|
|
|
108
108
|
```tsx
|
|
109
109
|
// src/tasty-config.ts
|
|
@@ -114,10 +114,7 @@ configure({
|
|
|
114
114
|
'#primary': 'oklch(55% 0.25 265)',
|
|
115
115
|
'#surface': '#fff',
|
|
116
116
|
'#text': '#111',
|
|
117
|
-
$
|
|
118
|
-
$radius: '4px',
|
|
119
|
-
'$border-width': '1px',
|
|
120
|
-
'$outline-width': '2px',
|
|
117
|
+
'$sidebar-width': '280px',
|
|
121
118
|
},
|
|
122
119
|
});
|
|
123
120
|
```
|
|
@@ -135,7 +132,20 @@ configure({
|
|
|
135
132
|
});
|
|
136
133
|
```
|
|
137
134
|
|
|
138
|
-
|
|
135
|
+
Tokens are injected as `:root` CSS custom properties when the first style is rendered, and every component using `#primary` follows them. You can also use standard CSS color values such as `rgb(...)`, `hsl(...)`, and named colors directly; the built-in `okhsl(...)` and `okhst(...)` functions are the recommended choice when you want authored colors that stay aligned with Tasty's design-system-oriented workflow.
|
|
136
|
+
|
|
137
|
+
Units work differently: `x`, `r`, `cr`, `bw`, and `ow` compile to plain values when styles are generated (`2x` → `16px` with the default `8px` step), so setting a `$gap` token does not change them. Change the scale with `configure({ units })`:
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
configure({
|
|
141
|
+
units: {
|
|
142
|
+
x: '4px', // spacing step: 2x → 8px
|
|
143
|
+
r: '8px', // radius: 1r → 8px
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
When the scale has to change at runtime, for example per theme or density, point the unit at a custom property and define that property as a token. With `units: { x: 'var(--gap)' }` and `tokens: { $gap: '8px' }`, `2x` compiles to `calc(2 * var(--gap))`. See [Built-in Units](dsl.md#built-in-units) for the defaults.
|
|
139
149
|
|
|
140
150
|
> **Note:** `configure({ replaceTokens })` is a separate mechanism — it replaces tokens with literal values at parse time (baked into CSS). Use it for value aliases like `$card-padding: '4x'` that should be resolved during style generation, not for defining color or unit values. See [Configuration — Replace Tokens](configuration.md#replace-tokens-parse-time-substitution) for details.
|
|
141
151
|
|
|
@@ -145,7 +155,7 @@ See [Configuration](configuration.md) for the full `configure()` API — tokens,
|
|
|
145
155
|
|
|
146
156
|
## ESLint plugin
|
|
147
157
|
|
|
148
|
-
The ESLint plugin catches invalid style properties, bad token references, malformed state keys, and other mistakes at lint time — before they reach the browser.
|
|
158
|
+
The ESLint plugin catches invalid style properties, bad token references, malformed state keys, and other mistakes at lint time — before they reach the browser. It checks style objects and JSX style props alike, and runs under both ESLint 9+ and oxlint.
|
|
149
159
|
|
|
150
160
|
### Install
|
|
151
161
|
|
|
@@ -167,36 +177,80 @@ export default [
|
|
|
167
177
|
];
|
|
168
178
|
```
|
|
169
179
|
|
|
170
|
-
###
|
|
180
|
+
### Tell it about your design system
|
|
181
|
+
|
|
182
|
+
Without project configuration the plugin checks syntax and property names, but it cannot know which tokens, presets, or recipes exist. Add a `tasty.config.ts` at the project root to enable those checks for the names your `configure()` call defines:
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
// tasty.config.ts
|
|
186
|
+
export default {
|
|
187
|
+
tokens: ['#primary', '#surface', '#text', '$sidebar-width'],
|
|
188
|
+
states: ['@mobile', '@dark'],
|
|
189
|
+
presets: ['h1', 'h2', 't1', 't2'],
|
|
190
|
+
recipes: ['card'],
|
|
191
|
+
importSources: ['@my-org/ui'], // modules that re-export tasty()
|
|
192
|
+
};
|
|
193
|
+
```
|
|
171
194
|
|
|
172
|
-
|
|
195
|
+
Every list is optional; leaving one out skips that check. `importSources` matters when your design system re-exports `tasty()` from its own module: the plugin only lints calls whose import it can trace.
|
|
173
196
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
|
177
|
-
|
|
|
178
|
-
|
|
|
179
|
-
|
|
|
180
|
-
|
|
|
181
|
-
|
|
|
182
|
-
|
|
|
183
|
-
|
|
|
197
|
+
### What `recommended` catches
|
|
198
|
+
|
|
199
|
+
| Category | Rules | Examples |
|
|
200
|
+
| ------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ |
|
|
201
|
+
| Property validation | `known-property`, `valid-boolean-property`, `valid-sub-element` | Flags typos like `pading` or invalid boolean usage |
|
|
202
|
+
| Value validation | `valid-value`, `valid-color-token`, `valid-custom-unit` | Catches `#nonexistent` tokens, bad unit syntax |
|
|
203
|
+
| State validation | `valid-state-key`, `no-nested-state-map`, `require-default-state` | Validates state key syntax, ensures `''` default exists |
|
|
204
|
+
| Structure | `valid-styles-structure`, `no-important`, `no-nested-selector` | Prevents `!important`, invalid nesting |
|
|
205
|
+
| Static mode | `static-no-dynamic-values`, `static-valid-selector` | Enforces build-time constraints in `tastyStatic()` |
|
|
206
|
+
| Style properties | `valid-preset`, `valid-recipe`, `valid-transition`, `valid-directional-modifier`, `valid-radius-shape` | Validates preset names, recipe references, transition syntax |
|
|
207
|
+
| Token usage | `no-raw-color-values`, `consistent-token-usage`, `no-raw-transition-duration` | Suggests `#color` tokens over raw colors, units over raw pixel lengths |
|
|
208
|
+
| Preferred syntax | `prefer-shorthand-property`, `prefer-auto-calc`, `prefer-hide`, `prefer-state-negation`, and others | Suggests `fill` over `backgroundColor`, `(...)` over `calc(...)` |
|
|
209
|
+
| Component props | `no-style-prop`, `no-classname-prop`, `no-styles-prop` | Points escape hatches toward `tokens`, `mods`, and the component's props |
|
|
210
|
+
| Dynamic styles | `no-runtime-styles-mutation`, `no-style-spread` | Warns on JavaScript values and spreads inside runtime style objects |
|
|
184
211
|
|
|
185
212
|
### Strict config
|
|
186
213
|
|
|
187
|
-
For stricter governance, use `tasty.configs.strict`. It adds
|
|
214
|
+
For stricter governance, use `tasty.configs.strict`. It adds existence checks for `$` custom properties and `@` state aliases, and validates state definitions in `configure()` and `tasty.config.ts`:
|
|
188
215
|
|
|
189
216
|
```js
|
|
190
217
|
export default [tasty.configs.strict];
|
|
191
218
|
```
|
|
192
219
|
|
|
220
|
+
See the [plugin README](https://github.com/tenphi/eslint-plugin-tasty) for every rule, the oxlint setup, and the experimental type-aware mode for JSX props.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## TypeScript autocomplete
|
|
225
|
+
|
|
226
|
+
Component props are typed from their definitions without extra setup: `modProps` value lists become unions, `variant` is typed from the variant keys, and sub-elements are typed components. To get autocomplete for your own token, preset, and theme names inside style values, register them with module augmentation:
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
// tasty.d.ts
|
|
230
|
+
import '@tenphi/tasty';
|
|
231
|
+
|
|
232
|
+
declare module '@tenphi/tasty' {
|
|
233
|
+
interface TastyNamedColors {
|
|
234
|
+
primary: true;
|
|
235
|
+
surface: true;
|
|
236
|
+
text: true;
|
|
237
|
+
}
|
|
238
|
+
interface TastyPresetNames {
|
|
239
|
+
h1: true;
|
|
240
|
+
t2: true;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
See [Configuration → TypeScript](configuration.md#typescript) for every augmentation interface and how component props are typed.
|
|
246
|
+
|
|
193
247
|
---
|
|
194
248
|
|
|
195
249
|
## Editor support
|
|
196
250
|
|
|
197
|
-
**[VS Code Extension](https://github.com/tenphi/tasty-vscode-extension)** — Syntax highlighting for Tasty styles in TypeScript/TSX/JavaScript/JSX
|
|
251
|
+
**[VS Code Extension](https://github.com/tenphi/tasty-vscode-extension)** — Syntax highlighting for Tasty styles in TypeScript/TSX/JavaScript/JSX: color tokens, custom units, state keys, presets, and style properties inside `tasty()`, `tastyStatic()`, `styles` objects, and style functions. It also adds snippets for components, state maps, and `configure()`. Install the `.vsix` from the [latest GitHub release](https://github.com/tenphi/tasty-vscode-extension/releases/latest); the extension is not published to the marketplace.
|
|
198
252
|
|
|
199
|
-
**[Glaze](https://github.com/tenphi/glaze)** —
|
|
253
|
+
**[Glaze](https://github.com/tenphi/glaze)** — Not an editor tool, but the fastest way to fill `configure({ tokens })`: it generates light, dark, and high-contrast palettes from one hue in the OKHST color space, solves WCAG and APCA contrast, and exports them as Tasty tokens with `configure({ tokens: theme.tasty() })`. See [Glaze in the README](../README.md#glaze).
|
|
200
254
|
|
|
201
255
|
---
|
|
202
256
|
|
|
@@ -228,6 +282,7 @@ Both `tasty()` and `tastyStatic()` share the same DSL, tokens, units, and state
|
|
|
228
282
|
- **[Adoption Guide](adoption.md)** — Roll out Tasty inside an existing design system or platform team
|
|
229
283
|
- **[Comparison](comparison.md)** — Evaluate Tasty against other styling systems
|
|
230
284
|
- **[Configuration](configuration.md)** — Full `configure()` API: tokens, recipes, custom units, style handlers, TypeScript extensions
|
|
285
|
+
- **[Plugins & Extension Points](plugins.md)** — Add your own style properties, units, functions, and component props
|
|
231
286
|
- **[Style Properties](styles.md)** — Complete reference for all enhanced style properties
|
|
232
287
|
- **[Debug Utilities](debug.md)** — Inspect generated CSS and debug runtime behavior when styles do not look right
|
|
233
288
|
|
|
@@ -237,4 +292,6 @@ Both `tasty()` and `tastyStatic()` share the same DSL, tokens, units, and state
|
|
|
237
292
|
|
|
238
293
|
- Styles are missing on first render: make sure the file that calls `configure()` is imported before any `tasty()` component renders.
|
|
239
294
|
- Token or unit values are not what you expect: check your `configure({ tokens, units })` setup, then inspect the generated CSS variables with [Debug Utilities](debug.md).
|
|
295
|
+
- Changing a `$gap` or `$radius` token does not change `2x` or `1r`: units compile to plain values from `configure({ units })`. Point a unit at the custom property (`units: { x: 'var(--gap)' }`) if it should follow the token.
|
|
296
|
+
- The linter accepts unknown tokens or presets: list them in `tasty.config.ts` so the plugin knows what your design system defines.
|
|
240
297
|
- You need zero-runtime delivery: use server-only [`tasty()` with server rendering](ssr.md) to keep the full React API, or use [`tastyStatic()` build-time extraction](tasty-static.md) when styles must be compiled before rendering.
|
package/docs/injector.md
CHANGED
|
@@ -10,10 +10,10 @@ The Style Injector is the core engine behind Tasty's styling system, providing:
|
|
|
10
10
|
|
|
11
11
|
- **Hash-based deduplication** - Identical CSS gets the same className
|
|
12
12
|
- **DOM-driven lifetime** - Styles are collected once nothing renders them
|
|
13
|
-
- **
|
|
13
|
+
- **Selector assembly** - Prepends the doubled `.tX.tX` class and root prefix to the pipeline's selector fragments (sub-element and `&` nesting is flattened earlier, by the pipeline)
|
|
14
14
|
- **At-rule injection** - First-class `@keyframes`, `@property`, `@font-face`, `@counter-style`, and `@function` support
|
|
15
15
|
- **Smart cleanup** - CSS rules batched cleanup, keyframes disposed immediately
|
|
16
|
-
- **SSR support** - Deterministic class names and
|
|
16
|
+
- **SSR support** - Deterministic class names shared with `ServerStyleCollector`, and hydration of server-rendered classes
|
|
17
17
|
- **Multiple roots** - Works with Document and ShadowRoot
|
|
18
18
|
- **Non-stacking cleanups** - Prevents timeout accumulation for better performance
|
|
19
19
|
|
|
@@ -55,13 +55,15 @@ Injects CSS rules and returns a className with dispose function.
|
|
|
55
55
|
```typescript
|
|
56
56
|
import { inject } from '@tenphi/tasty';
|
|
57
57
|
|
|
58
|
-
//
|
|
58
|
+
// Rules flagged needsClassName get the generated class prepended (doubled)
|
|
59
59
|
const result = inject([{
|
|
60
|
-
selector: '
|
|
60
|
+
selector: '',
|
|
61
61
|
declarations: 'color: red; padding: 10px;',
|
|
62
|
+
needsClassName: true,
|
|
62
63
|
}]);
|
|
63
64
|
|
|
64
|
-
console.log(result.className); // 't
|
|
65
|
+
console.log(result.className); // 'th8n0uh' — `t` + base36 hash of the rules
|
|
66
|
+
// Injected: .th8n0uh.th8n0uh { color: red; padding: 10px; }
|
|
65
67
|
|
|
66
68
|
// Release the pin; the class becomes collectible once nothing renders it
|
|
67
69
|
result.dispose();
|
|
@@ -175,7 +177,7 @@ const result = testInjector.inject(rules);
|
|
|
175
177
|
Injects CSS keyframes with automatic deduplication.
|
|
176
178
|
|
|
177
179
|
```typescript
|
|
178
|
-
// Generated name (
|
|
180
|
+
// Generated name (tk0, tk1, … — namePrefix + 'k' + counter)
|
|
179
181
|
const fadeIn = keyframes({
|
|
180
182
|
from: { opacity: 0 },
|
|
181
183
|
to: { opacity: 1 },
|
|
@@ -253,6 +255,7 @@ Dispose, ref-counted cleanup and GC therefore behave identically in every mode.
|
|
|
253
255
|
- **Configuration is locked after styles are generated** - calling `configure()` after first render will emit a warning and be ignored
|
|
254
256
|
- `gc.touchInterval`: Number of renders between sweeps. When the counter reaches this value, a sweep is scheduled via `requestIdleCallback`; without idle callbacks nothing is collected automatically.
|
|
255
257
|
- `gc.grace`: How long a class is left alone after collection first notices nothing is carrying it, in milliseconds (default `10000`).
|
|
258
|
+
- Automatic sweeps are opt-in: they run only when `configure({ gc })` is set (`gc: {}` enables them with these defaults). Without it nothing is collected until you call `gc()` or `cleanup()`.
|
|
256
259
|
|
|
257
260
|
**What a sweep does.** Everything the injector holds falls into one of five bands, and only the last is ever deleted:
|
|
258
261
|
|
|
@@ -289,7 +292,7 @@ Beyond ordinary rules, the injector has a dedicated entry point per at-rule. All
|
|
|
289
292
|
|
|
290
293
|
Every one of these takes an optional `root` (a `Document` or `ShadowRoot`).
|
|
291
294
|
|
|
292
|
-
`func()` also
|
|
295
|
+
`counterStyle()` and `func()` also accept `weak: true`, which registers the rule **without** overriding an existing one of the same name. `configure()` uses it for global `@counter-style` and `@function` definitions so a component-local definition always wins over the global default, regardless of which is injected first.
|
|
293
296
|
|
|
294
297
|
```ts
|
|
295
298
|
import { counterStyle, fontFace, func, property } from '@tenphi/tasty';
|
|
@@ -306,22 +309,24 @@ func('$$negative', { args: ['$value'], result: '(-1 * $value)' });
|
|
|
306
309
|
|
|
307
310
|
### Style Result Format
|
|
308
311
|
|
|
309
|
-
The injector works with `StyleResult` objects from the
|
|
312
|
+
The injector works with `StyleResult` objects from the pipeline (`renderStyles`):
|
|
310
313
|
|
|
311
314
|
```typescript
|
|
312
315
|
interface StyleResult {
|
|
313
|
-
selector: string;
|
|
314
|
-
declarations: string;
|
|
315
|
-
atRules?: string[];
|
|
316
|
-
|
|
316
|
+
selector: string; // full selector, or a fragment when needsClassName
|
|
317
|
+
declarations: string; // CSS declarations
|
|
318
|
+
atRules?: string[]; // @media, @container, @supports, …
|
|
319
|
+
needsClassName?: boolean; // prepend the generated `.tX.tX` (OR'd fragments joined with '|||')
|
|
320
|
+
rootPrefix?: string; // root-state prefix placed before the class
|
|
321
|
+
startingStyle?: boolean; // wrap declarations in @starting-style
|
|
317
322
|
}
|
|
318
323
|
|
|
319
324
|
// Example StyleResult
|
|
320
325
|
const styleRule: StyleResult = {
|
|
321
|
-
selector: '
|
|
326
|
+
selector: ':where([data-hovered])',
|
|
322
327
|
declarations: 'padding: 8px 16px; background: blue; color: white;',
|
|
323
328
|
atRules: ['@media (min-width: 768px)'],
|
|
324
|
-
|
|
329
|
+
needsClassName: true,
|
|
325
330
|
};
|
|
326
331
|
```
|
|
327
332
|
|
|
@@ -339,28 +344,24 @@ API here calls it for you.
|
|
|
339
344
|
### Deduplication & Performance
|
|
340
345
|
|
|
341
346
|
```typescript
|
|
342
|
-
|
|
343
|
-
const button1 = inject([{
|
|
344
|
-
selector: '.t-btn1',
|
|
345
|
-
declarations: 'padding: 8px; color: red;'
|
|
346
|
-
}]);
|
|
347
|
+
const rule = { selector: '', declarations: 'padding: 8px; color: red;', needsClassName: true };
|
|
347
348
|
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
declarations: 'padding: 8px; color: red;' // Same declarations
|
|
351
|
-
}]);
|
|
349
|
+
const button1 = inject([rule], { cacheKey: 'button' });
|
|
350
|
+
const button2 = inject([rule], { cacheKey: 'button' });
|
|
352
351
|
|
|
353
|
-
//
|
|
352
|
+
// One class, one copy of the CSS
|
|
354
353
|
console.log(button1.className === button2.className); // true
|
|
355
354
|
```
|
|
356
355
|
|
|
356
|
+
Without `cacheKey` the class name still matches (it hashes the rules), but the rule is inserted again and the pin count restarts at 1.
|
|
357
|
+
|
|
357
358
|
### Pinning
|
|
358
359
|
|
|
359
360
|
```typescript
|
|
360
361
|
// Multiple callers using the same styles
|
|
361
|
-
const comp1 = inject([commonStyle]);
|
|
362
|
-
const comp2 = inject([commonStyle]);
|
|
363
|
-
const comp3 = inject([commonStyle]);
|
|
362
|
+
const comp1 = inject([commonStyle], { cacheKey: 'common' });
|
|
363
|
+
const comp2 = inject([commonStyle], { cacheKey: 'common' });
|
|
364
|
+
const comp3 = inject([commonStyle], { cacheKey: 'common' });
|
|
364
365
|
|
|
365
366
|
// Style is pinned while any caller holds a handle
|
|
366
367
|
comp1.dispose(); // pins: 3 → 2
|
|
@@ -439,15 +440,16 @@ const shadowAnimation = keyframes({
|
|
|
439
440
|
|
|
440
441
|
### Server-Side Rendering
|
|
441
442
|
|
|
443
|
+
`getCSSText()` reads the injector's stylesheets, so it needs a DOM; on a server it throws. Collect server CSS with `ServerStyleCollector` from `@tenphi/tasty/ssr` (`collector.getCSS()`) — see [SSR](./ssr.md).
|
|
444
|
+
|
|
442
445
|
```typescript
|
|
443
446
|
import { getCSSText, getCSSTextForNode } from '@tenphi/tasty';
|
|
447
|
+
import { render } from '@testing-library/react';
|
|
444
448
|
|
|
445
|
-
//
|
|
446
|
-
const cssText = getCSSText();
|
|
449
|
+
const cssText = getCSSText(); // everything the injector holds (browser / test DOM)
|
|
447
450
|
|
|
448
|
-
|
|
449
|
-
const
|
|
450
|
-
const componentCSS = getCSSTextForNode(container);
|
|
451
|
+
const { container } = render(<MyComponent />);
|
|
452
|
+
const componentCSS = getCSSTextForNode(container); // only the CSS this subtree uses
|
|
451
453
|
```
|
|
452
454
|
|
|
453
455
|
### Test Environment Detection
|
|
@@ -520,17 +522,14 @@ console.log({
|
|
|
520
522
|
### Debug Information
|
|
521
523
|
|
|
522
524
|
```typescript
|
|
523
|
-
|
|
524
|
-
const debugInfo = injector.instance.getDebugInfo();
|
|
525
|
+
import { tastyDebug } from '@tenphi/tasty';
|
|
525
526
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
unusedStyles: debugInfo.unusedStyles, // Styles marked for cleanup
|
|
529
|
-
totalSheets: debugInfo.totalSheets, // Number of stylesheets
|
|
530
|
-
totalRules: debugInfo.totalRules, // Total CSS rules
|
|
531
|
-
});
|
|
527
|
+
const { activeClasses, unusedClasses, hotClasses, totalRuleCount } =
|
|
528
|
+
tastyDebug.summary({ raw: true });
|
|
532
529
|
```
|
|
533
530
|
|
|
531
|
+
See [Debug Utilities](./debug.md).
|
|
532
|
+
|
|
534
533
|
### Cleanup History
|
|
535
534
|
|
|
536
535
|
```typescript
|
package/docs/methodology.md
CHANGED
|
@@ -119,16 +119,17 @@ Style props accept state maps, so responsive values work through the same API:
|
|
|
119
119
|
|
|
120
120
|
Tasty exports predefined style prop lists that group properties by role. Use them instead of hand-picking arrays:
|
|
121
121
|
|
|
122
|
-
| Preset | Properties
|
|
123
|
-
| ------------------ |
|
|
124
|
-
| `FLOW_STYLES` | flow, gap, columnGap, rowGap, align, justify, placeItems, placeContent, alignItems, alignContent, justifyItems, justifyContent, gridColumns, gridRows, gridTemplate, gridAreas | Layout containers (`Space`, `Grid`) |
|
|
125
|
-
| `POSITION_STYLES` | gridArea, gridColumn, gridRow, order, placeSelf, alignSelf, justifySelf, zIndex, physical/logical margin, inset, scroll margin/padding, position
|
|
126
|
-
| `DIMENSION_STYLES` | width, height, blockSize, inlineSize, flexBasis, flexGrow, flexShrink, flex
|
|
127
|
-
| `COLOR_STYLES` | color, fill, fade, image
|
|
128
|
-
| `BLOCK_STYLES` | padding, blockPadding, inlinePadding, overflow, scrollbar, textAlign, border, blockBorder, inlineBorder, radius, shadow, outline
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
131
|
-
| `
|
|
122
|
+
| Preset | Properties | Typical use |
|
|
123
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
|
|
124
|
+
| `FLOW_STYLES` | flow, gap, columnGap, rowGap, align, justify, place, placeItems, placeContent, alignItems, alignContent, justifyItems, justifyContent, gridColumns, gridRows, gridTemplate, gridAreas | Layout containers (`Space`, `Grid`) |
|
|
125
|
+
| `POSITION_STYLES` | gridArea, gridColumn, gridRow, order, placeSelf, alignSelf, justifySelf, zIndex, physical/logical margin, inset, scroll margin/padding, position | Positioned elements (`Button`, `Badge`) |
|
|
126
|
+
| `DIMENSION_STYLES` | width, height, blockSize, inlineSize, flexBasis, flexGrow, flexShrink, flex | Sized elements |
|
|
127
|
+
| `COLOR_STYLES` | color, fill, fade, image | Color-customizable elements |
|
|
128
|
+
| `BLOCK_STYLES` | padding, blockPadding, inlinePadding, overflow, scrollbar, textAlign, border, blockBorder, inlineBorder, radius, shadow, outline | Block-level containers |
|
|
129
|
+
| `TEXT_STYLES` | textTransform, fontWeight, fontStyle | Text elements |
|
|
130
|
+
| `CONTAINER_STYLES` | All of the above combined (+ BASE_STYLES) | Fully flexible containers |
|
|
131
|
+
| `OUTER_STYLES` | POSITION_STYLES + DIMENSION_STYLES + block outer (border, radius, shadow, outline) | Components whose outer shell is customizable |
|
|
132
|
+
| `INNER_STYLES` | BASE_STYLES + COLOR_STYLES + block inner (padding, overflow, scrollbar) + FLOW_STYLES | Components whose inner layout is customizable |
|
|
132
133
|
|
|
133
134
|
```tsx
|
|
134
135
|
import { tasty, FLOW_STYLES, POSITION_STYLES } from '@tenphi/tasty';
|
|
@@ -156,7 +157,10 @@ Match the preset to the component's role:
|
|
|
156
157
|
|
|
157
158
|
- **Layout containers** (`Space`, `Box`, `Grid`) — `FLOW_STYLES`, optionally with `DIMENSION_STYLES`
|
|
158
159
|
- **Positioned elements** (`Button`, `Badge`) — `POSITION_STYLES`
|
|
159
|
-
- **Text elements** —
|
|
160
|
+
- **Text elements** — `['color', ...TEXT_STYLES]`
|
|
161
|
+
|
|
162
|
+
`BASE_STYLES` (`display`, `font`, `preset`, `hide`, `whiteSpace`, `opacity`, `transition`) are accepted as props on every component without being listed; extend that set app-wide with [`configure({ baseStyleProps })`](configuration.md#base-style-props).
|
|
163
|
+
|
|
160
164
|
- **Compound components** — typically none; styling happens via sub-elements and extension
|
|
161
165
|
|
|
162
166
|
### The governance trade-off
|
|
@@ -220,6 +224,26 @@ modProps: ['isLoading', 'isSelected'] as const,
|
|
|
220
224
|
|
|
221
225
|
For the full API reference, see [React API — Mod Props](react-api.md#mod-props).
|
|
222
226
|
|
|
227
|
+
### variants vs modProps
|
|
228
|
+
|
|
229
|
+
Use `variants` for looks that replace each other (primary, danger, ghost) and `modProps` for states that combine (`isLoading`, `size`):
|
|
230
|
+
|
|
231
|
+
```tsx
|
|
232
|
+
const Button = tasty({
|
|
233
|
+
as: 'button',
|
|
234
|
+
modProps: { isLoading: Boolean, size: ['sm', 'md', 'lg'] as const },
|
|
235
|
+
styles: { padding: { '': '1.5x 3x', 'size=sm': '1x 2x' }, radius: '1r' },
|
|
236
|
+
variants: {
|
|
237
|
+
default: { fill: '#primary', color: '#on-primary' },
|
|
238
|
+
danger: { fill: '#danger', color: '#on-danger' },
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
<Button variant="danger" size="sm" isLoading />;
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
`variant` is typed from the variant keys, the `default` variant applies when no `variant` prop is passed, and only variants that render emit CSS. See [React API — Variants](react-api.md#variants).
|
|
246
|
+
|
|
223
247
|
---
|
|
224
248
|
|
|
225
249
|
## tokens prop for dynamic values
|
|
@@ -335,10 +359,9 @@ const LargeCard = tasty(Card, {
|
|
|
335
359
|
|
|
336
360
|
Why? Styled wrappers are:
|
|
337
361
|
|
|
338
|
-
- **
|
|
339
|
-
- **Stable** — the style object is defined once, not recreated on every render
|
|
362
|
+
- **Stable** — the style object is defined once at module level, not recreated on every render
|
|
340
363
|
- **Composable** — another engineer can extend `LargeCard` further
|
|
341
|
-
- **Inspectable** —
|
|
364
|
+
- **Inspectable** — give it a `qa` (`tasty(Card, { qa: 'LargeCard', styles })`) and it appears as `TastyWrappedComponent(LargeCard)` in React DevTools and as `data-qa="LargeCard"` in the DOM
|
|
342
365
|
- **Lint-friendly** — the ESLint plugin's `no-styles-prop` rule flags direct usage
|
|
343
366
|
|
|
344
367
|
The `styles` prop exists as an escape hatch — for prototyping, one-off overrides during development, or cases where wrapping is impractical. It should not be the default way product engineers customize components.
|
|
@@ -453,11 +476,13 @@ const DangerCard = tasty(Card, {
|
|
|
453
476
|
|
|
454
477
|
This is preferred over `<Card styles={{ border: '1bw solid #danger' }}>` because:
|
|
455
478
|
|
|
456
|
-
1.
|
|
479
|
+
1. The styles live in one module-level definition instead of being rebuilt inside render
|
|
457
480
|
2. `DangerCard` can be extended further by others
|
|
458
|
-
3. It
|
|
481
|
+
3. It is searchable in code, and named in DevTools once you give it a `qa`
|
|
459
482
|
4. The ESLint `no-styles-prop` rule encourages this pattern
|
|
460
483
|
|
|
484
|
+
A wrapper does not re-expose the base's sub-element components: `DangerCard.Title` is `undefined` and not part of its type. Keep using the base's parts inside it — `<DangerCard><Card.Title>…</Card.Title></DangerCard>`. The wrapper's `Title` styles still apply, because they target `[data-element="Title"]`.
|
|
485
|
+
|
|
461
486
|
---
|
|
462
487
|
|
|
463
488
|
## How configuration simplifies components
|
|
@@ -554,7 +579,7 @@ fill: 'oklch(55% 0.25 265)',
|
|
|
554
579
|
fill: '#primary',
|
|
555
580
|
```
|
|
556
581
|
|
|
557
|
-
Tokens ensure consistency across components and make theme changes a one-line update.
|
|
582
|
+
Tokens ensure consistency across components and make theme changes a one-line update. The recommended lint config flags this with `tasty/no-raw-color-values` and `tasty/consistent-token-usage`.
|
|
558
583
|
|
|
559
584
|
### Using CSS property names when Tasty alternatives exist
|
|
560
585
|
|
|
@@ -570,7 +595,7 @@ radius: '1r',
|
|
|
570
595
|
flow: 'column',
|
|
571
596
|
```
|
|
572
597
|
|
|
573
|
-
Tasty's enhanced properties provide concise syntax, better composability, and simpler overrides. See [recommended props](styles.md#recommended-props) for the full mapping.
|
|
598
|
+
Tasty's enhanced properties provide concise syntax, better composability, and simpler overrides. See [recommended props](styles.md#recommended-props) for the full mapping. `tasty/prefer-shorthand-property` flags the native spellings.
|
|
574
599
|
|
|
575
600
|
### Changing styles prop at runtime
|
|
576
601
|
|
|
@@ -593,7 +618,7 @@ const Card = tasty({
|
|
|
593
618
|
});
|
|
594
619
|
```
|
|
595
620
|
|
|
596
|
-
Modifiers are compiled into exclusive selectors once. Changing `styles` at runtime forces Tasty to regenerate and re-inject CSS.
|
|
621
|
+
Modifiers are compiled into exclusive selectors once. Changing `styles` at runtime forces Tasty to regenerate and re-inject CSS. `tasty/no-styles-prop` and `tasty/no-runtime-styles-mutation` flag these patterns.
|
|
597
622
|
|
|
598
623
|
### Overusing style prop
|
|
599
624
|
|
|
@@ -607,7 +632,7 @@ const DangerButton = tasty(Button, {
|
|
|
607
632
|
});
|
|
608
633
|
```
|
|
609
634
|
|
|
610
|
-
The `style` prop bypasses tokens, units, and state maps. It should only be used for third-party library integration.
|
|
635
|
+
The `style` prop bypasses tokens, units, and state maps. It should only be used for third-party library integration. `tasty/no-style-prop` flags it on Tasty components.
|
|
611
636
|
|
|
612
637
|
### Skipping elements for compound components
|
|
613
638
|
|
package/docs/migration-v3.md
CHANGED
|
@@ -188,8 +188,8 @@ A key whose prefix doesn't match its value type is ignored with a development wa
|
|
|
188
188
|
## 7. Moved subpath
|
|
189
189
|
|
|
190
190
|
```diff
|
|
191
|
-
- import {
|
|
192
|
-
+ import {
|
|
191
|
+
- import { withTastyZero } from '@tenphi/tasty/next';
|
|
192
|
+
+ import { withTastyZero } from '@tenphi/tasty/zero/next';
|
|
193
193
|
```
|
|
194
194
|
|
|
195
195
|
The build-time extraction Next.js wrapper already lived under the `zero` namespace internally; the top-level `/next` path collided confusingly with `@tenphi/tasty/ssr/next`, which is a different integration.
|
package/docs/pipeline.md
CHANGED
|
@@ -76,7 +76,7 @@ Output: CSSRule[]
|
|
|
76
76
|
|
|
77
77
|
**Simplification** (`simplifyCondition` in `simplify.ts`) is not a separate numbered stage. It runs inside OR expansion, exclusive building, `expandExclusiveOrs` branch cleanup, combination ANDs, merge-by-value ORs, and materialization. Every call is cached by condition unique-id, so the repetition is cheap.
|
|
78
78
|
|
|
79
|
-
**Post-pass:** After `processStyles` collects rules from every handler, `runPipeline` (`index.ts`) filters duplicates using a key of `selector|declarations|atRules|rootPrefix|startingStyle`, then **stable-sorts rules by their cascade `order` hint (ascending source priority)** so lower-priority rules come first and higher-priority rules win, and finally emits every `@starting-style` rule **after** all normal rules.
|
|
79
|
+
**Post-pass:** After `processStyles` collects rules from every handler, `runPipeline` (`index.ts`) filters duplicates using a key of `selector|declarations|atRules|rootPrefix|startingStyle`, then **stable-sorts rules by their cascade `order` hint (ascending source priority)** so lower-priority rules come first and higher-priority rules win, and finally emits every `@starting-style` rule **after** all normal rules. Each Stage 4 snapshot takes the highest source priority among its entries as its `order` hint; when Stage 6 merges same-output rules, the merged rule keeps the lowest `order` of its group, so a rule carrying the `_` floor's value keeps the floor's early position (threaded `ComputedRule → CSSRule`). This ordering is cascade-critical because Stage 7 equalizes specificity with `:where()`: once every state selector carries zero specificity, source order alone decides which of two overlapping rules wins — most importantly the `_` fallback floor (low order, emitted first) versus the states layered over it, and `@starting-style` versus its normal counterpart. Mutually-exclusive rules are unaffected by ordering (they never both match), so reordering them is safe.
|
|
80
80
|
|
|
81
81
|
---
|
|
82
82
|
|
|
@@ -197,7 +197,9 @@ Merging two same-value entries with conditions `C_h` (higher priority) and `C_l`
|
|
|
197
197
|
|
|
198
198
|
The merge is safe iff for every entry `e_m` strictly between them in priority with a different value,
|
|
199
199
|
|
|
200
|
-
|
|
200
|
+
```
|
|
201
|
+
simplify(C_m & C_l & !C_h) = FALSE
|
|
202
|
+
```
|
|
201
203
|
|
|
202
204
|
i.e. there is no scenario where the intermediate state could have matched, the lower same-value entry would also have matched, and the higher one would not. This is the only way the merge could leak through and shadow the intermediate state.
|
|
203
205
|
|
|
@@ -362,7 +364,7 @@ This is the companion to **Stage 2a** (user-OR expansion). The split exists beca
|
|
|
362
364
|
|
|
363
365
|
| Stage | Runs on | Sees ORs from | Sorts branches? |
|
|
364
366
|
|---|---|---|---|
|
|
365
|
-
| 2a `expandOrConditions` | `ParsedStyleEntry.condition` | User-authored
|
|
367
|
+
| 2a `expandOrConditions` | `ParsedStyleEntry.condition` | User-authored `\|` in state keys | No — user order is stable |
|
|
366
368
|
| 3 `expandExclusiveOrs` | `ExclusiveStyleEntry.exclusiveCondition` | De Morgan negation inside `buildExclusiveConditions` | Yes — at-rule branches first |
|
|
367
369
|
|
|
368
370
|
### How It Works
|
|
@@ -545,12 +547,13 @@ interface CSSRule {
|
|
|
545
547
|
declarations: string; // CSS declarations (e.g. 'color: red;')
|
|
546
548
|
atRules?: string[]; // Wrapping at-rules
|
|
547
549
|
rootPrefix?: string; // Root state prefix
|
|
550
|
+
rootState?: string; // Root-state segment without its :root anchor; lets a root selector take states directly
|
|
548
551
|
startingStyle?: boolean; // Wrap declarations in @starting-style
|
|
549
552
|
order?: number; // Internal cascade order hint (ascending source priority); stripped before injection
|
|
550
553
|
}
|
|
551
554
|
```
|
|
552
555
|
|
|
553
|
-
When `renderStyles` runs **without** a selector, returned rules include `needsClassName: true` (compatibility field for the injector); that flag is not part of `CSSRule`
|
|
556
|
+
When `renderStyles` runs **without** a selector, returned rules include `needsClassName: true` (compatibility field for the injector); that flag is not part of `CSSRule` (defined in `materialize-types.ts`).
|
|
554
557
|
|
|
555
558
|
---
|
|
556
559
|
|
|
@@ -618,7 +621,7 @@ Applies boolean algebra rules to reduce condition complexity and detect impossib
|
|
|
618
621
|
- `A & (A | B) = A`
|
|
619
622
|
- `A | (A & B) = A`
|
|
620
623
|
|
|
621
|
-
7. **Range intersection**: For **media and container** dimension queries, impossible ranges simplify to `FALSE` (e.g. `@media(w > 400px) & @media(w < 300px)`). Ranges with compatible bounds are also merged in place (`w >= 400 & w <= 800` → a single bounded range).
|
|
624
|
+
7. **Range intersection**: For **media and container** dimension queries, impossible ranges simplify to `FALSE` (e.g. `@media(w > 400px) & @media(w < 300px)`). Ranges with compatible bounds are also merged in place (`w >= 400 & w <= 800` → a single bounded range). Only bounds in the same unit are compared: `600px` and `40em` are never merged or found contradictory, because a query `em` follows the user's default font size. A bound with no numeric value (`calc(…)`) and a range whose two bounds use different units are left as they are.
|
|
622
625
|
|
|
623
626
|
8. **Container style queries**: Conflicting or redundant `@container` style conditions on the same property can be reduced (see `simplify.ts` around the container-style conflict pass).
|
|
624
627
|
|
|
@@ -642,7 +645,7 @@ LRU and small auxiliary caches:
|
|
|
642
645
|
| Cache | Size | Key | Purpose |
|
|
643
646
|
|-------|------|-----|---------|
|
|
644
647
|
| `pipelineCache` | 5000 | `pipelineCacheKey \|\| stringifyStyles(styles)` | Skip full pipeline for identical styles |
|
|
645
|
-
| `parseCache` | 5000 | `trimmedStateKey + '
|
|
648
|
+
| `parseCache` | 5000 | `trimmedStateKey + '\0' + (isSubElement ? '1' : '0') + '\0' + (localPredefinedStates non-empty ? JSON.stringify(localPredefinedStates) : '')` | Skip re-parsing identical state keys in context |
|
|
646
649
|
| `simplifyCache` | 5000 | `getConditionUniqueId(node)` | Skip re-simplifying identical conditions |
|
|
647
650
|
| `conditionCache` | 3000 | `getConditionUniqueId(node)` in `conditionToCSS` | Skip re-materializing identical conditions |
|
|
648
651
|
| `variantKeyCache` | — | `WeakMap<SelectorVariant, string>` | Stable string keys for variants during materialization |
|
|
@@ -702,24 +705,24 @@ Each snapshot yields distinct declarations; no merge.
|
|
|
702
705
|
|
|
703
706
|
### Stage 7: Materialize CSS
|
|
704
707
|
|
|
705
|
-
Using `renderStyles(styles, '.t1')
|
|
708
|
+
Using `renderStyles(styles, '.t1', { doubleSelector: true })`. The injector doubles the class the same way (`.t1.t1`) so Tasty owns specificity without `!important`, and every state is wrapped in `:where()` so it adds none. Rules come out in ascending priority:
|
|
706
709
|
|
|
707
710
|
```css
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
+
@media (not (prefers-color-scheme: dark)) {
|
|
712
|
+
.t1.t1:where(:not([data-hovered])) {
|
|
713
|
+
color: var(--white-color);
|
|
714
|
+
--current-color: var(--white-color);
|
|
715
|
+
}
|
|
711
716
|
}
|
|
712
717
|
@media (prefers-color-scheme: dark) {
|
|
713
|
-
.t1:not([data-hovered]) {
|
|
718
|
+
.t1.t1:where(:not([data-hovered])) {
|
|
714
719
|
color: var(--dark-color);
|
|
715
720
|
--current-color: var(--dark-color);
|
|
716
721
|
}
|
|
717
722
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
--current-color: var(--white-color);
|
|
722
|
-
}
|
|
723
|
+
.t1.t1:where([data-hovered]) {
|
|
724
|
+
color: var(--highlight-color);
|
|
725
|
+
--current-color: var(--highlight-color);
|
|
723
726
|
}
|
|
724
727
|
```
|
|
725
728
|
|