@stylix/core 6.2.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 +83 -59
- package/dist/index.js +276 -195
- 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,5 +1,61 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
|
-
import React, { createContext,
|
|
2
|
+
import React, { createContext, useRef, useInsertionEffect, useContext, useEffect } from 'react';
|
|
3
|
+
|
|
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
|
+
}
|
|
3
59
|
|
|
4
60
|
function classifyProps(props, knownStyleProps) {
|
|
5
61
|
const styles = {};
|
|
@@ -587,8 +643,8 @@ function mapObject(source, mapFn, context) {
|
|
|
587
643
|
|
|
588
644
|
const defaultIgnoreUnits = [
|
|
589
645
|
'aspect-ratio',
|
|
590
|
-
'columns',
|
|
591
646
|
'column-count',
|
|
647
|
+
'columns',
|
|
592
648
|
'fill-opacity',
|
|
593
649
|
'flex',
|
|
594
650
|
'flex-grow',
|
|
@@ -596,15 +652,15 @@ const defaultIgnoreUnits = [
|
|
|
596
652
|
'font-weight',
|
|
597
653
|
'line-height',
|
|
598
654
|
'opacity',
|
|
655
|
+
'order',
|
|
599
656
|
'orphans',
|
|
600
657
|
'stroke-opacity',
|
|
601
658
|
'widows',
|
|
602
659
|
'z-index',
|
|
603
660
|
'zoom',
|
|
604
|
-
'order',
|
|
605
661
|
];
|
|
606
662
|
/**
|
|
607
|
-
* 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`.
|
|
608
664
|
*/
|
|
609
665
|
const defaultUnits = (unit = 'px', ignoreProps = defaultIgnoreUnits) => {
|
|
610
666
|
return {
|
|
@@ -651,6 +707,36 @@ const hoistKeyframes = {
|
|
|
651
707
|
},
|
|
652
708
|
};
|
|
653
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
|
+
|
|
654
740
|
/**
|
|
655
741
|
* Expands media objects using the media definitions from the Stylix context.
|
|
656
742
|
*/
|
|
@@ -684,10 +770,21 @@ function processMediaStyles(mediaDef, styleProps, styles) {
|
|
|
684
770
|
// An object for a style prop is definitely a media object
|
|
685
771
|
for (const mediaKey in styleValue) {
|
|
686
772
|
result[mediaKey] ||= [];
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
[
|
|
690
|
-
|
|
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
|
+
}
|
|
691
788
|
}
|
|
692
789
|
continue;
|
|
693
790
|
}
|
|
@@ -889,6 +986,7 @@ const defaultPlugins = [
|
|
|
889
986
|
mergeArrays,
|
|
890
987
|
propCasing,
|
|
891
988
|
hoistKeyframes,
|
|
989
|
+
hoistLayers,
|
|
892
990
|
replace$$class,
|
|
893
991
|
defaultPixelUnits,
|
|
894
992
|
cleanStyles,
|
|
@@ -911,152 +1009,6 @@ function createStyleCollector() {
|
|
|
911
1009
|
return collector;
|
|
912
1010
|
}
|
|
913
1011
|
|
|
914
|
-
const detectSSR = () => !(typeof window !== 'undefined' && window.document?.head?.appendChild);
|
|
915
|
-
function useIsoLayoutEffect(fn, deps, runOnSsr, isSsr = detectSSR()) {
|
|
916
|
-
if (isSsr) {
|
|
917
|
-
if (runOnSsr)
|
|
918
|
-
return fn();
|
|
919
|
-
}
|
|
920
|
-
else {
|
|
921
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: isSsr should never change
|
|
922
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: dependencies are passed as-is
|
|
923
|
-
useLayoutEffect(fn, deps);
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
let defaultStyleProps;
|
|
928
|
-
function createStylixContext(userValues = {}) {
|
|
929
|
-
if (!defaultStyleProps) {
|
|
930
|
-
defaultStyleProps = {};
|
|
931
|
-
for (const value of cssProps) {
|
|
932
|
-
defaultStyleProps[simplifyStylePropName(value)] = value;
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
const ctx = {
|
|
936
|
-
id: userValues.id || '$default',
|
|
937
|
-
devMode: !!userValues.devMode,
|
|
938
|
-
styleProps: defaultStyleProps,
|
|
939
|
-
media: userValues.media,
|
|
940
|
-
styleElement: userValues.styleElement,
|
|
941
|
-
plugins: defaultPlugins.flat(),
|
|
942
|
-
styleCounter: 0,
|
|
943
|
-
rules: {},
|
|
944
|
-
ssr: userValues.ssr ?? detectSSR(),
|
|
945
|
-
cleanupRequest: undefined,
|
|
946
|
-
requestApply: false,
|
|
947
|
-
classifyProps(props) {
|
|
948
|
-
const [styles, other] = classifyProps(props, this.styleProps);
|
|
949
|
-
return [styles, other];
|
|
950
|
-
},
|
|
951
|
-
};
|
|
952
|
-
if (userValues.plugins?.length) {
|
|
953
|
-
const flatPlugins = userValues.plugins.flat();
|
|
954
|
-
for (const i in flatPlugins) {
|
|
955
|
-
const plugin = flatPlugins[i];
|
|
956
|
-
let pluginIndex = -1;
|
|
957
|
-
if (plugin.before)
|
|
958
|
-
pluginIndex = ctx.plugins.findIndex((v) => v.name === plugin.before);
|
|
959
|
-
else if (plugin.after) {
|
|
960
|
-
pluginIndex = ctx.plugins.findIndex((v) => v.name === plugin.before);
|
|
961
|
-
if (pluginIndex !== -1)
|
|
962
|
-
pluginIndex += 1;
|
|
963
|
-
}
|
|
964
|
-
else if (plugin.atIndex !== undefined)
|
|
965
|
-
pluginIndex = plugin.atIndex;
|
|
966
|
-
if (pluginIndex === -1)
|
|
967
|
-
ctx.plugins.push(plugin);
|
|
968
|
-
else
|
|
969
|
-
ctx.plugins.splice(pluginIndex, 0, plugin);
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
applyPlugins('initialize', null, null, ctx);
|
|
973
|
-
return ctx;
|
|
974
|
-
}
|
|
975
|
-
// The React context object
|
|
976
|
-
const stylixContext = createContext(undefined);
|
|
977
|
-
let defaultStylixContext;
|
|
978
|
-
/**
|
|
979
|
-
* Gets the current Stylix context.
|
|
980
|
-
*/
|
|
981
|
-
function useStylixContext() {
|
|
982
|
-
const ctx = useContext(stylixContext);
|
|
983
|
-
if (!ctx) {
|
|
984
|
-
if (!defaultStylixContext)
|
|
985
|
-
defaultStylixContext = createStylixContext();
|
|
986
|
-
return defaultStylixContext;
|
|
987
|
-
}
|
|
988
|
-
return ctx;
|
|
989
|
-
}
|
|
990
|
-
function StylixProvider({ id, devMode, plugins, media, styleElement, children, ssr, }) {
|
|
991
|
-
const ctx = useRef(null);
|
|
992
|
-
if (!ctx.current)
|
|
993
|
-
ctx.current = createStylixContext({ id, devMode, plugins, media, styleElement, ssr });
|
|
994
|
-
ctx.current.styleCollector = useContext(styleCollectorContext);
|
|
995
|
-
// When the component is unmounted, remove the style element, if any
|
|
996
|
-
useEffect(() => {
|
|
997
|
-
return () => {
|
|
998
|
-
ctx.current?.styleElement?.remove();
|
|
999
|
-
};
|
|
1000
|
-
}, []);
|
|
1001
|
-
return jsx(stylixContext.Provider, { value: ctx.current, children: children });
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
function flattenRules(ctx) {
|
|
1005
|
-
return Object.values(ctx.rules)
|
|
1006
|
-
.flatMap((val) => (val && val.refs > 0 ? val.rules : []))
|
|
1007
|
-
.filter(Boolean);
|
|
1008
|
-
}
|
|
1009
|
-
/**
|
|
1010
|
-
* Applies rules from given StylixContext to the <style> element.
|
|
1011
|
-
*/
|
|
1012
|
-
function applyRules(ctx) {
|
|
1013
|
-
if (ctx.styleCollector) {
|
|
1014
|
-
const flattenedRules = flattenRules(ctx);
|
|
1015
|
-
ctx.styleCollector.length = 0;
|
|
1016
|
-
ctx.styleCollector.push(...flattenedRules);
|
|
1017
|
-
return;
|
|
1018
|
-
}
|
|
1019
|
-
if (ctx.ssr)
|
|
1020
|
-
return;
|
|
1021
|
-
const supportsAdoptedStylesheets = 'adoptedStyleSheets' in document;
|
|
1022
|
-
// If there's no style element, and we're in dev mode, create one
|
|
1023
|
-
if (!ctx.styleElement && (ctx.devMode || !supportsAdoptedStylesheets)) {
|
|
1024
|
-
ctx.styleElement = document.createElement('style');
|
|
1025
|
-
ctx.styleElement.className = 'stylix';
|
|
1026
|
-
if (ctx.id)
|
|
1027
|
-
ctx.styleElement.id = `stylix-${ctx.id}`;
|
|
1028
|
-
document.head.appendChild(ctx.styleElement);
|
|
1029
|
-
}
|
|
1030
|
-
if (ctx.styleElement) {
|
|
1031
|
-
// If there's a style element, use it
|
|
1032
|
-
const flattenedRules = flattenRules(ctx);
|
|
1033
|
-
ctx.styleElement.innerHTML = flattenedRules.join('\n');
|
|
1034
|
-
}
|
|
1035
|
-
else {
|
|
1036
|
-
// Still no stylesheet yet, create one
|
|
1037
|
-
if (!ctx.stylesheet) {
|
|
1038
|
-
ctx.stylesheet = new CSSStyleSheet();
|
|
1039
|
-
if (supportsAdoptedStylesheets) {
|
|
1040
|
-
document.adoptedStyleSheets.push(ctx.stylesheet);
|
|
1041
|
-
}
|
|
1042
|
-
else if (ctx.stylesheet.ownerNode) {
|
|
1043
|
-
document.head.appendChild(ctx.stylesheet.ownerNode);
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
const stylesheet = ctx.stylesheet;
|
|
1047
|
-
const flattenedRules = flattenRules(ctx);
|
|
1048
|
-
if (stylesheet.replaceSync) {
|
|
1049
|
-
try {
|
|
1050
|
-
stylesheet.replaceSync(flattenedRules.join('\n'));
|
|
1051
|
-
}
|
|
1052
|
-
catch (e) {
|
|
1053
|
-
// Errors are ignored, this just means that a browser doesn't support a certain CSS feature.
|
|
1054
|
-
console.warn(e);
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
1012
|
function getParentComponentName() {
|
|
1061
1013
|
const internals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
|
|
1062
1014
|
const stack = internals?.ReactDebugCurrentFrame?.getStackAddendum?.()?.split('\n') || [];
|
|
@@ -1073,6 +1025,9 @@ function getParentComponentName() {
|
|
|
1073
1025
|
* Serialize selector and styles to css rule string
|
|
1074
1026
|
*/
|
|
1075
1027
|
function serialize(selector, styles) {
|
|
1028
|
+
if (selector.startsWith('@') && Array.isArray(styles)) {
|
|
1029
|
+
return `${selector} ${styles.join(', ')};`;
|
|
1030
|
+
}
|
|
1076
1031
|
const lines = [];
|
|
1077
1032
|
for (const key in styles) {
|
|
1078
1033
|
const value = styles[key];
|
|
@@ -1092,6 +1047,11 @@ function stylesToRuleArray(styles, className, context) {
|
|
|
1092
1047
|
try {
|
|
1093
1048
|
const processedStyles = applyPlugins('processStyles', styles, className, context);
|
|
1094
1049
|
const result = [];
|
|
1050
|
+
// Handle @layer rules first
|
|
1051
|
+
if (processedStyles['@layer']) {
|
|
1052
|
+
result.push(serialize('@layer', processedStyles['@layer']));
|
|
1053
|
+
delete processedStyles['@layer'];
|
|
1054
|
+
}
|
|
1095
1055
|
for (const key in processedStyles) {
|
|
1096
1056
|
const value = processedStyles[key];
|
|
1097
1057
|
result.push(serialize(key, value));
|
|
@@ -1128,68 +1088,80 @@ function cleanup(ctx) {
|
|
|
1128
1088
|
ctx.cleanupRequest = setTimeout(doCleanup, 100);
|
|
1129
1089
|
}
|
|
1130
1090
|
}
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
const stylixCtx = useStylixContext();
|
|
1139
|
-
const prevStylesJson = useRef('');
|
|
1140
|
-
// Preprocess styles with plugins
|
|
1141
|
-
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
|
|
1142
1098
|
styles = applyPlugins('preprocessStyles', styles, null, stylixCtx);
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
prevStylesJson.current = stylesJson;
|
|
1148
|
-
options.debugLabel ||= stylixCtx.devMode ? getParentComponentName() : '';
|
|
1149
|
-
if (stylesJson && !stylixCtx.rules[stylesJson]) {
|
|
1099
|
+
// Generate styles key
|
|
1100
|
+
stylesKey = styles ? (global ? 'global:' : '') + JSON.stringify(styles) : '';
|
|
1101
|
+
}
|
|
1102
|
+
if (stylesKey && !stylixCtx.rules[stylesKey]) {
|
|
1150
1103
|
stylixCtx.styleCounter++;
|
|
1151
|
-
const
|
|
1104
|
+
const debugLabel = config.debugLabel || (stylixCtx.devMode && getParentComponentName()) || '';
|
|
1105
|
+
const className = `stylix-${(stylixCtx.styleCounter).toString(36)}${debugLabel ? `-${debugLabel}` : ''}`;
|
|
1152
1106
|
// If not global styles, wrap original styles with classname
|
|
1153
|
-
if (!
|
|
1107
|
+
if (!global)
|
|
1154
1108
|
styles = { [`.${className}`]: styles };
|
|
1155
|
-
stylixCtx.rules[
|
|
1109
|
+
stylixCtx.rules[stylesKey] = {
|
|
1156
1110
|
className,
|
|
1157
1111
|
rules: stylesToRuleArray(styles, className, stylixCtx),
|
|
1158
1112
|
refs: 0,
|
|
1159
1113
|
};
|
|
1160
1114
|
}
|
|
1161
|
-
|
|
1115
|
+
const isChanged = stylesKey !== priorKey;
|
|
1116
|
+
const ruleSet = stylesKey ? stylixCtx.rules[stylesKey] : null;
|
|
1117
|
+
if (isChanged) {
|
|
1118
|
+
// Mark styles to be applied
|
|
1162
1119
|
stylixCtx.requestApply = true;
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
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++;
|
|
1167
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;
|
|
1168
1149
|
// Apply styles if requested.
|
|
1169
|
-
// This runs on every render. We utilize
|
|
1150
|
+
// This runs on every render. We utilize useInsertionEffect so that it runs *after* all the other
|
|
1170
1151
|
// renders have completed. stylixCtx.requestApply guards against multiple runs. This reduces the number of calls
|
|
1171
1152
|
// to applyRules(), but prevents styles potentially being added to the DOM after other components force the
|
|
1172
1153
|
// browser to compute styles.
|
|
1173
|
-
|
|
1154
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: stylixCtx is stable
|
|
1155
|
+
useInsertionEffect(() => {
|
|
1174
1156
|
if (!stylixCtx.requestApply)
|
|
1175
1157
|
return;
|
|
1176
1158
|
stylixCtx.requestApply = false;
|
|
1177
1159
|
applyRules(stylixCtx);
|
|
1178
|
-
}, undefined, true, stylixCtx.ssr);
|
|
1179
|
-
useIsoLayoutEffect(() => {
|
|
1180
|
-
if (!stylesJson || !changed)
|
|
1181
|
-
return;
|
|
1182
1160
|
return () => {
|
|
1183
|
-
const ruleSet = stylixCtx.rules[stylesJson];
|
|
1184
|
-
if (!ruleSet)
|
|
1185
|
-
return;
|
|
1186
|
-
ruleSet.refs--;
|
|
1187
|
-
if (ruleSet.refs <= 0)
|
|
1188
|
-
stylixCtx.rules[stylesJson] = undefined;
|
|
1189
1161
|
cleanup(stylixCtx);
|
|
1190
1162
|
};
|
|
1191
|
-
}, [
|
|
1192
|
-
return
|
|
1163
|
+
}, [s.key]);
|
|
1164
|
+
return s.className;
|
|
1193
1165
|
}
|
|
1194
1166
|
function useKeyframes(keyframes) {
|
|
1195
1167
|
return useStyles({ '@keyframes $$class': keyframes }, { global: true });
|
|
@@ -1198,6 +1170,115 @@ function useGlobalStyles(styles, options = { disabled: false }) {
|
|
|
1198
1170
|
return useStyles(styles, { ...options, global: true });
|
|
1199
1171
|
}
|
|
1200
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
|
+
|
|
1201
1282
|
function _Stylix(props, ref) {
|
|
1202
1283
|
const { $el, $render, $css, className: outerClassName, children, ...rest } = props;
|
|
1203
1284
|
const ctx = useStylixContext();
|
|
@@ -1214,7 +1295,7 @@ function _Stylix(props, ref) {
|
|
|
1214
1295
|
if (React.isValidElement($el)) {
|
|
1215
1296
|
const $elProps = {
|
|
1216
1297
|
...$el.props,
|
|
1217
|
-
ref,
|
|
1298
|
+
ref: ('ref' in $el && $el.ref) || ref,
|
|
1218
1299
|
/**
|
|
1219
1300
|
* `allProps` must override `$el.props` because the latter may contain default prop values provided by defaultProps.
|
|
1220
1301
|
* The expectation is that for <$ $el={<SomeComponent />} someComponentProp="my value" />,
|