diff --git a/changes/44805-fix-custom-variable-modal-clears-on-focus-switch b/changes/44805-fix-custom-variable-modal-clears-on-focus-switch new file mode 100644 index 0000000000..f5960fe0b9 --- /dev/null +++ b/changes/44805-fix-custom-variable-modal-clears-on-focus-switch @@ -0,0 +1 @@ +* Fixed a bug where the "Add custom variable" modal would clear entered values when switching focus to another browser tab or application window. diff --git a/frontend/components/Modal/Modal.tests.tsx b/frontend/components/Modal/Modal.tests.tsx index 5627fa1f00..34779a9b37 100644 --- a/frontend/components/Modal/Modal.tests.tsx +++ b/frontend/components/Modal/Modal.tests.tsx @@ -154,6 +154,28 @@ describe("Modal", () => { expect(onExit).toHaveBeenCalledTimes(1); }); + it("does not call onExit when a backdrop mousedown is interrupted by the window losing focus", () => { + const onExit = jest.fn(); + const { container } = render( + +
content
+
+ ); + + const background = container.querySelector(".modal__background"); + if (!background) throw new Error("Background element not found"); + + // User presses down on the backdrop then the window loses focus (tab switch / + // app switch) before releasing — mouseup never fires on the page. + fireEvent.mouseDown(background); + fireEvent.blur(window); + // On return, the stale mousedown state must not close the modal. + fireEvent.mouseUp(background); + act(() => jest.runAllTimers()); + + expect(onExit).not.toHaveBeenCalled(); + }); + it("does not call onExit when clicking the background if disableClosingModal is true", async () => { const onExit = jest.fn(); const { container } = render( diff --git a/frontend/components/Modal/Modal.tsx b/frontend/components/Modal/Modal.tsx index 1324d27de0..e175b2c4b9 100644 --- a/frontend/components/Modal/Modal.tsx +++ b/frontend/components/Modal/Modal.tsx @@ -105,6 +105,16 @@ const Modal = ({ return undefined; }, [onEnter]); + useEffect(() => { + const onWindowBlur = () => { + isDownOnBackgroundRef.current = false; + }; + window.addEventListener("blur", onWindowBlur); + return () => { + window.removeEventListener("blur", onWindowBlur); + }; + }, []); + const backgroundClasses = classnames(`${baseClass}__background`, { [`${baseClass}__hidden`]: isHidden, [`${baseClass}__closing`]: isClosing, diff --git a/frontend/pages/ManageControlsPage/Variables/Variables.tsx b/frontend/pages/ManageControlsPage/Variables/Variables.tsx index 33de551be6..839b913a97 100644 --- a/frontend/pages/ManageControlsPage/Variables/Variables.tsx +++ b/frontend/pages/ManageControlsPage/Variables/Variables.tsx @@ -11,7 +11,10 @@ import { IVariable } from "interfaces/variables"; import { AppContext } from "context/app"; import { stringToClipboard } from "utilities/copy_text"; -import { FLEET_WEBSITE_URL } from "utilities/constants"; +import { + DEFAULT_USE_QUERY_OPTIONS, + FLEET_WEBSITE_URL, +} from "utilities/constants"; import CustomLink from "components/CustomLink"; import { HumanTimeDiffWithDateTip } from "components/HumanTimeDiffWithDateTip"; import ListItem from "components/ListItem/ListItem"; @@ -62,7 +65,9 @@ const Variables = ({ router, location }: IVariablesProps) => { IListVariablesResponse, Error, IListVariablesResponse - >(["variables", apiParams], () => variablesAPI.getVariables(apiParams)); + >(["variables", apiParams], () => variablesAPI.getVariables(apiParams), { + ...DEFAULT_USE_QUERY_OPTIONS, + }); // Open the Add variable modal via deep-link (e.g. from the command // palette). Gate on the same predicate the in-page button uses — the