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.
Files changed (48) hide show
  1. package/dist/api/create-theme.js +43 -0
  2. package/dist/api/css.js +20 -0
  3. package/dist/api/global-css.d.ts +0 -11
  4. package/dist/api/global-css.js +89 -0
  5. package/dist/api/keyframes.js +95 -0
  6. package/dist/api/provider.d.ts +3 -3
  7. package/dist/api/provider.js +109 -0
  8. package/dist/api/styled.js +170 -0
  9. package/dist/api/use-theme.js +21 -0
  10. package/dist/constants.d.ts +2 -3
  11. package/dist/constants.js +144 -0
  12. package/dist/core/cache.d.ts +5 -9
  13. package/dist/core/cache.js +68 -0
  14. package/dist/core/compiler.js +198 -0
  15. package/dist/core/theme-manager.d.ts +0 -8
  16. package/dist/core/theme-manager.js +97 -0
  17. package/dist/core/variants.js +32 -0
  18. package/dist/create-stoop-server.d.ts +33 -0
  19. package/dist/create-stoop-server.js +130 -0
  20. package/dist/create-stoop.d.ts +2 -5
  21. package/dist/create-stoop.js +147 -0
  22. package/dist/index.js +5 -13
  23. package/dist/inject/browser.d.ts +2 -3
  24. package/dist/inject/browser.js +149 -0
  25. package/dist/inject/dedup.js +38 -0
  26. package/dist/inject/index.d.ts +0 -1
  27. package/dist/inject/index.js +75 -0
  28. package/dist/inject/ssr.d.ts +0 -1
  29. package/dist/inject/ssr.js +46 -0
  30. package/dist/ssr.d.ts +21 -0
  31. package/dist/ssr.js +19 -0
  32. package/dist/types/index.d.ts +10 -5
  33. package/dist/types/index.js +5 -0
  34. package/dist/types/react-polymorphic-types.d.ts +4 -8
  35. package/dist/utils/environment.d.ts +6 -0
  36. package/dist/utils/environment.js +12 -0
  37. package/dist/utils/string.d.ts +16 -9
  38. package/dist/utils/string.js +253 -0
  39. package/dist/utils/theme-map.d.ts +0 -3
  40. package/dist/utils/theme-map.js +97 -0
  41. package/dist/utils/theme-validation.js +36 -0
  42. package/dist/utils/theme.d.ts +3 -3
  43. package/dist/utils/theme.js +279 -0
  44. package/dist/utils/type-guards.js +38 -0
  45. package/dist/utils/utilities.js +43 -0
  46. package/package.json +24 -25
  47. package/LICENSE.md +0 -21
  48. package/README.md +0 -180
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Shared constants used throughout the library.
3
+ * Includes cache size limits and nesting depth limits.
4
+ */
5
+ export const EMPTY_CSS = Object.freeze({});
6
+ export const MAX_CSS_CACHE_SIZE = 10000;
7
+ export const MAX_CLASS_NAME_CACHE_SIZE = 5000;
8
+ export const MAX_CSS_NESTING_DEPTH = 10;
9
+ /**
10
+ * Approved theme scales - only these scales are allowed in theme objects.
11
+ */
12
+ export const APPROVED_THEME_SCALES = [
13
+ "colors",
14
+ "opacities",
15
+ "space",
16
+ "radii",
17
+ "sizes",
18
+ "fonts",
19
+ "fontWeights",
20
+ "fontSizes",
21
+ "lineHeights",
22
+ "letterSpacings",
23
+ "shadows",
24
+ "zIndices",
25
+ "transitions",
26
+ ];
27
+ /**
28
+ * Default themeMap mapping CSS properties to theme scales.
29
+ * Covers 150+ common CSS properties for zero-config experience.
30
+ * Missing properties gracefully fallback to pattern-based auto-detection.
31
+ */
32
+ export const DEFAULT_THEME_MAP = {
33
+ accentColor: "colors",
34
+ animation: "transitions",
35
+ animationDelay: "transitions",
36
+ animationDuration: "transitions",
37
+ animationTimingFunction: "transitions",
38
+ backdropFilter: "shadows",
39
+ background: "colors",
40
+ backgroundColor: "colors",
41
+ blockSize: "sizes",
42
+ border: "colors",
43
+ borderBlockColor: "colors",
44
+ borderBlockEndColor: "colors",
45
+ borderBlockStartColor: "colors",
46
+ borderBottomColor: "colors",
47
+ borderBottomLeftRadius: "radii",
48
+ borderBottomRightRadius: "radii",
49
+ borderColor: "colors",
50
+ borderEndEndRadius: "radii",
51
+ borderEndStartRadius: "radii",
52
+ borderInlineColor: "colors",
53
+ borderInlineEndColor: "colors",
54
+ borderInlineStartColor: "colors",
55
+ borderLeftColor: "colors",
56
+ borderRadius: "radii",
57
+ borderRightColor: "colors",
58
+ borderStartEndRadius: "radii",
59
+ borderStartStartRadius: "radii",
60
+ borderTopColor: "colors",
61
+ borderTopLeftRadius: "radii",
62
+ borderTopRightRadius: "radii",
63
+ bottom: "space",
64
+ boxShadow: "shadows",
65
+ caretColor: "colors",
66
+ color: "colors",
67
+ columnGap: "space",
68
+ columnRuleColor: "colors",
69
+ fill: "colors",
70
+ filter: "shadows",
71
+ flexBasis: "sizes",
72
+ floodColor: "colors",
73
+ font: "fontSizes",
74
+ fontFamily: "fonts",
75
+ fontSize: "fontSizes",
76
+ fontWeight: "fontWeights",
77
+ gap: "space",
78
+ gridColumnGap: "space",
79
+ gridGap: "space",
80
+ gridRowGap: "space",
81
+ height: "sizes",
82
+ inlineSize: "sizes",
83
+ inset: "space",
84
+ insetBlock: "space",
85
+ insetBlockEnd: "space",
86
+ insetBlockStart: "space",
87
+ insetInline: "space",
88
+ insetInlineEnd: "space",
89
+ insetInlineStart: "space",
90
+ left: "space",
91
+ letterSpacing: "letterSpacings",
92
+ lightingColor: "colors",
93
+ lineHeight: "lineHeights",
94
+ margin: "space",
95
+ marginBlock: "space",
96
+ marginBlockEnd: "space",
97
+ marginBlockStart: "space",
98
+ marginBottom: "space",
99
+ marginInline: "space",
100
+ marginInlineEnd: "space",
101
+ marginInlineStart: "space",
102
+ marginLeft: "space",
103
+ marginRight: "space",
104
+ marginTop: "space",
105
+ maxBlockSize: "sizes",
106
+ maxHeight: "sizes",
107
+ maxInlineSize: "sizes",
108
+ maxWidth: "sizes",
109
+ minBlockSize: "sizes",
110
+ minHeight: "sizes",
111
+ minInlineSize: "sizes",
112
+ minWidth: "sizes",
113
+ opacity: "opacities",
114
+ outline: "colors",
115
+ outlineColor: "colors",
116
+ padding: "space",
117
+ paddingBlock: "space",
118
+ paddingBlockEnd: "space",
119
+ paddingBlockStart: "space",
120
+ paddingBottom: "space",
121
+ paddingInline: "space",
122
+ paddingInlineEnd: "space",
123
+ paddingInlineStart: "space",
124
+ paddingLeft: "space",
125
+ paddingRight: "space",
126
+ paddingTop: "space",
127
+ right: "space",
128
+ rowGap: "space",
129
+ size: "sizes",
130
+ stopColor: "colors",
131
+ stroke: "colors",
132
+ textDecorationColor: "colors",
133
+ textEmphasisColor: "colors",
134
+ textShadow: "shadows",
135
+ top: "space",
136
+ transition: "transitions",
137
+ transitionDelay: "transitions",
138
+ transitionDuration: "transitions",
139
+ transitionProperty: "transitions",
140
+ transitionTimingFunction: "transitions",
141
+ width: "sizes",
142
+ zIndex: "zIndices",
143
+ };
144
+ export const STOOP_COMPONENT_SYMBOL = Symbol.for("stoop.component");
@@ -7,7 +7,7 @@
7
7
  * LRU Cache implementation for class names and CSS strings.
8
8
  * Automatically evicts least recently used entries when size limit is exceeded.
9
9
  */
10
- declare class LRUCache<K, V> extends Map<K, V> {
10
+ export declare class LRUCache<K, V> extends Map<K, V> {
11
11
  private readonly maxSize;
12
12
  constructor(maxSize: number);
13
13
  get(key: K): V | undefined;
@@ -16,24 +16,20 @@ declare class LRUCache<K, V> extends Map<K, V> {
16
16
  export declare const classNameCache: LRUCache<string, string>;
17
17
  export declare const cssStringCache: LRUCache<string, string>;
18
18
  /**
19
- * Checks if a CSS string is cached.
19
+ * Checks if a CSS string has been injected.
20
+ * Uses a Set for O(1) lookup.
20
21
  *
21
22
  * @param css - CSS string to check
22
- * @returns True if CSS is cached
23
+ * @returns True if CSS has been injected
23
24
  */
24
25
  export declare function isCachedStyle(css: string): boolean;
25
26
  /**
26
- * Marks a CSS string as cached.
27
+ * Marks a CSS string as injected.
27
28
  *
28
29
  * @param css - CSS string to cache
29
30
  */
30
31
  export declare function markStyleAsCached(css: string): void;
31
- /**
32
- * Limits cache size by evicting least recently used entries.
33
- */
34
- export declare function limitCacheSize(): void;
35
32
  /**
36
33
  * Clears all cached styles.
37
34
  */
38
35
  export declare function clearStyleCache(): void;
39
- export {};
@@ -0,0 +1,68 @@
1
+ /**
2
+ * CSS compilation caching system.
3
+ * Tracks compiled CSS strings and class names to prevent duplicate work.
4
+ * Implements LRU (Least Recently Used) eviction when cache size limits are exceeded.
5
+ */
6
+ import { MAX_CLASS_NAME_CACHE_SIZE, MAX_CSS_CACHE_SIZE } from "../constants";
7
+ /**
8
+ * LRU Cache implementation for class names and CSS strings.
9
+ * Automatically evicts least recently used entries when size limit is exceeded.
10
+ */
11
+ export class LRUCache extends Map {
12
+ maxSize;
13
+ constructor(maxSize) {
14
+ super();
15
+ this.maxSize = maxSize;
16
+ }
17
+ get(key) {
18
+ const value = super.get(key);
19
+ if (value !== undefined) {
20
+ super.delete(key);
21
+ super.set(key, value);
22
+ }
23
+ return value;
24
+ }
25
+ set(key, value) {
26
+ if (super.has(key)) {
27
+ super.delete(key);
28
+ }
29
+ else if (this.size >= this.maxSize) {
30
+ const firstKey = this.keys().next().value;
31
+ if (firstKey !== undefined) {
32
+ super.delete(firstKey);
33
+ }
34
+ }
35
+ super.set(key, value);
36
+ return this;
37
+ }
38
+ }
39
+ export const classNameCache = new LRUCache(MAX_CLASS_NAME_CACHE_SIZE);
40
+ export const cssStringCache = new LRUCache(MAX_CSS_CACHE_SIZE);
41
+ // Separate cache for tracking injected CSS strings (used by browser injection)
42
+ const injectedStylesCache = new Set();
43
+ /**
44
+ * Checks if a CSS string has been injected.
45
+ * Uses a Set for O(1) lookup.
46
+ *
47
+ * @param css - CSS string to check
48
+ * @returns True if CSS has been injected
49
+ */
50
+ export function isCachedStyle(css) {
51
+ return injectedStylesCache.has(css);
52
+ }
53
+ /**
54
+ * Marks a CSS string as injected.
55
+ *
56
+ * @param css - CSS string to cache
57
+ */
58
+ export function markStyleAsCached(css) {
59
+ injectedStylesCache.add(css);
60
+ }
61
+ /**
62
+ * Clears all cached styles.
63
+ */
64
+ export function clearStyleCache() {
65
+ classNameCache.clear();
66
+ cssStringCache.clear();
67
+ injectedStylesCache.clear();
68
+ }
@@ -0,0 +1,198 @@
1
+ /**
2
+ * CSS compilation engine.
3
+ * Converts CSS objects to CSS strings and generates unique class names.
4
+ * Handles nested selectors, media queries, styled component targeting, and theme tokens.
5
+ */
6
+ import { MAX_CSS_NESTING_DEPTH, STOOP_COMPONENT_SYMBOL } from "../constants";
7
+ import { injectCSS } from "../inject";
8
+ import { escapeCSSValue, hash, sanitizeCSSPropertyName, sanitizeCSSSelector, sanitizeMediaQuery, sanitizePrefix, } from "../utils/string";
9
+ import { replaceThemeTokensWithVars } from "../utils/theme";
10
+ import { isValidCSSObject } from "../utils/type-guards";
11
+ import { applyUtilities } from "../utils/utilities";
12
+ import { classNameCache, cssStringCache } from "./cache";
13
+ /**
14
+ * Checks if a key/value pair represents a styled component reference.
15
+ *
16
+ * @param key - Property key to check
17
+ * @param value - Property value to check
18
+ * @returns True if key/value represents a styled component reference
19
+ */
20
+ function isStyledComponentKey(key, value) {
21
+ if (typeof key === "symbol" && key === STOOP_COMPONENT_SYMBOL) {
22
+ return true;
23
+ }
24
+ if (typeof value === "object" && value !== null && STOOP_COMPONENT_SYMBOL in value) {
25
+ return true;
26
+ }
27
+ if (typeof key === "string" && key.startsWith("__STOOP_COMPONENT_")) {
28
+ return true;
29
+ }
30
+ return false;
31
+ }
32
+ /**
33
+ * Extracts the class name from a styled component reference.
34
+ *
35
+ * @param key - Property key
36
+ * @param value - Property value
37
+ * @returns Extracted class name or empty string
38
+ */
39
+ function getClassNameFromKeyOrValue(key, value) {
40
+ if (typeof value === "object" &&
41
+ value !== null &&
42
+ "__stoopClassName" in value &&
43
+ typeof value.__stoopClassName === "string") {
44
+ return value.__stoopClassName;
45
+ }
46
+ if (typeof key === "string" && key.startsWith("__STOOP_COMPONENT_")) {
47
+ return key.replace("__STOOP_COMPONENT_", "");
48
+ }
49
+ return "";
50
+ }
51
+ /**
52
+ * Converts a CSS object to a CSS string with proper nesting and selectors.
53
+ * Handles pseudo-selectors, media queries, combinators, and styled component targeting.
54
+ *
55
+ * @param obj - CSS object to convert
56
+ * @param selector - Current selector context
57
+ * @param depth - Current nesting depth
58
+ * @param media - Media query breakpoints
59
+ * @returns CSS string
60
+ */
61
+ function cssObjectToString(obj, selector = "", depth = 0, media) {
62
+ if (!obj || typeof obj !== "object") {
63
+ return "";
64
+ }
65
+ if (depth > MAX_CSS_NESTING_DEPTH) {
66
+ return "";
67
+ }
68
+ const cssProperties = [];
69
+ const nestedRulesList = [];
70
+ const keys = Object.keys(obj).sort();
71
+ for (const key of keys) {
72
+ const value = obj[key];
73
+ if (isStyledComponentKey(key, value)) {
74
+ const componentClassName = getClassNameFromKeyOrValue(key, value);
75
+ if (!componentClassName) {
76
+ continue;
77
+ }
78
+ const sanitizedClassName = sanitizeCSSSelector(componentClassName);
79
+ if (!sanitizedClassName) {
80
+ continue;
81
+ }
82
+ const componentSelector = selector
83
+ ? `${selector} .${sanitizedClassName}`
84
+ : `.${sanitizedClassName}`;
85
+ const nestedCss = isValidCSSObject(value)
86
+ ? cssObjectToString(value, componentSelector, depth + 1, media)
87
+ : "";
88
+ if (nestedCss) {
89
+ nestedRulesList.push(nestedCss);
90
+ }
91
+ continue;
92
+ }
93
+ if (isValidCSSObject(value)) {
94
+ if (media && key in media) {
95
+ const mediaQuery = sanitizeMediaQuery(media[key]);
96
+ if (mediaQuery) {
97
+ const nestedCss = cssObjectToString(value, selector, depth + 1, media);
98
+ if (nestedCss) {
99
+ nestedRulesList.push(`${mediaQuery} { ${nestedCss} }`);
100
+ }
101
+ }
102
+ }
103
+ else if (key.startsWith("@")) {
104
+ const sanitizedKey = sanitizeCSSSelector(key);
105
+ if (sanitizedKey) {
106
+ const nestedCss = cssObjectToString(value, selector, depth + 1, media);
107
+ if (nestedCss) {
108
+ nestedRulesList.push(`${sanitizedKey} { ${nestedCss} }`);
109
+ }
110
+ }
111
+ }
112
+ else if (key.includes("&")) {
113
+ const sanitizedKey = sanitizeCSSSelector(key);
114
+ if (sanitizedKey) {
115
+ const parts = sanitizedKey.split("&");
116
+ const nestedSelector = parts.join(selector);
117
+ const nestedCss = cssObjectToString(value, nestedSelector, depth + 1, media);
118
+ if (nestedCss) {
119
+ nestedRulesList.push(nestedCss);
120
+ }
121
+ }
122
+ }
123
+ else if (key.startsWith(":")) {
124
+ const sanitizedKey = sanitizeCSSSelector(key);
125
+ if (sanitizedKey) {
126
+ const nestedSelector = `${selector}${sanitizedKey}`;
127
+ const nestedCss = cssObjectToString(value, nestedSelector, depth + 1, media);
128
+ if (nestedCss) {
129
+ nestedRulesList.push(nestedCss);
130
+ }
131
+ }
132
+ }
133
+ else if (key.includes(" ") || key.includes(">") || key.includes("+") || key.includes("~")) {
134
+ const sanitizedKey = sanitizeCSSSelector(key);
135
+ if (sanitizedKey) {
136
+ const startsWithCombinator = /^[\s>+~]/.test(sanitizedKey.trim());
137
+ const nestedSelector = startsWithCombinator
138
+ ? `${selector}${sanitizedKey}`
139
+ : `${selector} ${sanitizedKey}`;
140
+ const nestedCss = cssObjectToString(value, nestedSelector, depth + 1, media);
141
+ if (nestedCss) {
142
+ nestedRulesList.push(nestedCss);
143
+ }
144
+ }
145
+ }
146
+ else {
147
+ const sanitizedKey = sanitizeCSSSelector(key);
148
+ if (sanitizedKey) {
149
+ const nestedSelector = selector ? `${selector} ${sanitizedKey}` : sanitizedKey;
150
+ const nestedCss = cssObjectToString(value, nestedSelector, depth + 1, media);
151
+ if (nestedCss) {
152
+ nestedRulesList.push(nestedCss);
153
+ }
154
+ }
155
+ }
156
+ }
157
+ else if (value !== undefined) {
158
+ const property = sanitizeCSSPropertyName(key);
159
+ if (property && (typeof value === "string" || typeof value === "number")) {
160
+ const escapedValue = escapeCSSValue(value);
161
+ cssProperties.push(`${property}: ${escapedValue};`);
162
+ }
163
+ }
164
+ }
165
+ const rule = cssProperties.length > 0 ? `${selector} { ${cssProperties.join(" ")} }` : "";
166
+ return rule + nestedRulesList.join("");
167
+ }
168
+ /**
169
+ * Compiles CSS objects into CSS strings and generates unique class names.
170
+ * Handles nested selectors, media queries, styled component targeting, and theme tokens.
171
+ *
172
+ * @param styles - CSS object to compile
173
+ * @param currentTheme - Theme for token resolution
174
+ * @param prefix - Optional prefix for generated class names
175
+ * @param media - Optional media query breakpoints
176
+ * @param utils - Optional utility functions
177
+ * @param themeMap - Optional theme scale mappings
178
+ * @returns Generated class name
179
+ */
180
+ export function compileCSS(styles, currentTheme, prefix = "stoop", media, utils, themeMap) {
181
+ const sanitizedPrefix = sanitizePrefix(prefix);
182
+ const stylesWithUtils = applyUtilities(styles, utils);
183
+ const themedStyles = replaceThemeTokensWithVars(stylesWithUtils, currentTheme, themeMap);
184
+ const cssString = cssObjectToString(themedStyles, "", 0, media);
185
+ const stylesHash = hash(cssString);
186
+ const className = sanitizedPrefix ? `${sanitizedPrefix}-${stylesHash}` : `css-${stylesHash}`;
187
+ const cacheKey = `${sanitizedPrefix}:${className}`;
188
+ const cachedCSS = cssStringCache.get(cacheKey);
189
+ if (cachedCSS) {
190
+ injectCSS(cachedCSS, sanitizedPrefix, cacheKey);
191
+ return className;
192
+ }
193
+ const fullCSS = cssObjectToString(themedStyles, `.${className}`, 0, media);
194
+ cssStringCache.set(cacheKey, fullCSS);
195
+ classNameCache.set(cacheKey, className);
196
+ injectCSS(fullCSS, sanitizedPrefix, cacheKey);
197
+ return className;
198
+ }
@@ -13,16 +13,8 @@ import type { Theme } from "../types";
13
13
  * @param prefix - Optional prefix for theme scoping
14
14
  */
15
15
  export declare function registerDefaultTheme(theme: Theme, prefix?: string): void;
16
- /**
17
- * Gets the default theme for a given prefix.
18
- *
19
- * @param prefix - Optional prefix for theme scoping
20
- * @returns Default theme or null if not registered
21
- */
22
- export declare function getDefaultTheme(prefix?: string): Theme | null;
23
16
  /**
24
17
  * Updates CSS custom properties when theme changes.
25
- * Automatically merges the theme with the default theme to ensure all properties are present.
26
18
  *
27
19
  * @param theme - Theme object to generate CSS variables from
28
20
  * @param prefix - Optional prefix for CSS variable names
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Theme variable management.
3
+ * Updates CSS custom properties when theme changes.
4
+ * Ensures CSS variables are injected and kept in sync with theme updates.
5
+ * Automatically merges themes with the default theme when applied.
6
+ */
7
+ import { injectThemeVariables } from "../inject";
8
+ import { isBrowser } from "../utils/environment";
9
+ import { generateCSSVariables, themesAreEqual } from "../utils/theme";
10
+ const defaultThemes = new Map();
11
+ const mergedThemeCache = new WeakMap();
12
+ /**
13
+ * Registers the default theme for a given prefix.
14
+ * Called automatically by createStoop.
15
+ *
16
+ * @param theme - Default theme from createStoop
17
+ * @param prefix - Optional prefix for theme scoping
18
+ */
19
+ export function registerDefaultTheme(theme, prefix = "stoop") {
20
+ const sanitizedPrefix = prefix || "";
21
+ defaultThemes.set(sanitizedPrefix, theme);
22
+ }
23
+ /**
24
+ * Gets the default theme for a given prefix.
25
+ *
26
+ * @param prefix - Optional prefix for theme scoping
27
+ * @returns Default theme or null if not registered
28
+ */
29
+ function getDefaultTheme(prefix = "stoop") {
30
+ const sanitizedPrefix = prefix || "";
31
+ return defaultThemes.get(sanitizedPrefix) || null;
32
+ }
33
+ /**
34
+ * Merges a theme with the default theme if it's not already the default theme.
35
+ * This ensures all themes extend the default theme, keeping all original properties.
36
+ * Uses caching to avoid repeated merging of the same theme objects.
37
+ *
38
+ * @param theme - Theme to merge
39
+ * @param prefix - Optional prefix for theme scoping
40
+ * @returns Merged theme (or original if it's the default theme)
41
+ */
42
+ function mergeWithDefaultTheme(theme, prefix = "stoop") {
43
+ const defaultTheme = getDefaultTheme(prefix);
44
+ if (!defaultTheme) {
45
+ return theme;
46
+ }
47
+ if (themesAreEqual(theme, defaultTheme)) {
48
+ return theme;
49
+ }
50
+ let prefixCache = mergedThemeCache.get(theme);
51
+ if (!prefixCache) {
52
+ prefixCache = new Map();
53
+ mergedThemeCache.set(theme, prefixCache);
54
+ }
55
+ const cached = prefixCache.get(prefix);
56
+ if (cached) {
57
+ return cached;
58
+ }
59
+ const merged = { ...defaultTheme };
60
+ const allThemeKeys = Object.keys(theme);
61
+ for (const key of allThemeKeys) {
62
+ if (key === "media") {
63
+ continue;
64
+ }
65
+ const themeValue = theme[key];
66
+ const defaultValue = defaultTheme[key];
67
+ if (themeValue &&
68
+ typeof themeValue === "object" &&
69
+ !Array.isArray(themeValue) &&
70
+ defaultValue &&
71
+ typeof defaultValue === "object" &&
72
+ !Array.isArray(defaultValue)) {
73
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
74
+ merged[key] = { ...defaultValue, ...themeValue };
75
+ }
76
+ else if (themeValue !== undefined) {
77
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
78
+ merged[key] = themeValue;
79
+ }
80
+ }
81
+ prefixCache.set(prefix, merged);
82
+ return merged;
83
+ }
84
+ /**
85
+ * Updates CSS custom properties when theme changes.
86
+ *
87
+ * @param theme - Theme object to generate CSS variables from
88
+ * @param prefix - Optional prefix for CSS variable names
89
+ */
90
+ export function updateThemeVariables(theme, prefix = "stoop") {
91
+ if (!isBrowser()) {
92
+ return;
93
+ }
94
+ const mergedTheme = mergeWithDefaultTheme(theme, prefix);
95
+ const cssVars = generateCSSVariables(mergedTheme, prefix);
96
+ injectThemeVariables(cssVars, mergedTheme, prefix);
97
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Variant application logic.
3
+ * Merges variant styles with base styles based on component props.
4
+ * Optimized to avoid unnecessary object spreads when no variants are applied.
5
+ */
6
+ /**
7
+ * Applies variant styles to base styles based on component props.
8
+ *
9
+ * @param variants - Variant configuration object
10
+ * @param props - Component props containing variant values
11
+ * @param baseStyles - Base styles to merge with variant styles
12
+ * @returns Merged CSS object
13
+ */
14
+ export function applyVariants(variants, props, baseStyles) {
15
+ let hasVariants = false;
16
+ const appliedVariantStyles = [];
17
+ for (const variantName in variants) {
18
+ const propValue = props[variantName];
19
+ if (propValue === undefined) {
20
+ continue;
21
+ }
22
+ const variantOptions = variants[variantName];
23
+ const key = propValue === true ? "true" : propValue === false ? "false" : String(propValue);
24
+ if (variantOptions[key]) {
25
+ appliedVariantStyles.push(variantOptions[key]);
26
+ hasVariants = true;
27
+ }
28
+ }
29
+ return hasVariants
30
+ ? { ...baseStyles, ...appliedVariantStyles.reduce((acc, style) => ({ ...acc, ...style }), {}) }
31
+ : baseStyles;
32
+ }
@@ -0,0 +1,33 @@
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 type { CSS, StoopConfig, Theme } from "./types";
9
+ /**
10
+ * Server-safe Stoop instance interface.
11
+ * Only includes APIs that work without React dependencies.
12
+ */
13
+ export interface StoopServerInstance {
14
+ config: StoopConfig & {
15
+ prefix: string;
16
+ };
17
+ createTheme: (themeOverride: Partial<Theme>) => Theme;
18
+ css: (styles: CSS) => string;
19
+ getCssText: (theme?: string | Theme) => string;
20
+ globalCss: (...args: CSS[]) => () => void;
21
+ keyframes: (definition: Record<string, CSS>) => string;
22
+ preloadTheme: (theme: string | Theme) => void;
23
+ theme: Theme;
24
+ warmCache: (styles: CSS[]) => void;
25
+ }
26
+ /**
27
+ * Creates a server-safe Stoop instance with the provided configuration.
28
+ * Only includes APIs that work without React: css, globalCss, keyframes, getCssText, etc.
29
+ *
30
+ * @param config - Configuration object containing theme, media queries, utilities, and optional prefix/themeMap
31
+ * @returns StoopServerInstance with server-safe API functions
32
+ */
33
+ export declare function createStoop(config: StoopConfig): StoopServerInstance;