mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
[react-ui] usePress from useKeyboard and useTap (#16772)
This implements 'usePress' in user-space as a combination of 'useKeyboard' and 'useTap'. The existing 'usePress' API is preserved for now. The previous 'usePress' implementation is moved to 'PressLegacy'.
This commit is contained in:
committed by
GitHub
parent
494300b366
commit
3af05de1aa
@@ -7,11 +7,11 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {KeyboardEvent} from 'react-ui/events/src/dom/Keyboard';
|
||||
import type {KeyboardEvent} from 'react-ui/events/keyboard';
|
||||
|
||||
import React from 'react';
|
||||
import {tabFocusableImpl} from './TabbableScope';
|
||||
import {useKeyboard} from '../../events/keyboard';
|
||||
import {useKeyboard} from 'react-ui/events/keyboard';
|
||||
|
||||
type GridComponentProps = {
|
||||
children: React.Node,
|
||||
|
||||
@@ -8,16 +8,17 @@
|
||||
*/
|
||||
|
||||
import type {ReactScopeMethods} from 'shared/ReactTypes';
|
||||
import type {KeyboardEvent} from 'react-ui/events/src/dom/Keyboard';
|
||||
import type {KeyboardEvent} from 'react-ui/events/keyboard';
|
||||
|
||||
import React from 'react';
|
||||
import {TabbableScope} from './TabbableScope';
|
||||
import {useKeyboard} from '../../events/keyboard';
|
||||
import {useKeyboard} from 'react-ui/events/keyboard';
|
||||
|
||||
type TabFocusControllerProps = {
|
||||
children: React.Node,
|
||||
contain?: boolean,
|
||||
};
|
||||
|
||||
const {useRef} = React;
|
||||
|
||||
function getTabbableNodes(scope: ReactScopeMethods) {
|
||||
|
||||
12
packages/react-ui/events/press-legacy.js
vendored
Normal file
12
packages/react-ui/events/press-legacy.js
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = require('./src/dom/PressLegacy');
|
||||
11
packages/react-ui/events/src/dom/Keyboard.js
vendored
11
packages/react-ui/events/src/dom/Keyboard.js
vendored
@@ -27,6 +27,7 @@ type KeyboardProps = {|
|
||||
onClick?: (e: KeyboardEvent) => ?boolean,
|
||||
onKeyDown?: (e: KeyboardEvent) => ?boolean,
|
||||
onKeyUp?: (e: KeyboardEvent) => ?boolean,
|
||||
preventClick?: boolean,
|
||||
preventKeys?: PreventKeysArray,
|
||||
|};
|
||||
|
||||
@@ -256,6 +257,12 @@ const keyboardResponderImpl = {
|
||||
);
|
||||
}
|
||||
} else if (type === 'click' && isVirtualClick(event)) {
|
||||
if (props.preventClick !== false) {
|
||||
// 'click' occurs before or after 'keyup', and may need native
|
||||
// behavior prevented
|
||||
nativeEvent.preventDefault();
|
||||
state.defaultPrevented = true;
|
||||
}
|
||||
const onClick = props.onClick;
|
||||
if (onClick != null) {
|
||||
dispatchKeyboardEvent(
|
||||
@@ -266,10 +273,6 @@ const keyboardResponderImpl = {
|
||||
state.defaultPrevented,
|
||||
);
|
||||
}
|
||||
if (state.defaultPrevented && !nativeEvent.defaultPrevented) {
|
||||
// 'click' occurs before 'keyup' and may need native behavior prevented
|
||||
nativeEvent.preventDefault();
|
||||
}
|
||||
} else if (type === 'keyup') {
|
||||
state.isActive = false;
|
||||
const onKeyUp = props.onKeyUp;
|
||||
|
||||
930
packages/react-ui/events/src/dom/Press.js
vendored
930
packages/react-ui/events/src/dom/Press.js
vendored
@@ -7,858 +7,178 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {
|
||||
ReactDOMResponderEvent,
|
||||
ReactDOMResponderContext,
|
||||
PointerType,
|
||||
} from 'shared/ReactDOMTypes';
|
||||
import type {
|
||||
EventPriority,
|
||||
ReactEventResponderListener,
|
||||
} from 'shared/ReactTypes';
|
||||
import type {PointerType} from 'shared/ReactDOMTypes';
|
||||
|
||||
import React from 'react';
|
||||
import {DiscreteEvent, UserBlockingEvent} from 'shared/ReactTypes';
|
||||
import {useTap} from 'react-ui/events/tap';
|
||||
import {useKeyboard} from 'react-ui/events/keyboard';
|
||||
|
||||
type PressProps = {|
|
||||
disabled: boolean,
|
||||
pressRetentionOffset: {
|
||||
top: number,
|
||||
right: number,
|
||||
bottom: number,
|
||||
left: number,
|
||||
},
|
||||
preventDefault: boolean,
|
||||
onPress: (e: PressEvent) => void,
|
||||
onPressChange: boolean => void,
|
||||
onPressEnd: (e: PressEvent) => void,
|
||||
onPressMove: (e: PressEvent) => void,
|
||||
onPressStart: (e: PressEvent) => void,
|
||||
|};
|
||||
const emptyObject = {};
|
||||
|
||||
type PressState = {
|
||||
activationPosition: null | $ReadOnly<{|
|
||||
x: number,
|
||||
y: number,
|
||||
|}>,
|
||||
addedRootEvents: boolean,
|
||||
buttons: 0 | 1 | 4,
|
||||
isActivePressed: boolean,
|
||||
isActivePressStart: boolean,
|
||||
isPressed: boolean,
|
||||
isPressWithinResponderRegion: boolean,
|
||||
pointerType: PointerType,
|
||||
pressTarget: null | Element | Document,
|
||||
responderRegionOnActivation: null | $ReadOnly<{|
|
||||
bottom: number,
|
||||
left: number,
|
||||
right: number,
|
||||
top: number,
|
||||
|}>,
|
||||
responderRegionOnDeactivation: null | $ReadOnly<{|
|
||||
bottom: number,
|
||||
left: number,
|
||||
right: number,
|
||||
top: number,
|
||||
|}>,
|
||||
ignoreEmulatedMouseEvents: boolean,
|
||||
activePointerId: null | number,
|
||||
shouldPreventClick: boolean,
|
||||
touchEvent: null | Touch,
|
||||
};
|
||||
type PressProps = $ReadOnly<{|
|
||||
disabled?: boolean,
|
||||
preventDefault?: boolean,
|
||||
onPress?: (e: PressEvent) => void,
|
||||
onPressChange?: boolean => void,
|
||||
onPressEnd?: (e: PressEvent) => void,
|
||||
onPressMove?: (e: PressEvent) => void,
|
||||
onPressStart?: (e: PressEvent) => void,
|
||||
|}>;
|
||||
|
||||
type PressEventType =
|
||||
| 'press'
|
||||
| 'pressmove'
|
||||
| 'pressstart'
|
||||
| 'presschange'
|
||||
| 'pressmove'
|
||||
| 'pressend'
|
||||
| 'presschange';
|
||||
| 'press';
|
||||
|
||||
type PressEvent = {|
|
||||
altKey: boolean,
|
||||
buttons: 0 | 1 | 4,
|
||||
clientX: null | number,
|
||||
clientY: null | number,
|
||||
buttons: null | 0 | 1 | 4,
|
||||
ctrlKey: boolean,
|
||||
defaultPrevented: boolean,
|
||||
key: null | string,
|
||||
metaKey: boolean,
|
||||
pageX: null | number,
|
||||
pageY: null | number,
|
||||
pageX: number,
|
||||
pageY: number,
|
||||
pointerType: PointerType,
|
||||
screenX: null | number,
|
||||
screenY: null | number,
|
||||
shiftKey: boolean,
|
||||
target: Element | Document,
|
||||
target: null | Element,
|
||||
timeStamp: number,
|
||||
type: PressEventType,
|
||||
x: null | number,
|
||||
y: null | number,
|
||||
x: number,
|
||||
y: number,
|
||||
|};
|
||||
|
||||
const hasPointerEvents =
|
||||
typeof window !== 'undefined' && window.PointerEvent !== undefined;
|
||||
|
||||
const isMac =
|
||||
typeof window !== 'undefined' && window.navigator != null
|
||||
? /^Mac/.test(window.navigator.platform)
|
||||
: false;
|
||||
|
||||
const DEFAULT_PRESS_RETENTION_OFFSET = {
|
||||
bottom: 20,
|
||||
top: 20,
|
||||
left: 20,
|
||||
right: 20,
|
||||
};
|
||||
|
||||
const targetEventTypes = hasPointerEvents
|
||||
? ['keydown_active', 'pointerdown', 'click_active']
|
||||
: ['keydown_active', 'touchstart', 'mousedown', 'click_active'];
|
||||
|
||||
const rootEventTypes = hasPointerEvents
|
||||
? ['pointerup', 'pointermove', 'pointercancel', 'click', 'keyup', 'scroll']
|
||||
: [
|
||||
'click',
|
||||
'keyup',
|
||||
'scroll',
|
||||
'mousemove',
|
||||
'touchmove',
|
||||
'touchcancel',
|
||||
// Used as a 'cancel' signal for mouse interactions
|
||||
'dragstart',
|
||||
'mouseup',
|
||||
'touchend',
|
||||
];
|
||||
|
||||
function isFunction(obj): boolean {
|
||||
return typeof obj === 'function';
|
||||
}
|
||||
|
||||
function createPressEvent(
|
||||
context: ReactDOMResponderContext,
|
||||
type: PressEventType,
|
||||
target: Element | Document,
|
||||
pointerType: PointerType,
|
||||
event: ?ReactDOMResponderEvent,
|
||||
touchEvent: null | Touch,
|
||||
defaultPrevented: boolean,
|
||||
state: PressState,
|
||||
): PressEvent {
|
||||
const timeStamp = context.getTimeStamp();
|
||||
let clientX = null;
|
||||
let clientY = null;
|
||||
let pageX = null;
|
||||
let pageY = null;
|
||||
let screenX = null;
|
||||
let screenY = null;
|
||||
let altKey = false;
|
||||
let ctrlKey = false;
|
||||
let metaKey = false;
|
||||
let shiftKey = false;
|
||||
|
||||
if (event) {
|
||||
const nativeEvent = (event.nativeEvent: any);
|
||||
({altKey, ctrlKey, metaKey, shiftKey} = nativeEvent);
|
||||
// Only check for one property, checking for all of them is costly. We can assume
|
||||
// if clientX exists, so do the rest.
|
||||
let eventObject;
|
||||
eventObject = (touchEvent: any) || (nativeEvent: any);
|
||||
if (eventObject) {
|
||||
({clientX, clientY, pageX, pageY, screenX, screenY} = eventObject);
|
||||
}
|
||||
}
|
||||
function createGestureState(e: any, type: PressEventType): PressEvent {
|
||||
return {
|
||||
altKey,
|
||||
buttons: state.buttons,
|
||||
clientX,
|
||||
clientY,
|
||||
ctrlKey,
|
||||
defaultPrevented,
|
||||
metaKey,
|
||||
pageX,
|
||||
pageY,
|
||||
pointerType,
|
||||
screenX,
|
||||
screenY,
|
||||
shiftKey,
|
||||
target,
|
||||
timeStamp,
|
||||
altKey: e.altKey,
|
||||
buttons: e.buttons,
|
||||
ctrlKey: e.ctrlKey,
|
||||
defaultPrevented: e.defaultPrevented,
|
||||
key: e.key,
|
||||
metaKey: e.metaKey,
|
||||
pageX: e.pageX,
|
||||
pageY: e.pageX,
|
||||
pointerType: e.pointerType,
|
||||
shiftKey: e.shiftKey,
|
||||
target: e.target,
|
||||
timeStamp: e.timeStamp,
|
||||
type,
|
||||
x: clientX,
|
||||
y: clientY,
|
||||
x: e.x,
|
||||
y: e.y,
|
||||
};
|
||||
}
|
||||
|
||||
function dispatchEvent(
|
||||
event: ?ReactDOMResponderEvent,
|
||||
listener: any => void,
|
||||
context: ReactDOMResponderContext,
|
||||
state: PressState,
|
||||
name: PressEventType,
|
||||
eventPriority: EventPriority,
|
||||
): void {
|
||||
const target = ((state.pressTarget: any): Element | Document);
|
||||
const pointerType = state.pointerType;
|
||||
const defaultPrevented =
|
||||
(event != null && event.nativeEvent.defaultPrevented === true) ||
|
||||
(name === 'press' && state.shouldPreventClick);
|
||||
const touchEvent = state.touchEvent;
|
||||
const syntheticEvent = createPressEvent(
|
||||
context,
|
||||
name,
|
||||
target,
|
||||
pointerType,
|
||||
event,
|
||||
touchEvent,
|
||||
defaultPrevented,
|
||||
state,
|
||||
);
|
||||
context.dispatchEvent(syntheticEvent, listener, eventPriority);
|
||||
}
|
||||
|
||||
function dispatchPressChangeEvent(
|
||||
context: ReactDOMResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
const onPressChange = props.onPressChange;
|
||||
if (isFunction(onPressChange)) {
|
||||
const bool = state.isActivePressed;
|
||||
context.dispatchEvent(bool, onPressChange, DiscreteEvent);
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchPressStartEvents(
|
||||
event: ReactDOMResponderEvent,
|
||||
context: ReactDOMResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
state.isPressed = true;
|
||||
|
||||
if (!state.isActivePressStart) {
|
||||
state.isActivePressStart = true;
|
||||
const nativeEvent: any = event.nativeEvent;
|
||||
const {clientX: x, clientY: y} = state.touchEvent || nativeEvent;
|
||||
const wasActivePressed = state.isActivePressed;
|
||||
state.isActivePressed = true;
|
||||
if (x !== undefined && y !== undefined) {
|
||||
state.activationPosition = {x, y};
|
||||
}
|
||||
const onPressStart = props.onPressStart;
|
||||
|
||||
if (isFunction(onPressStart)) {
|
||||
dispatchEvent(
|
||||
event,
|
||||
onPressStart,
|
||||
context,
|
||||
state,
|
||||
'pressstart',
|
||||
DiscreteEvent,
|
||||
);
|
||||
}
|
||||
if (!wasActivePressed) {
|
||||
dispatchPressChangeEvent(context, props, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchPressEndEvents(
|
||||
event: ?ReactDOMResponderEvent,
|
||||
context: ReactDOMResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
state.isActivePressStart = false;
|
||||
state.isPressed = false;
|
||||
|
||||
if (state.isActivePressed) {
|
||||
state.isActivePressed = false;
|
||||
const onPressEnd = props.onPressEnd;
|
||||
|
||||
if (isFunction(onPressEnd)) {
|
||||
dispatchEvent(
|
||||
event,
|
||||
onPressEnd,
|
||||
context,
|
||||
state,
|
||||
'pressend',
|
||||
DiscreteEvent,
|
||||
);
|
||||
}
|
||||
dispatchPressChangeEvent(context, props, state);
|
||||
}
|
||||
|
||||
state.responderRegionOnDeactivation = null;
|
||||
}
|
||||
|
||||
function dispatchCancel(
|
||||
event: ReactDOMResponderEvent,
|
||||
context: ReactDOMResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
state.touchEvent = null;
|
||||
if (state.isPressed) {
|
||||
state.ignoreEmulatedMouseEvents = false;
|
||||
dispatchPressEndEvents(event, context, props, state);
|
||||
}
|
||||
removeRootEventTypes(context, state);
|
||||
}
|
||||
|
||||
function isValidKeyboardEvent(nativeEvent: Object): boolean {
|
||||
const {key, target} = nativeEvent;
|
||||
const {tagName, isContentEditable} = target;
|
||||
// Accessibility for keyboards. Space and Enter only.
|
||||
// "Spacebar" is for IE 11
|
||||
function isValidKey(e): boolean {
|
||||
const {key, target} = e;
|
||||
const {tagName, isContentEditable} = (target: any);
|
||||
return (
|
||||
(key === 'Enter' || key === ' ' || key === 'Spacebar') &&
|
||||
(key === 'Enter' || key === ' ') &&
|
||||
(tagName !== 'INPUT' &&
|
||||
tagName !== 'TEXTAREA' &&
|
||||
isContentEditable !== true)
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: account for touch hit slop
|
||||
function calculateResponderRegion(
|
||||
context: ReactDOMResponderContext,
|
||||
target: Element,
|
||||
props: PressProps,
|
||||
) {
|
||||
const pressRetentionOffset = context.objectAssign(
|
||||
{},
|
||||
DEFAULT_PRESS_RETENTION_OFFSET,
|
||||
props.pressRetentionOffset,
|
||||
);
|
||||
/**
|
||||
* The lack of built-in composition for gesture responders means we have to
|
||||
* selectively ignore callbacks from useKeyboard or useTap if the other is
|
||||
* active.
|
||||
*/
|
||||
export function usePress(props: PressProps) {
|
||||
const safeProps = props || emptyObject;
|
||||
const {
|
||||
disabled,
|
||||
preventDefault,
|
||||
onPress,
|
||||
onPressChange,
|
||||
onPressEnd,
|
||||
onPressMove,
|
||||
onPressStart,
|
||||
} = safeProps;
|
||||
|
||||
let {left, right, bottom, top} = target.getBoundingClientRect();
|
||||
const [active, updateActive] = React.useState(null);
|
||||
|
||||
if (pressRetentionOffset) {
|
||||
if (pressRetentionOffset.bottom != null) {
|
||||
bottom += pressRetentionOffset.bottom;
|
||||
}
|
||||
if (pressRetentionOffset.left != null) {
|
||||
left -= pressRetentionOffset.left;
|
||||
}
|
||||
if (pressRetentionOffset.right != null) {
|
||||
right += pressRetentionOffset.right;
|
||||
}
|
||||
if (pressRetentionOffset.top != null) {
|
||||
top -= pressRetentionOffset.top;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
bottom,
|
||||
top,
|
||||
left,
|
||||
right,
|
||||
};
|
||||
}
|
||||
|
||||
function getTouchFromPressEvent(nativeEvent: TouchEvent): null | Touch {
|
||||
const targetTouches = nativeEvent.targetTouches;
|
||||
if (targetTouches.length > 0) {
|
||||
return targetTouches[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function unmountResponder(
|
||||
context: ReactDOMResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
if (state.isPressed) {
|
||||
removeRootEventTypes(context, state);
|
||||
dispatchPressEndEvents(null, context, props, state);
|
||||
}
|
||||
}
|
||||
|
||||
function addRootEventTypes(
|
||||
context: ReactDOMResponderContext,
|
||||
state: PressState,
|
||||
): void {
|
||||
if (!state.addedRootEvents) {
|
||||
state.addedRootEvents = true;
|
||||
context.addRootEventTypes(rootEventTypes);
|
||||
}
|
||||
}
|
||||
|
||||
function removeRootEventTypes(
|
||||
context: ReactDOMResponderContext,
|
||||
state: PressState,
|
||||
): void {
|
||||
if (state.addedRootEvents) {
|
||||
state.addedRootEvents = false;
|
||||
context.removeRootEventTypes(rootEventTypes);
|
||||
}
|
||||
}
|
||||
|
||||
function getTouchById(
|
||||
nativeEvent: TouchEvent,
|
||||
pointerId: null | number,
|
||||
): null | Touch {
|
||||
const changedTouches = nativeEvent.changedTouches;
|
||||
for (let i = 0; i < changedTouches.length; i++) {
|
||||
const touch = changedTouches[i];
|
||||
if (touch.identifier === pointerId) {
|
||||
return touch;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getTouchTarget(context: ReactDOMResponderContext, touchEvent: Touch) {
|
||||
const doc = context.getActiveDocument();
|
||||
return doc.elementFromPoint(touchEvent.clientX, touchEvent.clientY);
|
||||
}
|
||||
|
||||
function updateIsPressWithinResponderRegion(
|
||||
nativeEventOrTouchEvent: Event | Touch,
|
||||
context: ReactDOMResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
// Calculate the responder region we use for deactivation if not
|
||||
// already done during move event.
|
||||
if (state.responderRegionOnDeactivation == null) {
|
||||
state.responderRegionOnDeactivation = calculateResponderRegion(
|
||||
context,
|
||||
((state.pressTarget: any): Element),
|
||||
props,
|
||||
);
|
||||
}
|
||||
const {responderRegionOnActivation, responderRegionOnDeactivation} = state;
|
||||
let left, top, right, bottom;
|
||||
|
||||
if (responderRegionOnActivation != null) {
|
||||
left = responderRegionOnActivation.left;
|
||||
top = responderRegionOnActivation.top;
|
||||
right = responderRegionOnActivation.right;
|
||||
bottom = responderRegionOnActivation.bottom;
|
||||
|
||||
if (responderRegionOnDeactivation != null) {
|
||||
left = Math.min(left, responderRegionOnDeactivation.left);
|
||||
top = Math.min(top, responderRegionOnDeactivation.top);
|
||||
right = Math.max(right, responderRegionOnDeactivation.right);
|
||||
bottom = Math.max(bottom, responderRegionOnDeactivation.bottom);
|
||||
}
|
||||
}
|
||||
const {clientX: x, clientY: y} = (nativeEventOrTouchEvent: any);
|
||||
|
||||
state.isPressWithinResponderRegion =
|
||||
left != null &&
|
||||
right != null &&
|
||||
top != null &&
|
||||
bottom != null &&
|
||||
x !== null &&
|
||||
y !== null &&
|
||||
(x >= left && x <= right && y >= top && y <= bottom);
|
||||
}
|
||||
|
||||
// After some investigation work, screen reader virtual
|
||||
// clicks (NVDA, Jaws, VoiceOver) do not have co-ords associated with the click
|
||||
// event and "detail" is always 0 (where normal clicks are > 0)
|
||||
function isScreenReaderVirtualClick(nativeEvent): boolean {
|
||||
return (
|
||||
nativeEvent.detail === 0 &&
|
||||
nativeEvent.screenX === 0 &&
|
||||
nativeEvent.screenY === 0 &&
|
||||
nativeEvent.clientX === 0 &&
|
||||
nativeEvent.clientY === 0
|
||||
);
|
||||
}
|
||||
|
||||
function targetIsDocument(target: null | Node): boolean {
|
||||
// When target is null, it is the root
|
||||
return target === null || target.nodeType === 9;
|
||||
}
|
||||
|
||||
const pressResponderImpl = {
|
||||
targetEventTypes,
|
||||
getInitialState(): PressState {
|
||||
return {
|
||||
activationPosition: null,
|
||||
addedRootEvents: false,
|
||||
buttons: 0,
|
||||
isActivePressed: false,
|
||||
isActivePressStart: false,
|
||||
isPressed: false,
|
||||
isPressWithinResponderRegion: true,
|
||||
pointerType: '',
|
||||
pressTarget: null,
|
||||
responderRegionOnActivation: null,
|
||||
responderRegionOnDeactivation: null,
|
||||
ignoreEmulatedMouseEvents: false,
|
||||
activePointerId: null,
|
||||
shouldPreventClick: false,
|
||||
touchEvent: null,
|
||||
};
|
||||
},
|
||||
onEvent(
|
||||
event: ReactDOMResponderEvent,
|
||||
context: ReactDOMResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
const {pointerType, type} = event;
|
||||
|
||||
if (props.disabled) {
|
||||
removeRootEventTypes(context, state);
|
||||
dispatchPressEndEvents(event, context, props, state);
|
||||
state.ignoreEmulatedMouseEvents = false;
|
||||
return;
|
||||
}
|
||||
const nativeEvent: any = event.nativeEvent;
|
||||
const isPressed = state.isPressed;
|
||||
|
||||
switch (type) {
|
||||
// START
|
||||
case 'pointerdown':
|
||||
case 'keydown':
|
||||
case 'mousedown':
|
||||
case 'touchstart': {
|
||||
if (!isPressed) {
|
||||
const isTouchEvent = type === 'touchstart';
|
||||
const isPointerEvent = type === 'pointerdown';
|
||||
const isKeyboardEvent = pointerType === 'keyboard';
|
||||
const isMouseEvent = pointerType === 'mouse';
|
||||
|
||||
// Ignore emulated mouse events
|
||||
if (type === 'mousedown' && state.ignoreEmulatedMouseEvents) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.shouldPreventClick = false;
|
||||
if (isTouchEvent) {
|
||||
state.ignoreEmulatedMouseEvents = true;
|
||||
} else if (isKeyboardEvent) {
|
||||
// Ignore unrelated key events
|
||||
if (isValidKeyboardEvent(nativeEvent)) {
|
||||
const {
|
||||
altKey,
|
||||
ctrlKey,
|
||||
metaKey,
|
||||
shiftKey,
|
||||
} = (nativeEvent: MouseEvent);
|
||||
if (nativeEvent.key === ' ') {
|
||||
nativeEvent.preventDefault();
|
||||
} else if (
|
||||
props.preventDefault !== false &&
|
||||
!shiftKey &&
|
||||
!metaKey &&
|
||||
!ctrlKey &&
|
||||
!altKey
|
||||
) {
|
||||
state.shouldPreventClick = true;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// We set these here, before the button check so we have this
|
||||
// data around for handling of the context menu
|
||||
state.pointerType = pointerType;
|
||||
const pressTarget = (state.pressTarget = context.getResponderNode());
|
||||
if (isPointerEvent) {
|
||||
state.activePointerId = nativeEvent.pointerId;
|
||||
} else if (isTouchEvent) {
|
||||
const touchEvent = getTouchFromPressEvent(nativeEvent);
|
||||
if (touchEvent === null) {
|
||||
return;
|
||||
}
|
||||
state.touchEvent = touchEvent;
|
||||
state.activePointerId = touchEvent.identifier;
|
||||
}
|
||||
|
||||
// Ignore any device buttons except primary/middle and touch/pen contact.
|
||||
// Additionally we ignore primary-button + ctrl-key with Macs as that
|
||||
// acts like right-click and opens the contextmenu.
|
||||
if (
|
||||
nativeEvent.buttons === 2 ||
|
||||
nativeEvent.buttons > 4 ||
|
||||
(isMac && isMouseEvent && nativeEvent.ctrlKey)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// Exclude document targets
|
||||
if (!targetIsDocument(pressTarget)) {
|
||||
state.responderRegionOnActivation = calculateResponderRegion(
|
||||
context,
|
||||
((pressTarget: any): Element),
|
||||
props,
|
||||
);
|
||||
}
|
||||
state.responderRegionOnDeactivation = null;
|
||||
state.isPressWithinResponderRegion = true;
|
||||
state.buttons = nativeEvent.buttons;
|
||||
dispatchPressStartEvents(event, context, props, state);
|
||||
addRootEventTypes(context, state);
|
||||
} else {
|
||||
// Prevent spacebar press from scrolling the window
|
||||
if (isValidKeyboardEvent(nativeEvent) && nativeEvent.key === ' ') {
|
||||
nativeEvent.preventDefault();
|
||||
}
|
||||
const tap = useTap({
|
||||
disabled: disabled || active === 'keyboard',
|
||||
preventDefault,
|
||||
onTapStart(e) {
|
||||
if (active == null) {
|
||||
updateActive('tap');
|
||||
if (onPressStart != null) {
|
||||
onPressStart(createGestureState(e, 'pressstart'));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'click': {
|
||||
if (state.shouldPreventClick) {
|
||||
nativeEvent.preventDefault();
|
||||
},
|
||||
onTapChange: onPressChange,
|
||||
onTapUpdate(e) {
|
||||
if (active === 'tap') {
|
||||
if (onPressMove != null) {
|
||||
onPressMove(createGestureState(e, 'pressmove'));
|
||||
}
|
||||
const onPress = props.onPress;
|
||||
|
||||
if (isFunction(onPress) && isScreenReaderVirtualClick(nativeEvent)) {
|
||||
state.pointerType = 'keyboard';
|
||||
state.pressTarget = context.getResponderNode();
|
||||
const preventDefault = props.preventDefault;
|
||||
|
||||
if (preventDefault !== false) {
|
||||
nativeEvent.preventDefault();
|
||||
}
|
||||
dispatchEvent(event, onPress, context, state, 'press', DiscreteEvent);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
onRootEvent(
|
||||
event: ReactDOMResponderEvent,
|
||||
context: ReactDOMResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
let {pointerType, target, type} = event;
|
||||
|
||||
const nativeEvent: any = event.nativeEvent;
|
||||
const isPressed = state.isPressed;
|
||||
const activePointerId = state.activePointerId;
|
||||
const previousPointerType = state.pointerType;
|
||||
|
||||
switch (type) {
|
||||
// MOVE
|
||||
case 'pointermove':
|
||||
case 'mousemove':
|
||||
case 'touchmove': {
|
||||
let touchEvent;
|
||||
// Ignore emulated events (pointermove will dispatch touch and mouse events)
|
||||
// Ignore pointermove events during a keyboard press.
|
||||
if (previousPointerType !== pointerType) {
|
||||
return;
|
||||
},
|
||||
onTapEnd(e) {
|
||||
if (active === 'tap') {
|
||||
if (onPressEnd != null) {
|
||||
onPressEnd(createGestureState(e, 'pressend'));
|
||||
}
|
||||
if (
|
||||
type === 'pointermove' &&
|
||||
activePointerId !== nativeEvent.pointerId
|
||||
) {
|
||||
return;
|
||||
} else if (type === 'touchmove') {
|
||||
touchEvent = getTouchById(nativeEvent, activePointerId);
|
||||
if (touchEvent === null) {
|
||||
return;
|
||||
}
|
||||
state.touchEvent = touchEvent;
|
||||
if (onPress != null && e.buttons !== 4) {
|
||||
onPress(createGestureState(e, 'press'));
|
||||
}
|
||||
const pressTarget = state.pressTarget;
|
||||
|
||||
if (pressTarget !== null && !targetIsDocument(pressTarget)) {
|
||||
if (
|
||||
pointerType === 'mouse' &&
|
||||
context.isTargetWithinNode(target, pressTarget)
|
||||
) {
|
||||
state.isPressWithinResponderRegion = true;
|
||||
} else {
|
||||
// Calculate the responder region we use for deactivation, as the
|
||||
// element dimensions may have changed since activation.
|
||||
updateIsPressWithinResponderRegion(
|
||||
touchEvent || nativeEvent,
|
||||
context,
|
||||
props,
|
||||
state,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.isPressWithinResponderRegion) {
|
||||
if (isPressed) {
|
||||
const onPressMove = props.onPressMove;
|
||||
|
||||
if (isFunction(onPressMove)) {
|
||||
dispatchEvent(
|
||||
event,
|
||||
onPressMove,
|
||||
context,
|
||||
state,
|
||||
'pressmove',
|
||||
UserBlockingEvent,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
dispatchPressStartEvents(event, context, props, state);
|
||||
}
|
||||
} else {
|
||||
dispatchPressEndEvents(event, context, props, state);
|
||||
}
|
||||
break;
|
||||
updateActive(null);
|
||||
}
|
||||
|
||||
// END
|
||||
case 'pointerup':
|
||||
case 'keyup':
|
||||
case 'mouseup':
|
||||
case 'touchend': {
|
||||
if (isPressed) {
|
||||
const buttons = state.buttons;
|
||||
let isKeyboardEvent = false;
|
||||
let touchEvent;
|
||||
if (
|
||||
type === 'pointerup' &&
|
||||
activePointerId !== nativeEvent.pointerId
|
||||
) {
|
||||
return;
|
||||
} else if (type === 'touchend') {
|
||||
touchEvent = getTouchById(nativeEvent, activePointerId);
|
||||
if (touchEvent === null) {
|
||||
return;
|
||||
}
|
||||
state.touchEvent = touchEvent;
|
||||
target = getTouchTarget(context, touchEvent);
|
||||
} else if (type === 'keyup') {
|
||||
// Ignore unrelated keyboard events
|
||||
if (!isValidKeyboardEvent(nativeEvent)) {
|
||||
return;
|
||||
}
|
||||
isKeyboardEvent = true;
|
||||
removeRootEventTypes(context, state);
|
||||
} else if (buttons === 4) {
|
||||
// Remove the root events here as no 'click' event is dispatched when this 'button' is pressed.
|
||||
removeRootEventTypes(context, state);
|
||||
}
|
||||
|
||||
// Determine whether to call preventDefault on subsequent native events.
|
||||
if (
|
||||
context.isTargetWithinResponder(target) &&
|
||||
context.isTargetWithinHostComponent(target, 'a')
|
||||
) {
|
||||
const {
|
||||
altKey,
|
||||
ctrlKey,
|
||||
metaKey,
|
||||
shiftKey,
|
||||
} = (nativeEvent: MouseEvent);
|
||||
// Check "open in new window/tab" and "open context menu" key modifiers
|
||||
const preventDefault = props.preventDefault;
|
||||
|
||||
if (
|
||||
preventDefault !== false &&
|
||||
!shiftKey &&
|
||||
!metaKey &&
|
||||
!ctrlKey &&
|
||||
!altKey
|
||||
) {
|
||||
state.shouldPreventClick = true;
|
||||
}
|
||||
}
|
||||
|
||||
const pressTarget = state.pressTarget;
|
||||
dispatchPressEndEvents(event, context, props, state);
|
||||
const onPress = props.onPress;
|
||||
|
||||
if (pressTarget !== null && isFunction(onPress)) {
|
||||
if (
|
||||
!isKeyboardEvent &&
|
||||
pressTarget !== null &&
|
||||
!targetIsDocument(pressTarget)
|
||||
) {
|
||||
if (
|
||||
pointerType === 'mouse' &&
|
||||
context.isTargetWithinNode(target, pressTarget)
|
||||
) {
|
||||
state.isPressWithinResponderRegion = true;
|
||||
} else {
|
||||
// If the event target isn't within the press target, check if we're still
|
||||
// within the responder region. The region may have changed if the
|
||||
// element's layout was modified after activation.
|
||||
updateIsPressWithinResponderRegion(
|
||||
touchEvent || nativeEvent,
|
||||
context,
|
||||
props,
|
||||
state,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.isPressWithinResponderRegion && buttons !== 4) {
|
||||
dispatchEvent(
|
||||
event,
|
||||
onPress,
|
||||
context,
|
||||
state,
|
||||
'press',
|
||||
DiscreteEvent,
|
||||
);
|
||||
}
|
||||
}
|
||||
state.touchEvent = null;
|
||||
} else if (type === 'mouseup') {
|
||||
state.ignoreEmulatedMouseEvents = false;
|
||||
},
|
||||
onTapCancel(e) {
|
||||
if (active === 'tap') {
|
||||
if (onPressEnd != null) {
|
||||
onPressEnd(createGestureState(e, 'pressend'));
|
||||
}
|
||||
break;
|
||||
updateActive(null);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
case 'click': {
|
||||
// "keyup" occurs after "click"
|
||||
if (previousPointerType !== 'keyboard') {
|
||||
removeRootEventTypes(context, state);
|
||||
const keyboard = useKeyboard({
|
||||
disabled: disabled || active === 'tap',
|
||||
preventClick: preventDefault !== false,
|
||||
preventKeys: preventDefault !== false ? [' ', 'Enter'] : [],
|
||||
onClick(e) {
|
||||
if (active == null && onPress != null) {
|
||||
onPress(createGestureState(e, 'press'));
|
||||
}
|
||||
},
|
||||
onKeyDown(e) {
|
||||
if (active == null && isValidKey(e)) {
|
||||
updateActive('keyboard');
|
||||
if (onPressStart != null) {
|
||||
onPressStart(createGestureState(e, 'pressstart'));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// CANCEL
|
||||
case 'scroll': {
|
||||
// We ignore incoming scroll events when using mouse events
|
||||
if (previousPointerType === 'mouse') {
|
||||
return;
|
||||
if (onPressChange != null) {
|
||||
onPressChange(true);
|
||||
}
|
||||
const pressTarget = state.pressTarget;
|
||||
const scrollTarget = nativeEvent.target;
|
||||
const doc = context.getActiveDocument();
|
||||
// If the scroll target is the document or if the press target
|
||||
// is inside the scroll target, then this a scroll that should
|
||||
// trigger a cancel.
|
||||
if (
|
||||
pressTarget !== null &&
|
||||
(scrollTarget === doc ||
|
||||
context.isTargetWithinNode(pressTarget, scrollTarget))
|
||||
) {
|
||||
dispatchCancel(event, context, props, state);
|
||||
// stop propagation
|
||||
return false;
|
||||
}
|
||||
},
|
||||
onKeyUp(e) {
|
||||
if (active === 'keyboard' && isValidKey(e)) {
|
||||
if (onPressChange != null) {
|
||||
onPressChange(false);
|
||||
}
|
||||
break;
|
||||
if (onPressEnd != null) {
|
||||
onPressEnd(createGestureState(e, 'pressend'));
|
||||
}
|
||||
if (onPress != null) {
|
||||
onPress(createGestureState(e, 'press'));
|
||||
}
|
||||
updateActive(null);
|
||||
// stop propagation
|
||||
return false;
|
||||
}
|
||||
case 'pointercancel':
|
||||
case 'touchcancel':
|
||||
case 'dragstart': {
|
||||
dispatchCancel(event, context, props, state);
|
||||
}
|
||||
}
|
||||
},
|
||||
onUnmount(
|
||||
context: ReactDOMResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
) {
|
||||
unmountResponder(context, props, state);
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export const PressResponder = React.unstable_createResponder(
|
||||
'Press',
|
||||
pressResponderImpl,
|
||||
);
|
||||
|
||||
export function usePress(
|
||||
props: PressProps,
|
||||
): ReactEventResponderListener<any, any> {
|
||||
return React.unstable_useResponder(PressResponder, props);
|
||||
return [tap, keyboard];
|
||||
}
|
||||
|
||||
864
packages/react-ui/events/src/dom/PressLegacy.js
vendored
Normal file
864
packages/react-ui/events/src/dom/PressLegacy.js
vendored
Normal file
@@ -0,0 +1,864 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {
|
||||
ReactDOMResponderEvent,
|
||||
ReactDOMResponderContext,
|
||||
PointerType,
|
||||
} from 'shared/ReactDOMTypes';
|
||||
import type {
|
||||
EventPriority,
|
||||
ReactEventResponderListener,
|
||||
} from 'shared/ReactTypes';
|
||||
|
||||
import React from 'react';
|
||||
import {DiscreteEvent, UserBlockingEvent} from 'shared/ReactTypes';
|
||||
|
||||
type PressProps = {|
|
||||
disabled: boolean,
|
||||
pressRetentionOffset: {
|
||||
top: number,
|
||||
right: number,
|
||||
bottom: number,
|
||||
left: number,
|
||||
},
|
||||
preventDefault: boolean,
|
||||
onPress: (e: PressEvent) => void,
|
||||
onPressChange: boolean => void,
|
||||
onPressEnd: (e: PressEvent) => void,
|
||||
onPressMove: (e: PressEvent) => void,
|
||||
onPressStart: (e: PressEvent) => void,
|
||||
|};
|
||||
|
||||
type PressState = {
|
||||
activationPosition: null | $ReadOnly<{|
|
||||
x: number,
|
||||
y: number,
|
||||
|}>,
|
||||
addedRootEvents: boolean,
|
||||
buttons: 0 | 1 | 4,
|
||||
isActivePressed: boolean,
|
||||
isActivePressStart: boolean,
|
||||
isPressed: boolean,
|
||||
isPressWithinResponderRegion: boolean,
|
||||
pointerType: PointerType,
|
||||
pressTarget: null | Element | Document,
|
||||
responderRegionOnActivation: null | $ReadOnly<{|
|
||||
bottom: number,
|
||||
left: number,
|
||||
right: number,
|
||||
top: number,
|
||||
|}>,
|
||||
responderRegionOnDeactivation: null | $ReadOnly<{|
|
||||
bottom: number,
|
||||
left: number,
|
||||
right: number,
|
||||
top: number,
|
||||
|}>,
|
||||
ignoreEmulatedMouseEvents: boolean,
|
||||
activePointerId: null | number,
|
||||
shouldPreventClick: boolean,
|
||||
touchEvent: null | Touch,
|
||||
};
|
||||
|
||||
type PressEventType =
|
||||
| 'press'
|
||||
| 'pressmove'
|
||||
| 'pressstart'
|
||||
| 'pressend'
|
||||
| 'presschange';
|
||||
|
||||
type PressEvent = {|
|
||||
altKey: boolean,
|
||||
buttons: 0 | 1 | 4,
|
||||
clientX: null | number,
|
||||
clientY: null | number,
|
||||
ctrlKey: boolean,
|
||||
defaultPrevented: boolean,
|
||||
metaKey: boolean,
|
||||
pageX: null | number,
|
||||
pageY: null | number,
|
||||
pointerType: PointerType,
|
||||
screenX: null | number,
|
||||
screenY: null | number,
|
||||
shiftKey: boolean,
|
||||
target: Element | Document,
|
||||
timeStamp: number,
|
||||
type: PressEventType,
|
||||
x: null | number,
|
||||
y: null | number,
|
||||
|};
|
||||
|
||||
const hasPointerEvents =
|
||||
typeof window !== 'undefined' && window.PointerEvent !== undefined;
|
||||
|
||||
const isMac =
|
||||
typeof window !== 'undefined' && window.navigator != null
|
||||
? /^Mac/.test(window.navigator.platform)
|
||||
: false;
|
||||
|
||||
const DEFAULT_PRESS_RETENTION_OFFSET = {
|
||||
bottom: 20,
|
||||
top: 20,
|
||||
left: 20,
|
||||
right: 20,
|
||||
};
|
||||
|
||||
const targetEventTypes = hasPointerEvents
|
||||
? ['keydown_active', 'pointerdown', 'click_active']
|
||||
: ['keydown_active', 'touchstart', 'mousedown', 'click_active'];
|
||||
|
||||
const rootEventTypes = hasPointerEvents
|
||||
? ['pointerup', 'pointermove', 'pointercancel', 'click', 'keyup', 'scroll']
|
||||
: [
|
||||
'click',
|
||||
'keyup',
|
||||
'scroll',
|
||||
'mousemove',
|
||||
'touchmove',
|
||||
'touchcancel',
|
||||
// Used as a 'cancel' signal for mouse interactions
|
||||
'dragstart',
|
||||
'mouseup',
|
||||
'touchend',
|
||||
];
|
||||
|
||||
function isFunction(obj): boolean {
|
||||
return typeof obj === 'function';
|
||||
}
|
||||
|
||||
function createPressEvent(
|
||||
context: ReactDOMResponderContext,
|
||||
type: PressEventType,
|
||||
target: Element | Document,
|
||||
pointerType: PointerType,
|
||||
event: ?ReactDOMResponderEvent,
|
||||
touchEvent: null | Touch,
|
||||
defaultPrevented: boolean,
|
||||
state: PressState,
|
||||
): PressEvent {
|
||||
const timeStamp = context.getTimeStamp();
|
||||
let clientX = null;
|
||||
let clientY = null;
|
||||
let pageX = null;
|
||||
let pageY = null;
|
||||
let screenX = null;
|
||||
let screenY = null;
|
||||
let altKey = false;
|
||||
let ctrlKey = false;
|
||||
let metaKey = false;
|
||||
let shiftKey = false;
|
||||
|
||||
if (event) {
|
||||
const nativeEvent = (event.nativeEvent: any);
|
||||
({altKey, ctrlKey, metaKey, shiftKey} = nativeEvent);
|
||||
// Only check for one property, checking for all of them is costly. We can assume
|
||||
// if clientX exists, so do the rest.
|
||||
let eventObject;
|
||||
eventObject = (touchEvent: any) || (nativeEvent: any);
|
||||
if (eventObject) {
|
||||
({clientX, clientY, pageX, pageY, screenX, screenY} = eventObject);
|
||||
}
|
||||
}
|
||||
return {
|
||||
altKey,
|
||||
buttons: state.buttons,
|
||||
clientX,
|
||||
clientY,
|
||||
ctrlKey,
|
||||
defaultPrevented,
|
||||
metaKey,
|
||||
pageX,
|
||||
pageY,
|
||||
pointerType,
|
||||
screenX,
|
||||
screenY,
|
||||
shiftKey,
|
||||
target,
|
||||
timeStamp,
|
||||
type,
|
||||
x: clientX,
|
||||
y: clientY,
|
||||
};
|
||||
}
|
||||
|
||||
function dispatchEvent(
|
||||
event: ?ReactDOMResponderEvent,
|
||||
listener: any => void,
|
||||
context: ReactDOMResponderContext,
|
||||
state: PressState,
|
||||
name: PressEventType,
|
||||
eventPriority: EventPriority,
|
||||
): void {
|
||||
const target = ((state.pressTarget: any): Element | Document);
|
||||
const pointerType = state.pointerType;
|
||||
const defaultPrevented =
|
||||
(event != null && event.nativeEvent.defaultPrevented === true) ||
|
||||
(name === 'press' && state.shouldPreventClick);
|
||||
const touchEvent = state.touchEvent;
|
||||
const syntheticEvent = createPressEvent(
|
||||
context,
|
||||
name,
|
||||
target,
|
||||
pointerType,
|
||||
event,
|
||||
touchEvent,
|
||||
defaultPrevented,
|
||||
state,
|
||||
);
|
||||
context.dispatchEvent(syntheticEvent, listener, eventPriority);
|
||||
}
|
||||
|
||||
function dispatchPressChangeEvent(
|
||||
context: ReactDOMResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
const onPressChange = props.onPressChange;
|
||||
if (isFunction(onPressChange)) {
|
||||
const bool = state.isActivePressed;
|
||||
context.dispatchEvent(bool, onPressChange, DiscreteEvent);
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchPressStartEvents(
|
||||
event: ReactDOMResponderEvent,
|
||||
context: ReactDOMResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
state.isPressed = true;
|
||||
|
||||
if (!state.isActivePressStart) {
|
||||
state.isActivePressStart = true;
|
||||
const nativeEvent: any = event.nativeEvent;
|
||||
const {clientX: x, clientY: y} = state.touchEvent || nativeEvent;
|
||||
const wasActivePressed = state.isActivePressed;
|
||||
state.isActivePressed = true;
|
||||
if (x !== undefined && y !== undefined) {
|
||||
state.activationPosition = {x, y};
|
||||
}
|
||||
const onPressStart = props.onPressStart;
|
||||
|
||||
if (isFunction(onPressStart)) {
|
||||
dispatchEvent(
|
||||
event,
|
||||
onPressStart,
|
||||
context,
|
||||
state,
|
||||
'pressstart',
|
||||
DiscreteEvent,
|
||||
);
|
||||
}
|
||||
if (!wasActivePressed) {
|
||||
dispatchPressChangeEvent(context, props, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchPressEndEvents(
|
||||
event: ?ReactDOMResponderEvent,
|
||||
context: ReactDOMResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
state.isActivePressStart = false;
|
||||
state.isPressed = false;
|
||||
|
||||
if (state.isActivePressed) {
|
||||
state.isActivePressed = false;
|
||||
const onPressEnd = props.onPressEnd;
|
||||
|
||||
if (isFunction(onPressEnd)) {
|
||||
dispatchEvent(
|
||||
event,
|
||||
onPressEnd,
|
||||
context,
|
||||
state,
|
||||
'pressend',
|
||||
DiscreteEvent,
|
||||
);
|
||||
}
|
||||
dispatchPressChangeEvent(context, props, state);
|
||||
}
|
||||
|
||||
state.responderRegionOnDeactivation = null;
|
||||
}
|
||||
|
||||
function dispatchCancel(
|
||||
event: ReactDOMResponderEvent,
|
||||
context: ReactDOMResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
state.touchEvent = null;
|
||||
if (state.isPressed) {
|
||||
state.ignoreEmulatedMouseEvents = false;
|
||||
dispatchPressEndEvents(event, context, props, state);
|
||||
}
|
||||
removeRootEventTypes(context, state);
|
||||
}
|
||||
|
||||
function isValidKeyboardEvent(nativeEvent: Object): boolean {
|
||||
const {key, target} = nativeEvent;
|
||||
const {tagName, isContentEditable} = target;
|
||||
// Accessibility for keyboards. Space and Enter only.
|
||||
// "Spacebar" is for IE 11
|
||||
return (
|
||||
(key === 'Enter' || key === ' ' || key === 'Spacebar') &&
|
||||
(tagName !== 'INPUT' &&
|
||||
tagName !== 'TEXTAREA' &&
|
||||
isContentEditable !== true)
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: account for touch hit slop
|
||||
function calculateResponderRegion(
|
||||
context: ReactDOMResponderContext,
|
||||
target: Element,
|
||||
props: PressProps,
|
||||
) {
|
||||
const pressRetentionOffset = context.objectAssign(
|
||||
{},
|
||||
DEFAULT_PRESS_RETENTION_OFFSET,
|
||||
props.pressRetentionOffset,
|
||||
);
|
||||
|
||||
let {left, right, bottom, top} = target.getBoundingClientRect();
|
||||
|
||||
if (pressRetentionOffset) {
|
||||
if (pressRetentionOffset.bottom != null) {
|
||||
bottom += pressRetentionOffset.bottom;
|
||||
}
|
||||
if (pressRetentionOffset.left != null) {
|
||||
left -= pressRetentionOffset.left;
|
||||
}
|
||||
if (pressRetentionOffset.right != null) {
|
||||
right += pressRetentionOffset.right;
|
||||
}
|
||||
if (pressRetentionOffset.top != null) {
|
||||
top -= pressRetentionOffset.top;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
bottom,
|
||||
top,
|
||||
left,
|
||||
right,
|
||||
};
|
||||
}
|
||||
|
||||
function getTouchFromPressEvent(nativeEvent: TouchEvent): null | Touch {
|
||||
const targetTouches = nativeEvent.targetTouches;
|
||||
if (targetTouches.length > 0) {
|
||||
return targetTouches[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function unmountResponder(
|
||||
context: ReactDOMResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
if (state.isPressed) {
|
||||
removeRootEventTypes(context, state);
|
||||
dispatchPressEndEvents(null, context, props, state);
|
||||
}
|
||||
}
|
||||
|
||||
function addRootEventTypes(
|
||||
context: ReactDOMResponderContext,
|
||||
state: PressState,
|
||||
): void {
|
||||
if (!state.addedRootEvents) {
|
||||
state.addedRootEvents = true;
|
||||
context.addRootEventTypes(rootEventTypes);
|
||||
}
|
||||
}
|
||||
|
||||
function removeRootEventTypes(
|
||||
context: ReactDOMResponderContext,
|
||||
state: PressState,
|
||||
): void {
|
||||
if (state.addedRootEvents) {
|
||||
state.addedRootEvents = false;
|
||||
context.removeRootEventTypes(rootEventTypes);
|
||||
}
|
||||
}
|
||||
|
||||
function getTouchById(
|
||||
nativeEvent: TouchEvent,
|
||||
pointerId: null | number,
|
||||
): null | Touch {
|
||||
const changedTouches = nativeEvent.changedTouches;
|
||||
for (let i = 0; i < changedTouches.length; i++) {
|
||||
const touch = changedTouches[i];
|
||||
if (touch.identifier === pointerId) {
|
||||
return touch;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getTouchTarget(context: ReactDOMResponderContext, touchEvent: Touch) {
|
||||
const doc = context.getActiveDocument();
|
||||
return doc.elementFromPoint(touchEvent.clientX, touchEvent.clientY);
|
||||
}
|
||||
|
||||
function updateIsPressWithinResponderRegion(
|
||||
nativeEventOrTouchEvent: Event | Touch,
|
||||
context: ReactDOMResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
// Calculate the responder region we use for deactivation if not
|
||||
// already done during move event.
|
||||
if (state.responderRegionOnDeactivation == null) {
|
||||
state.responderRegionOnDeactivation = calculateResponderRegion(
|
||||
context,
|
||||
((state.pressTarget: any): Element),
|
||||
props,
|
||||
);
|
||||
}
|
||||
const {responderRegionOnActivation, responderRegionOnDeactivation} = state;
|
||||
let left, top, right, bottom;
|
||||
|
||||
if (responderRegionOnActivation != null) {
|
||||
left = responderRegionOnActivation.left;
|
||||
top = responderRegionOnActivation.top;
|
||||
right = responderRegionOnActivation.right;
|
||||
bottom = responderRegionOnActivation.bottom;
|
||||
|
||||
if (responderRegionOnDeactivation != null) {
|
||||
left = Math.min(left, responderRegionOnDeactivation.left);
|
||||
top = Math.min(top, responderRegionOnDeactivation.top);
|
||||
right = Math.max(right, responderRegionOnDeactivation.right);
|
||||
bottom = Math.max(bottom, responderRegionOnDeactivation.bottom);
|
||||
}
|
||||
}
|
||||
const {clientX: x, clientY: y} = (nativeEventOrTouchEvent: any);
|
||||
|
||||
state.isPressWithinResponderRegion =
|
||||
left != null &&
|
||||
right != null &&
|
||||
top != null &&
|
||||
bottom != null &&
|
||||
x !== null &&
|
||||
y !== null &&
|
||||
(x >= left && x <= right && y >= top && y <= bottom);
|
||||
}
|
||||
|
||||
// After some investigation work, screen reader virtual
|
||||
// clicks (NVDA, Jaws, VoiceOver) do not have co-ords associated with the click
|
||||
// event and "detail" is always 0 (where normal clicks are > 0)
|
||||
function isScreenReaderVirtualClick(nativeEvent): boolean {
|
||||
return (
|
||||
nativeEvent.detail === 0 &&
|
||||
nativeEvent.screenX === 0 &&
|
||||
nativeEvent.screenY === 0 &&
|
||||
nativeEvent.clientX === 0 &&
|
||||
nativeEvent.clientY === 0
|
||||
);
|
||||
}
|
||||
|
||||
function targetIsDocument(target: null | Node): boolean {
|
||||
// When target is null, it is the root
|
||||
return target === null || target.nodeType === 9;
|
||||
}
|
||||
|
||||
const pressResponderImpl = {
|
||||
targetEventTypes,
|
||||
getInitialState(): PressState {
|
||||
return {
|
||||
activationPosition: null,
|
||||
addedRootEvents: false,
|
||||
buttons: 0,
|
||||
isActivePressed: false,
|
||||
isActivePressStart: false,
|
||||
isPressed: false,
|
||||
isPressWithinResponderRegion: true,
|
||||
pointerType: '',
|
||||
pressTarget: null,
|
||||
responderRegionOnActivation: null,
|
||||
responderRegionOnDeactivation: null,
|
||||
ignoreEmulatedMouseEvents: false,
|
||||
activePointerId: null,
|
||||
shouldPreventClick: false,
|
||||
touchEvent: null,
|
||||
};
|
||||
},
|
||||
onEvent(
|
||||
event: ReactDOMResponderEvent,
|
||||
context: ReactDOMResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
const {pointerType, type} = event;
|
||||
|
||||
if (props.disabled) {
|
||||
removeRootEventTypes(context, state);
|
||||
dispatchPressEndEvents(event, context, props, state);
|
||||
state.ignoreEmulatedMouseEvents = false;
|
||||
return;
|
||||
}
|
||||
const nativeEvent: any = event.nativeEvent;
|
||||
const isPressed = state.isPressed;
|
||||
|
||||
switch (type) {
|
||||
// START
|
||||
case 'pointerdown':
|
||||
case 'keydown':
|
||||
case 'mousedown':
|
||||
case 'touchstart': {
|
||||
if (!isPressed) {
|
||||
const isTouchEvent = type === 'touchstart';
|
||||
const isPointerEvent = type === 'pointerdown';
|
||||
const isKeyboardEvent = pointerType === 'keyboard';
|
||||
const isMouseEvent = pointerType === 'mouse';
|
||||
|
||||
// Ignore emulated mouse events
|
||||
if (type === 'mousedown' && state.ignoreEmulatedMouseEvents) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.shouldPreventClick = false;
|
||||
if (isTouchEvent) {
|
||||
state.ignoreEmulatedMouseEvents = true;
|
||||
} else if (isKeyboardEvent) {
|
||||
// Ignore unrelated key events
|
||||
if (isValidKeyboardEvent(nativeEvent)) {
|
||||
const {
|
||||
altKey,
|
||||
ctrlKey,
|
||||
metaKey,
|
||||
shiftKey,
|
||||
} = (nativeEvent: MouseEvent);
|
||||
if (nativeEvent.key === ' ') {
|
||||
nativeEvent.preventDefault();
|
||||
} else if (
|
||||
props.preventDefault !== false &&
|
||||
!shiftKey &&
|
||||
!metaKey &&
|
||||
!ctrlKey &&
|
||||
!altKey
|
||||
) {
|
||||
state.shouldPreventClick = true;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// We set these here, before the button check so we have this
|
||||
// data around for handling of the context menu
|
||||
state.pointerType = pointerType;
|
||||
const pressTarget = (state.pressTarget = context.getResponderNode());
|
||||
if (isPointerEvent) {
|
||||
state.activePointerId = nativeEvent.pointerId;
|
||||
} else if (isTouchEvent) {
|
||||
const touchEvent = getTouchFromPressEvent(nativeEvent);
|
||||
if (touchEvent === null) {
|
||||
return;
|
||||
}
|
||||
state.touchEvent = touchEvent;
|
||||
state.activePointerId = touchEvent.identifier;
|
||||
}
|
||||
|
||||
// Ignore any device buttons except primary/middle and touch/pen contact.
|
||||
// Additionally we ignore primary-button + ctrl-key with Macs as that
|
||||
// acts like right-click and opens the contextmenu.
|
||||
if (
|
||||
nativeEvent.buttons === 2 ||
|
||||
nativeEvent.buttons > 4 ||
|
||||
(isMac && isMouseEvent && nativeEvent.ctrlKey)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// Exclude document targets
|
||||
if (!targetIsDocument(pressTarget)) {
|
||||
state.responderRegionOnActivation = calculateResponderRegion(
|
||||
context,
|
||||
((pressTarget: any): Element),
|
||||
props,
|
||||
);
|
||||
}
|
||||
state.responderRegionOnDeactivation = null;
|
||||
state.isPressWithinResponderRegion = true;
|
||||
state.buttons = nativeEvent.buttons;
|
||||
dispatchPressStartEvents(event, context, props, state);
|
||||
addRootEventTypes(context, state);
|
||||
} else {
|
||||
// Prevent spacebar press from scrolling the window
|
||||
if (isValidKeyboardEvent(nativeEvent) && nativeEvent.key === ' ') {
|
||||
nativeEvent.preventDefault();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'click': {
|
||||
if (state.shouldPreventClick) {
|
||||
nativeEvent.preventDefault();
|
||||
}
|
||||
const onPress = props.onPress;
|
||||
|
||||
if (isFunction(onPress) && isScreenReaderVirtualClick(nativeEvent)) {
|
||||
state.pointerType = 'keyboard';
|
||||
state.pressTarget = context.getResponderNode();
|
||||
const preventDefault = props.preventDefault;
|
||||
|
||||
if (preventDefault !== false) {
|
||||
nativeEvent.preventDefault();
|
||||
}
|
||||
dispatchEvent(event, onPress, context, state, 'press', DiscreteEvent);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
onRootEvent(
|
||||
event: ReactDOMResponderEvent,
|
||||
context: ReactDOMResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
let {pointerType, target, type} = event;
|
||||
|
||||
const nativeEvent: any = event.nativeEvent;
|
||||
const isPressed = state.isPressed;
|
||||
const activePointerId = state.activePointerId;
|
||||
const previousPointerType = state.pointerType;
|
||||
|
||||
switch (type) {
|
||||
// MOVE
|
||||
case 'pointermove':
|
||||
case 'mousemove':
|
||||
case 'touchmove': {
|
||||
let touchEvent;
|
||||
// Ignore emulated events (pointermove will dispatch touch and mouse events)
|
||||
// Ignore pointermove events during a keyboard press.
|
||||
if (previousPointerType !== pointerType) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
type === 'pointermove' &&
|
||||
activePointerId !== nativeEvent.pointerId
|
||||
) {
|
||||
return;
|
||||
} else if (type === 'touchmove') {
|
||||
touchEvent = getTouchById(nativeEvent, activePointerId);
|
||||
if (touchEvent === null) {
|
||||
return;
|
||||
}
|
||||
state.touchEvent = touchEvent;
|
||||
}
|
||||
const pressTarget = state.pressTarget;
|
||||
|
||||
if (pressTarget !== null && !targetIsDocument(pressTarget)) {
|
||||
if (
|
||||
pointerType === 'mouse' &&
|
||||
context.isTargetWithinNode(target, pressTarget)
|
||||
) {
|
||||
state.isPressWithinResponderRegion = true;
|
||||
} else {
|
||||
// Calculate the responder region we use for deactivation, as the
|
||||
// element dimensions may have changed since activation.
|
||||
updateIsPressWithinResponderRegion(
|
||||
touchEvent || nativeEvent,
|
||||
context,
|
||||
props,
|
||||
state,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.isPressWithinResponderRegion) {
|
||||
if (isPressed) {
|
||||
const onPressMove = props.onPressMove;
|
||||
|
||||
if (isFunction(onPressMove)) {
|
||||
dispatchEvent(
|
||||
event,
|
||||
onPressMove,
|
||||
context,
|
||||
state,
|
||||
'pressmove',
|
||||
UserBlockingEvent,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
dispatchPressStartEvents(event, context, props, state);
|
||||
}
|
||||
} else {
|
||||
dispatchPressEndEvents(event, context, props, state);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// END
|
||||
case 'pointerup':
|
||||
case 'keyup':
|
||||
case 'mouseup':
|
||||
case 'touchend': {
|
||||
if (isPressed) {
|
||||
const buttons = state.buttons;
|
||||
let isKeyboardEvent = false;
|
||||
let touchEvent;
|
||||
if (
|
||||
type === 'pointerup' &&
|
||||
activePointerId !== nativeEvent.pointerId
|
||||
) {
|
||||
return;
|
||||
} else if (type === 'touchend') {
|
||||
touchEvent = getTouchById(nativeEvent, activePointerId);
|
||||
if (touchEvent === null) {
|
||||
return;
|
||||
}
|
||||
state.touchEvent = touchEvent;
|
||||
target = getTouchTarget(context, touchEvent);
|
||||
} else if (type === 'keyup') {
|
||||
// Ignore unrelated keyboard events
|
||||
if (!isValidKeyboardEvent(nativeEvent)) {
|
||||
return;
|
||||
}
|
||||
isKeyboardEvent = true;
|
||||
removeRootEventTypes(context, state);
|
||||
} else if (buttons === 4) {
|
||||
// Remove the root events here as no 'click' event is dispatched when this 'button' is pressed.
|
||||
removeRootEventTypes(context, state);
|
||||
}
|
||||
|
||||
// Determine whether to call preventDefault on subsequent native events.
|
||||
if (
|
||||
context.isTargetWithinResponder(target) &&
|
||||
context.isTargetWithinHostComponent(target, 'a')
|
||||
) {
|
||||
const {
|
||||
altKey,
|
||||
ctrlKey,
|
||||
metaKey,
|
||||
shiftKey,
|
||||
} = (nativeEvent: MouseEvent);
|
||||
// Check "open in new window/tab" and "open context menu" key modifiers
|
||||
const preventDefault = props.preventDefault;
|
||||
|
||||
if (
|
||||
preventDefault !== false &&
|
||||
!shiftKey &&
|
||||
!metaKey &&
|
||||
!ctrlKey &&
|
||||
!altKey
|
||||
) {
|
||||
state.shouldPreventClick = true;
|
||||
}
|
||||
}
|
||||
|
||||
const pressTarget = state.pressTarget;
|
||||
dispatchPressEndEvents(event, context, props, state);
|
||||
const onPress = props.onPress;
|
||||
|
||||
if (pressTarget !== null && isFunction(onPress)) {
|
||||
if (
|
||||
!isKeyboardEvent &&
|
||||
pressTarget !== null &&
|
||||
!targetIsDocument(pressTarget)
|
||||
) {
|
||||
if (
|
||||
pointerType === 'mouse' &&
|
||||
context.isTargetWithinNode(target, pressTarget)
|
||||
) {
|
||||
state.isPressWithinResponderRegion = true;
|
||||
} else {
|
||||
// If the event target isn't within the press target, check if we're still
|
||||
// within the responder region. The region may have changed if the
|
||||
// element's layout was modified after activation.
|
||||
updateIsPressWithinResponderRegion(
|
||||
touchEvent || nativeEvent,
|
||||
context,
|
||||
props,
|
||||
state,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.isPressWithinResponderRegion && buttons !== 4) {
|
||||
dispatchEvent(
|
||||
event,
|
||||
onPress,
|
||||
context,
|
||||
state,
|
||||
'press',
|
||||
DiscreteEvent,
|
||||
);
|
||||
}
|
||||
}
|
||||
state.touchEvent = null;
|
||||
} else if (type === 'mouseup') {
|
||||
state.ignoreEmulatedMouseEvents = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'click': {
|
||||
// "keyup" occurs after "click"
|
||||
if (previousPointerType !== 'keyboard') {
|
||||
removeRootEventTypes(context, state);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// CANCEL
|
||||
case 'scroll': {
|
||||
// We ignore incoming scroll events when using mouse events
|
||||
if (previousPointerType === 'mouse') {
|
||||
return;
|
||||
}
|
||||
const pressTarget = state.pressTarget;
|
||||
const scrollTarget = nativeEvent.target;
|
||||
const doc = context.getActiveDocument();
|
||||
// If the scroll target is the document or if the press target
|
||||
// is inside the scroll target, then this a scroll that should
|
||||
// trigger a cancel.
|
||||
if (
|
||||
pressTarget !== null &&
|
||||
(scrollTarget === doc ||
|
||||
context.isTargetWithinNode(pressTarget, scrollTarget))
|
||||
) {
|
||||
dispatchCancel(event, context, props, state);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'pointercancel':
|
||||
case 'touchcancel':
|
||||
case 'dragstart': {
|
||||
dispatchCancel(event, context, props, state);
|
||||
}
|
||||
}
|
||||
},
|
||||
onUnmount(
|
||||
context: ReactDOMResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
) {
|
||||
unmountResponder(context, props, state);
|
||||
},
|
||||
};
|
||||
|
||||
export const PressResponder = React.unstable_createResponder(
|
||||
'Press',
|
||||
pressResponderImpl,
|
||||
);
|
||||
|
||||
export function usePress(
|
||||
props: PressProps,
|
||||
): ReactEventResponderListener<any, any> {
|
||||
return React.unstable_useResponder(PressResponder, props);
|
||||
}
|
||||
65
packages/react-ui/events/src/dom/Tap.js
vendored
65
packages/react-ui/events/src/dom/Tap.js
vendored
@@ -36,26 +36,6 @@ type TapProps = $ReadOnly<{|
|
||||
onTapUpdate?: (e: TapEvent) => void,
|
||||
|}>;
|
||||
|
||||
type TapState = {|
|
||||
activePointerId: null | number,
|
||||
buttons: 0 | 1 | 4,
|
||||
gestureState: TapGestureState,
|
||||
ignoreEmulatedEvents: boolean,
|
||||
initialPosition: {|x: number, y: number|},
|
||||
isActive: boolean,
|
||||
pointerType: PointerType,
|
||||
responderTarget: null | Element,
|
||||
rootEvents: null | Array<string>,
|
||||
shouldPreventClick: boolean,
|
||||
|};
|
||||
|
||||
type TapEventType =
|
||||
| 'tap-cancel'
|
||||
| 'tap-change'
|
||||
| 'tap-end'
|
||||
| 'tap-start'
|
||||
| 'tap-update';
|
||||
|
||||
type TapGestureState = {|
|
||||
altKey: boolean,
|
||||
buttons: 0 | 1 | 4,
|
||||
@@ -80,10 +60,31 @@ type TapGestureState = {|
|
||||
y: number,
|
||||
|};
|
||||
|
||||
type TapEvent = $ReadOnly<{|
|
||||
type TapState = {|
|
||||
activePointerId: null | number,
|
||||
buttons: 0 | 1 | 4,
|
||||
gestureState: TapGestureState,
|
||||
ignoreEmulatedEvents: boolean,
|
||||
initialPosition: {|x: number, y: number|},
|
||||
isActive: boolean,
|
||||
pointerType: PointerType,
|
||||
responderTarget: null | Element,
|
||||
rootEvents: null | Array<string>,
|
||||
shouldPreventClick: boolean,
|
||||
|};
|
||||
|
||||
type TapEventType =
|
||||
| 'tap:cancel'
|
||||
| 'tap:change'
|
||||
| 'tap:end'
|
||||
| 'tap:start'
|
||||
| 'tap:update';
|
||||
|
||||
type TapEvent = {|
|
||||
...TapGestureState,
|
||||
defaultPrevented: boolean,
|
||||
type: TapEventType,
|
||||
|}>;
|
||||
|};
|
||||
|
||||
/**
|
||||
* Native event dependencies
|
||||
@@ -380,6 +381,7 @@ function dispatchStart(
|
||||
const payload = context.objectAssign({}, state.gestureState, {type});
|
||||
dispatchDiscreteEvent(context, payload, onTapStart);
|
||||
}
|
||||
dispatchChange(context, props, state);
|
||||
}
|
||||
|
||||
function dispatchChange(
|
||||
@@ -414,8 +416,13 @@ function dispatchEnd(
|
||||
): void {
|
||||
const type = 'tap:end';
|
||||
const onTapEnd = props.onTapEnd;
|
||||
dispatchChange(context, props, state);
|
||||
if (onTapEnd != null) {
|
||||
const payload = context.objectAssign({}, state.gestureState, {type});
|
||||
const defaultPrevented = state.shouldPreventClick === true;
|
||||
const payload = context.objectAssign({}, state.gestureState, {
|
||||
defaultPrevented,
|
||||
type,
|
||||
});
|
||||
dispatchDiscreteEvent(context, payload, onTapEnd);
|
||||
}
|
||||
}
|
||||
@@ -427,6 +434,7 @@ function dispatchCancel(
|
||||
): void {
|
||||
const type = 'tap:cancel';
|
||||
const onTapCancel = props.onTapCancel;
|
||||
dispatchChange(context, props, state);
|
||||
if (onTapCancel != null) {
|
||||
const payload = context.objectAssign({}, state.gestureState, {type});
|
||||
dispatchDiscreteEvent(context, payload, onTapCancel);
|
||||
@@ -451,8 +459,8 @@ const responderImpl = {
|
||||
if (props.disabled) {
|
||||
removeRootEventTypes(context, state);
|
||||
if (state.isActive) {
|
||||
dispatchCancel(context, props, state);
|
||||
state.isActive = false;
|
||||
dispatchCancel(context, props, state);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -498,7 +506,6 @@ const responderImpl = {
|
||||
state.initialPosition.y = gestureState.y;
|
||||
|
||||
dispatchStart(context, props, state);
|
||||
dispatchChange(context, props, state);
|
||||
addRootEventTypes(rootEventTypes, context, state);
|
||||
|
||||
if (!hasPointerEvents) {
|
||||
@@ -522,7 +529,7 @@ const responderImpl = {
|
||||
const hitTarget = getHitTarget(event, context, state);
|
||||
|
||||
switch (eventType) {
|
||||
// MOVE
|
||||
// UPDATE
|
||||
case 'pointermove':
|
||||
case 'mousemove':
|
||||
case 'touchmove': {
|
||||
@@ -557,7 +564,6 @@ const responderImpl = {
|
||||
dispatchUpdate(context, props, state);
|
||||
} else {
|
||||
state.isActive = false;
|
||||
dispatchChange(context, props, state);
|
||||
dispatchCancel(context, props, state);
|
||||
}
|
||||
}
|
||||
@@ -578,7 +584,6 @@ const responderImpl = {
|
||||
state.gestureState = createGestureState(context, props, state, event);
|
||||
|
||||
state.isActive = false;
|
||||
dispatchChange(context, props, state);
|
||||
if (context.isTargetWithinResponder(hitTarget)) {
|
||||
// Determine whether to call preventDefault on subsequent native events.
|
||||
if (hasModifierKey(event)) {
|
||||
@@ -606,7 +611,6 @@ const responderImpl = {
|
||||
if (state.isActive && isActivePointer(event, state)) {
|
||||
state.gestureState = createGestureState(context, props, state, event);
|
||||
state.isActive = false;
|
||||
dispatchChange(context, props, state);
|
||||
dispatchCancel(context, props, state);
|
||||
}
|
||||
break;
|
||||
@@ -625,7 +629,6 @@ const responderImpl = {
|
||||
) {
|
||||
state.gestureState = createGestureState(context, props, state, event);
|
||||
state.isActive = false;
|
||||
dispatchChange(context, props, state);
|
||||
dispatchCancel(context, props, state);
|
||||
}
|
||||
break;
|
||||
@@ -647,8 +650,8 @@ const responderImpl = {
|
||||
): void {
|
||||
removeRootEventTypes(context, state);
|
||||
if (state.isActive) {
|
||||
dispatchCancel(context, props, state);
|
||||
state.isActive = false;
|
||||
dispatchCancel(context, props, state);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -158,7 +158,7 @@ describe('Keyboard responder', () => {
|
||||
});
|
||||
|
||||
// e.g, "Enter" on link
|
||||
test('keyboard click is between key events', () => {
|
||||
test('click is between key events', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
target.keydown({key: 'Enter'});
|
||||
target.keyup({key: 'Enter'});
|
||||
@@ -168,7 +168,7 @@ describe('Keyboard responder', () => {
|
||||
expect.objectContaining({
|
||||
altKey: false,
|
||||
ctrlKey: false,
|
||||
defaultPrevented: false,
|
||||
defaultPrevented: true,
|
||||
metaKey: false,
|
||||
pointerType: 'keyboard',
|
||||
shiftKey: false,
|
||||
@@ -180,7 +180,7 @@ describe('Keyboard responder', () => {
|
||||
});
|
||||
|
||||
// e.g., "Spacebar" on button
|
||||
test('keyboard click is after key events', () => {
|
||||
test('click is after key events', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
target.keydown({key: 'Enter'});
|
||||
target.keyup({key: 'Enter'});
|
||||
@@ -190,7 +190,27 @@ describe('Keyboard responder', () => {
|
||||
expect.objectContaining({
|
||||
altKey: false,
|
||||
ctrlKey: false,
|
||||
defaultPrevented: false,
|
||||
defaultPrevented: true,
|
||||
metaKey: false,
|
||||
pointerType: 'keyboard',
|
||||
shiftKey: false,
|
||||
target: target.node,
|
||||
timeStamp: expect.any(Number),
|
||||
type: 'keyboard:click',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
// e.g, generated by a screen-reader
|
||||
test('click is orphan', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
target.virtualclick();
|
||||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
expect(onClick).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
altKey: false,
|
||||
ctrlKey: false,
|
||||
defaultPrevented: true,
|
||||
metaKey: false,
|
||||
pointerType: 'keyboard',
|
||||
shiftKey: false,
|
||||
@@ -326,6 +346,51 @@ describe('Keyboard responder', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('preventClick', () => {
|
||||
function render(props) {
|
||||
const ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useKeyboard(props);
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
return ref;
|
||||
}
|
||||
|
||||
test('prevents native click by default', () => {
|
||||
const onClick = jest.fn();
|
||||
const preventDefault = jest.fn();
|
||||
const ref = render({onClick});
|
||||
|
||||
const target = createEventTarget(ref.current);
|
||||
target.virtualclick({preventDefault});
|
||||
|
||||
expect(preventDefault).toBeCalled();
|
||||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
expect(onClick).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
defaultPrevented: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('allows native behaviour if false', () => {
|
||||
const onClick = jest.fn();
|
||||
const preventDefault = jest.fn();
|
||||
const ref = render({onClick, preventClick: false});
|
||||
|
||||
const target = createEventTarget(ref.current);
|
||||
target.virtualclick({preventDefault});
|
||||
expect(preventDefault).not.toBeCalled();
|
||||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
expect(onClick).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
defaultPrevented: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('preventKeys', () => {
|
||||
function render(props) {
|
||||
const ref = React.createRef();
|
||||
@@ -347,9 +412,8 @@ describe('Keyboard responder', () => {
|
||||
target.keydown({key: 'Tab', preventDefault});
|
||||
target.virtualclick({preventDefault: preventDefaultClick});
|
||||
|
||||
expect(onKeyDown).toHaveBeenCalledTimes(1);
|
||||
expect(preventDefault).toBeCalled();
|
||||
expect(preventDefaultClick).toBeCalled();
|
||||
expect(onKeyDown).toHaveBeenCalledTimes(1);
|
||||
expect(onKeyDown).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
defaultPrevented: true,
|
||||
@@ -362,19 +426,12 @@ describe('Keyboard responder', () => {
|
||||
test('key config matches (modifier keys)', () => {
|
||||
const onKeyDown = jest.fn();
|
||||
const preventDefault = jest.fn();
|
||||
const preventDefaultClick = jest.fn();
|
||||
const ref = render({onKeyDown, preventKeys: [['Tab', {shiftKey: true}]]});
|
||||
|
||||
const target = createEventTarget(ref.current);
|
||||
target.keydown({key: 'Tab', preventDefault, shiftKey: true});
|
||||
target.virtualclick({
|
||||
preventDefault: preventDefaultClick,
|
||||
shiftKey: true,
|
||||
});
|
||||
|
||||
expect(onKeyDown).toHaveBeenCalledTimes(1);
|
||||
expect(preventDefault).toBeCalled();
|
||||
expect(preventDefaultClick).toBeCalled();
|
||||
expect(onKeyDown).toHaveBeenCalledTimes(1);
|
||||
expect(onKeyDown).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
defaultPrevented: true,
|
||||
@@ -388,19 +445,12 @@ describe('Keyboard responder', () => {
|
||||
test('key config does not match (modifier keys)', () => {
|
||||
const onKeyDown = jest.fn();
|
||||
const preventDefault = jest.fn();
|
||||
const preventDefaultClick = jest.fn();
|
||||
const ref = render({onKeyDown, preventKeys: [['Tab', {shiftKey: true}]]});
|
||||
|
||||
const target = createEventTarget(ref.current);
|
||||
target.keydown({key: 'Tab', preventDefault, shiftKey: false});
|
||||
target.virtualclick({
|
||||
preventDefault: preventDefaultClick,
|
||||
shiftKey: false,
|
||||
});
|
||||
|
||||
expect(onKeyDown).toHaveBeenCalledTimes(1);
|
||||
expect(preventDefault).not.toBeCalled();
|
||||
expect(preventDefaultClick).not.toBeCalled();
|
||||
expect(onKeyDown).toHaveBeenCalledTimes(1);
|
||||
expect(onKeyDown).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
defaultPrevented: false,
|
||||
|
||||
@@ -35,7 +35,7 @@ describe('mixing responders with the heritage event system', () => {
|
||||
});
|
||||
|
||||
it('should properly only flush sync once when the event systems are mixed', () => {
|
||||
const usePress = require('react-ui/events/press').usePress;
|
||||
const useTap = require('react-ui/events/tap').useTap;
|
||||
const ref = React.createRef();
|
||||
let renderCounts = 0;
|
||||
|
||||
@@ -43,12 +43,12 @@ describe('mixing responders with the heritage event system', () => {
|
||||
const [, updateCounter] = React.useState(0);
|
||||
renderCounts++;
|
||||
|
||||
function handlePress() {
|
||||
function handleTap() {
|
||||
updateCounter(count => count + 1);
|
||||
}
|
||||
|
||||
const listener = usePress({
|
||||
onPress: handlePress,
|
||||
const listener = useTap({
|
||||
onTapEnd: handleTap,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -104,7 +104,7 @@ describe('mixing responders with the heritage event system', () => {
|
||||
});
|
||||
|
||||
it('should properly flush sync when the event systems are mixed with unstable_flushDiscreteUpdates', () => {
|
||||
const usePress = require('react-ui/events/press').usePress;
|
||||
const useTap = require('react-ui/events/tap').useTap;
|
||||
const ref = React.createRef();
|
||||
let renderCounts = 0;
|
||||
|
||||
@@ -112,12 +112,12 @@ describe('mixing responders with the heritage event system', () => {
|
||||
const [, updateCounter] = React.useState(0);
|
||||
renderCounts++;
|
||||
|
||||
function handlePress() {
|
||||
function handleTap() {
|
||||
updateCounter(count => count + 1);
|
||||
}
|
||||
|
||||
const listener = usePress({
|
||||
onPress: handlePress,
|
||||
const listener = useTap({
|
||||
onTapEnd: handleTap,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -177,7 +177,7 @@ describe('mixing responders with the heritage event system', () => {
|
||||
'event systems',
|
||||
async () => {
|
||||
const {useState} = React;
|
||||
const usePress = require('react-ui/events/press').usePress;
|
||||
const useTap = require('react-ui/events/tap').useTap;
|
||||
|
||||
const button = React.createRef();
|
||||
|
||||
@@ -187,7 +187,7 @@ describe('mixing responders with the heritage event system', () => {
|
||||
const [pressesCount, updatePressesCount] = useState(0);
|
||||
const [clicksCount, updateClicksCount] = useState(0);
|
||||
|
||||
function handlePress() {
|
||||
function handleTap() {
|
||||
// This dispatches a synchronous, discrete event in the legacy event
|
||||
// system. However, because it's nested inside the new event system,
|
||||
// its updates should not flush until the end of the outer handler.
|
||||
@@ -198,14 +198,14 @@ describe('mixing responders with the heritage event system', () => {
|
||||
updatePressesCount(pressesCount + 1);
|
||||
}
|
||||
|
||||
const listener = usePress({
|
||||
onPress: handlePress,
|
||||
const tap = useTap({
|
||||
onTapEnd: handleTap,
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
listeners={listener}
|
||||
listeners={tap}
|
||||
ref={button}
|
||||
onClick={() => updateClicksCount(clicksCount + 1)}>
|
||||
Presses: {pressesCount}, Clicks: {clicksCount}
|
||||
@@ -237,37 +237,35 @@ describe('mixing responders with the heritage event system', () => {
|
||||
it('is async for non-input events', () => {
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
ReactFeatureFlags.enableUserBlockingEvents = true;
|
||||
const usePress = require('react-ui/events/press').usePress;
|
||||
const useTap = require('react-ui/events/tap').useTap;
|
||||
const useInput = require('react-ui/events/input').useInput;
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
let input;
|
||||
|
||||
let ops = [];
|
||||
|
||||
function Component({innerRef, onChange, controlledValue, pressListener}) {
|
||||
const inputListener = useInput({
|
||||
onChange,
|
||||
});
|
||||
function Component({innerRef, onChange, controlledValue, listeners}) {
|
||||
const inputListener = useInput({onChange});
|
||||
return (
|
||||
<input
|
||||
type="text"
|
||||
ref={innerRef}
|
||||
value={controlledValue}
|
||||
listeners={[inputListener, pressListener]}
|
||||
listeners={[inputListener, listeners]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function PressWrapper({innerRef, onPress, onChange, controlledValue}) {
|
||||
const pressListener = usePress({
|
||||
onPress,
|
||||
function PressWrapper({innerRef, onTap, onChange, controlledValue}) {
|
||||
const tap = useTap({
|
||||
onTapEnd: onTap,
|
||||
});
|
||||
return (
|
||||
<Component
|
||||
onChange={onChange}
|
||||
innerRef={el => (input = el)}
|
||||
controlledValue={controlledValue}
|
||||
pressListener={pressListener}
|
||||
listeners={tap}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -284,7 +282,7 @@ describe('mixing responders with the heritage event system', () => {
|
||||
this.state.value === 'changed' ? 'changed [!]' : this.state.value;
|
||||
return (
|
||||
<PressWrapper
|
||||
onPress={this.reset}
|
||||
onTap={this.reset}
|
||||
onChange={this.onChange}
|
||||
innerRef={el => (input = el)}
|
||||
controlledValue={controlledValue}
|
||||
@@ -307,10 +305,18 @@ describe('mixing responders with the heritage event system', () => {
|
||||
|
||||
// Trigger a click event
|
||||
input.dispatchEvent(
|
||||
new MouseEvent('mousedown', {bubbles: true, cancelable: true}),
|
||||
new MouseEvent('mousedown', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
buttons: 1,
|
||||
}),
|
||||
);
|
||||
input.dispatchEvent(
|
||||
new MouseEvent('mouseup', {bubbles: true, cancelable: true}),
|
||||
new MouseEvent('mouseup', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
buttons: 0,
|
||||
}),
|
||||
);
|
||||
// Nothing should have changed
|
||||
expect(ops).toEqual([]);
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
import {
|
||||
buttonsType,
|
||||
createEventTarget,
|
||||
describeWithPointerEvent,
|
||||
setPointerEvent,
|
||||
} from '../testing-library';
|
||||
|
||||
let React;
|
||||
let ReactFeatureFlags;
|
||||
let ReactDOM;
|
||||
let PressResponder;
|
||||
let usePress;
|
||||
|
||||
function initializeModules(hasPointerEvents) {
|
||||
@@ -28,23 +28,12 @@ function initializeModules(hasPointerEvents) {
|
||||
ReactFeatureFlags.enableFlareAPI = true;
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
PressResponder = require('react-ui/events/press').PressResponder;
|
||||
usePress = require('react-ui/events/press').usePress;
|
||||
}
|
||||
|
||||
function removePressMoveStrings(eventString) {
|
||||
if (eventString === 'onPressMove') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const forcePointerEvents = true;
|
||||
const environmentTable = [[forcePointerEvents], [!forcePointerEvents]];
|
||||
|
||||
const pointerTypesTable = [['mouse'], ['touch']];
|
||||
|
||||
describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
describeWithPointerEvent('Press responder', hasPointerEvents => {
|
||||
let container;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -60,33 +49,49 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
});
|
||||
|
||||
describe('disabled', () => {
|
||||
let onPressStart, onPress, onPressEnd, ref;
|
||||
let onPressStart, onPressChange, onPressMove, onPressEnd, onPress, ref;
|
||||
|
||||
beforeEach(() => {
|
||||
onPressStart = jest.fn();
|
||||
onPress = jest.fn();
|
||||
onPressChange = jest.fn();
|
||||
onPressMove = jest.fn();
|
||||
onPressEnd = jest.fn();
|
||||
onPress = jest.fn();
|
||||
ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = usePress({
|
||||
disabled: true,
|
||||
onPressStart,
|
||||
onPress,
|
||||
onPressChange,
|
||||
onPressMove,
|
||||
onPressEnd,
|
||||
onPress,
|
||||
});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
document.elementFromPoint = () => ref.current;
|
||||
});
|
||||
|
||||
it('does not call callbacks', () => {
|
||||
test('does not call callbacks for pointers', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown();
|
||||
target.pointerup();
|
||||
expect(onPressStart).not.toBeCalled();
|
||||
expect(onPress).not.toBeCalled();
|
||||
expect(onPressChange).not.toBeCalled();
|
||||
expect(onPressMove).not.toBeCalled();
|
||||
expect(onPressEnd).not.toBeCalled();
|
||||
expect(onPress).not.toBeCalled();
|
||||
});
|
||||
|
||||
test('does not call callbacks for keyboard', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
target.keydown({key: 'Enter'});
|
||||
target.keyup({key: 'Enter'});
|
||||
expect(onPressStart).not.toBeCalled();
|
||||
expect(onPressChange).not.toBeCalled();
|
||||
expect(onPressMove).not.toBeCalled();
|
||||
expect(onPressEnd).not.toBeCalled();
|
||||
expect(onPress).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -131,17 +136,6 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
);
|
||||
});
|
||||
|
||||
it('is not called after pointer move following middle-button press', () => {
|
||||
const node = ref.current;
|
||||
const target = createEventTarget(node);
|
||||
target.setBoundingClientRect({x: 0, y: 0, width: 100, height: 100});
|
||||
target.pointerdown({buttons: buttonsType.middle, pointerType: 'mouse'});
|
||||
target.pointerup({pointerType: 'mouse'});
|
||||
target.pointerhover({x: 110, y: 110});
|
||||
target.pointerhover({x: 50, y: 50});
|
||||
expect(onPressStart).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('ignores any events not caused by primary/middle-click or touch/pen contact', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({buttons: buttonsType.secondary});
|
||||
@@ -231,7 +225,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
const target = createEventTarget(ref.current);
|
||||
target.keydown({key: 'Enter'});
|
||||
// click occurs before keyup
|
||||
target.click();
|
||||
target.virtualclick();
|
||||
target.keyup({key: 'Enter'});
|
||||
expect(onPressEnd).toHaveBeenCalledTimes(1);
|
||||
expect(onPressEnd).toHaveBeenCalledWith(
|
||||
@@ -399,28 +393,6 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
);
|
||||
});
|
||||
|
||||
it('is called if target rect is not right but the target is (for mouse events)', () => {
|
||||
const buttonRef = React.createRef();
|
||||
const divRef = React.createRef();
|
||||
|
||||
const Component = () => {
|
||||
const listener = usePress({onPress});
|
||||
return (
|
||||
<div ref={divRef} listeners={listener}>
|
||||
<button ref={buttonRef} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
|
||||
const target = createEventTarget(divRef.current);
|
||||
target.setBoundingClientRect({x: 0, y: 0, width: 0, height: 0});
|
||||
const innerTarget = createEventTarget(buttonRef.current);
|
||||
innerTarget.pointerdown({pointerType: 'mouse'});
|
||||
innerTarget.pointerup({pointerType: 'mouse'});
|
||||
expect(onPress).toBeCalled();
|
||||
});
|
||||
|
||||
it('is called once after virtual screen reader "click" event', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
const preventDefault = jest.fn();
|
||||
@@ -488,386 +460,6 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
});
|
||||
});
|
||||
|
||||
describe.each(pointerTypesTable)('press with movement: %s', pointerType => {
|
||||
let events, ref, outerRef;
|
||||
|
||||
beforeEach(() => {
|
||||
events = [];
|
||||
ref = React.createRef();
|
||||
outerRef = React.createRef();
|
||||
const createEventHandler = msg => () => {
|
||||
events.push(msg);
|
||||
};
|
||||
const Component = () => {
|
||||
const listener = usePress({
|
||||
onPress: createEventHandler('onPress'),
|
||||
onPressChange: createEventHandler('onPressChange'),
|
||||
onPressMove: createEventHandler('onPressMove'),
|
||||
onPressStart: createEventHandler('onPressStart'),
|
||||
onPressEnd: createEventHandler('onPressEnd'),
|
||||
});
|
||||
return (
|
||||
<div ref={outerRef}>
|
||||
<div ref={ref} listeners={listener} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
document.elementFromPoint = () => ref.current;
|
||||
});
|
||||
|
||||
const rectMock = {width: 100, height: 100, x: 50, y: 50};
|
||||
const pressRectOffset = 20;
|
||||
const coordinatesInside = {
|
||||
x: rectMock.x - pressRectOffset,
|
||||
y: rectMock.y - pressRectOffset,
|
||||
};
|
||||
const coordinatesOutside = {
|
||||
x: rectMock.x - pressRectOffset - 1,
|
||||
y: rectMock.y - pressRectOffset - 1,
|
||||
};
|
||||
|
||||
describe('within bounds of hit rect', () => {
|
||||
/** ┌──────────────────┐
|
||||
* │ ┌────────────┐ │
|
||||
* │ │ VisualRect │ │
|
||||
* │ └────────────┘ │
|
||||
* │ HitRect X │ <= Move to X and release
|
||||
* └──────────────────┘
|
||||
*/
|
||||
it('"onPress*" events are called immediately', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
target.setBoundingClientRect(rectMock);
|
||||
target.pointerdown({pointerType});
|
||||
target.pointermove({pointerType, ...coordinatesInside});
|
||||
target.pointerup({pointerType, ...coordinatesInside});
|
||||
expect(events).toEqual([
|
||||
'onPressStart',
|
||||
'onPressChange',
|
||||
'onPressMove',
|
||||
'onPressEnd',
|
||||
'onPressChange',
|
||||
'onPress',
|
||||
]);
|
||||
});
|
||||
|
||||
it('"onPress*" events are correctly called with target change', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
const outerTarget = createEventTarget(outerRef.current);
|
||||
target.setBoundingClientRect(rectMock);
|
||||
target.pointerdown({pointerType});
|
||||
target.pointermove({pointerType, ...coordinatesInside});
|
||||
// TODO: this sequence may differ in the future between PointerEvent and mouse fallback when
|
||||
// use 'setPointerCapture'.
|
||||
if (pointerType === 'touch') {
|
||||
target.pointermove({pointerType, ...coordinatesOutside});
|
||||
} else {
|
||||
outerTarget.pointermove({pointerType, ...coordinatesOutside});
|
||||
}
|
||||
target.pointermove({pointerType, ...coordinatesInside});
|
||||
target.pointerup({pointerType, ...coordinatesInside});
|
||||
|
||||
expect(events.filter(removePressMoveStrings)).toEqual([
|
||||
'onPressStart',
|
||||
'onPressChange',
|
||||
'onPressEnd',
|
||||
'onPressChange',
|
||||
'onPressStart',
|
||||
'onPressChange',
|
||||
'onPressEnd',
|
||||
'onPressChange',
|
||||
'onPress',
|
||||
]);
|
||||
});
|
||||
|
||||
it('press retention offset can be configured', () => {
|
||||
let localEvents = [];
|
||||
const localRef = React.createRef();
|
||||
const createEventHandler = msg => () => {
|
||||
localEvents.push(msg);
|
||||
};
|
||||
const pressRetentionOffset = {top: 40, bottom: 40, left: 40, right: 40};
|
||||
|
||||
const Component = () => {
|
||||
const listener = usePress({
|
||||
onPress: createEventHandler('onPress'),
|
||||
onPressChange: createEventHandler('onPressChange'),
|
||||
onPressMove: createEventHandler('onPressMove'),
|
||||
onPressStart: createEventHandler('onPressStart'),
|
||||
onPressEnd: createEventHandler('onPressEnd'),
|
||||
pressRetentionOffset,
|
||||
});
|
||||
return <div ref={localRef} listeners={listener} />;
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
|
||||
const target = createEventTarget(localRef.current);
|
||||
target.setBoundingClientRect(rectMock);
|
||||
target.pointerdown({pointerType});
|
||||
target.pointermove({
|
||||
pointerType,
|
||||
x: rectMock.x,
|
||||
y: rectMock.y,
|
||||
});
|
||||
target.pointerup({pointerType, ...coordinatesInside});
|
||||
expect(localEvents).toEqual([
|
||||
'onPressStart',
|
||||
'onPressChange',
|
||||
'onPressMove',
|
||||
'onPressEnd',
|
||||
'onPressChange',
|
||||
'onPress',
|
||||
]);
|
||||
});
|
||||
|
||||
it('responder region accounts for decrease in element dimensions', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
target.setBoundingClientRect(rectMock);
|
||||
target.pointerdown({pointerType});
|
||||
// emulate smaller dimensions change on activation
|
||||
target.setBoundingClientRect({width: 80, height: 80, y: 60, x: 60});
|
||||
const coordinates = {x: rectMock.x, y: rectMock.y};
|
||||
// move to an area within the pre-activation region
|
||||
target.pointermove({pointerType, ...coordinates});
|
||||
target.pointerup({pointerType, ...coordinates});
|
||||
expect(events).toEqual([
|
||||
'onPressStart',
|
||||
'onPressChange',
|
||||
'onPressMove',
|
||||
'onPressEnd',
|
||||
'onPressChange',
|
||||
'onPress',
|
||||
]);
|
||||
});
|
||||
|
||||
it('responder region accounts for increase in element dimensions', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
target.setBoundingClientRect(rectMock);
|
||||
target.pointerdown({pointerType});
|
||||
// emulate larger dimensions change on activation
|
||||
target.setBoundingClientRect({width: 200, height: 200, y: 0, x: 0});
|
||||
const coordinates = {x: rectMock.x - 50, y: rectMock.y - 50};
|
||||
// move to an area within the post-activation region
|
||||
target.pointermove({pointerType, ...coordinates});
|
||||
target.pointerup({pointerType, ...coordinates});
|
||||
expect(events).toEqual([
|
||||
'onPressStart',
|
||||
'onPressChange',
|
||||
'onPressMove',
|
||||
'onPressEnd',
|
||||
'onPressChange',
|
||||
'onPress',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('beyond bounds of hit rect', () => {
|
||||
/** ┌──────────────────┐
|
||||
* │ ┌────────────┐ │
|
||||
* │ │ VisualRect │ │
|
||||
* │ └────────────┘ │
|
||||
* │ HitRect │
|
||||
* └──────────────────┘
|
||||
* X <= Move to X and release
|
||||
*/
|
||||
it('"onPress" is not called on release', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
const targetContainer = createEventTarget(container);
|
||||
target.setBoundingClientRect(rectMock);
|
||||
target.pointerdown({pointerType});
|
||||
target.pointermove({pointerType, ...coordinatesInside});
|
||||
if (pointerType === 'mouse') {
|
||||
// TODO: use setPointerCapture so this is only true for fallback mouse events.
|
||||
targetContainer.pointermove({pointerType, ...coordinatesOutside});
|
||||
targetContainer.pointerup({pointerType, ...coordinatesOutside});
|
||||
} else {
|
||||
target.pointermove({pointerType, ...coordinatesOutside});
|
||||
target.pointerup({pointerType, ...coordinatesOutside});
|
||||
}
|
||||
expect(events.filter(removePressMoveStrings)).toEqual([
|
||||
'onPressStart',
|
||||
'onPressChange',
|
||||
'onPressEnd',
|
||||
'onPressChange',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('"onPress" is called on re-entry to hit rect', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
const targetContainer = createEventTarget(container);
|
||||
target.setBoundingClientRect(rectMock);
|
||||
target.pointerdown({pointerType});
|
||||
target.pointermove({pointerType, ...coordinatesInside});
|
||||
if (pointerType === 'mouse') {
|
||||
// TODO: use setPointerCapture so this is only true for fallback mouse events.
|
||||
targetContainer.pointermove({pointerType, ...coordinatesOutside});
|
||||
} else {
|
||||
target.pointermove({pointerType, ...coordinatesOutside});
|
||||
}
|
||||
target.pointermove({pointerType, ...coordinatesInside});
|
||||
target.pointerup({pointerType, ...coordinatesInside});
|
||||
|
||||
expect(events).toEqual([
|
||||
'onPressStart',
|
||||
'onPressChange',
|
||||
'onPressMove',
|
||||
'onPressEnd',
|
||||
'onPressChange',
|
||||
'onPressStart',
|
||||
'onPressChange',
|
||||
'onPressEnd',
|
||||
'onPressChange',
|
||||
'onPress',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('nested responders', () => {
|
||||
if (hasPointerEvents) {
|
||||
it('dispatch events in the correct order', () => {
|
||||
const events = [];
|
||||
const ref = React.createRef();
|
||||
const createEventHandler = msg => () => {
|
||||
events.push(msg);
|
||||
};
|
||||
|
||||
const Inner = () => {
|
||||
const listener = usePress({
|
||||
onPress: createEventHandler('inner: onPress'),
|
||||
onPressChange: createEventHandler('inner: onPressChange'),
|
||||
onPressMove: createEventHandler('inner: onPressMove'),
|
||||
onPressStart: createEventHandler('inner: onPressStart'),
|
||||
onPressEnd: createEventHandler('inner: onPressEnd'),
|
||||
});
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
listeners={listener}
|
||||
onPointerDown={createEventHandler('pointerdown')}
|
||||
onPointerUp={createEventHandler('pointerup')}
|
||||
onKeyDown={createEventHandler('keydown')}
|
||||
onKeyUp={createEventHandler('keyup')}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const Outer = () => {
|
||||
const listener = usePress({
|
||||
onPress: createEventHandler('outer: onPress'),
|
||||
onPressChange: createEventHandler('outer: onPressChange'),
|
||||
onPressMove: createEventHandler('outer: onPressMove'),
|
||||
onPressStart: createEventHandler('outer: onPressStart'),
|
||||
onPressEnd: createEventHandler('outer: onPressEnd'),
|
||||
});
|
||||
return (
|
||||
<div listeners={listener}>
|
||||
<Inner />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
ReactDOM.render(<Outer />, container);
|
||||
|
||||
const target = createEventTarget(ref.current);
|
||||
target.setBoundingClientRect({x: 0, y: 0, width: 100, height: 100});
|
||||
target.pointerdown();
|
||||
target.pointerup();
|
||||
expect(events).toEqual([
|
||||
'inner: onPressStart',
|
||||
'inner: onPressChange',
|
||||
'pointerdown',
|
||||
'inner: onPressEnd',
|
||||
'inner: onPressChange',
|
||||
'inner: onPress',
|
||||
'pointerup',
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
describe('correctly not propagate', () => {
|
||||
it('for onPress', () => {
|
||||
const ref = React.createRef();
|
||||
const onPress = jest.fn();
|
||||
|
||||
const Inner = () => {
|
||||
const listener = usePress({onPress});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
};
|
||||
|
||||
const Outer = () => {
|
||||
const listener = usePress({onPress});
|
||||
return (
|
||||
<div listeners={listener}>
|
||||
<Inner />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
ReactDOM.render(<Outer />, container);
|
||||
|
||||
const target = createEventTarget(ref.current);
|
||||
target.setBoundingClientRect({x: 0, y: 0, width: 100, height: 100});
|
||||
target.pointerdown();
|
||||
target.pointerup();
|
||||
expect(onPress).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('for onPressStart/onPressEnd', () => {
|
||||
const ref = React.createRef();
|
||||
const onPressStart = jest.fn();
|
||||
const onPressEnd = jest.fn();
|
||||
|
||||
const Inner = () => {
|
||||
const listener = usePress({onPressStart, onPressEnd});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
};
|
||||
|
||||
const Outer = () => {
|
||||
const listener = usePress({onPressStart, onPressEnd});
|
||||
return (
|
||||
<div listeners={listener}>
|
||||
<Inner />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
ReactDOM.render(<Outer />, container);
|
||||
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown();
|
||||
expect(onPressStart).toHaveBeenCalledTimes(1);
|
||||
expect(onPressEnd).toHaveBeenCalledTimes(0);
|
||||
target.pointerup();
|
||||
expect(onPressStart).toHaveBeenCalledTimes(1);
|
||||
expect(onPressEnd).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('for onPressChange', () => {
|
||||
const ref = React.createRef();
|
||||
const onPressChange = jest.fn();
|
||||
|
||||
const Inner = () => {
|
||||
const listener = usePress({onPressChange});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
};
|
||||
|
||||
const Outer = () => {
|
||||
const listener = usePress({onPressChange});
|
||||
return (
|
||||
<div listeners={listener}>
|
||||
<Inner />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
ReactDOM.render(<Outer />, container);
|
||||
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown();
|
||||
expect(onPressChange).toHaveBeenCalledTimes(1);
|
||||
target.pointerup();
|
||||
expect(onPressChange).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('link components', () => {
|
||||
it('prevents native behavior by default', () => {
|
||||
const onPress = jest.fn();
|
||||
@@ -891,7 +483,8 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
|
||||
it('prevents native behaviour for keyboard events by default', () => {
|
||||
const onPress = jest.fn();
|
||||
const preventDefault = jest.fn();
|
||||
const preventDefaultClick = jest.fn();
|
||||
const preventDefaultKeyDown = jest.fn();
|
||||
const ref = React.createRef();
|
||||
|
||||
const Component = () => {
|
||||
@@ -901,10 +494,12 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
ReactDOM.render(<Component />, container);
|
||||
|
||||
const target = createEventTarget(ref.current);
|
||||
target.keydown({key: 'Enter'});
|
||||
target.click({preventDefault});
|
||||
target.keydown({key: 'Enter', preventDefault: preventDefaultKeyDown});
|
||||
target.virtualclick({preventDefault: preventDefaultClick});
|
||||
target.keyup({key: 'Enter'});
|
||||
expect(preventDefault).toBeCalled();
|
||||
expect(preventDefaultKeyDown).toBeCalled();
|
||||
expect(preventDefaultClick).toBeCalled();
|
||||
expect(onPress).toHaveBeenCalledTimes(1);
|
||||
expect(onPress).toHaveBeenCalledWith(
|
||||
expect.objectContaining({defaultPrevented: true}),
|
||||
);
|
||||
@@ -1010,99 +605,16 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
|
||||
const target = createEventTarget(ref.current);
|
||||
target.keydown({key: 'Enter'});
|
||||
target.click({preventDefault});
|
||||
target.virtualclick({preventDefault});
|
||||
target.keyup({key: 'Enter'});
|
||||
expect(preventDefault).not.toBeCalled();
|
||||
expect(onPress).toHaveBeenCalledTimes(1);
|
||||
expect(onPress).toHaveBeenCalledWith(
|
||||
expect.objectContaining({defaultPrevented: false}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('responder cancellation', () => {
|
||||
it.each(pointerTypesTable)('ends on pointer cancel', pointerType => {
|
||||
const onPressEnd = jest.fn();
|
||||
const ref = React.createRef();
|
||||
|
||||
const Component = () => {
|
||||
const listener = usePress({onPressEnd});
|
||||
return <a href="#" ref={ref} listeners={listener} />;
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({pointerType});
|
||||
target.pointercancel({pointerType});
|
||||
expect(onPressEnd).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('does end on "scroll" to document (not mouse)', () => {
|
||||
const onPressEnd = jest.fn();
|
||||
const ref = React.createRef();
|
||||
|
||||
const Component = () => {
|
||||
const listener = usePress({onPressEnd});
|
||||
return <a href="#" ref={ref} listeners={listener} />;
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
|
||||
const target = createEventTarget(ref.current);
|
||||
const targetDocument = createEventTarget(document);
|
||||
target.pointerdown({pointerType: 'touch'});
|
||||
targetDocument.scroll();
|
||||
expect(onPressEnd).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('does end on "scroll" to a parent container (not mouse)', () => {
|
||||
const onPressEnd = jest.fn();
|
||||
const ref = React.createRef();
|
||||
const containerRef = React.createRef();
|
||||
|
||||
const Component = () => {
|
||||
const listener = usePress({onPressEnd});
|
||||
return (
|
||||
<div ref={containerRef}>
|
||||
<a ref={ref} listeners={listener} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
|
||||
const target = createEventTarget(ref.current);
|
||||
const targetContainer = createEventTarget(containerRef.current);
|
||||
target.pointerdown({pointerType: 'touch'});
|
||||
targetContainer.scroll();
|
||||
expect(onPressEnd).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('does not end on "scroll" to an element outside', () => {
|
||||
const onPressEnd = jest.fn();
|
||||
const ref = React.createRef();
|
||||
const outsideRef = React.createRef();
|
||||
|
||||
const Component = () => {
|
||||
const listener = usePress({onPressEnd});
|
||||
return (
|
||||
<div>
|
||||
<a ref={ref} listeners={listener} />
|
||||
<span ref={outsideRef} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
|
||||
const target = createEventTarget(ref.current);
|
||||
const targetOutside = createEventTarget(outsideRef.current);
|
||||
target.pointerdown();
|
||||
targetOutside.scroll();
|
||||
expect(onPressEnd).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('expect displayName to show up for event component', () => {
|
||||
expect(PressResponder.displayName).toBe('Press');
|
||||
});
|
||||
|
||||
it('should not trigger an invariant in addRootEventTypes()', () => {
|
||||
const ref = React.createRef();
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -703,7 +703,9 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
|
||||
target.pointerdown();
|
||||
target.pointerup({preventDefault});
|
||||
expect(preventDefault).toBeCalled();
|
||||
expect(onTapEnd).toBeCalled();
|
||||
expect(onTapEnd).toHaveBeenCalledWith(
|
||||
expect.objectContaining({defaultPrevented: true}),
|
||||
);
|
||||
});
|
||||
|
||||
test('prevents native behaviour by default (inner target)', () => {
|
||||
@@ -711,7 +713,9 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
|
||||
innerTarget.pointerdown();
|
||||
innerTarget.pointerup({preventDefault});
|
||||
expect(preventDefault).toBeCalled();
|
||||
expect(onTapEnd).toBeCalled();
|
||||
expect(onTapEnd).toHaveBeenCalledWith(
|
||||
expect.objectContaining({defaultPrevented: true}),
|
||||
);
|
||||
});
|
||||
|
||||
test('allows native behaviour by default (modifier keys)', () => {
|
||||
@@ -720,7 +724,9 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
|
||||
target.pointerdown({[modifierKey]: true});
|
||||
target.pointerup({[modifierKey]: true, preventDefault});
|
||||
expect(preventDefault).not.toBeCalled();
|
||||
expect(onTapEnd).toBeCalled();
|
||||
expect(onTapEnd).toHaveBeenCalledWith(
|
||||
expect.objectContaining({defaultPrevented: false}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -731,7 +737,9 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
|
||||
target.pointerdown();
|
||||
target.pointerup({preventDefault});
|
||||
expect(preventDefault).not.toBeCalled();
|
||||
expect(onTapEnd).toBeCalled();
|
||||
expect(onTapEnd).toHaveBeenCalledWith(
|
||||
expect.objectContaining({defaultPrevented: false}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
4
packages/react-ui/npm/drag.js
vendored
4
packages/react-ui/npm/drag.js
vendored
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-events-drag.production.min.js');
|
||||
module.exports = require('./cjs/react-ui-events/drag.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-events-drag.development.js');
|
||||
module.exports = require('./cjs/react-ui-events/drag.development.js');
|
||||
}
|
||||
|
||||
4
packages/react-ui/npm/focus.js
vendored
4
packages/react-ui/npm/focus.js
vendored
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-events-focus.production.min.js');
|
||||
module.exports = require('./cjs/react-ui-events/focus.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-events-focus.development.js');
|
||||
module.exports = require('./cjs/react-ui-events/focus.development.js');
|
||||
}
|
||||
|
||||
4
packages/react-ui/npm/hover.js
vendored
4
packages/react-ui/npm/hover.js
vendored
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-events-hover.production.min.js');
|
||||
module.exports = require('./cjs/react-ui-events/hover.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-events-hover.development.js');
|
||||
module.exports = require('./cjs/react-ui-events/hover.development.js');
|
||||
}
|
||||
|
||||
4
packages/react-ui/npm/input.js
vendored
4
packages/react-ui/npm/input.js
vendored
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-events-input.production.min.js');
|
||||
module.exports = require('./cjs/react-ui-events/input.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-events-input.development.js');
|
||||
module.exports = require('./cjs/react-ui-events/input.development.js');
|
||||
}
|
||||
|
||||
4
packages/react-ui/npm/keyboard.js
vendored
4
packages/react-ui/npm/keyboard.js
vendored
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-events-keyboard.production.min.js');
|
||||
module.exports = require('./cjs/react-ui-events/keyboard.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-events-keyboard.development.js');
|
||||
module.exports = require('./cjs/react-ui-events/keyboard.development.js');
|
||||
}
|
||||
|
||||
7
packages/react-ui/npm/press-legacy.js
vendored
Normal file
7
packages/react-ui/npm/press-legacy.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-ui-events/press-legacy.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-ui-events/press-legacy.development.js');
|
||||
}
|
||||
4
packages/react-ui/npm/press.js
vendored
4
packages/react-ui/npm/press.js
vendored
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-events-press.production.min.js');
|
||||
module.exports = require('./cjs/react-ui-events/press.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-events-press.development.js');
|
||||
module.exports = require('./cjs/react-ui-events/press.development.js');
|
||||
}
|
||||
|
||||
4
packages/react-ui/npm/scroll.js
vendored
4
packages/react-ui/npm/scroll.js
vendored
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-events-scroll.production.min.js');
|
||||
module.exports = require('./cjs/react-ui-events/scroll.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-events-scroll.development.js');
|
||||
module.exports = require('./cjs/react-ui-events/scroll.development.js');
|
||||
}
|
||||
|
||||
4
packages/react-ui/npm/swipe.js
vendored
4
packages/react-ui/npm/swipe.js
vendored
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-events-swipe.production.min.js');
|
||||
module.exports = require('./cjs/react-ui-events/swipe.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-events-swipe.development.js');
|
||||
module.exports = require('./cjs/react-ui-events/swipe.development.js');
|
||||
}
|
||||
|
||||
4
packages/react-ui/npm/tap.js
vendored
4
packages/react-ui/npm/tap.js
vendored
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-events-tap.production.min.js');
|
||||
module.exports = require('./cjs/react-ui-events/tap.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-events-tap.development.js');
|
||||
module.exports = require('./cjs/react-ui-events/tap.development.js');
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"events/input.js",
|
||||
"events/keyboard.js",
|
||||
"events/press.js",
|
||||
"events/press-legacy.js",
|
||||
"events/scroll.js",
|
||||
"events/swipe.js",
|
||||
"events/tap.js",
|
||||
|
||||
@@ -597,6 +597,21 @@ const bundles = [
|
||||
moduleType: NON_FIBER_RENDERER,
|
||||
entry: 'react-ui/events/press',
|
||||
global: 'ReactEventsPress',
|
||||
externals: ['react', 'react-ui/events/tap', 'react-ui/events/keyboard'],
|
||||
},
|
||||
|
||||
{
|
||||
bundleTypes: [
|
||||
UMD_DEV,
|
||||
UMD_PROD,
|
||||
NODE_DEV,
|
||||
NODE_PROD,
|
||||
FB_WWW_DEV,
|
||||
FB_WWW_PROD,
|
||||
],
|
||||
moduleType: NON_FIBER_RENDERER,
|
||||
entry: 'react-ui/events/press-legacy',
|
||||
global: 'ReactEventsPressLegacy',
|
||||
externals: ['react'],
|
||||
},
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ const importSideEffects = Object.freeze({
|
||||
const knownGlobals = Object.freeze({
|
||||
react: 'React',
|
||||
'react-dom': 'ReactDOM',
|
||||
'react-ui/events/keyboard': 'ReactEventsKeyboard',
|
||||
'react-ui/events/tap': 'ReactEventsTap',
|
||||
scheduler: 'Scheduler',
|
||||
'scheduler/tracing': 'SchedulerTracing',
|
||||
'scheduler/unstable_mock': 'SchedulerMock',
|
||||
|
||||
@@ -12,7 +12,8 @@ const esNextPaths = [
|
||||
'packages/*/*.js',
|
||||
// Source files
|
||||
'packages/*/src/**/*.js',
|
||||
'packages/react-ui/*/src/**/*.js',
|
||||
'packages/react-ui/**/*.js',
|
||||
'packages/react-ui/**/*.js',
|
||||
'packages/legacy-events/**/*.js',
|
||||
'packages/shared/**/*.js',
|
||||
// Shims and Flow environment
|
||||
|
||||
Reference in New Issue
Block a user