diff --git a/apps/builder/app/builder/features/style-panel/controls/color/color-control.tsx b/apps/builder/app/builder/features/style-panel/controls/color/color-control.tsx index 39636db98301..1cbf10e1c53b 100644 --- a/apps/builder/app/builder/features/style-panel/controls/color/color-control.tsx +++ b/apps/builder/app/builder/features/style-panel/controls/color/color-control.tsx @@ -1,4 +1,4 @@ -import type { StyleProperty } from "@webstudio-is/css-engine"; +import type { CssProperty, StyleProperty } from "@webstudio-is/css-engine"; import { ColorPicker } from "../../shared/color-picker"; import { styleConfigByName } from "../../shared/configs"; import { @@ -7,7 +7,11 @@ import { } from "../../shared/model"; import { deleteProperty, setProperty } from "../../shared/use-style-data"; -export const ColorControl = ({ property }: { property: StyleProperty }) => { +export const ColorControl = ({ + property, +}: { + property: StyleProperty | CssProperty; +}) => { const computedStyleDecl = useComputedStyleDecl(property); const value = computedStyleDecl.cascadedValue; const currentColor = computedStyleDecl.usedValue; diff --git a/apps/builder/app/builder/features/style-panel/controls/image/image-control.tsx b/apps/builder/app/builder/features/style-panel/controls/image/image-control.tsx index 21fc5aa4e230..a932039d985a 100644 --- a/apps/builder/app/builder/features/style-panel/controls/image/image-control.tsx +++ b/apps/builder/app/builder/features/style-panel/controls/image/image-control.tsx @@ -8,7 +8,11 @@ import { import { $assets } from "~/shared/nano-states"; import { ImageManager } from "~/builder/shared/image-manager"; import { useEffect, useState } from "react"; -import type { InvalidValue, StyleProperty } from "@webstudio-is/css-engine"; +import type { + CssProperty, + InvalidValue, + StyleProperty, +} from "@webstudio-is/css-engine"; import { useComputedStyleDecl } from "../../shared/model"; import { getRepeatedStyleItem, @@ -32,7 +36,7 @@ export const ImageControl = ({ property, index, }: { - property: StyleProperty; + property: StyleProperty | CssProperty; index: number; }) => { const assets = useStore($assets); diff --git a/apps/builder/app/builder/features/style-panel/controls/menu/menu-control.tsx b/apps/builder/app/builder/features/style-panel/controls/menu/menu-control.tsx index 7fd37fe50b3b..33eee7667678 100644 --- a/apps/builder/app/builder/features/style-panel/controls/menu/menu-control.tsx +++ b/apps/builder/app/builder/features/style-panel/controls/menu/menu-control.tsx @@ -1,5 +1,9 @@ import { useState } from "react"; -import { toValue, type StyleProperty } from "@webstudio-is/css-engine"; +import { + toValue, + type CssProperty, + type StyleProperty, +} from "@webstudio-is/css-engine"; import type { IconComponent } from "@webstudio-is/icons"; import { Box, @@ -15,7 +19,10 @@ import { MenuCheckedIcon, theme, } from "@webstudio-is/design-system"; -import { declarationDescriptions } from "@webstudio-is/css-data"; +import { + camelCaseProperty, + declarationDescriptions, +} from "@webstudio-is/css-data"; import { humanizeString } from "~/shared/string-utils"; import { setProperty } from "../../shared/use-style-data"; import { useComputedStyleDecl } from "../../shared/model"; @@ -25,7 +32,7 @@ export const MenuControl = ({ property, items, }: { - property: StyleProperty; + property: StyleProperty | CssProperty; items: Array<{ name: string; label: string; @@ -41,7 +48,7 @@ export const MenuControl = ({ const Icon = currentItem?.icon ?? items[0].icon; const description = declarationDescriptions[ - `${property}:${ + `${camelCaseProperty(property)}:${ descriptionValue ?? currentValue }` as keyof typeof declarationDescriptions ]; diff --git a/apps/builder/app/builder/features/style-panel/controls/position/position-control.tsx b/apps/builder/app/builder/features/style-panel/controls/position/position-control.tsx index 82420746bf0a..51662235a085 100644 --- a/apps/builder/app/builder/features/style-panel/controls/position/position-control.tsx +++ b/apps/builder/app/builder/features/style-panel/controls/position/position-control.tsx @@ -1,9 +1,13 @@ -import { propertyDescriptions } from "@webstudio-is/css-data"; +import { + camelCaseProperty, + propertyDescriptions, +} from "@webstudio-is/css-data"; import { TupleValue, TupleValueItem, type StyleValue, type StyleProperty, + type CssProperty, } from "@webstudio-is/css-engine"; import { Flex, Grid, PositionGrid } from "@webstudio-is/design-system"; import type { ComputedStyleDecl } from "~/shared/style-object-model"; @@ -53,7 +57,7 @@ export const PositionControl = ({ property, styleDecl, }: { - property: StyleProperty; + property: StyleProperty | CssProperty; styleDecl: ComputedStyleDecl; }) => { const { items } = styleConfigByName(property); @@ -79,7 +83,7 @@ export const PositionControl = ({ diff --git a/apps/builder/app/builder/features/style-panel/controls/select/select-control.tsx b/apps/builder/app/builder/features/style-panel/controls/select/select-control.tsx index fed0b995003b..f0de8a895be6 100644 --- a/apps/builder/app/builder/features/style-panel/controls/select/select-control.tsx +++ b/apps/builder/app/builder/features/style-panel/controls/select/select-control.tsx @@ -1,7 +1,12 @@ -import { declarationDescriptions, parseCssValue } from "@webstudio-is/css-data"; +import { + camelCaseProperty, + declarationDescriptions, + parseCssValue, +} from "@webstudio-is/css-data"; import { StyleValue, toValue, + type CssProperty, type StyleProperty, } from "@webstudio-is/css-engine"; import { Box, Select, theme } from "@webstudio-is/design-system"; @@ -24,7 +29,7 @@ export const SelectControl = ({ index, items = styleConfigByName(property).items, }: { - property: StyleProperty; + property: StyleProperty | CssProperty; index?: number; items?: Array<{ label: string; name: string }>; }) => { @@ -54,7 +59,9 @@ export const SelectControl = ({ const hasDescription = options.length > 0 && options.some( - (option) => declarationDescriptions[`${property}:${option}`] !== undefined + (option) => + declarationDescriptions[`${camelCaseProperty(property)}:${option}`] !== + undefined ); return ( @@ -74,7 +81,7 @@ export const SelectControl = ({ return; } // Preview on mouse enter or focus. - const nextValue = parseCssValue(property, name); + const nextValue = parseCssValue(camelCaseProperty(property), name); setValue(nextValue, { isEphemeral: true }); }} onOpenChange={(isOpen) => { @@ -88,7 +95,8 @@ export const SelectControl = ({ return; } - const description = declarationDescriptions[`${property}:${option}`]; + const description = + declarationDescriptions[`${camelCaseProperty(property)}:${option}`]; return ( {description ?? `The ${noCase(property)} is ${option}`} diff --git a/apps/builder/app/builder/features/style-panel/controls/text/text-control.tsx b/apps/builder/app/builder/features/style-panel/controls/text/text-control.tsx index 8a0cf8e8903a..632f3ab75206 100644 --- a/apps/builder/app/builder/features/style-panel/controls/text/text-control.tsx +++ b/apps/builder/app/builder/features/style-panel/controls/text/text-control.tsx @@ -1,5 +1,9 @@ import { useState } from "react"; -import type { StyleProperty, StyleValue } from "@webstudio-is/css-engine"; +import type { + CssProperty, + StyleProperty, + StyleValue, +} from "@webstudio-is/css-engine"; import { CssValueInput, type IntermediateStyleValue, @@ -11,7 +15,11 @@ import { useComputedStyleDecl, } from "../../shared/model"; -export const TextControl = ({ property }: { property: StyleProperty }) => { +export const TextControl = ({ + property, +}: { + property: StyleProperty | CssProperty; +}) => { const computedStyleDecl = useComputedStyleDecl(property); const value = computedStyleDecl.cascadedValue; const setValue = setProperty(property); diff --git a/apps/builder/app/builder/features/style-panel/controls/toggle-group/toggle-group-control.tsx b/apps/builder/app/builder/features/style-panel/controls/toggle-group/toggle-group-control.tsx index d8873fa86f91..eb452fea18d8 100644 --- a/apps/builder/app/builder/features/style-panel/controls/toggle-group/toggle-group-control.tsx +++ b/apps/builder/app/builder/features/style-panel/controls/toggle-group/toggle-group-control.tsx @@ -1,5 +1,8 @@ import { useState, type JSX, type ReactNode } from "react"; -import { declarationDescriptions } from "@webstudio-is/css-data"; +import { + camelCaseProperty, + declarationDescriptions, +} from "@webstudio-is/css-data"; import { AlertIcon } from "@webstudio-is/icons"; import { Flex, @@ -11,6 +14,7 @@ import { import { hyphenateProperty, toValue, + type CssProperty, type StyleProperty, } from "@webstudio-is/css-engine"; import { humanizeString } from "~/shared/string-utils"; @@ -38,7 +42,7 @@ export const ToggleGroupTooltip = ({ label?: string; code?: string; description: string | undefined; - properties: StyleProperty[]; + properties: (StyleProperty | CssProperty)[]; isAdvanced?: boolean; children: ReactNode; }) => { @@ -152,7 +156,7 @@ export const ToggleGroupControl = ({ description={ item.description ?? declarationDescriptions[ - `${properties[0]}:${item.value}` as keyof typeof declarationDescriptions + `${camelCaseProperty(properties[0])}:${item.value}` as keyof typeof declarationDescriptions ] } properties={properties} diff --git a/apps/builder/app/builder/features/style-panel/controls/toggle/toggle-control.tsx b/apps/builder/app/builder/features/style-panel/controls/toggle/toggle-control.tsx index 8f839c6dda15..960f1d4a8edf 100644 --- a/apps/builder/app/builder/features/style-panel/controls/toggle/toggle-control.tsx +++ b/apps/builder/app/builder/features/style-panel/controls/toggle/toggle-control.tsx @@ -1,6 +1,13 @@ import { ToggleButton } from "@webstudio-is/design-system"; -import { declarationDescriptions } from "@webstudio-is/css-data"; -import { toValue, type StyleProperty } from "@webstudio-is/css-engine"; +import { + camelCaseProperty, + declarationDescriptions, +} from "@webstudio-is/css-data"; +import { + toValue, + type CssProperty, + type StyleProperty, +} from "@webstudio-is/css-engine"; import type { IconComponent } from "@webstudio-is/icons"; import { humanizeString } from "~/shared/string-utils"; import { setProperty } from "../../shared/use-style-data"; @@ -11,7 +18,7 @@ export const ToggleControl = ({ property, items, }: { - property: StyleProperty; + property: StyleProperty | CssProperty; items: Array<{ name: string; label: string; @@ -32,7 +39,7 @@ export const ToggleControl = ({ computedStyleDecl.source.name !== "default" && currentItem === undefined; const description = declarationDescriptions[ - `${property}:${currentValue}` as keyof typeof declarationDescriptions + `${camelCaseProperty(property)}:${currentValue}` as keyof typeof declarationDescriptions ]; return ( diff --git a/apps/builder/app/builder/features/style-panel/property-label.tsx b/apps/builder/app/builder/features/style-panel/property-label.tsx index b5c8800d94f8..453a8dcaa158 100644 --- a/apps/builder/app/builder/features/style-panel/property-label.tsx +++ b/apps/builder/app/builder/features/style-panel/property-label.tsx @@ -5,6 +5,7 @@ import { AlertIcon, ResetIcon } from "@webstudio-is/icons"; import { hyphenateProperty, toValue, + type CssProperty, type StyleProperty, } from "@webstudio-is/css-engine"; import { @@ -238,7 +239,7 @@ export const PropertyLabel = ({ }: { label: string; description?: string; - properties: [StyleProperty, ...StyleProperty[]]; + properties: [StyleProperty | CssProperty, ...(StyleProperty | CssProperty)[]]; }) => { const styles = useComputedStyles(properties); const styleValueSourceColor = getPriorityStyleValueSource(styles); @@ -301,7 +302,7 @@ export const PropertySectionLabel = ({ }: { label: string; description: string | undefined; - properties: [StyleProperty, ...StyleProperty[]]; + properties: [StyleProperty | CssProperty, ...(StyleProperty | CssProperty)[]]; }) => { const styles = useComputedStyles(properties); const styleValueSourceColor = getPriorityStyleValueSource(styles); @@ -371,7 +372,10 @@ export const PropertyInlineLabel = ({ label: string; title?: string; description?: string; - properties?: [StyleProperty, ...StyleProperty[]]; + properties?: [ + StyleProperty | CssProperty, + ...(StyleProperty | CssProperty)[], + ]; disabled?: boolean; }) => { const [isOpen, setIsOpen] = useState(false); @@ -427,7 +431,7 @@ export const PropertyValueTooltip = ({ }: { label: string; description: string | undefined; - properties: [StyleProperty, ...StyleProperty[]]; + properties: [StyleProperty | CssProperty, ...(StyleProperty | CssProperty)[]]; isAdvanced?: boolean; children: ReactNode; }) => { diff --git a/apps/builder/app/builder/features/style-panel/sections/advanced/advanced.tsx b/apps/builder/app/builder/features/style-panel/sections/advanced/advanced.tsx index 2b9d053e0993..48a5e57e67bc 100644 --- a/apps/builder/app/builder/features/style-panel/sections/advanced/advanced.tsx +++ b/apps/builder/app/builder/features/style-panel/sections/advanced/advanced.tsx @@ -74,7 +74,7 @@ const AdvancedStyleSection = (props: { }) => { const { label, children, properties, onAdd } = props; const [isOpen, setIsOpen] = useOpenState(label); - const styles = useComputedStyles(properties.map(camelCaseProperty)); + const styles = useComputedStyles(properties); return ( { } const batch = createBatchUpdate(); for (const [property, value] of styleMap) { - batch.setProperty(camelCaseProperty(property as CssProperty))(value); + batch.setProperty(property as CssProperty)(value); } batch.publish({ listed: true }); return styleMap; @@ -127,10 +127,9 @@ const AdvancedPropertyLabel = ({ property: CssProperty; onReset?: () => void; }) => { - const camelCasedProperty = camelCaseProperty(property); - const styleDecl = useComputedStyleDecl(camelCasedProperty); + const styleDecl = useComputedStyleDecl(property); const label = hyphenateProperty(property); - const description = propertyDescriptions[property]; + const description = propertyDescriptions[camelCaseProperty(property)]; const [isOpen, setIsOpen] = useState(false); return ( { if (event.altKey) { event.preventDefault(); - deleteProperty(camelCasedProperty); + deleteProperty(property); onReset?.(); return; } @@ -155,7 +154,7 @@ const AdvancedPropertyLabel = ({ description={description} styles={[styleDecl]} onReset={() => { - deleteProperty(camelCasedProperty); + deleteProperty(property); setIsOpen(false); onReset?.(); }} @@ -191,8 +190,7 @@ const AdvancedPropertyValue = ({ inputRef?: RefObject; }) => { // @todo conversion should be removed once data is in dash case - const camelCasedProperty = camelCaseProperty(property); - const styleDecl = useComputedStyleDecl(camelCasedProperty); + const styleDecl = useComputedStyleDecl(property); const inputRef = useRef(null); const isColor = colord(toValue(styleDecl.usedValue)).isValid(); @@ -209,21 +207,21 @@ const AdvancedPropertyValue = ({ onChange={(styleValue) => { const options = { isEphemeral: true, listed: true }; if (styleValue) { - setProperty(camelCasedProperty)(styleValue, options); + setProperty(property)(styleValue, options); } else { - deleteProperty(camelCasedProperty, options); + deleteProperty(property, options); } }} onChangeComplete={(styleValue) => { - setProperty(camelCasedProperty)(styleValue); + setProperty(property)(styleValue); }} /> ) } - property={camelCasedProperty} + property={property} styleSource={styleDecl.source.name} getOptions={() => [ - ...styleConfigByName(camelCasedProperty).items.map((item) => ({ + ...styleConfigByName(property).items.map((item) => ({ type: "keyword" as const, value: item.name, })), @@ -235,12 +233,12 @@ const AdvancedPropertyValue = ({ styleValue.type === "keyword" && styleValue.value.startsWith("--") ) { - setProperty(camelCasedProperty)( + setProperty(property)( { type: "var", value: styleValue.value.slice(2) }, { ...options, listed: true } ); } else { - setProperty(camelCasedProperty)(styleValue, { + setProperty(property)(styleValue, { ...options, listed: true, }); diff --git a/apps/builder/app/builder/features/style-panel/sections/size/size.tsx b/apps/builder/app/builder/features/style-panel/sections/size/size.tsx index b93298a0ed1c..1ec33bc72f4a 100644 --- a/apps/builder/app/builder/features/style-panel/sections/size/size.tsx +++ b/apps/builder/app/builder/features/style-panel/sections/size/size.tsx @@ -1,5 +1,8 @@ -import { propertyDescriptions } from "@webstudio-is/css-data"; -import type { StyleProperty } from "@webstudio-is/css-engine"; +import { + camelCaseProperty, + propertyDescriptions, +} from "@webstudio-is/css-data"; +import type { CssProperty, StyleProperty } from "@webstudio-is/css-engine"; import { Flex, Grid, @@ -24,12 +27,16 @@ import { PropertyLabel } from "../../property-label"; import { useComputedStyleDecl } from "../../shared/model"; import { deleteProperty } from "../../shared/use-style-data"; -const SizeProperty = ({ property }: { property: StyleProperty }) => { +const SizeProperty = ({ + property, +}: { + property: StyleProperty | CssProperty; +}) => { return ( diff --git a/apps/builder/app/builder/features/style-panel/shared/color-picker.tsx b/apps/builder/app/builder/features/style-panel/shared/color-picker.tsx index b01a81d6441c..a9638b989cc2 100644 --- a/apps/builder/app/builder/features/style-panel/shared/color-picker.tsx +++ b/apps/builder/app/builder/features/style-panel/shared/color-picker.tsx @@ -10,6 +10,7 @@ import type { KeywordValue, RgbValue, VarValue, + CssProperty, } from "@webstudio-is/css-engine"; import { Popover, @@ -174,7 +175,7 @@ type ColorPickerProps = { value: StyleValue; currentColor: StyleValue; getOptions?: () => Array; - property: StyleProperty; + property: StyleProperty | CssProperty; disabled?: boolean; }; diff --git a/apps/builder/app/builder/features/style-panel/shared/configs.ts b/apps/builder/app/builder/features/style-panel/shared/configs.ts index c0f8acd143f4..456efda3129e 100644 --- a/apps/builder/app/builder/features/style-panel/shared/configs.ts +++ b/apps/builder/app/builder/features/style-panel/shared/configs.ts @@ -1,5 +1,9 @@ -import type { StyleProperty } from "@webstudio-is/css-engine"; -import { keywordValues, properties } from "@webstudio-is/css-data"; +import type { CssProperty, StyleProperty } from "@webstudio-is/css-engine"; +import { + camelCaseProperty, + keywordValues, + properties, +} from "@webstudio-is/css-data"; import { humanizeString } from "~/shared/string-utils"; import type * as Controls from "../controls"; @@ -41,17 +45,18 @@ const getControl = (property: StyleProperty): Control => { const styleConfigCache = new Map(); -export const styleConfigByName = (propertyName: StyleProperty): StyleConfig => { - const fromCache = styleConfigCache.get(propertyName); +export const styleConfigByName = ( + propertyName: StyleProperty | CssProperty +): StyleConfig => { + // @todo propertyName is more narrow, only the props + // in that category, we are widening the type to include all properties + const property = camelCaseProperty(propertyName) as Property; + const fromCache = styleConfigCache.get(property); if (fromCache) { return fromCache; } - // @todo propertyName is more narrow, only the props - // in that category, we are widening the type to include all properties - const property = propertyName as Property; - const keywords = keywordValues[property] || []; const label = humanizeString(property); // property data does not exist for css custom properties @@ -65,7 +70,7 @@ export const styleConfigByName = (propertyName: StyleProperty): StyleConfig => { ...("mdnUrl" in propertyData && { mdnUrl: propertyData.mdnUrl }), }; - styleConfigCache.set(propertyName, result); + styleConfigCache.set(property, result); return result; }; diff --git a/apps/builder/app/builder/features/style-panel/shared/css-value-input/css-value-input.tsx b/apps/builder/app/builder/features/style-panel/shared/css-value-input/css-value-input.tsx index 7ff2850fd1df..553f01d9deb7 100644 --- a/apps/builder/app/builder/features/style-panel/shared/css-value-input/css-value-input.tsx +++ b/apps/builder/app/builder/features/style-panel/shared/css-value-input/css-value-input.tsx @@ -19,6 +19,7 @@ import { Text, } from "@webstudio-is/design-system"; import type { + CssProperty, KeywordValue, StyleProperty, StyleValue, @@ -41,6 +42,7 @@ import { useUnitSelect, type UnitOption } from "./unit-select"; import { parseIntermediateOrInvalidValue } from "./parse-intermediate-or-invalid-value"; import { toValue } from "@webstudio-is/css-engine"; import { + camelCaseProperty, declarationDescriptions, isValidDeclaration, properties, @@ -303,7 +305,7 @@ type CssValueInputProps = Pick< | "inputRef" > & { styleSource: StyleValueSourceColor; - property: StyleProperty; + property: StyleProperty | CssProperty; value: StyleValue | undefined; intermediateValue: CssValueInputValue | undefined; /** @@ -476,7 +478,7 @@ export const CssValueInput = ({ prefix, showSuffix = true, styleSource, - property, + property: multiCaseProperty, getOptions = () => [], onHighlight, onAbort, @@ -490,6 +492,7 @@ export const CssValueInput = ({ placeholder, ...props }: CssValueInputProps) => { + const property = camelCaseProperty(multiCaseProperty); const value = props.intermediateValue ?? props.value ?? initialValue; const valueRef = useRef(value); valueRef.current = value; diff --git a/apps/builder/app/builder/features/style-panel/shared/model.tsx b/apps/builder/app/builder/features/style-panel/shared/model.tsx index 13c589d7fb6b..fb68f408bfca 100644 --- a/apps/builder/app/builder/features/style-panel/shared/model.tsx +++ b/apps/builder/app/builder/features/style-panel/shared/model.tsx @@ -6,6 +6,7 @@ import { camelCaseProperty, properties } from "@webstudio-is/css-data"; import { compareMedia, toVarFallback, + type CssProperty, type StyleProperty, type StyleValue, type VarValue, @@ -336,9 +337,9 @@ export const useStyleObjectModel = () => { return useStore($model); }; -export const useComputedStyleDecl = (property: StyleProperty) => { +export const useComputedStyleDecl = (property: StyleProperty | CssProperty) => { const $store = useMemo( - () => createComputedStyleDeclStore(property), + () => createComputedStyleDeclStore(camelCaseProperty(property)), [property] ); return useStore($store); @@ -391,13 +392,16 @@ export const getInstanceStyleDecl = ( }); }; -export const useComputedStyles = (properties: StyleProperty[]) => { +export const useComputedStyles = ( + properties: (StyleProperty | CssProperty)[] +) => { // cache each computed style store const cachedStores = useRef( new Map>() ); const stores: ReadableAtom[] = []; - for (const property of properties) { + for (const multiCaseProperty of properties) { + const property = camelCaseProperty(multiCaseProperty); let store = cachedStores.current.get(property); if (store === undefined) { store = createComputedStyleDeclStore(property); diff --git a/apps/builder/app/builder/features/style-panel/shared/style-section.tsx b/apps/builder/app/builder/features/style-panel/shared/style-section.tsx index 7b161661fb0c..ad153c2b2d88 100644 --- a/apps/builder/app/builder/features/style-panel/shared/style-section.tsx +++ b/apps/builder/app/builder/features/style-panel/shared/style-section.tsx @@ -1,15 +1,15 @@ -import type { StyleProperty } from "@webstudio-is/css-engine"; +import type { ReactNode } from "react"; +import { PlusIcon } from "@webstudio-is/icons"; +import type { CssProperty, StyleProperty } from "@webstudio-is/css-engine"; import { SectionTitle, SectionTitleButton, SectionTitleLabel, } from "@webstudio-is/design-system"; -import type { ReactNode } from "react"; import { CollapsibleSectionRoot, useOpenState, } from "~/builder/shared/collapsible-section"; -import { PlusIcon } from "@webstudio-is/icons"; import { useComputedStyles } from "./model"; import type { ComputedStyleDecl } from "~/shared/style-object-model"; import { PropertySectionLabel } from "../property-label"; @@ -37,7 +37,7 @@ export const getDots = (styles: ComputedStyleDecl[]) => { export const StyleSection = (props: { label: string; - properties: StyleProperty[]; + properties: (StyleProperty | CssProperty)[]; // @todo remove to keep sections consistent fullWidth?: boolean; children: ReactNode; @@ -65,7 +65,7 @@ export const StyleSection = (props: { export const RepeatedStyleSection = (props: { label: string; description: string; - properties: [StyleProperty, ...StyleProperty[]]; + properties: [StyleProperty | CssProperty, ...(StyleProperty | CssProperty)[]]; collapsible?: boolean; onAdd: () => void; children: ReactNode; diff --git a/apps/builder/app/builder/features/style-panel/shared/use-style-data.ts b/apps/builder/app/builder/features/style-panel/shared/use-style-data.ts index 545b16530f1b..e04a9daac786 100644 --- a/apps/builder/app/builder/features/style-panel/shared/use-style-data.ts +++ b/apps/builder/app/builder/features/style-panel/shared/use-style-data.ts @@ -1,10 +1,9 @@ -import { - type Breakpoint, - type Instance, - getStyleDeclKey, - StyleDecl, -} from "@webstudio-is/sdk"; -import type { StyleProperty, StyleValue } from "@webstudio-is/css-engine"; +import { getStyleDeclKey, type StyleDecl } from "@webstudio-is/sdk"; +import type { + CssProperty, + StyleProperty, + StyleValue, +} from "@webstudio-is/css-engine"; import { $selectedBreakpoint, $selectedOrLastStyleSourceSelector, @@ -16,6 +15,7 @@ import { import { serverSyncStore } from "~/shared/sync"; import { $ephemeralStyles } from "~/canvas/stores"; import { $selectedInstance } from "~/shared/awareness"; +import { camelCaseProperty } from "@webstudio-is/css-data"; export type StyleUpdate = | { @@ -28,13 +28,6 @@ export type StyleUpdate = value: StyleValue; }; -type StyleUpdates = { - id: Instance["id"]; - updates: Array; - breakpoint: Breakpoint; - state: undefined | string; -}; - export type StyleUpdateOptions = { isEphemeral?: boolean; listed?: boolean }; export type SetValue = ( @@ -42,22 +35,24 @@ export type SetValue = ( options?: StyleUpdateOptions ) => void; -export type SetProperty = (property: StyleProperty) => SetValue; +export type SetProperty = (property: StyleProperty | CssProperty) => SetValue; export type DeleteProperty = ( - property: StyleProperty, + property: StyleProperty | CssProperty, options?: StyleUpdateOptions ) => void; export type CreateBatchUpdate = () => { - setProperty: (property: StyleProperty) => (style: StyleValue) => void; - deleteProperty: (property: StyleProperty) => void; + setProperty: ( + property: StyleProperty | CssProperty + ) => (style: StyleValue) => void; + deleteProperty: (property: StyleProperty | CssProperty) => void; publish: (options?: StyleUpdateOptions) => void; }; const publishUpdates = ( type: "update" | "preview", - updates: StyleUpdates["updates"], + updates: StyleUpdate[], options: StyleUpdateOptions ) => { if (updates.length === 0) { @@ -146,7 +141,13 @@ const publishUpdates = ( export const setProperty: SetProperty = (property) => { return (value, options: StyleUpdateOptions = { isEphemeral: false }) => { if (value.type !== "invalid") { - const updates = [{ operation: "set" as const, property, value }]; + const updates = [ + { + operation: "set" as const, + property: camelCaseProperty(property), + value, + }, + ]; const type = options.isEphemeral ? "preview" : "update"; publishUpdates(type, updates, options); @@ -155,30 +156,39 @@ export const setProperty: SetProperty = (property) => { }; export const deleteProperty = ( - property: StyleProperty, + property: StyleProperty | CssProperty, options: StyleUpdateOptions = { isEphemeral: false } ) => { - const updates = [{ operation: "delete" as const, property }]; + const updates = [ + { operation: "delete" as const, property: camelCaseProperty(property) }, + ]; const type = options.isEphemeral ? "preview" : "update"; publishUpdates(type, updates, options); }; export const createBatchUpdate = () => { - let updates: StyleUpdates["updates"] = []; + let updates: StyleUpdate[] = []; - const setProperty = (property: StyleProperty) => { + const setProperty = (property: StyleProperty | CssProperty) => { const setValue = (value: StyleValue) => { if (value.type === "invalid") { return; } - updates.push({ operation: "set", property, value }); + updates.push({ + operation: "set", + property: camelCaseProperty(property), + value, + }); }; return setValue; }; - const deleteProperty = (property: StyleProperty) => { - updates.push({ operation: "delete", property }); + const deleteProperty = (property: StyleProperty | CssProperty) => { + updates.push({ + operation: "delete", + property: camelCaseProperty(property), + }); }; const publish = (options: StyleUpdateOptions = { isEphemeral: false }) => { diff --git a/packages/css-data/src/parse-css.test.ts b/packages/css-data/src/parse-css.test.ts index 708fd392e67d..bac3717e828c 100644 --- a/packages/css-data/src/parse-css.test.ts +++ b/packages/css-data/src/parse-css.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "vitest"; -import { parseCss, parseMediaQuery } from "./parse-css"; +import { camelCaseProperty, parseCss, parseMediaQuery } from "./parse-css"; describe("Parse CSS", () => { test("longhand property name with keyword value", () => { @@ -768,3 +768,25 @@ test("parse media query", () => { }); expect(parseMediaQuery(`(hover: hover)`)).toEqual(undefined); }); + +test("camel case css property", () => { + expect(camelCaseProperty("margin-top")).toEqual("marginTop"); + expect(camelCaseProperty("-webkit-font-smoothing")).toEqual( + "WebkitFontSmoothing" + ); + expect(camelCaseProperty("-moz-osx-font-smoothing")).toEqual( + "MozOsxFontSmoothing" + ); +}); + +test("camel case css property multiple times", () => { + expect(camelCaseProperty(camelCaseProperty("margin-top"))).toEqual( + "marginTop" + ); + expect( + camelCaseProperty(camelCaseProperty("-webkit-font-smoothing")) + ).toEqual("WebkitFontSmoothing"); + expect( + camelCaseProperty(camelCaseProperty("-moz-osx-font-smoothing")) + ).toEqual("MozOsxFontSmoothing"); +}); diff --git a/packages/css-data/src/parse-css.ts b/packages/css-data/src/parse-css.ts index 45806fbab954..2da171028992 100644 --- a/packages/css-data/src/parse-css.ts +++ b/packages/css-data/src/parse-css.ts @@ -48,13 +48,21 @@ const normalizeProperty = (property: string): CssProperty => { * and convert to camel case only unprefixed properties * @todo stop converting to camel case and use hyphenated format */ -export const camelCaseProperty = (property: CssProperty): StyleProperty => { - property = normalizeProperty(property); +export const camelCaseProperty = ( + property: CssProperty | StyleProperty +): StyleProperty => { + property = normalizeProperty(property) as CssProperty | StyleProperty; // these are manually added with pascal case - if (property === "-webkit-font-smoothing") { + if ( + property === "-webkit-font-smoothing" || + property === "WebkitFontSmoothing" + ) { return "WebkitFontSmoothing"; } - if (property === "-moz-osx-font-smoothing") { + if ( + property === "-moz-osx-font-smoothing" || + property === "MozOsxFontSmoothing" + ) { return "MozOsxFontSmoothing"; } if (property.startsWith("-")) {