@wordpress/compose 7.40.1-next.v.202602241322.0 → 7.40.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +8 -6
  3. package/build/hooks/use-copy-on-click/index.cjs +43 -18
  4. package/build/hooks/use-copy-on-click/index.cjs.map +4 -4
  5. package/build/hooks/use-copy-to-clipboard/index.cjs +58 -12
  6. package/build/hooks/use-copy-to-clipboard/index.cjs.map +4 -4
  7. package/build/hooks/use-media-query/index.cjs +12 -9
  8. package/build/hooks/use-media-query/index.cjs.map +3 -3
  9. package/build/hooks/use-viewport-match/index.cjs +2 -2
  10. package/build/hooks/use-viewport-match/index.cjs.map +2 -2
  11. package/build-module/hooks/use-copy-on-click/index.mjs +44 -19
  12. package/build-module/hooks/use-copy-on-click/index.mjs.map +3 -3
  13. package/build-module/hooks/use-copy-to-clipboard/index.mjs +53 -12
  14. package/build-module/hooks/use-copy-to-clipboard/index.mjs.map +3 -3
  15. package/build-module/hooks/use-media-query/index.mjs +12 -9
  16. package/build-module/hooks/use-media-query/index.mjs.map +3 -3
  17. package/build-module/hooks/use-viewport-match/index.mjs +2 -2
  18. package/build-module/hooks/use-viewport-match/index.mjs.map +2 -2
  19. package/build-types/hooks/use-copy-on-click/index.d.ts +8 -9
  20. package/build-types/hooks/use-copy-on-click/index.d.ts.map +1 -1
  21. package/build-types/hooks/use-copy-to-clipboard/index.d.ts +22 -6
  22. package/build-types/hooks/use-copy-to-clipboard/index.d.ts.map +1 -1
  23. package/build-types/hooks/use-isomorphic-layout-effect/index.d.ts +2 -2
  24. package/build-types/hooks/use-isomorphic-layout-effect/index.d.ts.map +1 -1
  25. package/build-types/hooks/use-media-query/index.d.ts +4 -3
  26. package/build-types/hooks/use-media-query/index.d.ts.map +1 -1
  27. package/build-types/hooks/use-viewport-match/index.d.ts +2 -1
  28. package/build-types/hooks/use-viewport-match/index.d.ts.map +1 -1
  29. package/build-types/lock-unlock.d.ts +2 -0
  30. package/build-types/lock-unlock.d.ts.map +1 -0
  31. package/build-types/private-apis.d.ts +5 -0
  32. package/build-types/private-apis.d.ts.map +1 -0
  33. package/build-types/utils/subscribe-delegated-listener/index.d.ts +27 -0
  34. package/build-types/utils/subscribe-delegated-listener/index.d.ts.map +1 -0
  35. package/package.json +9 -10
  36. package/src/hooks/use-copy-on-click/index.ts +101 -0
  37. package/src/hooks/use-copy-on-click/test/index.tsx +162 -0
  38. package/src/hooks/use-copy-to-clipboard/index.ts +120 -0
  39. package/src/hooks/use-copy-to-clipboard/test/index.tsx +144 -0
  40. package/src/hooks/use-media-query/{index.js → index.ts} +27 -16
  41. package/src/hooks/use-viewport-match/index.js +3 -2
  42. package/src/hooks/use-viewport-match/test/index.js +32 -10
  43. package/src/hooks/use-copy-on-click/index.js +0 -75
  44. package/src/hooks/use-copy-to-clipboard/index.js +0 -69
@@ -0,0 +1,162 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { render, screen, act } from '@testing-library/react';
5
+ import userEvent from '@testing-library/user-event';
6
+
7
+ /**
8
+ * WordPress dependencies
9
+ */
10
+ import deprecated from '@wordpress/deprecated';
11
+ import { useRef } from '@wordpress/element';
12
+
13
+ /**
14
+ * Internal dependencies
15
+ */
16
+ import useCopyOnClick from '../';
17
+
18
+ jest.mock( '@wordpress/deprecated' );
19
+
20
+ interface TestComponentProps {
21
+ text: string | ( () => string );
22
+ timeout?: number;
23
+ }
24
+
25
+ describe( 'useCopyOnClick', () => {
26
+ const TestComponent = ( { text, timeout = 4000 }: TestComponentProps ) => {
27
+ const ref = useRef< HTMLButtonElement >( null );
28
+ const hasCopied = useCopyOnClick( ref, text, timeout );
29
+ return (
30
+ <button ref={ ref } type="button">
31
+ { hasCopied ? 'Copied!' : 'Copy' }
32
+ </button>
33
+ );
34
+ };
35
+
36
+ it( 'should call deprecated when the hook is used', () => {
37
+ jest.mocked( deprecated ).mockClear();
38
+ render( <TestComponent text="test text" /> );
39
+
40
+ expect( deprecated ).toHaveBeenCalledWith(
41
+ 'wp.compose.useCopyOnClick',
42
+ {
43
+ since: '5.8',
44
+ alternative: 'wp.compose.useCopyToClipboard',
45
+ }
46
+ );
47
+ } );
48
+
49
+ it( 'should copy text on click', async () => {
50
+ const user = userEvent.setup();
51
+ render( <TestComponent text="test text" /> );
52
+
53
+ const writeTextMock = jest
54
+ .spyOn( navigator.clipboard, 'writeText' )
55
+ .mockResolvedValue();
56
+
57
+ await user.click( screen.getByRole( 'button' ) );
58
+
59
+ expect( writeTextMock ).toHaveBeenCalledTimes( 1 );
60
+ expect( writeTextMock ).toHaveBeenCalledWith( 'test text' );
61
+ } );
62
+
63
+ it( 'should set hasCopied to true when copy succeeds', async () => {
64
+ const user = userEvent.setup();
65
+ render( <TestComponent text="test text" /> );
66
+
67
+ jest.spyOn( navigator.clipboard, 'writeText' ).mockResolvedValue();
68
+
69
+ expect( screen.getByRole( 'button' ) ).toHaveTextContent( 'Copy' );
70
+
71
+ await user.click( screen.getByRole( 'button' ) );
72
+
73
+ await act( async () => {
74
+ await new Promise( ( resolve ) => setTimeout( resolve, 0 ) );
75
+ } );
76
+
77
+ expect( screen.getByRole( 'button' ) ).toHaveTextContent( 'Copied!' );
78
+ } );
79
+
80
+ it( 'should reset hasCopied after timeout', async () => {
81
+ jest.useFakeTimers();
82
+ const user = userEvent.setup( {
83
+ advanceTimers: jest.advanceTimersByTime,
84
+ } );
85
+ render( <TestComponent text="test text" timeout={ 1000 } /> );
86
+
87
+ jest.spyOn( navigator.clipboard, 'writeText' ).mockResolvedValue();
88
+
89
+ await user.click( screen.getByRole( 'button' ) );
90
+
91
+ await act( async () => {
92
+ const p = new Promise( ( resolve ) => setTimeout( resolve, 0 ) );
93
+ jest.advanceTimersByTime( 0 );
94
+ await p;
95
+ } );
96
+ expect( screen.getByRole( 'button' ) ).toHaveTextContent( 'Copied!' );
97
+
98
+ act( () => {
99
+ jest.advanceTimersByTime( 1000 );
100
+ } );
101
+ expect( screen.getByRole( 'button' ) ).toHaveTextContent( 'Copy' );
102
+
103
+ jest.useRealTimers();
104
+ } );
105
+
106
+ it( 'should not set hasCopied when copy fails', async () => {
107
+ const user = userEvent.setup();
108
+ render( <TestComponent text="test text" /> );
109
+
110
+ jest.spyOn( navigator.clipboard, 'writeText' ).mockRejectedValue(
111
+ new Error()
112
+ );
113
+
114
+ await user.click( screen.getByRole( 'button' ) );
115
+
116
+ await act( async () => {
117
+ await new Promise( ( resolve ) => setTimeout( resolve, 0 ) );
118
+ } );
119
+
120
+ expect( screen.getByRole( 'button' ) ).toHaveTextContent( 'Copy' );
121
+ } );
122
+
123
+ it( 'should not update hasCopied after unmount', async () => {
124
+ const renderSpy = jest.fn();
125
+
126
+ const SpyComponent = ( { text }: { text: string } ) => {
127
+ const ref = useRef< HTMLButtonElement >( null );
128
+ const hasCopied = useCopyOnClick( ref, text );
129
+ renderSpy( hasCopied );
130
+ return (
131
+ <button ref={ ref } type="button">
132
+ { hasCopied ? 'Copied!' : 'Copy' }
133
+ </button>
134
+ );
135
+ };
136
+
137
+ let resolvePromise: () => void;
138
+ const delayedPromise = new Promise< void >( ( resolve ) => {
139
+ resolvePromise = resolve;
140
+ } );
141
+ jest.spyOn( navigator.clipboard, 'writeText' ).mockReturnValue(
142
+ delayedPromise as Promise< void >
143
+ );
144
+
145
+ const user = userEvent.setup();
146
+ const { unmount } = render( <SpyComponent text="test" /> );
147
+
148
+ expect( renderSpy ).toHaveBeenLastCalledWith( false );
149
+ const renderCountBeforeUnmount = renderSpy.mock.calls.length;
150
+
151
+ await user.click( screen.getByRole( 'button' ) );
152
+ unmount();
153
+
154
+ await act( async () => {
155
+ resolvePromise();
156
+ await new Promise( ( resolve ) => setTimeout( resolve, 0 ) );
157
+ } );
158
+
159
+ // No additional renders after unmount — setHasCopied(true) was not called.
160
+ expect( renderSpy ).toHaveBeenCalledTimes( renderCountBeforeUnmount );
161
+ } );
162
+ } );
@@ -0,0 +1,120 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useRef, useLayoutEffect } from '@wordpress/element';
5
+ import type { MutableRefObject, RefCallback } from 'react';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import useRefEffect from '../use-ref-effect';
11
+
12
+ /**
13
+ * Copies text to the clipboard using the Clipboard API when available,
14
+ * with a fallback for non-secure contexts (e.g. HTTP) and older browsers.
15
+ *
16
+ * @param text The text to copy.
17
+ * @param trigger The element that triggered the copy.
18
+ * @return Resolves to true if successful, false otherwise.
19
+ */
20
+ export async function copyToClipboard(
21
+ text: string,
22
+ trigger: Element | null
23
+ ): Promise< boolean > {
24
+ if ( ! trigger ) {
25
+ return false;
26
+ }
27
+ const { ownerDocument } = trigger;
28
+ if ( ! ownerDocument ) {
29
+ return false;
30
+ }
31
+ const { defaultView } = ownerDocument;
32
+ try {
33
+ if ( defaultView?.navigator?.clipboard?.writeText ) {
34
+ await defaultView.navigator.clipboard.writeText( text );
35
+ return true;
36
+ }
37
+ // Fallback for non-secure contexts (HTTP) and older browsers.
38
+ const textarea = ownerDocument.createElement( 'textarea' );
39
+ textarea.value = text;
40
+ textarea.setAttribute( 'readonly', '' );
41
+ textarea.style.position = 'fixed';
42
+ textarea.style.left = '-9999px';
43
+ textarea.style.top = '-9999px';
44
+ ownerDocument.body.appendChild( textarea );
45
+ textarea.select();
46
+ const success = ownerDocument.execCommand( 'copy' );
47
+ textarea.remove();
48
+ return success;
49
+ } catch {
50
+ return false;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Clears the current selection and restores focus to the trigger element.
56
+ *
57
+ * @param trigger The element that triggered the copy.
58
+ */
59
+ export function clearSelection( trigger: Element ): void {
60
+ if ( 'focus' in trigger && typeof trigger.focus === 'function' ) {
61
+ trigger.focus();
62
+ }
63
+ trigger.ownerDocument?.defaultView?.getSelection()?.removeAllRanges();
64
+ }
65
+
66
+ /**
67
+ * @template T
68
+ * @param value
69
+ * @return A ref to assign to the target element.
70
+ */
71
+ function useUpdatedRef< T >( value: T ): MutableRefObject< T > {
72
+ const ref = useRef< T >( value );
73
+ useLayoutEffect( () => {
74
+ ref.current = value;
75
+ }, [ value ] );
76
+ return ref;
77
+ }
78
+
79
+ /**
80
+ * Copies the given text to the clipboard when the element is clicked.
81
+ *
82
+ * @template T
83
+ * @param text The text to copy. Use a function if not
84
+ * already available and expensive to compute.
85
+ * @param onSuccess Called when to text is copied.
86
+ *
87
+ * @return A ref to assign to the target element.
88
+ */
89
+ export default function useCopyToClipboard< T extends HTMLElement >(
90
+ text: string | ( () => string ),
91
+ onSuccess?: () => void
92
+ ): RefCallback< T > {
93
+ const textRef = useUpdatedRef( text );
94
+ const onSuccessRef = useUpdatedRef( onSuccess );
95
+ return useRefEffect( ( node ) => {
96
+ // Flag to prevent callbacks after unmount when the Promise resolves.
97
+ let isActive = true;
98
+ const handleClick = async () => {
99
+ const textToCopy =
100
+ typeof textRef.current === 'function'
101
+ ? textRef.current()
102
+ : textRef.current || '';
103
+ const success = await copyToClipboard( textToCopy, node );
104
+ if ( ! isActive ) {
105
+ return;
106
+ }
107
+ if ( success ) {
108
+ clearSelection( node );
109
+ if ( onSuccessRef.current ) {
110
+ onSuccessRef.current();
111
+ }
112
+ }
113
+ };
114
+ node.addEventListener( 'click', handleClick );
115
+ return () => {
116
+ isActive = false;
117
+ node.removeEventListener( 'click', handleClick );
118
+ };
119
+ }, [] );
120
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { render, screen, act } from '@testing-library/react';
5
+ import userEvent from '@testing-library/user-event';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import useCopyToClipboard, { copyToClipboard, clearSelection } from '../';
11
+
12
+ interface TestComponentProps {
13
+ text: string | ( () => string );
14
+ onSuccess?: () => void;
15
+ }
16
+
17
+ describe( 'useCopyToClipboard', () => {
18
+ const TestComponent = ( { text, onSuccess }: TestComponentProps ) => {
19
+ const ref = useCopyToClipboard( text, onSuccess );
20
+ return <button ref={ ref }>Copy</button>;
21
+ };
22
+
23
+ it( 'should copy text on click', async () => {
24
+ const user = userEvent.setup();
25
+ render( <TestComponent text="test text" /> );
26
+
27
+ const writeTextMock = jest
28
+ .spyOn( navigator.clipboard, 'writeText' )
29
+ .mockResolvedValue();
30
+
31
+ await user.click( screen.getByRole( 'button' ) );
32
+
33
+ expect( writeTextMock ).toHaveBeenCalledTimes( 1 );
34
+ expect( writeTextMock ).toHaveBeenCalledWith( 'test text' );
35
+ } );
36
+
37
+ it( 'should call onSuccess when copy succeeds', async () => {
38
+ const user = userEvent.setup();
39
+ const onSuccess = jest.fn();
40
+ render( <TestComponent text="test text" onSuccess={ onSuccess } /> );
41
+
42
+ await user.click( screen.getByRole( 'button' ) );
43
+
44
+ expect( onSuccess ).toHaveBeenCalledTimes( 1 );
45
+ } );
46
+
47
+ it( 'should call onSuccess when copy empty text', async () => {
48
+ const user = userEvent.setup();
49
+ const onSuccess = jest.fn();
50
+ render( <TestComponent text="" onSuccess={ onSuccess } /> );
51
+
52
+ await user.click( screen.getByRole( 'button' ) );
53
+
54
+ expect( onSuccess ).toHaveBeenCalledTimes( 1 );
55
+ } );
56
+
57
+ it( 'should not call onSuccess when copy fails', async () => {
58
+ const user = userEvent.setup();
59
+ const onSuccess = jest.fn();
60
+ render( <TestComponent text="test text" onSuccess={ onSuccess } /> );
61
+
62
+ jest.spyOn( navigator.clipboard, 'writeText' ).mockRejectedValue(
63
+ new Error()
64
+ );
65
+
66
+ await user.click( screen.getByRole( 'button' ) );
67
+
68
+ expect( onSuccess ).not.toHaveBeenCalled();
69
+ } );
70
+
71
+ it( 'should not call onSuccess after unmount', async () => {
72
+ let resolvePromise: () => void;
73
+ const delayedPromise = new Promise< void >( ( resolve ) => {
74
+ resolvePromise = resolve;
75
+ } );
76
+ jest.spyOn( navigator.clipboard, 'writeText' ).mockReturnValue(
77
+ delayedPromise
78
+ );
79
+
80
+ const user = userEvent.setup();
81
+ const onSuccess = jest.fn();
82
+ const { unmount } = render(
83
+ <TestComponent text="test" onSuccess={ onSuccess } />
84
+ );
85
+
86
+ await user.click( screen.getByRole( 'button' ) );
87
+ unmount();
88
+
89
+ await act( async () => {
90
+ resolvePromise();
91
+ await new Promise( ( resolve ) => setTimeout( resolve, 0 ) );
92
+ } );
93
+
94
+ expect( onSuccess ).not.toHaveBeenCalled();
95
+ } );
96
+ } );
97
+
98
+ describe( 'copyToClipboard', () => {
99
+ it( 'should use execCommand fallback when clipboard API is not available', async () => {
100
+ const trigger = document.createElement( 'button' );
101
+ document.body.appendChild( trigger );
102
+
103
+ const originalClipboard = navigator.clipboard;
104
+ Object.defineProperty( navigator, 'clipboard', {
105
+ value: undefined,
106
+ configurable: true,
107
+ } );
108
+
109
+ // JSDOM does not implement execCommand; add a mock for the fallback path.
110
+ // See: https://github.com/jsdom/jsdom/issues/1742
111
+ const execCommandMock = jest.fn().mockReturnValue( true );
112
+ Object.defineProperty( document, 'execCommand', {
113
+ value: execCommandMock,
114
+ configurable: true,
115
+ writable: true,
116
+ } );
117
+
118
+ const result = await copyToClipboard( 'fallback text', trigger );
119
+
120
+ expect( result ).toBe( true );
121
+ expect( execCommandMock ).toHaveBeenCalledWith( 'copy' );
122
+
123
+ delete ( document as { execCommand?: unknown } ).execCommand;
124
+ Object.defineProperty( navigator, 'clipboard', {
125
+ value: originalClipboard,
126
+ configurable: true,
127
+ } );
128
+ document.body.removeChild( trigger );
129
+ } );
130
+ } );
131
+
132
+ describe( 'clearSelection', () => {
133
+ it( 'should focus the trigger element', () => {
134
+ const trigger = document.createElement( 'button' );
135
+ document.body.appendChild( trigger );
136
+ const focusMock = jest.spyOn( trigger, 'focus' );
137
+
138
+ clearSelection( trigger );
139
+
140
+ expect( focusMock ).toHaveBeenCalledTimes( 1 );
141
+
142
+ document.body.removeChild( trigger );
143
+ } );
144
+ } );
@@ -3,30 +3,38 @@
3
3
  */
4
4
  import { useMemo, useSyncExternalStore } from '@wordpress/element';
5
5
 
6
- const matchMediaCache = new Map();
6
+ type MQLCache = Map< string, MediaQueryList >;
7
+
8
+ const perWindowCache = new WeakMap< Window, MQLCache >();
7
9
 
8
10
  /**
9
11
  * A new MediaQueryList object for the media query
10
12
  *
11
- * @param {string} [query] Media Query.
12
- * @return {MediaQueryList|null} A new object for the media query
13
+ * @param view Window.
14
+ * @param [query] Media Query.
13
15
  */
14
- function getMediaQueryList( query ) {
16
+ function getMediaQueryList(
17
+ view: Window,
18
+ query?: string
19
+ ): MediaQueryList | null {
15
20
  if ( ! query ) {
16
21
  return null;
17
22
  }
18
23
 
24
+ const matchMediaCache: MQLCache = perWindowCache.get( view ) ?? new Map();
25
+
26
+ if ( ! perWindowCache.has( view ) ) {
27
+ perWindowCache.set( view, matchMediaCache );
28
+ }
29
+
19
30
  let match = matchMediaCache.get( query );
20
31
 
21
32
  if ( match ) {
22
33
  return match;
23
34
  }
24
35
 
25
- if (
26
- typeof window !== 'undefined' &&
27
- typeof window.matchMedia === 'function'
28
- ) {
29
- match = window.matchMedia( query );
36
+ if ( typeof view?.matchMedia === 'function' ) {
37
+ match = view.matchMedia( query );
30
38
  matchMediaCache.set( query, match );
31
39
  return match;
32
40
  }
@@ -37,16 +45,19 @@ function getMediaQueryList( query ) {
37
45
  /**
38
46
  * Runs a media query and returns its value when it changes.
39
47
  *
40
- * @param {string} [query] Media Query.
41
- * @return {boolean} return value of the media query.
48
+ * @param [query] Media Query.
49
+ * @param [view] Window instance, else default to global window
50
+ * @return return value of the media query.
42
51
  */
43
- export default function useMediaQuery( query ) {
52
+ export default function useMediaQuery(
53
+ query?: string,
54
+ view: Window = window
55
+ ): boolean {
44
56
  const source = useMemo( () => {
45
- const mediaQueryList = getMediaQueryList( query );
57
+ const mediaQueryList = getMediaQueryList( view, query );
46
58
 
47
59
  return {
48
- /** @type {(onStoreChange: () => void) => () => void} */
49
- subscribe( onStoreChange ) {
60
+ subscribe( onStoreChange: any ) {
50
61
  if ( ! mediaQueryList ) {
51
62
  return () => {};
52
63
  }
@@ -64,7 +75,7 @@ export default function useMediaQuery( query ) {
64
75
  return mediaQueryList?.matches ?? false;
65
76
  },
66
77
  };
67
- }, [ query ] );
78
+ }, [ view, query ] );
68
79
 
69
80
  return useSyncExternalStore(
70
81
  source.subscribe,
@@ -64,6 +64,7 @@ ViewportMatchWidthContext.displayName = 'ViewportMatchWidthContext';
64
64
  *
65
65
  * @param {WPBreakpoint} breakpoint Breakpoint size name.
66
66
  * @param {WPViewportOperator} [operator=">="] Viewport operator.
67
+ * @param {Window} [view=window] Window instance in which to perform viewport matching.
67
68
  *
68
69
  * @example
69
70
  *
@@ -74,12 +75,12 @@ ViewportMatchWidthContext.displayName = 'ViewportMatchWidthContext';
74
75
  *
75
76
  * @return {boolean} Whether viewport matches query.
76
77
  */
77
- const useViewportMatch = ( breakpoint, operator = '>=' ) => {
78
+ const useViewportMatch = ( breakpoint, operator = '>=', view = window ) => {
78
79
  const simulatedWidth = useContext( ViewportMatchWidthContext );
79
80
  const mediaQuery =
80
81
  ! simulatedWidth &&
81
82
  `(${ CONDITIONS[ operator ] }: ${ BREAKPOINTS[ breakpoint ] }px)`;
82
- const mediaQueryResult = useMediaQuery( mediaQuery || undefined );
83
+ const mediaQueryResult = useMediaQuery( mediaQuery || undefined, view );
83
84
  if ( simulatedWidth ) {
84
85
  return OPERATOR_EVALUATORS[ operator ](
85
86
  BREAKPOINTS[ breakpoint ],
@@ -44,15 +44,18 @@ describe( 'useViewportMatch', () => {
44
44
  expect( useMediaQueryMock ).toHaveBeenCalledTimes( 3 );
45
45
  expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
46
46
  1,
47
- '(max-width: 1280px)'
47
+ '(max-width: 1280px)',
48
+ window
48
49
  );
49
50
  expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
50
51
  2,
51
- '(min-width: 782px)'
52
+ '(min-width: 782px)',
53
+ window
52
54
  );
53
55
  expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
54
56
  3,
55
- '(min-width: 600px)'
57
+ '(min-width: 600px)',
58
+ window
56
59
  );
57
60
  } );
58
61
 
@@ -76,15 +79,18 @@ describe( 'useViewportMatch', () => {
76
79
  expect( useMediaQueryMock ).toHaveBeenCalledTimes( 3 );
77
80
  expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
78
81
  1,
79
- '(min-width: 1440px)'
82
+ '(min-width: 1440px)',
83
+ window
80
84
  );
81
85
  expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
82
86
  2,
83
- '(max-width: 960px)'
87
+ '(max-width: 960px)',
88
+ window
84
89
  );
85
90
  expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
86
91
  3,
87
- '(max-width: 480px)'
92
+ '(max-width: 480px)',
93
+ window
88
94
  );
89
95
  } );
90
96
 
@@ -121,9 +127,25 @@ describe( 'useViewportMatch', () => {
121
127
 
122
128
  expect( useMediaQueryMock ).toHaveBeenCalledTimes( 4 );
123
129
  // `useMediaQuery` is expected to receive `undefined` when simulating width.
124
- expect( useMediaQueryMock ).toHaveBeenNthCalledWith( 1, undefined );
125
- expect( useMediaQueryMock ).toHaveBeenNthCalledWith( 2, undefined );
126
- expect( useMediaQueryMock ).toHaveBeenNthCalledWith( 3, undefined );
127
- expect( useMediaQueryMock ).toHaveBeenNthCalledWith( 4, undefined );
130
+ expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
131
+ 1,
132
+ undefined,
133
+ window
134
+ );
135
+ expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
136
+ 2,
137
+ undefined,
138
+ window
139
+ );
140
+ expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
141
+ 3,
142
+ undefined,
143
+ window
144
+ );
145
+ expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
146
+ 4,
147
+ undefined,
148
+ window
149
+ );
128
150
  } );
129
151
  } );
@@ -1,75 +0,0 @@
1
- /**
2
- * External dependencies
3
- */
4
- import Clipboard from 'clipboard';
5
-
6
- /**
7
- * WordPress dependencies
8
- */
9
- import { useRef, useEffect, useState } from '@wordpress/element';
10
- import deprecated from '@wordpress/deprecated';
11
-
12
- /**
13
- * Copies the text to the clipboard when the element is clicked.
14
- *
15
- * @deprecated
16
- *
17
- * @param {React.RefObject<string | Element | NodeListOf<Element>>} ref Reference with the element.
18
- * @param {string|Function} text The text to copy.
19
- * @param {number} [timeout] Optional timeout to reset the returned
20
- * state. 4 seconds by default.
21
- *
22
- * @return {boolean} Whether or not the text has been copied. Resets after the
23
- * timeout.
24
- */
25
- export default function useCopyOnClick( ref, text, timeout = 4000 ) {
26
- deprecated( 'wp.compose.useCopyOnClick', {
27
- since: '5.8',
28
- alternative: 'wp.compose.useCopyToClipboard',
29
- } );
30
-
31
- /** @type {React.MutableRefObject<Clipboard | undefined>} */
32
- const clipboardRef = useRef( undefined );
33
- const [ hasCopied, setHasCopied ] = useState( false );
34
-
35
- useEffect( () => {
36
- /** @type {number | undefined} */
37
- let timeoutId;
38
-
39
- if ( ! ref.current ) {
40
- return;
41
- }
42
-
43
- // Clipboard listens to click events.
44
- clipboardRef.current = new Clipboard( ref.current, {
45
- text: () => ( typeof text === 'function' ? text() : text ),
46
- } );
47
-
48
- clipboardRef.current.on( 'success', ( { clearSelection, trigger } ) => {
49
- // Clearing selection will move focus back to the triggering button,
50
- // ensuring that it is not reset to the body, and further that it is
51
- // kept within the rendered node.
52
- clearSelection();
53
-
54
- // Handle ClipboardJS focus bug, see https://github.com/zenorocha/clipboard.js/issues/680
55
- if ( trigger ) {
56
- /** @type {HTMLElement} */ ( trigger ).focus();
57
- }
58
-
59
- if ( timeout ) {
60
- setHasCopied( true );
61
- clearTimeout( timeoutId );
62
- timeoutId = setTimeout( () => setHasCopied( false ), timeout );
63
- }
64
- } );
65
-
66
- return () => {
67
- if ( clipboardRef.current ) {
68
- clipboardRef.current.destroy();
69
- }
70
- clearTimeout( timeoutId );
71
- };
72
- }, [ text, timeout, setHasCopied ] );
73
-
74
- return hasCopied;
75
- }