@wordpress/block-library 8.19.7 → 8.19.9

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.
@@ -218,28 +218,36 @@ function block_core_image_render_lightbox( $block_content, $block ) {
218
218
  )
219
219
  );
220
220
  $w->next_tag( 'img' );
221
- $w->set_attribute( 'data-wp-init', 'effects.core.image.setCurrentSrc' );
221
+ $w->set_attribute( 'data-wp-init', 'effects.core.image.initOriginImage' );
222
222
  $w->set_attribute( 'data-wp-on--load', 'actions.core.image.handleLoad' );
223
223
  $w->set_attribute( 'data-wp-effect', 'effects.core.image.setButtonStyles' );
224
+ // We need to set an event callback on the `img` specifically
225
+ // because the `figure` element can also contain a caption, and
226
+ // we don't want to trigger the lightbox when the caption is clicked.
227
+ $w->set_attribute( 'data-wp-on--click', 'actions.core.image.showLightbox' );
224
228
  $w->set_attribute( 'data-wp-effect--setStylesOnResize', 'effects.core.image.setStylesOnResize' );
225
229
  $body_content = $w->get_updated_html();
226
230
 
227
- // Wrap the image in the body content with a button.
231
+ // Add a button alongside image in the body content.
228
232
  $img = null;
229
233
  preg_match( '/<img[^>]+>/', $body_content, $img );
230
234
 
231
235
  $button =
232
236
  $img[0]
233
237
  . '<button
238
+ class="lightbox-trigger"
234
239
  type="button"
235
240
  aria-haspopup="dialog"
236
241
  aria-label="' . esc_attr( $aria_label ) . '"
237
242
  data-wp-on--click="actions.core.image.showLightbox"
238
- data-wp-style--width="context.core.image.imageButtonWidth"
239
- data-wp-style--height="context.core.image.imageButtonHeight"
240
- data-wp-style--left="context.core.image.imageButtonLeft"
243
+ data-wp-style--right="context.core.image.imageButtonRight"
241
244
  data-wp-style--top="context.core.image.imageButtonTop"
242
- ></button>';
245
+ style="background: #000"
246
+ >
247
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false">
248
+ <Path stroke="#FFFFFF" d="M6 4a2 2 0 0 0-2 2v3h1.5V6a.5.5 0 0 1 .5-.5h3V4H6Zm3 14.5H6a.5.5 0 0 1-.5-.5v-3H4v3a2 2 0 0 0 2 2h3v-1.5Zm6 1.5v-1.5h3a.5.5 0 0 0 .5-.5v-3H20v3a2 2 0 0 1-2 2h-3Zm3-16a2 2 0 0 1 2 2v3h-1.5V6a.5.5 0 0 0-.5-.5h-3V4h3Z" />
249
+ </svg>
250
+ </button>';
243
251
 
244
252
  $body_content = preg_replace( '/<img[^>]+>/', $button, $body_content );
245
253
 
@@ -312,12 +320,13 @@ function block_core_image_render_lightbox( $block_content, $block ) {
312
320
  data-wp-on--touchmove="actions.core.image.handleTouchMove"
313
321
  data-wp-on--touchend="actions.core.image.handleTouchEnd"
314
322
  data-wp-on--click="actions.core.image.hideLightbox"
323
+ tabindex="-1"
315
324
  >
316
325
  <button type="button" aria-label="$close_button_label" style="fill: $close_button_color" class="close-button" data-wp-on--click="actions.core.image.hideLightbox">
317
326
  $close_button_icon
318
327
  </button>
319
328
  <div class="lightbox-image-container">$initial_image_content</div>
320
- <div class="lightbox-image-container">$enlarged_image_content</div>
329
+ <div class="lightbox-image-container">$enlarged_image_content</div>
321
330
  <div class="scrim" style="background-color: $background_color" aria-hidden="true"></div>
322
331
  </div>
323
332
  HTML;
@@ -157,14 +157,28 @@
157
157
  display: flex;
158
158
  flex-direction: column;
159
159
 
160
+ img {
161
+ cursor: zoom-in;
162
+ }
163
+
164
+ img:hover + button {
165
+ opacity: 1;
166
+ }
167
+
160
168
  button {
169
+ opacity: 0;
161
170
  border: none;
162
- background: none;
171
+ background: #000;
163
172
  cursor: zoom-in;
164
- width: 100%;
165
- height: 100%;
173
+ width: 24px;
174
+ height: 24px;
166
175
  position: absolute;
167
176
  z-index: 100;
177
+ top: 10px;
178
+ right: 10px;
179
+ text-align: center;
180
+ padding: 0;
181
+ border-radius: 10%;
168
182
 
169
183
  &:focus-visible {
170
184
  outline: 5px auto #212121;
@@ -172,10 +186,19 @@
172
186
  outline-offset: 5px;
173
187
  }
174
188
 
189
+ &:hover {
190
+ cursor: pointer;
191
+ opacity: 1;
192
+ }
193
+
194
+ &:focus {
195
+ opacity: 1;
196
+ }
197
+
175
198
  &:hover,
176
199
  &:focus,
177
200
  &:not(:hover):not(:active):not(.has-background) {
178
- background: none;
201
+ background: #000;
179
202
  border: none;
180
203
  }
181
204
  }
package/src/image/view.js CHANGED
@@ -103,12 +103,10 @@ store(
103
103
  context.core.image.lastFocusedElement =
104
104
  window.document.activeElement;
105
105
  context.core.image.scrollDelta = 0;
106
+ context.core.image.pointerType = event.pointerType;
106
107
 
107
108
  context.core.image.lightboxEnabled = true;
108
- setStyles(
109
- context,
110
- event.target.previousElementSibling
111
- );
109
+ setStyles( context, context.core.image.imageRef );
112
110
 
113
111
  context.core.image.scrollTopReset =
114
112
  window.pageYOffset ||
@@ -151,12 +149,15 @@ store(
151
149
  'scroll',
152
150
  scrollCallback
153
151
  );
152
+ // If we don't delay before changing the focus,
153
+ // the focus ring will appear on Firefox before
154
+ // the image has finished animating, which looks broken.
155
+ context.core.image.lightboxTriggerRef.focus( {
156
+ preventScroll: true,
157
+ } );
154
158
  }, 450 );
155
159
 
156
160
  context.core.image.lightboxEnabled = false;
157
- context.core.image.lastFocusedElement.focus( {
158
- preventScroll: true,
159
- } );
160
161
  }
161
162
  },
162
163
  handleKeydown: ( { context, actions, event } ) => {
@@ -191,11 +192,12 @@ store(
191
192
  }
192
193
  }
193
194
  },
194
- handleLoad: ( { state, context, effects, ref } ) => {
195
+ // This is fired just by lazily loaded
196
+ // images on the page, not all images.
197
+ handleLoad: ( { context, effects, ref } ) => {
195
198
  context.core.image.imageLoaded = true;
196
199
  context.core.image.imageCurrentSrc = ref.currentSrc;
197
200
  effects.core.image.setButtonStyles( {
198
- state,
199
201
  context,
200
202
  ref,
201
203
  } );
@@ -258,17 +260,18 @@ store(
258
260
  effects: {
259
261
  core: {
260
262
  image: {
261
- setCurrentSrc: ( { context, ref } ) => {
263
+ initOriginImage: ( { context, ref } ) => {
264
+ context.core.image.imageRef = ref;
265
+ context.core.image.lightboxTriggerRef =
266
+ ref.parentElement.querySelector(
267
+ '.lightbox-trigger'
268
+ );
262
269
  if ( ref.complete ) {
263
270
  context.core.image.imageLoaded = true;
264
271
  context.core.image.imageCurrentSrc = ref.currentSrc;
265
272
  }
266
273
  },
267
274
  initLightbox: async ( { context, ref } ) => {
268
- context.core.image.figureRef =
269
- ref.querySelector( 'figure' );
270
- context.core.image.imageRef =
271
- ref.querySelector( 'img' );
272
275
  if ( context.core.image.lightboxEnabled ) {
273
276
  const focusableElements =
274
277
  ref.querySelectorAll( focusableSelectors );
@@ -279,10 +282,11 @@ store(
279
282
  focusableElements.length - 1
280
283
  ];
281
284
 
282
- ref.querySelector( '.close-button' ).focus();
285
+ // Move focus to the dialog when opening it.
286
+ ref.focus();
283
287
  }
284
288
  },
285
- setButtonStyles: ( { state, context, ref } ) => {
289
+ setButtonStyles: ( { context, ref } ) => {
286
290
  const {
287
291
  naturalWidth,
288
292
  naturalHeight,
@@ -291,54 +295,71 @@ store(
291
295
  } = ref;
292
296
 
293
297
  // If the image isn't loaded yet, we can't
294
- // calculate how big the button should be.
298
+ // calculate where the button should be.
295
299
  if ( naturalWidth === 0 || naturalHeight === 0 ) {
296
300
  return;
297
301
  }
298
302
 
299
- // Subscribe to the window dimensions so we can
300
- // recalculate the styles if the window is resized.
301
- if (
302
- ( state.core.image.windowWidth ||
303
- state.core.image.windowHeight ) &&
304
- context.core.image.scaleAttr === 'contain'
305
- ) {
306
- // In the case of an image with object-fit: contain, the
307
- // size of the img element can be larger than the image itself,
308
- // so we need to calculate the size of the button to match.
303
+ const figure = ref.parentElement;
304
+ const figureWidth = ref.parentElement.clientWidth;
305
+
306
+ // We need special handling for the height because
307
+ // a caption will cause the figure to be taller than
308
+ // the image, which means we need to account for that
309
+ // when calculating the placement of the button in the
310
+ // top right corner of the image.
311
+ let figureHeight = ref.parentElement.clientHeight;
312
+ const caption = figure.querySelector( 'figcaption' );
313
+ if ( caption ) {
314
+ const captionComputedStyle =
315
+ window.getComputedStyle( caption );
316
+ figureHeight =
317
+ figureHeight -
318
+ caption.offsetHeight -
319
+ parseFloat( captionComputedStyle.marginTop ) -
320
+ parseFloat( captionComputedStyle.marginBottom );
321
+ }
322
+
323
+ const buttonOffsetTop = figureHeight - offsetHeight;
324
+ const buttonOffsetRight = figureWidth - offsetWidth;
309
325
 
326
+ // In the case of an image with object-fit: contain, the
327
+ // size of the <img> element can be larger than the image itself,
328
+ // so we need to calculate where to place the button.
329
+ if ( context.core.image.scaleAttr === 'contain' ) {
310
330
  // Natural ratio of the image.
311
331
  const naturalRatio = naturalWidth / naturalHeight;
312
332
  // Offset ratio of the image.
313
333
  const offsetRatio = offsetWidth / offsetHeight;
314
334
 
315
- if ( naturalRatio > offsetRatio ) {
335
+ if ( naturalRatio >= offsetRatio ) {
316
336
  // If it reaches the width first, keep
317
- // the width and recalculate the height.
318
- context.core.image.imageButtonWidth =
319
- offsetWidth;
320
- const buttonHeight = offsetWidth / naturalRatio;
321
- context.core.image.imageButtonHeight =
322
- buttonHeight;
337
+ // the width and compute the height.
338
+ const referenceHeight =
339
+ offsetWidth / naturalRatio;
323
340
  context.core.image.imageButtonTop =
324
- ( offsetHeight - buttonHeight ) / 2;
341
+ ( offsetHeight - referenceHeight ) / 2 +
342
+ buttonOffsetTop +
343
+ 10;
344
+ context.core.image.imageButtonRight =
345
+ buttonOffsetRight + 10;
325
346
  } else {
326
347
  // If it reaches the height first, keep
327
- // the height and recalculate the width.
328
- context.core.image.imageButtonHeight =
329
- offsetHeight;
330
- const buttonWidth = offsetHeight * naturalRatio;
331
- context.core.image.imageButtonWidth =
332
- buttonWidth;
333
- context.core.image.imageButtonLeft =
334
- ( offsetWidth - buttonWidth ) / 2;
348
+ // the height and compute the width.
349
+ const referenceWidth =
350
+ offsetHeight * naturalRatio;
351
+ context.core.image.imageButtonTop =
352
+ buttonOffsetTop + 10;
353
+ context.core.image.imageButtonRight =
354
+ ( offsetWidth - referenceWidth ) / 2 +
355
+ buttonOffsetRight +
356
+ 10;
335
357
  }
336
358
  } else {
337
- // In all other cases, we can trust that the size of
338
- // the image is the right size for the button as well.
339
-
340
- context.core.image.imageButtonWidth = offsetWidth;
341
- context.core.image.imageButtonHeight = offsetHeight;
359
+ context.core.image.imageButtonTop =
360
+ buttonOffsetTop + 10;
361
+ context.core.image.imageButtonRight =
362
+ buttonOffsetRight + 10;
342
363
  }
343
364
  },
344
365
  setStylesOnResize: ( { state, context, ref } ) => {
@@ -90,6 +90,13 @@ function block_core_navigation_add_directives_to_submenu( $w, $block_attributes
90
90
  $w->set_attribute( 'data-wp-effect', 'effects.core.navigation.initMenu' );
91
91
  $w->set_attribute( 'data-wp-on--focusout', 'actions.core.navigation.handleMenuFocusout' );
92
92
  $w->set_attribute( 'data-wp-on--keydown', 'actions.core.navigation.handleMenuKeydown' );
93
+
94
+ // This is a fix for Safari. Without it, Safari doesn't change the active
95
+ // element when the user clicks on a button. It can be removed once we add
96
+ // an overlay to capture the clicks, instead of relying on the focusout
97
+ // event.
98
+ $w->set_attribute( 'tabindex', '-1' );
99
+
93
100
  if ( ! isset( $block_attributes['openSubmenusOnClick'] ) || false === $block_attributes['openSubmenusOnClick'] ) {
94
101
  $w->set_attribute( 'data-wp-on--mouseenter', 'actions.core.navigation.openMenuOnHover' );
95
102
  $w->set_attribute( 'data-wp-on--mouseleave', 'actions.core.navigation.closeMenuOnHover' );
@@ -13,10 +13,14 @@ const focusableSelectors = [
13
13
  '[tabindex]:not([tabindex^="-"])',
14
14
  ];
15
15
 
16
+ // This is a fix for Safari in iOS/iPadOS. Without it, Safari doesn't focus out
17
+ // when the user taps in the body. It can be removed once we add an overlay to
18
+ // capture the clicks, instead of relying on the focusout event.
19
+ document.addEventListener( 'click', () => {} );
20
+
16
21
  const openMenu = ( store, menuOpenedOn ) => {
17
- const { context, ref, selectors } = store;
22
+ const { context, selectors } = store;
18
23
  selectors.core.navigation.menuOpenedBy( store )[ menuOpenedOn ] = true;
19
- context.core.navigation.previousFocus = ref;
20
24
  if ( context.core.navigation.type === 'overlay' ) {
21
25
  // Add a `has-modal-open` class to the <html> root.
22
26
  document.documentElement.classList.add( 'has-modal-open' );
@@ -33,7 +37,7 @@ const closeMenu = ( store, menuClosedOn ) => {
33
37
  window.document.activeElement
34
38
  )
35
39
  ) {
36
- context.core.navigation.previousFocus.focus();
40
+ context.core.navigation.previousFocus?.focus();
37
41
  }
38
42
  context.core.navigation.modal = null;
39
43
  context.core.navigation.previousFocus = null;
@@ -130,6 +134,8 @@ wpStore( {
130
134
  closeMenu( store, 'hover' );
131
135
  },
132
136
  openMenuOnClick( store ) {
137
+ const { context, ref } = store;
138
+ context.core.navigation.previousFocus = ref;
133
139
  openMenu( store, 'click' );
134
140
  },
135
141
  closeMenuOnClick( store ) {
@@ -140,13 +146,16 @@ wpStore( {
140
146
  openMenu( store, 'focus' );
141
147
  },
142
148
  toggleMenuOnClick: ( store ) => {
143
- const { selectors } = store;
149
+ const { selectors, context, ref } = store;
150
+ // 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
151
+ if ( window.document.activeElement !== ref ) ref.focus();
144
152
  const menuOpenedBy =
145
153
  selectors.core.navigation.menuOpenedBy( store );
146
154
  if ( menuOpenedBy.click || menuOpenedBy.focus ) {
147
155
  closeMenu( store, 'click' );
148
156
  closeMenu( store, 'focus' );
149
157
  } else {
158
+ context.core.navigation.previousFocus = ref;
150
159
  openMenu( store, 'click' );
151
160
  }
152
161
  },
@@ -194,11 +203,14 @@ wpStore( {
194
203
  // event.relatedTarget === The element receiving focus (if any)
195
204
  // When focusout is outsite the document,
196
205
  // `window.document.activeElement` doesn't change.
206
+
207
+ // The event.relatedTarget is null when something outside the navigation menu is clicked. This is only necessary for Safari.
197
208
  if (
198
- ! context.core.navigation.modal?.contains(
209
+ event.relatedTarget === null ||
210
+ ( ! context.core.navigation.modal?.contains(
199
211
  event.relatedTarget
200
212
  ) &&
201
- event.target !== window.document.activeElement
213
+ event.target !== window.document.activeElement )
202
214
  ) {
203
215
  closeMenu( store, 'click' );
204
216
  closeMenu( store, 'focus' );