Skip to content

Commit 5638794

Browse files
committed
Add support for @position-try rules types
1 parent 383bf1e commit 5638794

31 files changed

+796
-350
lines changed

.changeset/minions-chips-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 `@position-try` rules

fixtures/features/src/features.css.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,14 @@ export const styleVariantsCompositionInSelector = styleVariants({
6060
globalStyle(`body ${styleVariantsCompositionInSelector.variant}`, {
6161
fontSize: '24px',
6262
});
63+
64+
// Style with position-try
65+
export const styleWithPositionTry = style({
66+
backgroundColor: 'black',
67+
'@position-try': {
68+
'--custom-left': {
69+
width: '100px',
70+
margin: '0 10px 0 0',
71+
},
72+
},
73+
});

fixtures/features/src/html.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import * as styles from './features.css';
21
import testNodes from '../test-nodes.json';
2+
import * as styles from './features.css';
33

44
export default `
55
<div id="${testNodes.mergedStyle}" class="${styles.mergedStyle}">Merged style</div>
@@ -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.styleWithPositionTry}" class="${styles.styleVariantsCompositionInSelector.variant}">Style with @position-try</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+
"styleWithPositionTry": "styleWithPositionTry"
910
}

fixtures/next-app-router/tsconfig.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"name": "next"
99
}
1010
],
11-
"strictNullChecks": true
11+
"strictNullChecks": true,
12+
"module": "esnext"
1213
},
1314
"include": [
1415
"next-env.d.ts",
@@ -17,5 +18,7 @@
1718
".next/types/**/*.ts",
1819
"dist/types/**/*.ts"
1920
],
20-
"exclude": ["node_modules"]
21+
"exclude": [
22+
"node_modules"
23+
]
2124
}

fixtures/next-pages-router/tsconfig.json

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,16 @@
33
"compilerOptions": {
44
"forceConsistentCasingInFileNames": true,
55
"jsx": "preserve",
6-
"strictNullChecks": true
6+
"strictNullChecks": true,
7+
"module": "esnext",
8+
"moduleResolution": "node"
79
},
8-
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
9-
"exclude": ["node_modules"]
10+
"include": [
11+
"next-env.d.ts",
12+
"**/*.ts",
13+
"**/*.tsx"
14+
],
15+
"exclude": [
16+
"node_modules"
17+
]
1018
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"test:unit": "pnpm test:jest && pnpm test:vitest",
1717
"test:jest": "cross-env NODE_OPTIONS=--experimental-vm-modules jest",
1818
"test:vitest": "vitest --watch=false",
19-
"test:playwright": "NODE_OPTIONS=--no-experimental-fetch pnpm test:build-next && playwright test",
19+
"test:playwright": "NODE_OPTIONS=--no-experimental-fetch pnpm test:build-next && playwright test --update-snapshots",
2020
"test:build-next": "tsx scripts/copy-next-plugin && pnpm --filter=@fixtures/next-* clean-build",
2121
"format": "prettier --write .",
2222
"lint": "pnpm run '/^lint:.*/'",

packages/css/src/transformCss.test.ts

Lines changed: 282 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { setFileScope, endFileScope } from './fileScope';
2-
import { createVar } from './vars';
3-
import { transformCss } from './transformCss';
1+
import { endFileScope, setFileScope } from './fileScope';
42
import { style } from './style';
3+
import { transformCss } from './transformCss';
4+
import { createVar } from './vars';
55

66
setFileScope('test');
77

@@ -2500,6 +2500,285 @@ describe('transformCss', () => {
25002500
}
25012501
`);
25022502
});
2503+
2504+
it('should handle @position-try declaration', () => {
2505+
expect(
2506+
transformCss({
2507+
composedClassLists: [],
2508+
localClassNames: ['testClass'],
2509+
cssObjs: [
2510+
{
2511+
type: 'local',
2512+
selector: 'testClass',
2513+
rule: {
2514+
display: 'flex',
2515+
'@position-try': {
2516+
'--custom-left': {
2517+
width: '100px',
2518+
margin: '0 10px 0 0',
2519+
},
2520+
},
2521+
},
2522+
},
2523+
],
2524+
}).join('\n'),
2525+
).toMatchInlineSnapshot(`
2526+
.testClass {
2527+
display: flex;
2528+
}
2529+
@position-try --custom-left {
2530+
.testClass {
2531+
width: 100px;
2532+
margin: 0 10px 0 0;
2533+
}
2534+
}
2535+
`);
2536+
});
2537+
2538+
it('should multiple custom position inside @position-try declaration', () => {
2539+
expect(
2540+
transformCss({
2541+
composedClassLists: [],
2542+
localClassNames: ['testClass'],
2543+
cssObjs: [
2544+
{
2545+
type: 'local',
2546+
selector: 'testClass',
2547+
rule: {
2548+
display: 'flex',
2549+
'@position-try': {
2550+
'--custom-left': {
2551+
width: '100px',
2552+
margin: '0 10px 0 0',
2553+
},
2554+
'--custom-right': {
2555+
width: '100px',
2556+
margin: '0 10px 0 0',
2557+
},
2558+
},
2559+
},
2560+
},
2561+
],
2562+
}).join('\n'),
2563+
).toMatchInlineSnapshot(`
2564+
.testClass {
2565+
display: flex;
2566+
}
2567+
@position-try --custom-left {
2568+
.testClass {
2569+
width: 100px;
2570+
margin: 0 10px 0 0;
2571+
}
2572+
}
2573+
@position-try --custom-right {
2574+
.testClass {
2575+
width: 100px;
2576+
margin: 0 10px 0 0;
2577+
}
2578+
}
2579+
`);
2580+
});
2581+
2582+
it('should handle @position-try inside media queries', () => {
2583+
expect(
2584+
transformCss({
2585+
composedClassLists: [],
2586+
localClassNames: ['testClass'],
2587+
cssObjs: [
2588+
{
2589+
type: 'local',
2590+
selector: 'testClass',
2591+
rule: {
2592+
display: 'flex',
2593+
'@media': {
2594+
'screen and (min-width: 700px)': {
2595+
'@position-try': {
2596+
'--custom-left': {
2597+
width: '100px',
2598+
margin: '0 10px 0 0',
2599+
},
2600+
},
2601+
},
2602+
},
2603+
},
2604+
},
2605+
],
2606+
}).join('\n'),
2607+
).toMatchInlineSnapshot(`
2608+
.testClass {
2609+
display: flex;
2610+
}
2611+
@media screen and (min-width: 700px) {
2612+
@position-try --custom-left {
2613+
.testClass {
2614+
width: 100px;
2615+
margin: 0 10px 0 0;
2616+
}
2617+
}
2618+
}
2619+
`);
2620+
});
2621+
2622+
it('should handle @position-try inside container queries', () => {
2623+
expect(
2624+
transformCss({
2625+
composedClassLists: [],
2626+
localClassNames: ['testClass'],
2627+
cssObjs: [
2628+
{
2629+
type: 'local',
2630+
selector: 'testClass',
2631+
rule: {
2632+
display: 'flex',
2633+
'@container': {
2634+
'sidebar (min-width: 700px)': {
2635+
'@position-try': {
2636+
'--custom-left': {
2637+
width: '100px',
2638+
margin: '0 10px 0 0',
2639+
},
2640+
},
2641+
},
2642+
},
2643+
},
2644+
},
2645+
],
2646+
}).join('\n'),
2647+
).toMatchInlineSnapshot(`
2648+
.testClass {
2649+
display: flex;
2650+
}
2651+
@container sidebar (min-width: 700px) {
2652+
@position-try --custom-left {
2653+
.testClass {
2654+
width: 100px;
2655+
margin: 0 10px 0 0;
2656+
}
2657+
}
2658+
}
2659+
`);
2660+
});
2661+
2662+
it('should handle @position-try inside a layer', () => {
2663+
expect(
2664+
transformCss({
2665+
composedClassLists: [],
2666+
localClassNames: ['testClass'],
2667+
cssObjs: [
2668+
{
2669+
type: 'local',
2670+
selector: 'testClass',
2671+
rule: {
2672+
'@layer': {
2673+
'mock-layer': {
2674+
'@position-try': {
2675+
'--custom-left': {
2676+
width: '100px',
2677+
margin: '0 10px 0 0',
2678+
},
2679+
},
2680+
},
2681+
},
2682+
},
2683+
},
2684+
],
2685+
}).join('\n'),
2686+
).toMatchInlineSnapshot(`
2687+
@layer mock-layer;
2688+
@layer mock-layer {
2689+
@position-try --custom-left {
2690+
.testClass {
2691+
width: 100px;
2692+
margin: 0 10px 0 0;
2693+
}
2694+
}
2695+
}
2696+
`);
2697+
});
2698+
2699+
it('should throw an error when not using a <dashed-ident> type in @position-try scope', () => {
2700+
expect(() =>
2701+
transformCss({
2702+
composedClassLists: [],
2703+
localClassNames: ['testClass'],
2704+
cssObjs: [
2705+
{
2706+
type: 'local',
2707+
selector: 'testClass',
2708+
rule: {
2709+
display: 'flex',
2710+
'@position-try': {
2711+
// @ts-expect-error This test is to cover the error for non-allowed properties inside @position-try scope
2712+
invalidName: {
2713+
backgroundColor: 'blue',
2714+
},
2715+
},
2716+
},
2717+
},
2718+
],
2719+
}),
2720+
).toThrowErrorMatchingInlineSnapshot(
2721+
'Invalid @position-try name: "invalidName". Position names must follow the <dashed-ident> type (--custom-name).',
2722+
);
2723+
});
2724+
2725+
it('should throw an error when using a non allowed property inside @position-try scope', () => {
2726+
expect(() =>
2727+
transformCss({
2728+
composedClassLists: [],
2729+
localClassNames: ['testClass'],
2730+
cssObjs: [
2731+
{
2732+
type: 'local',
2733+
selector: 'testClass',
2734+
rule: {
2735+
display: 'flex',
2736+
'@position-try': {
2737+
'--custom-left': {
2738+
// @ts-expect-error This test is to cover the error for non-allowed properties inside @position-try scope
2739+
backgroundColor: 'blue',
2740+
},
2741+
},
2742+
},
2743+
},
2744+
],
2745+
}),
2746+
).toThrowErrorMatchingInlineSnapshot(
2747+
'Invalid properties in @position-try --custom-left rule: backgroundColor. Only inset, margin, sizing, self-alignment, position-anchor, and position-area properties are allowed.',
2748+
);
2749+
});
2750+
2751+
it('should throw an error when a at-rule is use inside @position-try scope', () => {
2752+
expect(() =>
2753+
transformCss({
2754+
composedClassLists: [],
2755+
localClassNames: ['testClass'],
2756+
cssObjs: [
2757+
{
2758+
type: 'local',
2759+
selector: 'testClass',
2760+
rule: {
2761+
display: 'flex',
2762+
'@position-try': {
2763+
'--custom-left': {
2764+
width: '100px',
2765+
margin: '0 10px 0 0',
2766+
// @ts-expect-error This test is to cover the error when a at-rule is used inside @position-try scope
2767+
'@media': {
2768+
'screen and (min-width: 700px)': {
2769+
display: 'grid',
2770+
},
2771+
},
2772+
},
2773+
},
2774+
},
2775+
},
2776+
],
2777+
}),
2778+
).toThrowErrorMatchingInlineSnapshot(
2779+
'Nested at-rules (e.g. "@media") are not allowed inside @position-try block.',
2780+
);
2781+
});
25032782
});
25042783

25052784
endFileScope();

0 commit comments

Comments
 (0)