Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 89 additions & 1 deletion packages/design-system/src/components/color-picker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import { describe, test, expect } from "vitest";
import type { ColorSpace } from "hdr-color-input";
import type { ColorValue } from "@webstudio-is/css-engine";
import { __testing__ } from "./color-picker";
const { cssStringToStyleValue, shouldCommitColorChange } = __testing__;
const {
cssStringToStyleValue,
shouldCommitColorChange,
styleValueToColorInputColorSpace,
styleValueToColorInputValue,
} = __testing__;

describe("shouldCommitColorChange", () => {
test("returns false when values serialize identically", () => {
Expand Down Expand Up @@ -203,3 +208,86 @@ describe("cssStringToStyleValue", () => {
});
});
});

describe("styleValueToColorInputColorSpace", () => {
test("keeps pasted hex values in hex mode", () => {
expect(
styleValueToColorInputColorSpace({
type: "color",
colorSpace: "hex",
components: [1, 0.3412, 0.2],
alpha: 1,
})
).toBe("hex");
});

test("keeps rgb values in srgb mode", () => {
expect(
styleValueToColorInputColorSpace({
type: "color",
colorSpace: "srgb",
components: [1, 0.3412, 0.2],
alpha: 1,
})
).toBe("srgb");
expect(
styleValueToColorInputColorSpace({
type: "rgb",
r: 255,
g: 87,
b: 51,
alpha: 1,
})
).toBe("srgb");
});

test("maps internal color space names to color-input names", () => {
expect(
styleValueToColorInputColorSpace({
type: "color",
colorSpace: "p3",
components: [0.4, 0.6, 0.3],
alpha: 1,
})
).toBe("display-p3");
expect(
styleValueToColorInputColorSpace({
type: "color",
colorSpace: "a98rgb",
components: [0.4, 0.6, 0.3],
alpha: 1,
})
).toBe("a98-rgb");
});

test("uses srgb mode for color keywords", () => {
expect(
styleValueToColorInputColorSpace({
type: "keyword",
value: "red",
})
).toBe("srgb");
});
});

describe("styleValueToColorInputValue", () => {
test("converts color keywords to concrete rgb input values", () => {
expect(
styleValueToColorInputValue({
type: "keyword",
value: "red",
})
).toBe("rgb(255, 0, 0)");
});

test("keeps color values in their serialized format", () => {
expect(
styleValueToColorInputValue({
type: "color",
colorSpace: "hex",
components: [1, 0.3412, 0.2],
alpha: 1,
})
).toBe("#ff5733");
});
});
48 changes: 44 additions & 4 deletions packages/design-system/src/components/color-picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,36 @@ const cssStringToStyleValue = (
};
};

const styleValueToColorInputColorSpace = (
value: StyleValue
): ColorSpace | undefined => {
if (value.type === "rgb") {
return "srgb";
}
if (value.type === "color") {
switch (value.colorSpace) {
case "p3":
return "display-p3";
case "a98rgb":
return "a98-rgb";
default:
return value.colorSpace;
}
}
if (parseColor(toValue(value)) !== undefined) {
return "srgb";
}
};

const styleValueToColorInputValue = (value: StyleValue) => {
const valueString = toValue(value);
if (value.type === "color" || value.type === "rgb") {
return valueString;
}
const parsedColor = parseColor(valueString);
return parsedColor === undefined ? valueString : serializeColor(parsedColor);
};

// ─── ColorThumb ──────────────────────────────────────────────────────────────

const borderColorSwatch = parseColor(rawTheme.colors.borderColorSwatch);
Expand Down Expand Up @@ -163,6 +193,8 @@ const shouldCommitColorChange = (
// pass through to the real chip underneath.
export const __testing__ = {
cssStringToStyleValue,
styleValueToColorInputColorSpace,
styleValueToColorInputValue,
shouldCommitColorChange,
};

Expand Down Expand Up @@ -200,6 +232,8 @@ export const ColorPicker = ({
};

const colorString = toValue(value);
const colorSpace = styleValueToColorInputColorSpace(value);
const colorInputValue = styleValueToColorInputValue(value);

const overrideContrast = useCallback(() => {
const colorInputElement = colorInputRef.current;
Expand Down Expand Up @@ -239,11 +273,16 @@ export const ColorPicker = ({
// Sync external value changes into the web component.
useEffect(() => {
const colorInputElement = colorInputRef.current;
if (colorInputElement && colorInputElement.value !== colorString) {
colorInputElement.value = colorString;
if (colorInputElement && colorSpace !== undefined) {
colorInputElement.colorspace = colorSpace;
} else {
colorInputElement?.removeAttribute("colorspace");
}
if (colorInputElement && colorInputElement.value !== colorInputValue) {
colorInputElement.value = colorInputValue;
}
overrideContrast();
}, [colorString, overrideContrast]);
}, [colorSpace, colorInputValue, overrideContrast]);

// Wire up change / open / close events.
useEffect(() => {
Expand Down Expand Up @@ -380,7 +419,8 @@ export const ColorPicker = ({
{disabled === false && (
<color-input
ref={colorInputRef}
value={colorString}
value={colorInputValue}
colorspace={colorSpace}
theme="light"
class={`${textClass} ${scopeClass}`}
/>
Expand Down
14 changes: 14 additions & 0 deletions packages/project/src/db/build-patch-permissions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@ describe("getRequiredPermitForBuildPatchTransaction", () => {
).toBe("edit");
});

test("allows content block instance edits with edit permit", () => {
expect(
getRequiredPermitForBuildPatchTransaction(
transaction("instances", [
{
op: "replace",
path: ["instance-1", "children"],
value: [{ type: "text", value: "Title" }],
},
])
)
).toBe("edit");
});

test("requires build permit for style edits", () => {
expect(
getRequiredPermitForBuildPatchTransaction(transaction("styles"))
Expand Down
2 changes: 1 addition & 1 deletion packages/project/src/db/build-patch-permissions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { AuthPermit } from "@webstudio-is/trpc-interface/index.server";
import type { BuildPatchTransaction } from "./build-patch-core";

const contentNamespaces = new Set(["props"]);
const contentNamespaces = new Set(["instances", "props"]);

export const getRequiredPermitForBuildPatchTransaction = (
transaction: BuildPatchTransaction
Expand Down
Loading