stoop 0.2.1 → 0.3.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/dist/api/create-theme.js +43 -0
- package/dist/api/css.js +20 -0
- package/dist/api/global-css.d.ts +0 -11
- package/dist/api/global-css.js +89 -0
- package/dist/api/keyframes.js +95 -0
- package/dist/api/provider.d.ts +3 -3
- package/dist/api/provider.js +109 -0
- package/dist/api/styled.js +170 -0
- package/dist/api/use-theme.js +21 -0
- package/dist/constants.d.ts +2 -3
- package/dist/constants.js +144 -0
- package/dist/core/cache.d.ts +5 -9
- package/dist/core/cache.js +68 -0
- package/dist/core/compiler.js +198 -0
- package/dist/core/theme-manager.d.ts +0 -8
- package/dist/core/theme-manager.js +97 -0
- package/dist/core/variants.js +32 -0
- package/dist/create-stoop-server.d.ts +33 -0
- package/dist/create-stoop-server.js +130 -0
- package/dist/create-stoop.d.ts +2 -5
- package/dist/create-stoop.js +147 -0
- package/dist/index.js +5 -13
- package/dist/inject/browser.d.ts +2 -3
- package/dist/inject/browser.js +149 -0
- package/dist/inject/dedup.js +38 -0
- package/dist/inject/index.d.ts +0 -1
- package/dist/inject/index.js +75 -0
- package/dist/inject/ssr.d.ts +0 -1
- package/dist/inject/ssr.js +46 -0
- package/dist/ssr.d.ts +21 -0
- package/dist/ssr.js +19 -0
- package/dist/types/index.d.ts +10 -5
- package/dist/types/index.js +5 -0
- package/dist/types/react-polymorphic-types.d.ts +4 -8
- package/dist/utils/environment.d.ts +6 -0
- package/dist/utils/environment.js +12 -0
- package/dist/utils/string.d.ts +16 -9
- package/dist/utils/string.js +253 -0
- package/dist/utils/theme-map.d.ts +0 -3
- package/dist/utils/theme-map.js +97 -0
- package/dist/utils/theme-validation.js +36 -0
- package/dist/utils/theme.d.ts +3 -3
- package/dist/utils/theme.js +279 -0
- package/dist/utils/type-guards.js +38 -0
- package/dist/utils/utilities.js +43 -0
- package/package.json +24 -25
- package/LICENSE.md +0 -21
- package/README.md +0 -180
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-safe factory function that creates a Stoop instance.
|
|
3
|
+
* Only includes APIs that work without React: css, globalCss, keyframes, getCssText, etc.
|
|
4
|
+
* Does NOT include: styled, Provider, useTheme
|
|
5
|
+
*
|
|
6
|
+
* This is the entry point for Server Components in Next.js App Router.
|
|
7
|
+
*/
|
|
8
|
+
import { createTheme as createThemeFactory } from "./api/create-theme";
|
|
9
|
+
import { createCSSFunction } from "./api/css";
|
|
10
|
+
import { createGlobalCSSFunction } from "./api/global-css";
|
|
11
|
+
import { createKeyframesFunction } from "./api/keyframes";
|
|
12
|
+
import { DEFAULT_THEME_MAP } from "./constants";
|
|
13
|
+
import { compileCSS } from "./core/compiler";
|
|
14
|
+
import { registerDefaultTheme, updateThemeVariables } from "./core/theme-manager";
|
|
15
|
+
import { getCssText as getCssTextBase, registerTheme } from "./inject";
|
|
16
|
+
import { getRootRegex, sanitizePrefix } from "./utils/string";
|
|
17
|
+
import { generateCSSVariables } from "./utils/theme";
|
|
18
|
+
import { validateTheme } from "./utils/theme-validation";
|
|
19
|
+
/**
|
|
20
|
+
* Creates a server-safe Stoop instance with the provided configuration.
|
|
21
|
+
* Only includes APIs that work without React: css, globalCss, keyframes, getCssText, etc.
|
|
22
|
+
*
|
|
23
|
+
* @param config - Configuration object containing theme, media queries, utilities, and optional prefix/themeMap
|
|
24
|
+
* @returns StoopServerInstance with server-safe API functions
|
|
25
|
+
*/
|
|
26
|
+
export function createStoop(config) {
|
|
27
|
+
const { media: configMedia, prefix = "stoop", theme, themeMap: userThemeMap, utils } = config;
|
|
28
|
+
const sanitizedPrefix = sanitizePrefix(prefix);
|
|
29
|
+
const validatedTheme = validateTheme(theme);
|
|
30
|
+
const media = validatedTheme.media || configMedia;
|
|
31
|
+
const mergedThemeMap = {
|
|
32
|
+
...DEFAULT_THEME_MAP,
|
|
33
|
+
...userThemeMap,
|
|
34
|
+
};
|
|
35
|
+
registerDefaultTheme(validatedTheme, sanitizedPrefix);
|
|
36
|
+
registerTheme(validatedTheme, sanitizedPrefix);
|
|
37
|
+
const css = createCSSFunction(validatedTheme, sanitizedPrefix, media, utils, mergedThemeMap);
|
|
38
|
+
const createTheme = createThemeFactory(validatedTheme);
|
|
39
|
+
const globalCss = createGlobalCSSFunction(validatedTheme, sanitizedPrefix, media, utils, mergedThemeMap);
|
|
40
|
+
const keyframes = createKeyframesFunction(sanitizedPrefix, validatedTheme, mergedThemeMap);
|
|
41
|
+
const themeObject = Object.freeze({ ...validatedTheme });
|
|
42
|
+
/**
|
|
43
|
+
* Pre-compiles CSS objects to warm the cache.
|
|
44
|
+
* Useful for eliminating FOUC by pre-compiling common styles.
|
|
45
|
+
*
|
|
46
|
+
* @param styles - Array of CSS objects to pre-compile
|
|
47
|
+
*/
|
|
48
|
+
function warmCache(styles) {
|
|
49
|
+
for (const style of styles) {
|
|
50
|
+
try {
|
|
51
|
+
compileCSS(style, validatedTheme, sanitizedPrefix, media, utils, mergedThemeMap);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Skip invalid styles
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Preloads a theme by injecting its CSS variables before React renders.
|
|
60
|
+
* Useful for preventing FOUC when loading a non-default theme from localStorage.
|
|
61
|
+
*
|
|
62
|
+
* @param theme - Theme to preload (can be theme name string or Theme object)
|
|
63
|
+
*/
|
|
64
|
+
function preloadTheme(theme) {
|
|
65
|
+
let themeToInject;
|
|
66
|
+
if (typeof theme === "string") {
|
|
67
|
+
if (config.themes && config.themes[theme]) {
|
|
68
|
+
themeToInject = config.themes[theme];
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
|
|
72
|
+
// eslint-disable-next-line no-console
|
|
73
|
+
console.warn(`[Stoop] Theme "${theme}" not found. Available themes: ${config.themes ? Object.keys(config.themes).join(", ") : "none"}`);
|
|
74
|
+
}
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
themeToInject = theme;
|
|
80
|
+
}
|
|
81
|
+
updateThemeVariables(themeToInject, sanitizedPrefix);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Gets all injected CSS text for server-side rendering.
|
|
85
|
+
* Always includes theme CSS variables.
|
|
86
|
+
*
|
|
87
|
+
* @param theme - Optional theme (name or object) to include vars for (defaults to default theme)
|
|
88
|
+
* @returns CSS text string with theme variables and component styles
|
|
89
|
+
*/
|
|
90
|
+
function getCssText(theme) {
|
|
91
|
+
let themeToUse = validatedTheme;
|
|
92
|
+
if (theme) {
|
|
93
|
+
if (typeof theme === "string") {
|
|
94
|
+
if (config.themes && config.themes[theme]) {
|
|
95
|
+
themeToUse = config.themes[theme];
|
|
96
|
+
}
|
|
97
|
+
else if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
|
|
98
|
+
// eslint-disable-next-line no-console
|
|
99
|
+
console.warn(`[Stoop] Theme "${theme}" not found. Using default theme. Available: ${config.themes ? Object.keys(config.themes).join(", ") : "none"}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
themeToUse = theme;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
let result = "";
|
|
107
|
+
const themeVars = generateCSSVariables(themeToUse, sanitizedPrefix);
|
|
108
|
+
if (themeVars) {
|
|
109
|
+
result += themeVars + "\n";
|
|
110
|
+
}
|
|
111
|
+
const baseCss = getCssTextBase();
|
|
112
|
+
const rootRegex = getRootRegex(sanitizedPrefix);
|
|
113
|
+
const cssWithoutThemeVars = baseCss.replace(rootRegex, "").trim();
|
|
114
|
+
if (cssWithoutThemeVars) {
|
|
115
|
+
result += (result ? "\n" : "") + cssWithoutThemeVars;
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
config: { ...config, prefix: sanitizedPrefix },
|
|
121
|
+
createTheme,
|
|
122
|
+
css,
|
|
123
|
+
getCssText,
|
|
124
|
+
globalCss,
|
|
125
|
+
keyframes,
|
|
126
|
+
preloadTheme,
|
|
127
|
+
theme: themeObject,
|
|
128
|
+
warmCache,
|
|
129
|
+
};
|
|
130
|
+
}
|
package/dist/create-stoop.d.ts
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Main factory function that creates a Stoop instance.
|
|
3
|
-
* Configures theme, media queries, utilities, and returns all API functions.
|
|
4
|
-
*/
|
|
5
1
|
import type { StoopConfig, StoopInstance } from "./types";
|
|
6
2
|
/**
|
|
7
3
|
* Creates a Stoop instance with the provided configuration.
|
|
4
|
+
* Includes all APIs: styled, Provider, useTheme, etc.
|
|
8
5
|
*
|
|
9
6
|
* @param config - Configuration object containing theme, media queries, utilities, and optional prefix/themeMap
|
|
10
|
-
* @returns StoopInstance with all API functions
|
|
7
|
+
* @returns StoopInstance with all API functions
|
|
11
8
|
*/
|
|
12
9
|
export declare function createStoop(config: StoopConfig): StoopInstance;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createTheme as createThemeFactory } from "./api/create-theme";
|
|
3
|
+
import { createCSSFunction } from "./api/css";
|
|
4
|
+
import { createGlobalCSSFunction } from "./api/global-css";
|
|
5
|
+
import { createKeyframesFunction } from "./api/keyframes";
|
|
6
|
+
import { createProvider } from "./api/provider";
|
|
7
|
+
import { createStyledFunction } from "./api/styled";
|
|
8
|
+
import { createUseThemeHook } from "./api/use-theme";
|
|
9
|
+
import { DEFAULT_THEME_MAP } from "./constants";
|
|
10
|
+
import { compileCSS } from "./core/compiler";
|
|
11
|
+
import { registerDefaultTheme, updateThemeVariables } from "./core/theme-manager";
|
|
12
|
+
import { getCssText as getCssTextBase, registerTheme } from "./inject";
|
|
13
|
+
import { getRootRegex, sanitizePrefix } from "./utils/string";
|
|
14
|
+
import { generateCSSVariables } from "./utils/theme";
|
|
15
|
+
import { validateTheme } from "./utils/theme-validation";
|
|
16
|
+
/**
|
|
17
|
+
* Creates a Stoop instance with the provided configuration.
|
|
18
|
+
* Includes all APIs: styled, Provider, useTheme, etc.
|
|
19
|
+
*
|
|
20
|
+
* @param config - Configuration object containing theme, media queries, utilities, and optional prefix/themeMap
|
|
21
|
+
* @returns StoopInstance with all API functions
|
|
22
|
+
*/
|
|
23
|
+
export function createStoop(config) {
|
|
24
|
+
const { media: configMedia, prefix = "stoop", theme, themeMap: userThemeMap, utils } = config;
|
|
25
|
+
const sanitizedPrefix = sanitizePrefix(prefix);
|
|
26
|
+
const validatedTheme = validateTheme(theme);
|
|
27
|
+
const media = validatedTheme.media || configMedia;
|
|
28
|
+
const mergedThemeMap = {
|
|
29
|
+
...DEFAULT_THEME_MAP,
|
|
30
|
+
...userThemeMap,
|
|
31
|
+
};
|
|
32
|
+
registerDefaultTheme(validatedTheme, sanitizedPrefix);
|
|
33
|
+
registerTheme(validatedTheme, sanitizedPrefix);
|
|
34
|
+
const css = createCSSFunction(validatedTheme, sanitizedPrefix, media, utils, mergedThemeMap);
|
|
35
|
+
const createTheme = createThemeFactory(validatedTheme);
|
|
36
|
+
const globalCss = createGlobalCSSFunction(validatedTheme, sanitizedPrefix, media, utils, mergedThemeMap);
|
|
37
|
+
const keyframes = createKeyframesFunction(sanitizedPrefix, validatedTheme, mergedThemeMap);
|
|
38
|
+
const themeObject = Object.freeze({ ...validatedTheme });
|
|
39
|
+
/**
|
|
40
|
+
* Pre-compiles CSS objects to warm the cache.
|
|
41
|
+
* Useful for eliminating FOUC by pre-compiling common styles.
|
|
42
|
+
*
|
|
43
|
+
* @param styles - Array of CSS objects to pre-compile
|
|
44
|
+
*/
|
|
45
|
+
function warmCache(styles) {
|
|
46
|
+
for (const style of styles) {
|
|
47
|
+
try {
|
|
48
|
+
compileCSS(style, validatedTheme, sanitizedPrefix, media, utils, mergedThemeMap);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Skip invalid styles
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Preloads a theme by injecting its CSS variables before React renders.
|
|
57
|
+
* Useful for preventing FOUC when loading a non-default theme from localStorage.
|
|
58
|
+
*
|
|
59
|
+
* @param theme - Theme to preload (can be theme name string or Theme object)
|
|
60
|
+
*/
|
|
61
|
+
function preloadTheme(theme) {
|
|
62
|
+
let themeToInject;
|
|
63
|
+
if (typeof theme === "string") {
|
|
64
|
+
if (config.themes && config.themes[theme]) {
|
|
65
|
+
themeToInject = config.themes[theme];
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
|
|
69
|
+
// eslint-disable-next-line no-console
|
|
70
|
+
console.warn(`[Stoop] Theme "${theme}" not found. Available themes: ${config.themes ? Object.keys(config.themes).join(", ") : "none"}`);
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
themeToInject = theme;
|
|
77
|
+
}
|
|
78
|
+
updateThemeVariables(themeToInject, sanitizedPrefix);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Gets all injected CSS text for server-side rendering.
|
|
82
|
+
* Always includes theme CSS variables.
|
|
83
|
+
*
|
|
84
|
+
* @param theme - Optional theme (name or object) to include vars for (defaults to default theme)
|
|
85
|
+
* @returns CSS text string with theme variables and component styles
|
|
86
|
+
*/
|
|
87
|
+
function getCssText(theme) {
|
|
88
|
+
let themeToUse = validatedTheme;
|
|
89
|
+
if (theme) {
|
|
90
|
+
if (typeof theme === "string") {
|
|
91
|
+
if (config.themes && config.themes[theme]) {
|
|
92
|
+
themeToUse = config.themes[theme];
|
|
93
|
+
}
|
|
94
|
+
else if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
|
|
95
|
+
// eslint-disable-next-line no-console
|
|
96
|
+
console.warn(`[Stoop] Theme "${theme}" not found. Using default theme. Available: ${config.themes ? Object.keys(config.themes).join(", ") : "none"}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
themeToUse = theme;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
let result = "";
|
|
104
|
+
const themeVars = generateCSSVariables(themeToUse, sanitizedPrefix);
|
|
105
|
+
if (themeVars) {
|
|
106
|
+
result += themeVars + "\n";
|
|
107
|
+
}
|
|
108
|
+
const baseCss = getCssTextBase();
|
|
109
|
+
const rootRegex = getRootRegex(sanitizedPrefix);
|
|
110
|
+
const cssWithoutThemeVars = baseCss.replace(rootRegex, "").trim();
|
|
111
|
+
if (cssWithoutThemeVars) {
|
|
112
|
+
result += (result ? "\n" : "") + cssWithoutThemeVars;
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
// Create Provider and useTheme if themes are configured
|
|
117
|
+
let Provider;
|
|
118
|
+
let useTheme;
|
|
119
|
+
let themeContext;
|
|
120
|
+
if (config.themes) {
|
|
121
|
+
const mergedThemesForProvider = {};
|
|
122
|
+
for (const [themeName, themeOverride] of Object.entries(config.themes)) {
|
|
123
|
+
mergedThemesForProvider[themeName] = createTheme(themeOverride);
|
|
124
|
+
}
|
|
125
|
+
const { Provider: ProviderComponent, ThemeContext, ThemeManagementContext, } = createProvider(mergedThemesForProvider, validatedTheme, sanitizedPrefix);
|
|
126
|
+
themeContext = ThemeContext;
|
|
127
|
+
Provider = ProviderComponent;
|
|
128
|
+
useTheme = createUseThemeHook(ThemeManagementContext);
|
|
129
|
+
}
|
|
130
|
+
// Create styled function
|
|
131
|
+
const styled = createStyledFunction(validatedTheme, sanitizedPrefix, media, utils, mergedThemeMap, themeContext);
|
|
132
|
+
// Return instance with all APIs
|
|
133
|
+
return {
|
|
134
|
+
config: { ...config, prefix: sanitizedPrefix },
|
|
135
|
+
createTheme,
|
|
136
|
+
css,
|
|
137
|
+
getCssText,
|
|
138
|
+
globalCss,
|
|
139
|
+
keyframes,
|
|
140
|
+
preloadTheme,
|
|
141
|
+
Provider,
|
|
142
|
+
styled,
|
|
143
|
+
theme: themeObject,
|
|
144
|
+
useTheme,
|
|
145
|
+
warmCache,
|
|
146
|
+
};
|
|
147
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
`);if(!J){l.clear(),d=null;return}let{sheet:$}=U;if($&&$.insertRule&&!J.includes("@")&&!J.includes("&"))try{let X=J.split(/(?<=})\s*(?=\S)/),q=!0;for(let Y of X){let Q=Y.trim();if(Q)try{$.insertRule(Q,$.cssRules.length)}catch{q=!1;break}}if(q){l.clear(),d=null;return}}catch{}let Z=U.textContent||"";U.textContent=Z+(Z?`
|
|
7
|
-
`:"")+J,l.clear(),d=null}function I4(J,$){if(l.set($,J),!d&&typeof requestAnimationFrame!=="undefined")d=requestAnimationFrame(nJ);else if(!d)nJ()}function KJ(J=""){if(typeof document==="undefined")throw new Error("Cannot access document in SSR context");let $=M(J);if(U){if(U.parentNode)return U;if(U.textContent)try{return document.head.appendChild(U),U}catch{}U=null}return U=document.createElement("style"),U.setAttribute("data-stoop",$||"stoop"),document.head.appendChild(U),U}function OJ(J,$,Z=""){if(!J)return;let X=M(Z),q=`__theme_vars_${X}`;if((cJ.get(q)??null)===J){if((_J.get(q)??null)!==$)_J.set(q,$);return}if(_J.set(q,$),cJ.set(q,J),typeof document==="undefined"){JJ(J);return}let Q=KJ(X),G=Q.textContent||"";if(g(q)){let W=X?`:root[data-stoop="${X}"]`:":root",F=new RegExp(`${W.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}\\s*\\{[\\s\\S]*?\\}`,"g"),B=G.replace(F,"").trim();Q.textContent=J+(B?`
|
|
8
|
-
`+B:""),N(q,J)}else Q.textContent=J+(G?`
|
|
9
|
-
`+G:""),N(q,J)}function mJ(J,$=""){let Z=M($);if(A4.set(Z,J),typeof document!=="undefined"){KJ(Z);let X=n(J,Z);OJ(X,J,Z)}}function _4(J,$,Z=""){if(typeof document==="undefined")return;let X=M(Z);if(U&&U.parentNode){let q=U.textContent||"",Y=J.replace(/\s+/g," ").trim();if(q.replace(/\s+/g," ").trim().includes(Y)){if(!g($))N($,J);return}}try{let q=KJ(X),Y=q.textContent||"",Q=J.replace(/\s+/g," ").trim();if(Y.replace(/\s+/g," ").trim().includes(Q)){if(!g($))N($,J);return}if(!Y){let W=HJ();if(W.size>0){let F=Array.from(W.entries()).filter(([B])=>!B.startsWith("__theme_vars_")&&B!==$).map(([,B])=>B).join(`
|
|
10
|
-
`);q.textContent=F+(F?`
|
|
11
|
-
`:"")+J}else q.textContent=J}else I4(J,$);if(!g($))N($,J)}catch{if(!g($))N($,J)}if(!pJ(J))JJ(J)}function lJ(J,$,Z=""){if(g($))return;if(N($,J),bJ(J))return;vJ(J);let X=M(Z);if(U&&U.parentNode)try{let q=U.textContent||"",Y=J.replace(/\s+/g," ").trim();if(q.replace(/\s+/g," ").trim().includes(Y))return}catch{}_4(J,$,X)}function iJ(){return U}function T(J,$="",Z){let X=Z||J;if(typeof document==="undefined"){if(!g(X))N(X,J);JJ(J);return}lJ(J,X,$)}function rJ(J,$,Z=""){OJ(J,$,Z)}function sJ(J,$=""){mJ(J,$)}function aJ(){if(typeof document!=="undefined"){let J=iJ();if(J&&J.parentNode){let $=J.textContent||"";if(!$&&HJ().size>0)return IJ();return $}}return IJ()}function $J(J,$){if(!$||!J||typeof J!=="object")return J;let Z={},X=Object.keys($);for(let q in J){let Y=J[q];if(X.includes(q)&&$[q])try{let Q=$[q](Y);if(Q&&typeof Q==="object")Object.assign(Z,Q)}catch{Z[q]=Y}else if(C(Y))Z[q]=$J(Y,$);else Z[q]=Y}return Z}function O4(J,$){if(typeof J==="symbol"&&J===t)return!0;if(typeof $==="object"&&$!==null&&t in $)return!0;if(typeof J==="string"&&J.startsWith("__STOOP_COMPONENT_"))return!0;return!1}function E4(J,$){if(typeof $==="object"&&$!==null&&"__stoopClassName"in $&&typeof $.__stoopClassName==="string")return $.__stoopClassName;if(typeof J==="string"&&J.startsWith("__STOOP_COMPONENT_"))return J.replace("__STOOP_COMPONENT_","");return""}function x(J,$="",Z=0,X){if(!J||typeof J!=="object")return"";if(Z>GJ)return"";let q="",Y="";for(let G in J){let W=J[G];if(O4(G,W)){let F=E4(G,W);if(!F)continue;let B=P(F);if(!B)continue;let D=`.${B}`,H=wJ(W)?x(W,D,Z+1,X):"";Y+=H;continue}if(wJ(W))if(X&&G in X){let F=DJ(X[G]);if(F){let B=x(W,$,Z+1,X);Y+=`${F} { ${B} }`}}else if(G.startsWith("@")){let F=P(G);if(F){let B=x(W,$,Z+1,X);Y+=`${F} { ${B} }`}}else if(G.includes("&")){let F=P(G);if(F){let B=F.replace(/&/g,$),D=x(W,B,Z+1,X);Y+=D}}else if(G.startsWith(":")){let F=P(G);if(F){let B=`${$}${F}`,D=x(W,B,Z+1,X);Y+=D}}else if(G.includes(" ")||G.includes(">")||G.includes("+")||G.includes("~")){let F=P(G);if(F){let B=`${$} ${F}`,D=x(W,B,Z+1,X);Y+=D}}else{let F=P(G);if(F){let B=$?`${$} ${F}`:F,D=x(W,B,Z+1,X);Y+=D}}else if(W!==void 0){let F=c(G);if(F&&(typeof W==="string"||typeof W==="number")){let B=h(W);q+=`${F}: ${B}; `}}}return(q?`${$} { ${q.trim()}}`:"")+Y}function y(J,$,Z="",X,q,Y){let Q=M(Z),G=$J(J,q),W=S(G,$,Y),F=x(W,"",0,X),B=FJ(F),D=`${Q}:${B}`,H=p.get(D);if(H){let R=e.get(D);if(R)return T(R,Q,D),H;let A=x(W,`.${H}`,0,X);return e.set(D,A),T(A,Q,D),H}let L=Q?`${Q}-${B}`:`css-${B}`,O=x(W,`.${L}`,0,X);return p.set(D,L),e.set(D,O),TJ(),T(O,Q,D),L}function tJ(J,$="",Z,X,q){return function Y(Q){return y(Q,J,$,Z,X,q)}}function eJ(J,$="",Z,X,q){let Y=new Set;return function Q(G){if(typeof document==="undefined")return()=>{};let W=WJ(G);if(Y.has(W))return()=>{};Y.add(W);function F(O,R=0){if(R>GJ)return"";let A="";return Object.entries(O).forEach(([z,_])=>{if(C(_))if(Z&&z in Z){let K=DJ(Z[z]);if(K){let w=F(_,R+1);A+=`${K} { ${w} }`}}else if(z.startsWith("@")){let K=P(z);if(K){let w=F(_,R+1);A+=`${K} { ${w} }`}}else{let K=P(z);if(K){let w=F(_,R+1);A+=`${K} { ${w} }`}}else if(_!==void 0){let K=c(z);if(K&&(typeof _==="string"||typeof _==="number")){let w=h(_);A+=`${K}: ${w}; `}}}),A}let B=M($),D=$J(G,X),H=S(D,J,q),L=F(H);return T(L,B,`__global_${W}`),()=>{Y.delete(W)}}}function M4(J,$,Z,X){let q=`@keyframes ${$} {`,Y=Object.keys(J).sort((Q,G)=>{let W=parseFloat(Q.replace("%","")),F=parseFloat(G.replace("%",""));if(Q==="from")return-1;if(G==="from")return 1;if(Q==="to")return 1;if(G==="to")return-1;return W-F});for(let Q of Y){if(!SJ(Q))continue;let G=J[Q];if(!G||typeof G!=="object")continue;q+=` ${Q} {`;let W=S(G,Z,X);for(let F in W){let B=W[F];if(B!==void 0&&(typeof B==="string"||typeof B==="number")){let D=c(F);if(D){let H=h(B);q+=` ${D}: ${H};`}}}q+=" }"}return q+=" }",q}function J4(J="",$,Z){let X=M(J),q=new Map;return function Y(Q){let G=WJ(Q),W=q.get(G);if(W)return W;let F=G.slice(0,8),B=X?`${X}-${F}`:`stoop-${F}`,D=M4(Q,B,$,Z),H=`__keyframes_${B}`;return T(D,X,H),q.set(G,B),B}}import{createContext as V4,useCallback as EJ,useLayoutEffect as q4,useMemo as MJ,useState as P4}from"react";var $4=new Map;function Z4(J,$=""){let Z=$||"";$4.set(Z,J)}function z4(J=""){let $=J||"";return $4.get($)||null}function j4(J,$=""){let Z=z4($);if(!Z)return J;if(uJ(J,Z))return J;let X={...Z},q=Object.keys(J);for(let Y of q){if(Y==="media")continue;let Q=J[Y],G=Z[Y];if(Q&&typeof Q==="object"&&!Array.isArray(Q)&&G&&typeof G==="object"&&!Array.isArray(G))X[Y]={...G,...Q};else if(Q!==void 0)X[Y]=Q}return X}function LJ(J,$=""){if(typeof document==="undefined")return;let Z=j4(J,$),X=n(Z,$);rJ(X,Z,$)}import{jsxDEV as Y4}from"react/jsx-dev-runtime";function X4(J,$,Z,X=""){let q=V4(null),Y=Object.keys($),Q=Y[0]||"default";function G({attribute:W="data-theme",children:F,defaultTheme:B,storageKey:D="stoop-theme"}){let H=EJ(()=>{if(typeof window==="undefined")return B||Q;try{let w=localStorage.getItem(D);if(w&&$[w])return w}catch{}return B||Q},[B,D]),[L,O]=P4(H),R=MJ(()=>{return $[L]||$[B||Q]||Z},[L,B]);q4(()=>{if(R)LJ(R,X)},[R]),q4(()=>{if(typeof document!=="undefined"&&W)document.documentElement.setAttribute(W,L)},[L,W]);let A=EJ((w)=>{if($[w]){O(w);try{localStorage.setItem(D,w)}catch{}}else if(typeof process!=="undefined"&&process.env?.NODE_ENV!=="production")console.warn(`[Stoop] Theme "${w}" not found. Available themes: ${Y.join(", ")}`)},[D]),z=MJ(()=>({theme:R,themeName:L}),[R,L]),_=EJ(()=>{let V=(Y.indexOf(L)+1)%Y.length,k=Y[V];A(k)},[L,A]),K=MJ(()=>({availableThemes:Y,setTheme:A,theme:R,themeName:L,toggleTheme:_}),[R,L,A,_]);return Y4(J.Provider,{value:z,children:Y4(q.Provider,{value:K,children:F},void 0,!1,void 0,this)},void 0,!1,void 0,this)}return{Provider:G,ThemeManagementContext:q}}import{useMemo as zJ,forwardRef as N4,createElement as x4,useContext as g4}from"react";function G4(J,$,Z){let X=!1,q=[];for(let Y in J){let Q=$[Y];if(Q===void 0)continue;let G=J[Y],W=Q===!0?"true":Q===!1?"false":String(Q);if(G[W])q.push(G[W]),X=!0}return X?Object.assign({},Z,...q):Z}function b4(J){return{__isStoopStyled:!0,__stoopClassName:J,[t]:J,toString:()=>`__STOOP_COMPONENT_${J}`}}function v4(J){return typeof J==="object"&&J!==null&&"__isStoopStyled"in J&&J.__isStoopStyled===!0}function T4(J,$){if(!$)return{elementProps:J,variantProps:{}};let Z=new Set(Object.keys($)),X={},q={};for(let Y in J)if(Z.has(Y))X[Y]=J[Y];else q[Y]=J[Y];return{elementProps:q,variantProps:X}}function Q4(J,$="",Z,X,q,Y){return function Q(G,W,F){let B=W||YJ,D=F;if(W&&"variants"in W&&typeof W.variants==="object"){D=W.variants;let{compoundVariants:A,variants:z,..._}=W;B=_}let H=y(B,J,$,Z,X,q),L;if(typeof G!=="string"&&v4(G))L=G.__stoopClassName;let R=N4(function A(z,_){let{as:K,className:w,css:V,...k}=z,I=K||G,E=V&&typeof V==="object"&&V!==null?V:YJ,{elementProps:b,variantProps:v}=T4(k,D),i=g4(Y||NJ)?.theme||J,qJ=i.media||Z,r=zJ(()=>{if(!D)return"";let j=Object.entries(v);if(j.length===0)return"";return j.sort(([u],[s])=>u.localeCompare(s)).map(([u,s])=>`${u}:${String(s)}`).join("|")},[D,...Object.values(v)]),jJ=zJ(()=>{let j=B;if(D&&r)j=G4(D,v,B);if(E!==YJ)j=Object.assign({},j,E);return j},[r,E]),F4=zJ(()=>{let j=[];if(L)j.push(L);let u=y(jJ,i,$,qJ,X,q);if(u)j.push(u);if(w){let s=typeof w==="string"?w:String(w),VJ=fJ(s);if(VJ)j.push(VJ)}return j.length>0?j.join(" "):void 0},[jJ,i,$,qJ,X,q,w,L]);return x4(I,{...b,className:F4,ref:_})});return R.selector=b4(H),R}}import{useContext as k4}from"react";function B4(J){return function $(){let Z=k4(J);if(!Z)throw new Error("useTheme must be used within a Provider");return Z}}function f4(J){let{media:$,prefix:Z="",theme:X,themeMap:q,utils:Y}=J,Q=M(Z),G=BJ(X),W=G.media||$,F={...a,...q},B=C4(null);Z4(G,Q),sJ(G,Q);let D=tJ(G,Q,W,Y,F),H=xJ(G),L=eJ(G,Q,W,Y,F),O=J4(Q,G,F),R=Q4(G,Q,W,Y,F,B),A=Object.freeze({...G});function z(I){for(let E of I)try{y(E,G,Q,W,Y,F)}catch{}}function _(I){let E;if(typeof I==="string")if(J.themes&&J.themes[I])E=J.themes[I];else{if(typeof process!=="undefined"&&process.env?.NODE_ENV!=="production")console.warn(`[Stoop] Theme "${I}" not found. Available themes: ${J.themes?Object.keys(J.themes).join(", "):"none"}`);return}else E=I;LJ(E,Q)}function K(I){let E=G;if(I)if(typeof I==="string"){if(J.themes&&J.themes[I])E=J.themes[I];else if(typeof process!=="undefined"&&process.env?.NODE_ENV!=="production")console.warn(`[Stoop] Theme "${I}" not found. Using default theme. Available: ${J.themes?Object.keys(J.themes).join(", "):"none"}`)}else E=I;let b="",v=n(E,Q);if(v)b+=v+`
|
|
12
|
-
`;let ZJ=aJ(),i=Q?`:root[data-stoop="${Q}"]`:":root",qJ=new RegExp(`${i.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}\\s*\\{[^}]*\\}`,"g"),r=ZJ.replace(qJ,"").trim();if(r)b+=(b?`
|
|
13
|
-
`:"")+r;return b}let w,V;if(J.themes){let I={};for(let[v,ZJ]of Object.entries(J.themes))I[v]=H(ZJ);let{Provider:E,ThemeManagementContext:b}=X4(B,I,G,Q);w=E,V=B4(b)}let k={config:{...J,prefix:Q},createTheme:H,css:D,getCssText:K,globalCss:L,keyframes:O,preloadTheme:_,styled:R,theme:A,warmCache:z};if(w)k.Provider=w;if(V)k.useTheme=V;return k}export{f4 as createStoop};
|
|
1
|
+
/**
|
|
2
|
+
* Main entry point for Stoop CSS-in-JS library.
|
|
3
|
+
* Exports the createStoop factory function and essential public types.
|
|
4
|
+
*/
|
|
5
|
+
export { createStoop } from "./create-stoop";
|
package/dist/inject/browser.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import type { Theme } from "../types";
|
|
7
7
|
/**
|
|
8
8
|
* Gets or creates the stylesheet element for CSS injection.
|
|
9
|
+
* Reuses the SSR stylesheet if it exists to prevent FOUC.
|
|
9
10
|
*
|
|
10
11
|
* @param prefix - Optional prefix for stylesheet identification
|
|
11
12
|
* @returns HTMLStyleElement
|
|
@@ -24,8 +25,6 @@ export declare function injectThemeVariables(cssVars: string, theme: Theme, pref
|
|
|
24
25
|
/**
|
|
25
26
|
* Registers a theme for injection (browser-specific).
|
|
26
27
|
* Automatically ensures stylesheet exists and injects theme variables.
|
|
27
|
-
* Note: This is typically called with the default theme from createStoop.
|
|
28
|
-
* Additional themes should use updateThemeVariables which handles merging.
|
|
29
28
|
*
|
|
30
29
|
* @param theme - Theme object to register
|
|
31
30
|
* @param prefix - Optional prefix for CSS variables
|
|
@@ -54,6 +53,6 @@ export declare function injectBrowserCSS(css: string, ruleKey: string, prefix?:
|
|
|
54
53
|
*/
|
|
55
54
|
export declare function getStylesheetElement(): HTMLStyleElement | null;
|
|
56
55
|
/**
|
|
57
|
-
* Clears the stylesheet and all
|
|
56
|
+
* Clears the stylesheet and all caches.
|
|
58
57
|
*/
|
|
59
58
|
export declare function clearStylesheet(): void;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-specific CSS injection.
|
|
3
|
+
* Manages a single stylesheet element that gets updated with new CSS rules.
|
|
4
|
+
* Handles theme variable injection, deduplication, and stylesheet lifecycle.
|
|
5
|
+
*/
|
|
6
|
+
import { isBrowser } from "../utils/environment";
|
|
7
|
+
import { getRootRegex, sanitizePrefix } from "../utils/string";
|
|
8
|
+
import { generateCSSVariables } from "../utils/theme";
|
|
9
|
+
import * as dedup from "./dedup";
|
|
10
|
+
import * as ssr from "./ssr";
|
|
11
|
+
let stylesheetElement = null;
|
|
12
|
+
const lastInjectedThemes = new Map();
|
|
13
|
+
const lastInjectedCSSVars = new Map();
|
|
14
|
+
/**
|
|
15
|
+
* Gets or creates the stylesheet element for CSS injection.
|
|
16
|
+
* Reuses the SSR stylesheet if it exists to prevent FOUC.
|
|
17
|
+
*
|
|
18
|
+
* @param prefix - Optional prefix for stylesheet identification
|
|
19
|
+
* @returns HTMLStyleElement
|
|
20
|
+
* @throws Error if called in SSR context
|
|
21
|
+
*/
|
|
22
|
+
export function getStylesheet(prefix = "stoop") {
|
|
23
|
+
if (!isBrowser()) {
|
|
24
|
+
throw new Error("Cannot access document in SSR context");
|
|
25
|
+
}
|
|
26
|
+
const sanitizedPrefix = sanitizePrefix(prefix);
|
|
27
|
+
if (!stylesheetElement || !stylesheetElement.parentNode) {
|
|
28
|
+
const ssrStylesheet = document.getElementById("stoop-ssr");
|
|
29
|
+
if (ssrStylesheet) {
|
|
30
|
+
stylesheetElement = ssrStylesheet;
|
|
31
|
+
stylesheetElement.setAttribute("data-stoop", sanitizedPrefix || "stoop");
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
stylesheetElement = document.createElement("style");
|
|
35
|
+
stylesheetElement.setAttribute("data-stoop", sanitizedPrefix || "stoop");
|
|
36
|
+
document.head.appendChild(stylesheetElement);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return stylesheetElement;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Injects theme CSS variables into the stylesheet.
|
|
43
|
+
* Automatically ensures stylesheet exists before injection.
|
|
44
|
+
*
|
|
45
|
+
* @param cssVars - CSS variables string
|
|
46
|
+
* @param theme - Theme object
|
|
47
|
+
* @param prefix - Optional prefix for CSS variables
|
|
48
|
+
*/
|
|
49
|
+
export function injectThemeVariables(cssVars, theme, prefix = "stoop") {
|
|
50
|
+
if (!cssVars) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const sanitizedPrefix = sanitizePrefix(prefix);
|
|
54
|
+
const key = `__theme_vars_${sanitizedPrefix}`;
|
|
55
|
+
const lastCSSVars = lastInjectedCSSVars.get(key) ?? null;
|
|
56
|
+
if (lastCSSVars === cssVars) {
|
|
57
|
+
const lastTheme = lastInjectedThemes.get(key) ?? null;
|
|
58
|
+
if (lastTheme !== theme) {
|
|
59
|
+
lastInjectedThemes.set(key, theme);
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
lastInjectedThemes.set(key, theme);
|
|
64
|
+
lastInjectedCSSVars.set(key, cssVars);
|
|
65
|
+
if (!isBrowser()) {
|
|
66
|
+
ssr.addToSSRCache(cssVars);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const sheet = getStylesheet(sanitizedPrefix);
|
|
70
|
+
const currentCSS = sheet.textContent || "";
|
|
71
|
+
if (dedup.isInjectedRule(key)) {
|
|
72
|
+
const rootRegex = getRootRegex(sanitizedPrefix);
|
|
73
|
+
const withoutVars = currentCSS.replace(rootRegex, "").trim();
|
|
74
|
+
sheet.textContent = cssVars + (withoutVars ? "\n" + withoutVars : "");
|
|
75
|
+
dedup.markRuleAsInjected(key, cssVars);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
sheet.textContent = cssVars + (currentCSS ? "\n" + currentCSS : "");
|
|
79
|
+
dedup.markRuleAsInjected(key, cssVars);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Registers a theme for injection (browser-specific).
|
|
84
|
+
* Automatically ensures stylesheet exists and injects theme variables.
|
|
85
|
+
*
|
|
86
|
+
* @param theme - Theme object to register
|
|
87
|
+
* @param prefix - Optional prefix for CSS variables
|
|
88
|
+
*/
|
|
89
|
+
export function registerTheme(theme, prefix = "stoop") {
|
|
90
|
+
const sanitizedPrefix = sanitizePrefix(prefix);
|
|
91
|
+
if (isBrowser()) {
|
|
92
|
+
getStylesheet(sanitizedPrefix);
|
|
93
|
+
const cssVars = generateCSSVariables(theme, sanitizedPrefix);
|
|
94
|
+
injectThemeVariables(cssVars, theme, sanitizedPrefix);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Updates the stylesheet with new CSS rules.
|
|
99
|
+
*
|
|
100
|
+
* @param css - CSS string to inject
|
|
101
|
+
* @param ruleKey - Unique key for deduplication
|
|
102
|
+
* @param prefix - Optional prefix for CSS rules
|
|
103
|
+
*/
|
|
104
|
+
export function updateStylesheet(css, ruleKey, prefix = "stoop") {
|
|
105
|
+
if (!isBrowser()) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const sanitizedPrefix = sanitizePrefix(prefix);
|
|
109
|
+
if (dedup.isInjectedRule(ruleKey)) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const sheet = getStylesheet(sanitizedPrefix);
|
|
113
|
+
const currentCSS = sheet.textContent || "";
|
|
114
|
+
sheet.textContent = currentCSS + (currentCSS ? "\n" : "") + css;
|
|
115
|
+
dedup.markRuleAsInjected(ruleKey, css);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Injects CSS into the browser stylesheet with deduplication.
|
|
119
|
+
*
|
|
120
|
+
* @param css - CSS string to inject
|
|
121
|
+
* @param ruleKey - Unique key for deduplication
|
|
122
|
+
* @param prefix - Optional prefix for CSS rules
|
|
123
|
+
*/
|
|
124
|
+
export function injectBrowserCSS(css, ruleKey, prefix = "stoop") {
|
|
125
|
+
if (dedup.isInjectedRule(ruleKey)) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const sanitizedPrefix = sanitizePrefix(prefix);
|
|
129
|
+
updateStylesheet(css, ruleKey, sanitizedPrefix);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Gets the current stylesheet element.
|
|
133
|
+
*
|
|
134
|
+
* @returns HTMLStyleElement or null if not created
|
|
135
|
+
*/
|
|
136
|
+
export function getStylesheetElement() {
|
|
137
|
+
return stylesheetElement;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Clears the stylesheet and all caches.
|
|
141
|
+
*/
|
|
142
|
+
export function clearStylesheet() {
|
|
143
|
+
if (stylesheetElement && stylesheetElement.parentNode) {
|
|
144
|
+
stylesheetElement.parentNode.removeChild(stylesheetElement);
|
|
145
|
+
}
|
|
146
|
+
stylesheetElement = null;
|
|
147
|
+
lastInjectedThemes.clear();
|
|
148
|
+
lastInjectedCSSVars.clear();
|
|
149
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS injection deduplication.
|
|
3
|
+
* Tracks which CSS rules have been injected to prevent duplicates.
|
|
4
|
+
* Used by both browser and SSR injection systems.
|
|
5
|
+
*/
|
|
6
|
+
const injectedRules = new Map();
|
|
7
|
+
/**
|
|
8
|
+
* Checks if a CSS rule has already been injected.
|
|
9
|
+
*
|
|
10
|
+
* @param key - Rule key to check
|
|
11
|
+
* @returns True if rule is already injected
|
|
12
|
+
*/
|
|
13
|
+
export function isInjectedRule(key) {
|
|
14
|
+
return injectedRules.has(key);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Marks a CSS rule as injected.
|
|
18
|
+
*
|
|
19
|
+
* @param key - Rule key
|
|
20
|
+
* @param css - CSS string
|
|
21
|
+
*/
|
|
22
|
+
export function markRuleAsInjected(key, css) {
|
|
23
|
+
injectedRules.set(key, css);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Gets all injected rules as a new Map.
|
|
27
|
+
*
|
|
28
|
+
* @returns Map of all injected rules
|
|
29
|
+
*/
|
|
30
|
+
export function getAllInjectedRules() {
|
|
31
|
+
return new Map(injectedRules);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Clears all injected rule tracking.
|
|
35
|
+
*/
|
|
36
|
+
export function clearInjectedRules() {
|
|
37
|
+
injectedRules.clear();
|
|
38
|
+
}
|
package/dist/inject/index.d.ts
CHANGED
|
@@ -23,7 +23,6 @@ export declare function injectCSS(css: string, prefix?: string, ruleKey?: string
|
|
|
23
23
|
export declare function injectThemeVariables(cssVars: string, theme: Theme, prefix?: string): void;
|
|
24
24
|
/**
|
|
25
25
|
* Registers a theme for injection.
|
|
26
|
-
* Automatically ensures stylesheet exists and injects theme variables.
|
|
27
26
|
*
|
|
28
27
|
* @param theme - Theme object to register
|
|
29
28
|
* @param prefix - Optional prefix for CSS variables
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS injection public API.
|
|
3
|
+
* Composes browser, SSR, and deduplication modules into a unified interface.
|
|
4
|
+
* Provides single stylesheet injection with automatic SSR support.
|
|
5
|
+
*/
|
|
6
|
+
import { clearStyleCache } from "../core/cache";
|
|
7
|
+
import { isBrowser } from "../utils/environment";
|
|
8
|
+
import * as browser from "./browser";
|
|
9
|
+
import * as dedup from "./dedup";
|
|
10
|
+
import * as ssr from "./ssr";
|
|
11
|
+
export { isInjectedRule } from "./dedup";
|
|
12
|
+
/**
|
|
13
|
+
* Injects CSS into the document with automatic SSR support.
|
|
14
|
+
*
|
|
15
|
+
* @param css - CSS string to inject
|
|
16
|
+
* @param prefix - Optional prefix for CSS rules
|
|
17
|
+
* @param ruleKey - Optional unique key for deduplication
|
|
18
|
+
*/
|
|
19
|
+
export function injectCSS(css, prefix = "stoop", ruleKey) {
|
|
20
|
+
const key = ruleKey || css;
|
|
21
|
+
if (!isBrowser()) {
|
|
22
|
+
if (!dedup.isInjectedRule(key)) {
|
|
23
|
+
dedup.markRuleAsInjected(key, css);
|
|
24
|
+
}
|
|
25
|
+
ssr.addToSSRCache(css);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
browser.injectBrowserCSS(css, key, prefix);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Injects theme CSS variables into the document.
|
|
32
|
+
*
|
|
33
|
+
* @param cssVars - CSS variables string
|
|
34
|
+
* @param theme - Theme object
|
|
35
|
+
* @param prefix - Optional prefix for CSS variables
|
|
36
|
+
*/
|
|
37
|
+
export function injectThemeVariables(cssVars, theme, prefix = "stoop") {
|
|
38
|
+
browser.injectThemeVariables(cssVars, theme, prefix);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Registers a theme for injection.
|
|
42
|
+
*
|
|
43
|
+
* @param theme - Theme object to register
|
|
44
|
+
* @param prefix - Optional prefix for CSS variables
|
|
45
|
+
*/
|
|
46
|
+
export function registerTheme(theme, prefix = "stoop") {
|
|
47
|
+
browser.registerTheme(theme, prefix);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Gets all injected CSS text (browser or SSR).
|
|
51
|
+
*
|
|
52
|
+
* @returns CSS text string
|
|
53
|
+
*/
|
|
54
|
+
export function getCssText() {
|
|
55
|
+
if (isBrowser()) {
|
|
56
|
+
const sheetElement = browser.getStylesheetElement();
|
|
57
|
+
if (sheetElement && sheetElement.parentNode) {
|
|
58
|
+
const sheetCSS = sheetElement.textContent || "";
|
|
59
|
+
if (!sheetCSS && dedup.getAllInjectedRules().size > 0) {
|
|
60
|
+
return ssr.getSSRCacheText();
|
|
61
|
+
}
|
|
62
|
+
return sheetCSS;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return ssr.getSSRCacheText();
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Clears all injected CSS and caches.
|
|
69
|
+
*/
|
|
70
|
+
export function clearStylesheet() {
|
|
71
|
+
browser.clearStylesheet();
|
|
72
|
+
dedup.clearInjectedRules();
|
|
73
|
+
ssr.clearSSRCache();
|
|
74
|
+
clearStyleCache();
|
|
75
|
+
}
|