@react-aria/interactions 3.15.1 → 3.17.0
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/import.mjs +216 -173
- package/dist/main.js +215 -172
- package/dist/main.js.map +1 -1
- package/dist/module.js +216 -173
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/PressResponder.tsx +1 -0
- package/src/useHover.ts +1 -2
- package/src/useInteractOutside.ts +25 -22
- package/src/useLongPress.ts +1 -0
- package/src/useMove.ts +43 -41
- package/src/usePress.ts +188 -129
- package/src/utils.ts +10 -7
package/src/usePress.ts
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
// See https://github.com/facebook/react/tree/cc7c1aece46a6b69b41958d731e0fd27c94bfc6c/packages/react-interactions
|
|
17
17
|
|
|
18
18
|
import {disableTextSelection, restoreTextSelection} from './textSelection';
|
|
19
|
-
import {DOMAttributes, FocusableElement, PointerType, PressEvents} from '@react-types/shared';
|
|
20
|
-
import {focusWithoutScrolling, isVirtualClick, isVirtualPointerEvent, mergeProps, useGlobalListeners, useSyncRef} from '@react-aria/utils';
|
|
19
|
+
import {DOMAttributes, FocusableElement, PressEvent as IPressEvent, PointerType, PressEvents} from '@react-types/shared';
|
|
20
|
+
import {focusWithoutScrolling, isVirtualClick, isVirtualPointerEvent, mergeProps, useEffectEvent, useGlobalListeners, useSyncRef} from '@react-aria/utils';
|
|
21
21
|
import {PressResponderContext} from './context';
|
|
22
22
|
import {RefObject, useContext, useEffect, useMemo, useRef, useState} from 'react';
|
|
23
23
|
|
|
@@ -84,6 +84,35 @@ function usePressResponderContext(props: PressHookProps): PressHookProps {
|
|
|
84
84
|
return props;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
class PressEvent implements IPressEvent {
|
|
88
|
+
type: IPressEvent['type'];
|
|
89
|
+
pointerType: PointerType;
|
|
90
|
+
target: Element;
|
|
91
|
+
shiftKey: boolean;
|
|
92
|
+
ctrlKey: boolean;
|
|
93
|
+
metaKey: boolean;
|
|
94
|
+
altKey: boolean;
|
|
95
|
+
#shouldStopPropagation = true;
|
|
96
|
+
|
|
97
|
+
constructor(type: IPressEvent['type'], pointerType: PointerType, originalEvent: EventBase) {
|
|
98
|
+
this.type = type;
|
|
99
|
+
this.pointerType = pointerType;
|
|
100
|
+
this.target = originalEvent.currentTarget as Element;
|
|
101
|
+
this.shiftKey = originalEvent.shiftKey;
|
|
102
|
+
this.metaKey = originalEvent.metaKey;
|
|
103
|
+
this.ctrlKey = originalEvent.ctrlKey;
|
|
104
|
+
this.altKey = originalEvent.altKey;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
continuePropagation() {
|
|
108
|
+
this.#shouldStopPropagation = false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
get shouldStopPropagation() {
|
|
112
|
+
return this.#shouldStopPropagation;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
87
116
|
/**
|
|
88
117
|
* Handles press interactions across mouse, touch, keyboard, and screen readers.
|
|
89
118
|
* It normalizes behavior across browsers and platforms, and handles many nuances
|
|
@@ -105,8 +134,6 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
105
134
|
ref: _, // Removing `ref` from `domProps` because TypeScript is dumb
|
|
106
135
|
...domProps
|
|
107
136
|
} = usePressResponderContext(props);
|
|
108
|
-
let propsRef = useRef<PressHookProps>(null);
|
|
109
|
-
propsRef.current = {onPress, onPressChange, onPressStart, onPressEnd, onPressUp, isDisabled, shouldCancelOnPointerExit};
|
|
110
137
|
|
|
111
138
|
let [isPressed, setPressed] = useState(false);
|
|
112
139
|
let ref = useRef<PressState>({
|
|
@@ -122,129 +149,122 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
122
149
|
|
|
123
150
|
let {addGlobalListener, removeAllGlobalListeners} = useGlobalListeners();
|
|
124
151
|
|
|
125
|
-
let
|
|
152
|
+
let triggerPressStart = useEffectEvent((originalEvent: EventBase, pointerType: PointerType) => {
|
|
126
153
|
let state = ref.current;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
154
|
+
if (isDisabled || state.didFirePressStart) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
132
157
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
metaKey: originalEvent.metaKey,
|
|
140
|
-
ctrlKey: originalEvent.ctrlKey,
|
|
141
|
-
altKey: originalEvent.altKey
|
|
142
|
-
});
|
|
143
|
-
}
|
|
158
|
+
let shouldStopPropagation = true;
|
|
159
|
+
if (onPressStart) {
|
|
160
|
+
let event = new PressEvent('pressstart', pointerType, originalEvent);
|
|
161
|
+
onPressStart(event);
|
|
162
|
+
shouldStopPropagation = event.shouldStopPropagation;
|
|
163
|
+
}
|
|
144
164
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
165
|
+
if (onPressChange) {
|
|
166
|
+
onPressChange(true);
|
|
167
|
+
}
|
|
148
168
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
169
|
+
state.didFirePressStart = true;
|
|
170
|
+
setPressed(true);
|
|
171
|
+
return shouldStopPropagation;
|
|
172
|
+
});
|
|
152
173
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
174
|
+
let triggerPressEnd = useEffectEvent((originalEvent: EventBase, pointerType: PointerType, wasPressed = true) => {
|
|
175
|
+
let state = ref.current;
|
|
176
|
+
if (!state.didFirePressStart) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
158
179
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (onPressEnd) {
|
|
163
|
-
onPressEnd({
|
|
164
|
-
type: 'pressend',
|
|
165
|
-
pointerType,
|
|
166
|
-
target: originalEvent.currentTarget as Element,
|
|
167
|
-
shiftKey: originalEvent.shiftKey,
|
|
168
|
-
metaKey: originalEvent.metaKey,
|
|
169
|
-
ctrlKey: originalEvent.ctrlKey,
|
|
170
|
-
altKey: originalEvent.altKey
|
|
171
|
-
});
|
|
172
|
-
}
|
|
180
|
+
state.ignoreClickAfterPress = true;
|
|
181
|
+
state.didFirePressStart = false;
|
|
173
182
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
183
|
+
let shouldStopPropagation = true;
|
|
184
|
+
if (onPressEnd) {
|
|
185
|
+
let event = new PressEvent('pressend', pointerType, originalEvent);
|
|
186
|
+
onPressEnd(event);
|
|
187
|
+
shouldStopPropagation = event.shouldStopPropagation;
|
|
188
|
+
}
|
|
177
189
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
onPress({
|
|
182
|
-
type: 'press',
|
|
183
|
-
pointerType,
|
|
184
|
-
target: originalEvent.currentTarget as Element,
|
|
185
|
-
shiftKey: originalEvent.shiftKey,
|
|
186
|
-
metaKey: originalEvent.metaKey,
|
|
187
|
-
ctrlKey: originalEvent.ctrlKey,
|
|
188
|
-
altKey: originalEvent.altKey
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
};
|
|
190
|
+
if (onPressChange) {
|
|
191
|
+
onPressChange(false);
|
|
192
|
+
}
|
|
192
193
|
|
|
193
|
-
|
|
194
|
-
let {onPressUp, isDisabled} = propsRef.current;
|
|
195
|
-
if (isDisabled) {
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
194
|
+
setPressed(false);
|
|
198
195
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
shiftKey: originalEvent.shiftKey,
|
|
205
|
-
metaKey: originalEvent.metaKey,
|
|
206
|
-
ctrlKey: originalEvent.ctrlKey,
|
|
207
|
-
altKey: originalEvent.altKey
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
};
|
|
196
|
+
if (onPress && wasPressed && !isDisabled) {
|
|
197
|
+
let event = new PressEvent('press', pointerType, originalEvent);
|
|
198
|
+
onPress(event);
|
|
199
|
+
shouldStopPropagation &&= event.shouldStopPropagation;
|
|
200
|
+
}
|
|
211
201
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
202
|
+
return shouldStopPropagation;
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
let triggerPressUp = useEffectEvent((originalEvent: EventBase, pointerType: PointerType) => {
|
|
206
|
+
if (isDisabled) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (onPressUp) {
|
|
211
|
+
let event = new PressEvent('pressup', pointerType, originalEvent);
|
|
212
|
+
onPressUp(event);
|
|
213
|
+
return event.shouldStopPropagation;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return true;
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
let cancel = useEffectEvent((e: EventBase) => {
|
|
220
|
+
let state = ref.current;
|
|
221
|
+
if (state.isPressed) {
|
|
222
|
+
if (state.isOverTarget) {
|
|
223
|
+
triggerPressEnd(createEvent(state.target, e), state.pointerType, false);
|
|
225
224
|
}
|
|
226
|
-
|
|
225
|
+
state.isPressed = false;
|
|
226
|
+
state.isOverTarget = false;
|
|
227
|
+
state.activePointerId = null;
|
|
228
|
+
state.pointerType = null;
|
|
229
|
+
removeAllGlobalListeners();
|
|
230
|
+
if (!allowTextSelectionOnPress) {
|
|
231
|
+
restoreTextSelection(state.target);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
let cancelOnPointerExit = useEffectEvent((e: EventBase) => {
|
|
237
|
+
if (shouldCancelOnPointerExit) {
|
|
238
|
+
cancel(e);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
227
241
|
|
|
242
|
+
let pressProps = useMemo(() => {
|
|
243
|
+
let state = ref.current;
|
|
228
244
|
let pressProps: DOMAttributes = {
|
|
229
245
|
onKeyDown(e) {
|
|
230
246
|
if (isValidKeyboardEvent(e.nativeEvent, e.currentTarget) && e.currentTarget.contains(e.target as Element)) {
|
|
231
247
|
if (shouldPreventDefaultKeyboard(e.target as Element, e.key)) {
|
|
232
248
|
e.preventDefault();
|
|
233
249
|
}
|
|
234
|
-
e.stopPropagation();
|
|
235
250
|
|
|
236
251
|
// If the event is repeating, it may have started on a different element
|
|
237
252
|
// after which focus moved to the current element. Ignore these events and
|
|
238
253
|
// only handle the first key down event.
|
|
254
|
+
let shouldStopPropagation = true;
|
|
239
255
|
if (!state.isPressed && !e.repeat) {
|
|
240
256
|
state.target = e.currentTarget;
|
|
241
257
|
state.isPressed = true;
|
|
242
|
-
triggerPressStart(e, 'keyboard');
|
|
258
|
+
shouldStopPropagation = triggerPressStart(e, 'keyboard');
|
|
243
259
|
|
|
244
260
|
// Focus may move before the key up event, so register the event on the document
|
|
245
261
|
// instead of the same element where the key down event occurred.
|
|
246
262
|
addGlobalListener(document, 'keyup', onKeyUp, false);
|
|
247
263
|
}
|
|
264
|
+
|
|
265
|
+
if (shouldStopPropagation) {
|
|
266
|
+
e.stopPropagation();
|
|
267
|
+
}
|
|
248
268
|
} else if (e.key === 'Enter' && isHTMLAnchorLink(e.currentTarget)) {
|
|
249
269
|
// If the target is a link, we won't have handled this above because we want the default
|
|
250
270
|
// browser behavior to open the link when pressing Enter. But we still need to prevent
|
|
@@ -263,7 +283,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
263
283
|
}
|
|
264
284
|
|
|
265
285
|
if (e && e.button === 0) {
|
|
266
|
-
|
|
286
|
+
let shouldStopPropagation = true;
|
|
267
287
|
if (isDisabled) {
|
|
268
288
|
e.preventDefault();
|
|
269
289
|
}
|
|
@@ -276,13 +296,17 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
276
296
|
focusWithoutScrolling(e.currentTarget);
|
|
277
297
|
}
|
|
278
298
|
|
|
279
|
-
triggerPressStart(e, 'virtual');
|
|
280
|
-
triggerPressUp(e, 'virtual');
|
|
281
|
-
triggerPressEnd(e, 'virtual');
|
|
299
|
+
let stopPressStart = triggerPressStart(e, 'virtual');
|
|
300
|
+
let stopPressUp = triggerPressUp(e, 'virtual');
|
|
301
|
+
let stopPressEnd = triggerPressEnd(e, 'virtual');
|
|
302
|
+
shouldStopPropagation = stopPressStart && stopPressUp && stopPressEnd;
|
|
282
303
|
}
|
|
283
304
|
|
|
284
305
|
state.ignoreEmulatedMouseEvents = false;
|
|
285
306
|
state.ignoreClickAfterPress = false;
|
|
307
|
+
if (shouldStopPropagation) {
|
|
308
|
+
e.stopPropagation();
|
|
309
|
+
}
|
|
286
310
|
}
|
|
287
311
|
}
|
|
288
312
|
};
|
|
@@ -292,13 +316,16 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
292
316
|
if (shouldPreventDefaultKeyboard(e.target as Element, e.key)) {
|
|
293
317
|
e.preventDefault();
|
|
294
318
|
}
|
|
295
|
-
e.stopPropagation();
|
|
296
319
|
|
|
297
320
|
state.isPressed = false;
|
|
298
321
|
let target = e.target as Element;
|
|
299
|
-
triggerPressEnd(createEvent(state.target, e), 'keyboard', state.target.contains(target));
|
|
322
|
+
let shouldStopPropagation = triggerPressEnd(createEvent(state.target, e), 'keyboard', state.target.contains(target));
|
|
300
323
|
removeAllGlobalListeners();
|
|
301
324
|
|
|
325
|
+
if (shouldStopPropagation) {
|
|
326
|
+
e.stopPropagation();
|
|
327
|
+
}
|
|
328
|
+
|
|
302
329
|
// If the target is a link, trigger the click method to open the URL,
|
|
303
330
|
// but defer triggering pressEnd until onClick event handler.
|
|
304
331
|
if (state.target instanceof HTMLElement && state.target.contains(target) && (isHTMLAnchorLink(state.target) || state.target.getAttribute('role') === 'link')) {
|
|
@@ -331,7 +358,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
331
358
|
|
|
332
359
|
state.pointerType = e.pointerType;
|
|
333
360
|
|
|
334
|
-
|
|
361
|
+
let shouldStopPropagation = true;
|
|
335
362
|
if (!state.isPressed) {
|
|
336
363
|
state.isPressed = true;
|
|
337
364
|
state.isOverTarget = true;
|
|
@@ -346,12 +373,16 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
346
373
|
disableTextSelection(state.target);
|
|
347
374
|
}
|
|
348
375
|
|
|
349
|
-
triggerPressStart(e, state.pointerType);
|
|
376
|
+
shouldStopPropagation = triggerPressStart(e, state.pointerType);
|
|
350
377
|
|
|
351
378
|
addGlobalListener(document, 'pointermove', onPointerMove, false);
|
|
352
379
|
addGlobalListener(document, 'pointerup', onPointerUp, false);
|
|
353
380
|
addGlobalListener(document, 'pointercancel', onPointerCancel, false);
|
|
354
381
|
}
|
|
382
|
+
|
|
383
|
+
if (shouldStopPropagation) {
|
|
384
|
+
e.stopPropagation();
|
|
385
|
+
}
|
|
355
386
|
};
|
|
356
387
|
|
|
357
388
|
pressProps.onMouseDown = (e) => {
|
|
@@ -401,9 +432,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
401
432
|
} else if (state.isOverTarget) {
|
|
402
433
|
state.isOverTarget = false;
|
|
403
434
|
triggerPressEnd(createEvent(state.target, e), state.pointerType, false);
|
|
404
|
-
|
|
405
|
-
cancel(e);
|
|
406
|
-
}
|
|
435
|
+
cancelOnPointerExit(e);
|
|
407
436
|
}
|
|
408
437
|
};
|
|
409
438
|
|
|
@@ -451,8 +480,8 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
451
480
|
e.preventDefault();
|
|
452
481
|
}
|
|
453
482
|
|
|
454
|
-
e.stopPropagation();
|
|
455
483
|
if (state.ignoreEmulatedMouseEvents) {
|
|
484
|
+
e.stopPropagation();
|
|
456
485
|
return;
|
|
457
486
|
}
|
|
458
487
|
|
|
@@ -465,7 +494,10 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
465
494
|
focusWithoutScrolling(e.currentTarget);
|
|
466
495
|
}
|
|
467
496
|
|
|
468
|
-
triggerPressStart(e, state.pointerType);
|
|
497
|
+
let shouldStopPropagation = triggerPressStart(e, state.pointerType);
|
|
498
|
+
if (shouldStopPropagation) {
|
|
499
|
+
e.stopPropagation();
|
|
500
|
+
}
|
|
469
501
|
|
|
470
502
|
addGlobalListener(document, 'mouseup', onMouseUp, false);
|
|
471
503
|
};
|
|
@@ -475,10 +507,14 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
475
507
|
return;
|
|
476
508
|
}
|
|
477
509
|
|
|
478
|
-
|
|
510
|
+
let shouldStopPropagation = true;
|
|
479
511
|
if (state.isPressed && !state.ignoreEmulatedMouseEvents) {
|
|
480
512
|
state.isOverTarget = true;
|
|
481
|
-
triggerPressStart(e, state.pointerType);
|
|
513
|
+
shouldStopPropagation = triggerPressStart(e, state.pointerType);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (shouldStopPropagation) {
|
|
517
|
+
e.stopPropagation();
|
|
482
518
|
}
|
|
483
519
|
};
|
|
484
520
|
|
|
@@ -487,13 +523,15 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
487
523
|
return;
|
|
488
524
|
}
|
|
489
525
|
|
|
490
|
-
|
|
526
|
+
let shouldStopPropagation = true;
|
|
491
527
|
if (state.isPressed && !state.ignoreEmulatedMouseEvents) {
|
|
492
528
|
state.isOverTarget = false;
|
|
493
|
-
triggerPressEnd(e, state.pointerType, false);
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
529
|
+
shouldStopPropagation = triggerPressEnd(e, state.pointerType, false);
|
|
530
|
+
cancelOnPointerExit(e);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (shouldStopPropagation) {
|
|
534
|
+
e.stopPropagation();
|
|
497
535
|
}
|
|
498
536
|
};
|
|
499
537
|
|
|
@@ -535,7 +573,6 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
535
573
|
return;
|
|
536
574
|
}
|
|
537
575
|
|
|
538
|
-
e.stopPropagation();
|
|
539
576
|
let touch = getTouchFromEvent(e.nativeEvent);
|
|
540
577
|
if (!touch) {
|
|
541
578
|
return;
|
|
@@ -557,7 +594,10 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
557
594
|
disableTextSelection(state.target);
|
|
558
595
|
}
|
|
559
596
|
|
|
560
|
-
triggerPressStart(e, state.pointerType);
|
|
597
|
+
let shouldStopPropagation = triggerPressStart(e, state.pointerType);
|
|
598
|
+
if (shouldStopPropagation) {
|
|
599
|
+
e.stopPropagation();
|
|
600
|
+
}
|
|
561
601
|
|
|
562
602
|
addGlobalListener(window, 'scroll', onScroll, true);
|
|
563
603
|
};
|
|
@@ -567,23 +607,26 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
567
607
|
return;
|
|
568
608
|
}
|
|
569
609
|
|
|
570
|
-
e.stopPropagation();
|
|
571
610
|
if (!state.isPressed) {
|
|
611
|
+
e.stopPropagation();
|
|
572
612
|
return;
|
|
573
613
|
}
|
|
574
614
|
|
|
575
615
|
let touch = getTouchById(e.nativeEvent, state.activePointerId);
|
|
616
|
+
let shouldStopPropagation = true;
|
|
576
617
|
if (touch && isOverTarget(touch, e.currentTarget)) {
|
|
577
618
|
if (!state.isOverTarget) {
|
|
578
619
|
state.isOverTarget = true;
|
|
579
|
-
triggerPressStart(e, state.pointerType);
|
|
620
|
+
shouldStopPropagation = triggerPressStart(e, state.pointerType);
|
|
580
621
|
}
|
|
581
622
|
} else if (state.isOverTarget) {
|
|
582
623
|
state.isOverTarget = false;
|
|
583
|
-
triggerPressEnd(e, state.pointerType, false);
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
624
|
+
shouldStopPropagation = triggerPressEnd(e, state.pointerType, false);
|
|
625
|
+
cancelOnPointerExit(e);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (shouldStopPropagation) {
|
|
629
|
+
e.stopPropagation();
|
|
587
630
|
}
|
|
588
631
|
};
|
|
589
632
|
|
|
@@ -592,17 +635,22 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
592
635
|
return;
|
|
593
636
|
}
|
|
594
637
|
|
|
595
|
-
e.stopPropagation();
|
|
596
638
|
if (!state.isPressed) {
|
|
639
|
+
e.stopPropagation();
|
|
597
640
|
return;
|
|
598
641
|
}
|
|
599
642
|
|
|
600
643
|
let touch = getTouchById(e.nativeEvent, state.activePointerId);
|
|
644
|
+
let shouldStopPropagation = true;
|
|
601
645
|
if (touch && isOverTarget(touch, e.currentTarget)) {
|
|
602
646
|
triggerPressUp(e, state.pointerType);
|
|
603
|
-
triggerPressEnd(e, state.pointerType);
|
|
647
|
+
shouldStopPropagation = triggerPressEnd(e, state.pointerType);
|
|
604
648
|
} else if (state.isOverTarget) {
|
|
605
|
-
triggerPressEnd(e, state.pointerType, false);
|
|
649
|
+
shouldStopPropagation = triggerPressEnd(e, state.pointerType, false);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
if (shouldStopPropagation) {
|
|
653
|
+
e.stopPropagation();
|
|
606
654
|
}
|
|
607
655
|
|
|
608
656
|
state.isPressed = false;
|
|
@@ -648,7 +696,18 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
648
696
|
}
|
|
649
697
|
|
|
650
698
|
return pressProps;
|
|
651
|
-
}, [
|
|
699
|
+
}, [
|
|
700
|
+
addGlobalListener,
|
|
701
|
+
isDisabled,
|
|
702
|
+
preventFocusOnPress,
|
|
703
|
+
removeAllGlobalListeners,
|
|
704
|
+
allowTextSelectionOnPress,
|
|
705
|
+
cancel,
|
|
706
|
+
cancelOnPointerExit,
|
|
707
|
+
triggerPressEnd,
|
|
708
|
+
triggerPressStart,
|
|
709
|
+
triggerPressUp
|
|
710
|
+
]);
|
|
652
711
|
|
|
653
712
|
// Remove user-select: none in case component unmounts immediately after pressStart
|
|
654
713
|
// eslint-disable-next-line arrow-body-style
|
|
@@ -779,7 +838,7 @@ function shouldPreventDefaultKeyboard(target: Element, key: string) {
|
|
|
779
838
|
}
|
|
780
839
|
|
|
781
840
|
if (target instanceof HTMLButtonElement) {
|
|
782
|
-
return target.type !== 'submit';
|
|
841
|
+
return target.type !== 'submit' && target.type !== 'reset';
|
|
783
842
|
}
|
|
784
843
|
|
|
785
844
|
return true;
|
package/src/utils.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import {FocusEvent as ReactFocusEvent, useCallback, useRef} from 'react';
|
|
14
|
-
import {useLayoutEffect} from '@react-aria/utils';
|
|
14
|
+
import {useEffectEvent, useLayoutEffect} from '@react-aria/utils';
|
|
15
15
|
|
|
16
16
|
export class SyntheticFocusEvent<Target = Element> implements ReactFocusEvent<Target> {
|
|
17
17
|
nativeEvent: FocusEvent;
|
|
@@ -64,10 +64,8 @@ export class SyntheticFocusEvent<Target = Element> implements ReactFocusEvent<Ta
|
|
|
64
64
|
export function useSyntheticBlurEvent<Target = Element>(onBlur: (e: ReactFocusEvent<Target>) => void) {
|
|
65
65
|
let stateRef = useRef({
|
|
66
66
|
isFocused: false,
|
|
67
|
-
onBlur,
|
|
68
67
|
observer: null as MutationObserver
|
|
69
68
|
});
|
|
70
|
-
stateRef.current.onBlur = onBlur;
|
|
71
69
|
|
|
72
70
|
// Clean up MutationObserver on unmount. See below.
|
|
73
71
|
// eslint-disable-next-line arrow-body-style
|
|
@@ -81,6 +79,10 @@ export function useSyntheticBlurEvent<Target = Element>(onBlur: (e: ReactFocusEv
|
|
|
81
79
|
};
|
|
82
80
|
}, []);
|
|
83
81
|
|
|
82
|
+
let dispatchBlur = useEffectEvent((e: SyntheticFocusEvent<Target>) => {
|
|
83
|
+
onBlur?.(e);
|
|
84
|
+
});
|
|
85
|
+
|
|
84
86
|
// This function is called during a React onFocus event.
|
|
85
87
|
return useCallback((e: ReactFocusEvent<Target>) => {
|
|
86
88
|
// React does not fire onBlur when an element is disabled. https://github.com/facebook/react/issues/9142
|
|
@@ -101,7 +103,7 @@ export function useSyntheticBlurEvent<Target = Element>(onBlur: (e: ReactFocusEv
|
|
|
101
103
|
|
|
102
104
|
if (target.disabled) {
|
|
103
105
|
// For backward compatibility, dispatch a (fake) React synthetic event.
|
|
104
|
-
|
|
106
|
+
dispatchBlur(new SyntheticFocusEvent('blur', e));
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
// We no longer need the MutationObserver once the target is blurred.
|
|
@@ -116,12 +118,13 @@ export function useSyntheticBlurEvent<Target = Element>(onBlur: (e: ReactFocusEv
|
|
|
116
118
|
stateRef.current.observer = new MutationObserver(() => {
|
|
117
119
|
if (stateRef.current.isFocused && target.disabled) {
|
|
118
120
|
stateRef.current.observer.disconnect();
|
|
119
|
-
target.
|
|
120
|
-
target.dispatchEvent(new FocusEvent('
|
|
121
|
+
let relatedTargetEl = target === document.activeElement ? null : document.activeElement;
|
|
122
|
+
target.dispatchEvent(new FocusEvent('blur', {relatedTarget: relatedTargetEl}));
|
|
123
|
+
target.dispatchEvent(new FocusEvent('focusout', {bubbles: true, relatedTarget: relatedTargetEl}));
|
|
121
124
|
}
|
|
122
125
|
});
|
|
123
126
|
|
|
124
127
|
stateRef.current.observer.observe(target, {attributes: true, attributeFilter: ['disabled']});
|
|
125
128
|
}
|
|
126
|
-
}, []);
|
|
129
|
+
}, [dispatchBlur]);
|
|
127
130
|
}
|