vendor: 5,426 bytes, lines 1-203
1/*! 2 * PhotoSwipe Lightbox 5.4.3 - https://photoswipe.com 3 * (c) 2023 Dmytro Semenov 4 */ 5/** @typedef {import('../photoswipe.js').Point} Point */ 6 7/** 8 * @template {keyof HTMLElementTagNameMap} T 9 * @param {string} className 10 * @param {T} tagName 11 * @param {Node} [appendToEl] 12 * @returns {HTMLElementTagNameMap[T]} 13 */ 14function createElement(className, tagName, appendToEl) { 15 const el = document.createElement(tagName); 16 17 if (className) { 18 el.className = className; 19 } 20 21 if (appendToEl) { 22 appendToEl.appendChild(el); 23 } 24 25 return el; 26} 27/** 28 * Get transform string 29 * 30 * @param {number} x 31 * @param {number} [y] 32 * @param {number} [scale] 33 * @returns {string} 34 */ 35 36function toTransformString(x, y, scale) { 37 let propValue = `translate3d(${x}px,${y || 0}px,0)`; 38 39 if (scale !== undefined) { 40 propValue += ` scale3d(${scale},${scale},1)`; 41 } 42 43 return propValue; 44} 45/** 46 * Apply width and height CSS properties to element 47 * 48 * @param {HTMLElement} el 49 * @param {string | number} w 50 * @param {string | number} h 51 */ 52 53function setWidthHeight(el, w, h) { 54 el.style.width = typeof w === 'number' ? `${w}px` : w; 55 el.style.height = typeof h === 'number' ? `${h}px` : h; 56} 57/** @typedef {LOAD_STATE[keyof LOAD_STATE]} LoadState */ 58 59/** @type {{ IDLE: 'idle'; LOADING: 'loading'; LOADED: 'loaded'; ERROR: 'error' }} */ 60 61const LOAD_STATE = { 62 IDLE: 'idle', 63 LOADING: 'loading', 64 LOADED: 'loaded', 65 ERROR: 'error' 66}; 67/** 68 * Check if click or keydown event was dispatched 69 * with a special key or via mouse wheel. 70 * 71 * @param {MouseEvent | KeyboardEvent} e 72 * @returns {boolean} 73 */ 74 75function specialKeyUsed(e) { 76 return 'button' in e && e.button === 1 || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey; 77} 78/** 79 * Parse `gallery` or `children` options. 80 * 81 * @param {import('../photoswipe.js').ElementProvider} [option] 82 * @param {string} [legacySelector] 83 * @param {HTMLElement | Document} [parent] 84 * @returns HTMLElement[] 85 */ 86 87function getElementsFromOption(option, legacySelector, parent = document) { 88 /** @type {HTMLElement[]} */ 89 let elements = []; 90 91 if (option instanceof Element) { 92 elements = [option]; 93 } else if (option instanceof NodeList || Array.isArray(option)) { 94 elements = Array.from(option); 95 } else { 96 const selector = typeof option === 'string' ? option : legacySelector; 97 98 if (selector) { 99 elements = Array.from(parent.querySelectorAll(selector)); 100 } 101 } 102 103 return elements; 104} 105/** 106 * Check if variable is PhotoSwipe class 107 * 108 * @param {any} fn 109 * @returns {boolean} 110 */ 111 112function isPswpClass(fn) { 113 return typeof fn === 'function' && fn.prototype && fn.prototype.goTo; 114} 115/** 116 * Check if browser is Safari 117 * 118 * @returns {boolean} 119 */ 120 121function isSafari() { 122 return !!(navigator.vendor && navigator.vendor.match(/apple/i)); 123} 124 125/** @typedef {import('../lightbox/lightbox.js').default} PhotoSwipeLightbox */ 126 127/** @typedef {import('../photoswipe.js').default} PhotoSwipe */ 128 129/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */ 130 131/** @typedef {import('../photoswipe.js').DataSource} DataSource */ 132 133/** @typedef {import('../ui/ui-element.js').UIElementData} UIElementData */ 134 135/** @typedef {import('../slide/content.js').default} ContentDefault */ 136 137/** @typedef {import('../slide/slide.js').default} Slide */ 138 139/** @typedef {import('../slide/slide.js').SlideData} SlideData */ 140 141/** @typedef {import('../slide/zoom-level.js').default} ZoomLevel */ 142 143/** @typedef {import('../slide/get-thumb-bounds.js').Bounds} Bounds */ 144 145/** 146 * Allow adding an arbitrary props to the Content 147 * https://photoswipe.com/custom-content/#using-webp-image-format 148 * @typedef {ContentDefault & Record<string, any>} Content 149 */ 150 151/** @typedef {{ x?: number; y?: number }} Point */ 152 153/** 154 * @typedef {Object} PhotoSwipeEventsMap https://photoswipe.com/events/ 155 * 156 * 157 * https://photoswipe.com/adding-ui-elements/ 158 * 159 * @prop {undefined} uiRegister 160 * @prop {{ data: UIElementData }} uiElementCreate 161 * 162 * 163 * https://photoswipe.com/events/#initialization-events 164 * 165 * @prop {undefined} beforeOpen 166 * @prop {undefined} firstUpdate 167 * @prop {undefined} initialLayout 168 * @prop {undefined} change 169 * @prop {undefined} afterInit 170 * @prop {undefined} bindEvents 171 * 172 * 173 * https://photoswipe.com/events/#opening-or-closing-transition-events 174 * 175 * @prop {undefined} openingAnimationStart 176 * @prop {undefined} openingAnimationEnd 177 * @prop {undefined} closingAnimationStart 178 * @prop {undefined} closingAnimationEnd 179 * 180 * 181 * https://photoswipe.com/events/#closing-events 182 * 183 * @prop {undefined} close 184 * @prop {undefined} destroy 185 * 186 * 187 * https://photoswipe.com/events/#pointer-and-gesture-events 188 * 189 * @prop {{ originalEvent: PointerEvent }} pointerDown 190 * @prop {{ originalEvent: PointerEvent }} pointerMove 191 * @prop {{ originalEvent: PointerEvent }} pointerUp 192 * @prop {{ bgOpacity: number }} pinchClose can be default prevented 193 * @prop {{ panY: number }} verticalDrag can be default prevented 194 * 195 * 196 * https://photoswipe.com/events/#slide-content-events 197 * 198 * @prop {{ content: Content }} contentInit 199 * @prop {{ content: Content; isLazy: boolean }} contentLoad can be default prevented 200 * @prop {{ content: Content; isLazy: boolean }} contentLoadImage can be default prevented 201 * @prop {{ content: Content; slide: Slide; isError?: boolean }} loadComplete 202 * @prop {{ content: Content; slide: Slide }} loadError 203 * @prop {{ content: Content; width: number; height: number }}
203 contentResize can be default prevented 204 * @prop {{ content: Content; width: number; height: number; slide: Slide }} imageSizeChange 205 * @prop {{ content: Content }} contentLazyLoad can be default prevented 206 * @prop {{ content: Content }} contentAppend can be default prevented 207 * @prop {{ content: Content }} contentActivate can be default prevented 208 * @prop {{ content: Content }} contentDeactivate can be default prevented 209 * @prop {{ content: Content }} contentRemove can be default prevented 210 * @prop {{ content: Content }} contentDestroy can be default prevented 211 * 212 * 213 * undocumented 214 * 215 * @prop {{ point: Point; originalEvent: PointerEvent }} imageClickAction can be default prevented 216 * @prop {{ point: Point; originalEvent: PointerEvent }} bgClickAction can be default prevented 217 * @prop {{ point: Point; originalEvent: PointerEvent }} tapAction can be default prevented 218 * @prop {{ point: Point; originalEvent: PointerEvent }} doubleTapAction can be default prevented 219 * 220 * @prop {{ originalEvent: KeyboardEvent }} keydown can be default prevented 221 * @prop {{ x: number; dragging: boolean }} moveMainScroll 222 * @prop {{ slide: Slide }} firstZoomPan 223 * @prop {{ slide: Slide | undefined, data: SlideData, index: number }} gettingData 224 * @prop {undefined} beforeResize 225 * @prop {undefined} resize 226 * @prop {undefined} viewportSize 227 * @prop {undefined} updateScrollOffset 228 * @prop {{ slide: Slide }} slideInit 229 * @prop {{ slide: Slide }} afterSetContent 230 * @prop {{ slide: Slide }} slideLoad 231 * @prop {{ slide: Slide }} appendHeavy can be default prevented 232 * @prop {{ slide: Slide }} appendHeavyContent 233 * @prop {{ slide: Slide }} slideActivate 234 * @prop {{ slide: Slide }} slideDeactivate 235 * @prop {{ slide: Slide }} slideDestroy 236 * @prop {{ destZoomLevel: number, centerPoint: Point | undefined, transitionDuration: number | false | undefined }} beforeZoomTo 237 * @prop {{ slide: Slide }} zoomPanUpdate 238 * @prop {{ slide: Slide }} initialZoomPan 239 * @prop {{ slide: Slide }} calcSlideSize 240 * @prop {undefined} resolutionChanged 241 * @prop {{ originalEvent: WheelEvent }} wheel can be default prevented 242 * @prop {{ content: Content }} contentAppendImage can be default prevented 243 * @prop {{ index: number; itemData: SlideData }} lazyLoadSlide can be default prevented 244 * @prop {undefined} lazyLoad 245 * @prop {{ slide: Slide }} calcBounds 246 * @prop {{ zoomLevels: ZoomLevel, slideData: SlideData }} zoomLevelsUpdate 247 * 248 * 249 * legacy 250 * 251 * @prop {undefined} init 252 * @prop {undefined} initialZoomIn 253 * @prop {undefined} initialZoomOut 254 * @prop {undefined} initialZoomInEnd 255 * @prop {undefined} initialZoomOutEnd 256 * @prop {{ dataSource: DataSource | undefined, numItems: number }} numItems 257 * @prop {{ itemData: SlideData; index: number }} itemData 258 * @prop {{ index: number, itemData: SlideData, instance: PhotoSwipe }} thumbBounds 259 */ 260 261/** 262 * @typedef {Object} PhotoSwipeFiltersMap https://photoswipe.com/filters/ 263 * 264 * @prop {(numItems: number, dataSource: DataSource | undefined) => number} numItems 265 * Modify the total amount of slides. Example on Data sources page. 266 * https://photoswipe.com/filters/#numitems 267 * 268 * @prop {(itemData: SlideData, index: number) => SlideData} itemData 269 * Modify slide item data. Example on Data sources page. 270 * https://photoswipe.com/filters/#itemdata 271 * 272 * @prop {(itemData: SlideData, element: HTMLElement, linkEl: HTMLAnchorElement) => SlideData} domItemData 273 * Modify item data when it's parsed from DOM element. Example on Data sources page. 274 * https://photoswipe.com/filters/#domitemdata 275 * 276 * @prop {(clickedIndex: number, e: MouseEvent, instance: PhotoSwipeLightbox) => number} clickedIndex 277 * Modify clicked gallery item index. 278 * https://photoswipe.com/filters/#clickedindex 279 * 280 * @prop {(placeholderSrc: string | false, content: Content) => string | false} placeholderSrc 281 * Modify placeholder image source. 282 * https://photoswipe.com/filters/#placeholdersrc 283 * 284 * @prop {(isContentLoading: boolean, content: Content) => boolean} isContentLoading 285 * Modify if the content is currently loading. 286 * https://photoswipe.com/filters/#iscontentloading 287 * 288 * @prop {(isContentZoomable: boolean, content: Content) => boolean} isContentZoomable 289 * Modify if the content can be zoomed. 290 * https://photoswipe.com/filters/#iscontentzoomable 291 * 292 * @prop {(useContentPlaceholder: boolean, content: Content) => boolean} useContentPlaceholder 293 * Modify if the placeholder should be used for the content. 294 * https://photoswipe.com/filters/#usecontentplaceholder 295 * 296 * @prop {(isKeepingPlaceholder: boolean, content: Content) => boolean} isKeepingPlaceholder 297 * Modify if the placeholder should be kept after the content is loaded. 298 * https://photoswipe.com/filters/#iskeepingplaceholder 299 * 300 * 301 * @prop {(contentErrorElement: HTMLElement, content: Content) => HTMLElement} contentErrorElement 302 * Modify an element when the content has error state (for example, if image cannot be loaded). 303 * https://photoswipe.com/filters/#contenterrorelement 304 * 305 * @prop {(element: HTMLElement, data: UIElementData) => HTMLElement} uiElement 306 * Modify a UI element that's being created. 307 * https://photoswipe.com/filters/#uielement 308 * 309 * @prop {(thumbnail: HTMLElement | null | undefined, itemData: SlideData, index: number) => HTMLElement} thumbEl 310 * Modify the thumbnail element from which opening zoom animation starts or ends. 311 * https://photoswipe.com/filters/#thumbel 312 * 313 * @prop {(thumbBounds: Bounds | undefined, itemData: SlideData, index: number) => Bounds} thumbBounds 314 * Modify the thumbnail bounds from which opening zoom animation starts or ends. 315 * https://photoswipe.com/filters/#thumbbounds 316 *
vendor: 43,509 bytes, lines 317-1960
317 * @prop {(srcsetSizesWidth: number, content: Content) => number} srcsetSizesWidth 318 * 319 * @prop {(preventPointerEvent: boolean, event: PointerEvent, pointerType: string) => boolean} preventPointerEvent 320 * 321 */ 322 323/** 324 * @template {keyof PhotoSwipeFiltersMap} T 325 * @typedef {{ fn: PhotoSwipeFiltersMap[T], priority: number }} Filter 326 */ 327 328/** 329 * @template {keyof PhotoSwipeEventsMap} T 330 * @typedef {PhotoSwipeEventsMap[T] extends undefined ? PhotoSwipeEvent<T> : PhotoSwipeEvent<T> & PhotoSwipeEventsMap[T]} AugmentedEvent 331 */ 332 333/** 334 * @template {keyof PhotoSwipeEventsMap} T 335 * @typedef {(event: AugmentedEvent<T>) => void} EventCallback 336 */ 337 338/** 339 * Base PhotoSwipe event object 340 * 341 * @template {keyof PhotoSwipeEventsMap} T 342 */ 343class PhotoSwipeEvent { 344 /** 345 * @param {T} type 346 * @param {PhotoSwipeEventsMap[T]} [details] 347 */ 348 constructor(type, details) { 349 this.type = type; 350 this.defaultPrevented = false; 351 352 if (details) { 353 Object.assign(this, details); 354 } 355 } 356 357 preventDefault() { 358 this.defaultPrevented = true; 359 } 360 361} 362/** 363 * PhotoSwipe base class that can listen and dispatch for events. 364 * Shared by PhotoSwipe Core and PhotoSwipe Lightbox, extended by base.js 365 */ 366 367 368class Eventable { 369 constructor() { 370 /** 371 * @type {{ [T in keyof PhotoSwipeEventsMap]?: ((event: AugmentedEvent<T>) => void)[] }} 372 */ 373 this._listeners = {}; 374 /** 375 * @type {{ [T in keyof PhotoSwipeFiltersMap]?: Filter<T>[] }} 376 */ 377 378 this._filters = {}; 379 /** @type {PhotoSwipe | undefined} */ 380 381 this.pswp = undefined; 382 /** @type {PhotoSwipeOptions | undefined} */ 383 384 this.options = undefined; 385 } 386 /** 387 * @template {keyof PhotoSwipeFiltersMap} T 388 * @param {T} name 389 * @param {PhotoSwipeFiltersMap[T]} fn 390 * @param {number} priority 391 */ 392 393 394 addFilter(name, fn, priority = 100) { 395 var _this$_filters$name, _this$_filters$name2, _this$pswp; 396 397 if (!this._filters[name]) { 398 this._filters[name] = []; 399 } 400 401 (_this$_filters$name = this._filters[name]) === null || _this$_filters$name === void 0 || _this$_filters$name.push({ 402 fn, 403 priority 404 }); 405 (_this$_filters$name2 = this._filters[name]) === null || _this$_filters$name2 === void 0 || _this$_filters$name2.sort((f1, f2) => f1.priority - f2.priority); 406 (_this$pswp = this.pswp) === null || _this$pswp === void 0 || _this$pswp.addFilter(name, fn, priority); 407 } 408 /** 409 * @template {keyof PhotoSwipeFiltersMap} T 410 * @param {T} name 411 * @param {PhotoSwipeFiltersMap[T]} fn 412 */ 413 414 415 removeFilter(name, fn) { 416 if (this._filters[name]) { 417 // @ts-expect-error 418 this._filters[name] = this._filters[name].filter(filter => filter.fn !== fn); 419 } 420 421 if (this.pswp) { 422 this.pswp.removeFilter(name, fn); 423 } 424 } 425 /** 426 * @template {keyof PhotoSwipeFiltersMap} T 427 * @param {T} name 428 * @param {Parameters<PhotoSwipeFiltersMap[T]>} args 429 * @returns {Parameters<PhotoSwipeFiltersMap[T]>[0]} 430 */ 431 432 433 applyFilters(name, ...args) { 434 var _this$_filters$name3; 435 436 (_this$_filters$name3 = this._filters[name]) === null || _this$_filters$name3 === void 0 || _this$_filters$name3.forEach(filter => { 437 // @ts-expect-error 438 args[0] = filter.fn.apply(this, args); 439 }); 440 return args[0]; 441 } 442 /** 443 * @template {keyof PhotoSwipeEventsMap} T 444 * @param {T} name 445 * @param {EventCallback<T>} fn 446 */ 447 448 449 on(name, fn) { 450 var _this$_listeners$name, _this$pswp2; 451 452 if (!this._listeners[name]) { 453 this._listeners[name] = []; 454 } 455 456 (_this$_listeners$name = this._listeners[name]) === null || _this$_listeners$name === void 0 || _this$_listeners$name.push(fn); // When binding events to lightbox, 457 // also bind events to PhotoSwipe Core, 458 // if it's open. 459 460 (_this$pswp2 = this.pswp) === null || _this$pswp2 === void 0 || _this$pswp2.on(name, fn); 461 } 462 /** 463 * @template {keyof PhotoSwipeEventsMap} T 464 * @param {T} name 465 * @param {EventCallback<T>} fn 466 */ 467 468 469 off(name, fn) { 470 var _this$pswp3; 471 472 if (this._listeners[name]) { 473 // @ts-expect-error 474 this._listeners[name] = this._listeners[name].filter(listener => fn !== listener); 475 } 476 477 (_this$pswp3 = this.pswp) === null || _this$pswp3 === void 0 || _this$pswp3.off(name, fn); 478 } 479 /** 480 * @template {keyof PhotoSwipeEventsMap} T 481 * @param {T} name 482 * @param {PhotoSwipeEventsMap[T]} [details] 483 * @returns {AugmentedEvent<T>} 484 */ 485 486 487 dispatch(name, details) { 488 var _this$_listeners$name2; 489 490 if (this.pswp) { 491 return this.pswp.dispatch(name, details); 492 } 493 494 const event = 495 /** @type {AugmentedEvent<T>} */ 496 new PhotoSwipeEvent(name, details); 497 (_this$_listeners$name2 = this._listeners[name]) === null || _this$_listeners$name2 === void 0 || _this$_listeners$name2.forEach(listener => { 498 listener.call(this, event); 499 }); 500 return event; 501 } 502 503} 504 505class Placeholder { 506 /** 507 * @param {string | false} imageSrc 508 * @param {HTMLElement} container 509 */ 510 constructor(imageSrc, container) { 511 // Create placeholder 512 // (stretched thumbnail or simple div behind the main image) 513 514 /** @type {HTMLImageElement | HTMLDivElement | null} */ 515 this.element = createElement('pswp__img pswp__img--placeholder', imageSrc ? 'img' : 'div', container); 516 517 if (imageSrc) { 518 const imgEl = 519 /** @type {HTMLImageElement} */ 520 this.element; 521 imgEl.decoding = 'async'; 522 imgEl.alt = ''; 523 imgEl.src = imageSrc; 524 imgEl.setAttribute('role', 'presentation'); 525 } 526 527 this.element.setAttribute('aria-hidden', 'true'); 528 } 529 /** 530 * @param {number} width 531 * @param {number} height 532 */ 533 534 535 setDisplayedSize(width, height) { 536 if (!this.element) { 537 return; 538 } 539 540 if (this.element.tagName === 'IMG') { 541 // Use transform scale() to modify img placeholder size 542 // (instead of changing width/height directly). 543 // This helps with performance, specifically in iOS15 Safari. 544 setWidthHeight(this.element, 250, 'auto'); 545 this.element.style.transformOrigin = '0 0'; 546 this.element.style.transform = toTransformString(0, 0, width / 250); 547 } else { 548 setWidthHeight(this.element, width, height); 549 } 550 } 551 552 destroy() { 553 var _this$element; 554 555 if ((_this$element = this.element) !== null && _this$element !== void 0 && _this$element.parentNode) { 556 this.element.remove(); 557 } 558 559 this.element = null; 560 } 561 562} 563 564/** @typedef {import('./slide.js').default} Slide */ 565 566/** @typedef {import('./slide.js').SlideData} SlideData */ 567 568/** @typedef {import('../core/base.js').default} PhotoSwipeBase */ 569 570/** @typedef {import('../util/util.js').LoadState} LoadState */ 571 572class Content { 573 /** 574 * @param {SlideData} itemData Slide data 575 * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance 576 * @param {number} index 577 */ 578 constructor(itemData, instance, index) { 579 this.instance = instance; 580 this.data = itemData; 581 this.index = index; 582 /** @type {HTMLImageElement | HTMLDivElement | undefined} */ 583 584 this.element = undefined; 585 /** @type {Placeholder | undefined} */ 586 587 this.placeholder = undefined; 588 /** @type {Slide | undefined} */ 589 590 this.slide = undefined; 591 this.displayedImageWidth = 0; 592 this.displayedImageHeight = 0; 593 this.width = Number(this.data.w) || Number(this.data.width) || 0; 594 this.height = Number(this.data.h) || Number(this.data.height) || 0; 595 this.isAttached = false; 596 this.hasSlide = false; 597 this.isDecoding = false; 598 /** @type {LoadState} */ 599 600 this.state = LOAD_STATE.IDLE; 601 602 if (this.data.type) { 603 this.type = this.data.type; 604 } else if (this.data.src) { 605 this.type = 'image'; 606 } else { 607 this.type = 'html'; 608 } 609 610 this.instance.dispatch('contentInit', { 611 content: this 612 }); 613 } 614 615 removePlaceholder() { 616 if (this.placeholder && !this.keepPlaceholder()) { 617 // With delay, as image might be loaded, but not rendered 618 setTimeout(() => { 619 if (this.placeholder) { 620 this.placeholder.destroy(); 621 this.placeholder = undefined; 622 } 623 }, 1000); 624 } 625 } 626 /** 627 * Preload content 628 * 629 * @param {boolean} isLazy 630 * @param {boolean} [reload] 631 */ 632 633 634 load(isLazy, reload) { 635 if (this.slide && this.usePlaceholder()) { 636 if (!this.placeholder) { 637 const placeholderSrc = this.instance.applyFilters('placeholderSrc', // use image-based placeholder only for the first slide, 638 // as rendering (even small stretched thumbnail) is an expensive operation 639 this.data.msrc && this.slide.isFirstSlide ? this.data.msrc : false, this); 640 this.placeholder = new Placeholder(placeholderSrc, this.slide.container); 641 } else { 642 const placeholderEl = this.placeholder.element; // Add placeholder to DOM if it was already created 643 644 if (placeholderEl && !placeholderEl.parentElement) { 645 this.slide.container.prepend(placeholderEl); 646 } 647 } 648 } 649 650 if (this.element && !reload) { 651 return; 652 } 653 654 if (this.instance.dispatch('contentLoad', { 655 content: this, 656 isLazy 657 }).defaultPrevented) { 658 return; 659 } 660 661 if (this.isImageContent()) { 662 this.element = createElement('pswp__img', 'img'); // Start loading only after width is defined, as sizes might depend on it. 663 // Due to Safari feature, we must define sizes before srcset. 664 665 if (this.displayedImageWidth) { 666 this.loadImage(isLazy); 667 } 668 } else { 669 this.element = createElement('pswp__content', 'div'); 670 this.element.innerHTML = this.data.html || ''; 671 } 672 673 if (reload && this.slide) { 674 this.slide.updateContentSize(true); 675 } 676 } 677 /** 678 * Preload image 679 * 680 * @param {boolean} isLazy 681 */ 682 683 684 loadImage(isLazy) { 685 var _this$data$src, _this$data$alt; 686 687 if (!this.isImageContent() || !this.element || this.instance.dispatch('contentLoadImage', { 688 content: this, 689 isLazy 690 }).defaultPrevented) { 691 return; 692 } 693 694 const imageElement = 695 /** @type HTMLImageElement */ 696 this.element; 697 this.updateSrcsetSizes(); 698 699 if (this.data.srcset) { 700 imageElement.srcset = this.data.srcset; 701 } 702 703 imageElement.src = (_this$data$src = this.data.src) !== null && _this$data$src !== void 0 ? _this$data$src : ''; 704 imageElement.alt = (_this$data$alt = this.data.alt) !== null && _this$data$alt !== void 0 ? _this$data$alt : ''; 705 this.state = LOAD_STATE.LOADING; 706 707 if (imageElement.complete) { 708 this.onLoaded(); 709 } else { 710 imageElement.onload = () => { 711 this.onLoaded(); 712 }; 713 714 imageElement.onerror = () => { 715 this.onError(); 716 }; 717 } 718 } 719 /** 720 * Assign slide to content 721 * 722 * @param {Slide} slide 723 */ 724 725 726 setSlide(slide) { 727 this.slide = slide; 728 this.hasSlide = true; 729 this.instance = slide.pswp; // todo: do we need to unset slide? 730 } 731 /** 732 * Content load success handler 733 */ 734 735 736 onLoaded() { 737 this.state = LOAD_STATE.LOADED; 738 739 if (this.slide && this.element) { 740 this.instance.dispatch('loadComplete', { 741 slide: this.slide, 742 content: this 743 }); // if content is reloaded 744 745 if (this.slide.isActive && this.slide.heavyAppended && !this.element.parentNode) { 746 this.append(); 747 this.slide.updateContentSize(true); 748 } 749 750 if (this.state === LOAD_STATE.LOADED || this.state === LOAD_STATE.ERROR) { 751 this.removePlaceholder(); 752 } 753 } 754 } 755 /** 756 * Content load error handler 757 */ 758 759 760 onError() { 761 this.state = LOAD_STATE.ERROR; 762 763 if (this.slide) { 764 this.displayError(); 765 this.instance.dispatch('loadComplete', { 766 slide: this.slide, 767 isError: true, 768 content: this 769 }); 770 this.instance.dispatch('loadError', { 771 slide: this.slide, 772 content: this 773 }); 774 } 775 } 776 /** 777 * @returns {Boolean} If the content is currently loading 778 */ 779 780 781 isLoading() { 782 return this.instance.applyFilters('isContentLoading', this.state === LOAD_STATE.LOADING, this); 783 } 784 /** 785 * @returns {Boolean} If the content is in error state 786 */ 787 788 789 isError() { 790 return this.state === LOAD_STATE.ERROR; 791 } 792 /** 793 * @returns {boolean} If the content is image 794 */ 795 796 797 isImageContent() { 798 return this.type === 'image'; 799 } 800 /** 801 * Update content size 802 * 803 * @param {Number} width 804 * @param {Number} height 805 */ 806 807 808 setDisplayedSize(width, height) { 809 if (!this.element) { 810 return; 811 } 812 813 if (this.placeholder) { 814 this.placeholder.setDisplayedSize(width, height); 815 } 816 817 if (this.instance.dispatch('contentResize', { 818 content: this, 819 width, 820 height 821 }).defaultPrevented) { 822 return; 823 } 824 825 setWidthHeight(this.element, width, height); 826 827 if (this.isImageContent() && !this.isError()) { 828 const isInitialSizeUpdate = !this.displayedImageWidth && width; 829 this.displayedImageWidth = width; 830 this.displayedImageHeight = height; 831 832 if (isInitialSizeUpdate) { 833 this.loadImage(false); 834 } else { 835 this.updateSrcsetSizes(); 836 } 837 838 if (this.slide) { 839 this.instance.dispatch('imageSizeChange', { 840 slide: this.slide, 841 width, 842 height, 843 content: this 844 }); 845 } 846 } 847 } 848 /** 849 * @returns {boolean} If the content can be zoomed 850 */ 851 852 853 isZoomable() { 854 return this.instance.applyFilters('isContentZoomable', this.isImageContent() && this.state !== LOAD_STATE.ERROR, this); 855 } 856 /** 857 * Update image srcset sizes attribute based on width and height 858 */ 859 860 861 updateSrcsetSizes() { 862 // Handle srcset sizes attribute. 863 // 864 // Never lower quality, if it was increased previously. 865 // Chrome does this automatically, Firefox and Safari do not, 866 // so we store largest used size in dataset. 867 if (!this.isImageContent() || !this.element || !this.data.srcset) { 868 return; 869 } 870 871 const image = 872 /** @type HTMLImageElement */ 873 this.element; 874 const sizesWidth = this.instance.applyFilters('srcsetSizesWidth', this.displayedImageWidth, this); 875 876 if (!image.dataset.largestUsedSize || sizesWidth > parseInt(image.dataset.largestUsedSize, 10)) { 877 image.sizes = sizesWidth + 'px'; 878 image.dataset.largestUsedSize = String(sizesWidth); 879 } 880 } 881 /** 882 * @returns {boolean} If content should use a placeholder (from msrc by default) 883 */ 884 885 886 usePlaceholder() { 887 return this.instance.applyFilters('useContentPlaceholder', this.isImageContent(), this); 888 } 889 /** 890 * Preload content with lazy-loading param 891 */ 892 893 894 lazyLoad() { 895 if (this.instance.dispatch('contentLazyLoad', { 896 content: this 897 }).defaultPrevented) { 898 return; 899 } 900 901 this.load(true); 902 } 903 /** 904 * @returns {boolean} If placeholder should be kept after content is loaded 905 */ 906 907 908 keepPlaceholder() { 909 return this.instance.applyFilters('isKeepingPlaceholder', this.isLoading(), this); 910 } 911 /** 912 * Destroy the content 913 */ 914 915 916 destroy() { 917 this.hasSlide = false; 918 this.slide = undefined; 919 920 if (this.instance.dispatch('contentDestroy', { 921 content: this 922 }).defaultPrevented) { 923 return; 924 } 925 926 this.remove(); 927 928 if (this.placeholder) { 929 this.placeholder.destroy(); 930 this.placeholder = undefined; 931 } 932 933 if (this.isImageContent() && this.element) { 934 this.element.onload = null; 935 this.element.onerror = null; 936 this.element = undefined; 937 } 938 } 939 /** 940 * Display error message 941 */ 942 943 944 displayError() { 945 if (this.slide) { 946 var _this$instance$option, _this$instance$option2; 947 948 let errorMsgEl = createElement('pswp__error-msg', 'div'); 949 errorMsgEl.innerText = (_this$instance$option = (_this$instance$option2 = this.instance.options) === null || _this$instance$option2 === void 0 ? void 0 : _this$instance$option2.errorMsg) !== null && _this$instance$option !== void 0 ? _this$instance$option : ''; 950 errorMsgEl = 951 /** @type {HTMLDivElement} */ 952 this.instance.applyFilters('contentErrorElement', errorMsgEl, this); 953 this.element = createElement('pswp__content pswp__error-msg-container', 'div'); 954 this.element.appendChild(errorMsgEl); 955 this.slide.container.innerText = ''; 956 this.slide.container.appendChild(this.element); 957 this.slide.updateContentSize(true); 958 this.removePlaceholder(); 959 } 960 } 961 /** 962 * Append the content 963 */ 964 965 966 append() { 967 if (this.isAttached || !this.element) { 968 return; 969 } 970 971 this.isAttached = true; 972 973 if (this.state === LOAD_STATE.ERROR) { 974 this.displayError(); 975 return; 976 } 977 978 if (this.instance.dispatch('contentAppend', { 979 content: this 980 }).defaultPrevented) { 981 return; 982 } 983 984 const supportsDecode = ('decode' in this.element); 985 986 if (this.isImageContent()) { 987 // Use decode() on nearby slides 988 // 989 // Nearby slide images are in DOM and not hidden via display:none. 990 // However, they are placed offscreen (to the left and right side). 991 // 992 // Some browsers do not composite the image until it's actually visible, 993 // using decode() helps. 994 // 995 // You might ask "why dont you just decode() and then append all images", 996 // that's because I want to show image before it's fully loaded, 997 // as browser can render parts of image while it is loading. 998 // We do not do this in Safari due to partial loading bug. 999 if (supportsDecode && this.slide && (!this.slide.isActive || isSafari())) { 1000 this.isDecoding = true; // purposefully using finally instead of then, 1001 // as if srcset sizes changes dynamically - it may cause decode error 1002 1003 /** @type {HTMLImageElement} */ 1004 1005 this.element.decode().catch(() => {}).finally(() => { 1006 this.isDecoding = false; 1007 this.appendImage(); 1008 }); 1009 } else { 1010 this.appendImage(); 1011 } 1012 } else if (this.slide && !this.element.parentNode) { 1013 this.slide.container.appendChild(this.element); 1014 } 1015 } 1016 /** 1017 * Activate the slide, 1018 * active slide is generally the current one, 1019 * meaning the user can see it. 1020 */ 1021 1022 1023 activate() { 1024 if (this.instance.dispatch('contentActivate', { 1025 content: this 1026 }).defaultPrevented || !this.slide) { 1027 return; 1028 } 1029 1030 if (this.isImageContent() && this.isDecoding && !isSafari()) { 1031 // add image to slide when it becomes active, 1032 // even if it's not finished decoding 1033 this.appendImage(); 1034 } else if (this.isError()) { 1035 this.load(false, true); // try to reload 1036 } 1037 1038 if (this.slide.holderElement) { 1039 this.slide.holderElement.setAttribute('aria-hidden', 'false'); 1040 } 1041 } 1042 /** 1043 * Deactivate the content 1044 */ 1045 1046 1047 deactivate() { 1048 this.instance.dispatch('contentDeactivate', { 1049 content: this 1050 }); 1051 1052 if (this.slide && this.slide.holderElement) { 1053 this.slide.holderElement.setAttribute('aria-hidden', 'true'); 1054 } 1055 } 1056 /** 1057 * Remove the content from DOM 1058 */ 1059 1060 1061 remove() { 1062 this.isAttached = false; 1063 1064 if (this.instance.dispatch('contentRemove', { 1065 content: this 1066 }).defaultPrevented) { 1067 return; 1068 } 1069 1070 if (this.element && this.element.parentNode) { 1071 this.element.remove(); 1072 } 1073 1074 if (this.placeholder && this.placeholder.element) { 1075 this.placeholder.element.remove(); 1076 } 1077 } 1078 /** 1079 * Append the image content to slide container 1080 */ 1081 1082 1083 appendImage() { 1084 if (!this.isAttached) { 1085 return; 1086 } 1087 1088 if (this.instance.dispatch('contentAppendImage', { 1089 content: this 1090 }).defaultPrevented) { 1091 return; 1092 } // ensure that element exists and is not already appended 1093 1094 1095 if (this.slide && this.element && !this.element.parentNode) { 1096 this.slide.container.appendChild(this.element); 1097 } 1098 1099 if (this.state === LOAD_STATE.LOADED || this.state === LOAD_STATE.ERROR) { 1100 this.removePlaceholder(); 1101 } 1102 } 1103 1104} 1105 1106/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */ 1107 1108/** @typedef {import('../core/base.js').default} PhotoSwipeBase */ 1109 1110/** @typedef {import('../photoswipe.js').Point} Point */ 1111 1112/** @typedef {import('../slide/slide.js').SlideData} SlideData */ 1113 1114/** 1115 * @param {PhotoSwipeOptions} options 1116 * @param {PhotoSwipeBase} pswp 1117 * @returns {Point} 1118 */ 1119function getViewportSize(options, pswp) { 1120 if (options.getViewportSizeFn) { 1121 const newViewportSize = options.getViewportSizeFn(options, pswp); 1122 1123 if (newViewportSize) { 1124 return newViewportSize; 1125 } 1126 } 1127 1128 return { 1129 x: document.documentElement.clientWidth, 1130 // TODO: height on mobile is very incosistent due to toolbar 1131 // find a way to improve this 1132 // 1133 // document.documentElement.clientHeight - doesn't seem to work well 1134 y: window.innerHeight 1135 }; 1136} 1137/** 1138 * Parses padding option. 1139 * Supported formats: 1140 * 1141 * // Object 1142 * padding: { 1143 * top: 0, 1144 * bottom: 0, 1145 * left: 0, 1146 * right: 0 1147 * } 1148 * 1149 * // A function that returns the object 1150 * paddingFn: (viewportSize, itemData, index) => { 1151 * return { 1152 * top: 0, 1153 * bottom: 0, 1154 * left: 0, 1155 * right: 0 1156 * }; 1157 * } 1158 * 1159 * // Legacy variant 1160 * paddingLeft: 0, 1161 * paddingRight: 0, 1162 * paddingTop: 0, 1163 * paddingBottom: 0, 1164 * 1165 * @param {'left' | 'top' | 'bottom' | 'right'} prop 1166 * @param {PhotoSwipeOptions} options PhotoSwipe options 1167 * @param {Point} viewportSize PhotoSwipe viewport size, for example: { x:800, y:600 } 1168 * @param {SlideData} itemData Data about the slide 1169 * @param {number} index Slide index 1170 * @returns {number} 1171 */ 1172 1173function parsePaddingOption(prop, options, viewportSize, itemData, index) { 1174 let paddingValue = 0; 1175 1176 if (options.paddingFn) { 1177 paddingValue = options.paddingFn(viewportSize, itemData, index)[prop]; 1178 } else if (options.padding) { 1179 paddingValue = options.padding[prop]; 1180 } else { 1181 const legacyPropName = 'padding' + prop[0].toUpperCase() + prop.slice(1); // @ts-expect-error 1182 1183 if (options[legacyPropName]) { 1184 // @ts-expect-error 1185 paddingValue = options[legacyPropName]; 1186 } 1187 } 1188 1189 return Number(paddingValue) || 0; 1190} 1191/** 1192 * @param {PhotoSwipeOptions} options 1193 * @param {Point} viewportSize 1194 * @param {SlideData} itemData 1195 * @param {number} index 1196 * @returns {Point} 1197 */ 1198 1199function getPanAreaSize(options, viewportSize, itemData, index) { 1200 return { 1201 x: viewportSize.x - parsePaddingOption('left', options, viewportSize, itemData, index) - parsePaddingOption('right', options, viewportSize, itemData, index), 1202 y: viewportSize.y - parsePaddingOption('top', options, viewportSize, itemData, index) - parsePaddingOption('bottom', options, viewportSize, itemData, index) 1203 }; 1204} 1205 1206const MAX_IMAGE_WIDTH = 4000; 1207/** @typedef {import('../photoswipe.js').default} PhotoSwipe */ 1208 1209/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */ 1210 1211/** @typedef {import('../photoswipe.js').Point} Point */ 1212 1213/** @typedef {import('../slide/slide.js').SlideData} SlideData */ 1214 1215/** @typedef {'fit' | 'fill' | number | ((zoomLevelObject: ZoomLevel) => number)} ZoomLevelOption */ 1216 1217/** 1218 * Calculates zoom levels for specific slide. 1219 * Depends on viewport size and image size. 1220 */ 1221 1222class ZoomLevel { 1223 /** 1224 * @param {PhotoSwipeOptions} options PhotoSwipe options 1225 * @param {SlideData} itemData Slide data 1226 * @param {number} index Slide index 1227 * @param {PhotoSwipe} [pswp] PhotoSwipe instance, can be undefined if not initialized yet 1228 */ 1229 constructor(options, itemData, index, pswp) { 1230 this.pswp = pswp; 1231 this.options = options; 1232 this.itemData = itemData; 1233 this.index = index; 1234 /** @type { Point | null } */ 1235 1236 this.panAreaSize = null; 1237 /** @type { Point | null } */ 1238 1239 this.elementSize = null; 1240 this.fit = 1; 1241 this.fill = 1; 1242 this.vFill = 1; 1243 this.initial = 1; 1244 this.secondary = 1; 1245 this.max = 1; 1246 this.min = 1; 1247 } 1248 /** 1249 * Calculate initial, secondary and maximum zoom level for the specified slide. 1250 * 1251 * It should be called when either image or viewport size changes. 1252 * 1253 * @param {number} maxWidth 1254 * @param {number} maxHeight 1255 * @param {Point} panAreaSize 1256 */ 1257 1258 1259 update(maxWidth, maxHeight, panAreaSize) { 1260 /** @type {Point} */ 1261 const elementSize = { 1262 x: maxWidth, 1263 y: maxHeight 1264 }; 1265 this.elementSize = elementSize; 1266 this.panAreaSize = panAreaSize; 1267 const hRatio = panAreaSize.x / elementSize.x; 1268 const vRatio = panAreaSize.y / elementSize.y; 1269 this.fit = Math.min(1, hRatio < vRatio ? hRatio : vRatio); 1270 this.fill = Math.min(1, hRatio > vRatio ? hRatio : vRatio); // zoom.vFill defines zoom level of the image 1271 // when it has 100% of viewport vertical space (height) 1272 1273 this.vFill = Math.min(1, vRatio); 1274 this.initial = this._getInitial(); 1275 this.secondary = this._getSecondary(); 1276 this.max = Math.max(this.initial, this.secondary, this._getMax()); 1277 this.min = Math.min(this.fit, this.initial, this.secondary); 1278 1279 if (this.pswp) { 1280 this.pswp.dispatch('zoomLevelsUpdate', { 1281 zoomLevels: this, 1282 slideData: this.itemData 1283 }); 1284 } 1285 } 1286 /** 1287 * Parses user-defined zoom option. 1288 * 1289 * @private 1290 * @param {'initial' | 'secondary' | 'max'} optionPrefix Zoom level option prefix (initial, secondary, max) 1291 * @returns { number | undefined } 1292 */ 1293 1294 1295 _parseZoomLevelOption(optionPrefix) { 1296 const optionName = 1297 /** @type {'initialZoomLevel' | 'secondaryZoomLevel' | 'maxZoomLevel'} */ 1298 optionPrefix + 'ZoomLevel'; 1299 const optionValue = this.options[optionName]; 1300 1301 if (!optionValue) { 1302 return; 1303 } 1304 1305 if (typeof optionValue === 'function') { 1306 return optionValue(this); 1307 } 1308 1309 if (optionValue === 'fill') { 1310 return this.fill; 1311 } 1312 1313 if (optionValue === 'fit') { 1314 return this.fit; 1315 } 1316 1317 return Number(optionValue); 1318 } 1319 /** 1320 * Get zoom level to which image will be zoomed after double-tap gesture, 1321 * or when user clicks on zoom icon, 1322 * or mouse-click on image itself. 1323 * If you return 1 image will be zoomed to its original size. 1324 * 1325 * @private 1326 * @return {number} 1327 */ 1328 1329 1330 _getSecondary() { 1331 let currZoomLevel = this._parseZoomLevelOption('secondary'); 1332 1333 if (currZoomLevel) { 1334 return currZoomLevel; 1335 } // 3x of "fit" state, but not larger than original 1336 1337 1338 currZoomLevel = Math.min(1, this.fit * 3); 1339 1340 if (this.elementSize && currZoomLevel * this.elementSize.x > MAX_IMAGE_WIDTH) { 1341 currZoomLevel = MAX_IMAGE_WIDTH / this.elementSize.x; 1342 } 1343 1344 return currZoomLevel; 1345 } 1346 /** 1347 * Get initial image zoom level. 1348 * 1349 * @private 1350 * @return {number} 1351 */ 1352 1353 1354 _getInitial() { 1355 return this._parseZoomLevelOption('initial') || this.fit; 1356 } 1357 /** 1358 * Maximum zoom level when user zooms 1359 * via zoom/pinch gesture, 1360 * via cmd/ctrl-wheel or via trackpad. 1361 * 1362 * @private 1363 * @return {number} 1364 */ 1365 1366 1367 _getMax() { 1368 // max zoom level is x4 from "fit state", 1369 // used for zoom gesture and ctrl/trackpad zoom 1370 return this._parseZoomLevelOption('max') || Math.max(1, this.fit * 4); 1371 } 1372 1373} 1374 1375/** 1376 * Lazy-load an image 1377 * This function is used both by Lightbox and PhotoSwipe core, 1378 * thus it can be called before dialog is opened. 1379 * 1380 * @param {SlideData} itemData Data about the slide 1381 * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance 1382 * @param {number} index 1383 * @returns {Content} Image that is being decoded or false. 1384 */ 1385 1386function lazyLoadData(itemData, instance, index) { 1387 const content = instance.createContentFromData(itemData, index); 1388 /** @type {ZoomLevel | undefined} */ 1389 1390 let zoomLevel; 1391 const { 1392 options 1393 } = instance; // We need to know dimensions of the image to preload it, 1394 // as it might use srcset, and we need to define sizes 1395 1396 if (options) { 1397 zoomLevel = new ZoomLevel(options, itemData, -1); 1398 let viewportSize; 1399 1400 if (instance.pswp) { 1401 viewportSize = instance.pswp.viewportSize; 1402 } else { 1403 viewportSize = getViewportSize(options, instance); 1404 } 1405 1406 const panAreaSize = getPanAreaSize(options, viewportSize, itemData, index); 1407 zoomLevel.update(content.width, content.height, panAreaSize); 1408 } 1409 1410 content.lazyLoad(); 1411 1412 if (zoomLevel) { 1413 content.setDisplayedSize(Math.ceil(content.width * zoomLevel.initial), Math.ceil(content.height * zoomLevel.initial)); 1414 } 1415 1416 return content; 1417} 1418/** 1419 * Lazy-loads specific slide. 1420 * This function is used both by Lightbox and PhotoSwipe core, 1421 * thus it can be called before dialog is opened. 1422 * 1423 * By default, it loads image based on viewport size and initial zoom level. 1424 * 1425 * @param {number} index Slide index 1426 * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox eventable instance 1427 * @returns {Content | undefined} 1428 */ 1429 1430function lazyLoadSlide(index, instance) { 1431 const itemData = instance.getItemData(index); 1432 1433 if (instance.dispatch('lazyLoadSlide', { 1434 index, 1435 itemData 1436 }).defaultPrevented) { 1437 return; 1438 } 1439 1440 return lazyLoadData(itemData, instance, index); 1441} 1442 1443/** @typedef {import("../photoswipe.js").default} PhotoSwipe */ 1444 1445/** @typedef {import("../slide/slide.js").SlideData} SlideData */ 1446 1447/** 1448 * PhotoSwipe base class that can retrieve data about every slide. 1449 * Shared by PhotoSwipe Core and PhotoSwipe Lightbox 1450 */ 1451 1452class PhotoSwipeBase extends Eventable { 1453 /** 1454 * Get total number of slides 1455 * 1456 * @returns {number} 1457 */ 1458 getNumItems() { 1459 var _this$options; 1460 1461 let numItems = 0; 1462 const dataSource = (_this$options = this.options) === null || _this$options === void 0 ? void 0 : _this$options.dataSource; 1463 1464 if (dataSource && 'length' in dataSource) { 1465 // may be an array or just object with length property 1466 numItems = dataSource.length; 1467 } else if (dataSource && 'gallery' in dataSource) { 1468 // query DOM elements 1469 if (!dataSource.items) { 1470 dataSource.items = this._getGalleryDOMElements(dataSource.gallery); 1471 } 1472 1473 if (dataSource.items) { 1474 numItems = dataSource.items.length; 1475 } 1476 } // legacy event, before filters were introduced 1477 1478 1479 const event = this.dispatch('numItems', { 1480 dataSource, 1481 numItems 1482 }); 1483 return this.applyFilters('numItems', event.numItems, dataSource); 1484 } 1485 /** 1486 * @param {SlideData} slideData 1487 * @param {number} index 1488 * @returns {Content} 1489 */ 1490 1491 1492 createContentFromData(slideData, index) { 1493 return new Content(slideData, this, index); 1494 } 1495 /** 1496 * Get item data by index. 1497 * 1498 * "item data" should contain normalized information that PhotoSwipe needs to generate a slide. 1499 * For example, it may contain properties like 1500 * `src`, `srcset`, `w`, `h`, which will be used to generate a slide with image. 1501 * 1502 * @param {number} index 1503 * @returns {SlideData} 1504 */ 1505 1506 1507 getItemData(index) { 1508 var _this$options2; 1509 1510 const dataSource = (_this$options2 = this.options) === null || _this$options2 === void 0 ? void 0 : _this$options2.dataSource; 1511 /** @type {SlideData | HTMLElement} */ 1512 1513 let dataSourceItem = {}; 1514 1515 if (Array.isArray(dataSource)) { 1516 // Datasource is an array of elements 1517 dataSourceItem = dataSource[index]; 1518 } else if (dataSource && 'gallery' in dataSource) { 1519 // dataSource has gallery property, 1520 // thus it was created by Lightbox, based on 1521 // gallery and children options 1522 // query DOM elements 1523 if (!dataSource.items) { 1524 dataSource.items = this._getGalleryDOMElements(dataSource.gallery); 1525 } 1526 1527 dataSourceItem = dataSource.items[index]; 1528 } 1529 1530 let itemData = dataSourceItem; 1531 1532 if (itemData instanceof Element) { 1533 itemData = this._domElementToItemData(itemData); 1534 } // Dispatching the itemData event, 1535 // it's a legacy verion before filters were introduced 1536 1537 1538 const event = this.dispatch('itemData', { 1539 itemData: itemData || {}, 1540 index 1541 }); 1542 return this.applyFilters('itemData', event.itemData, index); 1543 } 1544 /** 1545 * Get array of gallery DOM elements, 1546 * based on childSelector and gallery element. 1547 * 1548 * @param {HTMLElement} galleryElement 1549 * @returns {HTMLElement[]} 1550 */ 1551 1552 1553 _getGalleryDOMElements(galleryElement) { 1554 var _this$options3, _this$options4; 1555 1556 if ((_this$options3 = this.options) !== null && _this$options3 !== void 0 && _this$options3.children || (_this$options4 = this.options) !== null && _this$options4 !== void 0 && _this$options4.childSelector) { 1557 return getElementsFromOption(this.options.children, this.options.childSelector, galleryElement) || []; 1558 } 1559 1560 return [galleryElement]; 1561 } 1562 /** 1563 * Converts DOM element to item data object. 1564 * 1565 * @param {HTMLElement} element DOM element 1566 * @returns {SlideData} 1567 */ 1568 1569 1570 _domElementToItemData(element) { 1571 /** @type {SlideData} */ 1572 const itemData = { 1573 element 1574 }; 1575 const linkEl = 1576 /** @type {HTMLAnchorElement} */ 1577 element.tagName === 'A' ? element : element.querySelector('a'); 1578 1579 if (linkEl) { 1580 // src comes from data-pswp-src attribute, 1581 // if it's empty link href is used 1582 itemData.src = linkEl.dataset.pswpSrc || linkEl.href; 1583 1584 if (linkEl.dataset.pswpSrcset) { 1585 itemData.srcset = linkEl.dataset.pswpSrcset; 1586 } 1587 1588 itemData.width = linkEl.dataset.pswpWidth ? parseInt(linkEl.dataset.pswpWidth, 10) : 0; 1589 itemData.height = linkEl.dataset.pswpHeight ? parseInt(linkEl.dataset.pswpHeight, 10) : 0; // support legacy w & h properties 1590 1591 itemData.w = itemData.width; 1592 itemData.h = itemData.height; 1593 1594 if (linkEl.dataset.pswpType) { 1595 itemData.type = linkEl.dataset.pswpType; 1596 } 1597 1598 const thumbnailEl = element.querySelector('img'); 1599 1600 if (thumbnailEl) { 1601 var _thumbnailEl$getAttri; 1602 1603 // msrc is URL to placeholder image that's displayed before large image is loaded 1604 // by default it's displayed only for the first slide 1605 itemData.msrc = thumbnailEl.currentSrc || thumbnailEl.src; 1606 itemData.alt = (_thumbnailEl$getAttri = thumbnailEl.getAttribute('alt')) !== null && _thumbnailEl$getAttri !== void 0 ? _thumbnailEl$getAttri : ''; 1607 } 1608 1609 if (linkEl.dataset.pswpCropped || linkEl.dataset.cropped) { 1610 itemData.thumbCropped = true; 1611 } 1612 } 1613 1614 return this.applyFilters('domItemData', itemData, element, linkEl); 1615 } 1616 /** 1617 * Lazy-load by slide data 1618 * 1619 * @param {SlideData} itemData Data about the slide 1620 * @param {number} index 1621 * @returns {Content} Image that is being decoded or false. 1622 */ 1623 1624 1625 lazyLoadData(itemData, index) { 1626 return lazyLoadData(itemData, this, index); 1627 } 1628 1629} 1630 1631/** 1632 * @template T 1633 * @typedef {import('../types.js').Type<T>} Type<T> 1634 */ 1635 1636/** @typedef {import('../photoswipe.js').default} PhotoSwipe */ 1637 1638/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */ 1639 1640/** @typedef {import('../photoswipe.js').DataSource} DataSource */ 1641 1642/** @typedef {import('../photoswipe.js').Point} Point */ 1643 1644/** @typedef {import('../slide/content.js').default} Content */ 1645 1646/** @typedef {import('../core/eventable.js').PhotoSwipeEventsMap} PhotoSwipeEventsMap */ 1647 1648/** @typedef {import('../core/eventable.js').PhotoSwipeFiltersMap} PhotoSwipeFiltersMap */ 1649 1650/** 1651 * @template {keyof PhotoSwipeEventsMap} T 1652 * @typedef {import('../core/eventable.js').EventCallback<T>} EventCallback<T> 1653 */ 1654 1655/** 1656 * PhotoSwipe Lightbox 1657 * 1658 * - If user has unsupported browser it falls back to default browser action (just opens URL) 1659 * - Binds click event to links that should open PhotoSwipe 1660 * - parses DOM strcture for PhotoSwipe (retrieves large image URLs and sizes) 1661 * - Initializes PhotoSwipe 1662 * 1663 * 1664 * Loader options use the same object as PhotoSwipe, and supports such options: 1665 * 1666 * gallery - Element | Element[] | NodeList | string selector for the gallery element 1667 * children - Element | Element[] | NodeList | string selector for the gallery children 1668 * 1669 */ 1670 1671class PhotoSwipeLightbox extends PhotoSwipeBase { 1672 /** 1673 * @param {PhotoSwipeOptions} [options] 1674 */ 1675 constructor(options) { 1676 super(); 1677 /** @type {PhotoSwipeOptions} */ 1678 1679 this.options = options || {}; 1680 this._uid = 0; 1681 this.shouldOpen = false; 1682 /** 1683 * @private 1684 * @type {Content | undefined} 1685 */ 1686 1687 this._preloadedContent = undefined; 1688 this.onThumbnailsClick = this.onThumbnailsClick.bind(this); 1689 } 1690 /** 1691 * Initialize lightbox, should be called only once. 1692 * It's not included in the main constructor, so you may bind events before it. 1693 */ 1694 1695 1696 init() { 1697 // Bind click events to each gallery 1698 getElementsFromOption(this.options.gallery, this.options.gallerySelector).forEach(galleryElement => { 1699 galleryElement.addEventListener('click', this.onThumbnailsClick, false); 1700 }); 1701 } 1702 /** 1703 * @param {MouseEvent} e 1704 */ 1705 1706 1707 onThumbnailsClick(e) { 1708 // Exit and allow default browser action if: 1709 if (specialKeyUsed(e) // ... if clicked with a special key (ctrl/cmd...) 1710 || window.pswp) { 1711 // ... if PhotoSwipe is already open 1712 return; 1713 } // If both clientX and clientY are 0 or not defined, 1714 // the event is likely triggered by keyboard, 1715 // so we do not pass the initialPoint 1716 // 1717 // Note that some screen readers emulate the mouse position, 1718 // so it's not the ideal way to detect them. 1719 // 1720 1721 /** @type {Point | null} */ 1722 1723 1724 let initialPoint = { 1725 x: e.clientX, 1726 y: e.clientY 1727 }; 1728 1729 if (!initialPoint.x && !initialPoint.y) { 1730 initialPoint = null; 1731 } 1732 1733 let clickedIndex = this.getClickedIndex(e); 1734 clickedIndex = this.applyFilters('clickedIndex', clickedIndex, e, this); 1735 /** @type {DataSource} */ 1736 1737 const dataSource = { 1738 gallery: 1739 /** @type {HTMLElement} */ 1740 e.currentTarget 1741 }; 1742 1743 if (clickedIndex >= 0) { 1744 e.preventDefault(); 1745 this.loadAndOpen(clickedIndex, dataSource, initialPoint); 1746 } 1747 } 1748 /** 1749 * Get index of gallery item that was clicked. 1750 * 1751 * @param {MouseEvent} e click event 1752 * @returns {number} 1753 */ 1754 1755 1756 getClickedIndex(e) { 1757 // legacy option 1758 if (this.options.getClickedIndexFn) { 1759 return this.options.getClickedIndexFn.call(this, e); 1760 } 1761 1762 const clickedTarget = 1763 /** @type {HTMLElement} */ 1764 e.target; 1765 const childElements = getElementsFromOption(this.options.children, this.options.childSelector, 1766 /** @type {HTMLElement} */ 1767 e.currentTarget); 1768 const clickedChildIndex = childElements.findIndex(child => child === clickedTarget || child.contains(clickedTarget)); 1769 1770 if (clickedChildIndex !== -1) { 1771 return clickedChildIndex; 1772 } else if (this.options.children || this.options.childSelector) { 1773 // click wasn't on a child element 1774 return -1; 1775 } // There is only one item (which is the gallery) 1776 1777 1778 return 0; 1779 } 1780 /** 1781 * Load and open PhotoSwipe 1782 * 1783 * @param {number} index 1784 * @param {DataSource} [dataSource] 1785 * @param {Point | null} [initialPoint] 1786 * @returns {boolean} 1787 */ 1788 1789 1790 loadAndOpen(index, dataSource, initialPoint) { 1791 // Check if the gallery is already open 1792 if (window.pswp || !this.options) { 1793 return false; 1794 } // Use the first gallery element if dataSource is not provided 1795 1796 1797 if (!dataSource && this.options.gallery && this.options.children) { 1798 const galleryElements = getElementsFromOption(this.options.gallery); 1799 1800 if (galleryElements[0]) { 1801 dataSource = { 1802 gallery: galleryElements[0] 1803 }; 1804 } 1805 } // set initial index 1806 1807 1808 this.options.index = index; // define options for PhotoSwipe constructor 1809 1810 this.options.initialPointerPos = initialPoint; 1811 this.shouldOpen = true; 1812 this.preload(index, dataSource); 1813 return true; 1814 } 1815 /** 1816 * Load the main module and the slide content by index 1817 * 1818 * @param {number} index 1819 * @param {DataSource} [dataSource] 1820 */ 1821 1822 1823 preload(index, dataSource) { 1824 const { 1825 options 1826 } = this; 1827 1828 if (dataSource) { 1829 options.dataSource = dataSource; 1830 } // Add the main module 1831 1832 /** @type {Promise<Type<PhotoSwipe>>[]} */ 1833 1834 1835 const promiseArray = []; 1836 const pswpModuleType = typeof options.pswpModule; 1837 1838 if (isPswpClass(options.pswpModule)) { 1839 promiseArray.push(Promise.resolve( 1840 /** @type {Type<PhotoSwipe>} */ 1841 options.pswpModule)); 1842 } else if (pswpModuleType === 'string') { 1843 throw new Error('pswpModule as string is no longer supported'); 1844 } else if (pswpModuleType === 'function') { 1845 promiseArray.push( 1846 /** @type {() => Promise<Type<PhotoSwipe>>} */ 1847 options.pswpModule()); 1848 } else { 1849 throw new Error('pswpModule is not valid'); 1850 } // Add custom-defined promise, if any 1851 1852 1853 if (typeof options.openPromise === 'function') { 1854 // allow developers to perform some task before opening 1855 promiseArray.push(options.openPromise()); 1856 } 1857 1858 if (options.preloadFirstSlide !== false && index >= 0) { 1859 this._preloadedContent = lazyLoadSlide(index, this); 1860 } // Wait till all promises resolve and open PhotoSwipe 1861 1862 1863 const uid = ++this._uid; 1864 Promise.all(promiseArray).then(iterableModules => { 1865 if (this.shouldOpen) { 1866 const mainModule = iterableModules[0]; 1867 1868 this._openPhotoswipe(mainModule, uid); 1869 } 1870 }); 1871 } 1872 /** 1873 * @private 1874 * @param {Type<PhotoSwipe> | { default: Type<PhotoSwipe> }} module 1875 * @param {number} uid 1876 */ 1877 1878 1879 _openPhotoswipe(module, uid) { 1880 // Cancel opening if UID doesn't match the current one 1881 // (if user clicked on another gallery item before current was loaded). 1882 // 1883 // Or if shouldOpen flag is set to false 1884 // (developer may modify it via public API) 1885 if (uid !== this._uid && this.shouldOpen) { 1886 return; 1887 } 1888 1889 this.shouldOpen = false; // PhotoSwipe is already open 1890 1891 if (window.pswp) { 1892 return; 1893 } 1894 /** 1895 * Pass data to PhotoSwipe and open init 1896 * 1897 * @type {PhotoSwipe} 1898 */ 1899 1900 1901 const pswp = typeof module === 'object' ? new module.default(this.options) // eslint-disable-line 1902 : new module(this.options); // eslint-disable-line 1903 1904 this.pswp = pswp; 1905 window.pswp = pswp; // map listeners from Lightbox to PhotoSwipe Core 1906 1907 /** @type {(keyof PhotoSwipeEventsMap)[]} */ 1908 1909 Object.keys(this._listeners).forEach(name => { 1910 var _this$_listeners$name; 1911 1912 (_this$_listeners$name = this._listeners[name]) === null || _this$_listeners$name === void 0 || _this$_listeners$name.forEach(fn => { 1913 pswp.on(name, 1914 /** @type {EventCallback<typeof name>} */ 1915 fn); 1916 }); 1917 }); // same with filters 1918 1919 /** @type {(keyof PhotoSwipeFiltersMap)[]} */ 1920 1921 Object.keys(this._filters).forEach(name => { 1922 var _this$_filters$name; 1923 1924 (_this$_filters$name = this._filters[name]) === null || _this$_filters$name === void 0 || _this$_filters$name.forEach(filter => { 1925 pswp.addFilter(name, filter.fn, filter.priority); 1926 }); 1927 }); 1928 1929 if (this._preloadedContent) { 1930 pswp.contentLoader.addToCache(this._preloadedContent); 1931 this._preloadedContent = undefined; 1932 } 1933 1934 pswp.on('destroy', () => { 1935 // clean up public variables 1936 this.pswp = undefined; 1937 delete window.pswp; 1938 }); 1939 pswp.init(); 1940 } 1941 /** 1942 * Unbinds all events, closes PhotoSwipe if it's open. 1943 */ 1944 1945 1946 destroy() { 1947 var _this$pswp; 1948 1949 (_this$pswp = this.pswp) === null || _this$pswp === void 0 || _this$pswp.destroy(); 1950 this.shouldOpen = false; 1951 this._listeners = {}; 1952 getElementsFromOption(this.options.gallery, this.options.gallerySelector).forEach(galleryElement => { 1953 galleryElement.removeEventListener('click', this.onThumbnailsClick, false); 1954 }); 1955 } 1956 1957} 1958 1959export { PhotoSwipeLightbox as default }; 1960//# sourceMappingURL=photoswipe-lightbox.esm.js.map
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.