UNPKG

@wordpress/compose

Version:
8 lines (7 loc) 6.82 kB
{ "version": 3, "sources": ["../../../src/hooks/use-merge-refs/index.ts"], "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { useRef, useCallback, useLayoutEffect } from '@wordpress/element';\nimport type { MutableRefObject, Ref, RefCallback } from 'react';\n\n// Returns a cleanup function if the ref callback returned one (React 19 ref\n// callback cleanup pattern), otherwise `undefined`. Object refs never have a\n// cleanup and only set `.current`.\nfunction assignRef< T >( ref: Ref< T >, value: T ): ( () => void ) | undefined {\n\tif ( typeof ref === 'function' ) {\n\t\tconst returned = ref( value );\n\t\treturn typeof returned === 'function' ? returned : undefined;\n\t} else if ( ref && ref.hasOwnProperty( 'current' ) ) {\n\t\t( ref as MutableRefObject< T > ).current = value;\n\t}\n\treturn undefined;\n}\n\n// Tear down a ref at the given index: prefer the stored cleanup; otherwise\n// fall back to calling the ref with `null`.\nfunction detachRef< T >(\n\tref: Ref< T >,\n\tindex: number,\n\tcleanups: Array< ( () => void ) | undefined >\n): void {\n\tconst cleanup = cleanups[ index ];\n\tif ( cleanup ) {\n\t\tcleanups[ index ] = undefined;\n\t\tcleanup();\n\t} else {\n\t\tassignRef( ref, null );\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 * Inner ref callbacks may return a cleanup function (React 19's ref callback\n * cleanup pattern). When a ref callback returns a function, that function is\n * invoked at teardown (node change, dependency change, or unmount) **instead\n * of** the callback being called with `null`. Callbacks that do not return a\n * cleanup continue to receive `null` on teardown as before.\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 = useCallback( ( 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 * @param refs The refs to be merged.\n * @return The merged ref callback.\n */\nexport default function useMergeRefs< T >(\n\trefs: Ref< T >[]\n): RefCallback< T > {\n\tconst elementRef = useRef< T | null >( null );\n\t// The refs that are attached to the element: set when the element\n\t// attaches, and kept in sync when a changed ref is swapped on render.\n\tconst attachedRefsRef = useRef< Ref< T >[] >( [] );\n\tconst currentRefsRef = useRef( refs );\n\t// Position-indexed cleanups returned by inner ref callbacks. A slot is\n\t// `undefined` when the ref at that position did not return a cleanup (or\n\t// is an object ref / disabled).\n\tconst cleanupsRef = useRef< Array< ( () => void ) | undefined > >( [] );\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 attached ref with `null` and the\n\t// new ref with the node. Comparing against the refs that are attached to\n\t// the element, rather than the previous render's refs, also covers an\n\t// element that attaches outside a render of this component (a merged ref\n\t// passed to a child that mounts the element in its own commit): the\n\t// change is still detected on the next render. When the element attached\n\t// during this commit, the ref callback has already been called with the\n\t// current refs, which compare equal here.\n\tuseLayoutEffect( () => {\n\t\tconst element = elementRef.current;\n\n\t\tif ( element === null ) {\n\t\t\treturn;\n\t\t}\n\n\t\trefs.forEach( ( ref, index ) => {\n\t\t\tconst attachedRef = attachedRefsRef.current[ index ];\n\t\t\tif ( ref !== attachedRef ) {\n\t\t\t\tdetachRef( attachedRef, index, cleanupsRef.current );\n\t\t\t\tcleanupsRef.current[ index ] = assignRef( ref, element );\n\t\t\t}\n\t\t} );\n\n\t\tattachedRefsRef.current = refs;\n\t}, refs );\n\n\t// There should be no dependencies so that `callback` is only called when\n\t// the node changes.\n\treturn useCallback( ( value: T | null ) => {\n\t\t// Update the element so it can be used when calling ref callbacks on a\n\t\t// dependency change.\n\t\telementRef.current = value;\n\n\t\t// When an element changes, the current ref callback should be called\n\t\t// with the new element and the attached one with `null`.\n\t\tif ( value === null ) {\n\t\t\tattachedRefsRef.current.forEach( ( ref, index ) => {\n\t\t\t\tdetachRef( ref, index, cleanupsRef.current );\n\t\t\t} );\n\t\t\tattachedRefsRef.current = [];\n\t\t} else {\n\t\t\tattachedRefsRef.current = currentRefsRef.current;\n\t\t\tattachedRefsRef.current.forEach( ( ref, index ) => {\n\t\t\t\tcleanupsRef.current[ index ] = assignRef( ref, value );\n\t\t\t} );\n\t\t}\n\t}, [] );\n}\n"], "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,qBAAqD;AAMrD,SAAS,UAAgB,KAAe,OAAuC;AAC9E,MAAK,OAAO,QAAQ,YAAa;AAChC,UAAM,WAAW,IAAK,KAAM;AAC5B,WAAO,OAAO,aAAa,aAAa,WAAW;AAAA,EACpD,WAAY,OAAO,IAAI,eAAgB,SAAU,GAAI;AACpD,IAAE,IAA+B,UAAU;AAAA,EAC5C;AACA,SAAO;AACR;AAIA,SAAS,UACR,KACA,OACA,UACO;AACP,QAAM,UAAU,SAAU,KAAM;AAChC,MAAK,SAAU;AACd,aAAU,KAAM,IAAI;AACpB,YAAQ;AAAA,EACT,OAAO;AACN,cAAW,KAAK,IAAK;AAAA,EACtB;AACD;AA6Ce,SAAR,aACN,MACmB;AACnB,QAAM,iBAAa,uBAAoB,IAAK;AAG5C,QAAM,sBAAkB,uBAAsB,CAAC,CAAE;AACjD,QAAM,qBAAiB,uBAAQ,IAAK;AAIpC,QAAM,kBAAc,uBAA+C,CAAC,CAAE;AAItE,iBAAe,UAAU;AAUzB,sCAAiB,MAAM;AACtB,UAAM,UAAU,WAAW;AAE3B,QAAK,YAAY,MAAO;AACvB;AAAA,IACD;AAEA,SAAK,QAAS,CAAE,KAAK,UAAW;AAC/B,YAAM,cAAc,gBAAgB,QAAS,KAAM;AACnD,UAAK,QAAQ,aAAc;AAC1B,kBAAW,aAAa,OAAO,YAAY,OAAQ;AACnD,oBAAY,QAAS,KAAM,IAAI,UAAW,KAAK,OAAQ;AAAA,MACxD;AAAA,IACD,CAAE;AAEF,oBAAgB,UAAU;AAAA,EAC3B,GAAG,IAAK;AAIR,aAAO,4BAAa,CAAE,UAAqB;AAG1C,eAAW,UAAU;AAIrB,QAAK,UAAU,MAAO;AACrB,sBAAgB,QAAQ,QAAS,CAAE,KAAK,UAAW;AAClD,kBAAW,KAAK,OAAO,YAAY,OAAQ;AAAA,MAC5C,CAAE;AACF,sBAAgB,UAAU,CAAC;AAAA,IAC5B,OAAO;AACN,sBAAgB,UAAU,eAAe;AACzC,sBAAgB,QAAQ,QAAS,CAAE,KAAK,UAAW;AAClD,oBAAY,QAAS,KAAM,IAAI,UAAW,KAAK,KAAM;AAAA,MACtD,CAAE;AAAA,IACH;AAAA,EACD,GAAG,CAAC,CAAE;AACP;", "names": [] }