@samitouri / QOS-React / commits / a41b217708

Add additional event API responder surfaces (#15248)

* Add rest of event modules + small fixes

Dominic Gannaway committed Mar 29, 2019 at 10:31 UTC a41b217708fcf2a8a325c83f89a480b2ebd91ce6
14 files changed +659 -14
packages/events/EventTypes.js
+7 -2
@@ -9,6 +9,7 @@
9
10 import SyntheticEvent from 'events/SyntheticEvent';
11 import type {AnyNativeEvent} from 'events/PluginModuleType';
12 +import type {ReactEventResponderEventType} from 'shared/ReactTypes';
13
14 export type EventResponderContext = {
15 event: AnyNativeEvent,
@@ -30,8 +31,12 @@ export type EventResponderContext = {
31 isTargetOwned: EventTarget => boolean,
32 isTargetWithinEventComponent: EventTarget => boolean,
33 isPositionWithinTouchHitTarget: (x: number, y: number) => boolean,
33 - addRootEventTypes: (rootEventTypes: Array<string>) => void,
34 - removeRootEventTypes: (rootEventTypes: Array<string>) => void,
34 + addRootEventTypes: (
35 + rootEventTypes: Array<ReactEventResponderEventType>,
36 + ) => void,
37 + removeRootEventTypes: (
38 + rootEventTypes: Array<ReactEventResponderEventType>,
39 + ) => void,
40 requestOwnership: (target: EventTarget | null) => boolean,
41 releaseOwnership: (target: EventTarget | null) => boolean,
42 };
packages/react-dom/src/events/DOMEventResponderSystem.js
+28 -6
@@ -44,6 +44,7 @@ const targetEventTypeCached: Map<
44 Array<ReactEventResponderEventType>,
45 Set<DOMTopLevelEventType>,
46 > = new Map();
47 +const targetOwnership: Map<EventTarget, Fiber> = new Map();
48
49 type EventListener = (event: SyntheticEvent) => void;
50
@@ -204,16 +205,37 @@ DOMEventResponderContext.prototype.isPositionWithinTouchHitTarget = function() {
205 // TODO
206 };
207
207 -DOMEventResponderContext.prototype.isTargetOwned = function() {
208 - // TODO
208 +DOMEventResponderContext.prototype.isTargetOwned = function(
209 + targetElement: Element | Node,
210 +): boolean {
211 + const targetDoc = targetElement.ownerDocument;
212 + return targetOwnership.has(targetDoc);
213 };
214
211 -DOMEventResponderContext.prototype.requestOwnership = function() {
212 - // TODO
215 +DOMEventResponderContext.prototype.requestOwnership = function(
216 + targetElement: Element | Node,
217 +): boolean {
218 + const targetDoc = targetElement.ownerDocument;
219 + if (targetOwnership.has(targetDoc)) {
220 + return false;
221 + }
222 + targetOwnership.set(targetDoc, this._fiber);
223 + return true;
224 };
225
215 -DOMEventResponderContext.prototype.releaseOwnership = function() {
216 - // TODO
226 +DOMEventResponderContext.prototype.releaseOwnership = function(
227 + targetElement: Element | Node,
228 +): boolean {
229 + const targetDoc = targetElement.ownerDocument;
230 + if (!targetOwnership.has(targetDoc)) {
231 + return false;
232 + }
233 + const owner = targetOwnership.get(targetDoc);
234 + if (owner === this._fiber || owner === this._fiber.alternate) {
235 + targetOwnership.delete(targetDoc);
236 + return true;
237 + }
238 + return false;
239 };
240
241 function getTargetEventTypes(
packages/react-events/drag.js new
+14
@@ -0,0 +1,14 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +'use strict';
11 +
12 +const Drag = require('./src/Drag');
13 +
14 +module.exports = Drag.default || Drag;
packages/react-events/focus.js new
+14
@@ -0,0 +1,14 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +'use strict';
11 +
12 +const Focus = require('./src/Focus');
13 +
14 +module.exports = Focus.default || Focus;
packages/react-events/npm/drag.js new
+7
@@ -0,0 +1,7 @@
1 +'use strict';
2 +
3 +if (process.env.NODE_ENV === 'production') {
4 + module.exports = require('./cjs/react-events-drag.production.min.js');
5 +} else {
6 + module.exports = require('./cjs/react-events-drag.development.js');
7 +}
packages/react-events/npm/focus.js new
+7
@@ -0,0 +1,7 @@
1 +'use strict';
2 +
3 +if (process.env.NODE_ENV === 'production') {
4 + module.exports = require('./cjs/react-events-focus.production.min.js');
5 +} else {
6 + module.exports = require('./cjs/react-events-focus.development.js');
7 +}
packages/react-events/npm/swipe.js new
+7
@@ -0,0 +1,7 @@
1 +'use strict';
2 +
3 +if (process.env.NODE_ENV === 'production') {
4 + module.exports = require('./cjs/react-events-swipe.production.min.js');
5 +} else {
6 + module.exports = require('./cjs/react-events-swipe.development.js');
7 +}
packages/react-events/package.json
+5
@@ -12,6 +12,11 @@
12 "LICENSE",
13 "README.md",
14 "press.js",
15 + "hover.js",
16 + "focus.js",
17 + "swipe.js",
18 + "drag.js",
19 + "index.js",
20 "build-info.json",
21 "cjs/",
22 "umd/"
packages/react-events/src/Drag.js new
+187
@@ -0,0 +1,187 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {EventResponderContext} from 'events/EventTypes';
11 +import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
12 +
13 +const targetEventTypes = ['pointerdown', 'pointercancel'];
14 +const rootEventTypes = ['pointerup', {name: 'pointermove', passive: false}];
15 +
16 +type DragState = {
17 + dragTarget: null | EventTarget,
18 + isPointerDown: boolean,
19 + isDragging: boolean,
20 + startX: number,
21 + startY: number,
22 + x: number,
23 + y: number,
24 +};
25 +
26 +// In the case we don't have PointerEvents (Safari), we listen to touch events
27 +// too
28 +if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
29 + targetEventTypes.push('touchstart', 'touchend', 'mousedown', 'touchcancel');
30 + rootEventTypes.push('mouseup', 'mousemove', {
31 + name: 'touchmove',
32 + passive: false,
33 + });
34 +}
35 +
36 +function dispatchDragEvent(
37 + context: EventResponderContext,
38 + name: string,
39 + listener: (e: Object) => void,
40 + state: DragState,
41 + discrete: boolean,
42 + eventData?: {
43 + diffX: number,
44 + diffY: number,
45 + },
46 +): void {
47 + context.dispatchEvent(name, listener, state.dragTarget, discrete, eventData);
48 +}
49 +
50 +const DragResponder = {
51 + targetEventTypes,
52 + createInitialState(): DragState {
53 + return {
54 + dragTarget: null,
55 + isPointerDown: false,
56 + isDragging: false,
57 + startX: 0,
58 + startY: 0,
59 + x: 0,
60 + y: 0,
61 + };
62 + },
63 + handleEvent(
64 + context: EventResponderContext,
65 + props: Object,
66 + state: DragState,
67 + ): void {
68 + const {eventTarget, eventType, event} = context;
69 +
70 + switch (eventType) {
71 + case 'touchstart':
72 + case 'mousedown':
73 + case 'pointerdown': {
74 + if (!state.isDragging) {
75 + const obj =
76 + eventType === 'touchstart' ? (event: any).changedTouches[0] : event;
77 + const x = (state.startX = (obj: any).screenX);
78 + const y = (state.startY = (obj: any).screenY);
79 + state.x = x;
80 + state.y = y;
81 + state.dragTarget = eventTarget;
82 + state.isPointerDown = true;
83 + context.addRootEventTypes(rootEventTypes);
84 + }
85 + break;
86 + }
87 + case 'touchmove':
88 + case 'mousemove':
89 + case 'pointermove': {
90 + if (context.isPassive()) {
91 + return;
92 + }
93 + if (state.isPointerDown) {
94 + const obj =
95 + eventType === 'touchmove' ? (event: any).changedTouches[0] : event;
96 + const x = (obj: any).screenX;
97 + const y = (obj: any).screenY;
98 + state.x = x;
99 + state.y = y;
100 + if (!state.isDragging && x !== state.startX && y !== state.startY) {
101 + let shouldEnableDragging = true;
102 +
103 + if (
104 + props.onShouldClaimOwnership &&
105 + props.onShouldClaimOwnership()
106 + ) {
107 + shouldEnableDragging = context.requestOwnership(state.dragTarget);
108 + }
109 + if (shouldEnableDragging) {
110 + state.isDragging = true;
111 + if (props.onDragChange) {
112 + const dragChangeEventListener = () => {
113 + props.onDragChange(true);
114 + };
115 + context.dispatchEvent(
116 + 'dragchange',
117 + dragChangeEventListener,
118 + state.dragTarget,
119 + true,
120 + );
121 + }
122 + } else {
123 + state.dragTarget = null;
124 + state.isPointerDown = false;
125 + context.removeRootEventTypes(rootEventTypes);
126 + }
127 + } else {
128 + if (props.onDragMove) {
129 + const eventData = {
130 + diffX: x - state.startX,
131 + diffY: y - state.startY,
132 + };
133 + dispatchDragEvent(
134 + context,
135 + 'dragmove',
136 + props.onDragMove,
137 + state,
138 + false,
139 + eventData,
140 + );
141 + }
142 + (event: any).preventDefault();
143 + }
144 + }
145 + break;
146 + }
147 + case 'pointercancel':
148 + case 'touchcancel':
149 + case 'touchend':
150 + case 'mouseup':
151 + case 'pointerup': {
152 + if (state.isDragging) {
153 + if (props.onShouldClaimOwnership) {
154 + context.releaseOwnership(state.dragTarget);
155 + }
156 + if (props.onDragEnd) {
157 + dispatchDragEvent(context, 'dragend', props.onDragEnd, state, true);
158 + }
159 + if (props.onDragChange) {
160 + const dragChangeEventListener = () => {
161 + props.onDragChange(false);
162 + };
163 + context.dispatchEvent(
164 + 'dragchange',
165 + dragChangeEventListener,
166 + state.dragTarget,
167 + true,
168 + );
169 + }
170 + state.isDragging = false;
171 + }
172 + if (state.isPointerDown) {
173 + state.dragTarget = null;
174 + state.isPointerDown = false;
175 + context.removeRootEventTypes(rootEventTypes);
176 + }
177 + break;
178 + }
179 + }
180 + },
181 +};
182 +
183 +export default {
184 + $$typeof: REACT_EVENT_COMPONENT_TYPE,
185 + props: null,
186 + responder: DragResponder,
187 +};
packages/react-events/src/Focus.js new
+101
@@ -0,0 +1,101 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {EventResponderContext} from 'events/EventTypes';
11 +import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
12 +
13 +const targetEventTypes = [
14 + {name: 'focus', passive: true, capture: true},
15 + {name: 'blur', passive: true, capture: true},
16 +];
17 +
18 +type FocusState = {
19 + isFocused: boolean,
20 +};
21 +
22 +function dispatchFocusInEvents(context: EventResponderContext, props: Object) {
23 + const {event, eventTarget} = context;
24 + if (context.isTargetWithinEventComponent((event: any).relatedTarget)) {
25 + return;
26 + }
27 + if (props.onFocus) {
28 + context.dispatchEvent('focus', props.onFocus, eventTarget, true);
29 + }
30 + if (props.onFocusChange) {
31 + const focusChangeEventListener = () => {
32 + props.onFocusChange(true);
33 + };
34 + context.dispatchEvent(
35 + 'focuschange',
36 + focusChangeEventListener,
37 + eventTarget,
38 + true,
39 + );
40 + }
41 +}
42 +
43 +function dispatchFocusOutEvents(context: EventResponderContext, props: Object) {
44 + const {event, eventTarget} = context;
45 + if (context.isTargetWithinEventComponent((event: any).relatedTarget)) {
46 + return;
47 + }
48 + if (props.onBlur) {
49 + context.dispatchEvent('blur', props.onBlur, eventTarget, true);
50 + }
51 + if (props.onFocusChange) {
52 + const focusChangeEventListener = () => {
53 + props.onFocusChange(false);
54 + };
55 + context.dispatchEvent(
56 + 'focuschange',
57 + focusChangeEventListener,
58 + eventTarget,
59 + true,
60 + );
61 + }
62 +}
63 +
64 +const FocusResponder = {
65 + targetEventTypes,
66 + createInitialState(): FocusState {
67 + return {
68 + isFocused: false,
69 + };
70 + },
71 + handleEvent(
72 + context: EventResponderContext,
73 + props: Object,
74 + state: FocusState,
75 + ): void {
76 + const {eventTarget, eventType} = context;
77 +
78 + switch (eventType) {
79 + case 'focus': {
80 + if (!state.isFocused && !context.isTargetOwned(eventTarget)) {
81 + dispatchFocusInEvents(context, props);
82 + state.isFocused = true;
83 + }
84 + break;
85 + }
86 + case 'blur': {
87 + if (state.isFocused) {
88 + dispatchFocusOutEvents(context, props);
89 + state.isFocused = false;
90 + }
91 + break;
92 + }
93 + }
94 + },
95 +};
96 +
97 +export default {
98 + $$typeof: REACT_EVENT_COMPONENT_TYPE,
99 + props: null,
100 + responder: FocusResponder,
101 +};
packages/react-events/src/Press.js
+6 -6
@@ -17,13 +17,13 @@ const targetEventTypes = [
17 'pointercancel',
18 'contextmenu',
19 ];
20 -const rootEventTypes = ['pointerup', 'scroll'];
20 +const rootEventTypes = [{name: 'pointerup', passive: false}, 'scroll'];
21
22 // In the case we don't have PointerEvents (Safari), we listen to touch events
23 // too
24 if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
25 targetEventTypes.push('touchstart', 'touchend', 'mousedown', 'touchcancel');
26 - rootEventTypes.push('mouseup');
26 + rootEventTypes.push({name: 'mouseup', passive: false});
27 }
28
29 type PressState = {
@@ -169,11 +169,11 @@ const PressResponder = {
169 // Android can show previous of anchor tags that requires working
170 // with click rather than touch events (and mouse down/up).
171 if (!isAnchorTagElement(eventTarget)) {
172 - keyPressEventListener = (e, key) => {
172 + keyPressEventListener = e => {
173 if (!e.isDefaultPrevented() && !e.nativeEvent.defaultPrevented) {
174 e.preventDefault();
175 state.defaultPrevented = true;
176 - props.onPress(e, key);
176 + props.onPress(e);
177 }
178 };
179 }
@@ -279,8 +279,8 @@ const PressResponder = {
279 props.onPress &&
280 !(state.isLongPressed && props.longPressCancelsPress)
281 ) {
282 - const pressEventListener = (e, key) => {
283 - props.onPress(e, key);
282 + const pressEventListener = e => {
283 + props.onPress(e);
284 if (e.nativeEvent.defaultPrevented) {
285 state.defaultPrevented = true;
286 }
packages/react-events/src/Swipe.js new
+217
@@ -0,0 +1,217 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {EventResponderContext} from 'events/EventTypes';
11 +import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
12 +
13 +const targetEventTypes = ['pointerdown', 'pointercancel'];
14 +const rootEventTypes = ['pointerup', {name: 'pointermove', passive: false}];
15 +
16 +// In the case we don't have PointerEvents (Safari), we listen to touch events
17 +// too
18 +if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
19 + targetEventTypes.push('touchstart', 'touchend', 'mousedown', 'touchcancel');
20 + rootEventTypes.push('mouseup', 'mousemove', {
21 + name: 'touchmove',
22 + passive: false,
23 + });
24 +}
25 +
26 +function dispatchSwipeEvent(
27 + context: EventResponderContext,
28 + name: string,
29 + listener: (e: Object) => void,
30 + state: SwipeState,
31 + discrete: boolean,
32 + eventData?: {
33 + diffX: number,
34 + diffY: number,
35 + },
36 +) {
37 + context.dispatchEvent(name, listener, state.swipeTarget, discrete, eventData);
38 +}
39 +
40 +type SwipeState = {
41 + direction: number,
42 + isSwiping: boolean,
43 + lastDirection: number,
44 + startX: number,
45 + startY: number,
46 + touchId: null | number,
47 + swipeTarget: null | EventTarget,
48 + x: number,
49 + y: number,
50 +};
51 +
52 +const SwipeResponder = {
53 + targetEventTypes,
54 + createInitialState(): SwipeState {
55 + return {
56 + direction: 0,
57 + isSwiping: false,
58 + lastDirection: 0,
59 + startX: 0,
60 + startY: 0,
61 + touchId: null,
62 + swipeTarget: null,
63 + x: 0,
64 + y: 0,
65 + };
66 + },
67 + handleEvent(
68 + context: EventResponderContext,
69 + props: Object,
70 + state: SwipeState,
71 + ): void {
72 + const {eventTarget, eventType, event} = context;
73 +
74 + switch (eventType) {
75 + case 'touchstart':
76 + case 'mousedown':
77 + case 'pointerdown': {
78 + if (!state.isSwiping && !context.isTargetOwned(eventTarget)) {
79 + let obj = event;
80 + if (eventType === 'touchstart') {
81 + obj = (event: any).targetTouches[0];
82 + state.touchId = obj.identifier;
83 + }
84 + const x = (obj: any).screenX;
85 + const y = (obj: any).screenY;
86 +
87 + let shouldEnableSwiping = true;
88 +
89 + if (props.onShouldClaimOwnership && props.onShouldClaimOwnership()) {
90 + shouldEnableSwiping = context.requestOwnership(eventTarget);
91 + }
92 + if (shouldEnableSwiping) {
93 + state.isSwiping = true;
94 + state.startX = x;
95 + state.startY = y;
96 + state.x = x;
97 + state.y = y;
98 + state.swipeTarget = eventTarget;
99 + context.addRootEventTypes(rootEventTypes);
100 + } else {
101 + state.touchId = null;
102 + }
103 + }
104 + break;
105 + }
106 + case 'touchmove':
107 + case 'mousemove':
108 + case 'pointermove': {
109 + if (context.isPassive()) {
110 + return;
111 + }
112 + if (state.isSwiping) {
113 + let obj = null;
114 + if (eventType === 'touchmove') {
115 + const targetTouches = (event: any).targetTouches;
116 + for (let i = 0; i < targetTouches.length; i++) {
117 + if (state.touchId === targetTouches[i].identifier) {
118 + obj = targetTouches[i];
119 + break;
120 + }
121 + }
122 + } else {
123 + obj = event;
124 + }
125 + if (obj === null) {
126 + state.isSwiping = false;
127 + state.swipeTarget = null;
128 + state.touchId = null;
129 + context.removeRootEventTypes(rootEventTypes);
130 + return;
131 + }
132 + const x = (obj: any).screenX;
133 + const y = (obj: any).screenY;
134 + if (x < state.x && props.onSwipeLeft) {
135 + state.direction = 3;
136 + } else if (x > state.x && props.onSwipeRight) {
137 + state.direction = 1;
138 + }
139 + state.x = x;
140 + state.y = y;
141 + if (props.onSwipeMove) {
142 + const eventData = {
143 + diffX: x - state.startX,
144 + diffY: y - state.startY,
145 + };
146 + dispatchSwipeEvent(
147 + context,
148 + 'swipemove',
149 + props.onSwipeMove,
150 + state,
151 + false,
152 + eventData,
153 + );
154 + (event: any).preventDefault();
155 + }
156 + }
157 + break;
158 + }
159 + case 'pointercancel':
160 + case 'touchcancel':
161 + case 'touchend':
162 + case 'mouseup':
163 + case 'pointerup': {
164 + if (state.isSwiping) {
165 + if (state.x === state.startX && state.y === state.startY) {
166 + return;
167 + }
168 + if (props.onShouldClaimOwnership) {
169 + context.releaseOwnership(state.swipeTarget);
170 + }
171 + const direction = state.direction;
172 + const lastDirection = state.lastDirection;
173 + if (direction !== lastDirection) {
174 + if (props.onSwipeLeft && direction === 3) {
175 + dispatchSwipeEvent(
176 + context,
177 + 'swipeleft',
178 + props.onSwipeLeft,
179 + state,
180 + true,
181 + );
182 + } else if (props.onSwipeRight && direction === 1) {
183 + dispatchSwipeEvent(
184 + context,
185 + 'swiperight',
186 + props.onSwipeRight,
187 + state,
188 + true,
189 + );
190 + }
191 + }
192 + if (props.onSwipeEnd) {
193 + dispatchSwipeEvent(
194 + context,
195 + 'swipeend',
196 + props.onSwipeEnd,
197 + state,
198 + true,
199 + );
200 + }
201 + state.lastDirection = direction;
202 + state.isSwiping = false;
203 + state.swipeTarget = null;
204 + state.touchId = null;
205 + context.removeRootEventTypes(rootEventTypes);
206 + }
207 + break;
208 + }
209 + }
210 + },
211 +};
212 +
213 +export default {
214 + $$typeof: REACT_EVENT_COMPONENT_TYPE,
215 + props: null,
216 + responder: SwipeResponder,
217 +};
packages/react-events/swipe.js new
+14
@@ -0,0 +1,14 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +'use strict';
11 +
12 +const Swipe = require('./src/Swipe');
13 +
14 +module.exports = Swipe.default || Swipe;
scripts/rollup/bundles.js
+45
@@ -507,6 +507,51 @@ const bundles = [
507 global: 'ReactEventsHover',
508 externals: [],
509 },
510 +
511 + {
512 + bundleTypes: [
513 + UMD_DEV,
514 + UMD_PROD,
515 + NODE_DEV,
516 + NODE_PROD,
517 + FB_WWW_DEV,
518 + FB_WWW_PROD,
519 + ],
520 + moduleType: NON_FIBER_RENDERER,
521 + entry: 'react-events/focus',
522 + global: 'ReactEventsFocus',
523 + externals: [],
524 + },
525 +
526 + {
527 + bundleTypes: [
528 + UMD_DEV,
529 + UMD_PROD,
530 + NODE_DEV,
531 + NODE_PROD,
532 + FB_WWW_DEV,
533 + FB_WWW_PROD,
534 + ],
535 + moduleType: NON_FIBER_RENDERER,
536 + entry: 'react-events/swipe',
537 + global: 'ReactEventsSwipe',
538 + externals: [],
539 + },
540 +
541 + {
542 + bundleTypes: [
543 + UMD_DEV,
544 + UMD_PROD,
545 + NODE_DEV,
546 + NODE_PROD,
547 + FB_WWW_DEV,
548 + FB_WWW_PROD,
549 + ],
550 + moduleType: NON_FIBER_RENDERER,
551 + entry: 'react-events/drag',
552 + global: 'ReactEventsDrag',
553 + externals: [],
554 + },
555 ];
556
557 // Based on deep-freeze by substack (public domain)