@wordpress/compose 8.1.0 → 8.1.1
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 +20 -1
- package/README.md +5 -5
- package/build/higher-order/pure/index.cjs +15 -0
- package/build/higher-order/pure/index.cjs.map +3 -3
- package/build/hooks/use-media-query/index.cjs +2 -2
- package/build/hooks/use-media-query/index.cjs.map +2 -2
- package/build/hooks/use-merge-refs/index.cjs +28 -8
- package/build/hooks/use-merge-refs/index.cjs.map +2 -2
- package/build/hooks/use-viewport-match/index.cjs +1 -1
- package/build/hooks/use-viewport-match/index.cjs.map +2 -2
- package/build-module/higher-order/pure/index.mjs +5 -0
- package/build-module/higher-order/pure/index.mjs.map +2 -2
- package/build-module/hooks/use-media-query/index.mjs +2 -2
- package/build-module/hooks/use-media-query/index.mjs.map +2 -2
- package/build-module/hooks/use-merge-refs/index.mjs +28 -8
- package/build-module/hooks/use-merge-refs/index.mjs.map +2 -2
- package/build-module/hooks/use-viewport-match/index.mjs +1 -1
- package/build-module/hooks/use-viewport-match/index.mjs.map +2 -2
- package/build-types/higher-order/pure/index.d.ts.map +1 -1
- package/build-types/hooks/use-media-query/index.d.ts +1 -1
- package/build-types/hooks/use-media-query/index.d.ts.map +1 -1
- package/build-types/hooks/use-merge-refs/index.d.ts +7 -5
- package/build-types/hooks/use-merge-refs/index.d.ts.map +1 -1
- package/build-types/hooks/use-viewport-match/index.d.ts +1 -1
- package/build-types/hooks/use-viewport-match/index.d.ts.map +1 -1
- package/package.json +16 -12
- package/src/higher-order/pure/index.tsx +6 -0
- package/src/higher-order/pure/test/index.js +14 -81
- package/src/hooks/use-drop-zone/README.md +1 -1
- package/src/hooks/use-media-query/index.ts +10 -3
- package/src/hooks/use-media-query/test/ssr.js +47 -0
- package/src/hooks/use-merge-refs/index.ts +49 -18
- package/src/hooks/use-merge-refs/test/index.js +288 -0
- package/src/hooks/use-viewport-match/index.js +8 -2
- package/src/higher-order/with-network-connectivity/README.md +0 -20
- package/src/higher-order/with-network-connectivity/index.native.js +0 -19
- package/src/higher-order/with-preferred-color-scheme/index.native.js +0 -40
- package/src/hooks/use-constrained-tabbing/index.native.js +0 -14
- package/src/hooks/use-focus-outside/index.native.js +0 -170
- package/src/hooks/use-keyboard-shortcut/index.native.js +0 -2
- package/src/hooks/use-network-connectivity/index.native.js +0 -59
- package/src/hooks/use-network-connectivity/test/index.native.js +0 -87
- package/src/hooks/use-preferred-color-scheme/index.android.js +0 -30
- package/src/hooks/use-preferred-color-scheme/index.ios.js +0 -13
- package/src/hooks/use-preferred-color-scheme-style/index.native.js +0 -33
- package/src/hooks/use-resize-observer/index.native.js +0 -1
- package/src/hooks/use-resize-observer/legacy/index.native.js +0 -59
- package/src/hooks/use-resize-observer/legacy/test/index.native.js +0 -46
- package/src/index.native.js +0 -44
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-merge-refs/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAoB,GAAG,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-merge-refs/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAoB,GAAG,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AA+BhE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,MAAM,CAAC,OAAO,UAAU,YAAY,CAAE,CAAC,EACtC,IAAI,EAAE,GAAG,CAAE,CAAC,CAAE,EAAE,GACd,WAAW,CAAE,CAAC,CAAE,CAkElB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type WPBreakpoint = "xhuge" | "huge" | "wide" | "xlarge" | "large" | "medium" | "small" | "mobile";
|
|
2
2
|
export type WPViewportOperator = ">=" | "<";
|
|
3
|
-
declare function useViewportMatch(breakpoint: WPBreakpoint, operator?: WPViewportOperator, view?: Window): boolean;
|
|
3
|
+
declare function useViewportMatch(breakpoint: WPBreakpoint, operator?: WPViewportOperator, view?: Window | undefined): boolean;
|
|
4
4
|
declare namespace useViewportMatch {
|
|
5
5
|
var __experimentalWidthProvider: import("react").Provider<number | null>;
|
|
6
6
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-viewport-match/index.js"],"names":[],"mappings":"AAWG,YAA0F,YAAY,GAA5F,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAc;AAsBtG,YAAsB,kBAAkB,GAA9B,IAAI,GAAG,GAAG,CAAoB;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-viewport-match/index.js"],"names":[],"mappings":"AAWG,YAA0F,YAAY,GAA5F,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAc;AAsBtG,YAAsB,kBAAkB,GAA9B,IAAI,GAAG,GAAG,CAAoB;kCA6C1C,UAAU,EAdA,YAcA,EACV,QAAQ,GAdE,kBAcK,EAGf,IAAI,GAhBM,MAAM,GAAC,SAgBwC,GAP9C,OAAO;;;;eA0BJ,gBAAgB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wordpress/compose",
|
|
3
|
-
"version": "8.1.
|
|
3
|
+
"version": "8.1.1",
|
|
4
4
|
"description": "WordPress higher-order components (HOCs).",
|
|
5
5
|
"author": "The WordPress Contributors",
|
|
6
6
|
"license": "GPL-2.0-or-later",
|
|
@@ -41,26 +41,30 @@
|
|
|
41
41
|
},
|
|
42
42
|
"./package.json": "./package.json"
|
|
43
43
|
},
|
|
44
|
-
"react-native": "src/index",
|
|
45
44
|
"wpScript": true,
|
|
46
45
|
"types": "build-types",
|
|
47
46
|
"sideEffects": false,
|
|
48
47
|
"dependencies": {
|
|
49
48
|
"@types/mousetrap": "^1.6.8",
|
|
50
|
-
"@
|
|
51
|
-
"@wordpress/
|
|
52
|
-
"@wordpress/
|
|
53
|
-
"@wordpress/
|
|
54
|
-
"@wordpress/
|
|
55
|
-
"@wordpress/
|
|
56
|
-
"@wordpress/
|
|
57
|
-
"@wordpress/
|
|
49
|
+
"@types/react": "^18.3.27",
|
|
50
|
+
"@wordpress/deprecated": "^4.48.1",
|
|
51
|
+
"@wordpress/dom": "^4.48.1",
|
|
52
|
+
"@wordpress/element": "^8.0.1",
|
|
53
|
+
"@wordpress/is-shallow-equal": "^5.48.1",
|
|
54
|
+
"@wordpress/keycodes": "^4.48.1",
|
|
55
|
+
"@wordpress/priority-queue": "^3.48.1",
|
|
56
|
+
"@wordpress/private-apis": "^1.48.1",
|
|
57
|
+
"@wordpress/undo-manager": "^1.48.1",
|
|
58
58
|
"change-case": "^4.1.2",
|
|
59
59
|
"mousetrap": "^1.6.5",
|
|
60
60
|
"use-memo-one": "^1.1.1"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
|
-
"
|
|
63
|
+
"@testing-library/dom": "^10.4.1",
|
|
64
|
+
"@testing-library/react": "^16.3.2",
|
|
65
|
+
"@testing-library/user-event": "^14.6.1",
|
|
66
|
+
"mock-match-media": "^0.4.2",
|
|
67
|
+
"react-dom": "^18.3.1"
|
|
64
68
|
},
|
|
65
69
|
"peerDependencies": {
|
|
66
70
|
"react": "^18.0.0"
|
|
@@ -68,5 +72,5 @@
|
|
|
68
72
|
"publishConfig": {
|
|
69
73
|
"access": "public"
|
|
70
74
|
},
|
|
71
|
-
"gitHead": "
|
|
75
|
+
"gitHead": "99df7432c5c7cb83ba41146fd1f57f3c19004305"
|
|
72
76
|
}
|
|
@@ -8,6 +8,7 @@ import type { ComponentType, ComponentClass } from 'react';
|
|
|
8
8
|
*/
|
|
9
9
|
import { isShallowEqual } from '@wordpress/is-shallow-equal';
|
|
10
10
|
import { Component } from '@wordpress/element';
|
|
11
|
+
import deprecated from '@wordpress/deprecated';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Internal dependencies
|
|
@@ -23,6 +24,11 @@ import { createHigherOrderComponent } from '../../utils/create-higher-order-comp
|
|
|
23
24
|
const pure = createHigherOrderComponent( function < Props extends {} >(
|
|
24
25
|
WrappedComponent: ComponentType< Props >
|
|
25
26
|
): ComponentType< Props > {
|
|
27
|
+
deprecated( 'wp.compose.pure', {
|
|
28
|
+
since: '7.1',
|
|
29
|
+
alternative: 'Use `memo` or `PureComponent` instead',
|
|
30
|
+
} );
|
|
31
|
+
|
|
26
32
|
if ( WrappedComponent.prototype instanceof Component ) {
|
|
27
33
|
return class extends ( WrappedComponent as ComponentClass< Props > ) {
|
|
28
34
|
shouldComponentUpdate( nextProps: Props, nextState: any ) {
|
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
4
|
import { render, screen } from '@testing-library/react';
|
|
5
|
-
import userEvent from '@testing-library/user-event';
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* WordPress dependencies
|
|
9
8
|
*/
|
|
10
|
-
import {
|
|
9
|
+
import { logged } from '@wordpress/deprecated';
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* Internal dependencies
|
|
@@ -15,88 +14,22 @@ import { Component } from '@wordpress/element';
|
|
|
15
14
|
import pure from '../';
|
|
16
15
|
|
|
17
16
|
describe( 'pure', () => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
} );
|
|
23
|
-
const { rerender } = render( <MyComp /> );
|
|
24
|
-
|
|
25
|
-
// Updating with same props doesn't rerender.
|
|
26
|
-
rerender( <MyComp /> );
|
|
27
|
-
expect( screen.getByTestId( 'counter' ) ).toHaveTextContent( '1' );
|
|
28
|
-
|
|
29
|
-
// New prop should trigger a rerender.
|
|
30
|
-
rerender( <MyComp { ...{ prop: 'a' } } /> );
|
|
31
|
-
expect( screen.getByTestId( 'counter' ) ).toHaveTextContent( '2' );
|
|
32
|
-
|
|
33
|
-
// Keeping the same prop value should not rerender.
|
|
34
|
-
rerender( <MyComp { ...{ prop: 'a' } } /> );
|
|
35
|
-
expect( screen.getByTestId( 'counter' ) ).toHaveTextContent( '2' );
|
|
36
|
-
|
|
37
|
-
// Changing the prop value should rerender.
|
|
38
|
-
rerender( <MyComp { ...{ prop: 'b' } } /> );
|
|
39
|
-
expect( screen.getByTestId( 'counter' ) ).toHaveTextContent( '3' );
|
|
17
|
+
afterEach( () => {
|
|
18
|
+
for ( const key in logged ) {
|
|
19
|
+
delete logged[ key ];
|
|
20
|
+
}
|
|
40
21
|
} );
|
|
41
22
|
|
|
42
|
-
it( '
|
|
43
|
-
const
|
|
44
|
-
let i = 0;
|
|
45
|
-
const MyComp = pure(
|
|
46
|
-
class extends Component {
|
|
47
|
-
constructor() {
|
|
48
|
-
super( ...arguments );
|
|
49
|
-
this.state = {
|
|
50
|
-
val: '',
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
render() {
|
|
54
|
-
return (
|
|
55
|
-
<>
|
|
56
|
-
<p data-testid="counter">{ ++i }</p>
|
|
57
|
-
<input
|
|
58
|
-
type="text"
|
|
59
|
-
value={ this.state.val }
|
|
60
|
-
onChange={ ( e ) =>
|
|
61
|
-
this.setState( { val: e.target.value } )
|
|
62
|
-
}
|
|
63
|
-
/>
|
|
64
|
-
<input
|
|
65
|
-
type="button"
|
|
66
|
-
onClick={ () =>
|
|
67
|
-
this.setState( { val: this.state.val } )
|
|
68
|
-
}
|
|
69
|
-
/>
|
|
70
|
-
</>
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
const { rerender } = render( <MyComp /> );
|
|
23
|
+
it( 'wraps a component and logs a deprecation warning', () => {
|
|
24
|
+
const MyComp = pure( () => <p data-testid="content">content</p> );
|
|
77
25
|
|
|
78
|
-
|
|
79
|
-
rerender( <MyComp /> );
|
|
80
|
-
expect( screen.getByTestId( 'counter' ) ).toHaveTextContent( '1' );
|
|
26
|
+
render( <MyComp /> );
|
|
81
27
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
expect( screen.getByTestId( 'counter' ) ).toHaveTextContent( '2' );
|
|
89
|
-
|
|
90
|
-
// Changing the prop value should rerender.
|
|
91
|
-
rerender( <MyComp { ...{ prop: 'b' } } /> );
|
|
92
|
-
expect( screen.getByTestId( 'counter' ) ).toHaveTextContent( '3' );
|
|
93
|
-
|
|
94
|
-
// New state value should trigger a rerender.
|
|
95
|
-
await user.type( screen.getByRole( 'textbox' ), 'a' );
|
|
96
|
-
expect( screen.getByTestId( 'counter' ) ).toHaveTextContent( '4' );
|
|
97
|
-
|
|
98
|
-
// Keeping the same state value should not trigger a rerender.
|
|
99
|
-
await user.click( screen.getByRole( 'button' ) );
|
|
100
|
-
expect( screen.getByTestId( 'counter' ) ).toHaveTextContent( '4' );
|
|
28
|
+
expect( console ).toHaveWarnedWith(
|
|
29
|
+
'wp.compose.pure is deprecated since version 7.1. Please use Use `memo` or `PureComponent` instead instead.'
|
|
30
|
+
);
|
|
31
|
+
expect( screen.getByTestId( 'content' ) ).toHaveTextContent(
|
|
32
|
+
'content'
|
|
33
|
+
);
|
|
101
34
|
} );
|
|
102
35
|
} );
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A hook to facilitate drag and drop handling within a designated drop zone area. An optional `dropZoneElement` can be provided, however by default the drop zone is bound by the area where the returned `ref` is assigned.
|
|
4
4
|
|
|
5
|
-
When using a `dropZoneElement`, it is expected that the `ref` will be attached to a node that is a
|
|
5
|
+
When using a `dropZoneElement`, it is expected that the `ref` will be attached to a node that is a descendant of the `dropZoneElement`. Additionally, the element passed to `dropZoneElement` should be stored in state rather than a plain ref to ensure reactive updating when it changes.
|
|
6
6
|
|
|
7
7
|
## Usage
|
|
8
8
|
|
|
@@ -19,8 +19,11 @@ const EMPTY_SUBSCRIBER: MQLSubscriber = {
|
|
|
19
19
|
getValue: () => false,
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
function getMQLSubscriber(
|
|
23
|
-
|
|
22
|
+
function getMQLSubscriber(
|
|
23
|
+
view: Window | undefined,
|
|
24
|
+
query?: string
|
|
25
|
+
): MQLSubscriber {
|
|
26
|
+
if ( ! view || ! query || typeof view.matchMedia !== 'function' ) {
|
|
24
27
|
return EMPTY_SUBSCRIBER;
|
|
25
28
|
}
|
|
26
29
|
|
|
@@ -75,7 +78,11 @@ function getMQLSubscriber( view: Window, query?: string ): MQLSubscriber {
|
|
|
75
78
|
*/
|
|
76
79
|
export default function useMediaQuery(
|
|
77
80
|
query?: string,
|
|
78
|
-
|
|
81
|
+
// Resolve the default lazily so SSR (where `window` is undeclared) does not
|
|
82
|
+
// throw a ReferenceError when this default expression is evaluated.
|
|
83
|
+
view: Window | undefined = typeof window !== 'undefined'
|
|
84
|
+
? window
|
|
85
|
+
: undefined
|
|
79
86
|
): boolean {
|
|
80
87
|
const source = getMQLSubscriber( view, query );
|
|
81
88
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment node
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* External dependencies
|
|
7
|
+
*/
|
|
8
|
+
import { renderToString } from 'react-dom/server';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Internal dependencies
|
|
12
|
+
*/
|
|
13
|
+
import useMediaQuery from '../';
|
|
14
|
+
import useViewportMatch from '../../use-viewport-match';
|
|
15
|
+
|
|
16
|
+
const MediaQueryComponent = ( { query } ) => {
|
|
17
|
+
const result = useMediaQuery( query );
|
|
18
|
+
return `useMediaQuery: ${ result }`;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const ViewportMatchComponent = ( { breakpoint, operator } ) => {
|
|
22
|
+
const result = useViewportMatch( breakpoint, operator );
|
|
23
|
+
return `useViewportMatch: ${ result }`;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe( 'compose hooks in a server (no-window) environment', () => {
|
|
27
|
+
it( 'useMediaQuery renders to false without throwing when window is undefined', () => {
|
|
28
|
+
expect( typeof window ).toBe( 'undefined' );
|
|
29
|
+
expect(
|
|
30
|
+
renderToString( <MediaQueryComponent query="(min-width: 782px)" /> )
|
|
31
|
+
).toBe( 'useMediaQuery: false' );
|
|
32
|
+
} );
|
|
33
|
+
|
|
34
|
+
it( 'useMediaQuery renders to false when no query is provided', () => {
|
|
35
|
+
expect( renderToString( <MediaQueryComponent /> ) ).toBe(
|
|
36
|
+
'useMediaQuery: false'
|
|
37
|
+
);
|
|
38
|
+
} );
|
|
39
|
+
|
|
40
|
+
it( 'useViewportMatch renders to false without throwing when window is undefined', () => {
|
|
41
|
+
expect(
|
|
42
|
+
renderToString(
|
|
43
|
+
<ViewportMatchComponent breakpoint="small" operator=">=" />
|
|
44
|
+
)
|
|
45
|
+
).toBe( 'useViewportMatch: false' );
|
|
46
|
+
} );
|
|
47
|
+
} );
|
|
@@ -4,12 +4,33 @@
|
|
|
4
4
|
import { useRef, useCallback, useLayoutEffect } from '@wordpress/element';
|
|
5
5
|
import type { MutableRefObject, Ref, RefCallback } from 'react';
|
|
6
6
|
|
|
7
|
-
function
|
|
7
|
+
// Returns a cleanup function if the ref callback returned one (React 19 ref
|
|
8
|
+
// callback cleanup pattern), otherwise `undefined`. Object refs never have a
|
|
9
|
+
// cleanup and only set `.current`.
|
|
10
|
+
function assignRef< T >( ref: Ref< T >, value: T ): ( () => void ) | undefined {
|
|
8
11
|
if ( typeof ref === 'function' ) {
|
|
9
|
-
ref( value );
|
|
12
|
+
const returned = ref( value );
|
|
13
|
+
return typeof returned === 'function' ? returned : undefined;
|
|
10
14
|
} else if ( ref && ref.hasOwnProperty( 'current' ) ) {
|
|
11
15
|
( ref as MutableRefObject< T > ).current = value;
|
|
12
16
|
}
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Tear down a ref at the given index: prefer the stored cleanup; otherwise
|
|
21
|
+
// fall back to calling the ref with `null`.
|
|
22
|
+
function detachRef< T >(
|
|
23
|
+
ref: Ref< T >,
|
|
24
|
+
index: number,
|
|
25
|
+
cleanups: Array< ( () => void ) | undefined >
|
|
26
|
+
): void {
|
|
27
|
+
const cleanup = cleanups[ index ];
|
|
28
|
+
if ( cleanup ) {
|
|
29
|
+
cleanups[ index ] = undefined;
|
|
30
|
+
cleanup();
|
|
31
|
+
} else {
|
|
32
|
+
assignRef( ref, null );
|
|
33
|
+
}
|
|
13
34
|
}
|
|
14
35
|
|
|
15
36
|
/**
|
|
@@ -28,22 +49,24 @@ function assignRef< T >( ref: Ref< T >, value: T ) {
|
|
|
28
49
|
* old ref callback will be called with `null` and the new ref callback will be
|
|
29
50
|
* called with the same value.
|
|
30
51
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
52
|
+
* Inner ref callbacks may return a cleanup function (React 19's ref callback
|
|
53
|
+
* cleanup pattern). When a ref callback returns a function, that function is
|
|
54
|
+
* invoked at teardown (node change, dependency change, or unmount) **instead
|
|
55
|
+
* of** the callback being called with `null`. Callbacks that do not return a
|
|
56
|
+
* cleanup continue to receive `null` on teardown as before.
|
|
34
57
|
*
|
|
35
58
|
* It's also possible to _disable_ a ref (and its behaviour) by simply not
|
|
36
59
|
* passing the ref.
|
|
37
60
|
*
|
|
38
61
|
* ```jsx
|
|
39
|
-
* const ref =
|
|
62
|
+
* const ref = useCallback( ( node ) => {
|
|
40
63
|
* node.addEventListener( ... );
|
|
41
64
|
* return () => {
|
|
42
65
|
* node.removeEventListener( ... );
|
|
43
66
|
* };
|
|
44
67
|
* }, [ ...dependencies ] );
|
|
45
68
|
* const otherRef = useRef();
|
|
46
|
-
* const mergedRefs useMergeRefs( [
|
|
69
|
+
* const mergedRefs = useMergeRefs( [
|
|
47
70
|
* enabled && ref,
|
|
48
71
|
* otherRef,
|
|
49
72
|
* ] );
|
|
@@ -56,11 +79,15 @@ function assignRef< T >( ref: Ref< T >, value: T ) {
|
|
|
56
79
|
export default function useMergeRefs< T >(
|
|
57
80
|
refs: Ref< T >[]
|
|
58
81
|
): RefCallback< T > {
|
|
59
|
-
const
|
|
82
|
+
const elementRef = useRef< T | null >( null );
|
|
60
83
|
const isAttachedRef = useRef( false );
|
|
61
84
|
const didElementChangeRef = useRef( false );
|
|
62
85
|
const previousRefsRef = useRef< Ref< T >[] >( [] );
|
|
63
86
|
const currentRefsRef = useRef( refs );
|
|
87
|
+
// Position-indexed cleanups returned by inner ref callbacks. A slot is
|
|
88
|
+
// `undefined` when the ref at that position did not return a cleanup (or
|
|
89
|
+
// is an object ref / disabled).
|
|
90
|
+
const cleanupsRef = useRef< Array< ( () => void ) | undefined > >( [] );
|
|
64
91
|
|
|
65
92
|
// Update on render before the ref callback is called, so the ref callback
|
|
66
93
|
// always has access to the current refs.
|
|
@@ -77,8 +104,11 @@ export default function useMergeRefs< T >(
|
|
|
77
104
|
refs.forEach( ( ref, index ) => {
|
|
78
105
|
const previousRef = previousRefsRef.current[ index ];
|
|
79
106
|
if ( ref !== previousRef ) {
|
|
80
|
-
|
|
81
|
-
|
|
107
|
+
detachRef( previousRef, index, cleanupsRef.current );
|
|
108
|
+
cleanupsRef.current[ index ] = assignRef(
|
|
109
|
+
ref,
|
|
110
|
+
elementRef.current as T
|
|
111
|
+
);
|
|
82
112
|
}
|
|
83
113
|
} );
|
|
84
114
|
}
|
|
@@ -97,20 +127,21 @@ export default function useMergeRefs< T >(
|
|
|
97
127
|
return useCallback( ( value: T | null ) => {
|
|
98
128
|
// Update the element so it can be used when calling ref callbacks on a
|
|
99
129
|
// dependency change.
|
|
100
|
-
|
|
130
|
+
elementRef.current = value;
|
|
101
131
|
|
|
102
132
|
didElementChangeRef.current = true;
|
|
103
133
|
isAttachedRef.current = value !== null;
|
|
104
134
|
|
|
105
135
|
// When an element changes, the current ref callback should be called
|
|
106
136
|
// with the new element and the previous one with `null`.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
137
|
+
if ( value === null ) {
|
|
138
|
+
previousRefsRef.current.forEach( ( ref, index ) => {
|
|
139
|
+
detachRef( ref, index, cleanupsRef.current );
|
|
140
|
+
} );
|
|
141
|
+
} else {
|
|
142
|
+
currentRefsRef.current.forEach( ( ref, index ) => {
|
|
143
|
+
cleanupsRef.current[ index ] = assignRef( ref, value );
|
|
144
|
+
} );
|
|
114
145
|
}
|
|
115
146
|
}, [] );
|
|
116
147
|
}
|