Skip to content

Commit 22e8f2b

Browse files
authored
experimental: parse length and color in css variables (#4204)
Ref #3399 Here improved parsing to automatically infer unit and color style values in css custom properties. This will let us in the future leverage strictly typed custom properties for interpolation in animations. Now the benefit is proper ui and filtering out unit in color pickers and color in length inputs. Not implemented though yet. ![Screenshot 2024-10-03 at 13 31 20](https://github.com/user-attachments/assets/b32db3d3-8589-4cd4-b375-fb2036a308a0)
1 parent 1a4f663 commit 22e8f2b

File tree

6 files changed

+137
-24
lines changed

6 files changed

+137
-24
lines changed

apps/builder/app/builder/features/style-panel/shared/css-value-input/parse-intermediate-or-invalid-value.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,6 @@ export const parseIntermediateOrInvalidValue = (
2020
value = value.slice(0, -1);
2121
}
2222

23-
if (property.startsWith("--")) {
24-
return {
25-
type: "unparsed",
26-
value,
27-
};
28-
}
29-
3023
// When user enters a number, we don't know if its a valid unit value,
3124
// so we are going to parse it with a unit and if its not invalid - we take it.
3225
const ast = parse(value, { context: "value" });

apps/builder/app/builder/features/style-panel/shared/css-value-input/parse-intermediate-or-invalid-value.ts.test.ts

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -534,13 +534,75 @@ describe("Colors", () => {
534534
});
535535
});
536536

537-
test("parse css vars", () => {
538-
const result = parseIntermediateOrInvalidValue("color", {
539-
type: "intermediate",
540-
value: "var(--color)",
541-
});
542-
expect(result).toEqual({
537+
test("parse css variable reference", () => {
538+
expect(
539+
parseIntermediateOrInvalidValue("color", {
540+
type: "intermediate",
541+
value: "var(--color)",
542+
})
543+
).toEqual({
543544
type: "var",
544545
value: "color",
545546
});
546547
});
548+
549+
test("parse unit in css variable", () => {
550+
expect(
551+
parseIntermediateOrInvalidValue("--size", {
552+
type: "intermediate",
553+
value: "10px",
554+
})
555+
).toEqual({
556+
type: "unit",
557+
value: 10,
558+
unit: "px",
559+
});
560+
expect(
561+
parseIntermediateOrInvalidValue("--size", {
562+
type: "intermediate",
563+
value: "10",
564+
unit: "px",
565+
})
566+
).toEqual({
567+
type: "unit",
568+
value: 10,
569+
unit: "px",
570+
});
571+
});
572+
573+
test("parse color in css variable", () => {
574+
expect(
575+
parseIntermediateOrInvalidValue("--size", {
576+
type: "intermediate",
577+
value: "#0f0f0f",
578+
})
579+
).toEqual({
580+
type: "rgb",
581+
r: 15,
582+
g: 15,
583+
b: 15,
584+
alpha: 1,
585+
});
586+
});
587+
588+
test("parse css variables as unparsed", () => {
589+
expect(
590+
parseIntermediateOrInvalidValue("--size", {
591+
type: "intermediate",
592+
value: "url(https://my-image.com)",
593+
})
594+
).toEqual({
595+
type: "unparsed",
596+
value: "url(https://my-image.com)",
597+
});
598+
expect(
599+
parseIntermediateOrInvalidValue("--size", {
600+
type: "intermediate",
601+
value: "url(https://my-image.com)",
602+
unit: "px",
603+
})
604+
).toEqual({
605+
type: "unparsed",
606+
value: "url(https://my-image.com)",
607+
});
608+
});

apps/builder/app/builder/features/style-panel/shared/css-value-input/unit-select-options.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ export const buildOptions = (
4848

4949
const options: UnitOption[] = [];
5050

51-
if (property in properties === false) {
52-
return options;
53-
}
54-
const { unitGroups } = properties[property as keyof typeof properties];
51+
// show at least current unit when no property meta is available
52+
// for example in custom properties
53+
const unitGroups =
54+
properties[property as keyof typeof properties]?.unitGroups ?? [];
5555

5656
for (const unitGroup of unitGroups) {
5757
if (unitGroup === "number") {

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
type ComputedStyleDecl,
3333
type StyleObjectModel,
3434
} from "~/shared/style-object-model";
35+
import type { InstanceSelector } from "~/shared/tree-utils";
3536

3637
const $presetStyles = computed($registeredComponentMetas, (metas) => {
3738
const presetStyles = new Map<string, StyleValue>();
@@ -106,7 +107,10 @@ export const $definedStyles = computed(
106107
if (instanceSelector === undefined) {
107108
return definedProperties;
108109
}
109-
const instanceAndRootSelector = [...instanceSelector, ROOT_INSTANCE_ID];
110+
const instanceAndRootSelector =
111+
instanceSelector[0] === ROOT_INSTANCE_ID
112+
? instanceSelector
113+
: [...instanceSelector, ROOT_INSTANCE_ID];
110114
const inheritedStyleSources = new Set();
111115
const instanceStyleSources = new Set();
112116
const matchingBreakpoints = new Set(matchingBreakpointsArray);
@@ -180,9 +184,13 @@ export const createComputedStyleDeclStore = (property: StyleProperty) => {
180184
return computed(
181185
[$model, $selectedInstanceSelector, $selectedOrLastStyleSourceSelector],
182186
(model, instanceSelector, styleSourceSelector) => {
183-
const instanceAndRootSelector = instanceSelector
184-
? [...instanceSelector, ROOT_INSTANCE_ID]
185-
: undefined;
187+
let instanceAndRootSelector: undefined | InstanceSelector;
188+
if (instanceSelector) {
189+
instanceAndRootSelector =
190+
instanceSelector[0] === ROOT_INSTANCE_ID
191+
? instanceSelector
192+
: [...instanceSelector, ROOT_INSTANCE_ID];
193+
}
186194
return getComputedStyleDecl({
187195
model,
188196
instanceSelector: instanceAndRootSelector,

packages/css-data/src/parse-css-value.test.ts

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -531,9 +531,9 @@ test("support custom properties as unparsed values", () => {
531531
type: "unparsed",
532532
value: "blue",
533533
});
534-
expect(parseCssValue("--my-property", "1px")).toEqual({
534+
expect(parseCssValue("--my-property", "url(https://my-image.com)")).toEqual({
535535
type: "unparsed",
536-
value: "1px",
536+
value: "url(https://my-image.com)",
537537
});
538538
});
539539

@@ -564,6 +564,52 @@ test("support deeply nested var reference", () => {
564564
});
565565
});
566566

567+
test("support unit in custom property", () => {
568+
expect(parseCssValue("--size", "10")).toEqual({
569+
type: "unit",
570+
value: 10,
571+
unit: "number",
572+
});
573+
expect(parseCssValue("--size", "10px")).toEqual({
574+
type: "unit",
575+
value: 10,
576+
unit: "px",
577+
});
578+
expect(parseCssValue("--size", "10%")).toEqual({
579+
type: "unit",
580+
value: 10,
581+
unit: "%",
582+
});
583+
});
584+
585+
test("support color in custom property", () => {
586+
expect(parseCssValue("--color", "rgb(61 77 4)")).toEqual({
587+
type: "rgb",
588+
r: 61,
589+
g: 77,
590+
b: 4,
591+
alpha: 1,
592+
});
593+
expect(parseCssValue("--color", "rgba(61, 77, 4, 0.5)")).toEqual({
594+
type: "rgb",
595+
r: 61,
596+
g: 77,
597+
b: 4,
598+
alpha: 0.5,
599+
});
600+
expect(parseCssValue("--color", "#3d4d04")).toEqual({
601+
type: "rgb",
602+
r: 61,
603+
g: 77,
604+
b: 4,
605+
alpha: 1,
606+
});
607+
expect(parseCssValue("--color", "red")).toEqual({
608+
type: "unparsed",
609+
value: "red",
610+
});
611+
});
612+
567613
test("support custom properties var reference in custom property", () => {
568614
expect(parseCssValue("--bg", "var(--color)")).toEqual({
569615
type: "var",

packages/css-data/src/parse-css-value.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,11 @@ export const parseCssValue = (
456456
);
457457
// parse only references in custom properties
458458
if (property.startsWith("--")) {
459-
if (matchedValue?.type === "var") {
459+
if (
460+
matchedValue?.type === "var" ||
461+
matchedValue?.type === "unit" ||
462+
matchedValue?.type === "rgb"
463+
) {
460464
return matchedValue;
461465
}
462466
return { type: "unparsed", value: input };

0 commit comments

Comments
 (0)