-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Image: Reimplement lightbox trigger as a minimal button in corner of image #55212
Changes from all commits
2956be6
5a084d4
c264096
6302aea
de70bcb
25a91bc
994e04c
0e86105
d9357e5
cea71f1
e3c0d00
6bae728
c7bfa37
bd9d8b8
cd54522
09b2f7b
94d8c3a
dce84bc
c1e79f9
ef8f9e2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -103,12 +103,10 @@ store( | |
context.core.image.lastFocusedElement = | ||
window.document.activeElement; | ||
context.core.image.scrollDelta = 0; | ||
context.core.image.pointerType = event.pointerType; | ||
|
||
context.core.image.lightboxEnabled = true; | ||
setStyles( | ||
context, | ||
event.target.previousElementSibling | ||
); | ||
setStyles( context, context.core.image.imageRef ); | ||
|
||
context.core.image.scrollTopReset = | ||
window.pageYOffset || | ||
|
@@ -137,7 +135,7 @@ store( | |
false | ||
); | ||
}, | ||
hideLightbox: async ( { context } ) => { | ||
hideLightbox: async ( { context, event } ) => { | ||
context.core.image.hideAnimationEnabled = true; | ||
if ( context.core.image.lightboxEnabled ) { | ||
// We want to wait until the close animation is completed | ||
|
@@ -154,9 +152,16 @@ store( | |
}, 450 ); | ||
|
||
context.core.image.lightboxEnabled = false; | ||
context.core.image.lastFocusedElement.focus( { | ||
preventScroll: true, | ||
} ); | ||
|
||
// We want to avoid drawing attention to the button | ||
// after the lightbox closes for mouse and touch users. | ||
// Note that the `event.pointerType` property returns | ||
// as an empty string if a keyboard fired the event. | ||
if ( event.pointerType === '' ) { | ||
context.core.image.lastFocusedElement.focus( { | ||
preventScroll: true, | ||
} ); | ||
} | ||
} | ||
}, | ||
handleKeydown: ( { context, actions, event } ) => { | ||
|
@@ -191,11 +196,12 @@ store( | |
} | ||
} | ||
}, | ||
handleLoad: ( { state, context, effects, ref } ) => { | ||
// This is fired just by lazily loaded | ||
// images on the page, not all images. | ||
handleLoad: ( { context, effects, ref } ) => { | ||
context.core.image.imageLoaded = true; | ||
context.core.image.imageCurrentSrc = ref.currentSrc; | ||
effects.core.image.setButtonStyles( { | ||
state, | ||
context, | ||
ref, | ||
} ); | ||
|
@@ -258,17 +264,14 @@ store( | |
effects: { | ||
core: { | ||
image: { | ||
setCurrentSrc: ( { context, ref } ) => { | ||
initOriginImage: ( { context, ref } ) => { | ||
context.core.image.imageRef = ref; | ||
if ( ref.complete ) { | ||
context.core.image.imageLoaded = true; | ||
context.core.image.imageCurrentSrc = ref.currentSrc; | ||
} | ||
}, | ||
initLightbox: async ( { context, ref } ) => { | ||
context.core.image.figureRef = | ||
ref.querySelector( 'figure' ); | ||
context.core.image.imageRef = | ||
ref.querySelector( 'img' ); | ||
if ( context.core.image.lightboxEnabled ) { | ||
const focusableElements = | ||
ref.querySelectorAll( focusableSelectors ); | ||
|
@@ -279,10 +282,17 @@ store( | |
focusableElements.length - 1 | ||
]; | ||
|
||
ref.querySelector( '.close-button' ).focus(); | ||
// We want to avoid drawing unnecessary attention to the close | ||
// button for mouse and touch users. Note that even if opening | ||
// the lightbox via keyboard, the event fired is of type | ||
// `pointerEvent`, so we need to rely on the `event.pointerType` | ||
// property, which returns an empty string for keyboard events. | ||
if ( context.core.image.pointerType === '' ) { | ||
ref.querySelector( '.close-button' ).focus(); | ||
} | ||
Comment on lines
+285
to
+292
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why don't we want to focus the close button when opening the button by clicking the image? Right now, it keeps the active element outside of the modal, which I believe is not correct, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That was a point brought up some folks from design a while back — they did not want a close button at all because they felt it would distract and be unnecessary. I figured adding this so the focus ring doesn't become visible and draw attention to the close button would be a good move. Personally I would prefer to not have my attention immediately drawn to a close button when trying to get a better view of an image. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are potential accessibility issues here, though. The context would indeed be incorrect for folks who activate the lightbox using a mouse while also using a screen reader. Is this a use case we should handle? I'm currently searching Google for guidance around this, and will also ping @afercia @joedolson @alexstine for feedback. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem is that with that approach we are losing the focus and it is causing some issues. When opening an image by clicking on it:
|
||
} | ||
}, | ||
setButtonStyles: ( { state, context, ref } ) => { | ||
setButtonStyles: ( { context, ref } ) => { | ||
const { | ||
naturalWidth, | ||
naturalHeight, | ||
|
@@ -291,54 +301,71 @@ store( | |
} = ref; | ||
|
||
// If the image isn't loaded yet, we can't | ||
// calculate how big the button should be. | ||
// calculate where the button should be. | ||
if ( naturalWidth === 0 || naturalHeight === 0 ) { | ||
return; | ||
} | ||
|
||
// Subscribe to the window dimensions so we can | ||
// recalculate the styles if the window is resized. | ||
if ( | ||
( state.core.image.windowWidth || | ||
state.core.image.windowHeight ) && | ||
context.core.image.scaleAttr === 'contain' | ||
) { | ||
// In the case of an image with object-fit: contain, the | ||
// size of the img element can be larger than the image itself, | ||
// so we need to calculate the size of the button to match. | ||
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 ); | ||
figureHeight = | ||
figureHeight - | ||
caption.offsetHeight - | ||
parseFloat( captionComputedStyle.marginTop ) - | ||
parseFloat( captionComputedStyle.marginBottom ); | ||
} | ||
|
||
const buttonOffsetTop = figureHeight - offsetHeight; | ||
const buttonOffsetRight = figureWidth - offsetWidth; | ||
|
||
// In the case of an image with object-fit: contain, the | ||
// size of the <img> element can be larger than the image itself, | ||
// so we need to calculate where to place the button. | ||
if ( context.core.image.scaleAttr === 'contain' ) { | ||
// Natural ratio of the image. | ||
const naturalRatio = naturalWidth / naturalHeight; | ||
// Offset ratio of the image. | ||
const offsetRatio = offsetWidth / offsetHeight; | ||
|
||
if ( naturalRatio > offsetRatio ) { | ||
if ( naturalRatio >= offsetRatio ) { | ||
// If it reaches the width first, keep | ||
// the width and recalculate the height. | ||
context.core.image.imageButtonWidth = | ||
offsetWidth; | ||
const buttonHeight = offsetWidth / naturalRatio; | ||
context.core.image.imageButtonHeight = | ||
buttonHeight; | ||
// the width and compute the height. | ||
const referenceHeight = | ||
offsetWidth / naturalRatio; | ||
context.core.image.imageButtonTop = | ||
( offsetHeight - buttonHeight ) / 2; | ||
( offsetHeight - referenceHeight ) / 2 + | ||
buttonOffsetTop + | ||
10; | ||
context.core.image.imageButtonRight = | ||
buttonOffsetRight + 10; | ||
} else { | ||
// If it reaches the height first, keep | ||
// the height and recalculate the width. | ||
context.core.image.imageButtonHeight = | ||
offsetHeight; | ||
const buttonWidth = offsetHeight * naturalRatio; | ||
context.core.image.imageButtonWidth = | ||
buttonWidth; | ||
context.core.image.imageButtonLeft = | ||
( offsetWidth - buttonWidth ) / 2; | ||
// the height and compute the width. | ||
const referenceWidth = | ||
offsetHeight * naturalRatio; | ||
context.core.image.imageButtonTop = | ||
buttonOffsetTop + 10; | ||
context.core.image.imageButtonRight = | ||
( offsetWidth - referenceWidth ) / 2 + | ||
buttonOffsetRight + | ||
10; | ||
} | ||
} else { | ||
// In all other cases, we can trust that the size of | ||
// the image is the right size for the button as well. | ||
|
||
context.core.image.imageButtonWidth = offsetWidth; | ||
context.core.image.imageButtonHeight = offsetHeight; | ||
context.core.image.imageButtonTop = | ||
buttonOffsetTop + 10; | ||
context.core.image.imageButtonRight = | ||
buttonOffsetRight + 10; | ||
} | ||
}, | ||
setStylesOnResize: ( { state, context, ref } ) => { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as with the close button: if we navigate to a lightbox with the keyboard, but I close it using a click, I lose the focus, which I assume is not correct, right?