@wordpress/compose 5.0.5 → 5.1.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 (133) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/LICENSE.md +1 -1
  3. package/README.md +4 -16
  4. package/build/higher-order/if-condition/index.js.map +1 -1
  5. package/build/higher-order/pure/index.js +1 -1
  6. package/build/higher-order/pure/index.js.map +1 -1
  7. package/build/higher-order/with-global-events/index.js +5 -2
  8. package/build/higher-order/with-global-events/index.js.map +1 -1
  9. package/build/higher-order/with-instance-id/index.js +2 -6
  10. package/build/higher-order/with-instance-id/index.js.map +1 -1
  11. package/build/higher-order/with-safe-timeout/index.js.map +1 -1
  12. package/build/higher-order/with-state/index.js +3 -1
  13. package/build/higher-order/with-state/index.js.map +1 -1
  14. package/build/hooks/use-async-list/index.js +4 -3
  15. package/build/hooks/use-async-list/index.js.map +1 -1
  16. package/build/hooks/use-constrained-tabbing/index.js +2 -2
  17. package/build/hooks/use-constrained-tabbing/index.js.map +1 -1
  18. package/build/hooks/use-copy-on-click/index.js +9 -7
  19. package/build/hooks/use-copy-on-click/index.js.map +1 -1
  20. package/build/hooks/use-copy-to-clipboard/index.js +4 -3
  21. package/build/hooks/use-copy-to-clipboard/index.js.map +1 -1
  22. package/build/hooks/use-dialog/index.js +2 -2
  23. package/build/hooks/use-dialog/index.js.map +1 -1
  24. package/build/hooks/use-disabled/index.js +114 -0
  25. package/build/hooks/use-disabled/index.js.map +1 -0
  26. package/build/hooks/use-dragging/index.js +12 -11
  27. package/build/hooks/use-dragging/index.js.map +1 -1
  28. package/build/hooks/use-drop-zone/index.js +10 -9
  29. package/build/hooks/use-drop-zone/index.js.map +1 -1
  30. package/build/hooks/use-fixed-window-list/index.js +8 -8
  31. package/build/hooks/use-fixed-window-list/index.js.map +1 -1
  32. package/build/hooks/use-focus-on-mount/index.js +2 -1
  33. package/build/hooks/use-focus-on-mount/index.js.map +1 -1
  34. package/build/hooks/use-instance-id/index.js +2 -1
  35. package/build/hooks/use-instance-id/index.js.map +1 -1
  36. package/build/hooks/use-keyboard-shortcut/index.js +17 -15
  37. package/build/hooks/use-keyboard-shortcut/index.js.map +1 -1
  38. package/build/hooks/use-preferred-color-scheme/index.android.js +4 -3
  39. package/build/hooks/use-preferred-color-scheme/index.android.js.map +1 -1
  40. package/build/hooks/use-ref-effect/index.js +0 -1
  41. package/build/hooks/use-ref-effect/index.js.map +1 -1
  42. package/build/hooks/use-resize-observer/index.native.js +4 -3
  43. package/build/hooks/use-resize-observer/index.native.js.map +1 -1
  44. package/build/hooks/use-viewport-match/index.js +2 -1
  45. package/build/hooks/use-viewport-match/index.js.map +1 -1
  46. package/build/hooks/use-warn-on-change/index.js +5 -2
  47. package/build/hooks/use-warn-on-change/index.js.map +1 -1
  48. package/build/index.js +64 -56
  49. package/build/index.js.map +1 -1
  50. package/build/index.native.js +46 -46
  51. package/build/utils/create-higher-order-component/index.js +2 -2
  52. package/build/utils/create-higher-order-component/index.js.map +1 -1
  53. package/build-module/higher-order/if-condition/index.js +1 -1
  54. package/build-module/higher-order/if-condition/index.js.map +1 -1
  55. package/build-module/higher-order/pure/index.js +4 -1
  56. package/build-module/higher-order/pure/index.js.map +1 -1
  57. package/build-module/higher-order/with-global-events/index.js +5 -2
  58. package/build-module/higher-order/with-global-events/index.js.map +1 -1
  59. package/build-module/higher-order/with-instance-id/index.js +2 -6
  60. package/build-module/higher-order/with-instance-id/index.js.map +1 -1
  61. package/build-module/higher-order/with-safe-timeout/index.js +9 -1
  62. package/build-module/higher-order/with-safe-timeout/index.js.map +1 -1
  63. package/build-module/higher-order/with-state/index.js +3 -1
  64. package/build-module/higher-order/with-state/index.js.map +1 -1
  65. package/build-module/hooks/use-async-list/index.js +4 -3
  66. package/build-module/hooks/use-async-list/index.js.map +1 -1
  67. package/build-module/hooks/use-constrained-tabbing/index.js +2 -2
  68. package/build-module/hooks/use-constrained-tabbing/index.js.map +1 -1
  69. package/build-module/hooks/use-copy-on-click/index.js +9 -7
  70. package/build-module/hooks/use-copy-on-click/index.js.map +1 -1
  71. package/build-module/hooks/use-copy-to-clipboard/index.js +4 -3
  72. package/build-module/hooks/use-copy-to-clipboard/index.js.map +1 -1
  73. package/build-module/hooks/use-dialog/index.js +2 -2
  74. package/build-module/hooks/use-dialog/index.js.map +1 -1
  75. package/build-module/hooks/use-disabled/index.js +104 -0
  76. package/build-module/hooks/use-disabled/index.js.map +1 -0
  77. package/build-module/hooks/use-dragging/index.js +12 -11
  78. package/build-module/hooks/use-dragging/index.js.map +1 -1
  79. package/build-module/hooks/use-drop-zone/index.js +10 -9
  80. package/build-module/hooks/use-drop-zone/index.js.map +1 -1
  81. package/build-module/hooks/use-fixed-window-list/index.js +8 -8
  82. package/build-module/hooks/use-fixed-window-list/index.js.map +1 -1
  83. package/build-module/hooks/use-focus-on-mount/index.js +2 -1
  84. package/build-module/hooks/use-focus-on-mount/index.js.map +1 -1
  85. package/build-module/hooks/use-instance-id/index.js +2 -1
  86. package/build-module/hooks/use-instance-id/index.js.map +1 -1
  87. package/build-module/hooks/use-keyboard-shortcut/index.js +17 -15
  88. package/build-module/hooks/use-keyboard-shortcut/index.js.map +1 -1
  89. package/build-module/hooks/use-preferred-color-scheme/index.android.js +4 -3
  90. package/build-module/hooks/use-preferred-color-scheme/index.android.js.map +1 -1
  91. package/build-module/hooks/use-ref-effect/index.js +0 -1
  92. package/build-module/hooks/use-ref-effect/index.js.map +1 -1
  93. package/build-module/hooks/use-resize-observer/index.native.js +4 -3
  94. package/build-module/hooks/use-resize-observer/index.native.js.map +1 -1
  95. package/build-module/hooks/use-viewport-match/index.js +2 -1
  96. package/build-module/hooks/use-viewport-match/index.js.map +1 -1
  97. package/build-module/hooks/use-warn-on-change/index.js +5 -2
  98. package/build-module/hooks/use-warn-on-change/index.js.map +1 -1
  99. package/build-module/index.js +1 -0
  100. package/build-module/index.js.map +1 -1
  101. package/build-module/utils/create-higher-order-component/index.js +3 -3
  102. package/build-module/utils/create-higher-order-component/index.js.map +1 -1
  103. package/build-types/higher-order/if-condition/index.d.ts +2 -2
  104. package/build-types/higher-order/if-condition/index.d.ts.map +1 -1
  105. package/build-types/higher-order/pure/index.d.ts +6 -3
  106. package/build-types/higher-order/pure/index.d.ts.map +1 -1
  107. package/build-types/higher-order/with-global-events/index.d.ts.map +1 -1
  108. package/build-types/higher-order/with-instance-id/index.d.ts +3 -3
  109. package/build-types/higher-order/with-instance-id/index.d.ts.map +1 -1
  110. package/build-types/higher-order/with-safe-timeout/index.d.ts +2 -2
  111. package/build-types/higher-order/with-safe-timeout/index.d.ts.map +1 -1
  112. package/build-types/higher-order/with-state/index.d.ts.map +1 -1
  113. package/build-types/hooks/use-copy-on-click/index.d.ts.map +1 -1
  114. package/build-types/hooks/use-disabled/index.d.ts +23 -0
  115. package/build-types/hooks/use-disabled/index.d.ts.map +1 -0
  116. package/build-types/hooks/use-ref-effect/index.d.ts.map +1 -1
  117. package/build-types/index.d.ts +1 -0
  118. package/build-types/utils/create-higher-order-component/index.d.ts +4 -6
  119. package/build-types/utils/create-higher-order-component/index.d.ts.map +1 -1
  120. package/package.json +12 -9
  121. package/src/higher-order/if-condition/index.tsx +5 -6
  122. package/src/higher-order/pure/index.tsx +5 -5
  123. package/src/higher-order/with-global-events/index.js +1 -0
  124. package/src/higher-order/with-instance-id/index.tsx +8 -24
  125. package/src/higher-order/with-safe-timeout/index.tsx +5 -6
  126. package/src/higher-order/with-state/index.js +1 -0
  127. package/src/hooks/use-copy-on-click/index.js +1 -2
  128. package/src/hooks/use-disabled/index.js +113 -0
  129. package/src/hooks/use-disabled/test/index.js +92 -0
  130. package/src/hooks/use-ref-effect/index.ts +0 -1
  131. package/src/index.js +1 -0
  132. package/src/utils/create-higher-order-component/index.ts +15 -21
  133. package/tsconfig.tsbuildinfo +1 -1
@@ -8,18 +8,16 @@ import type { ComponentType } from 'react';
8
8
  * of this is the `pure` HOC which does not change the API surface of the component but
9
9
  * simply modifies the internals.
10
10
  */
11
- export declare type HigherOrderComponent<TInnerProps, TOuterProps> = (Inner: ComponentType<TInnerProps>) => ComponentType<TOuterProps>;
12
- export declare type SimpleHigherOrderComponent = <TProps>(Inner: ComponentType<TProps>) => ComponentType<TProps>;
13
- export declare type PropInjectingHigherOrderComponent<TRemovedProps> = <TProps extends TRemovedProps>(Inner: ComponentType<TProps>) => ComponentType<Omit<TProps, keyof TRemovedProps>>;
11
+ export declare type HigherOrderComponent<HOCProps extends Record<string, any>> = <InnerProps extends HOCProps>(Inner: ComponentType<InnerProps>) => {} extends HOCProps ? ComponentType<InnerProps> : ComponentType<Omit<InnerProps, keyof HOCProps>>;
14
12
  /**
15
13
  * Given a function mapping a component to an enhanced component and modifier
16
14
  * name, returns the enhanced component augmented with a generated displayName.
17
15
  *
18
- * @param mapComponentToEnhancedComponent Function mapping component to enhanced component.
19
- * @param modifierName Seed name from which to generated display name.
16
+ * @param mapComponent Function mapping component to enhanced component.
17
+ * @param modifierName Seed name from which to generated display name.
20
18
  *
21
19
  * @return Component class with generated display name assigned.
22
20
  */
23
- declare function createHigherOrderComponent<TInnerProps, TOuterProps>(mapComponent: HigherOrderComponent<TInnerProps, TOuterProps>, modifierName: string): HigherOrderComponent<TInnerProps, TOuterProps>;
21
+ declare function createHigherOrderComponent<HOCProps extends Record<string, any> = {}>(mapComponent: HigherOrderComponent<HOCProps>, modifierName: string): <InnerProps extends HOCProps>(Inner: ComponentType<InnerProps>) => {} extends HOCProps ? ComponentType<InnerProps> : ComponentType<Omit<InnerProps, keyof HOCProps>>;
24
22
  export default createHigherOrderComponent;
25
23
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/create-higher-order-component/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAE3C;;;;;;;;GAQG;AACH,oBAAY,oBAAoB,CAAE,WAAW,EAAE,WAAW,IAAK,CAC9D,KAAK,EAAE,aAAa,CAAE,WAAW,CAAE,KAC/B,aAAa,CAAE,WAAW,CAAE,CAAC;AAElC,oBAAY,0BAA0B,GAAG,CAAE,MAAM,EAChD,KAAK,EAAE,aAAa,CAAE,MAAM,CAAE,KAC1B,aAAa,CAAE,MAAM,CAAE,CAAC;AAE7B,oBAAY,iCAAiC,CAAE,aAAa,IAAK,CAChE,MAAM,SAAS,aAAa,EAE5B,KAAK,EAAE,aAAa,CAAE,MAAM,CAAE,KAC1B,aAAa,CAAE,IAAI,CAAE,MAAM,EAAE,MAAM,aAAa,CAAE,CAAE,CAAC;AAE1D;;;;;;;;GAQG;AAEH,iBAAS,0BAA0B,CAAE,WAAW,EAAE,WAAW,EAC5D,YAAY,EAAE,oBAAoB,CAAE,WAAW,EAAE,WAAW,CAAE,EAC9D,YAAY,EAAE,MAAM,GAClB,oBAAoB,CAAE,WAAW,EAAE,WAAW,CAAE,CASlD;AACD,eAAe,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/create-higher-order-component/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAE3C;;;;;;;;GAQG;AACH,oBAAY,oBAAoB,CAAE,QAAQ,SAAS,MAAM,CAAE,MAAM,EAAE,GAAG,CAAE,IAAK,CAC5E,UAAU,SAAS,QAAQ,EAE3B,KAAK,EAAE,aAAa,CAAE,UAAU,CAAE,KAC9B,EAAE,SAAS,QAAQ,GACrB,aAAa,CAAE,UAAU,CAAE,GAC3B,aAAa,CAAE,IAAI,CAAE,UAAU,EAAE,MAAM,QAAQ,CAAE,CAAE,CAAC;AAEvD;;;;;;;;GAQG;AACH,iBAAS,0BAA0B,CAClC,QAAQ,SAAS,MAAM,CAAE,MAAM,EAAE,GAAG,CAAE,GAAG,EAAE,EACzC,YAAY,EAAE,oBAAoB,CAAE,QAAQ,CAAE,EAAE,YAAY,EAAE,MAAM,wKAWtE;AAED,eAAe,0BAA0B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/compose",
3
- "version": "5.0.5",
3
+ "version": "5.1.0",
4
4
  "description": "WordPress higher-order components (HOCs).",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -29,23 +29,26 @@
29
29
  "types": "build-types",
30
30
  "sideEffects": false,
31
31
  "dependencies": {
32
- "@babel/runtime": "^7.13.10",
32
+ "@babel/runtime": "^7.16.0",
33
33
  "@types/lodash": "^4.14.172",
34
34
  "@types/mousetrap": "^1.6.8",
35
- "@wordpress/deprecated": "^3.2.2",
36
- "@wordpress/dom": "^3.2.6",
37
- "@wordpress/element": "^4.0.3",
38
- "@wordpress/is-shallow-equal": "^4.2.0",
39
- "@wordpress/keycodes": "^3.2.3",
40
- "@wordpress/priority-queue": "^2.2.2",
35
+ "@wordpress/deprecated": "^3.3.0",
36
+ "@wordpress/dom": "^3.3.0",
37
+ "@wordpress/element": "^4.1.0",
38
+ "@wordpress/is-shallow-equal": "^4.3.0",
39
+ "@wordpress/keycodes": "^3.3.0",
40
+ "@wordpress/priority-queue": "^2.3.0",
41
41
  "clipboard": "^2.0.8",
42
42
  "lodash": "^4.17.21",
43
43
  "mousetrap": "^1.6.5",
44
44
  "react-resize-aware": "^3.1.0",
45
45
  "use-memo-one": "^1.1.1"
46
46
  },
47
+ "peerDependencies": {
48
+ "react": "^17.0.0"
49
+ },
47
50
  "publishConfig": {
48
51
  "access": "public"
49
52
  },
50
- "gitHead": "393c2b5533837fd637e998d23f0124c081a10df0"
53
+ "gitHead": "d95ccb9366e249133cdb1d7b25c382446b9ee502"
51
54
  }
@@ -2,7 +2,6 @@
2
2
  * Internal dependencies
3
3
  */
4
4
  import createHigherOrderComponent from '../../utils/create-higher-order-component';
5
- import type { HigherOrderComponent } from '../../utils/create-higher-order-component';
6
5
 
7
6
  /**
8
7
  * Higher-order component creator, creating a new component which renders if
@@ -21,12 +20,12 @@ import type { HigherOrderComponent } from '../../utils/create-higher-order-compo
21
20
  *
22
21
  * @return Higher-order component.
23
22
  */
24
- const ifCondition = < TProps, >(
23
+ const ifCondition = < TProps extends Record< string, any > >(
25
24
  predicate: ( props: TProps ) => boolean
26
- ): HigherOrderComponent< TProps, TProps > =>
27
- createHigherOrderComponent(
28
- ( WrappedComponent ) => ( props: TProps ) => {
29
- if ( ! predicate( props ) ) {
25
+ ) =>
26
+ createHigherOrderComponent< {} >(
27
+ ( WrappedComponent ) => ( props ) => {
28
+ if ( ! predicate( props as TProps ) ) {
30
29
  return null;
31
30
  }
32
31
 
@@ -8,20 +8,20 @@ import { Component } from '@wordpress/element';
8
8
  * Internal dependencies
9
9
  */
10
10
  import createHigherOrderComponent from '../../utils/create-higher-order-component';
11
- import type { SimpleHigherOrderComponent } from '../../utils/create-higher-order-component';
12
11
 
13
12
  /**
14
13
  * External dependencies
15
14
  */
16
- // eslint-disable-next-line no-restricted-imports
17
15
  import type { ComponentType, ComponentClass } from 'react';
18
16
 
19
17
  /**
20
18
  * Given a component returns the enhanced component augmented with a component
21
- * only rerendering when its props/state change
19
+ * only re-rendering when its props/state change
22
20
  */
23
- const pure: SimpleHigherOrderComponent = createHigherOrderComponent(
24
- < TProps, >( Wrapped: ComponentType< TProps > ) => {
21
+ const pure = createHigherOrderComponent(
22
+ < TProps extends Record< string, any > >(
23
+ Wrapped: ComponentType< TProps >
24
+ ) => {
25
25
  if ( Wrapped.prototype instanceof Component ) {
26
26
  return class extends ( Wrapped as ComponentClass< TProps > ) {
27
27
  shouldComponentUpdate( nextProps: TProps, nextState: any ) {
@@ -46,6 +46,7 @@ export default function withGlobalEvents( eventTypesToHandlers ) {
46
46
  alternative: 'useEffect',
47
47
  } );
48
48
 
49
+ // @ts-ignore We don't need to fix the type-related issues because this is deprecated.
49
50
  return createHigherOrderComponent( ( WrappedComponent ) => {
50
51
  class Wrapper extends Component {
51
52
  constructor( /** @type {any} */ props ) {
@@ -1,37 +1,21 @@
1
- /**
2
- * External dependencies
3
- */
4
- // eslint-disable-next-line no-restricted-imports
5
- import type { ComponentType } from 'react';
6
-
7
1
  /**
8
2
  * Internal dependencies
9
3
  */
10
4
  import createHigherOrderComponent from '../../utils/create-higher-order-component';
11
- import type { PropInjectingHigherOrderComponent } from '../../utils/create-higher-order-component';
12
5
  import useInstanceId from '../../hooks/use-instance-id';
13
6
 
14
7
  /**
15
8
  * A Higher Order Component used to be provide a unique instance ID by
16
9
  * component.
17
10
  */
18
- const withInstanceId: PropInjectingHigherOrderComponent< {
11
+ const withInstanceId = createHigherOrderComponent< {
19
12
  instanceId: string | number;
20
- } > = createHigherOrderComponent(
21
- < TProps extends { instanceId: string | number } >(
22
- WrappedComponent: ComponentType< TProps >
23
- ) => {
24
- return ( props: Omit< TProps, 'instanceId' > ) => {
25
- const instanceId = useInstanceId( WrappedComponent );
26
- return (
27
- <WrappedComponent
28
- { ...( props as TProps ) }
29
- instanceId={ instanceId }
30
- />
31
- );
32
- };
33
- },
34
- 'withInstanceId'
35
- );
13
+ } >( ( WrappedComponent ) => {
14
+ return ( props ) => {
15
+ const instanceId = useInstanceId( WrappedComponent );
16
+ // @ts-ignore
17
+ return <WrappedComponent { ...props } instanceId={ instanceId } />;
18
+ };
19
+ }, 'withInstanceId' );
36
20
 
37
21
  export default withInstanceId;
@@ -2,7 +2,6 @@
2
2
  * External dependencies
3
3
  */
4
4
  import { without } from 'lodash';
5
- // eslint-disable-next-line no-restricted-imports
6
5
  import type { ComponentType } from 'react';
7
6
 
8
7
  /**
@@ -14,7 +13,6 @@ import { Component } from '@wordpress/element';
14
13
  * Internal dependencies
15
14
  */
16
15
  import createHigherOrderComponent from '../../utils/create-higher-order-component';
17
- import type { PropInjectingHigherOrderComponent } from '../../utils/create-higher-order-component';
18
16
 
19
17
  /**
20
18
  * We cannot use the `Window['setTimeout']` and `Window['clearTimeout']`
@@ -33,7 +31,7 @@ type TimeoutProps = {
33
31
  * A higher-order component used to provide and manage delayed function calls
34
32
  * that ought to be bound to a component's lifecycle.
35
33
  */
36
- const withSafeTimeout: PropInjectingHigherOrderComponent< TimeoutProps > = createHigherOrderComponent(
34
+ const withSafeTimeout = createHigherOrderComponent< TimeoutProps >(
37
35
  < TProps extends TimeoutProps >(
38
36
  OriginalComponent: ComponentType< TProps >
39
37
  ) => {
@@ -42,7 +40,7 @@ const withSafeTimeout: PropInjectingHigherOrderComponent< TimeoutProps > = creat
42
40
  > {
43
41
  timeouts: number[];
44
42
 
45
- constructor( props: any ) {
43
+ constructor( props: Omit< TProps, keyof TimeoutProps > ) {
46
44
  super( props );
47
45
  this.timeouts = [];
48
46
  this.setTimeout = this.setTimeout.bind( this );
@@ -72,8 +70,9 @@ const withSafeTimeout: PropInjectingHigherOrderComponent< TimeoutProps > = creat
72
70
  ...this.props,
73
71
  setTimeout: this.setTimeout,
74
72
  clearTimeout: this.clearTimeout,
75
- };
76
- return <OriginalComponent { ...( props as TProps ) } />;
73
+ } as TProps;
74
+
75
+ return <OriginalComponent { ...props } />;
77
76
  }
78
77
  };
79
78
  },
@@ -21,6 +21,7 @@ import createHigherOrderComponent from '../../utils/create-higher-order-componen
21
21
  */
22
22
  export default function withState( initialState = {} ) {
23
23
  deprecated( 'wp.compose.withState', {
24
+ since: '5.8',
24
25
  alternative: 'wp.element.useState',
25
26
  } );
26
27
 
@@ -26,8 +26,7 @@ import deprecated from '@wordpress/deprecated';
26
26
  export default function useCopyOnClick( ref, text, timeout = 4000 ) {
27
27
  /* eslint-enable jsdoc/no-undefined-types */
28
28
  deprecated( 'wp.compose.useCopyOnClick', {
29
- since: '10.3',
30
- plugin: 'Gutenberg',
29
+ since: '5.8',
31
30
  alternative: 'wp.compose.useCopyToClipboard',
32
31
  } );
33
32
 
@@ -0,0 +1,113 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { includes, debounce } from 'lodash';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { useCallback, useLayoutEffect, useRef } from '@wordpress/element';
10
+ import { focus } from '@wordpress/dom';
11
+
12
+ /**
13
+ * Names of control nodes which qualify for disabled behavior.
14
+ *
15
+ * See WHATWG HTML Standard: 4.10.18.5: "Enabling and disabling form controls: the disabled attribute".
16
+ *
17
+ * @see https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#enabling-and-disabling-form-controls:-the-disabled-attribute
18
+ *
19
+ * @type {string[]}
20
+ */
21
+ const DISABLED_ELIGIBLE_NODE_NAMES = [
22
+ 'BUTTON',
23
+ 'FIELDSET',
24
+ 'INPUT',
25
+ 'OPTGROUP',
26
+ 'OPTION',
27
+ 'SELECT',
28
+ 'TEXTAREA',
29
+ ];
30
+
31
+ /**
32
+ * In some circumstances, such as block previews, all focusable DOM elements
33
+ * (input fields, links, buttons, etc.) need to be disabled. This hook adds the
34
+ * behavior to disable nested DOM elements to the returned ref.
35
+ *
36
+ * @return {import('react').RefObject<HTMLElement>} Element Ref.
37
+ *
38
+ * @example
39
+ * ```js
40
+ * import { __experimentalUseDisabled as useDisabled } from '@wordpress/compose';
41
+ * const DisabledExample = () => {
42
+ * const disabledRef = useDisabled();
43
+ * return (
44
+ * <div ref={ disabledRef }>
45
+ * <a href="#">This link will have tabindex set to -1</a>
46
+ * <input placeholder="This input will have the disabled attribute added to it." type="text" />
47
+ * </div>
48
+ * );
49
+ * };
50
+ * ```
51
+ */
52
+ export default function useDisabled() {
53
+ /** @type {import('react').RefObject<HTMLElement>} */
54
+ const node = useRef( null );
55
+
56
+ const disable = () => {
57
+ if ( ! node.current ) {
58
+ return;
59
+ }
60
+
61
+ focus.focusable.find( node.current ).forEach( ( focusable ) => {
62
+ if (
63
+ includes( DISABLED_ELIGIBLE_NODE_NAMES, focusable.nodeName )
64
+ ) {
65
+ focusable.setAttribute( 'disabled', '' );
66
+ }
67
+
68
+ if ( focusable.nodeName === 'A' ) {
69
+ focusable.setAttribute( 'tabindex', '-1' );
70
+ }
71
+
72
+ const tabIndex = focusable.getAttribute( 'tabindex' );
73
+ if ( tabIndex !== null && tabIndex !== '-1' ) {
74
+ focusable.removeAttribute( 'tabindex' );
75
+ }
76
+
77
+ if ( focusable.hasAttribute( 'contenteditable' ) ) {
78
+ focusable.setAttribute( 'contenteditable', 'false' );
79
+ }
80
+ } );
81
+ };
82
+
83
+ // Debounce re-disable since disabling process itself will incur
84
+ // additional mutations which should be ignored.
85
+ const debouncedDisable = useCallback(
86
+ debounce( disable, undefined, { leading: true } ),
87
+ []
88
+ );
89
+
90
+ useLayoutEffect( () => {
91
+ disable();
92
+
93
+ /** @type {MutationObserver | undefined} */
94
+ let observer;
95
+ if ( node.current ) {
96
+ observer = new window.MutationObserver( debouncedDisable );
97
+ observer.observe( node.current, {
98
+ childList: true,
99
+ attributes: true,
100
+ subtree: true,
101
+ } );
102
+ }
103
+
104
+ return () => {
105
+ if ( observer ) {
106
+ observer.disconnect();
107
+ }
108
+ debouncedDisable.cancel();
109
+ };
110
+ }, [] );
111
+
112
+ return node;
113
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { render, screen, waitFor } from '@testing-library/react';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { forwardRef } from '@wordpress/element';
10
+
11
+ /**
12
+ * Internal dependencies
13
+ */
14
+ import useDisabled from '../';
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
+ jest.useRealTimers();
47
+
48
+ describe( 'useDisabled', () => {
49
+ const Form = forwardRef( ( { showButton }, ref ) => {
50
+ return (
51
+ <form ref={ ref }>
52
+ <input />
53
+ <a href="https://wordpress.org/">A link</a>
54
+ <p contentEditable={ true } tabIndex="0"></p>
55
+ { showButton && <button>Button</button> }
56
+ </form>
57
+ );
58
+ } );
59
+
60
+ function DisabledComponent( props ) {
61
+ const disabledRef = useDisabled();
62
+ return <Form ref={ disabledRef } { ...props } />;
63
+ }
64
+
65
+ it( 'will disable all fields', () => {
66
+ const { container } = render( <DisabledComponent /> );
67
+
68
+ const input = screen.getByRole( 'textbox' );
69
+ const link = screen.getByRole( 'link' );
70
+ const p = container.querySelector( 'p' );
71
+
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 );
77
+ } );
78
+
79
+ it( 'will disable an element rendered in an update to the component', async () => {
80
+ const { rerender } = render(
81
+ <DisabledComponent showButton={ false } />
82
+ );
83
+
84
+ expect( screen.queryByText( 'Button' ) ).not.toBeInTheDocument();
85
+ rerender( <DisabledComponent showButton={ true } /> );
86
+
87
+ const button = screen.getByText( 'Button' );
88
+ await waitFor( () => {
89
+ expect( button.hasAttribute( 'disabled' ) ).toBe( true );
90
+ } );
91
+ } );
92
+ } );
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- // eslint-disable-next-line no-restricted-imports
5
4
  import type { DependencyList, RefCallback } from 'react';
6
5
 
7
6
  /**
package/src/index.js CHANGED
@@ -17,6 +17,7 @@ export { default as useConstrainedTabbing } from './hooks/use-constrained-tabbin
17
17
  export { default as useCopyOnClick } from './hooks/use-copy-on-click';
18
18
  export { default as useCopyToClipboard } from './hooks/use-copy-to-clipboard';
19
19
  export { default as __experimentalUseDialog } from './hooks/use-dialog';
20
+ export { default as __experimentalUseDisabled } from './hooks/use-disabled';
20
21
  export { default as __experimentalUseDragging } from './hooks/use-dragging';
21
22
  export { default as useFocusOnMount } from './hooks/use-focus-on-mount';
22
23
  export { default as __experimentalUseFocusOutside } from './hooks/use-focus-outside';
@@ -2,7 +2,6 @@
2
2
  * External dependencies
3
3
  */
4
4
  import { camelCase, upperFirst } from 'lodash';
5
- // eslint-disable-next-line no-restricted-imports
6
5
  import type { ComponentType } from 'react';
7
6
 
8
7
  /**
@@ -14,35 +13,29 @@ import type { ComponentType } from 'react';
14
13
  * of this is the `pure` HOC which does not change the API surface of the component but
15
14
  * simply modifies the internals.
16
15
  */
17
- export type HigherOrderComponent< TInnerProps, TOuterProps > = (
18
- Inner: ComponentType< TInnerProps >
19
- ) => ComponentType< TOuterProps >;
20
-
21
- export type SimpleHigherOrderComponent = < TProps >(
22
- Inner: ComponentType< TProps >
23
- ) => ComponentType< TProps >;
24
-
25
- export type PropInjectingHigherOrderComponent< TRemovedProps > = <
26
- TProps extends TRemovedProps
16
+ export type HigherOrderComponent< HOCProps extends Record< string, any > > = <
17
+ InnerProps extends HOCProps
27
18
  >(
28
- Inner: ComponentType< TProps >
29
- ) => ComponentType< Omit< TProps, keyof TRemovedProps > >;
19
+ Inner: ComponentType< InnerProps >
20
+ ) => {} extends HOCProps
21
+ ? ComponentType< InnerProps >
22
+ : ComponentType< Omit< InnerProps, keyof HOCProps > >;
30
23
 
31
24
  /**
32
25
  * Given a function mapping a component to an enhanced component and modifier
33
26
  * name, returns the enhanced component augmented with a generated displayName.
34
27
  *
35
- * @param mapComponentToEnhancedComponent Function mapping component to enhanced component.
36
- * @param modifierName Seed name from which to generated display name.
28
+ * @param mapComponent Function mapping component to enhanced component.
29
+ * @param modifierName Seed name from which to generated display name.
37
30
  *
38
31
  * @return Component class with generated display name assigned.
39
32
  */
40
-
41
- function createHigherOrderComponent< TInnerProps, TOuterProps >(
42
- mapComponent: HigherOrderComponent< TInnerProps, TOuterProps >,
43
- modifierName: string
44
- ): HigherOrderComponent< TInnerProps, TOuterProps > {
45
- return ( Inner ) => {
33
+ function createHigherOrderComponent<
34
+ HOCProps extends Record< string, any > = {}
35
+ >( mapComponent: HigherOrderComponent< HOCProps >, modifierName: string ) {
36
+ return < InnerProps extends HOCProps >(
37
+ Inner: ComponentType< InnerProps >
38
+ ) => {
46
39
  const Outer = mapComponent( Inner );
47
40
  const displayName = Inner.displayName || Inner.name || 'Component';
48
41
  Outer.displayName = `${ upperFirst(
@@ -51,4 +44,5 @@ function createHigherOrderComponent< TInnerProps, TOuterProps >(
51
44
  return Outer;
52
45
  };
53
46
  }
47
+
54
48
  export default createHigherOrderComponent;