refactor: hyphenate css-value-input (#5003)

Migrated all css-value-input internals and usages to hyphenated
properties. Touched animation section a little.
This commit is contained in:
Bogdan Chadkin authored and GitHub committed 2025-03-15 17:30:35 +07:00
1 parent bee9615869
commit b4cc9ffbfb
16 files changed
+73 -104

No files matched your search

@@ -65,7 +65,7 @@ const OffsetInput = ({
intermediateValue={intermediateValue}
styleSource="default"
/* same as offset has 0 - 100% */
property={"fontStretch"}
property={"font-stretch"}
value={
value !== undefined
? {
@@ -109,7 +109,7 @@ const RangeValueInput = ({
styleSource="default"
value={value}
/* marginLeft to allow negative values */
property={"marginLeft"}
property={"margin-left"}
unitOptions={unitOptions}
intermediateValue={intermediateValue}
onChange={(styleValue) => {
@@ -156,8 +156,6 @@ const EasingInput = ({
StyleValue | IntermediateStyleValue
>();
const property = "animationTimingFunction";
return (
<CssValueInput
id={id}
@@ -173,7 +171,7 @@ const EasingInput = ({
value,
})),
]}
property={property}
property="animation-timing-function"
intermediateValue={intermediateValue}
onChange={(styleValue) => {
setIntermediateValue(styleValue);
@@ -115,7 +115,7 @@ const InsetValueInput = ({
styleSource="default"
value={value}
/* marginLeft to allow negative values */
property={"marginLeft"}
property="margin-left"
unitOptions={unitOptions}
intermediateValue={intermediateValue}
onChange={(styleValue) => {
@@ -1,8 +1,4 @@
import {
hyphenateProperty,
type CssProperty,
type StyleProperty,
} from "@webstudio-is/css-engine";
import type { CssProperty } from "@webstudio-is/css-engine";
import { keywordValues } from "@webstudio-is/css-data";
import { ColorPicker } from "../../shared/color-picker";
import {
@@ -11,11 +7,7 @@ import {
} from "../../shared/model";
import { deleteProperty, setProperty } from "../../shared/use-style-data";
export const ColorControl = ({
property,
}: {
property: StyleProperty | CssProperty;
}) => {
export const ColorControl = ({ property }: { property: CssProperty }) => {
const computedStyleDecl = useComputedStyleDecl(property);
const value = computedStyleDecl.cascadedValue;
const currentColor = computedStyleDecl.usedValue;
@@ -26,7 +18,7 @@ export const ColorControl = ({
value={value}
currentColor={currentColor}
getOptions={() => [
...(keywordValues[hyphenateProperty(property)] ?? []).map((item) => ({
...(keywordValues[property] ?? []).map((item) => ({
type: "keyword" as const,
value: item,
})),
@@ -35,9 +27,7 @@ export const ColorControl = ({
onChange={(styleValue) => setValue(styleValue, { isEphemeral: true })}
onChangeComplete={setValue}
onAbort={() => deleteProperty(property, { isEphemeral: true })}
onReset={() => {
deleteProperty(property);
}}
onReset={() => deleteProperty(property)}
/>
);
};
@@ -7,9 +7,7 @@ import {
TupleValue,
TupleValueItem,
type StyleValue,
type StyleProperty,
type CssProperty,
hyphenateProperty,
} from "@webstudio-is/css-engine";
import { Flex, Grid, PositionGrid } from "@webstudio-is/design-system";
import type { ComputedStyleDecl } from "~/shared/style-object-model";
@@ -58,16 +56,14 @@ export const PositionControl = ({
property,
styleDecl,
}: {
property: StyleProperty | CssProperty;
property: CssProperty;
styleDecl: ComputedStyleDecl;
}) => {
const value = toTuple(styleDecl.cascadedValue);
const keywords = (keywordValues[hyphenateProperty(property)] ?? []).map(
(value) => ({
type: "keyword" as const,
value,
})
);
const keywords = (keywordValues[property] ?? []).map((value) => ({
type: "keyword" as const,
value,
}));
const setValue = setProperty(property);
@@ -1,10 +1,5 @@
import { useState } from "react";
import {
hyphenateProperty,
type CssProperty,
type StyleProperty,
type StyleValue,
} from "@webstudio-is/css-engine";
import type { CssProperty, StyleValue } from "@webstudio-is/css-engine";
import { keywordValues } from "@webstudio-is/css-data";
import {
CssValueInput,
@@ -16,11 +11,7 @@ import {
useComputedStyleDecl,
} from "../../shared/model";
export const TextControl = ({
property,
}: {
property: StyleProperty | CssProperty;
}) => {
export const TextControl = ({ property }: { property: CssProperty }) => {
const computedStyleDecl = useComputedStyleDecl(property);
const value = computedStyleDecl.cascadedValue;
const setValue = setProperty(property);
@@ -34,7 +25,7 @@ export const TextControl = ({
value={value}
intermediateValue={intermediateValue}
getOptions={() => [
...(keywordValues[hyphenateProperty(property)] ?? []).map((value) => ({
...(keywordValues[property] ?? []).map((value) => ({
type: "keyword" as const,
value,
})),
@@ -6,7 +6,7 @@ import type {
} from "@webstudio-is/css-engine";
import { toValue } from "@webstudio-is/css-engine";
import { numericScrubControl } from "@webstudio-is/design-system";
import { camelCaseProperty, isValidDeclaration } from "@webstudio-is/css-data";
import { isValidDeclaration } from "@webstudio-is/css-data";
import { useModifierKeys } from "../../shared/modifier-keys";
import type { StyleUpdateOptions } from "../../shared/use-style-data";
import { parseIntermediateOrInvalidValue } from "../../shared/css-value-input/parse-intermediate-or-invalid-value";
@@ -85,7 +85,7 @@ export const useScrub = <P extends CssProperty>(props: {
} as const;
if (isValidDeclaration(property, toValue(value)) === false) {
value = parseIntermediateOrInvalidValue(camelCaseProperty(property), {
value = parseIntermediateOrInvalidValue(property, {
type: "intermediate",
value: `${value.value}`,
unit: value.unit,
@@ -125,7 +125,7 @@ export const TransitionContent = ({ index }: { index: number }) => {
<PropertyInlineLabel
label="Property"
description={propertyDescriptions.transitionProperty}
properties={["transitionProperty"]}
properties={["transition-property"]}
/>
<TransitionProperty
value={property ?? propertiesData["transition-property"].initial}
@@ -138,10 +138,10 @@ export const TransitionContent = ({ index }: { index: number }) => {
<PropertyInlineLabel
label="Duration"
description={propertyDescriptions.transitionDuration}
properties={["transitionDuration"]}
properties={["transition-duration"]}
/>
<CssValueInputContainer
property="transitionDuration"
property="transition-duration"
styleSource="local"
getOptions={() => $availableUnitVariables.get()}
value={duration ?? propertiesData["transition-duration"].initial}
@@ -163,10 +163,10 @@ export const TransitionContent = ({ index }: { index: number }) => {
<PropertyInlineLabel
label="Delay"
description={propertyDescriptions.transitionDelay}
properties={["transitionDelay"]}
properties={["transition-delay"]}
/>
<CssValueInputContainer
property="transitionDelay"
property="transition-delay"
styleSource="local"
getOptions={() => $availableUnitVariables.get()}
value={delay ?? propertiesData["transition-delay"].initial}
@@ -188,10 +188,10 @@ export const TransitionContent = ({ index }: { index: number }) => {
<PropertyInlineLabel
label="Easing"
description={propertyDescriptions.transitionTimingFunction}
properties={["transitionTimingFunction"]}
properties={["transition-timing-function"]}
/>
<CssValueInputContainer
property="transitionTimingFunction"
property="transition-timing-function"
styleSource="local"
getOptions={() => [
{ type: "keyword", value: "linear" },
@@ -5,7 +5,6 @@ import { useDebouncedCallback } from "use-debounce";
import { RgbaColorPicker } from "react-colorful";
import { EyedropperIcon } from "@webstudio-is/icons";
import type {
StyleProperty,
StyleValue,
KeywordValue,
RgbValue,
@@ -175,7 +174,7 @@ type ColorPickerProps = {
value: StyleValue;
currentColor: StyleValue;
getOptions?: () => Array<KeywordValue | VarValue>;
property: StyleProperty | CssProperty;
property: CssProperty;
disabled?: boolean;
};
@@ -67,7 +67,7 @@ export const WithIcons = () => {
return (
<CssValueInput
styleSource="preset"
property="alignItems"
property="align-items"
value={value}
intermediateValue={intermediateValue}
getOptions={() => [
@@ -116,7 +116,7 @@ export const WithUnits = () => {
<Flex css={{ gap: theme.spacing[9] }}>
<CssValueInput
styleSource="preset"
property="rowGap"
property="row-gap"
value={value}
intermediateValue={intermediateValue}
getOptions={() => [
@@ -172,7 +172,7 @@ export const Oversized = () => {
<Flex css={{ width: 100 }}>
<CssValueInput
styleSource="preset"
property="alignItems"
property="align-items"
value={value}
intermediateValue={intermediateValue}
onChange={(newValue) => {
@@ -21,7 +21,6 @@ import {
import type {
CssProperty,
KeywordValue,
StyleProperty,
StyleValue,
Unit,
VarValue,
@@ -40,7 +39,7 @@ import {
} from "react";
import { useUnitSelect, type UnitOption } from "./unit-select";
import { parseIntermediateOrInvalidValue } from "./parse-intermediate-or-invalid-value";
import { hyphenateProperty, toValue } from "@webstudio-is/css-engine";
import { toValue } from "@webstudio-is/css-engine";
import {
camelCaseProperty,
declarationDescriptions,
@@ -61,9 +60,8 @@ import {
import { useEffectEvent } from "~/shared/hook-utils/effect-event";
// We need to enable scrub on properties that can have numeric value.
const canBeNumber = (property: StyleProperty, value: CssValueInputValue) => {
const unitGroups =
propertiesData[hyphenateProperty(property)]?.unitGroups ?? [];
const canBeNumber = (property: CssProperty, value: CssValueInputValue) => {
const unitGroups = propertiesData[property]?.unitGroups ?? [];
// allow scrubbing css variables with unit value
return unitGroups.length !== 0 || value.type === "unit";
};
@@ -93,7 +91,7 @@ const useScrub = ({
defaultUnit: Unit | undefined;
value: CssValueInputValue;
intermediateValue: CssValueInputValue | undefined;
property: StyleProperty;
property: CssProperty;
onChange: (value: CssValueInputValue | undefined) => void;
onChangeComplete: (value: StyleValue) => void;
onAbort: () => void;
@@ -302,7 +300,7 @@ type CssValueInputProps = Pick<
| "inputRef"
> & {
styleSource: StyleValueSourceColor;
property: StyleProperty | CssProperty;
property: CssProperty;
value: StyleValue | undefined;
intermediateValue: CssValueInputValue | undefined;
/**
@@ -475,7 +473,7 @@ export const CssValueInput = ({
prefix,
showSuffix = true,
styleSource,
property: multiCaseProperty,
property,
getOptions = () => [],
onHighlight,
onAbort,
@@ -489,7 +487,6 @@ export const CssValueInput = ({
placeholder,
...props
}: CssValueInputProps) => {
const property = camelCaseProperty(multiCaseProperty);
const value = props.intermediateValue ?? props.value ?? initialValue;
const valueRef = useRef(value);
valueRef.current = value;
@@ -607,7 +604,7 @@ export const CssValueInput = ({
const [isUnitsOpen, unitSelectElement] = useUnitSelect({
options: unitOptions,
property: hyphenateProperty(multiCaseProperty),
property,
value,
onChange: (unitOrKeyword) => {
if (unitOrKeyword.type === "keyword") {
@@ -650,7 +647,7 @@ export const CssValueInput = ({
// value is a keyword or non numeric, try get browser style value and convert it
if (value.type === "keyword" || value.type === "intermediate") {
const styleValue = propertySizes[hyphenateProperty(property)];
const styleValue = propertySizes[property];
const convertedValue = convertUnits(unitSizes)(
styleValue?.value ?? 0,
styleValue?.unit ?? "number",
@@ -790,7 +787,7 @@ export const CssValueInput = ({
) {
description = option.description;
} else {
const key = `${property}:${toValue(
const key = `${camelCaseProperty(property)}:${toValue(
valueForDescription
)}` as keyof typeof declarationDescriptions;
description = declarationDescriptions[key];
@@ -808,7 +805,7 @@ export const CssValueInput = ({
.map((item) =>
item.type === "keyword"
? declarationDescriptions[
`${property}:${toValue(
`${camelCaseProperty(property)}:${toValue(
item
)}` as keyof typeof declarationDescriptions
]
@@ -1,9 +1,8 @@
import {
type StyleProperty,
type StyleValue,
type InvalidValue,
type Unit,
hyphenateProperty,
import type {
StyleValue,
InvalidValue,
Unit,
CssProperty,
} from "@webstudio-is/css-engine";
import {
units,
@@ -17,9 +16,8 @@ import { toKebabCase } from "../keyword-utils";
const unitsList = Object.values(units).flat();
const getDefaultUnit = (property: StyleProperty): Unit => {
const unitGroups =
propertiesData[hyphenateProperty(property)]?.unitGroups ?? [];
const getDefaultUnit = (property: CssProperty): Unit => {
const unitGroups = propertiesData[property]?.unitGroups ?? [];
for (const unitGroup of unitGroups) {
if (unitGroup === "number") {
@@ -41,7 +39,7 @@ const getDefaultUnit = (property: StyleProperty): Unit => {
};
export const parseIntermediateOrInvalidValue = (
property: StyleProperty,
property: CssProperty,
styleValue: IntermediateStyleValue | InvalidValue,
defaultUnit: Unit = getDefaultUnit(property),
originalValue?: string
@@ -2,11 +2,11 @@ import { describe, test, expect } from "vitest";
import { parseIntermediateOrInvalidValue } from "./parse-intermediate-or-invalid-value";
import { toKebabCase, toPascalCase } from "../keyword-utils";
const properties = ["width", "lineHeight"] as const;
const properties = ["width", "line-height"] as const;
const propertiesAndKeywords = [
["width", "auto" as string],
["lineHeight", "normal" as string],
["line-height", "normal" as string],
] as const;
test("forgive trailing semicolon", () => {
@@ -55,7 +55,7 @@ describe("Parse intermediate or invalid value without math evaluation", () => {
});
test("fallback to % if px is not supported", () => {
const result = parseIntermediateOrInvalidValue("fontStretch", {
const result = parseIntermediateOrInvalidValue("font-stretch", {
type: "intermediate",
value: "10",
});
@@ -133,7 +133,7 @@ describe("Parse intermediate or invalid value without math evaluation", () => {
});
test("keyword with pascal case name", () => {
const result = parseIntermediateOrInvalidValue("boxSizing", {
const result = parseIntermediateOrInvalidValue("box-sizing", {
type: "intermediate",
value: "Border Box",
unit: "em",
@@ -173,7 +173,7 @@ describe("Parse intermediate or invalid value without math evaluation", () => {
});
test("tolerate comma instead of dot typo while correctly parsing legit comma inside value", () => {
const result = parseIntermediateOrInvalidValue("transitionDuration", {
const result = parseIntermediateOrInvalidValue("transition-duration", {
type: "intermediate",
value: "1s, 2s",
});
@@ -327,7 +327,7 @@ describe("Returns invalid if can't parse", () => {
});
test("do not accept wrong keywords", () => {
const result = parseIntermediateOrInvalidValue("lineHeight", {
const result = parseIntermediateOrInvalidValue("line-height", {
type: "intermediate",
value: "auto",
});
@@ -341,7 +341,7 @@ describe("Returns invalid if can't parse", () => {
describe("Value ending with `-` should be considered unitless", () => {
test("Unitless intermediate transformed to unitless", () => {
const result = parseIntermediateOrInvalidValue("lineHeight", {
const result = parseIntermediateOrInvalidValue("line-height", {
type: "intermediate",
value: "10-",
});
@@ -354,7 +354,7 @@ describe("Value ending with `-` should be considered unitless", () => {
});
test("Unit intermediate transformed to unitless", () => {
const result = parseIntermediateOrInvalidValue("lineHeight", {
const result = parseIntermediateOrInvalidValue("line-height", {
type: "intermediate",
value: "10-",
unit: "em",
@@ -368,7 +368,7 @@ describe("Value ending with `-` should be considered unitless", () => {
});
test("Unit intermediate with space transformed to unitless", () => {
const result = parseIntermediateOrInvalidValue("lineHeight", {
const result = parseIntermediateOrInvalidValue("line-height", {
type: "intermediate",
value: "10 -",
unit: "em",
@@ -382,7 +382,7 @@ describe("Value ending with `-` should be considered unitless", () => {
});
test("Unit number intermediate transformed to unitless", () => {
const result = parseIntermediateOrInvalidValue("lineHeight", {
const result = parseIntermediateOrInvalidValue("line-height", {
type: "intermediate",
value: "10",
unit: "number",
@@ -396,7 +396,7 @@ describe("Value ending with `-` should be considered unitless", () => {
});
test("Unitless expression transformed to unitless", () => {
const result = parseIntermediateOrInvalidValue("lineHeight", {
const result = parseIntermediateOrInvalidValue("line-height", {
type: "intermediate",
value: "10 + 20 -",
unit: "px",
@@ -410,7 +410,7 @@ describe("Value ending with `-` should be considered unitless", () => {
});
test("Expression containing unit and unitless must be a unit", () => {
const result = parseIntermediateOrInvalidValue("lineHeight", {
const result = parseIntermediateOrInvalidValue("line-height", {
type: "intermediate",
value: "10px + 20 -",
unit: "px",
@@ -638,7 +638,7 @@ test("parse css variables as unparsed", () => {
test("parse z-index", () => {
expect(
parseIntermediateOrInvalidValue("zIndex", {
parseIntermediateOrInvalidValue("z-index", {
type: "intermediate",
value: "6.5",
unit: "number",
@@ -1,21 +1,21 @@
import { useEffect, useState } from "react";
import {
NestedInputButton,
rawTheme,
theme,
} from "@webstudio-is/design-system";
import { MaximizeIcon } from "@webstudio-is/icons";
import { useEffect, useState } from "react";
import type {
CssProperty,
InvalidValue,
StyleValue,
} from "@webstudio-is/css-engine";
import { EditorDialog } from "~/builder/shared/code-editor-base";
import { CssFragmentEditorContent } from "../css-fragment";
import type {
CssValueInputValue,
IntermediateStyleValue,
} from "./css-value-input";
import {
type InvalidValue,
type StyleProperty,
type StyleValue,
} from "@webstudio-is/css-engine";
import { parseIntermediateOrInvalidValue } from "./parse-intermediate-or-invalid-value";
export const cssButtonDisplay = "--ws-css-value-input-maximize-button-display";
@@ -44,7 +44,7 @@ export const ValueEditorDialog = ({
value,
onChangeComplete,
}: {
property: StyleProperty;
property: CssProperty;
value: string;
onChangeComplete: (value: StyleValue) => void;
}) => {
@@ -40,7 +40,7 @@ import { parseCssFragment } from "./css-fragment";
const filterFunctions = {
// https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/blur#syntax
// length
blur: { default: "0px", fakeProperty: "outlineOffset" },
blur: { default: "0px", fakeProperty: "outline-offset" },
// https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/brightness#formal_syntax
// number | percentage
brightness: { default: "0%", fakeProperty: "opacity" },
@@ -199,7 +199,7 @@ export const FilterSectionContent = ({
property={
filterFunction
? filterFunctions[filterFunction].fakeProperty
: "outlineOffset"
: "outline-offset"
}
styleSource="local"
value={
@@ -219,7 +219,7 @@ export const ShadowContent = ({
<CssValueInputContainer
key="boxShadowOffsetX"
// outline-offset is a fake property for validating box-shadow's offsetX.
property="outlineOffset"
property="outline-offset"
styleSource="local"
disabled={layer.type === "var"}
value={offsetX ?? { type: "unit", value: 0, unit: "px" }}
@@ -243,7 +243,7 @@ export const ShadowContent = ({
<CssValueInputContainer
key="boxShadowOffsetY"
// outline-offset is a fake property for validating box-shadow's offsetY.
property="outlineOffset"
property="outline-offset"
styleSource="local"
disabled={layer.type === "var"}
value={offsetY ?? { type: "unit", value: 0, unit: "px" }}
@@ -267,7 +267,7 @@ export const ShadowContent = ({
<CssValueInputContainer
key="boxShadowBlur"
// border-top-width is a fake property for validating box-shadow's blur.
property="borderTopWidth"
property="border-top-width"
styleSource="local"
disabled={layer.type === "var"}
value={blur ?? { type: "unit", value: 0, unit: "px" }}
@@ -292,7 +292,7 @@ export const ShadowContent = ({
<CssValueInputContainer
key="boxShadowSpread"
// outline-offset is a fake property for validating box-shadow's spread.
property="outlineOffset"
property="outline-offset"
styleSource="local"
disabled={layer.type === "var"}
value={spread ?? { type: "unit", value: 0, unit: "px" }}