-
Notifications
You must be signed in to change notification settings - Fork 50.4k
Add event touch hit target hit slop logic to experimental event API #15261
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3ad044a
310c379
4555c20
4fbb80c
7ed0ed0
3fc6be2
31fbcfa
a22acf5
19a7ab0
4813feb
6d6ff27
d1afdda
da06e19
deddd3d
2783766
15f37f3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,7 +15,7 @@ import {canUseDOM} from 'shared/ExecutionEnvironment'; | |
| import warningWithoutStack from 'shared/warningWithoutStack'; | ||
| import type {ReactEventResponderEventType} from 'shared/ReactTypes'; | ||
| import type {DOMTopLevelEventType} from 'events/TopLevelEventTypes'; | ||
| import {setListenToResponderEventTypes} from '../events/DOMEventResponderSystem'; | ||
| import getElementFromTouchHitTarget from 'shared/getElementFromTouchHitTarget'; | ||
|
|
||
| import { | ||
| getValueForAttribute, | ||
|
|
@@ -85,6 +85,15 @@ import possibleStandardNames from '../shared/possibleStandardNames'; | |
| import {validateProperties as validateARIAProperties} from '../shared/ReactDOMInvalidARIAHook'; | ||
| import {validateProperties as validateInputProperties} from '../shared/ReactDOMNullInputValuePropHook'; | ||
| import {validateProperties as validateUnknownProperties} from '../shared/ReactDOMUnknownPropertyHook'; | ||
| import {setListenToResponderEventTypes} from '../events/DOMEventResponderSystem'; | ||
| import {precacheFiberNode} from './ReactDOMComponentTree'; | ||
| import type { | ||
| Container, | ||
| HostContext, | ||
| HostContextDev, | ||
| HostContextProd, | ||
| Props, | ||
| } from './ReactDOMHostConfig'; | ||
|
|
||
| import {enableEventAPI} from 'shared/ReactFeatureFlags'; | ||
|
|
||
|
|
@@ -1343,3 +1352,151 @@ export function listenToEventResponderEventTypes( | |
| if (enableEventAPI) { | ||
| setListenToResponderEventTypes(listenToEventResponderEventTypes); | ||
| } | ||
|
|
||
| const emptyObject = {}; | ||
|
|
||
| export function createEventTargetHitSlop( | ||
| left: number | void | null, | ||
| right: number | void | null, | ||
| top: number | void | null, | ||
| bottom: number | void | null, | ||
| rootContainerInstance: Element | Document, | ||
| parentNamespace: string, | ||
| ): Element { | ||
| const hitSlopElement = createElement( | ||
| 'div', | ||
| emptyObject, | ||
| rootContainerInstance, | ||
| parentNamespace, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This won't work if the parentNamespace isn't HTML so you should throw if you don't intend to support other namespaces. |
||
| ); | ||
| const hitSlopElementStyle = ((hitSlopElement: any): HTMLElement).style; | ||
|
|
||
| hitSlopElementStyle.position = 'absolute'; | ||
| if (top != null) { | ||
| hitSlopElementStyle.top = `-${top}px`; | ||
| } | ||
| if (left != null) { | ||
| hitSlopElementStyle.left = `-${left}px`; | ||
| } | ||
| if (right != null) { | ||
| hitSlopElementStyle.right = `-${right}px`; | ||
| } | ||
| if (bottom != null) { | ||
| hitSlopElementStyle.bottom = `-${bottom}px`; | ||
| } | ||
| return hitSlopElement; | ||
| } | ||
|
|
||
| export function diffAndUpdateEventTargetHitSlop( | ||
| left: number | null | void, | ||
| right: number | null | void, | ||
| top: number | null | void, | ||
| bottom: number | null | void, | ||
| lastProps: Props, | ||
| hitSlopElement: HTMLElement, | ||
| ): void { | ||
| const hitSlopElementStyle = hitSlopElement.style; | ||
| if (lastProps.left !== left) { | ||
| if (left == null) { | ||
| hitSlopElementStyle.left = ''; | ||
| } else { | ||
| hitSlopElementStyle.left = `-${left}px`; | ||
| } | ||
| } | ||
| if (lastProps.right !== right) { | ||
| if (right == null) { | ||
| hitSlopElementStyle.right = ''; | ||
| } else { | ||
| hitSlopElementStyle.right = `-${right}px`; | ||
| } | ||
| } | ||
| if (lastProps.top !== top) { | ||
| if (top == null) { | ||
| hitSlopElementStyle.top = ''; | ||
| } else { | ||
| hitSlopElementStyle.top = `-${top}px`; | ||
| } | ||
| } | ||
| if (lastProps.bottom !== bottom) { | ||
| if (bottom == null) { | ||
| hitSlopElementStyle.bottom = ''; | ||
| } else { | ||
| hitSlopElementStyle.bottom = `-${bottom}px`; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| export function handleEventTouchHitTarget( | ||
| lastProps: Props, | ||
| nextProps: Props, | ||
| rootContainerInstance: Container, | ||
| internalInstanceHandle: Object, | ||
| hostContext: HostContext, | ||
| ): void { | ||
| if (enableEventAPI) { | ||
| // Validates that there is a single element | ||
| const node = getElementFromTouchHitTarget(internalInstanceHandle); | ||
| if (node !== null) { | ||
| let parentNamespace: string; | ||
| if (__DEV__) { | ||
| const hostContextDev = ((hostContext: any): HostContextDev); | ||
| parentNamespace = hostContextDev.namespace; | ||
| warning( | ||
| parentNamespace === HTML_NAMESPACE, | ||
| 'An event touch hit target was used in an unsupported DOM namespace. ' + | ||
| 'Ensure the touch hit target is used in a HTML namespace.', | ||
| ); | ||
| } else { | ||
| parentNamespace = ((hostContext: any): HostContextProd); | ||
| } | ||
|
|
||
| const element = ((node: any): HTMLElement); | ||
| // We update the event target state node to be that of the element. | ||
| // We can then diff this entry to determine if we need to add the | ||
| // hit slop element, or change the dimensions of the hit slop. | ||
| const lastElement = internalInstanceHandle.stateNode; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We shouldn't drill into fibers at this level. We assume that host configs don't have access to fibers. These are supposed to be opaque types.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wasn't aware of this constraint. I can re-work it so that |
||
| const left = nextProps.left; | ||
| const right = nextProps.right; | ||
| const top = nextProps.top; | ||
| const bottom = nextProps.bottom; | ||
|
|
||
| if (lastElement !== element) { | ||
| if (left == null && right == null && top == null && bottom == null) { | ||
| return; | ||
| } | ||
| internalInstanceHandle.stateNode = element; | ||
| const hitSlopElement = createEventTargetHitSlop( | ||
| left, | ||
| right, | ||
| top, | ||
| bottom, | ||
| rootContainerInstance, | ||
| parentNamespace, | ||
| ); | ||
| // We need to make the target relative so we can make the hit slop | ||
| // element inside it absolutely position around the target. | ||
| // TODO add a dev check for the computed style and warn if it isn't | ||
| // compatible. | ||
| element.style.position = 'relative'; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @necolas We need to come up with a good solution for this. We should mark this in the umbrella issue and track it as to how we can validate it and make sure stacking order etc is good.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This won't be resilient to the styles changing on the element. Updates to it might override this.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this was a bit talking point with @necolas too. There's no cheap way to get around having to do a computed lookup to find what the position is. Do you have any ideas on how we might solve this problem? |
||
| element.appendChild(hitSlopElement); | ||
| precacheFiberNode(internalInstanceHandle, hitSlopElement); | ||
| } else { | ||
| // We appended the hit slop to the element, so it will always be the last child. | ||
| // TODO add a DEV validation warning to ensure this remains correct. | ||
| const hitSlopElement = element.lastChild; | ||
|
|
||
| // Diff and update the sides of the hit slop | ||
| if (lastProps !== nextProps) { | ||
| diffAndUpdateEventTargetHitSlop( | ||
| left, | ||
| right, | ||
| top, | ||
| bottom, | ||
| lastProps, | ||
| ((hitSlopElement: any): HTMLElement), | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.