@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/README.md +140 -280
- package/dist/index.d.ts +85 -60
- package/dist/index.js +351 -301
- package/dist/index.js.map +1 -1
- package/docs/CLAUDE_CONTEXT.md +156 -0
- package/docs/cheatsheet.md +354 -0
- package/docs/patterns.md +754 -0
- package/docs/performance.md +291 -0
- package/docs/philosophy.md +168 -0
- package/package.json +23 -20
- package/tsconfig.json +0 -0
package/dist/index.js
CHANGED
|
@@ -1,13 +1,69 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
|
-
import React, { createContext,
|
|
2
|
+
import React, { createContext, useRef, useInsertionEffect, useContext, useEffect } from 'react';
|
|
3
3
|
|
|
4
|
-
function
|
|
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,
|
|
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,
|
|
79
|
+
function isStyleProp(prop, knownStyleProps) {
|
|
24
80
|
if (isValidJSXProp(prop)) {
|
|
25
81
|
const simplified = simplifyStylePropName(prop);
|
|
26
|
-
return simplified in
|
|
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,
|
|
631
|
+
function mapObject(source, mapFn, context) {
|
|
576
632
|
if (typeof source !== 'object')
|
|
577
633
|
return source;
|
|
578
|
-
const
|
|
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
|
-
|
|
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
|
|
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,
|
|
674
|
+
const defaultUnitsMap = (key, value, target, ctx, mapRecursive) => {
|
|
633
675
|
if (typeof value === 'number' && !ctx.ignoreProps.includes(key)) {
|
|
634
|
-
|
|
676
|
+
target[key] = String(value) + ctx.unit;
|
|
677
|
+
return;
|
|
635
678
|
}
|
|
636
|
-
|
|
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
|
-
|
|
701
|
-
|
|
702
|
-
[
|
|
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) =>
|
|
811
|
+
plugin: (_ctx, styles) => reduceArrays(styles),
|
|
728
812
|
};
|
|
729
|
-
function
|
|
730
|
-
|
|
731
|
-
return reduceArray(obj);
|
|
732
|
-
return reduceObjectProperties(obj);
|
|
813
|
+
function reduceArrays(obj) {
|
|
814
|
+
return _reduceArrays(obj);
|
|
733
815
|
}
|
|
734
|
-
function
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
if (Array.isArray(
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
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
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
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
|
-
//
|
|
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
|
|
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,
|
|
827
|
-
if (typeof key !== 'string' || key === '&')
|
|
828
|
-
|
|
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
|
-
|
|
883
|
+
target[context.ctx.styleProps[simpleKey]] = mapRecursive(value);
|
|
884
|
+
return;
|
|
832
885
|
}
|
|
833
|
-
|
|
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,
|
|
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
|
-
|
|
905
|
+
target[key] = value;
|
|
853
906
|
};
|
|
854
907
|
|
|
855
908
|
function _customPropsProcess(styles, customProps) {
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
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 (
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
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
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
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
|
|
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 (!
|
|
1107
|
+
if (!global)
|
|
1185
1108
|
styles = { [`.${className}`]: styles };
|
|
1186
|
-
stylixCtx.rules[
|
|
1109
|
+
stylixCtx.rules[stylesKey] = {
|
|
1187
1110
|
className,
|
|
1188
1111
|
rules: stylesToRuleArray(styles, className, stylixCtx),
|
|
1189
1112
|
refs: 0,
|
|
1190
1113
|
};
|
|
1191
1114
|
}
|
|
1192
|
-
|
|
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
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
}, [
|
|
1223
|
-
return
|
|
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" />,
|