@wordpress/compose 7.41.1-next.v.202603161435.0 → 7.43.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 CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 7.43.0 (2026-04-01)
6
+
7
+ ### Bug Fixes
8
+
9
+ - `useDialog`: Add `event.stopPropagation()` to the Escape key handler to prevent the event from bubbling to parent overlays ([#76861](https://github.com/WordPress/gutenberg/pull/76861)).
10
+
11
+ ## 7.42.0 (2026-03-18)
12
+
13
+ ### New Features
14
+
15
+ - Hooks `useMediaQuery` and `useViewportMatch` accept a new optional `view` argument of type `Window`, which enables consumers to perform media queries in a window other than the global one (e.g. an iframe) ([#76446](https://github.com/WordPress/gutenberg/pull/76446)).
16
+
5
17
  ## 7.41.0 (2026-03-04)
6
18
 
7
19
  ## 7.40.0 (2026-02-18)
package/README.md CHANGED
@@ -431,6 +431,7 @@ Runs a media query and returns its value when it changes.
431
431
  _Parameters_
432
432
 
433
433
  - _query_ `[string]`: Media Query.
434
+ - _view_ `[Window]`: Window instance, else default to global window
434
435
 
435
436
  _Returns_
436
437
 
@@ -592,6 +593,7 @@ _Parameters_
592
593
 
593
594
  - _breakpoint_ `WPBreakpoint`: Breakpoint size name.
594
595
  - _operator_ `[WPViewportOperator]`: Viewport operator.
596
+ - _view_ `[Window]`: Window instance in which to perform viewport matching.
595
597
 
596
598
  _Returns_
597
599
 
@@ -63,6 +63,7 @@ function useDialog(options) {
63
63
  node.addEventListener("keydown", (event) => {
64
64
  if (event.keyCode === import_keycodes.ESCAPE && !event.defaultPrevented && currentOptions.current?.onClose) {
65
65
  event.preventDefault();
66
+ event.stopPropagation();
66
67
  currentOptions.current.onClose();
67
68
  }
68
69
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/hooks/use-dialog/index.ts"],
4
- "sourcesContent": ["/**\n * External dependencies\n */\nimport type { RefCallback, SyntheticEvent } from 'react';\n\n/**\n * WordPress dependencies\n */\nimport { useRef, useEffect, useCallback } from '@wordpress/element';\nimport { ESCAPE } from '@wordpress/keycodes';\n\n/**\n * Internal dependencies\n */\nimport useConstrainedTabbing from '../use-constrained-tabbing';\nimport { useFocusOnMount } from '../use-focus-on-mount';\nimport useFocusReturn from '../use-focus-return';\nimport useFocusOutside from '../use-focus-outside';\nimport useMergeRefs from '../use-merge-refs';\n\ntype DialogOptions = {\n\t/**\n\t * Determines focus behavior when the dialog mounts.\n\t *\n\t * - `\"firstElement\"` focuses the first tabbable element within.\n\t * - `\"firstInputElement\"` focuses the first value control within.\n\t * - `true` focuses the element itself.\n\t * - `false` does nothing and _should not be used unless an accessible\n\t * substitute behavior is implemented_.\n\t *\n\t * @default 'firstElement'\n\t */\n\tfocusOnMount?: useFocusOnMount.Mode;\n\t/**\n\t * Determines whether tabbing is constrained to within the popover,\n\t * preventing keyboard focus from leaving the popover content without\n\t * explicit focus elsewhere, or whether the popover remains part of the\n\t * wider tab order.\n\t * If no value is passed, it will be derived from `focusOnMount`.\n\t *\n\t * @see focusOnMount\n\t * @default `focusOnMount` !== false\n\t */\n\tconstrainTabbing?: boolean;\n\tonClose?: () => void;\n\t/**\n\t * Use the `onClose` prop instead.\n\t *\n\t * @deprecated\n\t */\n\t__unstableOnClose?: (\n\t\ttype: string | undefined,\n\t\tevent: SyntheticEvent\n\t) => void;\n};\n\ntype useDialogReturn = [\n\tRefCallback< HTMLElement >,\n\tReturnType< typeof useFocusOutside > & Pick< HTMLElement, 'tabIndex' >,\n];\n\n/**\n * Returns a ref and props to apply to a dialog wrapper to enable the following behaviors:\n * - constrained tabbing.\n * - focus on mount.\n * - return focus on unmount.\n * - focus outside.\n *\n * @param options Dialog Options.\n */\nfunction useDialog( options: DialogOptions ): useDialogReturn {\n\tconst currentOptions = useRef< DialogOptions >( undefined );\n\tconst { constrainTabbing = options.focusOnMount !== false } = options;\n\tuseEffect( () => {\n\t\tcurrentOptions.current = options;\n\t}, Object.values( options ) );\n\tconst constrainedTabbingRef = useConstrainedTabbing();\n\tconst focusOnMountRef = useFocusOnMount( options.focusOnMount );\n\tconst focusReturnRef = useFocusReturn();\n\tconst focusOutsideProps = useFocusOutside( ( event ) => {\n\t\t// This unstable prop is here only to manage backward compatibility\n\t\t// for the Popover component otherwise, the onClose should be enough.\n\t\tif ( currentOptions.current?.__unstableOnClose ) {\n\t\t\tcurrentOptions.current.__unstableOnClose( 'focus-outside', event );\n\t\t} else if ( currentOptions.current?.onClose ) {\n\t\t\tcurrentOptions.current.onClose();\n\t\t}\n\t} );\n\tconst closeOnEscapeRef = useCallback( ( node: HTMLElement ) => {\n\t\tif ( ! node ) {\n\t\t\treturn;\n\t\t}\n\n\t\tnode.addEventListener( 'keydown', ( event: KeyboardEvent ) => {\n\t\t\t// Close on escape.\n\t\t\tif (\n\t\t\t\tevent.keyCode === ESCAPE &&\n\t\t\t\t! event.defaultPrevented &&\n\t\t\t\tcurrentOptions.current?.onClose\n\t\t\t) {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tcurrentOptions.current.onClose();\n\t\t\t}\n\t\t} );\n\t}, [] );\n\n\treturn [\n\t\tuseMergeRefs( [\n\t\t\tconstrainTabbing ? constrainedTabbingRef : null,\n\t\t\toptions.focusOnMount !== false ? focusReturnRef : null,\n\t\t\toptions.focusOnMount !== false ? focusOnMountRef : null,\n\t\t\tcloseOnEscapeRef,\n\t\t] ),\n\t\t{\n\t\t\t...focusOutsideProps,\n\t\t\ttabIndex: -1,\n\t\t},\n\t];\n}\n\nexport default useDialog;\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAQA,qBAA+C;AAC/C,sBAAuB;AAKvB,qCAAkC;AAClC,gCAAgC;AAChC,8BAA2B;AAC3B,+BAA4B;AAC5B,4BAAyB;AAoDzB,SAAS,UAAW,SAA0C;AAC7D,QAAM,qBAAiB,uBAAyB,MAAU;AAC1D,QAAM,EAAE,mBAAmB,QAAQ,iBAAiB,MAAM,IAAI;AAC9D,gCAAW,MAAM;AAChB,mBAAe,UAAU;AAAA,EAC1B,GAAG,OAAO,OAAQ,OAAQ,CAAE;AAC5B,QAAM,4BAAwB,+BAAAA,SAAsB;AACpD,QAAM,sBAAkB,2CAAiB,QAAQ,YAAa;AAC9D,QAAM,qBAAiB,wBAAAC,SAAe;AACtC,QAAM,wBAAoB,yBAAAC,SAAiB,CAAE,UAAW;AAGvD,QAAK,eAAe,SAAS,mBAAoB;AAChD,qBAAe,QAAQ,kBAAmB,iBAAiB,KAAM;AAAA,IAClE,WAAY,eAAe,SAAS,SAAU;AAC7C,qBAAe,QAAQ,QAAQ;AAAA,IAChC;AAAA,EACD,CAAE;AACF,QAAM,uBAAmB,4BAAa,CAAE,SAAuB;AAC9D,QAAK,CAAE,MAAO;AACb;AAAA,IACD;AAEA,SAAK,iBAAkB,WAAW,CAAE,UAA0B;AAE7D,UACC,MAAM,YAAY,0BAClB,CAAE,MAAM,oBACR,eAAe,SAAS,SACvB;AACD,cAAM,eAAe;AACrB,uBAAe,QAAQ,QAAQ;AAAA,MAChC;AAAA,IACD,CAAE;AAAA,EACH,GAAG,CAAC,CAAE;AAEN,SAAO;AAAA,QACN,sBAAAC,SAAc;AAAA,MACb,mBAAmB,wBAAwB;AAAA,MAC3C,QAAQ,iBAAiB,QAAQ,iBAAiB;AAAA,MAClD,QAAQ,iBAAiB,QAAQ,kBAAkB;AAAA,MACnD;AAAA,IACD,CAAE;AAAA,IACF;AAAA,MACC,GAAG;AAAA,MACH,UAAU;AAAA,IACX;AAAA,EACD;AACD;AAEA,IAAO,qBAAQ;",
4
+ "sourcesContent": ["/**\n * External dependencies\n */\nimport type { RefCallback, SyntheticEvent } from 'react';\n\n/**\n * WordPress dependencies\n */\nimport { useRef, useEffect, useCallback } from '@wordpress/element';\nimport { ESCAPE } from '@wordpress/keycodes';\n\n/**\n * Internal dependencies\n */\nimport useConstrainedTabbing from '../use-constrained-tabbing';\nimport { useFocusOnMount } from '../use-focus-on-mount';\nimport useFocusReturn from '../use-focus-return';\nimport useFocusOutside from '../use-focus-outside';\nimport useMergeRefs from '../use-merge-refs';\n\ntype DialogOptions = {\n\t/**\n\t * Determines focus behavior when the dialog mounts.\n\t *\n\t * - `\"firstElement\"` focuses the first tabbable element within.\n\t * - `\"firstInputElement\"` focuses the first value control within.\n\t * - `true` focuses the element itself.\n\t * - `false` does nothing and _should not be used unless an accessible\n\t * substitute behavior is implemented_.\n\t *\n\t * @default 'firstElement'\n\t */\n\tfocusOnMount?: useFocusOnMount.Mode;\n\t/**\n\t * Determines whether tabbing is constrained to within the popover,\n\t * preventing keyboard focus from leaving the popover content without\n\t * explicit focus elsewhere, or whether the popover remains part of the\n\t * wider tab order.\n\t * If no value is passed, it will be derived from `focusOnMount`.\n\t *\n\t * @see focusOnMount\n\t * @default `focusOnMount` !== false\n\t */\n\tconstrainTabbing?: boolean;\n\tonClose?: () => void;\n\t/**\n\t * Use the `onClose` prop instead.\n\t *\n\t * @deprecated\n\t */\n\t__unstableOnClose?: (\n\t\ttype: string | undefined,\n\t\tevent: SyntheticEvent\n\t) => void;\n};\n\ntype useDialogReturn = [\n\tRefCallback< HTMLElement >,\n\tReturnType< typeof useFocusOutside > & Pick< HTMLElement, 'tabIndex' >,\n];\n\n/**\n * Returns a ref and props to apply to a dialog wrapper to enable the following behaviors:\n * - constrained tabbing.\n * - focus on mount.\n * - return focus on unmount.\n * - focus outside.\n *\n * @param options Dialog Options.\n */\nfunction useDialog( options: DialogOptions ): useDialogReturn {\n\tconst currentOptions = useRef< DialogOptions >( undefined );\n\tconst { constrainTabbing = options.focusOnMount !== false } = options;\n\tuseEffect( () => {\n\t\tcurrentOptions.current = options;\n\t}, Object.values( options ) );\n\tconst constrainedTabbingRef = useConstrainedTabbing();\n\tconst focusOnMountRef = useFocusOnMount( options.focusOnMount );\n\tconst focusReturnRef = useFocusReturn();\n\tconst focusOutsideProps = useFocusOutside( ( event ) => {\n\t\t// This unstable prop is here only to manage backward compatibility\n\t\t// for the Popover component otherwise, the onClose should be enough.\n\t\tif ( currentOptions.current?.__unstableOnClose ) {\n\t\t\tcurrentOptions.current.__unstableOnClose( 'focus-outside', event );\n\t\t} else if ( currentOptions.current?.onClose ) {\n\t\t\tcurrentOptions.current.onClose();\n\t\t}\n\t} );\n\tconst closeOnEscapeRef = useCallback( ( node: HTMLElement ) => {\n\t\tif ( ! node ) {\n\t\t\treturn;\n\t\t}\n\n\t\tnode.addEventListener( 'keydown', ( event: KeyboardEvent ) => {\n\t\t\t// Close on escape.\n\t\t\tif (\n\t\t\t\tevent.keyCode === ESCAPE &&\n\t\t\t\t! event.defaultPrevented &&\n\t\t\t\tcurrentOptions.current?.onClose\n\t\t\t) {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tevent.stopPropagation();\n\t\t\t\tcurrentOptions.current.onClose();\n\t\t\t}\n\t\t} );\n\t}, [] );\n\n\treturn [\n\t\tuseMergeRefs( [\n\t\t\tconstrainTabbing ? constrainedTabbingRef : null,\n\t\t\toptions.focusOnMount !== false ? focusReturnRef : null,\n\t\t\toptions.focusOnMount !== false ? focusOnMountRef : null,\n\t\t\tcloseOnEscapeRef,\n\t\t] ),\n\t\t{\n\t\t\t...focusOutsideProps,\n\t\t\ttabIndex: -1,\n\t\t},\n\t];\n}\n\nexport default useDialog;\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAQA,qBAA+C;AAC/C,sBAAuB;AAKvB,qCAAkC;AAClC,gCAAgC;AAChC,8BAA2B;AAC3B,+BAA4B;AAC5B,4BAAyB;AAoDzB,SAAS,UAAW,SAA0C;AAC7D,QAAM,qBAAiB,uBAAyB,MAAU;AAC1D,QAAM,EAAE,mBAAmB,QAAQ,iBAAiB,MAAM,IAAI;AAC9D,gCAAW,MAAM;AAChB,mBAAe,UAAU;AAAA,EAC1B,GAAG,OAAO,OAAQ,OAAQ,CAAE;AAC5B,QAAM,4BAAwB,+BAAAA,SAAsB;AACpD,QAAM,sBAAkB,2CAAiB,QAAQ,YAAa;AAC9D,QAAM,qBAAiB,wBAAAC,SAAe;AACtC,QAAM,wBAAoB,yBAAAC,SAAiB,CAAE,UAAW;AAGvD,QAAK,eAAe,SAAS,mBAAoB;AAChD,qBAAe,QAAQ,kBAAmB,iBAAiB,KAAM;AAAA,IAClE,WAAY,eAAe,SAAS,SAAU;AAC7C,qBAAe,QAAQ,QAAQ;AAAA,IAChC;AAAA,EACD,CAAE;AACF,QAAM,uBAAmB,4BAAa,CAAE,SAAuB;AAC9D,QAAK,CAAE,MAAO;AACb;AAAA,IACD;AAEA,SAAK,iBAAkB,WAAW,CAAE,UAA0B;AAE7D,UACC,MAAM,YAAY,0BAClB,CAAE,MAAM,oBACR,eAAe,SAAS,SACvB;AACD,cAAM,eAAe;AACrB,cAAM,gBAAgB;AACtB,uBAAe,QAAQ,QAAQ;AAAA,MAChC;AAAA,IACD,CAAE;AAAA,EACH,GAAG,CAAC,CAAE;AAEN,SAAO;AAAA,QACN,sBAAAC,SAAc;AAAA,MACb,mBAAmB,wBAAwB;AAAA,MAC3C,QAAQ,iBAAiB,QAAQ,iBAAiB;AAAA,MAClD,QAAQ,iBAAiB,QAAQ,kBAAkB;AAAA,MACnD;AAAA,IACD,CAAE;AAAA,IACF;AAAA,MACC,GAAG;AAAA,MACH,UAAU;AAAA,IACX;AAAA,EACD;AACD;AAEA,IAAO,qBAAQ;",
6
6
  "names": ["useConstrainedTabbing", "useFocusReturn", "useFocusOutside", "useMergeRefs"]
7
7
  }
@@ -17,34 +17,37 @@ var __copyProps = (to, from, except, desc) => {
17
17
  };
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
 
20
- // packages/compose/src/hooks/use-media-query/index.js
20
+ // packages/compose/src/hooks/use-media-query/index.ts
21
21
  var use_media_query_exports = {};
22
22
  __export(use_media_query_exports, {
23
23
  default: () => useMediaQuery
24
24
  });
25
25
  module.exports = __toCommonJS(use_media_query_exports);
26
26
  var import_element = require("@wordpress/element");
27
- var matchMediaCache = /* @__PURE__ */ new Map();
28
- function getMediaQueryList(query) {
27
+ var perWindowCache = /* @__PURE__ */ new WeakMap();
28
+ function getMediaQueryList(view, query) {
29
29
  if (!query) {
30
30
  return null;
31
31
  }
32
+ const matchMediaCache = perWindowCache.get(view) ?? /* @__PURE__ */ new Map();
33
+ if (!perWindowCache.has(view)) {
34
+ perWindowCache.set(view, matchMediaCache);
35
+ }
32
36
  let match = matchMediaCache.get(query);
33
37
  if (match) {
34
38
  return match;
35
39
  }
36
- if (typeof window !== "undefined" && typeof window.matchMedia === "function") {
37
- match = window.matchMedia(query);
40
+ if (typeof view?.matchMedia === "function") {
41
+ match = view.matchMedia(query);
38
42
  matchMediaCache.set(query, match);
39
43
  return match;
40
44
  }
41
45
  return null;
42
46
  }
43
- function useMediaQuery(query) {
47
+ function useMediaQuery(query, view = window) {
44
48
  const source = (0, import_element.useMemo)(() => {
45
- const mediaQueryList = getMediaQueryList(query);
49
+ const mediaQueryList = getMediaQueryList(view, query);
46
50
  return {
47
- /** @type {(onStoreChange: () => void) => () => void} */
48
51
  subscribe(onStoreChange) {
49
52
  if (!mediaQueryList) {
50
53
  return () => {
@@ -62,7 +65,7 @@ function useMediaQuery(query) {
62
65
  return mediaQueryList?.matches ?? false;
63
66
  }
64
67
  };
65
- }, [query]);
68
+ }, [view, query]);
66
69
  return (0, import_element.useSyncExternalStore)(
67
70
  source.subscribe,
68
71
  source.getValue,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../../src/hooks/use-media-query/index.js"],
4
- "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { useMemo, useSyncExternalStore } from '@wordpress/element';\n\nconst matchMediaCache = new Map();\n\n/**\n * A new MediaQueryList object for the media query\n *\n * @param {string} [query] Media Query.\n * @return {MediaQueryList|null} A new object for the media query\n */\nfunction getMediaQueryList( query ) {\n\tif ( ! query ) {\n\t\treturn null;\n\t}\n\n\tlet match = matchMediaCache.get( query );\n\n\tif ( match ) {\n\t\treturn match;\n\t}\n\n\tif (\n\t\ttypeof window !== 'undefined' &&\n\t\ttypeof window.matchMedia === 'function'\n\t) {\n\t\tmatch = window.matchMedia( query );\n\t\tmatchMediaCache.set( query, match );\n\t\treturn match;\n\t}\n\n\treturn null;\n}\n\n/**\n * Runs a media query and returns its value when it changes.\n *\n * @param {string} [query] Media Query.\n * @return {boolean} return value of the media query.\n */\nexport default function useMediaQuery( query ) {\n\tconst source = useMemo( () => {\n\t\tconst mediaQueryList = getMediaQueryList( query );\n\n\t\treturn {\n\t\t\t/** @type {(onStoreChange: () => void) => () => void} */\n\t\t\tsubscribe( onStoreChange ) {\n\t\t\t\tif ( ! mediaQueryList ) {\n\t\t\t\t\treturn () => {};\n\t\t\t\t}\n\n\t\t\t\t// Avoid a fatal error when browsers don't support `addEventListener` on MediaQueryList.\n\t\t\t\tmediaQueryList.addEventListener?.( 'change', onStoreChange );\n\t\t\t\treturn () => {\n\t\t\t\t\tmediaQueryList.removeEventListener?.(\n\t\t\t\t\t\t'change',\n\t\t\t\t\t\tonStoreChange\n\t\t\t\t\t);\n\t\t\t\t};\n\t\t\t},\n\t\t\tgetValue() {\n\t\t\t\treturn mediaQueryList?.matches ?? false;\n\t\t\t},\n\t\t};\n\t}, [ query ] );\n\n\treturn useSyncExternalStore(\n\t\tsource.subscribe,\n\t\tsource.getValue,\n\t\t() => false\n\t);\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,qBAA8C;AAE9C,IAAM,kBAAkB,oBAAI,IAAI;AAQhC,SAAS,kBAAmB,OAAQ;AACnC,MAAK,CAAE,OAAQ;AACd,WAAO;AAAA,EACR;AAEA,MAAI,QAAQ,gBAAgB,IAAK,KAAM;AAEvC,MAAK,OAAQ;AACZ,WAAO;AAAA,EACR;AAEA,MACC,OAAO,WAAW,eAClB,OAAO,OAAO,eAAe,YAC5B;AACD,YAAQ,OAAO,WAAY,KAAM;AACjC,oBAAgB,IAAK,OAAO,KAAM;AAClC,WAAO;AAAA,EACR;AAEA,SAAO;AACR;AAQe,SAAR,cAAgC,OAAQ;AAC9C,QAAM,aAAS,wBAAS,MAAM;AAC7B,UAAM,iBAAiB,kBAAmB,KAAM;AAEhD,WAAO;AAAA;AAAA,MAEN,UAAW,eAAgB;AAC1B,YAAK,CAAE,gBAAiB;AACvB,iBAAO,MAAM;AAAA,UAAC;AAAA,QACf;AAGA,uBAAe,mBAAoB,UAAU,aAAc;AAC3D,eAAO,MAAM;AACZ,yBAAe;AAAA,YACd;AAAA,YACA;AAAA,UACD;AAAA,QACD;AAAA,MACD;AAAA,MACA,WAAW;AACV,eAAO,gBAAgB,WAAW;AAAA,MACnC;AAAA,IACD;AAAA,EACD,GAAG,CAAE,KAAM,CAAE;AAEb,aAAO;AAAA,IACN,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,EACP;AACD;",
3
+ "sources": ["../../../src/hooks/use-media-query/index.ts"],
4
+ "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { useMemo, useSyncExternalStore } from '@wordpress/element';\n\ntype MQLCache = Map< string, MediaQueryList >;\n\nconst perWindowCache = new WeakMap< Window, MQLCache >();\n\n/**\n * A new MediaQueryList object for the media query\n *\n * @param view Window.\n * @param [query] Media Query.\n */\nfunction getMediaQueryList(\n\tview: Window,\n\tquery?: string\n): MediaQueryList | null {\n\tif ( ! query ) {\n\t\treturn null;\n\t}\n\n\tconst matchMediaCache: MQLCache = perWindowCache.get( view ) ?? new Map();\n\n\tif ( ! perWindowCache.has( view ) ) {\n\t\tperWindowCache.set( view, matchMediaCache );\n\t}\n\n\tlet match = matchMediaCache.get( query );\n\n\tif ( match ) {\n\t\treturn match;\n\t}\n\n\tif ( typeof view?.matchMedia === 'function' ) {\n\t\tmatch = view.matchMedia( query );\n\t\tmatchMediaCache.set( query, match );\n\t\treturn match;\n\t}\n\n\treturn null;\n}\n\n/**\n * Runs a media query and returns its value when it changes.\n *\n * @param [query] Media Query.\n * @param [view] Window instance, else default to global window\n * @return return value of the media query.\n */\nexport default function useMediaQuery(\n\tquery?: string,\n\tview: Window = window\n): boolean {\n\tconst source = useMemo( () => {\n\t\tconst mediaQueryList = getMediaQueryList( view, query );\n\n\t\treturn {\n\t\t\tsubscribe( onStoreChange: any ) {\n\t\t\t\tif ( ! mediaQueryList ) {\n\t\t\t\t\treturn () => {};\n\t\t\t\t}\n\n\t\t\t\t// Avoid a fatal error when browsers don't support `addEventListener` on MediaQueryList.\n\t\t\t\tmediaQueryList.addEventListener?.( 'change', onStoreChange );\n\t\t\t\treturn () => {\n\t\t\t\t\tmediaQueryList.removeEventListener?.(\n\t\t\t\t\t\t'change',\n\t\t\t\t\t\tonStoreChange\n\t\t\t\t\t);\n\t\t\t\t};\n\t\t\t},\n\t\t\tgetValue() {\n\t\t\t\treturn mediaQueryList?.matches ?? false;\n\t\t\t},\n\t\t};\n\t}, [ view, query ] );\n\n\treturn useSyncExternalStore(\n\t\tsource.subscribe,\n\t\tsource.getValue,\n\t\t() => false\n\t);\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,qBAA8C;AAI9C,IAAM,iBAAiB,oBAAI,QAA4B;AAQvD,SAAS,kBACR,MACA,OACwB;AACxB,MAAK,CAAE,OAAQ;AACd,WAAO;AAAA,EACR;AAEA,QAAM,kBAA4B,eAAe,IAAK,IAAK,KAAK,oBAAI,IAAI;AAExE,MAAK,CAAE,eAAe,IAAK,IAAK,GAAI;AACnC,mBAAe,IAAK,MAAM,eAAgB;AAAA,EAC3C;AAEA,MAAI,QAAQ,gBAAgB,IAAK,KAAM;AAEvC,MAAK,OAAQ;AACZ,WAAO;AAAA,EACR;AAEA,MAAK,OAAO,MAAM,eAAe,YAAa;AAC7C,YAAQ,KAAK,WAAY,KAAM;AAC/B,oBAAgB,IAAK,OAAO,KAAM;AAClC,WAAO;AAAA,EACR;AAEA,SAAO;AACR;AASe,SAAR,cACN,OACA,OAAe,QACL;AACV,QAAM,aAAS,wBAAS,MAAM;AAC7B,UAAM,iBAAiB,kBAAmB,MAAM,KAAM;AAEtD,WAAO;AAAA,MACN,UAAW,eAAqB;AAC/B,YAAK,CAAE,gBAAiB;AACvB,iBAAO,MAAM;AAAA,UAAC;AAAA,QACf;AAGA,uBAAe,mBAAoB,UAAU,aAAc;AAC3D,eAAO,MAAM;AACZ,yBAAe;AAAA,YACd;AAAA,YACA;AAAA,UACD;AAAA,QACD;AAAA,MACD;AAAA,MACA,WAAW;AACV,eAAO,gBAAgB,WAAW;AAAA,MACnC;AAAA,IACD;AAAA,EACD,GAAG,CAAE,MAAM,KAAM,CAAE;AAEnB,aAAO;AAAA,IACN,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,EACP;AACD;",
6
6
  "names": []
7
7
  }
@@ -58,10 +58,10 @@ var ViewportMatchWidthContext = (0, import_element.createContext)(
58
58
  null
59
59
  );
60
60
  ViewportMatchWidthContext.displayName = "ViewportMatchWidthContext";
61
- var useViewportMatch = (breakpoint, operator = ">=") => {
61
+ var useViewportMatch = (breakpoint, operator = ">=", view = window) => {
62
62
  const simulatedWidth = (0, import_element.useContext)(ViewportMatchWidthContext);
63
63
  const mediaQuery = !simulatedWidth && `(${CONDITIONS[operator]}: ${BREAKPOINTS[breakpoint]}px)`;
64
- const mediaQueryResult = (0, import_use_media_query.default)(mediaQuery || void 0);
64
+ const mediaQueryResult = (0, import_use_media_query.default)(mediaQuery || void 0, view);
65
65
  if (simulatedWidth) {
66
66
  return OPERATOR_EVALUATORS[operator](
67
67
  BREAKPOINTS[breakpoint],
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/hooks/use-viewport-match/index.js"],
4
- "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { createContext, useContext } from '@wordpress/element';\n\n/**\n * Internal dependencies\n */\nimport useMediaQuery from '../use-media-query';\n\n/**\n * @typedef {\"xhuge\" | \"huge\" | \"wide\" | \"xlarge\" | \"large\" | \"medium\" | \"small\" | \"mobile\"} WPBreakpoint\n */\n\n/**\n * Hash of breakpoint names with pixel width at which it becomes effective.\n *\n * @see _breakpoints.scss\n *\n * @type {Record<WPBreakpoint, number>}\n */\nconst BREAKPOINTS = {\n\txhuge: 1920,\n\thuge: 1440,\n\twide: 1280,\n\txlarge: 1080,\n\tlarge: 960,\n\tmedium: 782,\n\tsmall: 600,\n\tmobile: 480,\n};\n\n/**\n * @typedef {\">=\" | \"<\"} WPViewportOperator\n */\n\n/**\n * Object mapping media query operators to the condition to be used.\n *\n * @type {Record<WPViewportOperator, string>}\n */\nconst CONDITIONS = {\n\t'>=': 'min-width',\n\t'<': 'max-width',\n};\n\n/**\n * Object mapping media query operators to a function that given a breakpointValue and a width evaluates if the operator matches the values.\n *\n * @type {Record<WPViewportOperator, (breakpointValue: number, width: number) => boolean>}\n */\nconst OPERATOR_EVALUATORS = {\n\t'>=': ( breakpointValue, width ) => width >= breakpointValue,\n\t'<': ( breakpointValue, width ) => width < breakpointValue,\n};\n\nconst ViewportMatchWidthContext = createContext(\n\t/** @type {null | number} */ ( null )\n);\nViewportMatchWidthContext.displayName = 'ViewportMatchWidthContext';\n\n/**\n * Returns true if the viewport matches the given query, or false otherwise.\n *\n * @param {WPBreakpoint} breakpoint Breakpoint size name.\n * @param {WPViewportOperator} [operator=\">=\"] Viewport operator.\n *\n * @example\n *\n * ```js\n * useViewportMatch( 'huge', '<' );\n * useViewportMatch( 'medium' );\n * ```\n *\n * @return {boolean} Whether viewport matches query.\n */\nconst useViewportMatch = ( breakpoint, operator = '>=' ) => {\n\tconst simulatedWidth = useContext( ViewportMatchWidthContext );\n\tconst mediaQuery =\n\t\t! simulatedWidth &&\n\t\t`(${ CONDITIONS[ operator ] }: ${ BREAKPOINTS[ breakpoint ] }px)`;\n\tconst mediaQueryResult = useMediaQuery( mediaQuery || undefined );\n\tif ( simulatedWidth ) {\n\t\treturn OPERATOR_EVALUATORS[ operator ](\n\t\t\tBREAKPOINTS[ breakpoint ],\n\t\t\tsimulatedWidth\n\t\t);\n\t}\n\treturn mediaQueryResult;\n};\n\nuseViewportMatch.__experimentalWidthProvider =\n\tViewportMatchWidthContext.Provider;\n\nexport default useViewportMatch;\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,qBAA0C;AAK1C,6BAA0B;AAa1B,IAAM,cAAc;AAAA,EACnB,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AACT;AAWA,IAAM,aAAa;AAAA,EAClB,MAAM;AAAA,EACN,KAAK;AACN;AAOA,IAAM,sBAAsB;AAAA,EAC3B,MAAM,CAAE,iBAAiB,UAAW,SAAS;AAAA,EAC7C,KAAK,CAAE,iBAAiB,UAAW,QAAQ;AAC5C;AAEA,IAAM,gCAA4B;AAAA;AAAA,EACF;AAChC;AACA,0BAA0B,cAAc;AAiBxC,IAAM,mBAAmB,CAAE,YAAY,WAAW,SAAU;AAC3D,QAAM,qBAAiB,2BAAY,yBAA0B;AAC7D,QAAM,aACL,CAAE,kBACF,IAAK,WAAY,QAAS,CAAE,KAAM,YAAa,UAAW,CAAE;AAC7D,QAAM,uBAAmB,uBAAAA,SAAe,cAAc,MAAU;AAChE,MAAK,gBAAiB;AACrB,WAAO,oBAAqB,QAAS;AAAA,MACpC,YAAa,UAAW;AAAA,MACxB;AAAA,IACD;AAAA,EACD;AACA,SAAO;AACR;AAEA,iBAAiB,8BAChB,0BAA0B;AAE3B,IAAO,6BAAQ;",
4
+ "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { createContext, useContext } from '@wordpress/element';\n\n/**\n * Internal dependencies\n */\nimport useMediaQuery from '../use-media-query';\n\n/**\n * @typedef {\"xhuge\" | \"huge\" | \"wide\" | \"xlarge\" | \"large\" | \"medium\" | \"small\" | \"mobile\"} WPBreakpoint\n */\n\n/**\n * Hash of breakpoint names with pixel width at which it becomes effective.\n *\n * @see _breakpoints.scss\n *\n * @type {Record<WPBreakpoint, number>}\n */\nconst BREAKPOINTS = {\n\txhuge: 1920,\n\thuge: 1440,\n\twide: 1280,\n\txlarge: 1080,\n\tlarge: 960,\n\tmedium: 782,\n\tsmall: 600,\n\tmobile: 480,\n};\n\n/**\n * @typedef {\">=\" | \"<\"} WPViewportOperator\n */\n\n/**\n * Object mapping media query operators to the condition to be used.\n *\n * @type {Record<WPViewportOperator, string>}\n */\nconst CONDITIONS = {\n\t'>=': 'min-width',\n\t'<': 'max-width',\n};\n\n/**\n * Object mapping media query operators to a function that given a breakpointValue and a width evaluates if the operator matches the values.\n *\n * @type {Record<WPViewportOperator, (breakpointValue: number, width: number) => boolean>}\n */\nconst OPERATOR_EVALUATORS = {\n\t'>=': ( breakpointValue, width ) => width >= breakpointValue,\n\t'<': ( breakpointValue, width ) => width < breakpointValue,\n};\n\nconst ViewportMatchWidthContext = createContext(\n\t/** @type {null | number} */ ( null )\n);\nViewportMatchWidthContext.displayName = 'ViewportMatchWidthContext';\n\n/**\n * Returns true if the viewport matches the given query, or false otherwise.\n *\n * @param {WPBreakpoint} breakpoint Breakpoint size name.\n * @param {WPViewportOperator} [operator=\">=\"] Viewport operator.\n * @param {Window} [view=window] Window instance in which to perform viewport matching.\n *\n * @example\n *\n * ```js\n * useViewportMatch( 'huge', '<' );\n * useViewportMatch( 'medium' );\n * ```\n *\n * @return {boolean} Whether viewport matches query.\n */\nconst useViewportMatch = ( breakpoint, operator = '>=', view = window ) => {\n\tconst simulatedWidth = useContext( ViewportMatchWidthContext );\n\tconst mediaQuery =\n\t\t! simulatedWidth &&\n\t\t`(${ CONDITIONS[ operator ] }: ${ BREAKPOINTS[ breakpoint ] }px)`;\n\tconst mediaQueryResult = useMediaQuery( mediaQuery || undefined, view );\n\tif ( simulatedWidth ) {\n\t\treturn OPERATOR_EVALUATORS[ operator ](\n\t\t\tBREAKPOINTS[ breakpoint ],\n\t\t\tsimulatedWidth\n\t\t);\n\t}\n\treturn mediaQueryResult;\n};\n\nuseViewportMatch.__experimentalWidthProvider =\n\tViewportMatchWidthContext.Provider;\n\nexport default useViewportMatch;\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,qBAA0C;AAK1C,6BAA0B;AAa1B,IAAM,cAAc;AAAA,EACnB,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AACT;AAWA,IAAM,aAAa;AAAA,EAClB,MAAM;AAAA,EACN,KAAK;AACN;AAOA,IAAM,sBAAsB;AAAA,EAC3B,MAAM,CAAE,iBAAiB,UAAW,SAAS;AAAA,EAC7C,KAAK,CAAE,iBAAiB,UAAW,QAAQ;AAC5C;AAEA,IAAM,gCAA4B;AAAA;AAAA,EACF;AAChC;AACA,0BAA0B,cAAc;AAkBxC,IAAM,mBAAmB,CAAE,YAAY,WAAW,MAAM,OAAO,WAAY;AAC1E,QAAM,qBAAiB,2BAAY,yBAA0B;AAC7D,QAAM,aACL,CAAE,kBACF,IAAK,WAAY,QAAS,CAAE,KAAM,YAAa,UAAW,CAAE;AAC7D,QAAM,uBAAmB,uBAAAA,SAAe,cAAc,QAAW,IAAK;AACtE,MAAK,gBAAiB;AACrB,WAAO,oBAAqB,QAAS;AAAA,MACpC,YAAa,UAAW;AAAA,MACxB;AAAA,IACD;AAAA,EACD;AACA,SAAO;AACR;AAEA,iBAAiB,8BAChB,0BAA0B;AAE3B,IAAO,6BAAQ;",
6
6
  "names": ["useMediaQuery"]
7
7
  }
@@ -29,6 +29,7 @@ function useDialog(options) {
29
29
  node.addEventListener("keydown", (event) => {
30
30
  if (event.keyCode === ESCAPE && !event.defaultPrevented && currentOptions.current?.onClose) {
31
31
  event.preventDefault();
32
+ event.stopPropagation();
32
33
  currentOptions.current.onClose();
33
34
  }
34
35
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/hooks/use-dialog/index.ts"],
4
- "sourcesContent": ["/**\n * External dependencies\n */\nimport type { RefCallback, SyntheticEvent } from 'react';\n\n/**\n * WordPress dependencies\n */\nimport { useRef, useEffect, useCallback } from '@wordpress/element';\nimport { ESCAPE } from '@wordpress/keycodes';\n\n/**\n * Internal dependencies\n */\nimport useConstrainedTabbing from '../use-constrained-tabbing';\nimport { useFocusOnMount } from '../use-focus-on-mount';\nimport useFocusReturn from '../use-focus-return';\nimport useFocusOutside from '../use-focus-outside';\nimport useMergeRefs from '../use-merge-refs';\n\ntype DialogOptions = {\n\t/**\n\t * Determines focus behavior when the dialog mounts.\n\t *\n\t * - `\"firstElement\"` focuses the first tabbable element within.\n\t * - `\"firstInputElement\"` focuses the first value control within.\n\t * - `true` focuses the element itself.\n\t * - `false` does nothing and _should not be used unless an accessible\n\t * substitute behavior is implemented_.\n\t *\n\t * @default 'firstElement'\n\t */\n\tfocusOnMount?: useFocusOnMount.Mode;\n\t/**\n\t * Determines whether tabbing is constrained to within the popover,\n\t * preventing keyboard focus from leaving the popover content without\n\t * explicit focus elsewhere, or whether the popover remains part of the\n\t * wider tab order.\n\t * If no value is passed, it will be derived from `focusOnMount`.\n\t *\n\t * @see focusOnMount\n\t * @default `focusOnMount` !== false\n\t */\n\tconstrainTabbing?: boolean;\n\tonClose?: () => void;\n\t/**\n\t * Use the `onClose` prop instead.\n\t *\n\t * @deprecated\n\t */\n\t__unstableOnClose?: (\n\t\ttype: string | undefined,\n\t\tevent: SyntheticEvent\n\t) => void;\n};\n\ntype useDialogReturn = [\n\tRefCallback< HTMLElement >,\n\tReturnType< typeof useFocusOutside > & Pick< HTMLElement, 'tabIndex' >,\n];\n\n/**\n * Returns a ref and props to apply to a dialog wrapper to enable the following behaviors:\n * - constrained tabbing.\n * - focus on mount.\n * - return focus on unmount.\n * - focus outside.\n *\n * @param options Dialog Options.\n */\nfunction useDialog( options: DialogOptions ): useDialogReturn {\n\tconst currentOptions = useRef< DialogOptions >( undefined );\n\tconst { constrainTabbing = options.focusOnMount !== false } = options;\n\tuseEffect( () => {\n\t\tcurrentOptions.current = options;\n\t}, Object.values( options ) );\n\tconst constrainedTabbingRef = useConstrainedTabbing();\n\tconst focusOnMountRef = useFocusOnMount( options.focusOnMount );\n\tconst focusReturnRef = useFocusReturn();\n\tconst focusOutsideProps = useFocusOutside( ( event ) => {\n\t\t// This unstable prop is here only to manage backward compatibility\n\t\t// for the Popover component otherwise, the onClose should be enough.\n\t\tif ( currentOptions.current?.__unstableOnClose ) {\n\t\t\tcurrentOptions.current.__unstableOnClose( 'focus-outside', event );\n\t\t} else if ( currentOptions.current?.onClose ) {\n\t\t\tcurrentOptions.current.onClose();\n\t\t}\n\t} );\n\tconst closeOnEscapeRef = useCallback( ( node: HTMLElement ) => {\n\t\tif ( ! node ) {\n\t\t\treturn;\n\t\t}\n\n\t\tnode.addEventListener( 'keydown', ( event: KeyboardEvent ) => {\n\t\t\t// Close on escape.\n\t\t\tif (\n\t\t\t\tevent.keyCode === ESCAPE &&\n\t\t\t\t! event.defaultPrevented &&\n\t\t\t\tcurrentOptions.current?.onClose\n\t\t\t) {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tcurrentOptions.current.onClose();\n\t\t\t}\n\t\t} );\n\t}, [] );\n\n\treturn [\n\t\tuseMergeRefs( [\n\t\t\tconstrainTabbing ? constrainedTabbingRef : null,\n\t\t\toptions.focusOnMount !== false ? focusReturnRef : null,\n\t\t\toptions.focusOnMount !== false ? focusOnMountRef : null,\n\t\t\tcloseOnEscapeRef,\n\t\t] ),\n\t\t{\n\t\t\t...focusOutsideProps,\n\t\t\ttabIndex: -1,\n\t\t},\n\t];\n}\n\nexport default useDialog;\n"],
5
- "mappings": ";AAQA,SAAS,QAAQ,WAAW,mBAAmB;AAC/C,SAAS,cAAc;AAKvB,OAAO,2BAA2B;AAClC,SAAS,uBAAuB;AAChC,OAAO,oBAAoB;AAC3B,OAAO,qBAAqB;AAC5B,OAAO,kBAAkB;AAoDzB,SAAS,UAAW,SAA0C;AAC7D,QAAM,iBAAiB,OAAyB,MAAU;AAC1D,QAAM,EAAE,mBAAmB,QAAQ,iBAAiB,MAAM,IAAI;AAC9D,YAAW,MAAM;AAChB,mBAAe,UAAU;AAAA,EAC1B,GAAG,OAAO,OAAQ,OAAQ,CAAE;AAC5B,QAAM,wBAAwB,sBAAsB;AACpD,QAAM,kBAAkB,gBAAiB,QAAQ,YAAa;AAC9D,QAAM,iBAAiB,eAAe;AACtC,QAAM,oBAAoB,gBAAiB,CAAE,UAAW;AAGvD,QAAK,eAAe,SAAS,mBAAoB;AAChD,qBAAe,QAAQ,kBAAmB,iBAAiB,KAAM;AAAA,IAClE,WAAY,eAAe,SAAS,SAAU;AAC7C,qBAAe,QAAQ,QAAQ;AAAA,IAChC;AAAA,EACD,CAAE;AACF,QAAM,mBAAmB,YAAa,CAAE,SAAuB;AAC9D,QAAK,CAAE,MAAO;AACb;AAAA,IACD;AAEA,SAAK,iBAAkB,WAAW,CAAE,UAA0B;AAE7D,UACC,MAAM,YAAY,UAClB,CAAE,MAAM,oBACR,eAAe,SAAS,SACvB;AACD,cAAM,eAAe;AACrB,uBAAe,QAAQ,QAAQ;AAAA,MAChC;AAAA,IACD,CAAE;AAAA,EACH,GAAG,CAAC,CAAE;AAEN,SAAO;AAAA,IACN,aAAc;AAAA,MACb,mBAAmB,wBAAwB;AAAA,MAC3C,QAAQ,iBAAiB,QAAQ,iBAAiB;AAAA,MAClD,QAAQ,iBAAiB,QAAQ,kBAAkB;AAAA,MACnD;AAAA,IACD,CAAE;AAAA,IACF;AAAA,MACC,GAAG;AAAA,MACH,UAAU;AAAA,IACX;AAAA,EACD;AACD;AAEA,IAAO,qBAAQ;",
4
+ "sourcesContent": ["/**\n * External dependencies\n */\nimport type { RefCallback, SyntheticEvent } from 'react';\n\n/**\n * WordPress dependencies\n */\nimport { useRef, useEffect, useCallback } from '@wordpress/element';\nimport { ESCAPE } from '@wordpress/keycodes';\n\n/**\n * Internal dependencies\n */\nimport useConstrainedTabbing from '../use-constrained-tabbing';\nimport { useFocusOnMount } from '../use-focus-on-mount';\nimport useFocusReturn from '../use-focus-return';\nimport useFocusOutside from '../use-focus-outside';\nimport useMergeRefs from '../use-merge-refs';\n\ntype DialogOptions = {\n\t/**\n\t * Determines focus behavior when the dialog mounts.\n\t *\n\t * - `\"firstElement\"` focuses the first tabbable element within.\n\t * - `\"firstInputElement\"` focuses the first value control within.\n\t * - `true` focuses the element itself.\n\t * - `false` does nothing and _should not be used unless an accessible\n\t * substitute behavior is implemented_.\n\t *\n\t * @default 'firstElement'\n\t */\n\tfocusOnMount?: useFocusOnMount.Mode;\n\t/**\n\t * Determines whether tabbing is constrained to within the popover,\n\t * preventing keyboard focus from leaving the popover content without\n\t * explicit focus elsewhere, or whether the popover remains part of the\n\t * wider tab order.\n\t * If no value is passed, it will be derived from `focusOnMount`.\n\t *\n\t * @see focusOnMount\n\t * @default `focusOnMount` !== false\n\t */\n\tconstrainTabbing?: boolean;\n\tonClose?: () => void;\n\t/**\n\t * Use the `onClose` prop instead.\n\t *\n\t * @deprecated\n\t */\n\t__unstableOnClose?: (\n\t\ttype: string | undefined,\n\t\tevent: SyntheticEvent\n\t) => void;\n};\n\ntype useDialogReturn = [\n\tRefCallback< HTMLElement >,\n\tReturnType< typeof useFocusOutside > & Pick< HTMLElement, 'tabIndex' >,\n];\n\n/**\n * Returns a ref and props to apply to a dialog wrapper to enable the following behaviors:\n * - constrained tabbing.\n * - focus on mount.\n * - return focus on unmount.\n * - focus outside.\n *\n * @param options Dialog Options.\n */\nfunction useDialog( options: DialogOptions ): useDialogReturn {\n\tconst currentOptions = useRef< DialogOptions >( undefined );\n\tconst { constrainTabbing = options.focusOnMount !== false } = options;\n\tuseEffect( () => {\n\t\tcurrentOptions.current = options;\n\t}, Object.values( options ) );\n\tconst constrainedTabbingRef = useConstrainedTabbing();\n\tconst focusOnMountRef = useFocusOnMount( options.focusOnMount );\n\tconst focusReturnRef = useFocusReturn();\n\tconst focusOutsideProps = useFocusOutside( ( event ) => {\n\t\t// This unstable prop is here only to manage backward compatibility\n\t\t// for the Popover component otherwise, the onClose should be enough.\n\t\tif ( currentOptions.current?.__unstableOnClose ) {\n\t\t\tcurrentOptions.current.__unstableOnClose( 'focus-outside', event );\n\t\t} else if ( currentOptions.current?.onClose ) {\n\t\t\tcurrentOptions.current.onClose();\n\t\t}\n\t} );\n\tconst closeOnEscapeRef = useCallback( ( node: HTMLElement ) => {\n\t\tif ( ! node ) {\n\t\t\treturn;\n\t\t}\n\n\t\tnode.addEventListener( 'keydown', ( event: KeyboardEvent ) => {\n\t\t\t// Close on escape.\n\t\t\tif (\n\t\t\t\tevent.keyCode === ESCAPE &&\n\t\t\t\t! event.defaultPrevented &&\n\t\t\t\tcurrentOptions.current?.onClose\n\t\t\t) {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tevent.stopPropagation();\n\t\t\t\tcurrentOptions.current.onClose();\n\t\t\t}\n\t\t} );\n\t}, [] );\n\n\treturn [\n\t\tuseMergeRefs( [\n\t\t\tconstrainTabbing ? constrainedTabbingRef : null,\n\t\t\toptions.focusOnMount !== false ? focusReturnRef : null,\n\t\t\toptions.focusOnMount !== false ? focusOnMountRef : null,\n\t\t\tcloseOnEscapeRef,\n\t\t] ),\n\t\t{\n\t\t\t...focusOutsideProps,\n\t\t\ttabIndex: -1,\n\t\t},\n\t];\n}\n\nexport default useDialog;\n"],
5
+ "mappings": ";AAQA,SAAS,QAAQ,WAAW,mBAAmB;AAC/C,SAAS,cAAc;AAKvB,OAAO,2BAA2B;AAClC,SAAS,uBAAuB;AAChC,OAAO,oBAAoB;AAC3B,OAAO,qBAAqB;AAC5B,OAAO,kBAAkB;AAoDzB,SAAS,UAAW,SAA0C;AAC7D,QAAM,iBAAiB,OAAyB,MAAU;AAC1D,QAAM,EAAE,mBAAmB,QAAQ,iBAAiB,MAAM,IAAI;AAC9D,YAAW,MAAM;AAChB,mBAAe,UAAU;AAAA,EAC1B,GAAG,OAAO,OAAQ,OAAQ,CAAE;AAC5B,QAAM,wBAAwB,sBAAsB;AACpD,QAAM,kBAAkB,gBAAiB,QAAQ,YAAa;AAC9D,QAAM,iBAAiB,eAAe;AACtC,QAAM,oBAAoB,gBAAiB,CAAE,UAAW;AAGvD,QAAK,eAAe,SAAS,mBAAoB;AAChD,qBAAe,QAAQ,kBAAmB,iBAAiB,KAAM;AAAA,IAClE,WAAY,eAAe,SAAS,SAAU;AAC7C,qBAAe,QAAQ,QAAQ;AAAA,IAChC;AAAA,EACD,CAAE;AACF,QAAM,mBAAmB,YAAa,CAAE,SAAuB;AAC9D,QAAK,CAAE,MAAO;AACb;AAAA,IACD;AAEA,SAAK,iBAAkB,WAAW,CAAE,UAA0B;AAE7D,UACC,MAAM,YAAY,UAClB,CAAE,MAAM,oBACR,eAAe,SAAS,SACvB;AACD,cAAM,eAAe;AACrB,cAAM,gBAAgB;AACtB,uBAAe,QAAQ,QAAQ;AAAA,MAChC;AAAA,IACD,CAAE;AAAA,EACH,GAAG,CAAC,CAAE;AAEN,SAAO;AAAA,IACN,aAAc;AAAA,MACb,mBAAmB,wBAAwB;AAAA,MAC3C,QAAQ,iBAAiB,QAAQ,iBAAiB;AAAA,MAClD,QAAQ,iBAAiB,QAAQ,kBAAkB;AAAA,MACnD;AAAA,IACD,CAAE;AAAA,IACF;AAAA,MACC,GAAG;AAAA,MACH,UAAU;AAAA,IACX;AAAA,EACD;AACD;AAEA,IAAO,qBAAQ;",
6
6
  "names": []
7
7
  }
@@ -1,26 +1,29 @@
1
- // packages/compose/src/hooks/use-media-query/index.js
1
+ // packages/compose/src/hooks/use-media-query/index.ts
2
2
  import { useMemo, useSyncExternalStore } from "@wordpress/element";
3
- var matchMediaCache = /* @__PURE__ */ new Map();
4
- function getMediaQueryList(query) {
3
+ var perWindowCache = /* @__PURE__ */ new WeakMap();
4
+ function getMediaQueryList(view, query) {
5
5
  if (!query) {
6
6
  return null;
7
7
  }
8
+ const matchMediaCache = perWindowCache.get(view) ?? /* @__PURE__ */ new Map();
9
+ if (!perWindowCache.has(view)) {
10
+ perWindowCache.set(view, matchMediaCache);
11
+ }
8
12
  let match = matchMediaCache.get(query);
9
13
  if (match) {
10
14
  return match;
11
15
  }
12
- if (typeof window !== "undefined" && typeof window.matchMedia === "function") {
13
- match = window.matchMedia(query);
16
+ if (typeof view?.matchMedia === "function") {
17
+ match = view.matchMedia(query);
14
18
  matchMediaCache.set(query, match);
15
19
  return match;
16
20
  }
17
21
  return null;
18
22
  }
19
- function useMediaQuery(query) {
23
+ function useMediaQuery(query, view = window) {
20
24
  const source = useMemo(() => {
21
- const mediaQueryList = getMediaQueryList(query);
25
+ const mediaQueryList = getMediaQueryList(view, query);
22
26
  return {
23
- /** @type {(onStoreChange: () => void) => () => void} */
24
27
  subscribe(onStoreChange) {
25
28
  if (!mediaQueryList) {
26
29
  return () => {
@@ -38,7 +41,7 @@ function useMediaQuery(query) {
38
41
  return mediaQueryList?.matches ?? false;
39
42
  }
40
43
  };
41
- }, [query]);
44
+ }, [view, query]);
42
45
  return useSyncExternalStore(
43
46
  source.subscribe,
44
47
  source.getValue,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../../src/hooks/use-media-query/index.js"],
4
- "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { useMemo, useSyncExternalStore } from '@wordpress/element';\n\nconst matchMediaCache = new Map();\n\n/**\n * A new MediaQueryList object for the media query\n *\n * @param {string} [query] Media Query.\n * @return {MediaQueryList|null} A new object for the media query\n */\nfunction getMediaQueryList( query ) {\n\tif ( ! query ) {\n\t\treturn null;\n\t}\n\n\tlet match = matchMediaCache.get( query );\n\n\tif ( match ) {\n\t\treturn match;\n\t}\n\n\tif (\n\t\ttypeof window !== 'undefined' &&\n\t\ttypeof window.matchMedia === 'function'\n\t) {\n\t\tmatch = window.matchMedia( query );\n\t\tmatchMediaCache.set( query, match );\n\t\treturn match;\n\t}\n\n\treturn null;\n}\n\n/**\n * Runs a media query and returns its value when it changes.\n *\n * @param {string} [query] Media Query.\n * @return {boolean} return value of the media query.\n */\nexport default function useMediaQuery( query ) {\n\tconst source = useMemo( () => {\n\t\tconst mediaQueryList = getMediaQueryList( query );\n\n\t\treturn {\n\t\t\t/** @type {(onStoreChange: () => void) => () => void} */\n\t\t\tsubscribe( onStoreChange ) {\n\t\t\t\tif ( ! mediaQueryList ) {\n\t\t\t\t\treturn () => {};\n\t\t\t\t}\n\n\t\t\t\t// Avoid a fatal error when browsers don't support `addEventListener` on MediaQueryList.\n\t\t\t\tmediaQueryList.addEventListener?.( 'change', onStoreChange );\n\t\t\t\treturn () => {\n\t\t\t\t\tmediaQueryList.removeEventListener?.(\n\t\t\t\t\t\t'change',\n\t\t\t\t\t\tonStoreChange\n\t\t\t\t\t);\n\t\t\t\t};\n\t\t\t},\n\t\t\tgetValue() {\n\t\t\t\treturn mediaQueryList?.matches ?? false;\n\t\t\t},\n\t\t};\n\t}, [ query ] );\n\n\treturn useSyncExternalStore(\n\t\tsource.subscribe,\n\t\tsource.getValue,\n\t\t() => false\n\t);\n}\n"],
5
- "mappings": ";AAGA,SAAS,SAAS,4BAA4B;AAE9C,IAAM,kBAAkB,oBAAI,IAAI;AAQhC,SAAS,kBAAmB,OAAQ;AACnC,MAAK,CAAE,OAAQ;AACd,WAAO;AAAA,EACR;AAEA,MAAI,QAAQ,gBAAgB,IAAK,KAAM;AAEvC,MAAK,OAAQ;AACZ,WAAO;AAAA,EACR;AAEA,MACC,OAAO,WAAW,eAClB,OAAO,OAAO,eAAe,YAC5B;AACD,YAAQ,OAAO,WAAY,KAAM;AACjC,oBAAgB,IAAK,OAAO,KAAM;AAClC,WAAO;AAAA,EACR;AAEA,SAAO;AACR;AAQe,SAAR,cAAgC,OAAQ;AAC9C,QAAM,SAAS,QAAS,MAAM;AAC7B,UAAM,iBAAiB,kBAAmB,KAAM;AAEhD,WAAO;AAAA;AAAA,MAEN,UAAW,eAAgB;AAC1B,YAAK,CAAE,gBAAiB;AACvB,iBAAO,MAAM;AAAA,UAAC;AAAA,QACf;AAGA,uBAAe,mBAAoB,UAAU,aAAc;AAC3D,eAAO,MAAM;AACZ,yBAAe;AAAA,YACd;AAAA,YACA;AAAA,UACD;AAAA,QACD;AAAA,MACD;AAAA,MACA,WAAW;AACV,eAAO,gBAAgB,WAAW;AAAA,MACnC;AAAA,IACD;AAAA,EACD,GAAG,CAAE,KAAM,CAAE;AAEb,SAAO;AAAA,IACN,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,EACP;AACD;",
3
+ "sources": ["../../../src/hooks/use-media-query/index.ts"],
4
+ "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { useMemo, useSyncExternalStore } from '@wordpress/element';\n\ntype MQLCache = Map< string, MediaQueryList >;\n\nconst perWindowCache = new WeakMap< Window, MQLCache >();\n\n/**\n * A new MediaQueryList object for the media query\n *\n * @param view Window.\n * @param [query] Media Query.\n */\nfunction getMediaQueryList(\n\tview: Window,\n\tquery?: string\n): MediaQueryList | null {\n\tif ( ! query ) {\n\t\treturn null;\n\t}\n\n\tconst matchMediaCache: MQLCache = perWindowCache.get( view ) ?? new Map();\n\n\tif ( ! perWindowCache.has( view ) ) {\n\t\tperWindowCache.set( view, matchMediaCache );\n\t}\n\n\tlet match = matchMediaCache.get( query );\n\n\tif ( match ) {\n\t\treturn match;\n\t}\n\n\tif ( typeof view?.matchMedia === 'function' ) {\n\t\tmatch = view.matchMedia( query );\n\t\tmatchMediaCache.set( query, match );\n\t\treturn match;\n\t}\n\n\treturn null;\n}\n\n/**\n * Runs a media query and returns its value when it changes.\n *\n * @param [query] Media Query.\n * @param [view] Window instance, else default to global window\n * @return return value of the media query.\n */\nexport default function useMediaQuery(\n\tquery?: string,\n\tview: Window = window\n): boolean {\n\tconst source = useMemo( () => {\n\t\tconst mediaQueryList = getMediaQueryList( view, query );\n\n\t\treturn {\n\t\t\tsubscribe( onStoreChange: any ) {\n\t\t\t\tif ( ! mediaQueryList ) {\n\t\t\t\t\treturn () => {};\n\t\t\t\t}\n\n\t\t\t\t// Avoid a fatal error when browsers don't support `addEventListener` on MediaQueryList.\n\t\t\t\tmediaQueryList.addEventListener?.( 'change', onStoreChange );\n\t\t\t\treturn () => {\n\t\t\t\t\tmediaQueryList.removeEventListener?.(\n\t\t\t\t\t\t'change',\n\t\t\t\t\t\tonStoreChange\n\t\t\t\t\t);\n\t\t\t\t};\n\t\t\t},\n\t\t\tgetValue() {\n\t\t\t\treturn mediaQueryList?.matches ?? false;\n\t\t\t},\n\t\t};\n\t}, [ view, query ] );\n\n\treturn useSyncExternalStore(\n\t\tsource.subscribe,\n\t\tsource.getValue,\n\t\t() => false\n\t);\n}\n"],
5
+ "mappings": ";AAGA,SAAS,SAAS,4BAA4B;AAI9C,IAAM,iBAAiB,oBAAI,QAA4B;AAQvD,SAAS,kBACR,MACA,OACwB;AACxB,MAAK,CAAE,OAAQ;AACd,WAAO;AAAA,EACR;AAEA,QAAM,kBAA4B,eAAe,IAAK,IAAK,KAAK,oBAAI,IAAI;AAExE,MAAK,CAAE,eAAe,IAAK,IAAK,GAAI;AACnC,mBAAe,IAAK,MAAM,eAAgB;AAAA,EAC3C;AAEA,MAAI,QAAQ,gBAAgB,IAAK,KAAM;AAEvC,MAAK,OAAQ;AACZ,WAAO;AAAA,EACR;AAEA,MAAK,OAAO,MAAM,eAAe,YAAa;AAC7C,YAAQ,KAAK,WAAY,KAAM;AAC/B,oBAAgB,IAAK,OAAO,KAAM;AAClC,WAAO;AAAA,EACR;AAEA,SAAO;AACR;AASe,SAAR,cACN,OACA,OAAe,QACL;AACV,QAAM,SAAS,QAAS,MAAM;AAC7B,UAAM,iBAAiB,kBAAmB,MAAM,KAAM;AAEtD,WAAO;AAAA,MACN,UAAW,eAAqB;AAC/B,YAAK,CAAE,gBAAiB;AACvB,iBAAO,MAAM;AAAA,UAAC;AAAA,QACf;AAGA,uBAAe,mBAAoB,UAAU,aAAc;AAC3D,eAAO,MAAM;AACZ,yBAAe;AAAA,YACd;AAAA,YACA;AAAA,UACD;AAAA,QACD;AAAA,MACD;AAAA,MACA,WAAW;AACV,eAAO,gBAAgB,WAAW;AAAA,MACnC;AAAA,IACD;AAAA,EACD,GAAG,CAAE,MAAM,KAAM,CAAE;AAEnB,SAAO;AAAA,IACN,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,EACP;AACD;",
6
6
  "names": []
7
7
  }
@@ -24,10 +24,10 @@ var ViewportMatchWidthContext = createContext(
24
24
  null
25
25
  );
26
26
  ViewportMatchWidthContext.displayName = "ViewportMatchWidthContext";
27
- var useViewportMatch = (breakpoint, operator = ">=") => {
27
+ var useViewportMatch = (breakpoint, operator = ">=", view = window) => {
28
28
  const simulatedWidth = useContext(ViewportMatchWidthContext);
29
29
  const mediaQuery = !simulatedWidth && `(${CONDITIONS[operator]}: ${BREAKPOINTS[breakpoint]}px)`;
30
- const mediaQueryResult = useMediaQuery(mediaQuery || void 0);
30
+ const mediaQueryResult = useMediaQuery(mediaQuery || void 0, view);
31
31
  if (simulatedWidth) {
32
32
  return OPERATOR_EVALUATORS[operator](
33
33
  BREAKPOINTS[breakpoint],
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/hooks/use-viewport-match/index.js"],
4
- "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { createContext, useContext } from '@wordpress/element';\n\n/**\n * Internal dependencies\n */\nimport useMediaQuery from '../use-media-query';\n\n/**\n * @typedef {\"xhuge\" | \"huge\" | \"wide\" | \"xlarge\" | \"large\" | \"medium\" | \"small\" | \"mobile\"} WPBreakpoint\n */\n\n/**\n * Hash of breakpoint names with pixel width at which it becomes effective.\n *\n * @see _breakpoints.scss\n *\n * @type {Record<WPBreakpoint, number>}\n */\nconst BREAKPOINTS = {\n\txhuge: 1920,\n\thuge: 1440,\n\twide: 1280,\n\txlarge: 1080,\n\tlarge: 960,\n\tmedium: 782,\n\tsmall: 600,\n\tmobile: 480,\n};\n\n/**\n * @typedef {\">=\" | \"<\"} WPViewportOperator\n */\n\n/**\n * Object mapping media query operators to the condition to be used.\n *\n * @type {Record<WPViewportOperator, string>}\n */\nconst CONDITIONS = {\n\t'>=': 'min-width',\n\t'<': 'max-width',\n};\n\n/**\n * Object mapping media query operators to a function that given a breakpointValue and a width evaluates if the operator matches the values.\n *\n * @type {Record<WPViewportOperator, (breakpointValue: number, width: number) => boolean>}\n */\nconst OPERATOR_EVALUATORS = {\n\t'>=': ( breakpointValue, width ) => width >= breakpointValue,\n\t'<': ( breakpointValue, width ) => width < breakpointValue,\n};\n\nconst ViewportMatchWidthContext = createContext(\n\t/** @type {null | number} */ ( null )\n);\nViewportMatchWidthContext.displayName = 'ViewportMatchWidthContext';\n\n/**\n * Returns true if the viewport matches the given query, or false otherwise.\n *\n * @param {WPBreakpoint} breakpoint Breakpoint size name.\n * @param {WPViewportOperator} [operator=\">=\"] Viewport operator.\n *\n * @example\n *\n * ```js\n * useViewportMatch( 'huge', '<' );\n * useViewportMatch( 'medium' );\n * ```\n *\n * @return {boolean} Whether viewport matches query.\n */\nconst useViewportMatch = ( breakpoint, operator = '>=' ) => {\n\tconst simulatedWidth = useContext( ViewportMatchWidthContext );\n\tconst mediaQuery =\n\t\t! simulatedWidth &&\n\t\t`(${ CONDITIONS[ operator ] }: ${ BREAKPOINTS[ breakpoint ] }px)`;\n\tconst mediaQueryResult = useMediaQuery( mediaQuery || undefined );\n\tif ( simulatedWidth ) {\n\t\treturn OPERATOR_EVALUATORS[ operator ](\n\t\t\tBREAKPOINTS[ breakpoint ],\n\t\t\tsimulatedWidth\n\t\t);\n\t}\n\treturn mediaQueryResult;\n};\n\nuseViewportMatch.__experimentalWidthProvider =\n\tViewportMatchWidthContext.Provider;\n\nexport default useViewportMatch;\n"],
5
- "mappings": ";AAGA,SAAS,eAAe,kBAAkB;AAK1C,OAAO,mBAAmB;AAa1B,IAAM,cAAc;AAAA,EACnB,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AACT;AAWA,IAAM,aAAa;AAAA,EAClB,MAAM;AAAA,EACN,KAAK;AACN;AAOA,IAAM,sBAAsB;AAAA,EAC3B,MAAM,CAAE,iBAAiB,UAAW,SAAS;AAAA,EAC7C,KAAK,CAAE,iBAAiB,UAAW,QAAQ;AAC5C;AAEA,IAAM,4BAA4B;AAAA;AAAA,EACF;AAChC;AACA,0BAA0B,cAAc;AAiBxC,IAAM,mBAAmB,CAAE,YAAY,WAAW,SAAU;AAC3D,QAAM,iBAAiB,WAAY,yBAA0B;AAC7D,QAAM,aACL,CAAE,kBACF,IAAK,WAAY,QAAS,CAAE,KAAM,YAAa,UAAW,CAAE;AAC7D,QAAM,mBAAmB,cAAe,cAAc,MAAU;AAChE,MAAK,gBAAiB;AACrB,WAAO,oBAAqB,QAAS;AAAA,MACpC,YAAa,UAAW;AAAA,MACxB;AAAA,IACD;AAAA,EACD;AACA,SAAO;AACR;AAEA,iBAAiB,8BAChB,0BAA0B;AAE3B,IAAO,6BAAQ;",
4
+ "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { createContext, useContext } from '@wordpress/element';\n\n/**\n * Internal dependencies\n */\nimport useMediaQuery from '../use-media-query';\n\n/**\n * @typedef {\"xhuge\" | \"huge\" | \"wide\" | \"xlarge\" | \"large\" | \"medium\" | \"small\" | \"mobile\"} WPBreakpoint\n */\n\n/**\n * Hash of breakpoint names with pixel width at which it becomes effective.\n *\n * @see _breakpoints.scss\n *\n * @type {Record<WPBreakpoint, number>}\n */\nconst BREAKPOINTS = {\n\txhuge: 1920,\n\thuge: 1440,\n\twide: 1280,\n\txlarge: 1080,\n\tlarge: 960,\n\tmedium: 782,\n\tsmall: 600,\n\tmobile: 480,\n};\n\n/**\n * @typedef {\">=\" | \"<\"} WPViewportOperator\n */\n\n/**\n * Object mapping media query operators to the condition to be used.\n *\n * @type {Record<WPViewportOperator, string>}\n */\nconst CONDITIONS = {\n\t'>=': 'min-width',\n\t'<': 'max-width',\n};\n\n/**\n * Object mapping media query operators to a function that given a breakpointValue and a width evaluates if the operator matches the values.\n *\n * @type {Record<WPViewportOperator, (breakpointValue: number, width: number) => boolean>}\n */\nconst OPERATOR_EVALUATORS = {\n\t'>=': ( breakpointValue, width ) => width >= breakpointValue,\n\t'<': ( breakpointValue, width ) => width < breakpointValue,\n};\n\nconst ViewportMatchWidthContext = createContext(\n\t/** @type {null | number} */ ( null )\n);\nViewportMatchWidthContext.displayName = 'ViewportMatchWidthContext';\n\n/**\n * Returns true if the viewport matches the given query, or false otherwise.\n *\n * @param {WPBreakpoint} breakpoint Breakpoint size name.\n * @param {WPViewportOperator} [operator=\">=\"] Viewport operator.\n * @param {Window} [view=window] Window instance in which to perform viewport matching.\n *\n * @example\n *\n * ```js\n * useViewportMatch( 'huge', '<' );\n * useViewportMatch( 'medium' );\n * ```\n *\n * @return {boolean} Whether viewport matches query.\n */\nconst useViewportMatch = ( breakpoint, operator = '>=', view = window ) => {\n\tconst simulatedWidth = useContext( ViewportMatchWidthContext );\n\tconst mediaQuery =\n\t\t! simulatedWidth &&\n\t\t`(${ CONDITIONS[ operator ] }: ${ BREAKPOINTS[ breakpoint ] }px)`;\n\tconst mediaQueryResult = useMediaQuery( mediaQuery || undefined, view );\n\tif ( simulatedWidth ) {\n\t\treturn OPERATOR_EVALUATORS[ operator ](\n\t\t\tBREAKPOINTS[ breakpoint ],\n\t\t\tsimulatedWidth\n\t\t);\n\t}\n\treturn mediaQueryResult;\n};\n\nuseViewportMatch.__experimentalWidthProvider =\n\tViewportMatchWidthContext.Provider;\n\nexport default useViewportMatch;\n"],
5
+ "mappings": ";AAGA,SAAS,eAAe,kBAAkB;AAK1C,OAAO,mBAAmB;AAa1B,IAAM,cAAc;AAAA,EACnB,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AACT;AAWA,IAAM,aAAa;AAAA,EAClB,MAAM;AAAA,EACN,KAAK;AACN;AAOA,IAAM,sBAAsB;AAAA,EAC3B,MAAM,CAAE,iBAAiB,UAAW,SAAS;AAAA,EAC7C,KAAK,CAAE,iBAAiB,UAAW,QAAQ;AAC5C;AAEA,IAAM,4BAA4B;AAAA;AAAA,EACF;AAChC;AACA,0BAA0B,cAAc;AAkBxC,IAAM,mBAAmB,CAAE,YAAY,WAAW,MAAM,OAAO,WAAY;AAC1E,QAAM,iBAAiB,WAAY,yBAA0B;AAC7D,QAAM,aACL,CAAE,kBACF,IAAK,WAAY,QAAS,CAAE,KAAM,YAAa,UAAW,CAAE;AAC7D,QAAM,mBAAmB,cAAe,cAAc,QAAW,IAAK;AACtE,MAAK,gBAAiB;AACrB,WAAO,oBAAqB,QAAS;AAAA,MACpC,YAAa,UAAW;AAAA,MACxB;AAAA,IACD;AAAA,EACD;AACA,SAAO;AACR;AAEA,iBAAiB,8BAChB,0BAA0B;AAE3B,IAAO,6BAAQ;",
6
6
  "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-dialog/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAYzD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,eAAe,MAAM,sBAAsB,CAAC;AAGnD,KAAK,aAAa,GAAG;IACpB;;;;;;;;;;OAUG;IACH,YAAY,CAAC,EAAE,eAAe,CAAC,IAAI,CAAC;IACpC;;;;;;;;;OASG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,CACnB,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,KAAK,EAAE,cAAc,KACjB,IAAI,CAAC;CACV,CAAC;AAEF,KAAK,eAAe,GAAG;IACtB,WAAW,CAAE,WAAW,CAAE;IAC1B,UAAU,CAAE,OAAO,eAAe,CAAE,GAAG,IAAI,CAAE,WAAW,EAAE,UAAU,CAAE;CACtE,CAAC;AAEF;;;;;;;;GAQG;AACH,iBAAS,SAAS,CAAE,OAAO,EAAE,aAAa,GAAI,eAAe,CAgD5D;AAED,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-dialog/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAYzD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,eAAe,MAAM,sBAAsB,CAAC;AAGnD,KAAK,aAAa,GAAG;IACpB;;;;;;;;;;OAUG;IACH,YAAY,CAAC,EAAE,eAAe,CAAC,IAAI,CAAC;IACpC;;;;;;;;;OASG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,CACnB,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,KAAK,EAAE,cAAc,KACjB,IAAI,CAAC;CACV,CAAC;AAEF,KAAK,eAAe,GAAG;IACtB,WAAW,CAAE,WAAW,CAAE;IAC1B,UAAU,CAAE,OAAO,eAAe,CAAE,GAAG,IAAI,CAAE,WAAW,EAAE,UAAU,CAAE;CACtE,CAAC;AAEF;;;;;;;;GAQG;AACH,iBAAS,SAAS,CAAE,OAAO,EAAE,aAAa,GAAI,eAAe,CAiD5D;AAED,eAAe,SAAS,CAAC"}
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Runs a media query and returns its value when it changes.
3
3
  *
4
- * @param {string} [query] Media Query.
5
- * @return {boolean} return value of the media query.
4
+ * @param [query] Media Query.
5
+ * @param [view] Window instance, else default to global window
6
+ * @return return value of the media query.
6
7
  */
7
- export default function useMediaQuery(query?: string): boolean;
8
+ export default function useMediaQuery(query?: string, view?: Window): boolean;
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-media-query/index.js"],"names":[],"mappings":"AAoCA;;;;;GAKG;AACH,8CAHW,MAAM,GACL,OAAO,CAiClB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-media-query/index.ts"],"names":[],"mappings":"AA4CA;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,UAAU,aAAa,CACpC,KAAK,CAAC,EAAE,MAAM,EACd,IAAI,GAAE,MAAe,GACnB,OAAO,CA8BT"}
@@ -6,6 +6,7 @@ export type WPViewportOperator = ">=" | "<";
6
6
  *
7
7
  * @param {WPBreakpoint} breakpoint Breakpoint size name.
8
8
  * @param {WPViewportOperator} [operator=">="] Viewport operator.
9
+ * @param {Window} [view=window] Window instance in which to perform viewport matching.
9
10
  *
10
11
  * @example
11
12
  *
@@ -16,7 +17,7 @@ export type WPViewportOperator = ">=" | "<";
16
17
  *
17
18
  * @return {boolean} Whether viewport matches query.
18
19
  */
19
- declare function useViewportMatch(breakpoint: WPBreakpoint, operator?: WPViewportOperator): boolean;
20
+ declare function useViewportMatch(breakpoint: WPBreakpoint, operator?: WPViewportOperator, view?: Window): boolean;
20
21
  declare namespace useViewportMatch {
21
22
  let __experimentalWidthProvider: import("react").Provider<number | null>;
22
23
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-viewport-match/index.js"],"names":[],"mappings":";2BAWa,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ;iCAsB9E,IAAI,GAAG,GAAG;AA4BvB;;;;;;;;;;;;;;GAcG;AACH,8CAZW,YAAY,aACZ,kBAAkB,GASjB,OAAO,CAelB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-viewport-match/index.js"],"names":[],"mappings":";2BAWa,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ;iCAsB9E,IAAI,GAAG,GAAG;AA4BvB;;;;;;;;;;;;;;;GAeG;AACH,8CAbW,YAAY,aACZ,kBAAkB,SAClB,MAAM,GASL,OAAO,CAelB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/compose",
3
- "version": "7.41.1-next.v.202603161435.0+ab4981c4f",
3
+ "version": "7.43.0",
4
4
  "description": "WordPress higher-order components (HOCs).",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -47,13 +47,13 @@
47
47
  "sideEffects": false,
48
48
  "dependencies": {
49
49
  "@types/mousetrap": "^1.6.8",
50
- "@wordpress/deprecated": "^4.41.1-next.v.202603161435.0+ab4981c4f",
51
- "@wordpress/dom": "^4.41.1-next.v.202603161435.0+ab4981c4f",
52
- "@wordpress/element": "^6.41.1-next.v.202603161435.0+ab4981c4f",
53
- "@wordpress/is-shallow-equal": "^5.41.1-next.v.202603161435.0+ab4981c4f",
54
- "@wordpress/keycodes": "^4.41.1-next.v.202603161435.0+ab4981c4f",
55
- "@wordpress/priority-queue": "^3.41.1-next.v.202603161435.0+ab4981c4f",
56
- "@wordpress/undo-manager": "^1.41.1-next.v.202603161435.0+ab4981c4f",
50
+ "@wordpress/deprecated": "^4.43.0",
51
+ "@wordpress/dom": "^4.43.0",
52
+ "@wordpress/element": "^6.43.0",
53
+ "@wordpress/is-shallow-equal": "^5.43.0",
54
+ "@wordpress/keycodes": "^4.43.0",
55
+ "@wordpress/priority-queue": "^3.43.0",
56
+ "@wordpress/undo-manager": "^1.43.0",
57
57
  "change-case": "^4.1.2",
58
58
  "mousetrap": "^1.6.5",
59
59
  "use-memo-one": "^1.1.1"
@@ -67,5 +67,5 @@
67
67
  "publishConfig": {
68
68
  "access": "public"
69
69
  },
70
- "gitHead": "748f4e4564fcc0e6ae90200d90bb993a3cef5828"
70
+ "gitHead": "2cea90674d11aa521ec3f71652fb3a6a4c383969"
71
71
  }
@@ -99,6 +99,7 @@ function useDialog( options: DialogOptions ): useDialogReturn {
99
99
  currentOptions.current?.onClose
100
100
  ) {
101
101
  event.preventDefault();
102
+ event.stopPropagation();
102
103
  currentOptions.current.onClose();
103
104
  }
104
105
  } );
@@ -0,0 +1,148 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { render, screen, fireEvent } from '@testing-library/react';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import useDialog from '..';
10
+
11
+ function Dialog( { onClose }: { onClose?: () => void } ) {
12
+ const [ ref, props ] = useDialog( {
13
+ onClose,
14
+ focusOnMount: false,
15
+ } );
16
+
17
+ return (
18
+ <div ref={ ref } { ...props } role="dialog" aria-label="Test dialog">
19
+ <p>Dialog content</p>
20
+ <button>Inside</button>
21
+ </div>
22
+ );
23
+ }
24
+
25
+ // `useDialog` currently detects the Escape key via the deprecated `keyCode`
26
+ // property (`event.keyCode === 27`). In jsdom, `userEvent.keyboard('[Escape]')`
27
+ // does not set `keyCode` to 27, so we use `fireEvent` to control the event
28
+ // shape. Once the hook is updated to use `event.key === 'Escape'`, these tests
29
+ // should be rewritten to use `userEvent` for more realistic event simulation.
30
+ function pressEscapeOn( element: HTMLElement ) {
31
+ fireEvent.keyDown( element, { keyCode: 27, key: 'Escape' } );
32
+ }
33
+
34
+ describe( 'useDialog', () => {
35
+ it( 'should call onClose when Escape is pressed', () => {
36
+ const onClose = jest.fn();
37
+
38
+ render( <Dialog onClose={ onClose } /> );
39
+
40
+ pressEscapeOn( screen.getByRole( 'dialog' ) );
41
+
42
+ expect( onClose ).toHaveBeenCalledTimes( 1 );
43
+ } );
44
+
45
+ it( 'should not call onClose when Escape is pressed if defaultPrevented', () => {
46
+ const onClose = jest.fn();
47
+
48
+ render( <Dialog onClose={ onClose } /> );
49
+
50
+ const dialog = screen.getByRole( 'dialog' );
51
+ const event = new KeyboardEvent( 'keydown', {
52
+ keyCode: 27,
53
+ bubbles: true,
54
+ cancelable: true,
55
+ } );
56
+ event.preventDefault();
57
+ dialog.dispatchEvent( event );
58
+
59
+ expect( onClose ).not.toHaveBeenCalled();
60
+ } );
61
+
62
+ it( 'should stop Escape event from propagating to parent elements', () => {
63
+ const onClose = jest.fn();
64
+ const parentKeyDownHandler = jest.fn();
65
+
66
+ render(
67
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions
68
+ <div onKeyDown={ parentKeyDownHandler }>
69
+ <Dialog onClose={ onClose } />
70
+ </div>
71
+ );
72
+
73
+ pressEscapeOn( screen.getByRole( 'button', { name: 'Inside' } ) );
74
+
75
+ expect( onClose ).toHaveBeenCalledTimes( 1 );
76
+ expect( parentKeyDownHandler ).not.toHaveBeenCalled();
77
+ } );
78
+
79
+ it( 'should let Escape propagate when there is no onClose handler', () => {
80
+ const parentKeyDownHandler = jest.fn();
81
+
82
+ render(
83
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions
84
+ <div onKeyDown={ parentKeyDownHandler }>
85
+ <Dialog />
86
+ </div>
87
+ );
88
+
89
+ pressEscapeOn( screen.getByRole( 'button', { name: 'Inside' } ) );
90
+
91
+ expect( parentKeyDownHandler ).toHaveBeenCalledTimes( 1 );
92
+ } );
93
+
94
+ it( 'should close only the innermost dialog when nested', () => {
95
+ const outerOnClose = jest.fn();
96
+ const innerOnClose = jest.fn();
97
+
98
+ function NestedDialogs() {
99
+ const [ outerRef, outerProps ] = useDialog( {
100
+ onClose: outerOnClose,
101
+ focusOnMount: false,
102
+ } );
103
+ const [ innerRef, innerProps ] = useDialog( {
104
+ onClose: innerOnClose,
105
+ focusOnMount: false,
106
+ } );
107
+
108
+ return (
109
+ <div
110
+ ref={ outerRef }
111
+ { ...outerProps }
112
+ role="dialog"
113
+ aria-label="Outer"
114
+ >
115
+ <div
116
+ ref={ innerRef }
117
+ { ...innerProps }
118
+ role="dialog"
119
+ aria-label="Inner"
120
+ >
121
+ <button>Focusable</button>
122
+ </div>
123
+ </div>
124
+ );
125
+ }
126
+
127
+ render( <NestedDialogs /> );
128
+
129
+ pressEscapeOn( screen.getByRole( 'button', { name: 'Focusable' } ) );
130
+
131
+ expect( innerOnClose ).toHaveBeenCalledTimes( 1 );
132
+ expect( outerOnClose ).not.toHaveBeenCalled();
133
+ } );
134
+
135
+ it( 'should not call onClose for non-Escape keys', () => {
136
+ const onClose = jest.fn();
137
+
138
+ render( <Dialog onClose={ onClose } /> );
139
+
140
+ const dialog = screen.getByRole( 'dialog' );
141
+
142
+ fireEvent.keyDown( dialog, { keyCode: 13, key: 'Enter' } );
143
+ fireEvent.keyDown( dialog, { keyCode: 65, key: 'a' } );
144
+ fireEvent.keyDown( dialog, { keyCode: 9, key: 'Tab' } );
145
+
146
+ expect( onClose ).not.toHaveBeenCalled();
147
+ } );
148
+ } );
@@ -3,30 +3,38 @@
3
3
  */
4
4
  import { useMemo, useSyncExternalStore } from '@wordpress/element';
5
5
 
6
- const matchMediaCache = new Map();
6
+ type MQLCache = Map< string, MediaQueryList >;
7
+
8
+ const perWindowCache = new WeakMap< Window, MQLCache >();
7
9
 
8
10
  /**
9
11
  * A new MediaQueryList object for the media query
10
12
  *
11
- * @param {string} [query] Media Query.
12
- * @return {MediaQueryList|null} A new object for the media query
13
+ * @param view Window.
14
+ * @param [query] Media Query.
13
15
  */
14
- function getMediaQueryList( query ) {
16
+ function getMediaQueryList(
17
+ view: Window,
18
+ query?: string
19
+ ): MediaQueryList | null {
15
20
  if ( ! query ) {
16
21
  return null;
17
22
  }
18
23
 
24
+ const matchMediaCache: MQLCache = perWindowCache.get( view ) ?? new Map();
25
+
26
+ if ( ! perWindowCache.has( view ) ) {
27
+ perWindowCache.set( view, matchMediaCache );
28
+ }
29
+
19
30
  let match = matchMediaCache.get( query );
20
31
 
21
32
  if ( match ) {
22
33
  return match;
23
34
  }
24
35
 
25
- if (
26
- typeof window !== 'undefined' &&
27
- typeof window.matchMedia === 'function'
28
- ) {
29
- match = window.matchMedia( query );
36
+ if ( typeof view?.matchMedia === 'function' ) {
37
+ match = view.matchMedia( query );
30
38
  matchMediaCache.set( query, match );
31
39
  return match;
32
40
  }
@@ -37,16 +45,19 @@ function getMediaQueryList( query ) {
37
45
  /**
38
46
  * Runs a media query and returns its value when it changes.
39
47
  *
40
- * @param {string} [query] Media Query.
41
- * @return {boolean} return value of the media query.
48
+ * @param [query] Media Query.
49
+ * @param [view] Window instance, else default to global window
50
+ * @return return value of the media query.
42
51
  */
43
- export default function useMediaQuery( query ) {
52
+ export default function useMediaQuery(
53
+ query?: string,
54
+ view: Window = window
55
+ ): boolean {
44
56
  const source = useMemo( () => {
45
- const mediaQueryList = getMediaQueryList( query );
57
+ const mediaQueryList = getMediaQueryList( view, query );
46
58
 
47
59
  return {
48
- /** @type {(onStoreChange: () => void) => () => void} */
49
- subscribe( onStoreChange ) {
60
+ subscribe( onStoreChange: any ) {
50
61
  if ( ! mediaQueryList ) {
51
62
  return () => {};
52
63
  }
@@ -64,7 +75,7 @@ export default function useMediaQuery( query ) {
64
75
  return mediaQueryList?.matches ?? false;
65
76
  },
66
77
  };
67
- }, [ query ] );
78
+ }, [ view, query ] );
68
79
 
69
80
  return useSyncExternalStore(
70
81
  source.subscribe,
@@ -64,6 +64,7 @@ ViewportMatchWidthContext.displayName = 'ViewportMatchWidthContext';
64
64
  *
65
65
  * @param {WPBreakpoint} breakpoint Breakpoint size name.
66
66
  * @param {WPViewportOperator} [operator=">="] Viewport operator.
67
+ * @param {Window} [view=window] Window instance in which to perform viewport matching.
67
68
  *
68
69
  * @example
69
70
  *
@@ -74,12 +75,12 @@ ViewportMatchWidthContext.displayName = 'ViewportMatchWidthContext';
74
75
  *
75
76
  * @return {boolean} Whether viewport matches query.
76
77
  */
77
- const useViewportMatch = ( breakpoint, operator = '>=' ) => {
78
+ const useViewportMatch = ( breakpoint, operator = '>=', view = window ) => {
78
79
  const simulatedWidth = useContext( ViewportMatchWidthContext );
79
80
  const mediaQuery =
80
81
  ! simulatedWidth &&
81
82
  `(${ CONDITIONS[ operator ] }: ${ BREAKPOINTS[ breakpoint ] }px)`;
82
- const mediaQueryResult = useMediaQuery( mediaQuery || undefined );
83
+ const mediaQueryResult = useMediaQuery( mediaQuery || undefined, view );
83
84
  if ( simulatedWidth ) {
84
85
  return OPERATOR_EVALUATORS[ operator ](
85
86
  BREAKPOINTS[ breakpoint ],
@@ -44,15 +44,18 @@ describe( 'useViewportMatch', () => {
44
44
  expect( useMediaQueryMock ).toHaveBeenCalledTimes( 3 );
45
45
  expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
46
46
  1,
47
- '(max-width: 1280px)'
47
+ '(max-width: 1280px)',
48
+ window
48
49
  );
49
50
  expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
50
51
  2,
51
- '(min-width: 782px)'
52
+ '(min-width: 782px)',
53
+ window
52
54
  );
53
55
  expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
54
56
  3,
55
- '(min-width: 600px)'
57
+ '(min-width: 600px)',
58
+ window
56
59
  );
57
60
  } );
58
61
 
@@ -76,15 +79,18 @@ describe( 'useViewportMatch', () => {
76
79
  expect( useMediaQueryMock ).toHaveBeenCalledTimes( 3 );
77
80
  expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
78
81
  1,
79
- '(min-width: 1440px)'
82
+ '(min-width: 1440px)',
83
+ window
80
84
  );
81
85
  expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
82
86
  2,
83
- '(max-width: 960px)'
87
+ '(max-width: 960px)',
88
+ window
84
89
  );
85
90
  expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
86
91
  3,
87
- '(max-width: 480px)'
92
+ '(max-width: 480px)',
93
+ window
88
94
  );
89
95
  } );
90
96
 
@@ -121,9 +127,25 @@ describe( 'useViewportMatch', () => {
121
127
 
122
128
  expect( useMediaQueryMock ).toHaveBeenCalledTimes( 4 );
123
129
  // `useMediaQuery` is expected to receive `undefined` when simulating width.
124
- expect( useMediaQueryMock ).toHaveBeenNthCalledWith( 1, undefined );
125
- expect( useMediaQueryMock ).toHaveBeenNthCalledWith( 2, undefined );
126
- expect( useMediaQueryMock ).toHaveBeenNthCalledWith( 3, undefined );
127
- expect( useMediaQueryMock ).toHaveBeenNthCalledWith( 4, undefined );
130
+ expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
131
+ 1,
132
+ undefined,
133
+ window
134
+ );
135
+ expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
136
+ 2,
137
+ undefined,
138
+ window
139
+ );
140
+ expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
141
+ 3,
142
+ undefined,
143
+ window
144
+ );
145
+ expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
146
+ 4,
147
+ undefined,
148
+ window
149
+ );
128
150
  } );
129
151
  } );