diff --git a/packages/block-library/src/file/view.js b/packages/block-library/src/file/view.js
index 79340223f007cb..62c31b7b365ff2 100644
--- a/packages/block-library/src/file/view.js
+++ b/packages/block-library/src/file/view.js
@@ -7,10 +7,14 @@ import { store } from '@wordpress/interactivity';
*/
import { browserSupportsPdfs } from './utils';
-store( 'core/file', {
- state: {
- get hasPdfPreview() {
- return browserSupportsPdfs();
+store(
+ 'core/file',
+ {
+ state: {
+ get hasPdfPreview() {
+ return browserSupportsPdfs();
+ },
},
},
-} );
+ { lock: true }
+);
diff --git a/packages/block-library/src/image/view.js b/packages/block-library/src/image/view.js
index 2d5268e4836cb7..8ae0149726570c 100644
--- a/packages/block-library/src/image/view.js
+++ b/packages/block-library/src/image/view.js
@@ -77,269 +77,278 @@ function handleScroll( ctx ) {
}
}
-const { state, actions, callbacks } = store( 'core/image', {
- state: {
- windowWidth: window.innerWidth,
- windowHeight: window.innerHeight,
- get roleAttribute() {
- const ctx = getContext();
- return ctx.lightboxEnabled ? 'dialog' : null;
- },
- get ariaModal() {
- const ctx = getContext();
- return ctx.lightboxEnabled ? 'true' : null;
- },
- get dialogLabel() {
- const ctx = getContext();
- return ctx.lightboxEnabled ? ctx.dialogLabel : null;
- },
- get lightboxObjectFit() {
- const ctx = getContext();
- if ( ctx.initialized ) {
- return 'cover';
- }
- },
- get enlargedImgSrc() {
- const ctx = getContext();
- return ctx.initialized
- ? ctx.imageUploadedSrc
- : 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
- },
- },
- actions: {
- showLightbox( event ) {
- const ctx = getContext();
- // We can't initialize the lightbox until the reference
- // image is loaded, otherwise the UX is broken.
- if ( ! ctx.imageLoaded ) {
- return;
- }
- ctx.initialized = true;
- ctx.lastFocusedElement = window.document.activeElement;
- ctx.scrollDelta = 0;
- ctx.pointerType = event.pointerType;
-
- ctx.lightboxEnabled = true;
- setStyles( ctx, ctx.imageRef );
-
- ctx.scrollTopReset =
- window.pageYOffset || document.documentElement.scrollTop;
-
- // In most cases, this value will be 0, but this is included
- // in case a user has created a page with horizontal scrolling.
- ctx.scrollLeftReset =
- window.pageXOffset || document.documentElement.scrollLeft;
-
- // We define and bind the scroll callback here so
- // that we can pass the context and as an argument.
- // We may be able to change this in the future if we
- // define the scroll callback in the store instead, but
- // this approach seems to tbe clearest for now.
- scrollCallback = handleScroll.bind( null, ctx );
-
- // We need to add a scroll event listener to the window
- // here because we are unable to otherwise access it via
- // the Interactivity API directives. If we add a native way
- // to access the window, we can remove this.
- window.addEventListener( 'scroll', scrollCallback, false );
- },
- hideLightbox() {
- const ctx = getContext();
- ctx.hideAnimationEnabled = true;
- if ( ctx.lightboxEnabled ) {
- // We want to wait until the close animation is completed
- // before allowing a user to scroll again. The duration of this
- // animation is defined in the styles.scss and depends on if the
- // animation is 'zoom' or 'fade', but in any case we should wait
- // a few milliseconds longer than the duration, otherwise a user
- // may scroll too soon and cause the animation to look sloppy.
- setTimeout( function () {
- window.removeEventListener( 'scroll', scrollCallback );
- // If we don't delay before changing the focus,
- // the focus ring will appear on Firefox before
- // the image has finished animating, which looks broken.
- ctx.lightboxTriggerRef.focus( {
- preventScroll: true,
- } );
- }, 450 );
-
- ctx.lightboxEnabled = false;
- }
+const { state, actions, callbacks } = store(
+ 'core/image',
+ {
+ state: {
+ windowWidth: window.innerWidth,
+ windowHeight: window.innerHeight,
+ get roleAttribute() {
+ const ctx = getContext();
+ return ctx.lightboxEnabled ? 'dialog' : null;
+ },
+ get ariaModal() {
+ const ctx = getContext();
+ return ctx.lightboxEnabled ? 'true' : null;
+ },
+ get dialogLabel() {
+ const ctx = getContext();
+ return ctx.lightboxEnabled ? ctx.dialogLabel : null;
+ },
+ get lightboxObjectFit() {
+ const ctx = getContext();
+ if ( ctx.initialized ) {
+ return 'cover';
+ }
+ },
+ get enlargedImgSrc() {
+ const ctx = getContext();
+ return ctx.initialized
+ ? ctx.imageUploadedSrc
+ : 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
+ },
},
- handleKeydown( event ) {
- const ctx = getContext();
- if ( ctx.lightboxEnabled ) {
- if ( event.key === 'Tab' || event.keyCode === 9 ) {
- // If shift + tab it change the direction
- if (
- event.shiftKey &&
- window.document.activeElement ===
- ctx.firstFocusableElement
- ) {
- event.preventDefault();
- ctx.lastFocusableElement.focus();
- } else if (
- ! event.shiftKey &&
- window.document.activeElement ===
- ctx.lastFocusableElement
- ) {
- event.preventDefault();
- ctx.firstFocusableElement.focus();
- }
+ actions: {
+ showLightbox( event ) {
+ const ctx = getContext();
+ // We can't initialize the lightbox until the reference
+ // image is loaded, otherwise the UX is broken.
+ if ( ! ctx.imageLoaded ) {
+ return;
}
+ ctx.initialized = true;
+ ctx.lastFocusedElement = window.document.activeElement;
+ ctx.scrollDelta = 0;
+ ctx.pointerType = event.pointerType;
+
+ ctx.lightboxEnabled = true;
+ setStyles( ctx, ctx.imageRef );
+
+ ctx.scrollTopReset =
+ window.pageYOffset || document.documentElement.scrollTop;
+
+ // In most cases, this value will be 0, but this is included
+ // in case a user has created a page with horizontal scrolling.
+ ctx.scrollLeftReset =
+ window.pageXOffset || document.documentElement.scrollLeft;
+
+ // We define and bind the scroll callback here so
+ // that we can pass the context and as an argument.
+ // We may be able to change this in the future if we
+ // define the scroll callback in the store instead, but
+ // this approach seems to tbe clearest for now.
+ scrollCallback = handleScroll.bind( null, ctx );
+
+ // We need to add a scroll event listener to the window
+ // here because we are unable to otherwise access it via
+ // the Interactivity API directives. If we add a native way
+ // to access the window, we can remove this.
+ window.addEventListener( 'scroll', scrollCallback, false );
+ },
+ hideLightbox() {
+ const ctx = getContext();
+ ctx.hideAnimationEnabled = true;
+ if ( ctx.lightboxEnabled ) {
+ // We want to wait until the close animation is completed
+ // before allowing a user to scroll again. The duration of this
+ // animation is defined in the styles.scss and depends on if the
+ // animation is 'zoom' or 'fade', but in any case we should wait
+ // a few milliseconds longer than the duration, otherwise a user
+ // may scroll too soon and cause the animation to look sloppy.
+ setTimeout( function () {
+ window.removeEventListener( 'scroll', scrollCallback );
+ // If we don't delay before changing the focus,
+ // the focus ring will appear on Firefox before
+ // the image has finished animating, which looks broken.
+ ctx.lightboxTriggerRef.focus( {
+ preventScroll: true,
+ } );
+ }, 450 );
+
+ ctx.lightboxEnabled = false;
+ }
+ },
+ handleKeydown( event ) {
+ const ctx = getContext();
+ if ( ctx.lightboxEnabled ) {
+ if ( event.key === 'Tab' || event.keyCode === 9 ) {
+ // If shift + tab it change the direction
+ if (
+ event.shiftKey &&
+ window.document.activeElement ===
+ ctx.firstFocusableElement
+ ) {
+ event.preventDefault();
+ ctx.lastFocusableElement.focus();
+ } else if (
+ ! event.shiftKey &&
+ window.document.activeElement ===
+ ctx.lastFocusableElement
+ ) {
+ event.preventDefault();
+ ctx.firstFocusableElement.focus();
+ }
+ }
- if ( event.key === 'Escape' || event.keyCode === 27 ) {
- actions.hideLightbox( event );
+ if ( event.key === 'Escape' || event.keyCode === 27 ) {
+ actions.hideLightbox( event );
+ }
}
- }
- },
- // This is fired just by lazily loaded
- // images on the page, not all images.
- handleLoad() {
- const ctx = getContext();
- const { ref } = getElement();
- ctx.imageLoaded = true;
- ctx.imageCurrentSrc = ref.currentSrc;
- callbacks.setButtonStyles();
- },
- handleTouchStart() {
- isTouching = true;
- },
- handleTouchMove( event ) {
- const ctx = getContext();
- // On mobile devices, we want to prevent triggering the
- // scroll event because otherwise the page jumps around as
- // we reset the scroll position. This also means that closing
- // the lightbox requires that a user perform a simple tap. This
- // may be changed in the future if we find a better alternative
- // to override or reset the scroll position during swipe actions.
- if ( ctx.lightboxEnabled ) {
- event.preventDefault();
- }
- },
- handleTouchEnd() {
- // We need to wait a few milliseconds before resetting
- // to ensure that pinch to zoom works consistently
- // on mobile devices when the lightbox is open.
- lastTouchTime = Date.now();
- isTouching = false;
- },
- },
- callbacks: {
- initOriginImage() {
- const ctx = getContext();
- const { ref } = getElement();
- ctx.imageRef = ref;
- if ( ref.complete ) {
+ },
+ // This is fired just by lazily loaded
+ // images on the page, not all images.
+ handleLoad() {
+ const ctx = getContext();
+ const { ref } = getElement();
ctx.imageLoaded = true;
ctx.imageCurrentSrc = ref.currentSrc;
- }
- },
- initTriggerButton() {
- const ctx = getContext();
- const { ref } = getElement();
- ctx.lightboxTriggerRef = ref;
- },
- initLightbox() {
- const ctx = getContext();
- const { ref } = getElement();
- if ( ctx.lightboxEnabled ) {
- const focusableElements =
- ref.querySelectorAll( focusableSelectors );
- ctx.firstFocusableElement = focusableElements[ 0 ];
- ctx.lastFocusableElement =
- focusableElements[ focusableElements.length - 1 ];
-
- // Move focus to the dialog when opening it.
- ref.focus();
- }
+ callbacks.setButtonStyles();
+ },
+ handleTouchStart() {
+ isTouching = true;
+ },
+ handleTouchMove( event ) {
+ const ctx = getContext();
+ // On mobile devices, we want to prevent triggering the
+ // scroll event because otherwise the page jumps around as
+ // we reset the scroll position. This also means that closing
+ // the lightbox requires that a user perform a simple tap. This
+ // may be changed in the future if we find a better alternative
+ // to override or reset the scroll position during swipe actions.
+ if ( ctx.lightboxEnabled ) {
+ event.preventDefault();
+ }
+ },
+ handleTouchEnd() {
+ // We need to wait a few milliseconds before resetting
+ // to ensure that pinch to zoom works consistently
+ // on mobile devices when the lightbox is open.
+ lastTouchTime = Date.now();
+ isTouching = false;
+ },
},
- setButtonStyles() {
- const { ref } = getElement();
- const { naturalWidth, naturalHeight, offsetWidth, offsetHeight } =
- ref;
-
- // If the image isn't loaded yet, we can't
- // calculate where the button should be.
- if ( naturalWidth === 0 || naturalHeight === 0 ) {
- return;
- }
+ callbacks: {
+ initOriginImage() {
+ const ctx = getContext();
+ const { ref } = getElement();
+ ctx.imageRef = ref;
+ if ( ref.complete ) {
+ ctx.imageLoaded = true;
+ ctx.imageCurrentSrc = ref.currentSrc;
+ }
+ },
+ initTriggerButton() {
+ const ctx = getContext();
+ const { ref } = getElement();
+ ctx.lightboxTriggerRef = ref;
+ },
+ initLightbox() {
+ const ctx = getContext();
+ const { ref } = getElement();
+ if ( ctx.lightboxEnabled ) {
+ const focusableElements =
+ ref.querySelectorAll( focusableSelectors );
+ ctx.firstFocusableElement = focusableElements[ 0 ];
+ ctx.lastFocusableElement =
+ focusableElements[ focusableElements.length - 1 ];
+
+ // Move focus to the dialog when opening it.
+ ref.focus();
+ }
+ },
+ setButtonStyles() {
+ const { ref } = getElement();
+ const {
+ naturalWidth,
+ naturalHeight,
+ offsetWidth,
+ offsetHeight,
+ } = ref;
+
+ // If the image isn't loaded yet, we can't
+ // calculate where the button should be.
+ if ( naturalWidth === 0 || naturalHeight === 0 ) {
+ return;
+ }
- const figure = ref.parentElement;
- const figureWidth = ref.parentElement.clientWidth;
-
- // We need special handling for the height because
- // a caption will cause the figure to be taller than
- // the image, which means we need to account for that
- // when calculating the placement of the button in the
- // top right corner of the image.
- let figureHeight = ref.parentElement.clientHeight;
- const caption = figure.querySelector( 'figcaption' );
- if ( caption ) {
- const captionComputedStyle = window.getComputedStyle( caption );
- if (
- ! [ 'absolute', 'fixed' ].includes(
- captionComputedStyle.position
- )
- ) {
- figureHeight =
- figureHeight -
- caption.offsetHeight -
- parseFloat( captionComputedStyle.marginTop ) -
- parseFloat( captionComputedStyle.marginBottom );
+ const figure = ref.parentElement;
+ const figureWidth = ref.parentElement.clientWidth;
+
+ // We need special handling for the height because
+ // a caption will cause the figure to be taller than
+ // the image, which means we need to account for that
+ // when calculating the placement of the button in the
+ // top right corner of the image.
+ let figureHeight = ref.parentElement.clientHeight;
+ const caption = figure.querySelector( 'figcaption' );
+ if ( caption ) {
+ const captionComputedStyle =
+ window.getComputedStyle( caption );
+ if (
+ ! [ 'absolute', 'fixed' ].includes(
+ captionComputedStyle.position
+ )
+ ) {
+ figureHeight =
+ figureHeight -
+ caption.offsetHeight -
+ parseFloat( captionComputedStyle.marginTop ) -
+ parseFloat( captionComputedStyle.marginBottom );
+ }
}
- }
- const buttonOffsetTop = figureHeight - offsetHeight;
- const buttonOffsetRight = figureWidth - offsetWidth;
-
- const ctx = getContext();
-
- // In the case of an image with object-fit: contain, the
- // size of the element can be larger than the image itself,
- // so we need to calculate where to place the button.
- if ( ctx.scaleAttr === 'contain' ) {
- // Natural ratio of the image.
- const naturalRatio = naturalWidth / naturalHeight;
- // Offset ratio of the image.
- const offsetRatio = offsetWidth / offsetHeight;
-
- if ( naturalRatio >= offsetRatio ) {
- // If it reaches the width first, keep
- // the width and compute the height.
- const referenceHeight = offsetWidth / naturalRatio;
- ctx.imageButtonTop =
- ( offsetHeight - referenceHeight ) / 2 +
- buttonOffsetTop +
- 16;
- ctx.imageButtonRight = buttonOffsetRight + 16;
+ const buttonOffsetTop = figureHeight - offsetHeight;
+ const buttonOffsetRight = figureWidth - offsetWidth;
+
+ const ctx = getContext();
+
+ // In the case of an image with object-fit: contain, the
+ // size of the element can be larger than the image itself,
+ // so we need to calculate where to place the button.
+ if ( ctx.scaleAttr === 'contain' ) {
+ // Natural ratio of the image.
+ const naturalRatio = naturalWidth / naturalHeight;
+ // Offset ratio of the image.
+ const offsetRatio = offsetWidth / offsetHeight;
+
+ if ( naturalRatio >= offsetRatio ) {
+ // If it reaches the width first, keep
+ // the width and compute the height.
+ const referenceHeight = offsetWidth / naturalRatio;
+ ctx.imageButtonTop =
+ ( offsetHeight - referenceHeight ) / 2 +
+ buttonOffsetTop +
+ 16;
+ ctx.imageButtonRight = buttonOffsetRight + 16;
+ } else {
+ // If it reaches the height first, keep
+ // the height and compute the width.
+ const referenceWidth = offsetHeight * naturalRatio;
+ ctx.imageButtonTop = buttonOffsetTop + 16;
+ ctx.imageButtonRight =
+ ( offsetWidth - referenceWidth ) / 2 +
+ buttonOffsetRight +
+ 16;
+ }
} else {
- // If it reaches the height first, keep
- // the height and compute the width.
- const referenceWidth = offsetHeight * naturalRatio;
ctx.imageButtonTop = buttonOffsetTop + 16;
- ctx.imageButtonRight =
- ( offsetWidth - referenceWidth ) / 2 +
- buttonOffsetRight +
- 16;
+ ctx.imageButtonRight = buttonOffsetRight + 16;
}
- } else {
- ctx.imageButtonTop = buttonOffsetTop + 16;
- ctx.imageButtonRight = buttonOffsetRight + 16;
- }
- },
- setStylesOnResize() {
- const ctx = getContext();
- const { ref } = getElement();
- if (
- ctx.lightboxEnabled &&
- ( state.windowWidth || state.windowHeight )
- ) {
- setStyles( ctx, ref );
- }
+ },
+ setStylesOnResize() {
+ const ctx = getContext();
+ const { ref } = getElement();
+ if (
+ ctx.lightboxEnabled &&
+ ( state.windowWidth || state.windowHeight )
+ ) {
+ setStyles( ctx, ref );
+ }
+ },
},
},
-} );
+ { lock: true }
+);
window.addEventListener(
'resize',
diff --git a/packages/block-library/src/navigation/view.js b/packages/block-library/src/navigation/view.js
index d42832a1f8d02e..eb553eee9ca181 100644
--- a/packages/block-library/src/navigation/view.js
+++ b/packages/block-library/src/navigation/view.js
@@ -23,193 +23,206 @@ const focusableSelectors = [
// capture the clicks, instead of relying on the focusout event.
document.addEventListener( 'click', () => {} );
-const { state, actions } = store( 'core/navigation', {
- state: {
- get roleAttribute() {
- const ctx = getContext();
- return ctx.type === 'overlay' && state.isMenuOpen ? 'dialog' : null;
- },
- get ariaModal() {
- const ctx = getContext();
- return ctx.type === 'overlay' && state.isMenuOpen ? 'true' : null;
- },
- get ariaLabel() {
- const ctx = getContext();
- return ctx.type === 'overlay' && state.isMenuOpen
- ? ctx.ariaLabel
- : null;
- },
- get isMenuOpen() {
- // The menu is opened if either `click`, `hover` or `focus` is true.
- return (
- Object.values( state.menuOpenedBy ).filter( Boolean ).length > 0
- );
- },
- get menuOpenedBy() {
- const ctx = getContext();
- return ctx.type === 'overlay'
- ? ctx.overlayOpenedBy
- : ctx.submenuOpenedBy;
- },
- },
- actions: {
- openMenuOnHover() {
- const { type, overlayOpenedBy } = getContext();
- if (
- type === 'submenu' &&
- // Only open on hover if the overlay is closed.
- Object.values( overlayOpenedBy || {} ).filter( Boolean )
- .length === 0
- )
- actions.openMenu( 'hover' );
- },
- closeMenuOnHover() {
- actions.closeMenu( 'hover' );
- },
- openMenuOnClick() {
- const ctx = getContext();
- const { ref } = getElement();
- ctx.previousFocus = ref;
- actions.openMenu( 'click' );
- },
- closeMenuOnClick() {
- actions.closeMenu( 'click' );
- actions.closeMenu( 'focus' );
- },
- openMenuOnFocus() {
- actions.openMenu( 'focus' );
- },
- toggleMenuOnClick() {
- const ctx = getContext();
- const { ref } = getElement();
- // Safari won't send focus to the clicked element, so we need to manually place it: https://bugs.webkit.org/show_bug.cgi?id=22261
- if ( window.document.activeElement !== ref ) ref.focus();
- const { menuOpenedBy } = state;
- if ( menuOpenedBy.click || menuOpenedBy.focus ) {
- actions.closeMenu( 'click' );
- actions.closeMenu( 'focus' );
- } else {
+const { state, actions } = store(
+ 'core/navigation',
+ {
+ state: {
+ get roleAttribute() {
+ const ctx = getContext();
+ return ctx.type === 'overlay' && state.isMenuOpen
+ ? 'dialog'
+ : null;
+ },
+ get ariaModal() {
+ const ctx = getContext();
+ return ctx.type === 'overlay' && state.isMenuOpen
+ ? 'true'
+ : null;
+ },
+ get ariaLabel() {
+ const ctx = getContext();
+ return ctx.type === 'overlay' && state.isMenuOpen
+ ? ctx.ariaLabel
+ : null;
+ },
+ get isMenuOpen() {
+ // The menu is opened if either `click`, `hover` or `focus` is true.
+ return (
+ Object.values( state.menuOpenedBy ).filter( Boolean )
+ .length > 0
+ );
+ },
+ get menuOpenedBy() {
+ const ctx = getContext();
+ return ctx.type === 'overlay'
+ ? ctx.overlayOpenedBy
+ : ctx.submenuOpenedBy;
+ },
+ },
+ actions: {
+ openMenuOnHover() {
+ const { type, overlayOpenedBy } = getContext();
+ if (
+ type === 'submenu' &&
+ // Only open on hover if the overlay is closed.
+ Object.values( overlayOpenedBy || {} ).filter( Boolean )
+ .length === 0
+ )
+ actions.openMenu( 'hover' );
+ },
+ closeMenuOnHover() {
+ actions.closeMenu( 'hover' );
+ },
+ openMenuOnClick() {
+ const ctx = getContext();
+ const { ref } = getElement();
ctx.previousFocus = ref;
actions.openMenu( 'click' );
- }
- },
- handleMenuKeydown( event ) {
- const { type, firstFocusableElement, lastFocusableElement } =
- getContext();
- if ( state.menuOpenedBy.click ) {
- // If Escape close the menu.
- if ( event?.key === 'Escape' ) {
+ },
+ closeMenuOnClick() {
+ actions.closeMenu( 'click' );
+ actions.closeMenu( 'focus' );
+ },
+ openMenuOnFocus() {
+ actions.openMenu( 'focus' );
+ },
+ toggleMenuOnClick() {
+ const ctx = getContext();
+ const { ref } = getElement();
+ // Safari won't send focus to the clicked element, so we need to manually place it: https://bugs.webkit.org/show_bug.cgi?id=22261
+ if ( window.document.activeElement !== ref ) ref.focus();
+ const { menuOpenedBy } = state;
+ if ( menuOpenedBy.click || menuOpenedBy.focus ) {
actions.closeMenu( 'click' );
actions.closeMenu( 'focus' );
- return;
+ } else {
+ ctx.previousFocus = ref;
+ actions.openMenu( 'click' );
}
+ },
+ handleMenuKeydown( event ) {
+ const { type, firstFocusableElement, lastFocusableElement } =
+ getContext();
+ if ( state.menuOpenedBy.click ) {
+ // If Escape close the menu.
+ if ( event?.key === 'Escape' ) {
+ actions.closeMenu( 'click' );
+ actions.closeMenu( 'focus' );
+ return;
+ }
- // Trap focus if it is an overlay (main menu).
- if ( type === 'overlay' && event.key === 'Tab' ) {
- // If shift + tab it change the direction.
- if (
- event.shiftKey &&
- window.document.activeElement === firstFocusableElement
- ) {
- event.preventDefault();
- lastFocusableElement.focus();
- } else if (
- ! event.shiftKey &&
- window.document.activeElement === lastFocusableElement
- ) {
- event.preventDefault();
- firstFocusableElement.focus();
+ // Trap focus if it is an overlay (main menu).
+ if ( type === 'overlay' && event.key === 'Tab' ) {
+ // If shift + tab it change the direction.
+ if (
+ event.shiftKey &&
+ window.document.activeElement ===
+ firstFocusableElement
+ ) {
+ event.preventDefault();
+ lastFocusableElement.focus();
+ } else if (
+ ! event.shiftKey &&
+ window.document.activeElement ===
+ lastFocusableElement
+ ) {
+ event.preventDefault();
+ firstFocusableElement.focus();
+ }
}
}
- }
- },
- handleMenuFocusout( event ) {
- const { modal } = getContext();
- // If focus is outside modal, and in the document, close menu
- // event.target === The element losing focus
- // event.relatedTarget === The element receiving focus (if any)
- // When focusout is outsite the document,
- // `window.document.activeElement` doesn't change.
+ },
+ handleMenuFocusout( event ) {
+ const { modal } = getContext();
+ // If focus is outside modal, and in the document, close menu
+ // event.target === The element losing focus
+ // event.relatedTarget === The element receiving focus (if any)
+ // When focusout is outsite the document,
+ // `window.document.activeElement` doesn't change.
- // The event.relatedTarget is null when something outside the navigation menu is clicked. This is only necessary for Safari.
- if (
- event.relatedTarget === null ||
- ( ! modal?.contains( event.relatedTarget ) &&
- event.target !== window.document.activeElement )
- ) {
- actions.closeMenu( 'click' );
- actions.closeMenu( 'focus' );
- }
- },
+ // The event.relatedTarget is null when something outside the navigation menu is clicked. This is only necessary for Safari.
+ if (
+ event.relatedTarget === null ||
+ ( ! modal?.contains( event.relatedTarget ) &&
+ event.target !== window.document.activeElement )
+ ) {
+ actions.closeMenu( 'click' );
+ actions.closeMenu( 'focus' );
+ }
+ },
- openMenu( menuOpenedOn = 'click' ) {
- const { type } = getContext();
- state.menuOpenedBy[ menuOpenedOn ] = true;
- if ( type === 'overlay' ) {
- // Add a `has-modal-open` class to the root.
- document.documentElement.classList.add( 'has-modal-open' );
- }
- },
+ openMenu( menuOpenedOn = 'click' ) {
+ const { type } = getContext();
+ state.menuOpenedBy[ menuOpenedOn ] = true;
+ if ( type === 'overlay' ) {
+ // Add a `has-modal-open` class to the root.
+ document.documentElement.classList.add( 'has-modal-open' );
+ }
+ },
- closeMenu( menuClosedOn = 'click' ) {
- const ctx = getContext();
- state.menuOpenedBy[ menuClosedOn ] = false;
- // Check if the menu is still open or not.
- if ( ! state.isMenuOpen ) {
- if ( ctx.modal?.contains( window.document.activeElement ) ) {
- ctx.previousFocus?.focus();
+ closeMenu( menuClosedOn = 'click' ) {
+ const ctx = getContext();
+ state.menuOpenedBy[ menuClosedOn ] = false;
+ // Check if the menu is still open or not.
+ if ( ! state.isMenuOpen ) {
+ if (
+ ctx.modal?.contains( window.document.activeElement )
+ ) {
+ ctx.previousFocus?.focus();
+ }
+ ctx.modal = null;
+ ctx.previousFocus = null;
+ if ( ctx.type === 'overlay' ) {
+ document.documentElement.classList.remove(
+ 'has-modal-open'
+ );
+ }
}
- ctx.modal = null;
- ctx.previousFocus = null;
- if ( ctx.type === 'overlay' ) {
- document.documentElement.classList.remove(
- 'has-modal-open'
- );
+ },
+ },
+ callbacks: {
+ initMenu() {
+ const ctx = getContext();
+ const { ref } = getElement();
+ if ( state.isMenuOpen ) {
+ const focusableElements =
+ ref.querySelectorAll( focusableSelectors );
+ ctx.modal = ref;
+ ctx.firstFocusableElement = focusableElements[ 0 ];
+ ctx.lastFocusableElement =
+ focusableElements[ focusableElements.length - 1 ];
}
- }
- },
- },
- callbacks: {
- initMenu() {
- const ctx = getContext();
- const { ref } = getElement();
- if ( state.isMenuOpen ) {
- const focusableElements =
- ref.querySelectorAll( focusableSelectors );
- ctx.modal = ref;
- ctx.firstFocusableElement = focusableElements[ 0 ];
- ctx.lastFocusableElement =
- focusableElements[ focusableElements.length - 1 ];
- }
- },
- focusFirstElement() {
- const { ref } = getElement();
- if ( state.isMenuOpen ) {
- const focusableElements =
- ref.querySelectorAll( focusableSelectors );
- focusableElements?.[ 0 ]?.focus();
- }
- },
- initNav() {
- const context = getContext();
- const mediaQuery = window.matchMedia(
- `(max-width: ${ NAVIGATION_MOBILE_COLLAPSE })`
- );
+ },
+ focusFirstElement() {
+ const { ref } = getElement();
+ if ( state.isMenuOpen ) {
+ const focusableElements =
+ ref.querySelectorAll( focusableSelectors );
+ focusableElements?.[ 0 ]?.focus();
+ }
+ },
+ initNav() {
+ const context = getContext();
+ const mediaQuery = window.matchMedia(
+ `(max-width: ${ NAVIGATION_MOBILE_COLLAPSE })`
+ );
- // Run once to set the initial state.
- context.isCollapsed = mediaQuery.matches;
+ // Run once to set the initial state.
+ context.isCollapsed = mediaQuery.matches;
- function handleCollapse( event ) {
- context.isCollapsed = event.matches;
- }
+ function handleCollapse( event ) {
+ context.isCollapsed = event.matches;
+ }
- // Run on resize to update the state.
- mediaQuery.addEventListener( 'change', handleCollapse );
+ // Run on resize to update the state.
+ mediaQuery.addEventListener( 'change', handleCollapse );
- // Remove the listener when the component is unmounted.
- return () => {
- mediaQuery.removeEventListener( 'change', handleCollapse );
- };
+ // Remove the listener when the component is unmounted.
+ return () => {
+ mediaQuery.removeEventListener( 'change', handleCollapse );
+ };
+ },
},
},
-} );
+ { lock: true }
+);
diff --git a/packages/block-library/src/query/view.js b/packages/block-library/src/query/view.js
index ee811b4b8e90f1..396bf4de643698 100644
--- a/packages/block-library/src/query/view.js
+++ b/packages/block-library/src/query/view.js
@@ -18,54 +18,62 @@ const isValidEvent = ( event ) =>
! event.shiftKey &&
! event.defaultPrevented;
-store( 'core/query', {
- actions: {
- *navigate( event ) {
- const ctx = getContext();
- const { ref } = getElement();
- const queryRef = ref.closest(
- '.wp-block-query[data-wp-router-region]'
- );
- const isDisabled = queryRef?.dataset.wpNavigationDisabled;
+store(
+ 'core/query',
+ {
+ actions: {
+ *navigate( event ) {
+ const ctx = getContext();
+ const { ref } = getElement();
+ const queryRef = ref.closest(
+ '.wp-block-query[data-wp-router-region]'
+ );
+ const isDisabled = queryRef?.dataset.wpNavigationDisabled;
- if ( isValidLink( ref ) && isValidEvent( event ) && ! isDisabled ) {
- event.preventDefault();
+ if (
+ isValidLink( ref ) &&
+ isValidEvent( event ) &&
+ ! isDisabled
+ ) {
+ event.preventDefault();
- const { actions } = yield import(
- '@wordpress/interactivity-router'
- );
- yield actions.navigate( ref.href );
- ctx.url = ref.href;
+ const { actions } = yield import(
+ '@wordpress/interactivity-router'
+ );
+ yield actions.navigate( ref.href );
+ ctx.url = ref.href;
- // Focus the first anchor of the Query block.
- const firstAnchor = `.wp-block-post-template a[href]`;
- queryRef.querySelector( firstAnchor )?.focus();
- }
- },
- *prefetch() {
- const { ref } = getElement();
- const queryRef = ref.closest(
- '.wp-block-query[data-wp-router-region]'
- );
- const isDisabled = queryRef?.dataset.wpNavigationDisabled;
- if ( isValidLink( ref ) && ! isDisabled ) {
- const { actions } = yield import(
- '@wordpress/interactivity-router'
+ // Focus the first anchor of the Query block.
+ const firstAnchor = `.wp-block-post-template a[href]`;
+ queryRef.querySelector( firstAnchor )?.focus();
+ }
+ },
+ *prefetch() {
+ const { ref } = getElement();
+ const queryRef = ref.closest(
+ '.wp-block-query[data-wp-router-region]'
);
- yield actions.prefetch( ref.href );
- }
+ const isDisabled = queryRef?.dataset.wpNavigationDisabled;
+ if ( isValidLink( ref ) && ! isDisabled ) {
+ const { actions } = yield import(
+ '@wordpress/interactivity-router'
+ );
+ yield actions.prefetch( ref.href );
+ }
+ },
},
- },
- callbacks: {
- *prefetch() {
- const { url } = getContext();
- const { ref } = getElement();
- if ( url && isValidLink( ref ) ) {
- const { actions } = yield import(
- '@wordpress/interactivity-router'
- );
- yield actions.prefetch( ref.href );
- }
+ callbacks: {
+ *prefetch() {
+ const { url } = getContext();
+ const { ref } = getElement();
+ if ( url && isValidLink( ref ) ) {
+ const { actions } = yield import(
+ '@wordpress/interactivity-router'
+ );
+ yield actions.prefetch( ref.href );
+ }
+ },
},
},
-} );
+ { lock: true }
+);
diff --git a/packages/block-library/src/search/view.js b/packages/block-library/src/search/view.js
index b633bf971f363a..0e4c462a2e3213 100644
--- a/packages/block-library/src/search/view.js
+++ b/packages/block-library/src/search/view.js
@@ -3,66 +3,70 @@
*/
import { store, getContext, getElement } from '@wordpress/interactivity';
-const { actions } = store( 'core/search', {
- state: {
- get ariaLabel() {
- const {
- isSearchInputVisible,
- ariaLabelCollapsed,
- ariaLabelExpanded,
- } = getContext();
- return isSearchInputVisible
- ? ariaLabelExpanded
- : ariaLabelCollapsed;
+const { actions } = store(
+ 'core/search',
+ {
+ state: {
+ get ariaLabel() {
+ const {
+ isSearchInputVisible,
+ ariaLabelCollapsed,
+ ariaLabelExpanded,
+ } = getContext();
+ return isSearchInputVisible
+ ? ariaLabelExpanded
+ : ariaLabelCollapsed;
+ },
+ get ariaControls() {
+ const { isSearchInputVisible, inputId } = getContext();
+ return isSearchInputVisible ? null : inputId;
+ },
+ get type() {
+ const { isSearchInputVisible } = getContext();
+ return isSearchInputVisible ? 'submit' : 'button';
+ },
+ get tabindex() {
+ const { isSearchInputVisible } = getContext();
+ return isSearchInputVisible ? '0' : '-1';
+ },
},
- get ariaControls() {
- const { isSearchInputVisible, inputId } = getContext();
- return isSearchInputVisible ? null : inputId;
- },
- get type() {
- const { isSearchInputVisible } = getContext();
- return isSearchInputVisible ? 'submit' : 'button';
- },
- get tabindex() {
- const { isSearchInputVisible } = getContext();
- return isSearchInputVisible ? '0' : '-1';
- },
- },
- actions: {
- openSearchInput( event ) {
- const ctx = getContext();
- const { ref } = getElement();
- if ( ! ctx.isSearchInputVisible ) {
- event.preventDefault();
- ctx.isSearchInputVisible = true;
- ref.parentElement.querySelector( 'input' ).focus();
- }
- },
- closeSearchInput() {
- const ctx = getContext();
- ctx.isSearchInputVisible = false;
- },
- handleSearchKeydown( event ) {
- const { ref } = getElement();
- // If Escape close the menu.
- if ( event?.key === 'Escape' ) {
- actions.closeSearchInput();
- ref.querySelector( 'button' ).focus();
- }
- },
- handleSearchFocusout( event ) {
- const { ref } = getElement();
- // If focus is outside search form, and in the document, close menu
- // event.target === The element losing focus
- // event.relatedTarget === The element receiving focus (if any)
- // When focusout is outside the document,
- // `window.document.activeElement` doesn't change.
- if (
- ! ref.contains( event.relatedTarget ) &&
- event.target !== window.document.activeElement
- ) {
- actions.closeSearchInput();
- }
+ actions: {
+ openSearchInput( event ) {
+ const ctx = getContext();
+ const { ref } = getElement();
+ if ( ! ctx.isSearchInputVisible ) {
+ event.preventDefault();
+ ctx.isSearchInputVisible = true;
+ ref.parentElement.querySelector( 'input' ).focus();
+ }
+ },
+ closeSearchInput() {
+ const ctx = getContext();
+ ctx.isSearchInputVisible = false;
+ },
+ handleSearchKeydown( event ) {
+ const { ref } = getElement();
+ // If Escape close the menu.
+ if ( event?.key === 'Escape' ) {
+ actions.closeSearchInput();
+ ref.querySelector( 'button' ).focus();
+ }
+ },
+ handleSearchFocusout( event ) {
+ const { ref } = getElement();
+ // If focus is outside search form, and in the document, close menu
+ // event.target === The element losing focus
+ // event.relatedTarget === The element receiving focus (if any)
+ // When focusout is outside the document,
+ // `window.document.activeElement` doesn't change.
+ if (
+ ! ref.contains( event.relatedTarget ) &&
+ event.target !== window.document.activeElement
+ ) {
+ actions.closeSearchInput();
+ }
+ },
},
},
-} );
+ { lock: true }
+);