diff --git a/apps/builder/app/builder/features/settings-panel/props-section/props-section.tsx b/apps/builder/app/builder/features/settings-panel/props-section/props-section.tsx index bcf3873f0efc..29b6b07dde00 100644 --- a/apps/builder/app/builder/features/settings-panel/props-section/props-section.tsx +++ b/apps/builder/app/builder/features/settings-panel/props-section/props-section.tsx @@ -7,6 +7,7 @@ import { type Instance, type Props, descendantComponent, + rootComponent, } from "@webstudio-is/sdk"; import { theme, @@ -344,7 +345,7 @@ export const PropsSectionContainer = ({ }); const propsMetas = useStore($selectedInstancePropsMetas); - if (propsMetas.size === 0) { + if (propsMetas.size === 0 || instance.component === rootComponent) { return; } diff --git a/apps/builder/app/canvas/collapsed.ts b/apps/builder/app/canvas/collapsed.ts index b9b5cbdd63a4..4099b8d7e794 100644 --- a/apps/builder/app/canvas/collapsed.ts +++ b/apps/builder/app/canvas/collapsed.ts @@ -10,6 +10,7 @@ import { import { $selectedBreakpoint } from "~/shared/nano-states"; import { $selectedPage } from "~/shared/awareness"; import { serverSyncStore } from "~/shared/sync"; +import { doNotTrackMutation } from "~/shared/dom-utils"; const isHtmlTag = (tag: string): tag is HtmlTags => htmlTags.includes(tag as HtmlTags); @@ -34,22 +35,6 @@ const isSelectorSupported = (selector: string) => { } }; -// This mark helps us detect if mutations were caused by the collapse algorithm -const markCollapsedMutationProperty = `--ws-sys-collapsed-mutation`; - -/** - * Avoid infinite loop of mutations - */ -export const hasCollapsedMutationRecord = ( - mutationRecords: MutationRecord[] -) => { - return mutationRecords.some((record) => - record.type === "attributes" - ? (record.oldValue?.includes(markCollapsedMutationProperty) ?? false) - : false - ); -}; - const getInstanceSize = (instanceId: string, tagName: HtmlTags | undefined) => { const metas = $registeredComponentMetas.get(); const breakpoints = $breakpoints.get(); @@ -246,14 +231,25 @@ const recalculate = () => { // If most elements are collapsed at the next step, scrollHeight becomes equal to clientHeight, // which resets the scroll position. To prevent this, we set the document's height to the current scrollHeight // to preserve the scroll position. - const preserveHeight = document.documentElement.style.height; - - // Mark that we are in the process of recalculating collapsed elements - document.documentElement.style.setProperty( - markCollapsedMutationProperty, - `true` - ); - document.documentElement.style.height = `${document.documentElement.scrollHeight}px`; + // use nested element to avoid full page repaint and freeze on big projects + let collapsedElement = document.querySelector( + "#ws-collapsed" + ) as null | HTMLElement; + if (!collapsedElement) { + collapsedElement = document.createElement("div") as HTMLElement; + collapsedElement.style.position = "absolute"; + collapsedElement.style.top = "0px"; + collapsedElement.style.left = "0px"; + collapsedElement.style.right = "0px"; + collapsedElement.setAttribute("id", "ws-collapsed"); + collapsedElement.setAttribute("hidden", "true"); + // Mark that we are in the process of recalculating collapsed elements + // to avoid infinite loop of mutations + doNotTrackMutation(collapsedElement); + document.documentElement.appendChild(collapsedElement); + } + collapsedElement.removeAttribute("hidden"); + collapsedElement.style.height = `${document.documentElement.scrollHeight}px`; // Now combine all operations in batches. @@ -299,8 +295,7 @@ const recalculate = () => { element.setAttribute(collapsedAttribute, value); } - document.documentElement.style.height = preserveHeight; - document.documentElement.style.removeProperty(markCollapsedMutationProperty); + collapsedElement.setAttribute("hidden", "true"); }; /** diff --git a/apps/builder/app/canvas/instance-selected.ts b/apps/builder/app/canvas/instance-selected.ts index 817218af21e8..67688d0ea0d3 100644 --- a/apps/builder/app/canvas/instance-selected.ts +++ b/apps/builder/app/canvas/instance-selected.ts @@ -27,10 +27,7 @@ import { } from "~/shared/dom-utils"; import { subscribeScrollState } from "~/canvas/shared/scroll-state"; import { $selectedInstanceOutline } from "~/shared/nano-states"; -import { - hasCollapsedMutationRecord, - setDataCollapsed, -} from "~/canvas/collapsed"; +import { setDataCollapsed } from "~/canvas/collapsed"; import type { InstanceSelector } from "~/shared/tree-utils"; import { $awareness } from "~/shared/awareness"; @@ -236,7 +233,7 @@ const subscribeSelectedInstance = ( // Lightweight update const updateOutline: MutationCallback = (mutationRecords) => { - if (hasCollapsedMutationRecord(mutationRecords)) { + if (hasDoNotTrackMutationRecord(mutationRecords)) { return; } @@ -250,10 +247,6 @@ const subscribeSelectedInstance = ( return; } - if (hasCollapsedMutationRecord(mutationRecords)) { - return; - } - update(); }; diff --git a/apps/builder/app/shared/dom-utils.ts b/apps/builder/app/shared/dom-utils.ts index 9c474c294abd..7ce61ac76a49 100644 --- a/apps/builder/app/shared/dom-utils.ts +++ b/apps/builder/app/shared/dom-utils.ts @@ -219,6 +219,10 @@ export const getAllElementsBoundingBox = ( const doNotTrackMutationAttribute = "data-ws-do-not-track-mutation"; +export const doNotTrackMutation = (element: Element) => { + element.setAttribute(doNotTrackMutationAttribute, "true"); +}; + export const hasDoNotTrackMutationAttribute = (element: Element) => { return element.hasAttribute(doNotTrackMutationAttribute); }; @@ -363,7 +367,10 @@ export const scrollIntoView = (anchor: HTMLElement, rect: DOMRect) => { requestAnimationFrame(() => { const savedPosition = (scrollParent as HTMLElement).style.position; - (scrollParent as HTMLElement).style.position = "relative"; + // avoid updating to prevent full page repaint and freeze on big projects + if (scrollParent.tagName !== "HTML") { + (scrollParent as HTMLElement).style.position = "relative"; + } const matrix = getViewportToLocalMatrix(scrollParent); @@ -387,6 +394,9 @@ export const scrollIntoView = (anchor: HTMLElement, rect: DOMRect) => { scrollParent.removeChild(scrollDiv); - (scrollParent as HTMLElement).style.position = savedPosition; + // avoid updating to prevent full page repaint and freeze on big projects + if (scrollParent.tagName !== "HTML") { + (scrollParent as HTMLElement).style.position = savedPosition; + } }); };