Skip to content

Commit aaf8d6f

Browse files
tellthemachinesmcsfellatrixandrewserongt-hamano
committed
useMediaQuery: support in-iframe queries via new WindowContext (#76446)
Co-authored-by: tellthemachines <isabel_brison@git.wordpress.org> Co-authored-by: mcsf <mcsf@git.wordpress.org> Co-authored-by: ellatrix <ellatrix@git.wordpress.org> Co-authored-by: andrewserong <andrewserong@git.wordpress.org> Co-authored-by: t-hamano <wildworks@git.wordpress.org> Co-authored-by: ramonjd <ramonopoly@git.wordpress.org>
1 parent 18835e5 commit aaf8d6f

10 files changed

Lines changed: 95 additions & 45 deletions

File tree

‎packages/block-editor/src/components/block-list/block.js‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
} from '@wordpress/blocks';
2424
import { withFilters } from '@wordpress/components';
2525
import { withDispatch, useSelect } from '@wordpress/data';
26-
import { compose } from '@wordpress/compose';
26+
import { compose, useRefEffect } from '@wordpress/compose';
2727
import { safeHTML } from '@wordpress/dom';
2828

2929
/**
@@ -741,10 +741,19 @@ function BlockListBlockProvider( props ) {
741741
[ clientId, rootClientId ]
742742
);
743743

744+
const defaultViewRef = useRefEffect( ( element ) => {
745+
if ( element ) {
746+
const { ownerDocument } = element;
747+
const { defaultView } = ownerDocument;
748+
defaultViewRef.current = defaultView;
749+
}
750+
}, [] );
751+
744752
// Use block visibility hook with data from existing useSelect to avoid extra subscription
745753
const { isBlockCurrentlyHidden } = useBlockVisibility( {
746754
blockVisibility: selectedProps?.blockVisibility,
747755
deviceType: selectedProps?.deviceType,
756+
view: defaultViewRef.current,
748757
} );
749758

750759
// Users of the editor.BlockListBlock filter used to be able to

‎packages/block-editor/src/components/block-list/use-block-props/index.js‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import clsx from 'clsx';
99
import { useContext } from '@wordpress/element';
1010
import { __, sprintf } from '@wordpress/i18n';
1111
import { __unstableGetBlockProps as getBlockProps } from '@wordpress/blocks';
12-
import { useMergeRefs, useDisabled } from '@wordpress/compose';
12+
import { useMergeRefs, useDisabled, useRefEffect } from '@wordpress/compose';
1313
import warning from '@wordpress/warning';
1414

1515
/**
@@ -107,13 +107,22 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) {
107107
deviceType,
108108
} = useContext( PrivateBlockContext );
109109

110+
const defaultViewRef = useRefEffect( ( element ) => {
111+
if ( element ) {
112+
const { ownerDocument } = element;
113+
const { defaultView } = ownerDocument;
114+
defaultViewRef.current = defaultView;
115+
}
116+
}, [] );
117+
110118
// translators: %s: Type of block (i.e. Text, Image etc)
111119
const blockLabel = sprintf( __( 'Block: %s' ), blockTitle );
112120
const htmlSuffix = mode === 'html' && ! __unstableIsHtml ? '-visual' : '';
113121
const ffDragRef = useFirefoxDraggableCompatibility();
114122
const isHoverEnabled = ! isWithinSectionBlock;
115123
const mergedRefs = useMergeRefs( [
116124
props.ref,
125+
defaultViewRef,
117126
useFocusFirstElement( { clientId, initialPosition } ),
118127
useBlockRefProvider( clientId ),
119128
useFocusHandler( clientId ),
@@ -144,6 +153,7 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) {
144153
const { isBlockCurrentlyHidden } = useBlockVisibility( {
145154
blockVisibility,
146155
deviceType,
156+
view: defaultViewRef.current,
147157
} );
148158

149159
// Ensures it warns only inside the `edit` implementation for the block.

‎packages/block-editor/src/components/block-visibility/use-block-visibility.js‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,18 @@ import { BLOCK_VISIBILITY_VIEWPORTS } from './constants';
1414
* @param {Object} options Parameters to avoid extra store subscriptions.
1515
* @param {Object|boolean} options.blockVisibility Block visibility metadata.
1616
* @param {string} options.deviceType Current device type ('desktop', 'tablet', 'mobile').
17+
* @param {Window?} options.view Window instance in which to perform viewport matching
1718
* @return {Object} Object with `isBlockCurrentlyHidden` (boolean) and `currentViewport` (string) properties.
1819
*/
1920
export default function useBlockVisibility( options = {} ) {
2021
const {
2122
blockVisibility = undefined,
2223
deviceType = BLOCK_VISIBILITY_VIEWPORTS.desktop.key,
24+
view = window,
2325
} = options;
2426

25-
const isLargerThanMobile = useViewportMatch( 'mobile', '>=' ); // >= 480px
26-
const isLargerThanTablet = useViewportMatch( 'medium', '>=' ); // >= 782px
27+
const isLargerThanMobile = useViewportMatch( 'mobile', '>=', view ); // >= 480px
28+
const isLargerThanTablet = useViewportMatch( 'medium', '>=', view ); // >= 782px
2729

2830
/*
2931
* Priority:

‎packages/block-editor/src/components/iframe/index.js‎

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,7 @@ import clsx from 'clsx';
88
*/
99
import { useState, createPortal, forwardRef } from '@wordpress/element';
1010
import { __ } from '@wordpress/i18n';
11-
import {
12-
useMergeRefs,
13-
useRefEffect,
14-
useDisabled,
15-
useViewportMatch,
16-
} from '@wordpress/compose';
11+
import { useMergeRefs, useRefEffect, useDisabled } from '@wordpress/compose';
1712
import { __experimentalStyleProvider as StyleProvider } from '@wordpress/components';
1813
import { useSelect } from '@wordpress/data';
1914

@@ -25,8 +20,6 @@ import { getCompatibilityStyles } from './get-compatibility-styles';
2520
import { useScaleCanvas } from './use-scale-canvas';
2621
import { store as blockEditorStore } from '../../store';
2722

28-
const ViewportWidthProvider = useViewportMatch.__experimentalWidthProvider;
29-
3023
function bubbleEvent( event, Constructor, frame ) {
3124
const init = {};
3225

@@ -274,7 +267,6 @@ function Iframe( {
274267
const {
275268
contentResizeListener,
276269
containerResizeListener,
277-
containerWidth,
278270
isZoomedOut,
279271
scaleContainerWidth,
280272
} = useScaleCanvas( {
@@ -355,9 +347,7 @@ function Iframe( {
355347
>
356348
{ contentResizeListener }
357349
<StyleProvider document={ iframeDocument }>
358-
<ViewportWidthProvider value={ containerWidth }>
359-
{ children }
360-
</ViewportWidthProvider>
350+
{ children }
361351
</StyleProvider>
362352
</body>,
363353
iframeDocument.documentElement

‎packages/block-editor/src/components/iframe/use-scale-canvas.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,6 @@ export function useScaleCanvas( {
484484
return {
485485
isZoomedOut,
486486
scaleContainerWidth,
487-
containerWidth,
488487
contentResizeListener,
489488
containerResizeListener,
490489
};

‎packages/compose/CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
### New Features
6+
7+
- 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)).
8+
59
## 7.40.0 (2026-02-18)
610

711
## 7.39.0 (2026-01-29)

‎packages/compose/README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,7 @@ Runs a media query and returns its value when it changes.
431431
_Parameters_
432432

433433
- _query_ `[string]`: Media Query.
434+
- _view_ `[Window]`: Window instance, else default to global window
434435

435436
_Returns_
436437

@@ -592,6 +593,7 @@ _Parameters_
592593

593594
- _breakpoint_ `WPBreakpoint`: Breakpoint size name.
594595
- _operator_ `[WPViewportOperator]`: Viewport operator.
596+
- _view_ `[Window]`: Window instance in which to perform viewport matching.
595597

596598
_Returns_
597599

packages/compose/src/hooks/use-media-query/index.js renamed to packages/compose/src/hooks/use-media-query/index.ts

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,38 @@
33
*/
44
import { useMemo, useSyncExternalStore } from '@wordpress/element';
55

6-
const matchMediaCache = new Map();
6+
type MQLCache = Map< string, MediaQueryList >;
7+
8+
const perWindowCache = new WeakMap< Window, MQLCache >();
79

810
/**
911
* A new MediaQueryList object for the media query
1012
*
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.
1315
*/
14-
function getMediaQueryList( query ) {
16+
function getMediaQueryList(
17+
view: Window,
18+
query?: string
19+
): MediaQueryList | null {
1520
if ( ! query ) {
1621
return null;
1722
}
1823

24+
const matchMediaCache: MQLCache = perWindowCache.get( view ) ?? new Map();
25+
26+
if ( ! perWindowCache.has( view ) ) {
27+
perWindowCache.set( view, matchMediaCache );
28+
}
29+
1930
let match = matchMediaCache.get( query );
2031

2132
if ( match ) {
2233
return match;
2334
}
2435

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 );
3038
matchMediaCache.set( query, match );
3139
return match;
3240
}
@@ -37,16 +45,19 @@ function getMediaQueryList( query ) {
3745
/**
3846
* Runs a media query and returns its value when it changes.
3947
*
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.
4251
*/
43-
export default function useMediaQuery( query ) {
52+
export default function useMediaQuery(
53+
query?: string,
54+
view: Window = window
55+
): boolean {
4456
const source = useMemo( () => {
45-
const mediaQueryList = getMediaQueryList( query );
57+
const mediaQueryList = getMediaQueryList( view, query );
4658

4759
return {
48-
/** @type {(onStoreChange: () => void) => () => void} */
49-
subscribe( onStoreChange ) {
60+
subscribe( onStoreChange: any ) {
5061
if ( ! mediaQueryList ) {
5162
return () => {};
5263
}
@@ -64,7 +75,7 @@ export default function useMediaQuery( query ) {
6475
return mediaQueryList?.matches ?? false;
6576
},
6677
};
67-
}, [ query ] );
78+
}, [ view, query ] );
6879

6980
return useSyncExternalStore(
7081
source.subscribe,

‎packages/compose/src/hooks/use-viewport-match/index.js‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ ViewportMatchWidthContext.displayName = 'ViewportMatchWidthContext';
6464
*
6565
* @param {WPBreakpoint} breakpoint Breakpoint size name.
6666
* @param {WPViewportOperator} [operator=">="] Viewport operator.
67+
* @param {Window} [view=window] Window instance in which to perform viewport matching.
6768
*
6869
* @example
6970
*
@@ -74,12 +75,12 @@ ViewportMatchWidthContext.displayName = 'ViewportMatchWidthContext';
7475
*
7576
* @return {boolean} Whether viewport matches query.
7677
*/
77-
const useViewportMatch = ( breakpoint, operator = '>=' ) => {
78+
const useViewportMatch = ( breakpoint, operator = '>=', view = window ) => {
7879
const simulatedWidth = useContext( ViewportMatchWidthContext );
7980
const mediaQuery =
8081
! simulatedWidth &&
8182
`(${ CONDITIONS[ operator ] }: ${ BREAKPOINTS[ breakpoint ] }px)`;
82-
const mediaQueryResult = useMediaQuery( mediaQuery || undefined );
83+
const mediaQueryResult = useMediaQuery( mediaQuery || undefined, view );
8384
if ( simulatedWidth ) {
8485
return OPERATOR_EVALUATORS[ operator ](
8586
BREAKPOINTS[ breakpoint ],

‎packages/compose/src/hooks/use-viewport-match/test/index.js‎

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,18 @@ describe( 'useViewportMatch', () => {
4444
expect( useMediaQueryMock ).toHaveBeenCalledTimes( 3 );
4545
expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
4646
1,
47-
'(max-width: 1280px)'
47+
'(max-width: 1280px)',
48+
window
4849
);
4950
expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
5051
2,
51-
'(min-width: 782px)'
52+
'(min-width: 782px)',
53+
window
5254
);
5355
expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
5456
3,
55-
'(min-width: 600px)'
57+
'(min-width: 600px)',
58+
window
5659
);
5760
} );
5861

@@ -76,15 +79,18 @@ describe( 'useViewportMatch', () => {
7679
expect( useMediaQueryMock ).toHaveBeenCalledTimes( 3 );
7780
expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
7881
1,
79-
'(min-width: 1440px)'
82+
'(min-width: 1440px)',
83+
window
8084
);
8185
expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
8286
2,
83-
'(max-width: 960px)'
87+
'(max-width: 960px)',
88+
window
8489
);
8590
expect( useMediaQueryMock ).toHaveBeenNthCalledWith(
8691
3,
87-
'(max-width: 480px)'
92+
'(max-width: 480px)',
93+
window
8894
);
8995
} );
9096

@@ -121,9 +127,25 @@ describe( 'useViewportMatch', () => {
121127

122128
expect( useMediaQueryMock ).toHaveBeenCalledTimes( 4 );
123129
// `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+
);
128150
} );
129151
} );

0 commit comments

Comments
 (0)