@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.
Files changed (309) hide show
  1. package/README.md +397 -185
  2. package/dist/async-storage-DKK-wTD4.js +44 -0
  3. package/dist/async-storage-DKK-wTD4.js.map +1 -0
  4. package/dist/babel-CMYf4JZ2.d.ts +83 -0
  5. package/dist/collector-Ctn5s74s.d.ts +145 -0
  6. package/dist/collector-DMSR6ANK.js +304 -0
  7. package/dist/collector-DMSR6ANK.js.map +1 -0
  8. package/dist/config-B23gZrfP.js +12317 -0
  9. package/dist/config-B23gZrfP.js.map +1 -0
  10. package/dist/config-CZx8_DCu.d.ts +1296 -0
  11. package/dist/context-CA8YKeMn.js +24 -0
  12. package/dist/context-CA8YKeMn.js.map +1 -0
  13. package/dist/core/index.d.ts +5 -33
  14. package/dist/core/index.js +6 -26
  15. package/dist/core-B1tyn69d.js +1573 -0
  16. package/dist/core-B1tyn69d.js.map +1 -0
  17. package/dist/css-writer-CvAxkR6S.js +390 -0
  18. package/dist/css-writer-CvAxkR6S.js.map +1 -0
  19. package/dist/format-global-rules-DklyaXv-.js +22 -0
  20. package/dist/format-global-rules-DklyaXv-.js.map +1 -0
  21. package/dist/format-rules-BCTWVvcS.js +130 -0
  22. package/dist/format-rules-BCTWVvcS.js.map +1 -0
  23. package/dist/hydrate-BCPvkJj3.js +37 -0
  24. package/dist/hydrate-BCPvkJj3.js.map +1 -0
  25. package/dist/index-B3o0aCYV.d.ts +1538 -0
  26. package/dist/index-CsvB0EXZ.d.ts +1909 -0
  27. package/dist/index.d.ts +5 -40
  28. package/dist/index.js +921 -32
  29. package/dist/index.js.map +1 -0
  30. package/dist/keyframes-Dxdx3KOo.js +493 -0
  31. package/dist/keyframes-Dxdx3KOo.js.map +1 -0
  32. package/dist/{utils/merge-styles.js → merge-styles-C-uwpoNW.js} +4 -6
  33. package/dist/merge-styles-C-uwpoNW.js.map +1 -0
  34. package/dist/{utils/merge-styles.d.ts → merge-styles-DJTSfz_M.d.ts} +3 -3
  35. package/dist/{utils/resolve-recipes.js → resolve-recipes-BuOvSEa2.js} +5 -8
  36. package/dist/resolve-recipes-BuOvSEa2.js.map +1 -0
  37. package/dist/ssr/astro-client.d.ts +1 -0
  38. package/dist/ssr/astro-client.js +19 -0
  39. package/dist/ssr/astro-client.js.map +1 -0
  40. package/dist/ssr/astro-middleware-static.d.ts +16 -0
  41. package/dist/ssr/astro-middleware-static.js +18 -0
  42. package/dist/ssr/astro-middleware-static.js.map +1 -0
  43. package/dist/ssr/astro-middleware.d.ts +17 -0
  44. package/dist/ssr/astro-middleware.js +19 -0
  45. package/dist/ssr/astro-middleware.js.map +1 -0
  46. package/dist/ssr/astro.d.ts +93 -0
  47. package/dist/ssr/astro.js +157 -0
  48. package/dist/ssr/astro.js.map +1 -0
  49. package/dist/ssr/index.d.ts +37 -0
  50. package/dist/ssr/index.js +10 -0
  51. package/dist/ssr/index.js.map +1 -0
  52. package/dist/ssr/next.d.ts +45 -0
  53. package/dist/ssr/next.js +74 -0
  54. package/dist/ssr/next.js.map +1 -0
  55. package/dist/static/index.d.ts +91 -5
  56. package/dist/static/index.js +49 -4
  57. package/dist/static/index.js.map +1 -0
  58. package/dist/static/inject.d.ts +5 -0
  59. package/dist/static/inject.js +17 -0
  60. package/dist/static/inject.js.map +1 -0
  61. package/dist/zero/babel.d.ts +2 -108
  62. package/dist/zero/babel.js +235 -39
  63. package/dist/zero/babel.js.map +1 -1
  64. package/dist/zero/index.d.ts +81 -3
  65. package/dist/zero/index.js +2 -4
  66. package/dist/zero/next.d.ts +56 -30
  67. package/dist/zero/next.js +106 -41
  68. package/dist/zero/next.js.map +1 -1
  69. package/docs/README.md +39 -0
  70. package/docs/adoption.md +323 -0
  71. package/docs/ai-agents.md +224 -0
  72. package/docs/comparison.md +448 -0
  73. package/docs/configuration.md +823 -0
  74. package/docs/debug.md +322 -0
  75. package/docs/design-system.md +455 -0
  76. package/docs/dsl.md +1021 -0
  77. package/docs/getting-started.md +238 -0
  78. package/docs/injector.md +614 -0
  79. package/docs/methodology.md +648 -0
  80. package/docs/migration-v3.md +285 -0
  81. package/docs/pipeline.md +741 -0
  82. package/docs/plugins.md +353 -0
  83. package/docs/react-api.md +681 -0
  84. package/docs/runtime-benchmarks.md +216 -0
  85. package/docs/ssr.md +451 -0
  86. package/docs/styles.md +645 -0
  87. package/docs/tasty-static.md +582 -0
  88. package/package.json +149 -40
  89. package/tasty.config.ts +6 -0
  90. package/dist/_virtual/_rolldown/runtime.js +0 -8
  91. package/dist/chunks/cacheKey.js +0 -70
  92. package/dist/chunks/cacheKey.js.map +0 -1
  93. package/dist/chunks/definitions.d.ts +0 -37
  94. package/dist/chunks/definitions.js +0 -260
  95. package/dist/chunks/definitions.js.map +0 -1
  96. package/dist/chunks/renderChunk.js +0 -61
  97. package/dist/chunks/renderChunk.js.map +0 -1
  98. package/dist/config.d.ts +0 -280
  99. package/dist/config.js +0 -403
  100. package/dist/config.js.map +0 -1
  101. package/dist/debug.d.ts +0 -204
  102. package/dist/debug.js +0 -733
  103. package/dist/debug.js.map +0 -1
  104. package/dist/hooks/useGlobalStyles.d.ts +0 -27
  105. package/dist/hooks/useGlobalStyles.js +0 -56
  106. package/dist/hooks/useGlobalStyles.js.map +0 -1
  107. package/dist/hooks/useKeyframes.d.ts +0 -56
  108. package/dist/hooks/useKeyframes.js +0 -54
  109. package/dist/hooks/useKeyframes.js.map +0 -1
  110. package/dist/hooks/useProperty.d.ts +0 -79
  111. package/dist/hooks/useProperty.js +0 -91
  112. package/dist/hooks/useProperty.js.map +0 -1
  113. package/dist/hooks/useRawCSS.d.ts +0 -53
  114. package/dist/hooks/useRawCSS.js +0 -28
  115. package/dist/hooks/useRawCSS.js.map +0 -1
  116. package/dist/hooks/useStyles.d.ts +0 -40
  117. package/dist/hooks/useStyles.js +0 -169
  118. package/dist/hooks/useStyles.js.map +0 -1
  119. package/dist/injector/index.d.ts +0 -157
  120. package/dist/injector/index.js +0 -154
  121. package/dist/injector/index.js.map +0 -1
  122. package/dist/injector/injector.d.ts +0 -139
  123. package/dist/injector/injector.js +0 -404
  124. package/dist/injector/injector.js.map +0 -1
  125. package/dist/injector/sheet-manager.d.ts +0 -127
  126. package/dist/injector/sheet-manager.js +0 -714
  127. package/dist/injector/sheet-manager.js.map +0 -1
  128. package/dist/injector/types.d.ts +0 -135
  129. package/dist/keyframes/index.js +0 -206
  130. package/dist/keyframes/index.js.map +0 -1
  131. package/dist/parser/classify.js +0 -319
  132. package/dist/parser/classify.js.map +0 -1
  133. package/dist/parser/const.js +0 -33
  134. package/dist/parser/const.js.map +0 -1
  135. package/dist/parser/lru.js +0 -109
  136. package/dist/parser/lru.js.map +0 -1
  137. package/dist/parser/parser.d.ts +0 -25
  138. package/dist/parser/parser.js +0 -116
  139. package/dist/parser/parser.js.map +0 -1
  140. package/dist/parser/tokenizer.js +0 -69
  141. package/dist/parser/tokenizer.js.map +0 -1
  142. package/dist/parser/types.d.ts +0 -51
  143. package/dist/parser/types.js +0 -46
  144. package/dist/parser/types.js.map +0 -1
  145. package/dist/pipeline/conditions.d.ts +0 -134
  146. package/dist/pipeline/conditions.js +0 -400
  147. package/dist/pipeline/conditions.js.map +0 -1
  148. package/dist/pipeline/exclusive.js +0 -231
  149. package/dist/pipeline/exclusive.js.map +0 -1
  150. package/dist/pipeline/index.d.ts +0 -53
  151. package/dist/pipeline/index.js +0 -641
  152. package/dist/pipeline/index.js.map +0 -1
  153. package/dist/pipeline/materialize.js +0 -908
  154. package/dist/pipeline/materialize.js.map +0 -1
  155. package/dist/pipeline/parseStateKey.d.ts +0 -15
  156. package/dist/pipeline/parseStateKey.js +0 -430
  157. package/dist/pipeline/parseStateKey.js.map +0 -1
  158. package/dist/pipeline/simplify.js +0 -557
  159. package/dist/pipeline/simplify.js.map +0 -1
  160. package/dist/plugins/okhsl-plugin.d.ts +0 -35
  161. package/dist/plugins/okhsl-plugin.js +0 -371
  162. package/dist/plugins/okhsl-plugin.js.map +0 -1
  163. package/dist/plugins/types.d.ts +0 -69
  164. package/dist/properties/index.js +0 -158
  165. package/dist/properties/index.js.map +0 -1
  166. package/dist/states/index.d.ts +0 -49
  167. package/dist/states/index.js +0 -416
  168. package/dist/states/index.js.map +0 -1
  169. package/dist/static/tastyStatic.d.ts +0 -46
  170. package/dist/static/tastyStatic.js +0 -31
  171. package/dist/static/tastyStatic.js.map +0 -1
  172. package/dist/static/types.d.ts +0 -49
  173. package/dist/static/types.js +0 -24
  174. package/dist/static/types.js.map +0 -1
  175. package/dist/styles/align.d.ts +0 -15
  176. package/dist/styles/align.js +0 -14
  177. package/dist/styles/align.js.map +0 -1
  178. package/dist/styles/border.d.ts +0 -25
  179. package/dist/styles/border.js +0 -114
  180. package/dist/styles/border.js.map +0 -1
  181. package/dist/styles/color.d.ts +0 -14
  182. package/dist/styles/color.js +0 -23
  183. package/dist/styles/color.js.map +0 -1
  184. package/dist/styles/createStyle.js +0 -77
  185. package/dist/styles/createStyle.js.map +0 -1
  186. package/dist/styles/dimension.js +0 -97
  187. package/dist/styles/dimension.js.map +0 -1
  188. package/dist/styles/display.d.ts +0 -37
  189. package/dist/styles/display.js +0 -67
  190. package/dist/styles/display.js.map +0 -1
  191. package/dist/styles/fade.d.ts +0 -15
  192. package/dist/styles/fade.js +0 -58
  193. package/dist/styles/fade.js.map +0 -1
  194. package/dist/styles/fill.d.ts +0 -44
  195. package/dist/styles/fill.js +0 -51
  196. package/dist/styles/fill.js.map +0 -1
  197. package/dist/styles/flow.d.ts +0 -16
  198. package/dist/styles/flow.js +0 -12
  199. package/dist/styles/flow.js.map +0 -1
  200. package/dist/styles/gap.d.ts +0 -31
  201. package/dist/styles/gap.js +0 -37
  202. package/dist/styles/gap.js.map +0 -1
  203. package/dist/styles/height.d.ts +0 -17
  204. package/dist/styles/height.js +0 -20
  205. package/dist/styles/height.js.map +0 -1
  206. package/dist/styles/index.d.ts +0 -2
  207. package/dist/styles/index.js +0 -9
  208. package/dist/styles/index.js.map +0 -1
  209. package/dist/styles/inset.d.ts +0 -52
  210. package/dist/styles/inset.js +0 -150
  211. package/dist/styles/inset.js.map +0 -1
  212. package/dist/styles/justify.d.ts +0 -15
  213. package/dist/styles/justify.js +0 -14
  214. package/dist/styles/justify.js.map +0 -1
  215. package/dist/styles/list.d.ts +0 -16
  216. package/dist/styles/list.js +0 -98
  217. package/dist/styles/list.js.map +0 -1
  218. package/dist/styles/margin.d.ts +0 -24
  219. package/dist/styles/margin.js +0 -104
  220. package/dist/styles/margin.js.map +0 -1
  221. package/dist/styles/outline.d.ts +0 -29
  222. package/dist/styles/outline.js +0 -65
  223. package/dist/styles/outline.js.map +0 -1
  224. package/dist/styles/padding.d.ts +0 -24
  225. package/dist/styles/padding.js +0 -104
  226. package/dist/styles/padding.js.map +0 -1
  227. package/dist/styles/predefined.d.ts +0 -73
  228. package/dist/styles/predefined.js +0 -241
  229. package/dist/styles/predefined.js.map +0 -1
  230. package/dist/styles/preset.d.ts +0 -47
  231. package/dist/styles/preset.js +0 -126
  232. package/dist/styles/preset.js.map +0 -1
  233. package/dist/styles/radius.d.ts +0 -14
  234. package/dist/styles/radius.js +0 -51
  235. package/dist/styles/radius.js.map +0 -1
  236. package/dist/styles/scrollbar.d.ts +0 -21
  237. package/dist/styles/scrollbar.js +0 -105
  238. package/dist/styles/scrollbar.js.map +0 -1
  239. package/dist/styles/shadow.d.ts +0 -14
  240. package/dist/styles/shadow.js +0 -24
  241. package/dist/styles/shadow.js.map +0 -1
  242. package/dist/styles/styledScrollbar.d.ts +0 -47
  243. package/dist/styles/styledScrollbar.js +0 -38
  244. package/dist/styles/styledScrollbar.js.map +0 -1
  245. package/dist/styles/transition.d.ts +0 -14
  246. package/dist/styles/transition.js +0 -158
  247. package/dist/styles/transition.js.map +0 -1
  248. package/dist/styles/types.d.ts +0 -498
  249. package/dist/styles/width.d.ts +0 -17
  250. package/dist/styles/width.js +0 -20
  251. package/dist/styles/width.js.map +0 -1
  252. package/dist/tasty.d.ts +0 -982
  253. package/dist/tasty.js +0 -191
  254. package/dist/tasty.js.map +0 -1
  255. package/dist/tokens/typography.d.ts +0 -19
  256. package/dist/tokens/typography.js +0 -237
  257. package/dist/tokens/typography.js.map +0 -1
  258. package/dist/types.d.ts +0 -184
  259. package/dist/utils/cache-wrapper.js +0 -26
  260. package/dist/utils/cache-wrapper.js.map +0 -1
  261. package/dist/utils/case-converter.js +0 -8
  262. package/dist/utils/case-converter.js.map +0 -1
  263. package/dist/utils/colors.d.ts +0 -5
  264. package/dist/utils/colors.js +0 -9
  265. package/dist/utils/colors.js.map +0 -1
  266. package/dist/utils/css-types.d.ts +0 -7
  267. package/dist/utils/dotize.d.ts +0 -26
  268. package/dist/utils/dotize.js +0 -122
  269. package/dist/utils/dotize.js.map +0 -1
  270. package/dist/utils/filter-base-props.d.ts +0 -15
  271. package/dist/utils/filter-base-props.js +0 -45
  272. package/dist/utils/filter-base-props.js.map +0 -1
  273. package/dist/utils/get-display-name.d.ts +0 -7
  274. package/dist/utils/get-display-name.js +0 -10
  275. package/dist/utils/get-display-name.js.map +0 -1
  276. package/dist/utils/hsl-to-rgb.js +0 -38
  277. package/dist/utils/hsl-to-rgb.js.map +0 -1
  278. package/dist/utils/is-dev-env.js +0 -19
  279. package/dist/utils/is-dev-env.js.map +0 -1
  280. package/dist/utils/is-valid-element-type.js +0 -15
  281. package/dist/utils/is-valid-element-type.js.map +0 -1
  282. package/dist/utils/merge-styles.js.map +0 -1
  283. package/dist/utils/mod-attrs.d.ts +0 -8
  284. package/dist/utils/mod-attrs.js +0 -21
  285. package/dist/utils/mod-attrs.js.map +0 -1
  286. package/dist/utils/okhsl-to-rgb.js +0 -296
  287. package/dist/utils/okhsl-to-rgb.js.map +0 -1
  288. package/dist/utils/process-tokens.d.ts +0 -31
  289. package/dist/utils/process-tokens.js +0 -171
  290. package/dist/utils/process-tokens.js.map +0 -1
  291. package/dist/utils/resolve-recipes.d.ts +0 -17
  292. package/dist/utils/resolve-recipes.js.map +0 -1
  293. package/dist/utils/string.js +0 -8
  294. package/dist/utils/string.js.map +0 -1
  295. package/dist/utils/styles.d.ts +0 -183
  296. package/dist/utils/styles.js +0 -585
  297. package/dist/utils/styles.js.map +0 -1
  298. package/dist/utils/typography.d.ts +0 -36
  299. package/dist/utils/typography.js +0 -53
  300. package/dist/utils/typography.js.map +0 -1
  301. package/dist/utils/warnings.d.ts +0 -16
  302. package/dist/utils/warnings.js +0 -16
  303. package/dist/utils/warnings.js.map +0 -1
  304. package/dist/zero/css-writer.d.ts +0 -45
  305. package/dist/zero/css-writer.js +0 -74
  306. package/dist/zero/css-writer.js.map +0 -1
  307. package/dist/zero/extractor.d.ts +0 -24
  308. package/dist/zero/extractor.js +0 -150
  309. package/dist/zero/extractor.js.map +0 -1
@@ -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.