@wordpress/compose 8.7.2-next.v.202609031004.0 → 8.8.2-next.v.202609171837.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +11 -1
  2. package/README.md +90 -90
  3. package/build/hooks/use-fixed-window-list/index.cjs.map +2 -2
  4. package/build/hooks/use-focus-outside/index.cjs +1 -0
  5. package/build/hooks/use-focus-outside/index.cjs.map +2 -2
  6. package/build/hooks/use-keyboard-shortcut/index.cjs.map +2 -2
  7. package/build/hooks/use-resize-observer/use-resize-observer.cjs +9 -0
  8. package/build/hooks/use-resize-observer/use-resize-observer.cjs.map +2 -2
  9. package/build-module/hooks/use-fixed-window-list/index.mjs.map +2 -2
  10. package/build-module/hooks/use-focus-outside/index.mjs +1 -0
  11. package/build-module/hooks/use-focus-outside/index.mjs.map +2 -2
  12. package/build-module/hooks/use-keyboard-shortcut/index.mjs.map +2 -2
  13. package/build-module/hooks/use-resize-observer/use-resize-observer.mjs +10 -1
  14. package/build-module/hooks/use-resize-observer/use-resize-observer.mjs.map +2 -2
  15. package/build-types/hooks/use-focus-outside/index.d.ts.map +1 -1
  16. package/build-types/hooks/use-resize-observer/use-resize-observer.d.ts.map +1 -1
  17. package/package.json +13 -13
  18. package/src/higher-order/pure/test/index.jsdom.test.jsx +1 -0
  19. package/src/higher-order/with-global-events/test/index.jsdom.test.jsx +18 -15
  20. package/src/higher-order/with-global-events/test/listener.jsdom.test.js +13 -4
  21. package/src/higher-order/with-instance-id/test/index.jsdom.test.jsx +1 -0
  22. package/src/higher-order/with-state/test/index.jsdom.test.jsx +1 -0
  23. package/src/hooks/use-copy-on-click/test/index.jsdom.test.tsx +18 -17
  24. package/src/hooks/use-copy-to-clipboard/test/index.jsdom.test.tsx +12 -11
  25. package/src/hooks/use-dialog/test/index.jsdom.test.tsx +14 -13
  26. package/src/hooks/use-disabled/test/index.jsdom.test.jsx +1 -0
  27. package/src/hooks/use-drop-zone/test/index.jsdom.test.jsx +1 -0
  28. package/src/hooks/use-fixed-window-list/index.ts +1 -1
  29. package/src/hooks/use-focus-outside/index.ts +3 -3
  30. package/src/hooks/use-focus-outside/test/index.jsdom.test.jsx +7 -8
  31. package/src/hooks/use-instance-id/test/index.jsdom.test.jsx +1 -0
  32. package/src/hooks/use-keyboard-shortcut/index.ts +3 -3
  33. package/src/hooks/use-media-query/test/{index.jsdom.test.jsx → index.browser.test.jsx} +23 -21
  34. package/src/hooks/use-merge-refs/test/index.jsdom.test.jsx +1 -0
  35. package/src/hooks/use-observable-value/test/index.jsdom.test.jsx +2 -1
  36. package/src/hooks/use-resize-observer/test/index.browser.test.ts +24 -0
  37. package/src/hooks/use-resize-observer/use-resize-observer.ts +13 -1
  38. package/src/hooks/use-state-with-history/test/index.jsdom.test.jsx +1 -0
  39. package/src/hooks/use-viewport-match/test/index.jsdom.test.jsx +2 -3
  40. package/src/utils/subscribe-delegated-listener/test/index.jsdom.test.js +17 -16
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/hooks/use-resize-observer/use-resize-observer.ts"],
4
- "sourcesContent": ["import { useRef } from '@wordpress/element';\nimport useEvent from '../use-event';\n\n// This is the current implementation of `useResizeObserver`.\n//\n// The legacy implementation is still supported for backwards compatibility.\n// This is achieved by overloading the exported function with both signatures,\n// and detecting which API is being used at runtime.\nexport function useResizeObserver< T extends HTMLElement >(\n\tcallback: ResizeObserverCallback,\n\tresizeObserverOptions: ResizeObserverOptions = {}\n): ( element?: T | null ) => void {\n\tconst callbackEvent = useEvent( callback );\n\n\tconst observedElementRef = useRef< T | null >( null );\n\tconst resizeObserverRef = useRef< ResizeObserver >( undefined );\n\treturn useEvent( ( element?: T | null ) => {\n\t\tif ( element === observedElementRef.current ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Set up `ResizeObserver`.\n\t\tresizeObserverRef.current ??= new ResizeObserver( callbackEvent );\n\t\tconst { current: resizeObserver } = resizeObserverRef;\n\n\t\t// Unobserve previous element.\n\t\tif ( observedElementRef.current ) {\n\t\t\tresizeObserver.unobserve( observedElementRef.current );\n\t\t}\n\n\t\t// Observe new element.\n\t\tobservedElementRef.current = element ?? null;\n\t\tif ( element ) {\n\t\t\tresizeObserver.observe( element, resizeObserverOptions );\n\t\t}\n\t} );\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAuB;AACvB,uBAAqB;AAOd,SAAS,kBACf,UACA,wBAA+C,CAAC,GACf;AACjC,QAAM,oBAAgB,iBAAAA,SAAU,QAAS;AAEzC,QAAM,yBAAqB,uBAAoB,IAAK;AACpD,QAAM,wBAAoB,uBAA0B,MAAU;AAC9D,aAAO,iBAAAA,SAAU,CAAE,YAAwB;AAC1C,QAAK,YAAY,mBAAmB,SAAU;AAC7C;AAAA,IACD;AAGA,sBAAkB,YAAY,IAAI,eAAgB,aAAc;AAChE,UAAM,EAAE,SAAS,eAAe,IAAI;AAGpC,QAAK,mBAAmB,SAAU;AACjC,qBAAe,UAAW,mBAAmB,OAAQ;AAAA,IACtD;AAGA,uBAAmB,UAAU,WAAW;AACxC,QAAK,SAAU;AACd,qBAAe,QAAS,SAAS,qBAAsB;AAAA,IACxD;AAAA,EACD,CAAE;AACH;",
4
+ "sourcesContent": ["import { useEffect, useRef } from '@wordpress/element';\nimport useEvent from '../use-event';\n\n// This is the current implementation of `useResizeObserver`.\n//\n// The legacy implementation is still supported for backwards compatibility.\n// This is achieved by overloading the exported function with both signatures,\n// and detecting which API is being used at runtime.\nexport function useResizeObserver< T extends HTMLElement >(\n\tcallback: ResizeObserverCallback,\n\tresizeObserverOptions: ResizeObserverOptions = {}\n): ( element?: T | null ) => void {\n\tconst callbackEvent = useEvent( callback );\n\n\tconst observedElementRef = useRef< T | null >( null );\n\tconst resizeObserverRef = useRef< ResizeObserver >( undefined );\n\tuseEffect( () => {\n\t\tif ( observedElementRef.current ) {\n\t\t\tresizeObserverRef.current?.observe(\n\t\t\t\tobservedElementRef.current,\n\t\t\t\tresizeObserverOptions\n\t\t\t);\n\t\t}\n\t\treturn () => resizeObserverRef.current?.disconnect();\n\t\t// This effect owns the observer lifecycle. The returned ref handles\n\t\t// element and option changes without recreating the observer.\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, [] );\n\treturn useEvent( ( element?: T | null ) => {\n\t\tif ( element === observedElementRef.current ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Set up `ResizeObserver`.\n\t\tresizeObserverRef.current ??= new ResizeObserver( callbackEvent );\n\t\tconst { current: resizeObserver } = resizeObserverRef;\n\n\t\t// Unobserve previous element.\n\t\tif ( observedElementRef.current ) {\n\t\t\tresizeObserver.unobserve( observedElementRef.current );\n\t\t}\n\n\t\t// Observe new element.\n\t\tobservedElementRef.current = element ?? null;\n\t\tif ( element ) {\n\t\t\tresizeObserver.observe( element, resizeObserverOptions );\n\t\t}\n\t} );\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAkC;AAClC,uBAAqB;AAOd,SAAS,kBACf,UACA,wBAA+C,CAAC,GACf;AACjC,QAAM,oBAAgB,iBAAAA,SAAU,QAAS;AAEzC,QAAM,yBAAqB,uBAAoB,IAAK;AACpD,QAAM,wBAAoB,uBAA0B,MAAU;AAC9D,gCAAW,MAAM;AAChB,QAAK,mBAAmB,SAAU;AACjC,wBAAkB,SAAS;AAAA,QAC1B,mBAAmB;AAAA,QACnB;AAAA,MACD;AAAA,IACD;AACA,WAAO,MAAM,kBAAkB,SAAS,WAAW;AAAA,EAIpD,GAAG,CAAC,CAAE;AACN,aAAO,iBAAAA,SAAU,CAAE,YAAwB;AAC1C,QAAK,YAAY,mBAAmB,SAAU;AAC7C;AAAA,IACD;AAGA,sBAAkB,YAAY,IAAI,eAAgB,aAAc;AAChE,UAAM,EAAE,SAAS,eAAe,IAAI;AAGpC,QAAK,mBAAmB,SAAU;AACjC,qBAAe,UAAW,mBAAmB,OAAQ;AAAA,IACtD;AAGA,uBAAmB,UAAU,WAAW;AACxC,QAAK,SAAU;AACd,qBAAe,QAAS,SAAS,qBAAsB;AAAA,IACxD;AAAA,EACD,CAAE;AACH;",
6
6
  "names": ["useEvent"]
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/hooks/use-fixed-window-list/index.ts"],
4
- "sourcesContent": ["import { useState, useLayoutEffect, useRef } from '@wordpress/element';\nimport { getScrollContainer } from '@wordpress/dom';\nimport { PAGEUP, PAGEDOWN, HOME, END } from '@wordpress/keycodes';\nimport useEvent from '../use-event';\n\nconst DEFAULT_INIT_WINDOW_SIZE = 30;\n\ninterface FixedWindowList {\n\t/** Items visible in the viewport, as of the last rendered window */\n\tvisibleItems: number;\n\t/** Start index of the window */\n\tstart: number;\n\t/** End index of the window */\n\tend: number;\n\t/** Returns true if item is in the window */\n\titemInView: ( index: number ) => boolean;\n}\n\ninterface FixedWindowListOptions {\n\t/** Renders windowOverscan number of items before and after the calculated visible window. */\n\twindowOverscan?: number;\n\t/** When false avoids calculating the window size */\n\tuseWindowing?: boolean;\n\t/** Initial window size to use on first render before we can calculate the window size. */\n\tinitWindowSize?: number;\n\t/** Used to recalculate the window size when the expanded state of a list changes. */\n\texpandedState?: any;\n}\n\n/**\n *\n * @param elementRef Used to find the closest scroll container that contains element.\n * @param itemHeight Fixed item height in pixels\n * @param totalItems Total items in list\n * @param [options] Options object\n * @return Array with the fixed window list and setter\n */\nexport default function useFixedWindowList(\n\telementRef: React.RefObject< HTMLElement >,\n\titemHeight: number,\n\ttotalItems: number,\n\toptions?: FixedWindowListOptions\n): [\n\tFixedWindowList,\n\tReact.Dispatch< React.SetStateAction< FixedWindowList > >,\n] {\n\tconst {\n\t\twindowOverscan,\n\t\tuseWindowing = true,\n\t\tinitWindowSize = DEFAULT_INIT_WINDOW_SIZE,\n\t\texpandedState,\n\t} = options ?? {};\n\n\tconst [ fixedListWindow, setFixedListWindow ] = useState< FixedWindowList >(\n\t\t{\n\t\t\tvisibleItems: initWindowSize,\n\t\t\tstart: 0,\n\t\t\tend: initWindowSize,\n\t\t\titemInView: ( index: number ) => {\n\t\t\t\treturn index >= 0 && index <= initWindowSize;\n\t\t\t},\n\t\t}\n\t);\n\n\t// Kept out of state so that measuring the list does not, on its own, force a\n\t// re-render. Only the rendered window does that.\n\tconst visibleItemsRef = useRef( initWindowSize );\n\n\t// The measuring effect re-runs whenever the list changes, so the effect\n\t// running can't on its own tell the very first measurement from later ones.\n\tconst isFirstMeasurementRef = useRef( true );\n\n\t// Stable identity, so the listeners below never have to be re-attached, and\n\t// reads the latest props on every call.\n\tconst measureWindow = useEvent( ( initRender?: boolean ) => {\n\t\tconst scrollContainer = getScrollContainer( elementRef.current );\n\t\tif ( ! scrollContainer ) {\n\t\t\treturn;\n\t\t}\n\t\tconst visibleItems = Math.ceil(\n\t\t\tscrollContainer.clientHeight / itemHeight\n\t\t);\n\t\tvisibleItemsRef.current = visibleItems;\n\t\tconst isFirstMeasurement = isFirstMeasurementRef.current;\n\t\tisFirstMeasurementRef.current = false;\n\t\t// Aim to keep opening list view fast, afterward we can optimize for scrolling.\n\t\tconst overscan = initRender\n\t\t\t? visibleItems\n\t\t\t: windowOverscan ?? visibleItems;\n\t\tconst firstViewableIndex = Math.floor(\n\t\t\tscrollContainer.scrollTop / itemHeight\n\t\t);\n\t\tconst start = Math.max( 0, firstViewableIndex - overscan );\n\t\tconst end = Math.min(\n\t\t\ttotalItems - 1,\n\t\t\tfirstViewableIndex + visibleItems + overscan\n\t\t);\n\t\tsetFixedListWindow( ( lastWindow ) => {\n\t\t\t// Rendering the window is the expensive part, and for the items in\n\t\t\t// `lastWindow` it is already paid for. When the next window has\n\t\t\t// nothing to add, keep the current one: a window that only shrinks\n\t\t\t// drops nodes that are known to be needed again and forces another\n\t\t\t// style recalculation, with nothing new to show for it.\n\t\t\tif ( lastWindow.start <= start && lastWindow.end >= end ) {\n\t\t\t\treturn lastWindow;\n\t\t\t}\n\t\t\t// The first window is rendered before the list can be measured, so\n\t\t\t// it is sized by `initWindowSize` rather than by the viewport, and\n\t\t\t// the measured window is normally wider than it by the overscan.\n\t\t\t// Nothing has painted yet though, so there is no scrolling for the\n\t\t\t// overscan to absorb: covering the visible items is enough, and\n\t\t\t// keeping that window saves a render pass while the list opens.\n\t\t\tif (\n\t\t\t\tisFirstMeasurement &&\n\t\t\t\tlastWindow.start <= firstViewableIndex &&\n\t\t\t\tlastWindow.end >= firstViewableIndex + visibleItems\n\t\t\t) {\n\t\t\t\treturn lastWindow;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tvisibleItems,\n\t\t\t\tstart,\n\t\t\t\tend,\n\t\t\t\titemInView: ( index: number ) => {\n\t\t\t\t\treturn start <= index && index <= end;\n\t\t\t\t},\n\t\t\t};\n\t\t} );\n\t} );\n\n\tconst handleKeyDown = useEvent(\n\t\t( event: KeyboardEvent, scrollContainer: Element ) => {\n\t\t\tswitch ( event.keyCode ) {\n\t\t\t\tcase HOME: {\n\t\t\t\t\treturn scrollContainer.scrollTo( { top: 0 } );\n\t\t\t\t}\n\t\t\t\tcase END: {\n\t\t\t\t\treturn scrollContainer.scrollTo( {\n\t\t\t\t\t\ttop: totalItems * itemHeight,\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t\tcase PAGEUP: {\n\t\t\t\t\treturn scrollContainer.scrollTo( {\n\t\t\t\t\t\ttop:\n\t\t\t\t\t\t\tscrollContainer.scrollTop -\n\t\t\t\t\t\t\tvisibleItemsRef.current * itemHeight,\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t\tcase PAGEDOWN: {\n\t\t\t\t\treturn scrollContainer.scrollTo( {\n\t\t\t\t\t\ttop:\n\t\t\t\t\t\t\tscrollContainer.scrollTop +\n\t\t\t\t\t\t\tvisibleItemsRef.current * itemHeight,\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t);\n\n\t// Measure whenever something that the window is derived from changes.\n\tuseLayoutEffect( () => {\n\t\tif ( ! useWindowing ) {\n\t\t\treturn;\n\t\t}\n\t\tmeasureWindow( true );\n\t}, [\n\t\tuseWindowing,\n\t\tmeasureWindow,\n\t\titemHeight,\n\t\ttotalItems,\n\t\twindowOverscan,\n\t\texpandedState,\n\t] );\n\n\t// Only ever attaches and detaches listeners. The deps are the things that\n\t// can change which element the scroll container is, or whether there is one\n\t// at all: a list that is too short to overflow has no scroll container to\n\t// attach to, and it grows into one by gaining items or by being expanded.\n\tuseLayoutEffect( () => {\n\t\tif ( ! useWindowing ) {\n\t\t\treturn;\n\t\t}\n\t\tconst scrollContainer = getScrollContainer( elementRef.current );\n\t\tif ( ! scrollContainer ) {\n\t\t\treturn;\n\t\t}\n\t\tconst { defaultView } = scrollContainer.ownerDocument;\n\t\t// `scroll` and `resize` already fire at about the rendering rate, so\n\t\t// there is nothing to gain from debouncing them.\n\t\tconst onMeasure = () => measureWindow();\n\t\tconst onKeyDown = ( event: KeyboardEvent ) =>\n\t\t\thandleKeyDown( event, scrollContainer );\n\n\t\tscrollContainer.addEventListener( 'scroll', onMeasure );\n\t\tdefaultView?.addEventListener( 'resize', onMeasure );\n\t\tdefaultView?.addEventListener( 'keydown', onKeyDown );\n\n\t\treturn () => {\n\t\t\tscrollContainer.removeEventListener( 'scroll', onMeasure );\n\t\t\tdefaultView?.removeEventListener( 'resize', onMeasure );\n\t\t\tdefaultView?.removeEventListener( 'keydown', onKeyDown );\n\t\t};\n\t}, [\n\t\tuseWindowing,\n\t\telementRef,\n\t\titemHeight,\n\t\ttotalItems,\n\t\texpandedState,\n\t\tmeasureWindow,\n\t\thandleKeyDown,\n\t] );\n\n\treturn [ fixedListWindow, setFixedListWindow ];\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { useState, useLayoutEffect, useRef } from '@wordpress/element';\nimport { getScrollContainer } from '@wordpress/dom';\nimport { PAGEUP, PAGEDOWN, HOME, END } from '@wordpress/keycodes';\nimport useEvent from '../use-event';\n\nconst DEFAULT_INIT_WINDOW_SIZE = 30;\n\ninterface FixedWindowList {\n\t/** Items visible in the viewport, as of the last rendered window */\n\tvisibleItems: number;\n\t/** Start index of the window */\n\tstart: number;\n\t/** End index of the window */\n\tend: number;\n\t/** Returns true if item is in the window */\n\titemInView: ( index: number ) => boolean;\n}\n\ninterface FixedWindowListOptions {\n\t/** Renders windowOverscan number of items before and after the calculated visible window. */\n\twindowOverscan?: number;\n\t/** When false avoids calculating the window size */\n\tuseWindowing?: boolean;\n\t/** Initial window size to use on first render before we can calculate the window size. */\n\tinitWindowSize?: number;\n\t/** Used to recalculate the window size when the expanded state of a list changes. */\n\texpandedState?: any;\n}\n\n/**\n *\n * @param elementRef Used to find the closest scroll container that contains element.\n * @param itemHeight Fixed item height in pixels\n * @param totalItems Total items in list\n * @param [options] Options object\n * @return Array with the fixed window list and setter\n */\nexport default function useFixedWindowList(\n\telementRef: React.RefObject< HTMLElement >,\n\titemHeight: number,\n\ttotalItems: number,\n\toptions?: FixedWindowListOptions\n): [\n\tFixedWindowList,\n\tReact.Dispatch< React.SetStateAction< FixedWindowList > >,\n] {\n\tconst {\n\t\twindowOverscan,\n\t\tuseWindowing = true,\n\t\tinitWindowSize = DEFAULT_INIT_WINDOW_SIZE,\n\t\texpandedState,\n\t} = options ?? {};\n\n\tconst [ fixedListWindow, setFixedListWindow ] = useState< FixedWindowList >(\n\t\t{\n\t\t\tvisibleItems: initWindowSize,\n\t\t\tstart: 0,\n\t\t\tend: initWindowSize,\n\t\t\titemInView: ( index: number ) => {\n\t\t\t\treturn index >= 0 && index <= initWindowSize;\n\t\t\t},\n\t\t}\n\t);\n\n\t// Kept out of state so that measuring the list does not, on its own, force a\n\t// re-render. Only the rendered window does that.\n\tconst visibleItemsRef = useRef( initWindowSize );\n\n\t// The measuring effect re-runs whenever the list changes, so the effect\n\t// running can't on its own tell the very first measurement from later ones.\n\tconst isFirstMeasurementRef = useRef( true );\n\n\t// Stable identity, so the listeners below never have to be re-attached, and\n\t// reads the latest props on every call.\n\tconst measureWindow = useEvent( ( initRender?: boolean ) => {\n\t\tconst scrollContainer = getScrollContainer( elementRef.current );\n\t\tif ( ! scrollContainer ) {\n\t\t\treturn;\n\t\t}\n\t\tconst visibleItems = Math.ceil(\n\t\t\tscrollContainer.clientHeight / itemHeight\n\t\t);\n\t\tvisibleItemsRef.current = visibleItems;\n\t\tconst isFirstMeasurement = isFirstMeasurementRef.current;\n\t\tisFirstMeasurementRef.current = false;\n\t\t// Aim to keep opening list view fast, afterward we can optimize for scrolling.\n\t\tconst overscan = initRender\n\t\t\t? visibleItems\n\t\t\t: ( windowOverscan ?? visibleItems );\n\t\tconst firstViewableIndex = Math.floor(\n\t\t\tscrollContainer.scrollTop / itemHeight\n\t\t);\n\t\tconst start = Math.max( 0, firstViewableIndex - overscan );\n\t\tconst end = Math.min(\n\t\t\ttotalItems - 1,\n\t\t\tfirstViewableIndex + visibleItems + overscan\n\t\t);\n\t\tsetFixedListWindow( ( lastWindow ) => {\n\t\t\t// Rendering the window is the expensive part, and for the items in\n\t\t\t// `lastWindow` it is already paid for. When the next window has\n\t\t\t// nothing to add, keep the current one: a window that only shrinks\n\t\t\t// drops nodes that are known to be needed again and forces another\n\t\t\t// style recalculation, with nothing new to show for it.\n\t\t\tif ( lastWindow.start <= start && lastWindow.end >= end ) {\n\t\t\t\treturn lastWindow;\n\t\t\t}\n\t\t\t// The first window is rendered before the list can be measured, so\n\t\t\t// it is sized by `initWindowSize` rather than by the viewport, and\n\t\t\t// the measured window is normally wider than it by the overscan.\n\t\t\t// Nothing has painted yet though, so there is no scrolling for the\n\t\t\t// overscan to absorb: covering the visible items is enough, and\n\t\t\t// keeping that window saves a render pass while the list opens.\n\t\t\tif (\n\t\t\t\tisFirstMeasurement &&\n\t\t\t\tlastWindow.start <= firstViewableIndex &&\n\t\t\t\tlastWindow.end >= firstViewableIndex + visibleItems\n\t\t\t) {\n\t\t\t\treturn lastWindow;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tvisibleItems,\n\t\t\t\tstart,\n\t\t\t\tend,\n\t\t\t\titemInView: ( index: number ) => {\n\t\t\t\t\treturn start <= index && index <= end;\n\t\t\t\t},\n\t\t\t};\n\t\t} );\n\t} );\n\n\tconst handleKeyDown = useEvent(\n\t\t( event: KeyboardEvent, scrollContainer: Element ) => {\n\t\t\tswitch ( event.keyCode ) {\n\t\t\t\tcase HOME: {\n\t\t\t\t\treturn scrollContainer.scrollTo( { top: 0 } );\n\t\t\t\t}\n\t\t\t\tcase END: {\n\t\t\t\t\treturn scrollContainer.scrollTo( {\n\t\t\t\t\t\ttop: totalItems * itemHeight,\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t\tcase PAGEUP: {\n\t\t\t\t\treturn scrollContainer.scrollTo( {\n\t\t\t\t\t\ttop:\n\t\t\t\t\t\t\tscrollContainer.scrollTop -\n\t\t\t\t\t\t\tvisibleItemsRef.current * itemHeight,\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t\tcase PAGEDOWN: {\n\t\t\t\t\treturn scrollContainer.scrollTo( {\n\t\t\t\t\t\ttop:\n\t\t\t\t\t\t\tscrollContainer.scrollTop +\n\t\t\t\t\t\t\tvisibleItemsRef.current * itemHeight,\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t);\n\n\t// Measure whenever something that the window is derived from changes.\n\tuseLayoutEffect( () => {\n\t\tif ( ! useWindowing ) {\n\t\t\treturn;\n\t\t}\n\t\tmeasureWindow( true );\n\t}, [\n\t\tuseWindowing,\n\t\tmeasureWindow,\n\t\titemHeight,\n\t\ttotalItems,\n\t\twindowOverscan,\n\t\texpandedState,\n\t] );\n\n\t// Only ever attaches and detaches listeners. The deps are the things that\n\t// can change which element the scroll container is, or whether there is one\n\t// at all: a list that is too short to overflow has no scroll container to\n\t// attach to, and it grows into one by gaining items or by being expanded.\n\tuseLayoutEffect( () => {\n\t\tif ( ! useWindowing ) {\n\t\t\treturn;\n\t\t}\n\t\tconst scrollContainer = getScrollContainer( elementRef.current );\n\t\tif ( ! scrollContainer ) {\n\t\t\treturn;\n\t\t}\n\t\tconst { defaultView } = scrollContainer.ownerDocument;\n\t\t// `scroll` and `resize` already fire at about the rendering rate, so\n\t\t// there is nothing to gain from debouncing them.\n\t\tconst onMeasure = () => measureWindow();\n\t\tconst onKeyDown = ( event: KeyboardEvent ) =>\n\t\t\thandleKeyDown( event, scrollContainer );\n\n\t\tscrollContainer.addEventListener( 'scroll', onMeasure );\n\t\tdefaultView?.addEventListener( 'resize', onMeasure );\n\t\tdefaultView?.addEventListener( 'keydown', onKeyDown );\n\n\t\treturn () => {\n\t\t\tscrollContainer.removeEventListener( 'scroll', onMeasure );\n\t\t\tdefaultView?.removeEventListener( 'resize', onMeasure );\n\t\t\tdefaultView?.removeEventListener( 'keydown', onKeyDown );\n\t\t};\n\t}, [\n\t\tuseWindowing,\n\t\telementRef,\n\t\titemHeight,\n\t\ttotalItems,\n\t\texpandedState,\n\t\tmeasureWindow,\n\t\thandleKeyDown,\n\t] );\n\n\treturn [ fixedListWindow, setFixedListWindow ];\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -51,6 +51,7 @@ function useFocusOutside(onFocusOutside) {
51
51
  if (ignoreForRelatedTarget && event.relatedTarget?.closest(ignoreForRelatedTarget)) {
52
52
  return;
53
53
  }
54
+ clearTimeout(blurCheckTimeoutIdRef.current);
54
55
  blurCheckTimeoutIdRef.current = setTimeout(() => {
55
56
  if (!document.hasFocus()) {
56
57
  event.preventDefault();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/hooks/use-focus-outside/index.ts"],
4
- "sourcesContent": ["import { 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 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"],
5
- "mappings": ";AAAA,SAAS,aAAa,WAAW,cAAc;AAM/C,IAAM,qBAAqB,CAAE,UAAU,QAAS;AAsBhD,SAAS,wBACR,aACuC;AACvC,MAAK,EAAI,uBAAuB,OAAO,cAAgB;AACtD,WAAO;AAAA,EACR;AACA,UAAS,YAAY,UAAW;AAAA,IAC/B,KAAK;AAAA,IACL,KAAK;AACJ,aAAO;AAAA,IAER,KAAK;AACJ,aAAO,mBAAmB;AAAA,QACvB,YAAkC;AAAA,MACrC;AAAA,EACF;AAEA,SAAO;AACR;AAqBe,SAAR,gBACN,gBACwB;AACxB,QAAM,2BAA2B,OAAQ,cAAe;AACxD,YAAW,MAAM;AAChB,6BAAyB,UAAU;AAAA,EACpC,GAAG,CAAE,cAAe,CAAE;AAEtB,QAAM,sBAAsB,OAAQ,KAAM;AAE1C,QAAM,wBAAwB,OAAkB,MAAU;AAK1D,QAAM,kBAAkB,YAAa,MAAM;AAC1C,iBAAc,sBAAsB,OAAQ;AAAA,EAC7C,GAAG,CAAC,CAAE;AAGN,YAAW,MAAM;AAChB,QAAK,CAAE,gBAAiB;AACvB,sBAAgB;AAAA,IACjB;AAAA,EACD,GAAG,CAAE,gBAAgB,eAAgB,CAAE;AAYvC,QAAM,uBAEF,YAAa,CAAE,UAAW;AAC7B,UAAM,EAAE,MAAM,OAAO,IAAI;AACzB,UAAM,mBAAmB,CAAE,WAAW,UAAW,EAAE,SAAU,IAAK;AAElE,QAAK,kBAAmB;AACvB,0BAAoB,UAAU;AAAA,IAC/B,WAAY,wBAAyB,MAAO,GAAI;AAC/C,0BAAoB,UAAU;AAAA,IAC/B;AAAA,EACD,GAAG,CAAC,CAAE;AASN,QAAM,iBAA0C,YAAa,CAAE,UAAW;AAGzE,UAAM,QAAQ;AAGd,QAAK,oBAAoB,SAAU;AAClC;AAAA,IACD;AASA,UAAM,yBAAyB,MAAM,OAAO;AAAA,MAC3C;AAAA,IACD;AACA,QACC,0BACA,MAAM,eAAe,QAAS,sBAAuB,GACpD;AACD;AAAA,IACD;AAEA,0BAAsB,UAAU,WAAY,MAAM;AAKjD,UAAK,CAAE,SAAS,SAAS,GAAI;AAC5B,cAAM,eAAe;AACrB;AAAA,MACD;AAEA,UAAK,eAAe,OAAO,yBAAyB,SAAU;AAC7D,iCAAyB,QAAS,KAAM;AAAA,MACzC;AAAA,IACD,GAAG,CAAE;AAAA,EACN,GAAG,CAAC,CAAE;AAEN,SAAO;AAAA,IACN,SAAS;AAAA,IACT,aAAa;AAAA,IACb,WAAW;AAAA,IACX,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,QAAQ;AAAA,EACT;AACD;",
4
+ "sourcesContent": ["import { 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\tHTMLButtonElement | HTMLLinkElement | 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 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\t// Keep only the latest blur check so a subsequent focus event can cancel it.\n\t\tclearTimeout( blurCheckTimeoutIdRef.current );\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"],
5
+ "mappings": ";AAAA,SAAS,aAAa,WAAW,cAAc;AAM/C,IAAM,qBAAqB,CAAE,UAAU,QAAS;AAoBhD,SAAS,wBACR,aACuC;AACvC,MAAK,EAAI,uBAAuB,OAAO,cAAgB;AACtD,WAAO;AAAA,EACR;AACA,UAAS,YAAY,UAAW;AAAA,IAC/B,KAAK;AAAA,IACL,KAAK;AACJ,aAAO;AAAA,IAER,KAAK;AACJ,aAAO,mBAAmB;AAAA,QACvB,YAAkC;AAAA,MACrC;AAAA,EACF;AAEA,SAAO;AACR;AAqBe,SAAR,gBACN,gBACwB;AACxB,QAAM,2BAA2B,OAAQ,cAAe;AACxD,YAAW,MAAM;AAChB,6BAAyB,UAAU;AAAA,EACpC,GAAG,CAAE,cAAe,CAAE;AAEtB,QAAM,sBAAsB,OAAQ,KAAM;AAE1C,QAAM,wBAAwB,OAAkB,MAAU;AAK1D,QAAM,kBAAkB,YAAa,MAAM;AAC1C,iBAAc,sBAAsB,OAAQ;AAAA,EAC7C,GAAG,CAAC,CAAE;AAGN,YAAW,MAAM;AAChB,QAAK,CAAE,gBAAiB;AACvB,sBAAgB;AAAA,IACjB;AAAA,EACD,GAAG,CAAE,gBAAgB,eAAgB,CAAE;AAYvC,QAAM,uBAEF,YAAa,CAAE,UAAW;AAC7B,UAAM,EAAE,MAAM,OAAO,IAAI;AACzB,UAAM,mBAAmB,CAAE,WAAW,UAAW,EAAE,SAAU,IAAK;AAElE,QAAK,kBAAmB;AACvB,0BAAoB,UAAU;AAAA,IAC/B,WAAY,wBAAyB,MAAO,GAAI;AAC/C,0BAAoB,UAAU;AAAA,IAC/B;AAAA,EACD,GAAG,CAAC,CAAE;AASN,QAAM,iBAA0C,YAAa,CAAE,UAAW;AAGzE,UAAM,QAAQ;AAGd,QAAK,oBAAoB,SAAU;AAClC;AAAA,IACD;AASA,UAAM,yBAAyB,MAAM,OAAO;AAAA,MAC3C;AAAA,IACD;AACA,QACC,0BACA,MAAM,eAAe,QAAS,sBAAuB,GACpD;AACD;AAAA,IACD;AAGA,iBAAc,sBAAsB,OAAQ;AAC5C,0BAAsB,UAAU,WAAY,MAAM;AAKjD,UAAK,CAAE,SAAS,SAAS,GAAI;AAC5B,cAAM,eAAe;AACrB;AAAA,MACD;AAEA,UAAK,eAAe,OAAO,yBAAyB,SAAU;AAC7D,iCAAyB,QAAS,KAAM;AAAA,MACzC;AAAA,IACD,GAAG,CAAE;AAAA,EACN,GAAG,CAAC,CAAE;AAEN,SAAO;AAAA,IACN,SAAS;AAAA,IACT,aAAa;AAAA,IACb,WAAW;AAAA,IACX,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,QAAQ;AAAA,EACT;AACD;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/hooks/use-keyboard-shortcut/index.ts"],
4
- "sourcesContent": ["import Mousetrap from 'mousetrap';\nimport 'mousetrap/plugins/global-bind/mousetrap-global-bind.js';\nimport type { ExtendedKeyboardEvent } from 'mousetrap';\nimport type { RefObject } from '@wordpress/element';\nimport { useEffect, useRef } from '@wordpress/element';\nimport { isAppleOS } from '@wordpress/keycodes';\n\ntype KeyboardShortcutConfig = {\n\t/**\n\t * Handle keyboard events anywhere including inside textarea/input fields.\n\t */\n\tbindGlobal: boolean;\n\t/**\n\t * Event name used to trigger the handler, defaults to keydown.\n\t */\n\teventName: string;\n\t/**\n\t * Disables the keyboard handler if the value is true.\n\t */\n\tisDisabled: boolean;\n\t/**\n\t * React reference to the DOM element used to catch the keyboard event.\n\t */\n\ttarget: RefObject< HTMLElement >;\n};\n\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 shortcuts Keyboard Shortcuts.\n * @param callback Shortcut callback.\n * @param options Shortcut options.\n * @param options.bindGlobal\n * @param options.eventName\n * @param options.isDisabled\n * @param options.target\n */\nfunction useKeyboardShortcut(\n\tshortcuts: string[] | string,\n\tcallback: ( e: ExtendedKeyboardEvent, combo: string ) => void,\n\t{\n\t\tbindGlobal = false,\n\t\teventName = 'keydown',\n\t\tisDisabled = false,\n\t\ttarget,\n\t}: Partial< KeyboardShortcutConfig > = {}\n) {\n\tconst currentCallbackRef = useRef( callback );\n\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\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 ( document as unknown as Element )\n\t\t);\n\n\t\tconst shortcutsArray = Array.isArray( shortcuts )\n\t\t\t? shortcuts\n\t\t\t: [ shortcuts ];\n\n\t\tshortcutsArray.forEach( ( shortcut: string ) => {\n\t\t\tconst keys = shortcut.split( '+' );\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\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-expect-error` Mousetrap typings don’t include `bindGlobal`\n\t\t\t// It exists at runtime via the plugin\n\t\t\t// `any` is used here to allow dynamic access via `bindFn`\n\t\t\t( mousetrap as any )[ bindFn ](\n\t\t\t\tshortcut,\n\t\t\t\t( ...args: [ Mousetrap.ExtendedKeyboardEvent, string ] ) =>\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"],
5
- "mappings": ";AAAA,OAAO,eAAe;AACtB,OAAO;AAGP,SAAS,WAAW,cAAc;AAClC,SAAS,iBAAiB;AAkC1B,SAAS,oBACR,WACA,UACA;AAAA,EACC,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,aAAa;AAAA,EACb;AACD,IAAuC,CAAC,GACvC;AACD,QAAM,qBAAqB,OAAQ,QAAS;AAE5C,YAAW,MAAM;AAChB,uBAAmB,UAAU;AAAA,EAC9B,GAAG,CAAE,QAAS,CAAE;AAEhB,YAAW,MAAM;AAChB,QAAK,YAAa;AACjB;AAAA,IACD;AAEA,UAAM,YAAY,IAAI;AAAA,MACrB,UAAU,OAAO,UACd,OAAO;AAAA;AAAA;AAAA;AAAA,QAIL;AAAA;AAAA,IACN;AAEA,UAAM,iBAAiB,MAAM,QAAS,SAAU,IAC7C,YACA,CAAE,SAAU;AAEf,mBAAe,QAAS,CAAE,aAAsB;AAC/C,YAAM,OAAO,SAAS,MAAO,GAAI;AACjC,YAAM,YAAY,IAAI;AAAA,QACrB,KAAK,OAAQ,CAAE,UAAW,MAAM,SAAS,CAAE;AAAA,MAC5C;AACA,YAAM,SAAS,UAAU,IAAK,KAAM;AACpC,YAAM,WAAW,UAAU,IAAK,OAAQ;AAExC,UACC,UAAU,MACN,UAAU,SAAS,KAAK,UACzB,UAAU,SAAS,KAAK,UAAU,WACpC;AACD,cAAM,IAAI;AAAA,UACT,eAAgB,QAAS;AAAA,QAC1B;AAAA,MACD;AAEA,YAAM,SAAS,aAAa,eAAe;AAI3C,MAAE,UAAoB,MAAO;AAAA,QAC5B;AAAA,QACA,IAAK,SACJ,mBAAmB,QAAS,GAAG,IAAK;AAAA,QACrC;AAAA,MACD;AAAA,IACD,CAAE;AAEF,WAAO,MAAM;AACZ,gBAAU,MAAM;AAAA,IACjB;AAAA,EACD,GAAG,CAAE,WAAW,YAAY,WAAW,QAAQ,UAAW,CAAE;AAC7D;AAEA,IAAO,gCAAQ;",
4
+ "sourcesContent": ["import Mousetrap from 'mousetrap';\nimport 'mousetrap/plugins/global-bind/mousetrap-global-bind.js';\nimport type { ExtendedKeyboardEvent } from 'mousetrap';\nimport type { RefObject } from '@wordpress/element';\nimport { useEffect, useRef } from '@wordpress/element';\nimport { isAppleOS } from '@wordpress/keycodes';\n\ntype KeyboardShortcutConfig = {\n\t/**\n\t * Handle keyboard events anywhere including inside textarea/input fields.\n\t */\n\tbindGlobal: boolean;\n\t/**\n\t * Event name used to trigger the handler, defaults to keydown.\n\t */\n\teventName: string;\n\t/**\n\t * Disables the keyboard handler if the value is true.\n\t */\n\tisDisabled: boolean;\n\t/**\n\t * React reference to the DOM element used to catch the keyboard event.\n\t */\n\ttarget: RefObject< HTMLElement >;\n};\n\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 shortcuts Keyboard Shortcuts.\n * @param callback Shortcut callback.\n * @param options Shortcut options.\n * @param options.bindGlobal\n * @param options.eventName\n * @param options.isDisabled\n * @param options.target\n */\nfunction useKeyboardShortcut(\n\tshortcuts: string[] | string,\n\tcallback: ( e: ExtendedKeyboardEvent, combo: string ) => void,\n\t{\n\t\tbindGlobal = false,\n\t\teventName = 'keydown',\n\t\tisDisabled = false,\n\t\ttarget,\n\t}: Partial< KeyboardShortcutConfig > = {}\n) {\n\tconst currentCallbackRef = useRef( callback );\n\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\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\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\t// necessary to maintain the existing behavior.\n\t\t\t\t\t( document as unknown as Element )\n\t\t);\n\n\t\tconst shortcutsArray = Array.isArray( shortcuts )\n\t\t\t? shortcuts\n\t\t\t: [ shortcuts ];\n\n\t\tshortcutsArray.forEach( ( shortcut: string ) => {\n\t\t\tconst keys = shortcut.split( '+' );\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\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-expect-error` Mousetrap typings don’t include `bindGlobal`\n\t\t\t// It exists at runtime via the plugin\n\t\t\t// `any` is used here to allow dynamic access via `bindFn`\n\t\t\t( mousetrap as any )[ bindFn ](\n\t\t\t\tshortcut,\n\t\t\t\t( ...args: [ Mousetrap.ExtendedKeyboardEvent, string ] ) =>\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"],
5
+ "mappings": ";AAAA,OAAO,eAAe;AACtB,OAAO;AAGP,SAAS,WAAW,cAAc;AAClC,SAAS,iBAAiB;AAkC1B,SAAS,oBACR,WACA,UACA;AAAA,EACC,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,aAAa;AAAA,EACb;AACD,IAAuC,CAAC,GACvC;AACD,QAAM,qBAAqB,OAAQ,QAAS;AAE5C,YAAW,MAAM;AAChB,uBAAmB,UAAU;AAAA,EAC9B,GAAG,CAAE,QAAS,CAAE;AAEhB,YAAW,MAAM;AAChB,QAAK,YAAa;AACjB;AAAA,IACD;AAEA,UAAM,YAAY,IAAI;AAAA,MACrB,UAAU,OAAO,UACd,OAAO;AAAA;AAAA;AAAA;AAAA,QAIN;AAAA;AAAA,IACL;AAEA,UAAM,iBAAiB,MAAM,QAAS,SAAU,IAC7C,YACA,CAAE,SAAU;AAEf,mBAAe,QAAS,CAAE,aAAsB;AAC/C,YAAM,OAAO,SAAS,MAAO,GAAI;AACjC,YAAM,YAAY,IAAI;AAAA,QACrB,KAAK,OAAQ,CAAE,UAAW,MAAM,SAAS,CAAE;AAAA,MAC5C;AACA,YAAM,SAAS,UAAU,IAAK,KAAM;AACpC,YAAM,WAAW,UAAU,IAAK,OAAQ;AAExC,UACC,UAAU,MACN,UAAU,SAAS,KAAK,UACzB,UAAU,SAAS,KAAK,UAAU,WACpC;AACD,cAAM,IAAI;AAAA,UACT,eAAgB,QAAS;AAAA,QAC1B;AAAA,MACD;AAEA,YAAM,SAAS,aAAa,eAAe;AAI3C,MAAE,UAAoB,MAAO;AAAA,QAC5B;AAAA,QACA,IAAK,SACJ,mBAAmB,QAAS,GAAG,IAAK;AAAA,QACrC;AAAA,MACD;AAAA,IACD,CAAE;AAEF,WAAO,MAAM;AACZ,gBAAU,MAAM;AAAA,IACjB;AAAA,EACD,GAAG,CAAE,WAAW,YAAY,WAAW,QAAQ,UAAW,CAAE;AAC7D;AAEA,IAAO,gCAAQ;",
6
6
  "names": []
7
7
  }
@@ -1,10 +1,19 @@
1
1
  // packages/compose/src/hooks/use-resize-observer/use-resize-observer.ts
2
- import { useRef } from "@wordpress/element";
2
+ import { useEffect, useRef } from "@wordpress/element";
3
3
  import useEvent from "../use-event/index.mjs";
4
4
  function useResizeObserver(callback, resizeObserverOptions = {}) {
5
5
  const callbackEvent = useEvent(callback);
6
6
  const observedElementRef = useRef(null);
7
7
  const resizeObserverRef = useRef(void 0);
8
+ useEffect(() => {
9
+ if (observedElementRef.current) {
10
+ resizeObserverRef.current?.observe(
11
+ observedElementRef.current,
12
+ resizeObserverOptions
13
+ );
14
+ }
15
+ return () => resizeObserverRef.current?.disconnect();
16
+ }, []);
8
17
  return useEvent((element) => {
9
18
  if (element === observedElementRef.current) {
10
19
  return;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/hooks/use-resize-observer/use-resize-observer.ts"],
4
- "sourcesContent": ["import { useRef } from '@wordpress/element';\nimport useEvent from '../use-event';\n\n// This is the current implementation of `useResizeObserver`.\n//\n// The legacy implementation is still supported for backwards compatibility.\n// This is achieved by overloading the exported function with both signatures,\n// and detecting which API is being used at runtime.\nexport function useResizeObserver< T extends HTMLElement >(\n\tcallback: ResizeObserverCallback,\n\tresizeObserverOptions: ResizeObserverOptions = {}\n): ( element?: T | null ) => void {\n\tconst callbackEvent = useEvent( callback );\n\n\tconst observedElementRef = useRef< T | null >( null );\n\tconst resizeObserverRef = useRef< ResizeObserver >( undefined );\n\treturn useEvent( ( element?: T | null ) => {\n\t\tif ( element === observedElementRef.current ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Set up `ResizeObserver`.\n\t\tresizeObserverRef.current ??= new ResizeObserver( callbackEvent );\n\t\tconst { current: resizeObserver } = resizeObserverRef;\n\n\t\t// Unobserve previous element.\n\t\tif ( observedElementRef.current ) {\n\t\t\tresizeObserver.unobserve( observedElementRef.current );\n\t\t}\n\n\t\t// Observe new element.\n\t\tobservedElementRef.current = element ?? null;\n\t\tif ( element ) {\n\t\t\tresizeObserver.observe( element, resizeObserverOptions );\n\t\t}\n\t} );\n}\n"],
5
- "mappings": ";AAAA,SAAS,cAAc;AACvB,OAAO,cAAc;AAOd,SAAS,kBACf,UACA,wBAA+C,CAAC,GACf;AACjC,QAAM,gBAAgB,SAAU,QAAS;AAEzC,QAAM,qBAAqB,OAAoB,IAAK;AACpD,QAAM,oBAAoB,OAA0B,MAAU;AAC9D,SAAO,SAAU,CAAE,YAAwB;AAC1C,QAAK,YAAY,mBAAmB,SAAU;AAC7C;AAAA,IACD;AAGA,sBAAkB,YAAY,IAAI,eAAgB,aAAc;AAChE,UAAM,EAAE,SAAS,eAAe,IAAI;AAGpC,QAAK,mBAAmB,SAAU;AACjC,qBAAe,UAAW,mBAAmB,OAAQ;AAAA,IACtD;AAGA,uBAAmB,UAAU,WAAW;AACxC,QAAK,SAAU;AACd,qBAAe,QAAS,SAAS,qBAAsB;AAAA,IACxD;AAAA,EACD,CAAE;AACH;",
4
+ "sourcesContent": ["import { useEffect, useRef } from '@wordpress/element';\nimport useEvent from '../use-event';\n\n// This is the current implementation of `useResizeObserver`.\n//\n// The legacy implementation is still supported for backwards compatibility.\n// This is achieved by overloading the exported function with both signatures,\n// and detecting which API is being used at runtime.\nexport function useResizeObserver< T extends HTMLElement >(\n\tcallback: ResizeObserverCallback,\n\tresizeObserverOptions: ResizeObserverOptions = {}\n): ( element?: T | null ) => void {\n\tconst callbackEvent = useEvent( callback );\n\n\tconst observedElementRef = useRef< T | null >( null );\n\tconst resizeObserverRef = useRef< ResizeObserver >( undefined );\n\tuseEffect( () => {\n\t\tif ( observedElementRef.current ) {\n\t\t\tresizeObserverRef.current?.observe(\n\t\t\t\tobservedElementRef.current,\n\t\t\t\tresizeObserverOptions\n\t\t\t);\n\t\t}\n\t\treturn () => resizeObserverRef.current?.disconnect();\n\t\t// This effect owns the observer lifecycle. The returned ref handles\n\t\t// element and option changes without recreating the observer.\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, [] );\n\treturn useEvent( ( element?: T | null ) => {\n\t\tif ( element === observedElementRef.current ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Set up `ResizeObserver`.\n\t\tresizeObserverRef.current ??= new ResizeObserver( callbackEvent );\n\t\tconst { current: resizeObserver } = resizeObserverRef;\n\n\t\t// Unobserve previous element.\n\t\tif ( observedElementRef.current ) {\n\t\t\tresizeObserver.unobserve( observedElementRef.current );\n\t\t}\n\n\t\t// Observe new element.\n\t\tobservedElementRef.current = element ?? null;\n\t\tif ( element ) {\n\t\t\tresizeObserver.observe( element, resizeObserverOptions );\n\t\t}\n\t} );\n}\n"],
5
+ "mappings": ";AAAA,SAAS,WAAW,cAAc;AAClC,OAAO,cAAc;AAOd,SAAS,kBACf,UACA,wBAA+C,CAAC,GACf;AACjC,QAAM,gBAAgB,SAAU,QAAS;AAEzC,QAAM,qBAAqB,OAAoB,IAAK;AACpD,QAAM,oBAAoB,OAA0B,MAAU;AAC9D,YAAW,MAAM;AAChB,QAAK,mBAAmB,SAAU;AACjC,wBAAkB,SAAS;AAAA,QAC1B,mBAAmB;AAAA,QACnB;AAAA,MACD;AAAA,IACD;AACA,WAAO,MAAM,kBAAkB,SAAS,WAAW;AAAA,EAIpD,GAAG,CAAC,CAAE;AACN,SAAO,SAAU,CAAE,YAAwB;AAC1C,QAAK,YAAY,mBAAmB,SAAU;AAC7C;AAAA,IACD;AAGA,sBAAkB,YAAY,IAAI,eAAgB,aAAc;AAChE,UAAM,EAAE,SAAS,eAAe,IAAI;AAGpC,QAAK,mBAAmB,SAAU;AACjC,qBAAe,UAAW,mBAAmB,OAAQ;AAAA,IACtD;AAGA,uBAAmB,UAAU,WAAW;AACxC,QAAK,SAAU;AACd,qBAAe,QAAS,SAAS,qBAAsB;AAAA,IACxD;AAAA,EACD,CAAE;AACH;",
6
6
  "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-focus-outside/index.ts"],"names":[],"mappings":"AAgDA,KAAK,qBAAqB,GAAG;IAC5B,OAAO,EAAE,KAAK,CAAC,iBAAiB,CAAC;IACjC,WAAW,EAAE,KAAK,CAAC,iBAAiB,CAAC;IACrC,SAAS,EAAE,KAAK,CAAC,iBAAiB,CAAC;IACnC,YAAY,EAAE,KAAK,CAAC,iBAAiB,CAAC;IACtC,UAAU,EAAE,KAAK,CAAC,iBAAiB,CAAC;IACpC,MAAM,EAAE,KAAK,CAAC,iBAAiB,CAAC;CAChC,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,CAAC,OAAO,UAAU,eAAe,CACtC,cAAc,EAAE,CAAE,CAAE,KAAK,EAAE,KAAK,CAAC,UAAU,KAAM,IAAI,CAAE,GAAG,SAAS,GACjE,qBAAqB,CAyGvB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-focus-outside/index.ts"],"names":[],"mappings":"AA8CA,KAAK,qBAAqB,GAAG;IAC5B,OAAO,EAAE,KAAK,CAAC,iBAAiB,CAAC;IACjC,WAAW,EAAE,KAAK,CAAC,iBAAiB,CAAC;IACrC,SAAS,EAAE,KAAK,CAAC,iBAAiB,CAAC;IACnC,YAAY,EAAE,KAAK,CAAC,iBAAiB,CAAC;IACtC,UAAU,EAAE,KAAK,CAAC,iBAAiB,CAAC;IACpC,MAAM,EAAE,KAAK,CAAC,iBAAiB,CAAC;CAChC,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,CAAC,OAAO,UAAU,eAAe,CACtC,cAAc,EAAE,CAAE,CAAE,KAAK,EAAE,KAAK,CAAC,UAAU,KAAM,IAAI,CAAE,GAAG,SAAS,GACjE,qBAAqB,CA2GvB"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-resize-observer.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-resize-observer/use-resize-observer.ts"],"names":[],"mappings":"AAQA,wBAAgB,iBAAiB,CAAE,CAAC,SAAS,WAAW,EACvD,QAAQ,EAAE,sBAAsB,EAChC,qBAAqB,GAAE,qBAA0B,GAC/C,CAAE,OAAO,CAAC,EAAE,CAAC,GAAG,IAAI,KAAM,IAAI,CAyBhC"}
1
+ {"version":3,"file":"use-resize-observer.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-resize-observer/use-resize-observer.ts"],"names":[],"mappings":"AAQA,wBAAgB,iBAAiB,CAAE,CAAC,SAAS,WAAW,EACvD,QAAQ,EAAE,sBAAsB,EAChC,qBAAqB,GAAE,qBAA0B,GAC/C,CAAE,OAAO,CAAC,EAAE,CAAC,GAAG,IAAI,KAAM,IAAI,CAqChC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/compose",
3
- "version": "8.7.2-next.v.202609031004.0",
3
+ "version": "8.8.2-next.v.202609171837.0",
4
4
  "description": "WordPress higher-order components (HOCs).",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -46,26 +46,26 @@
46
46
  "sideEffects": false,
47
47
  "dependencies": {
48
48
  "@types/mousetrap": "^1.6.8",
49
- "@wordpress/deprecated": "^4.54.1-next.v.202609031004.0",
50
- "@wordpress/dom": "^4.54.1-next.v.202609031004.0",
51
- "@wordpress/element": "^8.6.2-next.v.202609031004.0",
52
- "@wordpress/is-shallow-equal": "^5.54.1-next.v.202609031004.0",
53
- "@wordpress/keycodes": "^4.54.1-next.v.202609031004.0",
54
- "@wordpress/priority-queue": "^3.54.1-next.v.202609031004.0",
55
- "@wordpress/private-apis": "^1.54.2-next.v.202609031004.0",
56
- "@wordpress/undo-manager": "^1.54.1-next.v.202609031004.0",
49
+ "@wordpress/deprecated": "^4.55.1-next.v.202609171837.0",
50
+ "@wordpress/dom": "^4.55.2-next.v.202609171837.0",
51
+ "@wordpress/element": "^8.7.1-next.v.202609171837.0",
52
+ "@wordpress/is-shallow-equal": "^5.55.1-next.v.202609171837.0",
53
+ "@wordpress/keycodes": "^4.56.1-next.v.202609171837.0",
54
+ "@wordpress/priority-queue": "^3.55.1-next.v.202609171837.0",
55
+ "@wordpress/private-apis": "^1.55.1-next.v.202609171837.0",
56
+ "@wordpress/undo-manager": "^1.55.1-next.v.202609171837.0",
57
57
  "change-case": "^4.1.2",
58
58
  "mousetrap": "^1.6.5",
59
59
  "use-memo-one": "^1.1.1"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@testing-library/dom": "^10.4.1",
63
- "@testing-library/react": "^16.3.2",
63
+ "@testing-library/react": "^16.3.3",
64
64
  "@testing-library/user-event": "^14.6.1",
65
- "@types/jest": "^30.0.0",
66
65
  "mock-match-media": "^0.4.2",
67
66
  "react-dom": "^18.3.1",
68
- "vitest": "^4.1.10"
67
+ "vitest": "^5.0.0",
68
+ "vitest-browser-react": "^2.3.0"
69
69
  },
70
70
  "peerDependencies": {
71
71
  "@types/react": "^18 || ^19",
@@ -79,5 +79,5 @@
79
79
  "publishConfig": {
80
80
  "access": "public"
81
81
  },
82
- "gitHead": "7275d6c6d93b542fca3cd98cdb9c7344ddd11e19"
82
+ "gitHead": "7fc9f6676debc888d2f071d17cb71837040d4b48"
83
83
  }
@@ -1,3 +1,4 @@
1
+ import { afterEach, describe, expect, it } from 'vitest';
1
2
  import { render, screen } from '@testing-library/react';
2
3
  import { logged } from '@wordpress/deprecated';
3
4
  import pure from '../';
@@ -1,20 +1,23 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
1
2
  import { render, screen } from '@testing-library/react';
2
3
  import { Component } from '@wordpress/element';
3
4
  import withGlobalEvents from '../';
4
5
  import Listener from '../listener';
5
6
 
6
- jest.mock( '../listener', () => {
7
- const ActualListener = jest.requireActual( '../listener' ).default;
7
+ vi.mock( import( '../listener' ), async ( importOriginal ) => {
8
+ const { default: ActualListener } = await importOriginal();
8
9
 
9
- return class extends ActualListener {
10
- constructor() {
11
- super( ...arguments );
10
+ return {
11
+ default: class extends ActualListener {
12
+ constructor() {
13
+ super( ...arguments );
12
14
 
13
- this.constructor._instance = this;
15
+ this.constructor._instance = this;
14
16
 
15
- jest.spyOn( this, 'add' );
16
- jest.spyOn( this, 'remove' );
17
- }
17
+ vi.spyOn( this, 'add' );
18
+ vi.spyOn( this, 'remove' );
19
+ }
20
+ },
18
21
  };
19
22
  } );
20
23
 
@@ -30,12 +33,12 @@ describe( 'withGlobalEvents', () => {
30
33
  }
31
34
  }
32
35
 
33
- beforeAll( () => {
34
- jest.spyOn( OriginalComponent.prototype, 'handleResize' );
35
- } );
36
-
37
36
  beforeEach( () => {
38
- jest.clearAllMocks();
37
+ vi.spyOn( OriginalComponent.prototype, 'handleResize' );
38
+ if ( Listener._instance ) {
39
+ vi.spyOn( Listener._instance, 'add' );
40
+ vi.spyOn( Listener._instance, 'remove' );
41
+ }
39
42
  } );
40
43
 
41
44
  it( 'renders with original component', () => {
@@ -67,7 +70,7 @@ describe( 'withGlobalEvents', () => {
67
70
  const EnhancedComponent = withGlobalEvents( {
68
71
  resize: 'handleResize',
69
72
  } )( OriginalComponent );
70
- const onResize = jest.fn();
73
+ const onResize = vi.fn();
71
74
 
72
75
  render(
73
76
  <EnhancedComponent ref={ () => {} } onResize={ onResize }>
@@ -1,19 +1,28 @@
1
+ import {
2
+ afterAll,
3
+ beforeAll,
4
+ beforeEach,
5
+ describe,
6
+ expect,
7
+ it,
8
+ vi,
9
+ } from 'vitest';
1
10
  import Listener from '../listener';
2
11
 
3
12
  describe( 'Listener', () => {
4
- const createHandler = () => ( { handleEvent: jest.fn() } );
13
+ const createHandler = () => ( { handleEvent: vi.fn() } );
5
14
 
6
15
  let listener, _addEventListener, _removeEventListener;
7
16
  beforeAll( () => {
8
17
  _addEventListener = global.window.addEventListener;
9
18
  _removeEventListener = global.window.removeEventListener;
10
- global.window.addEventListener = jest.fn();
11
- global.window.removeEventListener = jest.fn();
19
+ global.window.addEventListener = vi.fn();
20
+ global.window.removeEventListener = vi.fn();
12
21
  } );
13
22
 
14
23
  beforeEach( () => {
15
24
  listener = new Listener();
16
- jest.clearAllMocks();
25
+ vi.clearAllMocks();
17
26
  } );
18
27
 
19
28
  afterAll( () => {
@@ -1,3 +1,4 @@
1
+ import { describe, expect, it } from 'vitest';
1
2
  import { render, screen } from '@testing-library/react';
2
3
  import withInstanceId from '../';
3
4
 
@@ -1,3 +1,4 @@
1
+ import { describe, expect, it } from 'vitest';
1
2
  import { render, screen } from '@testing-library/react';
2
3
  import userEvent from '@testing-library/user-event';
3
4
  import withState from '../';
@@ -1,10 +1,11 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
1
2
  import { render, screen, act } from '@testing-library/react';
2
3
  import userEvent from '@testing-library/user-event';
3
4
  import deprecated from '@wordpress/deprecated';
4
5
  import { useRef } from '@wordpress/element';
5
6
  import useCopyOnClick from '../';
6
7
 
7
- jest.mock( '@wordpress/deprecated' );
8
+ vi.mock( import( '@wordpress/deprecated' ) );
8
9
 
9
10
  interface TestComponentProps {
10
11
  text: string | ( () => string );
@@ -12,6 +13,10 @@ interface TestComponentProps {
12
13
  }
13
14
 
14
15
  describe( 'useCopyOnClick', () => {
16
+ afterEach( () => {
17
+ vi.useRealTimers();
18
+ } );
19
+
15
20
  const TestComponent = ( { text, timeout = 4000 }: TestComponentProps ) => {
16
21
  const ref = useRef< HTMLButtonElement >( null );
17
22
  const hasCopied = useCopyOnClick( ref, text, timeout );
@@ -23,7 +28,7 @@ describe( 'useCopyOnClick', () => {
23
28
  };
24
29
 
25
30
  it( 'should call deprecated when the hook is used', () => {
26
- jest.mocked( deprecated ).mockClear();
31
+ vi.mocked( deprecated ).mockClear();
27
32
  render( <TestComponent text="test text" /> );
28
33
 
29
34
  expect( deprecated ).toHaveBeenCalledWith(
@@ -39,7 +44,7 @@ describe( 'useCopyOnClick', () => {
39
44
  const user = userEvent.setup();
40
45
  render( <TestComponent text="test text" /> );
41
46
 
42
- const writeTextMock = jest
47
+ const writeTextMock = vi
43
48
  .spyOn( navigator.clipboard, 'writeText' )
44
49
  .mockResolvedValue();
45
50
 
@@ -53,7 +58,7 @@ describe( 'useCopyOnClick', () => {
53
58
  const user = userEvent.setup();
54
59
  render( <TestComponent text="test text" /> );
55
60
 
56
- jest.spyOn( navigator.clipboard, 'writeText' ).mockResolvedValue();
61
+ vi.spyOn( navigator.clipboard, 'writeText' ).mockResolvedValue();
57
62
 
58
63
  expect( screen.getByRole( 'button' ) ).toHaveTextContent( 'Copy' );
59
64
 
@@ -67,36 +72,32 @@ describe( 'useCopyOnClick', () => {
67
72
  } );
68
73
 
69
74
  it( 'should reset hasCopied after timeout', async () => {
70
- jest.useFakeTimers();
75
+ vi.useFakeTimers( { shouldAdvanceTime: true } );
71
76
  const user = userEvent.setup( {
72
- advanceTimers: jest.advanceTimersByTime,
77
+ advanceTimers: ( delay ) => vi.advanceTimersByTime( delay ),
73
78
  } );
74
79
  render( <TestComponent text="test text" timeout={ 1000 } /> );
75
80
 
76
- jest.spyOn( navigator.clipboard, 'writeText' ).mockResolvedValue();
81
+ vi.spyOn( navigator.clipboard, 'writeText' ).mockResolvedValue();
77
82
 
78
83
  await user.click( screen.getByRole( 'button' ) );
79
84
 
80
85
  await act( async () => {
81
- const p = new Promise( ( resolve ) => setTimeout( resolve, 0 ) );
82
- jest.advanceTimersByTime( 0 );
83
- await p;
86
+ await vi.advanceTimersByTimeAsync( 0 );
84
87
  } );
85
88
  expect( screen.getByRole( 'button' ) ).toHaveTextContent( 'Copied!' );
86
89
 
87
- act( () => {
88
- jest.advanceTimersByTime( 1000 );
90
+ await act( async () => {
91
+ await vi.advanceTimersByTimeAsync( 1000 );
89
92
  } );
90
93
  expect( screen.getByRole( 'button' ) ).toHaveTextContent( 'Copy' );
91
-
92
- jest.useRealTimers();
93
94
  } );
94
95
 
95
96
  it( 'should not set hasCopied when copy fails', async () => {
96
97
  const user = userEvent.setup();
97
98
  render( <TestComponent text="test text" /> );
98
99
 
99
- jest.spyOn( navigator.clipboard, 'writeText' ).mockRejectedValue(
100
+ vi.spyOn( navigator.clipboard, 'writeText' ).mockRejectedValue(
100
101
  new Error()
101
102
  );
102
103
 
@@ -110,7 +111,7 @@ describe( 'useCopyOnClick', () => {
110
111
  } );
111
112
 
112
113
  it( 'should not update hasCopied after unmount', async () => {
113
- const renderSpy = jest.fn();
114
+ const renderSpy = vi.fn();
114
115
 
115
116
  const SpyComponent = ( { text }: { text: string } ) => {
116
117
  const ref = useRef< HTMLButtonElement >( null );
@@ -127,7 +128,7 @@ describe( 'useCopyOnClick', () => {
127
128
  const delayedPromise = new Promise< void >( ( resolve ) => {
128
129
  resolvePromise = resolve;
129
130
  } );
130
- jest.spyOn( navigator.clipboard, 'writeText' ).mockReturnValue(
131
+ vi.spyOn( navigator.clipboard, 'writeText' ).mockReturnValue(
131
132
  delayedPromise as Promise< void >
132
133
  );
133
134
 
@@ -1,3 +1,4 @@
1
+ import { describe, expect, it, vi } from 'vitest';
1
2
  import { render, screen, act } from '@testing-library/react';
2
3
  import userEvent from '@testing-library/user-event';
3
4
  import useCopyToClipboard, { copyToClipboard, restoreFocus } from '../';
@@ -17,7 +18,7 @@ describe( 'useCopyToClipboard', () => {
17
18
  const user = userEvent.setup();
18
19
  render( <TestComponent text="test text" /> );
19
20
 
20
- const writeTextMock = jest
21
+ const writeTextMock = vi
21
22
  .spyOn( navigator.clipboard, 'writeText' )
22
23
  .mockResolvedValue();
23
24
 
@@ -29,7 +30,7 @@ describe( 'useCopyToClipboard', () => {
29
30
 
30
31
  it( 'should call onSuccess when copy succeeds', async () => {
31
32
  const user = userEvent.setup();
32
- const onSuccess = jest.fn();
33
+ const onSuccess = vi.fn();
33
34
  render( <TestComponent text="test text" onSuccess={ onSuccess } /> );
34
35
 
35
36
  await user.click( screen.getByRole( 'button' ) );
@@ -39,7 +40,7 @@ describe( 'useCopyToClipboard', () => {
39
40
 
40
41
  it( 'should call onSuccess when copy empty text', async () => {
41
42
  const user = userEvent.setup();
42
- const onSuccess = jest.fn();
43
+ const onSuccess = vi.fn();
43
44
  render( <TestComponent text="" onSuccess={ onSuccess } /> );
44
45
 
45
46
  await user.click( screen.getByRole( 'button' ) );
@@ -49,10 +50,10 @@ describe( 'useCopyToClipboard', () => {
49
50
 
50
51
  it( 'should not call onSuccess when copy fails', async () => {
51
52
  const user = userEvent.setup();
52
- const onSuccess = jest.fn();
53
+ const onSuccess = vi.fn();
53
54
  render( <TestComponent text="test text" onSuccess={ onSuccess } /> );
54
55
 
55
- jest.spyOn( navigator.clipboard, 'writeText' ).mockRejectedValue(
56
+ vi.spyOn( navigator.clipboard, 'writeText' ).mockRejectedValue(
56
57
  new Error()
57
58
  );
58
59
 
@@ -66,12 +67,12 @@ describe( 'useCopyToClipboard', () => {
66
67
  const delayedPromise = new Promise< void >( ( resolve ) => {
67
68
  resolvePromise = resolve;
68
69
  } );
69
- jest.spyOn( navigator.clipboard, 'writeText' ).mockReturnValue(
70
+ vi.spyOn( navigator.clipboard, 'writeText' ).mockReturnValue(
70
71
  delayedPromise
71
72
  );
72
73
 
73
74
  const user = userEvent.setup();
74
- const onSuccess = jest.fn();
75
+ const onSuccess = vi.fn();
75
76
  const { unmount } = render(
76
77
  <TestComponent text="test" onSuccess={ onSuccess } />
77
78
  );
@@ -92,7 +93,7 @@ describe( 'useCopyToClipboard', () => {
92
93
  const delayedPromise = new Promise< void >( ( resolve ) => {
93
94
  resolvePromise = resolve;
94
95
  } );
95
- jest.spyOn( navigator.clipboard, 'writeText' ).mockReturnValue(
96
+ vi.spyOn( navigator.clipboard, 'writeText' ).mockReturnValue(
96
97
  delayedPromise
97
98
  );
98
99
 
@@ -100,7 +101,7 @@ describe( 'useCopyToClipboard', () => {
100
101
  const { unmount } = render( <TestComponent text="test" /> );
101
102
 
102
103
  const button = screen.getByRole( 'button' );
103
- const focusSpy = jest.spyOn( button, 'focus' );
104
+ const focusSpy = vi.spyOn( button, 'focus' );
104
105
 
105
106
  await user.click( button );
106
107
  unmount();
@@ -129,7 +130,7 @@ describe( 'copyToClipboard', () => {
129
130
 
130
131
  // JSDOM does not implement execCommand; add a mock for the fallback path.
131
132
  // See: https://github.com/jsdom/jsdom/issues/1742
132
- const execCommandMock = jest.fn().mockReturnValue( true );
133
+ const execCommandMock = vi.fn().mockReturnValue( true );
133
134
  Object.defineProperty( document, 'execCommand', {
134
135
  value: execCommandMock,
135
136
  configurable: true,
@@ -154,7 +155,7 @@ describe( 'restoreFocus', () => {
154
155
  it( 'should focus the trigger element', () => {
155
156
  const trigger = document.createElement( 'button' );
156
157
  document.body.appendChild( trigger );
157
- const focusMock = jest.spyOn( trigger, 'focus' );
158
+ const focusMock = vi.spyOn( trigger, 'focus' );
158
159
 
159
160
  restoreFocus( trigger );
160
161