Skip to content

Commit

Permalink
add support for container queries
Browse files Browse the repository at this point in the history
  • Loading branch information
Zn4rK committed Jan 16, 2024
1 parent 96284bc commit 193546e
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 32 deletions.
5 changes: 5 additions & 0 deletions .changeset/tender-tigers-approve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@navita/engine': patch
---

support for container queries
3 changes: 3 additions & 0 deletions packages/engine/src/helpers/isContainerQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function isContainerQuery(property: string) {
return property.startsWith("@container");
}
24 changes: 21 additions & 3 deletions packages/engine/src/printers/printStyleBlocks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { StyleBlock } from "../types";
import { getPropertyPriority } from "../helpers/getPropertyPriority";
import type { StyleBlock } from "../types";

export function printStyleBlocks(blocks: StyleBlock[]) {
let stylesheet = '';
Expand All @@ -13,12 +13,20 @@ export function printStyleBlocks(blocks: StyleBlock[]) {
(previousStyle.selector !== style.selector) ||
(previousStyle.pseudo !== style.pseudo) ||
(previousStyle.media !== style.media) ||
(previousStyle.support !== style.support)
(previousStyle.support !== style.support) ||
(previousStyle.container !== style.container)
)
) {
stylesheet += '}';
}

// Close container queries:
// 1. When the current style is not a container query, and the previous style was container query
// 2. When the current style is a container query and the previous style was a container query with a different container query
if (previousStyle && previousStyle.container && (!style.container || (style.container !== previousStyle.container))) {
stylesheet += '}';
}

// Close support queries:
// 1. When the current style is not a support query, and the previous style was support query
// 2. When the current style is a support query and the previous style was a support query with a different support query
Expand All @@ -43,6 +51,11 @@ export function printStyleBlocks(blocks: StyleBlock[]) {
stylesheet += `@supports ${style.support}{`;
}

// Only add container queries if the previous style was not the same container query
if (style.container && (previousStyle?.container !== style.container)) {
stylesheet += `@container ${style.container}{`;
}

if (style.type === 'rule') {
const className = `.${style.id}`.repeat(getPropertyPriority(style.property));
stylesheet += `${className}${style.pseudo}{`;
Expand All @@ -52,7 +65,8 @@ export function printStyleBlocks(blocks: StyleBlock[]) {
(previousStyle?.selector !== style.selector) ||
(previousStyle?.pseudo !== style.pseudo) ||
(previousStyle?.media !== style.media) ||
(previousStyle?.support !== style.support)
(previousStyle?.support !== style.support) ||
(previousStyle?.container !== style.container)
)
) {
// If static, we don't add pseudo selectors currently
Expand All @@ -72,6 +86,10 @@ export function printStyleBlocks(blocks: StyleBlock[]) {
previousStyle = style;
}

if (previousStyle?.container) {
stylesheet += '}';
}

if (previousStyle?.support) {
stylesheet += '}';
}
Expand Down
97 changes: 68 additions & 29 deletions packages/engine/src/processStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { StyleRule } from "@navita/types";
import type { Cache } from "./cache";
import { generateCombinedAtRules } from "./helpers/generateCombinedAtRules";
import { hyphenateProperty } from "./helpers/hyphenateProperty";
import { isContainerQuery } from "./helpers/isContainerQuery";
import { isMediaQuery } from "./helpers/isMediaQuery";
import { isNestedSelector } from "./helpers/isNestedSelector";
import { isObject } from "./helpers/isObject";
Expand Down Expand Up @@ -29,12 +30,14 @@ export function processStyles({
pseudo = "",
media = "",
support = "",
container = "",
selector = ""
}: {
styles: StyleRule;
pseudo?: string;
media?: string;
support?: string;
container?: string;
selector?: string;
}) {
const result = [];
Expand All @@ -53,10 +56,15 @@ export function processStyles({
pseudo,
media: combinedMedia,
support,
container,
selector
})
);
} else if (isSupportsQuery(property)) {

continue;
}

if (isSupportsQuery(property)) {
const combinedSupport = generateCombinedAtRules(
support,
property.slice(9).trim()
Expand All @@ -68,10 +76,35 @@ export function processStyles({
pseudo,
media,
support: combinedSupport,
container,
selector
})
);
} else if (isNestedSelector(property)) {

continue;
}

if (isContainerQuery(property)) {
const combinedContainer = generateCombinedAtRules(
container,
property.slice(10).trim()
);

result.push(
...process({
styles: value,
pseudo,
media,
support,
container: combinedContainer,
selector
})
);

continue;
}

if (isNestedSelector(property)) {
// This is only allowed in simple pseudos currently.
const copies = property.split(',').map((p) => p.trim());

Expand All @@ -82,43 +115,49 @@ export function processStyles({
pseudo: pseudo + normalizeNestedProperty(copy),
media,
support,
container,
selector
})
);
}
} else {
console.warn("Unknown property", property);
}
} else {
let newProperty = normalizeCSSVarsProperty(property);
let newValue = value;

if (typeof value === "string") {
newValue = value.trim().replace(/;[\n\s]*$/, "");
newValue = normalizeCSSVarsValue(newValue);
continue;
}

if (typeof value === "number") {
newValue = pixelifyProperties(newProperty, value);
}
console.warn("Unknown property", property);

if (transformValuePropertyMap[newProperty]) {
newValue = transformValuePropertyMap[newProperty](value);
}
continue;
}

newProperty = hyphenateProperty(newProperty);

// Remove trailing semicolon and new lines with regex
result.push(cache.getOrStore({
type,
selector,
property: newProperty,
value: newValue,
pseudo,
media,
support
}));
let newProperty = normalizeCSSVarsProperty(property);
let newValue = value;

if (typeof value === "string") {
newValue = value.trim().replace(/;[\n\s]*$/, "");
newValue = normalizeCSSVarsValue(newValue);
}

if (typeof value === "number") {
newValue = pixelifyProperties(newProperty, value);
}

if (transformValuePropertyMap[newProperty]) {
newValue = transformValuePropertyMap[newProperty](value);
}

newProperty = hyphenateProperty(newProperty);

// Remove trailing semicolon and new lines with regex
result.push(cache.getOrStore({
type,
selector,
property: newProperty,
value: newValue,
pseudo,
media,
support,
container,
}));
}

return result as StyleBlock[];
Expand Down
1 change: 1 addition & 0 deletions packages/engine/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface StyleBlock {
pseudo: string;
media: string;
support: string;
container: string;
id: string | number;
}

Expand Down
62 changes: 62 additions & 0 deletions packages/engine/tests/src/printers/printStyleBlock.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,66 @@ describe('printStyleBlock', () => {
`".a1{margin:10px}.b1.b1{margin-top:10px}"`,
);
});

it('should handle container queries', () => {
const renderer = new Engine();

renderer.addStyle({
'@container (min-width: 100px)': {
background: 'green',
},
});

renderer.addStyle({
'@container named (min-width: 100px)': {
color: 'green',
'@container (min-width: 200px)': {
color: 'red',
},
},
});

// printStyleBlock assumes a sorted array of blocks.
const blocks = sortAtRules(renderer['caches'].rule.items());
const result = printStyleBlocks(blocks);

expect(result).toMatchInlineSnapshot(
`"@container (min-width: 100px){.a1{background:green}}@container named (min-width: 100px){.b1{color:green}}@container named (min-width: 100px) and (min-width: 200px){.b2{color:red}}"`,
);
});

it('should handle all types of at-rules', () => {
const renderer = new Engine();

renderer.addStyle({
'@media (min-width: 100px)': {
background: 'green',
'@supports (display: grid)': {
'@container (min-width: 100px)': {
color: 'green',
},
},
},
});

renderer.addStyle({
'@container (min-width: 100px)': {
background: 'green',
'@media (min-width: 100px)': {
color: 'green',
'@supports (display: grid)': {
color: 'green',
},
},
},
});

// printStyleBlock assumes a sorted array of blocks.
const blocks = sortAtRules(renderer['caches'].rule.items());
const result = printStyleBlocks(blocks);

expect(result).toMatchInlineSnapshot(
`"@media (min-width: 100px){.a1{background:green}@container (min-width: 100px){.d1{color:green}@supports (display: grid){.b1{color:green}}}.c1{background:green}}"`,
);
});
});
30 changes: 30 additions & 0 deletions packages/engine/tests/src/processStyles.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ describe('processStyles', () => {
pseudo: '::after',
media: '(min-width: 400px)',
support: '(display: grid)',
container: '',
}),
expect.objectContaining({
id: 2,
Expand All @@ -399,6 +400,7 @@ describe('processStyles', () => {
pseudo: '::before',
media: '(min-width: 400px)',
support: '(display: grid)',
container: '',
}),
]);
});
Expand Down Expand Up @@ -553,4 +555,32 @@ describe('processStyles', () => {
}),
]);
});

it('should handle container queries', () => {
const processStyles = createProcessStyles({
type: "rule",
});

const result = processStyles({
styles: {
"@container (min-width: 500px)": {
background: "blue",
},
},
});

expect(result).toHaveLength(1);
expect(result).toEqual([
expect.objectContaining({
id: 1,
property: "background",
value: "blue",
pseudo: '',
selector: '',
media: '',
support: '',
container: '(min-width: 500px)',
}),
]);
});
});

0 comments on commit 193546e

Please sign in to comment.