@tenphi/tasty 0.0.0-snapshot.c8bdaeb → 0.0.0-snapshot.c8c7b2a

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 (318) hide show
  1. package/README.md +309 -159
  2. package/dist/_virtual/_rolldown/runtime.js +1 -2
  3. package/dist/chunks/cacheKey.d.ts +1 -0
  4. package/dist/chunks/cacheKey.js +17 -10
  5. package/dist/chunks/cacheKey.js.map +1 -1
  6. package/dist/chunks/definitions.d.ts +1 -1
  7. package/dist/chunks/definitions.js +3 -5
  8. package/dist/chunks/definitions.js.map +1 -1
  9. package/dist/chunks/index.d.ts +1 -0
  10. package/dist/chunks/renderChunk.d.ts +1 -0
  11. package/dist/chunks/renderChunk.js +32 -34
  12. package/dist/chunks/renderChunk.js.map +1 -1
  13. package/dist/compute-styles.d.ts +31 -0
  14. package/dist/compute-styles.js +322 -0
  15. package/dist/compute-styles.js.map +1 -0
  16. package/dist/config.d.ts +157 -30
  17. package/dist/config.js +233 -45
  18. package/dist/config.js.map +1 -1
  19. package/dist/core/index.d.ts +12 -11
  20. package/dist/core/index.js +12 -11
  21. package/dist/counter-style/index.js +51 -0
  22. package/dist/counter-style/index.js.map +1 -0
  23. package/dist/debug.d.ts +26 -141
  24. package/dist/debug.js +359 -639
  25. package/dist/debug.js.map +1 -1
  26. package/dist/font-face/index.js +63 -0
  27. package/dist/font-face/index.js.map +1 -0
  28. package/dist/hooks/index.d.ts +7 -0
  29. package/dist/hooks/useCounterStyle.d.ts +36 -0
  30. package/dist/hooks/useCounterStyle.js +65 -0
  31. package/dist/hooks/useCounterStyle.js.map +1 -0
  32. package/dist/hooks/useFontFace.d.ts +45 -0
  33. package/dist/hooks/useFontFace.js +66 -0
  34. package/dist/hooks/useFontFace.js.map +1 -0
  35. package/dist/hooks/useGlobalStyles.d.ts +21 -2
  36. package/dist/hooks/useGlobalStyles.js +60 -28
  37. package/dist/hooks/useGlobalStyles.js.map +1 -1
  38. package/dist/hooks/useKeyframes.d.ts +4 -2
  39. package/dist/hooks/useKeyframes.js +44 -43
  40. package/dist/hooks/useKeyframes.js.map +1 -1
  41. package/dist/hooks/useProperty.d.ts +4 -2
  42. package/dist/hooks/useProperty.js +36 -31
  43. package/dist/hooks/useProperty.js.map +1 -1
  44. package/dist/hooks/useRawCSS.d.ts +13 -44
  45. package/dist/hooks/useRawCSS.js +91 -16
  46. package/dist/hooks/useRawCSS.js.map +1 -1
  47. package/dist/hooks/useStyles.d.ts +3 -3
  48. package/dist/hooks/useStyles.js +8 -146
  49. package/dist/hooks/useStyles.js.map +1 -1
  50. package/dist/index.d.ts +15 -12
  51. package/dist/index.js +15 -12
  52. package/dist/injector/index.d.ts +36 -11
  53. package/dist/injector/index.js +43 -12
  54. package/dist/injector/index.js.map +1 -1
  55. package/dist/injector/injector.d.ts +62 -3
  56. package/dist/injector/injector.js +293 -46
  57. package/dist/injector/injector.js.map +1 -1
  58. package/dist/injector/sheet-manager.d.ts +22 -17
  59. package/dist/injector/sheet-manager.js +64 -79
  60. package/dist/injector/sheet-manager.js.map +1 -1
  61. package/dist/injector/types.d.ts +121 -21
  62. package/dist/keyframes/index.js +1 -1
  63. package/dist/parser/classify.js +6 -6
  64. package/dist/parser/classify.js.map +1 -1
  65. package/dist/parser/const.js +30 -3
  66. package/dist/parser/const.js.map +1 -1
  67. package/dist/parser/lru.js +1 -1
  68. package/dist/parser/lru.js.map +1 -1
  69. package/dist/parser/parser.js +2 -3
  70. package/dist/parser/parser.js.map +1 -1
  71. package/dist/parser/tokenizer.js +1 -1
  72. package/dist/parser/tokenizer.js.map +1 -1
  73. package/dist/parser/types.js +1 -1
  74. package/dist/parser/types.js.map +1 -1
  75. package/dist/pipeline/conditions.js +1 -1
  76. package/dist/pipeline/conditions.js.map +1 -1
  77. package/dist/pipeline/exclusive.js +58 -4
  78. package/dist/pipeline/exclusive.js.map +1 -1
  79. package/dist/pipeline/index.d.ts +3 -1
  80. package/dist/pipeline/index.js +82 -34
  81. package/dist/pipeline/index.js.map +1 -1
  82. package/dist/pipeline/materialize.js +436 -123
  83. package/dist/pipeline/materialize.js.map +1 -1
  84. package/dist/pipeline/parseStateKey.d.ts +1 -1
  85. package/dist/pipeline/parseStateKey.js +21 -13
  86. package/dist/pipeline/parseStateKey.js.map +1 -1
  87. package/dist/pipeline/simplify.js +181 -7
  88. package/dist/pipeline/simplify.js.map +1 -1
  89. package/dist/pipeline/warnings.js +1 -1
  90. package/dist/plugins/index.d.ts +2 -0
  91. package/dist/plugins/okhsl-plugin.js +3 -277
  92. package/dist/plugins/okhsl-plugin.js.map +1 -1
  93. package/dist/plugins/types.d.ts +20 -2
  94. package/dist/properties/index.js +83 -19
  95. package/dist/properties/index.js.map +1 -1
  96. package/dist/properties/property-type-resolver.d.ts +24 -0
  97. package/dist/properties/property-type-resolver.js +90 -0
  98. package/dist/properties/property-type-resolver.js.map +1 -0
  99. package/dist/rsc-cache.js +79 -0
  100. package/dist/rsc-cache.js.map +1 -0
  101. package/dist/ssr/astro-client.d.ts +1 -0
  102. package/dist/ssr/astro-client.js +19 -0
  103. package/dist/ssr/astro-client.js.map +1 -0
  104. package/dist/ssr/astro-middleware.d.ts +15 -0
  105. package/dist/ssr/astro-middleware.js +19 -0
  106. package/dist/ssr/astro-middleware.js.map +1 -0
  107. package/dist/ssr/astro.d.ts +108 -0
  108. package/dist/ssr/astro.js +149 -0
  109. package/dist/ssr/astro.js.map +1 -0
  110. package/dist/ssr/async-storage.d.ts +17 -0
  111. package/dist/ssr/async-storage.js +44 -0
  112. package/dist/ssr/async-storage.js.map +1 -0
  113. package/dist/ssr/collect-auto-properties.js +58 -0
  114. package/dist/ssr/collect-auto-properties.js.map +1 -0
  115. package/dist/ssr/collector.d.ts +98 -0
  116. package/dist/ssr/collector.js +235 -0
  117. package/dist/ssr/collector.js.map +1 -0
  118. package/dist/ssr/context.js +16 -0
  119. package/dist/ssr/context.js.map +1 -0
  120. package/dist/ssr/format-global-rules.js +22 -0
  121. package/dist/ssr/format-global-rules.js.map +1 -0
  122. package/dist/ssr/format-keyframes.js +69 -0
  123. package/dist/ssr/format-keyframes.js.map +1 -0
  124. package/dist/ssr/format-property.js +49 -0
  125. package/dist/ssr/format-property.js.map +1 -0
  126. package/dist/ssr/format-rules.js +73 -0
  127. package/dist/ssr/format-rules.js.map +1 -0
  128. package/dist/ssr/hydrate.d.ts +29 -0
  129. package/dist/ssr/hydrate.js +45 -0
  130. package/dist/ssr/hydrate.js.map +1 -0
  131. package/dist/ssr/index.d.ts +4 -0
  132. package/dist/ssr/index.js +10 -0
  133. package/dist/ssr/index.js.map +1 -0
  134. package/dist/ssr/next.d.ts +46 -0
  135. package/dist/ssr/next.js +75 -0
  136. package/dist/ssr/next.js.map +1 -0
  137. package/dist/ssr/ssr-collector-ref.js +29 -0
  138. package/dist/ssr/ssr-collector-ref.js.map +1 -0
  139. package/dist/states/index.js +13 -259
  140. package/dist/states/index.js.map +1 -1
  141. package/dist/static/index.js +1 -2
  142. package/dist/static/inject.d.ts +5 -0
  143. package/dist/static/inject.js +17 -0
  144. package/dist/static/inject.js.map +1 -0
  145. package/dist/static/tastyStatic.js +1 -2
  146. package/dist/static/tastyStatic.js.map +1 -1
  147. package/dist/static/types.js +1 -1
  148. package/dist/styles/border.d.ts +1 -1
  149. package/dist/styles/border.js +30 -24
  150. package/dist/styles/border.js.map +1 -1
  151. package/dist/styles/color.d.ts +1 -1
  152. package/dist/styles/color.js +11 -8
  153. package/dist/styles/color.js.map +1 -1
  154. package/dist/styles/const.js +17 -0
  155. package/dist/styles/const.js.map +1 -0
  156. package/dist/styles/createStyle.js +28 -26
  157. package/dist/styles/createStyle.js.map +1 -1
  158. package/dist/styles/dimension.js +15 -3
  159. package/dist/styles/dimension.js.map +1 -1
  160. package/dist/styles/directional.js +133 -0
  161. package/dist/styles/directional.js.map +1 -0
  162. package/dist/styles/display.d.ts +3 -10
  163. package/dist/styles/display.js +45 -39
  164. package/dist/styles/display.js.map +1 -1
  165. package/dist/styles/fade.d.ts +1 -1
  166. package/dist/styles/fade.js +9 -5
  167. package/dist/styles/fade.js.map +1 -1
  168. package/dist/styles/fill.d.ts +2 -2
  169. package/dist/styles/fill.js +9 -9
  170. package/dist/styles/fill.js.map +1 -1
  171. package/dist/styles/flow.js +1 -1
  172. package/dist/styles/gap.d.ts +1 -1
  173. package/dist/styles/gap.js +4 -3
  174. package/dist/styles/gap.js.map +1 -1
  175. package/dist/styles/height.d.ts +1 -1
  176. package/dist/styles/height.js +1 -2
  177. package/dist/styles/height.js.map +1 -1
  178. package/dist/styles/index.d.ts +0 -1
  179. package/dist/styles/index.js +3 -4
  180. package/dist/styles/index.js.map +1 -1
  181. package/dist/styles/inset.d.ts +1 -29
  182. package/dist/styles/inset.js +19 -135
  183. package/dist/styles/inset.js.map +1 -1
  184. package/dist/styles/list.d.ts +5 -5
  185. package/dist/styles/list.js +4 -2
  186. package/dist/styles/list.js.map +1 -1
  187. package/dist/styles/margin.d.ts +1 -1
  188. package/dist/styles/margin.js +17 -89
  189. package/dist/styles/margin.js.map +1 -1
  190. package/dist/styles/outline.d.ts +1 -1
  191. package/dist/styles/outline.js +6 -16
  192. package/dist/styles/outline.js.map +1 -1
  193. package/dist/styles/padding.d.ts +1 -1
  194. package/dist/styles/padding.js +17 -89
  195. package/dist/styles/padding.js.map +1 -1
  196. package/dist/styles/placement.d.ts +37 -0
  197. package/dist/styles/placement.js +74 -0
  198. package/dist/styles/placement.js.map +1 -0
  199. package/dist/styles/predefined.d.ts +4 -6
  200. package/dist/styles/predefined.js +9 -13
  201. package/dist/styles/predefined.js.map +1 -1
  202. package/dist/styles/preset.d.ts +6 -1
  203. package/dist/styles/preset.js +54 -53
  204. package/dist/styles/preset.js.map +1 -1
  205. package/dist/styles/radius.d.ts +1 -3
  206. package/dist/styles/radius.js +38 -6
  207. package/dist/styles/radius.js.map +1 -1
  208. package/dist/styles/scrollMargin.d.ts +24 -0
  209. package/dist/styles/scrollMargin.js +32 -0
  210. package/dist/styles/scrollMargin.js.map +1 -0
  211. package/dist/styles/scrollbar.d.ts +9 -5
  212. package/dist/styles/scrollbar.js +31 -92
  213. package/dist/styles/scrollbar.js.map +1 -1
  214. package/dist/styles/shadow.d.ts +1 -1
  215. package/dist/styles/shadow.js +4 -3
  216. package/dist/styles/shadow.js.map +1 -1
  217. package/dist/styles/shared.js +17 -0
  218. package/dist/styles/shared.js.map +1 -0
  219. package/dist/styles/transition.d.ts +1 -1
  220. package/dist/styles/transition.js +6 -5
  221. package/dist/styles/transition.js.map +1 -1
  222. package/dist/styles/types.d.ts +94 -28
  223. package/dist/styles/width.d.ts +1 -1
  224. package/dist/styles/width.js +1 -2
  225. package/dist/styles/width.js.map +1 -1
  226. package/dist/tasty.d.ts +46 -894
  227. package/dist/tasty.js +101 -59
  228. package/dist/tasty.js.map +1 -1
  229. package/dist/types.d.ts +1 -1
  230. package/dist/utils/cache-wrapper.js +4 -9
  231. package/dist/utils/cache-wrapper.js.map +1 -1
  232. package/dist/utils/case-converter.js +1 -1
  233. package/dist/utils/color-math.d.ts +46 -0
  234. package/dist/utils/color-math.js +749 -0
  235. package/dist/utils/color-math.js.map +1 -0
  236. package/dist/utils/color-space.d.ts +5 -0
  237. package/dist/utils/color-space.js +228 -0
  238. package/dist/utils/color-space.js.map +1 -0
  239. package/dist/utils/colors.js +3 -2
  240. package/dist/utils/colors.js.map +1 -1
  241. package/dist/utils/deps-equal.js +15 -0
  242. package/dist/utils/deps-equal.js.map +1 -0
  243. package/dist/utils/dotize.js +1 -1
  244. package/dist/utils/dotize.js.map +1 -1
  245. package/dist/utils/filter-base-props.d.ts +1 -1
  246. package/dist/utils/filter-base-props.js +1 -1
  247. package/dist/utils/filter-base-props.js.map +1 -1
  248. package/dist/utils/get-display-name.js +1 -1
  249. package/dist/utils/has-keys.js +13 -0
  250. package/dist/utils/has-keys.js.map +1 -0
  251. package/dist/utils/hash.js +14 -0
  252. package/dist/utils/hash.js.map +1 -0
  253. package/dist/utils/is-dev-env.js +1 -1
  254. package/dist/utils/is-dev-env.js.map +1 -1
  255. package/dist/utils/is-valid-element-type.js +1 -1
  256. package/dist/utils/merge-styles.js +1 -2
  257. package/dist/utils/merge-styles.js.map +1 -1
  258. package/dist/utils/mod-attrs.d.ts +0 -2
  259. package/dist/utils/mod-attrs.js +3 -4
  260. package/dist/utils/mod-attrs.js.map +1 -1
  261. package/dist/utils/process-tokens.d.ts +3 -17
  262. package/dist/utils/process-tokens.js +19 -107
  263. package/dist/utils/process-tokens.js.map +1 -1
  264. package/dist/utils/resolve-recipes.js +1 -2
  265. package/dist/utils/resolve-recipes.js.map +1 -1
  266. package/dist/utils/selector-transform.js +32 -0
  267. package/dist/utils/selector-transform.js.map +1 -0
  268. package/dist/utils/string.js +1 -1
  269. package/dist/utils/styles.d.ts +3 -82
  270. package/dist/utils/styles.js +29 -399
  271. package/dist/utils/styles.js.map +1 -1
  272. package/dist/utils/typography.d.ts +35 -13
  273. package/dist/utils/typography.js +16 -18
  274. package/dist/utils/typography.js.map +1 -1
  275. package/dist/utils/warnings.js +1 -1
  276. package/dist/zero/babel.d.ts +57 -84
  277. package/dist/zero/babel.js +217 -37
  278. package/dist/zero/babel.js.map +1 -1
  279. package/dist/zero/css-writer.js +1 -2
  280. package/dist/zero/css-writer.js.map +1 -1
  281. package/dist/zero/extractor.js +119 -3
  282. package/dist/zero/extractor.js.map +1 -1
  283. package/dist/zero/index.js +1 -2
  284. package/dist/zero/next.d.ts +56 -30
  285. package/dist/zero/next.js +105 -40
  286. package/dist/zero/next.js.map +1 -1
  287. package/docs/README.md +31 -0
  288. package/docs/adoption.md +298 -0
  289. package/docs/comparison.md +419 -0
  290. package/docs/configuration.md +394 -0
  291. package/docs/debug.md +320 -0
  292. package/docs/design-system.md +436 -0
  293. package/docs/dsl.md +688 -0
  294. package/docs/getting-started.md +217 -0
  295. package/docs/injector.md +544 -0
  296. package/docs/methodology.md +616 -0
  297. package/docs/pipeline.md +519 -0
  298. package/docs/react-api.md +557 -0
  299. package/docs/ssr.md +442 -0
  300. package/docs/styles.md +596 -0
  301. package/docs/tasty-static.md +532 -0
  302. package/package.json +68 -36
  303. package/dist/styles/align.d.ts +0 -15
  304. package/dist/styles/align.js +0 -14
  305. package/dist/styles/align.js.map +0 -1
  306. package/dist/styles/justify.d.ts +0 -15
  307. package/dist/styles/justify.js +0 -14
  308. package/dist/styles/justify.js.map +0 -1
  309. package/dist/styles/styledScrollbar.d.ts +0 -47
  310. package/dist/styles/styledScrollbar.js +0 -38
  311. package/dist/styles/styledScrollbar.js.map +0 -1
  312. package/dist/tokens/typography.d.ts +0 -19
  313. package/dist/tokens/typography.js +0 -237
  314. package/dist/tokens/typography.js.map +0 -1
  315. package/dist/utils/hsl-to-rgb.js +0 -38
  316. package/dist/utils/hsl-to-rgb.js.map +0 -1
  317. package/dist/utils/okhsl-to-rgb.js +0 -296
  318. package/dist/utils/okhsl-to-rgb.js.map +0 -1
@@ -1,15 +1,29 @@
1
+ import { hashString } from "../utils/hash.js";
1
2
  import { renderStyles } from "../pipeline/index.js";
3
+ import { getConfig } from "../config.js";
2
4
  import { injectGlobal } from "../injector/index.js";
5
+ import { getStyleTarget, pushRSCCSS } from "../rsc-cache.js";
6
+ import { collectAutoInferredProperties, collectAutoInferredPropertiesRSC } from "../ssr/collect-auto-properties.js";
3
7
  import { resolveRecipes } from "../utils/resolve-recipes.js";
4
- import { useInsertionEffect, useMemo, useRef } from "react";
5
-
8
+ import { formatGlobalRules } from "../ssr/format-global-rules.js";
6
9
  //#region src/hooks/useGlobalStyles.ts
10
+ const clientGlobalEntries = /* @__PURE__ */ new Map();
7
11
  /**
8
- * Hook to inject global styles for a given selector.
12
+ * Inject global styles for a given selector.
9
13
  * Useful for styling elements by selector without generating classNames.
10
14
  *
15
+ * SSR-aware: when a ServerStyleCollector is available, CSS is collected
16
+ * during the render phase instead of being injected into the DOM.
17
+ *
18
+ * Works in all environments: client, SSR with collector, and React Server Components.
19
+ *
20
+ * Injected styles are permanent — they are not cleaned up on component unmount.
21
+ * Use the `id` option for update tracking when styles change over the
22
+ * component lifecycle.
23
+ *
11
24
  * @param selector - CSS selector to apply styles to (e.g., '.my-class', ':root', 'body')
12
25
  * @param styles - Tasty styles object
26
+ * @param options - Optional settings including `id` for update tracking
13
27
  *
14
28
  * @example
15
29
  * ```tsx
@@ -24,33 +38,51 @@ import { useInsertionEffect, useMemo, useRef } from "react";
24
38
  * }
25
39
  * ```
26
40
  */
27
- function useGlobalStyles(selector, styles) {
28
- const disposeRef = useRef(null);
29
- const resolvedStyles = useMemo(() => {
30
- if (!styles) return styles;
31
- return resolveRecipes(styles);
32
- }, [styles]);
33
- const styleResults = useMemo(() => {
34
- if (!resolvedStyles) return [];
35
- if (!selector) {
36
- console.warn("[Tasty] useGlobalStyles: selector is required and cannot be empty. Styles will not be injected.");
37
- return [];
41
+ function useGlobalStyles(selector, styles, options) {
42
+ if (!styles) return;
43
+ if (!selector) {
44
+ console.warn("[Tasty] useGlobalStyles: selector is required and cannot be empty. Styles will not be injected.");
45
+ return;
46
+ }
47
+ const target = getStyleTarget();
48
+ if (target.mode === "client") {
49
+ const slotKey = options?.id ?? selector;
50
+ const stylesKey = JSON.stringify(styles);
51
+ const existing = clientGlobalEntries.get(slotKey);
52
+ if (existing && existing.stylesKey === stylesKey) return;
53
+ }
54
+ const resolvedStyles = resolveRecipes(styles);
55
+ const styleResults = renderStyles(resolvedStyles, selector);
56
+ if (styleResults.length === 0) return;
57
+ if (target.mode === "ssr") {
58
+ target.collector.collectInternals();
59
+ const css = formatGlobalRules(styleResults);
60
+ if (css) {
61
+ const key = options?.id ? `global:${options.id}` : `global:${selector}:${hashString(css)}`;
62
+ target.collector.collectGlobalStyles(key, css);
38
63
  }
39
- return renderStyles(resolvedStyles, selector);
40
- }, [resolvedStyles, selector]);
41
- useInsertionEffect(() => {
42
- disposeRef.current?.();
43
- if (styleResults.length > 0) {
44
- const { dispose } = injectGlobal(styleResults);
45
- disposeRef.current = dispose;
46
- } else disposeRef.current = null;
47
- return () => {
48
- disposeRef.current?.();
49
- disposeRef.current = null;
50
- };
51
- }, [styleResults]);
64
+ if (getConfig().autoPropertyTypes !== false) collectAutoInferredProperties(styleResults, target.collector, resolvedStyles);
65
+ return;
66
+ }
67
+ if (target.mode === "rsc") {
68
+ const css = formatGlobalRules(styleResults);
69
+ if (css) {
70
+ const key = options?.id ? `__global:${options.id}` : `__global:${selector}:${hashString(css)}`;
71
+ pushRSCCSS(target.cache, key, css);
72
+ }
73
+ if (getConfig().autoPropertyTypes !== false) collectAutoInferredPropertiesRSC(styleResults, target.cache, resolvedStyles);
74
+ return;
75
+ }
76
+ const slotKey = options?.id ?? selector;
77
+ const existing = clientGlobalEntries.get(slotKey);
78
+ if (existing) existing.dispose();
79
+ const { dispose } = injectGlobal(styleResults);
80
+ clientGlobalEntries.set(slotKey, {
81
+ stylesKey: JSON.stringify(styles),
82
+ dispose
83
+ });
52
84
  }
53
-
54
85
  //#endregion
55
86
  export { useGlobalStyles };
87
+
56
88
  //# sourceMappingURL=useGlobalStyles.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useGlobalStyles.js","names":[],"sources":["../../src/hooks/useGlobalStyles.ts"],"sourcesContent":["import { useInsertionEffect, useMemo, useRef } from 'react';\n\nimport { injectGlobal } from '../injector';\nimport type { StyleResult } from '../pipeline';\nimport { renderStyles } from '../pipeline';\nimport type { Styles } from '../styles/types';\nimport { resolveRecipes } from '../utils/resolve-recipes';\n\n/**\n * Hook to inject global styles for a given selector.\n * Useful for styling elements by selector without generating classNames.\n *\n * @param selector - CSS selector to apply styles to (e.g., '.my-class', ':root', 'body')\n * @param styles - Tasty styles object\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * useGlobalStyles('.card', {\n * padding: '2x',\n * radius: '1r',\n * fill: '#white',\n * });\n *\n * return <div className=\"card\">Content</div>;\n * }\n * ```\n */\nexport function useGlobalStyles(selector: string, styles?: Styles): void {\n const disposeRef = useRef<(() => void) | null>(null);\n\n // Resolve recipes before rendering (zero overhead if no recipes configured)\n const resolvedStyles = useMemo(() => {\n if (!styles) return styles;\n return resolveRecipes(styles);\n }, [styles]);\n\n // Render styles with the provided selector\n // Note: renderStyles overload with selector string returns StyleResult[] directly\n const styleResults = useMemo((): StyleResult[] => {\n if (!resolvedStyles) return [];\n\n // Validate selector - empty string would cause renderStyles to return RenderResult instead of StyleResult[]\n if (!selector) {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n '[Tasty] useGlobalStyles: selector is required and cannot be empty. ' +\n 'Styles will not be injected.',\n );\n }\n return [];\n }\n\n const result = renderStyles(resolvedStyles, selector);\n // When a non-empty selector is provided, renderStyles returns StyleResult[]\n return result as StyleResult[];\n }, [resolvedStyles, selector]);\n\n // Inject as global styles\n useInsertionEffect(() => {\n disposeRef.current?.();\n\n if (styleResults.length > 0) {\n const { dispose } = injectGlobal(styleResults);\n disposeRef.current = dispose;\n } else {\n disposeRef.current = null;\n }\n\n return () => {\n disposeRef.current?.();\n disposeRef.current = null;\n };\n }, [styleResults]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,gBAAgB,UAAkB,QAAuB;CACvE,MAAM,aAAa,OAA4B,KAAK;CAGpD,MAAM,iBAAiB,cAAc;AACnC,MAAI,CAAC,OAAQ,QAAO;AACpB,SAAO,eAAe,OAAO;IAC5B,CAAC,OAAO,CAAC;CAIZ,MAAM,eAAe,cAA6B;AAChD,MAAI,CAAC,eAAgB,QAAO,EAAE;AAG9B,MAAI,CAAC,UAAU;AAEX,WAAQ,KACN,kGAED;AAEH,UAAO,EAAE;;AAKX,SAFe,aAAa,gBAAgB,SAAS;IAGpD,CAAC,gBAAgB,SAAS,CAAC;AAG9B,0BAAyB;AACvB,aAAW,WAAW;AAEtB,MAAI,aAAa,SAAS,GAAG;GAC3B,MAAM,EAAE,YAAY,aAAa,aAAa;AAC9C,cAAW,UAAU;QAErB,YAAW,UAAU;AAGvB,eAAa;AACX,cAAW,WAAW;AACtB,cAAW,UAAU;;IAEtB,CAAC,aAAa,CAAC"}
1
+ {"version":3,"file":"useGlobalStyles.js","names":[],"sources":["../../src/hooks/useGlobalStyles.ts"],"sourcesContent":["import { getConfig } from '../config';\nimport { injectGlobal } from '../injector';\nimport type { StyleResult } from '../pipeline';\nimport { renderStyles } from '../pipeline';\nimport { getStyleTarget, pushRSCCSS } from '../rsc-cache';\nimport {\n collectAutoInferredProperties,\n collectAutoInferredPropertiesRSC,\n} from '../ssr/collect-auto-properties';\nimport { formatGlobalRules } from '../ssr/format-global-rules';\nimport type { Styles } from '../styles/types';\nimport { hashString } from '../utils/hash';\nimport { resolveRecipes } from '../utils/resolve-recipes';\n\ninterface UseGlobalStylesOptions {\n /**\n * Stable identifier for update tracking (client-only). When provided,\n * changing the styles will dispose the previous injection and inject the\n * new one. Without an id, the selector is used as the slot key.\n * In RSC mode, renders are single-pass so update tracking does not apply.\n */\n id?: string;\n}\n\ninterface ClientGlobalEntry {\n stylesKey: string;\n dispose: () => void;\n}\n\nconst clientGlobalEntries = new Map<string, ClientGlobalEntry>();\n\n/* @internal — used only for tests */\nexport function _resetGlobalStylesCache(): void {\n clientGlobalEntries.clear();\n}\n\n/**\n * Inject global styles for a given selector.\n * Useful for styling elements by selector without generating classNames.\n *\n * SSR-aware: when a ServerStyleCollector is available, CSS is collected\n * during the render phase instead of being injected into the DOM.\n *\n * Works in all environments: client, SSR with collector, and React Server Components.\n *\n * Injected styles are permanent — they are not cleaned up on component unmount.\n * Use the `id` option for update tracking when styles change over the\n * component lifecycle.\n *\n * @param selector - CSS selector to apply styles to (e.g., '.my-class', ':root', 'body')\n * @param styles - Tasty styles object\n * @param options - Optional settings including `id` for update tracking\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * useGlobalStyles('.card', {\n * padding: '2x',\n * radius: '1r',\n * fill: '#white',\n * });\n *\n * return <div className=\"card\">Content</div>;\n * }\n * ```\n */\nexport function useGlobalStyles(\n selector: string,\n styles?: Styles,\n options?: UseGlobalStylesOptions,\n): void {\n if (!styles) return;\n\n if (!selector) {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n '[Tasty] useGlobalStyles: selector is required and cannot be empty. ' +\n 'Styles will not be injected.',\n );\n }\n return;\n }\n\n const target = getStyleTarget();\n\n // Client fast path: skip resolveRecipes/renderStyles if styles haven't changed\n if (target.mode === 'client') {\n const slotKey = options?.id ?? selector;\n const stylesKey = JSON.stringify(styles);\n const existing = clientGlobalEntries.get(slotKey);\n if (existing && existing.stylesKey === stylesKey) return;\n }\n\n const resolvedStyles = resolveRecipes(styles);\n\n const styleResults = renderStyles(resolvedStyles, selector) as StyleResult[];\n\n if (styleResults.length === 0) return;\n\n if (target.mode === 'ssr') {\n target.collector.collectInternals();\n\n const css = formatGlobalRules(styleResults);\n if (css) {\n const key = options?.id\n ? `global:${options.id}`\n : `global:${selector}:${hashString(css)}`;\n target.collector.collectGlobalStyles(key, css);\n }\n\n if (getConfig().autoPropertyTypes !== false) {\n collectAutoInferredProperties(\n styleResults,\n target.collector,\n resolvedStyles,\n );\n }\n return;\n }\n\n if (target.mode === 'rsc') {\n const css = formatGlobalRules(styleResults);\n if (css) {\n const key = options?.id\n ? `__global:${options.id}`\n : `__global:${selector}:${hashString(css)}`;\n pushRSCCSS(target.cache, key, css);\n }\n\n if (getConfig().autoPropertyTypes !== false) {\n collectAutoInferredPropertiesRSC(\n styleResults,\n target.cache,\n resolvedStyles,\n );\n }\n return;\n }\n\n // Client path\n const slotKey = options?.id ?? selector;\n\n const existing = clientGlobalEntries.get(slotKey);\n if (existing) {\n existing.dispose();\n }\n\n const { dispose } = injectGlobal(styleResults);\n clientGlobalEntries.set(slotKey, {\n stylesKey: JSON.stringify(styles),\n dispose,\n });\n}\n"],"mappings":";;;;;;;;;AA6BA,MAAM,sCAAsB,IAAI,KAAgC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqChE,SAAgB,gBACd,UACA,QACA,SACM;AACN,KAAI,CAAC,OAAQ;AAEb,KAAI,CAAC,UAAU;AAEX,UAAQ,KACN,kGAED;AAEH;;CAGF,MAAM,SAAS,gBAAgB;AAG/B,KAAI,OAAO,SAAS,UAAU;EAC5B,MAAM,UAAU,SAAS,MAAM;EAC/B,MAAM,YAAY,KAAK,UAAU,OAAO;EACxC,MAAM,WAAW,oBAAoB,IAAI,QAAQ;AACjD,MAAI,YAAY,SAAS,cAAc,UAAW;;CAGpD,MAAM,iBAAiB,eAAe,OAAO;CAE7C,MAAM,eAAe,aAAa,gBAAgB,SAAS;AAE3D,KAAI,aAAa,WAAW,EAAG;AAE/B,KAAI,OAAO,SAAS,OAAO;AACzB,SAAO,UAAU,kBAAkB;EAEnC,MAAM,MAAM,kBAAkB,aAAa;AAC3C,MAAI,KAAK;GACP,MAAM,MAAM,SAAS,KACjB,UAAU,QAAQ,OAClB,UAAU,SAAS,GAAG,WAAW,IAAI;AACzC,UAAO,UAAU,oBAAoB,KAAK,IAAI;;AAGhD,MAAI,WAAW,CAAC,sBAAsB,MACpC,+BACE,cACA,OAAO,WACP,eACD;AAEH;;AAGF,KAAI,OAAO,SAAS,OAAO;EACzB,MAAM,MAAM,kBAAkB,aAAa;AAC3C,MAAI,KAAK;GACP,MAAM,MAAM,SAAS,KACjB,YAAY,QAAQ,OACpB,YAAY,SAAS,GAAG,WAAW,IAAI;AAC3C,cAAW,OAAO,OAAO,KAAK,IAAI;;AAGpC,MAAI,WAAW,CAAC,sBAAsB,MACpC,kCACE,cACA,OAAO,OACP,eACD;AAEH;;CAIF,MAAM,UAAU,SAAS,MAAM;CAE/B,MAAM,WAAW,oBAAoB,IAAI,QAAQ;AACjD,KAAI,SACF,UAAS,SAAS;CAGpB,MAAM,EAAE,YAAY,aAAa,aAAa;AAC9C,qBAAoB,IAAI,SAAS;EAC/B,WAAW,KAAK,UAAU,OAAO;EACjC;EACD,CAAC"}
@@ -6,8 +6,10 @@ interface UseKeyframesOptions {
6
6
  root?: Document | ShadowRoot;
7
7
  }
8
8
  /**
9
- * Hook to inject CSS @keyframes and return the generated animation name.
10
- * Handles keyframes injection with proper cleanup on unmount or dependency changes.
9
+ * Inject CSS @keyframes and return the generated animation name.
10
+ * Deduplicates by content — identical steps always return the same name.
11
+ *
12
+ * Works in all environments: client, SSR with collector, and React Server Components.
11
13
  *
12
14
  * @example Basic usage - steps object is the dependency
13
15
  * ```tsx
@@ -1,54 +1,55 @@
1
+ import { hashString } from "../utils/hash.js";
1
2
  import { keyframes } from "../injector/index.js";
2
- import { useInsertionEffect, useMemo, useRef } from "react";
3
-
3
+ import { getStyleTarget, pushRSCCSS } from "../rsc-cache.js";
4
+ import { formatKeyframesCSS } from "../ssr/format-keyframes.js";
5
+ import { depsEqual } from "../utils/deps-equal.js";
4
6
  //#region src/hooks/useKeyframes.ts
7
+ const clientContentToName = /* @__PURE__ */ new Map();
8
+ const factoryDepsCache = /* @__PURE__ */ new Map();
5
9
  function useKeyframes(stepsOrFactory, depsOrOptions, options) {
6
10
  const isFactory = typeof stepsOrFactory === "function";
7
11
  const deps = isFactory && Array.isArray(depsOrOptions) ? depsOrOptions : void 0;
8
12
  const opts = isFactory ? options : depsOrOptions;
9
- const stepsData = useMemo(() => {
10
- const steps = isFactory ? stepsOrFactory() : stepsOrFactory;
11
- if (!steps || Object.keys(steps).length === 0) return null;
12
- return steps;
13
- }, isFactory ? deps ?? [] : [stepsOrFactory]);
14
- const renderResultRef = useRef(null);
15
- const effectResultRef = useRef(null);
16
- const name = useMemo(() => {
17
- renderResultRef.current?.dispose();
18
- renderResultRef.current = null;
19
- if (!stepsData) return "";
20
- const result = keyframes(stepsData, {
21
- name: opts?.name,
22
- root: opts?.root
23
- });
24
- renderResultRef.current = result;
25
- return result.toString();
26
- }, [
27
- stepsData,
28
- opts?.name,
29
- opts?.root
30
- ]);
31
- useInsertionEffect(() => {
32
- effectResultRef.current?.dispose();
33
- effectResultRef.current = null;
34
- if (stepsData) effectResultRef.current = keyframes(stepsData, {
35
- name: opts?.name,
36
- root: opts?.root
37
- });
38
- return () => {
39
- effectResultRef.current?.dispose();
40
- effectResultRef.current = null;
41
- renderResultRef.current?.dispose();
42
- renderResultRef.current = null;
43
- };
44
- }, [
45
- stepsData,
46
- opts?.name,
47
- opts?.root
48
- ]);
13
+ const target = getStyleTarget();
14
+ if (isFactory && deps && opts?.name && target.mode === "client") {
15
+ const cached = factoryDepsCache.get(opts.name);
16
+ if (cached && depsEqual(cached.deps, deps)) return cached.name;
17
+ }
18
+ const steps = isFactory ? stepsOrFactory() : stepsOrFactory;
19
+ if (!steps || Object.keys(steps).length === 0) return "";
20
+ if (target.mode === "ssr") {
21
+ const actualName = target.collector.allocateKeyframeName(opts?.name);
22
+ const css = formatKeyframesCSS(actualName, steps);
23
+ target.collector.collectKeyframes(actualName, css);
24
+ return actualName;
25
+ }
26
+ if (target.mode === "rsc") {
27
+ const serializedContent = JSON.stringify(steps);
28
+ const key = `__kf:${opts?.name ?? ""}:${serializedContent}`;
29
+ const existingName = target.cache.generatedNames.get(key);
30
+ if (existingName) return existingName;
31
+ const actualName = opts?.name ?? `k${hashString(serializedContent)}`;
32
+ const css = formatKeyframesCSS(actualName, steps);
33
+ pushRSCCSS(target.cache, key, css);
34
+ target.cache.generatedNames.set(key, actualName);
35
+ return actualName;
36
+ }
37
+ const serializedContent = JSON.stringify(steps);
38
+ const cacheKey = `${opts?.name ?? ""}:${serializedContent}`;
39
+ const cachedName = clientContentToName.get(cacheKey);
40
+ if (cachedName) return cachedName;
41
+ const name = keyframes(steps, {
42
+ name: opts?.name,
43
+ root: opts?.root
44
+ }).toString();
45
+ clientContentToName.set(cacheKey, name);
46
+ if (deps && opts?.name) factoryDepsCache.set(opts.name, {
47
+ deps,
48
+ name
49
+ });
49
50
  return name;
50
51
  }
51
-
52
52
  //#endregion
53
53
  export { useKeyframes };
54
+
54
55
  //# sourceMappingURL=useKeyframes.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useKeyframes.js","names":[],"sources":["../../src/hooks/useKeyframes.ts"],"sourcesContent":["import { useInsertionEffect, useMemo, useRef } from 'react';\n\nimport { keyframes } from '../injector';\nimport type { KeyframesResult, KeyframesSteps } from '../injector/types';\n\ninterface UseKeyframesOptions {\n name?: string;\n root?: Document | ShadowRoot;\n}\n\n/**\n * Hook to inject CSS @keyframes and return the generated animation name.\n * Handles keyframes injection with proper cleanup on unmount or dependency changes.\n *\n * @example Basic usage - steps object is the dependency\n * ```tsx\n * function MyComponent() {\n * const bounce = useKeyframes({\n * '0%': { transform: 'scale(1)' },\n * '50%': { transform: 'scale(1.1)' },\n * '100%': { transform: 'scale(1)' },\n * });\n *\n * return <div style={{ animation: `${bounce} 1s infinite` }}>Bouncing</div>;\n * }\n * ```\n *\n * @example With custom name\n * ```tsx\n * function MyComponent() {\n * const fadeIn = useKeyframes(\n * { from: { opacity: 0 }, to: { opacity: 1 } },\n * { name: 'fadeIn' }\n * );\n *\n * return <div style={{ animation: `${fadeIn} 0.3s ease-out` }}>Fading in</div>;\n * }\n * ```\n *\n * @example Factory function with dependencies\n * ```tsx\n * function MyComponent({ scale }: { scale: number }) {\n * const pulse = useKeyframes(\n * () => ({\n * '0%': { transform: 'scale(1)' },\n * '100%': { transform: `scale(${scale})` },\n * }),\n * [scale]\n * );\n *\n * return <div style={{ animation: `${pulse} 1s infinite` }}>Pulsing</div>;\n * }\n * ```\n */\n\n// Overload 1: Static steps object\nexport function useKeyframes(\n steps: KeyframesSteps,\n options?: UseKeyframesOptions,\n): string;\n\n// Overload 2: Factory function with dependencies\nexport function useKeyframes(\n factory: () => KeyframesSteps,\n deps: readonly unknown[],\n options?: UseKeyframesOptions,\n): string;\n\n// Implementation\nexport function useKeyframes(\n stepsOrFactory: KeyframesSteps | (() => KeyframesSteps),\n depsOrOptions?: readonly unknown[] | UseKeyframesOptions,\n options?: UseKeyframesOptions,\n): string {\n // Detect which overload is being used\n const isFactory = typeof stepsOrFactory === 'function';\n\n // Parse arguments based on overload\n const deps =\n isFactory && Array.isArray(depsOrOptions) ? depsOrOptions : undefined;\n const opts = isFactory\n ? options\n : (depsOrOptions as UseKeyframesOptions | undefined);\n\n // Memoize the keyframes steps to get a stable reference\n const stepsData = useMemo(\n () => {\n const steps = isFactory\n ? (stepsOrFactory as () => KeyframesSteps)()\n : (stepsOrFactory as KeyframesSteps);\n\n if (!steps || Object.keys(steps).length === 0) {\n return null;\n }\n\n return steps;\n },\n\n isFactory ? (deps ?? []) : [stepsOrFactory],\n );\n\n // Store keyframes results for cleanup - we need to track both the render-time\n // injection (for the name) and the effect-time injection (for Strict Mode safety)\n const renderResultRef = useRef<KeyframesResult | null>(null);\n const effectResultRef = useRef<KeyframesResult | null>(null);\n\n // Inject keyframes during render to ensure the animation name is available\n // immediately. The keyframes() function uses reference counting internally,\n // so multiple calls with the same content are deduplicated.\n const name = useMemo(() => {\n // Dispose previous render-time result if deps changed\n renderResultRef.current?.dispose();\n renderResultRef.current = null;\n\n if (!stepsData) {\n return '';\n }\n\n // Inject keyframes synchronously\n const result = keyframes(stepsData, {\n name: opts?.name,\n root: opts?.root,\n });\n\n renderResultRef.current = result;\n\n return result.toString();\n }, [stepsData, opts?.name, opts?.root]);\n\n // Handle injection and cleanup in useInsertionEffect to properly support\n // React 18+ Strict Mode double-invocation (mount → unmount → mount).\n // The effect setup re-injects the keyframes if cleanup was called, ensuring\n // the CSS exists after Strict Mode remounts.\n useInsertionEffect(() => {\n // Dispose previous effect-time result\n effectResultRef.current?.dispose();\n effectResultRef.current = null;\n\n // Re-inject keyframes. This ensures the CSS exists after Strict Mode cleanup.\n // The keyframes() function uses reference counting, so this is idempotent\n // if the CSS wasn't disposed.\n if (stepsData) {\n const result = keyframes(stepsData, {\n name: opts?.name,\n root: opts?.root,\n });\n effectResultRef.current = result;\n }\n\n // Cleanup on unmount or when dependencies change.\n // Dispose both the effect-time and render-time results to properly\n // decrement the reference count.\n return () => {\n effectResultRef.current?.dispose();\n effectResultRef.current = null;\n renderResultRef.current?.dispose();\n renderResultRef.current = null;\n };\n }, [stepsData, opts?.name, opts?.root]);\n\n return name;\n}\n"],"mappings":";;;;AAqEA,SAAgB,aACd,gBACA,eACA,SACQ;CAER,MAAM,YAAY,OAAO,mBAAmB;CAG5C,MAAM,OACJ,aAAa,MAAM,QAAQ,cAAc,GAAG,gBAAgB;CAC9D,MAAM,OAAO,YACT,UACC;CAGL,MAAM,YAAY,cACV;EACJ,MAAM,QAAQ,YACT,gBAAyC,GACzC;AAEL,MAAI,CAAC,SAAS,OAAO,KAAK,MAAM,CAAC,WAAW,EAC1C,QAAO;AAGT,SAAO;IAGT,YAAa,QAAQ,EAAE,GAAI,CAAC,eAAe,CAC5C;CAID,MAAM,kBAAkB,OAA+B,KAAK;CAC5D,MAAM,kBAAkB,OAA+B,KAAK;CAK5D,MAAM,OAAO,cAAc;AAEzB,kBAAgB,SAAS,SAAS;AAClC,kBAAgB,UAAU;AAE1B,MAAI,CAAC,UACH,QAAO;EAIT,MAAM,SAAS,UAAU,WAAW;GAClC,MAAM,MAAM;GACZ,MAAM,MAAM;GACb,CAAC;AAEF,kBAAgB,UAAU;AAE1B,SAAO,OAAO,UAAU;IACvB;EAAC;EAAW,MAAM;EAAM,MAAM;EAAK,CAAC;AAMvC,0BAAyB;AAEvB,kBAAgB,SAAS,SAAS;AAClC,kBAAgB,UAAU;AAK1B,MAAI,UAKF,iBAAgB,UAJD,UAAU,WAAW;GAClC,MAAM,MAAM;GACZ,MAAM,MAAM;GACb,CAAC;AAOJ,eAAa;AACX,mBAAgB,SAAS,SAAS;AAClC,mBAAgB,UAAU;AAC1B,mBAAgB,SAAS,SAAS;AAClC,mBAAgB,UAAU;;IAE3B;EAAC;EAAW,MAAM;EAAM,MAAM;EAAK,CAAC;AAEvC,QAAO"}
1
+ {"version":3,"file":"useKeyframes.js","names":[],"sources":["../../src/hooks/useKeyframes.ts"],"sourcesContent":["import { keyframes } from '../injector';\nimport type { KeyframesSteps } from '../injector/types';\nimport { getStyleTarget, pushRSCCSS } from '../rsc-cache';\nimport { formatKeyframesCSS } from '../ssr/format-keyframes';\nimport { depsEqual } from '../utils/deps-equal';\nimport { hashString } from '../utils/hash';\n\ninterface UseKeyframesOptions {\n name?: string;\n root?: Document | ShadowRoot;\n}\n\nconst clientContentToName = new Map<string, string>();\n\ninterface FactoryDepsEntry {\n deps: readonly unknown[];\n name: string;\n}\n\nconst factoryDepsCache = new Map<string, FactoryDepsEntry>();\n\n/* @internal — used only for tests */\nexport function _resetKeyframesCache(): void {\n clientContentToName.clear();\n factoryDepsCache.clear();\n}\n\n/**\n * Inject CSS @keyframes and return the generated animation name.\n * Deduplicates by content — identical steps always return the same name.\n *\n * Works in all environments: client, SSR with collector, and React Server Components.\n *\n * @example Basic usage - steps object is the dependency\n * ```tsx\n * function MyComponent() {\n * const bounce = useKeyframes({\n * '0%': { transform: 'scale(1)' },\n * '50%': { transform: 'scale(1.1)' },\n * '100%': { transform: 'scale(1)' },\n * });\n *\n * return <div style={{ animation: `${bounce} 1s infinite` }}>Bouncing</div>;\n * }\n * ```\n *\n * @example With custom name\n * ```tsx\n * function MyComponent() {\n * const fadeIn = useKeyframes(\n * { from: { opacity: 0 }, to: { opacity: 1 } },\n * { name: 'fadeIn' }\n * );\n *\n * return <div style={{ animation: `${fadeIn} 0.3s ease-out` }}>Fading in</div>;\n * }\n * ```\n *\n * @example Factory function with dependencies\n * ```tsx\n * function MyComponent({ scale }: { scale: number }) {\n * const pulse = useKeyframes(\n * () => ({\n * '0%': { transform: 'scale(1)' },\n * '100%': { transform: `scale(${scale})` },\n * }),\n * [scale]\n * );\n *\n * return <div style={{ animation: `${pulse} 1s infinite` }}>Pulsing</div>;\n * }\n * ```\n */\n\n// Overload 1: Static steps object\nexport function useKeyframes(\n steps: KeyframesSteps,\n options?: UseKeyframesOptions,\n): string;\n\n// Overload 2: Factory function with dependencies\nexport function useKeyframes(\n factory: () => KeyframesSteps,\n deps: readonly unknown[],\n options?: UseKeyframesOptions,\n): string;\n\n// Implementation\nexport function useKeyframes(\n stepsOrFactory: KeyframesSteps | (() => KeyframesSteps),\n depsOrOptions?: readonly unknown[] | UseKeyframesOptions,\n options?: UseKeyframesOptions,\n): string {\n const isFactory = typeof stepsOrFactory === 'function';\n\n const deps =\n isFactory && Array.isArray(depsOrOptions) ? depsOrOptions : undefined;\n const opts = isFactory\n ? options\n : (depsOrOptions as UseKeyframesOptions | undefined);\n\n const target = getStyleTarget();\n\n // Client deps cache: skip factory re-evaluation when deps haven't changed\n if (isFactory && deps && opts?.name && target.mode === 'client') {\n const cached = factoryDepsCache.get(opts.name);\n if (cached && depsEqual(cached.deps, deps)) {\n return cached.name;\n }\n }\n\n const steps = isFactory\n ? (stepsOrFactory as () => KeyframesSteps)()\n : (stepsOrFactory as KeyframesSteps);\n\n if (!steps || Object.keys(steps).length === 0) {\n return '';\n }\n\n if (target.mode === 'ssr') {\n const actualName = target.collector.allocateKeyframeName(opts?.name);\n const css = formatKeyframesCSS(actualName, steps);\n target.collector.collectKeyframes(actualName, css);\n return actualName;\n }\n\n if (target.mode === 'rsc') {\n const serializedContent = JSON.stringify(steps);\n const key = `__kf:${opts?.name ?? ''}:${serializedContent}`;\n\n const existingName = target.cache.generatedNames.get(key);\n if (existingName) return existingName;\n\n const actualName = opts?.name ?? `k${hashString(serializedContent)}`;\n const css = formatKeyframesCSS(actualName, steps);\n pushRSCCSS(target.cache, key, css);\n target.cache.generatedNames.set(key, actualName);\n return actualName;\n }\n\n // Client path: stable name via content-based dedup\n const serializedContent = JSON.stringify(steps);\n const cacheKey = `${opts?.name ?? ''}:${serializedContent}`;\n\n const cachedName = clientContentToName.get(cacheKey);\n if (cachedName) {\n return cachedName;\n }\n\n const result = keyframes(steps, {\n name: opts?.name,\n root: opts?.root,\n });\n\n const name = result.toString();\n clientContentToName.set(cacheKey, name);\n\n if (deps && opts?.name) {\n factoryDepsCache.set(opts.name, { deps, name });\n }\n\n return name;\n}\n"],"mappings":";;;;;;AAYA,MAAM,sCAAsB,IAAI,KAAqB;AAOrD,MAAM,mCAAmB,IAAI,KAA+B;AAqE5D,SAAgB,aACd,gBACA,eACA,SACQ;CACR,MAAM,YAAY,OAAO,mBAAmB;CAE5C,MAAM,OACJ,aAAa,MAAM,QAAQ,cAAc,GAAG,gBAAgB,KAAA;CAC9D,MAAM,OAAO,YACT,UACC;CAEL,MAAM,SAAS,gBAAgB;AAG/B,KAAI,aAAa,QAAQ,MAAM,QAAQ,OAAO,SAAS,UAAU;EAC/D,MAAM,SAAS,iBAAiB,IAAI,KAAK,KAAK;AAC9C,MAAI,UAAU,UAAU,OAAO,MAAM,KAAK,CACxC,QAAO,OAAO;;CAIlB,MAAM,QAAQ,YACT,gBAAyC,GACzC;AAEL,KAAI,CAAC,SAAS,OAAO,KAAK,MAAM,CAAC,WAAW,EAC1C,QAAO;AAGT,KAAI,OAAO,SAAS,OAAO;EACzB,MAAM,aAAa,OAAO,UAAU,qBAAqB,MAAM,KAAK;EACpE,MAAM,MAAM,mBAAmB,YAAY,MAAM;AACjD,SAAO,UAAU,iBAAiB,YAAY,IAAI;AAClD,SAAO;;AAGT,KAAI,OAAO,SAAS,OAAO;EACzB,MAAM,oBAAoB,KAAK,UAAU,MAAM;EAC/C,MAAM,MAAM,QAAQ,MAAM,QAAQ,GAAG,GAAG;EAExC,MAAM,eAAe,OAAO,MAAM,eAAe,IAAI,IAAI;AACzD,MAAI,aAAc,QAAO;EAEzB,MAAM,aAAa,MAAM,QAAQ,IAAI,WAAW,kBAAkB;EAClE,MAAM,MAAM,mBAAmB,YAAY,MAAM;AACjD,aAAW,OAAO,OAAO,KAAK,IAAI;AAClC,SAAO,MAAM,eAAe,IAAI,KAAK,WAAW;AAChD,SAAO;;CAIT,MAAM,oBAAoB,KAAK,UAAU,MAAM;CAC/C,MAAM,WAAW,GAAG,MAAM,QAAQ,GAAG,GAAG;CAExC,MAAM,aAAa,oBAAoB,IAAI,SAAS;AACpD,KAAI,WACF,QAAO;CAQT,MAAM,OALS,UAAU,OAAO;EAC9B,MAAM,MAAM;EACZ,MAAM,MAAM;EACb,CAAC,CAEkB,UAAU;AAC9B,qBAAoB,IAAI,UAAU,KAAK;AAEvC,KAAI,QAAQ,MAAM,KAChB,kBAAiB,IAAI,KAAK,MAAM;EAAE;EAAM;EAAM,CAAC;AAGjD,QAAO"}
@@ -22,17 +22,19 @@ interface UsePropertyOptions {
22
22
  root?: Document | ShadowRoot;
23
23
  }
24
24
  /**
25
- * Hook to register a CSS @property custom property.
25
+ * Register a CSS @property custom property.
26
26
  * This enables advanced features like animating custom properties.
27
27
  *
28
28
  * Note: @property rules are global and persistent once defined.
29
- * The hook ensures the property is only registered once per root.
29
+ * The function ensures the property is only registered once per root.
30
30
  *
31
31
  * Accepts tasty token syntax for the property name:
32
32
  * - `$name` → defines `--name`
33
33
  * - `#name` → defines `--name-color` (auto-sets syntax: '<color>', defaults initialValue: 'transparent')
34
34
  * - `--name` → defines `--name` (legacy format)
35
35
  *
36
+ * Works in all environments: client, SSR with collector, and React Server Components.
37
+ *
36
38
  * @param name - The property token ($name, #name) or CSS property name (--name)
37
39
  * @param options - Property configuration
38
40
  *
@@ -1,19 +1,21 @@
1
1
  import { getGlobalInjector } from "../config.js";
2
- import { useInsertionEffect, useMemo } from "react";
3
-
2
+ import { getStyleTarget, pushRSCCSS } from "../rsc-cache.js";
3
+ import { formatPropertyCSS } from "../ssr/format-property.js";
4
4
  //#region src/hooks/useProperty.ts
5
5
  /**
6
- * Hook to register a CSS @property custom property.
6
+ * Register a CSS @property custom property.
7
7
  * This enables advanced features like animating custom properties.
8
8
  *
9
9
  * Note: @property rules are global and persistent once defined.
10
- * The hook ensures the property is only registered once per root.
10
+ * The function ensures the property is only registered once per root.
11
11
  *
12
12
  * Accepts tasty token syntax for the property name:
13
13
  * - `$name` → defines `--name`
14
14
  * - `#name` → defines `--name-color` (auto-sets syntax: '<color>', defaults initialValue: 'transparent')
15
15
  * - `--name` → defines `--name` (legacy format)
16
16
  *
17
+ * Works in all environments: client, SSR with collector, and React Server Components.
18
+ *
17
19
  * @param name - The property token ($name, #name) or CSS property name (--name)
18
20
  * @param options - Property configuration
19
21
  *
@@ -55,37 +57,40 @@ import { useInsertionEffect, useMemo } from "react";
55
57
  * ```
56
58
  */
57
59
  function useProperty(name, options) {
58
- useInsertionEffect(() => {
59
- if (!name) {
60
- console.warn(`[Tasty] useProperty: property name is required`);
61
- return;
62
- }
63
- const injector = getGlobalInjector();
64
- if (injector.isPropertyDefined(name, { root: options?.root })) return;
65
- injector.property(name, {
60
+ if (!name) {
61
+ console.warn(`[Tasty] useProperty: property name is required`);
62
+ return;
63
+ }
64
+ const target = getStyleTarget();
65
+ if (target.mode === "ssr") {
66
+ target.collector.collectInternals();
67
+ const css = formatPropertyCSS(name, {
66
68
  syntax: options?.syntax,
67
69
  inherits: options?.inherits,
68
- initialValue: options?.initialValue,
69
- root: options?.root
70
+ initialValue: options?.initialValue
70
71
  });
71
- }, [
72
- name,
73
- useMemo(() => {
74
- if (!options) return "";
75
- return JSON.stringify({
76
- syntax: options.syntax,
77
- inherits: options.inherits,
78
- initialValue: options.initialValue
79
- });
80
- }, [
81
- options?.syntax,
82
- options?.inherits,
83
- options?.initialValue
84
- ]),
85
- options?.root
86
- ]);
72
+ if (css) target.collector.collectProperty(name, css);
73
+ return;
74
+ }
75
+ if (target.mode === "rsc") {
76
+ const css = formatPropertyCSS(name, {
77
+ syntax: options?.syntax,
78
+ inherits: options?.inherits,
79
+ initialValue: options?.initialValue
80
+ });
81
+ if (css) pushRSCCSS(target.cache, `__prop:${name}`, css);
82
+ return;
83
+ }
84
+ const injector = getGlobalInjector();
85
+ if (injector.isPropertyDefined(name, { root: options?.root })) return;
86
+ injector.property(name, {
87
+ syntax: options?.syntax,
88
+ inherits: options?.inherits,
89
+ initialValue: options?.initialValue,
90
+ root: options?.root
91
+ });
87
92
  }
88
-
89
93
  //#endregion
90
94
  export { useProperty };
95
+
91
96
  //# sourceMappingURL=useProperty.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useProperty.js","names":[],"sources":["../../src/hooks/useProperty.ts"],"sourcesContent":["import { useInsertionEffect, useMemo } from 'react';\n\nimport { getGlobalInjector } from '../config';\n\nexport interface UsePropertyOptions {\n /**\n * CSS syntax string for the property (e.g., '<color>', '<length>', '<angle>').\n * For color tokens (#name), this is auto-set to '<color>' and cannot be overridden.\n * @see https://developer.mozilla.org/en-US/docs/Web/CSS/@property/syntax\n */\n syntax?: string;\n /**\n * Whether the property inherits from parent elements\n * @default true\n */\n inherits?: boolean;\n /**\n * Initial value for the property.\n * For color tokens (#name), this defaults to 'transparent' if not specified.\n */\n initialValue?: string | number;\n /**\n * Shadow root or document to inject into\n */\n root?: Document | ShadowRoot;\n}\n\n/**\n * Hook to register a CSS @property custom property.\n * This enables advanced features like animating custom properties.\n *\n * Note: @property rules are global and persistent once defined.\n * The hook ensures the property is only registered once per root.\n *\n * Accepts tasty token syntax for the property name:\n * - `$name` → defines `--name`\n * - `#name` → defines `--name-color` (auto-sets syntax: '<color>', defaults initialValue: 'transparent')\n * - `--name` → defines `--name` (legacy format)\n *\n * @param name - The property token ($name, #name) or CSS property name (--name)\n * @param options - Property configuration\n *\n * @example Basic property with token syntax\n * ```tsx\n * function Spinner() {\n * useProperty('$rotation', {\n * syntax: '<angle>',\n * inherits: false,\n * initialValue: '0deg',\n * });\n *\n * return <div className=\"spinner\" />;\n * }\n * ```\n *\n * @example Color property with token syntax (auto-sets syntax)\n * ```tsx\n * function MyComponent() {\n * useProperty('#theme', {\n * initialValue: 'red', // syntax: '<color>' is auto-set\n * });\n *\n * // Now --theme-color can be animated with CSS transitions\n * return <div style={{ '--theme-color': 'blue' } as React.CSSProperties}>Colored</div>;\n * }\n * ```\n *\n * @example Legacy format (still supported)\n * ```tsx\n * function ResizableBox() {\n * useProperty('--box-size', {\n * syntax: '<length>',\n * initialValue: '100px',\n * });\n *\n * return <div style={{ width: 'var(--box-size)' }} />;\n * }\n * ```\n */\nexport function useProperty(name: string, options?: UsePropertyOptions): void {\n // Memoize the options to create a stable dependency\n const optionsKey = useMemo(() => {\n if (!options) return '';\n return JSON.stringify({\n syntax: options.syntax,\n inherits: options.inherits,\n initialValue: options.initialValue,\n });\n }, [options?.syntax, options?.inherits, options?.initialValue]);\n\n useInsertionEffect(() => {\n if (!name) {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(`[Tasty] useProperty: property name is required`);\n }\n return;\n }\n\n const injector = getGlobalInjector();\n\n // Check if already defined (properties are persistent)\n // The injector handles token parsing internally\n if (injector.isPropertyDefined(name, { root: options?.root })) {\n return;\n }\n\n // Register the property\n // The injector handles $name, #name, --name parsing and auto-sets\n // syntax for color tokens\n injector.property(name, {\n syntax: options?.syntax,\n inherits: options?.inherits,\n initialValue: options?.initialValue,\n root: options?.root,\n });\n\n // No cleanup - @property rules are global and persistent\n // Re-registering is a no-op anyway due to the isPropertyDefined check\n }, [name, optionsKey, options?.root]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+EA,SAAgB,YAAY,MAAc,SAAoC;AAW5E,0BAAyB;AACvB,MAAI,CAAC,MAAM;AAEP,WAAQ,KAAK,iDAAiD;AAEhE;;EAGF,MAAM,WAAW,mBAAmB;AAIpC,MAAI,SAAS,kBAAkB,MAAM,EAAE,MAAM,SAAS,MAAM,CAAC,CAC3D;AAMF,WAAS,SAAS,MAAM;GACtB,QAAQ,SAAS;GACjB,UAAU,SAAS;GACnB,cAAc,SAAS;GACvB,MAAM,SAAS;GAChB,CAAC;IAID;EAAC;EArCe,cAAc;AAC/B,OAAI,CAAC,QAAS,QAAO;AACrB,UAAO,KAAK,UAAU;IACpB,QAAQ,QAAQ;IAChB,UAAU,QAAQ;IAClB,cAAc,QAAQ;IACvB,CAAC;KACD;GAAC,SAAS;GAAQ,SAAS;GAAU,SAAS;GAAa,CAAC;EA8BzC,SAAS;EAAK,CAAC"}
1
+ {"version":3,"file":"useProperty.js","names":[],"sources":["../../src/hooks/useProperty.ts"],"sourcesContent":["import { getGlobalInjector } from '../config';\nimport { getStyleTarget, pushRSCCSS } from '../rsc-cache';\nimport { formatPropertyCSS } from '../ssr/format-property';\n\nexport interface UsePropertyOptions {\n /**\n * CSS syntax string for the property (e.g., '<color>', '<length>', '<angle>').\n * For color tokens (#name), this is auto-set to '<color>' and cannot be overridden.\n * @see https://developer.mozilla.org/en-US/docs/Web/CSS/@property/syntax\n */\n syntax?: string;\n /**\n * Whether the property inherits from parent elements\n * @default true\n */\n inherits?: boolean;\n /**\n * Initial value for the property.\n * For color tokens (#name), this defaults to 'transparent' if not specified.\n */\n initialValue?: string | number;\n /**\n * Shadow root or document to inject into\n */\n root?: Document | ShadowRoot;\n}\n\n/**\n * Register a CSS @property custom property.\n * This enables advanced features like animating custom properties.\n *\n * Note: @property rules are global and persistent once defined.\n * The function ensures the property is only registered once per root.\n *\n * Accepts tasty token syntax for the property name:\n * - `$name` → defines `--name`\n * - `#name` → defines `--name-color` (auto-sets syntax: '<color>', defaults initialValue: 'transparent')\n * - `--name` → defines `--name` (legacy format)\n *\n * Works in all environments: client, SSR with collector, and React Server Components.\n *\n * @param name - The property token ($name, #name) or CSS property name (--name)\n * @param options - Property configuration\n *\n * @example Basic property with token syntax\n * ```tsx\n * function Spinner() {\n * useProperty('$rotation', {\n * syntax: '<angle>',\n * inherits: false,\n * initialValue: '0deg',\n * });\n *\n * return <div className=\"spinner\" />;\n * }\n * ```\n *\n * @example Color property with token syntax (auto-sets syntax)\n * ```tsx\n * function MyComponent() {\n * useProperty('#theme', {\n * initialValue: 'red', // syntax: '<color>' is auto-set\n * });\n *\n * // Now --theme-color can be animated with CSS transitions\n * return <div style={{ '--theme-color': 'blue' } as React.CSSProperties}>Colored</div>;\n * }\n * ```\n *\n * @example Legacy format (still supported)\n * ```tsx\n * function ResizableBox() {\n * useProperty('--box-size', {\n * syntax: '<length>',\n * initialValue: '100px',\n * });\n *\n * return <div style={{ width: 'var(--box-size)' }} />;\n * }\n * ```\n */\nexport function useProperty(name: string, options?: UsePropertyOptions): void {\n if (!name) {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(`[Tasty] useProperty: property name is required`);\n }\n return;\n }\n\n const target = getStyleTarget();\n\n if (target.mode === 'ssr') {\n target.collector.collectInternals();\n\n const css = formatPropertyCSS(name, {\n syntax: options?.syntax,\n inherits: options?.inherits,\n initialValue: options?.initialValue,\n });\n if (css) {\n target.collector.collectProperty(name, css);\n }\n return;\n }\n\n if (target.mode === 'rsc') {\n const css = formatPropertyCSS(name, {\n syntax: options?.syntax,\n inherits: options?.inherits,\n initialValue: options?.initialValue,\n });\n if (css) {\n pushRSCCSS(target.cache, `__prop:${name}`, css);\n }\n return;\n }\n\n const injector = getGlobalInjector();\n\n if (injector.isPropertyDefined(name, { root: options?.root })) {\n return;\n }\n\n injector.property(name, {\n syntax: options?.syntax,\n inherits: options?.inherits,\n initialValue: options?.initialValue,\n root: options?.root,\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiFA,SAAgB,YAAY,MAAc,SAAoC;AAC5E,KAAI,CAAC,MAAM;AAEP,UAAQ,KAAK,iDAAiD;AAEhE;;CAGF,MAAM,SAAS,gBAAgB;AAE/B,KAAI,OAAO,SAAS,OAAO;AACzB,SAAO,UAAU,kBAAkB;EAEnC,MAAM,MAAM,kBAAkB,MAAM;GAClC,QAAQ,SAAS;GACjB,UAAU,SAAS;GACnB,cAAc,SAAS;GACxB,CAAC;AACF,MAAI,IACF,QAAO,UAAU,gBAAgB,MAAM,IAAI;AAE7C;;AAGF,KAAI,OAAO,SAAS,OAAO;EACzB,MAAM,MAAM,kBAAkB,MAAM;GAClC,QAAQ,SAAS;GACjB,UAAU,SAAS;GACnB,cAAc,SAAS;GACxB,CAAC;AACF,MAAI,IACF,YAAW,OAAO,OAAO,UAAU,QAAQ,IAAI;AAEjD;;CAGF,MAAM,WAAW,mBAAmB;AAEpC,KAAI,SAAS,kBAAkB,MAAM,EAAE,MAAM,SAAS,MAAM,CAAC,CAC3D;AAGF,UAAS,SAAS,MAAM;EACtB,QAAQ,SAAS;EACjB,UAAU,SAAS;EACnB,cAAc,SAAS;EACvB,MAAM,SAAS;EAChB,CAAC"}
@@ -1,51 +1,20 @@
1
1
  //#region src/hooks/useRawCSS.d.ts
2
2
  interface UseRawCSSOptions {
3
+ /**
4
+ * Shadow root or document to inject into.
5
+ * Note: `root` is not part of the update-tracking comparison — changing
6
+ * only the root for the same id/content will not re-inject.
7
+ */
3
8
  root?: Document | ShadowRoot;
9
+ /**
10
+ * Stable identifier for update tracking (client-only). When provided,
11
+ * changing the CSS content will dispose the previous injection and inject
12
+ * the new one. Without an id, deduplication is purely content-based (same
13
+ * CSS is injected only once). In RSC mode, renders are single-pass so
14
+ * update tracking does not apply.
15
+ */
16
+ id?: string;
4
17
  }
5
- /**
6
- * Hook to inject raw CSS text directly without parsing.
7
- * This is a low-overhead alternative for injecting global CSS that doesn't need tasty processing.
8
- *
9
- * The CSS is inserted into a separate style element (data-tasty-raw) to avoid conflicts
10
- * with tasty's chunked style sheets.
11
- *
12
- * @example Static CSS string
13
- * ```tsx
14
- * function GlobalStyles() {
15
- * useRawCSS(`
16
- * body {
17
- * margin: 0;
18
- * padding: 0;
19
- * font-family: sans-serif;
20
- * }
21
- * `);
22
- *
23
- * return null;
24
- * }
25
- * ```
26
- *
27
- * @example Factory function with dependencies (like useMemo)
28
- * ```tsx
29
- * function ThemeStyles({ theme }: { theme: 'light' | 'dark' }) {
30
- * useRawCSS(() => `
31
- * :root {
32
- * --bg-color: ${theme === 'dark' ? '#1a1a1a' : '#ffffff'};
33
- * --text-color: ${theme === 'dark' ? '#ffffff' : '#1a1a1a'};
34
- * }
35
- * `, [theme]);
36
- *
37
- * return null;
38
- * }
39
- * ```
40
- *
41
- * @example With options
42
- * ```tsx
43
- * function ShadowStyles({ shadowRoot }) {
44
- * useRawCSS(() => `.scoped { color: red; }`, [], { root: shadowRoot });
45
- * return null;
46
- * }
47
- * ```
48
- */
49
18
  declare function useRawCSS(css: string, options?: UseRawCSSOptions): void;
50
19
  declare function useRawCSS(factory: () => string, deps: readonly unknown[], options?: UseRawCSSOptions): void;
51
20
  //#endregion
@@ -1,28 +1,103 @@
1
+ import { hashString } from "../utils/hash.js";
1
2
  import { injectRawCSS } from "../injector/index.js";
2
- import { useInsertionEffect, useMemo, useRef } from "react";
3
-
3
+ import { getStyleTarget, pushRSCCSS } from "../rsc-cache.js";
4
+ import { depsEqual } from "../utils/deps-equal.js";
4
5
  //#region src/hooks/useRawCSS.ts
6
+ const clientEntries = /* @__PURE__ */ new Map();
7
+ const clientContentDedup = /* @__PURE__ */ new Set();
8
+ const factoryDepsCache = /* @__PURE__ */ new Map();
9
+ /**
10
+ * Inject raw CSS text directly without parsing.
11
+ * This is a low-overhead alternative for injecting global CSS that doesn't need tasty processing.
12
+ *
13
+ * The CSS is inserted into a separate style element (data-tasty-raw) to avoid conflicts
14
+ * with tasty's chunked style sheets.
15
+ *
16
+ * Works in all environments: client, SSR with collector, and React Server Components.
17
+ *
18
+ * Injected styles are permanent — they are not cleaned up on component unmount.
19
+ * Use the `id` option for update tracking when styles change over the
20
+ * component lifecycle.
21
+ *
22
+ * @example Static CSS string
23
+ * ```tsx
24
+ * function GlobalStyles() {
25
+ * useRawCSS(`
26
+ * body {
27
+ * margin: 0;
28
+ * padding: 0;
29
+ * font-family: sans-serif;
30
+ * }
31
+ * `);
32
+ *
33
+ * return null;
34
+ * }
35
+ * ```
36
+ *
37
+ * @example Factory function with dependencies
38
+ * ```tsx
39
+ * function ThemeStyles({ theme }: { theme: 'light' | 'dark' }) {
40
+ * useRawCSS(() => `
41
+ * :root {
42
+ * --bg-color: ${theme === 'dark' ? '#1a1a1a' : '#ffffff'};
43
+ * --text-color: ${theme === 'dark' ? '#ffffff' : '#1a1a1a'};
44
+ * }
45
+ * `, [theme], { id: 'theme-vars' });
46
+ *
47
+ * return null;
48
+ * }
49
+ * ```
50
+ *
51
+ * @example With options
52
+ * ```tsx
53
+ * function ShadowStyles({ shadowRoot }) {
54
+ * useRawCSS(() => `.scoped { color: red; }`, [], { root: shadowRoot });
55
+ * return null;
56
+ * }
57
+ * ```
58
+ */
5
59
  function useRawCSS(cssOrFactory, depsOrOptions, options) {
6
60
  const isFactory = typeof cssOrFactory === "function";
7
61
  const deps = isFactory && Array.isArray(depsOrOptions) ? depsOrOptions : void 0;
8
62
  const opts = isFactory ? options : depsOrOptions;
9
- const css = useMemo(() => isFactory ? cssOrFactory() : cssOrFactory, isFactory ? deps ?? [] : [cssOrFactory]);
10
- const disposeRef = useRef(null);
11
- useInsertionEffect(() => {
12
- disposeRef.current?.();
13
- if (!css.trim()) {
14
- disposeRef.current = null;
15
- return;
63
+ const target = getStyleTarget();
64
+ if (isFactory && deps && opts?.id && target.mode === "client") {
65
+ const cachedDeps = factoryDepsCache.get(opts.id);
66
+ if (cachedDeps && depsEqual(cachedDeps, deps)) return;
67
+ }
68
+ const css = isFactory ? cssOrFactory() : cssOrFactory;
69
+ if (!css.trim()) return;
70
+ if (target.mode === "ssr") {
71
+ const key = opts?.id ? `raw:${opts.id}` : `raw:${hashString(css)}`;
72
+ target.collector.collectRawCSS(key, css);
73
+ return;
74
+ }
75
+ if (target.mode === "rsc") {
76
+ const key = opts?.id ? `__raw:${opts.id}` : `__raw:${hashString(css)}`;
77
+ pushRSCCSS(target.cache, key, css);
78
+ return;
79
+ }
80
+ const id = opts?.id;
81
+ if (id) {
82
+ const existing = clientEntries.get(id);
83
+ if (existing) {
84
+ if (existing.contentKey === css) return;
85
+ existing.dispose();
16
86
  }
17
87
  const { dispose } = injectRawCSS(css, opts);
18
- disposeRef.current = dispose;
19
- return () => {
20
- disposeRef.current?.();
21
- disposeRef.current = null;
22
- };
23
- }, [css, opts?.root]);
88
+ clientEntries.set(id, {
89
+ contentKey: css,
90
+ dispose
91
+ });
92
+ if (deps) factoryDepsCache.set(id, deps);
93
+ } else {
94
+ const contentKey = hashString(css);
95
+ if (clientContentDedup.has(contentKey)) return;
96
+ clientContentDedup.add(contentKey);
97
+ injectRawCSS(css, opts);
98
+ }
24
99
  }
25
-
26
100
  //#endregion
27
101
  export { useRawCSS };
102
+
28
103
  //# sourceMappingURL=useRawCSS.js.map