Skip to content

Commit 1eaa63e

Browse files
lfantonevceder
authored andcommitted
Add transform function for @starting-style
--co-authored-by: Viktor Ceder <[email protected]> --co-authored-by: Luciano Fantone <[email protected]>
1 parent 6714033 commit 1eaa63e

29 files changed

+1729
-1343
lines changed

.changeset/tender-beans-cheat.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@vanilla-extract/css': minor
3+
---
4+
5+
`style`: Add support for `@starting-style` rules
6+
**EXAMPLE USAGE**:
7+
```ts
8+
import { style } from '@vanilla-extact/css';
9+
export const styleWithStartingStyle = style({
10+
backgroundColor: 'black',
11+
'@starting-style': {
12+
backgroundColor: 'white',
13+
},
14+
});
15+
``

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: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2500,6 +2500,340 @@ 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 handle @starting-style inside selectors', () => {
2644+
expect(
2645+
transformCss({
2646+
composedClassLists: [],
2647+
localClassNames: ['testClass'],
2648+
cssObjs: [
2649+
{
2650+
type: 'local',
2651+
selector: 'testClass',
2652+
rule: {
2653+
selectors: {
2654+
'&:hover': {
2655+
top: '0',
2656+
'@starting-style': {
2657+
top: '100%',
2658+
},
2659+
},
2660+
},
2661+
},
2662+
},
2663+
],
2664+
}).join('\n'),
2665+
).toMatchInlineSnapshot(`
2666+
.testClass:hover {
2667+
top: 0;
2668+
@starting-style {
2669+
top: 100%;
2670+
}
2671+
}
2672+
`);
2673+
});
2674+
2675+
it('should handle @starting-style inside @supports', () => {
2676+
expect(
2677+
transformCss({
2678+
composedClassLists: [],
2679+
localClassNames: ['testClass'],
2680+
cssObjs: [
2681+
{
2682+
type: 'local',
2683+
selector: 'testClass',
2684+
rule: {
2685+
'@supports': {
2686+
'(display: grid)': {
2687+
top: '0',
2688+
'@starting-style': {
2689+
top: '100%',
2690+
},
2691+
},
2692+
},
2693+
},
2694+
},
2695+
],
2696+
}).join('\n'),
2697+
).toMatchInlineSnapshot(`
2698+
@supports (display: grid) {
2699+
.testClass {
2700+
top: 0;
2701+
@starting-style {
2702+
top: 100%;
2703+
}
2704+
}
2705+
}
2706+
`);
2707+
});
2708+
2709+
it('should process both simple pseudos and selectors inside @starting-style', () => {
2710+
expect(
2711+
transformCss({
2712+
composedClassLists: [],
2713+
localClassNames: ['testClass'],
2714+
cssObjs: [
2715+
{
2716+
type: 'local',
2717+
selector: 'testClass',
2718+
rule: {
2719+
'@starting-style': {
2720+
color: 'green',
2721+
':hover': {
2722+
color: 'red',
2723+
},
2724+
':focus': {
2725+
backgroundColor: 'blue',
2726+
},
2727+
selectors: {
2728+
'&.active': {
2729+
color: 'purple',
2730+
},
2731+
'& + &': {
2732+
marginLeft: '10px',
2733+
},
2734+
},
2735+
},
2736+
},
2737+
},
2738+
],
2739+
}).join('\n'),
2740+
).toMatchInlineSnapshot(`
2741+
.testClass {
2742+
@starting-style {
2743+
color: green;
2744+
:hover {
2745+
color: red;
2746+
}
2747+
:focus {
2748+
background-color: blue;
2749+
}
2750+
selectors {
2751+
&.active {
2752+
color: purple;
2753+
}
2754+
& + & {
2755+
margin-left: 10px;
2756+
}
2757+
}
2758+
}
2759+
}
2760+
`);
2761+
});
2762+
2763+
it('should not process simple pseudos and selectors inside @starting-style for non-local root types', () => {
2764+
expect(
2765+
transformCss({
2766+
composedClassLists: [],
2767+
localClassNames: [],
2768+
cssObjs: [
2769+
{
2770+
type: 'global',
2771+
selector: '.globalClass',
2772+
rule: {
2773+
'@starting-style': {
2774+
color: 'green',
2775+
':hover': {
2776+
color: 'red',
2777+
},
2778+
':focus': {
2779+
backgroundColor: 'blue',
2780+
},
2781+
selectors: {
2782+
'&.active': {
2783+
color: 'purple',
2784+
},
2785+
},
2786+
},
2787+
},
2788+
},
2789+
],
2790+
}).join('\n'),
2791+
).toMatchInlineSnapshot(`
2792+
.globalClass {
2793+
@starting-style {
2794+
color: green;
2795+
:hover {
2796+
color: red;
2797+
}
2798+
:focus {
2799+
background-color: blue;
2800+
}
2801+
selectors {
2802+
&.active {
2803+
color: purple;
2804+
}
2805+
}
2806+
}
2807+
}
2808+
`);
2809+
});
2810+
2811+
it('should throw an error when a at-rule is use inside @starting-style scope', () => {
2812+
expect(() =>
2813+
transformCss({
2814+
composedClassLists: [],
2815+
localClassNames: ['testClass'],
2816+
cssObjs: [
2817+
{
2818+
type: 'local',
2819+
selector: 'testClass',
2820+
rule: {
2821+
'@starting-style': {
2822+
// @ts-expect-error - Using a media query inside @starting-style for testing purposes
2823+
'@media': {
2824+
'screen and (min-width: 700px)': {
2825+
display: 'grid',
2826+
},
2827+
},
2828+
},
2829+
},
2830+
},
2831+
],
2832+
}),
2833+
).toThrowErrorMatchingInlineSnapshot(
2834+
'Nested at-rules (e.g. "@media") are not allowed inside @starting-style.',
2835+
);
2836+
});
25032837
});
25042838

25052839
endFileScope();

0 commit comments

Comments
 (0)