From e67b13d64d06bfca28d58646cc8fad13e72f8a5b Mon Sep 17 00:00:00 2001 From: Ivan Starkov Date: Tue, 13 Feb 2024 22:40:28 +0500 Subject: [PATCH] fix: Dialog focus issues (#2908) ## Description closes #2889 ## 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.example` and the `builder/env-check.js` if mandatory --- .../controls/select/select-control.tsx | 3 +- apps/builder/app/canvas/shared/styles.ts | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) 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);