Skip to content

Commit f5de618

Browse files
committed
Add native support for CSS variables with fixed names
StyleX on web has a feature that prevents auto-generation of scoped CSS var names created by `defineVars`. If the user-provided var key is a string that starts with `--`, the key is preserved as-is and its value can even be redefined in other `defineVars` calls. Adding support for this on native involves removing the legacy feature that converted kebab-case variable names to camelCase. Therefore, it's a breaking change that requires users to modify to the `customProperties` map (adding `--` prefix to all keys) passed to any use of the legacy `ThemeProvider` context. Close #73 Fix #50
1 parent 55aa8d2 commit f5de618

File tree

7 files changed

+84
-86
lines changed

7 files changed

+84
-86
lines changed

apps/examples/src/components/App.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -654,14 +654,14 @@ const animateSequence = css.keyframes({
654654
});
655655

656656
const themedTokens = css.createTheme(tokens, {
657-
squareColor: 'purple',
657+
'--square-color': 'purple',
658658
textColor: 'purple',
659659
inputColor: 'purple',
660660
inputPlaceholderColor: 'mediumpurple'
661661
});
662662

663663
const themedTokensAlt = css.createTheme(tokens, {
664-
squareColor: 'darkorange',
664+
'--square-color': 'darkorange',
665665
textColor: 'darkorange',
666666
inputColor: 'orangered',
667667
inputPlaceholderColor: 'orange'
@@ -684,7 +684,7 @@ const styles = css.create({
684684
square: {
685685
height: 100,
686686
width: 100,
687-
backgroundColor: tokens.squareColor
687+
backgroundColor: 'var(--square-color)' // check global vars work too
688688
},
689689
pseudoStates: {
690690
height: 50,

apps/examples/src/components/tokens.stylex.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ import { css } from 'react-strict-dom';
1313

1414
export const tokens: StyleVars<
1515
$ReadOnly<{
16-
squareColor: string,
16+
'--square-color': string,
1717
textColor: string,
1818
inputColor: string,
1919
inputPlaceholderColor: string
2020
}>
2121
> = css.defineVars({
22-
squareColor: 'red',
22+
'--square-color': 'red', // '--' prefix makes this is a global var
2323
textColor: {
2424
default: 'darkred',
2525
'@media (prefers-color-scheme: dark)': 'lightred'

packages/react-strict-dom/src/native/stylex/customProperties.js

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,16 @@ import { CSSUnparsedValue } from './typed-om/CSSUnparsedValue';
1313
import { CSSVariableReferenceValue } from './typed-om/CSSVariableReferenceValue';
1414
import { warnMsg } from '../../shared/logUtils';
1515

16-
const memoizedValues = new Map<string, string>();
17-
18-
function camelize(s: string): string {
19-
const memoizedValue = memoizedValues.get(s);
20-
if (memoizedValue != null) {
21-
return memoizedValue;
22-
}
23-
const result = s.replace(/-./g, (x) => x.toUpperCase()[1]);
24-
memoizedValues.set(s, result);
25-
return result;
26-
}
27-
2816
function normalizeVariableName(name: string): string {
2917
if (__DEV__) {
3018
if (!name.startsWith('--')) {
3119
throw new Error("Invalid variable name, must begin with '--'");
3220
}
3321
}
34-
// TODO: remove camelize
35-
// https://github.com/facebook/react-strict-dom/pull/73
36-
return camelize(name.substring(2));
22+
// Scoped vars created by defineVars all start with __var__.
23+
// But global vars manually created with '--' prefixed keys don't.
24+
const varName = name.startsWith('--__var__') ? name.substring(2) : name;
25+
return varName;
3726
}
3827

3928
export function stringContainsVariables(input: string): boolean {

packages/react-strict-dom/src/native/stylex/index.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -448,8 +448,12 @@ export const createTheme = (
448448
const result: MutableCustomProperties = { $$theme: 'theme' };
449449
for (const key in baseTokens) {
450450
const varName: string = baseTokens[key];
451-
const normalizedKey = varName.replace(RE_CAPTURE_VAR_NAME, '$1');
452-
result[normalizedKey] = overrides[key];
451+
const match = varName.match(RE_CAPTURE_VAR_NAME);
452+
if (match) {
453+
const x = match[1];
454+
const normalizedKey = x.startsWith('__var__') ? x : `--${x}`;
455+
result[normalizedKey] = overrides[key];
456+
}
453457
}
454458
return result;
455459
};
@@ -466,8 +470,11 @@ export const defineVars = (tokens: CustomProperties): Tokens => {
466470
const result: Tokens = {};
467471
for (const key in tokens) {
468472
const value = tokens[key];
469-
const customPropName = `${key}__id__${defineVarsCount++}`;
470-
result[key] = `var(--${customPropName})`;
473+
const isGlobalVar = key.startsWith('--');
474+
const customPropName = isGlobalVar
475+
? key
476+
: `__var__${defineVarsCount++}_${key}`;
477+
result[key] = isGlobalVar ? `var(${key})` : `var(--${customPropName})`;
471478
// NOTE: it's generally not a good idea to mutate the default context,
472479
// but defineVars is always called before any component body is evaluated,
473480
// and so it's safe to do so here.

packages/react-strict-dom/tests/__snapshots__/css-test.native.js.snap-native

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@
22

33
exports[`properties: custom property css.createTheme: css.__customProperties 1`] = `
44
{
5-
"rootColor__id__1": "red",
6-
"rootColor__id__3": "red",
7-
"themeAwareColor__id__2": {
5+
"--pass-through": "purple",
6+
"__var__1_rootColor": "red",
7+
"__var__2_themeAwareColor": {
88
"@media (prefers-color-scheme: dark)": "green",
99
"default": "blue",
1010
},
11+
"__var__3_rootColor": "red",
1112
}
1213
`;
1314

1415
exports[`properties: custom property css.createTheme: theme 1`] = `
1516
{
1617
"$$theme": "theme",
17-
"rootColor__id__3": "green",
18+
"__var__3_rootColor": "green",
1819
}
1920
`;
2021

@@ -27,8 +28,9 @@ exports[`properties: custom property css.defineConsts: constants 1`] = `
2728

2829
exports[`properties: custom property css.defineVars: css.__customProperties 1`] = `
2930
{
30-
"rootColor__id__1": "red",
31-
"themeAwareColor__id__2": {
31+
"--pass-through": "purple",
32+
"__var__1_rootColor": "red",
33+
"__var__2_themeAwareColor": {
3234
"@media (prefers-color-scheme: dark)": "green",
3335
"default": "blue",
3436
},
@@ -37,8 +39,9 @@ exports[`properties: custom property css.defineVars: css.__customProperties 1`]
3739

3840
exports[`properties: custom property css.defineVars: tokens 1`] = `
3941
{
40-
"rootColor": "var(--rootColor__id__1)",
41-
"themeAwareColor": "var(--themeAwareColor__id__2)",
42+
"--pass-through": "var(--pass-through)",
43+
"rootColor": "var(--__var__1_rootColor)",
44+
"themeAwareColor": "var(--__var__2_themeAwareColor)",
4245
}
4346
`;
4447

packages/react-strict-dom/tests/css-test.native.js

Lines changed: 52 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,7 +1157,7 @@ describe('properties: custom property', () => {
11571157
test('legacy strings', () => {
11581158
const options = {
11591159
customProperties: {
1160-
rootColor: 'red'
1160+
'--rootColor': 'red'
11611161
}
11621162
};
11631163
expect(
@@ -1183,7 +1183,8 @@ describe('properties: custom property', () => {
11831183
themeAwareColor: {
11841184
default: 'blue',
11851185
'@media (prefers-color-scheme: dark)': 'green'
1186-
}
1186+
},
1187+
'--pass-through': 'purple'
11871188
});
11881189
expect(tokens).toMatchSnapshot('tokens');
11891190
expect(css.__customProperties).toMatchSnapshot('css.__customProperties');
@@ -1193,6 +1194,9 @@ describe('properties: custom property', () => {
11931194
expect(
11941195
resolveCustomPropertyValue(options, ['color', tokens.themeAwareColor])
11951196
).toEqual('blue');
1197+
expect(
1198+
resolveCustomPropertyValue(options, ['color', 'var(--pass-through)'])
1199+
).toEqual('purple');
11961200
// dark theme
11971201
expect(
11981202
resolveCustomPropertyValue({ colorScheme: 'dark' }, [
@@ -1255,7 +1259,9 @@ describe('properties: custom property', () => {
12551259

12561260
test('parses a basic var correctly', () => {
12571261
const options = {
1258-
customProperties: { test: 'red' }
1262+
customProperties: {
1263+
'--test': 'red'
1264+
}
12591265
};
12601266
expect(
12611267
resolveCustomPropertyValue(options, ['color', 'var(--test)'])
@@ -1264,7 +1270,9 @@ describe('properties: custom property', () => {
12641270

12651271
test('parses a var with a default value', () => {
12661272
const options = {
1267-
customProperties: { test: 'red' }
1273+
customProperties: {
1274+
'--test': 'red'
1275+
}
12681276
};
12691277
expect(
12701278
resolveCustomPropertyValue(options, ['color', 'var(--test, blue)'])
@@ -1274,32 +1282,11 @@ describe('properties: custom property', () => {
12741282
).toEqual('blue');
12751283
});
12761284

1277-
// TODO: this transform should not be supported. Custom properties are case sensitive.
1278-
test('parses kebab case var to camel case', () => {
1279-
const options = {
1280-
customProperties: { testVar: 'red' }
1281-
};
1282-
expect(
1283-
resolveCustomPropertyValue(options, ['color', 'var(--test-var)'])
1284-
).toEqual('red');
1285-
});
1286-
1287-
// TODO: this transform should not be supported. Custom properties are case sensitive.
1288-
test('parses kebab case var with a default value', () => {
1289-
const options = {
1290-
customProperties: { testVar: 'red' }
1291-
};
1292-
expect(
1293-
resolveCustomPropertyValue(options, ['color', 'var(--test-var, blue)'])
1294-
).toEqual('red');
1295-
expect(
1296-
resolveCustomPropertyValue(options, ['color', 'var(--not-found, blue)'])
1297-
).toEqual('blue');
1298-
});
1299-
13001285
test('parses a var with a default value containing spaces', () => {
13011286
const options = {
1302-
customProperties: { color: 'rgb(0,0,0)' }
1287+
customProperties: {
1288+
'--color': 'rgb(0,0,0)'
1289+
}
13031290
};
13041291
expect(
13051292
resolveCustomPropertyValue(options, [
@@ -1317,7 +1304,9 @@ describe('properties: custom property', () => {
13171304

13181305
test('parses a var and falls back to default value containing a var', () => {
13191306
const options = {
1320-
customProperties: { color: 'red' }
1307+
customProperties: {
1308+
'--color': 'red'
1309+
}
13211310
};
13221311
expect(
13231312
resolveCustomPropertyValue(options, [
@@ -1330,7 +1319,7 @@ describe('properties: custom property', () => {
13301319
test('parses a var and falls back to a default value containing spaces and embedded var', () => {
13311320
const options = {
13321321
customProperties: {
1333-
test: '255'
1322+
'--test': '255'
13341323
}
13351324
};
13361325
expect(
@@ -1343,7 +1332,9 @@ describe('properties: custom property', () => {
13431332

13441333
test('basic var value lookup works', () => {
13451334
const options = {
1346-
customProperties: { number: 10 }
1335+
customProperties: {
1336+
'--number': 10
1337+
}
13471338
};
13481339
expect(
13491340
resolveCustomPropertyValue(options, ['borderWidth', 'var(--number)'])
@@ -1352,7 +1343,9 @@ describe('properties: custom property', () => {
13521343

13531344
test('var value lookup with pixel prop conversion', () => {
13541345
const options = {
1355-
customProperties: { pxNumber: '10px' }
1346+
customProperties: {
1347+
'--pxNumber': '10px'
1348+
}
13561349
};
13571350
expect(
13581351
resolveCustomPropertyValue(options, ['borderWidth', 'var(--pxNumber)'])
@@ -1361,7 +1354,9 @@ describe('properties: custom property', () => {
13611354

13621355
test('var value lookup with em prop conversion', () => {
13631356
const options = {
1364-
customProperties: { emNumber: '10em' }
1357+
customProperties: {
1358+
'--emNumber': '10em'
1359+
}
13651360
};
13661361
expect(
13671362
resolveCustomPropertyValue(options, ['borderWidth', 'var(--emNumber)'])
@@ -1371,9 +1366,9 @@ describe('properties: custom property', () => {
13711366
test('var value lookup with reference to another var', () => {
13721367
const options = {
13731368
customProperties: {
1374-
number: 10,
1375-
refToNumber: 'var(--number)',
1376-
refToRefToNumber: 'var(--refToNumber)'
1369+
'--number': 10,
1370+
'--refToNumber': 'var(--number)',
1371+
'--refToRefToNumber': 'var(--refToNumber)'
13771372
}
13781373
};
13791374
expect(
@@ -1389,7 +1384,9 @@ describe('properties: custom property', () => {
13891384

13901385
test('var with hover styles', () => {
13911386
const options = {
1392-
customProperties: { test: '#333' },
1387+
customProperties: {
1388+
'--test': '#333'
1389+
},
13931390
hover: true
13941391
};
13951392
const { underTest } = css.create({
@@ -1413,7 +1410,9 @@ describe('properties: custom property', () => {
14131410

14141411
test('rgb(a) function with args applied through a single var', () => {
14151412
const options = {
1416-
customProperties: { example: '24, 48, 96' }
1413+
customProperties: {
1414+
'--example': '24, 48, 96'
1415+
}
14171416
};
14181417
expect(
14191418
resolveCustomPropertyValue(options, ['color', 'rgb(var(--example))'])
@@ -1429,11 +1428,11 @@ describe('properties: custom property', () => {
14291428
test('rgba function with args applied through multiple (& nested) vars', () => {
14301429
const options = {
14311430
customProperties: {
1432-
red: 255,
1433-
green: 96,
1434-
blue: 16,
1435-
rgb: 'var(--red), var(--green), var(--blue)',
1436-
alpha: 0.42
1431+
'--red': 255,
1432+
'--green': 96,
1433+
'--blue': 16,
1434+
'--rgb': 'var(--red), var(--green), var(--blue)',
1435+
'--alpha': 0.42
14371436
}
14381437
};
14391438
expect(
@@ -1447,11 +1446,11 @@ describe('properties: custom property', () => {
14471446
test('textShadow with nested/multiple vars', () => {
14481447
const options = {
14491448
customProperties: {
1450-
height: '20px',
1451-
width: '10px',
1452-
size: 'var(--width) var(--height)',
1453-
radius: '30px',
1454-
red: 'red'
1449+
'--height': '20px',
1450+
'--width': '10px',
1451+
'--size': 'var(--width) var(--height)',
1452+
'--radius': '30px',
1453+
'--red': 'red'
14551454
}
14561455
};
14571456
const styles = css.create({
@@ -1474,10 +1473,10 @@ describe('properties: custom property', () => {
14741473
test('transform with nested/multiple vars', () => {
14751474
const options = {
14761475
customProperties: {
1477-
distance: 20,
1478-
angle: '45deg',
1479-
translateX: 'translateX(var(--distance))',
1480-
rotateX: 'rotateX(var(--angle))'
1476+
'--distance': 20,
1477+
'--angle': '45deg',
1478+
'--translateX': 'translateX(var(--distance))',
1479+
'--rotateX': 'rotateX(var(--angle))'
14811480
}
14821481
};
14831482
const styles = css.create({
@@ -1495,7 +1494,7 @@ describe('properties: custom property', () => {
14951494
test('css variable declaration inside a media query', () => {
14961495
const options = {
14971496
customProperties: {
1498-
example: '42px'
1497+
'--example': '42px'
14991498
},
15001499
viewportWidth: 450
15011500
};

packages/react-strict-dom/tests/html-test.native.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ describe('<html.*>', () => {
9898
test('legacy: ThemeProvider', () => {
9999
const { ThemeProvider } = contexts;
100100
const customProperties = {
101-
rootColor: 'red'
101+
'--rootColor': 'red'
102102
};
103103
const styles = css.create({
104104
root: {

0 commit comments

Comments
 (0)