app-studio 0.7.9 → 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.
@@ -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,22 +1765,71 @@
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();
1770
+ // Non-hyphenated HTML/SVG attributes that must never be treated as style props.
1771
+ // Hyphenated attributes (aria-*, data-*, etc.) are caught by a prefix/hyphen check below.
1772
+ const htmlOnlyAttributes = /*#__PURE__*/new Set([
1773
+ // Accessibility
1774
+ 'role', 'tabIndex',
1775
+ // Global HTML attributes
1776
+ 'id', 'title', 'lang', 'dir', 'hidden', 'draggable', 'contentEditable', 'spellCheck', 'nonce', 'slot', 'is', 'inputMode', 'enterKeyHint', 'autofocus', 'autoFocus', 'translate',
1777
+ // Form attributes
1778
+ 'autoComplete', 'name', 'disabled', 'readOnly', 'required', 'checked', 'selected', 'multiple', 'value', 'defaultValue', 'defaultChecked', 'placeholder', 'htmlFor', 'type', 'accept', 'maxLength', 'minLength', 'pattern', 'noValidate', 'formAction', 'formEncType', 'formMethod', 'formNoValidate', 'formTarget',
1779
+ // Link/navigation attributes
1780
+ 'href', 'target', 'rel', 'download', 'referrerPolicy', 'integrity', 'crossOrigin',
1781
+ // Form container attributes
1782
+ 'action', 'method', 'encType',
1783
+ // Media attributes
1784
+ 'autoPlay', 'controls', 'loop', 'muted', 'playsInline', 'poster', 'preload', 'mediaGroup',
1785
+ // Image/embed attributes
1786
+ 'loading', 'decoding', 'sizes', 'srcDoc', 'srcLang', 'srcSet', 'useMap',
1787
+ // Table attributes
1788
+ 'colSpan', 'rowSpan', 'cols', 'rows', 'headers', 'scope', 'span',
1789
+ // Iframe/embed attributes
1790
+ 'sandbox', 'allowFullScreen', 'frameBorder', 'scrolling', 'seamless', 'allow',
1791
+ // Interactive attributes
1792
+ 'open', 'cite', 'dateTime', 'reversed', 'start', 'high', 'low', 'optimum', 'wrap', 'shape', 'size', 'summary',
1793
+ // Script/resource attributes
1794
+ 'async', 'defer', 'noModule', 'charSet', 'httpEquiv', 'manifest',
1795
+ // Microdata/RDFa attributes
1796
+ 'about', 'datatype', 'inlist', 'prefix', 'property', 'resource', 'typeof', 'vocab', 'itemProp', 'itemScope', 'itemType', 'itemID', 'itemRef',
1797
+ // Deprecated but still used
1798
+ 'classID', 'contextMenu', 'keyParams', 'keyType', 'kind', 'label', 'list', 'profile', 'radioGroup', 'wmode', 'capture', 'challenge', 'scoped', 'step', 'form',
1799
+ // SVG-only attributes (not CSS properties)
1800
+ 'viewBox', 'preserveAspectRatio', 'xmlns', 'xlinkHref', 'xmlBase', 'xmlLang', 'xmlSpace', 'd', 'pathLength', 'points', 'markerEnd', 'markerMid', 'markerStart', 'clipPathUnits', 'gradientUnits', 'gradientTransform', 'patternUnits', 'patternTransform', 'patternContentUnits', 'spreadMethod', 'startOffset', 'stdDeviation', 'stitchTiles', 'surfaceScale', 'textLength', 'lengthAdjust', 'maskUnits', 'maskContentUnits', 'filterUnits', 'primitiveUnits', 'numOctaves', 'baseFrequency', 'seed', 'result', 'in2', 'values', 'keyTimes', 'keySplines', 'repeatCount', 'repeatDur', 'calcMode', 'attributeName', 'attributeType', 'begin', 'dur', 'end', 'by']);
1814
1801
  // Improved style prop detection
1815
1802
  const isStyleProp = prop => {
1816
1803
  // First check if it's a common event handler (these should never be treated as style props)
1817
1804
  if (commonEventHandlers.has(prop)) {
1818
1805
  return false;
1819
1806
  }
1807
+ // HTML attributes should never be treated as style props
1808
+ if (htmlOnlyAttributes.has(prop)) {
1809
+ return false;
1810
+ }
1811
+ // Any prop containing a hyphen is an HTML attribute (aria-*, data-*, accept-charset, etc.)
1812
+ // In React, CSS properties are always camelCase — only CSS custom properties use hyphens (--*),
1813
+ // and data-style-* is a special convention handled separately below.
1814
+ if (prop.includes('-') && !prop.startsWith('--') && !prop.startsWith('data-style-')) {
1815
+ return false;
1816
+ }
1820
1817
  // Check if it's a valid CSS property or custom style prop
1821
1818
  if (cssProperties.has(prop) || extraKeys.has(prop) || prop.startsWith('--') || prop.startsWith('data-style-') && !includeKeys.has(prop)) {
1822
1819
  return true;
1823
1820
  }
1824
1821
  // Check if it's a valid CSS property using CSS.supports (browser environment)
1822
+ // Results are cached to avoid repeated CSS.supports calls
1825
1823
  if (typeof CSS !== 'undefined' && CSS.supports) {
1824
+ const cached = cssSupportCache.get(prop);
1825
+ if (cached !== undefined) return cached;
1826
1826
  try {
1827
1827
  const kebabProp = vendorPrefixToKebabCase(prop);
1828
- return CSS.supports(kebabProp, 'inherit');
1828
+ const result = CSS.supports(kebabProp, 'inherit');
1829
+ cssSupportCache.set(prop, result);
1830
+ return result;
1829
1831
  } catch {
1832
+ cssSupportCache.set(prop, false);
1830
1833
  return false;
1831
1834
  }
1832
1835
  }
@@ -1960,67 +1963,25 @@
1960
1963
 
1961
1964
  /**
1962
1965
  * Mapping of standard CSS properties to their vendor-prefixed equivalents.
1963
- * This helps ensure cross-browser compatibility by automatically applying
1964
- * the appropriate vendor prefixes when needed.
1966
+ *
1967
+ * Optimized for modern browsers (Chrome 100+, Safari 15+, Firefox 91+).
1968
+ * Most properties no longer need vendor prefixes in these browsers.
1969
+ * Only -webkit- prefixes are kept where still needed by Safari/WebKit.
1970
+ *
1971
+ * Removed prefixes:
1972
+ * - -moz- (Firefox 91+ supports all standard properties unprefixed)
1973
+ * - -ms- (IE/Edge Legacy no longer supported)
1974
+ * - -o- (Opera uses Blink engine, same as Chrome)
1975
+ * - -webkit- for animation, transform, transition, flexbox, boxShadow,
1976
+ * boxSizing, columns, borderImage, backgroundSize, backgroundOrigin,
1977
+ * perspective, hyphens (all unprefixed in Safari 15+)
1965
1978
  */
1966
- // Properties that commonly need vendor prefixes across browsers
1979
+ // Properties that still need vendor prefixes in modern browsers
1967
1980
  const vendorPrefixedProperties = {
1968
- // Animation properties
1969
- animation: ['-webkit-animation', '-moz-animation', '-o-animation'],
1970
- animationDelay: ['-webkit-animation-delay', '-moz-animation-delay', '-o-animation-delay'],
1971
- animationDirection: ['-webkit-animation-direction', '-moz-animation-direction', '-o-animation-direction'],
1972
- animationDuration: ['-webkit-animation-duration', '-moz-animation-duration', '-o-animation-duration'],
1973
- animationFillMode: ['-webkit-animation-fill-mode', '-moz-animation-fill-mode', '-o-animation-fill-mode'],
1974
- animationIterationCount: ['-webkit-animation-iteration-count', '-moz-animation-iteration-count', '-o-animation-iteration-count'],
1975
- animationName: ['-webkit-animation-name', '-moz-animation-name', '-o-animation-name'],
1976
- animationPlayState: ['-webkit-animation-play-state', '-moz-animation-play-state', '-o-animation-play-state'],
1977
- animationTimingFunction: ['-webkit-animation-timing-function', '-moz-animation-timing-function', '-o-animation-timing-function'],
1978
- // Transform properties
1979
- transform: ['-webkit-transform', '-moz-transform', '-ms-transform', '-o-transform'],
1980
- transformOrigin: ['-webkit-transform-origin', '-moz-transform-origin', '-ms-transform-origin', '-o-transform-origin'],
1981
- transformStyle: ['-webkit-transform-style', '-moz-transform-style', '-ms-transform-style'],
1982
- // Transition properties
1983
- transition: ['-webkit-transition', '-moz-transition', '-ms-transition', '-o-transition'],
1984
- transitionDelay: ['-webkit-transition-delay', '-moz-transition-delay', '-ms-transition-delay', '-o-transition-delay'],
1985
- transitionDuration: ['-webkit-transition-duration', '-moz-transition-duration', '-ms-transition-duration', '-o-transition-duration'],
1986
- transitionProperty: ['-webkit-transition-property', '-moz-transition-property', '-ms-transition-property', '-o-transition-property'],
1987
- transitionTimingFunction: ['-webkit-transition-timing-function', '-moz-transition-timing-function', '-ms-transition-timing-function', '-o-transition-timing-function'],
1988
- // Flexbox properties
1989
- flex: ['-webkit-flex', '-ms-flex'],
1990
- flexBasis: ['-webkit-flex-basis', '-ms-flex-basis'],
1991
- flexDirection: ['-webkit-flex-direction', '-ms-flex-direction'],
1992
- flexFlow: ['-webkit-flex-flow', '-ms-flex-flow'],
1993
- flexGrow: ['-webkit-flex-grow', '-ms-flex-positive'],
1994
- flexShrink: ['-webkit-flex-shrink', '-ms-flex-negative'],
1995
- flexWrap: ['-webkit-flex-wrap', '-ms-flex-wrap'],
1996
- justifyContent: ['-webkit-justify-content', '-ms-flex-pack'],
1997
- alignItems: ['-webkit-align-items', '-ms-flex-align'],
1998
- alignContent: ['-webkit-align-content', '-ms-flex-line-pack'],
1999
- alignSelf: ['-webkit-align-self', '-ms-flex-item-align'],
2000
- order: ['-webkit-order', '-ms-flex-order'],
2001
- // Other commonly prefixed properties
2002
- appearance: ['-webkit-appearance', '-moz-appearance', '-ms-appearance'],
2003
- backfaceVisibility: ['-webkit-backface-visibility', '-moz-backface-visibility'],
2004
- backgroundClip: ['-webkit-background-clip', '-moz-background-clip'],
2005
- backgroundOrigin: ['-webkit-background-origin', '-moz-background-origin'],
2006
- backgroundSize: ['-webkit-background-size', '-moz-background-size', '-o-background-size'],
2007
- borderImage: ['-webkit-border-image', '-moz-border-image', '-o-border-image'],
2008
- boxShadow: ['-webkit-box-shadow', '-moz-box-shadow'],
2009
- boxSizing: ['-webkit-box-sizing', '-moz-box-sizing'],
2010
- columns: ['-webkit-columns', '-moz-columns'],
2011
- columnCount: ['-webkit-column-count', '-moz-column-count'],
2012
- columnGap: ['-webkit-column-gap', '-moz-column-gap'],
2013
- columnRule: ['-webkit-column-rule', '-moz-column-rule'],
2014
- columnWidth: ['-webkit-column-width', '-moz-column-width'],
2015
- filter: ['-webkit-filter'],
2016
- fontSmoothing: ['-webkit-font-smoothing', '-moz-osx-font-smoothing'],
2017
- hyphens: ['-webkit-hyphens', '-moz-hyphens', '-ms-hyphens'],
1981
+ // Properties that still need -webkit- in Safari
1982
+ backgroundClip: ['-webkit-background-clip'],
2018
1983
  maskImage: ['-webkit-mask-image'],
2019
- perspective: ['-webkit-perspective', '-moz-perspective'],
2020
- perspectiveOrigin: ['-webkit-perspective-origin', '-moz-perspective-origin'],
2021
- textSizeAdjust: ['-webkit-text-size-adjust', '-moz-text-size-adjust', '-ms-text-size-adjust'],
2022
- userSelect: ['-webkit-user-select', '-moz-user-select', '-ms-user-select'],
2023
- // Special webkit-only properties
1984
+ // Webkit-only properties (no unprefixed equivalent)
2024
1985
  textFillColor: ['-webkit-text-fill-color'],
2025
1986
  textStroke: ['-webkit-text-stroke'],
2026
1987
  textStrokeColor: ['-webkit-text-stroke-color'],
@@ -2260,20 +2221,18 @@
2260
2221
  return processStyleProperty(property, value, getColor);
2261
2222
  },
2262
2223
  normalizeCssValue(value) {
2224
+ const str = typeof value === 'string' ? value : String(value);
2263
2225
  // Handle CSS variables in values
2264
- if (typeof value === 'string' && value.startsWith('--')) {
2265
- // For CSS variable values, use a special prefix to avoid conflicts
2266
- return `var-${value.substring(2)}`;
2226
+ if (str.charCodeAt(0) === 45 && str.charCodeAt(1) === 45) {
2227
+ // starts with '--'
2228
+ return `var-${str.substring(2)}`;
2267
2229
  }
2268
2230
  // Handle vendor-prefixed values
2269
- if (typeof value === 'string' && value.startsWith('-webkit-')) {
2270
- // For webkit values, preserve the prefix but normalize the rest
2271
- const prefix = '-webkit-';
2272
- const rest = value.substring(prefix.length);
2273
- 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');
2274
- return `webkit-${normalizedRest}`;
2231
+ if (str.startsWith('-webkit-')) {
2232
+ return `webkit-${str.substring(8).replace(/\./g, 'p').replace(/\s+/g, '-').replace(/[^a-zA-Z0-9-]/g, '')}`;
2275
2233
  }
2276
- 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');
2234
+ // Single-pass normalization: replace dots with 'p', spaces with '-', strip non-alphanumeric
2235
+ return str.replace(/\./g, 'p').replace(/\s+/g, '-').replace(/[^a-zA-Z0-9-]/g, '');
2277
2236
  },
2278
2237
  generateUniqueClassName(css) {
2279
2238
  if (rawCssCache.has(css)) {
@@ -2637,21 +2596,24 @@
2637
2596
  devices = {};
2638
2597
  }
2639
2598
  const classes = [];
2640
- Object.keys(styles).forEach(property => {
2641
- const value = styles[property];
2642
- let mediaQueriesForClass = [];
2643
- if (context === 'media') {
2644
- if (mediaQueries[modifier]) {
2645
- mediaQueriesForClass = [mediaQueries[modifier]];
2646
- } else if (devices[modifier]) {
2647
- mediaQueriesForClass = devices[modifier].map(mq => mediaQueries[mq]).filter(mq => mq);
2648
- }
2599
+ const activeManager = manager || utilityClassManager;
2600
+ // Pre-compute media queries for this modifier (only done once per call)
2601
+ let mediaQueriesForClass = [];
2602
+ if (context === 'media') {
2603
+ if (mediaQueries[modifier]) {
2604
+ mediaQueriesForClass = [mediaQueries[modifier]];
2605
+ } else if (devices[modifier]) {
2606
+ mediaQueriesForClass = devices[modifier].map(mq => mediaQueries[mq]).filter(mq => mq);
2649
2607
  }
2608
+ }
2609
+ const keys = Object.keys(styles);
2610
+ for (let i = 0; i < keys.length; i++) {
2611
+ const value = styles[keys[i]];
2650
2612
  if (value !== undefined && value !== '') {
2651
- const classNames = (manager || utilityClassManager).getClassNames(property, value, context, modifier, getColor, mediaQueriesForClass);
2613
+ const classNames = activeManager.getClassNames(keys[i], value, context, modifier, getColor, mediaQueriesForClass);
2652
2614
  classes.push(...classNames);
2653
2615
  }
2654
- });
2616
+ }
2655
2617
  return classes;
2656
2618
  }
2657
2619
  // Add a function to handle nested pseudo-classes
@@ -2770,6 +2732,7 @@
2770
2732
  const extractUtilityClasses = (props, getColor, mediaQueries, devices, manager) => {
2771
2733
  const classes = [];
2772
2734
  const computedStyles = {};
2735
+ const activeManager = manager || utilityClassManager;
2773
2736
  // Handle widthHeight (shorthand for both width and height)
2774
2737
  if (props.widthHeight || props.height !== undefined && props.width !== undefined && props.height === props.width) {
2775
2738
  const widthHeightValue = props.widthHeight || props.width;
@@ -2777,21 +2740,30 @@
2777
2740
  computedStyles.width = formattedValue;
2778
2741
  computedStyles.height = formattedValue;
2779
2742
  }
2780
- // Handle padding and margin shorthands
2781
- const shorthandProps = {
2782
- paddingHorizontal: ['paddingLeft', 'paddingRight'],
2783
- paddingVertical: ['paddingTop', 'paddingBottom'],
2784
- marginHorizontal: ['marginLeft', 'marginRight'],
2785
- marginVertical: ['marginTop', 'marginBottom']
2786
- };
2787
- for (const [shorthand, properties] of Object.entries(shorthandProps)) {
2788
- const value = props[shorthand];
2789
- if (value !== undefined) {
2790
- const formattedValue = typeof value === 'number' ? `${value}px` : value;
2791
- properties.forEach(prop => {
2792
- computedStyles[prop] = formattedValue;
2793
- });
2794
- }
2743
+ // Handle padding and margin shorthands (inlined to avoid Object.entries overhead)
2744
+ const ph = props.paddingHorizontal;
2745
+ if (ph !== undefined) {
2746
+ const v = typeof ph === 'number' ? `${ph}px` : ph;
2747
+ computedStyles.paddingLeft = v;
2748
+ computedStyles.paddingRight = v;
2749
+ }
2750
+ const pv = props.paddingVertical;
2751
+ if (pv !== undefined) {
2752
+ const v = typeof pv === 'number' ? `${pv}px` : pv;
2753
+ computedStyles.paddingTop = v;
2754
+ computedStyles.paddingBottom = v;
2755
+ }
2756
+ const mh = props.marginHorizontal;
2757
+ if (mh !== undefined) {
2758
+ const v = typeof mh === 'number' ? `${mh}px` : mh;
2759
+ computedStyles.marginLeft = v;
2760
+ computedStyles.marginRight = v;
2761
+ }
2762
+ const mv = props.marginVertical;
2763
+ if (mv !== undefined) {
2764
+ const v = typeof mv === 'number' ? `${mv}px` : mv;
2765
+ computedStyles.marginTop = v;
2766
+ computedStyles.marginBottom = v;
2795
2767
  }
2796
2768
  // Handle shadows
2797
2769
  if (props.shadow !== undefined) {
@@ -2820,80 +2792,103 @@
2820
2792
  const animations = Array.isArray(props.animate) ? props.animate : [props.animate];
2821
2793
  Object.assign(computedStyles, AnimationUtils.processAnimations(animations, manager));
2822
2794
  }
2823
- const blendConfig = {
2824
- mode: 'difference',
2825
- color: 'white',
2826
- modeWithBg: 'overlay'
2827
- };
2828
- const setBlend = (props, style) => {
2795
+ // Handle default blend
2796
+ if (props.blend === true) {
2829
2797
  if (props.bgColor) {
2830
- style.mixBlendMode = blendConfig.modeWithBg;
2831
- style.color = 'white';
2798
+ computedStyles.mixBlendMode = 'overlay';
2799
+ computedStyles.color = 'white';
2832
2800
  } else {
2833
- style.mixBlendMode = blendConfig.mode;
2834
- style.color = blendConfig.color;
2801
+ computedStyles.mixBlendMode = 'difference';
2802
+ computedStyles.color = 'white';
2835
2803
  }
2836
- };
2837
- // Handle default blend
2838
- if (props.blend === true) {
2839
- setBlend(props, computedStyles);
2840
- Object.keys(props).forEach(property => {
2841
- if (props[property]?.color === undefined && (property.startsWith('_') || property === 'on' || property === 'media')) {
2842
- setBlend(props[property], props[property]);
2843
- }
2844
- });
2845
2804
  }
2846
- // Process base styles
2805
+ // Process base computed styles
2847
2806
  classes.push(...processStyles(computedStyles, 'base', '', getColor, {}, {}, manager));
2848
- // Collect underscore-prefixed properties (_hover, _focus, etc.)
2849
- const underscoreProps = {};
2850
- Object.keys(props).forEach(property => {
2851
- if (property.startsWith('_') && property.length > 1) {
2807
+ // SINGLE PASS over props: classify each prop into style, event, or underscore
2808
+ const propKeys = Object.keys(props);
2809
+ for (let i = 0; i < propKeys.length; i++) {
2810
+ const property = propKeys[i];
2811
+ const value = props[property];
2812
+ // Handle underscore-prefixed event properties (_hover, _focus, etc.)
2813
+ if (property.charCodeAt(0) === 95 && property.length > 1) {
2814
+ // 95 = '_'
2852
2815
  const eventName = property.substring(1);
2853
- underscoreProps[eventName] = props[property];
2816
+ classes.push(...processEventStyles(eventName, value, getColor, manager));
2817
+ // Handle blend for underscore props
2818
+ if (props.blend === true && value?.color === undefined) {
2819
+ if (props.bgColor) {
2820
+ value.mixBlendMode = 'overlay';
2821
+ value.color = 'white';
2822
+ } else {
2823
+ value.mixBlendMode = 'difference';
2824
+ value.color = 'white';
2825
+ }
2826
+ }
2827
+ continue;
2854
2828
  }
2855
- });
2856
- // Process standard style props
2857
- Object.keys(props).forEach(property => {
2858
- if (property !== 'style' && property !== 'css' && !property.startsWith('_') && (isStyleProp(property) || ['on', 'media'].includes(property))) {
2859
- const value = props[property];
2829
+ // Skip non-style props
2830
+ if (property === 'style' || property === 'css') continue;
2831
+ if (property === 'on') {
2832
+ // Process event-based styles
2860
2833
  if (typeof value === 'object' && value !== null) {
2861
- if (property === 'on') {
2862
- // Process event-based styles
2863
- Object.keys(value).forEach(event => {
2864
- classes.push(...processEventStyles(event, value[event], getColor, manager));
2865
- });
2866
- } else if (property === 'media') {
2867
- // Process media query styles
2868
- Object.keys(value).forEach(screenOrDevice => {
2869
- classes.push(...processStyles(value[screenOrDevice], 'media', screenOrDevice, getColor, mediaQueries, devices, manager));
2870
- });
2834
+ const events = Object.keys(value);
2835
+ for (let j = 0; j < events.length; j++) {
2836
+ classes.push(...processEventStyles(events[j], value[events[j]], getColor, manager));
2837
+ }
2838
+ // Handle blend for 'on' prop
2839
+ if (props.blend === true && value?.color === undefined) {
2840
+ if (props.bgColor) {
2841
+ value.mixBlendMode = 'overlay';
2842
+ value.color = 'white';
2843
+ } else {
2844
+ value.mixBlendMode = 'difference';
2845
+ value.color = 'white';
2846
+ }
2871
2847
  }
2872
- } else if (value !== undefined && value !== '') {
2873
- // Direct style property
2874
- classes.push(...(manager || utilityClassManager).getClassNames(property, value, 'base', '', getColor, []));
2875
2848
  }
2849
+ continue;
2876
2850
  }
2877
- });
2851
+ if (property === 'media') {
2852
+ // Process media query styles
2853
+ if (typeof value === 'object' && value !== null) {
2854
+ const screens = Object.keys(value);
2855
+ for (let j = 0; j < screens.length; j++) {
2856
+ classes.push(...processStyles(value[screens[j]], 'media', screens[j], getColor, mediaQueries, devices, manager));
2857
+ }
2858
+ // Handle blend for 'media' prop
2859
+ if (props.blend === true && value?.color === undefined) {
2860
+ if (props.bgColor) {
2861
+ value.mixBlendMode = 'overlay';
2862
+ value.color = 'white';
2863
+ } else {
2864
+ value.mixBlendMode = 'difference';
2865
+ value.color = 'white';
2866
+ }
2867
+ }
2868
+ }
2869
+ continue;
2870
+ }
2871
+ // Standard style props
2872
+ if (isStyleProp(property)) {
2873
+ if (value !== undefined && value !== '') {
2874
+ if (typeof value === 'object' && value !== null) {
2875
+ // Object-style props are not directly processed as base styles
2876
+ continue;
2877
+ }
2878
+ classes.push(...activeManager.getClassNames(property, value, 'base', '', getColor, []));
2879
+ }
2880
+ }
2881
+ }
2878
2882
  // Handle raw CSS - uses 'override' context for higher specificity
2879
2883
  if (props.css) {
2880
2884
  if (typeof props.css === 'object') {
2881
- // Object-style CSS gets processed with override context for higher priority
2882
- Object.assign(computedStyles, props.css);
2883
2885
  classes.push(...processStyles(props.css, 'override', '', getColor, {}, {}, manager));
2884
2886
  } else if (typeof props.css === 'string') {
2885
- // String-style CSS gets its own class in override context
2886
2887
  const uniqueClassName = ValueUtils.generateUniqueClassName(props.css);
2887
- (manager || utilityClassManager).injectRule(`.${uniqueClassName} { ${props.css} }`, 'override');
2888
+ activeManager.injectRule(`.${uniqueClassName} { ${props.css} }`, 'override');
2888
2889
  classes.push(uniqueClassName);
2889
2890
  }
2890
2891
  }
2891
- // Process underscore-prefixed event properties
2892
- if (Object.keys(underscoreProps).length > 0) {
2893
- Object.keys(underscoreProps).forEach(event => {
2894
- classes.push(...processEventStyles(event, underscoreProps[event], getColor, manager));
2895
- });
2896
- }
2897
2892
  return classes;
2898
2893
  };
2899
2894
 
@@ -2960,27 +2955,47 @@
2960
2955
  }, children);
2961
2956
  };
2962
2957
 
2958
+ // Set of special prop names that affect CSS generation
2959
+ const styleRelevantProps = /*#__PURE__*/new Set(['on', 'media', 'animate', 'css', 'shadow', 'blend', 'widthHeight', 'paddingHorizontal', 'paddingVertical', 'marginHorizontal', 'marginVertical']);
2960
+ // Skip these props from hash computation
2961
+ const skipHashProps = /*#__PURE__*/new Set(['children', 'ref', 'key', 'style']);
2962
+ /**
2963
+ * Fast serialization of a value for hashing purposes.
2964
+ * Avoids JSON.stringify overhead for common cases (strings, numbers, booleans).
2965
+ */
2966
+ function fastSerialize(value) {
2967
+ if (value === null) return 'n';
2968
+ const t = typeof value;
2969
+ if (t === 'string') return `s${value}`;
2970
+ if (t === 'number') return `d${value}`;
2971
+ if (t === 'boolean') return value ? 'T' : 'F';
2972
+ // Fall back to JSON.stringify only for complex objects
2973
+ return JSON.stringify(value);
2974
+ }
2963
2975
  /**
2964
2976
  * Computes a stable hash of style-relevant props.
2965
- * This is used to determine if style extraction needs to be re-run.
2977
+ * Optimized: avoids sorting, uses fast serialization, feeds directly to hash.
2966
2978
  */
2967
2979
  function hashStyleProps(props) {
2968
2980
  // Build a deterministic string representation of style-relevant props
2969
- const parts = [];
2970
- const sortedKeys = Object.keys(props).sort();
2971
- for (const key of sortedKeys) {
2981
+ // We use a single string accumulator instead of array + join
2982
+ let hashInput = '';
2983
+ const keys = Object.keys(props);
2984
+ for (let i = 0; i < keys.length; i++) {
2985
+ const key = keys[i];
2972
2986
  // Skip non-style props that don't affect CSS generation
2973
- if (key === 'children' || key === 'ref' || key === 'key') continue;
2987
+ if (skipHashProps.has(key)) continue;
2974
2988
  // Include style-relevant props
2975
- 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') {
2989
+ if (isStyleProp(key) || key.charCodeAt(0) === 95 ||
2990
+ // starts with '_'
2991
+ styleRelevantProps.has(key)) {
2976
2992
  const value = props[key];
2977
2993
  if (value !== undefined) {
2978
- // Use JSON.stringify for consistent serialization
2979
- parts.push(`${key}:${JSON.stringify(value)}`);
2994
+ hashInput += `|${key}:${fastSerialize(value)}`;
2980
2995
  }
2981
2996
  }
2982
2997
  }
2983
- return hash(parts.join('|'));
2998
+ return hash(hashInput);
2984
2999
  }
2985
3000
  /**
2986
3001
  * Custom hook that memoizes style extraction based on a stable hash of props.
@@ -2990,8 +3005,9 @@
2990
3005
  const cacheRef = React.useRef(null);
2991
3006
  // Compute hash directly — no useMemo since propsToProcess is always a new
2992
3007
  // reference (from destructuring), so the memo deps would always change.
2993
- const themeHash = theme ? JSON.stringify(theme) : '';
2994
- const currentHash = hashStyleProps(propsToProcess) + '|' + hash(themeHash);
3008
+ // Theme hash uses Object.values() concatenation instead of JSON.stringify
3009
+ const themeHash = theme ? hash(Object.values(theme).join('|')) : '';
3010
+ const currentHash = hashStyleProps(propsToProcess) + '|' + themeHash;
2995
3011
  // Only recompute classes if hash changed
2996
3012
  if (!cacheRef.current || cacheRef.current.hash !== currentHash) {
2997
3013
  const classes = extractUtilityClasses(propsToProcess, getColor, mediaQueries, devices, manager);
@@ -3168,18 +3184,24 @@
3168
3184
  after,
3169
3185
  ...otherProps
3170
3186
  } = rest;
3171
- // First, add all event handlers (they start with "on" and have a capital letter after)
3172
- Object.keys(otherProps).forEach(key => {
3173
- if (key.startsWith('on') && key.length > 2 && key[2] === key[2].toUpperCase()) {
3187
+ // Single pass: add event handlers and non-style props together
3188
+ const otherKeys = Object.keys(otherProps);
3189
+ for (let i = 0; i < otherKeys.length; i++) {
3190
+ const key = otherKeys[i];
3191
+ // Event handlers: start with "on" + uppercase letter
3192
+ if (key.charCodeAt(0) === 111 &&
3193
+ // 'o'
3194
+ key.charCodeAt(1) === 110 &&
3195
+ // 'n'
3196
+ key.length > 2 && key.charCodeAt(2) >= 65 && key.charCodeAt(2) <= 90 // uppercase A-Z
3197
+ ) {
3174
3198
  newProps[key] = otherProps[key];
3175
3199
  }
3176
- });
3177
- // Then add all other non-style props
3178
- Object.keys(otherProps).forEach(key => {
3179
- if (!excludedKeys.has(key) && !isStyleProp(key) || includeKeys.has(key)) {
3200
+ // Non-style props (pass through to DOM)
3201
+ else if (!excludedKeys.has(key) && !isStyleProp(key) || includeKeys.has(key)) {
3180
3202
  newProps[key] = otherProps[key];
3181
3203
  }
3182
- });
3204
+ }
3183
3205
  if (style) {
3184
3206
  newProps.style = style;
3185
3207
  }
@@ -3266,11 +3288,17 @@
3266
3288
  ref: ref
3267
3289
  }))));
3268
3290
 
3269
- const Image = /*#__PURE__*/React__default.forwardRef((props, ref) => (/*#__PURE__*/React__default.createElement(Element, Object.assign({
3270
- as: "img"
3271
- }, props, {
3272
- ref: ref
3273
- }))));
3291
+ const Image = /*#__PURE__*/React__default.forwardRef((props, ref) => {
3292
+ const imageProps = {
3293
+ ...props,
3294
+ alt: props.alt || ''
3295
+ };
3296
+ return /*#__PURE__*/React__default.createElement(Element, Object.assign({
3297
+ as: "img"
3298
+ }, imageProps, {
3299
+ ref: ref
3300
+ }));
3301
+ });
3274
3302
  const ImageBackground = /*#__PURE__*/React__default.forwardRef((_ref, ref) => {
3275
3303
  let {
3276
3304
  src,
@@ -3369,11 +3397,18 @@
3369
3397
  }, props, {
3370
3398
  ref: ref
3371
3399
  }))));
3372
- const Button = /*#__PURE__*/React__default.forwardRef((props, ref) => (/*#__PURE__*/React__default.createElement(Element, Object.assign({
3373
- as: "button"
3374
- }, props, {
3375
- ref: ref
3376
- }))));
3400
+ const Button = /*#__PURE__*/React__default.forwardRef((props, ref) => {
3401
+ {
3402
+ if (!props.children && !props['aria-label']) {
3403
+ console.warn('Accessibility Warning: Button is missing an accessible name. If it is an icon-only button, please provide an `aria-label`.');
3404
+ }
3405
+ }
3406
+ return /*#__PURE__*/React__default.createElement(Element, Object.assign({
3407
+ as: "button"
3408
+ }, props, {
3409
+ ref: ref
3410
+ }));
3411
+ });
3377
3412
 
3378
3413
  // animations.ts
3379
3414
  const fadeIn = function (_temp) {