From 2017153253cd0363bb89fbaf4f1dd5f8103b6f57 Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Wed, 26 Apr 2023 14:59:31 +0300 Subject: [PATCH] Migrate preset styles to array of declarations (#1531) Before preset styles used `{ tag: { property: value } }` format. Now we are switching to `{ tag: [{ state,property,value }] }` to support specifying states in preset styles. Same format will be used for embed template styles. Here switched only format. States in preset styles not supported in builder yet. --- .../style-panel/shared/style-info.test.ts | 1 - .../features/style-panel/shared/style-info.ts | 62 ++-- apps/builder/app/canvas/collapsed.ts | 10 +- apps/builder/app/canvas/shared/styles.ts | 17 +- .../src/components/blockquote.ws.tsx | 92 +++-- packages/react-sdk/src/components/body.ws.tsx | 43 +-- packages/react-sdk/src/components/bold.ws.tsx | 9 +- packages/react-sdk/src/components/box.ws.ts | 9 +- .../react-sdk/src/components/button.ws.tsx | 11 +- packages/react-sdk/src/components/code.ws.tsx | 60 ++- .../src/components/component-meta.ts | 7 +- packages/react-sdk/src/components/form.ws.tsx | 20 +- .../react-sdk/src/components/heading.ws.tsx | 9 +- .../react-sdk/src/components/image.ws.tsx | 26 +- .../react-sdk/src/components/input.ws.tsx | 9 +- .../react-sdk/src/components/italic.ws.tsx | 19 +- .../src/components/link-block.ws.tsx | 19 +- packages/react-sdk/src/components/link.ws.tsx | 26 +- .../react-sdk/src/components/list-item.ws.tsx | 9 +- packages/react-sdk/src/components/list.ws.tsx | 53 +-- .../react-sdk/src/components/paragraph.ws.tsx | 9 +- .../react-sdk/src/components/separator.ws.tsx | 51 +-- packages/react-sdk/src/components/span.ws.tsx | 9 +- .../react-sdk/src/components/subscript.ws.tsx | 9 +- .../src/components/superscript.ws.tsx | 9 +- .../src/components/text-block.ws.tsx | 21 +- packages/react-sdk/src/css/css.ts | 19 +- packages/react-sdk/src/css/normalize.ts | 341 ++++++++---------- packages/react-sdk/src/css/presets.ts | 59 ++- packages/react-sdk/src/css/style-rules.ts | 24 ++ packages/react-sdk/src/embed-template.ts | 11 + 31 files changed, 561 insertions(+), 512 deletions(-) diff --git a/apps/builder/app/builder/features/style-panel/shared/style-info.test.ts b/apps/builder/app/builder/features/style-panel/shared/style-info.test.ts index 4067a73c1b..e2a9b3c0b9 100644 --- a/apps/builder/app/builder/features/style-panel/shared/style-info.test.ts +++ b/apps/builder/app/builder/features/style-panel/shared/style-info.test.ts @@ -168,7 +168,6 @@ test("compute inherited styles", () => { ["2", "div"], ["3", "div"], ]), - cascadedBreakpointIds, selectedBreakpointId ) diff --git a/apps/builder/app/builder/features/style-panel/shared/style-info.ts b/apps/builder/app/builder/features/style-panel/shared/style-info.ts index 06d89a7783..9278976820 100644 --- a/apps/builder/app/builder/features/style-panel/shared/style-info.ts +++ b/apps/builder/app/builder/features/style-panel/shared/style-info.ts @@ -187,10 +187,9 @@ export const getCascadedInfo = ( return cascadedStyle; }; -export const getPresetStyle = ( +export const getInstanceComponent = ( instances: Instances, - instanceId: undefined | Instance["id"], - tagName: HtmlTags + instanceId: undefined | Instance["id"] ) => { if (instanceId === undefined) { return; @@ -199,10 +198,22 @@ export const getPresetStyle = ( if (instance === undefined) { return; } - if (tagName === undefined) { + return instance.component; +}; + +export const getPresetStyleRule = (component: string, tagName: HtmlTags) => { + const meta = getComponentMeta(component); + const presetStyles = meta?.presetStyle?.[tagName]; + if (presetStyles === undefined) { return; } - return getComponentMeta(instance.component)?.presetStyle?.[tagName]; + const presetStyle: Style = {}; + for (const styleDecl of presetStyles) { + if (styleDecl.state === undefined) { + presetStyle[styleDecl.property] = styleDecl.value; + } + } + return presetStyle; }; /** @@ -236,16 +247,20 @@ export const getInheritedInfo = ( const tagName = selectedInstanceIntanceToTag.get(instanceId); + const component = getInstanceComponent(instances, instanceId); const presetStyle = - tagName !== undefined - ? getPresetStyle(instances, ancestorInstance.id, tagName) + tagName !== undefined && component !== undefined + ? getComponentMeta(component)?.presetStyle?.[tagName] : undefined; if (presetStyle) { - for (const [styleProperty, styleValue] of Object.entries(presetStyle)) { - if (inheritableProperties.has(styleProperty)) { - inheritedStyle[styleProperty as StyleProperty] = { + for (const styleDecl of presetStyle) { + if ( + styleDecl.state === undefined && + inheritableProperties.has(styleDecl.property) + ) { + inheritedStyle[styleDecl.property] = { instanceId: ancestorInstance.id, - value: styleValue, + value: styleDecl.value, }; } } @@ -256,6 +271,7 @@ export const getInheritedInfo = ( for (const styleDecl of ancestorInstanceStyles) { if ( styleDecl.breakpointId === breakpointId && + styleDecl.state === undefined && inheritableProperties.has(styleDecl.property) ) { inheritedStyle[styleDecl.property] = { @@ -405,10 +421,11 @@ export const useStyleInfo = () => { return; } const tagName = selectedInstanceIntanceToTag?.get(selectedInstanceId); - - return tagName !== undefined - ? getPresetStyle(instances, selectedInstanceSelector?.[0], tagName) - : undefined; + const component = getInstanceComponent(instances, selectedInstanceId); + if (tagName === undefined || component === undefined) { + return; + } + return getPresetStyleRule(component, tagName); }, [instances, selectedInstanceSelector, selectedInstanceIntanceToTag]); const styleInfoData = useMemo(() => { @@ -481,15 +498,16 @@ export const useInstanceStyleData = ( const selectedBreakpointId = selectedBreakpoint?.id; const presetStyle = useMemo(() => { - const selectedInstanceId = instanceSelector?.[0]; - if (selectedInstanceId === undefined) { + const instanceId = instanceSelector?.[0]; + if (instanceId === undefined) { return; } - const tagName = selectedInstanceIntanceToTag?.get(selectedInstanceId); - - return tagName !== undefined - ? getPresetStyle(instances, instanceSelector?.[0], tagName) - : undefined; + const tagName = selectedInstanceIntanceToTag?.get(instanceId); + const component = getInstanceComponent(instances, instanceId); + if (tagName === undefined || component === undefined) { + return; + } + return getPresetStyleRule(component, tagName); }, [instances, instanceSelector, selectedInstanceIntanceToTag]); const cascadedBreakpointIds = useMemo( diff --git a/apps/builder/app/canvas/collapsed.ts b/apps/builder/app/canvas/collapsed.ts index 5f5ea4b25b..c943258bf8 100644 --- a/apps/builder/app/canvas/collapsed.ts +++ b/apps/builder/app/canvas/collapsed.ts @@ -2,7 +2,8 @@ import { collapsedAttribute, idAttribute } from "@webstudio-is/react-sdk"; import { getCascadedBreakpointIds, getCascadedInfo, - getPresetStyle, + getInstanceComponent, + getPresetStyleRule, } from "~/builder/features/style-panel/shared/style-info"; import { breakpointsStore, @@ -63,9 +64,10 @@ const getInstanceSize = (instanceId: string, tagName: HtmlTags | undefined) => { selectedBreakpointId ); + const component = getInstanceComponent(instances, instanceId); const presetStyle = - tagName !== undefined - ? getPresetStyle(instances, instanceId, tagName) + tagName !== undefined && component !== undefined + ? getPresetStyleRule(component, tagName) : undefined; const cascadedStyle = getCascadedInfo(stylesByInstanceId, instanceId, [ @@ -228,7 +230,7 @@ export const setDataCollapsed = (instanceId: string, syncExec = false) => { * In that case we just check the subtree of parent/common ancestor of changed elements to find collapsed elements **/ export const subscribeCollapsedToPubSub = () => - subscribe("sendStoreChanges", ({ source, changes }) => { + subscribe("sendStoreChanges", ({ changes }) => { for (const change of changes) { if (change.namespace === "styleSourceSelections") { for (const patch of change.patches) { diff --git a/apps/builder/app/canvas/shared/styles.ts b/apps/builder/app/canvas/shared/styles.ts index cb39de04d7..7db6b13c03 100644 --- a/apps/builder/app/canvas/shared/styles.ts +++ b/apps/builder/app/canvas/shared/styles.ts @@ -5,13 +5,13 @@ import { useStore } from "@nanostores/react"; import type { Assets } from "@webstudio-is/asset-uploader"; import { collapsedAttribute, - componentAttribute, getComponentMeta, getComponentNames, idAttribute, addGlobalRules, createImageValueTransformer, getParams, + getPresetStyleRules, } from "@webstudio-is/react-sdk"; import type { Instance, StyleDecl } from "@webstudio-is/project-build"; import { @@ -137,15 +137,12 @@ export const GlobalStyles = () => { for (const component of getComponentNames()) { const meta = getComponentMeta(component); const presetStyle = meta?.presetStyle; - if (presetStyle !== undefined) { - for (const [tag, style] of Object.entries(presetStyle)) { - presetStylesEngine.addStyleRule( - `${tag}:where([${componentAttribute}=${component}])`, - { - style, - } - ); - } + if (presetStyle === undefined) { + continue; + } + const rules = getPresetStyleRules(component, presetStyle); + for (const [selector, style] of rules) { + presetStylesEngine.addStyleRule(selector, { style }); } } presetStylesEngine.render(); diff --git a/packages/react-sdk/src/components/blockquote.ws.tsx b/packages/react-sdk/src/components/blockquote.ws.tsx index 97889ff6d6..ad12a17b29 100644 --- a/packages/react-sdk/src/components/blockquote.ws.tsx +++ b/packages/react-sdk/src/components/blockquote.ws.tsx @@ -1,72 +1,62 @@ -import type { Style } from "@webstudio-is/css-data"; import { BlockquoteIcon } from "@webstudio-is/icons"; import type { defaultTag } from "./blockquote"; -import type { WsComponentMeta, WsComponentPropsMeta } from "./component-meta"; +import type { + PresetStyle, + WsComponentMeta, + WsComponentPropsMeta, +} from "./component-meta"; import { props } from "./__generated__/blockquote.props"; const presetStyle = { - blockquote: { - marginTop: { - type: "unit", - value: 0, - unit: "number", + blockquote: [ + { + property: "marginTop", + value: { type: "unit", value: 0, unit: "number" }, }, - marginRight: { - type: "unit", - value: 0, - unit: "number", + { + property: "marginRight", + value: { type: "unit", value: 0, unit: "number" }, }, - marginBottom: { - type: "unit", - value: 10, - unit: "px", + { + property: "marginBottom", + value: { type: "unit", value: 10, unit: "px" }, }, - marginLeft: { - type: "unit", - value: 0, - unit: "number", + { + property: "marginLeft", + value: { type: "unit", value: 0, unit: "number" }, }, - paddingTop: { - type: "unit", - value: 10, - unit: "px", + { + property: "paddingTop", + value: { type: "unit", value: 10, unit: "px" }, }, - paddingBottom: { - type: "unit", - value: 10, - unit: "px", + { + property: "paddingBottom", + value: { type: "unit", value: 10, unit: "px" }, }, - paddingLeft: { - type: "unit", - value: 20, - unit: "px", + { + property: "paddingLeft", + value: { type: "unit", value: 20, unit: "px" }, }, - paddingRight: { - type: "unit", - value: 20, - unit: "px", + { + property: "paddingRight", + value: { type: "unit", value: 20, unit: "px" }, }, - borderLeftWidth: { - type: "unit", - value: 5, - unit: "px", + { + property: "borderLeftWidth", + value: { type: "unit", value: 5, unit: "px" }, }, - borderLeftStyle: { - type: "keyword", - value: "solid", + { + property: "borderLeftStyle", + value: { type: "keyword", value: "solid" }, }, - - borderLeftColor: { - type: "rgb", - r: 226, - g: 226, - b: 226, - alpha: 1, + { + property: "borderLeftColor", + value: { type: "rgb", r: 226, g: 226, b: 226, alpha: 1 }, }, - }, -} as const satisfies Record; + ], +} satisfies PresetStyle; export const meta: WsComponentMeta = { category: "typography", diff --git a/packages/react-sdk/src/components/body.ws.tsx b/packages/react-sdk/src/components/body.ws.tsx index e10c18f9e4..90d3965b07 100644 --- a/packages/react-sdk/src/components/body.ws.tsx +++ b/packages/react-sdk/src/components/body.ws.tsx @@ -1,38 +1,35 @@ import { BodyIcon } from "@webstudio-is/icons"; import { body } from "../css/normalize"; -import type { WsComponentMeta, WsComponentPropsMeta } from "./component-meta"; +import type { + PresetStyle, + WsComponentMeta, + WsComponentPropsMeta, +} from "./component-meta"; import { props } from "./__generated__/body.props"; import type { defaultTag } from "./body"; -import type { Style } from "@webstudio-is/css-data"; const presetStyle = { - body: { + body: [ ...body, - minHeight: { - type: "unit", - unit: "%", - value: 100, + { + property: "minHeight", + value: { type: "unit", unit: "%", value: 100 }, }, - - fontFamily: { - type: "keyword", - value: "Arial", + { + property: "fontFamily", + value: { type: "keyword", value: "Arial" }, }, - - fontSize: { - type: "unit", - unit: "px", - value: 14, + { + property: "fontSize", + value: { type: "unit", unit: "px", value: 14 }, }, - - lineHeight: { - type: "unit", - unit: "number", - value: 1.5, + { + property: "lineHeight", + value: { type: "unit", unit: "number", value: 1.5 }, }, - }, -} as const satisfies Record; + ], +} satisfies PresetStyle; export const meta: WsComponentMeta = { type: "container", diff --git a/packages/react-sdk/src/components/bold.ws.tsx b/packages/react-sdk/src/components/bold.ws.tsx index b571cb7bf1..caa57f810a 100644 --- a/packages/react-sdk/src/components/bold.ws.tsx +++ b/packages/react-sdk/src/components/bold.ws.tsx @@ -1,13 +1,16 @@ import { BoldIcon } from "@webstudio-is/icons"; -import type { WsComponentMeta, WsComponentPropsMeta } from "./component-meta"; +import type { + PresetStyle, + WsComponentMeta, + WsComponentPropsMeta, +} from "./component-meta"; import { props } from "./__generated__/bold.props"; import { b } from "../css/normalize"; -import type { Style } from "@webstudio-is/css-data"; import type { defaultTag } from "./bold"; const presetStyle = { b, -} as const satisfies Record; +} satisfies PresetStyle; export const meta: WsComponentMeta = { type: "rich-text-child", diff --git a/packages/react-sdk/src/components/box.ws.ts b/packages/react-sdk/src/components/box.ws.ts index d8bb87e9c7..74c6594a63 100644 --- a/packages/react-sdk/src/components/box.ws.ts +++ b/packages/react-sdk/src/components/box.ws.ts @@ -1,9 +1,12 @@ import { BoxIcon } from "@webstudio-is/icons"; -import type { WsComponentMeta, WsComponentPropsMeta } from "./component-meta"; +import type { + PresetStyle, + WsComponentMeta, + WsComponentPropsMeta, +} from "./component-meta"; import { props } from "./__generated__/box.props"; import type { Box } from "./box"; import type { ComponentProps } from "react"; -import type { Style } from "@webstudio-is/css-data"; import { div, address, @@ -30,7 +33,7 @@ const presetStyle = { main, nav, section, -} as const satisfies Record; +} satisfies PresetStyle; export const meta: WsComponentMeta = { category: "general", diff --git a/packages/react-sdk/src/components/button.ws.tsx b/packages/react-sdk/src/components/button.ws.tsx index 8812db5230..32be0af45b 100644 --- a/packages/react-sdk/src/components/button.ws.tsx +++ b/packages/react-sdk/src/components/button.ws.tsx @@ -1,13 +1,16 @@ import { ButtonElementIcon } from "@webstudio-is/icons"; -import type { WsComponentMeta, WsComponentPropsMeta } from "./component-meta"; -import { props } from "./__generated__/button.props"; import { button } from "../css/normalize"; +import type { + PresetStyle, + WsComponentMeta, + WsComponentPropsMeta, +} from "./component-meta"; +import { props } from "./__generated__/button.props"; import type { defaultTag } from "./button"; -import type { Style } from "@webstudio-is/css-data"; const presetStyle = { button, -} as const satisfies Record; +} satisfies PresetStyle; export const meta: WsComponentMeta = { category: "forms", diff --git a/packages/react-sdk/src/components/code.ws.tsx b/packages/react-sdk/src/components/code.ws.tsx index b63439d05f..03548c1a3c 100644 --- a/packages/react-sdk/src/components/code.ws.tsx +++ b/packages/react-sdk/src/components/code.ws.tsx @@ -1,44 +1,38 @@ import { CodeIcon } from "@webstudio-is/icons"; -import type { WsComponentMeta, WsComponentPropsMeta } from "./component-meta"; +import type { + PresetStyle, + WsComponentMeta, + WsComponentPropsMeta, +} from "./component-meta"; import { type defaultTag, displayVarNamespace } from "./code"; import { props } from "./__generated__/code.props"; -import type { Style, StyleValue } from "@webstudio-is/css-data"; import { code } from "../css/normalize"; -const display: StyleValue = { - type: "var", - value: displayVarNamespace, - fallbacks: [ +const presetStyle = { + code: [ + ...code, { - type: "keyword", - value: "inline-block", + property: "display", + value: { + type: "var", + value: displayVarNamespace, + fallbacks: [{ type: "keyword", value: "inline-block" }], + }, + }, + { + property: "paddingLeft", + value: { type: "unit", value: 0.2, unit: "em" }, + }, + { + property: "paddingRight", + value: { type: "unit", value: 0.2, unit: "em" }, + }, + { + property: "backgroundColor", + value: { type: "rgb", r: 238, g: 238, b: 238, alpha: 1 }, }, ], -}; - -const presetStyle = { - code: { - ...code, - display, - paddingLeft: { - type: "unit", - value: 0.2, - unit: "em", - }, - paddingRight: { - type: "unit", - value: 0.2, - unit: "em", - }, - backgroundColor: { - type: "rgb", - r: 238, - g: 238, - b: 238, - alpha: 1, - }, - }, -} as const satisfies Record; +} satisfies PresetStyle; export const meta: WsComponentMeta = { category: "general", diff --git a/packages/react-sdk/src/components/component-meta.ts b/packages/react-sdk/src/components/component-meta.ts index ae83dab616..bad51966df 100644 --- a/packages/react-sdk/src/components/component-meta.ts +++ b/packages/react-sdk/src/components/component-meta.ts @@ -1,12 +1,13 @@ import { z } from "zod"; import type { FunctionComponent } from "react"; import type { IconProps } from "@webstudio-is/icons"; -import type { Style } from "@webstudio-is/css-data"; import { PropMeta } from "@webstudio-is/generate-arg-types"; import type { htmlTags as HtmlTags } from "html-tags"; -import { WsEmbedTemplate } from "../embed-template"; +import { EmbedTemplateStyleDecl, WsEmbedTemplate } from "../embed-template"; -type PresetStyle = Partial>; +export type PresetStyle = Partial< + Record +>; // props are separated from the rest of the meta // so they can be exported separately and potentially tree-shaken diff --git a/packages/react-sdk/src/components/form.ws.tsx b/packages/react-sdk/src/components/form.ws.tsx index 425828c2ad..afa9a30d0a 100644 --- a/packages/react-sdk/src/components/form.ws.tsx +++ b/packages/react-sdk/src/components/form.ws.tsx @@ -1,20 +1,22 @@ -import type { Style } from "@webstudio-is/css-data"; import { FormIcon } from "@webstudio-is/icons"; import { form } from "../css/normalize"; -import type { WsComponentMeta, WsComponentPropsMeta } from "./component-meta"; +import type { + PresetStyle, + WsComponentMeta, + WsComponentPropsMeta, +} from "./component-meta"; import type { defaultTag } from "./form"; import { props } from "./__generated__/form.props"; const presetStyle = { - form: { + form: [ ...form, - minHeight: { - type: "unit", - unit: "px", - value: 20, + { + property: "minHeight", + value: { type: "unit", unit: "px", value: 20 }, }, - }, -} as const satisfies Record; + ], +} satisfies PresetStyle; export const meta: WsComponentMeta = { category: "forms", diff --git a/packages/react-sdk/src/components/heading.ws.tsx b/packages/react-sdk/src/components/heading.ws.tsx index 183f789bce..298d2119a7 100644 --- a/packages/react-sdk/src/components/heading.ws.tsx +++ b/packages/react-sdk/src/components/heading.ws.tsx @@ -1,8 +1,11 @@ -import type { Style } from "@webstudio-is/css-data"; import { HeadingIcon } from "@webstudio-is/icons"; import type { ComponentProps } from "react"; import { h1, h2, h3, h4, h5, h6 } from "../css/normalize"; -import type { WsComponentMeta, WsComponentPropsMeta } from "./component-meta"; +import type { + PresetStyle, + WsComponentMeta, + WsComponentPropsMeta, +} from "./component-meta"; import type { Heading } from "./heading"; import { props } from "./__generated__/heading.props"; @@ -15,7 +18,7 @@ const presetStyle = { h4, h5, h6, -} as const satisfies Record; +} satisfies PresetStyle; export const meta: WsComponentMeta = { category: "typography", diff --git a/packages/react-sdk/src/components/image.ws.tsx b/packages/react-sdk/src/components/image.ws.tsx index 9e3bf99ce2..abd17a9f15 100644 --- a/packages/react-sdk/src/components/image.ws.tsx +++ b/packages/react-sdk/src/components/image.ws.tsx @@ -1,28 +1,30 @@ -import type { Style } from "@webstudio-is/css-data"; import { ImageIcon } from "@webstudio-is/icons"; import { img } from "../css/normalize"; -import type { WsComponentMeta, WsComponentPropsMeta } from "./component-meta"; +import type { + PresetStyle, + WsComponentMeta, + WsComponentPropsMeta, +} from "./component-meta"; import type { defaultTag } from "./image"; import { props } from "./__generated__/image.props"; const presetStyle = { - img: { + img: [ ...img, // Otherwise on new image insert onto canvas it can overfit screen size multiple times - maxWidth: { - type: "unit", - unit: "%", - value: 100, + { + property: "maxWidth", + value: { type: "unit", unit: "%", value: 100 }, }, // inline | inline-block is not suitable because without line-height: 0 on the parent you get unsuitable spaces/margins // see https://stackoverflow.com/questions/24771194/is-the-margin-of-inline-block-4px-is-static-for-all-browsers - display: { - type: "keyword", - value: "block", + { + property: "display", + value: { type: "keyword", value: "block" }, }, - }, -} as const satisfies Record; + ], +} satisfies PresetStyle; export const meta: WsComponentMeta = { category: "media", diff --git a/packages/react-sdk/src/components/input.ws.tsx b/packages/react-sdk/src/components/input.ws.tsx index ec0d344eab..ec0acc39da 100644 --- a/packages/react-sdk/src/components/input.ws.tsx +++ b/packages/react-sdk/src/components/input.ws.tsx @@ -1,13 +1,16 @@ -import type { Style } from "@webstudio-is/css-data"; import { FormTextFieldIcon } from "@webstudio-is/icons"; import { input } from "../css/normalize"; -import type { WsComponentMeta, WsComponentPropsMeta } from "./component-meta"; +import type { + PresetStyle, + WsComponentMeta, + WsComponentPropsMeta, +} from "./component-meta"; import type { defaultTag } from "./input"; import { props } from "./__generated__/input.props"; const presetStyle = { input, -} as const satisfies Record; +} satisfies PresetStyle; export const meta: WsComponentMeta = { category: "forms", diff --git a/packages/react-sdk/src/components/italic.ws.tsx b/packages/react-sdk/src/components/italic.ws.tsx index a6b6dee682..df53561b4c 100644 --- a/packages/react-sdk/src/components/italic.ws.tsx +++ b/packages/react-sdk/src/components/italic.ws.tsx @@ -1,19 +1,22 @@ -import type { Style } from "@webstudio-is/css-data"; import { TextItalicIcon } from "@webstudio-is/icons"; import type { defaultTag } from "./italic"; -import type { WsComponentMeta, WsComponentPropsMeta } from "./component-meta"; +import type { + PresetStyle, + WsComponentMeta, + WsComponentPropsMeta, +} from "./component-meta"; import { props } from "./__generated__/italic.props"; import { i } from "../css/normalize"; const presetStyle = { - i: { + i: [ ...i, - fontStyle: { - type: "keyword", - value: "italic", + { + property: "fontStyle", + value: { type: "keyword", value: "italic" }, }, - }, -} as const satisfies Record; + ], +} satisfies PresetStyle; export const meta: WsComponentMeta = { type: "rich-text-child", diff --git a/packages/react-sdk/src/components/link-block.ws.tsx b/packages/react-sdk/src/components/link-block.ws.tsx index 3e5714be9f..5f9b5eda6a 100644 --- a/packages/react-sdk/src/components/link-block.ws.tsx +++ b/packages/react-sdk/src/components/link-block.ws.tsx @@ -1,20 +1,23 @@ import { BoxLinkIcon } from "@webstudio-is/icons"; -import type { WsComponentMeta, WsComponentPropsMeta } from "./component-meta"; +import type { + PresetStyle, + WsComponentMeta, + WsComponentPropsMeta, +} from "./component-meta"; import { props } from "./__generated__/link-block.props"; import { meta as linkMeta, propsMeta as linkPropsMeta } from "./link.ws"; import type { defaultTag } from "./link-block"; -import type { Style } from "@webstudio-is/css-data"; import { a } from "../css/normalize"; const presetStyle = { - a: { + a: [ ...a, - display: { - type: "keyword", - value: "inline-block", + { + property: "display", + value: { type: "keyword", value: "inline-block" }, }, - }, -} as const satisfies Record; + ], +} satisfies PresetStyle; export const meta: WsComponentMeta = { category: "general", diff --git a/packages/react-sdk/src/components/link.ws.tsx b/packages/react-sdk/src/components/link.ws.tsx index c532fdd97b..fd5815ca20 100644 --- a/packages/react-sdk/src/components/link.ws.tsx +++ b/packages/react-sdk/src/components/link.ws.tsx @@ -1,24 +1,26 @@ -import type { Style } from "@webstudio-is/css-data"; import { LinkIcon } from "@webstudio-is/icons"; import { a } from "../css/normalize"; -import type { WsComponentMeta, WsComponentPropsMeta } from "./component-meta"; +import type { + PresetStyle, + WsComponentMeta, + WsComponentPropsMeta, +} from "./component-meta"; import type { defaultTag } from "./link"; import { props } from "./__generated__/link.props"; const presetStyle = { - a: { + a: [ ...a, - minHeight: { - type: "unit", - unit: "em", - value: 1, + { + property: "minHeight", + value: { type: "unit", unit: "em", value: 1 }, }, - display: { - type: "keyword", - value: "inline-block", + { + property: "display", + value: { type: "keyword", value: "inline-block" }, }, - }, -} as const satisfies Record; + ], +} satisfies PresetStyle; export const meta: WsComponentMeta = { category: "general", diff --git a/packages/react-sdk/src/components/list-item.ws.tsx b/packages/react-sdk/src/components/list-item.ws.tsx index edccf98a39..8313419957 100644 --- a/packages/react-sdk/src/components/list-item.ws.tsx +++ b/packages/react-sdk/src/components/list-item.ws.tsx @@ -1,13 +1,16 @@ -import type { Style } from "@webstudio-is/css-data"; import { ListItemIcon } from "@webstudio-is/icons"; import { li } from "../css/normalize"; -import type { WsComponentMeta, WsComponentPropsMeta } from "./component-meta"; +import type { + PresetStyle, + WsComponentMeta, + WsComponentPropsMeta, +} from "./component-meta"; import type { defaultTag } from "./list-item"; import { props } from "./__generated__/list-item.props"; const presetStyle = { li, -} as const satisfies Record; +} satisfies PresetStyle; export const meta: WsComponentMeta = { category: "typography", diff --git a/packages/react-sdk/src/components/list.ws.tsx b/packages/react-sdk/src/components/list.ws.tsx index 2a6015d369..605735d142 100644 --- a/packages/react-sdk/src/components/list.ws.tsx +++ b/packages/react-sdk/src/components/list.ws.tsx @@ -1,42 +1,45 @@ import { ListIcon } from "@webstudio-is/icons"; -import type { WsComponentMeta, WsComponentPropsMeta } from "./component-meta"; +import type { + PresetStyle, + WsComponentMeta, + WsComponentPropsMeta, +} from "./component-meta"; import { props } from "./__generated__/list.props"; import type { ListTag } from "./list"; -import type { Style } from "@webstudio-is/css-data"; import { ol, ul } from "../css/normalize"; const presetStyle = { - ol: { + ol: [ ...ol, - marginTop: { - type: "keyword", - value: "0", + { + property: "marginTop", + value: { type: "keyword", value: "0" }, }, - marginBottom: { - type: "keyword", - value: "10px", + { + property: "marginBottom", + value: { type: "keyword", value: "10px" }, }, - paddingLeft: { - type: "keyword", - value: "40px", + { + property: "paddingLeft", + value: { type: "keyword", value: "40px" }, }, - }, - ul: { + ], + ul: [ ...ul, - marginTop: { - type: "keyword", - value: "0", + { + property: "marginTop", + value: { type: "keyword", value: "0" }, }, - marginBottom: { - type: "keyword", - value: "10px", + { + property: "marginBottom", + value: { type: "keyword", value: "10px" }, }, - paddingLeft: { - type: "keyword", - value: "40px", + { + property: "paddingLeft", + value: { type: "keyword", value: "40px" }, }, - }, -} as const satisfies Record; + ], +} satisfies PresetStyle; export const meta: WsComponentMeta = { category: "typography", diff --git a/packages/react-sdk/src/components/paragraph.ws.tsx b/packages/react-sdk/src/components/paragraph.ws.tsx index 1b6993e939..51866a2864 100644 --- a/packages/react-sdk/src/components/paragraph.ws.tsx +++ b/packages/react-sdk/src/components/paragraph.ws.tsx @@ -1,13 +1,16 @@ -import type { Style } from "@webstudio-is/css-data"; import { TextAlignLeftIcon } from "@webstudio-is/icons"; import { p } from "../css/normalize"; -import type { WsComponentMeta, WsComponentPropsMeta } from "./component-meta"; +import type { + PresetStyle, + WsComponentMeta, + WsComponentPropsMeta, +} from "./component-meta"; import type { defaultTag } from "./paragraph"; import { props } from "./__generated__/paragraph.props"; const presetStyle = { p, -} as const satisfies Record; +} satisfies PresetStyle; export const meta: WsComponentMeta = { category: "typography", diff --git a/packages/react-sdk/src/components/separator.ws.tsx b/packages/react-sdk/src/components/separator.ws.tsx index 9bf43a7c79..4023476fca 100644 --- a/packages/react-sdk/src/components/separator.ws.tsx +++ b/packages/react-sdk/src/components/separator.ws.tsx @@ -1,42 +1,43 @@ import { DashIcon } from "@webstudio-is/icons"; -import type { WsComponentMeta, WsComponentPropsMeta } from "./component-meta"; +import type { + PresetStyle, + WsComponentMeta, + WsComponentPropsMeta, +} from "./component-meta"; import { props } from "./__generated__/separator.props"; import type { defaultTag } from "./separator"; -import type { Style } from "@webstudio-is/css-data"; import { hr } from "../css/normalize"; const presetStyle = { - hr: { + hr: [ ...hr, - height: { - type: "keyword", - value: "1px", + { + property: "height", + value: { type: "keyword", value: "1px" }, }, - - backgroundColor: { - type: "keyword", - value: "gray", + { + property: "backgroundColor", + value: { type: "keyword", value: "gray" }, }, - - borderTopStyle: { - type: "keyword", - value: "none", + { + property: "borderTopStyle", + value: { type: "keyword", value: "none" }, }, - borderRightStyle: { - type: "keyword", - value: "none", + { + property: "borderRightStyle", + value: { type: "keyword", value: "none" }, }, - borderLeftStyle: { - type: "keyword", - value: "none", + { + property: "borderLeftStyle", + value: { type: "keyword", value: "none" }, }, - borderBottomStyle: { - type: "keyword", - value: "none", + { + property: "borderBottomStyle", + value: { type: "keyword", value: "none" }, }, - }, -} as const satisfies Record; + ], +} satisfies PresetStyle; export const meta: WsComponentMeta = { category: "general", diff --git a/packages/react-sdk/src/components/span.ws.tsx b/packages/react-sdk/src/components/span.ws.tsx index 0dfcf192d1..f01c77c85d 100644 --- a/packages/react-sdk/src/components/span.ws.tsx +++ b/packages/react-sdk/src/components/span.ws.tsx @@ -1,13 +1,16 @@ -import type { Style } from "@webstudio-is/css-data"; import { PaintBrushIcon } from "@webstudio-is/icons"; import { span } from "../css/normalize"; -import type { WsComponentMeta, WsComponentPropsMeta } from "./component-meta"; +import type { + PresetStyle, + WsComponentMeta, + WsComponentPropsMeta, +} from "./component-meta"; import type { defaultTag } from "./span"; import { props } from "./__generated__/span.props"; const presetStyle = { span, -} as const satisfies Record; +} satisfies PresetStyle; export const meta: WsComponentMeta = { type: "rich-text-child", diff --git a/packages/react-sdk/src/components/subscript.ws.tsx b/packages/react-sdk/src/components/subscript.ws.tsx index e4dd1610ed..e3b2bfb3f9 100644 --- a/packages/react-sdk/src/components/subscript.ws.tsx +++ b/packages/react-sdk/src/components/subscript.ws.tsx @@ -1,13 +1,16 @@ -import type { Style } from "@webstudio-is/css-data"; import { SubscriptIcon } from "@webstudio-is/icons"; import { sub } from "../css/normalize"; -import type { WsComponentMeta, WsComponentPropsMeta } from "./component-meta"; +import type { + PresetStyle, + WsComponentMeta, + WsComponentPropsMeta, +} from "./component-meta"; import type { defaultTag } from "./subscript"; import { props } from "./__generated__/subscript.props"; const presetStyle = { sub, -} as const satisfies Record; +} satisfies PresetStyle; export const meta: WsComponentMeta = { type: "rich-text-child", diff --git a/packages/react-sdk/src/components/superscript.ws.tsx b/packages/react-sdk/src/components/superscript.ws.tsx index 426a8a7d66..a8c8a2601f 100644 --- a/packages/react-sdk/src/components/superscript.ws.tsx +++ b/packages/react-sdk/src/components/superscript.ws.tsx @@ -1,13 +1,16 @@ -import type { Style } from "@webstudio-is/css-data"; import { SuperscriptIcon } from "@webstudio-is/icons"; import { sup } from "../css/normalize"; -import type { WsComponentMeta, WsComponentPropsMeta } from "./component-meta"; +import type { + PresetStyle, + WsComponentMeta, + WsComponentPropsMeta, +} from "./component-meta"; import type { defaultTag } from "./superscript"; import { props } from "./__generated__/superscript.props"; const presetStyle = { sup, -} as const satisfies Record; +} satisfies PresetStyle; export const meta: WsComponentMeta = { type: "rich-text-child", diff --git a/packages/react-sdk/src/components/text-block.ws.tsx b/packages/react-sdk/src/components/text-block.ws.tsx index 4f3a0394c3..135ba07651 100644 --- a/packages/react-sdk/src/components/text-block.ws.tsx +++ b/packages/react-sdk/src/components/text-block.ws.tsx @@ -1,21 +1,22 @@ import { TextBlockIcon } from "@webstudio-is/icons"; -import type { WsComponentMeta, WsComponentPropsMeta } from "./component-meta"; +import type { + PresetStyle, + WsComponentMeta, + WsComponentPropsMeta, +} from "./component-meta"; import { props } from "./__generated__/text-block.props"; import type { defaultTag } from "./text-block"; -import type { Style } from "@webstudio-is/css-data"; import { div } from "../css/normalize"; const presetStyle = { - div: { + div: [ ...div, - - minHeight: { - type: "unit", - unit: "em", - value: 1, + { + property: "minHeight", + value: { type: "unit", unit: "em", value: 1 }, }, - }, -} as const satisfies Record; + ], +} satisfies PresetStyle; export const meta: WsComponentMeta = { category: "typography", diff --git a/packages/react-sdk/src/css/css.ts b/packages/react-sdk/src/css/css.ts index 6d5a70d1f1..45d676d87f 100644 --- a/packages/react-sdk/src/css/css.ts +++ b/packages/react-sdk/src/css/css.ts @@ -3,9 +3,9 @@ import type { Asset, Assets } from "@webstudio-is/asset-uploader"; import type { Build } from "@webstudio-is/project-build"; import { getComponentNames } from "../components/components-utils"; import { getComponentMeta } from "../components"; -import { componentAttribute, idAttribute } from "../tree"; +import { idAttribute } from "../tree"; import { addGlobalRules } from "./global-rules"; -import { getStyleRules } from "./style-rules"; +import { getPresetStyleRules, getStyleRules } from "./style-rules"; type Data = { assets: Asset[]; @@ -64,15 +64,12 @@ export const generateCssText = (data: Data, options: CssOptions) => { for (const component of getComponentNames()) { const meta = getComponentMeta(component); const presetStyle = meta?.presetStyle; - if (presetStyle !== undefined) { - for (const [tag, style] of Object.entries(presetStyle)) { - engine.addStyleRule( - `${tag}:where([${componentAttribute}=${component}])`, - { - style, - } - ); - } + if (presetStyle === undefined) { + continue; + } + const rules = getPresetStyleRules(component, presetStyle); + for (const [selector, style] of rules) { + engine.addStyleRule(selector, { style }); } } diff --git a/packages/react-sdk/src/css/normalize.ts b/packages/react-sdk/src/css/normalize.ts index 2671a8d102..e5345d7e37 100644 --- a/packages/react-sdk/src/css/normalize.ts +++ b/packages/react-sdk/src/css/normalize.ts @@ -15,18 +15,17 @@ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE */ -import type { Style } from "@webstudio-is/css-data"; - // webstudio custom opinionated presets import { borders } from "./presets"; +import type { EmbedTemplateStyleDecl } from "../embed-template"; /** Use a better box model (opinionated). */ const boxSizing = { - type: "keyword", - value: "border-box", -} as const; + property: "boxSizing", + value: { type: "keyword", value: "border-box" }, +} satisfies EmbedTemplateStyleDecl; /** * We dont support rules like this now, implement boxSizing for each used element @@ -36,10 +35,7 @@ const boxSizing = { * box-sizing: border-box; } */ -const baseStyle = { - boxSizing, - ...borders, -} as const satisfies Style; +const baseStyle = [boxSizing, ...borders] satisfies EmbedTemplateStyleDecl[]; export const div = baseStyle; export const address = baseStyle; @@ -78,83 +74,78 @@ export const span = baseStyle; 2. Prevent adjustments of font size after orientation changes in iOS. 3. Use a more readable tab size (opinionated). */ -export const html = { +export const html = [ /* 1 */ - lineHeight: { - type: "unit", - value: 1.15, - unit: "number", + { + property: "lineHeight", + value: { type: "unit", value: 1.15, unit: "number" }, }, - /* 2 */ - textSizeAdjust: { - type: "unit", - value: 100, - unit: "%", + { + property: "textSizeAdjust", + value: { type: "unit", value: 100, unit: "%" }, }, - /* 3 */ - tabSize: { - type: "unit", - value: 4, - unit: "number", + { + property: "tabSize", + value: { type: "unit", value: 4, unit: "number" }, }, - boxSizing, ...borders, -} as const satisfies Style; +] satisfies EmbedTemplateStyleDecl[]; /** 1. Remove the margin in all browsers. 2. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3) */ -export const body = { +export const body = [ /* 1 */ - marginTop: { - type: "unit", - value: 0, - unit: "number", + { + property: "marginTop", + value: { type: "unit", value: 0, unit: "number" }, }, - marginRight: { - type: "unit", - value: 0, - unit: "number", + { + property: "marginRight", + value: { type: "unit", value: 0, unit: "number" }, }, - marginBottom: { - type: "unit", - value: 0, - unit: "number", + { + property: "marginBottom", + value: { type: "unit", value: 0, unit: "number" }, }, - marginLeft: { - type: "unit", - value: 0, - unit: "number", + { + property: "marginLeft", + value: { type: "unit", value: 0, unit: "number" }, }, /* 2 */ - fontFamily: { - type: "keyword", - value: `system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'`, + { + property: "fontFamily", + value: { + type: "keyword", + value: `system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'`, + }, }, boxSizing, ...borders, -} as const satisfies Style; +] satisfies EmbedTemplateStyleDecl[]; /** 1. Add the correct height in Firefox. 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) */ -export const hr = { +export const hr = [ /* 1 */ - height: { - type: "unit", - value: 0, - unit: "number", + { + property: "height", + value: { type: "unit", value: 0, unit: "number" }, }, /* 2 */ - color: { type: "keyword", value: "inherit" }, + { + property: "color", + value: { type: "keyword", value: "inherit" }, + }, boxSizing, ...borders, -} as const satisfies Style; +] satisfies EmbedTemplateStyleDecl[]; /** Add the correct text decoration in Chrome, Edge, and Safari. @@ -169,35 +160,37 @@ abbr[title] { /** Add the correct font weight in Edge and Safari. */ -export const b = { - fontWeight: { - type: "keyword", - value: "bolder", +export const b = [ + { + property: "fontWeight", + value: { type: "keyword", value: "bolder" }, }, boxSizing, ...borders, -} as const satisfies Style; +] satisfies EmbedTemplateStyleDecl[]; export const strong = b; /** 1. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3) 2. Correct the odd 'em' font sizing in all browsers. */ -export const code = { +export const code = [ /* 1 */ - fontFamily: { - type: "keyword", - value: `ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace`, + { + property: "fontFamily", + value: { + type: "keyword", + value: `ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace`, + }, }, /* 2 */ - fontSize: { - type: "unit", - value: 1, - unit: "em", + { + property: "fontSize", + value: { type: "unit", value: 1, unit: "em" }, }, boxSizing, ...borders, -} as const satisfies Style; +] satisfies EmbedTemplateStyleDecl[]; export const kbd = code; export const samp = code; @@ -207,60 +200,55 @@ export const pre = code; Add the correct font size in all browsers. */ -export const small = { - fontSize: { - type: "unit", - value: 80, - unit: "%", +export const small = [ + { + property: "fontSize", + value: { type: "unit", value: 80, unit: "%" }, }, boxSizing, ...borders, -} as const satisfies Style; +] satisfies EmbedTemplateStyleDecl[]; /** Prevent 'sub' and 'sup' elements from affecting the line height in all browsers. */ -const subSupBase = { - fontSize: { - type: "unit", - value: 75, - unit: "%", +const subSupBase = [ + { + property: "fontSize", + value: { type: "unit", value: 75, unit: "%" }, }, - lineHeight: { - type: "unit", - value: 0, - unit: "number", + { + property: "lineHeight", + value: { type: "unit", value: 0, unit: "number" }, }, - position: { - type: "keyword", - value: "relative", + { + property: "position", + value: { type: "keyword", value: "relative" }, }, - verticalAlign: { - type: "keyword", - value: "baseline", + { + property: "verticalAlign", + value: { type: "keyword", value: "baseline" }, }, boxSizing, ...borders, -} as const satisfies Style; +] satisfies EmbedTemplateStyleDecl[]; -export const sub = { +export const sub = [ ...subSupBase, - bottom: { - type: "unit", - value: -0.25, - unit: "em", + { + property: "bottom", + value: { type: "unit", value: -0.25, unit: "em" }, }, -} as const satisfies Style; +] satisfies EmbedTemplateStyleDecl[]; -export const sup = { +export const sup = [ ...subSupBase, - top: { - type: "unit", - value: -0.5, - unit: "em", + { + property: "top", + value: { type: "unit", value: -0.5, unit: "em" }, }, -} as const satisfies Style; +] satisfies EmbedTemplateStyleDecl[]; /* Tabular data @@ -272,33 +260,32 @@ Tabular data 2. Correct table border color inheritance in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) */ -export const table = { +export const table = [ /* 1 */ - textIndent: { - type: "unit", - value: 0, - unit: "number", + { + property: "textIndent", + value: { type: "unit", value: 0, unit: "number" }, }, ...borders, /* 2 */ - borderTopColor: { - type: "keyword", - value: "inherit", + { + property: "borderTopColor", + value: { type: "keyword", value: "inherit" }, }, - borderRightColor: { - type: "keyword", - value: "inherit", + { + property: "borderRightColor", + value: { type: "keyword", value: "inherit" }, }, - borderBottomColor: { - type: "keyword", - value: "inherit", + { + property: "borderBottomColor", + value: { type: "keyword", value: "inherit" }, }, - borderLeftColor: { - type: "keyword", - value: "inherit", + { + property: "borderLeftColor", + value: { type: "keyword", value: "inherit" }, }, boxSizing, -} as const satisfies Style; +] satisfies EmbedTemplateStyleDecl[]; /* Forms @@ -310,46 +297,40 @@ Forms 2. Remove the margin in Firefox and Safari. */ -const buttonBase = { +const buttonBase = [ /* 1 */ - fontFamily: { - type: "keyword", - value: "inherit", + { + property: "fontFamily", + value: { type: "keyword", value: "inherit" }, }, - fontSize: { - type: "unit", - value: 100, - unit: "%", + { + property: "fontSize", + value: { type: "unit", value: 100, unit: "%" }, }, - lineHeight: { - type: "unit", - value: 1.15, - unit: "number", + { + property: "lineHeight", + value: { type: "unit", value: 1.15, unit: "number" }, }, /* 2 */ - marginTop: { - type: "unit", - value: 0, - unit: "number", + { + property: "marginTop", + value: { type: "unit", value: 0, unit: "number" }, }, - marginRight: { - type: "unit", - value: 0, - unit: "number", + { + property: "marginRight", + value: { type: "unit", value: 0, unit: "number" }, }, - marginBottom: { - type: "unit", - value: 0, - unit: "number", + { + property: "marginBottom", + value: { type: "unit", value: 0, unit: "number" }, }, - marginLeft: { - type: "unit", - value: 0, - unit: "number", + { + property: "marginLeft", + value: { type: "unit", value: 0, unit: "number" }, }, boxSizing, ...borders, -} as const satisfies Style; +] satisfies EmbedTemplateStyleDecl[]; export const input = buttonBase; export const optgroup = buttonBase; @@ -358,13 +339,13 @@ export const textarea = buttonBase; /** Remove the inheritance of text transform in Edge and Firefox. */ -export const button = { +export const button = [ ...buttonBase, - textTransform: { - type: "keyword", - value: "none", + { + property: "textTransform", + value: { type: "keyword", value: "none" }, }, -} as const satisfies Style; +] satisfies EmbedTemplateStyleDecl[]; export const select = button; @@ -417,43 +398,39 @@ See: https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d4 Remove the padding so developers are not caught out when they zero out 'fieldset' elements in all browsers. */ -export const legend = { - paddingTop: { - type: "unit", - value: 0, - unit: "number", +export const legend = [ + { + property: "paddingTop", + value: { type: "unit", value: 0, unit: "number" }, }, - paddingRight: { - type: "unit", - value: 0, - unit: "number", + { + property: "paddingRight", + value: { type: "unit", value: 0, unit: "number" }, }, - paddingBottom: { - type: "unit", - value: 0, - unit: "number", + { + property: "paddingBottom", + value: { type: "unit", value: 0, unit: "number" }, }, - paddingLeft: { - type: "unit", - value: 0, - unit: "number", + { + property: "paddingLeft", + value: { type: "unit", value: 0, unit: "number" }, }, boxSizing, ...borders, -} as const satisfies Style; +] satisfies EmbedTemplateStyleDecl[]; /** Add the correct vertical alignment in Chrome and Firefox. */ -export const progress = { - verticalAlign: { - type: "keyword", - value: "baseline", +export const progress = [ + { + property: "verticalAlign", + value: { type: "keyword", value: "baseline" }, }, boxSizing, ...borders, -} as const satisfies Style; +] satisfies EmbedTemplateStyleDecl[]; /** Correct the cursor style of increment and decrement buttons in Safari. @@ -509,11 +486,11 @@ Interactive Add the correct display in Chrome and Safari. */ -export const summary = { - display: { - type: "keyword", - value: "list-item", +export const summary = [ + { + property: "display", + value: { type: "keyword", value: "list-item" }, }, boxSizing, ...borders, -} as const satisfies Style; +] satisfies EmbedTemplateStyleDecl[]; diff --git a/packages/react-sdk/src/css/presets.ts b/packages/react-sdk/src/css/presets.ts index d5f0131486..b3c9244ba7 100644 --- a/packages/react-sdk/src/css/presets.ts +++ b/packages/react-sdk/src/css/presets.ts @@ -1,42 +1,37 @@ -import type { Style } from "@webstudio-is/css-data"; +import type { EmbedTemplateStyleDecl } from "../embed-template"; -export const borders = { - borderTopColor: { - type: "keyword", - value: "currentColor", +export const borders: EmbedTemplateStyleDecl[] = [ + { + property: "borderTopColor", + value: { type: "keyword", value: "currentColor" }, }, - borderRightColor: { - type: "keyword", - value: "currentColor", + { + property: "borderRightColor", + value: { type: "keyword", value: "currentColor" }, }, - borderBottomColor: { - type: "keyword", - value: "currentColor", + { + property: "borderBottomColor", + value: { type: "keyword", value: "currentColor" }, }, - borderLeftColor: { - type: "keyword", - value: "currentColor", + { + property: "borderLeftColor", + value: { type: "keyword", value: "currentColor" }, }, - borderTopWidth: { - type: "unit", - value: 1, - unit: "px", + { + property: "borderTopWidth", + value: { type: "unit", value: 1, unit: "px" }, }, - - borderRightWidth: { - type: "unit", - value: 1, - unit: "px", + { + property: "borderRightWidth", + value: { type: "unit", value: 1, unit: "px" }, }, - borderBottomWidth: { - type: "unit", - value: 1, - unit: "px", + { + property: "borderBottomWidth", + value: { type: "unit", value: 1, unit: "px" }, }, - borderLeftWidth: { - type: "unit", - value: 1, - unit: "px", + { + property: "borderLeftWidth", + value: { type: "unit", value: 1, unit: "px" }, }, -} as const satisfies Style; +]; diff --git a/packages/react-sdk/src/css/style-rules.ts b/packages/react-sdk/src/css/style-rules.ts index ac541253f9..bb85a57652 100644 --- a/packages/react-sdk/src/css/style-rules.ts +++ b/packages/react-sdk/src/css/style-rules.ts @@ -6,6 +6,8 @@ import type { StyleSource, StyleSourceSelections, } from "@webstudio-is/project-build"; +import type { PresetStyle } from "../components/component-meta"; +import { componentAttribute } from "../tree"; type StyleRule = { instanceId: string; @@ -75,3 +77,25 @@ export const getStyleRules = ( return styleRules; }; + +export const getPresetStyleRules = ( + component: string, + presetStyle: PresetStyle +) => { + // group style declarations by selector to render as separate rules + const presetStyleRules = new Map(); + for (const [tag, styles] of Object.entries(presetStyle)) { + for (const styleDecl of styles) { + const selector = `${tag}:where([${componentAttribute}=${component}])${ + styleDecl.state ?? "" + }`; + let rule = presetStyleRules.get(selector); + if (rule === undefined) { + rule = {}; + presetStyleRules.set(selector, rule); + } + rule[styleDecl.property] = styleDecl.value; + } + } + return presetStyleRules; +}; diff --git a/packages/react-sdk/src/embed-template.ts b/packages/react-sdk/src/embed-template.ts index b94e6969b3..8cc46d5251 100644 --- a/packages/react-sdk/src/embed-template.ts +++ b/packages/react-sdk/src/embed-template.ts @@ -5,6 +5,7 @@ import { type InstancesList, PropsList, } from "@webstudio-is/project-build"; +import { StyleValue, type StyleProperty } from "@webstudio-is/css-data"; const EmbedTemplateText = z.object({ type: z.literal("text"), @@ -38,10 +39,19 @@ const EmbedTemplateProp = z.union([ type EmbedTemplateProp = z.infer; +export const EmbedTemplateStyleDecl = z.object({ + state: z.optional(z.string()), + property: z.string() as z.ZodType, + value: StyleValue, +}); + +export type EmbedTemplateStyleDecl = z.infer; + type EmbedTemplateInstance = { type: "instance"; component: string; props?: EmbedTemplateProp[]; + styles?: EmbedTemplateStyleDecl[]; children: Array; }; @@ -49,6 +59,7 @@ const EmbedTemplateInstance: z.ZodType = z.object({ type: z.literal("instance"), component: z.string(), props: z.optional(z.array(EmbedTemplateProp)), + styles: z.optional(z.array(EmbedTemplateStyleDecl)), children: z.lazy(() => WsEmbedTemplate), });