From 48808bc32b4437729a2d3aa74f1f1013ea354c8e Mon Sep 17 00:00:00 2001 From: Robin Zigmond Date: Fri, 5 Aug 2022 14:11:14 +0100 Subject: [PATCH] fix(popover-container): ensure clicks inside popover do not close it Fix bug where portals inside a component did not automatically work with useClickAwayListener. Refactor useClickAwayListener to no longer take an array of refs and return a handler which can be attached to the parent component and stops the clickAway handler firing on the document - this automatically handlers any React children no matter whether they are rendered as DOM children or not. Remove any refs in parent components which are now unneeded. fix 5322 --- cypress/e2e/common/popoverContainer.feature | 10 ++++- src/components/date/date.component.js | 33 +++++++------- src/components/date/date.spec.js | 6 ++- .../__internal__/submenu/submenu.component.js | 7 ++- .../multi-action-button.spec.tsx.snap | 1 + .../multi-action-button.component.tsx | 3 +- .../popover-container-test.stories.tsx | 23 ++++++++++ .../popover-container.component.tsx | 5 +-- .../split-button/split-button.component.tsx | 3 +- .../useClickAwayListener.spec.tsx | 16 ++++--- .../useClickAwayListener.ts | 43 +++++++++++-------- 11 files changed, 101 insertions(+), 49 deletions(-) diff --git a/cypress/e2e/common/popoverContainer.feature b/cypress/e2e/common/popoverContainer.feature index b4fc311be7..75d7ec31c1 100644 --- a/cypress/e2e/common/popoverContainer.feature +++ b/cypress/e2e/common/popoverContainer.feature @@ -43,4 +43,12 @@ Feature: Popover container component Examples: | key | | Enter | - | Space | \ No newline at end of file + | Space | + + @positive + Scenario Outline: Select component inside Popover container does not close the Popover container when an option is selected + Given I open "Popover container Test" component page "with select" + And I open popover container + And I click on Select text + When I select the "green" Option + Then Popover container is visible diff --git a/src/components/date/date.component.js b/src/components/date/date.component.js index b1b71ea0c1..9cc2982adb 100644 --- a/src/components/date/date.component.js +++ b/src/components/date/date.component.js @@ -57,7 +57,6 @@ const DateInput = ({ const wrapperRef = useRef(); const parentRef = useRef(); const inputRef = useRef(); - const pickerRef = useRef(); const alreadyFocused = useRef(false); const isBlurBlocked = useRef(false); const focusedViaPicker = useRef(false); @@ -107,6 +106,19 @@ const DateInput = ({ return ev; }; + const handleClickAway = () => { + if (open) { + alreadyFocused.current = true; + inputRef.current.focus(); + isBlurBlocked.current = false; + inputRef.current.blur(); + setOpen(false); + alreadyFocused.current = false; + } + }; + + const handleClickInside = useClickAwayListener(handleClickAway, "mousedown"); + const handleChange = (ev) => { isInitialValue.current = false; onChange(buildCustomEvent(ev)); @@ -214,6 +226,8 @@ const DateInput = ({ }; const handleMouseDown = (ev) => { + handleClickInside(ev); + if (disabled || readOnly) { return; } @@ -235,8 +249,9 @@ const DateInput = ({ handleMouseDown(e); }; - const handlePickerMouseDown = () => { + const handlePickerMouseDown = (ev) => { isBlurBlocked.current = true; + handleClickInside(ev); }; const assignInput = (input) => { @@ -304,19 +319,6 @@ const DateInput = ({ return value; }; - const handleClickAway = () => { - if (open) { - alreadyFocused.current = true; - inputRef.current.focus(); - isBlurBlocked.current = false; - inputRef.current.blur(); - setOpen(false); - alreadyFocused.current = false; - } - }; - - useClickAwayListener([parentRef, pickerRef], handleClickAway, "mousedown"); - return ( diff --git a/src/components/date/date.spec.js b/src/components/date/date.spec.js index bb20bf22ea..ead0c22cec 100644 --- a/src/components/date/date.spec.js +++ b/src/components/date/date.spec.js @@ -957,7 +957,11 @@ function simulateMouseDownOnPicker(wrapper) { }; act(() => { - input.simulate("mousedown", mockEvent); + input + .getDOMNode() + .dispatchEvent( + new MouseEvent("mousedown", { ...mockEvent, bubbles: true }) + ); }); } diff --git a/src/components/menu/__internal__/submenu/submenu.component.js b/src/components/menu/__internal__/submenu/submenu.component.js index 543fd719a0..c0e2485900 100644 --- a/src/components/menu/__internal__/submenu/submenu.component.js +++ b/src/components/menu/__internal__/submenu/submenu.component.js @@ -48,7 +48,6 @@ const Submenu = React.forwardRef( const [submenuOpen, setSubmenuOpen] = useState(false); const [submenuFocusIndex, setSubmenuFocusIndex] = useState(undefined); const [characterString, setCharacterString] = useState(""); - const submenuRef = useRef(); const formattedChildren = React.Children.map(children, (child) => { if (child.type === ScrollableBlock) { return [...child.props.children]; @@ -253,16 +252,16 @@ const Submenu = React.forwardRef( // eslint-disable-next-line react-hooks/exhaustive-deps }, [characterString]); - useClickAwayListener([submenuRef], handleClickAway); + const handleClickInside = useClickAwayListener(handleClickAway); if (inFullscreenView) { return ( openSubmenu() : undefined} onMouseLeave={() => closeSubmenu()} - ref={submenuRef} isSubmenuOpen={submenuOpen} + onClick={handleClickInside} >