|
1 import * as __WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__ from "@wordpress/interactivity"; |
|
2 /******/ // The require scope |
|
3 /******/ var __webpack_require__ = {}; |
|
4 /******/ |
|
5 /************************************************************************/ |
|
6 /******/ /* webpack/runtime/define property getters */ |
|
7 /******/ (() => { |
|
8 /******/ // define getter functions for harmony exports |
|
9 /******/ __webpack_require__.d = (exports, definition) => { |
|
10 /******/ for(var key in definition) { |
|
11 /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { |
|
12 /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); |
|
13 /******/ } |
|
14 /******/ } |
|
15 /******/ }; |
|
16 /******/ })(); |
|
17 /******/ |
|
18 /******/ /* webpack/runtime/hasOwnProperty shorthand */ |
|
19 /******/ (() => { |
|
20 /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) |
|
21 /******/ })(); |
|
22 /******/ |
|
23 /************************************************************************/ |
|
24 var __webpack_exports__ = {}; |
|
25 |
|
26 ;// external "@wordpress/interactivity" |
|
27 var x = (y) => { |
|
28 var x = {}; __webpack_require__.d(x, y); return x |
|
29 } |
|
30 var y = (x) => (() => (x)) |
|
31 const interactivity_namespaceObject = x({ ["getContext"]: () => (__WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__.getContext), ["getElement"]: () => (__WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__.getElement), ["store"]: () => (__WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__.store), ["withSyncEvent"]: () => (__WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__.withSyncEvent) }); |
|
32 ;// ./node_modules/@wordpress/block-library/build-module/image/view.js |
|
33 /** |
|
34 * WordPress dependencies |
|
35 */ |
|
36 |
|
37 |
|
38 /** |
|
39 * Tracks whether user is touching screen; used to differentiate behavior for |
|
40 * touch and mouse input. |
|
41 * |
|
42 * @type {boolean} |
|
43 */ |
|
44 let isTouching = false; |
|
45 |
|
46 /** |
|
47 * Tracks the last time the screen was touched; used to differentiate behavior |
|
48 * for touch and mouse input. |
|
49 * |
|
50 * @type {number} |
|
51 */ |
|
52 let lastTouchTime = 0; |
|
53 const { |
|
54 state, |
|
55 actions, |
|
56 callbacks |
|
57 } = (0,interactivity_namespaceObject.store)('core/image', { |
|
58 state: { |
|
59 currentImageId: null, |
|
60 get currentImage() { |
|
61 return state.metadata[state.currentImageId]; |
|
62 }, |
|
63 get overlayOpened() { |
|
64 return state.currentImageId !== null; |
|
65 }, |
|
66 get roleAttribute() { |
|
67 return state.overlayOpened ? 'dialog' : null; |
|
68 }, |
|
69 get ariaModal() { |
|
70 return state.overlayOpened ? 'true' : null; |
|
71 }, |
|
72 get enlargedSrc() { |
|
73 return state.currentImage.uploadedSrc || 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='; |
|
74 }, |
|
75 get figureStyles() { |
|
76 return state.overlayOpened && `${state.currentImage.figureStyles?.replace(/margin[^;]*;?/g, '')};`; |
|
77 }, |
|
78 get imgStyles() { |
|
79 return state.overlayOpened && `${state.currentImage.imgStyles?.replace(/;$/, '')}; object-fit:cover;`; |
|
80 }, |
|
81 get imageButtonRight() { |
|
82 const { |
|
83 imageId |
|
84 } = (0,interactivity_namespaceObject.getContext)(); |
|
85 return state.metadata[imageId].imageButtonRight; |
|
86 }, |
|
87 get imageButtonTop() { |
|
88 const { |
|
89 imageId |
|
90 } = (0,interactivity_namespaceObject.getContext)(); |
|
91 return state.metadata[imageId].imageButtonTop; |
|
92 }, |
|
93 get isContentHidden() { |
|
94 const ctx = (0,interactivity_namespaceObject.getContext)(); |
|
95 return state.overlayEnabled && state.currentImageId === ctx.imageId; |
|
96 }, |
|
97 get isContentVisible() { |
|
98 const ctx = (0,interactivity_namespaceObject.getContext)(); |
|
99 return !state.overlayEnabled && state.currentImageId === ctx.imageId; |
|
100 } |
|
101 }, |
|
102 actions: { |
|
103 showLightbox() { |
|
104 const { |
|
105 imageId |
|
106 } = (0,interactivity_namespaceObject.getContext)(); |
|
107 |
|
108 // Bails out if the image has not loaded yet. |
|
109 if (!state.metadata[imageId].imageRef?.complete) { |
|
110 return; |
|
111 } |
|
112 |
|
113 // Stores the positions of the scroll to fix it until the overlay is |
|
114 // closed. |
|
115 state.scrollTopReset = document.documentElement.scrollTop; |
|
116 state.scrollLeftReset = document.documentElement.scrollLeft; |
|
117 |
|
118 // Sets the current expanded image in the state and enables the overlay. |
|
119 state.overlayEnabled = true; |
|
120 state.currentImageId = imageId; |
|
121 |
|
122 // Computes the styles of the overlay for the animation. |
|
123 callbacks.setOverlayStyles(); |
|
124 }, |
|
125 hideLightbox() { |
|
126 if (state.overlayEnabled) { |
|
127 // Starts the overlay closing animation. The showClosingAnimation |
|
128 // class is used to avoid showing it on page load. |
|
129 state.showClosingAnimation = true; |
|
130 state.overlayEnabled = false; |
|
131 |
|
132 // Waits until the close animation has completed before allowing a |
|
133 // user to scroll again. The duration of this animation is defined in |
|
134 // the `styles.scss` file, but in any case we should wait a few |
|
135 // milliseconds longer than the duration, otherwise a user may scroll |
|
136 // too soon and cause the animation to look sloppy. |
|
137 setTimeout(function () { |
|
138 // Delays before changing the focus. Otherwise the focus ring will |
|
139 // appear on Firefox before the image has finished animating, which |
|
140 // looks broken. |
|
141 state.currentImage.buttonRef.focus({ |
|
142 preventScroll: true |
|
143 }); |
|
144 |
|
145 // Resets the current image id to mark the overlay as closed. |
|
146 state.currentImageId = null; |
|
147 }, 450); |
|
148 } |
|
149 }, |
|
150 handleKeydown: (0,interactivity_namespaceObject.withSyncEvent)(event => { |
|
151 if (state.overlayEnabled) { |
|
152 // Focuses the close button when the user presses the tab key. |
|
153 if (event.key === 'Tab') { |
|
154 event.preventDefault(); |
|
155 const { |
|
156 ref |
|
157 } = (0,interactivity_namespaceObject.getElement)(); |
|
158 ref.querySelector('button').focus(); |
|
159 } |
|
160 // Closes the lightbox when the user presses the escape key. |
|
161 if (event.key === 'Escape') { |
|
162 actions.hideLightbox(); |
|
163 } |
|
164 } |
|
165 }), |
|
166 handleTouchMove: (0,interactivity_namespaceObject.withSyncEvent)(event => { |
|
167 // On mobile devices, prevents triggering the scroll event because |
|
168 // otherwise the page jumps around when it resets the scroll position. |
|
169 // This also means that closing the lightbox requires that a user |
|
170 // perform a simple tap. This may be changed in the future if there is a |
|
171 // better alternative to override or reset the scroll position during |
|
172 // swipe actions. |
|
173 if (state.overlayEnabled) { |
|
174 event.preventDefault(); |
|
175 } |
|
176 }), |
|
177 handleTouchStart() { |
|
178 isTouching = true; |
|
179 }, |
|
180 handleTouchEnd() { |
|
181 // Waits a few milliseconds before resetting to ensure that pinch to |
|
182 // zoom works consistently on mobile devices when the lightbox is open. |
|
183 lastTouchTime = Date.now(); |
|
184 isTouching = false; |
|
185 }, |
|
186 handleScroll() { |
|
187 // Prevents scrolling behaviors that trigger content shift while the |
|
188 // lightbox is open. It would be better to accomplish through CSS alone, |
|
189 // but using overflow: hidden is currently the only way to do so and |
|
190 // that causes a layout to shift and prevents the zoom animation from |
|
191 // working in some cases because it's not possible to account for the |
|
192 // layout shift when doing the animation calculations. Instead, it uses |
|
193 // JavaScript to prevent and reset the scrolling behavior. |
|
194 if (state.overlayOpened) { |
|
195 // Avoids overriding the scroll behavior on mobile devices because |
|
196 // doing so breaks the pinch to zoom functionality, and users should |
|
197 // be able to zoom in further on the high-res image. |
|
198 if (!isTouching && Date.now() - lastTouchTime > 450) { |
|
199 // It doesn't rely on `event.preventDefault()` to prevent scrolling |
|
200 // because the scroll event can't be canceled, so it resets the |
|
201 // position instead. |
|
202 window.scrollTo(state.scrollLeftReset, state.scrollTopReset); |
|
203 } |
|
204 } |
|
205 } |
|
206 }, |
|
207 callbacks: { |
|
208 setOverlayStyles() { |
|
209 if (!state.overlayEnabled) { |
|
210 return; |
|
211 } |
|
212 let { |
|
213 naturalWidth, |
|
214 naturalHeight, |
|
215 offsetWidth: originalWidth, |
|
216 offsetHeight: originalHeight |
|
217 } = state.currentImage.imageRef; |
|
218 let { |
|
219 x: screenPosX, |
|
220 y: screenPosY |
|
221 } = state.currentImage.imageRef.getBoundingClientRect(); |
|
222 |
|
223 // Natural ratio of the image clicked to open the lightbox. |
|
224 const naturalRatio = naturalWidth / naturalHeight; |
|
225 // Original ratio of the image clicked to open the lightbox. |
|
226 let originalRatio = originalWidth / originalHeight; |
|
227 |
|
228 // If it has object-fit: contain, recalculates the original sizes |
|
229 // and the screen position without the blank spaces. |
|
230 if (state.currentImage.scaleAttr === 'contain') { |
|
231 if (naturalRatio > originalRatio) { |
|
232 const heightWithoutSpace = originalWidth / naturalRatio; |
|
233 // Recalculates screen position without the top space. |
|
234 screenPosY += (originalHeight - heightWithoutSpace) / 2; |
|
235 originalHeight = heightWithoutSpace; |
|
236 } else { |
|
237 const widthWithoutSpace = originalHeight * naturalRatio; |
|
238 // Recalculates screen position without the left space. |
|
239 screenPosX += (originalWidth - widthWithoutSpace) / 2; |
|
240 originalWidth = widthWithoutSpace; |
|
241 } |
|
242 } |
|
243 originalRatio = originalWidth / originalHeight; |
|
244 |
|
245 // Typically, it uses the image's full-sized dimensions. If those |
|
246 // dimensions have not been set (i.e. an external image with only one |
|
247 // size), the image's dimensions in the lightbox are the same |
|
248 // as those of the image in the content. |
|
249 let imgMaxWidth = parseFloat(state.currentImage.targetWidth !== 'none' ? state.currentImage.targetWidth : naturalWidth); |
|
250 let imgMaxHeight = parseFloat(state.currentImage.targetHeight !== 'none' ? state.currentImage.targetHeight : naturalHeight); |
|
251 |
|
252 // Ratio of the biggest image stored in the database. |
|
253 let imgRatio = imgMaxWidth / imgMaxHeight; |
|
254 let containerMaxWidth = imgMaxWidth; |
|
255 let containerMaxHeight = imgMaxHeight; |
|
256 let containerWidth = imgMaxWidth; |
|
257 let containerHeight = imgMaxHeight; |
|
258 |
|
259 // Checks if the target image has a different ratio than the original |
|
260 // one (thumbnail). Recalculates the width and height. |
|
261 if (naturalRatio.toFixed(2) !== imgRatio.toFixed(2)) { |
|
262 if (naturalRatio > imgRatio) { |
|
263 // If the width is reached before the height, it keeps the maxWidth |
|
264 // and recalculates the height unless the difference between the |
|
265 // maxHeight and the reducedHeight is higher than the maxWidth, |
|
266 // where it keeps the reducedHeight and recalculate the width. |
|
267 const reducedHeight = imgMaxWidth / naturalRatio; |
|
268 if (imgMaxHeight - reducedHeight > imgMaxWidth) { |
|
269 imgMaxHeight = reducedHeight; |
|
270 imgMaxWidth = reducedHeight * naturalRatio; |
|
271 } else { |
|
272 imgMaxHeight = imgMaxWidth / naturalRatio; |
|
273 } |
|
274 } else { |
|
275 // If the height is reached before the width, it keeps the maxHeight |
|
276 // and recalculate the width unlesss the difference between the |
|
277 // maxWidth and the reducedWidth is higher than the maxHeight, where |
|
278 // it keeps the reducedWidth and recalculate the height. |
|
279 const reducedWidth = imgMaxHeight * naturalRatio; |
|
280 if (imgMaxWidth - reducedWidth > imgMaxHeight) { |
|
281 imgMaxWidth = reducedWidth; |
|
282 imgMaxHeight = reducedWidth / naturalRatio; |
|
283 } else { |
|
284 imgMaxWidth = imgMaxHeight * naturalRatio; |
|
285 } |
|
286 } |
|
287 containerWidth = imgMaxWidth; |
|
288 containerHeight = imgMaxHeight; |
|
289 imgRatio = imgMaxWidth / imgMaxHeight; |
|
290 |
|
291 // Calculates the max size of the container. |
|
292 if (originalRatio > imgRatio) { |
|
293 containerMaxWidth = imgMaxWidth; |
|
294 containerMaxHeight = containerMaxWidth / originalRatio; |
|
295 } else { |
|
296 containerMaxHeight = imgMaxHeight; |
|
297 containerMaxWidth = containerMaxHeight * originalRatio; |
|
298 } |
|
299 } |
|
300 |
|
301 // If the image has been pixelated on purpose, it keeps that size. |
|
302 if (originalWidth > containerWidth || originalHeight > containerHeight) { |
|
303 containerWidth = originalWidth; |
|
304 containerHeight = originalHeight; |
|
305 } |
|
306 |
|
307 // Calculates the final lightbox image size and the scale factor. |
|
308 // MaxWidth is either the window container (accounting for padding) or |
|
309 // the image resolution. |
|
310 let horizontalPadding = 0; |
|
311 if (window.innerWidth > 480) { |
|
312 horizontalPadding = 80; |
|
313 } else if (window.innerWidth > 1920) { |
|
314 horizontalPadding = 160; |
|
315 } |
|
316 const verticalPadding = 80; |
|
317 const targetMaxWidth = Math.min(window.innerWidth - horizontalPadding, containerWidth); |
|
318 const targetMaxHeight = Math.min(window.innerHeight - verticalPadding, containerHeight); |
|
319 const targetContainerRatio = targetMaxWidth / targetMaxHeight; |
|
320 if (originalRatio > targetContainerRatio) { |
|
321 // If targetMaxWidth is reached before targetMaxHeight. |
|
322 containerWidth = targetMaxWidth; |
|
323 containerHeight = containerWidth / originalRatio; |
|
324 } else { |
|
325 // If targetMaxHeight is reached before targetMaxWidth. |
|
326 containerHeight = targetMaxHeight; |
|
327 containerWidth = containerHeight * originalRatio; |
|
328 } |
|
329 const containerScale = originalWidth / containerWidth; |
|
330 const lightboxImgWidth = imgMaxWidth * (containerWidth / containerMaxWidth); |
|
331 const lightboxImgHeight = imgMaxHeight * (containerHeight / containerMaxHeight); |
|
332 |
|
333 // As of this writing, using the calculations above will render the |
|
334 // lightbox with a small, erroneous whitespace on the left side of the |
|
335 // image in iOS Safari, perhaps due to an inconsistency in how browsers |
|
336 // handle absolute positioning and CSS transformation. In any case, |
|
337 // adding 1 pixel to the container width and height solves the problem, |
|
338 // though this can be removed if the issue is fixed in the future. |
|
339 state.overlayStyles = ` |
|
340 --wp--lightbox-initial-top-position: ${screenPosY}px; |
|
341 --wp--lightbox-initial-left-position: ${screenPosX}px; |
|
342 --wp--lightbox-container-width: ${containerWidth + 1}px; |
|
343 --wp--lightbox-container-height: ${containerHeight + 1}px; |
|
344 --wp--lightbox-image-width: ${lightboxImgWidth}px; |
|
345 --wp--lightbox-image-height: ${lightboxImgHeight}px; |
|
346 --wp--lightbox-scale: ${containerScale}; |
|
347 --wp--lightbox-scrollbar-width: ${window.innerWidth - document.documentElement.clientWidth}px; |
|
348 `; |
|
349 }, |
|
350 setButtonStyles() { |
|
351 const { |
|
352 imageId |
|
353 } = (0,interactivity_namespaceObject.getContext)(); |
|
354 const { |
|
355 ref |
|
356 } = (0,interactivity_namespaceObject.getElement)(); |
|
357 state.metadata[imageId].imageRef = ref; |
|
358 state.metadata[imageId].currentSrc = ref.currentSrc; |
|
359 const { |
|
360 naturalWidth, |
|
361 naturalHeight, |
|
362 offsetWidth, |
|
363 offsetHeight |
|
364 } = ref; |
|
365 |
|
366 // If the image isn't loaded yet, it can't calculate where the button |
|
367 // should be. |
|
368 if (naturalWidth === 0 || naturalHeight === 0) { |
|
369 return; |
|
370 } |
|
371 const figure = ref.parentElement; |
|
372 const figureWidth = ref.parentElement.clientWidth; |
|
373 |
|
374 // It needs special handling for the height because a caption will cause |
|
375 // the figure to be taller than the image, which means it needs to |
|
376 // account for that when calculating the placement of the button in the |
|
377 // top right corner of the image. |
|
378 let figureHeight = ref.parentElement.clientHeight; |
|
379 const caption = figure.querySelector('figcaption'); |
|
380 if (caption) { |
|
381 const captionComputedStyle = window.getComputedStyle(caption); |
|
382 if (!['absolute', 'fixed'].includes(captionComputedStyle.position)) { |
|
383 figureHeight = figureHeight - caption.offsetHeight - parseFloat(captionComputedStyle.marginTop) - parseFloat(captionComputedStyle.marginBottom); |
|
384 } |
|
385 } |
|
386 const buttonOffsetTop = figureHeight - offsetHeight; |
|
387 const buttonOffsetRight = figureWidth - offsetWidth; |
|
388 let imageButtonTop = buttonOffsetTop + 16; |
|
389 let imageButtonRight = buttonOffsetRight + 16; |
|
390 |
|
391 // In the case of an image with object-fit: contain, the size of the |
|
392 // <img> element can be larger than the image itself, so it needs to |
|
393 // calculate where to place the button. |
|
394 if (state.metadata[imageId].scaleAttr === 'contain') { |
|
395 // Natural ratio of the image. |
|
396 const naturalRatio = naturalWidth / naturalHeight; |
|
397 // Offset ratio of the image. |
|
398 const offsetRatio = offsetWidth / offsetHeight; |
|
399 if (naturalRatio >= offsetRatio) { |
|
400 // If it reaches the width first, it keeps the width and compute the |
|
401 // height. |
|
402 const referenceHeight = offsetWidth / naturalRatio; |
|
403 imageButtonTop = (offsetHeight - referenceHeight) / 2 + buttonOffsetTop + 16; |
|
404 imageButtonRight = buttonOffsetRight + 16; |
|
405 } else { |
|
406 // If it reaches the height first, it keeps the height and compute |
|
407 // the width. |
|
408 const referenceWidth = offsetHeight * naturalRatio; |
|
409 imageButtonTop = buttonOffsetTop + 16; |
|
410 imageButtonRight = (offsetWidth - referenceWidth) / 2 + buttonOffsetRight + 16; |
|
411 } |
|
412 } |
|
413 state.metadata[imageId].imageButtonTop = imageButtonTop; |
|
414 state.metadata[imageId].imageButtonRight = imageButtonRight; |
|
415 }, |
|
416 setOverlayFocus() { |
|
417 if (state.overlayEnabled) { |
|
418 // Moves the focus to the dialog when it opens. |
|
419 const { |
|
420 ref |
|
421 } = (0,interactivity_namespaceObject.getElement)(); |
|
422 ref.focus(); |
|
423 } |
|
424 }, |
|
425 initTriggerButton() { |
|
426 const { |
|
427 imageId |
|
428 } = (0,interactivity_namespaceObject.getContext)(); |
|
429 const { |
|
430 ref |
|
431 } = (0,interactivity_namespaceObject.getElement)(); |
|
432 state.metadata[imageId].buttonRef = ref; |
|
433 } |
|
434 } |
|
435 }, { |
|
436 lock: true |
|
437 }); |
|
438 |