app-studio 0.7.10 → 0.7.11

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.
@@ -783,91 +783,44 @@ const LIGHT_PREFIX = 'light-';
783
783
  const DARK_PREFIX = 'dark-';
784
784
  const TRANSPARENT = 'transparent';
785
785
  // --- CSS Variable Injection Helper ---
786
+ // Optimized: single-pass processing, minimal string allocations, minified output
786
787
  const generateCSSVariables = (theme, lightColors, darkColors) => {
787
- const variables = [];
788
- const lightVariables = [];
789
- const darkVariables = [];
790
- const themeVariables = [];
791
- // Helper to process object and generate variables
792
- const processObject = (obj, prefix, targetArray) => {
793
- Object.keys(obj).forEach(key => {
794
- const value = obj[key];
795
- const variableName = `${prefix}-${key}`.replace(/\./g, '-');
796
- if (typeof value === 'object' && value !== null) {
797
- processObject(value, variableName, targetArray);
798
- } else if (typeof value === 'string' || typeof value === 'number') {
799
- targetArray.push(`--${variableName}: ${value};`);
800
- }
801
- });
802
- };
803
- // 1. Generate ALL primitive variables (light and dark)
804
- // We prefix them with --light-color-... and --dark-color-...
805
- processObject(lightColors.main, 'color', variables);
806
- processObject(lightColors.palette, 'color', variables);
807
- processObject(lightColors.main, 'light-color', lightVariables);
808
- processObject(lightColors.palette, 'light-color', lightVariables);
809
- processObject(darkColors.main, 'dark-color', darkVariables);
810
- processObject(darkColors.palette, 'dark-color', darkVariables);
811
- // We collect the names that need mapping
812
- const genericColorVars = [];
813
- const collectGenericNames = (obj, prefix) => {
814
- Object.keys(obj).forEach(key => {
815
- const value = obj[key];
816
- const variableName = `${prefix}-${key}`.replace(/\./g, '-');
817
- if (typeof value === 'object' && value !== null) {
818
- collectGenericNames(value, variableName);
819
- } else {
820
- genericColorVars.push(variableName);
788
+ const rootVars = [];
789
+ const lightMappings = [];
790
+ const darkMappings = [];
791
+ // Single-pass helper: generates base, light, dark vars and theme-switch mappings together
792
+ const processColors = (lightObj, darkObj, prefix) => {
793
+ const keys = Object.keys(lightObj);
794
+ for (let i = 0; i < keys.length; i++) {
795
+ const key = keys[i];
796
+ const lightValue = lightObj[key];
797
+ const darkValue = darkObj?.[key];
798
+ const varName = `${prefix}-${key}`;
799
+ if (typeof lightValue === 'object' && lightValue !== null) {
800
+ processColors(lightValue, darkValue, varName);
801
+ } else if (typeof lightValue === 'string' || typeof lightValue === 'number') {
802
+ // :root gets base + light/dark prefixed vars
803
+ rootVars.push(`--${varName}:${lightValue};--light-${varName}:${lightValue};--dark-${varName}:${darkValue ?? lightValue}`);
804
+ // Theme-switching selectors
805
+ lightMappings.push(`--${varName}:var(--light-${varName})`);
806
+ darkMappings.push(`--${varName}:var(--dark-${varName})`);
821
807
  }
822
- });
823
- };
824
- collectGenericNames(lightColors.main, 'color');
825
- collectGenericNames(lightColors.palette, 'color');
826
- // 3. Process Theme variables (references)
827
- // Theme config uses dash notation (color-blue-500, theme-primary)
828
- const processTheme = (obj, prefix) => {
829
- Object.keys(obj).forEach(key => {
830
- const value = obj[key];
831
- const variableName = `${prefix}-${key}`;
832
- if (typeof value === 'object' && value !== null) {
833
- processTheme(value, variableName);
834
- } else if (typeof value === 'string') {
835
- if (value.startsWith('color-') || value.startsWith('theme-')) {
836
- // Convert 'color-blue-500' -> 'var(--color-blue-500)'
837
- themeVariables.push(`--${variableName}: var(--${value});`);
838
- } else {
839
- themeVariables.push(`--${variableName}: ${value};`);
840
- }
841
- }
842
- });
843
- };
844
- processTheme(theme, 'theme');
845
- // 4. Construct CSS
846
- // :root has all primitives
847
- // [data-theme='light'] maps color vars to light primitives
848
- // [data-theme='dark'] maps color vars to dark primitives
849
- const lightMappings = genericColorVars.map(name => `--${name}: var(--light-${name});`).join('\n ');
850
- const darkMappings = genericColorVars.map(name => `--${name}: var(--dark-${name});`).join('\n ');
851
- const css = `
852
- :root {
853
- /* Primitives */
854
- ${variables.join('\n ')}
855
- ${lightVariables.join('\n ')}
856
- ${darkVariables.join('\n ')}
857
-
858
- /* Theme Variables (Structural) */
859
- ${themeVariables.join('\n ')}
860
808
  }
861
-
862
- [data-theme='light'] {
863
- ${lightMappings}
864
- }
865
-
866
- [data-theme='dark'] {
867
- ${darkMappings}
809
+ };
810
+ processColors(lightColors.main, darkColors.main, 'color');
811
+ processColors(lightColors.palette, darkColors.palette, 'color');
812
+ // Process theme variables
813
+ const themeVars = [];
814
+ const themeKeys = Object.keys(theme);
815
+ for (let i = 0; i < themeKeys.length; i++) {
816
+ const key = themeKeys[i];
817
+ const value = theme[key];
818
+ if (typeof value === 'string') {
819
+ themeVars.push(value.startsWith('color-') || value.startsWith('theme-') ? `--theme-${key}:var(--${value})` : `--theme-${key}:${value}`);
868
820
  }
869
- `;
870
- return css;
821
+ }
822
+ // Build minified CSS (no unnecessary whitespace)
823
+ return `:root{${rootVars.join(';')};${themeVars.join(';')}}[data-theme='light']{${lightMappings.join(';')}}[data-theme='dark']{${darkMappings.join(';')}}`;
871
824
  };
872
825
  // --- Default Configuration ---
873
826
  // Theme values use dash notation (color-X or color-X-shade)
@@ -1774,9 +1727,10 @@ function propertyToKebabCase(property) {
1774
1727
  return vendorPrefixToKebabCase(property);
1775
1728
  }
1776
1729
  // Comprehensive list of CSS properties that should be converted to classes
1730
+ // NOTE: Uses a static set instead of document.createElement('div').style
1731
+ // to avoid DOM access at module load time (breaks SSR, adds startup overhead).
1732
+ // The manual list below is comprehensive and covers all commonly used CSS properties.
1777
1733
  const cssProperties = /*#__PURE__*/new Set([
1778
- // Standard CSS properties
1779
- ... /*#__PURE__*/Object.keys(/*#__PURE__*/document.createElement('div').style),
1780
1734
  // Box model
1781
1735
  'margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft', 'marginHorizontal', 'marginVertical', 'padding', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft', 'paddingHorizontal', 'paddingVertical', 'width', 'height', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight',
1782
1736
  // Positioning
@@ -1805,6 +1759,8 @@ const cssProperties = /*#__PURE__*/new Set([
1805
1759
  'textJustify', 'lineClamp', 'textIndent', 'perspective']);
1806
1760
  // Common React event handlers that should not be treated as style props
1807
1761
  const commonEventHandlers = /*#__PURE__*/new Set(['onClick', 'onChange', 'onSubmit', 'onFocus', 'onBlur', 'onKeyDown', 'onKeyUp', 'onKeyPress', 'onMouseDown', 'onMouseUp', 'onMouseMove', 'onMouseEnter', 'onMouseLeave', 'onTouchStart', 'onTouchEnd', 'onTouchMove', 'onScroll', 'onWheel', 'onDrag', 'onDragStart', 'onDragEnd', 'onDrop']);
1762
+ // Cache for CSS.supports results to avoid repeated DOM queries
1763
+ const cssSupportCache = /*#__PURE__*/new Map();
1808
1764
  // Non-hyphenated HTML/SVG attributes that must never be treated as style props.
1809
1765
  // Hyphenated attributes (aria-*, data-*, etc.) are caught by a prefix/hyphen check below.
1810
1766
  const htmlOnlyAttributes = /*#__PURE__*/new Set([
@@ -1857,11 +1813,17 @@ const isStyleProp = prop => {
1857
1813
  return true;
1858
1814
  }
1859
1815
  // Check if it's a valid CSS property using CSS.supports (browser environment)
1816
+ // Results are cached to avoid repeated CSS.supports calls
1860
1817
  if (typeof CSS !== 'undefined' && CSS.supports) {
1818
+ const cached = cssSupportCache.get(prop);
1819
+ if (cached !== undefined) return cached;
1861
1820
  try {
1862
1821
  const kebabProp = vendorPrefixToKebabCase(prop);
1863
- return CSS.supports(kebabProp, 'inherit');
1822
+ const result = CSS.supports(kebabProp, 'inherit');
1823
+ cssSupportCache.set(prop, result);
1824
+ return result;
1864
1825
  } catch {
1826
+ cssSupportCache.set(prop, false);
1865
1827
  return false;
1866
1828
  }
1867
1829
  }
@@ -1995,67 +1957,25 @@ const generateKeyframes = animation => {
1995
1957
 
1996
1958
  /**
1997
1959
  * Mapping of standard CSS properties to their vendor-prefixed equivalents.
1998
- * This helps ensure cross-browser compatibility by automatically applying
1999
- * the appropriate vendor prefixes when needed.
1960
+ *
1961
+ * Optimized for modern browsers (Chrome 100+, Safari 15+, Firefox 91+).
1962
+ * Most properties no longer need vendor prefixes in these browsers.
1963
+ * Only -webkit- prefixes are kept where still needed by Safari/WebKit.
1964
+ *
1965
+ * Removed prefixes:
1966
+ * - -moz- (Firefox 91+ supports all standard properties unprefixed)
1967
+ * - -ms- (IE/Edge Legacy no longer supported)
1968
+ * - -o- (Opera uses Blink engine, same as Chrome)
1969
+ * - -webkit- for animation, transform, transition, flexbox, boxShadow,
1970
+ * boxSizing, columns, borderImage, backgroundSize, backgroundOrigin,
1971
+ * perspective, hyphens (all unprefixed in Safari 15+)
2000
1972
  */
2001
- // Properties that commonly need vendor prefixes across browsers
1973
+ // Properties that still need vendor prefixes in modern browsers
2002
1974
  const vendorPrefixedProperties = {
2003
- // Animation properties
2004
- animation: ['-webkit-animation', '-moz-animation', '-o-animation'],
2005
- animationDelay: ['-webkit-animation-delay', '-moz-animation-delay', '-o-animation-delay'],
2006
- animationDirection: ['-webkit-animation-direction', '-moz-animation-direction', '-o-animation-direction'],
2007
- animationDuration: ['-webkit-animation-duration', '-moz-animation-duration', '-o-animation-duration'],
2008
- animationFillMode: ['-webkit-animation-fill-mode', '-moz-animation-fill-mode', '-o-animation-fill-mode'],
2009
- animationIterationCount: ['-webkit-animation-iteration-count', '-moz-animation-iteration-count', '-o-animation-iteration-count'],
2010
- animationName: ['-webkit-animation-name', '-moz-animation-name', '-o-animation-name'],
2011
- animationPlayState: ['-webkit-animation-play-state', '-moz-animation-play-state', '-o-animation-play-state'],
2012
- animationTimingFunction: ['-webkit-animation-timing-function', '-moz-animation-timing-function', '-o-animation-timing-function'],
2013
- // Transform properties
2014
- transform: ['-webkit-transform', '-moz-transform', '-ms-transform', '-o-transform'],
2015
- transformOrigin: ['-webkit-transform-origin', '-moz-transform-origin', '-ms-transform-origin', '-o-transform-origin'],
2016
- transformStyle: ['-webkit-transform-style', '-moz-transform-style', '-ms-transform-style'],
2017
- // Transition properties
2018
- transition: ['-webkit-transition', '-moz-transition', '-ms-transition', '-o-transition'],
2019
- transitionDelay: ['-webkit-transition-delay', '-moz-transition-delay', '-ms-transition-delay', '-o-transition-delay'],
2020
- transitionDuration: ['-webkit-transition-duration', '-moz-transition-duration', '-ms-transition-duration', '-o-transition-duration'],
2021
- transitionProperty: ['-webkit-transition-property', '-moz-transition-property', '-ms-transition-property', '-o-transition-property'],
2022
- transitionTimingFunction: ['-webkit-transition-timing-function', '-moz-transition-timing-function', '-ms-transition-timing-function', '-o-transition-timing-function'],
2023
- // Flexbox properties
2024
- flex: ['-webkit-flex', '-ms-flex'],
2025
- flexBasis: ['-webkit-flex-basis', '-ms-flex-basis'],
2026
- flexDirection: ['-webkit-flex-direction', '-ms-flex-direction'],
2027
- flexFlow: ['-webkit-flex-flow', '-ms-flex-flow'],
2028
- flexGrow: ['-webkit-flex-grow', '-ms-flex-positive'],
2029
- flexShrink: ['-webkit-flex-shrink', '-ms-flex-negative'],
2030
- flexWrap: ['-webkit-flex-wrap', '-ms-flex-wrap'],
2031
- justifyContent: ['-webkit-justify-content', '-ms-flex-pack'],
2032
- alignItems: ['-webkit-align-items', '-ms-flex-align'],
2033
- alignContent: ['-webkit-align-content', '-ms-flex-line-pack'],
2034
- alignSelf: ['-webkit-align-self', '-ms-flex-item-align'],
2035
- order: ['-webkit-order', '-ms-flex-order'],
2036
- // Other commonly prefixed properties
2037
- appearance: ['-webkit-appearance', '-moz-appearance', '-ms-appearance'],
2038
- backfaceVisibility: ['-webkit-backface-visibility', '-moz-backface-visibility'],
2039
- backgroundClip: ['-webkit-background-clip', '-moz-background-clip'],
2040
- backgroundOrigin: ['-webkit-background-origin', '-moz-background-origin'],
2041
- backgroundSize: ['-webkit-background-size', '-moz-background-size', '-o-background-size'],
2042
- borderImage: ['-webkit-border-image', '-moz-border-image', '-o-border-image'],
2043
- boxShadow: ['-webkit-box-shadow', '-moz-box-shadow'],
2044
- boxSizing: ['-webkit-box-sizing', '-moz-box-sizing'],
2045
- columns: ['-webkit-columns', '-moz-columns'],
2046
- columnCount: ['-webkit-column-count', '-moz-column-count'],
2047
- columnGap: ['-webkit-column-gap', '-moz-column-gap'],
2048
- columnRule: ['-webkit-column-rule', '-moz-column-rule'],
2049
- columnWidth: ['-webkit-column-width', '-moz-column-width'],
2050
- filter: ['-webkit-filter'],
2051
- fontSmoothing: ['-webkit-font-smoothing', '-moz-osx-font-smoothing'],
2052
- hyphens: ['-webkit-hyphens', '-moz-hyphens', '-ms-hyphens'],
1975
+ // Properties that still need -webkit- in Safari
1976
+ backgroundClip: ['-webkit-background-clip'],
2053
1977
  maskImage: ['-webkit-mask-image'],
2054
- perspective: ['-webkit-perspective', '-moz-perspective'],
2055
- perspectiveOrigin: ['-webkit-perspective-origin', '-moz-perspective-origin'],
2056
- textSizeAdjust: ['-webkit-text-size-adjust', '-moz-text-size-adjust', '-ms-text-size-adjust'],
2057
- userSelect: ['-webkit-user-select', '-moz-user-select', '-ms-user-select'],
2058
- // Special webkit-only properties
1978
+ // Webkit-only properties (no unprefixed equivalent)
2059
1979
  textFillColor: ['-webkit-text-fill-color'],
2060
1980
  textStroke: ['-webkit-text-stroke'],
2061
1981
  textStrokeColor: ['-webkit-text-stroke-color'],
@@ -2295,20 +2215,18 @@ const ValueUtils = {
2295
2215
  return processStyleProperty(property, value, getColor);
2296
2216
  },
2297
2217
  normalizeCssValue(value) {
2218
+ const str = typeof value === 'string' ? value : String(value);
2298
2219
  // Handle CSS variables in values
2299
- if (typeof value === 'string' && value.startsWith('--')) {
2300
- // For CSS variable values, use a special prefix to avoid conflicts
2301
- return `var-${value.substring(2)}`;
2220
+ if (str.charCodeAt(0) === 45 && str.charCodeAt(1) === 45) {
2221
+ // starts with '--'
2222
+ return `var-${str.substring(2)}`;
2302
2223
  }
2303
2224
  // Handle vendor-prefixed values
2304
- if (typeof value === 'string' && value.startsWith('-webkit-')) {
2305
- // For webkit values, preserve the prefix but normalize the rest
2306
- const prefix = '-webkit-';
2307
- const rest = value.substring(prefix.length);
2308
- const normalizedRest = rest.replace(/\./g, 'p').replace(/\s+/g, '-').replace(/[^a-zA-Z0-9\-]/g, '').replace(/%/g, 'pct').replace(/vw/g, 'vw').replace(/vh/g, 'vh').replace(/em/g, 'em').replace(/rem/g, 'rem');
2309
- return `webkit-${normalizedRest}`;
2225
+ if (str.startsWith('-webkit-')) {
2226
+ return `webkit-${str.substring(8).replace(/\./g, 'p').replace(/\s+/g, '-').replace(/[^a-zA-Z0-9-]/g, '')}`;
2310
2227
  }
2311
- return value.toString().replace(/\./g, 'p').replace(/\s+/g, '-').replace(/[^a-zA-Z0-9\-]/g, '').replace(/%/g, 'pct').replace(/vw/g, 'vw').replace(/vh/g, 'vh').replace(/em/g, 'em').replace(/rem/g, 'rem');
2228
+ // Single-pass normalization: replace dots with 'p', spaces with '-', strip non-alphanumeric
2229
+ return str.replace(/\./g, 'p').replace(/\s+/g, '-').replace(/[^a-zA-Z0-9-]/g, '');
2312
2230
  },
2313
2231
  generateUniqueClassName(css) {
2314
2232
  if (rawCssCache.has(css)) {
@@ -2672,21 +2590,24 @@ function processStyles(styles, context, modifier, getColor, mediaQueries, device
2672
2590
  devices = {};
2673
2591
  }
2674
2592
  const classes = [];
2675
- Object.keys(styles).forEach(property => {
2676
- const value = styles[property];
2677
- let mediaQueriesForClass = [];
2678
- if (context === 'media') {
2679
- if (mediaQueries[modifier]) {
2680
- mediaQueriesForClass = [mediaQueries[modifier]];
2681
- } else if (devices[modifier]) {
2682
- mediaQueriesForClass = devices[modifier].map(mq => mediaQueries[mq]).filter(mq => mq);
2683
- }
2593
+ const activeManager = manager || utilityClassManager;
2594
+ // Pre-compute media queries for this modifier (only done once per call)
2595
+ let mediaQueriesForClass = [];
2596
+ if (context === 'media') {
2597
+ if (mediaQueries[modifier]) {
2598
+ mediaQueriesForClass = [mediaQueries[modifier]];
2599
+ } else if (devices[modifier]) {
2600
+ mediaQueriesForClass = devices[modifier].map(mq => mediaQueries[mq]).filter(mq => mq);
2684
2601
  }
2602
+ }
2603
+ const keys = Object.keys(styles);
2604
+ for (let i = 0; i < keys.length; i++) {
2605
+ const value = styles[keys[i]];
2685
2606
  if (value !== undefined && value !== '') {
2686
- const classNames = (manager || utilityClassManager).getClassNames(property, value, context, modifier, getColor, mediaQueriesForClass);
2607
+ const classNames = activeManager.getClassNames(keys[i], value, context, modifier, getColor, mediaQueriesForClass);
2687
2608
  classes.push(...classNames);
2688
2609
  }
2689
- });
2610
+ }
2690
2611
  return classes;
2691
2612
  }
2692
2613
  // Add a function to handle nested pseudo-classes
@@ -2805,6 +2726,7 @@ function processEventStyles(eventName, eventStyles, getColor, manager) {
2805
2726
  const extractUtilityClasses = (props, getColor, mediaQueries, devices, manager) => {
2806
2727
  const classes = [];
2807
2728
  const computedStyles = {};
2729
+ const activeManager = manager || utilityClassManager;
2808
2730
  // Handle widthHeight (shorthand for both width and height)
2809
2731
  if (props.widthHeight || props.height !== undefined && props.width !== undefined && props.height === props.width) {
2810
2732
  const widthHeightValue = props.widthHeight || props.width;
@@ -2812,21 +2734,30 @@ const extractUtilityClasses = (props, getColor, mediaQueries, devices, manager)
2812
2734
  computedStyles.width = formattedValue;
2813
2735
  computedStyles.height = formattedValue;
2814
2736
  }
2815
- // Handle padding and margin shorthands
2816
- const shorthandProps = {
2817
- paddingHorizontal: ['paddingLeft', 'paddingRight'],
2818
- paddingVertical: ['paddingTop', 'paddingBottom'],
2819
- marginHorizontal: ['marginLeft', 'marginRight'],
2820
- marginVertical: ['marginTop', 'marginBottom']
2821
- };
2822
- for (const [shorthand, properties] of Object.entries(shorthandProps)) {
2823
- const value = props[shorthand];
2824
- if (value !== undefined) {
2825
- const formattedValue = typeof value === 'number' ? `${value}px` : value;
2826
- properties.forEach(prop => {
2827
- computedStyles[prop] = formattedValue;
2828
- });
2829
- }
2737
+ // Handle padding and margin shorthands (inlined to avoid Object.entries overhead)
2738
+ const ph = props.paddingHorizontal;
2739
+ if (ph !== undefined) {
2740
+ const v = typeof ph === 'number' ? `${ph}px` : ph;
2741
+ computedStyles.paddingLeft = v;
2742
+ computedStyles.paddingRight = v;
2743
+ }
2744
+ const pv = props.paddingVertical;
2745
+ if (pv !== undefined) {
2746
+ const v = typeof pv === 'number' ? `${pv}px` : pv;
2747
+ computedStyles.paddingTop = v;
2748
+ computedStyles.paddingBottom = v;
2749
+ }
2750
+ const mh = props.marginHorizontal;
2751
+ if (mh !== undefined) {
2752
+ const v = typeof mh === 'number' ? `${mh}px` : mh;
2753
+ computedStyles.marginLeft = v;
2754
+ computedStyles.marginRight = v;
2755
+ }
2756
+ const mv = props.marginVertical;
2757
+ if (mv !== undefined) {
2758
+ const v = typeof mv === 'number' ? `${mv}px` : mv;
2759
+ computedStyles.marginTop = v;
2760
+ computedStyles.marginBottom = v;
2830
2761
  }
2831
2762
  // Handle shadows
2832
2763
  if (props.shadow !== undefined) {
@@ -2855,80 +2786,103 @@ const extractUtilityClasses = (props, getColor, mediaQueries, devices, manager)
2855
2786
  const animations = Array.isArray(props.animate) ? props.animate : [props.animate];
2856
2787
  Object.assign(computedStyles, AnimationUtils.processAnimations(animations, manager));
2857
2788
  }
2858
- const blendConfig = {
2859
- mode: 'difference',
2860
- color: 'white',
2861
- modeWithBg: 'overlay'
2862
- };
2863
- const setBlend = (props, style) => {
2789
+ // Handle default blend
2790
+ if (props.blend === true) {
2864
2791
  if (props.bgColor) {
2865
- style.mixBlendMode = blendConfig.modeWithBg;
2866
- style.color = 'white';
2792
+ computedStyles.mixBlendMode = 'overlay';
2793
+ computedStyles.color = 'white';
2867
2794
  } else {
2868
- style.mixBlendMode = blendConfig.mode;
2869
- style.color = blendConfig.color;
2795
+ computedStyles.mixBlendMode = 'difference';
2796
+ computedStyles.color = 'white';
2870
2797
  }
2871
- };
2872
- // Handle default blend
2873
- if (props.blend === true) {
2874
- setBlend(props, computedStyles);
2875
- Object.keys(props).forEach(property => {
2876
- if (props[property]?.color === undefined && (property.startsWith('_') || property === 'on' || property === 'media')) {
2877
- setBlend(props[property], props[property]);
2878
- }
2879
- });
2880
2798
  }
2881
- // Process base styles
2799
+ // Process base computed styles
2882
2800
  classes.push(...processStyles(computedStyles, 'base', '', getColor, {}, {}, manager));
2883
- // Collect underscore-prefixed properties (_hover, _focus, etc.)
2884
- const underscoreProps = {};
2885
- Object.keys(props).forEach(property => {
2886
- if (property.startsWith('_') && property.length > 1) {
2801
+ // SINGLE PASS over props: classify each prop into style, event, or underscore
2802
+ const propKeys = Object.keys(props);
2803
+ for (let i = 0; i < propKeys.length; i++) {
2804
+ const property = propKeys[i];
2805
+ const value = props[property];
2806
+ // Handle underscore-prefixed event properties (_hover, _focus, etc.)
2807
+ if (property.charCodeAt(0) === 95 && property.length > 1) {
2808
+ // 95 = '_'
2887
2809
  const eventName = property.substring(1);
2888
- underscoreProps[eventName] = props[property];
2810
+ classes.push(...processEventStyles(eventName, value, getColor, manager));
2811
+ // Handle blend for underscore props
2812
+ if (props.blend === true && value?.color === undefined) {
2813
+ if (props.bgColor) {
2814
+ value.mixBlendMode = 'overlay';
2815
+ value.color = 'white';
2816
+ } else {
2817
+ value.mixBlendMode = 'difference';
2818
+ value.color = 'white';
2819
+ }
2820
+ }
2821
+ continue;
2889
2822
  }
2890
- });
2891
- // Process standard style props
2892
- Object.keys(props).forEach(property => {
2893
- if (property !== 'style' && property !== 'css' && !property.startsWith('_') && (isStyleProp(property) || ['on', 'media'].includes(property))) {
2894
- const value = props[property];
2823
+ // Skip non-style props
2824
+ if (property === 'style' || property === 'css') continue;
2825
+ if (property === 'on') {
2826
+ // Process event-based styles
2895
2827
  if (typeof value === 'object' && value !== null) {
2896
- if (property === 'on') {
2897
- // Process event-based styles
2898
- Object.keys(value).forEach(event => {
2899
- classes.push(...processEventStyles(event, value[event], getColor, manager));
2900
- });
2901
- } else if (property === 'media') {
2902
- // Process media query styles
2903
- Object.keys(value).forEach(screenOrDevice => {
2904
- classes.push(...processStyles(value[screenOrDevice], 'media', screenOrDevice, getColor, mediaQueries, devices, manager));
2905
- });
2828
+ const events = Object.keys(value);
2829
+ for (let j = 0; j < events.length; j++) {
2830
+ classes.push(...processEventStyles(events[j], value[events[j]], getColor, manager));
2831
+ }
2832
+ // Handle blend for 'on' prop
2833
+ if (props.blend === true && value?.color === undefined) {
2834
+ if (props.bgColor) {
2835
+ value.mixBlendMode = 'overlay';
2836
+ value.color = 'white';
2837
+ } else {
2838
+ value.mixBlendMode = 'difference';
2839
+ value.color = 'white';
2840
+ }
2906
2841
  }
2907
- } else if (value !== undefined && value !== '') {
2908
- // Direct style property
2909
- classes.push(...(manager || utilityClassManager).getClassNames(property, value, 'base', '', getColor, []));
2910
2842
  }
2843
+ continue;
2911
2844
  }
2912
- });
2845
+ if (property === 'media') {
2846
+ // Process media query styles
2847
+ if (typeof value === 'object' && value !== null) {
2848
+ const screens = Object.keys(value);
2849
+ for (let j = 0; j < screens.length; j++) {
2850
+ classes.push(...processStyles(value[screens[j]], 'media', screens[j], getColor, mediaQueries, devices, manager));
2851
+ }
2852
+ // Handle blend for 'media' prop
2853
+ if (props.blend === true && value?.color === undefined) {
2854
+ if (props.bgColor) {
2855
+ value.mixBlendMode = 'overlay';
2856
+ value.color = 'white';
2857
+ } else {
2858
+ value.mixBlendMode = 'difference';
2859
+ value.color = 'white';
2860
+ }
2861
+ }
2862
+ }
2863
+ continue;
2864
+ }
2865
+ // Standard style props
2866
+ if (isStyleProp(property)) {
2867
+ if (value !== undefined && value !== '') {
2868
+ if (typeof value === 'object' && value !== null) {
2869
+ // Object-style props are not directly processed as base styles
2870
+ continue;
2871
+ }
2872
+ classes.push(...activeManager.getClassNames(property, value, 'base', '', getColor, []));
2873
+ }
2874
+ }
2875
+ }
2913
2876
  // Handle raw CSS - uses 'override' context for higher specificity
2914
2877
  if (props.css) {
2915
2878
  if (typeof props.css === 'object') {
2916
- // Object-style CSS gets processed with override context for higher priority
2917
- Object.assign(computedStyles, props.css);
2918
2879
  classes.push(...processStyles(props.css, 'override', '', getColor, {}, {}, manager));
2919
2880
  } else if (typeof props.css === 'string') {
2920
- // String-style CSS gets its own class in override context
2921
2881
  const uniqueClassName = ValueUtils.generateUniqueClassName(props.css);
2922
- (manager || utilityClassManager).injectRule(`.${uniqueClassName} { ${props.css} }`, 'override');
2882
+ activeManager.injectRule(`.${uniqueClassName} { ${props.css} }`, 'override');
2923
2883
  classes.push(uniqueClassName);
2924
2884
  }
2925
2885
  }
2926
- // Process underscore-prefixed event properties
2927
- if (Object.keys(underscoreProps).length > 0) {
2928
- Object.keys(underscoreProps).forEach(event => {
2929
- classes.push(...processEventStyles(event, underscoreProps[event], getColor, manager));
2930
- });
2931
- }
2932
2886
  return classes;
2933
2887
  };
2934
2888
 
@@ -2995,27 +2949,47 @@ const AnalyticsProvider = _ref => {
2995
2949
  }, children);
2996
2950
  };
2997
2951
 
2952
+ // Set of special prop names that affect CSS generation
2953
+ const styleRelevantProps = /*#__PURE__*/new Set(['on', 'media', 'animate', 'css', 'shadow', 'blend', 'widthHeight', 'paddingHorizontal', 'paddingVertical', 'marginHorizontal', 'marginVertical']);
2954
+ // Skip these props from hash computation
2955
+ const skipHashProps = /*#__PURE__*/new Set(['children', 'ref', 'key', 'style']);
2956
+ /**
2957
+ * Fast serialization of a value for hashing purposes.
2958
+ * Avoids JSON.stringify overhead for common cases (strings, numbers, booleans).
2959
+ */
2960
+ function fastSerialize(value) {
2961
+ if (value === null) return 'n';
2962
+ const t = typeof value;
2963
+ if (t === 'string') return `s${value}`;
2964
+ if (t === 'number') return `d${value}`;
2965
+ if (t === 'boolean') return value ? 'T' : 'F';
2966
+ // Fall back to JSON.stringify only for complex objects
2967
+ return JSON.stringify(value);
2968
+ }
2998
2969
  /**
2999
2970
  * Computes a stable hash of style-relevant props.
3000
- * This is used to determine if style extraction needs to be re-run.
2971
+ * Optimized: avoids sorting, uses fast serialization, feeds directly to hash.
3001
2972
  */
3002
2973
  function hashStyleProps(props) {
3003
2974
  // Build a deterministic string representation of style-relevant props
3004
- const parts = [];
3005
- const sortedKeys = Object.keys(props).sort();
3006
- for (const key of sortedKeys) {
2975
+ // We use a single string accumulator instead of array + join
2976
+ let hashInput = '';
2977
+ const keys = Object.keys(props);
2978
+ for (let i = 0; i < keys.length; i++) {
2979
+ const key = keys[i];
3007
2980
  // Skip non-style props that don't affect CSS generation
3008
- if (key === 'children' || key === 'ref' || key === 'key') continue;
2981
+ if (skipHashProps.has(key)) continue;
3009
2982
  // Include style-relevant props
3010
- if (isStyleProp(key) || key.startsWith('_') || key === 'on' || key === 'media' || key === 'animate' || key === 'css' || key === 'shadow' || key === 'blend' || key === 'widthHeight' || key === 'paddingHorizontal' || key === 'paddingVertical' || key === 'marginHorizontal' || key === 'marginVertical') {
2983
+ if (isStyleProp(key) || key.charCodeAt(0) === 95 ||
2984
+ // starts with '_'
2985
+ styleRelevantProps.has(key)) {
3011
2986
  const value = props[key];
3012
2987
  if (value !== undefined) {
3013
- // Use JSON.stringify for consistent serialization
3014
- parts.push(`${key}:${JSON.stringify(value)}`);
2988
+ hashInput += `|${key}:${fastSerialize(value)}`;
3015
2989
  }
3016
2990
  }
3017
2991
  }
3018
- return hash(parts.join('|'));
2992
+ return hash(hashInput);
3019
2993
  }
3020
2994
  /**
3021
2995
  * Custom hook that memoizes style extraction based on a stable hash of props.
@@ -3025,8 +2999,9 @@ function useStableStyleMemo(propsToProcess, getColor, mediaQueries, devices, man
3025
2999
  const cacheRef = useRef(null);
3026
3000
  // Compute hash directly — no useMemo since propsToProcess is always a new
3027
3001
  // reference (from destructuring), so the memo deps would always change.
3028
- const themeHash = theme ? JSON.stringify(theme) : '';
3029
- const currentHash = hashStyleProps(propsToProcess) + '|' + hash(themeHash);
3002
+ // Theme hash uses Object.values() concatenation instead of JSON.stringify
3003
+ const themeHash = theme ? hash(Object.values(theme).join('|')) : '';
3004
+ const currentHash = hashStyleProps(propsToProcess) + '|' + themeHash;
3030
3005
  // Only recompute classes if hash changed
3031
3006
  if (!cacheRef.current || cacheRef.current.hash !== currentHash) {
3032
3007
  const classes = extractUtilityClasses(propsToProcess, getColor, mediaQueries, devices, manager);
@@ -3203,18 +3178,24 @@ const Element = /*#__PURE__*/React.memo(/*#__PURE__*/forwardRef((_ref, ref) => {
3203
3178
  after,
3204
3179
  ...otherProps
3205
3180
  } = rest;
3206
- // First, add all event handlers (they start with "on" and have a capital letter after)
3207
- Object.keys(otherProps).forEach(key => {
3208
- if (key.startsWith('on') && key.length > 2 && key[2] === key[2].toUpperCase()) {
3181
+ // Single pass: add event handlers and non-style props together
3182
+ const otherKeys = Object.keys(otherProps);
3183
+ for (let i = 0; i < otherKeys.length; i++) {
3184
+ const key = otherKeys[i];
3185
+ // Event handlers: start with "on" + uppercase letter
3186
+ if (key.charCodeAt(0) === 111 &&
3187
+ // 'o'
3188
+ key.charCodeAt(1) === 110 &&
3189
+ // 'n'
3190
+ key.length > 2 && key.charCodeAt(2) >= 65 && key.charCodeAt(2) <= 90 // uppercase A-Z
3191
+ ) {
3209
3192
  newProps[key] = otherProps[key];
3210
3193
  }
3211
- });
3212
- // Then add all other non-style props
3213
- Object.keys(otherProps).forEach(key => {
3214
- if (!excludedKeys.has(key) && !isStyleProp(key) || includeKeys.has(key)) {
3194
+ // Non-style props (pass through to DOM)
3195
+ else if (!excludedKeys.has(key) && !isStyleProp(key) || includeKeys.has(key)) {
3215
3196
  newProps[key] = otherProps[key];
3216
3197
  }
3217
- });
3198
+ }
3218
3199
  if (style) {
3219
3200
  newProps.style = style;
3220
3201
  }
@@ -3301,11 +3282,17 @@ const Span = /*#__PURE__*/React.forwardRef((props, ref) => (/*#__PURE__*/React.c
3301
3282
  ref: ref
3302
3283
  }))));
3303
3284
 
3304
- const Image = /*#__PURE__*/React.forwardRef((props, ref) => (/*#__PURE__*/React.createElement(Element, Object.assign({
3305
- as: "img"
3306
- }, props, {
3307
- ref: ref
3308
- }))));
3285
+ const Image = /*#__PURE__*/React.forwardRef((props, ref) => {
3286
+ const imageProps = {
3287
+ ...props,
3288
+ alt: props.alt || ''
3289
+ };
3290
+ return /*#__PURE__*/React.createElement(Element, Object.assign({
3291
+ as: "img"
3292
+ }, imageProps, {
3293
+ ref: ref
3294
+ }));
3295
+ });
3309
3296
  const ImageBackground = /*#__PURE__*/React.forwardRef((_ref, ref) => {
3310
3297
  let {
3311
3298
  src,
@@ -3404,11 +3391,18 @@ const Input = /*#__PURE__*/React.forwardRef((props, ref) => (/*#__PURE__*/React.
3404
3391
  }, props, {
3405
3392
  ref: ref
3406
3393
  }))));
3407
- const Button = /*#__PURE__*/React.forwardRef((props, ref) => (/*#__PURE__*/React.createElement(Element, Object.assign({
3408
- as: "button"
3409
- }, props, {
3410
- ref: ref
3411
- }))));
3394
+ const Button = /*#__PURE__*/React.forwardRef((props, ref) => {
3395
+ if (process.env.NODE_ENV !== 'production') {
3396
+ if (!props.children && !props['aria-label']) {
3397
+ console.warn('Accessibility Warning: Button is missing an accessible name. If it is an icon-only button, please provide an `aria-label`.');
3398
+ }
3399
+ }
3400
+ return /*#__PURE__*/React.createElement(Element, Object.assign({
3401
+ as: "button"
3402
+ }, props, {
3403
+ ref: ref
3404
+ }));
3405
+ });
3412
3406
 
3413
3407
  // animations.ts
3414
3408
  const fadeIn = function (_temp) {