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