diff --git a/packages/css-data/src/parse-css-value.test.ts b/packages/css-data/src/parse-css-value.test.ts index edecb1c288..deb933e0ed 100644 --- a/packages/css-data/src/parse-css-value.test.ts +++ b/packages/css-data/src/parse-css-value.test.ts @@ -105,6 +105,27 @@ describe("Parse CSS value", () => { }); }); + describe("Tuples", () => { + test("backgroundPosition", () => { + expect(parseCssValue("backgroundPosition", "left top")) + .toMatchInlineSnapshot(` +{ + "type": "tuple", + "value": [ + { + "type": "keyword", + "value": "left", + }, + { + "type": "keyword", + "value": "top", + }, + ], +} +`); + }); + }); + describe("Colors", () => { test("Color rgba values", () => { expect(parseCssValue("backgroundColor", "rgba(0,0,0,0)")).toEqual({ diff --git a/packages/css-data/src/parse-css-value.ts b/packages/css-data/src/parse-css-value.ts index fb7cc7a9de..5a78b1f09e 100644 --- a/packages/css-data/src/parse-css-value.ts +++ b/packages/css-data/src/parse-css-value.ts @@ -2,7 +2,12 @@ import { colord } from "colord"; import * as csstree from "css-tree"; import hyphenate from "hyphenate-style-name"; import warnOnce from "warn-once"; -import type { StyleProperty, StyleValue, Unit } from "@webstudio-is/css-engine"; +import { + TupleValue, + type StyleProperty, + type StyleValue, + type Unit, +} from "@webstudio-is/css-engine"; import { keywordValues } from "./__generated__/keyword-values"; import { units } from "./__generated__/units"; @@ -150,6 +155,27 @@ export const parseCssValue = ( } } + // Probably a tuple like background-position + if (ast != null && ast.type === "Value" && ast.children.size === 2) { + const tupleFirst = parseCssValue( + property, + csstree.generate(ast.children.first!) + ); + const tupleLast = parseCssValue( + property, + csstree.generate(ast.children.last!) + ); + + const tupleResult = TupleValue.safeParse({ + type: "tuple", + value: [tupleFirst, tupleLast], + }); + + if (tupleResult.success) { + return tupleResult.data; + } + } + return { type: "unparsed", value: input, diff --git a/packages/css-data/src/tailwind-parser/parse.test.ts b/packages/css-data/src/tailwind-parser/parse.test.ts index 31c59b111d..67d767f4b8 100644 --- a/packages/css-data/src/tailwind-parser/parse.test.ts +++ b/packages/css-data/src/tailwind-parser/parse.test.ts @@ -19,8 +19,11 @@ Quick Validation of Generated CSS in WebStudio: { "property": "backgroundImage", "value": { - "type": "unparsed", - "value": "linear-gradient(to right,rgba(99,102,241,1) 0%,rgba(99,102,241,0) 100%)" + "type": "layers", + "value": [{ + type: "unparsed", + value: "linear-gradient(to right,rgba(99,102,241,1) 0%,rgba(99,102,241,0) 100%)" + }] } } ], @@ -75,7 +78,7 @@ describe("parseTailwindToWebstudio", () => { }); test("substitute variables - gradient", async () => { - const tailwindClasses = `bg-gradient-to-r from-indigo-500`; + const tailwindClasses = `bg-left-top bg-gradient-to-r from-indigo-500 from-10% via-sky-500 via-30% to-emerald-500 to-90%`; expect(await parseTailwindToWebstudio(tailwindClasses)) .toMatchInlineSnapshot(` @@ -83,8 +86,34 @@ describe("parseTailwindToWebstudio", () => { { "property": "backgroundImage", "value": { - "type": "unparsed", - "value": "linear-gradient(to right,rgba(99,102,241,1) 0%,rgba(99,102,241,0) 100%)", + "type": "layers", + "value": [ + { + "type": "unparsed", + "value": "linear-gradient(to right,rgba(99,102,241,1) 10%,rgba(14,165,233,1) 30%,rgba(16,185,129,1) 90%)", + }, + ], + }, + }, + { + "property": "backgroundPosition", + "value": { + "type": "layers", + "value": [ + { + "type": "tuple", + "value": [ + { + "type": "keyword", + "value": "left", + }, + { + "type": "keyword", + "value": "top", + }, + ], + }, + ], }, }, ] diff --git a/packages/css-data/src/tailwind-parser/parse.ts b/packages/css-data/src/tailwind-parser/parse.ts index 802b461e33..f459091d88 100644 --- a/packages/css-data/src/tailwind-parser/parse.ts +++ b/packages/css-data/src/tailwind-parser/parse.ts @@ -7,7 +7,7 @@ import { expandTailwindShorthand } from "./shorthand"; import { substituteVariables } from "./substitute"; import warnOnce from "warn-once"; import { parseCssValue } from "../parse-css-value"; -import { type StyleProperty } from "@webstudio-is/css-engine"; +import { LayersValue, type StyleProperty } from "@webstudio-is/css-engine"; let unoLazy: UnoGenerator | undefined = undefined; @@ -55,6 +55,48 @@ const parseCssToWebstudio = (css: string) => { return styles; }; +/** + * In WebStudio, background-related properties are managed using a specialized "layer" type. + **/ +const postprocessBackgrounds = ( + styles: EmbedTemplateStyleDecl[], + warn = warnOnce +) => { + const backgroundProps = [ + "backgroundAttachment", + "backgroundClip", + "backgroundBlendMode", + "backgroundImage", + "backgroundOrigin", + "backgroundPosition", + "backgroundRepeat", + "backgroundSize", + ]; + + return styles.map((style) => { + if (backgroundProps.includes(style.property)) { + const layersResult = LayersValue.safeParse({ + type: "layers", + value: [style.value], + }); + + if (layersResult.success) { + return { + property: style.property, + value: layersResult.data, + }; + } + warn( + true, + `Failed to convert background property ${ + style.property + } with value ${JSON.stringify(style.value)} to layers` + ); + } + return style; + }); +}; + /** * Parses Tailwind classes to webstudio template format. */ @@ -63,6 +105,9 @@ export const parseTailwindToWebstudio = async ( warn = warnOnce ) => { const css = await parseTailwindToCss(classes, warn); - const styles = parseCssToWebstudio(css); + let styles = parseCssToWebstudio(css); + // postprocessing + styles = postprocessBackgrounds(styles, warn); + return styles; };