From d3dd6fd8066c4ac05f2d81ae4bab96d2d5d52d35 Mon Sep 17 00:00:00 2001 From: Alireza Date: Sat, 3 Aug 2024 12:45:15 +0200 Subject: [PATCH] feat(ModalWindow): improve imperative API by returning promise from open method example-app is also refactored to fix the regression in delete file(s) action. The issue started to happen after upgrading to the latest version of @react-aria/focus, but it's because the deletion happens in the confirmation dialog, and before its closed. So the node to restore the focus to is removed when the focus restoration logic runs. Note: upgrade of @react-aria/overlays didn't have anything to do with the fix, but was kept anyway. --- .../src/ProjectView/actions/deleteAction.tsx | 31 ++++--- packages/jui/cypress/e2e/file-actions.cy.ts | 3 +- packages/jui/package.json | 2 +- .../jui/src/ModalWindow/WindowManager.tsx | 93 ++++++++++++++----- yarn.lock | 32 +------ 5 files changed, 96 insertions(+), 65 deletions(-) diff --git a/packages/example-app/src/ProjectView/actions/deleteAction.tsx b/packages/example-app/src/ProjectView/actions/deleteAction.tsx index 646776ab..65476373 100644 --- a/packages/example-app/src/ProjectView/actions/deleteAction.tsx +++ b/packages/example-app/src/ProjectView/actions/deleteAction.tsx @@ -1,5 +1,5 @@ import path from "path"; -import { selector, useRecoilCallback } from "recoil"; +import { selector } from "recoil"; import React from "react"; import { @@ -22,7 +22,6 @@ import { selectedNodesState } from "../ProjectView.state"; import { findRootPaths } from "../../path-utils"; import { deleteDirCallback, - deleteFileCallback, deleteFilesCallback, } from "../../Project/fs-operations"; import { IntlMessageFormat } from "intl-messageformat"; @@ -52,7 +51,7 @@ export const deleteActionState = selector({ isDisabled: !get(activePathExistsState), actionPerformed: getCallback(({ snapshot }) => async () => { const deleteDir = getCallback(deleteDirCallback); - const deleteFile = getCallback(deleteFileCallback); + const deleteFiles = getCallback(deleteFilesCallback); const selectedNodes = snapshot.getLoadable(selectedNodesState).getValue(); const windowManager = snapshot .getLoadable(windowManagerRefState) @@ -105,12 +104,21 @@ export const deleteActionState = selector({ }); if (confirmed) { directories.forEach((pathname) => deleteDir(pathname)); - filePaths.forEach((pathname) => deleteFile(pathname)); + deleteFiles(filePaths); } } else { - windowManager?.open(({ close }) => ( - - )); + windowManager + ?.open(({ close }) => ( + + )) + .then((confirmed) => { + if (confirmed) { + deleteFiles(filePaths); + } + }); } }), }), @@ -120,11 +128,9 @@ function DeleteFilesConfirmationDialog({ close, filePaths, }: { - close: () => void; + close: (confirmed?: true) => void; filePaths: string[]; }) { - const deleteFiles = useRecoilCallback(deleteFilesCallback, []); - return ( notImplemented()}>} right={ <> - + + * + * + * } + * /> + * } + * /> + * + * )) + * .then((confirmed) => alert(`confirmed: ${confirmed ?? false}`)); + * }; + * ``` */ - open( + open( props: | React.ReactElement | ((args: { - close: () => void; + close: (result?: T) => void; }) => React.ReactElement) - ): void; + ): Promise; } const NotImplementedFn = () => { @@ -59,25 +94,39 @@ export const WindowManager: React.FC = ({ children }) => { const newKeyRef = useRef(0); const api = useMemo(() => { - const openModalWindow: WindowManagerAPI["open"] = (content) => { - newKeyRef.current++; - const close = () => { - setWindows((currentWindows) => - currentWindows.filter((aWindow) => aWindow !== window) - ); - }; - const window = ( - - {typeof content === "function" ? content({ close }) : content} - - ); - setWindows((currentWindows) => currentWindows.concat(window)); - }; return { - open: openModalWindow, + open: function (content: Parameters[0]) { + return new Promise((resolve) => { + newKeyRef.current++; + const close = (result?: T) => { + setWindows((currentWindows) => + currentWindows.filter((aWindow) => aWindow !== window) + ); + // Make sure (?) to resolve the promise after the dialog is closed, + // for the focus to be restored to the previous element before the + // potential further actions take place. + requestAnimationFrame(() => { + resolve(result); + }); + }; + const window = ( + { + close(); + }, + }} + key={newKeyRef.current} + > + {typeof content === "function" + ? // @ts-expect-error close signature is not correctly inferred + content({ close }) + : content} + + ); + setWindows((currentWindows) => currentWindows.concat(window)); + }); + }, }; }, []); diff --git a/yarn.lock b/yarn.lock index 734eac34..b4fefccd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2909,7 +2909,7 @@ __metadata: "@react-aria/link": ~3.2.5 "@react-aria/listbox": ~3.12.1 "@react-aria/menu": ~3.14.1 - "@react-aria/overlays": ~3.22.1 + "@react-aria/overlays": ~3.23.1 "@react-aria/progress": ~3.1.8 "@react-aria/select": ~3.14.7 "@react-aria/selection": ~3.18.1 @@ -5920,7 +5920,7 @@ __metadata: languageName: node linkType: hard -"@react-aria/overlays@npm:^3.22.1, @react-aria/overlays@npm:^3.23.1": +"@react-aria/overlays@npm:^3.22.1, @react-aria/overlays@npm:^3.23.1, @react-aria/overlays@npm:~3.23.1": version: 3.23.1 resolution: "@react-aria/overlays@npm:3.23.1" dependencies: @@ -5942,28 +5942,6 @@ __metadata: languageName: node linkType: hard -"@react-aria/overlays@npm:~3.22.1": - version: 3.22.1 - resolution: "@react-aria/overlays@npm:3.22.1" - dependencies: - "@react-aria/focus": ^3.17.1 - "@react-aria/i18n": ^3.11.1 - "@react-aria/interactions": ^3.21.3 - "@react-aria/ssr": ^3.9.4 - "@react-aria/utils": ^3.24.1 - "@react-aria/visually-hidden": ^3.8.12 - "@react-stately/overlays": ^3.6.7 - "@react-types/button": ^3.9.4 - "@react-types/overlays": ^3.8.7 - "@react-types/shared": ^3.23.1 - "@swc/helpers": ^0.5.0 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 14a81edee5d2d2af05d21f209c712e4838753aaec7ad7bea4d362a2c354c70cb7f5b2e70cf549ce644f5849af1be7721550cfa577e973d4241605f55ab916115 - languageName: node - linkType: hard - "@react-aria/progress@npm:~3.1.8": version: 3.1.8 resolution: "@react-aria/progress@npm:3.1.8" @@ -6054,7 +6032,7 @@ __metadata: languageName: node linkType: hard -"@react-aria/ssr@npm:^3.9.4, @react-aria/ssr@npm:^3.9.5": +"@react-aria/ssr@npm:^3.9.5": version: 3.9.5 resolution: "@react-aria/ssr@npm:3.9.5" dependencies: @@ -6150,7 +6128,7 @@ __metadata: languageName: node linkType: hard -"@react-aria/visually-hidden@npm:^3.8.12, @react-aria/visually-hidden@npm:^3.8.14, @react-aria/visually-hidden@npm:~3.8.14": +"@react-aria/visually-hidden@npm:^3.8.14, @react-aria/visually-hidden@npm:~3.8.14": version: 3.8.14 resolution: "@react-aria/visually-hidden@npm:3.8.14" dependencies: @@ -6502,7 +6480,7 @@ __metadata: languageName: node linkType: hard -"@react-types/overlays@npm:^3.6.1, @react-types/overlays@npm:^3.8.7, @react-types/overlays@npm:^3.8.9": +"@react-types/overlays@npm:^3.6.1, @react-types/overlays@npm:^3.8.9": version: 3.8.9 resolution: "@react-types/overlays@npm:3.8.9" dependencies: