Skip to content

Commit 2f269f8

Browse files
committed
fix: sort mixed min and max breakpoints properly
Added a couple of tests with style engine to figure out proper sorting.
1 parent 8d05217 commit 2f269f8

File tree

6 files changed

+140
-20
lines changed

6 files changed

+140
-20
lines changed

apps/builder/app/builder/features/style-panel/shared/model.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,8 @@ const $model = computed(
240240
}
241241
);
242242

243+
export { $model as _$model };
244+
243245
export const $computedStyleDeclarations = computed(
244246
[
245247
$model,

apps/builder/app/shared/copy-paste/plugin-webflow/plugin-webflow.test.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3003,6 +3003,11 @@ describe("Styles", () => {
30033003
background-color: rgba(0, 208, 255, 1)
30043004
}
30053005
}
3006+
@media all and (min-width: 1280px) {
3007+
Div Block 2 {
3008+
background-color: rgba(0, 255, 128, 1)
3009+
}
3010+
}
30063011
@media all and (max-width: 991px) {
30073012
Div Block 2 {
30083013
background-color: rgba(68, 0, 255, 1)
@@ -3017,11 +3022,6 @@ describe("Styles", () => {
30173022
Div Block 2 {
30183023
background-color: rgba(255, 0, 4, 1)
30193024
}
3020-
}
3021-
@media all and (min-width: 1280px) {
3022-
Div Block 2 {
3023-
background-color: rgba(0, 255, 128, 1)
3024-
}
30253025
}"
30263026
`);
30273027
});

apps/builder/app/shared/style-object-model.test.tsx

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ import {
1717
getComputedStyleDecl,
1818
getPresetStyleDeclKey,
1919
} from "./style-object-model";
20+
import {
21+
$breakpoints,
22+
$selectedBreakpointId,
23+
$styles,
24+
$styleSourceSelections,
25+
} from "./nano-states";
26+
import { _$model } from "~/builder/features/style-panel/shared/model";
2027

2128
/**
2229
* Create model fixture with a few features
@@ -1608,3 +1615,101 @@ describe("style value source", () => {
16081615
});
16091616
});
16101617
});
1618+
1619+
describe("compute matching breakpoints", () => {
1620+
test("min-width only breakpoints", () => {
1621+
const model = createModel({
1622+
css: `
1623+
@media mobile {
1624+
bodyLocal {
1625+
color: red;
1626+
}
1627+
}
1628+
`,
1629+
jsx: <$.Body ws:id="body" ws:tag="body" class="bodyLocal"></$.Body>,
1630+
});
1631+
$styles.set(model.styles);
1632+
$styleSourceSelections.set(model.styleSourceSelections);
1633+
$breakpoints.set(
1634+
new Map([
1635+
["desktop", { id: "desktop", label: "", minWidth: 991 }],
1636+
["mobile", { id: "mobile", label: "", minWidth: 479 }],
1637+
["base", { id: "base", label: "" }],
1638+
])
1639+
);
1640+
$selectedBreakpointId.set("desktop");
1641+
const computedStyleDecl = getComputedStyleDecl({
1642+
model: _$model.get(),
1643+
instanceSelector: ["body"],
1644+
property: "color",
1645+
});
1646+
expect(computedStyleDecl.computedValue).toEqual({
1647+
type: "keyword",
1648+
value: "red",
1649+
});
1650+
});
1651+
1652+
test("max-width only breakpoints", () => {
1653+
const model = createModel({
1654+
css: `
1655+
@media mobile {
1656+
bodyLocal {
1657+
color: red;
1658+
}
1659+
}
1660+
`,
1661+
jsx: <$.Body ws:id="body" ws:tag="body" class="bodyLocal"></$.Body>,
1662+
});
1663+
$styles.set(model.styles);
1664+
$styleSourceSelections.set(model.styleSourceSelections);
1665+
$breakpoints.set(
1666+
new Map([
1667+
["base", { id: "base", label: "" }],
1668+
["mobile", { id: "mobile", label: "", maxWidth: 479 }],
1669+
["desktop", { id: "desktop", label: "", maxWidth: 991 }],
1670+
])
1671+
);
1672+
$selectedBreakpointId.set("desktop");
1673+
const computedStyleDecl = getComputedStyleDecl({
1674+
model: _$model.get(),
1675+
instanceSelector: ["body"],
1676+
property: "color",
1677+
});
1678+
expect(computedStyleDecl.computedValue).toEqual({
1679+
type: "keyword",
1680+
value: "black",
1681+
});
1682+
});
1683+
1684+
test("mixed min-width and max-width breakpoints", () => {
1685+
const model = createModel({
1686+
css: `
1687+
@media mobile {
1688+
bodyLocal {
1689+
color: red;
1690+
}
1691+
}
1692+
`,
1693+
jsx: <$.Body ws:id="body" ws:tag="body" class="bodyLocal"></$.Body>,
1694+
});
1695+
$styles.set(model.styles);
1696+
$styleSourceSelections.set(model.styleSourceSelections);
1697+
$breakpoints.set(
1698+
new Map([
1699+
["desktop", { id: "desktop", label: "", minWidth: 991 }],
1700+
["base", { id: "base", label: "" }],
1701+
["mobile", { id: "mobile", label: "", maxWidth: 479 }],
1702+
])
1703+
);
1704+
$selectedBreakpointId.set("desktop");
1705+
const computedStyleDecl = getComputedStyleDecl({
1706+
model: _$model.get(),
1707+
instanceSelector: ["body"],
1708+
property: "color",
1709+
});
1710+
expect(computedStyleDecl.computedValue).toEqual({
1711+
type: "keyword",
1712+
value: "black",
1713+
});
1714+
});
1715+
});

packages/css-engine/src/core/compare-media.test.ts

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,25 +41,31 @@ describe("Compare media", () => {
4141
});
4242

4343
test("mixed max and min", () => {
44-
const initial = [
44+
expect(
45+
[
46+
{},
47+
{ maxWidth: 991 },
48+
{ maxWidth: 479 },
49+
{ maxWidth: 767 },
50+
{ minWidth: 1440 },
51+
{ minWidth: 1280 },
52+
{ minWidth: 1920 },
53+
].toSorted(compareMedia)
54+
).toStrictEqual([
4555
{},
46-
{ maxWidth: 991 },
47-
{ maxWidth: 479 },
48-
{ maxWidth: 767 },
49-
{ minWidth: 1440 },
5056
{ minWidth: 1280 },
57+
{ minWidth: 1440 },
5158
{ minWidth: 1920 },
52-
];
53-
const expected = [
54-
{},
5559
{ maxWidth: 991 },
5660
{ maxWidth: 767 },
5761
{ maxWidth: 479 },
58-
{ minWidth: 1280 },
59-
{ minWidth: 1440 },
60-
{ minWidth: 1920 },
61-
];
62-
const sorted = initial.sort(compareMedia);
63-
expect(sorted).toStrictEqual(expected);
62+
]);
63+
// test both directions of sorting
64+
expect(
65+
[{ maxWidth: 479 }, { minWidth: 991 }, {}].toSorted(compareMedia)
66+
).toStrictEqual([{}, { minWidth: 991 }, { maxWidth: 479 }]);
67+
expect(
68+
[{ minWidth: 991 }, {}, { maxWidth: 479 }].toSorted(compareMedia)
69+
).toStrictEqual([{}, { minWidth: 991 }, { maxWidth: 479 }]);
6470
});
6571
});

packages/css-engine/src/core/compare-media.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ export const compareMedia = (
2525
return optionB.maxWidth - optionA.maxWidth;
2626
}
2727

28+
if (optionA.minWidth !== undefined && optionB.maxWidth !== undefined) {
29+
return optionB.maxWidth - optionA.minWidth;
30+
}
31+
if (optionA.maxWidth !== undefined && optionB.minWidth !== undefined) {
32+
return optionB.minWidth - optionA.maxWidth;
33+
}
34+
2835
// Media with maxWith should render before minWith just to have the same sorting visually in the UI as in CSSOM.
2936
return "minWidth" in optionA ? 1 : -1;
3037
};

packages/tsconfig/base.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"display": "Default",
44
"compilerOptions": {
55
"module": "ES2022",
6-
"target": "ES2022",
6+
"target": "ES2023",
77
"esModuleInterop": true,
88
"forceConsistentCasingInFileNames": true,
99
"inlineSources": false,

0 commit comments

Comments
 (0)