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,43 @@
1
+ /**
2
+ * Theme extension API.
3
+ * Creates a function that deep merges theme overrides with a base theme.
4
+ */
5
+ import { validateTheme } from "../utils/theme-validation";
6
+ import { isThemeObject } from "../utils/type-guards";
7
+ /**
8
+ * Creates a function that extends a base theme with overrides.
9
+ * The returned function deep merges theme overrides with the base theme.
10
+ *
11
+ * @param baseTheme - Base theme to extend
12
+ * @returns Function that accepts theme overrides and returns a merged theme
13
+ */
14
+ export function createTheme(baseTheme) {
15
+ return function createTheme(themeOverrides) {
16
+ const validatedOverrides = validateTheme(themeOverrides);
17
+ function deepMerge(target, source) {
18
+ const result = { ...target };
19
+ const sourceKeys = Object.keys(source);
20
+ for (const key of sourceKeys) {
21
+ const sourceValue = source[key];
22
+ const targetValue = target[key];
23
+ if (isThemeObject(sourceValue) && isThemeObject(targetValue)) {
24
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
+ result[key] = { ...targetValue, ...sourceValue };
26
+ }
27
+ else if (sourceValue !== undefined) {
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ result[key] = sourceValue;
30
+ }
31
+ }
32
+ const targetKeys = Object.keys(target);
33
+ for (const key of targetKeys) {
34
+ if (!(key in result)) {
35
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
+ result[key] = target[key];
37
+ }
38
+ }
39
+ return result;
40
+ }
41
+ return deepMerge(baseTheme, validatedOverrides);
42
+ };
43
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * CSS class generation API.
3
+ * Creates a function that compiles CSS objects into class names.
4
+ */
5
+ import { compileCSS } from "../core/compiler";
6
+ /**
7
+ * Creates a CSS function that compiles CSS objects into class names.
8
+ *
9
+ * @param defaultTheme - Default theme for token resolution
10
+ * @param prefix - Optional prefix for generated class names
11
+ * @param media - Optional media query breakpoints
12
+ * @param utils - Optional utility functions
13
+ * @param themeMap - Optional theme scale mappings
14
+ * @returns Function that accepts CSS objects and returns class names
15
+ */
16
+ export function createCSSFunction(defaultTheme, prefix = "stoop", media, utils, themeMap) {
17
+ return function css(styles) {
18
+ return compileCSS(styles, defaultTheme, prefix, media, utils, themeMap);
19
+ };
20
+ }
@@ -4,15 +4,4 @@
4
4
  * Supports media queries, nested selectors, and theme tokens.
5
5
  */
6
6
  import type { CSS, Theme, ThemeScale, UtilityFunction } from "../types";
7
- /**
8
- * Creates a global CSS injection function.
9
- * Injects styles directly into the document with deduplication support.
10
- *
11
- * @param defaultTheme - Default theme for token resolution
12
- * @param prefix - Optional prefix for CSS rules
13
- * @param media - Optional media query breakpoints
14
- * @param utils - Optional utility functions
15
- * @param themeMap - Optional theme scale mappings
16
- * @returns Function that accepts CSS objects and returns a cleanup function
17
- */
18
7
  export declare function createGlobalCSSFunction(defaultTheme: Theme, prefix?: string, media?: Record<string, string>, utils?: Record<string, UtilityFunction>, themeMap?: Record<string, ThemeScale>): (styles: CSS) => () => void;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Global CSS injection API.
3
+ * Creates a function that injects global styles into the document.
4
+ * Supports media queries, nested selectors, and theme tokens.
5
+ */
6
+ import { MAX_CSS_NESTING_DEPTH } from "../constants";
7
+ import { injectCSS } from "../inject";
8
+ import { escapeCSSValue, hashObject, sanitizeCSSPropertyName, sanitizeCSSSelector, sanitizeMediaQuery, sanitizePrefix, } from "../utils/string";
9
+ import { replaceThemeTokensWithVars } from "../utils/theme";
10
+ import { isCSSObject } from "../utils/type-guards";
11
+ import { applyUtilities } from "../utils/utilities";
12
+ /**
13
+ * Creates a global CSS injection function.
14
+ * Injects styles directly into the document with deduplication support.
15
+ *
16
+ * @param defaultTheme - Default theme for token resolution
17
+ * @param prefix - Optional prefix for CSS rules
18
+ * @param media - Optional media query breakpoints
19
+ * @param utils - Optional utility functions
20
+ * @param themeMap - Optional theme scale mappings
21
+ * @returns Function that accepts CSS objects and returns a cleanup function
22
+ */
23
+ const globalInjectedStyles = new WeakMap();
24
+ function getInjectedSet(theme) {
25
+ let set = globalInjectedStyles.get(theme);
26
+ if (!set) {
27
+ set = new Set();
28
+ globalInjectedStyles.set(theme, set);
29
+ }
30
+ return set;
31
+ }
32
+ export function createGlobalCSSFunction(defaultTheme, prefix = "stoop", media, utils, themeMap) {
33
+ return function globalCss(styles) {
34
+ const injected = getInjectedSet(defaultTheme);
35
+ const cssKey = hashObject(styles);
36
+ if (injected.has(cssKey)) {
37
+ return () => { };
38
+ }
39
+ injected.add(cssKey);
40
+ function generateGlobalCSS(obj, depth = 0) {
41
+ if (depth > MAX_CSS_NESTING_DEPTH) {
42
+ return "";
43
+ }
44
+ let result = "";
45
+ const sortedEntries = Object.entries(obj).sort(([a], [b]) => a.localeCompare(b));
46
+ sortedEntries.forEach(([key, value]) => {
47
+ if (isCSSObject(value)) {
48
+ if (media && key in media) {
49
+ const mediaQuery = sanitizeMediaQuery(media[key]);
50
+ if (mediaQuery) {
51
+ const nestedCss = generateGlobalCSS(value, depth + 1);
52
+ result += `${mediaQuery} { ${nestedCss} }`;
53
+ }
54
+ }
55
+ else if (key.startsWith("@")) {
56
+ const sanitizedKey = sanitizeCSSSelector(key);
57
+ if (sanitizedKey) {
58
+ const nestedCss = generateGlobalCSS(value, depth + 1);
59
+ result += `${sanitizedKey} { ${nestedCss} }`;
60
+ }
61
+ }
62
+ else {
63
+ const sanitizedKey = sanitizeCSSSelector(key);
64
+ if (sanitizedKey) {
65
+ const nestedCss = generateGlobalCSS(value, depth + 1);
66
+ result += `${sanitizedKey} { ${nestedCss} }`;
67
+ }
68
+ }
69
+ }
70
+ else if (value !== undefined) {
71
+ const property = sanitizeCSSPropertyName(key);
72
+ if (property && (typeof value === "string" || typeof value === "number")) {
73
+ const escapedValue = escapeCSSValue(value);
74
+ result += `${property}: ${escapedValue}; `;
75
+ }
76
+ }
77
+ });
78
+ return result;
79
+ }
80
+ const sanitizedPrefix = sanitizePrefix(prefix);
81
+ const stylesWithUtils = applyUtilities(styles, utils);
82
+ const themedStyles = replaceThemeTokensWithVars(stylesWithUtils, defaultTheme, themeMap);
83
+ const cssText = generateGlobalCSS(themedStyles);
84
+ injectCSS(cssText, sanitizedPrefix, `__global_${cssKey}`);
85
+ return () => {
86
+ injected.delete(cssKey);
87
+ };
88
+ };
89
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * CSS keyframes animation API.
3
+ * Creates a function that generates and injects @keyframes rules.
4
+ * Caches animations by content hash to prevent duplicates.
5
+ */
6
+ import { LRUCache } from "../core/cache";
7
+ import { injectCSS } from "../inject";
8
+ import { hashObject, sanitizeCSSPropertyName, sanitizePrefix, validateKeyframeKey, } from "../utils/string";
9
+ import { replaceThemeTokensWithVars } from "../utils/theme";
10
+ /**
11
+ * Converts a keyframes object to a CSS @keyframes rule string.
12
+ *
13
+ * @param keyframesObj - Keyframes object with percentage/from/to keys
14
+ * @param animationName - Name for the animation
15
+ * @param theme - Optional theme for token resolution
16
+ * @param themeMap - Optional theme scale mappings
17
+ * @returns CSS @keyframes rule string
18
+ */
19
+ function keyframesToCSS(keyframesObj, animationName, theme, themeMap) {
20
+ let css = `@keyframes ${animationName} {`;
21
+ const sortedKeys = Object.keys(keyframesObj).sort((a, b) => {
22
+ const aNum = parseFloat(a.replace("%", ""));
23
+ const bNum = parseFloat(b.replace("%", ""));
24
+ if (a === "from") {
25
+ return -1;
26
+ }
27
+ if (b === "from") {
28
+ return 1;
29
+ }
30
+ if (a === "to") {
31
+ return 1;
32
+ }
33
+ if (b === "to") {
34
+ return -1;
35
+ }
36
+ return aNum - bNum;
37
+ });
38
+ for (const key of sortedKeys) {
39
+ if (!validateKeyframeKey(key)) {
40
+ continue;
41
+ }
42
+ const styles = keyframesObj[key];
43
+ if (!styles || typeof styles !== "object") {
44
+ continue;
45
+ }
46
+ css += ` ${key} {`;
47
+ const themedStyles = replaceThemeTokensWithVars(styles, theme, themeMap);
48
+ // Sort properties for deterministic CSS generation
49
+ const sortedProps = Object.keys(themedStyles).sort();
50
+ for (const prop of sortedProps) {
51
+ const value = themedStyles[prop];
52
+ if (value !== undefined && (typeof value === "string" || typeof value === "number")) {
53
+ const sanitizedProp = sanitizeCSSPropertyName(prop);
54
+ if (sanitizedProp) {
55
+ // Don't escape keyframe values - escaping breaks complex CSS functions
56
+ const cssValue = String(value);
57
+ css += ` ${sanitizedProp}: ${cssValue};`;
58
+ }
59
+ }
60
+ }
61
+ css += " }";
62
+ }
63
+ css += " }";
64
+ return css;
65
+ }
66
+ const KEYFRAME_CACHE_LIMIT = 500;
67
+ /**
68
+ * Creates a keyframes animation function.
69
+ * Generates and injects @keyframes rules with caching to prevent duplicates.
70
+ *
71
+ * @param prefix - Optional prefix for animation names
72
+ * @param theme - Optional theme for token resolution
73
+ * @param themeMap - Optional theme scale mappings
74
+ * @returns Function that accepts keyframes objects and returns animation names
75
+ */
76
+ export function createKeyframesFunction(prefix = "stoop", theme, themeMap) {
77
+ const sanitizedPrefix = sanitizePrefix(prefix);
78
+ const animationCache = new LRUCache(KEYFRAME_CACHE_LIMIT);
79
+ return function keyframes(keyframesObj) {
80
+ const keyframesKey = hashObject(keyframesObj);
81
+ const cachedName = animationCache.get(keyframesKey);
82
+ if (cachedName) {
83
+ return cachedName;
84
+ }
85
+ const hashValue = keyframesKey.slice(0, 8);
86
+ const animationName = sanitizedPrefix
87
+ ? `${sanitizedPrefix}-${hashValue}`
88
+ : `stoop-${hashValue}`;
89
+ const css = keyframesToCSS(keyframesObj, animationName, theme, themeMap);
90
+ const ruleKey = `__keyframes_${animationName}`;
91
+ injectCSS(css, sanitizedPrefix, ruleKey);
92
+ animationCache.set(keyframesKey, animationName);
93
+ return animationName;
94
+ };
95
+ }
@@ -7,13 +7,13 @@ import type { ProviderProps, Theme, ThemeContextValue, ThemeManagementContextVal
7
7
  /**
8
8
  * Creates a Provider component for theme management.
9
9
  *
10
- * @param ThemeContext - Stoop's theme context for styled components
11
10
  * @param themes - Map of theme names to theme objects
12
11
  * @param defaultTheme - Default theme object
13
12
  * @param prefix - Optional prefix for CSS variable scoping
14
- * @returns Provider component and theme management context
13
+ * @returns Provider component, theme context, and theme management context
15
14
  */
16
- export declare function createProvider(ThemeContext: Context<ThemeContextValue | null>, themes: Record<string, Theme>, defaultTheme: Theme, prefix?: string): {
15
+ export declare function createProvider(themes: Record<string, Theme>, defaultTheme: Theme, prefix?: string): {
17
16
  Provider: ComponentType<ProviderProps>;
17
+ ThemeContext: Context<ThemeContextValue | null>;
18
18
  ThemeManagementContext: Context<ThemeManagementContextValue | null>;
19
19
  };
@@ -0,0 +1,109 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ /**
4
+ * Theme Provider component.
5
+ * Manages theme state, localStorage persistence, and centralized theme variable updates.
6
+ */
7
+ import { createContext, useCallback, useLayoutEffect, useMemo, useState, } from "react";
8
+ import { updateThemeVariables } from "../core/theme-manager";
9
+ import { isBrowser } from "../utils/environment";
10
+ /**
11
+ * Creates a Provider component for theme management.
12
+ *
13
+ * @param themes - Map of theme names to theme objects
14
+ * @param defaultTheme - Default theme object
15
+ * @param prefix - Optional prefix for CSS variable scoping
16
+ * @returns Provider component, theme context, and theme management context
17
+ */
18
+ export function createProvider(themes, defaultTheme, prefix = "stoop") {
19
+ const ThemeContext = createContext(null);
20
+ const ThemeManagementContext = createContext(null);
21
+ const availableThemeNames = Object.keys(themes);
22
+ const firstThemeName = availableThemeNames[0] || "default";
23
+ function Provider({ attribute = "data-theme", children, defaultTheme: defaultThemeProp, storageKey = "stoop-theme", }) {
24
+ // SSR-safe initialization: always start with default, then hydrate from localStorage
25
+ const [themeName, setThemeNameState] = useState(() => {
26
+ // During SSR, always return the default theme
27
+ if (!isBrowser()) {
28
+ return defaultThemeProp || firstThemeName;
29
+ }
30
+ // On client, try to read from localStorage
31
+ try {
32
+ const stored = localStorage.getItem(storageKey);
33
+ if (stored && themes[stored]) {
34
+ return stored;
35
+ }
36
+ }
37
+ catch {
38
+ // localStorage access failed (e.g., in private browsing mode)
39
+ }
40
+ return defaultThemeProp || firstThemeName;
41
+ });
42
+ // Hydrate from localStorage after mount to prevent hydration mismatch
43
+ useLayoutEffect(() => {
44
+ if (!isBrowser()) {
45
+ return;
46
+ }
47
+ try {
48
+ const stored = localStorage.getItem(storageKey);
49
+ if (stored && themes[stored] && stored !== themeName) {
50
+ setThemeNameState(stored);
51
+ }
52
+ }
53
+ catch {
54
+ // localStorage access failed
55
+ }
56
+ }, [storageKey, themeName]);
57
+ const currentTheme = useMemo(() => {
58
+ return themes[themeName] || themes[defaultThemeProp || firstThemeName] || defaultTheme;
59
+ }, [themeName, defaultThemeProp, firstThemeName, themes]);
60
+ useLayoutEffect(() => {
61
+ if (currentTheme) {
62
+ updateThemeVariables(currentTheme, prefix);
63
+ }
64
+ }, [currentTheme, prefix]);
65
+ useLayoutEffect(() => {
66
+ if (isBrowser() && attribute) {
67
+ document.documentElement.setAttribute(attribute, themeName);
68
+ }
69
+ }, [themeName, attribute]);
70
+ const setTheme = useCallback((newThemeName) => {
71
+ if (themes[newThemeName]) {
72
+ setThemeNameState(newThemeName);
73
+ try {
74
+ localStorage.setItem(storageKey, newThemeName);
75
+ }
76
+ catch {
77
+ // localStorage access failed (e.g., in private browsing mode)
78
+ }
79
+ }
80
+ else if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
81
+ // eslint-disable-next-line no-console
82
+ console.warn(`[Stoop] Theme "${newThemeName}" not found. Available themes: ${availableThemeNames.join(", ")}`);
83
+ }
84
+ }, [storageKey, themes, availableThemeNames]);
85
+ const themeContextValue = useMemo(() => ({
86
+ theme: currentTheme,
87
+ themeName,
88
+ }), [currentTheme, themeName]);
89
+ const toggleTheme = useCallback(() => {
90
+ const currentIndex = availableThemeNames.indexOf(themeName);
91
+ const nextIndex = (currentIndex + 1) % availableThemeNames.length;
92
+ const newTheme = availableThemeNames[nextIndex];
93
+ setTheme(newTheme);
94
+ }, [themeName, setTheme]);
95
+ const managementContextValue = useMemo(() => ({
96
+ availableThemes: availableThemeNames,
97
+ setTheme,
98
+ theme: currentTheme,
99
+ themeName,
100
+ toggleTheme,
101
+ }), [currentTheme, themeName, setTheme, toggleTheme]);
102
+ return (_jsx(ThemeContext.Provider, { value: themeContextValue, children: _jsx(ThemeManagementContext.Provider, { value: managementContextValue, children: children }) }));
103
+ }
104
+ return {
105
+ Provider,
106
+ ThemeContext,
107
+ ThemeManagementContext,
108
+ };
109
+ }
@@ -0,0 +1,170 @@
1
+ "use client";
2
+ /**
3
+ * Styled component API.
4
+ * Creates polymorphic styled components with variant support, theme awareness,
5
+ * and CSS prop merging. Supports component targeting via selector references.
6
+ */
7
+ import { useMemo, forwardRef, createElement, useContext, createContext, } from "react";
8
+ import { EMPTY_CSS, STOOP_COMPONENT_SYMBOL } from "../constants";
9
+ import { compileCSS } from "../core/compiler";
10
+ import { applyVariants } from "../core/variants";
11
+ import { hash, sanitizeClassName } from "../utils/string";
12
+ let defaultThemeContext = null;
13
+ function getDefaultThemeContext() {
14
+ if (!defaultThemeContext) {
15
+ defaultThemeContext = createContext(null);
16
+ }
17
+ return defaultThemeContext;
18
+ }
19
+ /**
20
+ * Creates a styled component reference for selector targeting.
21
+ *
22
+ * @param className - Class name to reference
23
+ * @returns StyledComponentRef for use in CSS selectors
24
+ */
25
+ export function createStyledComponentRef(className) {
26
+ const ref = {
27
+ __isStoopStyled: true,
28
+ __stoopClassName: className,
29
+ [STOOP_COMPONENT_SYMBOL]: className,
30
+ toString: () => `__STOOP_COMPONENT_${className}`,
31
+ };
32
+ return ref;
33
+ }
34
+ /**
35
+ * Type guard for styled component references.
36
+ *
37
+ * @param value - Value to check
38
+ * @returns True if value is a styled component reference
39
+ */
40
+ function isStyledComponent(value) {
41
+ return (typeof value === "object" &&
42
+ value !== null &&
43
+ "__isStoopStyled" in value &&
44
+ value.__isStoopStyled === true);
45
+ }
46
+ /**
47
+ * Separates component props into variant props and element props.
48
+ * Variant props are used for style variants, element props are passed to the DOM element.
49
+ *
50
+ * @param props - All component props
51
+ * @param variants - Variant configuration
52
+ * @returns Object with separated elementProps and variantProps
53
+ */
54
+ function extractVariantProps(props, variants) {
55
+ if (!variants) {
56
+ return { elementProps: props, variantProps: {} };
57
+ }
58
+ const variantKeys = new Set(Object.keys(variants));
59
+ const variantProps = {};
60
+ const elementProps = {};
61
+ for (const key in props) {
62
+ if (variantKeys.has(key)) {
63
+ variantProps[key] = props[key];
64
+ }
65
+ else {
66
+ elementProps[key] = props[key];
67
+ }
68
+ }
69
+ return { elementProps, variantProps };
70
+ }
71
+ /**
72
+ * Creates a styled component factory function.
73
+ * Supports polymorphic components, variants, theme awareness, and CSS prop merging.
74
+ *
75
+ * @param defaultTheme - Default theme for token resolution
76
+ * @param prefix - Optional prefix for generated class names
77
+ * @param media - Optional media query breakpoints
78
+ * @param utils - Optional utility functions
79
+ * @param themeMap - Optional theme scale mappings
80
+ * @param themeContext - React context for theme values (instance-specific)
81
+ * @returns Styled component factory function
82
+ */
83
+ export function createStyledFunction(defaultTheme, prefix = "stoop", media, utils, themeMap, themeContext) {
84
+ return function styled(defaultElement, baseStylesOrVariants, variantsParam) {
85
+ let actualBaseStyles = (baseStylesOrVariants || EMPTY_CSS);
86
+ let actualVariants = variantsParam;
87
+ if (baseStylesOrVariants &&
88
+ "variants" in baseStylesOrVariants &&
89
+ typeof baseStylesOrVariants.variants === "object") {
90
+ actualVariants = baseStylesOrVariants.variants;
91
+ const { compoundVariants: __, variants: _, ...rest } = baseStylesOrVariants;
92
+ actualBaseStyles = rest;
93
+ }
94
+ let baseElementClassName;
95
+ if (typeof defaultElement !== "string" && isStyledComponent(defaultElement)) {
96
+ baseElementClassName = defaultElement.__stoopClassName;
97
+ }
98
+ const StyledComponent = forwardRef(function StyledComponent(propsWithBase, ref) {
99
+ const { as, className, css: cssStyles, ...restProps } = propsWithBase;
100
+ const element = (as || defaultElement);
101
+ const cssObject = useMemo(() => cssStyles && typeof cssStyles === "object" && cssStyles !== null
102
+ ? cssStyles
103
+ : EMPTY_CSS, [cssStyles]);
104
+ const { elementProps, variantProps } = extractVariantProps(restProps, actualVariants);
105
+ const contextValue = useContext(themeContext || getDefaultThemeContext());
106
+ const currentTheme = contextValue?.theme || defaultTheme;
107
+ const currentMedia = currentTheme.media ? { ...media, ...currentTheme.media } : media;
108
+ const variantKey = useMemo(() => {
109
+ if (!actualVariants) {
110
+ return "";
111
+ }
112
+ const variantEntries = Object.entries(variantProps);
113
+ if (variantEntries.length === 0) {
114
+ return "";
115
+ }
116
+ return variantEntries
117
+ .sort(([a], [b]) => a.localeCompare(b))
118
+ .map(([key, value]) => `${key}:${String(value)}`)
119
+ .join("|");
120
+ }, [variantProps]);
121
+ const finalStyles = useMemo(() => {
122
+ let componentStyles = actualBaseStyles;
123
+ if (actualVariants && variantKey) {
124
+ componentStyles = applyVariants(actualVariants, variantProps, actualBaseStyles);
125
+ }
126
+ if (cssObject !== EMPTY_CSS) {
127
+ componentStyles = Object.assign({}, componentStyles, cssObject);
128
+ }
129
+ return componentStyles;
130
+ }, [variantKey, cssObject, actualBaseStyles, actualVariants, variantProps]);
131
+ const finalClassName = useMemo(() => {
132
+ const classNames = [];
133
+ if (baseElementClassName) {
134
+ classNames.push(baseElementClassName);
135
+ }
136
+ const mergedClass = compileCSS(finalStyles, currentTheme, prefix, currentMedia, utils, themeMap);
137
+ if (mergedClass) {
138
+ classNames.push(mergedClass);
139
+ }
140
+ if (className) {
141
+ const classNameStr = typeof className === "string" ? className : String(className);
142
+ const sanitizedClassName = sanitizeClassName(classNameStr);
143
+ if (sanitizedClassName) {
144
+ classNames.push(sanitizedClassName);
145
+ }
146
+ }
147
+ return classNames.length > 0 ? classNames.join(" ") : undefined;
148
+ }, [
149
+ finalStyles,
150
+ className,
151
+ baseElementClassName,
152
+ currentTheme,
153
+ prefix,
154
+ currentMedia,
155
+ utils,
156
+ themeMap,
157
+ ]);
158
+ return createElement(element, {
159
+ ...elementProps,
160
+ className: finalClassName,
161
+ ref,
162
+ });
163
+ });
164
+ const selectorHash = hash(JSON.stringify(actualBaseStyles));
165
+ const selectorClassName = `${prefix}-${selectorHash}`;
166
+ const componentWithSelector = StyledComponent;
167
+ componentWithSelector.selector = createStyledComponentRef(selectorClassName);
168
+ return componentWithSelector;
169
+ };
170
+ }
@@ -0,0 +1,21 @@
1
+ "use client";
2
+ /**
3
+ * Theme management hook.
4
+ * Provides access to theme state and theme switching functions.
5
+ */
6
+ import { useContext } from "react";
7
+ /**
8
+ * Creates a useTheme hook for a specific theme management context.
9
+ *
10
+ * @param ThemeManagementContext - React context for theme management
11
+ * @returns Hook function that returns theme management context value
12
+ */
13
+ export function createUseThemeHook(ThemeManagementContext) {
14
+ return function useTheme() {
15
+ const context = useContext(ThemeManagementContext);
16
+ if (!context) {
17
+ throw new Error("useTheme must be used within a Provider");
18
+ }
19
+ return context;
20
+ };
21
+ }
@@ -1,13 +1,12 @@
1
1
  /**
2
2
  * Shared constants used throughout the library.
3
- * Includes cache size limits, nesting depth limits, and fallback context.
3
+ * Includes cache size limits and nesting depth limits.
4
4
  */
5
- import type { CSS, ThemeContextValue, ThemeScale } from "./types";
5
+ import type { CSS, ThemeScale } from "./types";
6
6
  export declare const EMPTY_CSS: CSS;
7
7
  export declare const MAX_CSS_CACHE_SIZE = 10000;
8
8
  export declare const MAX_CLASS_NAME_CACHE_SIZE = 5000;
9
9
  export declare const MAX_CSS_NESTING_DEPTH = 10;
10
- export declare const FALLBACK_CONTEXT: import("react").Context<ThemeContextValue>;
11
10
  /**
12
11
  * Approved theme scales - only these scales are allowed in theme objects.
13
12
  */