app-studio 0.7.10 → 0.7.12

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.
@@ -789,91 +789,44 @@
789
789
  const DARK_PREFIX = 'dark-';
790
790
  const TRANSPARENT = 'transparent';
791
791
  // --- CSS Variable Injection Helper ---
792
+ // Optimized: single-pass processing, minimal string allocations, minified output
792
793
  const generateCSSVariables = (theme, lightColors, darkColors) => {
793
- const variables = [];
794
- const lightVariables = [];
795
- const darkVariables = [];
796
- const themeVariables = [];
797
- // Helper to process object and generate variables
798
- const processObject = (obj, prefix, targetArray) => {
799
- Object.keys(obj).forEach(key => {
800
- const value = obj[key];
801
- const variableName = `${prefix}-${key}`.replace(/\./g, '-');
802
- if (typeof value === 'object' && value !== null) {
803
- processObject(value, variableName, targetArray);
804
- } else if (typeof value === 'string' || typeof value === 'number') {
805
- targetArray.push(`--${variableName}: ${value};`);
806
- }
807
- });
808
- };
809
- // 1. Generate ALL primitive variables (light and dark)
810
- // We prefix them with --light-color-... and --dark-color-...
811
- processObject(lightColors.main, 'color', variables);
812
- processObject(lightColors.palette, 'color', variables);
813
- processObject(lightColors.main, 'light-color', lightVariables);
814
- processObject(lightColors.palette, 'light-color', lightVariables);
815
- processObject(darkColors.main, 'dark-color', darkVariables);
816
- processObject(darkColors.palette, 'dark-color', darkVariables);
817
- // We collect the names that need mapping
818
- const genericColorVars = [];
819
- const collectGenericNames = (obj, prefix) => {
820
- Object.keys(obj).forEach(key => {
821
- const value = obj[key];
822
- const variableName = `${prefix}-${key}`.replace(/\./g, '-');
823
- if (typeof value === 'object' && value !== null) {
824
- collectGenericNames(value, variableName);
825
- } else {
826
- genericColorVars.push(variableName);
827
- }
828
- });
829
- };
830
- collectGenericNames(lightColors.main, 'color');
831
- collectGenericNames(lightColors.palette, 'color');
832
- // 3. Process Theme variables (references)
833
- // Theme config uses dash notation (color-blue-500, theme-primary)
834
- const processTheme = (obj, prefix) => {
835
- Object.keys(obj).forEach(key => {
836
- const value = obj[key];
837
- const variableName = `${prefix}-${key}`;
838
- if (typeof value === 'object' && value !== null) {
839
- processTheme(value, variableName);
840
- } else if (typeof value === 'string') {
841
- if (value.startsWith('color-') || value.startsWith('theme-')) {
842
- // Convert 'color-blue-500' -> 'var(--color-blue-500)'
843
- themeVariables.push(`--${variableName}: var(--${value});`);
844
- } else {
845
- themeVariables.push(`--${variableName}: ${value};`);
846
- }
794
+ const rootVars = [];
795
+ const lightMappings = [];
796
+ const darkMappings = [];
797
+ // Single-pass helper: generates base, light, dark vars and theme-switch mappings together
798
+ const processColors = (lightObj, darkObj, prefix) => {
799
+ const keys = Object.keys(lightObj);
800
+ for (let i = 0; i < keys.length; i++) {
801
+ const key = keys[i];
802
+ const lightValue = lightObj[key];
803
+ const darkValue = darkObj?.[key];
804
+ const varName = `${prefix}-${key}`;
805
+ if (typeof lightValue === 'object' && lightValue !== null) {
806
+ processColors(lightValue, darkValue, varName);
807
+ } else if (typeof lightValue === 'string' || typeof lightValue === 'number') {
808
+ // :root gets base + light/dark prefixed vars
809
+ rootVars.push(`--${varName}:${lightValue};--light-${varName}:${lightValue};--dark-${varName}:${darkValue ?? lightValue}`);
810
+ // Theme-switching selectors
811
+ lightMappings.push(`--${varName}:var(--light-${varName})`);
812
+ darkMappings.push(`--${varName}:var(--dark-${varName})`);
847
813
  }
848
- });
814
+ }
849
815
  };
850
- processTheme(theme, 'theme');
851
- // 4. Construct CSS
852
- // :root has all primitives
853
- // [data-theme='light'] maps color vars to light primitives
854
- // [data-theme='dark'] maps color vars to dark primitives
855
- const lightMappings = genericColorVars.map(name => `--${name}: var(--light-${name});`).join('\n ');
856
- const darkMappings = genericColorVars.map(name => `--${name}: var(--dark-${name});`).join('\n ');
857
- const css = `
858
- :root {
859
- /* Primitives */
860
- ${variables.join('\n ')}
861
- ${lightVariables.join('\n ')}
862
- ${darkVariables.join('\n ')}
863
-
864
- /* Theme Variables (Structural) */
865
- ${themeVariables.join('\n ')}
866
- }
867
-
868
- [data-theme='light'] {
869
- ${lightMappings}
870
- }
871
-
872
- [data-theme='dark'] {
873
- ${darkMappings}
816
+ processColors(lightColors.main, darkColors.main, 'color');
817
+ processColors(lightColors.palette, darkColors.palette, 'color');
818
+ // Process theme variables
819
+ const themeVars = [];
820
+ const themeKeys = Object.keys(theme);
821
+ for (let i = 0; i < themeKeys.length; i++) {
822
+ const key = themeKeys[i];
823
+ const value = theme[key];
824
+ if (typeof value === 'string') {
825
+ themeVars.push(value.startsWith('color-') || value.startsWith('theme-') ? `--theme-${key}:var(--${value})` : `--theme-${key}:${value}`);
826
+ }
874
827
  }
875
- `;
876
- return css;
828
+ // Build minified CSS (no unnecessary whitespace)
829
+ return `:root{${rootVars.join(';')};${themeVars.join(';')}}[data-theme='light']{${lightMappings.join(';')}}[data-theme='dark']{${darkMappings.join(';')}}`;
877
830
  };
878
831
  // --- Default Configuration ---
879
832
  // Theme values use dash notation (color-X or color-X-shade)
@@ -1780,9 +1733,10 @@
1780
1733
  return vendorPrefixToKebabCase(property);
1781
1734
  }
1782
1735
  // Comprehensive list of CSS properties that should be converted to classes
1736
+ // NOTE: Uses a static set instead of document.createElement('div').style
1737
+ // to avoid DOM access at module load time (breaks SSR, adds startup overhead).
1738
+ // The manual list below is comprehensive and covers all commonly used CSS properties.
1783
1739
  const cssProperties = /*#__PURE__*/new Set([
1784
- // Standard CSS properties
1785
- ... /*#__PURE__*/Object.keys(/*#__PURE__*/document.createElement('div').style),
1786
1740
  // Box model
1787
1741
  'margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft', 'marginHorizontal', 'marginVertical', 'padding', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft', 'paddingHorizontal', 'paddingVertical', 'width', 'height', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight',
1788
1742
  // Positioning
@@ -1811,6 +1765,8 @@
1811
1765
  'textJustify', 'lineClamp', 'textIndent', 'perspective']);
1812
1766
  // Common React event handlers that should not be treated as style props
1813
1767
  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']);
1768
+ // Cache for CSS.supports results to avoid repeated DOM queries
1769
+ const cssSupportCache = /*#__PURE__*/new Map();
1814
1770
  // Non-hyphenated HTML/SVG attributes that must never be treated as style props.
1815
1771
  // Hyphenated attributes (aria-*, data-*, etc.) are caught by a prefix/hyphen check below.
1816
1772
  const htmlOnlyAttributes = /*#__PURE__*/new Set([
@@ -1863,11 +1819,17 @@
1863
1819
  return true;
1864
1820
  }
1865
1821
  // Check if it's a valid CSS property using CSS.supports (browser environment)
1822
+ // Results are cached to avoid repeated CSS.supports calls
1866
1823
  if (typeof CSS !== 'undefined' && CSS.supports) {
1824
+ const cached = cssSupportCache.get(prop);
1825
+ if (cached !== undefined) return cached;
1867
1826
  try {
1868
1827
  const kebabProp = vendorPrefixToKebabCase(prop);
1869
- return CSS.supports(kebabProp, 'inherit');
1828
+ const result = CSS.supports(kebabProp, 'inherit');
1829
+ cssSupportCache.set(prop, result);
1830
+ return result;
1870
1831
  } catch {
1832
+ cssSupportCache.set(prop, false);
1871
1833
  return false;
1872
1834
  }
1873
1835
  }
@@ -1910,6 +1872,10 @@
1910
1872
  const kebabProperty = toKebabCase(property);
1911
1873
  // Convert numbers to pixels for appropriate properties
1912
1874
  if (typeof value === 'number') {
1875
+ // lineHeight only accepts integers: 1-3 are unitless multipliers, 4+ are pixel sizes
1876
+ if (property === 'lineHeight' && Number.isInteger(value) && value <= 3) {
1877
+ return value;
1878
+ }
1913
1879
  // Check if this is a property that should have px units
1914
1880
  // First check the property as is, then check with vendor prefixes removed
1915
1881
  const shouldAddPx = !NumberProps.has(property) && (numericCssProperties.has(kebabProperty) ||
@@ -2001,67 +1967,25 @@
2001
1967
 
2002
1968
  /**
2003
1969
  * Mapping of standard CSS properties to their vendor-prefixed equivalents.
2004
- * This helps ensure cross-browser compatibility by automatically applying
2005
- * the appropriate vendor prefixes when needed.
1970
+ *
1971
+ * Optimized for modern browsers (Chrome 100+, Safari 15+, Firefox 91+).
1972
+ * Most properties no longer need vendor prefixes in these browsers.
1973
+ * Only -webkit- prefixes are kept where still needed by Safari/WebKit.
1974
+ *
1975
+ * Removed prefixes:
1976
+ * - -moz- (Firefox 91+ supports all standard properties unprefixed)
1977
+ * - -ms- (IE/Edge Legacy no longer supported)
1978
+ * - -o- (Opera uses Blink engine, same as Chrome)
1979
+ * - -webkit- for animation, transform, transition, flexbox, boxShadow,
1980
+ * boxSizing, columns, borderImage, backgroundSize, backgroundOrigin,
1981
+ * perspective, hyphens (all unprefixed in Safari 15+)
2006
1982
  */
2007
- // Properties that commonly need vendor prefixes across browsers
1983
+ // Properties that still need vendor prefixes in modern browsers
2008
1984
  const vendorPrefixedProperties = {
2009
- // Animation properties
2010
- animation: ['-webkit-animation', '-moz-animation', '-o-animation'],
2011
- animationDelay: ['-webkit-animation-delay', '-moz-animation-delay', '-o-animation-delay'],
2012
- animationDirection: ['-webkit-animation-direction', '-moz-animation-direction', '-o-animation-direction'],
2013
- animationDuration: ['-webkit-animation-duration', '-moz-animation-duration', '-o-animation-duration'],
2014
- animationFillMode: ['-webkit-animation-fill-mode', '-moz-animation-fill-mode', '-o-animation-fill-mode'],
2015
- animationIterationCount: ['-webkit-animation-iteration-count', '-moz-animation-iteration-count', '-o-animation-iteration-count'],
2016
- animationName: ['-webkit-animation-name', '-moz-animation-name', '-o-animation-name'],
2017
- animationPlayState: ['-webkit-animation-play-state', '-moz-animation-play-state', '-o-animation-play-state'],
2018
- animationTimingFunction: ['-webkit-animation-timing-function', '-moz-animation-timing-function', '-o-animation-timing-function'],
2019
- // Transform properties
2020
- transform: ['-webkit-transform', '-moz-transform', '-ms-transform', '-o-transform'],
2021
- transformOrigin: ['-webkit-transform-origin', '-moz-transform-origin', '-ms-transform-origin', '-o-transform-origin'],
2022
- transformStyle: ['-webkit-transform-style', '-moz-transform-style', '-ms-transform-style'],
2023
- // Transition properties
2024
- transition: ['-webkit-transition', '-moz-transition', '-ms-transition', '-o-transition'],
2025
- transitionDelay: ['-webkit-transition-delay', '-moz-transition-delay', '-ms-transition-delay', '-o-transition-delay'],
2026
- transitionDuration: ['-webkit-transition-duration', '-moz-transition-duration', '-ms-transition-duration', '-o-transition-duration'],
2027
- transitionProperty: ['-webkit-transition-property', '-moz-transition-property', '-ms-transition-property', '-o-transition-property'],
2028
- transitionTimingFunction: ['-webkit-transition-timing-function', '-moz-transition-timing-function', '-ms-transition-timing-function', '-o-transition-timing-function'],
2029
- // Flexbox properties
2030
- flex: ['-webkit-flex', '-ms-flex'],
2031
- flexBasis: ['-webkit-flex-basis', '-ms-flex-basis'],
2032
- flexDirection: ['-webkit-flex-direction', '-ms-flex-direction'],
2033
- flexFlow: ['-webkit-flex-flow', '-ms-flex-flow'],
2034
- flexGrow: ['-webkit-flex-grow', '-ms-flex-positive'],
2035
- flexShrink: ['-webkit-flex-shrink', '-ms-flex-negative'],
2036
- flexWrap: ['-webkit-flex-wrap', '-ms-flex-wrap'],
2037
- justifyContent: ['-webkit-justify-content', '-ms-flex-pack'],
2038
- alignItems: ['-webkit-align-items', '-ms-flex-align'],
2039
- alignContent: ['-webkit-align-content', '-ms-flex-line-pack'],
2040
- alignSelf: ['-webkit-align-self', '-ms-flex-item-align'],
2041
- order: ['-webkit-order', '-ms-flex-order'],
2042
- // Other commonly prefixed properties
2043
- appearance: ['-webkit-appearance', '-moz-appearance', '-ms-appearance'],
2044
- backfaceVisibility: ['-webkit-backface-visibility', '-moz-backface-visibility'],
2045
- backgroundClip: ['-webkit-background-clip', '-moz-background-clip'],
2046
- backgroundOrigin: ['-webkit-background-origin', '-moz-background-origin'],
2047
- backgroundSize: ['-webkit-background-size', '-moz-background-size', '-o-background-size'],
2048
- borderImage: ['-webkit-border-image', '-moz-border-image', '-o-border-image'],
2049
- boxShadow: ['-webkit-box-shadow', '-moz-box-shadow'],
2050
- boxSizing: ['-webkit-box-sizing', '-moz-box-sizing'],
2051
- columns: ['-webkit-columns', '-moz-columns'],
2052
- columnCount: ['-webkit-column-count', '-moz-column-count'],
2053
- columnGap: ['-webkit-column-gap', '-moz-column-gap'],
2054
- columnRule: ['-webkit-column-rule', '-moz-column-rule'],
2055
- columnWidth: ['-webkit-column-width', '-moz-column-width'],
2056
- filter: ['-webkit-filter'],
2057
- fontSmoothing: ['-webkit-font-smoothing', '-moz-osx-font-smoothing'],
2058
- hyphens: ['-webkit-hyphens', '-moz-hyphens', '-ms-hyphens'],
1985
+ // Properties that still need -webkit- in Safari
1986
+ backgroundClip: ['-webkit-background-clip'],
2059
1987
  maskImage: ['-webkit-mask-image'],
2060
- perspective: ['-webkit-perspective', '-moz-perspective'],
2061
- perspectiveOrigin: ['-webkit-perspective-origin', '-moz-perspective-origin'],
2062
- textSizeAdjust: ['-webkit-text-size-adjust', '-moz-text-size-adjust', '-ms-text-size-adjust'],
2063
- userSelect: ['-webkit-user-select', '-moz-user-select', '-ms-user-select'],
2064
- // Special webkit-only properties
1988
+ // Webkit-only properties (no unprefixed equivalent)
2065
1989
  textFillColor: ['-webkit-text-fill-color'],
2066
1990
  textStroke: ['-webkit-text-stroke'],
2067
1991
  textStrokeColor: ['-webkit-text-stroke-color'],
@@ -2301,20 +2225,18 @@
2301
2225
  return processStyleProperty(property, value, getColor);
2302
2226
  },
2303
2227
  normalizeCssValue(value) {
2228
+ const str = typeof value === 'string' ? value : String(value);
2304
2229
  // Handle CSS variables in values
2305
- if (typeof value === 'string' && value.startsWith('--')) {
2306
- // For CSS variable values, use a special prefix to avoid conflicts
2307
- return `var-${value.substring(2)}`;
2230
+ if (str.charCodeAt(0) === 45 && str.charCodeAt(1) === 45) {
2231
+ // starts with '--'
2232
+ return `var-${str.substring(2)}`;
2308
2233
  }
2309
2234
  // Handle vendor-prefixed values
2310
- if (typeof value === 'string' && value.startsWith('-webkit-')) {
2311
- // For webkit values, preserve the prefix but normalize the rest
2312
- const prefix = '-webkit-';
2313
- const rest = value.substring(prefix.length);
2314
- 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');
2315
- return `webkit-${normalizedRest}`;
2235
+ if (str.startsWith('-webkit-')) {
2236
+ return `webkit-${str.substring(8).replace(/\./g, 'p').replace(/\s+/g, '-').replace(/[^a-zA-Z0-9-]/g, '')}`;
2316
2237
  }
2317
- 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');
2238
+ // Single-pass normalization: replace dots with 'p', spaces with '-', strip non-alphanumeric
2239
+ return str.replace(/\./g, 'p').replace(/\s+/g, '-').replace(/[^a-zA-Z0-9-]/g, '');
2318
2240
  },
2319
2241
  generateUniqueClassName(css) {
2320
2242
  if (rawCssCache.has(css)) {
@@ -2489,8 +2411,11 @@
2489
2411
  const cssProperty = propertyToKebabCase(property);
2490
2412
  let valueForCss = processedValue;
2491
2413
  // Handle numeric values for CSS
2492
- if (typeof valueForCss === 'number' && numericCssProperties.has(cssProperty)) {
2493
- valueForCss = `${valueForCss}px`;
2414
+ if (typeof valueForCss === 'number') {
2415
+ // lineHeight only accepts integers: 1-3 are unitless multipliers, 4+ are pixel sizes
2416
+ if (property === 'lineHeight' && Number.isInteger(valueForCss) && valueForCss <= 3) ; else if (numericCssProperties.has(cssProperty)) {
2417
+ valueForCss = `${valueForCss}px`;
2418
+ }
2494
2419
  }
2495
2420
  // Check if this property needs vendor prefixes
2496
2421
  const needsPrefixes = needsVendorPrefix(property);
@@ -2678,23 +2603,77 @@
2678
2603
  devices = {};
2679
2604
  }
2680
2605
  const classes = [];
2681
- Object.keys(styles).forEach(property => {
2682
- const value = styles[property];
2683
- let mediaQueriesForClass = [];
2684
- if (context === 'media') {
2685
- if (mediaQueries[modifier]) {
2686
- mediaQueriesForClass = [mediaQueries[modifier]];
2687
- } else if (devices[modifier]) {
2688
- mediaQueriesForClass = devices[modifier].map(mq => mediaQueries[mq]).filter(mq => mq);
2689
- }
2606
+ const activeManager = manager || utilityClassManager;
2607
+ // Pre-compute media queries for this modifier (only done once per call)
2608
+ let mediaQueriesForClass = [];
2609
+ if (context === 'media') {
2610
+ if (mediaQueries[modifier]) {
2611
+ mediaQueriesForClass = [mediaQueries[modifier]];
2612
+ } else if (devices[modifier]) {
2613
+ mediaQueriesForClass = devices[modifier].map(mq => mediaQueries[mq]).filter(mq => mq);
2690
2614
  }
2615
+ }
2616
+ const expandedStyles = expandShorthandStyles(styles);
2617
+ const keys = Object.keys(expandedStyles);
2618
+ for (let i = 0; i < keys.length; i++) {
2619
+ const value = expandedStyles[keys[i]];
2691
2620
  if (value !== undefined && value !== '') {
2692
- const classNames = (manager || utilityClassManager).getClassNames(property, value, context, modifier, getColor, mediaQueriesForClass);
2621
+ const classNames = activeManager.getClassNames(keys[i], value, context, modifier, getColor, mediaQueriesForClass);
2693
2622
  classes.push(...classNames);
2694
2623
  }
2695
- });
2624
+ }
2696
2625
  return classes;
2697
2626
  }
2627
+ /**
2628
+ * Expand shorthand props (widthHeight, paddingHorizontal, marginVertical, ...)
2629
+ * into their canonical CSS properties. Returns a new object so the caller's
2630
+ * input is not mutated. Keeps unknown shorthands only if their value is set.
2631
+ */
2632
+ function expandShorthandStyles(styles) {
2633
+ const wh = styles.widthHeight;
2634
+ const ph = styles.paddingHorizontal;
2635
+ const pv = styles.paddingVertical;
2636
+ const mh = styles.marginHorizontal;
2637
+ const mv = styles.marginVertical;
2638
+ if (wh === undefined && ph === undefined && pv === undefined && mh === undefined && mv === undefined) {
2639
+ return styles;
2640
+ }
2641
+ const out = {};
2642
+ const keys = Object.keys(styles);
2643
+ for (let i = 0; i < keys.length; i++) {
2644
+ const k = keys[i];
2645
+ if (k === 'widthHeight' || k === 'paddingHorizontal' || k === 'paddingVertical' || k === 'marginHorizontal' || k === 'marginVertical') {
2646
+ continue;
2647
+ }
2648
+ out[k] = styles[k];
2649
+ }
2650
+ if (wh !== undefined) {
2651
+ const v = typeof wh === 'number' ? `${wh}px` : wh;
2652
+ if (out.width === undefined) out.width = v;
2653
+ if (out.height === undefined) out.height = v;
2654
+ }
2655
+ if (ph !== undefined) {
2656
+ const v = typeof ph === 'number' ? `${ph}px` : ph;
2657
+ if (out.paddingLeft === undefined) out.paddingLeft = v;
2658
+ if (out.paddingRight === undefined) out.paddingRight = v;
2659
+ }
2660
+ if (pv !== undefined) {
2661
+ const v = typeof pv === 'number' ? `${pv}px` : pv;
2662
+ if (out.paddingTop === undefined) out.paddingTop = v;
2663
+ if (out.paddingBottom === undefined) out.paddingBottom = v;
2664
+ }
2665
+ if (mh !== undefined) {
2666
+ const v = typeof mh === 'number' ? `${mh}px` : mh;
2667
+ if (out.marginLeft === undefined) out.marginLeft = v;
2668
+ if (out.marginRight === undefined) out.marginRight = v;
2669
+ }
2670
+ if (mv !== undefined) {
2671
+ const v = typeof mv === 'number' ? `${mv}px` : mv;
2672
+ if (out.marginTop === undefined) out.marginTop = v;
2673
+ if (out.marginBottom === undefined) out.marginBottom = v;
2674
+ }
2675
+ return out;
2676
+ }
2698
2677
  // Add a function to handle nested pseudo-classes
2699
2678
  function processPseudoStyles(styles, parentPseudo, getColor, manager) {
2700
2679
  if (parentPseudo === void 0) {
@@ -2811,6 +2790,7 @@
2811
2790
  const extractUtilityClasses = (props, getColor, mediaQueries, devices, manager) => {
2812
2791
  const classes = [];
2813
2792
  const computedStyles = {};
2793
+ const activeManager = manager || utilityClassManager;
2814
2794
  // Handle widthHeight (shorthand for both width and height)
2815
2795
  if (props.widthHeight || props.height !== undefined && props.width !== undefined && props.height === props.width) {
2816
2796
  const widthHeightValue = props.widthHeight || props.width;
@@ -2818,21 +2798,30 @@
2818
2798
  computedStyles.width = formattedValue;
2819
2799
  computedStyles.height = formattedValue;
2820
2800
  }
2821
- // Handle padding and margin shorthands
2822
- const shorthandProps = {
2823
- paddingHorizontal: ['paddingLeft', 'paddingRight'],
2824
- paddingVertical: ['paddingTop', 'paddingBottom'],
2825
- marginHorizontal: ['marginLeft', 'marginRight'],
2826
- marginVertical: ['marginTop', 'marginBottom']
2827
- };
2828
- for (const [shorthand, properties] of Object.entries(shorthandProps)) {
2829
- const value = props[shorthand];
2830
- if (value !== undefined) {
2831
- const formattedValue = typeof value === 'number' ? `${value}px` : value;
2832
- properties.forEach(prop => {
2833
- computedStyles[prop] = formattedValue;
2834
- });
2835
- }
2801
+ // Handle padding and margin shorthands (inlined to avoid Object.entries overhead)
2802
+ const ph = props.paddingHorizontal;
2803
+ if (ph !== undefined) {
2804
+ const v = typeof ph === 'number' ? `${ph}px` : ph;
2805
+ computedStyles.paddingLeft = v;
2806
+ computedStyles.paddingRight = v;
2807
+ }
2808
+ const pv = props.paddingVertical;
2809
+ if (pv !== undefined) {
2810
+ const v = typeof pv === 'number' ? `${pv}px` : pv;
2811
+ computedStyles.paddingTop = v;
2812
+ computedStyles.paddingBottom = v;
2813
+ }
2814
+ const mh = props.marginHorizontal;
2815
+ if (mh !== undefined) {
2816
+ const v = typeof mh === 'number' ? `${mh}px` : mh;
2817
+ computedStyles.marginLeft = v;
2818
+ computedStyles.marginRight = v;
2819
+ }
2820
+ const mv = props.marginVertical;
2821
+ if (mv !== undefined) {
2822
+ const v = typeof mv === 'number' ? `${mv}px` : mv;
2823
+ computedStyles.marginTop = v;
2824
+ computedStyles.marginBottom = v;
2836
2825
  }
2837
2826
  // Handle shadows
2838
2827
  if (props.shadow !== undefined) {
@@ -2861,80 +2850,103 @@
2861
2850
  const animations = Array.isArray(props.animate) ? props.animate : [props.animate];
2862
2851
  Object.assign(computedStyles, AnimationUtils.processAnimations(animations, manager));
2863
2852
  }
2864
- const blendConfig = {
2865
- mode: 'difference',
2866
- color: 'white',
2867
- modeWithBg: 'overlay'
2868
- };
2869
- const setBlend = (props, style) => {
2853
+ // Handle default blend
2854
+ if (props.blend === true) {
2870
2855
  if (props.bgColor) {
2871
- style.mixBlendMode = blendConfig.modeWithBg;
2872
- style.color = 'white';
2856
+ computedStyles.mixBlendMode = 'overlay';
2857
+ computedStyles.color = 'white';
2873
2858
  } else {
2874
- style.mixBlendMode = blendConfig.mode;
2875
- style.color = blendConfig.color;
2859
+ computedStyles.mixBlendMode = 'difference';
2860
+ computedStyles.color = 'white';
2876
2861
  }
2877
- };
2878
- // Handle default blend
2879
- if (props.blend === true) {
2880
- setBlend(props, computedStyles);
2881
- Object.keys(props).forEach(property => {
2882
- if (props[property]?.color === undefined && (property.startsWith('_') || property === 'on' || property === 'media')) {
2883
- setBlend(props[property], props[property]);
2884
- }
2885
- });
2886
2862
  }
2887
- // Process base styles
2863
+ // Process base computed styles
2888
2864
  classes.push(...processStyles(computedStyles, 'base', '', getColor, {}, {}, manager));
2889
- // Collect underscore-prefixed properties (_hover, _focus, etc.)
2890
- const underscoreProps = {};
2891
- Object.keys(props).forEach(property => {
2892
- if (property.startsWith('_') && property.length > 1) {
2865
+ // SINGLE PASS over props: classify each prop into style, event, or underscore
2866
+ const propKeys = Object.keys(props);
2867
+ for (let i = 0; i < propKeys.length; i++) {
2868
+ const property = propKeys[i];
2869
+ const value = props[property];
2870
+ // Handle underscore-prefixed event properties (_hover, _focus, etc.)
2871
+ if (property.charCodeAt(0) === 95 && property.length > 1) {
2872
+ // 95 = '_'
2893
2873
  const eventName = property.substring(1);
2894
- underscoreProps[eventName] = props[property];
2874
+ classes.push(...processEventStyles(eventName, value, getColor, manager));
2875
+ // Handle blend for underscore props
2876
+ if (props.blend === true && value?.color === undefined) {
2877
+ if (props.bgColor) {
2878
+ value.mixBlendMode = 'overlay';
2879
+ value.color = 'white';
2880
+ } else {
2881
+ value.mixBlendMode = 'difference';
2882
+ value.color = 'white';
2883
+ }
2884
+ }
2885
+ continue;
2895
2886
  }
2896
- });
2897
- // Process standard style props
2898
- Object.keys(props).forEach(property => {
2899
- if (property !== 'style' && property !== 'css' && !property.startsWith('_') && (isStyleProp(property) || ['on', 'media'].includes(property))) {
2900
- const value = props[property];
2887
+ // Skip non-style props
2888
+ if (property === 'style' || property === 'css') continue;
2889
+ if (property === 'on') {
2890
+ // Process event-based styles
2901
2891
  if (typeof value === 'object' && value !== null) {
2902
- if (property === 'on') {
2903
- // Process event-based styles
2904
- Object.keys(value).forEach(event => {
2905
- classes.push(...processEventStyles(event, value[event], getColor, manager));
2906
- });
2907
- } else if (property === 'media') {
2908
- // Process media query styles
2909
- Object.keys(value).forEach(screenOrDevice => {
2910
- classes.push(...processStyles(value[screenOrDevice], 'media', screenOrDevice, getColor, mediaQueries, devices, manager));
2911
- });
2892
+ const events = Object.keys(value);
2893
+ for (let j = 0; j < events.length; j++) {
2894
+ classes.push(...processEventStyles(events[j], value[events[j]], getColor, manager));
2895
+ }
2896
+ // Handle blend for 'on' prop
2897
+ if (props.blend === true && value?.color === undefined) {
2898
+ if (props.bgColor) {
2899
+ value.mixBlendMode = 'overlay';
2900
+ value.color = 'white';
2901
+ } else {
2902
+ value.mixBlendMode = 'difference';
2903
+ value.color = 'white';
2904
+ }
2912
2905
  }
2913
- } else if (value !== undefined && value !== '') {
2914
- // Direct style property
2915
- classes.push(...(manager || utilityClassManager).getClassNames(property, value, 'base', '', getColor, []));
2916
2906
  }
2907
+ continue;
2917
2908
  }
2918
- });
2909
+ if (property === 'media') {
2910
+ // Process media query styles
2911
+ if (typeof value === 'object' && value !== null) {
2912
+ const screens = Object.keys(value);
2913
+ for (let j = 0; j < screens.length; j++) {
2914
+ classes.push(...processStyles(value[screens[j]], 'media', screens[j], getColor, mediaQueries, devices, manager));
2915
+ }
2916
+ // Handle blend for 'media' prop
2917
+ if (props.blend === true && value?.color === undefined) {
2918
+ if (props.bgColor) {
2919
+ value.mixBlendMode = 'overlay';
2920
+ value.color = 'white';
2921
+ } else {
2922
+ value.mixBlendMode = 'difference';
2923
+ value.color = 'white';
2924
+ }
2925
+ }
2926
+ }
2927
+ continue;
2928
+ }
2929
+ // Standard style props
2930
+ if (isStyleProp(property)) {
2931
+ if (value !== undefined && value !== '') {
2932
+ if (typeof value === 'object' && value !== null) {
2933
+ // Object-style props are not directly processed as base styles
2934
+ continue;
2935
+ }
2936
+ classes.push(...activeManager.getClassNames(property, value, 'base', '', getColor, []));
2937
+ }
2938
+ }
2939
+ }
2919
2940
  // Handle raw CSS - uses 'override' context for higher specificity
2920
2941
  if (props.css) {
2921
2942
  if (typeof props.css === 'object') {
2922
- // Object-style CSS gets processed with override context for higher priority
2923
- Object.assign(computedStyles, props.css);
2924
2943
  classes.push(...processStyles(props.css, 'override', '', getColor, {}, {}, manager));
2925
2944
  } else if (typeof props.css === 'string') {
2926
- // String-style CSS gets its own class in override context
2927
2945
  const uniqueClassName = ValueUtils.generateUniqueClassName(props.css);
2928
- (manager || utilityClassManager).injectRule(`.${uniqueClassName} { ${props.css} }`, 'override');
2946
+ activeManager.injectRule(`.${uniqueClassName} { ${props.css} }`, 'override');
2929
2947
  classes.push(uniqueClassName);
2930
2948
  }
2931
2949
  }
2932
- // Process underscore-prefixed event properties
2933
- if (Object.keys(underscoreProps).length > 0) {
2934
- Object.keys(underscoreProps).forEach(event => {
2935
- classes.push(...processEventStyles(event, underscoreProps[event], getColor, manager));
2936
- });
2937
- }
2938
2950
  return classes;
2939
2951
  };
2940
2952
 
@@ -3001,27 +3013,47 @@
3001
3013
  }, children);
3002
3014
  };
3003
3015
 
3016
+ // Set of special prop names that affect CSS generation
3017
+ const styleRelevantProps = /*#__PURE__*/new Set(['on', 'media', 'animate', 'css', 'shadow', 'blend', 'widthHeight', 'paddingHorizontal', 'paddingVertical', 'marginHorizontal', 'marginVertical']);
3018
+ // Skip these props from hash computation
3019
+ const skipHashProps = /*#__PURE__*/new Set(['children', 'ref', 'key', 'style']);
3020
+ /**
3021
+ * Fast serialization of a value for hashing purposes.
3022
+ * Avoids JSON.stringify overhead for common cases (strings, numbers, booleans).
3023
+ */
3024
+ function fastSerialize(value) {
3025
+ if (value === null) return 'n';
3026
+ const t = typeof value;
3027
+ if (t === 'string') return `s${value}`;
3028
+ if (t === 'number') return `d${value}`;
3029
+ if (t === 'boolean') return value ? 'T' : 'F';
3030
+ // Fall back to JSON.stringify only for complex objects
3031
+ return JSON.stringify(value);
3032
+ }
3004
3033
  /**
3005
3034
  * Computes a stable hash of style-relevant props.
3006
- * This is used to determine if style extraction needs to be re-run.
3035
+ * Optimized: avoids sorting, uses fast serialization, feeds directly to hash.
3007
3036
  */
3008
3037
  function hashStyleProps(props) {
3009
3038
  // Build a deterministic string representation of style-relevant props
3010
- const parts = [];
3011
- const sortedKeys = Object.keys(props).sort();
3012
- for (const key of sortedKeys) {
3039
+ // We use a single string accumulator instead of array + join
3040
+ let hashInput = '';
3041
+ const keys = Object.keys(props);
3042
+ for (let i = 0; i < keys.length; i++) {
3043
+ const key = keys[i];
3013
3044
  // Skip non-style props that don't affect CSS generation
3014
- if (key === 'children' || key === 'ref' || key === 'key') continue;
3045
+ if (skipHashProps.has(key)) continue;
3015
3046
  // Include style-relevant props
3016
- 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') {
3047
+ if (isStyleProp(key) || key.charCodeAt(0) === 95 ||
3048
+ // starts with '_'
3049
+ styleRelevantProps.has(key)) {
3017
3050
  const value = props[key];
3018
3051
  if (value !== undefined) {
3019
- // Use JSON.stringify for consistent serialization
3020
- parts.push(`${key}:${JSON.stringify(value)}`);
3052
+ hashInput += `|${key}:${fastSerialize(value)}`;
3021
3053
  }
3022
3054
  }
3023
3055
  }
3024
- return hash(parts.join('|'));
3056
+ return hash(hashInput);
3025
3057
  }
3026
3058
  /**
3027
3059
  * Custom hook that memoizes style extraction based on a stable hash of props.
@@ -3031,8 +3063,9 @@
3031
3063
  const cacheRef = React.useRef(null);
3032
3064
  // Compute hash directly — no useMemo since propsToProcess is always a new
3033
3065
  // reference (from destructuring), so the memo deps would always change.
3034
- const themeHash = theme ? JSON.stringify(theme) : '';
3035
- const currentHash = hashStyleProps(propsToProcess) + '|' + hash(themeHash);
3066
+ // Theme hash uses Object.values() concatenation instead of JSON.stringify
3067
+ const themeHash = theme ? hash(Object.values(theme).join('|')) : '';
3068
+ const currentHash = hashStyleProps(propsToProcess) + '|' + themeHash;
3036
3069
  // Only recompute classes if hash changed
3037
3070
  if (!cacheRef.current || cacheRef.current.hash !== currentHash) {
3038
3071
  const classes = extractUtilityClasses(propsToProcess, getColor, mediaQueries, devices, manager);
@@ -3209,18 +3242,24 @@
3209
3242
  after,
3210
3243
  ...otherProps
3211
3244
  } = rest;
3212
- // First, add all event handlers (they start with "on" and have a capital letter after)
3213
- Object.keys(otherProps).forEach(key => {
3214
- if (key.startsWith('on') && key.length > 2 && key[2] === key[2].toUpperCase()) {
3245
+ // Single pass: add event handlers and non-style props together
3246
+ const otherKeys = Object.keys(otherProps);
3247
+ for (let i = 0; i < otherKeys.length; i++) {
3248
+ const key = otherKeys[i];
3249
+ // Event handlers: start with "on" + uppercase letter
3250
+ if (key.charCodeAt(0) === 111 &&
3251
+ // 'o'
3252
+ key.charCodeAt(1) === 110 &&
3253
+ // 'n'
3254
+ key.length > 2 && key.charCodeAt(2) >= 65 && key.charCodeAt(2) <= 90 // uppercase A-Z
3255
+ ) {
3215
3256
  newProps[key] = otherProps[key];
3216
3257
  }
3217
- });
3218
- // Then add all other non-style props
3219
- Object.keys(otherProps).forEach(key => {
3220
- if (!excludedKeys.has(key) && !isStyleProp(key) || includeKeys.has(key)) {
3258
+ // Non-style props (pass through to DOM)
3259
+ else if (!excludedKeys.has(key) && !isStyleProp(key) || includeKeys.has(key)) {
3221
3260
  newProps[key] = otherProps[key];
3222
3261
  }
3223
- });
3262
+ }
3224
3263
  if (style) {
3225
3264
  newProps.style = style;
3226
3265
  }
@@ -3307,11 +3346,17 @@
3307
3346
  ref: ref
3308
3347
  }))));
3309
3348
 
3310
- const Image = /*#__PURE__*/React__default.forwardRef((props, ref) => (/*#__PURE__*/React__default.createElement(Element, Object.assign({
3311
- as: "img"
3312
- }, props, {
3313
- ref: ref
3314
- }))));
3349
+ const Image = /*#__PURE__*/React__default.forwardRef((props, ref) => {
3350
+ const imageProps = {
3351
+ ...props,
3352
+ alt: props.alt || ''
3353
+ };
3354
+ return /*#__PURE__*/React__default.createElement(Element, Object.assign({
3355
+ as: "img"
3356
+ }, imageProps, {
3357
+ ref: ref
3358
+ }));
3359
+ });
3315
3360
  const ImageBackground = /*#__PURE__*/React__default.forwardRef((_ref, ref) => {
3316
3361
  let {
3317
3362
  src,
@@ -3410,11 +3455,18 @@
3410
3455
  }, props, {
3411
3456
  ref: ref
3412
3457
  }))));
3413
- const Button = /*#__PURE__*/React__default.forwardRef((props, ref) => (/*#__PURE__*/React__default.createElement(Element, Object.assign({
3414
- as: "button"
3415
- }, props, {
3416
- ref: ref
3417
- }))));
3458
+ const Button = /*#__PURE__*/React__default.forwardRef((props, ref) => {
3459
+ {
3460
+ if (!props.children && !props['aria-label']) {
3461
+ console.warn('Accessibility Warning: Button is missing an accessible name. If it is an icon-only button, please provide an `aria-label`.');
3462
+ }
3463
+ }
3464
+ return /*#__PURE__*/React__default.createElement(Element, Object.assign({
3465
+ as: "button"
3466
+ }, props, {
3467
+ ref: ref
3468
+ }));
3469
+ });
3418
3470
 
3419
3471
  // animations.ts
3420
3472
  const fadeIn = function (_temp) {