diff --git a/src/components/MapControls.js b/src/components/MapControls.js index cc3ecd8..0658241 100644 --- a/src/components/MapControls.js +++ b/src/components/MapControls.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useRef } from "react"; import { Flex, Box, IconButton, Label } from "theme-ui"; import AddMapButton from "./AddMapButton"; @@ -135,6 +135,9 @@ function MapControls({ function handleToolClick(event, tool) { if (tool !== selectedTool) { onToolChange(tool); + } else if (currentSubmenu) { + setCurrentSubmenu(null); + setCurrentSubmenuOptions({}); } else if (subMenus[tool]) { const toolRect = event.target.getBoundingClientRect(); setCurrentSubmenu(tool); @@ -143,6 +146,8 @@ function MapControls({ left: `${toolRect.left - 16}px`, top: `${toolRect.bottom - toolRect.height / 2}px`, style: { transform: "translate(-100%, -50%)" }, + // Exclude this node from the sub menus auto close + excludeNode: event.target, }); } } @@ -161,6 +166,9 @@ function MapControls({ sx={{ height: "2px", width: "24px", borderRadius: "2px", opacity: 0.5 }} > ); + + const expanedMenuRef = useRef(); + return ( <> {divider} diff --git a/src/components/MapMenu.js b/src/components/MapMenu.js index 3be5c31..6ddfd75 100644 --- a/src/components/MapMenu.js +++ b/src/components/MapMenu.js @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import Modal from "react-modal"; import { useThemeUI } from "theme-ui"; @@ -13,19 +13,29 @@ function MapMenu({ right, children, style, + // A node to exclude from the pointer event for closing + excludeNode, }) { - function handleModalContent(node) { - if (node) { - // Close modal if interacting with any other element - function handlePointerDown(event) { - const path = event.composedPath(); - if (!path.includes(node)) { - onRequestClose(); - document.body.removeEventListener("pointerdown", handlePointerDown); - } - } - document.body.addEventListener("pointerdown", handlePointerDown); + // Save modal node in state to ensure that the pointer listeners + // are removed if the open state changed not from the onRequestClose + // callback + const [modalContentNode, setModalContentNode] = useState(null); + useEffect(() => { + // Close modal if interacting with any other element + function handlePointerDown(event) { + const path = event.composedPath(); + if ( + !path.includes(modalContentNode) && + !(excludeNode && path.includes(excludeNode)) + ) { + onRequestClose(); + document.body.removeEventListener("pointerdown", handlePointerDown); + } + } + + if (modalContentNode) { + document.body.addEventListener("pointerdown", handlePointerDown); // Check for wheel event to close modal as well document.body.addEventListener( "wheel", @@ -35,6 +45,15 @@ function MapMenu({ { once: true } ); } + return () => { + if (modalContentNode) { + document.body.removeEventListener("pointerdown", handlePointerDown); + } + }; + }, [modalContentNode, excludeNode, onRequestClose]); + + function handleModalContent(node) { + setModalContentNode(node); onModalContent(node); } @@ -72,6 +91,7 @@ MapMenu.defaultProps = { right: "initial", bottom: "initial", style: {}, + excludeNode: null, }; export default MapMenu;