PluginProbe
Gutenberg / 22.9.0
Gutenberg v22.9.0
23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 7.4.0 All 402 releases
gutenberg / build / modules / block-library / image / view.js

view.js in Gutenberg 22.9.0, at build/modules/block-library/image/view.js

464 lines 16.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 // packages/block-library/build-module/image/view.mjs
2 import {
3 store,
4 getContext,
5 getElement,
6 getConfig,
7 withSyncEvent,
8 withScope
9 } from "@wordpress/interactivity";
10
11 // packages/block-library/build-module/image/constants.mjs
12 var IMAGE_PRELOAD_DELAY = 200;
13
14 // packages/block-library/build-module/image/view.mjs
15 var isTouching = false;
16 var lastTouchTime = 0;
17 var touchStartEvent = {
18 startX: 0,
19 startY: 0,
20 startTime: 0
21 };
22 var focusableSelectors = [
23 ".wp-lightbox-close-button",
24 ".wp-lightbox-navigation-button"
25 ];
26 function getImageSrc({ uploadedSrc }) {
27 return uploadedSrc || "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=";
28 }
29 function getImageSrcset({ lightboxSrcset }) {
30 return lightboxSrcset || "";
31 }
32 var { state, actions, callbacks } = store(
33 "core/image",
34 {
35 state: {
36 selectedImageId: null,
37 selectedGalleryId: null,
38 preloadTimers: /* @__PURE__ */ new Map(),
39 preloadedImageIds: /* @__PURE__ */ new Set(),
40 get galleryImages() {
41 if (!state.selectedGalleryId) {
42 return [state.selectedImageId];
43 }
44 return Object.entries(state.metadata).filter(
45 ([, value]) => value.galleryId === state.selectedGalleryId
46 ).sort(([, a], [, b]) => {
47 const orderA = a.order ?? 0;
48 const orderB = b.order ?? 0;
49 return orderA - orderB;
50 }).map(([key]) => key);
51 },
52 get selectedImageIndex() {
53 return state.galleryImages.findIndex(
54 (id) => id === state.selectedImageId
55 );
56 },
57 get selectedImage() {
58 return state.metadata[state.selectedImageId];
59 },
60 get hasNavigationIcon() {
61 const { navigationButtonType } = state.selectedImage;
62 return navigationButtonType === "icon" || navigationButtonType === "both";
63 },
64 get hasNavigationText() {
65 const { navigationButtonType } = state.selectedImage;
66 return navigationButtonType === "text" || navigationButtonType === "both";
67 },
68 get thisImage() {
69 const { imageId } = getContext();
70 return state.metadata[imageId];
71 },
72 get hasNavigation() {
73 return state.galleryImages.length > 1;
74 },
75 get hasNextImage() {
76 return state.selectedImageIndex + 1 < state.galleryImages.length;
77 },
78 get hasPreviousImage() {
79 return state.selectedImageIndex - 1 >= 0;
80 },
81 get overlayOpened() {
82 return state.selectedImageId !== null;
83 },
84 get roleAttribute() {
85 return state.overlayOpened ? "dialog" : null;
86 },
87 get ariaModal() {
88 return state.overlayOpened ? "true" : null;
89 },
90 get ariaLabel() {
91 return state.selectedImage.customAriaLabel || getConfig().defaultAriaLabel;
92 },
93 get closeButtonAriaLabel() {
94 return state.hasNavigationText ? void 0 : getConfig().closeButtonText;
95 },
96 get prevButtonAriaLabel() {
97 return state.hasNavigationText ? void 0 : getConfig().prevButtonText;
98 },
99 get nextButtonAriaLabel() {
100 return state.hasNavigationText ? void 0 : getConfig().nextButtonText;
101 },
102 get enlargedSrc() {
103 return getImageSrc(state.selectedImage);
104 },
105 get enlargedSrcset() {
106 return getImageSrcset(state.selectedImage);
107 },
108 get figureStyles() {
109 return state.overlayOpened && `${state.selectedImage.figureStyles?.replace(
110 /margin[^;]*;?/g,
111 ""
112 )};`;
113 },
114 get imgStyles() {
115 return state.overlayOpened && `${state.selectedImage.imgStyles?.replace(
116 /;$/,
117 ""
118 )}; object-fit:cover;`;
119 },
120 get isContentHidden() {
121 const ctx = getContext();
122 return state.overlayEnabled && state.selectedImageId === ctx.imageId;
123 },
124 get isContentVisible() {
125 const ctx = getContext();
126 return !state.overlayEnabled && state.selectedImageId === ctx.imageId;
127 }
128 },
129 actions: {
130 showLightbox() {
131 const { imageId } = getContext();
132 if (!state.metadata[imageId].imageRef?.complete) {
133 return;
134 }
135 state.scrollTopReset = document.documentElement.scrollTop;
136 state.scrollLeftReset = document.documentElement.scrollLeft;
137 state.selectedImageId = imageId;
138 const { galleryId } = getContext("core/gallery") || {};
139 state.selectedGalleryId = galleryId || null;
140 state.overlayEnabled = true;
141 callbacks.setOverlayStyles();
142 },
143 hideLightbox() {
144 if (state.overlayEnabled) {
145 state.overlayEnabled = false;
146 setTimeout(function() {
147 state.selectedImage.buttonRef.focus({
148 preventScroll: true
149 });
150 state.selectedImageId = null;
151 state.selectedGalleryId = null;
152 }, 450);
153 }
154 },
155 showPreviousImage: withSyncEvent((event) => {
156 event.stopPropagation();
157 const nextIndex = state.hasPreviousImage ? state.selectedImageIndex - 1 : state.galleryImages.length - 1;
158 state.selectedImageId = state.galleryImages[nextIndex];
159 callbacks.setOverlayStyles();
160 }),
161 showNextImage: withSyncEvent((event) => {
162 event.stopPropagation();
163 const nextIndex = state.hasNextImage ? state.selectedImageIndex + 1 : 0;
164 state.selectedImageId = state.galleryImages[nextIndex];
165 callbacks.setOverlayStyles();
166 }),
167 handleKeydown: withSyncEvent((event) => {
168 if (state.overlayEnabled) {
169 if (event.key === "Escape") {
170 actions.hideLightbox();
171 } else if (event.key === "ArrowLeft") {
172 actions.showPreviousImage(event);
173 } else if (event.key === "ArrowRight") {
174 actions.showNextImage(event);
175 } else if (event.key === "Tab") {
176 const focusableElements = Array.from(
177 document.querySelectorAll(focusableSelectors)
178 );
179 const firstFocusableElement = focusableElements[0];
180 const lastFocusableElement = focusableElements[focusableElements.length - 1];
181 if (event.shiftKey && event.target === firstFocusableElement) {
182 event.preventDefault();
183 lastFocusableElement.focus();
184 } else if (!event.shiftKey && event.target === lastFocusableElement) {
185 event.preventDefault();
186 firstFocusableElement.focus();
187 }
188 }
189 }
190 }),
191 handleTouchMove: withSyncEvent((event) => {
192 if (state.overlayEnabled) {
193 event.preventDefault();
194 }
195 }),
196 handleTouchStart(event) {
197 isTouching = true;
198 const t = event.touches && event.touches[0];
199 if (t) {
200 touchStartEvent.startX = t.clientX;
201 touchStartEvent.startY = t.clientY;
202 touchStartEvent.startTime = Date.now();
203 }
204 },
205 handleTouchEnd: withSyncEvent((event) => {
206 const touchEndEvent = event.changedTouches && event.changedTouches[0] || event.touches && event.touches[0];
207 const now = Date.now();
208 if (touchEndEvent && state.overlayEnabled) {
209 const deltaX = touchEndEvent.clientX - touchStartEvent.startX;
210 const deltaY = touchEndEvent.clientY - touchStartEvent.startY;
211 const absDeltaX = Math.abs(deltaX);
212 const absDeltaY = Math.abs(deltaY);
213 const elapsedMs = now - touchStartEvent.startTime;
214 const isHorizontalSwipe = (
215 // Swipe distance is greater than 50px
216 absDeltaX > 50 && // Horizontal movement is much larger than the vertical movement
217 absDeltaX > absDeltaY * 1.5 && // Fast action of less than 800ms
218 elapsedMs < 800
219 );
220 if (isHorizontalSwipe) {
221 event.preventDefault();
222 if (deltaX < 0) {
223 actions.showNextImage(event);
224 } else {
225 actions.showPreviousImage(event);
226 }
227 }
228 }
229 lastTouchTime = now;
230 isTouching = false;
231 }),
232 handleScroll() {
233 if (state.overlayOpened) {
234 if (!isTouching && Date.now() - lastTouchTime > 450) {
235 window.scrollTo(
236 state.scrollLeftReset,
237 state.scrollTopReset
238 );
239 }
240 }
241 },
242 preloadImage() {
243 const { imageId } = getContext();
244 if (state.preloadedImageIds.has(imageId)) {
245 return;
246 }
247 const imageMetadata = state.metadata[imageId];
248 const imageLink = document.createElement("link");
249 imageLink.rel = "preload";
250 imageLink.as = "image";
251 imageLink.href = getImageSrc(imageMetadata);
252 const srcset = getImageSrcset(imageMetadata);
253 if (srcset) {
254 imageLink.setAttribute("imagesrcset", srcset);
255 imageLink.setAttribute("imagesizes", "100vw");
256 }
257 document.head.appendChild(imageLink);
258 state.preloadedImageIds.add(imageId);
259 },
260 preloadImageWithDelay() {
261 const { imageId } = getContext();
262 actions.cancelPreload();
263 const timerId = setTimeout(
264 withScope(() => {
265 actions.preloadImage();
266 state.preloadTimers.delete(imageId);
267 }),
268 IMAGE_PRELOAD_DELAY
269 );
270 state.preloadTimers.set(imageId, timerId);
271 },
272 cancelPreload() {
273 const { imageId } = getContext();
274 if (state.preloadTimers.has(imageId)) {
275 clearTimeout(state.preloadTimers.get(imageId));
276 state.preloadTimers.delete(imageId);
277 }
278 }
279 },
280 callbacks: {
281 setOverlayStyles() {
282 if (!state.overlayEnabled) {
283 return;
284 }
285 let {
286 naturalWidth,
287 naturalHeight,
288 offsetWidth: originalWidth,
289 offsetHeight: originalHeight
290 } = state.selectedImage.imageRef;
291 let { x: screenPosX, y: screenPosY } = state.selectedImage.imageRef.getBoundingClientRect();
292 const naturalRatio = naturalWidth / naturalHeight;
293 let originalRatio = originalWidth / originalHeight;
294 if (state.selectedImage.scaleAttr === "contain") {
295 if (naturalRatio > originalRatio) {
296 const heightWithoutSpace = originalWidth / naturalRatio;
297 screenPosY += (originalHeight - heightWithoutSpace) / 2;
298 originalHeight = heightWithoutSpace;
299 } else {
300 const widthWithoutSpace = originalHeight * naturalRatio;
301 screenPosX += (originalWidth - widthWithoutSpace) / 2;
302 originalWidth = widthWithoutSpace;
303 }
304 }
305 originalRatio = originalWidth / originalHeight;
306 let imgMaxWidth = parseFloat(
307 state.selectedImage.targetWidth && state.selectedImage.targetWidth !== "none" ? state.selectedImage.targetWidth : naturalWidth
308 );
309 let imgMaxHeight = parseFloat(
310 state.selectedImage.targetHeight && state.selectedImage.targetHeight !== "none" ? state.selectedImage.targetHeight : naturalHeight
311 );
312 let imgRatio = imgMaxWidth / imgMaxHeight;
313 let containerMaxWidth = imgMaxWidth;
314 let containerMaxHeight = imgMaxHeight;
315 let containerWidth = imgMaxWidth;
316 let containerHeight = imgMaxHeight;
317 if (naturalRatio.toFixed(2) !== imgRatio.toFixed(2)) {
318 if (naturalRatio > imgRatio) {
319 const reducedHeight = imgMaxWidth / naturalRatio;
320 if (imgMaxHeight - reducedHeight > imgMaxWidth) {
321 imgMaxHeight = reducedHeight;
322 imgMaxWidth = reducedHeight * naturalRatio;
323 } else {
324 imgMaxHeight = imgMaxWidth / naturalRatio;
325 }
326 } else {
327 const reducedWidth = imgMaxHeight * naturalRatio;
328 if (imgMaxWidth - reducedWidth > imgMaxHeight) {
329 imgMaxWidth = reducedWidth;
330 imgMaxHeight = reducedWidth / naturalRatio;
331 } else {
332 imgMaxWidth = imgMaxHeight * naturalRatio;
333 }
334 }
335 containerWidth = imgMaxWidth;
336 containerHeight = imgMaxHeight;
337 imgRatio = imgMaxWidth / imgMaxHeight;
338 if (originalRatio > imgRatio) {
339 containerMaxWidth = imgMaxWidth;
340 containerMaxHeight = containerMaxWidth / originalRatio;
341 } else {
342 containerMaxHeight = imgMaxHeight;
343 containerMaxWidth = containerMaxHeight * originalRatio;
344 }
345 }
346 if (originalWidth > containerWidth || originalHeight > containerHeight) {
347 containerWidth = originalWidth;
348 containerHeight = originalHeight;
349 }
350 let horizontalPadding = 0;
351 let verticalPadding = 160;
352 if (480 < window.innerWidth) {
353 horizontalPadding = 80;
354 verticalPadding = 160;
355 }
356 if (960 < window.innerWidth) {
357 horizontalPadding = state.hasNavigation ? 320 : 80;
358 verticalPadding = 80;
359 }
360 const targetMaxWidth = Math.min(
361 window.innerWidth - horizontalPadding,
362 containerWidth
363 );
364 const targetMaxHeight = Math.min(
365 window.innerHeight - verticalPadding,
366 containerHeight
367 );
368 const targetContainerRatio = targetMaxWidth / targetMaxHeight;
369 if (originalRatio > targetContainerRatio) {
370 containerWidth = targetMaxWidth;
371 containerHeight = containerWidth / originalRatio;
372 } else {
373 containerHeight = targetMaxHeight;
374 containerWidth = containerHeight * originalRatio;
375 }
376 const containerScale = originalWidth / containerWidth;
377 const lightboxImgWidth = imgMaxWidth * (containerWidth / containerMaxWidth);
378 const lightboxImgHeight = imgMaxHeight * (containerHeight / containerMaxHeight);
379 state.overlayStyles = `
380 --wp--lightbox-initial-top-position: ${screenPosY}px;
381 --wp--lightbox-initial-left-position: ${screenPosX}px;
382 --wp--lightbox-container-width: ${containerWidth + 1}px;
383 --wp--lightbox-container-height: ${containerHeight + 1}px;
384 --wp--lightbox-image-width: ${lightboxImgWidth}px;
385 --wp--lightbox-image-height: ${lightboxImgHeight}px;
386 --wp--lightbox-scale: ${containerScale};
387 --wp--lightbox-scrollbar-width: ${window.innerWidth - document.documentElement.clientWidth}px;
388 `;
389 },
390 setButtonStyles() {
391 const { ref } = getElement();
392 if (!ref) {
393 return;
394 }
395 const { imageId } = getContext();
396 state.metadata[imageId].imageRef = ref;
397 state.metadata[imageId].currentSrc = ref.currentSrc;
398 const {
399 naturalWidth,
400 naturalHeight,
401 offsetWidth,
402 offsetHeight
403 } = ref;
404 if (naturalWidth === 0 || naturalHeight === 0) {
405 return;
406 }
407 const figure = ref.parentElement;
408 const figureWidth = ref.parentElement.clientWidth;
409 let figureHeight = ref.parentElement.clientHeight;
410 const caption = figure.querySelector("figcaption");
411 if (caption) {
412 const captionComputedStyle = window.getComputedStyle(caption);
413 if (!["absolute", "fixed"].includes(
414 captionComputedStyle.position
415 )) {
416 figureHeight = figureHeight - caption.offsetHeight - parseFloat(captionComputedStyle.marginTop) - parseFloat(captionComputedStyle.marginBottom);
417 }
418 }
419 const buttonOffsetTop = figureHeight - offsetHeight;
420 const buttonOffsetRight = figureWidth - offsetWidth;
421 let buttonTop = buttonOffsetTop + 16;
422 let buttonRight = buttonOffsetRight + 16;
423 if (state.metadata[imageId].scaleAttr === "contain") {
424 const naturalRatio = naturalWidth / naturalHeight;
425 const offsetRatio = offsetWidth / offsetHeight;
426 if (naturalRatio >= offsetRatio) {
427 const referenceHeight = offsetWidth / naturalRatio;
428 buttonTop = (offsetHeight - referenceHeight) / 2 + buttonOffsetTop + 16;
429 buttonRight = buttonOffsetRight + 16;
430 } else {
431 const referenceWidth = offsetHeight * naturalRatio;
432 buttonTop = buttonOffsetTop + 16;
433 buttonRight = (offsetWidth - referenceWidth) / 2 + buttonOffsetRight + 16;
434 }
435 }
436 state.metadata[imageId].buttonTop = buttonTop;
437 state.metadata[imageId].buttonRight = buttonRight;
438 },
439 setOverlayFocus() {
440 if (state.overlayEnabled) {
441 const { ref } = getElement();
442 ref.focus();
443 }
444 },
445 setInertElements() {
446 document.querySelectorAll("body > :not(.wp-lightbox-overlay)").forEach((el) => {
447 if (state.overlayEnabled) {
448 el.setAttribute("inert", "");
449 } else {
450 el.removeAttribute("inert");
451 }
452 });
453 },
454 initTriggerButton() {
455 const { imageId } = getContext();
456 const { ref } = getElement();
457 state.metadata[imageId].buttonRef = ref;
458 }
459 }
460 },
461 { lock: true }
462 );
463 //# sourceMappingURL=view.js.map
464