diff --git a/apps/builder/app/builder/features/style-panel/shared/model.tsx b/apps/builder/app/builder/features/style-panel/shared/model.tsx index 77a75e1f3a79..aa8304e515ee 100644 --- a/apps/builder/app/builder/features/style-panel/shared/model.tsx +++ b/apps/builder/app/builder/features/style-panel/shared/model.tsx @@ -291,11 +291,26 @@ export const $availableColorVariables = computed( availableVariables.filter((value) => value.fallback?.type !== "unit") ); +// completely bust the cache when model is changed +let prevModel: undefined | StyleObjectModel; +// store cached computed declarations by following key +// instanceSelector + styleSourceId + state + property +const cache = new Map(); + +const validateCache = (model: StyleObjectModel) => { + if (model !== prevModel) { + prevModel = model; + cache.clear(); + } +}; + export const createComputedStyleDeclStore = (property: StyleProperty) => { return computed( [$model, $instanceAndRootSelector, $selectedOrLastStyleSourceSelector], (model, instanceSelector, styleSourceSelector) => { + validateCache(model); return getComputedStyleDecl({ + cache, model, instanceSelector, styleSourceId: styleSourceSelector?.styleSourceId, @@ -306,10 +321,6 @@ export const createComputedStyleDeclStore = (property: StyleProperty) => { ); }; -export const useStyleObjectModel = () => { - return useStore($model); -}; - export const useComputedStyleDecl = (property: StyleProperty) => { const $store = useMemo( () => createComputedStyleDeclStore(property), @@ -324,7 +335,9 @@ export const useParentComputedStyleDecl = (property: StyleProperty) => { computed( [$model, $instanceAndRootSelector], (model, instanceSelector) => { + validateCache(model); return getComputedStyleDecl({ + cache, model, instanceSelector: instanceSelector?.slice(1), property, diff --git a/apps/builder/app/shared/style-object-model.test.tsx b/apps/builder/app/shared/style-object-model.test.tsx index 756460cf75b2..3a84bfb06428 100644 --- a/apps/builder/app/shared/style-object-model.test.tsx +++ b/apps/builder/app/shared/style-object-model.test.tsx @@ -1576,3 +1576,30 @@ describe("style value source", () => { }); }); }); + +describe("cache", () => { + test("reuse computed values", () => { + const model = createModel({ + css: ` + bodyLocal { + color: red; + } + `, + jsx: <$.Body ws:id="body" ws:tag="body" class="bodyLocal">, + }); + const cache = new Map(); + const first = getComputedStyleDecl({ + cache, + model, + instanceSelector: ["body"], + property: "color", + }); + const second = getComputedStyleDecl({ + cache, + model, + instanceSelector: ["body"], + property: "color", + }); + expect(first).toBe(second); + }); +}); diff --git a/apps/builder/app/shared/style-object-model.ts b/apps/builder/app/shared/style-object-model.ts index 73533b44429a..94b1867670cf 100644 --- a/apps/builder/app/shared/style-object-model.ts +++ b/apps/builder/app/shared/style-object-model.ts @@ -285,6 +285,7 @@ export type ComputedStyleDecl = { * https://drafts.csswg.org/css-cascade-5/#value-stages */ export const getComputedStyleDecl = ({ + cache = new Map(), model, instanceSelector = [], styleSourceId, @@ -292,6 +293,7 @@ export const getComputedStyleDecl = ({ property, customPropertiesGraph = new Map(), }: { + cache?: Map; model: StyleObjectModel; instanceSelector?: InstanceSelector; styleSourceId?: StyleDecl["styleSourceId"]; @@ -302,6 +304,17 @@ export const getComputedStyleDecl = ({ */ customPropertiesGraph?: Map>; }): ComputedStyleDecl => { + const cacheKey = JSON.stringify({ + instanceSelector, + styleSourceId, + state, + property, + }); + const cachedValue = cache.get(cacheKey); + if (cachedValue) { + return cachedValue; + } + const isCustomProperty = property.startsWith("--"); const propertyData = isCustomProperty ? customPropertyData @@ -431,5 +444,13 @@ export const getComputedStyleDecl = ({ // fallback to inherited value cascadedValue ??= computedValue; - return { property, source, cascadedValue, computedValue, usedValue }; + const computedStyleDecl: ComputedStyleDecl = { + property, + source, + cascadedValue, + computedValue, + usedValue, + }; + cache.set(cacheKey, computedStyleDecl); + return computedStyleDecl; };