@wordpress/compose 5.16.1-next.4d3b314fd5.0 → 5.18.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +3 -0
  3. package/build/higher-order/with-global-events/listener.js +7 -7
  4. package/build/higher-order/with-global-events/listener.js.map +1 -1
  5. package/build/higher-order/with-safe-timeout/index.js +1 -7
  6. package/build/higher-order/with-safe-timeout/index.js.map +1 -1
  7. package/build/hooks/use-disabled/index.js +10 -104
  8. package/build/hooks/use-disabled/index.js.map +1 -1
  9. package/build/hooks/use-focus-outside/index.js +2 -8
  10. package/build/hooks/use-focus-outside/index.js.map +1 -1
  11. package/build/hooks/use-focus-outside/index.native.js +2 -8
  12. package/build/hooks/use-focus-outside/index.native.js.map +1 -1
  13. package/build/hooks/use-keyboard-shortcut/index.js +2 -3
  14. package/build/hooks/use-keyboard-shortcut/index.js.map +1 -1
  15. package/build/hooks/use-merge-refs/index.js +4 -2
  16. package/build/hooks/use-merge-refs/index.js.map +1 -1
  17. package/build/hooks/use-resize-observer/index.native.js +1 -0
  18. package/build/hooks/use-resize-observer/index.native.js.map +1 -1
  19. package/build-module/higher-order/with-global-events/listener.js +7 -6
  20. package/build-module/higher-order/with-global-events/listener.js.map +1 -1
  21. package/build-module/higher-order/with-safe-timeout/index.js +1 -6
  22. package/build-module/higher-order/with-safe-timeout/index.js.map +1 -1
  23. package/build-module/hooks/use-disabled/index.js +10 -102
  24. package/build-module/hooks/use-disabled/index.js.map +1 -1
  25. package/build-module/hooks/use-focus-outside/index.js +2 -7
  26. package/build-module/hooks/use-focus-outside/index.js.map +1 -1
  27. package/build-module/hooks/use-focus-outside/index.native.js +2 -7
  28. package/build-module/hooks/use-focus-outside/index.native.js.map +1 -1
  29. package/build-module/hooks/use-keyboard-shortcut/index.js +2 -2
  30. package/build-module/hooks/use-keyboard-shortcut/index.js.map +1 -1
  31. package/build-module/hooks/use-merge-refs/index.js +4 -2
  32. package/build-module/hooks/use-merge-refs/index.js.map +1 -1
  33. package/build-module/hooks/use-resize-observer/index.native.js +1 -0
  34. package/build-module/hooks/use-resize-observer/index.native.js.map +1 -1
  35. package/build-types/higher-order/with-global-events/listener.d.ts.map +1 -1
  36. package/build-types/higher-order/with-safe-timeout/index.d.ts.map +1 -1
  37. package/build-types/hooks/use-disabled/index.d.ts +4 -1
  38. package/build-types/hooks/use-disabled/index.d.ts.map +1 -1
  39. package/build-types/hooks/use-focus-outside/index.d.ts.map +1 -1
  40. package/build-types/hooks/use-keyboard-shortcut/index.d.ts.map +1 -1
  41. package/build-types/hooks/use-merge-refs/index.d.ts.map +1 -1
  42. package/package.json +8 -10
  43. package/src/higher-order/with-global-events/listener.js +6 -8
  44. package/src/higher-order/with-global-events/test/index.js +18 -29
  45. package/src/higher-order/with-safe-timeout/index.tsx +1 -6
  46. package/src/higher-order/with-state/test/index.js +13 -26
  47. package/src/hooks/use-disabled/index.ts +81 -0
  48. package/src/hooks/use-disabled/test/index.js +4 -36
  49. package/src/hooks/use-focus-outside/index.js +2 -8
  50. package/src/hooks/use-focus-outside/index.native.js +2 -8
  51. package/src/hooks/use-focus-outside/test/index.js +44 -45
  52. package/src/hooks/use-instance-id/test/index.js +9 -16
  53. package/src/hooks/use-keyboard-shortcut/index.js +4 -2
  54. package/src/hooks/use-media-query/test/index.js +33 -53
  55. package/src/hooks/use-merge-refs/index.js +6 -1
  56. package/src/hooks/use-merge-refs/test/index.js +44 -0
  57. package/src/hooks/use-resize-observer/index.native.js +5 -1
  58. package/src/hooks/use-resize-observer/test/index.native.js +18 -22
  59. package/src/hooks/use-viewport-match/test/index.js +78 -78
  60. package/tsconfig.tsbuildinfo +1 -1
  61. package/src/hooks/use-disabled/index.js +0 -203
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/higher-order/with-safe-timeout/index.tsx"],"names":[],"mappings":"AAUA;;GAEG;AACH,OAAO,EAEN,iBAAiB,EACjB,oBAAoB,EACpB,MAAM,2CAA2C,CAAC;AAEnD;;;;;;;GAOG;AACH,UAAU,YAAY;IACrB,UAAU,EAAE,CAAE,EAAE,EAAE,MAAM,IAAI,EAAE,KAAK,EAAE,MAAM,KAAM,MAAM,CAAC;IACxD,YAAY,EAAE,CAAE,EAAE,EAAE,MAAM,KAAM,IAAI,CAAC;CACrC;AAED;;;GAGG;AACH,QAAA,MAAM,eAAe;;kBAMR,MAAM,EAAE;;uBAaF,MAAM,IAAI,SAAS,MAAM;yBASvB,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;CAkB1B,CAAC;AAEF,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/higher-order/with-safe-timeout/index.tsx"],"names":[],"mappings":"AAKA;;GAEG;AACH,OAAO,EAEN,iBAAiB,EACjB,oBAAoB,EACpB,MAAM,2CAA2C,CAAC;AAEnD;;;;;;;GAOG;AACH,UAAU,YAAY;IACrB,UAAU,EAAE,CAAE,EAAE,EAAE,MAAM,IAAI,EAAE,KAAK,EAAE,MAAM,KAAM,MAAM,CAAC;IACxD,YAAY,EAAE,CAAE,EAAE,EAAE,MAAM,KAAM,IAAI,CAAC;CACrC;AAED;;;GAGG;AACH,QAAA,MAAM,eAAe;;kBAMR,MAAM,EAAE;;uBAaF,MAAM,IAAI,SAAS,MAAM;yBASvB,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;CAkB1B,CAAC;AAEF,eAAe,eAAe,CAAC"}
@@ -3,6 +3,8 @@
3
3
  * (input fields, links, buttons, etc.) need to be disabled. This hook adds the
4
4
  * behavior to disable nested DOM elements to the returned ref.
5
5
  *
6
+ * If you can, prefer the use of the inert HTML attribute.
7
+ *
6
8
  * @param {Object} config Configuration object.
7
9
  * @param {boolean=} config.isDisabled Whether the element should be disabled.
8
10
  * @return {import('react').RefCallback<HTMLElement>} Element Ref.
@@ -10,6 +12,7 @@
10
12
  * @example
11
13
  * ```js
12
14
  * import { useDisabled } from '@wordpress/compose';
15
+ *
13
16
  * const DisabledExample = () => {
14
17
  * const disabledRef = useDisabled();
15
18
  * return (
@@ -23,5 +26,5 @@
23
26
  */
24
27
  export default function useDisabled({ isDisabled: isDisabledProp, }?: {
25
28
  isDisabled?: boolean | undefined;
26
- }): import('react').RefCallback<HTMLElement>;
29
+ }): (instance: Node | null) => void;
27
30
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-disabled/index.js"],"names":[],"mappings":"AAmCA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH;IAjB4B,UAAU,GAA3B,OAAO;IACN,OAAO,OAAO,EAAE,WAAW,CAAC,WAAW,CAAC,CAgKnD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-disabled/index.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,CAAC,OAAO,UAAU,WAAW,CAAE,EACpC,UAAU,EAAE,cAAsB,GAClC;;CAAK,mCA8CL"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-focus-outside/index.js"],"names":[],"mappings":"AAsDA;;GAEG;AAEH;;;GAGG;AAEH;;;GAGG;AAEH;;GAEG;AAEH;;;;;;;;GAQG;AAEH;;;;;;;;;;GAUG;AACH,wDAPW,aAAa,GAGZ,uBAAuB,CAqGlC;oCA3KY,iBAAiB,GAAG,eAAe,GAAG,gBAAgB;6BAoCtD,OAAO,OAAO,EAAE,cAAc;oCAKhC,cAAc;;;;;wBAKX,aAAa;;8BAId,OAAO,OAAO,EAAE,gBAAgB,CAAC,wBAAwB,GAAG,SAAS,CAAC;;;;;aAKrE,aAAa;;;;YACb,aAAa;;;;iBACb,aAAa;;;;eACb,aAAa;;;;kBACb,aAAa;;;;gBACb,aAAa"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-focus-outside/index.js"],"names":[],"mappings":"AAgDA;;GAEG;AAEH;;;GAGG;AAEH;;;GAGG;AAEH;;GAEG;AAEH;;;;;;;;GAQG;AAEH;;;;;;;;;;GAUG;AACH,wDAPW,aAAa,GAGZ,uBAAuB,CAqGlC;oCA1KY,iBAAiB,GAAG,eAAe,GAAG,gBAAgB;6BAmCtD,OAAO,OAAO,EAAE,cAAc;oCAKhC,cAAc;;;;;wBAKX,aAAa;;8BAId,OAAO,OAAO,EAAE,gBAAgB,CAAC,wBAAwB,GAAG,SAAS,CAAC;;;;;aAKrE,aAAa;;;;YACb,aAAa;;;;iBACb,aAAa;;;;eACb,aAAa;;;;kBACb,aAAa;;;;gBACb,aAAa"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-keyboard-shortcut/index.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAaA;;;;;;;;;GASG;AAGH;;;;;;;;GAQG;AACH,gDAJW,MAAM,EAAE,GAAC,MAAM,gBACX,OAAO,WAAW,EAAE,qBAAqB,SAAS,MAAM,KAAK,IAAI,mDACrE,wBAAwB,QAsElC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-keyboard-shortcut/index.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAYA;;;;;;;;;GASG;AAGH;;;;;;;;GAQG;AACH,gDAJW,MAAM,EAAE,GAAC,MAAM,gBACX,OAAO,WAAW,EAAE,qBAAqB,SAAS,MAAM,KAAK,IAAI,mDACrE,wBAAwB,QAyElC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-merge-refs/index.js"],"names":[],"mappings":"AA4BA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wIAsDC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-merge-refs/index.js"],"names":[],"mappings":"AA4BA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wIA2DC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/compose",
3
- "version": "5.16.1-next.4d3b314fd5.0",
3
+ "version": "5.18.0",
4
4
  "description": "WordPress higher-order components (HOCs).",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -30,17 +30,15 @@
30
30
  "sideEffects": false,
31
31
  "dependencies": {
32
32
  "@babel/runtime": "^7.16.0",
33
- "@types/lodash": "^4.14.172",
34
33
  "@types/mousetrap": "^1.6.8",
35
- "@wordpress/deprecated": "^3.18.1-next.4d3b314fd5.0",
36
- "@wordpress/dom": "^3.18.1-next.4d3b314fd5.0",
37
- "@wordpress/element": "^4.16.1-next.4d3b314fd5.0",
38
- "@wordpress/is-shallow-equal": "^4.18.1-next.4d3b314fd5.0",
39
- "@wordpress/keycodes": "^3.18.1-next.4d3b314fd5.0",
40
- "@wordpress/priority-queue": "^2.18.1-next.4d3b314fd5.0",
34
+ "@wordpress/deprecated": "^3.20.0",
35
+ "@wordpress/dom": "^3.20.0",
36
+ "@wordpress/element": "^4.18.0",
37
+ "@wordpress/is-shallow-equal": "^4.20.0",
38
+ "@wordpress/keycodes": "^3.20.0",
39
+ "@wordpress/priority-queue": "^2.20.0",
41
40
  "change-case": "^4.1.2",
42
41
  "clipboard": "^2.0.8",
43
- "lodash": "^4.17.21",
44
42
  "mousetrap": "^1.6.5",
45
43
  "use-memo-one": "^1.1.1"
46
44
  },
@@ -50,5 +48,5 @@
50
48
  "publishConfig": {
51
49
  "access": "public"
52
50
  },
53
- "gitHead": "25054766423cb49d959eb656c2533530073ff5c2"
51
+ "gitHead": "a2ff0e6471c88436dad0287beb88d1729aa6f5dd"
54
52
  }
@@ -1,8 +1,3 @@
1
- /**
2
- * External dependencies
3
- */
4
- import { without } from 'lodash';
5
-
6
1
  /**
7
2
  * Class responsible for orchestrating event handling on the global window,
8
3
  * binding a single event to be shared across all handling instances, and
@@ -27,9 +22,12 @@ class Listener {
27
22
  }
28
23
 
29
24
  remove( /** @type {any} */ eventType, /** @type {any} */ instance ) {
30
- this.listeners[ eventType ] = without(
31
- this.listeners[ eventType ],
32
- instance
25
+ if ( ! this.listeners[ eventType ] ) {
26
+ return;
27
+ }
28
+
29
+ this.listeners[ eventType ] = this.listeners[ eventType ].filter(
30
+ ( /** @type {any} */ listener ) => listener !== instance
33
31
  );
34
32
 
35
33
  if ( ! this.listeners[ eventType ].length ) {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import TestRenderer from 'react-test-renderer';
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
- afterEach( () => {
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
- props.ref = () => {};
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( wrapper.root.findByType( 'div' ).children[ 0 ] ).toBe(
77
- 'Hello'
78
- );
59
+ expect( screen.getByText( 'Hello' ) ).toBeVisible();
79
60
  } );
80
61
 
81
62
  it( 'binds events from passed object', () => {
82
- mountEnhancedComponent();
63
+ const EnhancedComponent = withGlobalEvents( {
64
+ resize: 'handleResize',
65
+ } )( OriginalComponent );
83
66
 
84
- // Get the HOC wrapper instance.
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
- hocInstance
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
- mountEnhancedComponent( { onResize } );
82
+ render(
83
+ <EnhancedComponent ref={ () => {} } onResize={ onResize }>
84
+ Hello
85
+ </EnhancedComponent>
86
+ );
98
87
 
99
88
  const event = { type: 'resize' };
100
89
 
@@ -1,8 +1,3 @@
1
- /**
2
- * External dependencies
3
- */
4
- import { without } from 'lodash';
5
-
6
1
  /**
7
2
  * WordPress dependencies
8
3
  */
@@ -64,7 +59,7 @@ const withSafeTimeout = createHigherOrderComponent(
64
59
 
65
60
  clearTimeout( id: number ) {
66
61
  clearTimeout( id );
67
- this.timeouts = without( this.timeouts, id );
62
+ this.timeouts.filter( ( timeoutId ) => timeoutId !== id );
68
63
  }
69
64
 
70
65
  render() {
@@ -1,31 +1,19 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import TestUtils from 'react-dom/test-utils';
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
- const wrapper = TestUtils.renderIntoDocument(
42
- getTestComponent( EnhancedComponent )
43
- );
29
+ render( <EnhancedComponent /> );
44
30
 
45
- const buttonElement = () =>
46
- TestUtils.findRenderedDOMComponentWithTag( wrapper, 'button' );
31
+ const button = screen.getByRole( 'button' );
47
32
 
48
33
  expect( console ).toHaveWarned();
49
- expect( buttonElement().outerHTML ).toBe( '<button>0</button>' );
50
- TestUtils.Simulate.click( buttonElement() );
51
- expect( buttonElement().outerHTML ).toBe( '<button>1</button>' );
34
+ expect( button ).toHaveTextContent( '0' );
35
+
36
+ await user.click( button );
37
+
38
+ expect( button ).toHaveTextContent( '1' );
52
39
  } );
53
40
  } );
@@ -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.hasAttribute( 'disabled' ) ).toBe( true );
73
- expect( link.getAttribute( 'tabindex' ) ).toBe( '-1' );
74
- expect( p.getAttribute( 'contenteditable' ) ).toBe( 'false' );
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.hasAttribute( 'disabled' ) ).toBe( true );
57
+ expect( button ).toHaveAttribute( 'inert', 'true' );
90
58
  } );
91
59
  } );
92
60
  } );
@@ -1,8 +1,3 @@
1
- /**
2
- * External dependencies
3
- */
4
- import { includes } from 'lodash';
5
-
6
1
  /**
7
2
  * WordPress dependencies
8
3
  */
@@ -42,8 +37,7 @@ function isFocusNormalizedButton( eventTarget ) {
42
37
  return true;
43
38
 
44
39
  case 'INPUT':
45
- return includes(
46
- INPUT_BUTTON_TYPES,
40
+ return INPUT_BUTTON_TYPES.includes(
47
41
  /** @type {HTMLInputElement} */ ( eventTarget ).type
48
42
  );
49
43
  }
@@ -136,7 +130,7 @@ export default function useFocusOutside( onFocusOutside ) {
136
130
  */
137
131
  const normalizeButtonFocus = useCallback( ( event ) => {
138
132
  const { type, target } = event;
139
- const isInteractionEnd = includes( [ 'mouseup', 'touchend' ], type );
133
+ const isInteractionEnd = [ 'mouseup', 'touchend' ].includes( type );
140
134
 
141
135
  if ( isInteractionEnd ) {
142
136
  preventBlurCheck.current = false;
@@ -1,8 +1,3 @@
1
- /**
2
- * External dependencies
3
- */
4
- import { includes } from 'lodash';
5
-
6
1
  /**
7
2
  * WordPress dependencies
8
3
  */
@@ -39,8 +34,7 @@ function isFocusNormalizedButton( eventTarget ) {
39
34
  return true;
40
35
 
41
36
  case 'INPUT':
42
- return includes(
43
- INPUT_BUTTON_TYPES,
37
+ return INPUT_BUTTON_TYPES.includes(
44
38
  /** @type {HTMLInputElement} */ ( eventTarget ).type
45
39
  );
46
40
  }
@@ -133,7 +127,7 @@ export default function useFocusOutside( onFocusOutside ) {
133
127
  */
134
128
  const normalizeButtonFocus = useCallback( ( event ) => {
135
129
  const { type, target } = event;
136
- const isInteractionEnd = includes( [ 'mouseup', 'touchend' ], type );
130
+ const isInteractionEnd = [ 'mouseup', 'touchend' ].includes( type );
137
131
 
138
132
  if ( isInteractionEnd ) {
139
133
  preventBlurCheck.current = false;
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import TestUtils from 'react-dom/test-utils';
4
+ import { render, screen } from '@testing-library/react';
5
+ import userEvent from '@testing-library/user-event';
5
6
 
6
7
  /**
7
8
  * WordPress dependencies
@@ -12,38 +13,24 @@ import { Component } from '@wordpress/element';
12
13
  * Internal dependencies
13
14
  */
14
15
  import useFocusOutside from '../';
15
- import ReactDOM from 'react-dom';
16
16
 
17
- let wrapper, onFocusOutside;
17
+ let onFocusOutside;
18
18
 
19
19
  describe( 'useFocusOutside', () => {
20
20
  let origHasFocus;
21
21
 
22
22
  const FocusOutsideComponent = ( { onFocusOutside: callback } ) => (
23
23
  <div { ...useFocusOutside( callback ) }>
24
- <input />
24
+ <input type="text" />
25
25
  <input type="button" />
26
26
  </div>
27
27
  );
28
28
 
29
- // This is needed because TestUtils does not accept a stateless component.
30
- // anything run through a HOC ends up as a stateless component.
31
- const getTestComponent = ( WrappedComponent, props ) => {
32
- class TestComponent extends Component {
33
- render() {
34
- return <WrappedComponent { ...props } />;
35
- }
29
+ class TestComponent extends Component {
30
+ render() {
31
+ return <FocusOutsideComponent { ...this.props } />;
36
32
  }
37
- return <TestComponent />;
38
- };
39
-
40
- const simulateEvent = ( event, index = 0 ) => {
41
- const element = TestUtils.scryRenderedDOMComponentsWithTag(
42
- wrapper,
43
- 'input'
44
- );
45
- TestUtils.Simulate[ event ]( element[ index ] );
46
- };
33
+ }
47
34
 
48
35
  beforeEach( () => {
49
36
  // Mock document.hasFocus() to always be true for testing
@@ -52,9 +39,6 @@ describe( 'useFocusOutside', () => {
52
39
  document.hasFocus = () => true;
53
40
 
54
41
  onFocusOutside = jest.fn();
55
- wrapper = TestUtils.renderIntoDocument(
56
- getTestComponent( FocusOutsideComponent, { onFocusOutside } )
57
- );
58
42
  } );
59
43
 
60
44
  afterEach( () => {
@@ -62,24 +46,31 @@ describe( 'useFocusOutside', () => {
62
46
  } );
63
47
 
64
48
  it( 'should not call handler if focus shifts to element within component', () => {
65
- simulateEvent( 'focus' );
66
- simulateEvent( 'blur' );
67
- simulateEvent( 'focus', 1 );
49
+ render( <TestComponent onFocusOutside={ onFocusOutside } /> );
50
+
51
+ const input = screen.getByRole( 'textbox' );
52
+ const button = screen.getByRole( 'button' );
53
+
54
+ input.focus();
55
+ input.blur();
56
+ button.focus();
68
57
 
69
58
  jest.runAllTimers();
70
59
 
71
60
  expect( onFocusOutside ).not.toHaveBeenCalled();
72
61
  } );
73
62
 
74
- it( 'should not call handler if focus transitions via click to button', () => {
75
- simulateEvent( 'focus' );
76
- simulateEvent( 'mouseDown', 1 );
77
- simulateEvent( 'blur' );
63
+ it( 'should not call handler if focus transitions via click to button', async () => {
64
+ const user = userEvent.setup( {
65
+ advanceTimers: jest.advanceTimersByTime,
66
+ } );
67
+ render( <TestComponent onFocusOutside={ onFocusOutside } /> );
68
+
69
+ const input = screen.getByRole( 'textbox' );
70
+ const button = screen.getByRole( 'button' );
78
71
 
79
- // In most browsers, the input at index 1 would receive a focus event
80
- // at this point, but this is not guaranteed, which is the intention of
81
- // the normalization behavior tested here.
82
- simulateEvent( 'mouseUp', 1 );
72
+ input.focus();
73
+ await user.click( button );
83
74
 
84
75
  jest.runAllTimers();
85
76
 
@@ -87,8 +78,11 @@ describe( 'useFocusOutside', () => {
87
78
  } );
88
79
 
89
80
  it( 'should call handler if focus doesn’t shift to element within component', () => {
90
- simulateEvent( 'focus' );
91
- simulateEvent( 'blur' );
81
+ render( <TestComponent onFocusOutside={ onFocusOutside } /> );
82
+
83
+ const input = screen.getByRole( 'textbox' );
84
+ input.focus();
85
+ input.blur();
92
86
 
93
87
  jest.runAllTimers();
94
88
 
@@ -96,12 +90,15 @@ describe( 'useFocusOutside', () => {
96
90
  } );
97
91
 
98
92
  it( 'should not call handler if focus shifts outside the component when the document does not have focus', () => {
93
+ render( <TestComponent onFocusOutside={ onFocusOutside } /> );
94
+
99
95
  // Force document.hasFocus() to return false to simulate the window/document losing focus
100
96
  // See https://developer.mozilla.org/en-US/docs/Web/API/Document/hasFocus.
101
97
  document.hasFocus = () => false;
102
98
 
103
- simulateEvent( 'focus' );
104
- simulateEvent( 'blur' );
99
+ const input = screen.getByRole( 'textbox' );
100
+ input.focus();
101
+ input.blur();
105
102
 
106
103
  jest.runAllTimers();
107
104
 
@@ -109,14 +106,16 @@ describe( 'useFocusOutside', () => {
109
106
  } );
110
107
 
111
108
  it( 'should cancel check when unmounting while queued', () => {
112
- simulateEvent( 'focus' );
113
- simulateEvent( 'input' );
114
-
115
- ReactDOM.unmountComponentAtNode(
116
- // eslint-disable-next-line react/no-find-dom-node
117
- ReactDOM.findDOMNode( wrapper ).parentNode
109
+ const { rerender } = render(
110
+ <TestComponent onFocusOutside={ onFocusOutside } />
118
111
  );
119
112
 
113
+ const input = screen.getByRole( 'textbox' );
114
+ input.focus();
115
+ input.blur();
116
+
117
+ rerender( <div /> );
118
+
120
119
  jest.runAllTimers();
121
120
 
122
121
  expect( onFocusOutside ).not.toHaveBeenCalled();