diff --git a/apps/builder/app/builder/features/style-panel/sections/transforms/transform-origin.tsx b/apps/builder/app/builder/features/style-panel/sections/transforms/transform-and-perspective-origin.tsx similarity index 84% rename from apps/builder/app/builder/features/style-panel/sections/transforms/transform-origin.tsx rename to apps/builder/app/builder/features/style-panel/sections/transforms/transform-and-perspective-origin.tsx index c575eb6603..21505c796b 100644 --- a/apps/builder/app/builder/features/style-panel/sections/transforms/transform-origin.tsx +++ b/apps/builder/app/builder/features/style-panel/sections/transforms/transform-and-perspective-origin.tsx @@ -19,8 +19,7 @@ import { useMemo } from "react"; import { extractTransformOrPerspectiveOriginValues } from "./transform-extractors"; import { CssValueInputContainer } from "../../shared/css-value-input"; import type { StyleUpdateOptions } from "../../shared/use-style-data"; - -const property: StyleProperty = "transformOrigin"; +import { calculatePositionFromOrigin } from "./transform-utils"; // Fake properties to use in the CssValueInputContainer // x, y axis takes length | percentage | keyword @@ -29,44 +28,22 @@ const fakePropertyX: StyleProperty = "backgroundPositionX"; const fakePropertyY: StyleProperty = "backgroundPositionY"; const fakePropertyZ: StyleProperty = "outlineOffset"; -const keyworkToValue: Record = { - left: 0, - right: 100, - center: 50, - top: 0, - bottom: 100, -}; - -const calculateBackgroundPosition = (value: StyleValue | undefined) => { - if (value === undefined) { - return 50; - } - - if (value.type === "unit") { - return value.value; - } - - if (value.type === "keyword") { - return keyworkToValue[value.value]; - } - - return 0; -}; - -export const TransformOrigin = (props: SectionProps) => { - const { currentStyle, deleteProperty, setProperty } = props; +export const TransformAndPerspectiveOrigin = ( + props: SectionProps & { property: StyleProperty } +) => { + const { currentStyle, deleteProperty, setProperty, property } = props; const value = currentStyle[property]?.local; const { label } = styleConfigByName(property); const origin = useMemo(() => { - if (value?.type !== "tuple") { + if (value?.type !== "tuple" && value?.type !== "keyword") { return; } return extractTransformOrPerspectiveOriginValues(value); }, [value]); - const xInfo = useMemo(() => calculateBackgroundPosition(origin?.x), [origin]); - const yInfo = useMemo(() => calculateBackgroundPosition(origin?.y), [origin]); + const xInfo = useMemo(() => calculatePositionFromOrigin(origin?.x), [origin]); + const yInfo = useMemo(() => calculatePositionFromOrigin(origin?.y), [origin]); const xOriginKeywords: Array = ["left", "center", "right"].map( (value) => ({ type: "keyword", @@ -80,7 +57,7 @@ export const TransformOrigin = (props: SectionProps) => { }) ); - if (value?.type !== "tuple" || origin === undefined) { + if (origin === undefined) { return; } @@ -140,7 +117,7 @@ export const TransformOrigin = (props: SectionProps) => { ], }; - if (origin.z !== undefined) { + if (property === "transformOrigin" && origin.z !== undefined) { value.value.push(origin.z); } @@ -194,7 +171,7 @@ export const TransformOrigin = (props: SectionProps) => { } /> - {origin.z !== undefined && ( + {property === "transformOrigin" && origin.z !== undefined && ( { + const xAxisKeywordValues: Array = ["left", "right"]; + const yAxisKeywordValues: Array = ["top", "bottom"]; let x: KeywordValue | UnitValue = { type: "keyword", value: "center" }; let y: KeywordValue | UnitValue = { type: "keyword", value: "center" }; let z: UnitValue | undefined; + if (value.type === "keyword") { + if (value.value === "center") { + return { + x: value, + y: value, + }; + } + + if (yAxisKeywordValues.includes(value.value)) { + return { + x: x, + y: value, + }; + } + + if (xAxisKeywordValues.includes(value.value)) { + return { + x: value, + y: y, + }; + } + + return { + x: value, + y: value, + }; + } + // https://www.w3.org/TR/css-transforms-1/#transform-origin-property // If only one value is specified, the second value is assumed to be center. if (value.value.length === 1 && value.value[0].type === "unit") { @@ -81,11 +111,11 @@ export const extractTransformOrPerspectiveOriginValues = ( } if (value.value.length === 1 && value.value[0].type === "keyword") { - if (["top", "bottom"].includes(value.value[0].value)) { + if (yAxisKeywordValues.includes(value.value[0].value)) { y = value.value[0]; } - if (["left", "right"].includes(value.value[0].value)) { + if (xAxisKeywordValues.includes(value.value[0].value)) { x = value.value[0]; } } @@ -103,12 +133,15 @@ export const extractTransformOrPerspectiveOriginValues = ( x = first; y = second; - if (first.type === "keyword" && ["left", "right"].includes(first.value)) { + if ( + first.type === "keyword" && + xAxisKeywordValues.includes(first.value) + ) { x = first; y = second; } else if ( first.type === "keyword" && - ["top", "bottom"].includes(first.value) + yAxisKeywordValues.includes(first.value) ) { y = first; x = second; @@ -127,12 +160,15 @@ export const extractTransformOrPerspectiveOriginValues = ( y = second; z = third; - if (first.type === "keyword" && ["left", "right"].includes(first.value)) { + if ( + first.type === "keyword" && + xAxisKeywordValues.includes(first.value) + ) { x = first; y = second; } else if ( first.type === "keyword" && - ["top", "bottom"].includes(first.value) + yAxisKeywordValues.includes(first.value) ) { y = first; x = second; diff --git a/apps/builder/app/builder/features/style-panel/sections/transforms/transform-utils.ts b/apps/builder/app/builder/features/style-panel/sections/transforms/transform-utils.ts index 6fe94bd126..bd850c70f2 100644 --- a/apps/builder/app/builder/features/style-panel/sections/transforms/transform-utils.ts +++ b/apps/builder/app/builder/features/style-panel/sections/transforms/transform-utils.ts @@ -1,6 +1,7 @@ import { parseCssValue } from "@webstudio-is/css-data"; import { FunctionValue, + StyleValue, toValue, type TupleValue, type TupleValueItem, @@ -106,6 +107,11 @@ export const addDefaultsForTransormSection = (props: { return setProperty("transformOrigin")(transformOrigin); } + case "perspectiveOrigin": { + const perspectiveOrigin = parseCssValue("perspectiveOrigin", "50% 50%"); + return setProperty("perspectiveOrigin")(perspectiveOrigin); + } + case "backfaceVisibility": { return setProperty("backfaceVisibility")({ type: "keyword", @@ -167,11 +173,14 @@ export const isTransformPanelPropertyUsed = (params: { return currentStyle["backfaceVisibility"]?.local?.type === "keyword"; case "transformOrigin": - return currentStyle["transformOrigin"]?.local?.type === "tuple"; + return currentStyle["transformOrigin"]?.local !== undefined; case "perspective": return currentStyle["perspective"]?.local !== undefined; + case "perspectiveOrigin": + return currentStyle["perspectiveOrigin"]?.local !== undefined; + case "rotate": { const rotate = currentStyle["transform"]?.value; return ( @@ -379,3 +388,27 @@ export const updateRotateOrSkewPropertyValue = (props: { return newPropertyValue; }; + +const keywordToValue: Record = { + left: 0, + right: 100, + center: 50, + top: 0, + bottom: 100, +}; + +export const calculatePositionFromOrigin = (value: StyleValue | undefined) => { + if (value === undefined) { + return 50; + } + + if (value.type === "unit") { + return value.value; + } + + if (value.type === "keyword") { + return keywordToValue[value.value]; + } + + return 0; +}; diff --git a/apps/builder/app/builder/features/style-panel/sections/transforms/transforms.tsx b/apps/builder/app/builder/features/style-panel/sections/transforms/transforms.tsx index 4e1eb070b2..6eef53d0d3 100644 --- a/apps/builder/app/builder/features/style-panel/sections/transforms/transforms.tsx +++ b/apps/builder/app/builder/features/style-panel/sections/transforms/transforms.tsx @@ -45,7 +45,7 @@ import { getStyleSource } from "../../shared/style-info"; import { PropertyName } from "../../shared/property-name"; import { getDots } from "../../shared/collapsible-section"; import { isFeatureEnabled } from "@webstudio-is/feature-flags"; -import { TransformOrigin } from "./transform-origin"; +import { TransformAndPerspectiveOrigin } from "./transform-and-perspective-origin"; export const transformPanels = [ "translate", @@ -59,6 +59,7 @@ export const transformPanelDropdown = [ "backfaceVisibility", "transformOrigin", "perspective", + "perspectiveOrigin", ] as const; export type TransformPanel = (typeof transformPanels)[number]; @@ -71,6 +72,7 @@ export const properties = [ "transformOrigin", "backfaceVisibility", "perspective", + "perspectiveOrigin", ] satisfies Array; export const Section = (props: SectionProps) => { @@ -88,6 +90,12 @@ export const Section = (props: SectionProps) => { currentStyle["backfaceVisibility"] ); const perspectiveStyleSource = getStyleSource(currentStyle["perspective"]); + const transformOriginStyleSource = getStyleSource( + currentStyle["transformOrigin"] + ); + const perspectiveOriginStyleSource = getStyleSource( + currentStyle["perspectiveOrigin"] + ); const isAnyTransformPropertyAdded = transformPanels.some((panel) => isTransformPanelPropertyUsed({ @@ -104,6 +112,8 @@ export const Section = (props: SectionProps) => { batch.deleteProperty("transformOrigin"); batch.deleteProperty("backfaceVisibility"); batch.deleteProperty("perspective"); + batch.deleteProperty("perspectiveOrigin"); + batch.publish(); }; @@ -165,7 +175,9 @@ export const Section = (props: SectionProps) => { scaleStyleSource || rotateAndSkewStyleSrouce || backfaceVisibilityStyleSource || - perspectiveStyleSource + perspectiveStyleSource || + transformOriginStyleSource || + perspectiveOriginStyleSource } > {label} @@ -192,8 +204,9 @@ export const Section = (props: SectionProps) => { ) : undefined} - + + ); };