@wordpress/compose 5.17.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 +10 -0
- package/README.md +4 -1
- package/build/hooks/use-dialog/index.js.map +1 -1
- package/build/hooks/use-disabled/index.js +10 -98
- package/build/hooks/use-disabled/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/hooks/use-resize-observer/index.native.js +1 -0
- package/build/hooks/use-resize-observer/index.native.js.map +1 -1
- package/build-module/hooks/use-dialog/index.js.map +1 -1
- package/build-module/hooks/use-disabled/index.js +10 -97
- package/build-module/hooks/use-disabled/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-module/hooks/use-resize-observer/index.native.js +1 -0
- package/build-module/hooks/use-resize-observer/index.native.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-disabled/index.d.ts +4 -1
- package/build-types/hooks/use-disabled/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/higher-order/with-global-events/test/index.js +18 -29
- package/src/higher-order/with-state/test/index.js +13 -26
- package/src/hooks/use-dialog/index.ts +3 -4
- package/src/hooks/use-disabled/index.ts +81 -0
- package/src/hooks/use-disabled/test/index.js +4 -36
- package/src/hooks/use-focus-outside/{index.js → index.ts} +55 -62
- package/src/hooks/use-focus-outside/test/index.js +112 -84
- package/src/hooks/use-focusable-iframe/{index.js → index.ts} +8 -3
- package/src/hooks/use-instance-id/test/index.js +9 -16
- package/src/hooks/use-media-query/test/index.js +33 -53
- package/src/hooks/use-resize-observer/index.native.js +5 -1
- package/src/hooks/use-resize-observer/test/index.native.js +18 -22
- package/src/hooks/use-viewport-match/test/index.js +78 -78
- package/tsconfig.tsbuildinfo +1 -1
- package/src/hooks/use-disabled/index.js +0 -197
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
4
|
+
import { render, screen } from '@testing-library/react';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* WordPress dependencies
|
|
@@ -30,8 +30,6 @@ jest.mock( '../listener', () => {
|
|
|
30
30
|
} );
|
|
31
31
|
|
|
32
32
|
describe( 'withGlobalEvents', () => {
|
|
33
|
-
let wrapper;
|
|
34
|
-
|
|
35
33
|
class OriginalComponent extends Component {
|
|
36
34
|
handleResize( event ) {
|
|
37
35
|
this.props.onResize( event );
|
|
@@ -50,51 +48,42 @@ describe( 'withGlobalEvents', () => {
|
|
|
50
48
|
jest.clearAllMocks();
|
|
51
49
|
} );
|
|
52
50
|
|
|
53
|
-
|
|
54
|
-
if ( wrapper ) {
|
|
55
|
-
wrapper.unmount();
|
|
56
|
-
wrapper = null;
|
|
57
|
-
}
|
|
58
|
-
} );
|
|
59
|
-
|
|
60
|
-
function mountEnhancedComponent( props = {} ) {
|
|
51
|
+
it( 'renders with original component', () => {
|
|
61
52
|
const EnhancedComponent = withGlobalEvents( {
|
|
62
53
|
resize: 'handleResize',
|
|
63
54
|
} )( OriginalComponent );
|
|
64
55
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
wrapper = TestRenderer.create(
|
|
68
|
-
<EnhancedComponent { ...props }>Hello</EnhancedComponent>
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
it( 'renders with original component', () => {
|
|
73
|
-
mountEnhancedComponent();
|
|
56
|
+
render( <EnhancedComponent ref={ () => {} }>Hello</EnhancedComponent> );
|
|
74
57
|
|
|
75
58
|
expect( console ).toHaveWarned();
|
|
76
|
-
expect(
|
|
77
|
-
'Hello'
|
|
78
|
-
);
|
|
59
|
+
expect( screen.getByText( 'Hello' ) ).toBeVisible();
|
|
79
60
|
} );
|
|
80
61
|
|
|
81
62
|
it( 'binds events from passed object', () => {
|
|
82
|
-
|
|
63
|
+
const EnhancedComponent = withGlobalEvents( {
|
|
64
|
+
resize: 'handleResize',
|
|
65
|
+
} )( OriginalComponent );
|
|
83
66
|
|
|
84
|
-
|
|
85
|
-
const hocInstance =
|
|
86
|
-
wrapper.root.findByType( OriginalComponent ).parent.instance;
|
|
67
|
+
render( <EnhancedComponent ref={ () => {} }>Hello</EnhancedComponent> );
|
|
87
68
|
|
|
88
69
|
expect( Listener._instance.add ).toHaveBeenCalledWith(
|
|
89
70
|
'resize',
|
|
90
|
-
|
|
71
|
+
// If not `undefined`, then we consider handlers were properly bound to the wrapper component.
|
|
72
|
+
expect.any( Object )
|
|
91
73
|
);
|
|
92
74
|
} );
|
|
93
75
|
|
|
94
76
|
it( 'handles events', () => {
|
|
77
|
+
const EnhancedComponent = withGlobalEvents( {
|
|
78
|
+
resize: 'handleResize',
|
|
79
|
+
} )( OriginalComponent );
|
|
95
80
|
const onResize = jest.fn();
|
|
96
81
|
|
|
97
|
-
|
|
82
|
+
render(
|
|
83
|
+
<EnhancedComponent ref={ () => {} } onResize={ onResize }>
|
|
84
|
+
Hello
|
|
85
|
+
</EnhancedComponent>
|
|
86
|
+
);
|
|
98
87
|
|
|
99
88
|
const event = { type: 'resize' };
|
|
100
89
|
|
|
@@ -1,31 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
4
|
+
import { render, screen } from '@testing-library/react';
|
|
5
|
+
import userEvent from '@testing-library/user-event';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Internal dependencies
|
|
8
9
|
*/
|
|
9
10
|
import withState from '../';
|
|
10
11
|
|
|
11
|
-
/**
|
|
12
|
-
* WordPress dependencies
|
|
13
|
-
*/
|
|
14
|
-
import { Component } from '@wordpress/element';
|
|
15
|
-
|
|
16
|
-
// This is needed because TestUtils does not accept a stateless component.
|
|
17
|
-
// anything run through a HOC ends up as a stateless component.
|
|
18
|
-
const getTestComponent = ( WrappedComponent ) => {
|
|
19
|
-
class TestComponent extends Component {
|
|
20
|
-
render() {
|
|
21
|
-
return <WrappedComponent { ...this.props } />;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
return <TestComponent />;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
12
|
describe( 'withState', () => {
|
|
28
|
-
it( 'should pass initial state and allow updates', () => {
|
|
13
|
+
it( 'should pass initial state and allow updates', async () => {
|
|
14
|
+
const user = userEvent.setup( {
|
|
15
|
+
advanceTimers: jest.advanceTimersByTime,
|
|
16
|
+
} );
|
|
29
17
|
const EnhancedComponent = withState( {
|
|
30
18
|
count: 0,
|
|
31
19
|
} )( ( { count, setState } ) => (
|
|
@@ -38,16 +26,15 @@ describe( 'withState', () => {
|
|
|
38
26
|
</button>
|
|
39
27
|
) );
|
|
40
28
|
|
|
41
|
-
|
|
42
|
-
getTestComponent( EnhancedComponent )
|
|
43
|
-
);
|
|
29
|
+
render( <EnhancedComponent /> );
|
|
44
30
|
|
|
45
|
-
const
|
|
46
|
-
TestUtils.findRenderedDOMComponentWithTag( wrapper, 'button' );
|
|
31
|
+
const button = screen.getByRole( 'button' );
|
|
47
32
|
|
|
48
33
|
expect( console ).toHaveWarned();
|
|
49
|
-
expect(
|
|
50
|
-
|
|
51
|
-
|
|
34
|
+
expect( button ).toHaveTextContent( '0' );
|
|
35
|
+
|
|
36
|
+
await user.click( button );
|
|
37
|
+
|
|
38
|
+
expect( button ).toHaveTextContent( '1' );
|
|
52
39
|
} );
|
|
53
40
|
} );
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { debounce } from '../../utils/debounce';
|
|
5
|
+
import useRefEffect from '../use-ref-effect';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* In some circumstances, such as block previews, all focusable DOM elements
|
|
9
|
+
* (input fields, links, buttons, etc.) need to be disabled. This hook adds the
|
|
10
|
+
* behavior to disable nested DOM elements to the returned ref.
|
|
11
|
+
*
|
|
12
|
+
* If you can, prefer the use of the inert HTML attribute.
|
|
13
|
+
*
|
|
14
|
+
* @param {Object} config Configuration object.
|
|
15
|
+
* @param {boolean=} config.isDisabled Whether the element should be disabled.
|
|
16
|
+
* @return {import('react').RefCallback<HTMLElement>} Element Ref.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```js
|
|
20
|
+
* import { useDisabled } from '@wordpress/compose';
|
|
21
|
+
*
|
|
22
|
+
* const DisabledExample = () => {
|
|
23
|
+
* const disabledRef = useDisabled();
|
|
24
|
+
* return (
|
|
25
|
+
* <div ref={ disabledRef }>
|
|
26
|
+
* <a href="#">This link will have tabindex set to -1</a>
|
|
27
|
+
* <input placeholder="This input will have the disabled attribute added to it." type="text" />
|
|
28
|
+
* </div>
|
|
29
|
+
* );
|
|
30
|
+
* };
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export default function useDisabled( {
|
|
34
|
+
isDisabled: isDisabledProp = false,
|
|
35
|
+
} = {} ) {
|
|
36
|
+
return useRefEffect(
|
|
37
|
+
( node ) => {
|
|
38
|
+
if ( isDisabledProp ) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A variable keeping track of the previous updates in order to restore them. */
|
|
43
|
+
const updates: Function[] = [];
|
|
44
|
+
const disable = () => {
|
|
45
|
+
node.childNodes.forEach( ( child ) => {
|
|
46
|
+
if ( ! ( child instanceof HTMLElement ) ) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if ( ! child.getAttribute( 'inert' ) ) {
|
|
50
|
+
child.setAttribute( 'inert', 'true' );
|
|
51
|
+
updates.push( () => {
|
|
52
|
+
child.removeAttribute( 'inert' );
|
|
53
|
+
} );
|
|
54
|
+
}
|
|
55
|
+
} );
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Debounce re-disable since disabling process itself will incur
|
|
59
|
+
// additional mutations which should be ignored.
|
|
60
|
+
const debouncedDisable = debounce( disable, 0, {
|
|
61
|
+
leading: true,
|
|
62
|
+
} );
|
|
63
|
+
disable();
|
|
64
|
+
|
|
65
|
+
/** @type {MutationObserver | undefined} */
|
|
66
|
+
const observer = new window.MutationObserver( debouncedDisable );
|
|
67
|
+
observer.observe( node, {
|
|
68
|
+
childList: true,
|
|
69
|
+
} );
|
|
70
|
+
|
|
71
|
+
return () => {
|
|
72
|
+
if ( observer ) {
|
|
73
|
+
observer.disconnect();
|
|
74
|
+
}
|
|
75
|
+
debouncedDisable.cancel();
|
|
76
|
+
updates.forEach( ( update ) => update() );
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
[ isDisabledProp ]
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -13,36 +13,6 @@ import { forwardRef } from '@wordpress/element';
|
|
|
13
13
|
*/
|
|
14
14
|
import useDisabled from '../';
|
|
15
15
|
|
|
16
|
-
jest.mock( '@wordpress/dom', () => {
|
|
17
|
-
const focus = jest.requireActual( '../../../../../dom/src' ).focus;
|
|
18
|
-
|
|
19
|
-
return {
|
|
20
|
-
focus: {
|
|
21
|
-
...focus,
|
|
22
|
-
focusable: {
|
|
23
|
-
...focus.focusable,
|
|
24
|
-
find( context ) {
|
|
25
|
-
// In JSDOM, all elements have zero'd widths and height.
|
|
26
|
-
// This is a metric for focusable's `isVisible`, so find
|
|
27
|
-
// and apply an arbitrary non-zero width.
|
|
28
|
-
Array.from( context.querySelectorAll( '*' ) ).forEach(
|
|
29
|
-
( element ) => {
|
|
30
|
-
Object.defineProperties( element, {
|
|
31
|
-
offsetWidth: {
|
|
32
|
-
get: () => 1,
|
|
33
|
-
configurable: true,
|
|
34
|
-
},
|
|
35
|
-
} );
|
|
36
|
-
}
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
return focus.focusable.find( ...arguments );
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
} );
|
|
45
|
-
|
|
46
16
|
jest.useRealTimers();
|
|
47
17
|
|
|
48
18
|
describe( 'useDisabled', () => {
|
|
@@ -69,11 +39,9 @@ describe( 'useDisabled', () => {
|
|
|
69
39
|
const link = screen.getByRole( 'link' );
|
|
70
40
|
const p = container.querySelector( 'p' );
|
|
71
41
|
|
|
72
|
-
expect( input.
|
|
73
|
-
expect( link.
|
|
74
|
-
expect( p.
|
|
75
|
-
expect( p.hasAttribute( 'tabindex' ) ).toBe( false );
|
|
76
|
-
expect( p.hasAttribute( 'disabled' ) ).toBe( false );
|
|
42
|
+
expect( input ).toHaveAttribute( 'inert', 'true' );
|
|
43
|
+
expect( link ).toHaveAttribute( 'inert', 'true' );
|
|
44
|
+
expect( p ).toHaveAttribute( 'inert', 'true' );
|
|
77
45
|
} );
|
|
78
46
|
|
|
79
47
|
it( 'will disable an element rendered in an update to the component', async () => {
|
|
@@ -86,7 +54,7 @@ describe( 'useDisabled', () => {
|
|
|
86
54
|
|
|
87
55
|
const button = screen.getByText( 'Button' );
|
|
88
56
|
await waitFor( () => {
|
|
89
|
-
expect( button.
|
|
57
|
+
expect( button ).toHaveAttribute( 'inert', 'true' );
|
|
90
58
|
} );
|
|
91
59
|
} );
|
|
92
60
|
} );
|
|
@@ -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();
|