solid-styles
Version:
Zero-runtime styled components for SolidJS with Lightning CSS optimization and spring animations. Production-ready CSS-in-JS with 100% test coverage.
308 lines (259 loc) • 9.79 kB
text/typescript
/**
* Enhanced Styled Function with Lightning CSS Integration
*
* This is the main integration point that enhances the styled function
* with Lightning CSS optimizations while preserving animations
*/
import { Component, JSX, splitProps, mergeProps, createComponent } from "solid-js";
import { Dynamic } from "solid-js/web";
import { RuntimeResolver, resolvePropsToClass, shouldUseRuntimeStyles, initializeResolver } from "./resolver";
import { CSSVariableManager, getCSSVariableManager, shouldUseCSSVariables } from "./css-variables";
import type { AnimationDetection, ExtractedStyle } from "../types";
// Import the original styled function and utilities from the main package
import { css } from "../../src";
// Helper function to check if tag is HTML element
const isHTMLTag = (tag: any): boolean => typeof tag === "string";
interface EnhancedStyledOptions {
resolver?: RuntimeResolver;
cssVariableManager?: CSSVariableManager;
enableDevMode?: boolean;
}
/**
* Check if component has animation props
*/
function hasAnimationProps(props: any): boolean {
return !!(
props?.animate ||
props?.animation ||
props?.motion ||
props?.transition ||
props?.initial ||
props?.exit ||
props?.variants ||
props?.whileHover ||
props?.whileTap ||
props?.whileFocus ||
props?.whileInView
);
}
/**
* Detect animations in style template
*/
function detectAnimationsInTemplate(strings: TemplateStringsArray): AnimationDetection {
const template = strings.join("");
// Check for animation properties
const hasSpringAnimations =
template.includes("spring") ||
template.includes("animate") ||
template.includes("transition") ||
template.includes("transform");
const animatedProperties: string[] = [];
// Extract animated properties
const transformProps = template.match(/transform|translate|scale|rotate|skew/gi);
if (transformProps) {
animatedProperties.push(...transformProps);
}
return {
hasSpringAnimations,
animatedProperties,
requiresRuntime: hasSpringAnimations,
};
}
/**
* Create minimal component for animations (unchanged from current system)
*/
function createMinimalComponent(tag: any, strings: TemplateStringsArray, args: any[]) {
return (props: any) => {
// Use current BauCSS system for animations
const className = css(strings, ...args.map((arg) => (typeof arg === "function" ? arg(props) : arg)));
const [localProps, otherProps] = splitProps(props, ["className", "style"]);
const mergedProps = mergeProps(otherProps, {
className: `${className} ${localProps.className || ""}`.trim(),
style: localProps.style,
});
if (isHTMLTag(tag)) {
return createComponent(Dynamic as any, {
component: tag,
...mergedProps,
});
} else {
return createComponent(tag as any, mergedProps);
}
};
}
/**
* Create static optimized component
*/
function createStaticComponent(tag: any, staticClass: string, props: any, cssVariables?: Record<string, string>) {
const [localProps, otherProps] = splitProps(props, ["className", "style", "ref"]);
// Merge class names
const className = `${staticClass} ${localProps.className || ""}`.trim();
// Handle ref with CSS variables
const handleRef = (el: HTMLElement) => {
if (cssVariables && Object.keys(cssVariables).length > 0) {
const cssVarManager = getCSSVariableManager();
cssVarManager.applyCSSVariables(el, cssVariables);
}
// Call original ref if provided
if (typeof localProps.ref === "function") {
localProps.ref(el);
}
};
const mergedProps = mergeProps(otherProps, {
className,
style: localProps.style,
ref: handleRef,
});
if (isHTMLTag(tag)) {
return createComponent(Dynamic as any, {
component: tag,
...mergedProps,
});
} else {
return createComponent(tag as any, mergedProps);
}
}
/**
* Enhanced styled function with Lightning CSS integration
*
* This function provides the main API that developers use.
* It automatically detects whether to use static optimization or runtime styles.
*/
export function enhancedStyled(tag: any, options: EnhancedStyledOptions = {}) {
return (strings: TemplateStringsArray, ...args: any[]) => {
// Detect animations in template
const animationDetection = detectAnimationsInTemplate(strings);
// Component ID for tracking
const componentId = tag.toString();
return (props: any) => {
// CRITICAL: Animation detection stays identical to preserve spring system
if (hasAnimationProps(props) || animationDetection.hasSpringAnimations) {
console.log("[LIGHTNING] Animation props detected - using current system");
// Use EXISTING animation system - zero changes!
const MinimalStyledComp = createMinimalComponent(tag, strings, args);
// Import animated HOC dynamically to avoid circular dependencies
return import("../../animation/animatedStyled").then(({ animated }) => {
const AnimatedComp = animated(MinimalStyledComp);
return createComponent(AnimatedComp, props);
});
}
// Try static class resolution
const staticClass = resolvePropsToClass(props);
if (staticClass) {
console.log("[LIGHTNING] Using static class:", staticClass);
// Check if we need CSS variables
if (shouldUseCSSVariables(props, animationDetection)) {
const cssVarManager = getCSSVariableManager();
const cssVariables = cssVarManager.generateComponentVariables(componentId, props);
return createStaticComponent(tag, staticClass, props, cssVariables);
}
return createStaticComponent(tag, staticClass, props);
}
// Check if we should use CSS variables for theme
if (shouldUseCSSVariables(props, animationDetection)) {
console.log("[LIGHTNING] Using CSS variables for theme");
// Generate base class with CSS variables
const baseClass = css(
strings,
...args.map((arg) => (typeof arg === "function" ? `var(--styled-${componentId})` : arg))
);
const cssVarManager = getCSSVariableManager();
const cssVariables = cssVarManager.generateComponentVariables(componentId, props);
return createStaticComponent(tag, baseClass, props, cssVariables);
}
// Fallback: Use current BauCSS system
console.log("[LIGHTNING] Using current BauCSS system");
return createMinimalComponent(tag, strings, args)(props);
};
};
}
/**
* Export a function to enhance the existing makeStyled
* This is what gets integrated into the main styling system
*/
export function enhanceMakeStyled(originalMakeStyled: any) {
return function (...args: any[]) {
const styled = originalMakeStyled(...args);
return function enhancedStyledWrapper(tag: any, ...styleArgs: any[]) {
// Get the original styled component
const OriginalStyledComponent = styled(tag, ...styleArgs);
// Return enhanced version that checks for optimizations
return function OptimizedStyledComponent(strings: TemplateStringsArray, ...args: any[]) {
// First check if Lightning CSS optimization is available
if (typeof window !== "undefined" && window.__LIGHTNING_CSS_ENABLED__) {
return enhancedStyled(tag)(strings, ...args);
}
// Fallback to original implementation
return OriginalStyledComponent(strings, ...args);
};
};
};
}
/**
* Type augmentation for window object
*/
declare global {
interface Window {
__LIGHTNING_CSS_ENABLED__?: boolean;
__LIGHTNING_CSS_CONFIG__?: {
resolver?: RuntimeResolver;
cssVariableManager?: CSSVariableManager;
};
}
}
/**
* Initialize Lightning CSS runtime
*/
export function initializeLightningCSS(config: { staticClassMap: Map<string, string>; enableDevMode?: boolean }) {
if (typeof window !== "undefined") {
window.__LIGHTNING_CSS_ENABLED__ = true;
// Convert Map<string, string> to Map<string, ExtractedStyle>
const extractedStyleMap = new Map<string, ExtractedStyle>();
config.staticClassMap.forEach((className, propKey) => {
// Parse the prop combination from the key
let propCombination: Record<string, any> = {};
try {
// Try to parse as JSON first
propCombination = JSON.parse(propKey);
} catch {
// If not JSON, parse the key format like "variant:primary|size:large"
const pairs = propKey.split("|");
pairs.forEach((pair) => {
const [key, value] = pair.split(":");
if (key && value !== undefined) {
// Try to parse value as JSON (for booleans/numbers), otherwise use as string
try {
propCombination[key] = JSON.parse(value);
} catch {
propCombination[key] = value;
}
}
});
}
// Create ExtractedStyle object from className mapping
extractedStyleMap.set(propKey, {
className,
css: "", // CSS is already in the static file
propCombination,
});
});
// Initialize resolver with converted map
const resolver = new RuntimeResolver({
staticClassMap: extractedStyleMap,
fallbackToRuntime: true,
enableDevMode: config.enableDevMode,
});
// Initialize CSS variable manager
const cssVariableManager = getCSSVariableManager();
// IMPORTANT: Initialize the global resolver
initializeResolver({
staticClassMap: extractedStyleMap,
fallbackToRuntime: true,
enableDevMode: config.enableDevMode,
});
window.__LIGHTNING_CSS_CONFIG__ = {
resolver,
cssVariableManager,
};
}
}