@tenphi/tasty 0.0.0-snapshot.fafcbfe → 0.0.0-snapshot.fdb16ea
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 +397 -185
- package/dist/async-storage-DKK-wTD4.js +44 -0
- package/dist/async-storage-DKK-wTD4.js.map +1 -0
- package/dist/babel-CMYf4JZ2.d.ts +83 -0
- package/dist/collector-Ctn5s74s.d.ts +145 -0
- package/dist/collector-DMSR6ANK.js +304 -0
- package/dist/collector-DMSR6ANK.js.map +1 -0
- package/dist/config-B23gZrfP.js +12317 -0
- package/dist/config-B23gZrfP.js.map +1 -0
- package/dist/config-CZx8_DCu.d.ts +1296 -0
- package/dist/context-CA8YKeMn.js +24 -0
- package/dist/context-CA8YKeMn.js.map +1 -0
- package/dist/core/index.d.ts +5 -33
- package/dist/core/index.js +6 -26
- package/dist/core-B1tyn69d.js +1573 -0
- package/dist/core-B1tyn69d.js.map +1 -0
- package/dist/css-writer-CvAxkR6S.js +390 -0
- package/dist/css-writer-CvAxkR6S.js.map +1 -0
- package/dist/format-global-rules-DklyaXv-.js +22 -0
- package/dist/format-global-rules-DklyaXv-.js.map +1 -0
- package/dist/format-rules-BCTWVvcS.js +130 -0
- package/dist/format-rules-BCTWVvcS.js.map +1 -0
- package/dist/hydrate-BCPvkJj3.js +37 -0
- package/dist/hydrate-BCPvkJj3.js.map +1 -0
- package/dist/index-B3o0aCYV.d.ts +1538 -0
- package/dist/index-CsvB0EXZ.d.ts +1909 -0
- package/dist/index.d.ts +5 -40
- package/dist/index.js +921 -32
- package/dist/index.js.map +1 -0
- package/dist/keyframes-Dxdx3KOo.js +493 -0
- package/dist/keyframes-Dxdx3KOo.js.map +1 -0
- package/dist/{utils/merge-styles.js → merge-styles-C-uwpoNW.js} +4 -6
- package/dist/merge-styles-C-uwpoNW.js.map +1 -0
- package/dist/{utils/merge-styles.d.ts → merge-styles-DJTSfz_M.d.ts} +3 -3
- package/dist/{utils/resolve-recipes.js → resolve-recipes-BuOvSEa2.js} +5 -8
- package/dist/resolve-recipes-BuOvSEa2.js.map +1 -0
- package/dist/ssr/astro-client.d.ts +1 -0
- package/dist/ssr/astro-client.js +19 -0
- package/dist/ssr/astro-client.js.map +1 -0
- package/dist/ssr/astro-middleware-static.d.ts +16 -0
- package/dist/ssr/astro-middleware-static.js +18 -0
- package/dist/ssr/astro-middleware-static.js.map +1 -0
- package/dist/ssr/astro-middleware.d.ts +17 -0
- package/dist/ssr/astro-middleware.js +19 -0
- package/dist/ssr/astro-middleware.js.map +1 -0
- package/dist/ssr/astro.d.ts +93 -0
- package/dist/ssr/astro.js +157 -0
- package/dist/ssr/astro.js.map +1 -0
- package/dist/ssr/index.d.ts +37 -0
- package/dist/ssr/index.js +10 -0
- package/dist/ssr/index.js.map +1 -0
- package/dist/ssr/next.d.ts +45 -0
- package/dist/ssr/next.js +74 -0
- package/dist/ssr/next.js.map +1 -0
- package/dist/static/index.d.ts +91 -5
- package/dist/static/index.js +49 -4
- package/dist/static/index.js.map +1 -0
- package/dist/static/inject.d.ts +5 -0
- package/dist/static/inject.js +17 -0
- package/dist/static/inject.js.map +1 -0
- package/dist/zero/babel.d.ts +2 -108
- package/dist/zero/babel.js +235 -39
- package/dist/zero/babel.js.map +1 -1
- package/dist/zero/index.d.ts +81 -3
- package/dist/zero/index.js +2 -4
- package/dist/zero/next.d.ts +56 -30
- package/dist/zero/next.js +106 -41
- package/dist/zero/next.js.map +1 -1
- package/docs/README.md +39 -0
- package/docs/adoption.md +323 -0
- package/docs/ai-agents.md +224 -0
- package/docs/comparison.md +448 -0
- package/docs/configuration.md +823 -0
- package/docs/debug.md +322 -0
- package/docs/design-system.md +455 -0
- package/docs/dsl.md +1021 -0
- package/docs/getting-started.md +238 -0
- package/docs/injector.md +614 -0
- package/docs/methodology.md +648 -0
- package/docs/migration-v3.md +285 -0
- package/docs/pipeline.md +741 -0
- package/docs/plugins.md +353 -0
- package/docs/react-api.md +681 -0
- package/docs/runtime-benchmarks.md +216 -0
- package/docs/ssr.md +451 -0
- package/docs/styles.md +645 -0
- package/docs/tasty-static.md +582 -0
- package/package.json +149 -40
- package/tasty.config.ts +6 -0
- package/dist/_virtual/_rolldown/runtime.js +0 -8
- package/dist/chunks/cacheKey.js +0 -70
- package/dist/chunks/cacheKey.js.map +0 -1
- package/dist/chunks/definitions.d.ts +0 -37
- package/dist/chunks/definitions.js +0 -260
- package/dist/chunks/definitions.js.map +0 -1
- package/dist/chunks/renderChunk.js +0 -61
- package/dist/chunks/renderChunk.js.map +0 -1
- package/dist/config.d.ts +0 -280
- package/dist/config.js +0 -403
- package/dist/config.js.map +0 -1
- package/dist/debug.d.ts +0 -204
- package/dist/debug.js +0 -733
- package/dist/debug.js.map +0 -1
- package/dist/hooks/useGlobalStyles.d.ts +0 -27
- package/dist/hooks/useGlobalStyles.js +0 -56
- package/dist/hooks/useGlobalStyles.js.map +0 -1
- package/dist/hooks/useKeyframes.d.ts +0 -56
- package/dist/hooks/useKeyframes.js +0 -54
- package/dist/hooks/useKeyframes.js.map +0 -1
- package/dist/hooks/useProperty.d.ts +0 -79
- package/dist/hooks/useProperty.js +0 -91
- package/dist/hooks/useProperty.js.map +0 -1
- package/dist/hooks/useRawCSS.d.ts +0 -53
- package/dist/hooks/useRawCSS.js +0 -28
- package/dist/hooks/useRawCSS.js.map +0 -1
- package/dist/hooks/useStyles.d.ts +0 -40
- package/dist/hooks/useStyles.js +0 -169
- package/dist/hooks/useStyles.js.map +0 -1
- package/dist/injector/index.d.ts +0 -157
- package/dist/injector/index.js +0 -154
- package/dist/injector/index.js.map +0 -1
- package/dist/injector/injector.d.ts +0 -139
- package/dist/injector/injector.js +0 -404
- package/dist/injector/injector.js.map +0 -1
- package/dist/injector/sheet-manager.d.ts +0 -127
- package/dist/injector/sheet-manager.js +0 -714
- package/dist/injector/sheet-manager.js.map +0 -1
- package/dist/injector/types.d.ts +0 -135
- package/dist/keyframes/index.js +0 -206
- package/dist/keyframes/index.js.map +0 -1
- package/dist/parser/classify.js +0 -319
- package/dist/parser/classify.js.map +0 -1
- package/dist/parser/const.js +0 -33
- package/dist/parser/const.js.map +0 -1
- package/dist/parser/lru.js +0 -109
- package/dist/parser/lru.js.map +0 -1
- package/dist/parser/parser.d.ts +0 -25
- package/dist/parser/parser.js +0 -116
- package/dist/parser/parser.js.map +0 -1
- package/dist/parser/tokenizer.js +0 -69
- package/dist/parser/tokenizer.js.map +0 -1
- package/dist/parser/types.d.ts +0 -51
- package/dist/parser/types.js +0 -46
- package/dist/parser/types.js.map +0 -1
- package/dist/pipeline/conditions.d.ts +0 -134
- package/dist/pipeline/conditions.js +0 -400
- package/dist/pipeline/conditions.js.map +0 -1
- package/dist/pipeline/exclusive.js +0 -231
- package/dist/pipeline/exclusive.js.map +0 -1
- package/dist/pipeline/index.d.ts +0 -53
- package/dist/pipeline/index.js +0 -641
- package/dist/pipeline/index.js.map +0 -1
- package/dist/pipeline/materialize.js +0 -908
- package/dist/pipeline/materialize.js.map +0 -1
- package/dist/pipeline/parseStateKey.d.ts +0 -15
- package/dist/pipeline/parseStateKey.js +0 -430
- package/dist/pipeline/parseStateKey.js.map +0 -1
- package/dist/pipeline/simplify.js +0 -557
- package/dist/pipeline/simplify.js.map +0 -1
- package/dist/plugins/okhsl-plugin.d.ts +0 -35
- package/dist/plugins/okhsl-plugin.js +0 -371
- package/dist/plugins/okhsl-plugin.js.map +0 -1
- package/dist/plugins/types.d.ts +0 -69
- package/dist/properties/index.js +0 -158
- package/dist/properties/index.js.map +0 -1
- package/dist/states/index.d.ts +0 -49
- package/dist/states/index.js +0 -416
- package/dist/states/index.js.map +0 -1
- package/dist/static/tastyStatic.d.ts +0 -46
- package/dist/static/tastyStatic.js +0 -31
- package/dist/static/tastyStatic.js.map +0 -1
- package/dist/static/types.d.ts +0 -49
- package/dist/static/types.js +0 -24
- package/dist/static/types.js.map +0 -1
- package/dist/styles/align.d.ts +0 -15
- package/dist/styles/align.js +0 -14
- package/dist/styles/align.js.map +0 -1
- package/dist/styles/border.d.ts +0 -25
- package/dist/styles/border.js +0 -114
- package/dist/styles/border.js.map +0 -1
- package/dist/styles/color.d.ts +0 -14
- package/dist/styles/color.js +0 -23
- package/dist/styles/color.js.map +0 -1
- package/dist/styles/createStyle.js +0 -77
- package/dist/styles/createStyle.js.map +0 -1
- package/dist/styles/dimension.js +0 -97
- package/dist/styles/dimension.js.map +0 -1
- package/dist/styles/display.d.ts +0 -37
- package/dist/styles/display.js +0 -67
- package/dist/styles/display.js.map +0 -1
- package/dist/styles/fade.d.ts +0 -15
- package/dist/styles/fade.js +0 -58
- package/dist/styles/fade.js.map +0 -1
- package/dist/styles/fill.d.ts +0 -44
- package/dist/styles/fill.js +0 -51
- package/dist/styles/fill.js.map +0 -1
- package/dist/styles/flow.d.ts +0 -16
- package/dist/styles/flow.js +0 -12
- package/dist/styles/flow.js.map +0 -1
- package/dist/styles/gap.d.ts +0 -31
- package/dist/styles/gap.js +0 -37
- package/dist/styles/gap.js.map +0 -1
- package/dist/styles/height.d.ts +0 -17
- package/dist/styles/height.js +0 -20
- package/dist/styles/height.js.map +0 -1
- package/dist/styles/index.d.ts +0 -2
- package/dist/styles/index.js +0 -9
- package/dist/styles/index.js.map +0 -1
- package/dist/styles/inset.d.ts +0 -52
- package/dist/styles/inset.js +0 -150
- package/dist/styles/inset.js.map +0 -1
- package/dist/styles/justify.d.ts +0 -15
- package/dist/styles/justify.js +0 -14
- package/dist/styles/justify.js.map +0 -1
- package/dist/styles/list.d.ts +0 -16
- package/dist/styles/list.js +0 -98
- package/dist/styles/list.js.map +0 -1
- package/dist/styles/margin.d.ts +0 -24
- package/dist/styles/margin.js +0 -104
- package/dist/styles/margin.js.map +0 -1
- package/dist/styles/outline.d.ts +0 -29
- package/dist/styles/outline.js +0 -65
- package/dist/styles/outline.js.map +0 -1
- package/dist/styles/padding.d.ts +0 -24
- package/dist/styles/padding.js +0 -104
- package/dist/styles/padding.js.map +0 -1
- package/dist/styles/predefined.d.ts +0 -73
- package/dist/styles/predefined.js +0 -241
- package/dist/styles/predefined.js.map +0 -1
- package/dist/styles/preset.d.ts +0 -47
- package/dist/styles/preset.js +0 -126
- package/dist/styles/preset.js.map +0 -1
- package/dist/styles/radius.d.ts +0 -14
- package/dist/styles/radius.js +0 -51
- package/dist/styles/radius.js.map +0 -1
- package/dist/styles/scrollbar.d.ts +0 -21
- package/dist/styles/scrollbar.js +0 -105
- package/dist/styles/scrollbar.js.map +0 -1
- package/dist/styles/shadow.d.ts +0 -14
- package/dist/styles/shadow.js +0 -24
- package/dist/styles/shadow.js.map +0 -1
- package/dist/styles/styledScrollbar.d.ts +0 -47
- package/dist/styles/styledScrollbar.js +0 -38
- package/dist/styles/styledScrollbar.js.map +0 -1
- package/dist/styles/transition.d.ts +0 -14
- package/dist/styles/transition.js +0 -158
- package/dist/styles/transition.js.map +0 -1
- package/dist/styles/types.d.ts +0 -498
- package/dist/styles/width.d.ts +0 -17
- package/dist/styles/width.js +0 -20
- package/dist/styles/width.js.map +0 -1
- package/dist/tasty.d.ts +0 -982
- package/dist/tasty.js +0 -191
- package/dist/tasty.js.map +0 -1
- package/dist/tokens/typography.d.ts +0 -19
- package/dist/tokens/typography.js +0 -237
- package/dist/tokens/typography.js.map +0 -1
- package/dist/types.d.ts +0 -184
- package/dist/utils/cache-wrapper.js +0 -26
- package/dist/utils/cache-wrapper.js.map +0 -1
- package/dist/utils/case-converter.js +0 -8
- package/dist/utils/case-converter.js.map +0 -1
- package/dist/utils/colors.d.ts +0 -5
- package/dist/utils/colors.js +0 -9
- package/dist/utils/colors.js.map +0 -1
- package/dist/utils/css-types.d.ts +0 -7
- package/dist/utils/dotize.d.ts +0 -26
- package/dist/utils/dotize.js +0 -122
- package/dist/utils/dotize.js.map +0 -1
- package/dist/utils/filter-base-props.d.ts +0 -15
- package/dist/utils/filter-base-props.js +0 -45
- package/dist/utils/filter-base-props.js.map +0 -1
- package/dist/utils/get-display-name.d.ts +0 -7
- package/dist/utils/get-display-name.js +0 -10
- package/dist/utils/get-display-name.js.map +0 -1
- package/dist/utils/hsl-to-rgb.js +0 -38
- package/dist/utils/hsl-to-rgb.js.map +0 -1
- package/dist/utils/is-dev-env.js +0 -19
- package/dist/utils/is-dev-env.js.map +0 -1
- package/dist/utils/is-valid-element-type.js +0 -15
- package/dist/utils/is-valid-element-type.js.map +0 -1
- package/dist/utils/merge-styles.js.map +0 -1
- package/dist/utils/mod-attrs.d.ts +0 -8
- package/dist/utils/mod-attrs.js +0 -21
- package/dist/utils/mod-attrs.js.map +0 -1
- package/dist/utils/okhsl-to-rgb.js +0 -296
- package/dist/utils/okhsl-to-rgb.js.map +0 -1
- package/dist/utils/process-tokens.d.ts +0 -31
- package/dist/utils/process-tokens.js +0 -171
- package/dist/utils/process-tokens.js.map +0 -1
- package/dist/utils/resolve-recipes.d.ts +0 -17
- package/dist/utils/resolve-recipes.js.map +0 -1
- package/dist/utils/string.js +0 -8
- package/dist/utils/string.js.map +0 -1
- package/dist/utils/styles.d.ts +0 -183
- package/dist/utils/styles.js +0 -585
- package/dist/utils/styles.js.map +0 -1
- package/dist/utils/typography.d.ts +0 -36
- package/dist/utils/typography.js +0 -53
- package/dist/utils/typography.js.map +0 -1
- package/dist/utils/warnings.d.ts +0 -16
- package/dist/utils/warnings.js +0 -16
- package/dist/utils/warnings.js.map +0 -1
- package/dist/zero/css-writer.d.ts +0 -45
- package/dist/zero/css-writer.js +0 -74
- package/dist/zero/css-writer.js.map +0 -1
- package/dist/zero/extractor.d.ts +0 -24
- package/dist/zero/extractor.js +0 -150
- package/dist/zero/extractor.js.map +0 -1
package/docs/injector.md
ADDED
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
# Tasty Style Injector
|
|
2
|
+
|
|
3
|
+
A high-performance CSS-in-JS solution that powers the Tasty design system with efficient style injection, automatic cleanup, and first-class SSR support.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
The Style Injector is the core engine behind Tasty's styling system, providing:
|
|
10
|
+
|
|
11
|
+
- **Hash-based deduplication** - Identical CSS gets the same className
|
|
12
|
+
- **DOM-driven lifetime** - Styles are collected once nothing renders them
|
|
13
|
+
- **CSS nesting flattening** - Handles `&`, `.Class`, `SubElement` patterns
|
|
14
|
+
- **At-rule injection** - First-class `@keyframes`, `@property`, `@font-face`, `@counter-style`, and `@function` support
|
|
15
|
+
- **Smart cleanup** - CSS rules batched cleanup, keyframes disposed immediately
|
|
16
|
+
- **SSR support** - Deterministic class names and CSS extraction
|
|
17
|
+
- **Multiple roots** - Works with Document and ShadowRoot
|
|
18
|
+
- **Non-stacking cleanups** - Prevents timeout accumulation for better performance
|
|
19
|
+
|
|
20
|
+
> **Note:** This is internal infrastructure that powers Tasty components. Most developers will interact with the higher-level `tasty()` API instead.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Architecture
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
|
28
|
+
│ tasty() │────│ Style Injector │────│ Sheet Manager │
|
|
29
|
+
│ components │ │ │ │ │
|
|
30
|
+
└─────────────────┘ └──────────────────┘ └─────────────────┘
|
|
31
|
+
│ │ │
|
|
32
|
+
│ │ │
|
|
33
|
+
▼ ▼ ▼
|
|
34
|
+
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
|
35
|
+
│ Style Results │ │ Keyframes Manager│ │ Root Registry │
|
|
36
|
+
│ (CSS rules) │ │ │ │ │
|
|
37
|
+
└─────────────────┘ └──────────────────┘ └─────────────────┘
|
|
38
|
+
│ │
|
|
39
|
+
│ │
|
|
40
|
+
▼ ▼
|
|
41
|
+
┌─────────────────┐ ┌─────────────────┐
|
|
42
|
+
│ Hash Cache │ │ <style> elements│
|
|
43
|
+
│ Deduplication │ │ CSSStyleSheet │
|
|
44
|
+
└─────────────────┘ └─────────────────┘
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Core API
|
|
50
|
+
|
|
51
|
+
### `inject(rules, options?): InjectResult`
|
|
52
|
+
|
|
53
|
+
Injects CSS rules and returns a className with dispose function.
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { inject } from '@tenphi/tasty';
|
|
57
|
+
|
|
58
|
+
// Component styling - generates tasty class names
|
|
59
|
+
const result = inject([{
|
|
60
|
+
selector: '.t-abc123',
|
|
61
|
+
declarations: 'color: red; padding: 10px;',
|
|
62
|
+
}]);
|
|
63
|
+
|
|
64
|
+
console.log(result.className); // 't-abc123'
|
|
65
|
+
|
|
66
|
+
// Release the pin; the class becomes collectible once nothing renders it
|
|
67
|
+
result.dispose();
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`inject()` **pins** the class it returns, and `gc()` never evicts a pinned class
|
|
71
|
+
— that is what `dispose()` releases. Pass `{ pin: false }` when the caller keeps
|
|
72
|
+
no handle and the DOM is the only record that the class is in use; `dispose()`
|
|
73
|
+
is then a no-op. The render path (`tasty()` / `computeStyles()`) injects this
|
|
74
|
+
way, because a hook-free render has no unmount signal to dispose on.
|
|
75
|
+
|
|
76
|
+
### `injectGlobal(rules, options?): { dispose: () => void }`
|
|
77
|
+
|
|
78
|
+
Injects global styles that don't reserve tasty class names.
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
// Global styles - for body, resets, etc.
|
|
82
|
+
const globalResult = injectGlobal([
|
|
83
|
+
{
|
|
84
|
+
selector: 'body',
|
|
85
|
+
declarations: 'margin: 0; font-family: Arial;',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
selector: '.header',
|
|
89
|
+
declarations: 'background: blue; color: white;',
|
|
90
|
+
atRules: ['@media (min-width: 768px)'],
|
|
91
|
+
}
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
// Only returns dispose function - no className needed for global styles
|
|
95
|
+
globalResult.dispose();
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### `injectRawCSS(css, options?): { dispose: () => void }`
|
|
99
|
+
|
|
100
|
+
Injects raw CSS text directly without parsing. This is a low-overhead method for injecting CSS that doesn't need tasty processing.
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { injectRawCSS } from '@tenphi/tasty';
|
|
104
|
+
|
|
105
|
+
// Inject raw CSS
|
|
106
|
+
const { dispose } = injectRawCSS(`
|
|
107
|
+
body {
|
|
108
|
+
margin: 0;
|
|
109
|
+
padding: 0;
|
|
110
|
+
font-family: sans-serif;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.my-class {
|
|
114
|
+
color: red;
|
|
115
|
+
}
|
|
116
|
+
`);
|
|
117
|
+
|
|
118
|
+
// Later, remove the injected CSS
|
|
119
|
+
dispose();
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### `useRawCSS(css, options?)` or `useRawCSS(factory, deps, options?)`
|
|
123
|
+
|
|
124
|
+
Inject raw CSS without parsing. Hook-free — works in client components, SSR, and React Server Components.
|
|
125
|
+
|
|
126
|
+
Supports two overloads:
|
|
127
|
+
- **Static CSS**: `useRawCSS(cssString, options?)` — content-based deduplication
|
|
128
|
+
- **Factory function**: `useRawCSS(() => cssString, deps, options?)` — factory called on every invocation, dedup handled internally
|
|
129
|
+
|
|
130
|
+
Use the `id` option for update tracking — when the CSS changes for the same id, the previous injection is replaced:
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
import { useRawCSS } from '@tenphi/tasty';
|
|
134
|
+
|
|
135
|
+
// Static CSS
|
|
136
|
+
function GlobalReset() {
|
|
137
|
+
useRawCSS(`
|
|
138
|
+
body { margin: 0; padding: 0; }
|
|
139
|
+
`);
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Dynamic CSS with factory function and update tracking
|
|
144
|
+
function ThemeStyles({ theme }: { theme: 'dark' | 'light' }) {
|
|
145
|
+
useRawCSS(() => `
|
|
146
|
+
body {
|
|
147
|
+
margin: 0;
|
|
148
|
+
background: ${theme === 'dark' ? '#000' : '#fff'};
|
|
149
|
+
color: ${theme === 'dark' ? '#fff' : '#000'};
|
|
150
|
+
}
|
|
151
|
+
`, [theme], { id: 'theme-body' });
|
|
152
|
+
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### `createInjector(config?): StyleInjector`
|
|
158
|
+
|
|
159
|
+
Creates an isolated injector instance with custom configuration.
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import { createInjector } from '@tenphi/tasty';
|
|
163
|
+
|
|
164
|
+
// Create isolated instance for testing
|
|
165
|
+
const testInjector = createInjector({
|
|
166
|
+
devMode: true,
|
|
167
|
+
forceTextInjection: true,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const result = testInjector.inject(rules);
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### `keyframes(steps, nameOrOptions?): KeyframesResult`
|
|
174
|
+
|
|
175
|
+
Injects CSS keyframes with automatic deduplication.
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
// Generated name (k0, k1, k2...)
|
|
179
|
+
const fadeIn = keyframes({
|
|
180
|
+
from: { opacity: 0 },
|
|
181
|
+
to: { opacity: 1 },
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Custom name
|
|
185
|
+
const slideIn = keyframes({
|
|
186
|
+
'0%': { transform: 'translateX(-100%)' },
|
|
187
|
+
'100%': { transform: 'translateX(0)' },
|
|
188
|
+
}, 'slideInAnimation');
|
|
189
|
+
|
|
190
|
+
// Use in tasty styles (recommended)
|
|
191
|
+
const AnimatedBox = tasty({
|
|
192
|
+
styles: {
|
|
193
|
+
animation: `${fadeIn} 300ms ease-in`,
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Or use with injectGlobal for fixed selectors
|
|
198
|
+
injectGlobal([{
|
|
199
|
+
selector: '.my-animated-class',
|
|
200
|
+
declarations: `animation: ${slideIn} 500ms ease-out;`
|
|
201
|
+
}]);
|
|
202
|
+
|
|
203
|
+
// Cleanup keyframes (if needed)
|
|
204
|
+
fadeIn.dispose(); // Immediate keyframes deletion from DOM
|
|
205
|
+
slideIn.dispose(); // Immediate keyframes deletion from DOM
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### `configure(config): void`
|
|
209
|
+
|
|
210
|
+
Configures the Tasty style system. `configure()` is optional, but if you use it, it must be called **before** any styles are generated (before first render).
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import { configure } from '@tenphi/tasty';
|
|
214
|
+
|
|
215
|
+
configure({
|
|
216
|
+
devMode: true, // Enable development features (auto-detected)
|
|
217
|
+
maxRulesPerSheet: 8192, // Cap rules per stylesheet (default: 8192)
|
|
218
|
+
forceTextInjection: false, // Force textContent insertion (auto-detected for tests)
|
|
219
|
+
nonce: 'csp-nonce', // CSP nonce for security
|
|
220
|
+
gc: { // Garbage collection for unused styles
|
|
221
|
+
touchInterval: 1000, // Touch events between GC cycles (default: 1000)
|
|
222
|
+
capacity: 1000, // Max unused styles to retain (default: 1000)
|
|
223
|
+
},
|
|
224
|
+
states: { // Global predefined states for advanced state mapping
|
|
225
|
+
'@mobile': '@media(w < 768px)',
|
|
226
|
+
'@dark': '@root(schema=dark)',
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Auto-Detection Features:**
|
|
232
|
+
- `devMode`: Automatically enabled in development environments (detected via `isDevEnv()`)
|
|
233
|
+
- `forceTextInjection`: Automatically enabled in test environments (Jest, Vitest, Mocha, happy-dom, jsdom)
|
|
234
|
+
|
|
235
|
+
**Injection Modes:**
|
|
236
|
+
|
|
237
|
+
Each sheet picks its write mode once, when it is created, and keeps it for its
|
|
238
|
+
lifetime:
|
|
239
|
+
|
|
240
|
+
| Mode | How rules are written | How rules are removed |
|
|
241
|
+
|---|---|---|
|
|
242
|
+
| CSSOM (default) | `styleSheet.insertRule(rule, index)` | `styleSheet.deleteRule(index)` |
|
|
243
|
+
| Text (`forceTextInjection`, or when `styleElement.sheet` is unavailable) | appended to `<style>.textContent` | rule texts are tracked per sheet and the element's text is rebuilt without them |
|
|
244
|
+
| Adopted (ShadowRoot with constructable sheets) | `insertRule` on the constructable sheet | `deleteRule` on the constructable sheet |
|
|
245
|
+
|
|
246
|
+
Text mode cannot edit a single rule in place the way CSSOM can, so the sheet
|
|
247
|
+
keeps the inserted rule texts in index order and rewrites the element on delete.
|
|
248
|
+
Dispose, ref-counted cleanup and GC therefore behave identically in every mode.
|
|
249
|
+
|
|
250
|
+
**Configuration Notes:**
|
|
251
|
+
- Most options have sensible defaults and auto-detection
|
|
252
|
+
- `configure()` is optional - the injector works with defaults
|
|
253
|
+
- **Configuration is locked after styles are generated** - calling `configure()` after first render will emit a warning and be ignored
|
|
254
|
+
- `gc.touchInterval`: Number of touch events between GC cycles. Each style render counts as a touch. When the counter reaches this value, GC is scheduled via `requestIdleCallback`.
|
|
255
|
+
- `gc.capacity`: Maximum number of unused styles (not in the DOM, not pinned) to retain. When exceeded, the least recently used are evicted first. Rendered and pinned styles don't count against this limit.
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
### At-rule injection
|
|
260
|
+
|
|
261
|
+
Beyond ordinary rules, the injector has a dedicated entry point per at-rule. All of them deduplicate by name and are **permanent** — there is no ref-counting or dispose, because a rule that other CSS references by name cannot be safely removed while any of it might still apply.
|
|
262
|
+
|
|
263
|
+
| Function | Injects | Notes |
|
|
264
|
+
|---|---|---|
|
|
265
|
+
| `keyframes(steps, nameOrOptions?)` | `@keyframes` | The exception: ref-counted and disposed immediately when unused. Returns the injected name, which may differ from the requested one. |
|
|
266
|
+
| `property(name, options?)` | `@property` | `name` accepts `$name`, `#name`, or `--name`. Usually unnecessary — types are inferred automatically unless `configure({ autoPropertyTypes: false })`. |
|
|
267
|
+
| `fontFace(family, descriptors, options?)` | `@font-face` | Deduplicated by family **and** descriptor content, so several faces of one family coexist. |
|
|
268
|
+
| `counterStyle(name, descriptors, options?)` | `@counter-style` | |
|
|
269
|
+
| `func(name, definition, options?)` | `@function` | `name` accepts `$$name`, `$name`, or `--name`. Abbreviated because `function` is a reserved word. |
|
|
270
|
+
|
|
271
|
+
Every one of these takes an optional `root` (a `Document` or `ShadowRoot`).
|
|
272
|
+
|
|
273
|
+
`func()` also accepts `weak: true`, which registers the rule **without** overriding an existing one of the same name. `configure()` uses it for global `@function` definitions so a component-local definition always wins over the global default, regardless of which is injected first.
|
|
274
|
+
|
|
275
|
+
```ts
|
|
276
|
+
import { counterStyle, fontFace, func, property } from '@tenphi/tasty';
|
|
277
|
+
|
|
278
|
+
property('$card-elevation', { syntax: '<number>', initialValue: '0' });
|
|
279
|
+
fontFace('Inter', { src: 'url(/inter.woff2)', fontWeight: '400 700' });
|
|
280
|
+
counterStyle('dashes', { system: 'cyclic', symbols: '"—"', suffix: ' ' });
|
|
281
|
+
func('$$negative', { args: ['$value'], result: '(-1 * $value)' });
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Advanced Features
|
|
287
|
+
|
|
288
|
+
### Style Result Format
|
|
289
|
+
|
|
290
|
+
The injector works with `StyleResult` objects from the tasty parser:
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
interface StyleResult {
|
|
294
|
+
selector: string; // CSS selector
|
|
295
|
+
declarations: string; // CSS declarations
|
|
296
|
+
atRules?: string[]; // @media, @supports, etc.
|
|
297
|
+
nestingLevel?: number; // Nesting depth for specificity
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Example StyleResult
|
|
301
|
+
const styleRule: StyleResult = {
|
|
302
|
+
selector: '.t-button',
|
|
303
|
+
declarations: 'padding: 8px 16px; background: blue; color: white;',
|
|
304
|
+
atRules: ['@media (min-width: 768px)'],
|
|
305
|
+
nestingLevel: 0,
|
|
306
|
+
};
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Batched Injection
|
|
310
|
+
|
|
311
|
+
`configure({ batchInjection: true })` queues every sheet write — component
|
|
312
|
+
rules, global rules, raw CSS and at-rules — into one FIFO and applies them
|
|
313
|
+
together, so the document is style-invalidated once per flush instead of once per
|
|
314
|
+
component. `<TastyBatchProvider>` flushes in `useInsertionEffect`, before any
|
|
315
|
+
layout effect, so a queued write can never be observed by a measurement. See
|
|
316
|
+
[Batched injection](configuration.md#batched-injection) for the modes and the
|
|
317
|
+
ordering guarantee. `flushStyles()` applies pending writes on demand; every read
|
|
318
|
+
API here calls it for you.
|
|
319
|
+
|
|
320
|
+
### Deduplication & Performance
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
// Identical CSS rules get the same className
|
|
324
|
+
const button1 = inject([{
|
|
325
|
+
selector: '.t-btn1',
|
|
326
|
+
declarations: 'padding: 8px; color: red;'
|
|
327
|
+
}]);
|
|
328
|
+
|
|
329
|
+
const button2 = inject([{
|
|
330
|
+
selector: '.t-btn2',
|
|
331
|
+
declarations: 'padding: 8px; color: red;' // Same declarations
|
|
332
|
+
}]);
|
|
333
|
+
|
|
334
|
+
// Both get the same className due to deduplication
|
|
335
|
+
console.log(button1.className === button2.className); // true
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Pinning
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
// Multiple callers using the same styles
|
|
342
|
+
const comp1 = inject([commonStyle]);
|
|
343
|
+
const comp2 = inject([commonStyle]);
|
|
344
|
+
const comp3 = inject([commonStyle]);
|
|
345
|
+
|
|
346
|
+
// Style is pinned while any caller holds a handle
|
|
347
|
+
comp1.dispose(); // pins: 3 → 2
|
|
348
|
+
comp2.dispose(); // pins: 2 → 1
|
|
349
|
+
comp3.dispose(); // pins: 1 → 0, now up to the DOM and gc()
|
|
350
|
+
|
|
351
|
+
// Unpinned does not mean deleted: the rule stays cached and is reused instantly
|
|
352
|
+
// by the next inject(). gc() decides when it actually goes.
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Pinning covers callers that hold a handle. Styles that come from rendering are
|
|
356
|
+
never pinned — see `{ pin: false }` under
|
|
357
|
+
[`inject()`](#injectrules-options-injectresult) — so what keeps them alive is
|
|
358
|
+
being in the DOM, and nothing else.
|
|
359
|
+
|
|
360
|
+
### Garbage Collection
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
import { configure, gc } from '@tenphi/tasty';
|
|
364
|
+
|
|
365
|
+
// Keyframes: Disposed immediately when refCount = 0 (safer for global scope)
|
|
366
|
+
// CSS rules: Tracked by touch count and cleaned up via gc()
|
|
367
|
+
//
|
|
368
|
+
// A CSS rule is collectible when no element carries its class AND nobody
|
|
369
|
+
// pinned it with inject().
|
|
370
|
+
|
|
371
|
+
configure({
|
|
372
|
+
gc: {
|
|
373
|
+
touchInterval: 1000, // Schedule GC every 1000 touches
|
|
374
|
+
capacity: 1000, // Max unused styles to retain
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// Manual GC (synchronous, returns number of swept styles):
|
|
379
|
+
gc();
|
|
380
|
+
|
|
381
|
+
// Force-remove ALL unused styles (e.g. on route change or test teardown):
|
|
382
|
+
gc({ force: true });
|
|
383
|
+
|
|
384
|
+
// cleanup() is the same thing:
|
|
385
|
+
cleanup();
|
|
386
|
+
|
|
387
|
+
// GC is also triggered automatically by touch count during rendering.
|
|
388
|
+
// Every `touchInterval` touches, GC is scheduled via requestIdleCallback.
|
|
389
|
+
|
|
390
|
+
// Benefits:
|
|
391
|
+
// - Activity-proportional: busy apps trigger GC more often
|
|
392
|
+
// - DOM-safe: styles currently in the DOM are never evicted
|
|
393
|
+
// - Oldest-first: least recently used styles are evicted first
|
|
394
|
+
// - Keyframes: Immediate cleanup prevents global namespace pollution
|
|
395
|
+
// - Unused styles stay cached until evicted, so re-rendering them is a cache hit
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Shadow DOM Support
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
// Works with Shadow DOM
|
|
402
|
+
const shadowRoot = document.createElement('div').attachShadow({ mode: 'open' });
|
|
403
|
+
|
|
404
|
+
const shadowStyles = inject([{
|
|
405
|
+
selector: '.shadow-component',
|
|
406
|
+
declarations: 'color: purple;'
|
|
407
|
+
}], { root: shadowRoot });
|
|
408
|
+
|
|
409
|
+
// Keyframes in Shadow DOM
|
|
410
|
+
const shadowAnimation = keyframes({
|
|
411
|
+
from: { opacity: 0 },
|
|
412
|
+
to: { opacity: 1 }
|
|
413
|
+
}, { root: shadowRoot, name: 'shadowFade' });
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## SSR & Testing
|
|
419
|
+
|
|
420
|
+
### Server-Side Rendering
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
import { getCSSText, getCSSTextForNode } from '@tenphi/tasty';
|
|
424
|
+
|
|
425
|
+
// Extract all CSS for SSR
|
|
426
|
+
const cssText = getCSSText();
|
|
427
|
+
|
|
428
|
+
// Extract CSS for specific DOM subtree (like jest-styled-components)
|
|
429
|
+
const container = render(<MyComponent />);
|
|
430
|
+
const componentCSS = getCSSTextForNode(container);
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Test Environment Detection
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
// Automatically detected test environments:
|
|
437
|
+
// - NODE_ENV === 'test'
|
|
438
|
+
// - Jest globals (jest, describe, it, expect)
|
|
439
|
+
// - jsdom / HappyDOM user agent
|
|
440
|
+
// - Vitest globals (vitest)
|
|
441
|
+
// - Mocha globals (mocha)
|
|
442
|
+
|
|
443
|
+
import { configure, isTestEnvironment, resetConfig } from '@tenphi/tasty';
|
|
444
|
+
|
|
445
|
+
const isTest = isTestEnvironment();
|
|
446
|
+
|
|
447
|
+
// Reset config between tests to allow reconfiguration
|
|
448
|
+
beforeEach(() => {
|
|
449
|
+
resetConfig();
|
|
450
|
+
configure({
|
|
451
|
+
forceTextInjection: isTest, // More reliable in test environments
|
|
452
|
+
devMode: true, // Always enable dev features in tests
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### Memory Management in Tests
|
|
458
|
+
|
|
459
|
+
```typescript
|
|
460
|
+
// Clean up between tests
|
|
461
|
+
afterEach(() => {
|
|
462
|
+
cleanup(); // Force cleanup of unused styles
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
// Full cleanup after test suite
|
|
466
|
+
afterAll(() => {
|
|
467
|
+
destroy(); // Destroy all stylesheets and reset state
|
|
468
|
+
});
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
## Development Features
|
|
474
|
+
|
|
475
|
+
### Performance Metrics
|
|
476
|
+
|
|
477
|
+
When `devMode` is enabled, the injector tracks comprehensive metrics:
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
import { configure, injector } from '@tenphi/tasty';
|
|
481
|
+
|
|
482
|
+
configure({ devMode: true });
|
|
483
|
+
|
|
484
|
+
// Access metrics through the global injector
|
|
485
|
+
const metrics = injector.instance.getMetrics();
|
|
486
|
+
|
|
487
|
+
console.log({
|
|
488
|
+
cacheHits: metrics.hits, // Successful cache hits
|
|
489
|
+
cacheMisses: metrics.misses, // New styles injected
|
|
490
|
+
unusedHits: metrics.unusedHits, // Styles currently eligible for eviction (scans the DOM)
|
|
491
|
+
bulkCleanups: metrics.bulkCleanups, // Number of bulk cleanup operations
|
|
492
|
+
stylesCleanedUp: metrics.stylesCleanedUp, // Total styles removed in bulk cleanups
|
|
493
|
+
totalInsertions: metrics.totalInsertions, // Lifetime insertions
|
|
494
|
+
totalUnused: metrics.totalUnused, // Times a pinned style lost its last pin
|
|
495
|
+
startTime: metrics.startTime, // Metrics collection start timestamp
|
|
496
|
+
cleanupHistory: metrics.cleanupHistory, // Detailed cleanup operation history
|
|
497
|
+
});
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Debug Information
|
|
501
|
+
|
|
502
|
+
```typescript
|
|
503
|
+
// Get detailed information about injected styles
|
|
504
|
+
const debugInfo = injector.instance.getDebugInfo();
|
|
505
|
+
|
|
506
|
+
console.log({
|
|
507
|
+
activeStyles: debugInfo.activeStyles, // Currently active styles
|
|
508
|
+
unusedStyles: debugInfo.unusedStyles, // Styles marked for cleanup
|
|
509
|
+
totalSheets: debugInfo.totalSheets, // Number of stylesheets
|
|
510
|
+
totalRules: debugInfo.totalRules, // Total CSS rules
|
|
511
|
+
});
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### Cleanup History
|
|
515
|
+
|
|
516
|
+
```typescript
|
|
517
|
+
// Track cleanup operations over time
|
|
518
|
+
const metrics = injector.instance.getMetrics();
|
|
519
|
+
|
|
520
|
+
metrics.cleanupHistory.forEach(cleanup => {
|
|
521
|
+
console.log({
|
|
522
|
+
timestamp: new Date(cleanup.timestamp),
|
|
523
|
+
classesDeleted: cleanup.classesDeleted,
|
|
524
|
+
rulesDeleted: cleanup.rulesDeleted,
|
|
525
|
+
cssSize: cleanup.cssSize, // Total CSS size removed (bytes)
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
532
|
+
## Performance Optimizations
|
|
533
|
+
|
|
534
|
+
### Best Practices
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
// ✅ Reuse styles - identical CSS gets deduplicated
|
|
538
|
+
const buttonBase = 'padding: 8px 16px; border-radius: 4px;';
|
|
539
|
+
|
|
540
|
+
// ✅ Avoid frequent disposal and re-injection
|
|
541
|
+
// Let the injector handle cleanup
|
|
542
|
+
|
|
543
|
+
// ✅ Use bulk operations for global styles
|
|
544
|
+
injectGlobal([
|
|
545
|
+
{ selector: 'body', declarations: 'margin: 0;' },
|
|
546
|
+
{ selector: '*', declarations: 'box-sizing: border-box;' },
|
|
547
|
+
{ selector: '.container', declarations: 'max-width: 1200px;' }
|
|
548
|
+
]);
|
|
549
|
+
|
|
550
|
+
// ✅ Configure GC for your app (BEFORE first render!)
|
|
551
|
+
import { configure } from '@tenphi/tasty';
|
|
552
|
+
|
|
553
|
+
configure({
|
|
554
|
+
gc: {
|
|
555
|
+
touchInterval: 1000, // Schedule GC every 1000 style touches
|
|
556
|
+
capacity: 1000, // Max unused styles to retain
|
|
557
|
+
},
|
|
558
|
+
});
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### Memory Management
|
|
562
|
+
|
|
563
|
+
```typescript
|
|
564
|
+
// The injector automatically manages memory through:
|
|
565
|
+
|
|
566
|
+
// 1. Hash-based deduplication - same CSS = same className
|
|
567
|
+
// 2. DOM-driven lifetime - a rendered class is never evicted
|
|
568
|
+
// 3. Pinning - inject() callers hold their classes until they dispose
|
|
569
|
+
// 4. Immediate keyframes cleanup - disposed instantly when refCount = 0
|
|
570
|
+
// 5. Touch-count GC - unused CSS rules are evicted oldest-first when over capacity
|
|
571
|
+
|
|
572
|
+
// Manual cleanup is rarely needed but available:
|
|
573
|
+
cleanup(); // Remove every rule that is neither rendered nor pinned
|
|
574
|
+
destroy(); // Nuclear option: remove all stylesheets and reset
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
---
|
|
578
|
+
|
|
579
|
+
## Integration with Tasty
|
|
580
|
+
|
|
581
|
+
The Style Injector is seamlessly integrated with the higher-level Tasty API:
|
|
582
|
+
|
|
583
|
+
```jsx
|
|
584
|
+
// High-level tasty() API
|
|
585
|
+
const StyledButton = tasty({
|
|
586
|
+
styles: {
|
|
587
|
+
padding: '2x 4x',
|
|
588
|
+
fill: '#purple',
|
|
589
|
+
color: '#white',
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
// Internally uses the injector:
|
|
594
|
+
// 1. Styles are parsed into StyleResult objects
|
|
595
|
+
// 2. inject() is called with the parsed results, unpinned
|
|
596
|
+
// 3. Component gets the returned className
|
|
597
|
+
// 4. gc() reclaims the class once no element carries it
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
For most development, you'll use the [React API](./react-api.md) rather than the injector directly. The injector provides the high-performance foundation that makes Tasty's declarative styling possible.
|
|
601
|
+
|
|
602
|
+
---
|
|
603
|
+
|
|
604
|
+
## When to Use Direct Injection
|
|
605
|
+
|
|
606
|
+
Direct injector usage is recommended for:
|
|
607
|
+
|
|
608
|
+
- **Custom CSS-in-JS libraries** built on top of Tasty
|
|
609
|
+
- **Global styles** that don't fit the component model
|
|
610
|
+
- **Third-party integration** where you need low-level CSS control
|
|
611
|
+
- **Performance-critical scenarios** where you need direct control
|
|
612
|
+
- **Testing utilities** that need to inject or extract CSS
|
|
613
|
+
|
|
614
|
+
For regular component styling, prefer the [`tasty()` API](./react-api.md) which provides a more developer-friendly interface.
|