@stylix/core 6.1.1 → 6.3.0

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.
package/dist/index.js CHANGED
@@ -1,13 +1,69 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
- import React, { createContext, useLayoutEffect, useContext, useRef, useEffect } from 'react';
2
+ import React, { createContext, useRef, useInsertionEffect, useContext, useEffect } from 'react';
3
3
 
4
- function classifyProps(props, knownProps) {
4
+ function flattenRules(ctx) {
5
+ return Object.values(ctx.rules)
6
+ .flatMap((val) => (val && val.refs > 0 ? val.rules : []))
7
+ .filter(Boolean);
8
+ }
9
+ /**
10
+ * Applies rules from given StylixContext to the <style> element.
11
+ */
12
+ function applyRules(ctx) {
13
+ if (ctx.styleCollector) {
14
+ const flattenedRules = flattenRules(ctx);
15
+ ctx.styleCollector.length = 0;
16
+ ctx.styleCollector.push(...flattenedRules);
17
+ return;
18
+ }
19
+ if (ctx.ssr)
20
+ return;
21
+ const supportsAdoptedStylesheets = 'adoptedStyleSheets' in document;
22
+ // If there's no style element, and we're in dev mode, create one
23
+ if (!ctx.styleElement && (ctx.devMode || !supportsAdoptedStylesheets)) {
24
+ ctx.styleElement = document.createElement('style');
25
+ ctx.styleElement.className = 'stylix';
26
+ if (ctx.id)
27
+ ctx.styleElement.id = `stylix-${ctx.id}`;
28
+ document.head.appendChild(ctx.styleElement);
29
+ }
30
+ if (ctx.styleElement) {
31
+ // If there's a style element, use it
32
+ const flattenedRules = flattenRules(ctx);
33
+ ctx.styleElement.innerHTML = flattenedRules.join('\n');
34
+ }
35
+ else {
36
+ // Still no stylesheet yet, create one
37
+ if (!ctx.stylesheet) {
38
+ ctx.stylesheet = new CSSStyleSheet();
39
+ if (supportsAdoptedStylesheets) {
40
+ document.adoptedStyleSheets.push(ctx.stylesheet);
41
+ }
42
+ else if (ctx.stylesheet.ownerNode) {
43
+ document.head.appendChild(ctx.stylesheet.ownerNode);
44
+ }
45
+ }
46
+ const stylesheet = ctx.stylesheet;
47
+ const flattenedRules = flattenRules(ctx);
48
+ if (stylesheet.replaceSync) {
49
+ try {
50
+ stylesheet.replaceSync(flattenedRules.join('\n'));
51
+ }
52
+ catch (e) {
53
+ // Errors are ignored, this just means that a browser doesn't support a certain CSS feature.
54
+ console.warn(e);
55
+ }
56
+ }
57
+ }
58
+ }
59
+
60
+ function classifyProps(props, knownStyleProps) {
5
61
  const styles = {};
6
62
  const other = {};
7
63
  for (const prop in props) {
8
64
  // If prop is not a valid JSX prop, it must be a CSS selector.
9
65
  // If prop has a style prop name and the value is likely a style value, it's a style prop.
10
- if (!isValidJSXProp(prop) || isStyleProp(prop, knownProps)) {
66
+ if (!isValidJSXProp(prop) || isStyleProp(prop, knownStyleProps)) {
11
67
  styles[prop] = props[prop];
12
68
  }
13
69
  else {
@@ -20,10 +76,10 @@ function classifyProps(props, knownProps) {
20
76
  * Determines if `value` is a recognized CSS property (can be standard CSS or custom Stylix prop).
21
77
  * If it is, the simplified prop name is returned. Otherwise, false is returned.
22
78
  */
23
- function isStyleProp(prop, knownProps) {
79
+ function isStyleProp(prop, knownStyleProps) {
24
80
  if (isValidJSXProp(prop)) {
25
81
  const simplified = simplifyStylePropName(prop);
26
- return simplified in knownProps ? simplified : false;
82
+ return simplified in knownStyleProps ? simplified : false;
27
83
  }
28
84
  return false;
29
85
  }
@@ -572,37 +628,23 @@ const cleanStyles = {
572
628
  * // }
573
629
  * ```
574
630
  */
575
- function mapObject(source, map, context = {}) {
631
+ function mapObject(source, mapFn, context) {
576
632
  if (typeof source !== 'object')
577
633
  return source;
578
- const result = Array.isArray(source) ? [] : {};
634
+ const target = (Array.isArray(source) ? [] : {});
579
635
  for (const _key in source) {
580
636
  const key = Array.isArray(source) ? +_key : _key;
581
637
  const value = source[key];
582
638
  const contextClone = { ...context };
583
- const mapResult = map(key, value, source, contextClone, (value) => mapObject(value, map, contextClone));
584
- if (typeof mapResult === 'undefined')
585
- continue;
586
- if (Array.isArray(mapResult) && Array.isArray(source)) {
587
- result.push(...mapResult);
588
- continue;
589
- }
590
- if (typeof mapResult === 'object' &&
591
- !Array.isArray(mapResult) &&
592
- typeof source === 'object' &&
593
- !Array.isArray(source)) {
594
- Object.assign(result, mapResult);
595
- continue;
596
- }
597
- throw new Error(`mapObjectRecursive: return value of map function must be an object, array, or undefined, and must match the type of the source value. Type of source value was ${typeof source}, and type of returned value was ${typeof mapResult}.`);
639
+ mapFn(key, value, target, contextClone, (value) => mapObject(value, mapFn, contextClone));
598
640
  }
599
- return result;
641
+ return target;
600
642
  }
601
643
 
602
644
  const defaultIgnoreUnits = [
603
645
  'aspect-ratio',
604
- 'columns',
605
646
  'column-count',
647
+ 'columns',
606
648
  'fill-opacity',
607
649
  'flex',
608
650
  'flex-grow',
@@ -610,15 +652,15 @@ const defaultIgnoreUnits = [
610
652
  'font-weight',
611
653
  'line-height',
612
654
  'opacity',
655
+ 'order',
613
656
  'orphans',
614
657
  'stroke-opacity',
615
658
  'widows',
616
659
  'z-index',
617
660
  'zoom',
618
- 'order',
619
661
  ];
620
662
  /**
621
- * Adds unit (px, em, etc) to numeric values for any style properties not included in `ignoreProps`..
663
+ * Adds unit (px, em, etc) to numeric values for any style properties not included in `ignoreProps`.
622
664
  */
623
665
  const defaultUnits = (unit = 'px', ignoreProps = defaultIgnoreUnits) => {
624
666
  return {
@@ -629,11 +671,12 @@ const defaultUnits = (unit = 'px', ignoreProps = defaultIgnoreUnits) => {
629
671
  },
630
672
  };
631
673
  };
632
- const defaultUnitsMap = (key, value, _object, ctx, mapRecursive) => {
674
+ const defaultUnitsMap = (key, value, target, ctx, mapRecursive) => {
633
675
  if (typeof value === 'number' && !ctx.ignoreProps.includes(key)) {
634
- return { [key]: String(value) + ctx.unit };
676
+ target[key] = String(value) + ctx.unit;
677
+ return;
635
678
  }
636
- return { [key]: mapRecursive(value) };
679
+ target[key] = mapRecursive(value);
637
680
  };
638
681
  const defaultPixelUnits = defaultUnits();
639
682
 
@@ -664,6 +707,36 @@ const hoistKeyframes = {
664
707
  },
665
708
  };
666
709
 
710
+ function _hoistLayers(styles, root) {
711
+ for (const key in styles) {
712
+ const value = styles[key];
713
+ if (typeof value === 'string' && key.startsWith('@layer')) {
714
+ // Add layer rules as-is directly to root object
715
+ root['@layer'] ||= [];
716
+ root['@layer'].push(value.replace('@layer', '').trim());
717
+ if (styles !== root)
718
+ delete styles[key];
719
+ }
720
+ else if (isPlainObject(value)) {
721
+ // Recursively flatten nested styles
722
+ _hoistLayers(value, root);
723
+ }
724
+ }
725
+ return styles;
726
+ }
727
+ /**
728
+ * Hoists @layer declarations to root of styles object.
729
+ */
730
+ const hoistLayers = {
731
+ name: 'hoistLayers',
732
+ type: 'processStyles',
733
+ plugin(_ctx, styles) {
734
+ if (styles && typeof styles === 'object' && !Array.isArray(styles))
735
+ styles['@layer'] = [];
736
+ return _hoistLayers(styles, styles);
737
+ },
738
+ };
739
+
667
740
  /**
668
741
  * Expands media objects using the media definitions from the Stylix context.
669
742
  */
@@ -697,10 +770,21 @@ function processMediaStyles(mediaDef, styleProps, styles) {
697
770
  // An object for a style prop is definitely a media object
698
771
  for (const mediaKey in styleValue) {
699
772
  result[mediaKey] ||= [];
700
- result[mediaKey].push(mediaDef[mediaKey]({
701
- // process recursively
702
- [styleKey]: processMediaStyles(mediaDef, styleProps, styleValue[mediaKey]),
703
- }));
773
+ // mediaKey corresponds to a media definition
774
+ if (mediaKey in mediaDef) {
775
+ result[mediaKey].push(mediaDef[mediaKey]({
776
+ // process recursively
777
+ [styleKey]: processMediaStyles(mediaDef, styleProps, styleValue[mediaKey]),
778
+ }));
779
+ }
780
+ // mediaKey does not correspond to a media definition, it must be a @media or @container rule
781
+ else {
782
+ result[mediaKey].push({
783
+ [mediaKey]: {
784
+ [styleKey]: processMediaStyles(mediaDef, styleProps, styleValue[mediaKey]),
785
+ },
786
+ });
787
+ }
704
788
  }
705
789
  continue;
706
790
  }
@@ -724,76 +808,42 @@ function processMediaStyles(mediaDef, styleProps, styles) {
724
808
  const mergeArrays = {
725
809
  name: 'mergeArrays',
726
810
  type: 'processStyles',
727
- plugin: (_ctx, styles) => _mergeArrays(styles),
811
+ plugin: (_ctx, styles) => reduceArrays(styles),
728
812
  };
729
- function _mergeArrays(obj) {
730
- if (Array.isArray(obj))
731
- return reduceArray(obj);
732
- return reduceObjectProperties(obj);
813
+ function reduceArrays(obj) {
814
+ return _reduceArrays(obj);
733
815
  }
734
- function reduceArray(arr) {
735
- arr = arr.flat();
736
- let target = arr[0];
737
- if (Array.isArray(target)) {
738
- target = reduceArray(target);
739
- }
740
- for (let i = 1; i < arr.length; i++) {
741
- let source = arr[i];
742
- if (Array.isArray(source)) {
743
- source = reduceArray(source);
744
- }
745
- // ignore falsy values
746
- if (typeof source === 'undefined')
747
- continue;
748
- // if both values are primitives, the source value takes precedence
749
- if (typeof target !== 'object' && typeof source !== 'object') {
750
- target = source;
751
- continue;
752
- }
753
- // if target is primitive but source is object, replace target with source
754
- if (typeof target !== 'object') {
755
- target = source;
756
- continue;
816
+ function _reduceArrays(obj, target = {}) {
817
+ if (!obj || typeof obj !== 'object')
818
+ return obj;
819
+ if (Array.isArray(obj)) {
820
+ for (const item of obj) {
821
+ if (!item || typeof item !== 'object')
822
+ continue;
823
+ _reduceArrays(item, target);
757
824
  }
758
- for (const key in source) {
759
- const value = source[key];
760
- // if the key does not exist in target, just add it
761
- if (!(key in target))
762
- target[key] = value;
763
- // else, if the target value is an object or array:
764
- else if (typeof target[key] === 'object') {
765
- // if the source value is an object or array, convert target to array if necessary and push source value
766
- if (typeof value === 'object') {
767
- if (!Array.isArray(target[key]))
768
- target[key] = [target[key]];
769
- target[key].push(value);
770
- }
771
- // else, ignore the source value (it's primitive; object values take precedence)
825
+ return target;
826
+ }
827
+ for (const key in obj) {
828
+ const value = obj[key];
829
+ // If target[key] is an object
830
+ if (target[key] && typeof target[key] === 'object') {
831
+ // If value is an object, merge them
832
+ if (value && typeof value === 'object') {
833
+ _reduceArrays(value, target[key]);
772
834
  }
773
- // else, target value is primitive, overwrite target value
774
- else {
835
+ // If value is not undefined, replace target[key]
836
+ else if (value !== undefined) {
775
837
  target[key] = value;
776
838
  }
839
+ // otherwise do nothing, keep target[key] as is
840
+ }
841
+ // If target[key] is not an object, process normally
842
+ else {
843
+ target[key] = _reduceArrays(value, {});
777
844
  }
778
845
  }
779
- return reduceObjectProperties(target);
780
- }
781
- const _reduced = Symbol('reduced');
782
- function reduceObjectProperties(obj) {
783
- if (!obj || isEmpty(obj))
784
- return undefined;
785
- if (typeof obj !== 'object')
786
- return obj;
787
- if (obj?.[_reduced]) {
788
- return obj;
789
- }
790
- for (const k in obj) {
791
- if (!obj[k] || typeof obj[k] !== 'object')
792
- continue;
793
- obj[k] = _mergeArrays(obj[k]);
794
- }
795
- Object.defineProperty(obj, _reduced, { value: true, enumerable: false });
796
- return obj;
846
+ return target;
797
847
  }
798
848
 
799
849
  /**
@@ -823,14 +873,17 @@ const propCasing = {
823
873
  return mapObject(styles, propCasingMap, { ctx });
824
874
  },
825
875
  };
826
- const propCasingMap = (key, value, _object, context, mapRecursive) => {
827
- if (typeof key !== 'string' || key === '&')
828
- return { [key]: mapRecursive(value) };
876
+ const propCasingMap = (key, value, target, context, mapRecursive) => {
877
+ if (typeof key !== 'string' || key === '&') {
878
+ target[key] = mapRecursive(value);
879
+ return;
880
+ }
829
881
  const simpleKey = isStyleProp(key, context.ctx.styleProps);
830
882
  if (simpleKey) {
831
- return { [context.ctx.styleProps[simpleKey]]: mapRecursive(value) };
883
+ target[context.ctx.styleProps[simpleKey]] = mapRecursive(value);
884
+ return;
832
885
  }
833
- return { [key]: mapRecursive(value) };
886
+ target[key] = mapRecursive(value);
834
887
  };
835
888
 
836
889
  /**
@@ -843,35 +896,45 @@ const replace$$class = {
843
896
  return mapObject(styles, replace$$classMap, { ctx });
844
897
  },
845
898
  };
846
- const replace$$classMap = (key, value, _object, context, mapRecursive) => {
899
+ const replace$$classMap = (key, value, target, context, mapRecursive) => {
847
900
  value =
848
901
  typeof value === 'string'
849
902
  ? value.replaceAll('$$class', context.ctx.className || '')
850
903
  : mapRecursive(value);
851
904
  key = typeof key === 'string' ? key.replaceAll('$$class', context.ctx.className || '') : key;
852
- return { [key]: value };
905
+ target[key] = value;
853
906
  };
854
907
 
855
908
  function _customPropsProcess(styles, customProps) {
856
- return mapObject(styles, (key, value, source, _ctx, mapRecursive) => {
857
- if (!isValidJSXProp(key) || isPlainObject(value))
858
- return Array.isArray(source) ? [mapRecursive(value)] : { [key]: mapRecursive(value) };
909
+ if (typeof styles !== 'object' || styles === null)
910
+ return styles;
911
+ return mapObject(styles, (key, value, target, _ctx, mapRecursive) => {
912
+ if (!isValidJSXProp(key) || isPlainObject(value)) {
913
+ target[key] = mapRecursive(value);
914
+ return;
915
+ }
859
916
  const simpleKey = simplifyStylePropName(key);
860
917
  const propValue = customProps[simpleKey];
861
- if (!propValue)
862
- return { [key]: mapRecursive(value) };
863
- if (typeof propValue === 'object') {
864
- if (value)
865
- return mapRecursive(propValue);
866
- return undefined;
918
+ if (propValue && typeof propValue === 'object') {
919
+ // For object, merge the mapped value into target if original prop value is truthy
920
+ if (value) {
921
+ const mappedValue = mapRecursive(propValue);
922
+ Object.assign(target, mappedValue);
923
+ }
867
924
  }
868
- if (typeof propValue === 'string') {
869
- return { [propValue]: mapRecursive(value) };
925
+ else if (typeof propValue === 'string') {
926
+ // For string, just remap the prop name
927
+ target[propValue] = mapRecursive(value);
870
928
  }
871
- if (typeof propValue === 'function') {
872
- return mapRecursive(propValue(value));
929
+ else if (typeof propValue === 'function') {
930
+ // For function, call it with the original value and merge the result
931
+ const mappedValue = mapRecursive(propValue(value));
932
+ Object.assign(target, mappedValue);
933
+ }
934
+ else {
935
+ // Unknown type, just keep original
936
+ target[key] = mapRecursive(value);
873
937
  }
874
- return { [key]: mapRecursive(value) };
875
938
  });
876
939
  }
877
940
  const customProps = (customProps) => {
@@ -891,7 +954,7 @@ const customProps = (customProps) => {
891
954
  {
892
955
  name: 'customPropsProcess',
893
956
  type: 'processStyles',
894
- before: mergeArrays,
957
+ before: 'mergeArrays',
895
958
  plugin(_ctx, styles) {
896
959
  return _customPropsProcess(styles, customProps);
897
960
  },
@@ -923,6 +986,7 @@ const defaultPlugins = [
923
986
  mergeArrays,
924
987
  propCasing,
925
988
  hoistKeyframes,
989
+ hoistLayers,
926
990
  replace$$class,
927
991
  defaultPixelUnits,
928
992
  cleanStyles,
@@ -945,149 +1009,6 @@ function createStyleCollector() {
945
1009
  return collector;
946
1010
  }
947
1011
 
948
- const detectSSR = () => !(typeof window !== 'undefined' && window.document?.head?.appendChild);
949
- function useIsoLayoutEffect(fn, deps, runOnSsr, isSsr = detectSSR()) {
950
- if (isSsr) {
951
- if (runOnSsr)
952
- return fn();
953
- }
954
- else {
955
- // biome-ignore lint/correctness/useHookAtTopLevel: isSsr should never change
956
- // biome-ignore lint/correctness/useExhaustiveDependencies: dependencies are passed as-is
957
- useLayoutEffect(fn, deps);
958
- }
959
- }
960
-
961
- let defaultStyleProps;
962
- function createStylixContext(userValues = {}) {
963
- if (!defaultStyleProps) {
964
- defaultStyleProps = {};
965
- for (const value of cssProps) {
966
- defaultStyleProps[simplifyStylePropName(value)] = value;
967
- }
968
- }
969
- const ctx = {
970
- id: userValues.id || '$default',
971
- devMode: !!userValues.devMode,
972
- styleProps: defaultStyleProps,
973
- media: userValues.media,
974
- styleElement: userValues.styleElement,
975
- plugins: defaultPlugins.flat(),
976
- styleCounter: 0,
977
- rules: {},
978
- ssr: userValues.ssr ?? detectSSR(),
979
- cleanupRequest: undefined,
980
- requestApply: false,
981
- classifyProps(props) {
982
- const [styles, other] = classifyProps(props, this.styleProps);
983
- return [styles, other];
984
- },
985
- };
986
- if (userValues.plugins?.length) {
987
- const flatPlugins = userValues.plugins.flat();
988
- for (const i in flatPlugins) {
989
- const plugin = flatPlugins[i];
990
- let pluginIndex = -1;
991
- if (plugin.before && ctx.plugins.includes(plugin.before))
992
- pluginIndex = ctx.plugins.indexOf(plugin.before);
993
- else if (plugin.after && ctx.plugins.includes(plugin.after))
994
- pluginIndex = ctx.plugins.indexOf(plugin.after) + 1;
995
- else if (plugin.atIndex !== undefined)
996
- pluginIndex = plugin.atIndex;
997
- if (pluginIndex === -1)
998
- ctx.plugins.push(plugin);
999
- else
1000
- ctx.plugins.splice(pluginIndex, 0, plugin);
1001
- }
1002
- }
1003
- applyPlugins('initialize', null, null, ctx);
1004
- return ctx;
1005
- }
1006
- // The React context object
1007
- const stylixContext = createContext(undefined);
1008
- let defaultStylixContext;
1009
- /**
1010
- * Gets the current Stylix context.
1011
- */
1012
- function useStylixContext() {
1013
- const ctx = useContext(stylixContext);
1014
- if (!ctx) {
1015
- if (!defaultStylixContext)
1016
- defaultStylixContext = createStylixContext();
1017
- return defaultStylixContext;
1018
- }
1019
- return ctx;
1020
- }
1021
- function StylixProvider({ id, devMode, plugins, media, styleElement, children, ssr, }) {
1022
- const ctx = useRef(null);
1023
- if (!ctx.current)
1024
- ctx.current = createStylixContext({ id, devMode, plugins, media, styleElement, ssr });
1025
- ctx.current.styleCollector = useContext(styleCollectorContext);
1026
- // When the component is unmounted, remove the style element, if any
1027
- useEffect(() => {
1028
- return () => {
1029
- ctx.current?.styleElement?.remove();
1030
- };
1031
- }, []);
1032
- return jsx(stylixContext.Provider, { value: ctx.current, children: children });
1033
- }
1034
-
1035
- function flattenRules(ctx) {
1036
- return Object.values(ctx.rules)
1037
- .flatMap((val) => (val && val.refs > 0 ? val.rules : []))
1038
- .filter(Boolean);
1039
- }
1040
- /**
1041
- * Applies rules from given StylixContext to the <style> element.
1042
- */
1043
- function applyRules(ctx) {
1044
- if (ctx.styleCollector) {
1045
- const flattenedRules = flattenRules(ctx);
1046
- ctx.styleCollector.length = 0;
1047
- ctx.styleCollector.push(...flattenedRules);
1048
- return;
1049
- }
1050
- if (ctx.ssr)
1051
- return;
1052
- const supportsAdoptedStylesheets = 'adoptedStyleSheets' in document;
1053
- // If there's no style element, and we're in dev mode, create one
1054
- if (!ctx.styleElement && (ctx.devMode || !supportsAdoptedStylesheets)) {
1055
- ctx.styleElement = document.createElement('style');
1056
- ctx.styleElement.className = 'stylix';
1057
- if (ctx.id)
1058
- ctx.styleElement.id = `stylix-${ctx.id}`;
1059
- document.head.appendChild(ctx.styleElement);
1060
- }
1061
- if (ctx.styleElement) {
1062
- // If there's a style element, use it
1063
- const flattenedRules = flattenRules(ctx);
1064
- ctx.styleElement.innerHTML = flattenedRules.join('\n');
1065
- }
1066
- else {
1067
- // Still no stylesheet yet, create one
1068
- if (!ctx.stylesheet) {
1069
- ctx.stylesheet = new CSSStyleSheet();
1070
- if (supportsAdoptedStylesheets) {
1071
- document.adoptedStyleSheets.push(ctx.stylesheet);
1072
- }
1073
- else if (ctx.stylesheet.ownerNode) {
1074
- document.head.appendChild(ctx.stylesheet.ownerNode);
1075
- }
1076
- }
1077
- const stylesheet = ctx.stylesheet;
1078
- const flattenedRules = flattenRules(ctx);
1079
- if (stylesheet.replaceSync) {
1080
- try {
1081
- stylesheet.replaceSync(flattenedRules.join('\n'));
1082
- }
1083
- catch (e) {
1084
- // Errors are ignored, this just means that a browser doesn't support a certain CSS feature.
1085
- console.warn(e);
1086
- }
1087
- }
1088
- }
1089
- }
1090
-
1091
1012
  function getParentComponentName() {
1092
1013
  const internals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
1093
1014
  const stack = internals?.ReactDebugCurrentFrame?.getStackAddendum?.()?.split('\n') || [];
@@ -1104,6 +1025,9 @@ function getParentComponentName() {
1104
1025
  * Serialize selector and styles to css rule string
1105
1026
  */
1106
1027
  function serialize(selector, styles) {
1028
+ if (selector.startsWith('@') && Array.isArray(styles)) {
1029
+ return `${selector} ${styles.join(', ')};`;
1030
+ }
1107
1031
  const lines = [];
1108
1032
  for (const key in styles) {
1109
1033
  const value = styles[key];
@@ -1123,6 +1047,11 @@ function stylesToRuleArray(styles, className, context) {
1123
1047
  try {
1124
1048
  const processedStyles = applyPlugins('processStyles', styles, className, context);
1125
1049
  const result = [];
1050
+ // Handle @layer rules first
1051
+ if (processedStyles['@layer']) {
1052
+ result.push(serialize('@layer', processedStyles['@layer']));
1053
+ delete processedStyles['@layer'];
1054
+ }
1126
1055
  for (const key in processedStyles) {
1127
1056
  const value = processedStyles[key];
1128
1057
  result.push(serialize(key, value));
@@ -1159,68 +1088,80 @@ function cleanup(ctx) {
1159
1088
  ctx.cleanupRequest = setTimeout(doCleanup, 100);
1160
1089
  }
1161
1090
  }
1162
- /**
1163
- * Accepts a Stylix CSS object and returns a unique className.
1164
- * The styles are registered with the Stylix context and will be applied to the document.
1165
- * If `global` is false, provided styles will be nested within the generated classname.
1166
- * Returns the className if enabled, or an empty string.
1167
- */
1168
- function useStyles(styles, options = { global: false }) {
1169
- const stylixCtx = useStylixContext();
1170
- const prevStylesJson = useRef('');
1171
- // Preprocess styles with plugins
1172
- if (styles && !isEmpty(styles))
1091
+ function createStyles(config) {
1092
+ const { stylixCtx, global } = config;
1093
+ let styles = config.styles;
1094
+ const priorKey = config.key || '';
1095
+ let stylesKey = '';
1096
+ if (styles && !isEmpty(styles)) {
1097
+ // Preprocess styles with plugins
1173
1098
  styles = applyPlugins('preprocessStyles', styles, null, stylixCtx);
1174
- let stylesJson = styles && !isEmpty(styles) ? JSON.stringify(styles) : '';
1175
- if (stylesJson && options.global)
1176
- stylesJson = `global:${stylesJson}`;
1177
- const changed = stylesJson !== prevStylesJson.current;
1178
- prevStylesJson.current = stylesJson;
1179
- options.debugLabel ||= stylixCtx.devMode ? getParentComponentName() : '';
1180
- if (stylesJson && !stylixCtx.rules[stylesJson]) {
1099
+ // Generate styles key
1100
+ stylesKey = styles ? (global ? 'global:' : '') + JSON.stringify(styles) : '';
1101
+ }
1102
+ if (stylesKey && !stylixCtx.rules[stylesKey]) {
1181
1103
  stylixCtx.styleCounter++;
1182
- const className = `stylix-${(stylixCtx.styleCounter).toString(36)}${options.debugLabel ? `-${options.debugLabel}` : ''}`;
1104
+ const debugLabel = config.debugLabel || (stylixCtx.devMode && getParentComponentName()) || '';
1105
+ const className = `stylix-${(stylixCtx.styleCounter).toString(36)}${debugLabel ? `-${debugLabel}` : ''}`;
1183
1106
  // If not global styles, wrap original styles with classname
1184
- if (!options.global)
1107
+ if (!global)
1185
1108
  styles = { [`.${className}`]: styles };
1186
- stylixCtx.rules[stylesJson] = {
1109
+ stylixCtx.rules[stylesKey] = {
1187
1110
  className,
1188
1111
  rules: stylesToRuleArray(styles, className, stylixCtx),
1189
1112
  refs: 0,
1190
1113
  };
1191
1114
  }
1192
- if (changed)
1115
+ const isChanged = stylesKey !== priorKey;
1116
+ const ruleSet = stylesKey ? stylixCtx.rules[stylesKey] : null;
1117
+ if (isChanged) {
1118
+ // Mark styles to be applied
1193
1119
  stylixCtx.requestApply = true;
1194
- // When json changes, add/remove ref count
1195
- const ruleSet = stylixCtx.rules[stylesJson];
1196
- if (stylesJson && changed && ruleSet) {
1197
- ruleSet.refs++;
1120
+ // When json changes, add/remove ref count
1121
+ const priorRuleSet = priorKey ? stylixCtx.rules[priorKey] : null;
1122
+ if (priorRuleSet)
1123
+ priorRuleSet.refs--;
1124
+ if (ruleSet)
1125
+ ruleSet.refs++;
1198
1126
  }
1127
+ return {
1128
+ className: ruleSet?.className || '',
1129
+ key: stylesKey,
1130
+ };
1131
+ }
1132
+ /**
1133
+ * Accepts a Stylix CSS object and returns a unique className.
1134
+ * The styles are registered with the Stylix context and will be applied to the document.
1135
+ * If `global` is false, provided styles will be nested within the generated classname.
1136
+ * Returns the className if enabled, or an empty string.
1137
+ */
1138
+ function useStyles(styles, options = { global: false }) {
1139
+ const stylixCtx = useStylixContext();
1140
+ const prevStylesKey = useRef('');
1141
+ const s = createStyles({
1142
+ stylixCtx,
1143
+ styles,
1144
+ global: options.global,
1145
+ debugLabel: options.debugLabel,
1146
+ key: prevStylesKey.current,
1147
+ });
1148
+ prevStylesKey.current = s.key;
1199
1149
  // Apply styles if requested.
1200
- // This runs on every render. We utilize useLayoutEffect so that it runs *after* all the other
1150
+ // This runs on every render. We utilize useInsertionEffect so that it runs *after* all the other
1201
1151
  // renders have completed. stylixCtx.requestApply guards against multiple runs. This reduces the number of calls
1202
1152
  // to applyRules(), but prevents styles potentially being added to the DOM after other components force the
1203
1153
  // browser to compute styles.
1204
- useIsoLayoutEffect(() => {
1154
+ // biome-ignore lint/correctness/useExhaustiveDependencies: stylixCtx is stable
1155
+ useInsertionEffect(() => {
1205
1156
  if (!stylixCtx.requestApply)
1206
1157
  return;
1207
1158
  stylixCtx.requestApply = false;
1208
1159
  applyRules(stylixCtx);
1209
- }, undefined, true, stylixCtx.ssr);
1210
- useIsoLayoutEffect(() => {
1211
- if (!stylesJson || !changed)
1212
- return;
1213
1160
  return () => {
1214
- const ruleSet = stylixCtx.rules[stylesJson];
1215
- if (!ruleSet)
1216
- return;
1217
- ruleSet.refs--;
1218
- if (ruleSet.refs <= 0)
1219
- stylixCtx.rules[stylesJson] = undefined;
1220
1161
  cleanup(stylixCtx);
1221
1162
  };
1222
- }, [stylesJson], false, stylixCtx.ssr);
1223
- return stylixCtx.rules[stylesJson]?.className || '';
1163
+ }, [s.key]);
1164
+ return s.className;
1224
1165
  }
1225
1166
  function useKeyframes(keyframes) {
1226
1167
  return useStyles({ '@keyframes $$class': keyframes }, { global: true });
@@ -1229,6 +1170,115 @@ function useGlobalStyles(styles, options = { disabled: false }) {
1229
1170
  return useStyles(styles, { ...options, global: true });
1230
1171
  }
1231
1172
 
1173
+ const detectSSR = () => !(typeof window !== 'undefined' && window.document?.head?.appendChild);
1174
+
1175
+ /**
1176
+ * Default style props mapping. This will be populated on the first call to createStylixContext.
1177
+ */
1178
+ let defaultStyleProps;
1179
+ function createStylixContext(userValues = {}) {
1180
+ if (!defaultStyleProps) {
1181
+ defaultStyleProps = {};
1182
+ for (const value of cssProps) {
1183
+ defaultStyleProps[simplifyStylePropName(value)] = value;
1184
+ }
1185
+ }
1186
+ const ctx = {
1187
+ id: userValues.id || '$default',
1188
+ devMode: !!userValues.devMode,
1189
+ styleProps: defaultStyleProps,
1190
+ media: userValues.media,
1191
+ styleElement: userValues.styleElement,
1192
+ plugins: defaultPlugins.flat(),
1193
+ styleCounter: 0,
1194
+ rules: {},
1195
+ ssr: userValues.ssr ?? detectSSR(),
1196
+ cleanupRequest: undefined,
1197
+ requestApply: false,
1198
+ classifyProps(props) {
1199
+ const [styles, other] = classifyProps(props, this.styleProps);
1200
+ return [styles, other];
1201
+ },
1202
+ styles(styles, config) {
1203
+ const s = createStyles({
1204
+ stylixCtx: ctx,
1205
+ styles,
1206
+ global: config?.global || false,
1207
+ });
1208
+ applyRules(ctx);
1209
+ return s.className;
1210
+ },
1211
+ };
1212
+ if (userValues.plugins?.length) {
1213
+ const flatPlugins = userValues.plugins.flat();
1214
+ for (const i in flatPlugins) {
1215
+ const plugin = flatPlugins[i];
1216
+ let pluginIndex = -1;
1217
+ if (plugin.before)
1218
+ pluginIndex = ctx.plugins.findIndex((v) => v.name === plugin.before);
1219
+ else if (plugin.after) {
1220
+ pluginIndex = ctx.plugins.findIndex((v) => v.name === plugin.before);
1221
+ if (pluginIndex !== -1)
1222
+ pluginIndex += 1;
1223
+ }
1224
+ else if (plugin.atIndex !== undefined)
1225
+ pluginIndex = plugin.atIndex;
1226
+ if (pluginIndex === -1)
1227
+ ctx.plugins.push(plugin);
1228
+ else
1229
+ ctx.plugins.splice(pluginIndex, 0, plugin);
1230
+ }
1231
+ }
1232
+ applyPlugins('initialize', null, null, ctx);
1233
+ return ctx;
1234
+ }
1235
+ /**
1236
+ * The React context object for Stylix.
1237
+ */
1238
+ const stylixContext = createContext(undefined);
1239
+ /**
1240
+ * Default Stylix context, used when no provider is present.
1241
+ */
1242
+ let defaultStylixContext;
1243
+ /**
1244
+ * React hook that gets the current Stylix context.
1245
+ */
1246
+ function useStylixContext() {
1247
+ const ctx = useContext(stylixContext);
1248
+ if (!ctx) {
1249
+ if (!defaultStylixContext)
1250
+ defaultStylixContext = createStylixContext();
1251
+ return defaultStylixContext;
1252
+ }
1253
+ return ctx;
1254
+ }
1255
+ /**
1256
+ * StylixProvider component. Provides a Stylix context to its descendent elements.
1257
+ * Can either accept an existing context via the `context` prop, or create a new context
1258
+ * using the other configuration props.
1259
+ */
1260
+ function StylixProvider(props) {
1261
+ const { context, id, devMode, plugins, media, styleElement, children, ssr } = props;
1262
+ const ctx = useRef(context);
1263
+ if (!ctx.current)
1264
+ ctx.current = createStylixContext({
1265
+ id,
1266
+ devMode,
1267
+ plugins,
1268
+ media,
1269
+ styleElement,
1270
+ ssr,
1271
+ });
1272
+ ctx.current.styleCollector = useContext(styleCollectorContext);
1273
+ // When the component is unmounted, remove the style element, if any
1274
+ useEffect(() => {
1275
+ return () => {
1276
+ ctx.current?.styleElement?.remove();
1277
+ };
1278
+ }, []);
1279
+ return jsx(stylixContext.Provider, { value: ctx.current, children: children });
1280
+ }
1281
+
1232
1282
  function _Stylix(props, ref) {
1233
1283
  const { $el, $render, $css, className: outerClassName, children, ...rest } = props;
1234
1284
  const ctx = useStylixContext();
@@ -1245,7 +1295,7 @@ function _Stylix(props, ref) {
1245
1295
  if (React.isValidElement($el)) {
1246
1296
  const $elProps = {
1247
1297
  ...$el.props,
1248
- ref,
1298
+ ref: ('ref' in $el && $el.ref) || ref,
1249
1299
  /**
1250
1300
  * `allProps` must override `$el.props` because the latter may contain default prop values provided by defaultProps.
1251
1301
  * The expectation is that for <$ $el={<SomeComponent />} someComponentProp="my value" />,