diff --git a/apps/builder/app/builder/features/style-panel/controls/font-family/font-family-control.tsx b/apps/builder/app/builder/features/style-panel/controls/font-family/font-family-control.tsx index 7f2a3c7864b4..7cc8a31d6a0e 100644 --- a/apps/builder/app/builder/features/style-panel/controls/font-family/font-family-control.tsx +++ b/apps/builder/app/builder/features/style-panel/controls/font-family/font-family-control.tsx @@ -61,10 +61,6 @@ export const FontFamilyControl = () => { return toValue(value, (value) => value).replace(/"/g, ""); }, [value]); - if (value.type !== "fontFamily") { - return; - } - return ( @@ -73,7 +69,7 @@ export const FontFamilyControl = () => { title="Fonts" content={ { setValue({ type: "fontFamily", value: [newValue] }); }} diff --git a/apps/builder/app/builder/features/workspace/workspace.tsx b/apps/builder/app/builder/features/workspace/workspace.tsx index 93d0b4f88e6f..90dca120e34b 100644 --- a/apps/builder/app/builder/features/workspace/workspace.tsx +++ b/apps/builder/app/builder/features/workspace/workspace.tsx @@ -24,6 +24,8 @@ const workspaceStyle = css({ const canvasContainerStyle = css({ position: "absolute", transformOrigin: "0 0", + // We had a case where some Windows 10 + Chrome 129 users couldn't scroll iframe canvas. + willChange: "transform", }); const useMeasureWorkspace = () => { diff --git a/apps/builder/app/builder/shared/fonts-manager/fonts-manager.tsx b/apps/builder/app/builder/shared/fonts-manager/fonts-manager.tsx index 9338ca5fce2a..66a941e72ffc 100644 --- a/apps/builder/app/builder/shared/fonts-manager/fonts-manager.tsx +++ b/apps/builder/app/builder/shared/fonts-manager/fonts-manager.tsx @@ -63,7 +63,7 @@ const useLogic = ({ onChange, value }: FontsManagerProps) => { ); const currentIndex = useMemo(() => { - return groupedItems.findIndex((item) => item.label === value.value[0]); + return groupedItems.findIndex((item) => item.label === value?.value[0]); }, [groupedItems, value]); const handleChangeCurrent = (nextCurrentIndex: number) => { @@ -104,7 +104,7 @@ const useLogic = ({ onChange, value }: FontsManagerProps) => { }; type FontsManagerProps = { - value: FontFamilyValue; + value?: FontFamilyValue; onChange: (value?: string) => void; }; diff --git a/apps/builder/app/shared/style-object-model.test.tsx b/apps/builder/app/shared/style-object-model.test.tsx index c2c8ff22c729..256c2527abde 100644 --- a/apps/builder/app/shared/style-object-model.test.tsx +++ b/apps/builder/app/shared/style-object-model.test.tsx @@ -843,6 +843,32 @@ test("fallback cascaded value to inherited computed value", () => { ).toEqual({ type: "keyword", value: "currentColor" }); }); +test("work with unknown or invalid properties", () => { + const model = createModel({ + css: ` + bodyLocal { + unknown-property: [object Object]; + } + `, + jsx: <$.Body ws:id="body" class="bodyLocal">, + }); + const instanceSelector = ["body"]; + expect( + getComputedStyleDecl({ + model, + instanceSelector, + property: "unknownProperty", + }).usedValue + ).toEqual({ type: "unparsed", value: "[object Object]" }); + expect( + getComputedStyleDecl({ + model, + instanceSelector, + property: "undefinedProperty", + }).usedValue + ).toEqual({ type: "invalid", value: "" }); +}); + describe("selected style", () => { test("access selected style source value within cascade", () => { const model = createModel({ diff --git a/apps/builder/app/shared/style-object-model.ts b/apps/builder/app/shared/style-object-model.ts index 37fe8443006b..2e3073db4f25 100644 --- a/apps/builder/app/shared/style-object-model.ts +++ b/apps/builder/app/shared/style-object-model.ts @@ -236,6 +236,11 @@ const customPropertyData = { initial: guaranteedInvalidValue, }; +const invalidPropertyData = { + inherited: false, + initial: invalidValue, +}; + export type ComputedStyleDecl = { property: string; source: StyleValueSource; @@ -269,7 +274,7 @@ export const getComputedStyleDecl = ({ const isCustomProperty = property.startsWith("--"); const propertyData = isCustomProperty ? customPropertyData - : properties[property as keyof typeof properties]; + : (properties[property as keyof typeof properties] ?? invalidPropertyData); const inherited = propertyData.inherited; const initialValue: StyleValue = propertyData.initial; let computedValue: StyleValue = initialValue; diff --git a/packages/css-engine/src/core/to-value.test.ts b/packages/css-engine/src/core/to-value.test.ts index 5b5c8e9a47c0..e909933b8ad5 100644 --- a/packages/css-engine/src/core/to-value.test.ts +++ b/packages/css-engine/src/core/to-value.test.ts @@ -46,7 +46,7 @@ describe("Convert WS CSS Values to native CSS strings", () => { expect(value).toBe("var(--namespace, normal, 10px)"); }); - test("fontFamily stack", () => { + test("fontFamily is known stack name", () => { expect( toValue({ type: "fontFamily", @@ -57,7 +57,16 @@ describe("Convert WS CSS Values to native CSS strings", () => { ); }); - test("fontFamily unknown", () => { + test("fontFamily is a custom stack", () => { + expect( + toValue({ + type: "fontFamily", + value: ["DejaVu Sans Mono", "monospace"], + }) + ).toBe('"DejaVu Sans Mono", monospace'); + }); + + test("fontFamily is unknown family name", () => { expect( toValue({ type: "fontFamily", @@ -66,6 +75,24 @@ describe("Convert WS CSS Values to native CSS strings", () => { ).toBe("something-random, sans-serif"); }); + test("fontFamily is empty", () => { + expect( + toValue({ + type: "fontFamily", + value: [], + }) + ).toBe("sans-serif"); + }); + + test("fontFamily has duplicates", () => { + expect( + toValue({ + type: "fontFamily", + value: ["a", "a", "b"], + }) + ).toBe("a, b"); + }); + test("Transform font family value to override default fallback", () => { const value = toValue( { diff --git a/packages/css-engine/src/core/to-value.ts b/packages/css-engine/src/core/to-value.ts index 1d89ba899b06..3f1a314bc3f7 100644 --- a/packages/css-engine/src/core/to-value.ts +++ b/packages/css-engine/src/core/to-value.ts @@ -5,18 +5,28 @@ import type { StyleValue } from "../schema"; export type TransformValue = (styleValue: StyleValue) => undefined | StyleValue; const fallbackTransform: TransformValue = (styleValue) => { - if (styleValue.type === "fontFamily") { - const fonts = SYSTEM_FONTS.get(styleValue.value[0])?.stack ?? [ - styleValue.value[0], - DEFAULT_FONT_FALLBACK, - ]; - const value = Array.from(new Set(fonts)); - - return { - type: "fontFamily", - value, - }; + if (styleValue.type !== "fontFamily") { + return; } + + // By default we assume its a custom font stack. + let { value } = styleValue; + + // Shouldn't be possible, but just in case. + if (value.length === 0) { + value = [DEFAULT_FONT_FALLBACK]; + } + + // User provided a single name. It could be a specific font name or a stack name. + if (value.length === 1) { + const stack = SYSTEM_FONTS.get(value[0])?.stack; + value = stack ?? [value[0], DEFAULT_FONT_FALLBACK]; + } + + return { + type: "fontFamily", + value: Array.from(new Set(value)), + }; }; // Use JSON.stringify to escape double quotes and backslashes in strings as it automatically replaces " with \" and \ with \\. diff --git a/packages/design-system/src/components/scroll-area.tsx b/packages/design-system/src/components/scroll-area.tsx index 93b788531fd8..86a2f4102452 100644 --- a/packages/design-system/src/components/scroll-area.tsx +++ b/packages/design-system/src/components/scroll-area.tsx @@ -6,6 +6,8 @@ const ScrollAreaRoot = styled(Root, { boxSizing: "border-box", overflow: "hidden", display: "grid", + // We had a case where some Windows 10 + Chrome 129 users couldn't scroll style panel. + willChange: "transform", }); const ScrollAreaThumb = styled(Thumb, {