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