@wordpress/compose 5.18.0 → 5.19.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/CHANGELOG.md +8 -0
- package/README.md +1 -1
- package/build/hooks/use-dialog/index.js.map +1 -1
- package/build/hooks/use-focus-outside/index.js +15 -56
- package/build/hooks/use-focus-outside/index.js.map +1 -1
- package/build/hooks/use-focusable-iframe/index.js +5 -2
- package/build/hooks/use-focusable-iframe/index.js.map +1 -1
- package/build-module/hooks/use-dialog/index.js.map +1 -1
- package/build-module/hooks/use-focus-outside/index.js +15 -56
- package/build-module/hooks/use-focus-outside/index.js.map +1 -1
- package/build-module/hooks/use-focusable-iframe/index.js +5 -2
- package/build-module/hooks/use-focusable-iframe/index.js.map +1 -1
- package/build-types/hooks/use-dialog/index.d.ts +2 -2
- package/build-types/hooks/use-dialog/index.d.ts.map +1 -1
- package/build-types/hooks/use-focus-outside/index.d.ts +16 -63
- package/build-types/hooks/use-focus-outside/index.d.ts.map +1 -1
- package/build-types/hooks/use-focusable-iframe/index.d.ts +6 -2
- package/build-types/hooks/use-focusable-iframe/index.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/hooks/use-dialog/index.ts +3 -4
- package/src/hooks/use-focus-outside/{index.js → index.ts} +55 -62
- package/src/hooks/use-focus-outside/test/index.js +101 -72
- package/src/hooks/use-focusable-iframe/{index.js → index.ts} +8 -3
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,72 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* @callback EventCallback
|
|
6
|
-
* @param {SyntheticEvent} event input related event.
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* @typedef FocusOutsideReactElement
|
|
10
|
-
* @property {EventCallback} handleFocusOutside callback for a focus outside event.
|
|
11
|
-
*/
|
|
12
|
-
/**
|
|
13
|
-
* @typedef {import('react').MutableRefObject<FocusOutsideReactElement | undefined>} FocusOutsideRef
|
|
14
|
-
*/
|
|
15
|
-
/**
|
|
16
|
-
* @typedef {Object} FocusOutsideReturnValue
|
|
17
|
-
* @property {EventCallback} onFocus An event handler for focus events.
|
|
18
|
-
* @property {EventCallback} onBlur An event handler for blur events.
|
|
19
|
-
* @property {EventCallback} onMouseDown An event handler for mouse down events.
|
|
20
|
-
* @property {EventCallback} onMouseUp An event handler for mouse up events.
|
|
21
|
-
* @property {EventCallback} onTouchStart An event handler for touch start events.
|
|
22
|
-
* @property {EventCallback} onTouchEnd An event handler for touch end events.
|
|
2
|
+
* External dependencies
|
|
23
3
|
*/
|
|
4
|
+
import type { FocusEventHandler, MouseEventHandler, TouchEventHandler, FocusEvent } from 'react';
|
|
5
|
+
declare type UseFocusOutsideReturn = {
|
|
6
|
+
onFocus: FocusEventHandler;
|
|
7
|
+
onMouseDown: MouseEventHandler;
|
|
8
|
+
onMouseUp: MouseEventHandler;
|
|
9
|
+
onTouchStart: TouchEventHandler;
|
|
10
|
+
onTouchEnd: TouchEventHandler;
|
|
11
|
+
onBlur: FocusEventHandler;
|
|
12
|
+
};
|
|
24
13
|
/**
|
|
25
14
|
* A react hook that can be used to check whether focus has moved outside the
|
|
26
15
|
* element the event handlers are bound to.
|
|
27
16
|
*
|
|
28
|
-
* @param
|
|
29
|
-
*
|
|
17
|
+
* @param onFocusOutside A callback triggered when focus moves outside
|
|
18
|
+
* the element the event handlers are bound to.
|
|
30
19
|
*
|
|
31
|
-
* @return
|
|
32
|
-
*
|
|
33
|
-
* outside that element.
|
|
20
|
+
* @return An object containing event handlers. Bind the event handlers to a
|
|
21
|
+
* wrapping element element to capture when focus moves outside that element.
|
|
34
22
|
*/
|
|
35
|
-
export default function useFocusOutside(onFocusOutside:
|
|
36
|
-
export
|
|
37
|
-
export type SyntheticEvent = import('react').SyntheticEvent;
|
|
38
|
-
export type EventCallback = (event: SyntheticEvent) => any;
|
|
39
|
-
export type FocusOutsideReactElement = {
|
|
40
|
-
/**
|
|
41
|
-
* callback for a focus outside event.
|
|
42
|
-
*/
|
|
43
|
-
handleFocusOutside: EventCallback;
|
|
44
|
-
};
|
|
45
|
-
export type FocusOutsideRef = import('react').MutableRefObject<FocusOutsideReactElement | undefined>;
|
|
46
|
-
export type FocusOutsideReturnValue = {
|
|
47
|
-
/**
|
|
48
|
-
* An event handler for focus events.
|
|
49
|
-
*/
|
|
50
|
-
onFocus: EventCallback;
|
|
51
|
-
/**
|
|
52
|
-
* An event handler for blur events.
|
|
53
|
-
*/
|
|
54
|
-
onBlur: EventCallback;
|
|
55
|
-
/**
|
|
56
|
-
* An event handler for mouse down events.
|
|
57
|
-
*/
|
|
58
|
-
onMouseDown: EventCallback;
|
|
59
|
-
/**
|
|
60
|
-
* An event handler for mouse up events.
|
|
61
|
-
*/
|
|
62
|
-
onMouseUp: EventCallback;
|
|
63
|
-
/**
|
|
64
|
-
* An event handler for touch start events.
|
|
65
|
-
*/
|
|
66
|
-
onTouchStart: EventCallback;
|
|
67
|
-
/**
|
|
68
|
-
* An event handler for touch end events.
|
|
69
|
-
*/
|
|
70
|
-
onTouchEnd: EventCallback;
|
|
71
|
-
};
|
|
23
|
+
export default function useFocusOutside(onFocusOutside: (event: FocusEvent) => void): UseFocusOutsideReturn;
|
|
24
|
+
export {};
|
|
72
25
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-focus-outside/index.
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-focus-outside/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EACX,iBAAiB,EAEjB,iBAAiB,EACjB,iBAAiB,EACjB,UAAU,EAGV,MAAM,OAAO,CAAC;AAqDf,aAAK,qBAAqB,GAAG;IAC5B,OAAO,EAAE,iBAAiB,CAAC;IAC3B,WAAW,EAAE,iBAAiB,CAAC;IAC/B,SAAS,EAAE,iBAAiB,CAAC;IAC7B,YAAY,EAAE,iBAAiB,CAAC;IAChC,UAAU,EAAE,iBAAiB,CAAC;IAC9B,MAAM,EAAE,iBAAiB,CAAC;CAC1B,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,CAAC,OAAO,UAAU,eAAe,CACtC,cAAc,EAAE,CAAE,KAAK,EAAE,UAAU,KAAM,IAAI,GAC3C,qBAAqB,CA4FvB"}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import type { RefCallback } from 'react';
|
|
1
5
|
/**
|
|
2
6
|
* Dispatches a bubbling focus event when the iframe receives focus. Use
|
|
3
7
|
* `onFocus` as usual on the iframe or a parent element.
|
|
4
8
|
*
|
|
5
|
-
* @return
|
|
9
|
+
* @return Ref to pass to the iframe.
|
|
6
10
|
*/
|
|
7
|
-
export default function useFocusableIframe():
|
|
11
|
+
export default function useFocusableIframe(): RefCallback<HTMLIFrameElement>;
|
|
8
12
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-focusable-iframe/index.
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-focusable-iframe/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAOzC;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,kBAAkB,IAAI,WAAW,CAAE,iBAAiB,CAAE,CAsB7E"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wordpress/compose",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.19.0",
|
|
4
4
|
"description": "WordPress higher-order components (HOCs).",
|
|
5
5
|
"author": "The WordPress Contributors",
|
|
6
6
|
"license": "GPL-2.0-or-later",
|
|
@@ -31,12 +31,12 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@babel/runtime": "^7.16.0",
|
|
33
33
|
"@types/mousetrap": "^1.6.8",
|
|
34
|
-
"@wordpress/deprecated": "^3.
|
|
35
|
-
"@wordpress/dom": "^3.
|
|
36
|
-
"@wordpress/element": "^4.
|
|
37
|
-
"@wordpress/is-shallow-equal": "^4.
|
|
38
|
-
"@wordpress/keycodes": "^3.
|
|
39
|
-
"@wordpress/priority-queue": "^2.
|
|
34
|
+
"@wordpress/deprecated": "^3.21.0",
|
|
35
|
+
"@wordpress/dom": "^3.21.0",
|
|
36
|
+
"@wordpress/element": "^4.19.0",
|
|
37
|
+
"@wordpress/is-shallow-equal": "^4.21.0",
|
|
38
|
+
"@wordpress/keycodes": "^3.21.0",
|
|
39
|
+
"@wordpress/priority-queue": "^2.21.0",
|
|
40
40
|
"change-case": "^4.1.2",
|
|
41
41
|
"clipboard": "^2.0.8",
|
|
42
42
|
"mousetrap": "^1.6.5",
|
|
@@ -48,5 +48,5 @@
|
|
|
48
48
|
"publishConfig": {
|
|
49
49
|
"access": "public"
|
|
50
50
|
},
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "511f4cc1f0138641bc4394bc1cf36e833109c791"
|
|
52
52
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
|
-
import type {
|
|
4
|
+
import type { RefCallback, SyntheticEvent } from 'react';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* WordPress dependencies
|
|
@@ -17,7 +17,6 @@ import useFocusOnMount from '../use-focus-on-mount';
|
|
|
17
17
|
import useFocusReturn from '../use-focus-return';
|
|
18
18
|
import useFocusOutside from '../use-focus-outside';
|
|
19
19
|
import useMergeRefs from '../use-merge-refs';
|
|
20
|
-
import type { FocusOutsideReturnValue } from '../use-focus-outside';
|
|
21
20
|
|
|
22
21
|
type DialogOptions = {
|
|
23
22
|
focusOnMount?: Parameters< typeof useFocusOnMount >[ 0 ];
|
|
@@ -35,7 +34,7 @@ type DialogOptions = {
|
|
|
35
34
|
|
|
36
35
|
type useDialogReturn = [
|
|
37
36
|
RefCallback< HTMLElement >,
|
|
38
|
-
|
|
37
|
+
ReturnType< typeof useFocusOutside > & Pick< HTMLElement, 'tabIndex' >
|
|
39
38
|
];
|
|
40
39
|
|
|
41
40
|
/**
|
|
@@ -64,7 +63,7 @@ function useDialog( options: DialogOptions ): useDialogReturn {
|
|
|
64
63
|
currentOptions.current.onClose();
|
|
65
64
|
}
|
|
66
65
|
} );
|
|
67
|
-
const closeOnEscapeRef = useCallback( ( node ) => {
|
|
66
|
+
const closeOnEscapeRef = useCallback( ( node: HTMLElement ) => {
|
|
68
67
|
if ( ! node ) {
|
|
69
68
|
return;
|
|
70
69
|
}
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import type {
|
|
5
|
+
FocusEventHandler,
|
|
6
|
+
EventHandler,
|
|
7
|
+
MouseEventHandler,
|
|
8
|
+
TouchEventHandler,
|
|
9
|
+
FocusEvent,
|
|
10
|
+
MouseEvent,
|
|
11
|
+
TouchEvent,
|
|
12
|
+
} from 'react';
|
|
13
|
+
|
|
1
14
|
/**
|
|
2
15
|
* WordPress dependencies
|
|
3
16
|
*/
|
|
@@ -6,28 +19,32 @@ import { useCallback, useEffect, useRef } from '@wordpress/element';
|
|
|
6
19
|
/**
|
|
7
20
|
* Input types which are classified as button types, for use in considering
|
|
8
21
|
* whether element is a (focus-normalized) button.
|
|
9
|
-
*
|
|
10
|
-
* @type {string[]}
|
|
11
22
|
*/
|
|
12
23
|
const INPUT_BUTTON_TYPES = [ 'button', 'submit' ];
|
|
13
24
|
|
|
14
25
|
/**
|
|
15
|
-
*
|
|
26
|
+
* List of HTML button elements subject to focus normalization
|
|
27
|
+
*
|
|
28
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus
|
|
16
29
|
*/
|
|
30
|
+
type FocusNormalizedButton =
|
|
31
|
+
| HTMLButtonElement
|
|
32
|
+
| HTMLLinkElement
|
|
33
|
+
| HTMLInputElement;
|
|
17
34
|
|
|
18
|
-
// Disable reason: Rule doesn't support predicate return types.
|
|
19
|
-
/* eslint-disable jsdoc/valid-types */
|
|
20
35
|
/**
|
|
21
36
|
* Returns true if the given element is a button element subject to focus
|
|
22
37
|
* normalization, or false otherwise.
|
|
23
38
|
*
|
|
24
39
|
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus
|
|
25
40
|
*
|
|
26
|
-
* @param
|
|
41
|
+
* @param eventTarget The target from a mouse or touch event.
|
|
27
42
|
*
|
|
28
|
-
* @return
|
|
43
|
+
* @return Whether the element is a button element subject to focus normalization.
|
|
29
44
|
*/
|
|
30
|
-
function isFocusNormalizedButton(
|
|
45
|
+
function isFocusNormalizedButton(
|
|
46
|
+
eventTarget: EventTarget
|
|
47
|
+
): eventTarget is FocusNormalizedButton {
|
|
31
48
|
if ( ! ( eventTarget instanceof window.HTMLElement ) ) {
|
|
32
49
|
return false;
|
|
33
50
|
}
|
|
@@ -38,54 +55,35 @@ function isFocusNormalizedButton( eventTarget ) {
|
|
|
38
55
|
|
|
39
56
|
case 'INPUT':
|
|
40
57
|
return INPUT_BUTTON_TYPES.includes(
|
|
41
|
-
|
|
58
|
+
( eventTarget as HTMLInputElement ).type
|
|
42
59
|
);
|
|
43
60
|
}
|
|
44
61
|
|
|
45
62
|
return false;
|
|
46
63
|
}
|
|
47
|
-
/* eslint-enable jsdoc/valid-types */
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* @typedef {import('react').SyntheticEvent} SyntheticEvent
|
|
51
|
-
*/
|
|
52
64
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
*/
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* @typedef {import('react').MutableRefObject<FocusOutsideReactElement | undefined>} FocusOutsideRef
|
|
65
|
-
*/
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* @typedef {Object} FocusOutsideReturnValue
|
|
69
|
-
* @property {EventCallback} onFocus An event handler for focus events.
|
|
70
|
-
* @property {EventCallback} onBlur An event handler for blur events.
|
|
71
|
-
* @property {EventCallback} onMouseDown An event handler for mouse down events.
|
|
72
|
-
* @property {EventCallback} onMouseUp An event handler for mouse up events.
|
|
73
|
-
* @property {EventCallback} onTouchStart An event handler for touch start events.
|
|
74
|
-
* @property {EventCallback} onTouchEnd An event handler for touch end events.
|
|
75
|
-
*/
|
|
65
|
+
type UseFocusOutsideReturn = {
|
|
66
|
+
onFocus: FocusEventHandler;
|
|
67
|
+
onMouseDown: MouseEventHandler;
|
|
68
|
+
onMouseUp: MouseEventHandler;
|
|
69
|
+
onTouchStart: TouchEventHandler;
|
|
70
|
+
onTouchEnd: TouchEventHandler;
|
|
71
|
+
onBlur: FocusEventHandler;
|
|
72
|
+
};
|
|
76
73
|
|
|
77
74
|
/**
|
|
78
75
|
* A react hook that can be used to check whether focus has moved outside the
|
|
79
76
|
* element the event handlers are bound to.
|
|
80
77
|
*
|
|
81
|
-
* @param
|
|
82
|
-
*
|
|
78
|
+
* @param onFocusOutside A callback triggered when focus moves outside
|
|
79
|
+
* the element the event handlers are bound to.
|
|
83
80
|
*
|
|
84
|
-
* @return
|
|
85
|
-
*
|
|
86
|
-
* outside that element.
|
|
81
|
+
* @return An object containing event handlers. Bind the event handlers to a
|
|
82
|
+
* wrapping element element to capture when focus moves outside that element.
|
|
87
83
|
*/
|
|
88
|
-
export default function useFocusOutside(
|
|
84
|
+
export default function useFocusOutside(
|
|
85
|
+
onFocusOutside: ( event: FocusEvent ) => void
|
|
86
|
+
): UseFocusOutsideReturn {
|
|
89
87
|
const currentOnFocusOutside = useRef( onFocusOutside );
|
|
90
88
|
useEffect( () => {
|
|
91
89
|
currentOnFocusOutside.current = onFocusOutside;
|
|
@@ -93,10 +91,7 @@ export default function useFocusOutside( onFocusOutside ) {
|
|
|
93
91
|
|
|
94
92
|
const preventBlurCheck = useRef( false );
|
|
95
93
|
|
|
96
|
-
|
|
97
|
-
* @type {import('react').MutableRefObject<number | undefined>}
|
|
98
|
-
*/
|
|
99
|
-
const blurCheckTimeoutId = useRef();
|
|
94
|
+
const blurCheckTimeoutId = useRef< number | undefined >();
|
|
100
95
|
|
|
101
96
|
/**
|
|
102
97
|
* Cancel a blur check timeout.
|
|
@@ -124,20 +119,20 @@ export default function useFocusOutside( onFocusOutside ) {
|
|
|
124
119
|
* button elements when clicked, while others do. The logic here
|
|
125
120
|
* intends to normalize this as treating click on buttons as focus.
|
|
126
121
|
*
|
|
122
|
+
* @param event
|
|
127
123
|
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus
|
|
128
|
-
*
|
|
129
|
-
* @param {SyntheticEvent} event Event for mousedown or mouseup.
|
|
130
124
|
*/
|
|
131
|
-
const normalizeButtonFocus
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
125
|
+
const normalizeButtonFocus: EventHandler< MouseEvent | TouchEvent > =
|
|
126
|
+
useCallback( ( event ) => {
|
|
127
|
+
const { type, target } = event;
|
|
128
|
+
const isInteractionEnd = [ 'mouseup', 'touchend' ].includes( type );
|
|
129
|
+
|
|
130
|
+
if ( isInteractionEnd ) {
|
|
131
|
+
preventBlurCheck.current = false;
|
|
132
|
+
} else if ( isFocusNormalizedButton( target ) ) {
|
|
133
|
+
preventBlurCheck.current = true;
|
|
134
|
+
}
|
|
135
|
+
}, [] );
|
|
141
136
|
|
|
142
137
|
/**
|
|
143
138
|
* A callback triggered when a blur event occurs on the element the handler
|
|
@@ -145,10 +140,8 @@ export default function useFocusOutside( onFocusOutside ) {
|
|
|
145
140
|
*
|
|
146
141
|
* Calls the `onFocusOutside` callback in an immediate timeout if focus has
|
|
147
142
|
* move outside the bound element and is still within the document.
|
|
148
|
-
*
|
|
149
|
-
* @param {SyntheticEvent} event Blur event.
|
|
150
143
|
*/
|
|
151
|
-
const queueBlurCheck = useCallback( ( event ) => {
|
|
144
|
+
const queueBlurCheck: FocusEventHandler = useCallback( ( event ) => {
|
|
152
145
|
// React does not allow using an event reference asynchronously
|
|
153
146
|
// due to recycling behavior, except when explicitly persisted.
|
|
154
147
|
event.persist();
|
|
@@ -4,120 +4,149 @@
|
|
|
4
4
|
import { render, screen } from '@testing-library/react';
|
|
5
5
|
import userEvent from '@testing-library/user-event';
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
* WordPress dependencies
|
|
9
|
-
*/
|
|
10
|
-
import { Component } from '@wordpress/element';
|
|
11
|
-
|
|
12
7
|
/**
|
|
13
8
|
* Internal dependencies
|
|
14
9
|
*/
|
|
15
10
|
import useFocusOutside from '../';
|
|
16
11
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
let origHasFocus;
|
|
21
|
-
|
|
22
|
-
const FocusOutsideComponent = ( { onFocusOutside: callback } ) => (
|
|
12
|
+
const FocusOutsideComponent = ( { onFocusOutside: callback } ) => (
|
|
13
|
+
<div>
|
|
14
|
+
{ /* Wrapper */ }
|
|
23
15
|
<div { ...useFocusOutside( callback ) }>
|
|
24
16
|
<input type="text" />
|
|
25
|
-
<
|
|
17
|
+
<button>Button inside the wrapper</button>
|
|
18
|
+
<iframe title="test-iframe">
|
|
19
|
+
<button>Inside the iframe</button>
|
|
20
|
+
</iframe>
|
|
26
21
|
</div>
|
|
27
|
-
);
|
|
28
22
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
}
|
|
23
|
+
<button>Button outside the wrapper</button>
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
34
26
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
onFocusOutside = jest.fn();
|
|
42
|
-
} );
|
|
43
|
-
|
|
44
|
-
afterEach( () => {
|
|
45
|
-
document.hasFocus = origHasFocus;
|
|
46
|
-
} );
|
|
47
|
-
|
|
48
|
-
it( 'should not call handler if focus shifts to element within component', () => {
|
|
49
|
-
render( <TestComponent onFocusOutside={ onFocusOutside } /> );
|
|
27
|
+
describe( 'useFocusOutside', () => {
|
|
28
|
+
it( 'should not call handler if focus shifts to element within component', async () => {
|
|
29
|
+
const mockOnFocusOutside = jest.fn();
|
|
30
|
+
const user = userEvent.setup( {
|
|
31
|
+
advanceTimers: jest.advanceTimersByTime,
|
|
32
|
+
} );
|
|
50
33
|
|
|
51
|
-
|
|
52
|
-
|
|
34
|
+
render(
|
|
35
|
+
<FocusOutsideComponent onFocusOutside={ mockOnFocusOutside } />
|
|
36
|
+
);
|
|
53
37
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
38
|
+
// Tab through the interactive elements inside the wrapper,
|
|
39
|
+
// causing multiple focus/blur events.
|
|
40
|
+
await user.tab();
|
|
41
|
+
expect( screen.getByRole( 'textbox' ) ).toHaveFocus();
|
|
57
42
|
|
|
58
|
-
|
|
43
|
+
await user.tab();
|
|
44
|
+
expect(
|
|
45
|
+
screen.getByRole( 'button', { name: 'Button inside the wrapper' } )
|
|
46
|
+
).toHaveFocus();
|
|
59
47
|
|
|
60
|
-
expect(
|
|
48
|
+
expect( mockOnFocusOutside ).not.toHaveBeenCalled();
|
|
61
49
|
} );
|
|
62
50
|
|
|
63
51
|
it( 'should not call handler if focus transitions via click to button', async () => {
|
|
52
|
+
const mockOnFocusOutside = jest.fn();
|
|
64
53
|
const user = userEvent.setup( {
|
|
65
54
|
advanceTimers: jest.advanceTimersByTime,
|
|
66
55
|
} );
|
|
67
|
-
render( <TestComponent onFocusOutside={ onFocusOutside } /> );
|
|
68
|
-
|
|
69
|
-
const input = screen.getByRole( 'textbox' );
|
|
70
|
-
const button = screen.getByRole( 'button' );
|
|
71
56
|
|
|
72
|
-
|
|
73
|
-
|
|
57
|
+
render(
|
|
58
|
+
<FocusOutsideComponent onFocusOutside={ mockOnFocusOutside } />
|
|
59
|
+
);
|
|
74
60
|
|
|
75
|
-
|
|
61
|
+
// Click the input and the button, causing multiple focus/blur events.
|
|
62
|
+
await user.click( screen.getByRole( 'textbox' ) );
|
|
63
|
+
await user.click(
|
|
64
|
+
screen.getByRole( 'button', { name: 'Button inside the wrapper' } )
|
|
65
|
+
);
|
|
76
66
|
|
|
77
|
-
expect(
|
|
67
|
+
expect( mockOnFocusOutside ).not.toHaveBeenCalled();
|
|
78
68
|
} );
|
|
79
69
|
|
|
80
|
-
it( 'should call handler if focus
|
|
81
|
-
|
|
70
|
+
it( 'should call handler if focus shifts to element outside component', async () => {
|
|
71
|
+
const mockOnFocusOutside = jest.fn();
|
|
72
|
+
const user = userEvent.setup( {
|
|
73
|
+
advanceTimers: jest.advanceTimersByTime,
|
|
74
|
+
} );
|
|
75
|
+
|
|
76
|
+
render(
|
|
77
|
+
<FocusOutsideComponent onFocusOutside={ mockOnFocusOutside } />
|
|
78
|
+
);
|
|
82
79
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
// Click and focus button inside the wrapper
|
|
81
|
+
await user.click(
|
|
82
|
+
screen.getByRole( 'button', { name: 'Button inside the wrapper' } )
|
|
83
|
+
);
|
|
86
84
|
|
|
87
|
-
|
|
85
|
+
expect( mockOnFocusOutside ).not.toHaveBeenCalled();
|
|
88
86
|
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
// Click and focus button outside the wrapper
|
|
88
|
+
await user.click(
|
|
89
|
+
screen.getByRole( 'button', { name: 'Button outside the wrapper' } )
|
|
90
|
+
);
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
expect( mockOnFocusOutside ).toHaveBeenCalled();
|
|
93
|
+
} );
|
|
94
94
|
|
|
95
|
+
it( 'should not call handler if focus shifts outside the component when the document does not have focus', async () => {
|
|
95
96
|
// Force document.hasFocus() to return false to simulate the window/document losing focus
|
|
96
97
|
// See https://developer.mozilla.org/en-US/docs/Web/API/Document/hasFocus.
|
|
97
|
-
|
|
98
|
+
const mockedDocumentHasFocus = jest
|
|
99
|
+
.spyOn( document, 'hasFocus' )
|
|
100
|
+
.mockImplementation( () => false );
|
|
101
|
+
const mockOnFocusOutside = jest.fn();
|
|
102
|
+
const user = userEvent.setup( {
|
|
103
|
+
advanceTimers: jest.advanceTimersByTime,
|
|
104
|
+
} );
|
|
98
105
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
106
|
+
render(
|
|
107
|
+
<FocusOutsideComponent onFocusOutside={ mockOnFocusOutside } />
|
|
108
|
+
);
|
|
102
109
|
|
|
103
|
-
|
|
110
|
+
// Click and focus button inside the wrapper, then click and focus
|
|
111
|
+
// a button outside the wrapper.
|
|
112
|
+
await user.click(
|
|
113
|
+
screen.getByRole( 'button', { name: 'Button inside the wrapper' } )
|
|
114
|
+
);
|
|
115
|
+
await user.click(
|
|
116
|
+
screen.getByRole( 'button', { name: 'Button outside the wrapper' } )
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// The handler is not called thanks to the mocked return value of
|
|
120
|
+
// `document.hasFocus()`
|
|
121
|
+
expect( mockOnFocusOutside ).not.toHaveBeenCalled();
|
|
104
122
|
|
|
105
|
-
|
|
123
|
+
// Restore the `document.hasFocus()` function to its original implementation.
|
|
124
|
+
mockedDocumentHasFocus.mockRestore();
|
|
106
125
|
} );
|
|
107
126
|
|
|
108
|
-
it( 'should cancel check when unmounting while queued', () => {
|
|
109
|
-
const
|
|
110
|
-
|
|
127
|
+
it( 'should cancel check when unmounting while queued', async () => {
|
|
128
|
+
const mockOnFocusOutside = jest.fn();
|
|
129
|
+
const user = userEvent.setup( {
|
|
130
|
+
advanceTimers: jest.advanceTimersByTime,
|
|
131
|
+
} );
|
|
132
|
+
|
|
133
|
+
const { unmount } = render(
|
|
134
|
+
<FocusOutsideComponent onFocusOutside={ mockOnFocusOutside } />
|
|
111
135
|
);
|
|
112
136
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
137
|
+
// Click and focus button inside the wrapper.
|
|
138
|
+
const button = screen.getByRole( 'button', {
|
|
139
|
+
name: 'Button inside the wrapper',
|
|
140
|
+
} );
|
|
141
|
+
await user.click( button );
|
|
116
142
|
|
|
117
|
-
|
|
143
|
+
// Simulate a blur event and the wrapper unmounting while the blur event
|
|
144
|
+
// handler is queued
|
|
145
|
+
button.blur();
|
|
146
|
+
unmount();
|
|
118
147
|
|
|
119
148
|
jest.runAllTimers();
|
|
120
149
|
|
|
121
|
-
expect(
|
|
150
|
+
expect( mockOnFocusOutside ).not.toHaveBeenCalled();
|
|
122
151
|
} );
|
|
123
152
|
} );
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import type { RefCallback } from 'react';
|
|
5
|
+
|
|
1
6
|
/**
|
|
2
7
|
* Internal dependencies
|
|
3
8
|
*/
|
|
@@ -7,9 +12,9 @@ import useRefEffect from '../use-ref-effect';
|
|
|
7
12
|
* Dispatches a bubbling focus event when the iframe receives focus. Use
|
|
8
13
|
* `onFocus` as usual on the iframe or a parent element.
|
|
9
14
|
*
|
|
10
|
-
* @return
|
|
15
|
+
* @return Ref to pass to the iframe.
|
|
11
16
|
*/
|
|
12
|
-
export default function useFocusableIframe() {
|
|
17
|
+
export default function useFocusableIframe(): RefCallback< HTMLIFrameElement > {
|
|
13
18
|
return useRefEffect( ( element ) => {
|
|
14
19
|
const { ownerDocument } = element;
|
|
15
20
|
if ( ! ownerDocument ) return;
|
|
@@ -22,7 +27,7 @@ export default function useFocusableIframe() {
|
|
|
22
27
|
*/
|
|
23
28
|
function checkFocus() {
|
|
24
29
|
if ( ownerDocument && ownerDocument.activeElement === element ) {
|
|
25
|
-
|
|
30
|
+
( element as HTMLElement ).focus();
|
|
26
31
|
}
|
|
27
32
|
}
|
|
28
33
|
|