From 5a60b16850f02d4ed0c5346781519e37a600bab6 Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher Date: Fri, 5 Apr 2019 12:13:13 -0700 Subject: [PATCH 1/3] Minor naming changes to Hover events --- packages/react-events/src/Hover.js | 28 ++++++++-------- .../src/__tests__/Hover-test.internal.js | 33 ++++++++++--------- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/packages/react-events/src/Hover.js b/packages/react-events/src/Hover.js index 0668880268476..1944a84cb574e 100644 --- a/packages/react-events/src/Hover.js +++ b/packages/react-events/src/Hover.js @@ -36,8 +36,8 @@ type HoverEvent = {| type: HoverEventType, |}; -// const DEFAULT_HOVER_END_DELAY_MS = 0; -// const DEFAULT_HOVER_START_DELAY_MS = 0; +const DEFAULT_HOVER_END_DELAY_MS = 0; +const DEFAULT_HOVER_START_DELAY_MS = 0; const targetEventTypes = [ 'pointerover', @@ -98,7 +98,7 @@ function dispatchHoverStartEvents( state.hoverEndTimeout = null; } - const dispatch = () => { + const activate = () => { state.isActiveHovered = true; if (props.onHoverStart) { @@ -115,14 +115,14 @@ function dispatchHoverStartEvents( }; if (!state.isActiveHovered) { - const delay = calculateDelayMS(props.delayHoverStart, 0, 0); - if (delay > 0) { + const delayHoverStart = calculateDelayMS(props.delayHoverStart, 0, DEFAULT_HOVER_START_DELAY_MS); + if (delayHoverStart > 0) { state.hoverStartTimeout = context.setTimeout(() => { state.hoverStartTimeout = null; - dispatch(); - }, delay); + activate(); + }, delayHoverStart); } else { - dispatch(); + activate(); } } } @@ -145,7 +145,7 @@ function dispatchHoverEndEvents( state.hoverStartTimeout = null; } - const dispatch = () => { + const deactivate = () => { state.isActiveHovered = false; if (props.onHoverEnd) { @@ -162,13 +162,13 @@ function dispatchHoverEndEvents( }; if (state.isActiveHovered) { - const delay = calculateDelayMS(props.delayHoverEnd, 0, 0); - if (delay > 0) { + const delayHoverEnd = calculateDelayMS(props.delayHoverEnd, 0, DEFAULT_HOVER_END_DELAY_MS); + if (delayHoverEnd > 0) { state.hoverEndTimeout = context.setTimeout(() => { - dispatch(); - }, delay); + deactivate(); + }, delayHoverEnd); } else { - dispatch(); + deactivate(); } } } diff --git a/packages/react-events/src/__tests__/Hover-test.internal.js b/packages/react-events/src/__tests__/Hover-test.internal.js index c7cb373a1626b..0d29036f988f3 100644 --- a/packages/react-events/src/__tests__/Hover-test.internal.js +++ b/packages/react-events/src/__tests__/Hover-test.internal.js @@ -100,6 +100,24 @@ describe('Hover event responder', () => { expect(onHoverStart).toHaveBeenCalledTimes(1); }); + it('is reset if "pointerout" is dispatched during a delay', () => { + const element = ( + +
+ + ); + ReactDOM.render(element, container); + + ref.current.dispatchEvent(createPointerEvent('pointerover')); + jest.advanceTimersByTime(499); + ref.current.dispatchEvent(createPointerEvent('pointerout')); + jest.advanceTimersByTime(1); + expect(onHoverStart).not.toBeCalled(); + ref.current.dispatchEvent(createPointerEvent('pointerover')); + jest.runAllTimers(); + expect(onHoverStart).toHaveBeenCalledTimes(1); + }); + it('onHoverStart is called synchronously if delay is 0ms', () => { const element = ( @@ -132,21 +150,6 @@ describe('Hover event responder', () => { jest.runAllTimers(); expect(onHoverStart).toHaveBeenCalledTimes(1); }); - - it('onHoverStart is not called if "pointerout" is dispatched during a delay', () => { - const element = ( - -
- - ); - ReactDOM.render(element, container); - - ref.current.dispatchEvent(createPointerEvent('pointerover')); - jest.advanceTimersByTime(499); - ref.current.dispatchEvent(createPointerEvent('pointerout')); - jest.advanceTimersByTime(1); - expect(onHoverStart).not.toBeCalled(); - }); }); }); From f3696641d36fa4202757c1b9a6a5e0d58b6dbe64 Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher Date: Fri, 5 Apr 2019 12:13:29 -0700 Subject: [PATCH 2/3] Add delay props to Press event module --- packages/react-events/src/Hover.js | 12 +- packages/react-events/src/Press.js | 163 ++++++---- .../src/__tests__/Press-test.internal.js | 280 +++++++++++++++++- 3 files changed, 390 insertions(+), 65 deletions(-) diff --git a/packages/react-events/src/Hover.js b/packages/react-events/src/Hover.js index 1944a84cb574e..bc438d9c74ad8 100644 --- a/packages/react-events/src/Hover.js +++ b/packages/react-events/src/Hover.js @@ -115,7 +115,11 @@ function dispatchHoverStartEvents( }; if (!state.isActiveHovered) { - const delayHoverStart = calculateDelayMS(props.delayHoverStart, 0, DEFAULT_HOVER_START_DELAY_MS); + const delayHoverStart = calculateDelayMS( + props.delayHoverStart, + 0, + DEFAULT_HOVER_START_DELAY_MS, + ); if (delayHoverStart > 0) { state.hoverStartTimeout = context.setTimeout(() => { state.hoverStartTimeout = null; @@ -162,7 +166,11 @@ function dispatchHoverEndEvents( }; if (state.isActiveHovered) { - const delayHoverEnd = calculateDelayMS(props.delayHoverEnd, 0, DEFAULT_HOVER_END_DELAY_MS); + const delayHoverEnd = calculateDelayMS( + props.delayHoverEnd, + 0, + DEFAULT_HOVER_END_DELAY_MS, + ); if (delayHoverEnd > 0) { state.hoverEndTimeout = context.setTimeout(() => { deactivate(); diff --git a/packages/react-events/src/Press.js b/packages/react-events/src/Press.js index 00db07c72c423..5aacbbb44b018 100644 --- a/packages/react-events/src/Press.js +++ b/packages/react-events/src/Press.js @@ -27,11 +27,15 @@ type PressProps = { type PressState = { defaultPrevented: boolean, + isActivePressed: boolean, + isActivePressStart: boolean, isAnchorTouched: boolean, isLongPressed: boolean, isPressed: boolean, longPressTimeout: null | TimeoutID, pressTarget: null | Element | Document, + pressEndTimeout: null | TimeoutID, + pressStartTimeout: null | TimeoutID, shouldSkipMouseAfterTouch: boolean, }; @@ -49,9 +53,8 @@ type PressEvent = {| type: PressEventType, |}; -// const DEFAULT_PRESS_DELAY_MS = 0; -// const DEFAULT_PRESS_END_DELAY_MS = 0; -// const DEFAULT_PRESS_START_DELAY_MS = 0; +const DEFAULT_PRESS_END_DELAY_MS = 0; +const DEFAULT_PRESS_START_DELAY_MS = 0; const DEFAULT_LONG_PRESS_DELAY_MS = 500; const targetEventTypes = [ @@ -102,7 +105,7 @@ function dispatchPressChangeEvent( state: PressState, ): void { const listener = () => { - props.onPressChange(state.isPressed); + props.onPressChange(state.isActivePressed); }; dispatchEvent(context, state, 'presschange', listener); } @@ -118,6 +121,34 @@ function dispatchLongPressChangeEvent( dispatchEvent(context, state, 'longpresschange', listener); } +function activate(context, props, state) { + const wasActivePressed = state.isActivePressed; + state.isActivePressed = true; + + if (props.onPressStart) { + dispatchEvent(context, state, 'pressstart', props.onPressStart); + } + if (!wasActivePressed && props.onPressChange) { + dispatchPressChangeEvent(context, props, state); + } +} + +function deactivate(context, props, state) { + const wasLongPressed = state.isLongPressed; + state.isActivePressed = false; + state.isLongPressed = false; + + if (props.onPressEnd) { + dispatchEvent(context, state, 'pressend', props.onPressEnd); + } + if (props.onPressChange) { + dispatchPressChangeEvent(context, props, state); + } + if (wasLongPressed && props.onLongPressChange) { + dispatchLongPressChangeEvent(context, props, state); + } +} + function dispatchPressStartEvents( context: ResponderContext, props: PressProps, @@ -125,38 +156,58 @@ function dispatchPressStartEvents( ): void { state.isPressed = true; - if (props.onPressStart) { - dispatchEvent(context, state, 'pressstart', props.onPressStart); + if (state.pressEndTimeout !== null) { + clearTimeout(state.pressEndTimeout); + state.pressEndTimeout = null; } - if (props.onPressChange) { - dispatchPressChangeEvent(context, props, state); - } - if ((props.onLongPress || props.onLongPressChange) && !state.isLongPressed) { - const delayLongPress = calculateDelayMS( - props.delayLongPress, - 10, - DEFAULT_LONG_PRESS_DELAY_MS, - ); - state.longPressTimeout = context.setTimeout(() => { - state.isLongPressed = true; - state.longPressTimeout = null; - - if (props.onLongPress) { - const listener = e => { - props.onLongPress(e); - // TODO address this again at some point - // if (e.nativeEvent.defaultPrevented) { - // state.defaultPrevented = true; - // } - }; - dispatchEvent(context, state, 'longpress', listener); - } + const dispatch = () => { + state.isActivePressStart = true; + activate(context, props, state); + + if ( + (props.onLongPress || props.onLongPressChange) && + !state.isLongPressed + ) { + const delayLongPress = calculateDelayMS( + props.delayLongPress, + 10, + DEFAULT_LONG_PRESS_DELAY_MS, + ); + state.longPressTimeout = context.setTimeout(() => { + state.isLongPressed = true; + state.longPressTimeout = null; + if (props.onLongPress) { + const listener = e => { + props.onLongPress(e); + // TODO address this again at some point + // if (e.nativeEvent.defaultPrevented) { + // state.defaultPrevented = true; + // } + }; + dispatchEvent(context, state, 'longpress', listener); + } + if (props.onLongPressChange) { + dispatchLongPressChangeEvent(context, props, state); + } + }, delayLongPress); + } + }; - if (props.onLongPressChange) { - dispatchLongPressChangeEvent(context, props, state); - } - }, delayLongPress); + if (!state.isActivePressStart) { + const delayPressStart = calculateDelayMS( + props.delayPressStart, + 0, + DEFAULT_PRESS_START_DELAY_MS, + ); + if (delayPressStart > 0) { + state.pressStartTimeout = context.setTimeout(() => { + state.pressStartTimeout = null; + dispatch(); + }, delayPressStart); + } else { + dispatch(); + } } } @@ -165,25 +216,36 @@ function dispatchPressEndEvents( props: PressProps, state: PressState, ): void { + const wasActivePressStart = state.isActivePressStart; + + state.isActivePressStart = false; + state.isPressed = false; + if (state.longPressTimeout !== null) { clearTimeout(state.longPressTimeout); state.longPressTimeout = null; } - if (props.onPressEnd) { - dispatchEvent(context, state, 'pressend', props.onPressEnd); - } - if (state.isPressed) { - state.isPressed = false; - if (props.onPressChange) { - dispatchPressChangeEvent(context, props, state); - } + if (!wasActivePressStart && state.pressStartTimeout !== null) { + clearTimeout(state.pressStartTimeout); + state.pressStartTimeout = null; + // if we haven't yet activated (due to delays), activate now + activate(context, props, state); } - if (state.isLongPressed) { - state.isLongPressed = false; - if (props.onLongPressChange) { - dispatchLongPressChangeEvent(context, props, state); + if (state.isActivePressed) { + const delayPressEnd = calculateDelayMS( + props.delayPressEnd, + 0, + DEFAULT_PRESS_END_DELAY_MS, + ); + if (delayPressEnd > 0) { + state.pressEndTimeout = context.setTimeout(() => { + state.pressEndTimeout = null; + deactivate(context, props, state); + }, delayPressEnd); + } else { + deactivate(context, props, state); } } } @@ -208,13 +270,8 @@ function unmountResponder( state: PressState, ): void { if (state.isPressed) { - state.isPressed = false; - context.removeRootEventTypes(rootEventTypes); dispatchPressEndEvents(context, props, state); - if (state.longPressTimeout !== null) { - clearTimeout(state.longPressTimeout); - state.longPressTimeout = null; - } + context.removeRootEventTypes(rootEventTypes); } } @@ -223,10 +280,14 @@ const PressResponder = { createInitialState(): PressState { return { defaultPrevented: false, + isActivePressed: false, + isActivePressStart: false, isAnchorTouched: false, isLongPressed: false, isPressed: false, longPressTimeout: null, + pressEndTimeout: null, + pressStartTimeout: null, pressTarget: null, shouldSkipMouseAfterTouch: false, }; diff --git a/packages/react-events/src/__tests__/Press-test.internal.js b/packages/react-events/src/__tests__/Press-test.internal.js index 504f7e49d8d85..2c83bcaafd338 100644 --- a/packages/react-events/src/__tests__/Press-test.internal.js +++ b/packages/react-events/src/__tests__/Press-test.internal.js @@ -104,8 +104,70 @@ describe('Event responder: Press', () => { expect(onPressStart).toHaveBeenCalledTimes(1); }); - // TODO: complete delayPressStart tests - // describe('delayPressStart', () => {}); + describe('delayPressStart', () => { + it('can be configured', () => { + const element = ( + +
+ + ); + ReactDOM.render(element, container); + + ref.current.dispatchEvent(createPointerEvent('pointerdown')); + jest.advanceTimersByTime(1999); + expect(onPressStart).not.toBeCalled(); + jest.advanceTimersByTime(1); + expect(onPressStart).toHaveBeenCalledTimes(1); + }); + + it('is cut short if the press is released during a delay', () => { + const element = ( + +
+ + ); + ReactDOM.render(element, container); + + ref.current.dispatchEvent(createPointerEvent('pointerdown')); + jest.advanceTimersByTime(499); + expect(onPressStart).toHaveBeenCalledTimes(0); + ref.current.dispatchEvent(createPointerEvent('pointerup')); + expect(onPressStart).toHaveBeenCalledTimes(1); + jest.runAllTimers(); + expect(onPressStart).toHaveBeenCalledTimes(1); + }); + + it('onPressStart is called synchronously if delay is 0ms', () => { + const element = ( + +
+ + ); + ReactDOM.render(element, container); + + ref.current.dispatchEvent(createPointerEvent('pointerdown')); + expect(onPressStart).toHaveBeenCalledTimes(1); + }); + }); + + describe('delayPressEnd', () => { + it('onPressStart called each time a press is initiated', () => { + // This test makes sure that onPressStart is called each time a press + // starts, even if a delayPressEnd is delaying the deactivation of the + // previous press. + const element = ( + +
+ + ); + ReactDOM.render(element, container); + + ref.current.dispatchEvent(createPointerEvent('pointerdown')); + ref.current.dispatchEvent(createPointerEvent('pointerup')); + ref.current.dispatchEvent(createPointerEvent('pointerdown')); + expect(onPressStart).toHaveBeenCalledTimes(2); + }); + }); }); describe('onPressEnd', () => { @@ -165,8 +227,55 @@ describe('Event responder: Press', () => { expect(onPressEnd).toHaveBeenCalledTimes(1); }); - // TODO: complete delayPressStart tests - // describe('delayPressStart', () => {}); + describe('delayPressEnd', () => { + it('can be configured', () => { + const element = ( + +
+ + ); + ReactDOM.render(element, container); + + ref.current.dispatchEvent(createPointerEvent('pointerdown')); + ref.current.dispatchEvent(createPointerEvent('pointerup')); + jest.advanceTimersByTime(1999); + expect(onPressEnd).not.toBeCalled(); + jest.advanceTimersByTime(1); + expect(onPressEnd).toHaveBeenCalledTimes(1); + }); + + it('is reset if "pointerdown" is dispatched during a delay', () => { + const element = ( + +
+ + ); + ReactDOM.render(element, container); + + ref.current.dispatchEvent(createPointerEvent('pointerdown')); + ref.current.dispatchEvent(createPointerEvent('pointerup')); + jest.advanceTimersByTime(499); + ref.current.dispatchEvent(createPointerEvent('pointerdown')); + jest.advanceTimersByTime(1); + expect(onPressEnd).not.toBeCalled(); + ref.current.dispatchEvent(createPointerEvent('pointerup')); + jest.runAllTimers(); + expect(onPressEnd).toHaveBeenCalledTimes(1); + }); + }); + + it('onPressEnd is called synchronously if delay is 0ms', () => { + const element = ( + +
+ + ); + ReactDOM.render(element, container); + + ref.current.dispatchEvent(createPointerEvent('pointerdown')); + ref.current.dispatchEvent(createPointerEvent('pointerup')); + expect(onPressEnd).toHaveBeenCalledTimes(1); + }); }); describe('onPressChange', () => { @@ -201,6 +310,55 @@ describe('Event responder: Press', () => { expect(onPressChange).toHaveBeenCalledWith(false); }); + it('is called after delayed onPressStart', () => { + const element = ( + +
+ + ); + ReactDOM.render(element, container); + + ref.current.dispatchEvent(createPointerEvent('pointerdown')); + jest.advanceTimersByTime(499); + expect(onPressChange).not.toBeCalled(); + jest.advanceTimersByTime(1); + expect(onPressChange).toHaveBeenCalledTimes(1); + expect(onPressChange).toHaveBeenCalledWith(true); + }); + + it('is called after delayPressStart is cut short', () => { + const element = ( + +
+ + ); + ReactDOM.render(element, container); + + ref.current.dispatchEvent(createPointerEvent('pointerdown')); + jest.advanceTimersByTime(100); + ref.current.dispatchEvent(createPointerEvent('pointerup')); + expect(onPressChange).toHaveBeenCalledTimes(2); + }); + + it('is called after delayed onPressEnd', () => { + const element = ( + +
+ + ); + ReactDOM.render(element, container); + + ref.current.dispatchEvent(createPointerEvent('pointerdown')); + expect(onPressChange).toHaveBeenCalledTimes(1); + expect(onPressChange).toHaveBeenCalledWith(true); + ref.current.dispatchEvent(createPointerEvent('pointerup')); + jest.advanceTimersByTime(499); + expect(onPressChange).toHaveBeenCalledTimes(1); + jest.advanceTimersByTime(1); + expect(onPressChange).toHaveBeenCalledTimes(2); + expect(onPressChange).toHaveBeenCalledWith(false); + }); + // No PointerEvent fallbacks it('is called after "mousedown" and "mouseup" events', () => { ref.current.dispatchEvent(createPointerEvent('mousedown')); @@ -246,13 +404,26 @@ describe('Event responder: Press', () => { expect(onPress).toHaveBeenCalledTimes(1); }); + it('is always called immediately after press is released', () => { + const element = ( + +
+ + ); + ReactDOM.render(element, container); + + ref.current.dispatchEvent(createPointerEvent('pointerdown')); + ref.current.dispatchEvent(createPointerEvent('pointerup')); + expect(onPress).toHaveBeenCalledTimes(1); + }); + // No PointerEvent fallbacks // TODO: jsdom missing APIs - //it('is called after "touchend" event', () => { - //ref.current.dispatchEvent(createPointerEvent('touchstart')); - //ref.current.dispatchEvent(createPointerEvent('touchend')); - //expect(onPress).toHaveBeenCalledTimes(1); - //}); + // it('is called after "touchend" event', () => { + // ref.current.dispatchEvent(createPointerEvent('touchstart')); + // ref.current.dispatchEvent(createPointerEvent('touchend')); + // expect(onPress).toHaveBeenCalledTimes(1); + // }); }); describe('onLongPress', () => { @@ -332,7 +503,6 @@ describe('Event responder: Press', () => { expect(onLongPress).toHaveBeenCalledTimes(1); }); - /* it('compounds with "delayPressStart"', () => { const delayPressStart = 100; const element = ( @@ -343,12 +513,13 @@ describe('Event responder: Press', () => { ReactDOM.render(element, container); ref.current.dispatchEvent(createPointerEvent('pointerdown')); - jest.advanceTimersByTime(delayPressStart + DEFAULT_LONG_PRESS_DELAY - 1); + jest.advanceTimersByTime( + delayPressStart + DEFAULT_LONG_PRESS_DELAY - 1, + ); expect(onLongPress).not.toBeCalled(); jest.advanceTimersByTime(1); expect(onLongPress).toHaveBeenCalledTimes(1); }); - */ }); }); @@ -371,6 +542,28 @@ describe('Event responder: Press', () => { expect(onLongPressChange).toHaveBeenCalledTimes(2); expect(onLongPressChange).toHaveBeenCalledWith(false); }); + + it('is called after delayed onPressEnd', () => { + const onLongPressChange = jest.fn(); + const ref = React.createRef(); + const element = ( + +
+ + ); + ReactDOM.render(element, container); + + ref.current.dispatchEvent(createPointerEvent('pointerdown')); + jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY); + expect(onLongPressChange).toHaveBeenCalledTimes(1); + expect(onLongPressChange).toHaveBeenCalledWith(true); + ref.current.dispatchEvent(createPointerEvent('pointerup')); + jest.advanceTimersByTime(499); + expect(onLongPressChange).toHaveBeenCalledTimes(1); + jest.advanceTimersByTime(1); + expect(onLongPressChange).toHaveBeenCalledTimes(2); + expect(onLongPressChange).toHaveBeenCalledWith(false); + }); }); describe('onLongPressShouldCancelPress', () => { @@ -430,6 +623,69 @@ describe('Event responder: Press', () => { //}); //}); + describe('delayed and multiple events', () => { + it('dispatches in the correct order', () => { + let events; + const ref = React.createRef(); + const createEventHandler = msg => () => { + events.push(msg); + }; + + const element = ( + +
+ + ); + + ReactDOM.render(element, container); + + // 1 + events = []; + ref.current.dispatchEvent(createPointerEvent('pointerdown')); + ref.current.dispatchEvent(createPointerEvent('pointerup')); + ref.current.dispatchEvent(createPointerEvent('pointerdown')); + ref.current.dispatchEvent(createPointerEvent('pointerup')); + jest.runAllTimers(); + + expect(events).toEqual([ + 'onPressStart', + 'onPressChange', + 'onPress', + 'onPressStart', + 'onPress', + 'onPressEnd', + 'onPressChange', + ]); + + // 2 + events = []; + ref.current.dispatchEvent(createPointerEvent('pointerdown')); + jest.advanceTimersByTime(250); + jest.advanceTimersByTime(500); + ref.current.dispatchEvent(createPointerEvent('pointerup')); + jest.runAllTimers(); + + expect(events).toEqual([ + 'onPressStart', + 'onPressChange', + 'onLongPress', + 'onLongPressChange', + 'onPress', + 'onPressEnd', + 'onPressChange', + 'onLongPressChange', + ]); + }); + }); + describe('nested responders', () => { it('dispatch events in the correct order', () => { const events = []; From 487f28eae42313abab12bc833127785ef0640c0e Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher Date: Fri, 5 Apr 2019 09:33:10 -0700 Subject: [PATCH 3/3] Add examples to react-events README --- packages/react-events/README.md | 157 ++++++++++++++++++++++++-------- 1 file changed, 117 insertions(+), 40 deletions(-) diff --git a/packages/react-events/README.md b/packages/react-events/README.md index b90e166156412..9173084e54a5c 100644 --- a/packages/react-events/README.md +++ b/packages/react-events/README.md @@ -3,6 +3,10 @@ *This package is experimental. It is intended for use with the experimental React events API that is not available in open source builds.* +Event components do not render a host node. They listen to native browser events +dispatched on the host node of their child and transform those events into +high-level events for applications. + ## Focus @@ -10,7 +14,20 @@ The `Focus` module responds to focus and blur events on the element it wraps. Focus events are dispatched for `mouse`, `pen`, `touch`, and `keyboard` pointer types. +```js +// Example +const TextField = (props) => ( + + + +); ``` + +```js +// Types type FocusEvent = {} ``` @@ -38,69 +55,124 @@ The `Hover` module responds to hover events on the element it wraps. Hover events are only dispatched for `mouse` pointer types. Hover begins when the pointer enters the element's bounds and ends when the pointer leaves. +```js +// Example +const Link = (props) => ( + const [ hovered, setHovered ] = useState(false); + return ( + + + + ); +); ``` + +```js +// Types type HoverEvent = {} ``` -### disabled: boolean +### delayHoverEnd: number -Disables all `Hover` events. +The duration of the delay between when hover ends and when `onHoverEnd` is +called. -### onHoverStart: (e: HoverEvent) => void +### delayHoverStart: number -Called once the element is hovered. It will not be called if the pointer leaves -the element before the `delayHoverStart` threshold is exceeded. And it will not -be called more than once before `onHoverEnd` is called. +The duration of the delay between when hover starts and when `onHoverStart` is +called. -### onHoverEnd: (e: HoverEvent) => void +### disabled: boolean -Called once the element is no longer hovered. It will be cancelled if the -pointer leaves the element before the `delayHoverStart` threshold is exceeded. +Disables all `Hover` events. ### onHoverChange: boolean => void Called when the element changes hover state (i.e., after `onHoverStart` and `onHoverEnd`). -### delayHoverStart: number +### onHoverEnd: (e: HoverEvent) => void -The duration of the delay between when hover starts and when `onHoverStart` is -called. +Called once the element is no longer hovered. It will be cancelled if the +pointer leaves the element before the `delayHoverStart` threshold is exceeded. -### delayHoverEnd: number +### onHoverStart: (e: HoverEvent) => void -The duration of the delay between when hover ends and when `onHoverEnd` is -called. +Called once the element is hovered. It will not be called if the pointer leaves +the element before the `delayHoverStart` threshold is exceeded. And it will not +be called more than once before `onHoverEnd` is called. ## Press The `Press` module responds to press events on the element it wraps. Press events are dispatched for `mouse`, `pen`, `touch`, and `keyboard` pointer types. - +Press events are only dispatched for keyboards when pressing the Enter or +Spacebar keys. If neither `onPress` nor `onLongPress` are called, this signifies +that the press ended outside of the element hit bounds (i.e., the user aborted +the press). + +```js +// Example +const Button = (props) => ( + const [ pressed, setPressed ] = useState(false); + return ( + +
+ + ); +); ``` + +```js +// Types type PressEvent = {} + +type PressOffset = { + top: number, + right: number, + bottom: number, + right: number +}; ``` -### disabled: boolean +### delayLongPress: number = 500ms -Disables all `Press` events. +The duration of a press before `onLongPress` and `onLongPressChange` are called. -### onPressStart: (e: PressEvent) => void +### delayPressEnd: number -Called once the element is pressed down. If the press is released before the -`delayPressStart` threshold is exceeded then the delay is cut short and -`onPressStart` is called immediately. +The duration of the delay between when the press ends and when `onPressEnd` is +called. -### onPressEnd: (e: PressEvent) => void +### delayPressStart: number -Called once the element is no longer pressed. It will be cancelled if the press -starts again before the `delayPressEnd` threshold is exceeded. +The duration of a delay between when the press starts and when `onPressStart` is +called. This delay is cut short (and `onPressStart` is called) if the press is +released before the threshold is exceeded. -### onPressChange: boolean => void +### disabled: boolean -Called when the element changes press state (i.e., after `onPressStart` and -`onPressEnd`). +Disables all `Press` events. ### onLongPress: (e: PressEvent) => void @@ -117,25 +189,30 @@ Determines whether calling `onPress` should be cancelled if `onLongPress` or ### onPress: (e: PressEvent) => void -Called after `onPressEnd` only if `onLongPressShouldCancelPress` returns -`false`. +Called immediately after a press is released, unless either 1) the press is +released outside the hit bounds of the element (accounting for +`pressRetentionOffset` and `TouchHitTarget`), or 2) the press was a long press, +and `onLongPress` or `onLongPressChange` props are provided, and +`onLongPressCancelsPress()` is `true`. -### delayPressStart: number +### onPressChange: boolean => void -The duration of a delay between when the press starts and when `onPressStart` is -called. This delay is cut short if the press ends released before the threshold -is exceeded. +Called when the element changes press state (i.e., after `onPressStart` and +`onPressEnd`). -### delayPressEnd: number +### onPressEnd: (e: PressEvent) => void -The duration of the delay between when the press ends and when `onPressEnd` is -called. +Called once the element is no longer pressed. If the press starts again before +the `delayPressEnd` threshold is exceeded then the delay is reset to prevent +`onPressEnd` being called during a press. -### delayLongPress: number = 500ms +### onPressStart: (e: PressEvent) => void -The duration of a press before `onLongPress` and `onLongPressChange` are called. +Called once the element is pressed down. If the press is released before the +`delayPressStart` threshold is exceeded then the delay is cut short and +`onPressStart` is called immediately. -### pressRententionOffset: { top: number, right: number, bottom: number, right: number } +### pressRententionOffset: PressOffset Defines how far the pointer (while held down) may move outside the bounds of the element before it is deactivated. Once deactivated, the pointer (still held