@tamagui/react-native-use-responder-events 1.0.1-beta.194
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/PressResponder.js +373 -0
- package/dist/cjs/PressResponder.js.map +7 -0
- package/dist/cjs/ResponderSystem.js +365 -0
- package/dist/cjs/ResponderSystem.js.map +7 -0
- package/dist/cjs/ResponderTouchHistoryStore.js +184 -0
- package/dist/cjs/ResponderTouchHistoryStore.js.map +7 -0
- package/dist/cjs/createResponderEvent.js +152 -0
- package/dist/cjs/createResponderEvent.js.map +7 -0
- package/dist/cjs/index.js +19 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/types.js +98 -0
- package/dist/cjs/types.js.map +7 -0
- package/dist/cjs/useResponderEvents.js +76 -0
- package/dist/cjs/useResponderEvents.js.map +7 -0
- package/dist/cjs/utils.js +169 -0
- package/dist/cjs/utils.js.map +7 -0
- package/dist/esm/PressResponder.js +349 -0
- package/dist/esm/PressResponder.js.map +7 -0
- package/dist/esm/ResponderSystem.js +338 -0
- package/dist/esm/ResponderSystem.js.map +7 -0
- package/dist/esm/ResponderTouchHistoryStore.js +160 -0
- package/dist/esm/ResponderTouchHistoryStore.js.map +7 -0
- package/dist/esm/createResponderEvent.js +130 -0
- package/dist/esm/createResponderEvent.js.map +7 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +7 -0
- package/dist/esm/types.js +55 -0
- package/dist/esm/types.js.map +7 -0
- package/dist/esm/useResponderEvents.js +45 -0
- package/dist/esm/useResponderEvents.js.map +7 -0
- package/dist/esm/utils.js +137 -0
- package/dist/esm/utils.js.map +7 -0
- package/package.json +30 -0
- package/src/ResponderSystem.ts +531 -0
- package/src/ResponderTouchHistoryStore.ts +213 -0
- package/src/createResponderEvent.ts +187 -0
- package/src/index.ts +1 -0
- package/src/types.ts +78 -0
- package/src/useResponderEvents.ts +72 -0
- package/src/utils.ts +192 -0
- package/types/PressResponder.d.ts +92 -0
- package/types/ResponderSystem.d.ts +47 -0
- package/types/ResponderTouchHistoryStore.d.ts +37 -0
- package/types/createResponderEvent.d.ts +36 -0
- package/types/index.d.ts +2 -0
- package/types/types.d.ts +52 -0
- package/types/useResponderEvents.d.ts +9 -0
- package/types/utils.d.ts +38 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and 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
|
+
|
|
8
|
+
import type { Touch, TouchEvent } from './types'
|
|
9
|
+
import { isEndish, isMoveish, isStartish } from './types'
|
|
10
|
+
|
|
11
|
+
type TouchRecord = {
|
|
12
|
+
currentPageX: number
|
|
13
|
+
currentPageY: number
|
|
14
|
+
currentTimeStamp: number
|
|
15
|
+
previousPageX: number
|
|
16
|
+
previousPageY: number
|
|
17
|
+
previousTimeStamp: number
|
|
18
|
+
startPageX: number
|
|
19
|
+
startPageY: number
|
|
20
|
+
startTimeStamp: number
|
|
21
|
+
touchActive: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type TouchHistory = {
|
|
25
|
+
indexOfSingleActiveTouch: number
|
|
26
|
+
mostRecentTimeStamp: number
|
|
27
|
+
numberActiveTouches: number
|
|
28
|
+
touchBank: Array<TouchRecord>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Tracks the position and time of each active touch by `touch.identifier`. We
|
|
33
|
+
* should typically only see IDs in the range of 1-20 because IDs get recycled
|
|
34
|
+
* when touches end and start again.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
const MAX_TOUCH_BANK = 20
|
|
38
|
+
|
|
39
|
+
function timestampForTouch(touch: Touch): number {
|
|
40
|
+
// The legacy internal implementation provides "timeStamp", which has been
|
|
41
|
+
// renamed to "timestamp".
|
|
42
|
+
return touch['timeStamp'] || touch.timestamp
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* TODO: Instead of making gestures recompute filtered velocity, we could
|
|
47
|
+
* include a built in velocity computation that can be reused globally.
|
|
48
|
+
*/
|
|
49
|
+
function createTouchRecord(touch: Touch): TouchRecord {
|
|
50
|
+
return {
|
|
51
|
+
touchActive: true,
|
|
52
|
+
startPageX: touch.pageX,
|
|
53
|
+
startPageY: touch.pageY,
|
|
54
|
+
startTimeStamp: timestampForTouch(touch),
|
|
55
|
+
currentPageX: touch.pageX,
|
|
56
|
+
currentPageY: touch.pageY,
|
|
57
|
+
currentTimeStamp: timestampForTouch(touch),
|
|
58
|
+
previousPageX: touch.pageX,
|
|
59
|
+
previousPageY: touch.pageY,
|
|
60
|
+
previousTimeStamp: timestampForTouch(touch),
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function resetTouchRecord(touchRecord: TouchRecord, touch: Touch): void {
|
|
65
|
+
touchRecord.touchActive = true
|
|
66
|
+
touchRecord.startPageX = touch.pageX
|
|
67
|
+
touchRecord.startPageY = touch.pageY
|
|
68
|
+
touchRecord.startTimeStamp = timestampForTouch(touch)
|
|
69
|
+
touchRecord.currentPageX = touch.pageX
|
|
70
|
+
touchRecord.currentPageY = touch.pageY
|
|
71
|
+
touchRecord.currentTimeStamp = timestampForTouch(touch)
|
|
72
|
+
touchRecord.previousPageX = touch.pageX
|
|
73
|
+
touchRecord.previousPageY = touch.pageY
|
|
74
|
+
touchRecord.previousTimeStamp = timestampForTouch(touch)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getTouchIdentifier({ identifier }: Touch): number {
|
|
78
|
+
if (identifier == null) {
|
|
79
|
+
// eslint-disable-next-line no-console
|
|
80
|
+
console.error('Touch object is missing identifier.')
|
|
81
|
+
}
|
|
82
|
+
if (process.env.NODE_ENV === 'development') {
|
|
83
|
+
if (identifier > MAX_TOUCH_BANK) {
|
|
84
|
+
// eslint-disable-next-line no-console
|
|
85
|
+
console.error(
|
|
86
|
+
'Touch identifier %s is greater than maximum supported %s which causes ' +
|
|
87
|
+
'performance issues backfilling array locations for all of the indices.',
|
|
88
|
+
identifier,
|
|
89
|
+
MAX_TOUCH_BANK
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return identifier
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function recordTouchStart(touch: Touch, touchHistory): void {
|
|
97
|
+
const identifier = getTouchIdentifier(touch)
|
|
98
|
+
const touchRecord = touchHistory.touchBank[identifier]
|
|
99
|
+
if (touchRecord) {
|
|
100
|
+
resetTouchRecord(touchRecord, touch)
|
|
101
|
+
} else {
|
|
102
|
+
touchHistory.touchBank[identifier] = createTouchRecord(touch)
|
|
103
|
+
}
|
|
104
|
+
touchHistory.mostRecentTimeStamp = timestampForTouch(touch)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function recordTouchMove(touch: Touch, touchHistory): void {
|
|
108
|
+
const touchRecord = touchHistory.touchBank[getTouchIdentifier(touch)]
|
|
109
|
+
if (touchRecord) {
|
|
110
|
+
touchRecord.touchActive = true
|
|
111
|
+
touchRecord.previousPageX = touchRecord.currentPageX
|
|
112
|
+
touchRecord.previousPageY = touchRecord.currentPageY
|
|
113
|
+
touchRecord.previousTimeStamp = touchRecord.currentTimeStamp
|
|
114
|
+
touchRecord.currentPageX = touch.pageX
|
|
115
|
+
touchRecord.currentPageY = touch.pageY
|
|
116
|
+
touchRecord.currentTimeStamp = timestampForTouch(touch)
|
|
117
|
+
touchHistory.mostRecentTimeStamp = timestampForTouch(touch)
|
|
118
|
+
} else {
|
|
119
|
+
console.warn(
|
|
120
|
+
'Cannot record touch move without a touch start.\n',
|
|
121
|
+
`Touch Move: ${printTouch(touch)}\n`,
|
|
122
|
+
`Touch Bank: ${printTouchBank(touchHistory)}`
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function recordTouchEnd(touch: Touch, touchHistory): void {
|
|
128
|
+
const touchRecord = touchHistory.touchBank[getTouchIdentifier(touch)]
|
|
129
|
+
if (touchRecord) {
|
|
130
|
+
touchRecord.touchActive = false
|
|
131
|
+
touchRecord.previousPageX = touchRecord.currentPageX
|
|
132
|
+
touchRecord.previousPageY = touchRecord.currentPageY
|
|
133
|
+
touchRecord.previousTimeStamp = touchRecord.currentTimeStamp
|
|
134
|
+
touchRecord.currentPageX = touch.pageX
|
|
135
|
+
touchRecord.currentPageY = touch.pageY
|
|
136
|
+
touchRecord.currentTimeStamp = timestampForTouch(touch)
|
|
137
|
+
touchHistory.mostRecentTimeStamp = timestampForTouch(touch)
|
|
138
|
+
} else {
|
|
139
|
+
console.warn(
|
|
140
|
+
'Cannot record touch end without a touch start.\n',
|
|
141
|
+
`Touch End: ${printTouch(touch)}\n`,
|
|
142
|
+
`Touch Bank: ${printTouchBank(touchHistory)}`
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function printTouch(touch: Touch): string {
|
|
148
|
+
return JSON.stringify({
|
|
149
|
+
identifier: touch.identifier,
|
|
150
|
+
pageX: touch.pageX,
|
|
151
|
+
pageY: touch.pageY,
|
|
152
|
+
timestamp: timestampForTouch(touch),
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function printTouchBank(touchHistory): string {
|
|
157
|
+
const { touchBank } = touchHistory
|
|
158
|
+
let printed = JSON.stringify(touchBank.slice(0, MAX_TOUCH_BANK))
|
|
159
|
+
if (touchBank.length > MAX_TOUCH_BANK) {
|
|
160
|
+
printed += ' (original size: ' + touchBank.length + ')'
|
|
161
|
+
}
|
|
162
|
+
return printed
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export class ResponderTouchHistoryStore {
|
|
166
|
+
_touchHistory = {
|
|
167
|
+
touchBank: [], //Array<TouchRecord>
|
|
168
|
+
numberActiveTouches: 0,
|
|
169
|
+
// If there is only one active touch, we remember its location. This prevents
|
|
170
|
+
// us having to loop through all of the touches all the time in the most
|
|
171
|
+
// common case.
|
|
172
|
+
indexOfSingleActiveTouch: -1,
|
|
173
|
+
mostRecentTimeStamp: 0,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
recordTouchTrack(topLevelType: string, nativeEvent: TouchEvent): void {
|
|
177
|
+
const touchHistory = this._touchHistory
|
|
178
|
+
if (isMoveish(topLevelType)) {
|
|
179
|
+
nativeEvent.changedTouches.forEach((touch) => recordTouchMove(touch, touchHistory))
|
|
180
|
+
} else if (isStartish(topLevelType)) {
|
|
181
|
+
nativeEvent.changedTouches.forEach((touch) => recordTouchStart(touch, touchHistory))
|
|
182
|
+
touchHistory.numberActiveTouches = nativeEvent.touches.length
|
|
183
|
+
if (touchHistory.numberActiveTouches === 1) {
|
|
184
|
+
touchHistory.indexOfSingleActiveTouch = nativeEvent.touches[0].identifier
|
|
185
|
+
}
|
|
186
|
+
} else if (isEndish(topLevelType)) {
|
|
187
|
+
nativeEvent.changedTouches.forEach((touch) => recordTouchEnd(touch, touchHistory))
|
|
188
|
+
touchHistory.numberActiveTouches = nativeEvent.touches.length
|
|
189
|
+
if (touchHistory.numberActiveTouches === 1) {
|
|
190
|
+
const { touchBank } = touchHistory
|
|
191
|
+
for (let i = 0; i < touchBank.length; i++) {
|
|
192
|
+
const touchTrackToCheck = touchBank[i]
|
|
193
|
+
// @ts-ignore
|
|
194
|
+
if (touchTrackToCheck != null && touchTrackToCheck.touchActive) {
|
|
195
|
+
touchHistory.indexOfSingleActiveTouch = i
|
|
196
|
+
break
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (process.env.NODE_ENV === 'development') {
|
|
200
|
+
const activeRecord = touchBank[touchHistory.indexOfSingleActiveTouch]
|
|
201
|
+
// @ts-ignore
|
|
202
|
+
if (!(activeRecord != null && activeRecord.touchActive)) {
|
|
203
|
+
console.error('Cannot find single active touch.')
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
get touchHistory(): TouchHistory {
|
|
211
|
+
return this._touchHistory
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Nicolas Gallagher
|
|
3
|
+
* This source code is licensed under the MIT license found in the
|
|
4
|
+
* LICENSE file in the root directory of this source tree.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ResponderTouchHistoryStore, TouchHistory } from './ResponderTouchHistoryStore'
|
|
8
|
+
import { getBoundingClientRect } from './utils'
|
|
9
|
+
|
|
10
|
+
export type ResponderEvent = {
|
|
11
|
+
bubbles: boolean
|
|
12
|
+
cancelable: boolean
|
|
13
|
+
currentTarget: any
|
|
14
|
+
defaultPrevented: boolean | null
|
|
15
|
+
dispatchConfig: {
|
|
16
|
+
registrationName?: string
|
|
17
|
+
phasedRegistrationNames?: {
|
|
18
|
+
bubbled: string
|
|
19
|
+
captured: string
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
eventPhase: number | null
|
|
23
|
+
isDefaultPrevented: () => boolean
|
|
24
|
+
isPropagationStopped: () => boolean
|
|
25
|
+
isTrusted: boolean | null
|
|
26
|
+
preventDefault: () => void
|
|
27
|
+
stopPropagation: () => void
|
|
28
|
+
nativeEvent: TouchEvent
|
|
29
|
+
persist: () => void
|
|
30
|
+
target: any | null
|
|
31
|
+
timeStamp: number
|
|
32
|
+
touchHistory: TouchHistory
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const emptyFunction = () => {}
|
|
36
|
+
const emptyObject = {}
|
|
37
|
+
const emptyArray = []
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Safari produces very large identifiers that would cause the `touchBank` array
|
|
41
|
+
* length to be so large as to crash the browser, if not normalized like this.
|
|
42
|
+
* In the future the `touchBank` should use an object/map instead.
|
|
43
|
+
*/
|
|
44
|
+
function normalizeIdentifier(identifier) {
|
|
45
|
+
return identifier > 20 ? identifier % 20 : identifier
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Converts a native DOM event to a ResponderEvent.
|
|
50
|
+
* Mouse events are transformed into fake touch events.
|
|
51
|
+
*/
|
|
52
|
+
export default function createResponderEvent(
|
|
53
|
+
domEvent: any,
|
|
54
|
+
responderTouchHistoryStore: ResponderTouchHistoryStore
|
|
55
|
+
): ResponderEvent {
|
|
56
|
+
let rect
|
|
57
|
+
let propagationWasStopped = false
|
|
58
|
+
let changedTouches
|
|
59
|
+
let touches
|
|
60
|
+
|
|
61
|
+
const domEventChangedTouches = domEvent.changedTouches
|
|
62
|
+
const domEventType = domEvent.type
|
|
63
|
+
|
|
64
|
+
const metaKey = domEvent.metaKey === true
|
|
65
|
+
const shiftKey = domEvent.shiftKey === true
|
|
66
|
+
const force = (domEventChangedTouches && domEventChangedTouches[0].force) || 0
|
|
67
|
+
const identifier = normalizeIdentifier(
|
|
68
|
+
(domEventChangedTouches && domEventChangedTouches[0].identifier) || 0
|
|
69
|
+
)
|
|
70
|
+
const clientX = (domEventChangedTouches && domEventChangedTouches[0].clientX) || domEvent.clientX
|
|
71
|
+
const clientY = (domEventChangedTouches && domEventChangedTouches[0].clientY) || domEvent.clientY
|
|
72
|
+
const pageX = (domEventChangedTouches && domEventChangedTouches[0].pageX) || domEvent.pageX
|
|
73
|
+
const pageY = (domEventChangedTouches && domEventChangedTouches[0].pageY) || domEvent.pageY
|
|
74
|
+
const preventDefault =
|
|
75
|
+
typeof domEvent.preventDefault === 'function'
|
|
76
|
+
? domEvent.preventDefault.bind(domEvent)
|
|
77
|
+
: emptyFunction
|
|
78
|
+
const timestamp = domEvent.timeStamp
|
|
79
|
+
|
|
80
|
+
function normalizeTouches(touches) {
|
|
81
|
+
return Array.prototype.slice.call(touches).map((touch) => {
|
|
82
|
+
return {
|
|
83
|
+
force: touch.force,
|
|
84
|
+
identifier: normalizeIdentifier(touch.identifier),
|
|
85
|
+
get locationX() {
|
|
86
|
+
return locationX(touch.clientX)
|
|
87
|
+
},
|
|
88
|
+
get locationY() {
|
|
89
|
+
return locationY(touch.clientY)
|
|
90
|
+
},
|
|
91
|
+
pageX: touch.pageX,
|
|
92
|
+
pageY: touch.pageY,
|
|
93
|
+
target: touch.target,
|
|
94
|
+
timestamp,
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (domEventChangedTouches != null) {
|
|
100
|
+
changedTouches = normalizeTouches(domEventChangedTouches)
|
|
101
|
+
touches = normalizeTouches(domEvent.touches)
|
|
102
|
+
} else {
|
|
103
|
+
const emulatedTouches = [
|
|
104
|
+
{
|
|
105
|
+
force,
|
|
106
|
+
identifier,
|
|
107
|
+
get locationX() {
|
|
108
|
+
return locationX(clientX)
|
|
109
|
+
},
|
|
110
|
+
get locationY() {
|
|
111
|
+
return locationY(clientY)
|
|
112
|
+
},
|
|
113
|
+
pageX,
|
|
114
|
+
pageY,
|
|
115
|
+
target: domEvent.target,
|
|
116
|
+
timestamp,
|
|
117
|
+
},
|
|
118
|
+
]
|
|
119
|
+
changedTouches = emulatedTouches
|
|
120
|
+
touches =
|
|
121
|
+
domEventType === 'mouseup' || domEventType === 'dragstart' ? emptyArray : emulatedTouches
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const responderEvent = {
|
|
125
|
+
bubbles: true,
|
|
126
|
+
cancelable: true,
|
|
127
|
+
// `currentTarget` is set before dispatch
|
|
128
|
+
currentTarget: null,
|
|
129
|
+
defaultPrevented: domEvent.defaultPrevented,
|
|
130
|
+
dispatchConfig: emptyObject,
|
|
131
|
+
eventPhase: domEvent.eventPhase,
|
|
132
|
+
isDefaultPrevented() {
|
|
133
|
+
return domEvent.defaultPrevented
|
|
134
|
+
},
|
|
135
|
+
isPropagationStopped() {
|
|
136
|
+
return propagationWasStopped
|
|
137
|
+
},
|
|
138
|
+
isTrusted: domEvent.isTrusted,
|
|
139
|
+
nativeEvent: {
|
|
140
|
+
altKey: false,
|
|
141
|
+
ctrlKey: false,
|
|
142
|
+
metaKey,
|
|
143
|
+
shiftKey,
|
|
144
|
+
changedTouches,
|
|
145
|
+
force,
|
|
146
|
+
identifier,
|
|
147
|
+
get locationX() {
|
|
148
|
+
return locationX(clientX)
|
|
149
|
+
},
|
|
150
|
+
get locationY() {
|
|
151
|
+
return locationY(clientY)
|
|
152
|
+
},
|
|
153
|
+
pageX,
|
|
154
|
+
pageY,
|
|
155
|
+
target: domEvent.target,
|
|
156
|
+
timestamp,
|
|
157
|
+
touches,
|
|
158
|
+
type: domEventType,
|
|
159
|
+
},
|
|
160
|
+
persist: emptyFunction,
|
|
161
|
+
preventDefault,
|
|
162
|
+
stopPropagation() {
|
|
163
|
+
propagationWasStopped = true
|
|
164
|
+
},
|
|
165
|
+
target: domEvent.target,
|
|
166
|
+
timeStamp: timestamp,
|
|
167
|
+
touchHistory: responderTouchHistoryStore.touchHistory,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Using getters and functions serves two purposes:
|
|
171
|
+
// 1) The value of `currentTarget` is not initially available.
|
|
172
|
+
// 2) Measuring the clientRect may cause layout jank and should only be done on-demand.
|
|
173
|
+
function locationX(x) {
|
|
174
|
+
rect = rect || getBoundingClientRect(responderEvent.currentTarget)
|
|
175
|
+
if (rect) {
|
|
176
|
+
return x - rect.left
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function locationY(y) {
|
|
180
|
+
rect = rect || getBoundingClientRect(responderEvent.currentTarget)
|
|
181
|
+
if (rect) {
|
|
182
|
+
return y - rect.top
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return responderEvent as any
|
|
187
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './useResponderEvents'
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Nicolas Gallagher
|
|
3
|
+
* This source code is licensed under the MIT license found in the
|
|
4
|
+
* LICENSE file in the root directory of this source tree.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type Touch = {
|
|
8
|
+
force: number
|
|
9
|
+
identifier: number
|
|
10
|
+
// The locationX and locationY properties are non-standard additions
|
|
11
|
+
locationX: any
|
|
12
|
+
locationY: any
|
|
13
|
+
pageX: number
|
|
14
|
+
pageY: number
|
|
15
|
+
target: any
|
|
16
|
+
// Touches in a list have a timestamp property
|
|
17
|
+
timestamp: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type TouchEvent = {
|
|
21
|
+
altKey: boolean
|
|
22
|
+
ctrlKey: boolean
|
|
23
|
+
metaKey: boolean
|
|
24
|
+
shiftKey: boolean
|
|
25
|
+
// TouchList is an array in the Responder system
|
|
26
|
+
changedTouches: Array<Touch>
|
|
27
|
+
force: number
|
|
28
|
+
// React Native adds properties to the "nativeEvent that are usually only found on W3C Touches ‾\_(ツ)_/‾
|
|
29
|
+
identifier: number
|
|
30
|
+
locationX: any
|
|
31
|
+
locationY: any
|
|
32
|
+
pageX: number
|
|
33
|
+
pageY: number
|
|
34
|
+
target: any
|
|
35
|
+
// The timestamp has a lowercase "s" in the Responder system
|
|
36
|
+
timestamp: number
|
|
37
|
+
// TouchList is an array in the Responder system
|
|
38
|
+
touches: Array<Touch>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const BLUR = 'blur'
|
|
42
|
+
export const CONTEXT_MENU = 'contextmenu'
|
|
43
|
+
export const FOCUS_OUT = 'focusout'
|
|
44
|
+
export const MOUSE_DOWN = 'mousedown'
|
|
45
|
+
export const MOUSE_MOVE = 'mousemove'
|
|
46
|
+
export const MOUSE_UP = 'mouseup'
|
|
47
|
+
export const MOUSE_CANCEL = 'dragstart'
|
|
48
|
+
export const TOUCH_START = 'touchstart'
|
|
49
|
+
export const TOUCH_MOVE = 'touchmove'
|
|
50
|
+
export const TOUCH_END = 'touchend'
|
|
51
|
+
export const TOUCH_CANCEL = 'touchcancel'
|
|
52
|
+
export const SCROLL = 'scroll'
|
|
53
|
+
export const SELECT = 'select'
|
|
54
|
+
export const SELECTION_CHANGE = 'selectionchange'
|
|
55
|
+
|
|
56
|
+
export function isStartish(eventType: unknown): boolean {
|
|
57
|
+
return eventType === TOUCH_START || eventType === MOUSE_DOWN
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function isMoveish(eventType: unknown): boolean {
|
|
61
|
+
return eventType === TOUCH_MOVE || eventType === MOUSE_MOVE
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function isEndish(eventType: unknown): boolean {
|
|
65
|
+
return eventType === TOUCH_END || eventType === MOUSE_UP || isCancelish(eventType)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function isCancelish(eventType: unknown): boolean {
|
|
69
|
+
return eventType === TOUCH_CANCEL || eventType === MOUSE_CANCEL
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function isScroll(eventType: unknown): boolean {
|
|
73
|
+
return eventType === SCROLL
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function isSelectionChange(eventType: unknown): boolean {
|
|
77
|
+
return eventType === SELECT || eventType === SELECTION_CHANGE
|
|
78
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Nicolas Gallagher
|
|
3
|
+
* This source code is licensed under the MIT license found in the
|
|
4
|
+
* LICENSE file in the root directory of this source tree.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as React from 'react'
|
|
8
|
+
|
|
9
|
+
import * as ResponderSystem from './ResponderSystem'
|
|
10
|
+
|
|
11
|
+
export * from './utils'
|
|
12
|
+
|
|
13
|
+
const emptyObject = {}
|
|
14
|
+
|
|
15
|
+
export function useResponderEvents(
|
|
16
|
+
hostRef: any,
|
|
17
|
+
config: ResponderSystem.ResponderConfig = emptyObject
|
|
18
|
+
) {
|
|
19
|
+
const id = React.useId()
|
|
20
|
+
const isAttachedRef = React.useRef(false)
|
|
21
|
+
|
|
22
|
+
// This is a separate effects so it doesn't run when the config changes.
|
|
23
|
+
// On initial mount, attach global listeners if needed.
|
|
24
|
+
// On unmount, remove node potentially attached to the Responder System.
|
|
25
|
+
React.useEffect(() => {
|
|
26
|
+
ResponderSystem.attachListeners()
|
|
27
|
+
return () => {
|
|
28
|
+
ResponderSystem.removeNode(id)
|
|
29
|
+
}
|
|
30
|
+
}, [id])
|
|
31
|
+
|
|
32
|
+
// Register and unregister with the Responder System as necessary
|
|
33
|
+
React.useEffect(() => {
|
|
34
|
+
const {
|
|
35
|
+
onMoveShouldSetResponder,
|
|
36
|
+
onMoveShouldSetResponderCapture,
|
|
37
|
+
onScrollShouldSetResponder,
|
|
38
|
+
onScrollShouldSetResponderCapture,
|
|
39
|
+
onSelectionChangeShouldSetResponder,
|
|
40
|
+
onSelectionChangeShouldSetResponderCapture,
|
|
41
|
+
onStartShouldSetResponder,
|
|
42
|
+
onStartShouldSetResponderCapture,
|
|
43
|
+
} = config
|
|
44
|
+
|
|
45
|
+
const requiresResponderSystem =
|
|
46
|
+
onMoveShouldSetResponder != null ||
|
|
47
|
+
onMoveShouldSetResponderCapture != null ||
|
|
48
|
+
onScrollShouldSetResponder != null ||
|
|
49
|
+
onScrollShouldSetResponderCapture != null ||
|
|
50
|
+
onSelectionChangeShouldSetResponder != null ||
|
|
51
|
+
onSelectionChangeShouldSetResponderCapture != null ||
|
|
52
|
+
onStartShouldSetResponder != null ||
|
|
53
|
+
onStartShouldSetResponderCapture != null
|
|
54
|
+
|
|
55
|
+
const node = hostRef.current
|
|
56
|
+
|
|
57
|
+
if (requiresResponderSystem) {
|
|
58
|
+
ResponderSystem.addNode(id, node, config)
|
|
59
|
+
isAttachedRef.current = true
|
|
60
|
+
} else if (isAttachedRef.current) {
|
|
61
|
+
ResponderSystem.removeNode(id)
|
|
62
|
+
isAttachedRef.current = false
|
|
63
|
+
}
|
|
64
|
+
}, [config, hostRef, id])
|
|
65
|
+
|
|
66
|
+
if (process.env.NODE_ENV === 'development') {
|
|
67
|
+
React.useDebugValue({
|
|
68
|
+
isResponder: hostRef.current === ResponderSystem.getResponderNode(),
|
|
69
|
+
})
|
|
70
|
+
React.useDebugValue(config)
|
|
71
|
+
}
|
|
72
|
+
}
|