Skip to content

Commit ec4bbd9

Browse files
committed
Add transform function for @starting-style
1 parent 4074e47 commit ec4bbd9

30 files changed

+1526
-1286
lines changed

.changeset/tender-beans-cheat.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@vanilla-extract/css': minor
3+
---
4+
5+
Add support for `@position-try` rules

.zed/settings.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Folder-specific settings
2+
//
3+
// For a full list of overridable settings, and general information on folder-specific settings,
4+
// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
5+
{
6+
"code_actions_on_format": {
7+
"source.fixAll": true,
8+
"source.organizeImports": false
9+
}
10+
}

fixtures/features/src/features.css.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,11 @@ export const styleVariantsCompositionInSelector = styleVariants({
6060
globalStyle(`body ${styleVariantsCompositionInSelector.variant}`, {
6161
fontSize: '24px',
6262
});
63+
64+
// Style with starting-style
65+
export const styleWithStartingStyle = style({
66+
backgroundColor: 'black',
67+
'@starting-style': {
68+
backgroundColor: 'white',
69+
},
70+
});

fixtures/features/src/html.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export default `
99
<div id="${testNodes.compositionOnly}" class="${styles.compositionOnly}">Composition only</div>
1010
<div id="${testNodes.styleCompositionInSelector}" class="${styles.styleCompositionInSelector}">Style composition in selector</div>
1111
<div id="${testNodes.styleVariantsCompositionInSelector}" class="${styles.styleVariantsCompositionInSelector.variant}">Style variants composition in selector</div>
12+
<div id="${testNodes.styleWithStartingStyle}" class="${styles.styleWithStartingStyle}">Style with @starting-style rule</div>
1213
`;
1314

1415
// @ts-expect-error Vite env not defined

fixtures/features/test-nodes.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
"styleVariantsWithMappedComposition": "styleVariantsWithMappedComposition",
66
"compositionOnly": "compositionOnly",
77
"styleCompositionInSelector": "styleCompositionInSelector",
8-
"styleVariantsCompositionInSelector": "styleVariantsCompositionInSelector"
8+
"styleVariantsCompositionInSelector": "styleVariantsCompositionInSelector",
9+
"styleWithStartingStyle": "styleWithStartingStyle"
910
}

packages/css/src/transformCss.test.ts

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2500,6 +2500,172 @@ describe('transformCss', () => {
25002500
}
25012501
`);
25022502
});
2503+
2504+
it('should handle @starting-style declaration', () => {
2505+
expect(
2506+
transformCss({
2507+
composedClassLists: [],
2508+
localClassNames: ['testClass'],
2509+
cssObjs: [
2510+
{
2511+
type: 'local',
2512+
selector: 'testClass',
2513+
rule: {
2514+
opacity: 1,
2515+
top: '100%',
2516+
'@starting-style': {
2517+
opacity: 0,
2518+
top: '50%',
2519+
},
2520+
},
2521+
},
2522+
],
2523+
}).join('\n'),
2524+
).toMatchInlineSnapshot(`
2525+
.testClass {
2526+
opacity: 1;
2527+
top: 100%;
2528+
@starting-style {
2529+
opacity: 0;
2530+
top: 50%;
2531+
}
2532+
}
2533+
`);
2534+
});
2535+
2536+
it('should handle @starting-style inside media queries', () => {
2537+
expect(
2538+
transformCss({
2539+
composedClassLists: [],
2540+
localClassNames: ['testClass'],
2541+
cssObjs: [
2542+
{
2543+
type: 'local',
2544+
selector: 'testClass',
2545+
rule: {
2546+
display: 'flex',
2547+
'@media': {
2548+
'screen and (min-width: 700px)': {
2549+
top: '0',
2550+
'@starting-style': {
2551+
top: '100%',
2552+
},
2553+
},
2554+
},
2555+
},
2556+
},
2557+
],
2558+
}).join('\n'),
2559+
).toMatchInlineSnapshot(`
2560+
.testClass {
2561+
display: flex;
2562+
}
2563+
@media screen and (min-width: 700px) {
2564+
.testClass {
2565+
top: 0;
2566+
@starting-style {
2567+
top: 100%;
2568+
}
2569+
}
2570+
}
2571+
`);
2572+
});
2573+
2574+
it('should handle @starting-style inside container queries', () => {
2575+
expect(
2576+
transformCss({
2577+
composedClassLists: [],
2578+
localClassNames: ['testClass'],
2579+
cssObjs: [
2580+
{
2581+
type: 'local',
2582+
selector: 'testClass',
2583+
rule: {
2584+
'@container': {
2585+
'sidebar (min-width: 700px)': {
2586+
top: '0',
2587+
'@starting-style': {
2588+
top: '100%',
2589+
},
2590+
},
2591+
},
2592+
},
2593+
},
2594+
],
2595+
}).join('\n'),
2596+
).toMatchInlineSnapshot(`
2597+
@container sidebar (min-width: 700px) {
2598+
.testClass {
2599+
top: 0;
2600+
@starting-style {
2601+
top: 100%;
2602+
}
2603+
}
2604+
}
2605+
`);
2606+
});
2607+
2608+
it('should handle @starting-style inside a layer', () => {
2609+
expect(
2610+
transformCss({
2611+
composedClassLists: [],
2612+
localClassNames: ['testClass'],
2613+
cssObjs: [
2614+
{
2615+
type: 'local',
2616+
selector: 'testClass',
2617+
rule: {
2618+
'@layer': {
2619+
'mock-layer': {
2620+
top: '0',
2621+
'@starting-style': {
2622+
top: '100%',
2623+
},
2624+
},
2625+
},
2626+
},
2627+
},
2628+
],
2629+
}).join('\n'),
2630+
).toMatchInlineSnapshot(`
2631+
@layer mock-layer;
2632+
@layer mock-layer {
2633+
.testClass {
2634+
top: 0;
2635+
@starting-style {
2636+
top: 100%;
2637+
}
2638+
}
2639+
}
2640+
`);
2641+
});
2642+
2643+
it('should throw an error when a at-rule is use inside @starting-style scope', () => {
2644+
expect(() =>
2645+
transformCss({
2646+
composedClassLists: [],
2647+
localClassNames: ['testClass'],
2648+
cssObjs: [
2649+
{
2650+
type: 'local',
2651+
selector: 'testClass',
2652+
rule: {
2653+
'@starting-style': {
2654+
// @ts-expect-error - Using a media query inside @starting-style for testing purposes
2655+
'@media': {
2656+
'screen and (min-width: 700px)': {
2657+
display: 'grid',
2658+
},
2659+
},
2660+
},
2661+
},
2662+
},
2663+
],
2664+
}),
2665+
).toThrowErrorMatchingInlineSnapshot(
2666+
'Nested at-rules (e.g. "@media") are not allowed inside @starting-style.',
2667+
);
2668+
});
25032669
});
25042670

25052671
endFileScope();

packages/css/src/transformCss.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ class Stylesheet {
188188
this.transformMedia(root, root.rule['@media']);
189189
this.transformSupports(root, root.rule['@supports']);
190190
this.transformContainer(root, root.rule['@container']);
191+
this.transformStartingStyle(root, root.rule['@starting-style']);
191192

192193
this.transformSimplePseudos(root, root.rule);
193194
this.transformSelectors(root, root.rule);
@@ -408,6 +409,11 @@ class Stylesheet {
408409
selectorRule['@container'],
409410
conditions,
410411
);
412+
this.transformStartingStyle(
413+
root,
414+
selectorRule!['@starting-style'],
415+
conditions,
416+
);
411417
});
412418
}
413419

@@ -445,6 +451,11 @@ class Stylesheet {
445451
this.transformLayer(root, mediaRule!['@layer'], conditions);
446452
this.transformSupports(root, mediaRule!['@supports'], conditions);
447453
this.transformContainer(root, mediaRule!['@container'], conditions);
454+
this.transformStartingStyle(
455+
root,
456+
mediaRule!['@starting-style'],
457+
conditions,
458+
);
448459
}
449460
}
450461
}
@@ -481,6 +492,11 @@ class Stylesheet {
481492
this.transformLayer(root, containerRule!['@layer'], conditions);
482493
this.transformSupports(root, containerRule!['@supports'], conditions);
483494
this.transformMedia(root, containerRule!['@media'], conditions);
495+
this.transformStartingStyle(
496+
root,
497+
containerRule!['@starting-style'],
498+
conditions,
499+
);
484500
});
485501
}
486502
}
@@ -516,6 +532,11 @@ class Stylesheet {
516532
this.transformMedia(root, layerRule!['@media'], conditions);
517533
this.transformSupports(root, layerRule!['@supports'], conditions);
518534
this.transformContainer(root, layerRule!['@container'], conditions);
535+
this.transformStartingStyle(
536+
root,
537+
layerRule!['@starting-style'],
538+
conditions,
539+
);
519540
});
520541
}
521542
}
@@ -550,6 +571,11 @@ class Stylesheet {
550571
this.transformLayer(root, supportsRule!['@layer'], conditions);
551572
this.transformMedia(root, supportsRule!['@media'], conditions);
552573
this.transformContainer(root, supportsRule!['@container'], conditions);
574+
this.transformStartingStyle(
575+
root,
576+
supportsRule!['@starting-style'],
577+
conditions,
578+
);
553579
});
554580
}
555581
}
@@ -589,6 +615,42 @@ class Stylesheet {
589615
}
590616
}
591617

618+
transformStartingStyle(
619+
root: CSSStyleBlock | CSSSelectorBlock,
620+
rules: WithQueries<StyleWithSelectors>['@starting-style'],
621+
parentConditions: Array<string> = [],
622+
) {
623+
if (rules) {
624+
// Check if there are any nested at-rule keys inside this block.
625+
// The presence of any key starting with '@' indicates nested queries,
626+
// which are not allowed for @starting-style.
627+
const nestedAtRuleKey = Object.keys(rules).find((key) =>
628+
key.startsWith('@'),
629+
);
630+
if (nestedAtRuleKey) {
631+
throw new Error(
632+
`Nested at-rules (e.g. "${nestedAtRuleKey}") are not allowed inside @starting-style.`,
633+
);
634+
}
635+
636+
const conditions = [...parentConditions, '@starting-style'];
637+
638+
this.addConditionalRule(
639+
{
640+
selector: root.selector,
641+
rule: omit(rules, specialKeys),
642+
},
643+
conditions,
644+
);
645+
646+
// Process any simple pseudos or selectors associated with this style.
647+
if (root.type === 'local') {
648+
this.transformSimplePseudos(root, rules, conditions);
649+
this.transformSelectors(root, rules, conditions);
650+
}
651+
}
652+
}
653+
592654
toCss() {
593655
const css: Array<string> = [];
594656

packages/css/src/types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,16 @@ export type MediaQueries<StyleType> = Query<'@media', StyleType>;
7777
export type FeatureQueries<StyleType> = Query<'@supports', StyleType>;
7878
export type ContainerQueries<StyleType> = Query<'@container', StyleType>;
7979
export type Layers<StyleType> = Query<'@layer', StyleType>;
80+
export type StartingStyleQueries<StyleType> = {
81+
'@starting-style'?: Omit<StyleType, '@starting-style'>;
82+
};
8083

8184
interface AllQueries<StyleType>
8285
extends MediaQueries<StyleType & AllQueries<StyleType>>,
8386
FeatureQueries<StyleType & AllQueries<StyleType>>,
8487
ContainerQueries<StyleType & AllQueries<StyleType>>,
85-
Layers<StyleType & AllQueries<StyleType>> {}
88+
Layers<StyleType & AllQueries<StyleType>>,
89+
StartingStyleQueries<StyleType> {}
8690

8791
export type WithQueries<StyleType> = StyleType & AllQueries<StyleType>;
8892

packages/parcel-transformer/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"author": "mattcompiles",
2020
"license": "MIT",
2121
"dependencies": {
22-
"@parcel/plugin": "^2.7.0",
22+
"@parcel/plugin": "^2.15.0",
2323
"@vanilla-extract/integration": "workspace:^"
2424
}
2525
}

0 commit comments

Comments
 (0)