diff --git a/apps/builder/app/builder/features/style-panel/controls/select/select-control.tsx b/apps/builder/app/builder/features/style-panel/controls/select/select-control.tsx index 1663e8a8cf..6b4018d60c 100644 --- a/apps/builder/app/builder/features/style-panel/controls/select/select-control.tsx +++ b/apps/builder/app/builder/features/style-panel/controls/select/select-control.tsx @@ -9,6 +9,7 @@ export const SelectControl = ({ property, currentStyle, setProperty, + deleteProperty, items, }: ControlProps) => { const { items: defaultItems } = styleConfigByName(property); @@ -48,7 +49,7 @@ export const SelectControl = ({ onOpenChange={(isOpen) => { // Remove ephemeral changes when closing the menu. if (isOpen === false && styleValue !== undefined) { - setValue(styleValue, { isEphemeral: true }); + deleteProperty(property, { isEphemeral: true }); } }} /> diff --git a/apps/builder/app/canvas/shared/styles.ts b/apps/builder/app/canvas/shared/styles.ts index 7cdc8c49ab..6a57a260e9 100644 --- a/apps/builder/app/canvas/shared/styles.ts +++ b/apps/builder/app/canvas/shared/styles.ts @@ -116,7 +116,43 @@ const subscribePreviewMode = () => { const subscribeEphemeralStyle = (params: Params) => { // track custom properties added on previous ephemeral styles change const addedCustomProperties = new Set(); + + let timeoutHandle: number | undefined = undefined; + + const resetInert = () => { + document.body.removeAttribute("inert"); + clearTimeout(timeoutHandle); + timeoutHandle = undefined; + }; + + // 1000 ms is a reasonable time for the preview to reset. + // Anyway should never happen after user has finished preview changes (can happen during preview changes) + const AUTO_DISPOSE_INERT_TIMEOUT = 1000; + + // A brief delay to ensure mutation observers within the focus scope are activated by the preview changes. + const DISPOSE_INERT_TIMEOUT = 300; + + const setAutoDisposeInert = (timeout: number) => { + document.body.setAttribute("inert", "true"); + + // To prevent a completely non-interactive canvas due to edge cases, + // make sure to clean up preview changes if preview styles fail to reset correctly. + clearTimeout(timeoutHandle); + + timeoutHandle = window.setTimeout(resetInert, timeout); + }; + return $ephemeralStyles.subscribe((ephemeralStyles) => { + // Controls (e.g., radix focus scope) may inadvertently shift focus from inputs. + // Currently, there's no way to block focus shifts inside iframes (see https://github.com/w3c/webappsec-permissions-policy/issues/273 for future updates). + // Workaround: use the `inert` attribute on iframe body to prevent focus changes. + if (ephemeralStyles.length > 0) { + setAutoDisposeInert(AUTO_DISPOSE_INERT_TIMEOUT); + } else { + // Delayed reset same as set with smaller timeout + setAutoDisposeInert(DISPOSE_INERT_TIMEOUT); + } + // track custom properties not set on this change const deletedCustomProperties = new Set(addedCustomProperties);