From a38e82f99902131982eae15bcaa16e406d3cbafa Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Tue, 5 Mar 2024 23:22:34 +0100 Subject: [PATCH] feat: close dropdown on escape (#7750) --- .../dropdownMenu/DropdownMenu.test.tsx | 20 ++++++++++++++ .../dropdownMenu/DropdownMenuContent.tsx | 27 +++++++++++++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 packages/excalidraw/components/dropdownMenu/DropdownMenu.test.tsx diff --git a/packages/excalidraw/components/dropdownMenu/DropdownMenu.test.tsx b/packages/excalidraw/components/dropdownMenu/DropdownMenu.test.tsx new file mode 100644 index 000000000..3aae1d0c7 --- /dev/null +++ b/packages/excalidraw/components/dropdownMenu/DropdownMenu.test.tsx @@ -0,0 +1,20 @@ +import { Excalidraw } from "../../index"; +import { KEYS } from "../../keys"; +import { Keyboard } from "../../tests/helpers/ui"; +import { render, waitFor, getByTestId } from "../../tests/test-utils"; + +describe("Test ", () => { + it("should", async () => { + const { container } = await render(); + + expect(window.h.state.openMenu).toBe(null); + + getByTestId(container, "main-menu-trigger").click(); + expect(window.h.state.openMenu).toBe("canvas"); + + await waitFor(() => { + Keyboard.keyDown(KEYS.ESCAPE); + expect(window.h.state.openMenu).toBe(null); + }); + }); +}); diff --git a/packages/excalidraw/components/dropdownMenu/DropdownMenuContent.tsx b/packages/excalidraw/components/dropdownMenu/DropdownMenuContent.tsx index e266cecf4..374e5c05d 100644 --- a/packages/excalidraw/components/dropdownMenu/DropdownMenuContent.tsx +++ b/packages/excalidraw/components/dropdownMenu/DropdownMenuContent.tsx @@ -2,9 +2,12 @@ import { Island } from "../Island"; import { useDevice } from "../App"; import clsx from "clsx"; import Stack from "../Stack"; -import React, { useRef } from "react"; +import React, { useEffect, useRef } from "react"; import { DropdownMenuContentPropsContext } from "./common"; import { useOutsideClick } from "../../hooks/useOutsideClick"; +import { KEYS } from "../../keys"; +import { EVENT } from "../../constants"; +import { useStable } from "../../hooks/useStable"; const MenuContent = ({ children, @@ -25,10 +28,30 @@ const MenuContent = ({ const device = useDevice(); const menuRef = useRef(null); + const callbacksRef = useStable({ onClickOutside }); + useOutsideClick(menuRef, () => { - onClickOutside?.(); + callbacksRef.onClickOutside?.(); }); + useEffect(() => { + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === KEYS.ESCAPE) { + event.stopImmediatePropagation(); + callbacksRef.onClickOutside?.(); + } + }; + + document.addEventListener(EVENT.KEYDOWN, onKeyDown, { + // so that we can stop propagation of the event before it reaches + // event handlers that were bound before this one + capture: true, + }); + return () => { + document.removeEventListener(EVENT.KEYDOWN, onKeyDown); + }; + }, [callbacksRef]); + const classNames = clsx(`dropdown-menu ${className}`, { "dropdown-menu--mobile": device.editor.isMobile, }).trim();