Skip to content

Commit ec6e2a0

Browse files
authored
[feat] Add defineMarker() for custom markers for selector combinators (#1238)
* Add stylex.defaultTarget * Rename defaultTarget to defaultMarker * Add stylex.defaultTarget * Rename defaultTarget to defaultMarker * [feat] Add defineMarker() API for custom markers for combinator selectors * Fix flow/ts types for custom markers
1 parent e40cbaf commit ec6e2a0

File tree

16 files changed

+430
-37
lines changed

16 files changed

+430
-37
lines changed

examples/example-nextjs/components/Card.tsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,13 @@
88
*/
99

1010
import * as stylex from '@stylexjs/stylex';
11-
import { globalTokens as $, spacing, text, colors } from '@/app/globalTokens.stylex';
12-
import { tokens } from '@/app/CardTokens.stylex';
11+
import {
12+
globalTokens as $,
13+
spacing,
14+
text,
15+
colors,
16+
} from '@/app/globalTokens.stylex';
17+
import { cardMarker, headingMarker } from './CardTokens.stylex';
1318

1419
type Props = Readonly<{
1520
title: string;
@@ -20,12 +25,12 @@ type Props = Readonly<{
2025
export default function Card({ title, body, href }: Props) {
2126
return (
2227
<a
23-
{...stylex.props(styles.link)}
28+
{...stylex.props(styles.link, cardMarker)}
2429
href={href}
2530
rel="noopener noreferrer"
2631
target="_blank"
2732
>
28-
<h2 {...stylex.props(styles.h2)}>
33+
<h2 {...stylex.props(styles.h2, headingMarker)}>
2934
{title} <span {...stylex.props(styles.span)}></span>
3035
</h2>
3136
<p {...stylex.props(styles.p)}>{body}</p>
@@ -67,10 +72,6 @@ const styles = stylex.create({
6772
transitionDuration: '400ms',
6873
textAlign: 'center',
6974
textDecoration: 'none',
70-
[tokens.arrowTransform]: {
71-
default: 'translateX(0)',
72-
':hover': 'translateX(4px)',
73-
},
7475
},
7576
h2: {
7677
color: colors.blue3,
@@ -84,7 +85,11 @@ const styles = stylex.create({
8485
span: {
8586
display: 'inline-block',
8687
transitionProperty: 'transform',
87-
transform: tokens.arrowTransform,
88+
transform: {
89+
default: null,
90+
[stylex.when.ancestor(':hover', cardMarker)]: 'translateX(10px)',
91+
[stylex.when.ancestor(':hover', headingMarker)]: 'translateX(4px)',
92+
},
8893
transitionDuration: {
8994
default: '200ms',
9095
[REDUCE_MOTION]: '0s',

examples/example-nextjs/app/CardTokens.stylex.ts renamed to examples/example-nextjs/components/CardTokens.stylex.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,5 @@
99

1010
import * as stylex from '@stylexjs/stylex';
1111

12-
export const tokens = stylex.defineVars({
13-
arrowTransform: 'translateX(0)',
14-
});
12+
export const cardMarker = stylex.defineMarker();
13+
export const headingMarker = stylex.defineMarker();
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
'use strict';
9+
10+
jest.autoMockOff();
11+
12+
import { transformSync } from '@babel/core';
13+
import stylexPlugin from '../src/index';
14+
15+
function transform(source, opts = {}) {
16+
const { code, metadata } = transformSync(source, {
17+
filename: opts.filename || '/stylex/packages/vars.stylex.js',
18+
parserOpts: {
19+
flow: 'all',
20+
},
21+
babelrc: false,
22+
plugins: [
23+
[
24+
stylexPlugin,
25+
{
26+
unstable_moduleResolution: {
27+
rootDir: '/stylex/packages/',
28+
type: 'commonJS',
29+
},
30+
...opts,
31+
},
32+
],
33+
],
34+
});
35+
return { code, metadata };
36+
}
37+
38+
describe('@stylexjs/babel-plugin', () => {
39+
describe('[transform] stylex.defineMarker()', () => {
40+
test('member call', () => {
41+
const { code, metadata } = transform(`
42+
import * as stylex from '@stylexjs/stylex';
43+
export const fooBar = stylex.defineMarker();
44+
`);
45+
46+
expect(code).toMatchInlineSnapshot(`
47+
"import * as stylex from '@stylexjs/stylex';
48+
export const fooBar = {
49+
x1jdyizh: "x1jdyizh",
50+
$$css: true
51+
};"
52+
`);
53+
expect(metadata).toMatchInlineSnapshot(`
54+
{
55+
"stylex": [],
56+
}
57+
`);
58+
});
59+
60+
test('named import call', () => {
61+
const { code } = transform(`
62+
import { defineMarker } from '@stylexjs/stylex';
63+
export const baz = defineMarker();
64+
`);
65+
66+
expect(code).toMatchInlineSnapshot(`
67+
"import { defineMarker } from '@stylexjs/stylex';
68+
export const baz = {
69+
x1i61hkd: "x1i61hkd",
70+
$$css: true
71+
};"
72+
`);
73+
});
74+
});
75+
});

packages/@stylexjs/babel-plugin/__tests__/transform-stylex-when-test.js

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,16 @@ function transform(source, opts = {}) {
1919
flow: 'all',
2020
},
2121
babelrc: false,
22-
plugins: [[stylexPlugin, { ...opts }]],
22+
plugins: [
23+
[
24+
stylexPlugin,
25+
{
26+
treeshakeCompensation: true,
27+
unstable_moduleResolution: { type: 'haste' },
28+
...opts,
29+
},
30+
],
31+
],
2332
});
2433

2534
return result;
@@ -334,4 +343,42 @@ describe('@stylexjs/babel-plugin', () => {
334343
`);
335344
});
336345
});
346+
347+
describe('[transform] using custom markers', () => {
348+
test('named import of custom marker', () => {
349+
const { code } = transform(
350+
`
351+
import * as stylex from '@stylexjs/stylex';
352+
import {customMarker} from 'custom-marker.stylex';
353+
354+
const styles = stylex.create({
355+
foo: {
356+
backgroundColor: {
357+
default: 'blue',
358+
[stylex.when.ancestor(':hover', customMarker)]: 'red',
359+
},
360+
},
361+
});
362+
363+
const container = stylex.props(customMarker);
364+
const classNames = stylex.props(styles.foo);
365+
`,
366+
{ runtimeInjection: true },
367+
);
368+
369+
expect(code).toMatchInlineSnapshot(`
370+
"import _inject from "@stylexjs/stylex/lib/stylex-inject";
371+
var _inject2 = _inject;
372+
import * as stylex from '@stylexjs/stylex';
373+
import 'custom-marker.stylex';
374+
import { customMarker } from 'custom-marker.stylex';
375+
_inject2(".x1t391ir{background-color:blue}", 3000);
376+
_inject2(".x7rpj1w:where(.x1lc2aw:hover *){background-color:red}", 3011.3);
377+
const container = stylex.props(customMarker);
378+
const classNames = {
379+
className: "x1t391ir x7rpj1w"
380+
};"
381+
`);
382+
});
383+
});
337384
});
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
'use strict';
9+
10+
jest.autoMockOff();
11+
12+
import { transformSync } from '@babel/core';
13+
import stylexPlugin from '../src/index';
14+
import * as messages from '../src/shared/messages';
15+
16+
function transform(source, opts = {}) {
17+
const { code, metadata } = transformSync(source, {
18+
filename: opts.filename || '/stylex/packages/vars.stylex.js',
19+
parserOpts: {
20+
flow: 'all',
21+
},
22+
babelrc: false,
23+
plugins: [
24+
[
25+
stylexPlugin,
26+
{
27+
unstable_moduleResolution: {
28+
rootDir: '/stylex/packages/',
29+
type: 'commonJS',
30+
},
31+
...opts,
32+
},
33+
],
34+
],
35+
});
36+
return { code, metadata };
37+
}
38+
39+
describe('@stylexjs/babel-plugin', () => {
40+
describe('[validation] stylex.defineMarker()', () => {
41+
test('must be bound to a named export', () => {
42+
expect(() => {
43+
transform(`
44+
import * as stylex from '@stylexjs/stylex';
45+
const marker = stylex.defineMarker();
46+
`);
47+
}).toThrow(messages.nonExportNamedDeclaration('defineMarker'));
48+
});
49+
50+
test('no arguments allowed', () => {
51+
expect(() => {
52+
transform(`
53+
import * as stylex from '@stylexjs/stylex';
54+
export const marker = stylex.defineMarker(1);
55+
`);
56+
}).toThrow(messages.illegalArgumentLength('defineMarker', 0));
57+
});
58+
});
59+
});

packages/@stylexjs/babel-plugin/src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
LOGICAL_FLOAT_START_VAR,
3838
LOGICAL_FLOAT_END_VAR,
3939
} from './shared/preprocess-rules/legacy-expand-shorthands';
40+
import transformStyleXDefineMarker from './visitors/stylex-define-marker';
4041

4142
const NAME = 'stylex';
4243

@@ -310,6 +311,7 @@ function styleXTransform(): PluginObj<> {
310311
}
311312

312313
transformStyleXDefaultMarker(path, state);
314+
transformStyleXDefineMarker(path, state);
313315
transformStyleXDefineVars(path, state);
314316
transformStyleXDefineConsts(path, state);
315317
transformStyleXCreateTheme(path, state);

packages/@stylexjs/babel-plugin/src/shared/when/when.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,36 @@ import type { StyleXOptions } from '../common-types';
1111

1212
import { defaultOptions } from '../utils/default-options';
1313

14+
function fromProxy(value: mixed): ?string {
15+
if (
16+
typeof value === 'object' &&
17+
value != null &&
18+
value.__IS_PROXY === true &&
19+
typeof value.toString === 'function'
20+
) {
21+
return value.toString();
22+
}
23+
return null;
24+
}
25+
26+
function fromStyleXStyle(value: mixed): ?string {
27+
if (typeof value === 'object' && value != null && value.$$css === true) {
28+
return Object.keys(value).find((key) => key !== '$$css');
29+
}
30+
return null;
31+
}
32+
1433
function getDefaultMarkerClassName(
1534
options: StyleXOptions = defaultOptions,
1635
): string {
36+
const valueFromProxy = fromProxy(options);
37+
if (valueFromProxy != null) {
38+
return valueFromProxy;
39+
}
40+
const valueFromStyleXStyle = fromStyleXStyle(options);
41+
if (valueFromStyleXStyle != null) {
42+
return valueFromStyleXStyle;
43+
}
1744
const prefix =
1845
options.classNamePrefix != null ? `${options.classNamePrefix}-` : '';
1946
return `${prefix}default-marker`;

packages/@stylexjs/babel-plugin/src/utils/evaluate-path.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,11 @@ function evaluateThemeRef(
205205
if (key === '__IS_PROXY') {
206206
return true;
207207
}
208+
if (key === 'toString') {
209+
return () =>
210+
state.traversalState.options.classNamePrefix +
211+
utils.hash(utils.genFileBasedIdentifier({ fileName, exportName }));
212+
}
208213
return resolveKey(key);
209214
},
210215
set(_, key: string, value: string) {

packages/@stylexjs/babel-plugin/src/utils/state-manager.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ export default class StateManager {
155155
+stylexKeyframesImport: Set<string> = new Set();
156156
+stylexPositionTryImport: Set<string> = new Set();
157157
+stylexDefineVarsImport: Set<string> = new Set();
158+
+stylexDefineMarkerImport: Set<string> = new Set();
158159
+stylexDefineConstsImport: Set<string> = new Set();
159160
+stylexCreateThemeImport: Set<string> = new Set();
160161
+stylexTypesImport: Set<string> = new Set();

packages/@stylexjs/babel-plugin/src/visitors/imports.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ export function readImportDeclarations(
7979
if (importedName === 'defineVars') {
8080
state.stylexDefineVarsImport.add(localName);
8181
}
82+
if (importedName === 'defineMarker') {
83+
state.stylexDefineMarkerImport.add(localName);
84+
}
8285
if (importedName === 'defineConsts') {
8386
state.stylexDefineConstsImport.add(localName);
8487
}
@@ -158,6 +161,9 @@ export function readRequires(
158161
if (prop.key.name === 'defineVars') {
159162
state.stylexDefineVarsImport.add(value.name);
160163
}
164+
if (prop.key.name === 'defineMarker') {
165+
state.stylexDefineMarkerImport.add(value.name);
166+
}
161167
if (prop.key.name === 'defineConsts') {
162168
state.stylexDefineConstsImport.add(value.name);
163169
}

0 commit comments

Comments
 (0)