mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-09 05:26:24 +09:00
experimental: add ui controls for managing perspective-origin from transforms (#3950)
## Description This PR helps in managing perspective origin which is similar to transform origin under transform section. ## Steps for reproduction - Click perspective-origin from the dropdown on transform section. - Can change the values of x and y axises using the positional grid. - Can change values of x and y using the individual inputs by scrub/manually entering/keyboard up&down - Add values for perspective -origin from the advanced tab and update those values using the UI controls. - Reset the property using the label indicator. Use keywords from the suggestions dropdown for x and y axis like left, top, center etc. ## Code Review - [ ] hi @kof, I need you to do - conceptual review (architecture, feature-correctness) - detailed review (read every line) - test it on preview ## Before requesting a review - [x] made a self-review - [x] added inline comments where things may be not obvious (the "why", not "what") ## Before merging - [x] tested locally and on preview environment (preview dev login: 5de6) - [x] updated [test cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md) document - [ ] added tests - [ ] if any new env variables are added, added them to `.env` file 
This commit is contained in:
1 parent
b5585f850d
commit
cb73ab65bb
4 files changed
+104
-45
No files matched your search
+11
-34
@@ -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<string, number> = {
|
||||
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<KeywordValue> = ["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) => {
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
{origin.z !== undefined && (
|
||||
{property === "transformOrigin" && origin.z !== undefined && (
|
||||
<Flex gap="2" align="center">
|
||||
<Label>Z</Label>
|
||||
<CssValueInputContainer
|
||||
+43
-7
@@ -64,16 +64,46 @@ const isValidTransformOriginValue = (
|
||||
// Both transform and perspective origin shares the same syntax for their values.
|
||||
// The only difference is `transform-origin` can have a 3rd value for z-axis.
|
||||
export const extractTransformOrPerspectiveOriginValues = (
|
||||
value: TupleValue
|
||||
value: TupleValue | KeywordValue
|
||||
): {
|
||||
x: KeywordValue | UnitValue;
|
||||
y: KeywordValue | UnitValue;
|
||||
z?: UnitValue;
|
||||
} => {
|
||||
const xAxisKeywordValues: Array<string> = ["left", "right"];
|
||||
const yAxisKeywordValues: Array<string> = ["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;
|
||||
|
||||
+34
-1
@@ -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<string, number> = {
|
||||
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;
|
||||
};
|
||||
@@ -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<StyleProperty>;
|
||||
|
||||
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}
|
||||
|
||||
<BackfaceVisibility {...props} />
|
||||
<TransformOrigin {...props} />
|
||||
<TransformAndPerspectiveOrigin property="transformOrigin" {...props} />
|
||||
<TransformPerspective {...props} />
|
||||
<TransformAndPerspectiveOrigin property="perspectiveOrigin" {...props} />
|
||||
</CollapsibleSectionRoot>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user