@react-aria/interactions 3.9.0 → 3.11.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/main.js +63 -93
- package/dist/main.js.map +1 -1
- package/dist/module.js +44 -67
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +27 -26
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/DOMPropsContext.ts +6 -5
- package/src/DOMPropsResponder.tsx +1 -1
- package/src/PressResponder.tsx +2 -1
- package/src/Pressable.tsx +4 -3
- package/src/context.ts +2 -1
- package/src/index.ts +31 -12
- package/src/textSelection.ts +5 -5
- package/src/useDOMPropsResponder.ts +1 -1
- package/src/useFocus.ts +5 -5
- package/src/useFocusVisible.ts +3 -3
- package/src/useFocusWithin.ts +5 -4
- package/src/useHover.ts +7 -6
- package/src/useInteractOutside.ts +1 -1
- package/src/useKeyboard.ts +3 -4
- package/src/useLongPress.ts +5 -5
- package/src/useMove.ts +5 -5
- package/src/usePress.ts +81 -50
package/src/usePress.ts
CHANGED
|
@@ -16,11 +16,11 @@
|
|
|
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';
|
|
19
20
|
import {focusWithoutScrolling, mergeProps, useGlobalListeners, useSyncRef} from '@react-aria/utils';
|
|
20
|
-
import {HTMLAttributes, RefObject, useContext, useEffect, useMemo, useRef, useState} from 'react';
|
|
21
21
|
import {isVirtualClick} from './utils';
|
|
22
|
-
import {PointerType, PressEvents} from '@react-types/shared';
|
|
23
22
|
import {PressResponderContext} from './context';
|
|
23
|
+
import {RefObject, useContext, useEffect, useMemo, useRef, useState} from 'react';
|
|
24
24
|
|
|
25
25
|
export interface PressProps extends PressEvents {
|
|
26
26
|
/** Whether the target is in a controlled press state (e.g. an overlay it triggers is open). */
|
|
@@ -42,7 +42,7 @@ export interface PressProps extends PressEvents {
|
|
|
42
42
|
|
|
43
43
|
export interface PressHookProps extends PressProps {
|
|
44
44
|
/** A ref to the target element. */
|
|
45
|
-
ref?: RefObject<
|
|
45
|
+
ref?: RefObject<Element>
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
interface PressState {
|
|
@@ -51,7 +51,7 @@ interface PressState {
|
|
|
51
51
|
ignoreClickAfterPress: boolean,
|
|
52
52
|
didFirePressStart: boolean,
|
|
53
53
|
activePointerId: any,
|
|
54
|
-
target:
|
|
54
|
+
target: FocusableElement | null,
|
|
55
55
|
isOverTarget: boolean,
|
|
56
56
|
pointerType: PointerType,
|
|
57
57
|
userSelect?: string
|
|
@@ -69,7 +69,7 @@ export interface PressResult {
|
|
|
69
69
|
/** Whether the target is currently pressed. */
|
|
70
70
|
isPressed: boolean,
|
|
71
71
|
/** Props to spread on the target element. */
|
|
72
|
-
pressProps:
|
|
72
|
+
pressProps: DOMAttributes
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
function usePressResponderContext(props: PressHookProps): PressHookProps {
|
|
@@ -135,7 +135,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
135
135
|
onPressStart({
|
|
136
136
|
type: 'pressstart',
|
|
137
137
|
pointerType,
|
|
138
|
-
target: originalEvent.currentTarget as
|
|
138
|
+
target: originalEvent.currentTarget as Element,
|
|
139
139
|
shiftKey: originalEvent.shiftKey,
|
|
140
140
|
metaKey: originalEvent.metaKey,
|
|
141
141
|
ctrlKey: originalEvent.ctrlKey,
|
|
@@ -164,7 +164,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
164
164
|
onPressEnd({
|
|
165
165
|
type: 'pressend',
|
|
166
166
|
pointerType,
|
|
167
|
-
target: originalEvent.currentTarget as
|
|
167
|
+
target: originalEvent.currentTarget as Element,
|
|
168
168
|
shiftKey: originalEvent.shiftKey,
|
|
169
169
|
metaKey: originalEvent.metaKey,
|
|
170
170
|
ctrlKey: originalEvent.ctrlKey,
|
|
@@ -182,7 +182,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
182
182
|
onPress({
|
|
183
183
|
type: 'press',
|
|
184
184
|
pointerType,
|
|
185
|
-
target: originalEvent.currentTarget as
|
|
185
|
+
target: originalEvent.currentTarget as Element,
|
|
186
186
|
shiftKey: originalEvent.shiftKey,
|
|
187
187
|
metaKey: originalEvent.metaKey,
|
|
188
188
|
ctrlKey: originalEvent.ctrlKey,
|
|
@@ -201,7 +201,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
201
201
|
onPressUp({
|
|
202
202
|
type: 'pressup',
|
|
203
203
|
pointerType,
|
|
204
|
-
target: originalEvent.currentTarget as
|
|
204
|
+
target: originalEvent.currentTarget as Element,
|
|
205
205
|
shiftKey: originalEvent.shiftKey,
|
|
206
206
|
metaKey: originalEvent.metaKey,
|
|
207
207
|
ctrlKey: originalEvent.ctrlKey,
|
|
@@ -226,10 +226,10 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
226
226
|
}
|
|
227
227
|
};
|
|
228
228
|
|
|
229
|
-
let pressProps:
|
|
229
|
+
let pressProps: DOMAttributes = {
|
|
230
230
|
onKeyDown(e) {
|
|
231
|
-
if (isValidKeyboardEvent(e.nativeEvent) && e.currentTarget.contains(e.target as
|
|
232
|
-
if (shouldPreventDefaultKeyboard(e.target as Element)) {
|
|
231
|
+
if (isValidKeyboardEvent(e.nativeEvent, e.currentTarget) && e.currentTarget.contains(e.target as Element)) {
|
|
232
|
+
if (shouldPreventDefaultKeyboard(e.target as Element, e.key)) {
|
|
233
233
|
e.preventDefault();
|
|
234
234
|
}
|
|
235
235
|
e.stopPropagation();
|
|
@@ -238,7 +238,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
238
238
|
// after which focus moved to the current element. Ignore these events and
|
|
239
239
|
// only handle the first key down event.
|
|
240
240
|
if (!state.isPressed && !e.repeat) {
|
|
241
|
-
state.target = e.currentTarget
|
|
241
|
+
state.target = e.currentTarget;
|
|
242
242
|
state.isPressed = true;
|
|
243
243
|
triggerPressStart(e, 'keyboard');
|
|
244
244
|
|
|
@@ -246,15 +246,20 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
246
246
|
// instead of the same element where the key down event occurred.
|
|
247
247
|
addGlobalListener(document, 'keyup', onKeyUp, false);
|
|
248
248
|
}
|
|
249
|
+
} else if (e.key === 'Enter' && isHTMLAnchorLink(e.currentTarget)) {
|
|
250
|
+
// If the target is a link, we won't have handled this above because we want the default
|
|
251
|
+
// browser behavior to open the link when pressing Enter. But we still need to prevent
|
|
252
|
+
// default so that elements above do not also handle it (e.g. table row).
|
|
253
|
+
e.stopPropagation();
|
|
249
254
|
}
|
|
250
255
|
},
|
|
251
256
|
onKeyUp(e) {
|
|
252
|
-
if (isValidKeyboardEvent(e.nativeEvent) && !e.repeat && e.currentTarget.contains(e.target as
|
|
257
|
+
if (isValidKeyboardEvent(e.nativeEvent, e.currentTarget) && !e.repeat && e.currentTarget.contains(e.target as Element)) {
|
|
253
258
|
triggerPressUp(createEvent(state.target, e), 'keyboard');
|
|
254
259
|
}
|
|
255
260
|
},
|
|
256
261
|
onClick(e) {
|
|
257
|
-
if (e && !e.currentTarget.contains(e.target as
|
|
262
|
+
if (e && !e.currentTarget.contains(e.target as Element)) {
|
|
258
263
|
return;
|
|
259
264
|
}
|
|
260
265
|
|
|
@@ -284,20 +289,20 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
284
289
|
};
|
|
285
290
|
|
|
286
291
|
let onKeyUp = (e: KeyboardEvent) => {
|
|
287
|
-
if (state.isPressed && isValidKeyboardEvent(e)) {
|
|
288
|
-
if (shouldPreventDefaultKeyboard(e.target as Element)) {
|
|
292
|
+
if (state.isPressed && isValidKeyboardEvent(e, state.target)) {
|
|
293
|
+
if (shouldPreventDefaultKeyboard(e.target as Element, e.key)) {
|
|
289
294
|
e.preventDefault();
|
|
290
295
|
}
|
|
291
296
|
e.stopPropagation();
|
|
292
297
|
|
|
293
298
|
state.isPressed = false;
|
|
294
|
-
let target = e.target as
|
|
299
|
+
let target = e.target as Element;
|
|
295
300
|
triggerPressEnd(createEvent(state.target, e), 'keyboard', state.target.contains(target));
|
|
296
301
|
removeAllGlobalListeners();
|
|
297
302
|
|
|
298
303
|
// If the target is a link, trigger the click method to open the URL,
|
|
299
304
|
// but defer triggering pressEnd until onClick event handler.
|
|
300
|
-
if (state.target.contains(target) && isHTMLAnchorLink(state.target) || state.target.getAttribute('role') === 'link') {
|
|
305
|
+
if (state.target instanceof HTMLElement && state.target.contains(target) && (isHTMLAnchorLink(state.target) || state.target.getAttribute('role') === 'link')) {
|
|
301
306
|
state.target.click();
|
|
302
307
|
}
|
|
303
308
|
}
|
|
@@ -306,7 +311,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
306
311
|
if (typeof PointerEvent !== 'undefined') {
|
|
307
312
|
pressProps.onPointerDown = (e) => {
|
|
308
313
|
// Only handle left clicks, and ignore events that bubbled through portals.
|
|
309
|
-
if (e.button !== 0 || !e.currentTarget.contains(e.target as
|
|
314
|
+
if (e.button !== 0 || !e.currentTarget.contains(e.target as Element)) {
|
|
310
315
|
return;
|
|
311
316
|
}
|
|
312
317
|
|
|
@@ -321,7 +326,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
321
326
|
|
|
322
327
|
// Due to browser inconsistencies, especially on mobile browsers, we prevent
|
|
323
328
|
// default on pointer down and handle focusing the pressable element ourselves.
|
|
324
|
-
if (shouldPreventDefault(e.currentTarget as
|
|
329
|
+
if (shouldPreventDefault(e.currentTarget as Element)) {
|
|
325
330
|
e.preventDefault();
|
|
326
331
|
}
|
|
327
332
|
|
|
@@ -351,7 +356,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
351
356
|
};
|
|
352
357
|
|
|
353
358
|
pressProps.onMouseDown = (e) => {
|
|
354
|
-
if (!e.currentTarget.contains(e.target as
|
|
359
|
+
if (!e.currentTarget.contains(e.target as Element)) {
|
|
355
360
|
return;
|
|
356
361
|
}
|
|
357
362
|
|
|
@@ -359,7 +364,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
359
364
|
// Chrome and Firefox on touch Windows devices require mouse down events
|
|
360
365
|
// to be canceled in addition to pointer events, or an extra asynchronous
|
|
361
366
|
// focus event will be fired.
|
|
362
|
-
if (shouldPreventDefault(e.currentTarget as
|
|
367
|
+
if (shouldPreventDefault(e.currentTarget as Element)) {
|
|
363
368
|
e.preventDefault();
|
|
364
369
|
}
|
|
365
370
|
|
|
@@ -369,7 +374,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
369
374
|
|
|
370
375
|
pressProps.onPointerUp = (e) => {
|
|
371
376
|
// iOS fires pointerup with zero width and height, so check the pointerType recorded during pointerdown.
|
|
372
|
-
if (!e.currentTarget.contains(e.target as
|
|
377
|
+
if (!e.currentTarget.contains(e.target as Element) || state.pointerType === 'virtual') {
|
|
373
378
|
return;
|
|
374
379
|
}
|
|
375
380
|
|
|
@@ -427,7 +432,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
427
432
|
};
|
|
428
433
|
|
|
429
434
|
pressProps.onDragStart = (e) => {
|
|
430
|
-
if (!e.currentTarget.contains(e.target as
|
|
435
|
+
if (!e.currentTarget.contains(e.target as Element)) {
|
|
431
436
|
return;
|
|
432
437
|
}
|
|
433
438
|
|
|
@@ -437,13 +442,13 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
437
442
|
} else {
|
|
438
443
|
pressProps.onMouseDown = (e) => {
|
|
439
444
|
// Only handle left clicks
|
|
440
|
-
if (e.button !== 0 || !e.currentTarget.contains(e.target as
|
|
445
|
+
if (e.button !== 0 || !e.currentTarget.contains(e.target as Element)) {
|
|
441
446
|
return;
|
|
442
447
|
}
|
|
443
448
|
|
|
444
449
|
// Due to browser inconsistencies, especially on mobile browsers, we prevent
|
|
445
450
|
// default on mouse down and handle focusing the pressable element ourselves.
|
|
446
|
-
if (shouldPreventDefault(e.currentTarget
|
|
451
|
+
if (shouldPreventDefault(e.currentTarget)) {
|
|
447
452
|
e.preventDefault();
|
|
448
453
|
}
|
|
449
454
|
|
|
@@ -467,7 +472,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
467
472
|
};
|
|
468
473
|
|
|
469
474
|
pressProps.onMouseEnter = (e) => {
|
|
470
|
-
if (!e.currentTarget.contains(e.target as
|
|
475
|
+
if (!e.currentTarget.contains(e.target as Element)) {
|
|
471
476
|
return;
|
|
472
477
|
}
|
|
473
478
|
|
|
@@ -479,7 +484,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
479
484
|
};
|
|
480
485
|
|
|
481
486
|
pressProps.onMouseLeave = (e) => {
|
|
482
|
-
if (!e.currentTarget.contains(e.target as
|
|
487
|
+
if (!e.currentTarget.contains(e.target as Element)) {
|
|
483
488
|
return;
|
|
484
489
|
}
|
|
485
490
|
|
|
@@ -494,7 +499,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
494
499
|
};
|
|
495
500
|
|
|
496
501
|
pressProps.onMouseUp = (e) => {
|
|
497
|
-
if (!e.currentTarget.contains(e.target as
|
|
502
|
+
if (!e.currentTarget.contains(e.target as Element)) {
|
|
498
503
|
return;
|
|
499
504
|
}
|
|
500
505
|
|
|
@@ -527,7 +532,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
527
532
|
};
|
|
528
533
|
|
|
529
534
|
pressProps.onTouchStart = (e) => {
|
|
530
|
-
if (!e.currentTarget.contains(e.target as
|
|
535
|
+
if (!e.currentTarget.contains(e.target as Element)) {
|
|
531
536
|
return;
|
|
532
537
|
}
|
|
533
538
|
|
|
@@ -559,7 +564,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
559
564
|
};
|
|
560
565
|
|
|
561
566
|
pressProps.onTouchMove = (e) => {
|
|
562
|
-
if (!e.currentTarget.contains(e.target as
|
|
567
|
+
if (!e.currentTarget.contains(e.target as Element)) {
|
|
563
568
|
return;
|
|
564
569
|
}
|
|
565
570
|
|
|
@@ -584,7 +589,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
584
589
|
};
|
|
585
590
|
|
|
586
591
|
pressProps.onTouchEnd = (e) => {
|
|
587
|
-
if (!e.currentTarget.contains(e.target as
|
|
592
|
+
if (!e.currentTarget.contains(e.target as Element)) {
|
|
588
593
|
return;
|
|
589
594
|
}
|
|
590
595
|
|
|
@@ -612,7 +617,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
612
617
|
};
|
|
613
618
|
|
|
614
619
|
pressProps.onTouchCancel = (e) => {
|
|
615
|
-
if (!e.currentTarget.contains(e.target as
|
|
620
|
+
if (!e.currentTarget.contains(e.target as Element)) {
|
|
616
621
|
return;
|
|
617
622
|
}
|
|
618
623
|
|
|
@@ -623,7 +628,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
623
628
|
};
|
|
624
629
|
|
|
625
630
|
let onScroll = (e: Event) => {
|
|
626
|
-
if (state.isPressed && (e.target as
|
|
631
|
+
if (state.isPressed && (e.target as Element).contains(state.target)) {
|
|
627
632
|
cancel({
|
|
628
633
|
currentTarget: state.target,
|
|
629
634
|
shiftKey: false,
|
|
@@ -635,7 +640,7 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
635
640
|
};
|
|
636
641
|
|
|
637
642
|
pressProps.onDragStart = (e) => {
|
|
638
|
-
if (!e.currentTarget.contains(e.target as
|
|
643
|
+
if (!e.currentTarget.contains(e.target as Element)) {
|
|
639
644
|
return;
|
|
640
645
|
}
|
|
641
646
|
|
|
@@ -662,22 +667,21 @@ export function usePress(props: PressHookProps): PressResult {
|
|
|
662
667
|
};
|
|
663
668
|
}
|
|
664
669
|
|
|
665
|
-
function isHTMLAnchorLink(target:
|
|
670
|
+
function isHTMLAnchorLink(target: Element): boolean {
|
|
666
671
|
return target.tagName === 'A' && target.hasAttribute('href');
|
|
667
672
|
}
|
|
668
673
|
|
|
669
|
-
function isValidKeyboardEvent(event: KeyboardEvent): boolean {
|
|
670
|
-
const {key, code
|
|
671
|
-
const element =
|
|
672
|
-
const {tagName, isContentEditable} = element;
|
|
674
|
+
function isValidKeyboardEvent(event: KeyboardEvent, currentTarget: Element): boolean {
|
|
675
|
+
const {key, code} = event;
|
|
676
|
+
const element = currentTarget as HTMLElement;
|
|
673
677
|
const role = element.getAttribute('role');
|
|
674
678
|
// Accessibility for keyboards. Space and Enter only.
|
|
675
679
|
// "Spacebar" is for IE 11
|
|
676
680
|
return (
|
|
677
681
|
(key === 'Enter' || key === ' ' || key === 'Spacebar' || code === 'Space') &&
|
|
678
|
-
(
|
|
679
|
-
|
|
680
|
-
isContentEditable
|
|
682
|
+
!((element instanceof HTMLInputElement && !isValidInputKey(element, key)) ||
|
|
683
|
+
element instanceof HTMLTextAreaElement ||
|
|
684
|
+
element.isContentEditable) &&
|
|
681
685
|
// A link with a valid href should be handled natively,
|
|
682
686
|
// unless it also has role='button' and was triggered using Space.
|
|
683
687
|
(!isHTMLAnchorLink(element) || (role === 'button' && key !== 'Enter')) &&
|
|
@@ -708,7 +712,7 @@ function getTouchById(
|
|
|
708
712
|
return null;
|
|
709
713
|
}
|
|
710
714
|
|
|
711
|
-
function createEvent(target:
|
|
715
|
+
function createEvent(target: FocusableElement, e: EventBase): EventBase {
|
|
712
716
|
return {
|
|
713
717
|
currentTarget: target,
|
|
714
718
|
shiftKey: e.shiftKey,
|
|
@@ -758,19 +762,46 @@ function areRectanglesOverlapping(a: Rect, b: Rect) {
|
|
|
758
762
|
return true;
|
|
759
763
|
}
|
|
760
764
|
|
|
761
|
-
function isOverTarget(point: EventPoint, target:
|
|
765
|
+
function isOverTarget(point: EventPoint, target: Element) {
|
|
762
766
|
let rect = target.getBoundingClientRect();
|
|
763
767
|
let pointRect = getPointClientRect(point);
|
|
764
768
|
return areRectanglesOverlapping(rect, pointRect);
|
|
765
769
|
}
|
|
766
770
|
|
|
767
|
-
function shouldPreventDefault(target:
|
|
771
|
+
function shouldPreventDefault(target: Element) {
|
|
768
772
|
// We cannot prevent default if the target is a draggable element.
|
|
769
|
-
return !target.draggable;
|
|
773
|
+
return !(target instanceof HTMLElement) || !target.draggable;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function shouldPreventDefaultKeyboard(target: Element, key: string) {
|
|
777
|
+
if (target instanceof HTMLInputElement) {
|
|
778
|
+
return !isValidInputKey(target, key);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (target instanceof HTMLButtonElement) {
|
|
782
|
+
return target.type !== 'submit';
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
return true;
|
|
770
786
|
}
|
|
771
787
|
|
|
772
|
-
|
|
773
|
-
|
|
788
|
+
const nonTextInputTypes = new Set([
|
|
789
|
+
'checkbox',
|
|
790
|
+
'radio',
|
|
791
|
+
'range',
|
|
792
|
+
'color',
|
|
793
|
+
'file',
|
|
794
|
+
'image',
|
|
795
|
+
'button',
|
|
796
|
+
'submit',
|
|
797
|
+
'reset'
|
|
798
|
+
]);
|
|
799
|
+
|
|
800
|
+
function isValidInputKey(target: HTMLInputElement, key: string) {
|
|
801
|
+
// Only space should toggle checkboxes and radios, not enter.
|
|
802
|
+
return target.type === 'checkbox' || target.type === 'radio'
|
|
803
|
+
? key === ' '
|
|
804
|
+
: nonTextInputTypes.has(target.type);
|
|
774
805
|
}
|
|
775
806
|
|
|
776
807
|
function isVirtualPointerEvent(event: PointerEvent) {
|