فهرست منبع

feat: close dropdown on escape (#7750)

David Luzar 1 سال پیش
والد
کامیت
a38e82f999

+ 20 - 0
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 <DropdownMenu/>", () => {
+  it("should", async () => {
+    const { container } = await render(<Excalidraw />);
+
+    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);
+    });
+  });
+});

+ 25 - 2
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<HTMLDivElement>(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();