Skip to content

Commit d6e2177

Browse files
authored
fix: Collapsed recalculate loop during load (#4178)
## Description 1. What is this PR about (link the issue and add a short description) ## Steps for reproduction 1. click button 2. expect xyz ## 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 - [ ] made a self-review - [ ] added inline comments where things may be not obvious (the "why", not "what") ## Before merging - [ ] tested locally and on preview environment (preview dev login: 5de6) - [ ] 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
1 parent 84250f4 commit d6e2177

File tree

2 files changed

+45
-3
lines changed

2 files changed

+45
-3
lines changed

apps/builder/app/canvas/collapsed.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,22 @@ const isSelectorSupported = (selector: string) => {
3939
}
4040
};
4141

42+
// This mark helps us detect if mutations were caused by the collapse algorithm
43+
const markCollapsedMutationProperty = `--ws-sys-collapsed-mutation`;
44+
45+
/**
46+
* Avoid infinite loop of mutations
47+
*/
48+
export const hasCollapsedMutationRecord = (
49+
mutationRecords: MutationRecord[]
50+
) => {
51+
return mutationRecords.some((record) =>
52+
record.type === "attributes"
53+
? (record.oldValue?.includes(markCollapsedMutationProperty) ?? false)
54+
: false
55+
);
56+
};
57+
4258
const getInstanceSize = (instanceId: string, tagName: HtmlTags | undefined) => {
4359
const metas = $registeredComponentMetas.get();
4460
const breakpoints = $breakpoints.get();
@@ -183,6 +199,12 @@ const recalculate = () => {
183199
// which resets the scroll position. To prevent this, we set the document's height to the current scrollHeight
184200
// to preserve the scroll position.
185201
const preserveHeight = document.documentElement.style.height;
202+
203+
// Mark that we are in the process of recalculating collapsed elements
204+
document.documentElement.style.setProperty(
205+
markCollapsedMutationProperty,
206+
`true`
207+
);
186208
document.documentElement.style.height = `${document.documentElement.scrollHeight}px`;
187209

188210
// Now combine all operations in batches.
@@ -230,6 +252,7 @@ const recalculate = () => {
230252
}
231253

232254
document.documentElement.style.height = preserveHeight;
255+
document.documentElement.style.removeProperty(markCollapsedMutationProperty);
233256
};
234257

235258
/**

apps/builder/app/canvas/instance-selected.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ import {
2525
import { subscribeScrollState } from "~/canvas/shared/scroll-state";
2626
import { $selectedInstanceOutline } from "~/shared/nano-states";
2727
import type { UnitSizes } from "~/builder/features/style-panel/shared/css-value-input/convert-units";
28-
import { setDataCollapsed } from "~/canvas/collapsed";
28+
import {
29+
hasCollapsedMutationRecord,
30+
setDataCollapsed,
31+
} from "~/canvas/collapsed";
2932
import { getBrowserStyle } from "./features/webstudio-component/get-browser-style";
3033
import type { InstanceSelector } from "~/shared/tree-utils";
3134

@@ -238,37 +241,53 @@ const subscribeSelectedInstance = (
238241
};
239242

240243
// Lightweight update
241-
const updateOutline = () => {
244+
const updateOutline: MutationCallback = (mutationRecords) => {
245+
if (hasCollapsedMutationRecord(mutationRecords)) {
246+
return;
247+
}
248+
242249
showOutline();
243250
};
244251

245252
const resizeObserver = new ResizeObserver(update);
246253

254+
const mutationHandler: MutationCallback = (mutationRecords) => {
255+
if (hasCollapsedMutationRecord(mutationRecords)) {
256+
return;
257+
}
258+
259+
update();
260+
};
261+
247262
// detect movement of the element within same parent
248263
// React prevent remount when key stays the same
249264
// `attributes: true` fixes issues with popups after trigger text editing
250265
// that cause radix to incorrectly set content in a wrong position at first render
251-
const mutationObserver = new MutationObserver(update);
266+
const mutationObserver = new MutationObserver(mutationHandler);
252267

253268
const updateObservers = () => {
254269
for (const element of visibleElements) {
255270
resizeObserver.observe(element);
256271

257272
const parent = element?.parentElement;
273+
258274
if (parent) {
259275
mutationObserver.observe(parent, {
260276
childList: true,
261277
attributes: true,
278+
attributeOldValue: true,
262279
attributeFilter: ["style", "class"],
263280
});
264281
}
265282
}
266283
};
267284

268285
const bodyStyleMutationObserver = new MutationObserver(updateOutline);
286+
269287
// previewStyle variables
270288
bodyStyleMutationObserver.observe(document.body, {
271289
attributes: true,
290+
attributeOldValue: true,
272291
attributeFilter: ["style", "class"],
273292
});
274293

0 commit comments

Comments
 (0)