diff --git a/packages/@react-aria/interactions/src/useInteractOutside.ts b/packages/@react-aria/interactions/src/useInteractOutside.ts
index d2eec8b1d90..47cfca88a2c 100644
--- a/packages/@react-aria/interactions/src/useInteractOutside.ts
+++ b/packages/@react-aria/interactions/src/useInteractOutside.ts
@@ -37,6 +37,10 @@ export function useInteractOutside(props: InteractOutsideProps) {
let state = stateRef.current;
useEffect(() => {
+ if (!onInteractOutside) {
+ return;
+ }
+
let onPointerDown = (e) => {
if (isDisabled) {
return;
@@ -45,18 +49,17 @@ export function useInteractOutside(props: InteractOutsideProps) {
state.isPointerDown = true;
}
};
- /*
+
// FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=1675846 prevents us from using this pointerevent
// once it's fixed we can uncomment
// Use pointer events if available. Otherwise, fall back to mouse and touch events.
- if (typeof PointerEvent !== 'undefined') {
+ /* if (typeof PointerEvent !== 'undefined') {
let onPointerUp = (e) => {
if (state.isPointerDown && onInteractOutside && isValidEvent(e, ref)) {
state.isPointerDown = false;
onInteractOutside(e);
}
};
-
// changing these to capture phase fixed combobox
document.addEventListener('pointerdown', onPointerDown, true);
document.addEventListener('pointerup', onPointerUp, true);
@@ -78,6 +81,7 @@ export function useInteractOutside(props: InteractOutsideProps) {
}
};
+
let onTouchEnd = (e) => {
if (isDisabled) {
return;
@@ -100,6 +104,7 @@ export function useInteractOutside(props: InteractOutsideProps) {
document.removeEventListener('touchstart', onPointerDown, true);
document.removeEventListener('touchend', onTouchEnd, true);
};
+ // }
}, [onInteractOutside, ref, state.ignoreEmulatedMouseEvents, state.isPointerDown, isDisabled]);
}
diff --git a/packages/@react-aria/interactions/test/useInteractOutside.test.js b/packages/@react-aria/interactions/test/useInteractOutside.test.js
index 31747eaa0ea..007d7df18de 100644
--- a/packages/@react-aria/interactions/test/useInteractOutside.test.js
+++ b/packages/@react-aria/interactions/test/useInteractOutside.test.js
@@ -33,6 +33,7 @@ describe('useInteractOutside', function () {
describe('pointer events', function () {
installPointerEvent();
+ /* TODO enable these ones pointer events are restored to useInteractOutside
it('should fire interact outside events based on pointer events', function () {
let onInteractOutside = jest.fn();
let res = render(
@@ -40,12 +41,12 @@ describe('useInteractOutside', function () {
);
let el = res.getByText('test');
- fireEvent(el, pointerEvent('mousedown'));
- fireEvent(el, pointerEvent('mouseup'));
+ fireEvent(el, pointerEvent('pointerdown'));
+ fireEvent(el, pointerEvent('pointerup'));
expect(onInteractOutside).not.toHaveBeenCalled();
- fireEvent(document.body, pointerEvent('mousedown'));
- fireEvent(document.body, pointerEvent('mouseup'));
+ fireEvent(document.body, pointerEvent('pointerdown'));
+ fireEvent(document.body, pointerEvent('pointerup'));
expect(onInteractOutside).toHaveBeenCalledTimes(1);
});
@@ -55,14 +56,14 @@ describe('useInteractOutside', function () {
);
- fireEvent(document.body, pointerEvent('mousedown', {button: 1}));
- fireEvent(document.body, pointerEvent('mouseup', {button: 1}));
+ fireEvent(document.body, pointerEvent('pointerdown', {button: 1}));
+ fireEvent(document.body, pointerEvent('pointerup', {button: 1}));
expect(onInteractOutside).not.toHaveBeenCalled();
- fireEvent(document.body, pointerEvent('mousedown', {button: 0}));
- fireEvent(document.body, pointerEvent('mouseup'));
+ fireEvent(document.body, pointerEvent('pointerdown', {button: 0}));
+ fireEvent(document.body, pointerEvent('pointerup', {button: 0}));
expect(onInteractOutside).toHaveBeenCalledTimes(1);
- });
+ });*/
it('should not fire interact outside if there is a pointer up event without a pointer down first', function () {
// Fire pointer down before component with useInteractOutside is mounted
diff --git a/packages/@react-aria/menu/stories/useMenuTrigger.stories.tsx b/packages/@react-aria/menu/stories/useMenuTrigger.stories.tsx
new file mode 100644
index 00000000000..47cba2806df
--- /dev/null
+++ b/packages/@react-aria/menu/stories/useMenuTrigger.stories.tsx
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2020 Adobe. All rights reserved.
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+import {DismissButton, useOverlay} from '@react-aria/overlays';
+import {Flex} from '@react-spectrum/layout';
+import {FocusScope} from '@react-aria/focus';
+import {Item} from '@react-stately/collections';
+import {mergeProps} from '@react-aria/utils';
+import React from 'react';
+import {storiesOf} from '@storybook/react';
+import {useButton} from '@react-aria/button';
+import {useFocus} from '@react-aria/interactions';
+import {useMenu, useMenuItem, useMenuTrigger} from '@react-aria/menu';
+import {useMenuTriggerState} from '@react-stately/menu';
+import {useTreeState} from '@react-stately/tree';
+
+
+storiesOf('useMenuTrigger', module)
+ .add('2 menus', () => (
+
+
+ Copy
+ Cut
+ Paste
+
+
+ Copy
+ Cut
+ Paste
+
+
+ ))
+ .add('2 menus with disabled', () => (
+
+
+ Copy
+ Cut
+ Paste
+
+
+ Copy
+ Cut
+ Paste
+
+
+ ));
+
+function MenuButton(props) {
+ // Create state based on the incoming props
+ let state = useMenuTriggerState(props);
+
+ // Get props for the menu trigger and menu elements
+ let ref = React.useRef(null);
+
+ let shouldCloseOnInteractOutside = (element) => !ref?.current?.contains(element) ?? false;
+ let {menuTriggerProps, menuProps} = useMenuTrigger({}, state, ref);
+
+ // Get props for the button based on the trigger props from useMenuTrigger
+ let {buttonProps} = useButton({...menuTriggerProps, isDisabled: props.isDisabled}, ref);
+
+ return (
+
+
+ {state.isOpen && (
+ state.close()} />
+ )}
+
+ );
+}
+
+function MenuPopup(props) {
+ // Create menu state based on the incoming props
+ let state = useTreeState({...props, selectionMode: 'none'});
+
+ // Get props for the menu element
+ let ref = React.useRef();
+ let {menuProps} = useMenu(props, state, ref);
+
+ // Handle events that should cause the menu to close,
+ // e.g. blur, clicking outside, or pressing the escape key.
+ let overlayRef = React.useRef();
+ let {overlayProps} = useOverlay(
+ {
+ shouldCloseOnInteractOutside: props.shouldCloseOnInteractOutside,
+ onClose: props.onClose,
+ shouldCloseOnBlur: true,
+ isOpen: true,
+ isDismissable: true
+ },
+ overlayRef
+ );
+
+ // Wrap in so that focus is restored back to the
+ // trigger when the menu is closed. In addition, add hidden
+ // components at the start and end of the list
+ // to allow screen reader users to dismiss the popup easily.
+ return (
+
+