@wordpress/compose 7.6.0 → 7.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -0
- package/README.md +1 -1
- package/build/hooks/use-copy-on-click/index.js +5 -5
- package/build/hooks/use-copy-on-click/index.js.map +1 -1
- package/build/hooks/use-focus-on-mount/index.js +4 -4
- package/build/hooks/use-focus-on-mount/index.js.map +1 -1
- package/build/hooks/use-focus-outside/index.js +11 -11
- package/build/hooks/use-focus-outside/index.js.map +1 -1
- package/build/hooks/use-keyboard-shortcut/index.js +3 -3
- package/build/hooks/use-keyboard-shortcut/index.js.map +1 -1
- package/build/hooks/use-merge-refs/index.js +12 -12
- package/build/hooks/use-merge-refs/index.js.map +1 -1
- package/build/hooks/use-ref-effect/index.js +4 -4
- package/build/hooks/use-ref-effect/index.js.map +1 -1
- package/build/hooks/use-resize-observer/index.js +79 -184
- package/build/hooks/use-resize-observer/index.js.map +1 -1
- package/build-module/hooks/use-copy-on-click/index.js +5 -5
- package/build-module/hooks/use-copy-on-click/index.js.map +1 -1
- package/build-module/hooks/use-focus-on-mount/index.js +4 -4
- package/build-module/hooks/use-focus-on-mount/index.js.map +1 -1
- package/build-module/hooks/use-focus-outside/index.js +11 -11
- package/build-module/hooks/use-focus-outside/index.js.map +1 -1
- package/build-module/hooks/use-keyboard-shortcut/index.js +3 -3
- package/build-module/hooks/use-keyboard-shortcut/index.js.map +1 -1
- package/build-module/hooks/use-merge-refs/index.js +12 -12
- package/build-module/hooks/use-merge-refs/index.js.map +1 -1
- package/build-module/hooks/use-ref-effect/index.js +4 -4
- package/build-module/hooks/use-ref-effect/index.js.map +1 -1
- package/build-module/hooks/use-resize-observer/index.js +79 -184
- package/build-module/hooks/use-resize-observer/index.js.map +1 -1
- package/build-types/hooks/use-merge-refs/index.d.ts.map +1 -1
- package/build-types/hooks/use-resize-observer/index.d.ts +8 -14
- package/build-types/hooks/use-resize-observer/index.d.ts.map +1 -1
- package/package.json +9 -9
- package/src/hooks/use-copy-on-click/index.js +5 -5
- package/src/hooks/use-focus-on-mount/index.js +4 -4
- package/src/hooks/use-focus-outside/index.ts +11 -11
- package/src/hooks/use-keyboard-shortcut/index.js +3 -3
- package/src/hooks/use-merge-refs/index.js +15 -13
- package/src/hooks/use-ref-effect/index.ts +4 -4
- package/src/hooks/use-resize-observer/index.tsx +85 -288
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -500,7 +500,7 @@ _Returns_
|
|
|
500
500
|
|
|
501
501
|
### useResizeObserver
|
|
502
502
|
|
|
503
|
-
Hook which allows to listen the resize event of any target element when it changes
|
|
503
|
+
Hook which allows to listen to the resize event of any target element when it changes size. \_Note: `useResizeObserver` will report `null` sizes until after first render.
|
|
504
504
|
|
|
505
505
|
_Usage_
|
|
506
506
|
|
|
@@ -38,7 +38,7 @@ function useCopyOnClick(ref, text, timeout = 4000) {
|
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
/** @type {import('react').MutableRefObject<Clipboard | undefined>} */
|
|
41
|
-
const
|
|
41
|
+
const clipboardRef = (0, _element.useRef)();
|
|
42
42
|
const [hasCopied, setHasCopied] = (0, _element.useState)(false);
|
|
43
43
|
(0, _element.useEffect)(() => {
|
|
44
44
|
/** @type {number | undefined} */
|
|
@@ -48,10 +48,10 @@ function useCopyOnClick(ref, text, timeout = 4000) {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
// Clipboard listens to click events.
|
|
51
|
-
|
|
51
|
+
clipboardRef.current = new _clipboard.default(ref.current, {
|
|
52
52
|
text: () => typeof text === 'function' ? text() : text
|
|
53
53
|
});
|
|
54
|
-
|
|
54
|
+
clipboardRef.current.on('success', ({
|
|
55
55
|
clearSelection,
|
|
56
56
|
trigger
|
|
57
57
|
}) => {
|
|
@@ -71,8 +71,8 @@ function useCopyOnClick(ref, text, timeout = 4000) {
|
|
|
71
71
|
}
|
|
72
72
|
});
|
|
73
73
|
return () => {
|
|
74
|
-
if (
|
|
75
|
-
|
|
74
|
+
if (clipboardRef.current) {
|
|
75
|
+
clipboardRef.current.destroy();
|
|
76
76
|
}
|
|
77
77
|
clearTimeout(timeoutId);
|
|
78
78
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_clipboard","_interopRequireDefault","require","_element","_deprecated","useCopyOnClick","ref","text","timeout","deprecated","since","alternative","
|
|
1
|
+
{"version":3,"names":["_clipboard","_interopRequireDefault","require","_element","_deprecated","useCopyOnClick","ref","text","timeout","deprecated","since","alternative","clipboardRef","useRef","hasCopied","setHasCopied","useState","useEffect","timeoutId","current","Clipboard","on","clearSelection","trigger","focus","clearTimeout","setTimeout","destroy"],"sources":["@wordpress/compose/src/hooks/use-copy-on-click/index.js"],"sourcesContent":["/**\n * External dependencies\n */\nimport Clipboard from 'clipboard';\n\n/**\n * WordPress dependencies\n */\nimport { useRef, useEffect, useState } from '@wordpress/element';\nimport deprecated from '@wordpress/deprecated';\n\n/* eslint-disable jsdoc/no-undefined-types */\n/**\n * Copies the text to the clipboard when the element is clicked.\n *\n * @deprecated\n *\n * @param {import('react').RefObject<string | Element | NodeListOf<Element>>} ref Reference with the element.\n * @param {string|Function} text The text to copy.\n * @param {number} [timeout] Optional timeout to reset the returned\n * state. 4 seconds by default.\n *\n * @return {boolean} Whether or not the text has been copied. Resets after the\n * timeout.\n */\nexport default function useCopyOnClick( ref, text, timeout = 4000 ) {\n\t/* eslint-enable jsdoc/no-undefined-types */\n\tdeprecated( 'wp.compose.useCopyOnClick', {\n\t\tsince: '5.8',\n\t\talternative: 'wp.compose.useCopyToClipboard',\n\t} );\n\n\t/** @type {import('react').MutableRefObject<Clipboard | undefined>} */\n\tconst clipboardRef = useRef();\n\tconst [ hasCopied, setHasCopied ] = useState( false );\n\n\tuseEffect( () => {\n\t\t/** @type {number | undefined} */\n\t\tlet timeoutId;\n\n\t\tif ( ! ref.current ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Clipboard listens to click events.\n\t\tclipboardRef.current = new Clipboard( ref.current, {\n\t\t\ttext: () => ( typeof text === 'function' ? text() : text ),\n\t\t} );\n\n\t\tclipboardRef.current.on( 'success', ( { clearSelection, trigger } ) => {\n\t\t\t// Clearing selection will move focus back to the triggering button,\n\t\t\t// ensuring that it is not reset to the body, and further that it is\n\t\t\t// kept within the rendered node.\n\t\t\tclearSelection();\n\n\t\t\t// Handle ClipboardJS focus bug, see https://github.com/zenorocha/clipboard.js/issues/680\n\t\t\tif ( trigger ) {\n\t\t\t\t/** @type {HTMLElement} */ ( trigger ).focus();\n\t\t\t}\n\n\t\t\tif ( timeout ) {\n\t\t\t\tsetHasCopied( true );\n\t\t\t\tclearTimeout( timeoutId );\n\t\t\t\ttimeoutId = setTimeout( () => setHasCopied( false ), timeout );\n\t\t\t}\n\t\t} );\n\n\t\treturn () => {\n\t\t\tif ( clipboardRef.current ) {\n\t\t\t\tclipboardRef.current.destroy();\n\t\t\t}\n\t\t\tclearTimeout( timeoutId );\n\t\t};\n\t}, [ text, timeout, setHasCopied ] );\n\n\treturn hasCopied;\n}\n"],"mappings":";;;;;;;AAGA,IAAAA,UAAA,GAAAC,sBAAA,CAAAC,OAAA;AAKA,IAAAC,QAAA,GAAAD,OAAA;AACA,IAAAE,WAAA,GAAAH,sBAAA,CAAAC,OAAA;AATA;AACA;AACA;;AAGA;AACA;AACA;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACe,SAASG,cAAcA,CAAEC,GAAG,EAAEC,IAAI,EAAEC,OAAO,GAAG,IAAI,EAAG;EACnE;EACA,IAAAC,mBAAU,EAAE,2BAA2B,EAAE;IACxCC,KAAK,EAAE,KAAK;IACZC,WAAW,EAAE;EACd,CAAE,CAAC;;EAEH;EACA,MAAMC,YAAY,GAAG,IAAAC,eAAM,EAAC,CAAC;EAC7B,MAAM,CAAEC,SAAS,EAAEC,YAAY,CAAE,GAAG,IAAAC,iBAAQ,EAAE,KAAM,CAAC;EAErD,IAAAC,kBAAS,EAAE,MAAM;IAChB;IACA,IAAIC,SAAS;IAEb,IAAK,CAAEZ,GAAG,CAACa,OAAO,EAAG;MACpB;IACD;;IAEA;IACAP,YAAY,CAACO,OAAO,GAAG,IAAIC,kBAAS,CAAEd,GAAG,CAACa,OAAO,EAAE;MAClDZ,IAAI,EAAEA,CAAA,KAAQ,OAAOA,IAAI,KAAK,UAAU,GAAGA,IAAI,CAAC,CAAC,GAAGA;IACrD,CAAE,CAAC;IAEHK,YAAY,CAACO,OAAO,CAACE,EAAE,CAAE,SAAS,EAAE,CAAE;MAAEC,cAAc;MAAEC;IAAQ,CAAC,KAAM;MACtE;MACA;MACA;MACAD,cAAc,CAAC,CAAC;;MAEhB;MACA,IAAKC,OAAO,EAAG;QACd,0BAA6BA,OAAO,CAAGC,KAAK,CAAC,CAAC;MAC/C;MAEA,IAAKhB,OAAO,EAAG;QACdO,YAAY,CAAE,IAAK,CAAC;QACpBU,YAAY,CAAEP,SAAU,CAAC;QACzBA,SAAS,GAAGQ,UAAU,CAAE,MAAMX,YAAY,CAAE,KAAM,CAAC,EAAEP,OAAQ,CAAC;MAC/D;IACD,CAAE,CAAC;IAEH,OAAO,MAAM;MACZ,IAAKI,YAAY,CAACO,OAAO,EAAG;QAC3BP,YAAY,CAACO,OAAO,CAACQ,OAAO,CAAC,CAAC;MAC/B;MACAF,YAAY,CAAEP,SAAU,CAAC;IAC1B,CAAC;EACF,CAAC,EAAE,CAAEX,IAAI,EAAEC,OAAO,EAAEO,YAAY,CAAG,CAAC;EAEpC,OAAOD,SAAS;AACjB","ignoreList":[]}
|
|
@@ -56,7 +56,7 @@ function useFocusOnMount(focusOnMount = 'firstElement') {
|
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
/** @type {import('react').MutableRefObject<ReturnType<setTimeout> | undefined>} */
|
|
59
|
-
const
|
|
59
|
+
const timerIdRef = (0, _element.useRef)();
|
|
60
60
|
(0, _element.useEffect)(() => {
|
|
61
61
|
focusOnMountRef.current = focusOnMount;
|
|
62
62
|
}, [focusOnMount]);
|
|
@@ -69,7 +69,7 @@ function useFocusOnMount(focusOnMount = 'firstElement') {
|
|
|
69
69
|
return;
|
|
70
70
|
}
|
|
71
71
|
if (focusOnMountRef.current === 'firstElement') {
|
|
72
|
-
|
|
72
|
+
timerIdRef.current = setTimeout(() => {
|
|
73
73
|
const firstTabbable = _dom.focus.tabbable.find(node)[0];
|
|
74
74
|
if (firstTabbable) {
|
|
75
75
|
setFocus(firstTabbable);
|
|
@@ -79,8 +79,8 @@ function useFocusOnMount(focusOnMount = 'firstElement') {
|
|
|
79
79
|
}
|
|
80
80
|
setFocus(node);
|
|
81
81
|
return () => {
|
|
82
|
-
if (
|
|
83
|
-
clearTimeout(
|
|
82
|
+
if (timerIdRef.current) {
|
|
83
|
+
clearTimeout(timerIdRef.current);
|
|
84
84
|
}
|
|
85
85
|
};
|
|
86
86
|
}, []);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_element","require","_dom","_useRefEffect","_interopRequireDefault","useFocusOnMount","focusOnMount","focusOnMountRef","useRef","setFocus","target","focus","preventScroll","
|
|
1
|
+
{"version":3,"names":["_element","require","_dom","_useRefEffect","_interopRequireDefault","useFocusOnMount","focusOnMount","focusOnMountRef","useRef","setFocus","target","focus","preventScroll","timerIdRef","useEffect","current","useRefEffect","node","_node$ownerDocument$a","contains","ownerDocument","activeElement","setTimeout","firstTabbable","tabbable","find","clearTimeout"],"sources":["@wordpress/compose/src/hooks/use-focus-on-mount/index.js"],"sourcesContent":["/**\n * WordPress dependencies\n */\nimport { useRef, useEffect } from '@wordpress/element';\nimport { focus } from '@wordpress/dom';\n\n/**\n * Internal dependencies\n */\nimport useRefEffect from '../use-ref-effect';\n\n/**\n * Hook used to focus the first tabbable element on mount.\n *\n * @param {boolean | 'firstElement'} focusOnMount Focus on mount mode.\n * @return {import('react').RefCallback<HTMLElement>} Ref callback.\n *\n * @example\n * ```js\n * import { useFocusOnMount } from '@wordpress/compose';\n *\n * const WithFocusOnMount = () => {\n * const ref = useFocusOnMount()\n * return (\n * <div ref={ ref }>\n * <Button />\n * <Button />\n * </div>\n * );\n * }\n * ```\n */\nexport default function useFocusOnMount( focusOnMount = 'firstElement' ) {\n\tconst focusOnMountRef = useRef( focusOnMount );\n\n\t/**\n\t * Sets focus on a DOM element.\n\t *\n\t * @param {HTMLElement} target The DOM element to set focus to.\n\t * @return {void}\n\t */\n\tconst setFocus = ( target ) => {\n\t\ttarget.focus( {\n\t\t\t// When focusing newly mounted dialogs,\n\t\t\t// the position of the popover is often not right on the first render\n\t\t\t// This prevents the layout shifts when focusing the dialogs.\n\t\t\tpreventScroll: true,\n\t\t} );\n\t};\n\n\t/** @type {import('react').MutableRefObject<ReturnType<setTimeout> | undefined>} */\n\tconst timerIdRef = useRef();\n\n\tuseEffect( () => {\n\t\tfocusOnMountRef.current = focusOnMount;\n\t}, [ focusOnMount ] );\n\n\treturn useRefEffect( ( node ) => {\n\t\tif ( ! node || focusOnMountRef.current === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( node.contains( node.ownerDocument?.activeElement ?? null ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( focusOnMountRef.current === 'firstElement' ) {\n\t\t\ttimerIdRef.current = setTimeout( () => {\n\t\t\t\tconst firstTabbable = focus.tabbable.find( node )[ 0 ];\n\n\t\t\t\tif ( firstTabbable ) {\n\t\t\t\t\tsetFocus( firstTabbable );\n\t\t\t\t}\n\t\t\t}, 0 );\n\n\t\t\treturn;\n\t\t}\n\n\t\tsetFocus( node );\n\n\t\treturn () => {\n\t\t\tif ( timerIdRef.current ) {\n\t\t\t\tclearTimeout( timerIdRef.current );\n\t\t\t}\n\t\t};\n\t}, [] );\n}\n"],"mappings":";;;;;;;AAGA,IAAAA,QAAA,GAAAC,OAAA;AACA,IAAAC,IAAA,GAAAD,OAAA;AAKA,IAAAE,aAAA,GAAAC,sBAAA,CAAAH,OAAA;AATA;AACA;AACA;;AAIA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACe,SAASI,eAAeA,CAAEC,YAAY,GAAG,cAAc,EAAG;EACxE,MAAMC,eAAe,GAAG,IAAAC,eAAM,EAAEF,YAAa,CAAC;;EAE9C;AACD;AACA;AACA;AACA;AACA;EACC,MAAMG,QAAQ,GAAKC,MAAM,IAAM;IAC9BA,MAAM,CAACC,KAAK,CAAE;MACb;MACA;MACA;MACAC,aAAa,EAAE;IAChB,CAAE,CAAC;EACJ,CAAC;;EAED;EACA,MAAMC,UAAU,GAAG,IAAAL,eAAM,EAAC,CAAC;EAE3B,IAAAM,kBAAS,EAAE,MAAM;IAChBP,eAAe,CAACQ,OAAO,GAAGT,YAAY;EACvC,CAAC,EAAE,CAAEA,YAAY,CAAG,CAAC;EAErB,OAAO,IAAAU,qBAAY,EAAIC,IAAI,IAAM;IAAA,IAAAC,qBAAA;IAChC,IAAK,CAAED,IAAI,IAAIV,eAAe,CAACQ,OAAO,KAAK,KAAK,EAAG;MAClD;IACD;IAEA,IAAKE,IAAI,CAACE,QAAQ,EAAAD,qBAAA,GAAED,IAAI,CAACG,aAAa,EAAEC,aAAa,cAAAH,qBAAA,cAAAA,qBAAA,GAAI,IAAK,CAAC,EAAG;MACjE;IACD;IAEA,IAAKX,eAAe,CAACQ,OAAO,KAAK,cAAc,EAAG;MACjDF,UAAU,CAACE,OAAO,GAAGO,UAAU,CAAE,MAAM;QACtC,MAAMC,aAAa,GAAGZ,UAAK,CAACa,QAAQ,CAACC,IAAI,CAAER,IAAK,CAAC,CAAE,CAAC,CAAE;QAEtD,IAAKM,aAAa,EAAG;UACpBd,QAAQ,CAAEc,aAAc,CAAC;QAC1B;MACD,CAAC,EAAE,CAAE,CAAC;MAEN;IACD;IAEAd,QAAQ,CAAEQ,IAAK,CAAC;IAEhB,OAAO,MAAM;MACZ,IAAKJ,UAAU,CAACE,OAAO,EAAG;QACzBW,YAAY,CAAEb,UAAU,CAACE,OAAQ,CAAC;MACnC;IACD,CAAC;EACF,CAAC,EAAE,EAAG,CAAC;AACR","ignoreList":[]}
|
|
@@ -55,18 +55,18 @@ function isFocusNormalizedButton(eventTarget) {
|
|
|
55
55
|
* wrapping element element to capture when focus moves outside that element.
|
|
56
56
|
*/
|
|
57
57
|
function useFocusOutside(onFocusOutside) {
|
|
58
|
-
const
|
|
58
|
+
const currentOnFocusOutsideRef = (0, _element.useRef)(onFocusOutside);
|
|
59
59
|
(0, _element.useEffect)(() => {
|
|
60
|
-
|
|
60
|
+
currentOnFocusOutsideRef.current = onFocusOutside;
|
|
61
61
|
}, [onFocusOutside]);
|
|
62
|
-
const
|
|
63
|
-
const
|
|
62
|
+
const preventBlurCheckRef = (0, _element.useRef)(false);
|
|
63
|
+
const blurCheckTimeoutIdRef = (0, _element.useRef)();
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
66
|
* Cancel a blur check timeout.
|
|
67
67
|
*/
|
|
68
68
|
const cancelBlurCheck = (0, _element.useCallback)(() => {
|
|
69
|
-
clearTimeout(
|
|
69
|
+
clearTimeout(blurCheckTimeoutIdRef.current);
|
|
70
70
|
}, []);
|
|
71
71
|
|
|
72
72
|
// Cancel blur checks on unmount.
|
|
@@ -98,9 +98,9 @@ function useFocusOutside(onFocusOutside) {
|
|
|
98
98
|
} = event;
|
|
99
99
|
const isInteractionEnd = ['mouseup', 'touchend'].includes(type);
|
|
100
100
|
if (isInteractionEnd) {
|
|
101
|
-
|
|
101
|
+
preventBlurCheckRef.current = false;
|
|
102
102
|
} else if (isFocusNormalizedButton(target)) {
|
|
103
|
-
|
|
103
|
+
preventBlurCheckRef.current = true;
|
|
104
104
|
}
|
|
105
105
|
}, []);
|
|
106
106
|
|
|
@@ -117,7 +117,7 @@ function useFocusOutside(onFocusOutside) {
|
|
|
117
117
|
event.persist();
|
|
118
118
|
|
|
119
119
|
// Skip blur check if clicking button. See `normalizeButtonFocus`.
|
|
120
|
-
if (
|
|
120
|
+
if (preventBlurCheckRef.current) {
|
|
121
121
|
return;
|
|
122
122
|
}
|
|
123
123
|
|
|
@@ -132,7 +132,7 @@ function useFocusOutside(onFocusOutside) {
|
|
|
132
132
|
if (ignoreForRelatedTarget && event.relatedTarget?.closest(ignoreForRelatedTarget)) {
|
|
133
133
|
return;
|
|
134
134
|
}
|
|
135
|
-
|
|
135
|
+
blurCheckTimeoutIdRef.current = setTimeout(() => {
|
|
136
136
|
// If document is not focused then focus should remain
|
|
137
137
|
// inside the wrapped component and therefore we cancel
|
|
138
138
|
// this blur event thereby leaving focus in place.
|
|
@@ -141,8 +141,8 @@ function useFocusOutside(onFocusOutside) {
|
|
|
141
141
|
event.preventDefault();
|
|
142
142
|
return;
|
|
143
143
|
}
|
|
144
|
-
if ('function' === typeof
|
|
145
|
-
|
|
144
|
+
if ('function' === typeof currentOnFocusOutsideRef.current) {
|
|
145
|
+
currentOnFocusOutsideRef.current(event);
|
|
146
146
|
}
|
|
147
147
|
}, 0);
|
|
148
148
|
}, []);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_element","require","INPUT_BUTTON_TYPES","isFocusNormalizedButton","eventTarget","window","HTMLElement","nodeName","includes","type","useFocusOutside","onFocusOutside","
|
|
1
|
+
{"version":3,"names":["_element","require","INPUT_BUTTON_TYPES","isFocusNormalizedButton","eventTarget","window","HTMLElement","nodeName","includes","type","useFocusOutside","onFocusOutside","currentOnFocusOutsideRef","useRef","useEffect","current","preventBlurCheckRef","blurCheckTimeoutIdRef","cancelBlurCheck","useCallback","clearTimeout","normalizeButtonFocus","event","target","isInteractionEnd","queueBlurCheck","persist","ignoreForRelatedTarget","getAttribute","relatedTarget","closest","setTimeout","document","hasFocus","preventDefault","onFocus","onMouseDown","onMouseUp","onTouchStart","onTouchEnd","onBlur"],"sources":["@wordpress/compose/src/hooks/use-focus-outside/index.ts"],"sourcesContent":["/**\n * WordPress dependencies\n */\nimport { useCallback, useEffect, useRef } from '@wordpress/element';\n\n/**\n * Input types which are classified as button types, for use in considering\n * whether element is a (focus-normalized) button.\n */\nconst INPUT_BUTTON_TYPES = [ 'button', 'submit' ];\n\n/**\n * List of HTML button elements subject to focus normalization\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus\n */\ntype FocusNormalizedButton =\n\t| HTMLButtonElement\n\t| HTMLLinkElement\n\t| HTMLInputElement;\n\n/**\n * Returns true if the given element is a button element subject to focus\n * normalization, or false otherwise.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus\n *\n * @param eventTarget The target from a mouse or touch event.\n *\n * @return Whether the element is a button element subject to focus normalization.\n */\nfunction isFocusNormalizedButton(\n\teventTarget: EventTarget\n): eventTarget is FocusNormalizedButton {\n\tif ( ! ( eventTarget instanceof window.HTMLElement ) ) {\n\t\treturn false;\n\t}\n\tswitch ( eventTarget.nodeName ) {\n\t\tcase 'A':\n\t\tcase 'BUTTON':\n\t\t\treturn true;\n\n\t\tcase 'INPUT':\n\t\t\treturn INPUT_BUTTON_TYPES.includes(\n\t\t\t\t( eventTarget as HTMLInputElement ).type\n\t\t\t);\n\t}\n\n\treturn false;\n}\n\ntype UseFocusOutsideReturn = {\n\tonFocus: React.FocusEventHandler;\n\tonMouseDown: React.MouseEventHandler;\n\tonMouseUp: React.MouseEventHandler;\n\tonTouchStart: React.TouchEventHandler;\n\tonTouchEnd: React.TouchEventHandler;\n\tonBlur: React.FocusEventHandler;\n};\n\n/**\n * A react hook that can be used to check whether focus has moved outside the\n * element the event handlers are bound to.\n *\n * @param onFocusOutside A callback triggered when focus moves outside\n * the element the event handlers are bound to.\n *\n * @return An object containing event handlers. Bind the event handlers to a\n * wrapping element element to capture when focus moves outside that element.\n */\nexport default function useFocusOutside(\n\tonFocusOutside: ( ( event: React.FocusEvent ) => void ) | undefined\n): UseFocusOutsideReturn {\n\tconst currentOnFocusOutsideRef = useRef( onFocusOutside );\n\tuseEffect( () => {\n\t\tcurrentOnFocusOutsideRef.current = onFocusOutside;\n\t}, [ onFocusOutside ] );\n\n\tconst preventBlurCheckRef = useRef( false );\n\n\tconst blurCheckTimeoutIdRef = useRef< number | undefined >();\n\n\t/**\n\t * Cancel a blur check timeout.\n\t */\n\tconst cancelBlurCheck = useCallback( () => {\n\t\tclearTimeout( blurCheckTimeoutIdRef.current );\n\t}, [] );\n\n\t// Cancel blur checks on unmount.\n\tuseEffect( () => {\n\t\treturn () => cancelBlurCheck();\n\t}, [] );\n\n\t// Cancel a blur check if the callback or ref is no longer provided.\n\tuseEffect( () => {\n\t\tif ( ! onFocusOutside ) {\n\t\t\tcancelBlurCheck();\n\t\t}\n\t}, [ onFocusOutside, cancelBlurCheck ] );\n\n\t/**\n\t * Handles a mousedown or mouseup event to respectively assign and\n\t * unassign a flag for preventing blur check on button elements. Some\n\t * browsers, namely Firefox and Safari, do not emit a focus event on\n\t * button elements when clicked, while others do. The logic here\n\t * intends to normalize this as treating click on buttons as focus.\n\t *\n\t * @param event\n\t * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus\n\t */\n\tconst normalizeButtonFocus: React.EventHandler<\n\t\tReact.MouseEvent | React.TouchEvent\n\t> = useCallback( ( event ) => {\n\t\tconst { type, target } = event;\n\t\tconst isInteractionEnd = [ 'mouseup', 'touchend' ].includes( type );\n\n\t\tif ( isInteractionEnd ) {\n\t\t\tpreventBlurCheckRef.current = false;\n\t\t} else if ( isFocusNormalizedButton( target ) ) {\n\t\t\tpreventBlurCheckRef.current = true;\n\t\t}\n\t}, [] );\n\n\t/**\n\t * A callback triggered when a blur event occurs on the element the handler\n\t * is bound to.\n\t *\n\t * Calls the `onFocusOutside` callback in an immediate timeout if focus has\n\t * move outside the bound element and is still within the document.\n\t */\n\tconst queueBlurCheck: React.FocusEventHandler = useCallback( ( event ) => {\n\t\t// React does not allow using an event reference asynchronously\n\t\t// due to recycling behavior, except when explicitly persisted.\n\t\tevent.persist();\n\n\t\t// Skip blur check if clicking button. See `normalizeButtonFocus`.\n\t\tif ( preventBlurCheckRef.current ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// The usage of this attribute should be avoided. The only use case\n\t\t// would be when we load modals that are not React components and\n\t\t// therefore don't exist in the React tree. An example is opening\n\t\t// the Media Library modal from another dialog.\n\t\t// This attribute should contain a selector of the related target\n\t\t// we want to ignore, because we still need to trigger the blur event\n\t\t// on all other cases.\n\t\tconst ignoreForRelatedTarget = event.target.getAttribute(\n\t\t\t'data-unstable-ignore-focus-outside-for-relatedtarget'\n\t\t);\n\t\tif (\n\t\t\tignoreForRelatedTarget &&\n\t\t\tevent.relatedTarget?.closest( ignoreForRelatedTarget )\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tblurCheckTimeoutIdRef.current = setTimeout( () => {\n\t\t\t// If document is not focused then focus should remain\n\t\t\t// inside the wrapped component and therefore we cancel\n\t\t\t// this blur event thereby leaving focus in place.\n\t\t\t// https://developer.mozilla.org/en-US/docs/Web/API/Document/hasFocus.\n\t\t\tif ( ! document.hasFocus() ) {\n\t\t\t\tevent.preventDefault();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif ( 'function' === typeof currentOnFocusOutsideRef.current ) {\n\t\t\t\tcurrentOnFocusOutsideRef.current( event );\n\t\t\t}\n\t\t}, 0 );\n\t}, [] );\n\n\treturn {\n\t\tonFocus: cancelBlurCheck,\n\t\tonMouseDown: normalizeButtonFocus,\n\t\tonMouseUp: normalizeButtonFocus,\n\t\tonTouchStart: normalizeButtonFocus,\n\t\tonTouchEnd: normalizeButtonFocus,\n\t\tonBlur: queueBlurCheck,\n\t};\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -46,9 +46,9 @@ shortcuts, callback, {
|
|
|
46
46
|
// This is important for performance considerations.
|
|
47
47
|
target
|
|
48
48
|
} = {}) {
|
|
49
|
-
const
|
|
49
|
+
const currentCallbackRef = (0, _element.useRef)(callback);
|
|
50
50
|
(0, _element.useEffect)(() => {
|
|
51
|
-
|
|
51
|
+
currentCallbackRef.current = callback;
|
|
52
52
|
}, [callback]);
|
|
53
53
|
(0, _element.useEffect)(() => {
|
|
54
54
|
if (isDisabled) {
|
|
@@ -78,7 +78,7 @@ shortcuts, callback, {
|
|
|
78
78
|
// @ts-ignore `bindGlobal` is an undocumented property
|
|
79
79
|
mousetrap[bindFn](shortcut, ( /* eslint-disable jsdoc/valid-types */
|
|
80
80
|
/** @type {[e: import('mousetrap').ExtendedKeyboardEvent, combo: string]} */...args) => /* eslint-enable jsdoc/valid-types */
|
|
81
|
-
|
|
81
|
+
currentCallbackRef.current(...args), eventName);
|
|
82
82
|
});
|
|
83
83
|
return () => {
|
|
84
84
|
mousetrap.reset();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_mousetrap","_interopRequireDefault","require","_element","_keycodes","useKeyboardShortcut","shortcuts","callback","bindGlobal","eventName","isDisabled","target","
|
|
1
|
+
{"version":3,"names":["_mousetrap","_interopRequireDefault","require","_element","_keycodes","useKeyboardShortcut","shortcuts","callback","bindGlobal","eventName","isDisabled","target","currentCallbackRef","useRef","useEffect","current","mousetrap","Mousetrap","document","shortcutsArray","Array","isArray","forEach","shortcut","keys","split","modifiers","Set","filter","value","length","hasAlt","has","hasShift","isAppleOS","size","Error","bindFn","args","reset","_default","exports","default"],"sources":["@wordpress/compose/src/hooks/use-keyboard-shortcut/index.js"],"sourcesContent":["/**\n * External dependencies\n */\nimport Mousetrap from 'mousetrap';\nimport 'mousetrap/plugins/global-bind/mousetrap-global-bind';\n\n/**\n * WordPress dependencies\n */\nimport { useEffect, useRef } from '@wordpress/element';\nimport { isAppleOS } from '@wordpress/keycodes';\n\n/**\n * A block selection object.\n *\n * @typedef {Object} WPKeyboardShortcutConfig\n *\n * @property {boolean} [bindGlobal] Handle keyboard events anywhere including inside textarea/input fields.\n * @property {string} [eventName] Event name used to trigger the handler, defaults to keydown.\n * @property {boolean} [isDisabled] Disables the keyboard handler if the value is true.\n * @property {import('react').RefObject<HTMLElement>} [target] React reference to the DOM element used to catch the keyboard event.\n */\n\n/* eslint-disable jsdoc/valid-types */\n/**\n * Attach a keyboard shortcut handler.\n *\n * @see https://craig.is/killing/mice#api.bind for information about the `callback` parameter.\n *\n * @param {string[]|string} shortcuts Keyboard Shortcuts.\n * @param {(e: import('mousetrap').ExtendedKeyboardEvent, combo: string) => void} callback Shortcut callback.\n * @param {WPKeyboardShortcutConfig} options Shortcut options.\n */\nfunction useKeyboardShortcut(\n\t/* eslint-enable jsdoc/valid-types */\n\tshortcuts,\n\tcallback,\n\t{\n\t\tbindGlobal = false,\n\t\teventName = 'keydown',\n\t\tisDisabled = false, // This is important for performance considerations.\n\t\ttarget,\n\t} = {}\n) {\n\tconst currentCallbackRef = useRef( callback );\n\tuseEffect( () => {\n\t\tcurrentCallbackRef.current = callback;\n\t}, [ callback ] );\n\n\tuseEffect( () => {\n\t\tif ( isDisabled ) {\n\t\t\treturn;\n\t\t}\n\t\tconst mousetrap = new Mousetrap(\n\t\t\ttarget && target.current\n\t\t\t\t? target.current\n\t\t\t\t: // We were passing `document` here previously, so to successfully cast it to Element we must cast it first to `unknown`.\n\t\t\t\t // Not sure if this is a mistake but it was the behavior previous to the addition of types so we're just doing what's\n\t\t\t\t // necessary to maintain the existing behavior.\n\t\t\t\t /** @type {Element} */ ( /** @type {unknown} */ ( document ) )\n\t\t);\n\t\tconst shortcutsArray = Array.isArray( shortcuts )\n\t\t\t? shortcuts\n\t\t\t: [ shortcuts ];\n\t\tshortcutsArray.forEach( ( shortcut ) => {\n\t\t\tconst keys = shortcut.split( '+' );\n\t\t\t// Determines whether a key is a modifier by the length of the string.\n\t\t\t// E.g. if I add a pass a shortcut Shift+Cmd+M, it'll determine that\n\t\t\t// the modifiers are Shift and Cmd because they're not a single character.\n\t\t\tconst modifiers = new Set(\n\t\t\t\tkeys.filter( ( value ) => value.length > 1 )\n\t\t\t);\n\t\t\tconst hasAlt = modifiers.has( 'alt' );\n\t\t\tconst hasShift = modifiers.has( 'shift' );\n\n\t\t\t// This should be better moved to the shortcut registration instead.\n\t\t\tif (\n\t\t\t\tisAppleOS() &&\n\t\t\t\t( ( modifiers.size === 1 && hasAlt ) ||\n\t\t\t\t\t( modifiers.size === 2 && hasAlt && hasShift ) )\n\t\t\t) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Cannot bind ${ shortcut }. Alt and Shift+Alt modifiers are reserved for character input.`\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst bindFn = bindGlobal ? 'bindGlobal' : 'bind';\n\t\t\t// @ts-ignore `bindGlobal` is an undocumented property\n\t\t\tmousetrap[ bindFn ](\n\t\t\t\tshortcut,\n\t\t\t\t(\n\t\t\t\t\t/* eslint-disable jsdoc/valid-types */\n\t\t\t\t\t/** @type {[e: import('mousetrap').ExtendedKeyboardEvent, combo: string]} */ ...args\n\t\t\t\t) =>\n\t\t\t\t\t/* eslint-enable jsdoc/valid-types */\n\t\t\t\t\tcurrentCallbackRef.current( ...args ),\n\t\t\t\teventName\n\t\t\t);\n\t\t} );\n\n\t\treturn () => {\n\t\t\tmousetrap.reset();\n\t\t};\n\t}, [ shortcuts, bindGlobal, eventName, target, isDisabled ] );\n}\n\nexport default useKeyboardShortcut;\n"],"mappings":";;;;;;;AAGA,IAAAA,UAAA,GAAAC,sBAAA,CAAAC,OAAA;AACAA,OAAA;AAKA,IAAAC,QAAA,GAAAD,OAAA;AACA,IAAAE,SAAA,GAAAF,OAAA;AAVA;AACA;AACA;;AAIA;AACA;AACA;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASG,mBAAmBA,CAAA,CAC3B;AACAC,SAAS,EACTC,QAAQ,EACR;EACCC,UAAU,GAAG,KAAK;EAClBC,SAAS,GAAG,SAAS;EACrBC,UAAU,GAAG,KAAK;EAAE;EACpBC;AACD,CAAC,GAAG,CAAC,CAAC,EACL;EACD,MAAMC,kBAAkB,GAAG,IAAAC,eAAM,EAAEN,QAAS,CAAC;EAC7C,IAAAO,kBAAS,EAAE,MAAM;IAChBF,kBAAkB,CAACG,OAAO,GAAGR,QAAQ;EACtC,CAAC,EAAE,CAAEA,QAAQ,CAAG,CAAC;EAEjB,IAAAO,kBAAS,EAAE,MAAM;IAChB,IAAKJ,UAAU,EAAG;MACjB;IACD;IACA,MAAMM,SAAS,GAAG,IAAIC,kBAAS,CAC9BN,MAAM,IAAIA,MAAM,CAACI,OAAO,GACrBJ,MAAM,CAACI,OAAO;IACd;IACA;IACA;IACA,uBAAyB;IAAyBG,QACtD,CAAC;IACD,MAAMC,cAAc,GAAGC,KAAK,CAACC,OAAO,CAAEf,SAAU,CAAC,GAC9CA,SAAS,GACT,CAAEA,SAAS,CAAE;IAChBa,cAAc,CAACG,OAAO,CAAIC,QAAQ,IAAM;MACvC,MAAMC,IAAI,GAAGD,QAAQ,CAACE,KAAK,CAAE,GAAI,CAAC;MAClC;MACA;MACA;MACA,MAAMC,SAAS,GAAG,IAAIC,GAAG,CACxBH,IAAI,CAACI,MAAM,CAAIC,KAAK,IAAMA,KAAK,CAACC,MAAM,GAAG,CAAE,CAC5C,CAAC;MACD,MAAMC,MAAM,GAAGL,SAAS,CAACM,GAAG,CAAE,KAAM,CAAC;MACrC,MAAMC,QAAQ,GAAGP,SAAS,CAACM,GAAG,CAAE,OAAQ,CAAC;;MAEzC;MACA,IACC,IAAAE,mBAAS,EAAC,CAAC,KACPR,SAAS,CAACS,IAAI,KAAK,CAAC,IAAIJ,MAAM,IAC/BL,SAAS,CAACS,IAAI,KAAK,CAAC,IAAIJ,MAAM,IAAIE,QAAU,CAAE,EAChD;QACD,MAAM,IAAIG,KAAK,CACb,eAAeb,QAAU,iEAC3B,CAAC;MACF;MAEA,MAAMc,MAAM,GAAG7B,UAAU,GAAG,YAAY,GAAG,MAAM;MACjD;MACAQ,SAAS,CAAEqB,MAAM,CAAE,CAClBd,QAAQ,EACR,EACC;MACA,4EAA6E,GAAGe,IAAI,KAEpF;MACA1B,kBAAkB,CAACG,OAAO,CAAE,GAAGuB,IAAK,CAAC,EACtC7B,SACD,CAAC;IACF,CAAE,CAAC;IAEH,OAAO,MAAM;MACZO,SAAS,CAACuB,KAAK,CAAC,CAAC;IAClB,CAAC;EACF,CAAC,EAAE,CAAEjC,SAAS,EAAEE,UAAU,EAAEC,SAAS,EAAEE,MAAM,EAAED,UAAU,CAAG,CAAC;AAC9D;AAAC,IAAA8B,QAAA,GAAAC,OAAA,CAAAC,OAAA,GAEcrC,mBAAmB","ignoreList":[]}
|
|
@@ -76,38 +76,38 @@ function assignRef(ref, value) {
|
|
|
76
76
|
*/
|
|
77
77
|
function useMergeRefs(refs) {
|
|
78
78
|
const element = (0, _element.useRef)();
|
|
79
|
-
const
|
|
80
|
-
const
|
|
79
|
+
const isAttachedRef = (0, _element.useRef)(false);
|
|
80
|
+
const didElementChangeRef = (0, _element.useRef)(false);
|
|
81
81
|
/* eslint-disable jsdoc/no-undefined-types */
|
|
82
82
|
/** @type {import('react').MutableRefObject<TRef[]>} */
|
|
83
83
|
/* eslint-enable jsdoc/no-undefined-types */
|
|
84
|
-
const
|
|
85
|
-
const
|
|
84
|
+
const previousRefsRef = (0, _element.useRef)([]);
|
|
85
|
+
const currentRefsRef = (0, _element.useRef)(refs);
|
|
86
86
|
|
|
87
87
|
// Update on render before the ref callback is called, so the ref callback
|
|
88
88
|
// always has access to the current refs.
|
|
89
|
-
|
|
89
|
+
currentRefsRef.current = refs;
|
|
90
90
|
|
|
91
91
|
// If any of the refs change, call the previous ref with `null` and the new
|
|
92
92
|
// ref with the node, except when the element changes in the same cycle, in
|
|
93
93
|
// which case the ref callbacks will already have been called.
|
|
94
94
|
(0, _element.useLayoutEffect)(() => {
|
|
95
|
-
if (
|
|
95
|
+
if (didElementChangeRef.current === false && isAttachedRef.current === true) {
|
|
96
96
|
refs.forEach((ref, index) => {
|
|
97
|
-
const previousRef =
|
|
97
|
+
const previousRef = previousRefsRef.current[index];
|
|
98
98
|
if (ref !== previousRef) {
|
|
99
99
|
assignRef(previousRef, null);
|
|
100
100
|
assignRef(ref, element.current);
|
|
101
101
|
}
|
|
102
102
|
});
|
|
103
103
|
}
|
|
104
|
-
|
|
104
|
+
previousRefsRef.current = refs;
|
|
105
105
|
}, refs);
|
|
106
106
|
|
|
107
107
|
// No dependencies, must be reset after every render so ref callbacks are
|
|
108
108
|
// correctly called after a ref change.
|
|
109
109
|
(0, _element.useLayoutEffect)(() => {
|
|
110
|
-
|
|
110
|
+
didElementChangeRef.current = false;
|
|
111
111
|
});
|
|
112
112
|
|
|
113
113
|
// There should be no dependencies so that `callback` is only called when
|
|
@@ -116,12 +116,12 @@ function useMergeRefs(refs) {
|
|
|
116
116
|
// Update the element so it can be used when calling ref callbacks on a
|
|
117
117
|
// dependency change.
|
|
118
118
|
assignRef(element, value);
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
didElementChangeRef.current = true;
|
|
120
|
+
isAttachedRef.current = value !== null;
|
|
121
121
|
|
|
122
122
|
// When an element changes, the current ref callback should be called
|
|
123
123
|
// with the new element and the previous one with `null`.
|
|
124
|
-
const refsToAssign = value ?
|
|
124
|
+
const refsToAssign = value ? currentRefsRef.current : previousRefsRef.current;
|
|
125
125
|
|
|
126
126
|
// Update the latest refs.
|
|
127
127
|
for (const ref of refsToAssign) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_element","require","assignRef","ref","value","hasOwnProperty","current","useMergeRefs","refs","element","useRef","
|
|
1
|
+
{"version":3,"names":["_element","require","assignRef","ref","value","hasOwnProperty","current","useMergeRefs","refs","element","useRef","isAttachedRef","didElementChangeRef","previousRefsRef","currentRefsRef","useLayoutEffect","forEach","index","previousRef","useCallback","refsToAssign"],"sources":["@wordpress/compose/src/hooks/use-merge-refs/index.js"],"sourcesContent":["/**\n * WordPress dependencies\n */\nimport { useRef, useCallback, useLayoutEffect } from '@wordpress/element';\n\n/* eslint-disable jsdoc/valid-types */\n/**\n * @template T\n * @typedef {T extends import('react').Ref<infer R> ? R : never} TypeFromRef\n */\n/* eslint-enable jsdoc/valid-types */\n\n/**\n * @template T\n * @param {import('react').Ref<T>} ref\n * @param {T} value\n */\nfunction assignRef( ref, value ) {\n\tif ( typeof ref === 'function' ) {\n\t\tref( value );\n\t} else if ( ref && ref.hasOwnProperty( 'current' ) ) {\n\t\t/* eslint-disable jsdoc/no-undefined-types */\n\t\t/** @type {import('react').MutableRefObject<T>} */ ( ref ).current =\n\t\t\tvalue;\n\t\t/* eslint-enable jsdoc/no-undefined-types */\n\t}\n}\n\n/**\n * Merges refs into one ref callback.\n *\n * It also ensures that the merged ref callbacks are only called when they\n * change (as a result of a `useCallback` dependency update) OR when the ref\n * value changes, just as React does when passing a single ref callback to the\n * component.\n *\n * As expected, if you pass a new function on every render, the ref callback\n * will be called after every render.\n *\n * If you don't wish a ref callback to be called after every render, wrap it\n * with `useCallback( callback, dependencies )`. When a dependency changes, the\n * old ref callback will be called with `null` and the new ref callback will be\n * called with the same value.\n *\n * To make ref callbacks easier to use, you can also pass the result of\n * `useRefEffect`, which makes cleanup easier by allowing you to return a\n * cleanup function instead of handling `null`.\n *\n * It's also possible to _disable_ a ref (and its behaviour) by simply not\n * passing the ref.\n *\n * ```jsx\n * const ref = useRefEffect( ( node ) => {\n * node.addEventListener( ... );\n * return () => {\n * node.removeEventListener( ... );\n * };\n * }, [ ...dependencies ] );\n * const otherRef = useRef();\n * const mergedRefs useMergeRefs( [\n * enabled && ref,\n * otherRef,\n * ] );\n * return <div ref={ mergedRefs } />;\n * ```\n *\n * @template {import('react').Ref<any>} TRef\n * @param {Array<TRef>} refs The refs to be merged.\n *\n * @return {import('react').RefCallback<TypeFromRef<TRef>>} The merged ref callback.\n */\nexport default function useMergeRefs( refs ) {\n\tconst element = useRef();\n\tconst isAttachedRef = useRef( false );\n\tconst didElementChangeRef = useRef( false );\n\t/* eslint-disable jsdoc/no-undefined-types */\n\t/** @type {import('react').MutableRefObject<TRef[]>} */\n\t/* eslint-enable jsdoc/no-undefined-types */\n\tconst previousRefsRef = useRef( [] );\n\tconst currentRefsRef = useRef( refs );\n\n\t// Update on render before the ref callback is called, so the ref callback\n\t// always has access to the current refs.\n\tcurrentRefsRef.current = refs;\n\n\t// If any of the refs change, call the previous ref with `null` and the new\n\t// ref with the node, except when the element changes in the same cycle, in\n\t// which case the ref callbacks will already have been called.\n\tuseLayoutEffect( () => {\n\t\tif (\n\t\t\tdidElementChangeRef.current === false &&\n\t\t\tisAttachedRef.current === true\n\t\t) {\n\t\t\trefs.forEach( ( ref, index ) => {\n\t\t\t\tconst previousRef = previousRefsRef.current[ index ];\n\t\t\t\tif ( ref !== previousRef ) {\n\t\t\t\t\tassignRef( previousRef, null );\n\t\t\t\t\tassignRef( ref, element.current );\n\t\t\t\t}\n\t\t\t} );\n\t\t}\n\n\t\tpreviousRefsRef.current = refs;\n\t}, refs );\n\n\t// No dependencies, must be reset after every render so ref callbacks are\n\t// correctly called after a ref change.\n\tuseLayoutEffect( () => {\n\t\tdidElementChangeRef.current = false;\n\t} );\n\n\t// There should be no dependencies so that `callback` is only called when\n\t// the node changes.\n\treturn useCallback( ( value ) => {\n\t\t// Update the element so it can be used when calling ref callbacks on a\n\t\t// dependency change.\n\t\tassignRef( element, value );\n\n\t\tdidElementChangeRef.current = true;\n\t\tisAttachedRef.current = value !== null;\n\n\t\t// When an element changes, the current ref callback should be called\n\t\t// with the new element and the previous one with `null`.\n\t\tconst refsToAssign = value\n\t\t\t? currentRefsRef.current\n\t\t\t: previousRefsRef.current;\n\n\t\t// Update the latest refs.\n\t\tfor ( const ref of refsToAssign ) {\n\t\t\tassignRef( ref, value );\n\t\t}\n\t}, [] );\n}\n"],"mappings":";;;;;;AAGA,IAAAA,QAAA,GAAAC,OAAA;AAHA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,SAASC,SAASA,CAAEC,GAAG,EAAEC,KAAK,EAAG;EAChC,IAAK,OAAOD,GAAG,KAAK,UAAU,EAAG;IAChCA,GAAG,CAAEC,KAAM,CAAC;EACb,CAAC,MAAM,IAAKD,GAAG,IAAIA,GAAG,CAACE,cAAc,CAAE,SAAU,CAAC,EAAG;IACpD;IACA,kDAAqDF,GAAG,CAAGG,OAAO,GACjEF,KAAK;IACN;EACD;AACD;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACe,SAASG,YAAYA,CAAEC,IAAI,EAAG;EAC5C,MAAMC,OAAO,GAAG,IAAAC,eAAM,EAAC,CAAC;EACxB,MAAMC,aAAa,GAAG,IAAAD,eAAM,EAAE,KAAM,CAAC;EACrC,MAAME,mBAAmB,GAAG,IAAAF,eAAM,EAAE,KAAM,CAAC;EAC3C;EACA;EACA;EACA,MAAMG,eAAe,GAAG,IAAAH,eAAM,EAAE,EAAG,CAAC;EACpC,MAAMI,cAAc,GAAG,IAAAJ,eAAM,EAAEF,IAAK,CAAC;;EAErC;EACA;EACAM,cAAc,CAACR,OAAO,GAAGE,IAAI;;EAE7B;EACA;EACA;EACA,IAAAO,wBAAe,EAAE,MAAM;IACtB,IACCH,mBAAmB,CAACN,OAAO,KAAK,KAAK,IACrCK,aAAa,CAACL,OAAO,KAAK,IAAI,EAC7B;MACDE,IAAI,CAACQ,OAAO,CAAE,CAAEb,GAAG,EAAEc,KAAK,KAAM;QAC/B,MAAMC,WAAW,GAAGL,eAAe,CAACP,OAAO,CAAEW,KAAK,CAAE;QACpD,IAAKd,GAAG,KAAKe,WAAW,EAAG;UAC1BhB,SAAS,CAAEgB,WAAW,EAAE,IAAK,CAAC;UAC9BhB,SAAS,CAAEC,GAAG,EAAEM,OAAO,CAACH,OAAQ,CAAC;QAClC;MACD,CAAE,CAAC;IACJ;IAEAO,eAAe,CAACP,OAAO,GAAGE,IAAI;EAC/B,CAAC,EAAEA,IAAK,CAAC;;EAET;EACA;EACA,IAAAO,wBAAe,EAAE,MAAM;IACtBH,mBAAmB,CAACN,OAAO,GAAG,KAAK;EACpC,CAAE,CAAC;;EAEH;EACA;EACA,OAAO,IAAAa,oBAAW,EAAIf,KAAK,IAAM;IAChC;IACA;IACAF,SAAS,CAAEO,OAAO,EAAEL,KAAM,CAAC;IAE3BQ,mBAAmB,CAACN,OAAO,GAAG,IAAI;IAClCK,aAAa,CAACL,OAAO,GAAGF,KAAK,KAAK,IAAI;;IAEtC;IACA;IACA,MAAMgB,YAAY,GAAGhB,KAAK,GACvBU,cAAc,CAACR,OAAO,GACtBO,eAAe,CAACP,OAAO;;IAE1B;IACA,KAAM,MAAMH,GAAG,IAAIiB,YAAY,EAAG;MACjClB,SAAS,CAAEC,GAAG,EAAEC,KAAM,CAAC;IACxB;EACD,CAAC,EAAE,EAAG,CAAC;AACR","ignoreList":[]}
|
|
@@ -33,12 +33,12 @@ var _element = require("@wordpress/element");
|
|
|
33
33
|
* @return Ref callback.
|
|
34
34
|
*/
|
|
35
35
|
function useRefEffect(callback, dependencies) {
|
|
36
|
-
const
|
|
36
|
+
const cleanupRef = (0, _element.useRef)();
|
|
37
37
|
return (0, _element.useCallback)(node => {
|
|
38
38
|
if (node) {
|
|
39
|
-
|
|
40
|
-
} else if (
|
|
41
|
-
|
|
39
|
+
cleanupRef.current = callback(node);
|
|
40
|
+
} else if (cleanupRef.current) {
|
|
41
|
+
cleanupRef.current();
|
|
42
42
|
}
|
|
43
43
|
}, dependencies);
|
|
44
44
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_element","require","useRefEffect","callback","dependencies","
|
|
1
|
+
{"version":3,"names":["_element","require","useRefEffect","callback","dependencies","cleanupRef","useRef","useCallback","node","current"],"sources":["@wordpress/compose/src/hooks/use-ref-effect/index.ts"],"sourcesContent":["/**\n * External dependencies\n */\nimport type { DependencyList, RefCallback } from 'react';\n\n/**\n * WordPress dependencies\n */\nimport { useCallback, useRef } from '@wordpress/element';\n\n/**\n * Effect-like ref callback. Just like with `useEffect`, this allows you to\n * return a cleanup function to be run if the ref changes or one of the\n * dependencies changes. The ref is provided as an argument to the callback\n * functions. The main difference between this and `useEffect` is that\n * the `useEffect` callback is not called when the ref changes, but this is.\n * Pass the returned ref callback as the component's ref and merge multiple refs\n * with `useMergeRefs`.\n *\n * It's worth noting that if the dependencies array is empty, there's not\n * strictly a need to clean up event handlers for example, because the node is\n * to be removed. It *is* necessary if you add dependencies because the ref\n * callback will be called multiple times for the same node.\n *\n * @param callback Callback with ref as argument.\n * @param dependencies Dependencies of the callback.\n *\n * @return Ref callback.\n */\nexport default function useRefEffect< TElement = Node >(\n\tcallback: ( node: TElement ) => ( () => void ) | void,\n\tdependencies: DependencyList\n): RefCallback< TElement | null > {\n\tconst cleanupRef = useRef< ( () => void ) | void >();\n\treturn useCallback( ( node: TElement | null ) => {\n\t\tif ( node ) {\n\t\t\tcleanupRef.current = callback( node );\n\t\t} else if ( cleanupRef.current ) {\n\t\t\tcleanupRef.current();\n\t\t}\n\t}, dependencies );\n}\n"],"mappings":";;;;;;AAQA,IAAAA,QAAA,GAAAC,OAAA;AARA;AACA;AACA;;AAGA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACe,SAASC,YAAYA,CACnCC,QAAqD,EACrDC,YAA4B,EACK;EACjC,MAAMC,UAAU,GAAG,IAAAC,eAAM,EAA0B,CAAC;EACpD,OAAO,IAAAC,oBAAW,EAAIC,IAAqB,IAAM;IAChD,IAAKA,IAAI,EAAG;MACXH,UAAU,CAACI,OAAO,GAAGN,QAAQ,CAAEK,IAAK,CAAC;IACtC,CAAC,MAAM,IAAKH,UAAU,CAACI,OAAO,EAAG;MAChCJ,UAAU,CAACI,OAAO,CAAC,CAAC;IACrB;EACD,CAAC,EAAEL,YAAa,CAAC;AAClB","ignoreList":[]}
|