| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960 |
- /*!
- * PhotoSwipe Lightbox 5.4.4 - https://photoswipe.com
- * (c) 2024 Dmytro Semenov
- */
- /** @typedef {import('../photoswipe.js').Point} Point */
- /**
- * @template {keyof HTMLElementTagNameMap} T
- * @param {string} className
- * @param {T} tagName
- * @param {Node} [appendToEl]
- * @returns {HTMLElementTagNameMap[T]}
- */
- function createElement(className, tagName, appendToEl) {
- const el = document.createElement(tagName);
- if (className) {
- el.className = className;
- }
- if (appendToEl) {
- appendToEl.appendChild(el);
- }
- return el;
- }
- /**
- * Get transform string
- *
- * @param {number} x
- * @param {number} [y]
- * @param {number} [scale]
- * @returns {string}
- */
- function toTransformString(x, y, scale) {
- let propValue = `translate3d(${x}px,${y || 0}px,0)`;
- if (scale !== undefined) {
- propValue += ` scale3d(${scale},${scale},1)`;
- }
- return propValue;
- }
- /**
- * Apply width and height CSS properties to element
- *
- * @param {HTMLElement} el
- * @param {string | number} w
- * @param {string | number} h
- */
- function setWidthHeight(el, w, h) {
- el.style.width = typeof w === 'number' ? `${w}px` : w;
- el.style.height = typeof h === 'number' ? `${h}px` : h;
- }
- /** @typedef {LOAD_STATE[keyof LOAD_STATE]} LoadState */
- /** @type {{ IDLE: 'idle'; LOADING: 'loading'; LOADED: 'loaded'; ERROR: 'error' }} */
- const LOAD_STATE = {
- IDLE: 'idle',
- LOADING: 'loading',
- LOADED: 'loaded',
- ERROR: 'error'
- };
- /**
- * Check if click or keydown event was dispatched
- * with a special key or via mouse wheel.
- *
- * @param {MouseEvent | KeyboardEvent} e
- * @returns {boolean}
- */
- function specialKeyUsed(e) {
- return 'button' in e && e.button === 1 || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey;
- }
- /**
- * Parse `gallery` or `children` options.
- *
- * @param {import('../photoswipe.js').ElementProvider} [option]
- * @param {string} [legacySelector]
- * @param {HTMLElement | Document} [parent]
- * @returns HTMLElement[]
- */
- function getElementsFromOption(option, legacySelector, parent = document) {
- /** @type {HTMLElement[]} */
- let elements = [];
- if (option instanceof Element) {
- elements = [option];
- } else if (option instanceof NodeList || Array.isArray(option)) {
- elements = Array.from(option);
- } else {
- const selector = typeof option === 'string' ? option : legacySelector;
- if (selector) {
- elements = Array.from(parent.querySelectorAll(selector));
- }
- }
- return elements;
- }
- /**
- * Check if variable is PhotoSwipe class
- *
- * @param {any} fn
- * @returns {boolean}
- */
- function isPswpClass(fn) {
- return typeof fn === 'function' && fn.prototype && fn.prototype.goTo;
- }
- /**
- * Check if browser is Safari
- *
- * @returns {boolean}
- */
- function isSafari() {
- return !!(navigator.vendor && navigator.vendor.match(/apple/i));
- }
- /** @typedef {import('../lightbox/lightbox.js').default} PhotoSwipeLightbox */
- /** @typedef {import('../photoswipe.js').default} PhotoSwipe */
- /** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */
- /** @typedef {import('../photoswipe.js').DataSource} DataSource */
- /** @typedef {import('../ui/ui-element.js').UIElementData} UIElementData */
- /** @typedef {import('../slide/content.js').default} ContentDefault */
- /** @typedef {import('../slide/slide.js').default} Slide */
- /** @typedef {import('../slide/slide.js').SlideData} SlideData */
- /** @typedef {import('../slide/zoom-level.js').default} ZoomLevel */
- /** @typedef {import('../slide/get-thumb-bounds.js').Bounds} Bounds */
- /**
- * Allow adding an arbitrary props to the Content
- * https://photoswipe.com/custom-content/#using-webp-image-format
- * @typedef {ContentDefault & Record<string, any>} Content
- */
- /** @typedef {{ x?: number; y?: number }} Point */
- /**
- * @typedef {Object} PhotoSwipeEventsMap https://photoswipe.com/events/
- *
- *
- * https://photoswipe.com/adding-ui-elements/
- *
- * @prop {undefined} uiRegister
- * @prop {{ data: UIElementData }} uiElementCreate
- *
- *
- * https://photoswipe.com/events/#initialization-events
- *
- * @prop {undefined} beforeOpen
- * @prop {undefined} firstUpdate
- * @prop {undefined} initialLayout
- * @prop {undefined} change
- * @prop {undefined} afterInit
- * @prop {undefined} bindEvents
- *
- *
- * https://photoswipe.com/events/#opening-or-closing-transition-events
- *
- * @prop {undefined} openingAnimationStart
- * @prop {undefined} openingAnimationEnd
- * @prop {undefined} closingAnimationStart
- * @prop {undefined} closingAnimationEnd
- *
- *
- * https://photoswipe.com/events/#closing-events
- *
- * @prop {undefined} close
- * @prop {undefined} destroy
- *
- *
- * https://photoswipe.com/events/#pointer-and-gesture-events
- *
- * @prop {{ originalEvent: PointerEvent }} pointerDown
- * @prop {{ originalEvent: PointerEvent }} pointerMove
- * @prop {{ originalEvent: PointerEvent }} pointerUp
- * @prop {{ bgOpacity: number }} pinchClose can be default prevented
- * @prop {{ panY: number }} verticalDrag can be default prevented
- *
- *
- * https://photoswipe.com/events/#slide-content-events
- *
- * @prop {{ content: Content }} contentInit
- * @prop {{ content: Content; isLazy: boolean }} contentLoad can be default prevented
- * @prop {{ content: Content; isLazy: boolean }} contentLoadImage can be default prevented
- * @prop {{ content: Content; slide: Slide; isError?: boolean }} loadComplete
- * @prop {{ content: Content; slide: Slide }} loadError
- * @prop {{ content: Content; width: number; height: number }} contentResize can be default prevented
- * @prop {{ content: Content; width: number; height: number; slide: Slide }} imageSizeChange
- * @prop {{ content: Content }} contentLazyLoad can be default prevented
- * @prop {{ content: Content }} contentAppend can be default prevented
- * @prop {{ content: Content }} contentActivate can be default prevented
- * @prop {{ content: Content }} contentDeactivate can be default prevented
- * @prop {{ content: Content }} contentRemove can be default prevented
- * @prop {{ content: Content }} contentDestroy can be default prevented
- *
- *
- * undocumented
- *
- * @prop {{ point: Point; originalEvent: PointerEvent }} imageClickAction can be default prevented
- * @prop {{ point: Point; originalEvent: PointerEvent }} bgClickAction can be default prevented
- * @prop {{ point: Point; originalEvent: PointerEvent }} tapAction can be default prevented
- * @prop {{ point: Point; originalEvent: PointerEvent }} doubleTapAction can be default prevented
- *
- * @prop {{ originalEvent: KeyboardEvent }} keydown can be default prevented
- * @prop {{ x: number; dragging: boolean }} moveMainScroll
- * @prop {{ slide: Slide }} firstZoomPan
- * @prop {{ slide: Slide | undefined, data: SlideData, index: number }} gettingData
- * @prop {undefined} beforeResize
- * @prop {undefined} resize
- * @prop {undefined} viewportSize
- * @prop {undefined} updateScrollOffset
- * @prop {{ slide: Slide }} slideInit
- * @prop {{ slide: Slide }} afterSetContent
- * @prop {{ slide: Slide }} slideLoad
- * @prop {{ slide: Slide }} appendHeavy can be default prevented
- * @prop {{ slide: Slide }} appendHeavyContent
- * @prop {{ slide: Slide }} slideActivate
- * @prop {{ slide: Slide }} slideDeactivate
- * @prop {{ slide: Slide }} slideDestroy
- * @prop {{ destZoomLevel: number, centerPoint: Point | undefined, transitionDuration: number | false | undefined }} beforeZoomTo
- * @prop {{ slide: Slide }} zoomPanUpdate
- * @prop {{ slide: Slide }} initialZoomPan
- * @prop {{ slide: Slide }} calcSlideSize
- * @prop {undefined} resolutionChanged
- * @prop {{ originalEvent: WheelEvent }} wheel can be default prevented
- * @prop {{ content: Content }} contentAppendImage can be default prevented
- * @prop {{ index: number; itemData: SlideData }} lazyLoadSlide can be default prevented
- * @prop {undefined} lazyLoad
- * @prop {{ slide: Slide }} calcBounds
- * @prop {{ zoomLevels: ZoomLevel, slideData: SlideData }} zoomLevelsUpdate
- *
- *
- * legacy
- *
- * @prop {undefined} init
- * @prop {undefined} initialZoomIn
- * @prop {undefined} initialZoomOut
- * @prop {undefined} initialZoomInEnd
- * @prop {undefined} initialZoomOutEnd
- * @prop {{ dataSource: DataSource | undefined, numItems: number }} numItems
- * @prop {{ itemData: SlideData; index: number }} itemData
- * @prop {{ index: number, itemData: SlideData, instance: PhotoSwipe }} thumbBounds
- */
- /**
- * @typedef {Object} PhotoSwipeFiltersMap https://photoswipe.com/filters/
- *
- * @prop {(numItems: number, dataSource: DataSource | undefined) => number} numItems
- * Modify the total amount of slides. Example on Data sources page.
- * https://photoswipe.com/filters/#numitems
- *
- * @prop {(itemData: SlideData, index: number) => SlideData} itemData
- * Modify slide item data. Example on Data sources page.
- * https://photoswipe.com/filters/#itemdata
- *
- * @prop {(itemData: SlideData, element: HTMLElement, linkEl: HTMLAnchorElement) => SlideData} domItemData
- * Modify item data when it's parsed from DOM element. Example on Data sources page.
- * https://photoswipe.com/filters/#domitemdata
- *
- * @prop {(clickedIndex: number, e: MouseEvent, instance: PhotoSwipeLightbox) => number} clickedIndex
- * Modify clicked gallery item index.
- * https://photoswipe.com/filters/#clickedindex
- *
- * @prop {(placeholderSrc: string | false, content: Content) => string | false} placeholderSrc
- * Modify placeholder image source.
- * https://photoswipe.com/filters/#placeholdersrc
- *
- * @prop {(isContentLoading: boolean, content: Content) => boolean} isContentLoading
- * Modify if the content is currently loading.
- * https://photoswipe.com/filters/#iscontentloading
- *
- * @prop {(isContentZoomable: boolean, content: Content) => boolean} isContentZoomable
- * Modify if the content can be zoomed.
- * https://photoswipe.com/filters/#iscontentzoomable
- *
- * @prop {(useContentPlaceholder: boolean, content: Content) => boolean} useContentPlaceholder
- * Modify if the placeholder should be used for the content.
- * https://photoswipe.com/filters/#usecontentplaceholder
- *
- * @prop {(isKeepingPlaceholder: boolean, content: Content) => boolean} isKeepingPlaceholder
- * Modify if the placeholder should be kept after the content is loaded.
- * https://photoswipe.com/filters/#iskeepingplaceholder
- *
- *
- * @prop {(contentErrorElement: HTMLElement, content: Content) => HTMLElement} contentErrorElement
- * Modify an element when the content has error state (for example, if image cannot be loaded).
- * https://photoswipe.com/filters/#contenterrorelement
- *
- * @prop {(element: HTMLElement, data: UIElementData) => HTMLElement} uiElement
- * Modify a UI element that's being created.
- * https://photoswipe.com/filters/#uielement
- *
- * @prop {(thumbnail: HTMLElement | null | undefined, itemData: SlideData, index: number) => HTMLElement} thumbEl
- * Modify the thumbnail element from which opening zoom animation starts or ends.
- * https://photoswipe.com/filters/#thumbel
- *
- * @prop {(thumbBounds: Bounds | undefined, itemData: SlideData, index: number) => Bounds} thumbBounds
- * Modify the thumbnail bounds from which opening zoom animation starts or ends.
- * https://photoswipe.com/filters/#thumbbounds
- *
- * @prop {(srcsetSizesWidth: number, content: Content) => number} srcsetSizesWidth
- *
- * @prop {(preventPointerEvent: boolean, event: PointerEvent, pointerType: string) => boolean} preventPointerEvent
- *
- */
- /**
- * @template {keyof PhotoSwipeFiltersMap} T
- * @typedef {{ fn: PhotoSwipeFiltersMap[T], priority: number }} Filter
- */
- /**
- * @template {keyof PhotoSwipeEventsMap} T
- * @typedef {PhotoSwipeEventsMap[T] extends undefined ? PhotoSwipeEvent<T> : PhotoSwipeEvent<T> & PhotoSwipeEventsMap[T]} AugmentedEvent
- */
- /**
- * @template {keyof PhotoSwipeEventsMap} T
- * @typedef {(event: AugmentedEvent<T>) => void} EventCallback
- */
- /**
- * Base PhotoSwipe event object
- *
- * @template {keyof PhotoSwipeEventsMap} T
- */
- class PhotoSwipeEvent {
- /**
- * @param {T} type
- * @param {PhotoSwipeEventsMap[T]} [details]
- */
- constructor(type, details) {
- this.type = type;
- this.defaultPrevented = false;
- if (details) {
- Object.assign(this, details);
- }
- }
- preventDefault() {
- this.defaultPrevented = true;
- }
- }
- /**
- * PhotoSwipe base class that can listen and dispatch for events.
- * Shared by PhotoSwipe Core and PhotoSwipe Lightbox, extended by base.js
- */
- class Eventable {
- constructor() {
- /**
- * @type {{ [T in keyof PhotoSwipeEventsMap]?: ((event: AugmentedEvent<T>) => void)[] }}
- */
- this._listeners = {};
- /**
- * @type {{ [T in keyof PhotoSwipeFiltersMap]?: Filter<T>[] }}
- */
- this._filters = {};
- /** @type {PhotoSwipe | undefined} */
- this.pswp = undefined;
- /** @type {PhotoSwipeOptions | undefined} */
- this.options = undefined;
- }
- /**
- * @template {keyof PhotoSwipeFiltersMap} T
- * @param {T} name
- * @param {PhotoSwipeFiltersMap[T]} fn
- * @param {number} priority
- */
- addFilter(name, fn, priority = 100) {
- var _this$_filters$name, _this$_filters$name2, _this$pswp;
- if (!this._filters[name]) {
- this._filters[name] = [];
- }
- (_this$_filters$name = this._filters[name]) === null || _this$_filters$name === void 0 || _this$_filters$name.push({
- fn,
- priority
- });
- (_this$_filters$name2 = this._filters[name]) === null || _this$_filters$name2 === void 0 || _this$_filters$name2.sort((f1, f2) => f1.priority - f2.priority);
- (_this$pswp = this.pswp) === null || _this$pswp === void 0 || _this$pswp.addFilter(name, fn, priority);
- }
- /**
- * @template {keyof PhotoSwipeFiltersMap} T
- * @param {T} name
- * @param {PhotoSwipeFiltersMap[T]} fn
- */
- removeFilter(name, fn) {
- if (this._filters[name]) {
- // @ts-expect-error
- this._filters[name] = this._filters[name].filter(filter => filter.fn !== fn);
- }
- if (this.pswp) {
- this.pswp.removeFilter(name, fn);
- }
- }
- /**
- * @template {keyof PhotoSwipeFiltersMap} T
- * @param {T} name
- * @param {Parameters<PhotoSwipeFiltersMap[T]>} args
- * @returns {Parameters<PhotoSwipeFiltersMap[T]>[0]}
- */
- applyFilters(name, ...args) {
- var _this$_filters$name3;
- (_this$_filters$name3 = this._filters[name]) === null || _this$_filters$name3 === void 0 || _this$_filters$name3.forEach(filter => {
- // @ts-expect-error
- args[0] = filter.fn.apply(this, args);
- });
- return args[0];
- }
- /**
- * @template {keyof PhotoSwipeEventsMap} T
- * @param {T} name
- * @param {EventCallback<T>} fn
- */
- on(name, fn) {
- var _this$_listeners$name, _this$pswp2;
- if (!this._listeners[name]) {
- this._listeners[name] = [];
- }
- (_this$_listeners$name = this._listeners[name]) === null || _this$_listeners$name === void 0 || _this$_listeners$name.push(fn); // When binding events to lightbox,
- // also bind events to PhotoSwipe Core,
- // if it's open.
- (_this$pswp2 = this.pswp) === null || _this$pswp2 === void 0 || _this$pswp2.on(name, fn);
- }
- /**
- * @template {keyof PhotoSwipeEventsMap} T
- * @param {T} name
- * @param {EventCallback<T>} fn
- */
- off(name, fn) {
- var _this$pswp3;
- if (this._listeners[name]) {
- // @ts-expect-error
- this._listeners[name] = this._listeners[name].filter(listener => fn !== listener);
- }
- (_this$pswp3 = this.pswp) === null || _this$pswp3 === void 0 || _this$pswp3.off(name, fn);
- }
- /**
- * @template {keyof PhotoSwipeEventsMap} T
- * @param {T} name
- * @param {PhotoSwipeEventsMap[T]} [details]
- * @returns {AugmentedEvent<T>}
- */
- dispatch(name, details) {
- var _this$_listeners$name2;
- if (this.pswp) {
- return this.pswp.dispatch(name, details);
- }
- const event =
- /** @type {AugmentedEvent<T>} */
- new PhotoSwipeEvent(name, details);
- (_this$_listeners$name2 = this._listeners[name]) === null || _this$_listeners$name2 === void 0 || _this$_listeners$name2.forEach(listener => {
- listener.call(this, event);
- });
- return event;
- }
- }
- class Placeholder {
- /**
- * @param {string | false} imageSrc
- * @param {HTMLElement} container
- */
- constructor(imageSrc, container) {
- // Create placeholder
- // (stretched thumbnail or simple div behind the main image)
- /** @type {HTMLImageElement | HTMLDivElement | null} */
- this.element = createElement('pswp__img pswp__img--placeholder', imageSrc ? 'img' : 'div', container);
- if (imageSrc) {
- const imgEl =
- /** @type {HTMLImageElement} */
- this.element;
- imgEl.decoding = 'async';
- imgEl.alt = '';
- imgEl.src = imageSrc;
- imgEl.setAttribute('role', 'presentation');
- }
- this.element.setAttribute('aria-hidden', 'true');
- }
- /**
- * @param {number} width
- * @param {number} height
- */
- setDisplayedSize(width, height) {
- if (!this.element) {
- return;
- }
- if (this.element.tagName === 'IMG') {
- // Use transform scale() to modify img placeholder size
- // (instead of changing width/height directly).
- // This helps with performance, specifically in iOS15 Safari.
- setWidthHeight(this.element, 250, 'auto');
- this.element.style.transformOrigin = '0 0';
- this.element.style.transform = toTransformString(0, 0, width / 250);
- } else {
- setWidthHeight(this.element, width, height);
- }
- }
- destroy() {
- var _this$element;
- if ((_this$element = this.element) !== null && _this$element !== void 0 && _this$element.parentNode) {
- this.element.remove();
- }
- this.element = null;
- }
- }
- /** @typedef {import('./slide.js').default} Slide */
- /** @typedef {import('./slide.js').SlideData} SlideData */
- /** @typedef {import('../core/base.js').default} PhotoSwipeBase */
- /** @typedef {import('../util/util.js').LoadState} LoadState */
- class Content {
- /**
- * @param {SlideData} itemData Slide data
- * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance
- * @param {number} index
- */
- constructor(itemData, instance, index) {
- this.instance = instance;
- this.data = itemData;
- this.index = index;
- /** @type {HTMLImageElement | HTMLDivElement | undefined} */
- this.element = undefined;
- /** @type {Placeholder | undefined} */
- this.placeholder = undefined;
- /** @type {Slide | undefined} */
- this.slide = undefined;
- this.displayedImageWidth = 0;
- this.displayedImageHeight = 0;
- this.width = Number(this.data.w) || Number(this.data.width) || 0;
- this.height = Number(this.data.h) || Number(this.data.height) || 0;
- this.isAttached = false;
- this.hasSlide = false;
- this.isDecoding = false;
- /** @type {LoadState} */
- this.state = LOAD_STATE.IDLE;
- if (this.data.type) {
- this.type = this.data.type;
- } else if (this.data.src) {
- this.type = 'image';
- } else {
- this.type = 'html';
- }
- this.instance.dispatch('contentInit', {
- content: this
- });
- }
- removePlaceholder() {
- if (this.placeholder && !this.keepPlaceholder()) {
- // With delay, as image might be loaded, but not rendered
- setTimeout(() => {
- if (this.placeholder) {
- this.placeholder.destroy();
- this.placeholder = undefined;
- }
- }, 1000);
- }
- }
- /**
- * Preload content
- *
- * @param {boolean} isLazy
- * @param {boolean} [reload]
- */
- load(isLazy, reload) {
- if (this.slide && this.usePlaceholder()) {
- if (!this.placeholder) {
- const placeholderSrc = this.instance.applyFilters('placeholderSrc', // use image-based placeholder only for the first slide,
- // as rendering (even small stretched thumbnail) is an expensive operation
- this.data.msrc && this.slide.isFirstSlide ? this.data.msrc : false, this);
- this.placeholder = new Placeholder(placeholderSrc, this.slide.container);
- } else {
- const placeholderEl = this.placeholder.element; // Add placeholder to DOM if it was already created
- if (placeholderEl && !placeholderEl.parentElement) {
- this.slide.container.prepend(placeholderEl);
- }
- }
- }
- if (this.element && !reload) {
- return;
- }
- if (this.instance.dispatch('contentLoad', {
- content: this,
- isLazy
- }).defaultPrevented) {
- return;
- }
- if (this.isImageContent()) {
- this.element = createElement('pswp__img', 'img'); // Start loading only after width is defined, as sizes might depend on it.
- // Due to Safari feature, we must define sizes before srcset.
- if (this.displayedImageWidth) {
- this.loadImage(isLazy);
- }
- } else {
- this.element = createElement('pswp__content', 'div');
- this.element.innerHTML = this.data.html || '';
- }
- if (reload && this.slide) {
- this.slide.updateContentSize(true);
- }
- }
- /**
- * Preload image
- *
- * @param {boolean} isLazy
- */
- loadImage(isLazy) {
- var _this$data$src, _this$data$alt;
- if (!this.isImageContent() || !this.element || this.instance.dispatch('contentLoadImage', {
- content: this,
- isLazy
- }).defaultPrevented) {
- return;
- }
- const imageElement =
- /** @type HTMLImageElement */
- this.element;
- this.updateSrcsetSizes();
- if (this.data.srcset) {
- imageElement.srcset = this.data.srcset;
- }
- imageElement.src = (_this$data$src = this.data.src) !== null && _this$data$src !== void 0 ? _this$data$src : '';
- imageElement.alt = (_this$data$alt = this.data.alt) !== null && _this$data$alt !== void 0 ? _this$data$alt : '';
- this.state = LOAD_STATE.LOADING;
- if (imageElement.complete) {
- this.onLoaded();
- } else {
- imageElement.onload = () => {
- this.onLoaded();
- };
- imageElement.onerror = () => {
- this.onError();
- };
- }
- }
- /**
- * Assign slide to content
- *
- * @param {Slide} slide
- */
- setSlide(slide) {
- this.slide = slide;
- this.hasSlide = true;
- this.instance = slide.pswp; // todo: do we need to unset slide?
- }
- /**
- * Content load success handler
- */
- onLoaded() {
- this.state = LOAD_STATE.LOADED;
- if (this.slide && this.element) {
- this.instance.dispatch('loadComplete', {
- slide: this.slide,
- content: this
- }); // if content is reloaded
- if (this.slide.isActive && this.slide.heavyAppended && !this.element.parentNode) {
- this.append();
- this.slide.updateContentSize(true);
- }
- if (this.state === LOAD_STATE.LOADED || this.state === LOAD_STATE.ERROR) {
- this.removePlaceholder();
- }
- }
- }
- /**
- * Content load error handler
- */
- onError() {
- this.state = LOAD_STATE.ERROR;
- if (this.slide) {
- this.displayError();
- this.instance.dispatch('loadComplete', {
- slide: this.slide,
- isError: true,
- content: this
- });
- this.instance.dispatch('loadError', {
- slide: this.slide,
- content: this
- });
- }
- }
- /**
- * @returns {Boolean} If the content is currently loading
- */
- isLoading() {
- return this.instance.applyFilters('isContentLoading', this.state === LOAD_STATE.LOADING, this);
- }
- /**
- * @returns {Boolean} If the content is in error state
- */
- isError() {
- return this.state === LOAD_STATE.ERROR;
- }
- /**
- * @returns {boolean} If the content is image
- */
- isImageContent() {
- return this.type === 'image';
- }
- /**
- * Update content size
- *
- * @param {Number} width
- * @param {Number} height
- */
- setDisplayedSize(width, height) {
- if (!this.element) {
- return;
- }
- if (this.placeholder) {
- this.placeholder.setDisplayedSize(width, height);
- }
- if (this.instance.dispatch('contentResize', {
- content: this,
- width,
- height
- }).defaultPrevented) {
- return;
- }
- setWidthHeight(this.element, width, height);
- if (this.isImageContent() && !this.isError()) {
- const isInitialSizeUpdate = !this.displayedImageWidth && width;
- this.displayedImageWidth = width;
- this.displayedImageHeight = height;
- if (isInitialSizeUpdate) {
- this.loadImage(false);
- } else {
- this.updateSrcsetSizes();
- }
- if (this.slide) {
- this.instance.dispatch('imageSizeChange', {
- slide: this.slide,
- width,
- height,
- content: this
- });
- }
- }
- }
- /**
- * @returns {boolean} If the content can be zoomed
- */
- isZoomable() {
- return this.instance.applyFilters('isContentZoomable', this.isImageContent() && this.state !== LOAD_STATE.ERROR, this);
- }
- /**
- * Update image srcset sizes attribute based on width and height
- */
- updateSrcsetSizes() {
- // Handle srcset sizes attribute.
- //
- // Never lower quality, if it was increased previously.
- // Chrome does this automatically, Firefox and Safari do not,
- // so we store largest used size in dataset.
- if (!this.isImageContent() || !this.element || !this.data.srcset) {
- return;
- }
- const image =
- /** @type HTMLImageElement */
- this.element;
- const sizesWidth = this.instance.applyFilters('srcsetSizesWidth', this.displayedImageWidth, this);
- if (!image.dataset.largestUsedSize || sizesWidth > parseInt(image.dataset.largestUsedSize, 10)) {
- image.sizes = sizesWidth + 'px';
- image.dataset.largestUsedSize = String(sizesWidth);
- }
- }
- /**
- * @returns {boolean} If content should use a placeholder (from msrc by default)
- */
- usePlaceholder() {
- return this.instance.applyFilters('useContentPlaceholder', this.isImageContent(), this);
- }
- /**
- * Preload content with lazy-loading param
- */
- lazyLoad() {
- if (this.instance.dispatch('contentLazyLoad', {
- content: this
- }).defaultPrevented) {
- return;
- }
- this.load(true);
- }
- /**
- * @returns {boolean} If placeholder should be kept after content is loaded
- */
- keepPlaceholder() {
- return this.instance.applyFilters('isKeepingPlaceholder', this.isLoading(), this);
- }
- /**
- * Destroy the content
- */
- destroy() {
- this.hasSlide = false;
- this.slide = undefined;
- if (this.instance.dispatch('contentDestroy', {
- content: this
- }).defaultPrevented) {
- return;
- }
- this.remove();
- if (this.placeholder) {
- this.placeholder.destroy();
- this.placeholder = undefined;
- }
- if (this.isImageContent() && this.element) {
- this.element.onload = null;
- this.element.onerror = null;
- this.element = undefined;
- }
- }
- /**
- * Display error message
- */
- displayError() {
- if (this.slide) {
- var _this$instance$option, _this$instance$option2;
- let errorMsgEl = createElement('pswp__error-msg', 'div');
- 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 : '';
- errorMsgEl =
- /** @type {HTMLDivElement} */
- this.instance.applyFilters('contentErrorElement', errorMsgEl, this);
- this.element = createElement('pswp__content pswp__error-msg-container', 'div');
- this.element.appendChild(errorMsgEl);
- this.slide.container.innerText = '';
- this.slide.container.appendChild(this.element);
- this.slide.updateContentSize(true);
- this.removePlaceholder();
- }
- }
- /**
- * Append the content
- */
- append() {
- if (this.isAttached || !this.element) {
- return;
- }
- this.isAttached = true;
- if (this.state === LOAD_STATE.ERROR) {
- this.displayError();
- return;
- }
- if (this.instance.dispatch('contentAppend', {
- content: this
- }).defaultPrevented) {
- return;
- }
- const supportsDecode = ('decode' in this.element);
- if (this.isImageContent()) {
- // Use decode() on nearby slides
- //
- // Nearby slide images are in DOM and not hidden via display:none.
- // However, they are placed offscreen (to the left and right side).
- //
- // Some browsers do not composite the image until it's actually visible,
- // using decode() helps.
- //
- // You might ask "why dont you just decode() and then append all images",
- // that's because I want to show image before it's fully loaded,
- // as browser can render parts of image while it is loading.
- // We do not do this in Safari due to partial loading bug.
- if (supportsDecode && this.slide && (!this.slide.isActive || isSafari())) {
- this.isDecoding = true; // purposefully using finally instead of then,
- // as if srcset sizes changes dynamically - it may cause decode error
- /** @type {HTMLImageElement} */
- this.element.decode().catch(() => {}).finally(() => {
- this.isDecoding = false;
- this.appendImage();
- });
- } else {
- this.appendImage();
- }
- } else if (this.slide && !this.element.parentNode) {
- this.slide.container.appendChild(this.element);
- }
- }
- /**
- * Activate the slide,
- * active slide is generally the current one,
- * meaning the user can see it.
- */
- activate() {
- if (this.instance.dispatch('contentActivate', {
- content: this
- }).defaultPrevented || !this.slide) {
- return;
- }
- if (this.isImageContent() && this.isDecoding && !isSafari()) {
- // add image to slide when it becomes active,
- // even if it's not finished decoding
- this.appendImage();
- } else if (this.isError()) {
- this.load(false, true); // try to reload
- }
- if (this.slide.holderElement) {
- this.slide.holderElement.setAttribute('aria-hidden', 'false');
- }
- }
- /**
- * Deactivate the content
- */
- deactivate() {
- this.instance.dispatch('contentDeactivate', {
- content: this
- });
- if (this.slide && this.slide.holderElement) {
- this.slide.holderElement.setAttribute('aria-hidden', 'true');
- }
- }
- /**
- * Remove the content from DOM
- */
- remove() {
- this.isAttached = false;
- if (this.instance.dispatch('contentRemove', {
- content: this
- }).defaultPrevented) {
- return;
- }
- if (this.element && this.element.parentNode) {
- this.element.remove();
- }
- if (this.placeholder && this.placeholder.element) {
- this.placeholder.element.remove();
- }
- }
- /**
- * Append the image content to slide container
- */
- appendImage() {
- if (!this.isAttached) {
- return;
- }
- if (this.instance.dispatch('contentAppendImage', {
- content: this
- }).defaultPrevented) {
- return;
- } // ensure that element exists and is not already appended
- if (this.slide && this.element && !this.element.parentNode) {
- this.slide.container.appendChild(this.element);
- }
- if (this.state === LOAD_STATE.LOADED || this.state === LOAD_STATE.ERROR) {
- this.removePlaceholder();
- }
- }
- }
- /** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */
- /** @typedef {import('../core/base.js').default} PhotoSwipeBase */
- /** @typedef {import('../photoswipe.js').Point} Point */
- /** @typedef {import('../slide/slide.js').SlideData} SlideData */
- /**
- * @param {PhotoSwipeOptions} options
- * @param {PhotoSwipeBase} pswp
- * @returns {Point}
- */
- function getViewportSize(options, pswp) {
- if (options.getViewportSizeFn) {
- const newViewportSize = options.getViewportSizeFn(options, pswp);
- if (newViewportSize) {
- return newViewportSize;
- }
- }
- return {
- x: document.documentElement.clientWidth,
- // TODO: height on mobile is very incosistent due to toolbar
- // find a way to improve this
- //
- // document.documentElement.clientHeight - doesn't seem to work well
- y: window.innerHeight
- };
- }
- /**
- * Parses padding option.
- * Supported formats:
- *
- * // Object
- * padding: {
- * top: 0,
- * bottom: 0,
- * left: 0,
- * right: 0
- * }
- *
- * // A function that returns the object
- * paddingFn: (viewportSize, itemData, index) => {
- * return {
- * top: 0,
- * bottom: 0,
- * left: 0,
- * right: 0
- * };
- * }
- *
- * // Legacy variant
- * paddingLeft: 0,
- * paddingRight: 0,
- * paddingTop: 0,
- * paddingBottom: 0,
- *
- * @param {'left' | 'top' | 'bottom' | 'right'} prop
- * @param {PhotoSwipeOptions} options PhotoSwipe options
- * @param {Point} viewportSize PhotoSwipe viewport size, for example: { x:800, y:600 }
- * @param {SlideData} itemData Data about the slide
- * @param {number} index Slide index
- * @returns {number}
- */
- function parsePaddingOption(prop, options, viewportSize, itemData, index) {
- let paddingValue = 0;
- if (options.paddingFn) {
- paddingValue = options.paddingFn(viewportSize, itemData, index)[prop];
- } else if (options.padding) {
- paddingValue = options.padding[prop];
- } else {
- const legacyPropName = 'padding' + prop[0].toUpperCase() + prop.slice(1); // @ts-expect-error
- if (options[legacyPropName]) {
- // @ts-expect-error
- paddingValue = options[legacyPropName];
- }
- }
- return Number(paddingValue) || 0;
- }
- /**
- * @param {PhotoSwipeOptions} options
- * @param {Point} viewportSize
- * @param {SlideData} itemData
- * @param {number} index
- * @returns {Point}
- */
- function getPanAreaSize(options, viewportSize, itemData, index) {
- return {
- x: viewportSize.x - parsePaddingOption('left', options, viewportSize, itemData, index) - parsePaddingOption('right', options, viewportSize, itemData, index),
- y: viewportSize.y - parsePaddingOption('top', options, viewportSize, itemData, index) - parsePaddingOption('bottom', options, viewportSize, itemData, index)
- };
- }
- const MAX_IMAGE_WIDTH = 4000;
- /** @typedef {import('../photoswipe.js').default} PhotoSwipe */
- /** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */
- /** @typedef {import('../photoswipe.js').Point} Point */
- /** @typedef {import('../slide/slide.js').SlideData} SlideData */
- /** @typedef {'fit' | 'fill' | number | ((zoomLevelObject: ZoomLevel) => number)} ZoomLevelOption */
- /**
- * Calculates zoom levels for specific slide.
- * Depends on viewport size and image size.
- */
- class ZoomLevel {
- /**
- * @param {PhotoSwipeOptions} options PhotoSwipe options
- * @param {SlideData} itemData Slide data
- * @param {number} index Slide index
- * @param {PhotoSwipe} [pswp] PhotoSwipe instance, can be undefined if not initialized yet
- */
- constructor(options, itemData, index, pswp) {
- this.pswp = pswp;
- this.options = options;
- this.itemData = itemData;
- this.index = index;
- /** @type { Point | null } */
- this.panAreaSize = null;
- /** @type { Point | null } */
- this.elementSize = null;
- this.fit = 1;
- this.fill = 1;
- this.vFill = 1;
- this.initial = 1;
- this.secondary = 1;
- this.max = 1;
- this.min = 1;
- }
- /**
- * Calculate initial, secondary and maximum zoom level for the specified slide.
- *
- * It should be called when either image or viewport size changes.
- *
- * @param {number} maxWidth
- * @param {number} maxHeight
- * @param {Point} panAreaSize
- */
- update(maxWidth, maxHeight, panAreaSize) {
- /** @type {Point} */
- const elementSize = {
- x: maxWidth,
- y: maxHeight
- };
- this.elementSize = elementSize;
- this.panAreaSize = panAreaSize;
- const hRatio = panAreaSize.x / elementSize.x;
- const vRatio = panAreaSize.y / elementSize.y;
- this.fit = Math.min(1, hRatio < vRatio ? hRatio : vRatio);
- this.fill = Math.min(1, hRatio > vRatio ? hRatio : vRatio); // zoom.vFill defines zoom level of the image
- // when it has 100% of viewport vertical space (height)
- this.vFill = Math.min(1, vRatio);
- this.initial = this._getInitial();
- this.secondary = this._getSecondary();
- this.max = Math.max(this.initial, this.secondary, this._getMax());
- this.min = Math.min(this.fit, this.initial, this.secondary);
- if (this.pswp) {
- this.pswp.dispatch('zoomLevelsUpdate', {
- zoomLevels: this,
- slideData: this.itemData
- });
- }
- }
- /**
- * Parses user-defined zoom option.
- *
- * @private
- * @param {'initial' | 'secondary' | 'max'} optionPrefix Zoom level option prefix (initial, secondary, max)
- * @returns { number | undefined }
- */
- _parseZoomLevelOption(optionPrefix) {
- const optionName =
- /** @type {'initialZoomLevel' | 'secondaryZoomLevel' | 'maxZoomLevel'} */
- optionPrefix + 'ZoomLevel';
- const optionValue = this.options[optionName];
- if (!optionValue) {
- return;
- }
- if (typeof optionValue === 'function') {
- return optionValue(this);
- }
- if (optionValue === 'fill') {
- return this.fill;
- }
- if (optionValue === 'fit') {
- return this.fit;
- }
- return Number(optionValue);
- }
- /**
- * Get zoom level to which image will be zoomed after double-tap gesture,
- * or when user clicks on zoom icon,
- * or mouse-click on image itself.
- * If you return 1 image will be zoomed to its original size.
- *
- * @private
- * @return {number}
- */
- _getSecondary() {
- let currZoomLevel = this._parseZoomLevelOption('secondary');
- if (currZoomLevel) {
- return currZoomLevel;
- } // 3x of "fit" state, but not larger than original
- currZoomLevel = Math.min(1, this.fit * 3);
- if (this.elementSize && currZoomLevel * this.elementSize.x > MAX_IMAGE_WIDTH) {
- currZoomLevel = MAX_IMAGE_WIDTH / this.elementSize.x;
- }
- return currZoomLevel;
- }
- /**
- * Get initial image zoom level.
- *
- * @private
- * @return {number}
- */
- _getInitial() {
- return this._parseZoomLevelOption('initial') || this.fit;
- }
- /**
- * Maximum zoom level when user zooms
- * via zoom/pinch gesture,
- * via cmd/ctrl-wheel or via trackpad.
- *
- * @private
- * @return {number}
- */
- _getMax() {
- // max zoom level is x4 from "fit state",
- // used for zoom gesture and ctrl/trackpad zoom
- return this._parseZoomLevelOption('max') || Math.max(1, this.fit * 4);
- }
- }
- /**
- * Lazy-load an image
- * This function is used both by Lightbox and PhotoSwipe core,
- * thus it can be called before dialog is opened.
- *
- * @param {SlideData} itemData Data about the slide
- * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance
- * @param {number} index
- * @returns {Content} Image that is being decoded or false.
- */
- function lazyLoadData(itemData, instance, index) {
- const content = instance.createContentFromData(itemData, index);
- /** @type {ZoomLevel | undefined} */
- let zoomLevel;
- const {
- options
- } = instance; // We need to know dimensions of the image to preload it,
- // as it might use srcset, and we need to define sizes
- if (options) {
- zoomLevel = new ZoomLevel(options, itemData, -1);
- let viewportSize;
- if (instance.pswp) {
- viewportSize = instance.pswp.viewportSize;
- } else {
- viewportSize = getViewportSize(options, instance);
- }
- const panAreaSize = getPanAreaSize(options, viewportSize, itemData, index);
- zoomLevel.update(content.width, content.height, panAreaSize);
- }
- content.lazyLoad();
- if (zoomLevel) {
- content.setDisplayedSize(Math.ceil(content.width * zoomLevel.initial), Math.ceil(content.height * zoomLevel.initial));
- }
- return content;
- }
- /**
- * Lazy-loads specific slide.
- * This function is used both by Lightbox and PhotoSwipe core,
- * thus it can be called before dialog is opened.
- *
- * By default, it loads image based on viewport size and initial zoom level.
- *
- * @param {number} index Slide index
- * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox eventable instance
- * @returns {Content | undefined}
- */
- function lazyLoadSlide(index, instance) {
- const itemData = instance.getItemData(index);
- if (instance.dispatch('lazyLoadSlide', {
- index,
- itemData
- }).defaultPrevented) {
- return;
- }
- return lazyLoadData(itemData, instance, index);
- }
- /** @typedef {import("../photoswipe.js").default} PhotoSwipe */
- /** @typedef {import("../slide/slide.js").SlideData} SlideData */
- /**
- * PhotoSwipe base class that can retrieve data about every slide.
- * Shared by PhotoSwipe Core and PhotoSwipe Lightbox
- */
- class PhotoSwipeBase extends Eventable {
- /**
- * Get total number of slides
- *
- * @returns {number}
- */
- getNumItems() {
- var _this$options;
- let numItems = 0;
- const dataSource = (_this$options = this.options) === null || _this$options === void 0 ? void 0 : _this$options.dataSource;
- if (dataSource && 'length' in dataSource) {
- // may be an array or just object with length property
- numItems = dataSource.length;
- } else if (dataSource && 'gallery' in dataSource) {
- // query DOM elements
- if (!dataSource.items) {
- dataSource.items = this._getGalleryDOMElements(dataSource.gallery);
- }
- if (dataSource.items) {
- numItems = dataSource.items.length;
- }
- } // legacy event, before filters were introduced
- const event = this.dispatch('numItems', {
- dataSource,
- numItems
- });
- return this.applyFilters('numItems', event.numItems, dataSource);
- }
- /**
- * @param {SlideData} slideData
- * @param {number} index
- * @returns {Content}
- */
- createContentFromData(slideData, index) {
- return new Content(slideData, this, index);
- }
- /**
- * Get item data by index.
- *
- * "item data" should contain normalized information that PhotoSwipe needs to generate a slide.
- * For example, it may contain properties like
- * `src`, `srcset`, `w`, `h`, which will be used to generate a slide with image.
- *
- * @param {number} index
- * @returns {SlideData}
- */
- getItemData(index) {
- var _this$options2;
- const dataSource = (_this$options2 = this.options) === null || _this$options2 === void 0 ? void 0 : _this$options2.dataSource;
- /** @type {SlideData | HTMLElement} */
- let dataSourceItem = {};
- if (Array.isArray(dataSource)) {
- // Datasource is an array of elements
- dataSourceItem = dataSource[index];
- } else if (dataSource && 'gallery' in dataSource) {
- // dataSource has gallery property,
- // thus it was created by Lightbox, based on
- // gallery and children options
- // query DOM elements
- if (!dataSource.items) {
- dataSource.items = this._getGalleryDOMElements(dataSource.gallery);
- }
- dataSourceItem = dataSource.items[index];
- }
- let itemData = dataSourceItem;
- if (itemData instanceof Element) {
- itemData = this._domElementToItemData(itemData);
- } // Dispatching the itemData event,
- // it's a legacy verion before filters were introduced
- const event = this.dispatch('itemData', {
- itemData: itemData || {},
- index
- });
- return this.applyFilters('itemData', event.itemData, index);
- }
- /**
- * Get array of gallery DOM elements,
- * based on childSelector and gallery element.
- *
- * @param {HTMLElement} galleryElement
- * @returns {HTMLElement[]}
- */
- _getGalleryDOMElements(galleryElement) {
- var _this$options3, _this$options4;
- 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) {
- return getElementsFromOption(this.options.children, this.options.childSelector, galleryElement) || [];
- }
- return [galleryElement];
- }
- /**
- * Converts DOM element to item data object.
- *
- * @param {HTMLElement} element DOM element
- * @returns {SlideData}
- */
- _domElementToItemData(element) {
- /** @type {SlideData} */
- const itemData = {
- element
- };
- const linkEl =
- /** @type {HTMLAnchorElement} */
- element.tagName === 'A' ? element : element.querySelector('a');
- if (linkEl) {
- // src comes from data-pswp-src attribute,
- // if it's empty link href is used
- itemData.src = linkEl.dataset.pswpSrc || linkEl.href;
- if (linkEl.dataset.pswpSrcset) {
- itemData.srcset = linkEl.dataset.pswpSrcset;
- }
- itemData.width = linkEl.dataset.pswpWidth ? parseInt(linkEl.dataset.pswpWidth, 10) : 0;
- itemData.height = linkEl.dataset.pswpHeight ? parseInt(linkEl.dataset.pswpHeight, 10) : 0; // support legacy w & h properties
- itemData.w = itemData.width;
- itemData.h = itemData.height;
- if (linkEl.dataset.pswpType) {
- itemData.type = linkEl.dataset.pswpType;
- }
- const thumbnailEl = element.querySelector('img');
- if (thumbnailEl) {
- var _thumbnailEl$getAttri;
- // msrc is URL to placeholder image that's displayed before large image is loaded
- // by default it's displayed only for the first slide
- itemData.msrc = thumbnailEl.currentSrc || thumbnailEl.src;
- itemData.alt = (_thumbnailEl$getAttri = thumbnailEl.getAttribute('alt')) !== null && _thumbnailEl$getAttri !== void 0 ? _thumbnailEl$getAttri : '';
- }
- if (linkEl.dataset.pswpCropped || linkEl.dataset.cropped) {
- itemData.thumbCropped = true;
- }
- }
- return this.applyFilters('domItemData', itemData, element, linkEl);
- }
- /**
- * Lazy-load by slide data
- *
- * @param {SlideData} itemData Data about the slide
- * @param {number} index
- * @returns {Content} Image that is being decoded or false.
- */
- lazyLoadData(itemData, index) {
- return lazyLoadData(itemData, this, index);
- }
- }
- /**
- * @template T
- * @typedef {import('../types.js').Type<T>} Type<T>
- */
- /** @typedef {import('../photoswipe.js').default} PhotoSwipe */
- /** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */
- /** @typedef {import('../photoswipe.js').DataSource} DataSource */
- /** @typedef {import('../photoswipe.js').Point} Point */
- /** @typedef {import('../slide/content.js').default} Content */
- /** @typedef {import('../core/eventable.js').PhotoSwipeEventsMap} PhotoSwipeEventsMap */
- /** @typedef {import('../core/eventable.js').PhotoSwipeFiltersMap} PhotoSwipeFiltersMap */
- /**
- * @template {keyof PhotoSwipeEventsMap} T
- * @typedef {import('../core/eventable.js').EventCallback<T>} EventCallback<T>
- */
- /**
- * PhotoSwipe Lightbox
- *
- * - If user has unsupported browser it falls back to default browser action (just opens URL)
- * - Binds click event to links that should open PhotoSwipe
- * - parses DOM strcture for PhotoSwipe (retrieves large image URLs and sizes)
- * - Initializes PhotoSwipe
- *
- *
- * Loader options use the same object as PhotoSwipe, and supports such options:
- *
- * gallery - Element | Element[] | NodeList | string selector for the gallery element
- * children - Element | Element[] | NodeList | string selector for the gallery children
- *
- */
- class PhotoSwipeLightbox extends PhotoSwipeBase {
- /**
- * @param {PhotoSwipeOptions} [options]
- */
- constructor(options) {
- super();
- /** @type {PhotoSwipeOptions} */
- this.options = options || {};
- this._uid = 0;
- this.shouldOpen = false;
- /**
- * @private
- * @type {Content | undefined}
- */
- this._preloadedContent = undefined;
- this.onThumbnailsClick = this.onThumbnailsClick.bind(this);
- }
- /**
- * Initialize lightbox, should be called only once.
- * It's not included in the main constructor, so you may bind events before it.
- */
- init() {
- // Bind click events to each gallery
- getElementsFromOption(this.options.gallery, this.options.gallerySelector).forEach(galleryElement => {
- galleryElement.addEventListener('click', this.onThumbnailsClick, false);
- });
- }
- /**
- * @param {MouseEvent} e
- */
- onThumbnailsClick(e) {
- // Exit and allow default browser action if:
- if (specialKeyUsed(e) // ... if clicked with a special key (ctrl/cmd...)
- || window.pswp) {
- // ... if PhotoSwipe is already open
- return;
- } // If both clientX and clientY are 0 or not defined,
- // the event is likely triggered by keyboard,
- // so we do not pass the initialPoint
- //
- // Note that some screen readers emulate the mouse position,
- // so it's not the ideal way to detect them.
- //
- /** @type {Point | null} */
- let initialPoint = {
- x: e.clientX,
- y: e.clientY
- };
- if (!initialPoint.x && !initialPoint.y) {
- initialPoint = null;
- }
- let clickedIndex = this.getClickedIndex(e);
- clickedIndex = this.applyFilters('clickedIndex', clickedIndex, e, this);
- /** @type {DataSource} */
- const dataSource = {
- gallery:
- /** @type {HTMLElement} */
- e.currentTarget
- };
- if (clickedIndex >= 0) {
- e.preventDefault();
- this.loadAndOpen(clickedIndex, dataSource, initialPoint);
- }
- }
- /**
- * Get index of gallery item that was clicked.
- *
- * @param {MouseEvent} e click event
- * @returns {number}
- */
- getClickedIndex(e) {
- // legacy option
- if (this.options.getClickedIndexFn) {
- return this.options.getClickedIndexFn.call(this, e);
- }
- const clickedTarget =
- /** @type {HTMLElement} */
- e.target;
- const childElements = getElementsFromOption(this.options.children, this.options.childSelector,
- /** @type {HTMLElement} */
- e.currentTarget);
- const clickedChildIndex = childElements.findIndex(child => child === clickedTarget || child.contains(clickedTarget));
- if (clickedChildIndex !== -1) {
- return clickedChildIndex;
- } else if (this.options.children || this.options.childSelector) {
- // click wasn't on a child element
- return -1;
- } // There is only one item (which is the gallery)
- return 0;
- }
- /**
- * Load and open PhotoSwipe
- *
- * @param {number} index
- * @param {DataSource} [dataSource]
- * @param {Point | null} [initialPoint]
- * @returns {boolean}
- */
- loadAndOpen(index, dataSource, initialPoint) {
- // Check if the gallery is already open
- if (window.pswp || !this.options) {
- return false;
- } // Use the first gallery element if dataSource is not provided
- if (!dataSource && this.options.gallery && this.options.children) {
- const galleryElements = getElementsFromOption(this.options.gallery);
- if (galleryElements[0]) {
- dataSource = {
- gallery: galleryElements[0]
- };
- }
- } // set initial index
- this.options.index = index; // define options for PhotoSwipe constructor
- this.options.initialPointerPos = initialPoint;
- this.shouldOpen = true;
- this.preload(index, dataSource);
- return true;
- }
- /**
- * Load the main module and the slide content by index
- *
- * @param {number} index
- * @param {DataSource} [dataSource]
- */
- preload(index, dataSource) {
- const {
- options
- } = this;
- if (dataSource) {
- options.dataSource = dataSource;
- } // Add the main module
- /** @type {Promise<Type<PhotoSwipe>>[]} */
- const promiseArray = [];
- const pswpModuleType = typeof options.pswpModule;
- if (isPswpClass(options.pswpModule)) {
- promiseArray.push(Promise.resolve(
- /** @type {Type<PhotoSwipe>} */
- options.pswpModule));
- } else if (pswpModuleType === 'string') {
- throw new Error('pswpModule as string is no longer supported');
- } else if (pswpModuleType === 'function') {
- promiseArray.push(
- /** @type {() => Promise<Type<PhotoSwipe>>} */
- options.pswpModule());
- } else {
- throw new Error('pswpModule is not valid');
- } // Add custom-defined promise, if any
- if (typeof options.openPromise === 'function') {
- // allow developers to perform some task before opening
- promiseArray.push(options.openPromise());
- }
- if (options.preloadFirstSlide !== false && index >= 0) {
- this._preloadedContent = lazyLoadSlide(index, this);
- } // Wait till all promises resolve and open PhotoSwipe
- const uid = ++this._uid;
- Promise.all(promiseArray).then(iterableModules => {
- if (this.shouldOpen) {
- const mainModule = iterableModules[0];
- this._openPhotoswipe(mainModule, uid);
- }
- });
- }
- /**
- * @private
- * @param {Type<PhotoSwipe> | { default: Type<PhotoSwipe> }} module
- * @param {number} uid
- */
- _openPhotoswipe(module, uid) {
- // Cancel opening if UID doesn't match the current one
- // (if user clicked on another gallery item before current was loaded).
- //
- // Or if shouldOpen flag is set to false
- // (developer may modify it via public API)
- if (uid !== this._uid && this.shouldOpen) {
- return;
- }
- this.shouldOpen = false; // PhotoSwipe is already open
- if (window.pswp) {
- return;
- }
- /**
- * Pass data to PhotoSwipe and open init
- *
- * @type {PhotoSwipe}
- */
- const pswp = typeof module === 'object' ? new module.default(this.options) // eslint-disable-line
- : new module(this.options); // eslint-disable-line
- this.pswp = pswp;
- window.pswp = pswp; // map listeners from Lightbox to PhotoSwipe Core
- /** @type {(keyof PhotoSwipeEventsMap)[]} */
- Object.keys(this._listeners).forEach(name => {
- var _this$_listeners$name;
- (_this$_listeners$name = this._listeners[name]) === null || _this$_listeners$name === void 0 || _this$_listeners$name.forEach(fn => {
- pswp.on(name,
- /** @type {EventCallback<typeof name>} */
- fn);
- });
- }); // same with filters
- /** @type {(keyof PhotoSwipeFiltersMap)[]} */
- Object.keys(this._filters).forEach(name => {
- var _this$_filters$name;
- (_this$_filters$name = this._filters[name]) === null || _this$_filters$name === void 0 || _this$_filters$name.forEach(filter => {
- pswp.addFilter(name, filter.fn, filter.priority);
- });
- });
- if (this._preloadedContent) {
- pswp.contentLoader.addToCache(this._preloadedContent);
- this._preloadedContent = undefined;
- }
- pswp.on('destroy', () => {
- // clean up public variables
- this.pswp = undefined;
- delete window.pswp;
- });
- pswp.init();
- }
- /**
- * Unbinds all events, closes PhotoSwipe if it's open.
- */
- destroy() {
- var _this$pswp;
- (_this$pswp = this.pswp) === null || _this$pswp === void 0 || _this$pswp.destroy();
- this.shouldOpen = false;
- this._listeners = {};
- getElementsFromOption(this.options.gallery, this.options.gallerySelector).forEach(galleryElement => {
- galleryElement.removeEventListener('click', this.onThumbnailsClick, false);
- });
- }
- }
- export { PhotoSwipeLightbox as default };
- //# sourceMappingURL=photoswipe-lightbox.esm.js.map
|