@csszyx/dynamic 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts DELETED
@@ -1,122 +0,0 @@
1
- /**
2
- * @csszyx/dynamic — Runtime CSS injection with delta check.
3
- *
4
- * Converts sz objects to Tailwind class strings at runtime, injecting CSS
5
- * only for classes not already present in the pre-built stylesheet.
6
- *
7
- * Architecture:
8
- * Layer 1 (manifest): Lazy-fetched JSON of all classes in built CSS.
9
- * Layer 2 (generator): Tailwind class → CSS rule using v4 CSS var patterns.
10
- * Layer 3 (injector): 21-tier CSSStyleSheet for correct breakpoint cascade.
11
- *
12
- * SSR-safe: on server, returns class names only — no CSSOM access.
13
- *
14
- * @example
15
- * // Framework-agnostic usage
16
- * import { dynamic, preloadManifest } from '@csszyx/dynamic';
17
- *
18
- * await preloadManifest('/csszyx-manifest.json'); // optional: eagerly load
19
- * const cls = dynamic({ p: 4, bg: 'blue-500' }); // → "p-4 bg-blue-500"
20
- */
21
-
22
- // Import only the browser-safe (pure JS, no WASM) transform sub-path.
23
- // The main '@csszyx/compiler' entry bundles @csszyx/core (WASM) which cannot
24
- // run in browsers without a dedicated WASM loader.
25
- import type { ReadonlySzObject, SzObject } from '@csszyx/compiler/browser';
26
- import { transform } from '@csszyx/compiler/browser';
27
-
28
- import { generateCSSRule } from './css-generator.js';
29
- import { cleanup as injectorCleanup, injectRule, resolveTier } from './injector.js';
30
- import { ensureManifest, lookupManifest, preloadManifest as manifestPreload, resetManifest } from './manifest.js';
31
- import { isServer } from './ssr.js';
32
-
33
- export type { Tier } from './css-generator.js';
34
- export type { CSSManifest } from './manifest.js';
35
- export type { ReadonlySzObject, SzObject } from '@csszyx/compiler/browser';
36
-
37
- /**
38
- * Transforms sz props at runtime and injects CSS only for classes not already
39
- * in the pre-built stylesheet. Lazy-loads manifest on first call.
40
- *
41
- * SSR-safe: on server, returns class names without CSSOM access.
42
- *
43
- * @param szProps - sz object (e.g. { p: 4, bg: 'blue-500', hover: { bg: 'blue-600' } }). Accepts both mutable and `as const` objects.
44
- * @returns space-separated class string (e.g. "p-4 bg-blue-500 hover:bg-blue-600")
45
- *
46
- * @example
47
- * const cls = dynamic({ p: 4, bg: 'blue-500' });
48
- * // → "p-4 bg-blue-500" (injects CSS for classes not in built stylesheet)
49
- */
50
- export function dynamic(szProps: SzObject | ReadonlySzObject): string {
51
- const { className } = transform(szProps as SzObject);
52
- if (!className) {return '';}
53
-
54
- if (isServer) {
55
- // SSR: apply the mangle map if the Vite plugin exposed it via globalThis.
56
- // The plugin sets __csszyx_ssr_mangle_map in buildEnd(), which runs before
57
- // SSG rendering (Astro, Next.js) in the same Node.js process. Without this,
58
- // dynamic() returns unmangled names (e.g. "p-4") while built CSS only has
59
- // mangled selectors (e.g. ".q0"), so styles silently don't apply in SSR HTML.
60
- const ssrMangleMap = (globalThis as Record<string, unknown>).__csszyx_ssr_mangle_map as Record<string, string> | undefined;
61
- if (ssrMangleMap) {
62
- return className.split(' ').filter(Boolean)
63
- .map(c => ssrMangleMap[c] ?? c)
64
- .join(' ');
65
- }
66
- return className;
67
- }
68
-
69
- // Trigger manifest lazy-load (non-blocking — first render uses whatever is loaded)
70
- ensureManifest();
71
-
72
- const classes = className.split(' ').filter(Boolean);
73
- const outputClasses: string[] = [];
74
-
75
- for (const cls of classes) {
76
- const manifestResult = lookupManifest(cls);
77
-
78
- if (manifestResult !== null) {
79
- // Class is in built CSS. Use the resolved name (mangled in production).
80
- outputClasses.push(manifestResult);
81
- } else {
82
- // Class is not in built CSS — inject CSS rule.
83
- const tier = resolveTier(cls);
84
- const rule = generateCSSRule(cls);
85
- injectRule(cls, rule, tier);
86
- outputClasses.push(cls);
87
- }
88
- }
89
-
90
- return outputClasses.join(' ');
91
- }
92
-
93
- /**
94
- * Eagerly preloads the CSS manifest.
95
- * Without this, the manifest is lazy-loaded on the first dynamic() call.
96
- * Call at app startup or on route enter for zero-latency first inject.
97
- *
98
- * @param url - manifest URL (default: '/csszyx-manifest.json')
99
- * @returns promise that resolves when the manifest has been fetched and cached
100
- * @example
101
- * await preloadManifest('/csszyx-manifest.json');
102
- */
103
- export async function preloadManifest(url?: string): Promise<void> {
104
- return manifestPreload(url);
105
- }
106
-
107
- /**
108
- * Releases all CSSStyleSheet tiers from adoptedStyleSheets, clears the
109
- * injected class cache, and resets the manifest reference.
110
- *
111
- * Call on route unmount when the dynamic-styled component tree is destroyed.
112
- * Next dynamic() call will re-fetch the manifest and re-init sheets.
113
- *
114
- * @example
115
- * useEffect(() => {
116
- * return () => cleanup();
117
- * }, []);
118
- */
119
- export function cleanup(): void {
120
- injectorCleanup();
121
- resetManifest();
122
- }
package/src/injector.ts DELETED
@@ -1,226 +0,0 @@
1
- /**
2
- * CSSOM Injection with 21-tier CSSStyleSheet architecture.
3
- *
4
- * WHY 21 SHEETS:
5
- * Injecting all rules into a single sheet breaks cascade order.
6
- * If a linter sorts object keys alphabetically (lg, md, sm),
7
- * sm:p-4 could end up injected AFTER lg:p-8 — at 640px both @media
8
- * rules match and the later-injected sm rule incorrectly wins.
9
- *
10
- * Fix: One CSSStyleSheet per breakpoint tier. The `adoptedStyleSheets`
11
- * array ORDER determines cascade — not insertion time. Sheets are created
12
- * once in the correct ascending order and never reordered.
13
- *
14
- * CSS @layer collision note:
15
- * Tailwind v4 utilities are in `@layer utilities`. Rules injected via
16
- * CSSStyleSheet.insertRule() are unlayered — unlayered beats ALL layered
17
- * styles at equal specificity (CSS Cascade Level 5). This is intentional:
18
- * - Classes in manifest → delta check skips injection → no conflict
19
- * - Truly new classes → not in built CSS → no conflict
20
- * - Stale manifest → injected rule overrides built rule (same CSS value)
21
- */
22
-
23
- import { BREAKPOINTS, type Tier } from './css-generator.js';
24
-
25
- // ── Tier ordering ─────────────────────────────────────────────────────────────
26
-
27
- /**
28
- * 21 tiers in cascade order (index = priority, higher = wins).
29
- *
30
- * max-* tiers are DESCENDING by breakpoint size (opposite of min-*).
31
- * At a small viewport (e.g. 400px), ALL max-* variants match.
32
- * The most restrictive (max-sm = ≤640px) must win → must appear LAST.
33
- * Example at 400px: max-2xl, max-xl, max-lg, max-md, max-sm all match.
34
- * max-sm should win → index(max-sm) > index(max-md) > ... > index(max-2xl).
35
- */
36
- export const TIER_ORDER: readonly Tier[] = [
37
- 'base',
38
- // viewport min-width (ascending — sm wins over base, lg wins over md)
39
- 'sm', 'md', 'lg', 'xl', '2xl',
40
- // viewport max-width (descending size — max-sm is most restrictive, must win)
41
- 'max-2xl', 'max-xl', 'max-lg', 'max-md', 'max-sm',
42
- // container query min (ascending)
43
- '@sm', '@md', '@lg', '@xl', '@2xl',
44
- // container query max (descending)
45
- '@max-2xl', '@max-xl', '@max-lg', '@max-md', '@max-sm',
46
- ] as const;
47
-
48
- const TIER_SET = new Set<string>(TIER_ORDER);
49
-
50
- /**
51
- * Returns true if constructable stylesheets are available in the current environment.
52
- *
53
- * @returns true when CSSStyleSheet and adoptedStyleSheets are both supported
54
- */
55
- function supportsConstructableSheets(): boolean {
56
- return typeof document !== 'undefined' &&
57
- typeof CSSStyleSheet !== 'undefined' &&
58
- 'adoptedStyleSheets' in document;
59
- }
60
-
61
- // ── Module state ──────────────────────────────────────────────────────────────
62
-
63
- let sheets: Map<Tier, CSSStyleSheet> | null = null;
64
-
65
- /** StyleElement fallback for environments without constructable stylesheets. */
66
- let fallbackStyle: HTMLStyleElement | null = null;
67
-
68
- /** Set of all class names injected this session (prevents double inject). */
69
- const injected = new Set<string>();
70
-
71
- // ── Sheet initialisation ──────────────────────────────────────────────────────
72
-
73
- /**
74
- * Wraps a CSS rule in the appropriate @media or @container query for non-base tiers.
75
- * Base sheet holds all rules directly (no wrapper).
76
- *
77
- * @param cssRule - raw CSS rule string to wrap (e.g. ".p-4 { padding: 1rem }")
78
- * @param tier - breakpoint tier that determines the wrapping query type
79
- * @returns wrapped CSS string (e.g. "@media (min-width: 40rem) { .p-4 { padding: 1rem } }")
80
- */
81
- function wrapForTier(cssRule: string, tier: Tier): string {
82
- if (tier === 'base') {return cssRule;}
83
-
84
- // Container query tiers: @sm, @md, @lg, @xl, @2xl, @max-*
85
- if (tier.startsWith('@')) {
86
- const bp = tier.slice(1); // '@sm' → 'sm', '@max-sm' → 'max-sm'
87
- const bpValue = BREAKPOINTS[bp];
88
- if (!bpValue) {return cssRule;}
89
- const query = bp.startsWith('max-')
90
- ? `(width <= ${bpValue})`
91
- : `(width >= ${bpValue})`;
92
- return `@container ${query} { ${cssRule} }`;
93
- }
94
-
95
- // Viewport tiers: sm, md, lg, xl, 2xl, max-sm, max-md, ...
96
- const bpValue = BREAKPOINTS[tier];
97
- if (!bpValue) {return cssRule;}
98
-
99
- const query = tier.startsWith('max-')
100
- ? `(max-width: ${bpValue})`
101
- : `(min-width: ${bpValue})`;
102
- return `@media ${query} { ${cssRule} }`;
103
- }
104
-
105
- /**
106
- * Initialises one CSSStyleSheet per tier and appends them to adoptedStyleSheets in cascade order.
107
- *
108
- * @returns map from each Tier to its dedicated CSSStyleSheet
109
- */
110
- function initSheets(): Map<Tier, CSSStyleSheet> {
111
- const map = new Map<Tier, CSSStyleSheet>();
112
- const adopted: CSSStyleSheet[] = [];
113
-
114
- for (const tier of TIER_ORDER) {
115
- const sheet = new CSSStyleSheet();
116
- map.set(tier, sheet);
117
- adopted.push(sheet);
118
- }
119
-
120
- // Append to end — does NOT replace existing adoptedStyleSheets.
121
- document.adoptedStyleSheets = [...document.adoptedStyleSheets, ...adopted];
122
- return map;
123
- }
124
-
125
- /**
126
- * Creates and appends a fallback <style> element for environments without constructable stylesheets.
127
- *
128
- * @returns the newly created and appended HTMLStyleElement
129
- */
130
- function initFallbackStyle(): HTMLStyleElement {
131
- const el = document.createElement('style');
132
- el.id = '__csszyx_dynamic__';
133
- document.head.appendChild(el);
134
- return el;
135
- }
136
-
137
- // ── Tier resolution ───────────────────────────────────────────────────────────
138
-
139
- /**
140
- * Resolves which tier a class name belongs to based on its first variant segment.
141
- * Stacked variants: sm:hover:bg-blue → first segment = 'sm' → tier = sm.
142
- * dark:sm:bg-blue is invalid (dark is not a breakpoint) → tier = base.
143
- *
144
- * @param className - full Tailwind class name (e.g. "sm:hover:bg-blue-500")
145
- * @returns the breakpoint tier for this class, or 'base' if no breakpoint prefix is found
146
- */
147
- export function resolveTier(className: string): Tier {
148
- const colonIdx = className.indexOf(':');
149
- if (colonIdx === -1) {return 'base';}
150
-
151
- const prefix = className.slice(0, colonIdx);
152
- // Container query variants start with @
153
- const normalizedPrefix = prefix.startsWith('@') ? prefix : prefix;
154
-
155
- if (TIER_SET.has(normalizedPrefix)) {
156
- return normalizedPrefix as Tier;
157
- }
158
- return 'base';
159
- }
160
-
161
- // ── Public API ────────────────────────────────────────────────────────────────
162
-
163
- /**
164
- * Injects a CSS rule if the class has not already been injected this session.
165
- * No-op if the class is already known (delta check must be done by caller via manifest).
166
- *
167
- * @param className - original Tailwind class name (used as cache key)
168
- * @param cssRule - CSS rule string returned by generateCSSRule()
169
- * @param tier - which tier sheet to inject into
170
- */
171
- export function injectRule(className: string, cssRule: string, tier: Tier = 'base'): void {
172
- if (injected.has(className)) {return;}
173
- injected.add(className);
174
-
175
- if (!cssRule) {return;} // unknown class — still mark as "seen" to avoid repeat lookups
176
-
177
- if (supportsConstructableSheets()) {
178
- if (!sheets) {sheets = initSheets();}
179
- const resolved = sheets.get(tier) ?? sheets.get('base');
180
- if (!resolved) {return;}
181
- const sheet = resolved;
182
- const rule = wrapForTier(cssRule, tier);
183
- try {
184
- sheet.insertRule(rule, sheet.cssRules.length);
185
- } catch {
186
- // Malformed rule — silently ignore; class is still marked injected.
187
- }
188
- } else {
189
- // Fallback: append to a <style> element
190
- if (!fallbackStyle) {fallbackStyle = initFallbackStyle();}
191
- const rule = wrapForTier(cssRule, tier);
192
- fallbackStyle.sheet?.insertRule(rule, fallbackStyle.sheet.cssRules.length);
193
- }
194
- }
195
-
196
- /**
197
- * Checks whether a class name has already been injected this session.
198
- * Used to skip repeated calls for the same class within a render cycle.
199
- *
200
- * @param className - original Tailwind class name to check (e.g. "p-4")
201
- * @returns true if the class has already been injected in the current session
202
- */
203
- export function isInjected(className: string): boolean {
204
- return injected.has(className);
205
- }
206
-
207
- /**
208
- * Releases all tier sheets and clears injected state.
209
- * Call on route unmount / component cleanup.
210
- */
211
- export function cleanup(): void {
212
- if (sheets) {
213
- const tierSheets = new Set(sheets.values());
214
- document.adoptedStyleSheets = document.adoptedStyleSheets.filter(
215
- s => !tierSheets.has(s),
216
- );
217
- sheets = null;
218
- }
219
-
220
- if (fallbackStyle) {
221
- fallbackStyle.remove();
222
- fallbackStyle = null;
223
- }
224
-
225
- injected.clear();
226
- }
package/src/manifest.ts DELETED
@@ -1,119 +0,0 @@
1
- /**
2
- * Manifest loading and caching.
3
- *
4
- * The manifest is a JSON file generated at build time by @csszyx/unplugin.
5
- * It lists all original class names present in the built CSS so runtime
6
- * dynamic() can skip injection for classes already covered.
7
- *
8
- * The manifest is fetched lazily on first dynamic() call (Qwik surgical pattern).
9
- * Never inlined into HTML — each page only pays the cost if dynamic() is used.
10
- */
11
-
12
- /**
13
- *
14
- */
15
- export interface CSSManifest {
16
- version: string;
17
- buildId: string;
18
- /** Original (non-mangled) class names present in built CSS. */
19
- classes: string[];
20
- /** Original → mangled map. Present only when production mangling is enabled. */
21
- mangleMap?: Record<string, string>;
22
- }
23
-
24
- // Module-level state (lazy, reset by cleanup())
25
- let manifestClasses: Set<string> | null = null;
26
- let mangleMap: Record<string, string> | null = null;
27
- let manifestUrl = '/csszyx-manifest.json';
28
- let fetchPromise: Promise<void> | null = null;
29
-
30
- /**
31
- * Override the manifest URL (called by CsszyxProvider).
32
- *
33
- * @param url - absolute or relative URL to the csszyx manifest JSON file
34
- */
35
- export function setManifestUrl(url: string): void {
36
- manifestUrl = url;
37
- }
38
-
39
- /**
40
- * Returns true if the manifest has been loaded.
41
- *
42
- * @returns true once the manifest fetch has completed (even on failure)
43
- */
44
- export function isManifestLoaded(): boolean {
45
- return manifestClasses !== null;
46
- }
47
-
48
- /**
49
- * Lazily fetches and caches the manifest.
50
- * Returns the in-flight promise so concurrent calls coalesce.
51
- *
52
- * @returns promise that resolves when the manifest is loaded (or silently fails)
53
- */
54
- export function ensureManifest(): Promise<void> {
55
- if (manifestClasses !== null) {return Promise.resolve();}
56
- if (fetchPromise) {return fetchPromise;}
57
-
58
- fetchPromise = fetch(manifestUrl)
59
- .then(r => {
60
- if (!r.ok) {throw new Error(`csszyx: manifest fetch failed ${r.status}`);}
61
- return r.json() as Promise<CSSManifest>;
62
- })
63
- .then((data: CSSManifest) => {
64
- manifestClasses = new Set(data.classes);
65
- mangleMap = data.mangleMap ?? null;
66
- })
67
- .catch(() => {
68
- // Non-blocking: if manifest unavailable, all classes are treated as new.
69
- manifestClasses = new Set();
70
- mangleMap = null;
71
- fetchPromise = null; // allow retry
72
- });
73
-
74
- return fetchPromise;
75
- }
76
-
77
- /**
78
- * Eagerly preloads the manifest. Call at app startup for zero-latency first inject.
79
- * Without this, the first dynamic() call triggers a lazy fetch.
80
- *
81
- * @param url - optional manifest URL override (default: '/csszyx-manifest.json')
82
- * @returns promise that resolves when the manifest has been fetched and cached
83
- */
84
- export async function preloadManifest(url?: string): Promise<void> {
85
- if (url) {setManifestUrl(url);}
86
- return ensureManifest();
87
- }
88
-
89
- /**
90
- * Returns the CSS class name to use for a given original class name.
91
- *
92
- * - If manifest says the class is pre-built AND mangle map exists: returns mangled name.
93
- * - If manifest says the class is pre-built, no mangle map: returns original name.
94
- * - If class is NOT in manifest (or manifest not yet loaded): returns null (inject needed).
95
- *
96
- * @param originalClass - the original (non-mangled) Tailwind class name to look up
97
- * @returns the class name to use in the DOM (mangled or original), or null if injection is needed
98
- */
99
- export function lookupManifest(originalClass: string): string | null {
100
- if (manifestClasses === null) {return null;} // not loaded yet
101
- if (!manifestClasses.has(originalClass)) {return null;} // not in built CSS
102
-
103
- // Class is in built CSS
104
- if (mangleMap && originalClass in mangleMap) {
105
- return mangleMap[originalClass]; // return mangled name
106
- }
107
- return originalClass; // non-mangled build
108
- }
109
-
110
- /**
111
- * Releases all manifest state. Called by cleanup().
112
- * Next dynamic() call will re-fetch the manifest.
113
- */
114
- export function resetManifest(): void {
115
- manifestClasses = null;
116
- mangleMap = null;
117
- fetchPromise = null;
118
- manifestUrl = '/csszyx-manifest.json';
119
- }
package/src/react.ts DELETED
@@ -1,166 +0,0 @@
1
- /**
2
- * React integration for @csszyx/dynamic.
3
- *
4
- * Provides:
5
- * - sz: direct alias for dynamic() (cleaner import in React files)
6
- * - useSz(): hook returning stable { sz } object with cleanup on unmount
7
- * - CsszyxProvider: optional context for custom manifest URL
8
- *
9
- * @example
10
- * // Simple usage
11
- * import { sz } from '@csszyx/dynamic/react';
12
- * const cls = sz({ p: 4, bg: 'blue-500' });
13
- *
14
- * @example
15
- * // Hook usage (recommended — handles manifest + cleanup automatically)
16
- * import { useSz } from '@csszyx/dynamic/react';
17
- * function FormField({ schema }) {
18
- * const { sz } = useSz();
19
- * return <div className={sz(schema.style)}>{schema.label}</div>;
20
- * }
21
- *
22
- * @example
23
- * // Custom manifest URL
24
- * import { CsszyxProvider } from '@csszyx/dynamic/react';
25
- * <CsszyxProvider manifest="/assets/csszyx-manifest.json">
26
- * <App />
27
- * </CsszyxProvider>
28
- */
29
-
30
- import type { SzObject } from '@csszyx/compiler/browser';
31
- import {
32
- createContext,
33
- createElement,
34
- type ReactElement,
35
- type ReactNode,
36
- useCallback,
37
- useContext,
38
- useEffect,
39
- } from 'react';
40
-
41
- import { dynamic } from './index.js';
42
- import { cleanup as injectorCleanup } from './injector.js';
43
- import { preloadManifest, resetManifest, setManifestUrl } from './manifest.js';
44
-
45
- // ── Context ───────────────────────────────────────────────────────────────────
46
-
47
- /**
48
- *
49
- */
50
- interface CsszyxContextValue {
51
- manifestUrl: string;
52
- }
53
-
54
- const CsszyxContext = createContext<CsszyxContextValue>({
55
- manifestUrl: '/csszyx-manifest.json',
56
- });
57
-
58
- // ── sz alias ──────────────────────────────────────────────────────────────────
59
-
60
- /**
61
- * Runtime alias for dynamic(). Use in React files for consistency with
62
- * the build-time `sz` prop: `sz={{ p: 4 }}` at build, `sz({ p: 4 })` at runtime.
63
- */
64
- export const sz = dynamic;
65
-
66
- // ── useSz hook ────────────────────────────────────────────────────────────────
67
-
68
- /**
69
- *
70
- */
71
- export interface UseSzReturn {
72
- /**
73
- * Converts sz props to a class string, injecting CSS for new classes.
74
- * Stable reference — safe to pass as a prop without useMemo.
75
- */
76
- sz: (props: SzObject) => string;
77
- }
78
-
79
- /**
80
- * Deferred cleanup timer — shared across all useSz instances.
81
- *
82
- * WHY DEFERRED: React 18 StrictMode fires useEffect cleanup immediately after
83
- * the initial mount (simulating unmount → remount to catch side-effect bugs).
84
- * Without deferral, the first mount injects CSS → StrictMode cleanup fires →
85
- * CSS is wiped → no re-render → component renders with no styles.
86
- *
87
- * With setTimeout(0): StrictMode's cleanup schedules removal, then StrictMode's
88
- * remount runs synchronously and cancels the timer before it fires. On a true
89
- * navigation away, no remount happens, so the timer fires and cleans up.
90
- */
91
- let _cleanupTimer: ReturnType<typeof setTimeout> | null = null;
92
-
93
- /**
94
- * React hook for runtime dynamic styling.
95
- *
96
- * Returns a stable `{ sz }` object. Preloads the manifest on mount.
97
- * On unmount, releases CSSStyleSheet tiers and manifest cache — deferred
98
- * to survive React 18 StrictMode's double-mount/unmount cycle.
99
- *
100
- * @returns stable object containing the sz function for converting sz props to class strings
101
- * @example
102
- * const { sz } = useSz();
103
- * return <div className={sz(apiResponse.field.style)} />;
104
- */
105
- export function useSz(): UseSzReturn {
106
- const { manifestUrl } = useContext(CsszyxContext);
107
- const stableSz = useCallback((props: SzObject) => dynamic(props), []);
108
-
109
- useEffect(() => {
110
- // Cancel any pending cleanup from a previous unmount (handles StrictMode remount).
111
- if (_cleanupTimer !== null) {
112
- clearTimeout(_cleanupTimer);
113
- _cleanupTimer = null;
114
- }
115
- // Pre-fetch manifest (non-blocking)
116
- preloadManifest(manifestUrl);
117
-
118
- return () => {
119
- // Defer cleanup so a StrictMode remount can cancel it before it fires.
120
- _cleanupTimer = setTimeout(() => {
121
- injectorCleanup();
122
- resetManifest();
123
- _cleanupTimer = null;
124
- }, 0);
125
- };
126
- }, [manifestUrl]);
127
-
128
- return { sz: stableSz };
129
- }
130
-
131
- // ── CsszyxProvider ────────────────────────────────────────────────────────────
132
-
133
- /**
134
- *
135
- */
136
- interface CsszyxProviderProps {
137
- /** Custom manifest URL (default: '/csszyx-manifest.json'). */
138
- manifest: string;
139
- children: ReactNode;
140
- }
141
-
142
- /**
143
- * Optional React context for custom manifest path or non-root deployments.
144
- * Without Provider, dynamic() defaults to fetching /csszyx-manifest.json.
145
- *
146
- * @param props - component props
147
- * @param props.manifest - URL to the csszyx manifest JSON file
148
- * @param props.children - child React nodes to render within the provider
149
- * @returns React element wrapping children in a CsszyxContext.Provider
150
- * @example
151
- * <CsszyxProvider manifest="/api/assets/csszyx-manifest.json">
152
- * <App />
153
- * </CsszyxProvider>
154
- */
155
- export function CsszyxProvider({ manifest, children }: CsszyxProviderProps): ReactElement {
156
- useEffect(() => {
157
- setManifestUrl(manifest);
158
- preloadManifest(manifest);
159
- }, [manifest]);
160
-
161
- return createElement(
162
- CsszyxContext.Provider,
163
- { value: { manifestUrl: manifest } },
164
- children,
165
- );
166
- }
package/src/ssr.ts DELETED
@@ -1,5 +0,0 @@
1
- /**
2
- * SSR detection helper.
3
- * Used to guard CSSOM operations that are unavailable in Node.js.
4
- */
5
- export const isServer = typeof document === 'undefined';