From 3e94d9addfc390d0a53e796c5c4e87334d90d42d Mon Sep 17 00:00:00 2001 From: mishchuk Date: Wed, 21 Dec 2022 14:02:48 +1000 Subject: [PATCH 01/27] fix core/eventable types --- dist/types/core/eventable.d.ts | 14 ++++---------- src/js/core/eventable.js | 13 +++++-------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/dist/types/core/eventable.d.ts b/dist/types/core/eventable.d.ts index 979e26df..a9c5c733 100644 --- a/dist/types/core/eventable.d.ts +++ b/dist/types/core/eventable.d.ts @@ -368,17 +368,11 @@ export type PhotoSwipeFiltersMap = { thumbBounds: (thumbBounds: Bounds, itemData: SlideData, index: number) => Bounds; srcsetSizesWidth: (srcsetSizesWidth: number, content: Content) => number; }; -/** - * - */ export type Filter = { fn: PhotoSwipeFiltersMap[T]; priority: number; }; export type AugmentedEvent = PhotoSwipeEventsMap[T] extends undefined ? PhotoSwipeEvent : PhotoSwipeEvent & PhotoSwipeEventsMap[T]; -/** - * - */ export type EventCallback = (event: AugmentedEvent) => void; /** * PhotoSwipe base class that can listen and dispatch for events. @@ -732,7 +726,7 @@ declare class Eventable { thumbBounds?: Filter<"thumbBounds">[]; srcsetSizesWidth?: Filter<"srcsetSizesWidth">[]; }; - /** @type {PhotoSwipe=} */ + /** @type {PhotoSwipe | undefined} */ pswp: PhotoSwipe | undefined; /** @type {PhotoSwipeOptions} */ options: PhotoSwipeOptions; @@ -960,7 +954,7 @@ declare class Eventable { */ /** * @template {keyof PhotoSwipeFiltersMap} T - * @typedef {{ fn: PhotoSwipeFiltersMap[T], priority: number }} Filter + * @typedef {{ fn: PhotoSwipeFiltersMap[T], priority: number }} Filter */ /** * @template {keyof PhotoSwipeEventsMap} T @@ -968,7 +962,7 @@ declare class Eventable { */ /** * @template {keyof PhotoSwipeEventsMap} T - * @typedef {(event: AugmentedEvent) => void} EventCallback + * @typedef {(event: AugmentedEvent) => void} EventCallback */ /** * Base PhotoSwipe event object @@ -982,6 +976,6 @@ declare class PhotoSwipeEvent { */ constructor(type: T, details?: PhotoSwipeEventsMap[T]); type: T; - preventDefault(): void; defaultPrevented: boolean; + preventDefault(): void; } diff --git a/src/js/core/eventable.js b/src/js/core/eventable.js index ccd34457..4f07a95f 100644 --- a/src/js/core/eventable.js +++ b/src/js/core/eventable.js @@ -186,7 +186,7 @@ /** * @template {keyof PhotoSwipeFiltersMap} T - * @typedef {{ fn: PhotoSwipeFiltersMap[T], priority: number }} Filter + * @typedef {{ fn: PhotoSwipeFiltersMap[T], priority: number }} Filter */ /** @@ -196,7 +196,7 @@ /** * @template {keyof PhotoSwipeEventsMap} T - * @typedef {(event: AugmentedEvent) => void} EventCallback + * @typedef {(event: AugmentedEvent) => void} EventCallback */ /** @@ -211,6 +211,7 @@ class PhotoSwipeEvent { */ constructor(type, details) { this.type = type; + this.defaultPrevented = false; if (details) { Object.assign(this, details); } @@ -237,11 +238,11 @@ class Eventable { */ this._filters = {}; - /** @type {PhotoSwipe=} */ + /** @type {PhotoSwipe | undefined} */ this.pswp = undefined; /** @type {PhotoSwipeOptions} */ - this.options = undefined; + this.options = {}; } /** @@ -343,10 +344,6 @@ class Eventable { const event = /** @type {AugmentedEvent} */ (new PhotoSwipeEvent(name, details)); - if (!this._listeners) { - return event; - } - if (this._listeners[name]) { this._listeners[name].forEach((listener) => { listener.call(this, event); From 0c775333573d6a6327984c8ed9b9d256c0f6abdf Mon Sep 17 00:00:00 2001 From: mishchuk Date: Thu, 22 Dec 2022 13:37:50 +1000 Subject: [PATCH 02/27] fix core/base types --- dist/types/core/base.d.ts | 8 ++++++-- src/js/core/base.js | 16 ++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/dist/types/core/base.d.ts b/dist/types/core/base.d.ts index 26a52160..708e0182 100644 --- a/dist/types/core/base.d.ts +++ b/dist/types/core/base.d.ts @@ -19,6 +19,7 @@ declare class PhotoSwipeBase extends Eventable { /** * @param {SlideData} slideData * @param {number} index + * @returns {Content} */ createContentFromData(slideData: SlideData, index: number): Content; /** @@ -29,21 +30,24 @@ declare class PhotoSwipeBase extends Eventable { * `src`, `srcset`, `w`, `h`, which will be used to generate a slide with image. * * @param {number} index + * @returns {SlideData} */ - getItemData(index: number): import("../slide/slide.js").SlideData; + getItemData(index: number): SlideData; /** * Get array of gallery DOM elements, * based on childSelector and gallery element. * * @param {HTMLElement} galleryElement + * @returns {HTMLElement[]} */ _getGalleryDOMElements(galleryElement: HTMLElement): HTMLElement[]; /** * Converts DOM element to item data object. * * @param {HTMLElement} element DOM element + * @returns {SlideData} */ - _domElementToItemData(element: HTMLElement): import("../slide/slide.js").SlideData; + _domElementToItemData(element: HTMLElement): SlideData; /** * Lazy-load by slide data * diff --git a/src/js/core/base.js b/src/js/core/base.js index 54d1a7f0..867cac7c 100644 --- a/src/js/core/base.js +++ b/src/js/core/base.js @@ -1,7 +1,5 @@ import Eventable from './eventable.js'; -import { - getElementsFromOption -} from '../util/util.js'; +import { getElementsFromOption } from '../util/util.js'; import Content from '../slide/content.js'; import { lazyLoadData } from '../slide/loader.js'; @@ -49,6 +47,7 @@ class PhotoSwipeBase extends Eventable { /** * @param {SlideData} slideData * @param {number} index + * @returns {Content} */ createContentFromData(slideData, index) { // @ts-expect-error @@ -63,6 +62,7 @@ class PhotoSwipeBase extends Eventable { * `src`, `srcset`, `w`, `h`, which will be used to generate a slide with image. * * @param {number} index + * @returns {SlideData} */ getItemData(index) { const { dataSource } = this.options; @@ -104,6 +104,7 @@ class PhotoSwipeBase extends Eventable { * based on childSelector and gallery element. * * @param {HTMLElement} galleryElement + * @returns {HTMLElement[]} */ _getGalleryDOMElements(galleryElement) { if (this.options.children || this.options.childSelector) { @@ -121,16 +122,19 @@ class PhotoSwipeBase extends Eventable { * Converts DOM element to item data object. * * @param {HTMLElement} element DOM element + * @returns {SlideData} */ - // eslint-disable-next-line class-methods-use-this _domElementToItemData(element) { /** @type {SlideData} */ const itemData = { element }; - // eslint-disable-next-line max-len - const linkEl = /** @type {HTMLAnchorElement} */ (element.tagName === 'A' ? element : element.querySelector('a')); + const linkEl = /** @type {HTMLAnchorElement} */ ( + element.tagName === 'A' + ? element + : element.querySelector('a') + ); if (linkEl) { // src comes from data-pswp-src attribute, From 50bd95b8a3f0b021ad33d3ab6dbe3723dcac7e09 Mon Sep 17 00:00:00 2001 From: mishchuk Date: Thu, 22 Dec 2022 13:56:41 +1000 Subject: [PATCH 03/27] fix slide/loader types --- dist/types/core/base.d.ts | 2 +- dist/types/slide/loader.d.ts | 28 +++++++++++--------- src/js/core/base.js | 2 +- src/js/slide/loader.js | 51 ++++++++++++++++-------------------- 4 files changed, 39 insertions(+), 44 deletions(-) diff --git a/dist/types/core/base.d.ts b/dist/types/core/base.d.ts index 708e0182..466b9e10 100644 --- a/dist/types/core/base.d.ts +++ b/dist/types/core/base.d.ts @@ -53,7 +53,7 @@ declare class PhotoSwipeBase extends Eventable { * * @param {SlideData} itemData Data about the slide * @param {number} index - * @returns Image that is being decoded or false. + * @returns {Content} Image that is being decoded or false. */ lazyLoadData(itemData: SlideData, index: number): Content; } diff --git a/dist/types/slide/loader.d.ts b/dist/types/slide/loader.d.ts index 426374dd..bddf9049 100644 --- a/dist/types/slide/loader.d.ts +++ b/dist/types/slide/loader.d.ts @@ -4,29 +4,29 @@ * thus it can be called before dialog is opened. * * @param {SlideData} itemData Data about the slide - * @param {PhotoSwipe | PhotoSwipeLightbox | PhotoSwipeBase} instance PhotoSwipe instance + * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance * @param {number} index - * @returns Image that is being decoded or false. + * @returns {Content} Image that is being decoded or false. */ -export function lazyLoadData(itemData: SlideData, instance: PhotoSwipe | PhotoSwipeLightbox | PhotoSwipeBase, index: number): import("./content.js").default; +export function lazyLoadData(itemData: SlideData, instance: PhotoSwipeBase, index: number): 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. + * By default, it loads image based on viewport size and initial zoom level. * * @param {number} index Slide index - * @param {PhotoSwipe | PhotoSwipeLightbox} instance PhotoSwipe or PhotoSwipeLightbox eventable instance + * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox eventable instance + * @returns {Content | undefined} */ -export function lazyLoadSlide(index: number, instance: PhotoSwipe | PhotoSwipeLightbox): import("./content.js").default; +export function lazyLoadSlide(index: number, instance: PhotoSwipeBase): Content | undefined; export default ContentLoader; export type Content = import('./content.js').default; export type Slide = import('./slide.js').default; export type SlideData = import('./slide.js').SlideData; export type PhotoSwipeBase = import('../core/base.js').default; export type PhotoSwipe = import('../photoswipe.js').default; -export type PhotoSwipeLightbox = import('../lightbox/lightbox.js').default; declare class ContentLoader { /** * @param {PhotoSwipe} pswp @@ -39,17 +39,18 @@ declare class ContentLoader { /** * Lazy load nearby slides based on `preload` option. * - * @param {number=} diff Difference between slide indexes that was changed recently, or 0. + * @param {number} [diff] Difference between slide indexes that was changed recently, or 0. */ - updateLazy(diff?: number | undefined): void; + updateLazy(diff?: number): void; /** - * @param {number} index + * @param {number} initialIndex */ - loadSlideByIndex(index: number): void; + loadSlideByIndex(initialIndex: number): void; /** * @param {Slide} slide + * @returns {Content} */ - getContentBySlide(slide: Slide): import("./content.js").default; + getContentBySlide(slide: Slide): Content; /** * @param {Content} content */ @@ -62,7 +63,8 @@ declare class ContentLoader { removeByIndex(index: number): void; /** * @param {number} index + * @returns {Content | undefined} */ - getContentByIndex(index: number): import("./content.js").default; + getContentByIndex(index: number): Content | undefined; destroy(): void; } diff --git a/src/js/core/base.js b/src/js/core/base.js index 867cac7c..02d0a085 100644 --- a/src/js/core/base.js +++ b/src/js/core/base.js @@ -178,7 +178,7 @@ class PhotoSwipeBase extends Eventable { * * @param {SlideData} itemData Data about the slide * @param {number} index - * @returns Image that is being decoded or false. + * @returns {Content} Image that is being decoded or false. */ lazyLoadData(itemData, index) { return lazyLoadData(itemData, this, index); diff --git a/src/js/slide/loader.js b/src/js/slide/loader.js index f288f8f0..fd3c6655 100644 --- a/src/js/slide/loader.js +++ b/src/js/slide/loader.js @@ -6,7 +6,6 @@ import ZoomLevel from './zoom-level.js'; /** @typedef {import('./slide.js').SlideData} SlideData */ /** @typedef {import('../core/base.js').default} PhotoSwipeBase */ /** @typedef {import('../photoswipe.js').default} PhotoSwipe */ -/** @typedef {import('../lightbox/lightbox.js').default} PhotoSwipeLightbox */ const MIN_SLIDES_TO_CACHE = 5; @@ -16,28 +15,23 @@ const MIN_SLIDES_TO_CACHE = 5; * thus it can be called before dialog is opened. * * @param {SlideData} itemData Data about the slide - * @param {PhotoSwipe | PhotoSwipeLightbox | PhotoSwipeBase} instance PhotoSwipe instance + * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance * @param {number} index - * @returns Image that is being decoded or false. + * @returns {Content} Image that is being decoded or false. */ export function lazyLoadData(itemData, instance, index) { - // src/slide/content/content.js const content = instance.createContentFromData(itemData, index); - if (!content || !content.lazyLoad) { - return; - } - 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 - // @ts-expect-error should provide pswp instance? - const viewportSize = instance.viewportSize || getViewportSize(options, instance); - const panAreaSize = getPanAreaSize(options, viewportSize, itemData, index); - + // as it might use srcset, and we need to define sizes const zoomLevel = new ZoomLevel(options, itemData, -1); - zoomLevel.update(content.width, content.height, panAreaSize); + if (instance.pswp) { + const viewportSize = instance.pswp.viewportSize || getViewportSize(options, instance.pswp); + const panAreaSize = getPanAreaSize(options, viewportSize, itemData, index); + zoomLevel.update(content.width, content.height, panAreaSize); + } content.lazyLoad(); content.setDisplayedSize( @@ -54,10 +48,11 @@ export function lazyLoadData(itemData, instance, index) { * 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. + * By default, it loads image based on viewport size and initial zoom level. * * @param {number} index Slide index - * @param {PhotoSwipe | PhotoSwipeLightbox} instance PhotoSwipe or PhotoSwipeLightbox eventable instance + * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox eventable instance + * @returns {Content | undefined} */ export function lazyLoadSlide(index, instance) { const itemData = instance.getItemData(index); @@ -69,7 +64,6 @@ export function lazyLoadSlide(index, instance) { return lazyLoadData(itemData, instance, index); } - class ContentLoader { /** * @param {PhotoSwipe} pswp @@ -88,7 +82,7 @@ class ContentLoader { /** * Lazy load nearby slides based on `preload` option. * - * @param {number=} diff Difference between slide indexes that was changed recently, or 0. + * @param {number} [diff] Difference between slide indexes that was changed recently, or 0. */ updateLazy(diff) { const { pswp } = this; @@ -113,10 +107,10 @@ class ContentLoader { } /** - * @param {number} index + * @param {number} initialIndex */ - loadSlideByIndex(index) { - index = this.pswp.getLoopedIndex(index); + loadSlideByIndex(initialIndex) { + const index = this.pswp.getLoopedIndex(initialIndex); // try to get cached content let content = this.getContentByIndex(index); if (!content) { @@ -131,21 +125,19 @@ class ContentLoader { /** * @param {Slide} slide + * @returns {Content} */ getContentBySlide(slide) { let content = this.getContentByIndex(slide.index); if (!content) { // create content if not found in cache content = this.pswp.createContentFromData(slide.data, slide.index); - if (content) { - this.addToCache(content); - } + this.addToCache(content); } - if (content) { - // assign slide to content - content.setSlide(slide); - } + // assign slide to content + content.setSlide(slide); + return content; } @@ -183,6 +175,7 @@ class ContentLoader { /** * @param {number} index + * @returns {Content | undefined} */ getContentByIndex(index) { return this._cachedItems.find(content => content.index === index); @@ -190,7 +183,7 @@ class ContentLoader { destroy() { this._cachedItems.forEach(content => content.destroy()); - this._cachedItems = null; + this._cachedItems = []; } } From 6ce199750662984ce7946e32bf29128b470d3e89 Mon Sep 17 00:00:00 2001 From: mishchuk Date: Thu, 22 Dec 2022 16:38:57 +1000 Subject: [PATCH 04/27] fix slide/content types --- dist/types/slide/content.d.ts | 31 +++++---- src/js/core/base.js | 1 - src/js/slide/content.js | 119 +++++++++++++++++++--------------- 3 files changed, 83 insertions(+), 68 deletions(-) diff --git a/dist/types/slide/content.d.ts b/dist/types/slide/content.d.ts index f7a2d413..5ea9328d 100644 --- a/dist/types/slide/content.d.ts +++ b/dist/types/slide/content.d.ts @@ -1,42 +1,46 @@ export default Content; export type Slide = import('./slide.js').default; export type SlideData = import('./slide.js').SlideData; -export type PhotoSwipe = import('../photoswipe.js').default; +export type PhotoSwipeBase = import('../core/base.js').default; export type LoadState = import('../util/util.js').LoadState; /** @typedef {import('./slide.js').default} Slide */ /** @typedef {import('./slide.js').SlideData} SlideData */ -/** @typedef {import('../photoswipe.js').default} PhotoSwipe */ +/** @typedef {import('../core/base.js').default} PhotoSwipeBase */ /** @typedef {import('../util/util.js').LoadState} LoadState */ declare class Content { /** * @param {SlideData} itemData Slide data - * @param {PhotoSwipe} instance PhotoSwipe or PhotoSwipeLightbox instance + * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance * @param {number} index */ - constructor(itemData: SlideData, instance: PhotoSwipe, index: number); - instance: import("../photoswipe.js").default; + constructor(itemData: SlideData, instance: PhotoSwipeBase, index: number); + instance: import("../core/base.js").default; data: import("./slide.js").SlideData; index: number; - /** @type {HTMLImageElement | HTMLDivElement} */ - element: HTMLImageElement | HTMLDivElement; + /** @type {HTMLImageElement | HTMLDivElement | undefined} */ + element: HTMLImageElement | HTMLDivElement | undefined; + /** @type {Placeholder | undefined} */ + placeholder: Placeholder | undefined; + /** @type {Slide | undefined} */ + slide: Slide | undefined; displayedImageWidth: number; displayedImageHeight: number; width: number; height: number; isAttached: boolean; hasSlide: boolean; + isDecoding: boolean; /** @type {LoadState} */ state: LoadState; type: string; removePlaceholder(): void; - placeholder: Placeholder; /** * Preload content * - * @param {boolean=} isLazy - * @param {boolean=} reload + * @param {boolean} [isLazy] + * @param {boolean} [reload] */ - load(isLazy?: boolean | undefined, reload?: boolean | undefined): void; + load(isLazy?: boolean, reload?: boolean): void; /** * Preload image * @@ -49,7 +53,6 @@ declare class Content { * @param {Slide} slide */ setSlide(slide: Slide): void; - slide: import("./slide.js").default; /** * Content load success handler */ @@ -62,6 +65,9 @@ declare class Content { * @returns {Boolean} If the content is currently loading */ isLoading(): boolean; + /** + * @returns {Boolean} If the content is in error state + */ isError(): boolean; /** * @returns {boolean} If the content is image @@ -106,7 +112,6 @@ declare class Content { * Append the content */ append(): void; - isDecoding: boolean; /** * Activate the slide, * active slide is generally the current one, diff --git a/src/js/core/base.js b/src/js/core/base.js index 02d0a085..f338e01e 100644 --- a/src/js/core/base.js +++ b/src/js/core/base.js @@ -50,7 +50,6 @@ class PhotoSwipeBase extends Eventable { * @returns {Content} */ createContentFromData(slideData, index) { - // @ts-expect-error return new Content(slideData, this, index); } diff --git a/src/js/slide/content.js b/src/js/slide/content.js index 9121f9ac..e494cf12 100644 --- a/src/js/slide/content.js +++ b/src/js/slide/content.js @@ -3,13 +3,13 @@ import Placeholder from './placeholder.js'; /** @typedef {import('./slide.js').default} Slide */ /** @typedef {import('./slide.js').SlideData} SlideData */ -/** @typedef {import('../photoswipe.js').default} PhotoSwipe */ +/** @typedef {import('../core/base.js').default} PhotoSwipeBase */ /** @typedef {import('../util/util.js').LoadState} LoadState */ class Content { /** * @param {SlideData} itemData Slide data - * @param {PhotoSwipe} instance PhotoSwipe or PhotoSwipeLightbox instance + * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance * @param {number} index */ constructor(itemData, instance, index) { @@ -17,8 +17,12 @@ class Content { this.data = itemData; this.index = index; - /** @type {HTMLImageElement | HTMLDivElement} */ + /** @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; @@ -28,6 +32,7 @@ class Content { this.isAttached = false; this.hasSlide = false; + this.isDecoding = false; /** @type {LoadState} */ this.state = LOAD_STATE.IDLE; @@ -48,7 +53,7 @@ class Content { setTimeout(() => { if (this.placeholder) { this.placeholder.destroy(); - this.placeholder = null; + this.placeholder = undefined; } }, 1000); } @@ -57,8 +62,8 @@ class Content { /** * Preload content * - * @param {boolean=} isLazy - * @param {boolean=} reload + * @param {boolean} [isLazy] + * @param {boolean} [reload] */ load(isLazy, reload) { if (this.slide && this.usePlaceholder()) { @@ -99,7 +104,7 @@ class Content { this.loadImage(isLazy); } } else { - this.element = createElement('pswp__content'); + this.element = createElement('pswp__content', 'div'); this.element.innerHTML = this.data.html || ''; } @@ -114,12 +119,14 @@ class Content { * @param {boolean} isLazy */ loadImage(isLazy) { - const imageElement = /** @type HTMLImageElement */ (this.element); - - if (this.instance.dispatch('contentLoadImage', { content: this, isLazy }).defaultPrevented) { + 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) { @@ -164,7 +171,7 @@ class Content { onLoaded() { this.state = LOAD_STATE.LOADED; - if (this.slide) { + if (this.slide && this.element) { this.instance.dispatch('loadComplete', { slide: this.slide, content: this }); // if content is reloaded @@ -205,6 +212,9 @@ class Content { ); } + /** + * @returns {Boolean} If the content is in error state + */ isError() { return this.state === LOAD_STATE.ERROR; } @@ -231,8 +241,10 @@ class Content { this.placeholder.setDisplayedSize(width, height); } - // eslint-disable-next-line max-len - if (this.instance.dispatch('contentResize', { content: this, width, height }).defaultPrevented) { + if (this.instance.dispatch( + 'contentResize', + { content: this, width, height }).defaultPrevented + ) { return; } @@ -251,8 +263,10 @@ class Content { } if (this.slide) { - // eslint-disable-next-line max-len - this.instance.dispatch('imageSizeChange', { slide: this.slide, width, height, content: this }); + this.instance.dispatch( + 'imageSizeChange', + { slide: this.slide, width, height, content: this } + ); } } } @@ -277,24 +291,23 @@ class Content { // 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. - // 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.data.srcset) { - const image = /** @type HTMLImageElement */ (this.element); - const sizesWidth = this.instance.applyFilters( - 'srcsetSizesWidth', - this.displayedImageWidth, - this - ); + if (!this.isImageContent() || !this.element || !this.data.srcset) { + return; + } - if (!image.dataset.largestUsedSize - || sizesWidth > parseInt(image.dataset.largestUsedSize, 10)) { - image.sizes = sizesWidth + 'px'; - image.dataset.largestUsedSize = String(sizesWidth); - } + 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); } } @@ -336,7 +349,7 @@ class Content { */ destroy() { this.hasSlide = false; - this.slide = null; + this.slide = undefined; if (this.instance.dispatch('contentDestroy', { content: this }).defaultPrevented) { return; @@ -346,13 +359,13 @@ class Content { if (this.placeholder) { this.placeholder.destroy(); - this.placeholder = null; + this.placeholder = undefined; } if (this.isImageContent() && this.element) { this.element.onload = null; this.element.onerror = null; - this.element = null; + this.element = undefined; } } @@ -361,15 +374,14 @@ class Content { */ displayError() { if (this.slide) { - /** @type {HTMLElement} */ - let errorMsgEl = createElement('pswp__error-msg'); + let errorMsgEl = createElement('pswp__error-msg', 'div'); errorMsgEl.innerText = this.instance.options.errorMsg; - errorMsgEl = this.instance.applyFilters( + errorMsgEl = /** @type {HTMLDivElement} */ (this.instance.applyFilters( 'contentErrorElement', errorMsgEl, this - ); - this.element = createElement('pswp__content pswp__error-msg-container'); + )); + this.element = createElement('pswp__content pswp__error-msg-container', 'div'); this.element.appendChild(errorMsgEl); this.slide.container.innerText = ''; this.slide.container.appendChild(this.element); @@ -382,7 +394,7 @@ class Content { * Append the content */ append() { - if (this.isAttached) { + if (this.isAttached || !this.element) { return; } @@ -424,7 +436,7 @@ class Content { } else { this.appendImage(); } - } else if (this.element && !this.element.parentNode) { + } else if (this.slide && !this.element.parentNode) { this.slide.container.appendChild(this.element); } } @@ -435,22 +447,21 @@ class Content { * meaning the user can see it. */ activate() { - if (this.instance.dispatch('contentActivate', { content: this }).defaultPrevented) { + if (this.instance.dispatch('contentActivate', { content: this }).defaultPrevented + || !this.slide) { return; } - if (this.slide) { - 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.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'); - } + if (this.slide.holderElement) { + this.slide.holderElement.setAttribute('aria-hidden', 'false'); } } From f6d22f6227d942523eab001b42d9257a8e1e07ba Mon Sep 17 00:00:00 2001 From: mishchuk Date: Thu, 22 Dec 2022 16:58:39 +1000 Subject: [PATCH 05/27] fix slide/get-thumb-bounds types --- src/js/slide/get-thumb-bounds.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/js/slide/get-thumb-bounds.js b/src/js/slide/get-thumb-bounds.js index eb9fd29b..9270a10f 100644 --- a/src/js/slide/get-thumb-bounds.js +++ b/src/js/slide/get-thumb-bounds.js @@ -5,6 +5,7 @@ /** * @param {HTMLElement} el + * @returns Bounds */ function getBoundsByElement(el) { const thumbAreaRect = el.getBoundingClientRect(); @@ -19,6 +20,7 @@ function getBoundsByElement(el) { * @param {HTMLElement} el * @param {number} imageWidth * @param {number} imageHeight + * @returns Bounds */ function getCroppedBoundsByElement(el, imageWidth, imageHeight) { const thumbAreaRect = el.getBoundingClientRect(); @@ -80,9 +82,10 @@ export function getThumbBounds(index, itemData, instance) { } const { element } = itemData; + /** @type {Bounds | undefined} */ let thumbBounds; - /** @type {HTMLElement} */ - let thumbnail; + /** @type {HTMLElement | null} */ + let thumbnail = null; if (element && instance.options.thumbSelector !== false) { const thumbSelector = instance.options.thumbSelector || 'img'; From f6d4b9fec9ca17ca7c7c3d1f152d3967b96ebed0 Mon Sep 17 00:00:00 2001 From: mishchuk Date: Thu, 22 Dec 2022 17:20:52 +1000 Subject: [PATCH 06/27] fix slide/pan-bounds types --- dist/types/slide/pan-bounds.d.ts | 12 ++--------- src/js/slide/pan-bounds.js | 34 ++++++++++++-------------------- 2 files changed, 15 insertions(+), 31 deletions(-) diff --git a/dist/types/slide/pan-bounds.d.ts b/dist/types/slide/pan-bounds.d.ts index 83d502dc..9916c0b6 100644 --- a/dist/types/slide/pan-bounds.d.ts +++ b/dist/types/slide/pan-bounds.d.ts @@ -1,13 +1,7 @@ export default PanBounds; export type Slide = import('./slide.js').default; -export type Point = { - x?: number; - y?: number; -}; +export type Point = Record; export type Axis = 'x' | 'y'; -/** @typedef {import('./slide.js').default} Slide */ -/** @typedef {{ x?: number; y?: number }} Point */ -/** @typedef {'x' | 'y'} Axis */ /** * Calculates minimum, maximum and initial (center) bounds of a slide */ @@ -18,11 +12,8 @@ declare class PanBounds { constructor(slide: Slide); slide: import("./slide.js").default; currZoomLevel: number; - /** @type {Point} */ center: Point; - /** @type {Point} */ max: Point; - /** @type {Point} */ min: Point; /** * _getItemBounds @@ -42,6 +33,7 @@ declare class PanBounds { * * @param {Axis} axis x or y * @param {number} panOffset + * @returns {number} */ correctPan(axis: Axis, panOffset: number): number; } diff --git a/src/js/slide/pan-bounds.js b/src/js/slide/pan-bounds.js index a482a4e7..bef3aeba 100644 --- a/src/js/slide/pan-bounds.js +++ b/src/js/slide/pan-bounds.js @@ -1,12 +1,13 @@ -import { - clamp -} from '../util/util.js'; +import { clamp } from '../util/util.js'; import { parsePaddingOption } from '../util/viewport-size.js'; /** @typedef {import('./slide.js').default} Slide */ -/** @typedef {{ x?: number; y?: number }} Point */ +/** @typedef {Record} Point */ /** @typedef {'x' | 'y'} Axis */ +/** @type {Point} */ +const defaultPoint = { x: 0, y: 0 }; + /** * Calculates minimum, maximum and initial (center) bounds of a slide */ @@ -16,17 +17,10 @@ class PanBounds { */ constructor(slide) { this.slide = slide; - this.currZoomLevel = 1; - - /** @type {Point} */ - this.center = {}; - /** @type {Point} */ - this.max = {}; - /** @type {Point} */ - this.min = {}; - - this.reset(); + this.center = /** @type {Point} */ ({ ...defaultPoint }); + this.max = /** @type {Point} */ ({ ...defaultPoint }); + this.min = /** @type {Point} */ ({ ...defaultPoint }); } /** @@ -66,7 +60,7 @@ class PanBounds { const panAreaSize = this.slide.panAreaSize[axis]; // Default position of element. - // By defaul it is center of viewport: + // By default, it is center of viewport: this.center[axis] = Math.round((panAreaSize - elSize) / 2) + padding; // maximum pan position @@ -82,12 +76,9 @@ class PanBounds { // _getZeroBounds reset() { - this.center.x = 0; - this.center.y = 0; - this.max.x = 0; - this.max.y = 0; - this.min.x = 0; - this.min.y = 0; + this.center = { ...defaultPoint }; + this.max = { ...defaultPoint }; + this.min = { ...defaultPoint }; } /** @@ -95,6 +86,7 @@ class PanBounds { * * @param {Axis} axis x or y * @param {number} panOffset + * @returns {number} */ correctPan(axis, panOffset) { // checkPanBounds return clamp(panOffset, this.max[axis], this.min[axis]); From b0c777dbb55c3dcd7b30340bc08f6965f499c34c Mon Sep 17 00:00:00 2001 From: mishchuk Date: Thu, 22 Dec 2022 17:40:57 +1000 Subject: [PATCH 07/27] fix slide/placeholder types --- dist/types/slide/placeholder.d.ts | 3 ++- src/js/slide/placeholder.js | 17 ++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/dist/types/slide/placeholder.d.ts b/dist/types/slide/placeholder.d.ts index d0d13230..f9645dba 100644 --- a/dist/types/slide/placeholder.d.ts +++ b/dist/types/slide/placeholder.d.ts @@ -5,7 +5,8 @@ declare class Placeholder { * @param {HTMLElement} container */ constructor(imageSrc: string | false, container: HTMLElement); - element: HTMLDivElement | HTMLImageElement; + /** @type {HTMLImageElement | HTMLDivElement | null} */ + element: HTMLImageElement | HTMLDivElement | null; /** * @param {number} width * @param {number} height diff --git a/src/js/slide/placeholder.js b/src/js/slide/placeholder.js index 732349e5..631c1fc4 100644 --- a/src/js/slide/placeholder.js +++ b/src/js/slide/placeholder.js @@ -8,20 +8,19 @@ class Placeholder { 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' : '', + imageSrc ? 'img' : 'div', container ); if (imageSrc) { - /** @type {HTMLImageElement} */ - (this.element).decoding = 'async'; - /** @type {HTMLImageElement} */ - (this.element).alt = ''; - /** @type {HTMLImageElement} */ - (this.element).src = imageSrc; - this.element.setAttribute('role', 'presentation'); + const imgEl = /** @type {HTMLImageElement} */ (this.element); + imgEl.decoding = 'async'; + imgEl.alt = ''; + imgEl.src = imageSrc; + imgEl.setAttribute('role', 'presentation'); } this.element.setAttribute('aria-hidden', 'true'); @@ -49,7 +48,7 @@ class Placeholder { } destroy() { - if (this.element.parentNode) { + if (this.element?.parentNode) { this.element.remove(); } this.element = null; From 8af5c8437ae0549a6257ee93ab298ad060f30f54 Mon Sep 17 00:00:00 2001 From: mishchuk Date: Thu, 22 Dec 2022 18:57:16 +1000 Subject: [PATCH 08/27] fix slide/zoom-level types --- dist/types/slide/slide.d.ts | 6 ++--- dist/types/slide/zoom-level.d.ts | 43 +++++++++++++++---------------- src/js/slide/slide.js | 18 +++++++------ src/js/slide/zoom-level.js | 44 ++++++++++++++++++-------------- 4 files changed, 59 insertions(+), 52 deletions(-) diff --git a/dist/types/slide/slide.d.ts b/dist/types/slide/slide.d.ts index 2c2a0bf5..8970659c 100644 --- a/dist/types/slide/slide.d.ts +++ b/dist/types/slide/slide.d.ts @@ -1,6 +1,6 @@ export default Slide; export type PhotoSwipe = import('../photoswipe.js').default; -export type Point = import('../photoswipe.js').Point; +export type PanAreaSize = import('./zoom-level').PanAreaSize; export type SlideData = _SlideData & Record; export type _SlideData = { /** @@ -67,8 +67,8 @@ declare class Slide { pswp: import("../photoswipe.js").default; isActive: boolean; currentResolution: number; - /** @type {Point} */ - panAreaSize: Point; + /** @type {PanAreaSize | null} */ + panAreaSize: PanAreaSize | null; isFirstSlide: boolean; zoomLevels: ZoomLevel; pan: { diff --git a/dist/types/slide/zoom-level.d.ts b/dist/types/slide/zoom-level.d.ts index 1782622e..4bf1f3b5 100644 --- a/dist/types/slide/zoom-level.d.ts +++ b/dist/types/slide/zoom-level.d.ts @@ -3,10 +3,15 @@ export type PhotoSwipe = import('../photoswipe.js').default; export type PhotoSwipeOptions = import('../photoswipe.js').PhotoSwipeOptions; export type SlideData = import('../slide/slide.js').SlideData; export type ZoomLevelOption = number | "fit" | "fill" | ((zoomLevelObject: ZoomLevel) => number); +export type PanAreaSize = { + x: number; + y: number; +}; /** @typedef {import('../photoswipe.js').default} PhotoSwipe */ /** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */ /** @typedef {import('../slide/slide.js').SlideData} SlideData */ /** @typedef {'fit' | 'fill' | number | ((zoomLevelObject: ZoomLevel) => number)} ZoomLevelOption */ +/** @typedef {{ x: number; y: number }} PanAreaSize */ /** * Calculates zoom levels for specific slide. * Depends on viewport size and image size. @@ -16,13 +21,24 @@ declare 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 + * @param {PhotoSwipe} [pswp] PhotoSwipe instance, can be undefined if not initialized yet */ - constructor(options: PhotoSwipeOptions, itemData: SlideData, index: number, pswp?: PhotoSwipe | undefined); + constructor(options: PhotoSwipeOptions, itemData: SlideData, index: number, pswp?: PhotoSwipe); pswp: import("../photoswipe.js").default; options: import("../photoswipe.js").PhotoSwipeOptions; itemData: import("../slide/slide.js").SlideData; index: number; + /** @type { PanAreaSize | null } */ + panAreaSize: PanAreaSize | null; + /** @type { PanAreaSize | null } */ + elementSize: PanAreaSize | null; + fit: number; + fill: number; + vFill: number; + initial: number; + secondary: number; + max: number; + min: number; /** * Calculate initial, secondary and maximum zoom level for the specified slide. * @@ -30,32 +46,15 @@ declare class ZoomLevel { * * @param {number} maxWidth * @param {number} maxHeight - * @param {{ x?: number; y?: number }} panAreaSize + * @param {PanAreaSize} panAreaSize */ - update(maxWidth: number, maxHeight: number, panAreaSize: { - x?: number; - y?: number; - }): void; - elementSize: { - x: number; - y: number; - }; - panAreaSize: { - x?: number; - y?: number; - }; - fit: number; - fill: number; - vFill: number; - initial: number; - secondary: number; - max: number; - min: number; + update(maxWidth: number, maxHeight: number, panAreaSize: PanAreaSize): void; /** * Parses user-defined zoom option. * * @private * @param {'initial' | 'secondary' | 'max'} optionPrefix Zoom level option prefix (initial, secondary, max) + * @returns { number | undefined } */ private _parseZoomLevelOption; /** diff --git a/src/js/slide/slide.js b/src/js/slide/slide.js index fae57764..b46a2f5d 100644 --- a/src/js/slide/slide.js +++ b/src/js/slide/slide.js @@ -1,5 +1,5 @@ /** @typedef {import('../photoswipe.js').default} PhotoSwipe */ -/** @typedef {import('../photoswipe.js').Point} Point */ +/** @typedef {import('./zoom-level').PanAreaSize} PanAreaSize */ /** * @typedef {_SlideData & Record} SlideData @@ -46,8 +46,8 @@ class Slide { this.pswp = pswp; this.isActive = (index === pswp.currIndex); this.currentResolution = 0; - /** @type {Point} */ - this.panAreaSize = {}; + /** @type {PanAreaSize | null} */ + this.panAreaSize = null; this.isFirstSlide = (this.isActive && !pswp.opener.isOpen); @@ -454,12 +454,14 @@ class Slide { calculateSize() { const { pswp } = this; - equalizePoints( - this.panAreaSize, - getPanAreaSize(pswp.options, pswp.viewportSize, this.data, this.index) - ); + if (this.panAreaSize) { + equalizePoints( + this.panAreaSize, + getPanAreaSize(pswp.options, pswp.viewportSize, this.data, this.index) + ); - this.zoomLevels.update(this.width, this.height, this.panAreaSize); + this.zoomLevels.update(this.width, this.height, this.panAreaSize); + } pswp.dispatch('calcSlideSize', { slide: this diff --git a/src/js/slide/zoom-level.js b/src/js/slide/zoom-level.js index 29853243..2b541fc7 100644 --- a/src/js/slide/zoom-level.js +++ b/src/js/slide/zoom-level.js @@ -5,6 +5,7 @@ const MAX_IMAGE_WIDTH = 4000; /** @typedef {import('../slide/slide.js').SlideData} SlideData */ /** @typedef {'fit' | 'fill' | number | ((zoomLevelObject: ZoomLevel) => number)} ZoomLevelOption */ +/** @typedef {{ x: number; y: number }} PanAreaSize */ /** * Calculates zoom levels for specific slide. @@ -15,13 +16,24 @@ 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 + * @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 { PanAreaSize | null } */ + this.panAreaSize = null; + /** @type { PanAreaSize | 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; } /** @@ -31,18 +43,16 @@ class ZoomLevel { * * @param {number} maxWidth * @param {number} maxHeight - * @param {{ x?: number; y?: number }} panAreaSize + * @param {PanAreaSize} panAreaSize */ update(maxWidth, maxHeight, panAreaSize) { - this.elementSize = { - x: maxWidth, - y: maxHeight - }; - + /** @type {PanAreaSize} */ + const elementSize = { x: maxWidth, y: maxHeight }; + this.elementSize = elementSize; this.panAreaSize = panAreaSize; - const hRatio = this.panAreaSize.x / this.elementSize.x; - const vRatio = this.panAreaSize.y / this.elementSize.y; + 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); @@ -75,10 +85,12 @@ class ZoomLevel { * * @private * @param {'initial' | 'secondary' | 'max'} optionPrefix Zoom level option prefix (initial, secondary, max) + * @returns { number | undefined } */ _parseZoomLevelOption(optionPrefix) { - // eslint-disable-next-line max-len - const optionName = /** @type {'initialZoomLevel' | 'secondaryZoomLevel' | 'maxZoomLevel'} */ (optionPrefix + 'ZoomLevel'); + const optionName = /** @type {'initialZoomLevel' | 'secondaryZoomLevel' | 'maxZoomLevel'} */ ( + optionPrefix + 'ZoomLevel' + ); const optionValue = this.options[optionName]; if (!optionValue) { @@ -119,7 +131,7 @@ class ZoomLevel { // 3x of "fit" state, but not larger than original currZoomLevel = Math.min(1, this.fit * 3); - if (currZoomLevel * this.elementSize.x > MAX_IMAGE_WIDTH) { + if (this.elementSize && currZoomLevel * this.elementSize.x > MAX_IMAGE_WIDTH) { currZoomLevel = MAX_IMAGE_WIDTH / this.elementSize.x; } @@ -145,15 +157,9 @@ class ZoomLevel { * @return {number} */ _getMax() { - const currZoomLevel = this._parseZoomLevelOption('max'); - - if (currZoomLevel) { - return currZoomLevel; - } - // max zoom level is x4 from "fit state", // used for zoom gesture and ctrl/trackpad zoom - return Math.max(1, this.fit * 4); + return this._parseZoomLevelOption('max') || Math.max(1, this.fit * 4); } } From db26460c646de506387475bb03775ba71c8d40b0 Mon Sep 17 00:00:00 2001 From: mishchuk Date: Fri, 23 Dec 2022 12:10:10 +1000 Subject: [PATCH 09/27] fix util/util types --- dist/types/gestures/tap-handler.d.ts | 5 +-- dist/types/lightbox/lightbox.d.ts | 11 +++-- dist/types/photoswipe.d.ts | 13 +++--- dist/types/util/util.d.ts | 54 +++++++++++++------------ src/js/gestures/drag-handler.js | 2 +- src/js/gestures/gestures.js | 20 +++++----- src/js/gestures/tap-handler.js | 2 +- src/js/gestures/zoom-handler.js | 11 +++-- src/js/lightbox/lightbox.js | 5 ++- src/js/main-scroll.js | 2 +- src/js/photoswipe.js | 16 ++++---- src/js/slide/slide.js | 2 +- src/js/util/util.js | 60 +++++++++++++--------------- 13 files changed, 97 insertions(+), 106 deletions(-) diff --git a/dist/types/gestures/tap-handler.d.ts b/dist/types/gestures/tap-handler.d.ts index 50f15721..6148b497 100644 --- a/dist/types/gestures/tap-handler.d.ts +++ b/dist/types/gestures/tap-handler.d.ts @@ -4,11 +4,8 @@ export default TapHandler; */ export type AddPostfix = import('../types.js').AddPostfix; export type Gestures = import('./gestures.js').default; +export type Point = import('../photoswipe.js').Point; export type Actions = 'imageClick' | 'bgClick' | 'tap' | 'doubleTap'; -export type Point = { - x?: number; - y?: number; -}; /** * Tap, double-tap handler. */ diff --git a/dist/types/lightbox/lightbox.d.ts b/dist/types/lightbox/lightbox.d.ts index cb156983..d7901c32 100644 --- a/dist/types/lightbox/lightbox.d.ts +++ b/dist/types/lightbox/lightbox.d.ts @@ -6,6 +6,7 @@ export type Type = import('../types.js').Type; export type PhotoSwipe = import('../photoswipe.js').default; export type PhotoSwipeOptions = import('../photoswipe.js').PhotoSwipeOptions; export type DataSource = import('../photoswipe.js').DataSource; +export type Point = import('../photoswipe.js').Point; export type Content = import('../slide/content.js').default; export type PhotoSwipeEventsMap = import('../core/eventable.js').PhotoSwipeEventsMap; export type PhotoSwipeFiltersMap = import('../core/eventable.js').PhotoSwipeFiltersMap; @@ -20,6 +21,7 @@ export type EventCallback = import('../core/eventable.js').EventCallback; /** @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 */ @@ -67,13 +69,10 @@ declare class PhotoSwipeLightbox extends PhotoSwipeBase { * Load and open PhotoSwipe * * @param {number} index - * @param {DataSource=} dataSource - * @param {{ x?: number; y?: number }} [initialPoint] + * @param {DataSource} dataSource + * @param {Point | null} [initialPoint] */ - loadAndOpen(index: number, dataSource?: DataSource | undefined, initialPoint?: { - x?: number; - y?: number; - }): boolean; + loadAndOpen(index: number, dataSource: DataSource, initialPoint?: Point | null): boolean; shouldOpen: boolean; /** * Load the main module and the slide content by index diff --git a/dist/types/photoswipe.d.ts b/dist/types/photoswipe.d.ts index 1e5bd532..10f1ff34 100644 --- a/dist/types/photoswipe.d.ts +++ b/dist/types/photoswipe.d.ts @@ -18,8 +18,8 @@ export type EventCallback = import('./core/eventable.js').EventCallback; */ export type AugmentedEvent = import('./core/eventable.js').AugmentedEvent; export type Point = { - x?: number; - y?: number; + x: number; + y: number; id?: string | number; }; export type Size = { @@ -256,19 +256,16 @@ declare class PhotoSwipe extends PhotoSwipeBase { y?: number; }; /** - * @type {{ x?: number; y?: number }} + * @type {Point} * @private */ private _prevViewportSize; /** * Size of scrollable PhotoSwipe viewport * - * @type {{ x?: number; y?: number }} + * @type {Point} */ - viewportSize: { - x?: number; - y?: number; - }; + viewportSize: Point; /** * background (backdrop) opacity * diff --git a/dist/types/util/util.d.ts b/dist/types/util/util.d.ts index 15740213..3d9fd6ee 100644 --- a/dist/types/util/util.d.ts +++ b/dist/types/util/util.d.ts @@ -1,20 +1,18 @@ /** @typedef {import('../photoswipe.js').Point} Point */ -/** @typedef {undefined | null | false | '' | 0} Falsy */ -/** @typedef {keyof HTMLElementTagNameMap} HTMLElementTagName */ /** - * @template {HTMLElementTagName | Falsy} [T="div"] - * @template {Node | undefined} [NodeToAppendElementTo=undefined] - * @param {string=} className - * @param {T=} [tagName] - * @param {NodeToAppendElementTo=} appendToEl - * @returns {T extends HTMLElementTagName ? HTMLElementTagNameMap[T] : HTMLElementTagNameMap['div']} + * @template {keyof HTMLElementTagNameMap} T + * @param {string} className + * @param {T} tagName + * @param {Node} [appendToEl] + * @returns {HTMLElementTagNameMap[T]} */ -export function createElement(className?: string | undefined, tagName?: T, appendToEl?: NodeToAppendElementTo): T extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[T] : HTMLDivElement; +export function createElement(className: string, tagName: T, appendToEl?: Node): HTMLElementTagNameMap[T]; /** * @param {Point} p1 * @param {Point} p2 + * @returns {Point} */ -export function equalizePoints(p1: Point, p2: Point): import("../photoswipe.js").Point; +export function equalizePoints(p1: Point, p2: Point): Point; /** * @param {Point} p */ @@ -24,13 +22,15 @@ export function roundPoint(p: Point): void; * * @param {Point} p1 * @param {Point} p2 + * @returns {number} */ export function getDistanceBetween(p1: Point, p2: Point): number; /** - * Whether X and Y positions of points are qual + * Whether X and Y positions of points are equal * * @param {Point} p1 * @param {Point} p2 + * @returns {boolean} */ export function pointsEqual(p1: Point, p2: Point): boolean; /** @@ -39,34 +39,36 @@ export function pointsEqual(p1: Point, p2: Point): boolean; * @param {number} val * @param {number} min * @param {number} max + * @returns {number} */ export function clamp(val: number, min: number, max: number): number; /** * Get transform string * * @param {number} x - * @param {number=} y - * @param {number=} scale + * @param {number} [y] + * @param {number} [scale] + * @returns {string} */ -export function toTransformString(x: number, y?: number | undefined, scale?: number | undefined): string; +export function toTransformString(x: number, y?: number, scale?: number): string; /** * Apply transform:translate(x, y) scale(scale) to element * * @param {HTMLElement} el * @param {number} x - * @param {number=} y - * @param {number=} scale + * @param {number} [y] + * @param {number} [scale] */ -export function setTransform(el: HTMLElement, x: number, y?: number | undefined, scale?: number | undefined): void; +export function setTransform(el: HTMLElement, x: number, y?: number, scale?: number): void; /** * Apply CSS transition to element * * @param {HTMLElement} el - * @param {string=} prop CSS property to animate - * @param {number=} duration in ms - * @param {string=} ease CSS easing function + * @param {string} [prop] CSS property to animate + * @param {number} [duration] in ms + * @param {string} [ease] CSS easing function */ -export function setTransitionStyle(el: HTMLElement, prop?: string | undefined, duration?: number | undefined, ease?: string | undefined): void; +export function setTransitionStyle(el: HTMLElement, prop?: string, duration?: number, ease?: string): void; /** * Apply width and height CSS properties to element * @@ -89,23 +91,25 @@ export function decodeImage(img: HTMLImageElement): Promise -1) { // update existing pointer this._convertEventPosToPoint(pointerEvent, this._ongoingPointers[pointerIndex]); diff --git a/src/js/gestures/tap-handler.js b/src/js/gestures/tap-handler.js index 9798503c..db98c7c1 100644 --- a/src/js/gestures/tap-handler.js +++ b/src/js/gestures/tap-handler.js @@ -5,9 +5,9 @@ */ /** @typedef {import('./gestures.js').default} Gestures */ +/** @typedef {import('../photoswipe.js').Point} Point */ /** @typedef {'imageClick' | 'bgClick' | 'tap' | 'doubleTap'} Actions */ -/** @typedef {{ x?: number; y?: number }} Point */ /** * Whether the tap was performed on the main slide diff --git a/src/js/gestures/zoom-handler.js b/src/js/gestures/zoom-handler.js index 4622785b..c21fc29e 100644 --- a/src/js/gestures/zoom-handler.js +++ b/src/js/gestures/zoom-handler.js @@ -30,12 +30,11 @@ class ZoomHandler { this.gestures = gestures; this.pswp = this.gestures.pswp; /** @type {Point} */ - this._startPan = {}; - + this._startPan = { x: 0, y: 0 }; /** @type {Point} */ - this._startZoomPoint = {}; + this._startZoomPoint = { x: 0, y: 0 }; /** @type {Point} */ - this._zoomPoint = {}; + this._zoomPoint = { x: 0, y: 0 }; } start() { @@ -155,8 +154,8 @@ class ZoomHandler { const initialBgOpacity = pswp.bgOpacity; const restoreBgOpacity = pswp.bgOpacity < 1; - const initialPan = equalizePoints({}, currSlide.pan); - let destinationPan = equalizePoints({}, initialPan); + const initialPan = equalizePoints({ x: 0, y: 0 }, currSlide.pan); + let destinationPan = equalizePoints({ x: 0, y: 0 }, initialPan); if (ignoreGesture) { this._zoomPoint.x = 0; diff --git a/src/js/lightbox/lightbox.js b/src/js/lightbox/lightbox.js index b2282d4f..0ecd8b21 100644 --- a/src/js/lightbox/lightbox.js +++ b/src/js/lightbox/lightbox.js @@ -15,6 +15,7 @@ import { lazyLoadSlide } from '../slide/loader.js'; /** @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 */ @@ -136,8 +137,8 @@ class PhotoSwipeLightbox extends PhotoSwipeBase { * Load and open PhotoSwipe * * @param {number} index - * @param {DataSource=} dataSource - * @param {{ x?: number; y?: number }} [initialPoint] + * @param {DataSource} dataSource + * @param {Point | null} [initialPoint] */ loadAndOpen(index, dataSource, initialPoint) { // Check if the gallery is already open diff --git a/src/js/main-scroll.js b/src/js/main-scroll.js index e7d898ac..8fd23ea0 100644 --- a/src/js/main-scroll.js +++ b/src/js/main-scroll.js @@ -97,7 +97,7 @@ class MainScroll { // append our three slide holders - // previous, current, and next for (let i = 0; i < 3; i++) { - const el = createElement('pswp__item', false, this.pswp.container); + const el = createElement('pswp__item', 'div', this.pswp.container); el.setAttribute('role', 'group'); el.setAttribute('aria-roledescription', 'slide'); el.setAttribute('aria-hidden', 'true'); diff --git a/src/js/photoswipe.js b/src/js/photoswipe.js index 68bd618c..34e4a83e 100644 --- a/src/js/photoswipe.js +++ b/src/js/photoswipe.js @@ -40,7 +40,7 @@ import ContentLoader from './slide/loader.js'; * @typedef {import('./core/eventable.js').AugmentedEvent} AugmentedEvent */ -/** @typedef {{ x?: number; y?: number; id?: string | number }} Point */ +/** @typedef {{ x: number; y: number; id?: string | number }} Point */ /** @typedef {{ x?: number; y?: number }} Size */ /** @typedef {{ top: number; bottom: number; left: number; right: number }} Padding */ /** @typedef {SlideData[]} DataSourceArray */ @@ -268,17 +268,17 @@ class PhotoSwipe extends PhotoSwipeBase { this.offset = {}; /** - * @type {{ x?: number; y?: number }} + * @type {Point} * @private */ - this._prevViewportSize = {}; + this._prevViewportSize = { x: 0, y: 0 }; /** * Size of scrollable PhotoSwipe viewport * - * @type {{ x?: number; y?: number }} + * @type {Point} */ - this.viewportSize = {}; + this.viewportSize = { x: 0, y: 0 }; /** * background (backdrop) opacity @@ -700,7 +700,7 @@ class PhotoSwipe extends PhotoSwipeBase { */ _createMainStructure() { // root DOM element of PhotoSwipe (.pswp) - this.element = createElement('pswp'); + this.element = createElement('pswp', 'div'); this.element.setAttribute('tabindex', '-1'); this.element.setAttribute('role', 'dialog'); @@ -709,9 +709,9 @@ class PhotoSwipe extends PhotoSwipeBase { // Background is added as a separate element, // as animating opacity is faster than animating rgba() - this.bg = createElement('pswp__bg', false, this.element); + this.bg = createElement('pswp__bg', 'div', this.element); this.scrollWrap = createElement('pswp__scroll-wrap', 'section', this.element); - this.container = createElement('pswp__container', false, this.scrollWrap); + this.container = createElement('pswp__container', 'div', this.scrollWrap); // aria pattern: carousel this.scrollWrap.setAttribute('aria-roledescription', 'carousel'); diff --git a/src/js/slide/slide.js b/src/js/slide/slide.js index b46a2f5d..ac059bb2 100644 --- a/src/js/slide/slide.js +++ b/src/js/slide/slide.js @@ -65,7 +65,7 @@ class Slide { }; this.content = this.pswp.contentLoader.getContentBySlide(this); - this.container = createElement('pswp__zoom-wrap'); + this.container = createElement('pswp__zoom-wrap', 'div'); this.currZoomLevel = 1; /** @type {number} */ diff --git a/src/js/util/util.js b/src/js/util/util.js index 30c993f9..ba378b72 100644 --- a/src/js/util/util.js +++ b/src/js/util/util.js @@ -1,31 +1,27 @@ /** @typedef {import('../photoswipe.js').Point} Point */ -/** @typedef {undefined | null | false | '' | 0} Falsy */ -/** @typedef {keyof HTMLElementTagNameMap} HTMLElementTagName */ - /** - * @template {HTMLElementTagName | Falsy} [T="div"] - * @template {Node | undefined} [NodeToAppendElementTo=undefined] - * @param {string=} className - * @param {T=} [tagName] - * @param {NodeToAppendElementTo=} appendToEl - * @returns {T extends HTMLElementTagName ? HTMLElementTagNameMap[T] : HTMLElementTagNameMap['div']} + * @template {keyof HTMLElementTagNameMap} T + * @param {string} className + * @param {T} tagName + * @param {Node} [appendToEl] + * @returns {HTMLElementTagNameMap[T]} */ export function createElement(className, tagName, appendToEl) { - const el = document.createElement(tagName || 'div'); + const el = document.createElement(tagName); if (className) { el.className = className; } if (appendToEl) { appendToEl.appendChild(el); } - // @ts-expect-error return el; } /** * @param {Point} p1 * @param {Point} p2 + * @returns {Point} */ export function equalizePoints(p1, p2) { p1.x = p2.x; @@ -49,6 +45,7 @@ export function roundPoint(p) { * * @param {Point} p1 * @param {Point} p2 + * @returns {number} */ export function getDistanceBetween(p1, p2) { const x = Math.abs(p1.x - p2.x); @@ -57,10 +54,11 @@ export function getDistanceBetween(p1, p2) { } /** - * Whether X and Y positions of points are qual + * Whether X and Y positions of points are equal * * @param {Point} p1 * @param {Point} p2 + * @returns {boolean} */ export function pointsEqual(p1, p2) { return p1.x === p2.x && p1.y === p2.y; @@ -72,6 +70,7 @@ export function pointsEqual(p1, p2) { * @param {number} val * @param {number} min * @param {number} max + * @returns {number} */ export function clamp(val, min, max) { return Math.min(Math.max(val, min), max); @@ -81,18 +80,15 @@ export function clamp(val, min, max) { * Get transform string * * @param {number} x - * @param {number=} y - * @param {number=} scale + * @param {number} [y] + * @param {number} [scale] + * @returns {string} */ export function toTransformString(x, y, scale) { - let propValue = 'translate3d(' - + x + 'px,' + (y || 0) + 'px' - + ',0)'; + let propValue = `translate3d(${x}px,${y || 0}px,0)`; if (scale !== undefined) { - propValue += ' scale3d(' - + scale + ',' + scale - + ',1)'; + propValue += ` scale3d(${scale},${scale},1)`; } return propValue; @@ -103,8 +99,8 @@ export function toTransformString(x, y, scale) { * * @param {HTMLElement} el * @param {number} x - * @param {number=} y - * @param {number=} scale + * @param {number} [y] + * @param {number} [scale] */ export function setTransform(el, x, y, scale) { el.style.transform = toTransformString(x, y, scale); @@ -116,16 +112,16 @@ const defaultCSSEasing = 'cubic-bezier(.4,0,.22,1)'; * Apply CSS transition to element * * @param {HTMLElement} el - * @param {string=} prop CSS property to animate - * @param {number=} duration in ms - * @param {string=} ease CSS easing function + * @param {string} [prop] CSS property to animate + * @param {number} [duration] in ms + * @param {string} [ease] CSS easing function */ export function setTransitionStyle(el, prop, duration, ease) { // inOut: 'cubic-bezier(.4, 0, .22, 1)', // for "toggle state" transitions // out: 'cubic-bezier(0, 0, .22, 1)', // for "show" transitions // in: 'cubic-bezier(.4, 0, 1, 1)'// for "hide" transitions el.style.transition = prop - ? (prop + ' ' + duration + 'ms ' + (ease || defaultCSSEasing)) + ? `${prop} ${duration}ms ${ease || defaultCSSEasing}` : 'none'; } @@ -137,8 +133,8 @@ export function setTransitionStyle(el, prop, duration, ease) { * @param {string | number} h */ export function setWidthHeight(el, w, h) { - el.style.width = (typeof w === 'number') ? (w + 'px') : w; - el.style.height = (typeof h === 'number') ? (h + 'px') : h; + el.style.width = (typeof w === 'number') ? `${w}px` : w; + el.style.height = (typeof h === 'number') ? `${h}px` : h; } /** @@ -182,18 +178,17 @@ export const LOAD_STATE = { * with a special key or via mouse wheel. * * @param {MouseEvent | KeyboardEvent} e + * @returns {boolean} */ export function specialKeyUsed(e) { - if (e.which === 2 || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey) { - return true; - } + return e.which === 2 || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey; } /** * Parse `gallery` or `children` options. * * @param {import('../photoswipe.js').ElementProvider} option - * @param {string=} legacySelector + * @param {string} [legacySelector] * @param {HTMLElement | Document} [parent] * @returns HTMLElement[] */ @@ -219,6 +214,7 @@ export function getElementsFromOption(option, legacySelector, parent = document) * Check if variable is PhotoSwipe class * * @param {any} fn + * @returns {boolean} */ export function isPswpClass(fn) { return typeof fn === 'function' From ecda977d0f82f047855393c33573891477d62d4e Mon Sep 17 00:00:00 2001 From: mishchuk Date: Fri, 23 Dec 2022 12:29:36 +1000 Subject: [PATCH 10/27] fix util/animations types --- dist/types/util/animations.d.ts | 76 ++++++++++----------------- dist/types/util/css-animation.d.ts | 33 +++++++++--- dist/types/util/spring-animation.d.ts | 33 +++++++++--- dist/types/util/spring-easer.d.ts | 6 +-- src/js/util/animations.js | 57 +++++++------------- src/js/util/css-animation.js | 35 ++++++------ src/js/util/spring-animation.js | 20 +++++-- src/js/util/spring-easer.js | 4 +- 8 files changed, 138 insertions(+), 126 deletions(-) diff --git a/dist/types/util/animations.d.ts b/dist/types/util/animations.d.ts index eff15e98..7a5d06a6 100644 --- a/dist/types/util/animations.d.ts +++ b/dist/types/util/animations.d.ts @@ -1,49 +1,26 @@ export default Animations; -export type Animation = SpringAnimation | CSSAnimation; -export type AnimationProps = { - target?: HTMLElement | undefined; - name?: string | undefined; - start?: number | undefined; - end?: number | undefined; - duration?: number | undefined; - velocity?: number | undefined; - dampingRatio?: number | undefined; - naturalFrequency?: number | undefined; - onUpdate?: (end: number) => void; - onComplete?: () => void; - onFinish?: () => void; - transform?: string | undefined; - opacity?: string | undefined; - easing?: string | undefined; - isPan?: boolean | undefined; - isMainScroll?: boolean | undefined; +export type CssAnimationProps = import('./css-animation.js').CssAnimationProps; +export type SpringAnimationProps = import('./spring-animation.js').SpringAnimationProps; +export type SharedAnimationProps = { + name?: string; + isPan?: boolean; + isMainScroll?: boolean; + onComplete?: VoidFunction; + onFinish?: VoidFunction; }; -/** @typedef {SpringAnimation | CSSAnimation} Animation */ -/** - * @typedef {Object} AnimationProps - * - * @prop {HTMLElement=} target - * - * @prop {string=} name - * - * @prop {number=} start - * @prop {number=} end - * @prop {number=} duration - * @prop {number=} velocity - * @prop {number=} dampingRatio - * @prop {number=} naturalFrequency - * - * @prop {(end: number) => void} [onUpdate] - * @prop {() => void} [onComplete] - * @prop {() => void} [onFinish] - * - * @prop {string=} transform - * @prop {string=} opacity - * @prop {string=} easing - * - * @prop {boolean=} isPan - * @prop {boolean=} isMainScroll +export type Animation = SpringAnimation | CSSAnimation; +export type AnimationProps = SpringAnimationProps | CssAnimationProps; +/** @typedef {import('./css-animation.js').CssAnimationProps} CssAnimationProps */ +/** @typedef {import('./spring-animation.js').SpringAnimationProps} SpringAnimationProps */ +/** @typedef {Object} SharedAnimationProps + * @prop {string} [name] + * @prop {boolean} [isPan] + * @prop {boolean} [isMainScroll] + * @prop {VoidFunction} [onComplete] + * @prop {VoidFunction} [onFinish] */ +/** @typedef {SpringAnimation | CSSAnimation} Animation */ +/** @typedef {SpringAnimationProps | CssAnimationProps} AnimationProps */ /** * Manages animations */ @@ -51,18 +28,19 @@ declare class Animations { /** @type {Animation[]} */ activeAnimations: Animation[]; /** - * @param {AnimationProps} props + * @param {SpringAnimationProps} props */ - startSpring(props: AnimationProps): void; + startSpring(props: SpringAnimationProps): void; /** - * @param {AnimationProps} props + * @param {CssAnimationProps} props */ - startTransition(props: AnimationProps): void; + startTransition(props: CssAnimationProps): void; /** + * @private * @param {AnimationProps} props - * @param {boolean=} isSpring + * @param {boolean} [isSpring] */ - _start(props: AnimationProps, isSpring?: boolean | undefined): Animation; + private _start; /** * @param {Animation} animation */ diff --git a/dist/types/util/css-animation.d.ts b/dist/types/util/css-animation.d.ts index 5f2b8ff1..bdd04f90 100644 --- a/dist/types/util/css-animation.d.ts +++ b/dist/types/util/css-animation.d.ts @@ -1,6 +1,23 @@ export default CSSAnimation; -export type AnimationProps = import('./animations.js').AnimationProps; -/** @typedef {import('./animations.js').AnimationProps} AnimationProps */ +export type SharedAnimationProps = import('./animations.js').SharedAnimationProps; +export type DefaultCssAnimationProps = { + target: HTMLElement; + duration?: number; + easing?: string; + transform?: string; + opacity?: string; +}; +export type CssAnimationProps = SharedAnimationProps & DefaultCssAnimationProps; +/** @typedef {import('./animations.js').SharedAnimationProps} SharedAnimationProps */ +/** @typedef {Object} DefaultCssAnimationProps + * + * @prop {HTMLElement} target + * @prop {number} [duration] + * @prop {string} [easing] + * @prop {string} [transform] + * @prop {string} [opacity] + * */ +/** @typedef {SharedAnimationProps & DefaultCssAnimationProps} CssAnimationProps */ /** * Runs CSS transition. */ @@ -8,16 +25,17 @@ declare class CSSAnimation { /** * onComplete can be unpredictable, be careful about current state * - * @param {AnimationProps} props + * @param {CssAnimationProps} props */ - constructor(props: AnimationProps); - props: import("./animations.js").AnimationProps; - /** @type {() => void} */ - onFinish: () => void; + constructor(props: CssAnimationProps); + props: CssAnimationProps; + onFinish: VoidFunction; /** @private */ private _target; /** @private */ private _onComplete; + /** @private */ + private _finished; /** * @private * @param {TransitionEvent} e @@ -29,6 +47,5 @@ declare class CSSAnimation { * @private */ private _finalizeAnimation; - _finished: boolean; destroy(): void; } diff --git a/dist/types/util/spring-animation.d.ts b/dist/types/util/spring-animation.d.ts index 9d382ece..04d7c743 100644 --- a/dist/types/util/spring-animation.d.ts +++ b/dist/types/util/spring-animation.d.ts @@ -1,14 +1,33 @@ export default SpringAnimation; -export type AnimationProps = import('./animations.js').AnimationProps; -/** @typedef {import('./animations.js').AnimationProps} AnimationProps */ +export type SharedAnimationProps = import('./animations.js').SharedAnimationProps; +export type DefaultSpringAnimationProps = { + start: number; + end: number; + velocity: number; + dampingRatio?: number; + naturalFrequency?: number; + onUpdate: (end: number) => void; +}; +export type SpringAnimationProps = SharedAnimationProps & DefaultSpringAnimationProps; +/** @typedef {import('./animations.js').SharedAnimationProps} SharedAnimationProps */ +/** + * @typedef {Object} DefaultSpringAnimationProps + * + * @prop {number} start + * @prop {number} end + * @prop {number} velocity + * @prop {number} [dampingRatio] + * @prop {number} [naturalFrequency] + * @prop {(end: number) => void} onUpdate + */ +/** @typedef {SharedAnimationProps & DefaultSpringAnimationProps} SpringAnimationProps */ declare class SpringAnimation { /** - * @param {AnimationProps} props + * @param {SpringAnimationProps} props */ - constructor(props: AnimationProps); - props: import("./animations.js").AnimationProps; - /** @type {() => void} */ - onFinish: () => void; + constructor(props: SpringAnimationProps); + props: SpringAnimationProps; + onFinish: VoidFunction; _raf: number; destroy(): void; } diff --git a/dist/types/util/spring-easer.d.ts b/dist/types/util/spring-easer.d.ts index 4d59365a..951d41f2 100644 --- a/dist/types/util/spring-easer.d.ts +++ b/dist/types/util/spring-easer.d.ts @@ -6,19 +6,19 @@ declare class SpringEaser { /** * @param {number} initialVelocity Initial velocity, px per ms. * - * @param {number} dampingRatio + * @param {number} [dampingRatio] * Determines how bouncy animation will be. * From 0 to 1, 0 - always overshoot, 1 - do not overshoot. * "overshoot" refers to part of animation that * goes beyond the final value. * - * @param {number} naturalFrequency + * @param {number} [naturalFrequency] * Determines how fast animation will slow down. * The higher value - the stiffer the transition will be, * and the faster it will slow down. * Recommended value from 10 to 50 */ - constructor(initialVelocity: number, dampingRatio: number, naturalFrequency: number); + constructor(initialVelocity: number, dampingRatio?: number, naturalFrequency?: number); velocity: number; _dampingRatio: number; _naturalFrequency: number; diff --git a/src/js/util/animations.js b/src/js/util/animations.js index 345ec4e0..a070f4cb 100644 --- a/src/js/util/animations.js +++ b/src/js/util/animations.js @@ -1,34 +1,20 @@ import CSSAnimation from './css-animation.js'; import SpringAnimation from './spring-animation.js'; -/** @typedef {SpringAnimation | CSSAnimation} Animation */ - -/** - * @typedef {Object} AnimationProps - * - * @prop {HTMLElement=} target - * - * @prop {string=} name - * - * @prop {number=} start - * @prop {number=} end - * @prop {number=} duration - * @prop {number=} velocity - * @prop {number=} dampingRatio - * @prop {number=} naturalFrequency - * - * @prop {(end: number) => void} [onUpdate] - * @prop {() => void} [onComplete] - * @prop {() => void} [onFinish] - * - * @prop {string=} transform - * @prop {string=} opacity - * @prop {string=} easing - * - * @prop {boolean=} isPan - * @prop {boolean=} isMainScroll +/** @typedef {import('./css-animation.js').CssAnimationProps} CssAnimationProps */ +/** @typedef {import('./spring-animation.js').SpringAnimationProps} SpringAnimationProps */ + +/** @typedef {Object} SharedAnimationProps + * @prop {string} [name] + * @prop {boolean} [isPan] + * @prop {boolean} [isMainScroll] + * @prop {VoidFunction} [onComplete] + * @prop {VoidFunction} [onFinish] */ +/** @typedef {SpringAnimation | CSSAnimation} Animation */ +/** @typedef {SpringAnimationProps | CssAnimationProps} AnimationProps */ + /** * Manages animations */ @@ -39,36 +25,31 @@ class Animations { } /** - * @param {AnimationProps} props + * @param {SpringAnimationProps} props */ startSpring(props) { this._start(props, true); } /** - * @param {AnimationProps} props + * @param {CssAnimationProps} props */ startTransition(props) { this._start(props); } /** + * @private * @param {AnimationProps} props - * @param {boolean=} isSpring + * @param {boolean} [isSpring] */ _start(props, isSpring) { - /** @type {Animation} */ - let animation; - if (isSpring) { - animation = new SpringAnimation(props); - } else { - animation = new CSSAnimation(props); - } + const animation = isSpring + ? new SpringAnimation(/** @type SpringAnimationProps */ (props)) + : new CSSAnimation(/** @type CssAnimationProps */ (props)); this.activeAnimations.push(animation); animation.onFinish = () => this.stop(animation); - - return animation; } /** diff --git a/src/js/util/css-animation.js b/src/js/util/css-animation.js index 4eb3042e..60118dc4 100644 --- a/src/js/util/css-animation.js +++ b/src/js/util/css-animation.js @@ -2,7 +2,18 @@ import { setTransitionStyle, removeTransitionStyle } from './util.js'; const DEFAULT_EASING = 'cubic-bezier(.4,0,.22,1)'; -/** @typedef {import('./animations.js').AnimationProps} AnimationProps */ +/** @typedef {import('./animations.js').SharedAnimationProps} SharedAnimationProps */ + +/** @typedef {Object} DefaultCssAnimationProps + * + * @prop {HTMLElement} target + * @prop {number} [duration] + * @prop {string} [easing] + * @prop {string} [transform] + * @prop {string} [opacity] + * */ + +/** @typedef {SharedAnimationProps & DefaultCssAnimationProps} CssAnimationProps */ /** * Runs CSS transition. @@ -11,7 +22,7 @@ class CSSAnimation { /** * onComplete can be unpredictable, be careful about current state * - * @param {AnimationProps} props + * @param {CssAnimationProps} props */ constructor(props) { this.props = props; @@ -19,16 +30,11 @@ class CSSAnimation { target, onComplete, transform, - onFinish - // opacity - } = props; - - let { - duration, - easing, + onFinish = () => {}, + duration = 333, + easing = DEFAULT_EASING, } = props; - /** @type {() => void} */ this.onFinish = onFinish; // support only transform and opacity @@ -39,16 +45,15 @@ class CSSAnimation { this._target = target; /** @private */ this._onComplete = onComplete; - - duration = duration || 333; - easing = easing || DEFAULT_EASING; + /** @private */ + this._finished = false; /** @private */ this._onTransitionEnd = this._onTransitionEnd.bind(this); // Using timeout hack to make sure that animation // starts even if the animated property was changed recently, - // otherwise transitionend might not fire or transiton won't start. + // otherwise transitionend might not fire or transition won't start. // https://drafts.csswg.org/css-transitions/#starting // // ¯\_(ツ)_/¯ @@ -60,7 +65,7 @@ class CSSAnimation { target.addEventListener('transitioncancel', this._onTransitionEnd, false); // Safari occasionally does not emit transitionend event - // if element propery was modified during the transition, + // if element property was modified during the transition, // which may be caused by resize or third party component, // using timeout as a safety fallback this._helperTimeout = setTimeout(() => { diff --git a/src/js/util/spring-animation.js b/src/js/util/spring-animation.js index 79100b74..5c86baa4 100644 --- a/src/js/util/spring-animation.js +++ b/src/js/util/spring-animation.js @@ -1,10 +1,23 @@ import SpringEaser from './spring-easer.js'; -/** @typedef {import('./animations.js').AnimationProps} AnimationProps */ +/** @typedef {import('./animations.js').SharedAnimationProps} SharedAnimationProps */ + +/** + * @typedef {Object} DefaultSpringAnimationProps + * + * @prop {number} start + * @prop {number} end + * @prop {number} velocity + * @prop {number} [dampingRatio] + * @prop {number} [naturalFrequency] + * @prop {(end: number) => void} onUpdate + */ + +/** @typedef {SharedAnimationProps & DefaultSpringAnimationProps} SpringAnimationProps */ class SpringAnimation { /** - * @param {AnimationProps} props + * @param {SpringAnimationProps} props */ constructor(props) { this.props = props; @@ -15,12 +28,11 @@ class SpringAnimation { velocity, onUpdate, onComplete, - onFinish, + onFinish = () => {}, dampingRatio, naturalFrequency } = props; - /** @type {() => void} */ this.onFinish = onFinish; const easer = new SpringEaser(velocity, dampingRatio, naturalFrequency); diff --git a/src/js/util/spring-easer.js b/src/js/util/spring-easer.js index 833dd8bb..50d71277 100644 --- a/src/js/util/spring-easer.js +++ b/src/js/util/spring-easer.js @@ -8,13 +8,13 @@ class SpringEaser { /** * @param {number} initialVelocity Initial velocity, px per ms. * - * @param {number} dampingRatio + * @param {number} [dampingRatio] * Determines how bouncy animation will be. * From 0 to 1, 0 - always overshoot, 1 - do not overshoot. * "overshoot" refers to part of animation that * goes beyond the final value. * - * @param {number} naturalFrequency + * @param {number} [naturalFrequency] * Determines how fast animation will slow down. * The higher value - the stiffer the transition will be, * and the faster it will slow down. From 6009aec7083389b9809f574fcb5664a1b8733e87 Mon Sep 17 00:00:00 2001 From: mishchuk Date: Fri, 23 Dec 2022 17:25:30 +1000 Subject: [PATCH 11/27] fix util/dom-events types --- dist/types/util/dom-events.d.ts | 43 +++++++++++++++++---------------- src/js/gestures/gestures.js | 22 +++++++++++++---- src/js/keyboard.js | 4 +-- src/js/scroll-wheel.js | 2 +- src/js/util/dom-events.js | 35 +++++++++++++-------------- 5 files changed, 59 insertions(+), 47 deletions(-) diff --git a/dist/types/util/dom-events.d.ts b/dist/types/util/dom-events.d.ts index 0a74fd58..207db6ef 100644 --- a/dist/types/util/dom-events.d.ts +++ b/dist/types/util/dom-events.d.ts @@ -1,15 +1,15 @@ export default DOMEvents; export type PoolItem = { - target: HTMLElement | Window | Document; + target: HTMLElement | Window | Document | undefined | null; type: string; - listener: (e: any) => void; + listener: EventListenerOrEventListenerObject; passive: boolean; }; /** * @typedef {Object} PoolItem - * @prop {HTMLElement | Window | Document} target + * @prop {HTMLElement | Window | Document | undefined | null} target * @prop {string} type - * @prop {(e: any) => void} listener + * @prop {EventListenerOrEventListenerObject} listener * @prop {boolean} passive */ declare class DOMEvents { @@ -21,21 +21,21 @@ declare class DOMEvents { /** * Adds event listeners * - * @param {HTMLElement | Window | Document} target - * @param {string} type Can be multiple, separated by space. - * @param {(e: any) => void} listener - * @param {boolean=} passive + * @param {PoolItem['target']} target + * @param {PoolItem['type']} type Can be multiple, separated by space. + * @param {PoolItem['listener']} listener + * @param {PoolItem['passive']} [passive] */ - add(target: HTMLElement | Window | Document, type: string, listener: (e: any) => void, passive?: boolean | undefined): void; + add(target: PoolItem['target'], type: PoolItem['type'], listener: PoolItem['listener'], passive?: PoolItem['passive']): void; /** * Removes event listeners * - * @param {HTMLElement | Window | Document} target - * @param {string} type - * @param {(e: any) => void} listener - * @param {boolean=} passive + * @param {PoolItem['target']} target + * @param {PoolItem['type']} type + * @param {PoolItem['listener']} listener + * @param {PoolItem['passive']} [passive] */ - remove(target: HTMLElement | Window | Document, type: string, listener: (e: any) => void, passive?: boolean | undefined): void; + remove(target: PoolItem['target'], type: PoolItem['type'], listener: PoolItem['listener'], passive?: PoolItem['passive']): void; /** * Removes all bound events */ @@ -43,12 +43,13 @@ declare class DOMEvents { /** * Adds or removes event * - * @param {HTMLElement | Window | Document} target - * @param {string} type - * @param {(e: any) => void} listener - * @param {boolean} passive - * @param {boolean=} unbind Whether the event should be added or removed - * @param {boolean=} skipPool Whether events pool should be skipped + * @private + * @param {PoolItem['target']} target + * @param {PoolItem['type']} type + * @param {PoolItem['listener']} listener + * @param {PoolItem['passive']} [passive] + * @param {boolean} [unbind] Whether the event should be added or removed + * @param {boolean} [skipPool] Whether events pool should be skipped */ - _toggleListener(target: HTMLElement | Window | Document, type: string, listener: (e: any) => void, passive: boolean, unbind?: boolean | undefined, skipPool?: boolean | undefined): void; + private _toggleListener; } diff --git a/src/js/gestures/gestures.js b/src/js/gestures/gestures.js index 5de362c3..7fa5eb30 100644 --- a/src/js/gestures/gestures.js +++ b/src/js/gestures/gestures.js @@ -74,7 +74,11 @@ class Gestures { this.tapHandler = new TapHandler(this); pswp.on('bindEvents', () => { - pswp.events.add(pswp.scrollWrap, 'click', e => this._onClick(e)); + pswp.events.add( + pswp.scrollWrap, + 'click', + /** @type EventListener */(this._onClick.bind(this)) + ); if (this._pointerEventEnabled) { this._bindEvents('pointer', 'down', 'up', 'cancel'); @@ -110,11 +114,19 @@ class Gestures { const cancelEvent = cancel ? pref + cancel : ''; - events.add(pswp.scrollWrap, pref + down, this.onPointerDown.bind(this)); - events.add(window, pref + 'move', this.onPointerMove.bind(this)); - events.add(window, pref + up, this.onPointerUp.bind(this)); + events.add( + pswp.scrollWrap, + pref + down, + /** @type EventListener */(this.onPointerDown.bind(this)) + ); + events.add(window, pref + 'move', /** @type EventListener */(this.onPointerMove.bind(this))); + events.add(window, pref + up, /** @type EventListener */(this.onPointerUp.bind(this))); if (cancelEvent) { - events.add(pswp.scrollWrap, cancelEvent, this.onPointerUp.bind(this)); + events.add( + pswp.scrollWrap, + cancelEvent, + /** @type EventListener */(this.onPointerUp.bind(this)) + ); } } diff --git a/src/js/keyboard.js b/src/js/keyboard.js index d231fdce..748b5afe 100644 --- a/src/js/keyboard.js +++ b/src/js/keyboard.js @@ -27,8 +27,8 @@ class Keyboard { this._focusRoot(); } - pswp.events.add(document, 'focusin', this._onFocusIn.bind(this)); - pswp.events.add(document, 'keydown', this._onKeyDown.bind(this)); + pswp.events.add(document, 'focusin', /** @type EventListener */(this._onFocusIn.bind(this))); + pswp.events.add(document, 'keydown', /** @type EventListener */(this._onKeyDown.bind(this))); }); const lastActiveElement = /** @type {HTMLElement} */ (document.activeElement); diff --git a/src/js/scroll-wheel.js b/src/js/scroll-wheel.js index 08c05a28..7afda182 100644 --- a/src/js/scroll-wheel.js +++ b/src/js/scroll-wheel.js @@ -10,7 +10,7 @@ class ScrollWheel { */ constructor(pswp) { this.pswp = pswp; - pswp.events.add(pswp.element, 'wheel', this._onWheel.bind(this)); + pswp.events.add(pswp.element, 'wheel', /** @type EventListener */(this._onWheel.bind(this))); } /** diff --git a/src/js/util/dom-events.js b/src/js/util/dom-events.js index 2265c3b2..11f51145 100644 --- a/src/js/util/dom-events.js +++ b/src/js/util/dom-events.js @@ -10,12 +10,11 @@ try { } catch (e) {} /* eslint-enable */ - /** * @typedef {Object} PoolItem - * @prop {HTMLElement | Window | Document} target + * @prop {HTMLElement | Window | Document | undefined | null} target * @prop {string} type - * @prop {(e: any) => void} listener + * @prop {EventListenerOrEventListenerObject} listener * @prop {boolean} passive */ @@ -31,10 +30,10 @@ class DOMEvents { /** * Adds event listeners * - * @param {HTMLElement | Window | Document} target - * @param {string} type Can be multiple, separated by space. - * @param {(e: any) => void} listener - * @param {boolean=} passive + * @param {PoolItem['target']} target + * @param {PoolItem['type']} type Can be multiple, separated by space. + * @param {PoolItem['listener']} listener + * @param {PoolItem['passive']} [passive] */ add(target, type, listener, passive) { this._toggleListener(target, type, listener, passive); @@ -43,10 +42,10 @@ class DOMEvents { /** * Removes event listeners * - * @param {HTMLElement | Window | Document} target - * @param {string} type - * @param {(e: any) => void} listener - * @param {boolean=} passive + * @param {PoolItem['target']} target + * @param {PoolItem['type']} type + * @param {PoolItem['listener']} listener + * @param {PoolItem['passive']} [passive] */ remove(target, type, listener, passive) { this._toggleListener(target, type, listener, passive, true); @@ -72,12 +71,13 @@ class DOMEvents { /** * Adds or removes event * - * @param {HTMLElement | Window | Document} target - * @param {string} type - * @param {(e: any) => void} listener - * @param {boolean} passive - * @param {boolean=} unbind Whether the event should be added or removed - * @param {boolean=} skipPool Whether events pool should be skipped + * @private + * @param {PoolItem['target']} target + * @param {PoolItem['type']} type + * @param {PoolItem['listener']} listener + * @param {PoolItem['passive']} [passive] + * @param {boolean} [unbind] Whether the event should be added or removed + * @param {boolean} [skipPool] Whether events pool should be skipped */ _toggleListener(target, type, listener, passive, unbind, skipPool) { if (!target) { @@ -109,7 +109,6 @@ class DOMEvents { } } - // most PhotoSwipe events call preventDefault, // and we do not need browser to scroll the page const eventOptions = supportsPassive ? { passive: (passive || false) } : false; From 8032640a1e44e54f80b273d1b13af0069a9d9f1a Mon Sep 17 00:00:00 2001 From: mishchuk Date: Mon, 26 Dec 2022 10:33:31 +1000 Subject: [PATCH 12/27] fix util/viewport-size types --- dist/types/photoswipe.d.ts | 11 ++--------- dist/types/util/viewport-size.d.ts | 26 +++++++++----------------- src/js/photoswipe.js | 5 ++--- src/js/util/viewport-size.js | 12 +++++++----- 4 files changed, 20 insertions(+), 34 deletions(-) diff --git a/dist/types/photoswipe.d.ts b/dist/types/photoswipe.d.ts index 10f1ff34..ecda0a33 100644 --- a/dist/types/photoswipe.d.ts +++ b/dist/types/photoswipe.d.ts @@ -22,10 +22,6 @@ export type Point = { y: number; id?: string | number; }; -export type Size = { - x?: number; - y?: number; -}; export type Padding = { top: number; bottom: number; @@ -95,7 +91,7 @@ export type PhotoSwipeOptions = { /** * The option is checked frequently, so make sure it's performant. Overrides padding option if defined. For example: */ - paddingFn?: (viewportSize: Size, itemData: SlideData, index: number) => Padding; + paddingFn?: (viewportSize: Point, itemData: SlideData, index: number) => Padding; /** * Transition duration in milliseconds, can be 0. */ @@ -156,10 +152,7 @@ export type PhotoSwipeOptions = { /** * A function that should return slide viewport width and height, in format {x: 100, y: 100}. */ - getViewportSizeFn?: (options: PhotoSwipeOptions, pswp: PhotoSwipe) => { - x: number; - y: number; - }; + getViewportSizeFn?: (options: PhotoSwipeOptions, pswp: PhotoSwipe) => Point; /** * Message to display when the image wasn't able to load. If you need to display HTML - use contentErrorElement filter. */ diff --git a/dist/types/util/viewport-size.d.ts b/dist/types/util/viewport-size.d.ts index bbeb13f6..7ccbece7 100644 --- a/dist/types/util/viewport-size.d.ts +++ b/dist/types/util/viewport-size.d.ts @@ -1,14 +1,13 @@ /** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */ /** @typedef {import('../photoswipe.js').default} PhotoSwipe */ +/** @typedef {import('../photoswipe.js').Point} Point */ /** @typedef {import('../slide/slide.js').SlideData} SlideData */ /** * @param {PhotoSwipeOptions} options * @param {PhotoSwipe} pswp + * @returns {Point} */ -export function getViewportSize(options: PhotoSwipeOptions, pswp: PhotoSwipe): { - x: number; - y: number; -}; +export function getViewportSize(options: PhotoSwipeOptions, pswp: PhotoSwipe): Point; /** * Parses padding option. * Supported formats: @@ -39,28 +38,21 @@ export function getViewportSize(options: PhotoSwipeOptions, pswp: PhotoSwipe): { * * @param {'left' | 'top' | 'bottom' | 'right'} prop * @param {PhotoSwipeOptions} options PhotoSwipe options - * @param {{ x?: number; y?: number }} viewportSize PhotoSwipe viewport size, for example: { x:800, y:600 } + * @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} */ -export function parsePaddingOption(prop: 'left' | 'top' | 'bottom' | 'right', options: PhotoSwipeOptions, viewportSize: { - x?: number; - y?: number; -}, itemData: SlideData, index: number): number; +export function parsePaddingOption(prop: 'left' | 'top' | 'bottom' | 'right', options: PhotoSwipeOptions, viewportSize: Point, itemData: SlideData, index: number): number; /** * @param {PhotoSwipeOptions} options - * @param {{ x?: number; y?: number }} viewportSize + * @param {Point} viewportSize * @param {SlideData} itemData * @param {number} index + * @returns {Point} */ -export function getPanAreaSize(options: PhotoSwipeOptions, viewportSize: { - x?: number; - y?: number; -}, itemData: SlideData, index: number): { - x: number; - y: number; -}; +export function getPanAreaSize(options: PhotoSwipeOptions, viewportSize: Point, itemData: SlideData, index: number): Point; export type PhotoSwipeOptions = import('../photoswipe.js').PhotoSwipeOptions; export type PhotoSwipe = import('../photoswipe.js').default; +export type Point = import('../photoswipe.js').Point; export type SlideData = import('../slide/slide.js').SlideData; diff --git a/src/js/photoswipe.js b/src/js/photoswipe.js index 34e4a83e..d3c698cd 100644 --- a/src/js/photoswipe.js +++ b/src/js/photoswipe.js @@ -41,7 +41,6 @@ import ContentLoader from './slide/loader.js'; */ /** @typedef {{ x: number; y: number; id?: string | number }} Point */ -/** @typedef {{ x?: number; y?: number }} Size */ /** @typedef {{ top: number; bottom: number; left: number; right: number }} Padding */ /** @typedef {SlideData[]} DataSourceArray */ /** @typedef {{ gallery: HTMLElement; items?: HTMLElement[] }} DataSourceObject */ @@ -92,7 +91,7 @@ import ContentLoader from './slide/loader.js'; * @prop {Padding=} padding * Slide area padding (in pixels). * - * @prop {(viewportSize: Size, itemData: SlideData, index: number) => Padding} [paddingFn] + * @prop {(viewportSize: Point, itemData: SlideData, index: number) => Padding} [paddingFn] * The option is checked frequently, so make sure it's performant. Overrides padding option if defined. For example: * * @prop {number | false} [hideAnimationDuration] @@ -138,7 +137,7 @@ import ContentLoader from './slide/loader.js'; * @prop {string=} indexIndicatorSep * Used for slide count indicator ("1 of 10 "). * - * @prop {(options: PhotoSwipeOptions, pswp: PhotoSwipe) => { x: number; y: number }} [getViewportSizeFn] + * @prop {(options: PhotoSwipeOptions, pswp: PhotoSwipe) => Point} [getViewportSizeFn] * A function that should return slide viewport width and height, in format {x: 100, y: 100}. * * @prop {string=} errorMsg diff --git a/src/js/util/viewport-size.js b/src/js/util/viewport-size.js index 5657e8cb..759b0047 100644 --- a/src/js/util/viewport-size.js +++ b/src/js/util/viewport-size.js @@ -1,10 +1,12 @@ /** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */ /** @typedef {import('../photoswipe.js').default} PhotoSwipe */ +/** @typedef {import('../photoswipe.js').Point} Point */ /** @typedef {import('../slide/slide.js').SlideData} SlideData */ /** * @param {PhotoSwipeOptions} options * @param {PhotoSwipe} pswp + * @returns {Point} */ export function getViewportSize(options, pswp) { if (options.getViewportSizeFn) { @@ -55,14 +57,13 @@ export function getViewportSize(options, pswp) { * * @param {'left' | 'top' | 'bottom' | 'right'} prop * @param {PhotoSwipeOptions} options PhotoSwipe options - * @param {{ x?: number; y?: number }} viewportSize PhotoSwipe viewport size, for example: { x:800, y:600 } + * @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} */ export function parsePaddingOption(prop, options, viewportSize, itemData, index) { - /** @type {number} */ - let paddingValue; + let paddingValue = 0; if (options.paddingFn) { paddingValue = options.paddingFn(viewportSize, itemData, index)[prop]; @@ -77,14 +78,15 @@ export function parsePaddingOption(prop, options, viewportSize, itemData, index) } } - return paddingValue || 0; + return Number(paddingValue) || 0; } /** * @param {PhotoSwipeOptions} options - * @param {{ x?: number; y?: number }} viewportSize + * @param {Point} viewportSize * @param {SlideData} itemData * @param {number} index + * @returns {Point} */ export function getPanAreaSize(options, viewportSize, itemData, index) { return { From 586aa8e28b1e3370007efcbfafdecf71f0b9ef1c Mon Sep 17 00:00:00 2001 From: mishchuk Date: Mon, 26 Dec 2022 17:01:45 +1000 Subject: [PATCH 13/27] fix slide/slide types --- dist/types/photoswipe.d.ts | 5 +- dist/types/slide/slide.d.ts | 83 +++++++++++++++----------------- dist/types/slide/zoom-level.d.ts | 19 +++----- src/js/gestures/zoom-handler.js | 4 +- src/js/slide/slide.js | 68 +++++++++++++------------- src/js/slide/zoom-level.js | 10 ++-- 6 files changed, 90 insertions(+), 99 deletions(-) diff --git a/dist/types/photoswipe.d.ts b/dist/types/photoswipe.d.ts index ecda0a33..3a77e682 100644 --- a/dist/types/photoswipe.d.ts +++ b/dist/types/photoswipe.d.ts @@ -308,10 +308,7 @@ declare class PhotoSwipe extends PhotoSwipeBase { * * @param {Parameters} args */ - zoomTo(destZoomLevel: number, centerPoint: { - x?: number; - y?: number; - }, transitionDuration?: number | false, ignoreBounds?: boolean): void; + zoomTo(destZoomLevel: number, centerPoint?: Point, transitionDuration?: number | false, ignoreBounds?: boolean): void; /** * @see slide/slide.js toggleZoom */ diff --git a/dist/types/slide/slide.d.ts b/dist/types/slide/slide.d.ts index 8970659c..fbe55a3c 100644 --- a/dist/types/slide/slide.d.ts +++ b/dist/types/slide/slide.d.ts @@ -1,52 +1,52 @@ export default Slide; export type PhotoSwipe = import('../photoswipe.js').default; -export type PanAreaSize = import('./zoom-level').PanAreaSize; +export type Point = import('../photoswipe.js').Point; export type SlideData = _SlideData & Record; export type _SlideData = { /** * thumbnail element */ - element?: HTMLElement | undefined; + element?: HTMLElement; /** * image URL */ - src?: string | undefined; + src?: string; /** * image srcset */ - srcset?: string | undefined; + srcset?: string; /** * image width (deprecated) */ - w?: number | undefined; + w?: number; /** * image height (deprecated) */ - h?: number | undefined; + h?: number; /** * image width */ - width?: number | undefined; + width?: number; /** * image height */ - height?: number | undefined; + height?: number; /** * placeholder image URL that's displayed before large image is loaded */ - msrc?: string | undefined; + msrc?: string; /** * image alt text */ - alt?: string | undefined; + alt?: string; /** * whether thumbnail is cropped client-side or not */ - thumbCropped?: boolean | undefined; + thumbCropped?: boolean; /** * html content of a slide */ - html?: string | undefined; + html?: string; /** * slide type */ @@ -67,21 +67,22 @@ declare class Slide { pswp: import("../photoswipe.js").default; isActive: boolean; currentResolution: number; - /** @type {PanAreaSize | null} */ - panAreaSize: PanAreaSize | null; + /** @type {Point | null} */ + panAreaSize: Point | null; isFirstSlide: boolean; zoomLevels: ZoomLevel; - pan: { - x: number; - y: number; - }; + /** @type {Point} */ + pan: Point; content: import("./content.js").default; container: HTMLDivElement; + /** @type {HTMLElement | null} */ + holderElement: HTMLElement | null; currZoomLevel: number; /** @type {number} */ width: number; /** @type {number} */ height: number; + heavyAppended: boolean; bounds: PanBounds; prevDisplayedWidth: number; prevDisplayedHeight: number; @@ -97,7 +98,6 @@ declare class Slide { * @param {HTMLElement} holderElement */ append(holderElement: HTMLElement): void; - holderElement: HTMLElement; load(): void; /** * Append "heavy" DOM elements @@ -106,7 +106,6 @@ declare class Slide { * but generally these are large images. */ appendHeavy(): void; - heavyAppended: boolean; /** * Triggered when this slide is active (selected). * @@ -130,36 +129,30 @@ declare class Slide { * Apply size to current slide content, * based on the current resolution and scale. * - * @param {boolean=} force if size should be updated even if dimensions weren't changed + * @param {boolean} [force] if size should be updated even if dimensions weren't changed */ - updateContentSize(force?: boolean | undefined): void; + updateContentSize(force?: boolean): void; /** * @param {number} width * @param {number} height */ sizeChanged(width: number, height: number): boolean; - getPlaceholderElement(): HTMLDivElement | HTMLImageElement; + /** @returns {HTMLImageElement | HTMLDivElement | null | undefined} */ + getPlaceholderElement(): HTMLImageElement | HTMLDivElement | null | undefined; /** * Zoom current slide image to... * * @param {number} destZoomLevel Destination zoom level. - * @param {{ x?: number; y?: number }} centerPoint + * @param {Point} [centerPoint] * Transform origin center point, or false if viewport center should be used. * @param {number | false} [transitionDuration] Transition duration, may be set to 0. - * @param {boolean=} ignoreBounds Minimum and maximum zoom levels will be ignored. - * @return {boolean=} Returns true if animated. + * @param {boolean} [ignoreBounds] Minimum and maximum zoom levels will be ignored. */ - zoomTo(destZoomLevel: number, centerPoint: { - x?: number; - y?: number; - }, transitionDuration?: number | false, ignoreBounds?: boolean | undefined): boolean | undefined; + zoomTo(destZoomLevel: number, centerPoint?: Point, transitionDuration?: number | false, ignoreBounds?: boolean): void; /** - * @param {{ x?: number, y?: number }} [centerPoint] + * @param {Point} [centerPoint] */ - toggleZoom(centerPoint?: { - x?: number; - y?: number; - }): void; + toggleZoom(centerPoint?: Point): void; /** * Updates zoom level property and recalculates new pan bounds, * unlike zoomTo it does not apply transform (use applyCurrentZoomPan) @@ -174,15 +167,13 @@ declare class Slide { * pan bounds according to the new zoom level. * * @param {'x' | 'y'} axis - * @param {{ x?: number; y?: number }} [point] + * @param {Point} [point] * point based on which zoom is performed, usually refers to the current mouse position, * if false - viewport center will be used. - * @param {number=} prevZoomLevel Zoom level before new zoom was applied. + * @param {number} [prevZoomLevel] Zoom level before new zoom was applied. + * @returns {number} */ - calculateZoomToPanOffset(axis: 'x' | 'y', point?: { - x?: number; - y?: number; - }, prevZoomLevel?: number | undefined): number; + calculateZoomToPanOffset(axis: 'x' | 'y', point?: Point, prevZoomLevel?: number): number; /** * Apply pan and keep it within bounds. * @@ -192,10 +183,12 @@ declare class Slide { panTo(panX: number, panY: number): void; /** * If the slide in the current state can be panned by the user + * @returns {boolean} */ isPannable(): boolean; /** * If the slide can be zoomed + * @returns {boolean} */ isZoomable(): boolean; /** @@ -210,9 +203,11 @@ declare class Slide { * @param {number} x * @param {number} y * @param {number} zoom + * @private */ - _applyZoomTransform(x: number, y: number, zoom: number): void; + private _applyZoomTransform; calculateSize(): void; + /** @returns {string} */ getCurrentTransform(): string; /** * Set resolution and re-render the image. @@ -224,12 +219,12 @@ declare class Slide { * the same as image with zoom level 1 and resolution 1. * * Used to optimize animations and make - * sure that browser renders image in highest quality. + * sure that browser renders image in the highest quality. * Also used by responsive images to load the correct one. * * @param {number} newResolution */ - _setResolution(newResolution: number): void; + setResolution(newResolution: number): void; } import ZoomLevel from "./zoom-level.js"; import PanBounds from "./pan-bounds.js"; diff --git a/dist/types/slide/zoom-level.d.ts b/dist/types/slide/zoom-level.d.ts index 4bf1f3b5..0e2f3610 100644 --- a/dist/types/slide/zoom-level.d.ts +++ b/dist/types/slide/zoom-level.d.ts @@ -1,17 +1,14 @@ export default ZoomLevel; export type PhotoSwipe = import('../photoswipe.js').default; export type PhotoSwipeOptions = import('../photoswipe.js').PhotoSwipeOptions; +export type Point = import('../photoswipe.js').Point; export type SlideData = import('../slide/slide.js').SlideData; export type ZoomLevelOption = number | "fit" | "fill" | ((zoomLevelObject: ZoomLevel) => number); -export type PanAreaSize = { - x: number; - y: number; -}; /** @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 */ -/** @typedef {{ x: number; y: number }} PanAreaSize */ /** * Calculates zoom levels for specific slide. * Depends on viewport size and image size. @@ -28,10 +25,10 @@ declare class ZoomLevel { options: import("../photoswipe.js").PhotoSwipeOptions; itemData: import("../slide/slide.js").SlideData; index: number; - /** @type { PanAreaSize | null } */ - panAreaSize: PanAreaSize | null; - /** @type { PanAreaSize | null } */ - elementSize: PanAreaSize | null; + /** @type { Point | null } */ + panAreaSize: Point | null; + /** @type { Point | null } */ + elementSize: Point | null; fit: number; fill: number; vFill: number; @@ -46,9 +43,9 @@ declare class ZoomLevel { * * @param {number} maxWidth * @param {number} maxHeight - * @param {PanAreaSize} panAreaSize + * @param {Point} panAreaSize */ - update(maxWidth: number, maxHeight: number, panAreaSize: PanAreaSize): void; + update(maxWidth: number, maxHeight: number, panAreaSize: Point): void; /** * Parses user-defined zoom option. * diff --git a/src/js/gestures/zoom-handler.js b/src/js/gestures/zoom-handler.js index c21fc29e..b96532e3 100644 --- a/src/js/gestures/zoom-handler.js +++ b/src/js/gestures/zoom-handler.js @@ -191,7 +191,7 @@ class ZoomHandler { if (!panNeedsChange && !currZoomLevelNeedsChange && !restoreBgOpacity) { // update resolution after gesture - currSlide._setResolution(destinationZoomLevel); + currSlide.setResolution(destinationZoomLevel); currSlide.applyCurrentZoomPan(); // nothing to animate @@ -237,7 +237,7 @@ class ZoomHandler { }, onComplete: () => { // update resolution after transition ends - currSlide._setResolution(destinationZoomLevel); + currSlide.setResolution(destinationZoomLevel); currSlide.applyCurrentZoomPan(); } }); diff --git a/src/js/slide/slide.js b/src/js/slide/slide.js index ac059bb2..02a6aa16 100644 --- a/src/js/slide/slide.js +++ b/src/js/slide/slide.js @@ -1,20 +1,20 @@ /** @typedef {import('../photoswipe.js').default} PhotoSwipe */ -/** @typedef {import('./zoom-level').PanAreaSize} PanAreaSize */ +/** @typedef {import('../photoswipe.js').Point} Point */ /** * @typedef {_SlideData & Record} SlideData * @typedef {Object} _SlideData - * @prop {HTMLElement=} element thumbnail element - * @prop {string=} src image URL - * @prop {string=} srcset image srcset - * @prop {number=} w image width (deprecated) - * @prop {number=} h image height (deprecated) - * @prop {number=} width image width - * @prop {number=} height image height - * @prop {string=} msrc placeholder image URL that's displayed before large image is loaded - * @prop {string=} alt image alt text - * @prop {boolean=} thumbCropped whether thumbnail is cropped client-side or not - * @prop {string=} html html content of a slide + * @prop {HTMLElement} [element] thumbnail element + * @prop {string} [src] image URL + * @prop {string} [srcset] image srcset + * @prop {number} [w] image width (deprecated) + * @prop {number} [h] image height (deprecated) + * @prop {number} [width] image width + * @prop {number} [height] image height + * @prop {string} [msrc] placeholder image URL that's displayed before large image is loaded + * @prop {string} [alt] image alt text + * @prop {boolean} [thumbCropped] whether thumbnail is cropped client-side or not + * @prop {string} [html] html content of a slide * @prop {'image' | 'html' | string} [type] slide type */ @@ -46,7 +46,7 @@ class Slide { this.pswp = pswp; this.isActive = (index === pswp.currIndex); this.currentResolution = 0; - /** @type {PanAreaSize | null} */ + /** @type {Point | null} */ this.panAreaSize = null; this.isFirstSlide = (this.isActive && !pswp.opener.isOpen); @@ -58,21 +58,20 @@ class Slide { data: this.data, index }); - - this.pan = { - x: 0, - y: 0 - }; + /** @type {Point} */ + this.pan = { x: 0, y: 0 }; this.content = this.pswp.contentLoader.getContentBySlide(this); this.container = createElement('pswp__zoom-wrap', 'div'); + /** @type {HTMLElement | null} */ + this.holderElement = null; this.currZoomLevel = 1; /** @type {number} */ this.width = this.content.width; /** @type {number} */ this.height = this.content.height; - + this.heavyAppended = false; this.bounds = new PanBounds(this); this.prevDisplayedWidth = -1; @@ -117,7 +116,7 @@ class Slide { this.updateContentSize(); this.appendHeavy(); - this.holderElement.appendChild(this.container); + holderElement.appendChild(this.container); this.zoomAndPanToInitial(); @@ -237,7 +236,7 @@ class Slide { * Apply size to current slide content, * based on the current resolution and scale. * - * @param {boolean=} force if size should be updated even if dimensions weren't changed + * @param {boolean} [force] if size should be updated even if dimensions weren't changed */ updateContentSize(force) { // Use initial zoom level @@ -272,21 +271,19 @@ class Slide { return false; } + /** @returns {HTMLImageElement | HTMLDivElement | null | undefined} */ getPlaceholderElement() { - if (this.content.placeholder) { - return this.content.placeholder.element; - } + return this.content.placeholder?.element; } /** * Zoom current slide image to... * * @param {number} destZoomLevel Destination zoom level. - * @param {{ x?: number; y?: number }} centerPoint + * @param {Point} [centerPoint] * Transform origin center point, or false if viewport center should be used. * @param {number | false} [transitionDuration] Transition duration, may be set to 0. - * @param {boolean=} ignoreBounds Minimum and maximum zoom levels will be ignored. - * @return {boolean=} Returns true if animated. + * @param {boolean} [ignoreBounds] Minimum and maximum zoom levels will be ignored. */ zoomTo(destZoomLevel, centerPoint, transitionDuration, ignoreBounds) { const { pswp } = this; @@ -322,7 +319,7 @@ class Slide { roundPoint(this.pan); const finishTransition = () => { - this._setResolution(destZoomLevel); + this.setResolution(destZoomLevel); this.applyCurrentZoomPan(); }; @@ -342,7 +339,7 @@ class Slide { } /** - * @param {{ x?: number, y?: number }} [centerPoint] + * @param {Point} [centerPoint] */ toggleZoom(centerPoint) { this.zoomTo( @@ -371,10 +368,11 @@ class Slide { * pan bounds according to the new zoom level. * * @param {'x' | 'y'} axis - * @param {{ x?: number; y?: number }} [point] + * @param {Point} [point] * point based on which zoom is performed, usually refers to the current mouse position, * if false - viewport center will be used. - * @param {number=} prevZoomLevel Zoom level before new zoom was applied. + * @param {number} [prevZoomLevel] Zoom level before new zoom was applied. + * @returns {number} */ calculateZoomToPanOffset(axis, point, prevZoomLevel) { const totalPanDistance = this.bounds.max[axis] - this.bounds.min[axis]; @@ -407,6 +405,7 @@ class Slide { /** * If the slide in the current state can be panned by the user + * @returns {boolean} */ isPannable() { return this.width && (this.currZoomLevel > this.zoomLevels.fit); @@ -414,6 +413,7 @@ class Slide { /** * If the slide can be zoomed + * @returns {boolean} */ isZoomable() { return this.width && this.content.isZoomable(); @@ -445,6 +445,7 @@ class Slide { * @param {number} x * @param {number} y * @param {number} zoom + * @private */ _applyZoomTransform(x, y, zoom) { zoom /= this.currentResolution || this.zoomLevels.initial; @@ -468,6 +469,7 @@ class Slide { }); } + /** @returns {string} */ getCurrentTransform() { const scale = this.currZoomLevel / (this.currentResolution || this.zoomLevels.initial); return toTransformString(this.pan.x, this.pan.y, scale); @@ -483,12 +485,12 @@ class Slide { * the same as image with zoom level 1 and resolution 1. * * Used to optimize animations and make - * sure that browser renders image in highest quality. + * sure that browser renders image in the highest quality. * Also used by responsive images to load the correct one. * * @param {number} newResolution */ - _setResolution(newResolution) { + setResolution(newResolution) { if (newResolution === this.currentResolution) { return; } diff --git a/src/js/slide/zoom-level.js b/src/js/slide/zoom-level.js index 2b541fc7..45630eb2 100644 --- a/src/js/slide/zoom-level.js +++ b/src/js/slide/zoom-level.js @@ -2,10 +2,10 @@ 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 */ -/** @typedef {{ x: number; y: number }} PanAreaSize */ /** * Calculates zoom levels for specific slide. @@ -23,9 +23,9 @@ class ZoomLevel { this.options = options; this.itemData = itemData; this.index = index; - /** @type { PanAreaSize | null } */ + /** @type { Point | null } */ this.panAreaSize = null; - /** @type { PanAreaSize | null } */ + /** @type { Point | null } */ this.elementSize = null; this.fit = 1; this.fill = 1; @@ -43,10 +43,10 @@ class ZoomLevel { * * @param {number} maxWidth * @param {number} maxHeight - * @param {PanAreaSize} panAreaSize + * @param {Point} panAreaSize */ update(maxWidth, maxHeight, panAreaSize) { - /** @type {PanAreaSize} */ + /** @type {Point} */ const elementSize = { x: maxWidth, y: maxHeight }; this.elementSize = elementSize; this.panAreaSize = panAreaSize; From 941e957d8b982a53235766df9a978ee0a50264d3 Mon Sep 17 00:00:00 2001 From: mishchuk Date: Mon, 26 Dec 2022 18:12:42 +1000 Subject: [PATCH 14/27] fix gestures/gestures types --- dist/types/gestures/gestures.d.ts | 78 ++++++++++++++++++++----------- src/js/gestures/gestures.js | 74 +++++++++++++++++++---------- 2 files changed, 102 insertions(+), 50 deletions(-) diff --git a/dist/types/gestures/gestures.d.ts b/dist/types/gestures/gestures.d.ts index 8b2b4960..513962f5 100644 --- a/dist/types/gestures/gestures.d.ts +++ b/dist/types/gestures/gestures.d.ts @@ -14,8 +14,8 @@ declare class Gestures { */ constructor(pswp: PhotoSwipe); pswp: import("../photoswipe.js").default; - /** @type {'x' | 'y'} */ - dragAxis: 'x' | 'y'; + /** @type {'x' | 'y' | null} */ + dragAxis: 'x' | 'y' | null; /** @type {Point} */ p1: Point; /** @type {Point} */ @@ -30,41 +30,59 @@ declare class Gestures { startP2: Point; /** @type {Point} */ velocity: Point; - /** @type {Point} */ - _lastStartP1: Point; - /** @type {Point} */ - _intervalP1: Point; - _numActivePoints: number; - /** @type {Point[]} */ - _ongoingPointers: Point[]; - _touchEventEnabled: boolean; - _pointerEventEnabled: boolean; + /** @type {Point} + * @private + */ + private _lastStartP1; + /** @type {Point} + * @private + */ + private _intervalP1; + /** @private */ + private _numActivePoints; + /** @type {Point[]} + * @private + */ + private _ongoingPointers; + /** @private */ + private _touchEventEnabled; + /** @private */ + private _pointerEventEnabled; supportsTouch: boolean; + /** @private */ + private _intervalTime; + /** @private */ + private _velocityCalculated; + isMultitouch: boolean; + isDragging: boolean; + isZooming: boolean; + /** @type {number | null} + * @private + */ + private _raf; + /** @type {NodeJS.Timeout | null} + * @private + */ + private _tapTimer; drag: DragHandler; zoomLevels: ZoomHandler; tapHandler: TapHandler; /** - * + * @private * @param {'mouse' | 'touch' | 'pointer'} pref * @param {'down' | 'start'} down * @param {'up' | 'end'} up * @param {'cancel'} [cancel] */ - _bindEvents(pref: 'mouse' | 'touch' | 'pointer', down: 'down' | 'start', up: 'up' | 'end', cancel?: 'cancel'): void; + private _bindEvents; /** * @param {PointerEvent} e */ onPointerDown(e: PointerEvent): void; - pointerDown: boolean; - isMultitouch: boolean; /** * @param {PointerEvent} e */ onPointerMove(e: PointerEvent): void; - isZooming: boolean; - isDragging: boolean; - _intervalTime: number; - _velocityCalculated: boolean; /** * @private */ @@ -77,19 +95,18 @@ declare class Gestures { * @private */ private _rafRenderLoop; - raf: number; /** * Update velocity at 50ms interval * - * @param {boolean=} force + * @private + * @param {boolean} [force] */ - _updateVelocity(force?: boolean | undefined): void; + private _updateVelocity; /** * @private * @param {PointerEvent} e */ private _finishTap; - _tapTimer: NodeJS.Timeout; /** * @private */ @@ -100,6 +117,7 @@ declare class Gestures { * @private * @param {'x' | 'y'} axis * @param {number} duration + * @returns {number} */ private _getVelocity; /** @@ -120,9 +138,16 @@ declare class Gestures { * @param {'up' | 'down' | 'move'} pointerType Normalized pointer type */ private _updatePoints; - _updatePrevPoints(): void; - _updateStartPoints(): void; - _calculateDragDirection(): void; + /** update points that were used during previous rAF tick + * @private + */ + private _updatePrevPoints; + /** update points at the start of gesture + * @private + */ + private _updateStartPoints; + /** @private */ + private _calculateDragDirection; /** * Converts touch, pointer or mouse event * to PhotoSwipe point. @@ -130,6 +155,7 @@ declare class Gestures { * @private * @param {Touch | PointerEvent} e * @param {Point} p + * @returns {Point} */ private _convertEventPosToPoint; /** diff --git a/src/js/gestures/gestures.js b/src/js/gestures/gestures.js index 7fa5eb30..37d6f608 100644 --- a/src/js/gestures/gestures.js +++ b/src/js/gestures/gestures.js @@ -31,8 +31,8 @@ class Gestures { constructor(pswp) { this.pswp = pswp; - /** @type {'x' | 'y'} */ - this.dragAxis = undefined; + /** @type {'x' | 'y' | null} */ + this.dragAxis = null; // point objects are defined once and reused // PhotoSwipe keeps track only of two pointers, others are ignored @@ -51,18 +51,43 @@ class Gestures { /** @type {Point} */ this.velocity = { x: 0, y: 0 }; - /** @type {Point} */ + /** @type {Point} + * @private + */ this._lastStartP1 = { x: 0, y: 0 }; - /** @type {Point} */ + /** @type {Point} + * @private + */ this._intervalP1 = { x: 0, y: 0 }; + /** @private */ this._numActivePoints = 0; - /** @type {Point[]} */ + /** @type {Point[]} + * @private + */ this._ongoingPointers = []; - + /** @private */ this._touchEventEnabled = 'ontouchstart' in window; + /** @private */ this._pointerEventEnabled = !!(window.PointerEvent); this.supportsTouch = this._touchEventEnabled || (this._pointerEventEnabled && navigator.maxTouchPoints > 1); + /** @private */ + this._numActivePoints = 0; + /** @private */ + this._intervalTime = 0; + /** @private */ + this._velocityCalculated = false; + this.isMultitouch = false; + this.isDragging = false; + this.isZooming = false; + /** @type {number | null} + * @private + */ + this._raf = null; + /** @type {NodeJS.Timeout | null} + * @private + */ + this._tapTimer = null; if (!this.supportsTouch) { // disable pan to next slide for non-touch devices @@ -102,7 +127,7 @@ class Gestures { } /** - * + * @private * @param {'mouse' | 'touch' | 'pointer'} pref * @param {'down' | 'start'} down * @param {'up' | 'end'} up @@ -140,10 +165,7 @@ class Gestures { // // Desktop Safari allows to drag images when preventDefault isn't called on mousedown, // even though preventDefault IS called on mousemove. That's why we preventDefault mousedown. - let isMousePointer; - if (e.type === 'mousedown' || e.pointerType === 'mouse') { - isMousePointer = true; - } + const isMousePointer = e.type === 'mousedown' || e.pointerType === 'mouse'; // Allow dragging only via left mouse button. // http://www.quirksmode.org/js/events_properties.html @@ -176,8 +198,6 @@ class Gestures { this._updatePoints(e, 'down'); - this.pointerDown = true; - if (this._numActivePoints === 1) { this.dragAxis = null; // we need to store initial point to determine the main axis, @@ -286,7 +306,6 @@ class Gestures { } if (this._numActivePoints === 0) { - this.pointerDown = false; this._rafStopLoop(); if (this.isDragging) { @@ -329,14 +348,15 @@ class Gestures { } this._updatePrevPoints(); - this.raf = requestAnimationFrame(this._rafRenderLoop.bind(this)); + this._raf = requestAnimationFrame(this._rafRenderLoop.bind(this)); } } /** * Update velocity at 50ms interval * - * @param {boolean=} force + * @private + * @param {boolean} [force] */ _updateVelocity(force) { const time = Date.now(); @@ -418,6 +438,7 @@ class Gestures { * @private * @param {'x' | 'y'} axis * @param {number} duration + * @returns {number} */ _getVelocity(axis, duration) { // displacement is like distance, but can be negative. @@ -434,9 +455,9 @@ class Gestures { * @private */ _rafStopLoop() { - if (this.raf) { - cancelAnimationFrame(this.raf); - this.raf = null; + if (this._raf) { + cancelAnimationFrame(this._raf); + this._raf = null; } } @@ -448,7 +469,6 @@ class Gestures { // TODO find a way to disable e.preventDefault on some elements // via event or some class or something e.preventDefault(); - return true; } /** @@ -463,8 +483,8 @@ class Gestures { if (this._pointerEventEnabled) { const pointerEvent = /** @type {PointerEvent} */ (e); // Try to find the current pointer in ongoing pointers by its ID - const pointerIndex = this._ongoingPointers.findIndex((ongoingPoiner) => { - return ongoingPoiner.id === pointerEvent.pointerId; + const pointerIndex = this._ongoingPointers.findIndex((ongoingPointer) => { + return ongoingPointer.id === pointerEvent.pointerId; }); if (pointerType === 'up' && pointerIndex > -1) { @@ -517,19 +537,24 @@ class Gestures { } } - // update points that were used during previous rAF tick + /** update points that were used during previous rAF tick + * @private + */ _updatePrevPoints() { equalizePoints(this.prevP1, this.p1); equalizePoints(this.prevP2, this.p2); } - // update points at the start of gesture + /** update points at the start of gesture + * @private + */ _updateStartPoints() { equalizePoints(this.startP1, this.p1); equalizePoints(this.startP2, this.p2); this._updatePrevPoints(); } + /** @private */ _calculateDragDirection() { if (this.pswp.mainScroll.isShifted()) { // if main scroll position is shifted – direction is always horizontal @@ -556,6 +581,7 @@ class Gestures { * @private * @param {Touch | PointerEvent} e * @param {Point} p + * @returns {Point} */ _convertEventPosToPoint(e, p) { p.x = e.pageX - this.pswp.offset.x; From ac6edbffb880b891282f2bec585503c844a06d56 Mon Sep 17 00:00:00 2001 From: mishchuk Date: Mon, 26 Dec 2022 18:27:59 +1000 Subject: [PATCH 15/27] fix gestures/drag-handler types --- dist/types/gestures/drag-handler.d.ts | 5 +++-- src/js/gestures/drag-handler.js | 29 +++++++++++++++------------ 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/dist/types/gestures/drag-handler.d.ts b/dist/types/gestures/drag-handler.d.ts index cd9f6266..5152eea6 100644 --- a/dist/types/gestures/drag-handler.d.ts +++ b/dist/types/gestures/drag-handler.d.ts @@ -10,7 +10,6 @@ declare class DragHandler { */ constructor(gestures: Gestures); gestures: import("./gestures.js").default; - pswp: import("../photoswipe.js").default; /** @type {Point} */ startPan: Point; start(): void; @@ -29,6 +28,7 @@ declare class DragHandler { * * @private * @param {'x' | 'y'} axis + * @returns {boolean} */ private _panOrMoveMainScroll; /** @@ -40,6 +40,7 @@ declare class DragHandler { * * @private * @param {number} panY The current pan Y position. + * @returns {number} */ private _getVerticalDragRatio; /** @@ -50,7 +51,7 @@ declare class DragHandler { * @private * @param {'x' | 'y'} axis * @param {number} potentialPan - * @param {number=} customFriction (0.1 - 1) + * @param {number} [customFriction] (0.1 - 1) */ private _setPanWithFriction; } diff --git a/src/js/gestures/drag-handler.js b/src/js/gestures/drag-handler.js index 29e192e1..4f4ea748 100644 --- a/src/js/gestures/drag-handler.js +++ b/src/js/gestures/drag-handler.js @@ -18,6 +18,7 @@ const MIN_NEXT_SLIDE_SPEED = 0.5; /** * @param {number} initialVelocity * @param {number} decelerationRate + * @returns {number} */ function project(initialVelocity, decelerationRate) { return initialVelocity * decelerationRate / (1 - decelerationRate); @@ -32,14 +33,13 @@ class DragHandler { */ constructor(gestures) { this.gestures = gestures; - this.pswp = gestures.pswp; /** @type {Point} */ this.startPan = { x: 0, y: 0 }; } start() { - equalizePoints(this.startPan, this.pswp.currSlide.pan); - this.pswp.animations.stopAll(); + equalizePoints(this.startPan, this.gestures.pswp.currSlide.pan); + this.gestures.pswp.animations.stopAll(); } change() { @@ -89,7 +89,7 @@ class DragHandler { // Go next slide. // - // - if velocity and its direction is matched + // - if velocity and its direction is matched, // and we see at least tiny part of the next slide // // - or if we see less than 50% of the current slide @@ -129,15 +129,14 @@ class DragHandler { * @param {'x' | 'y'} axis */ _finishPanGestureForAxis(axis) { - const { pswp } = this; + const { pswp, velocity } = this.gestures; const { currSlide } = pswp; - const { velocity } = this.gestures; const { pan, bounds } = currSlide; const panPos = pan[axis]; const restoreBgOpacity = (pswp.bgOpacity < 1 && axis === 'y'); // 0.995 means - scroll view loses 0.5% of its velocity per millisecond - // Inceasing this number will reduce travel distance + // Increasing this number will reduce travel distance const decelerationRate = 0.995; // 0.99 // Pan position if there is no bounds @@ -208,6 +207,7 @@ class DragHandler { * * @private * @param {'x' | 'y'} axis + * @returns {boolean} */ _panOrMoveMainScroll(axis) { const { p1, pswp, dragAxis, prevP1, isMultitouch } = this.gestures; @@ -216,7 +216,7 @@ class DragHandler { const newMainScrollX = mainScroll.x + delta; if (!delta) { - return; + return false; } // Always move main scroll if image can not be panned @@ -295,8 +295,10 @@ class DragHandler { this._setPanWithFriction(axis, newPan); } } + + return false; } - // + // If we move above - the ratio is negative // If we move below the ratio is positive @@ -309,10 +311,11 @@ class DragHandler { * * @private * @param {number} panY The current pan Y position. + * @returns {number} */ _getVerticalDragRatio(panY) { - return (panY - this.pswp.currSlide.bounds.center.y) - / (this.pswp.viewportSize.y / 3); + return (panY - this.gestures.pswp.currSlide.bounds.center.y) + / (this.gestures.pswp.viewportSize.y / 3); } /** @@ -323,10 +326,10 @@ class DragHandler { * @private * @param {'x' | 'y'} axis * @param {number} potentialPan - * @param {number=} customFriction (0.1 - 1) + * @param {number} [customFriction] (0.1 - 1) */ _setPanWithFriction(axis, potentialPan, customFriction) { - const { pan, bounds } = this.pswp.currSlide; + const { pan, bounds } = this.gestures.pswp.currSlide; const correctedPan = bounds.correctPan(axis, potentialPan); // If we are out of pan bounds if (correctedPan !== potentialPan || customFriction) { From ca41582fdce738e2b2911407f14a0217ad2454e3 Mon Sep 17 00:00:00 2001 From: mishchuk Date: Mon, 26 Dec 2022 18:34:49 +1000 Subject: [PATCH 16/27] fix gestures/tap-handler types --- dist/types/gestures/tap-handler.d.ts | 3 ++- src/js/gestures/tap-handler.js | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/dist/types/gestures/tap-handler.d.ts b/dist/types/gestures/tap-handler.d.ts index 6148b497..74f2cdcc 100644 --- a/dist/types/gestures/tap-handler.d.ts +++ b/dist/types/gestures/tap-handler.d.ts @@ -31,9 +31,10 @@ declare class TapHandler { */ doubleTap(point: Point, originalEvent: PointerEvent): void; /** + * @private * @param {Actions} actionName * @param {Point} point * @param {PointerEvent} originalEvent */ - _doClickOrTapAction(actionName: Actions, point: Point, originalEvent: PointerEvent): void; + private _doClickOrTapAction; } diff --git a/src/js/gestures/tap-handler.js b/src/js/gestures/tap-handler.js index db98c7c1..c5227440 100644 --- a/src/js/gestures/tap-handler.js +++ b/src/js/gestures/tap-handler.js @@ -1,6 +1,5 @@ /** - * @template T - * @template P + * @template T, P * @typedef {import('../types.js').AddPostfix} AddPostfix */ @@ -14,6 +13,7 @@ * (rather than controls or caption). * * @param {PointerEvent} event + * @returns {boolean} */ function didTapOnMainContent(event) { return !!(/** @type {HTMLElement} */ (event.target).closest('.pswp__container')); @@ -68,6 +68,7 @@ class TapHandler { } /** + * @private * @param {Actions} actionName * @param {Point} point * @param {PointerEvent} originalEvent From 29b631bc0b82777634c10786a18d2ae0386dc2db Mon Sep 17 00:00:00 2001 From: mishchuk Date: Mon, 26 Dec 2022 19:05:06 +1000 Subject: [PATCH 17/27] fix gestures/zoom-handler types --- dist/types/gestures/zoom-handler.d.ts | 33 ++++++++++++++------- src/js/gestures/zoom-handler.js | 41 +++++++++++++++++---------- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/dist/types/gestures/zoom-handler.d.ts b/dist/types/gestures/zoom-handler.d.ts index b25bfb61..90f9e106 100644 --- a/dist/types/gestures/zoom-handler.d.ts +++ b/dist/types/gestures/zoom-handler.d.ts @@ -7,22 +7,33 @@ declare class ZoomHandler { */ constructor(gestures: Gestures); gestures: import("./gestures.js").default; - pswp: import("../photoswipe.js").default; - /** @type {Point} */ - _startPan: Point; - /** @type {Point} */ - _startZoomPoint: Point; - /** @type {Point} */ - _zoomPoint: Point; + /** + * @private + * @type {Point} + */ + private _startPan; + /** + * @private + * @type {Point} + */ + private _startZoomPoint; + /** + * @private + * @type {Point} + */ + private _zoomPoint; + /** @private */ + private _wasOverFitZoomLevel; + /** @private */ + private _startZoomLevel; start(): void; - _startZoomLevel: number; - _wasOverFitZoomLevel: boolean; change(): void; end(): void; /** * @private * @param {'x' | 'y'} axis * @param {number} currZoomLevel + * @returns {number} */ private _calculatePanForZoomLevel; /** @@ -30,8 +41,8 @@ declare class ZoomHandler { * beyond minimum or maximum values. * With animation. * - * @param {boolean=} ignoreGesture + * @param {boolean} [ignoreGesture] * Wether gesture coordinates should be ignored when calculating destination pan position. */ - correctZoomPan(ignoreGesture?: boolean | undefined): void; + correctZoomPan(ignoreGesture?: boolean): void; } diff --git a/src/js/gestures/zoom-handler.js b/src/js/gestures/zoom-handler.js index b96532e3..c146acbf 100644 --- a/src/js/gestures/zoom-handler.js +++ b/src/js/gestures/zoom-handler.js @@ -15,6 +15,7 @@ const LOWER_ZOOM_FRICTION = 0.15; * @param {Point} p * @param {Point} p1 * @param {Point} p2 + * @returns {Point} */ function getZoomPointsCenter(p, p1, p2) { p.x = (p1.x + p2.x) / 2; @@ -28,19 +29,31 @@ class ZoomHandler { */ constructor(gestures) { this.gestures = gestures; - this.pswp = this.gestures.pswp; - /** @type {Point} */ + /** + * @private + * @type {Point} + */ this._startPan = { x: 0, y: 0 }; - /** @type {Point} */ + /** + * @private + * @type {Point} + */ this._startZoomPoint = { x: 0, y: 0 }; - /** @type {Point} */ + /** + * @private + * @type {Point} + */ this._zoomPoint = { x: 0, y: 0 }; + /** @private */ + this._wasOverFitZoomLevel = false; + /** @private */ + this._startZoomLevel = 1; } start() { - this._startZoomLevel = this.pswp.currSlide.currZoomLevel; - equalizePoints(this._startPan, this.pswp.currSlide.pan); - this.pswp.animations.stopAllPan(); + this._startZoomLevel = this.gestures.pswp.currSlide.currZoomLevel; + equalizePoints(this._startPan, this.gestures.pswp.currSlide.pan); + this.gestures.pswp.animations.stopAllPan(); this._wasOverFitZoomLevel = false; } @@ -92,7 +105,7 @@ class ZoomHandler { } end() { - const { pswp } = this; + const { pswp } = this.gestures; const { currSlide } = pswp; if (currSlide.currZoomLevel < currSlide.zoomLevels.initial && !this._wasOverFitZoomLevel @@ -107,6 +120,7 @@ class ZoomHandler { * @private * @param {'x' | 'y'} axis * @param {number} currZoomLevel + * @returns {number} */ _calculatePanForZoomLevel(axis, currZoomLevel) { const zoomFactor = currZoomLevel / this._startZoomLevel; @@ -119,18 +133,18 @@ class ZoomHandler { * beyond minimum or maximum values. * With animation. * - * @param {boolean=} ignoreGesture + * @param {boolean} [ignoreGesture] * Wether gesture coordinates should be ignored when calculating destination pan position. */ correctZoomPan(ignoreGesture) { - const { pswp } = this; + const { pswp } = this.gestures; const { currSlide } = pswp; if (!currSlide.isZoomable()) { return; } - if (this._zoomPoint.x === undefined) { + if (this._zoomPoint.x === 0) { ignoreGesture = true; } @@ -184,10 +198,7 @@ class ZoomHandler { // return zoom level and its bounds to initial currSlide.setZoomLevel(prevZoomLevel); - let panNeedsChange = true; - if (pointsEqual(destinationPan, initialPan)) { - panNeedsChange = false; - } + const panNeedsChange = !pointsEqual(destinationPan, initialPan); if (!panNeedsChange && !currZoomLevelNeedsChange && !restoreBgOpacity) { // update resolution after gesture From 6ef5279d82334d5ce1c504195da468873bcbfd30 Mon Sep 17 00:00:00 2001 From: mishchuk Date: Tue, 27 Dec 2022 13:09:18 +1000 Subject: [PATCH 18/27] fix ui/ui-element types --- dist/types/ui/ui-element.d.ts | 16 ++++++++-------- src/js/ui/ui-element.js | 32 ++++++++++++++------------------ 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/dist/types/ui/ui-element.d.ts b/dist/types/ui/ui-element.d.ts index c9ea01cf..2f1cede3 100644 --- a/dist/types/ui/ui-element.d.ts +++ b/dist/types/ui/ui-element.d.ts @@ -5,23 +5,23 @@ export type PhotoSwipe = import('../photoswipe.js').default; */ export type Methods = import('../types.js').Methods; export type UIElementMarkupProps = { - isCustomSVG?: boolean | undefined; + isCustomSVG?: boolean; inner: string; - outlineID?: string | undefined; + outlineID?: string; size?: number | string; }; export type UIElementData = { name?: DefaultUIElements | string; - className?: string | undefined; - html?: UIElementMarkup | undefined; - isButton?: boolean | undefined; + className?: string; + html?: UIElementMarkup; + isButton?: boolean; tagName?: keyof HTMLElementTagNameMap; - title?: string | undefined; - ariaLabel?: string | undefined; + title?: string; + ariaLabel?: string; onInit?: (element: HTMLElement, pswp: PhotoSwipe) => void; onClick?: import("../types.js").Methods | ((e: MouseEvent, element: HTMLElement, pswp: PhotoSwipe) => void); appendTo?: 'bar' | 'wrapper' | 'root'; - order?: number | undefined; + order?: number; }; export type DefaultUIElements = 'arrowPrev' | 'arrowNext' | 'close' | 'zoom' | 'counter'; export type UIElementMarkup = string | UIElementMarkupProps; diff --git a/src/js/ui/ui-element.js b/src/js/ui/ui-element.js index 0789153e..422eb8a3 100644 --- a/src/js/ui/ui-element.js +++ b/src/js/ui/ui-element.js @@ -9,25 +9,25 @@ import { createElement } from '../util/util.js'; /** * @typedef {Object} UIElementMarkupProps - * @prop {boolean=} isCustomSVG + * @prop {boolean} [isCustomSVG] * @prop {string} inner - * @prop {string=} outlineID + * @prop {string} [outlineID] * @prop {number | string} [size] */ /** * @typedef {Object} UIElementData * @prop {DefaultUIElements | string} [name] - * @prop {string=} className - * @prop {UIElementMarkup=} html - * @prop {boolean=} isButton + * @prop {string} [className] + * @prop {UIElementMarkup} [html] + * @prop {boolean} [isButton] * @prop {keyof HTMLElementTagNameMap} [tagName] - * @prop {string=} title - * @prop {string=} ariaLabel + * @prop {string} [title] + * @prop {string} [ariaLabel] * @prop {(element: HTMLElement, pswp: PhotoSwipe) => void} [onInit] * @prop {Methods | ((e: MouseEvent, element: HTMLElement, pswp: PhotoSwipe) => void)} [onClick] * @prop {'bar' | 'wrapper' | 'root'} [appendTo] - * @prop {number=} order + * @prop {number} [order] */ /** @typedef {'arrowPrev' | 'arrowNext' | 'close' | 'zoom' | 'counter'} DefaultUIElements */ @@ -36,6 +36,7 @@ import { createElement } from '../util/util.js'; /** * @param {UIElementMarkup} [htmlData] + * @returns {string} */ function addElementHTML(htmlData) { if (typeof htmlData === 'string') { @@ -110,15 +111,12 @@ class UIElement { className += (data.className || `pswp__${data.name}`); } - /** @type {HTMLElement} */ - let element; let tagName = data.isButton ? (data.tagName || 'button') : (data.tagName || 'div'); tagName = /** @type {keyof HTMLElementTagNameMap} */ (tagName.toLowerCase()); - element = createElement(className, tagName); + /** @type {HTMLElement} */ + const element = createElement(className, tagName); if (data.isButton) { - // create button element - element = createElement(className, tagName); if (tagName === 'button') { /** @type {HTMLButtonElement} */ (element).type = 'button'; } @@ -137,7 +135,7 @@ class UIElement { } if (ariaLabel || title) { - /** @type {HTMLElement} */ (element).setAttribute('aria-label', ariaLabel || title); + element.setAttribute('aria-label', ariaLabel || title); } } @@ -159,7 +157,8 @@ class UIElement { // Top bar is default position const appendTo = data.appendTo || 'bar'; - let container; + /** @type {HTMLElement} root element by default */ + let container = pswp.element; if (appendTo === 'bar') { if (!pswp.topBar) { pswp.topBar = createElement('pswp__top-bar pswp__hide-on-close', 'div', pswp.scrollWrap); @@ -172,9 +171,6 @@ class UIElement { if (appendTo === 'wrapper') { container = pswp.scrollWrap; - } else { - // root element - container = pswp.element; } } From 17d1be07fc7713c2577e3dd073495c0f14b35382 Mon Sep 17 00:00:00 2001 From: mishchuk Date: Tue, 27 Dec 2022 13:20:51 +1000 Subject: [PATCH 19/27] fix ui/button-arrow types --- src/js/ui/button-arrow.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/ui/button-arrow.js b/src/js/ui/button-arrow.js index 77f574ce..3e140d44 100644 --- a/src/js/ui/button-arrow.js +++ b/src/js/ui/button-arrow.js @@ -9,7 +9,7 @@ * * @param {HTMLElement} element * @param {PhotoSwipe} pswp - * @param {boolean=} isNextButton + * @param {boolean} [isNextButton] */ function initArrowButton(element, pswp, isNextButton) { element.classList.add('pswp__button--arrow'); From 2b8bbd12b6d0583d718af31e9ef960ff4a413196 Mon Sep 17 00:00:00 2001 From: mishchuk Date: Tue, 27 Dec 2022 13:28:56 +1000 Subject: [PATCH 20/27] fix ui/loading-indicator types --- src/js/ui/loading-indicator.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/js/ui/loading-indicator.js b/src/js/ui/loading-indicator.js index 2a5a9a41..96df8e7f 100644 --- a/src/js/ui/loading-indicator.js +++ b/src/js/ui/loading-indicator.js @@ -10,10 +10,10 @@ export const loadingIndicator = { outlineID: 'pswp__icn-loading' }, onInit: (indicatorElement, pswp) => { - /** @type {boolean} */ + /** @type {boolean | undefined} */ let isVisible; - /** @type {NodeJS.Timeout} */ - let delayTimeout; + /** @type {NodeJS.Timeout | null} */ + let delayTimeout = null; /** * @param {string} className From 3f0b52340f0bd8f381981a86c18ef09960e7836a Mon Sep 17 00:00:00 2001 From: mishchuk Date: Tue, 27 Dec 2022 13:48:17 +1000 Subject: [PATCH 21/27] fix ui/ui types --- dist/types/ui/ui.d.ts | 15 +++++++++------ src/js/ui/loading-indicator.js | 3 --- src/js/ui/ui.js | 19 ++++++++++++------- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/dist/types/ui/ui.d.ts b/dist/types/ui/ui.d.ts index d436a4c6..616e240f 100644 --- a/dist/types/ui/ui.d.ts +++ b/dist/types/ui/ui.d.ts @@ -7,16 +7,17 @@ declare class UI { */ constructor(pswp: PhotoSwipe); pswp: import("../photoswipe.js").default; - /** @type {() => void} */ - updatePreloaderVisibility: () => void; - /** @type {number} */ - _lastUpdatedZoomLevel: number; - init(): void; isRegistered: boolean; /** @type {UIElementData[]} */ uiElementsData: UIElementData[]; /** @type {(UIElement | UIElementData)[]} */ items: (UIElement | UIElementData)[]; + /** + * @private + * @type {number | undefined} + */ + private _lastUpdatedZoomLevel; + init(): void; /** * @param {UIElementData} elementData */ @@ -24,7 +25,9 @@ declare class UI { /** * Fired each time zoom or pan position is changed. * Update classes that control visibility of zoom button and cursor icon. + * + * @private */ - _onZoomPanUpdate(): void; + private _onZoomPanUpdate; } import UIElement from "./ui-element.js"; diff --git a/src/js/ui/loading-indicator.js b/src/js/ui/loading-indicator.js index 96df8e7f..6602e70b 100644 --- a/src/js/ui/loading-indicator.js +++ b/src/js/ui/loading-indicator.js @@ -59,8 +59,5 @@ export const loadingIndicator = { updatePreloaderVisibility(); } }); - - // expose the method - pswp.ui.updatePreloaderVisibility = updatePreloaderVisibility; } }; diff --git a/src/js/ui/ui.js b/src/js/ui/ui.js index 23366ccc..88bdc1db 100644 --- a/src/js/ui/ui.js +++ b/src/js/ui/ui.js @@ -11,7 +11,7 @@ import { counterIndicator } from './counter-indicator.js'; /** * Set special class on element when image is zoomed. * - * By default it is used to adjust + * By default, it is used to adjust * zoom icon and zoom cursor via CSS. * * @param {HTMLElement} el @@ -27,18 +27,22 @@ class UI { */ constructor(pswp) { this.pswp = pswp; + this.isRegistered = false; + /** @type {UIElementData[]} */ + this.uiElementsData = []; + /** @type {(UIElement | UIElementData)[]} */ + this.items = []; - /** @type {() => void} */ - this.updatePreloaderVisibility = undefined; - - /** @type {number} */ + /** + * @private + * @type {number | undefined} + */ this._lastUpdatedZoomLevel = undefined; } init() { const { pswp } = this; this.isRegistered = false; - /** @type {UIElementData[]} */ this.uiElementsData = [ closeButton, arrowPrev, @@ -56,7 +60,6 @@ class UI { return (a.order || 0) - (b.order || 0); }); - /** @type {(UIElement | UIElementData)[]} */ this.items = []; this.isRegistered = true; @@ -87,6 +90,8 @@ class UI { /** * Fired each time zoom or pan position is changed. * Update classes that control visibility of zoom button and cursor icon. + * + * @private */ _onZoomPanUpdate() { const { template, currSlide, options } = this.pswp; From 28e675395e2db96c70c98501a24d2e345fce2490 Mon Sep 17 00:00:00 2001 From: mishchuk Date: Tue, 27 Dec 2022 15:07:02 +1000 Subject: [PATCH 22/27] fix keyboard types --- dist/types/keyboard.d.ts | 19 ++++++++------- src/js/keyboard.js | 50 ++++++++++++++++++++++++++++++---------- src/js/util/util.js | 2 +- 3 files changed, 48 insertions(+), 23 deletions(-) diff --git a/dist/types/keyboard.d.ts b/dist/types/keyboard.d.ts index a4b2af22..f290ceb0 100644 --- a/dist/types/keyboard.d.ts +++ b/dist/types/keyboard.d.ts @@ -4,14 +4,9 @@ export type PhotoSwipe = import('./photoswipe.js').default; * */ export type Methods = import('./types.js').Methods; -/** @typedef {import('./photoswipe.js').default} PhotoSwipe */ -/** - * @template T - * @typedef {import('./types.js').Methods} Methods - */ /** * - Manages keyboard shortcuts. - * - Heps trap focus within photoswipe. + * - Helps trap focus within photoswipe. */ declare class Keyboard { /** @@ -19,16 +14,20 @@ declare class Keyboard { */ constructor(pswp: PhotoSwipe); pswp: import("./photoswipe.js").default; - _focusRoot(): void; - _wasFocused: boolean; + /** @private */ + private _wasFocused; + /** @private */ + private _focusRoot; /** + * @private * @param {KeyboardEvent} e */ - _onKeyDown(e: KeyboardEvent): void; + private _onKeyDown; /** * Trap focus inside photoswipe * + * @private * @param {FocusEvent} e */ - _onFocusIn(e: FocusEvent): void; + private _onFocusIn; } diff --git a/src/js/keyboard.js b/src/js/keyboard.js index 748b5afe..1f655ee2 100644 --- a/src/js/keyboard.js +++ b/src/js/keyboard.js @@ -7,9 +7,29 @@ import { specialKeyUsed } from './util/util.js'; * @typedef {import('./types.js').Methods} Methods */ +const KeyboardKeyCodesMap = { + Escape: 27, + z: 90, + ArrowLeft: 37, + ArrowUp: 38, + ArrowRight: 39, + ArrowDown: 40, + Tab: 9, +}; + +/** + * @template {keyof KeyboardKeyCodesMap} T + * @param {T} key + * @param {boolean} isKeySupported + * @returns {T | number | undefined} + */ +const getKeyboardEventKey = (key, isKeySupported) => { + return isKeySupported ? key : KeyboardKeyCodesMap[key]; +}; + /** * - Manages keyboard shortcuts. - * - Heps trap focus within photoswipe. + * - Helps trap focus within photoswipe. */ class Keyboard { /** @@ -17,6 +37,8 @@ class Keyboard { */ constructor(pswp) { this.pswp = pswp; + /** @private */ + this._wasFocused = false; pswp.on('bindEvents', () => { // Dialog was likely opened by keyboard if initial point is not defined @@ -41,6 +63,7 @@ class Keyboard { }); } + /** @private */ _focusRoot() { if (!this._wasFocused) { this.pswp.element.focus(); @@ -49,6 +72,7 @@ class Keyboard { } /** + * @private * @param {KeyboardEvent} e */ _onKeyDown(e) { @@ -65,36 +89,37 @@ class Keyboard { return; } - /** @type {Methods} */ + /** @type {Methods | undefined} */ let keydownAction; - /** @type {'x' | 'y'} */ + /** @type {'x' | 'y' | undefined} */ let axis; - let isForward; + let isForward = false; + const isKeySupported = 'key' in e; - switch (e.keyCode) { - case 27: // esc + switch (isKeySupported ? e.key : e.keyCode) { + case getKeyboardEventKey('Escape', isKeySupported): if (pswp.options.escKey) { keydownAction = 'close'; } break; - case 90: // z key + case getKeyboardEventKey('z', isKeySupported): keydownAction = 'toggleZoom'; break; - case 37: // left + case getKeyboardEventKey('ArrowLeft', isKeySupported): axis = 'x'; break; - case 38: // top + case getKeyboardEventKey('ArrowUp', isKeySupported): axis = 'y'; break; - case 39: // right + case getKeyboardEventKey('ArrowRight', isKeySupported): axis = 'x'; isForward = true; break; - case 40: // bottom + case getKeyboardEventKey('ArrowDown', isKeySupported): isForward = true; axis = 'y'; break; - case 9: // tab + case getKeyboardEventKey('Tab', isKeySupported): this._focusRoot(); break; default: @@ -130,6 +155,7 @@ class Keyboard { /** * Trap focus inside photoswipe * + * @private * @param {FocusEvent} e */ _onFocusIn(e) { diff --git a/src/js/util/util.js b/src/js/util/util.js index ba378b72..7ab65c7e 100644 --- a/src/js/util/util.js +++ b/src/js/util/util.js @@ -181,7 +181,7 @@ export const LOAD_STATE = { * @returns {boolean} */ export function specialKeyUsed(e) { - return e.which === 2 || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey; + return ('button' in e && e.button === 1) || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey; } /** From 0cb7e55400506dc62e85a5f334cc7252232feee0 Mon Sep 17 00:00:00 2001 From: mishchuk Date: Wed, 28 Dec 2022 12:51:03 +1000 Subject: [PATCH 23/27] fix main-scroll types --- dist/types/main-scroll.d.ts | 27 +++++++++------- src/js/main-scroll.js | 61 +++++++++++++++++++------------------ 2 files changed, 48 insertions(+), 40 deletions(-) diff --git a/dist/types/main-scroll.d.ts b/dist/types/main-scroll.d.ts index aed43cd0..c0555f1a 100644 --- a/dist/types/main-scroll.d.ts +++ b/dist/types/main-scroll.d.ts @@ -18,24 +18,26 @@ declare class MainScroll { constructor(pswp: PhotoSwipe); pswp: import("./photoswipe.js").default; x: number; - /** @type {number} */ slideWidth: number; + /** @private */ + private _currPositionIndex; + /** @private */ + private _prevPositionIndex; + /** @private */ + private _containerShiftIndex; /** @type {ItemHolder[]} */ itemHolders: ItemHolder[]; /** * Position the scroller and slide containers * according to viewport size. * - * @param {boolean=} resizeSlides Whether slides content should resized + * @param {boolean} [resizeSlides] Whether slides content should resized */ - resize(resizeSlides?: boolean | undefined): void; + resize(resizeSlides?: boolean): void; /** * Reset X position of the main scroller to zero */ resetPosition(): void; - _currPositionIndex: number; - _prevPositionIndex: number; - _containerShiftIndex: number; /** * Create and append array of three items * that hold data about slides in DOM @@ -43,6 +45,7 @@ declare class MainScroll { appendHolders(): void; /** * Whether the main scroll can be horizontally swiped to the next or previous slide. + * @returns {boolean} */ canBeSwiped(): boolean; /** @@ -56,19 +59,21 @@ declare class MainScroll { * (for example `-1` will move to the last slide of the gallery). * * @param {number} diff - * @param {boolean=} animate - * @param {number=} velocityX + * @param {boolean} [animate] + * @param {number} [velocityX] * @returns {boolean} whether index was changed or not */ - moveIndexBy(diff: number, animate?: boolean | undefined, velocityX?: number | undefined): boolean; + moveIndexBy(diff: number, animate?: boolean, velocityX?: number): boolean; /** * X position of the main scroll for the current slide * (ignores position during dragging) + * @returns {number} */ getCurrSlideX(): number; /** * Whether scroll position is shifted. * For example, it will return true if the scroll is being dragged or animated. + * @returns {boolean} */ isShifted(): boolean; /** @@ -79,7 +84,7 @@ declare class MainScroll { * Move the X position of the main scroll container * * @param {number} x - * @param {boolean=} dragging + * @param {boolean} [dragging] */ - moveTo(x: number, dragging?: boolean | undefined): void; + moveTo(x: number, dragging?: boolean): void; } diff --git a/src/js/main-scroll.js b/src/js/main-scroll.js index 8fd23ea0..a24cad43 100644 --- a/src/js/main-scroll.js +++ b/src/js/main-scroll.js @@ -28,21 +28,23 @@ class MainScroll { constructor(pswp) { this.pswp = pswp; this.x = 0; - - /** @type {number} */ - this.slideWidth = undefined; + this.slideWidth = 0; + /** @private */ + this._currPositionIndex = 0; + /** @private */ + this._prevPositionIndex = 0; + /** @private */ + this._containerShiftIndex = -1; /** @type {ItemHolder[]} */ - this.itemHolders = undefined; - - this.resetPosition(); + this.itemHolders = []; } /** * Position the scroller and slide containers * according to viewport size. * - * @param {boolean=} resizeSlides Whether slides content should resized + * @param {boolean} [resizeSlides] Whether slides content should resized */ resize(resizeSlides) { const { pswp } = this; @@ -114,6 +116,7 @@ class MainScroll { /** * Whether the main scroll can be horizontally swiped to the next or previous slide. + * @returns {boolean} */ canBeSwiped() { return this.pswp.getNumItems() > 1; @@ -130,8 +133,8 @@ class MainScroll { * (for example `-1` will move to the last slide of the gallery). * * @param {number} diff - * @param {boolean=} animate - * @param {number=} velocityX + * @param {boolean} [animate] + * @param {number} [velocityX] * @returns {boolean} whether index was changed or not */ moveIndexBy(diff, animate, velocityX) { @@ -203,14 +206,13 @@ class MainScroll { } } - if (diff) { - return true; - } + return Boolean(diff); } /** * X position of the main scroll for the current slide * (ignores position during dragging) + * @returns {number} */ getCurrSlideX() { return this.slideWidth * this._currPositionIndex; @@ -219,6 +221,7 @@ class MainScroll { /** * Whether scroll position is shifted. * For example, it will return true if the scroll is being dragged or animated. + * @returns {boolean} */ isShifted() { return this.x !== this.getCurrSlideX(); @@ -240,6 +243,7 @@ class MainScroll { pswp.currIndex = pswp.potentialIndex; let diffAbs = Math.abs(positionDifference); + /** @type {ItemHolder | undefined} */ let tempHolder; if (diffAbs >= 3) { @@ -250,22 +254,26 @@ class MainScroll { for (let i = 0; i < diffAbs; i++) { if (positionDifference > 0) { tempHolder = this.itemHolders.shift(); - this.itemHolders[2] = tempHolder; // move first to last + if (tempHolder) { + this.itemHolders[2] = tempHolder; // move first to last - this._containerShiftIndex++; + this._containerShiftIndex++; - setTransform(tempHolder.el, (this._containerShiftIndex + 2) * this.slideWidth); + setTransform(tempHolder.el, (this._containerShiftIndex + 2) * this.slideWidth); - pswp.setContent(tempHolder, (pswp.currIndex - diffAbs) + i + 2); + pswp.setContent(tempHolder, (pswp.currIndex - diffAbs) + i + 2); + } } else { tempHolder = this.itemHolders.pop(); - this.itemHolders.unshift(tempHolder); // move last to first + if (tempHolder) { + this.itemHolders.unshift(tempHolder); // move last to first - this._containerShiftIndex--; + this._containerShiftIndex--; - setTransform(tempHolder.el, this._containerShiftIndex * this.slideWidth); + setTransform(tempHolder.el, this._containerShiftIndex * this.slideWidth); - pswp.setContent(tempHolder, (pswp.currIndex + diffAbs) - i - 2); + pswp.setContent(tempHolder, (pswp.currIndex + diffAbs) - i - 2); + } } } @@ -290,7 +298,7 @@ class MainScroll { } }); - pswp.currSlide = this.itemHolders[1].slide; + pswp.currSlide = this.itemHolders[1]?.slide; pswp.contentLoader.updateLazy(positionDifference); if (pswp.currSlide) { @@ -304,19 +312,14 @@ class MainScroll { * Move the X position of the main scroll container * * @param {number} x - * @param {boolean=} dragging + * @param {boolean} [dragging] */ moveTo(x, dragging) { - /** @type {number} */ - let newSlideIndexOffset; - /** @type {number} */ - let delta; - if (!this.pswp.canLoop() && dragging) { // Apply friction - newSlideIndexOffset = ((this.slideWidth * this._currPositionIndex) - x) / this.slideWidth; + let newSlideIndexOffset = ((this.slideWidth * this._currPositionIndex) - x) / this.slideWidth; newSlideIndexOffset += this.pswp.currIndex; - delta = Math.round(x - this.x); + const delta = Math.round(x - this.x); if ((newSlideIndexOffset < 0 && delta > 0) || (newSlideIndexOffset >= this.pswp.getNumItems() - 1 && delta < 0)) { From dfaf0415c3d374cb6524586cc5c73ced9006085b Mon Sep 17 00:00:00 2001 From: mishchuk Date: Wed, 28 Dec 2022 14:36:23 +1000 Subject: [PATCH 24/27] fix opener types --- dist/types/opener.d.ts | 88 +++++++++++++++++++++++++++++------------- src/js/opener.js | 66 ++++++++++++++++++++++++++----- 2 files changed, 119 insertions(+), 35 deletions(-) diff --git a/dist/types/opener.d.ts b/dist/types/opener.d.ts index 536c314f..e567ebad 100644 --- a/dist/types/opener.d.ts +++ b/dist/types/opener.d.ts @@ -14,38 +14,74 @@ declare class Opener { constructor(pswp: PhotoSwipe); pswp: import("./photoswipe.js").default; isClosed: boolean; - _prepareOpen(): void; - /** @type {false | Bounds} */ - _thumbBounds: false | Bounds; - open(): void; - close(): boolean; isOpen: boolean; - isOpening: boolean; isClosing: boolean; - _duration: number | false; - _applyStartProps(): void; - _placeholder: HTMLDivElement | HTMLImageElement; - _useAnimation: boolean; + isOpening: boolean; + /** + * @private + * @type {number | false | undefined} + */ + private _duration; + /** @private */ + private _useAnimation; + /** @private */ + private _croppedZoom; + /** @private */ + private _animateRootOpacity; + /** @private */ + private _animateBgOpacity; + /** + * @private + * @type { HTMLDivElement | HTMLImageElement | null | undefined } + */ + private _placeholder; + /** + * @private + * @type { HTMLDivElement | undefined } + */ + private _opacityElement; + /** + * @private + * @type { HTMLDivElement | undefined } + */ + private _cropContainer1; + /** + * @private + * @type { HTMLElement | undefined } + */ + private _cropContainer2; + /** + * @private + * @type {Bounds | null} + */ + private _thumbBounds; + /** @private */ + private _prepareOpen; + open(): void; + close(): void; + /** @private */ + private _applyStartProps; _animateZoom: boolean; - _animateRootOpacity: boolean; - _animateBgOpacity: boolean; - _opacityElement: HTMLDivElement; - _croppedZoom: boolean; - _cropContainer1: HTMLDivElement; - _cropContainer2: HTMLElement; - _start(): void; - _initiate(): void; - _onAnimationComplete(): void; - _animateToOpenState(): void; - _animateToClosedState(): void; - /** - * @param {boolean=} animate - */ - _setClosedStateZoomPan(animate?: boolean | undefined): void; + /** @private */ + private _start; + /** @private */ + private _initiate; + /** @private */ + private _onAnimationComplete; + /** @private */ + private _animateToOpenState; + /** @private */ + private _animateToClosedState; + /** + * @private + * @param {boolean} [animate] + */ + private _setClosedStateZoomPan; /** + * @private * @param {HTMLElement} target * @param {'transform' | 'opacity'} prop * @param {string} propValue */ - _animateTo(target: HTMLElement, prop: 'transform' | 'opacity', propValue: string): void; + private _animateTo; } diff --git a/src/js/opener.js b/src/js/opener.js index f6585f50..f5810e37 100644 --- a/src/js/opener.js +++ b/src/js/opener.js @@ -27,10 +27,51 @@ class Opener { constructor(pswp) { this.pswp = pswp; this.isClosed = true; - this._prepareOpen = this._prepareOpen.bind(this); + this.isOpen = false; + this.isClosing = false; + this.isOpening = false; + /** + * @private + * @type {number | false | undefined} + */ + this._duration = undefined; + /** @private */ + this._useAnimation = false; + /** @private */ + this._croppedZoom = false; + /** @private */ + this._animateRootOpacity = false; + /** @private */ + this._animateBgOpacity = false; + /** + * @private + * @type { HTMLDivElement | HTMLImageElement | null | undefined } + */ + this._placeholder = undefined; + /** + * @private + * @type { HTMLDivElement | undefined } + */ + this._opacityElement = undefined; + /** + * @private + * @type { HTMLDivElement | undefined } + */ + this._cropContainer1 = undefined; + /** + * @private + * @type { HTMLElement | undefined } + */ + this._cropContainer2 = undefined; + + /** + * @private + * @type {Bounds | null} + */ + this._thumbBounds = null; - /** @type {false | Bounds} */ - this._thumbBounds = undefined; + + this._prepareOpen = this._prepareOpen.bind(this); // Override initial zoom and pan position pswp.on('firstZoomPan', this._prepareOpen); @@ -46,7 +87,7 @@ class Opener { // if we close during opening animation // for now do nothing, // browsers aren't good at changing the direction of the CSS transition - return false; + return; } const slide = this.pswp.currSlide; @@ -64,10 +105,9 @@ class Opener { setTimeout(() => { this._start(); }, this._croppedZoom ? 30 : 0); - - return true; } + /** @private */ _prepareOpen() { this.pswp.off('firstZoomPan', this._prepareOpen); if (!this.isOpening) { @@ -82,6 +122,7 @@ class Opener { } } + /** @private */ _applyStartProps() { const { pswp } = this; const slide = this.pswp.currSlide; @@ -89,11 +130,11 @@ class Opener { if (options.showHideAnimationType === 'fade') { options.showHideOpacity = true; - this._thumbBounds = false; + this._thumbBounds = null; } else if (options.showHideAnimationType === 'none') { options.showHideOpacity = false; this._duration = 0; - this._thumbBounds = false; + this._thumbBounds = null; } else if (this.isOpening && pswp._initialThumbBounds) { // Use initial bounds if defined this._thumbBounds = pswp._initialThumbBounds; @@ -186,6 +227,7 @@ class Opener { } } + /** @private */ _start() { if (this.isOpening && this._useAnimation @@ -219,6 +261,7 @@ class Opener { } } + /** @private */ _initiate() { this.pswp.element.style.setProperty('--pswp-transition-duration', this._duration + 'ms'); @@ -249,6 +292,7 @@ class Opener { } } + /** @private */ _onAnimationComplete() { const { pswp } = this; this.isOpen = this.isOpening; @@ -277,6 +321,7 @@ class Opener { } } + /** @private */ _animateToOpenState() { const { pswp } = this; if (this._animateZoom) { @@ -302,6 +347,7 @@ class Opener { } } + /** @private */ _animateToClosedState() { const { pswp } = this; @@ -320,7 +366,8 @@ class Opener { } /** - * @param {boolean=} animate + * @private + * @param {boolean} [animate] */ _setClosedStateZoomPan(animate) { if (!this._thumbBounds) return; @@ -365,6 +412,7 @@ class Opener { } /** + * @private * @param {HTMLElement} target * @param {'transform' | 'opacity'} prop * @param {string} propValue From b1981d5f697da42856af13a16a17473c1e7634c4 Mon Sep 17 00:00:00 2001 From: mishchuk Date: Thu, 29 Dec 2022 18:44:14 +1000 Subject: [PATCH 25/27] switch on strictNullChecks --- dist/types/core/base.d.ts | 6 + dist/types/core/eventable.d.ts | 578 +++++++++++++------------ dist/types/gestures/zoom-handler.d.ts | 2 +- dist/types/lightbox/lightbox.d.ts | 10 +- dist/types/main-scroll.d.ts | 6 +- dist/types/opener.d.ts | 4 +- dist/types/photoswipe.d.ts | 332 +++++++++++--- dist/types/slide/content.d.ts | 4 +- dist/types/slide/get-thumb-bounds.d.ts | 4 +- dist/types/slide/loader.d.ts | 2 +- dist/types/slide/slide.d.ts | 40 +- dist/types/slide/zoom-level.d.ts | 4 +- dist/types/ui/ui-element.d.ts | 28 +- dist/types/util/animations.d.ts | 10 +- dist/types/util/css-animation.d.ts | 8 +- dist/types/util/dom-events.d.ts | 4 +- dist/types/util/spring-animation.d.ts | 6 +- dist/types/util/spring-easer.d.ts | 2 +- dist/types/util/util.d.ts | 15 +- src/js/core/base.js | 40 +- src/js/core/eventable.js | 71 +-- src/js/gestures/drag-handler.js | 33 +- src/js/gestures/gestures.js | 26 +- src/js/gestures/tap-handler.js | 6 +- src/js/gestures/zoom-handler.js | 19 +- src/js/keyboard.js | 6 +- src/js/lightbox/lightbox.js | 15 +- src/js/main-scroll.js | 7 +- src/js/opener.js | 97 +++-- src/js/photoswipe.js | 247 +++++------ src/js/slide/content.js | 7 +- src/js/slide/get-thumb-bounds.js | 14 +- src/js/slide/slide.js | 34 +- src/js/ui/loading-indicator.js | 4 +- src/js/ui/ui-element.js | 12 +- src/js/ui/ui.js | 7 +- src/js/util/css-animation.js | 2 +- src/js/util/dom-events.js | 3 +- src/js/util/spring-animation.js | 3 +- src/js/util/spring-easer.js | 5 +- src/js/util/util.js | 13 +- tsconfig.json | 2 +- 42 files changed, 997 insertions(+), 741 deletions(-) diff --git a/dist/types/core/base.d.ts b/dist/types/core/base.d.ts index 466b9e10..5687d48d 100644 --- a/dist/types/core/base.d.ts +++ b/dist/types/core/base.d.ts @@ -56,6 +56,12 @@ declare class PhotoSwipeBase extends Eventable { * @returns {Content} Image that is being decoded or false. */ lazyLoadData(itemData: SlideData, index: number): Content; + /** + * @protected + * @param {Partial} options + * @returns {PhotoSwipeOptions} + */ + protected _prepareOptions(options: Partial): PhotoSwipeOptions; } import Eventable from "./eventable.js"; import Content from "../slide/content.js"; diff --git a/dist/types/core/eventable.d.ts b/dist/types/core/eventable.d.ts index a9c5c733..06214e01 100644 --- a/dist/types/core/eventable.d.ts +++ b/dist/types/core/eventable.d.ts @@ -1,3 +1,199 @@ +/** @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} 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, 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) => 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, itemData: SlideData, index: number) => HTMLElement} thumbEl + * Modify the thubmnail element from which opening zoom animation starts or ends. + * https://photoswipe.com/filters/#thumbel + * + * @prop {(thumbBounds: Bounds, itemData: SlideData, index: number) => Bounds} thumbBounds + * Modify the thubmnail bounds from which opening zoom animation starts or ends. + * https://photoswipe.com/filters/#thumbbounds + * + * @prop {(srcsetSizesWidth: number, content: Content) => number} srcsetSizesWidth + * + */ +/** + * @template {keyof PhotoSwipeFiltersMap} T + * @typedef {{ fn: PhotoSwipeFiltersMap[T], priority: number }} Filter + */ +/** + * @template {keyof PhotoSwipeEventsMap} T + * @typedef {PhotoSwipeEventsMap[T] extends undefined ? PhotoSwipeEvent : PhotoSwipeEvent & PhotoSwipeEventsMap[T]} AugmentedEvent + */ +/** + * @template {keyof PhotoSwipeEventsMap} T + * @typedef {(event: AugmentedEvent) => void} EventCallback + */ +/** @type {PhotoSwipeOptions} */ +export const defaultOptions: PhotoSwipeOptions; export default Eventable; export type PhotoSwipeLightbox = import('../lightbox/lightbox.js').default; export type PhotoSwipe = import('../photoswipe.js').default; @@ -198,7 +394,7 @@ export type PhotoSwipeEventsMap = { slide: Slide; }; gettingData: { - slide: Slide; + slide: Slide | undefined; data: SlideData; index: number; }; @@ -235,8 +431,8 @@ export type PhotoSwipeEventsMap = { }; beforeZoomTo: { destZoomLevel: number; - centerPoint: Point; - transitionDuration: number | false; + centerPoint: Point | undefined; + transitionDuration: number | false | undefined; }; zoomPanUpdate: { slide: Slide; @@ -383,49 +579,49 @@ declare class Eventable { * @type {{ [T in keyof PhotoSwipeEventsMap]?: ((event: AugmentedEvent) => void)[] }} */ _listeners: { - uiRegister?: ((event: PhotoSwipeEvent<"uiRegister">) => void)[]; + uiRegister?: ((event: PhotoSwipeEvent<"uiRegister">) => void)[] | undefined; /** * https://photoswipe.com/events/#initialization-events */ uiElementCreate?: ((event: PhotoSwipeEvent<"uiElementCreate"> & { data: import("../ui/ui-element.js").UIElementData; - }) => void)[]; - beforeOpen?: ((event: PhotoSwipeEvent<"beforeOpen">) => void)[]; - firstUpdate?: ((event: PhotoSwipeEvent<"firstUpdate">) => void)[]; - initialLayout?: ((event: PhotoSwipeEvent<"initialLayout">) => void)[]; - change?: ((event: PhotoSwipeEvent<"change">) => void)[]; - afterInit?: ((event: PhotoSwipeEvent<"afterInit">) => void)[]; + }) => void)[] | undefined; + beforeOpen?: ((event: PhotoSwipeEvent<"beforeOpen">) => void)[] | undefined; + firstUpdate?: ((event: PhotoSwipeEvent<"firstUpdate">) => void)[] | undefined; + initialLayout?: ((event: PhotoSwipeEvent<"initialLayout">) => void)[] | undefined; + change?: ((event: PhotoSwipeEvent<"change">) => void)[] | undefined; + afterInit?: ((event: PhotoSwipeEvent<"afterInit">) => void)[] | undefined; /** * https://photoswipe.com/events/#opening-or-closing-transition-events */ - bindEvents?: ((event: PhotoSwipeEvent<"bindEvents">) => void)[]; - openingAnimationStart?: ((event: PhotoSwipeEvent<"openingAnimationStart">) => void)[]; - openingAnimationEnd?: ((event: PhotoSwipeEvent<"openingAnimationEnd">) => void)[]; - closingAnimationStart?: ((event: PhotoSwipeEvent<"closingAnimationStart">) => void)[]; + bindEvents?: ((event: PhotoSwipeEvent<"bindEvents">) => void)[] | undefined; + openingAnimationStart?: ((event: PhotoSwipeEvent<"openingAnimationStart">) => void)[] | undefined; + openingAnimationEnd?: ((event: PhotoSwipeEvent<"openingAnimationEnd">) => void)[] | undefined; + closingAnimationStart?: ((event: PhotoSwipeEvent<"closingAnimationStart">) => void)[] | undefined; /** * https://photoswipe.com/events/#closing-events */ - closingAnimationEnd?: ((event: PhotoSwipeEvent<"closingAnimationEnd">) => void)[]; - close?: ((event: PhotoSwipeEvent<"close">) => void)[]; + closingAnimationEnd?: ((event: PhotoSwipeEvent<"closingAnimationEnd">) => void)[] | undefined; + close?: ((event: PhotoSwipeEvent<"close">) => void)[] | undefined; /** * https://photoswipe.com/events/#pointer-and-gesture-events */ - destroy?: ((event: PhotoSwipeEvent<"destroy">) => void)[]; + destroy?: ((event: PhotoSwipeEvent<"destroy">) => void)[] | undefined; pointerDown?: ((event: PhotoSwipeEvent<"pointerDown"> & { originalEvent: PointerEvent; - }) => void)[]; + }) => void)[] | undefined; pointerMove?: ((event: PhotoSwipeEvent<"pointerMove"> & { originalEvent: PointerEvent; - }) => void)[]; + }) => void)[] | undefined; pointerUp?: ((event: PhotoSwipeEvent<"pointerUp"> & { originalEvent: PointerEvent; - }) => void)[]; + }) => void)[] | undefined; /** * can be default prevented */ pinchClose?: ((event: PhotoSwipeEvent<"pinchClose"> & { bgOpacity: number; - }) => void)[]; + }) => void)[] | undefined; /** * can be default prevented * @@ -434,33 +630,33 @@ declare class Eventable { */ verticalDrag?: ((event: PhotoSwipeEvent<"verticalDrag"> & { panY: number; - }) => void)[]; + }) => void)[] | undefined; contentInit?: ((event: PhotoSwipeEvent<"contentInit"> & { content: Content; - }) => void)[]; + }) => void)[] | undefined; /** * can be default prevented */ contentLoad?: ((event: PhotoSwipeEvent<"contentLoad"> & { content: Content; isLazy: boolean; - }) => void)[]; + }) => void)[] | undefined; /** * can be default prevented */ contentLoadImage?: ((event: PhotoSwipeEvent<"contentLoadImage"> & { content: Content; isLazy: boolean; - }) => void)[]; + }) => void)[] | undefined; loadComplete?: ((event: PhotoSwipeEvent<"loadComplete"> & { content: Content; slide: import("../slide/slide.js").default; - isError?: boolean; - }) => void)[]; + isError?: boolean | undefined; + }) => void)[] | undefined; loadError?: ((event: PhotoSwipeEvent<"loadError"> & { content: Content; slide: import("../slide/slide.js").default; - }) => void)[]; + }) => void)[] | undefined; /** * can be default prevented */ @@ -468,43 +664,43 @@ declare class Eventable { content: Content; width: number; height: number; - }) => void)[]; + }) => void)[] | undefined; imageSizeChange?: ((event: PhotoSwipeEvent<"imageSizeChange"> & { content: Content; width: number; height: number; slide: import("../slide/slide.js").default; - }) => void)[]; + }) => void)[] | undefined; /** * can be default prevented */ contentLazyLoad?: ((event: PhotoSwipeEvent<"contentLazyLoad"> & { content: Content; - }) => void)[]; + }) => void)[] | undefined; /** * can be default prevented */ contentAppend?: ((event: PhotoSwipeEvent<"contentAppend"> & { content: Content; - }) => void)[]; + }) => void)[] | undefined; /** * can be default prevented */ contentActivate?: ((event: PhotoSwipeEvent<"contentActivate"> & { content: Content; - }) => void)[]; + }) => void)[] | undefined; /** * can be default prevented */ contentDeactivate?: ((event: PhotoSwipeEvent<"contentDeactivate"> & { content: Content; - }) => void)[]; + }) => void)[] | undefined; /** * can be default prevented */ contentRemove?: ((event: PhotoSwipeEvent<"contentRemove"> & { content: Content; - }) => void)[]; + }) => void)[] | undefined; /** * can be default prevented * @@ -513,147 +709,147 @@ declare class Eventable { */ contentDestroy?: ((event: PhotoSwipeEvent<"contentDestroy"> & { content: Content; - }) => void)[]; + }) => void)[] | undefined; /** * can be default prevented */ imageClickAction?: ((event: PhotoSwipeEvent<"imageClickAction"> & { point: Point; originalEvent: PointerEvent; - }) => void)[]; + }) => void)[] | undefined; /** * can be default prevented */ bgClickAction?: ((event: PhotoSwipeEvent<"bgClickAction"> & { point: Point; originalEvent: PointerEvent; - }) => void)[]; + }) => void)[] | undefined; /** * can be default prevented */ tapAction?: ((event: PhotoSwipeEvent<"tapAction"> & { point: Point; originalEvent: PointerEvent; - }) => void)[]; + }) => void)[] | undefined; /** * can be default prevented */ doubleTapAction?: ((event: PhotoSwipeEvent<"doubleTapAction"> & { point: Point; originalEvent: PointerEvent; - }) => void)[]; + }) => void)[] | undefined; /** * can be default prevented */ keydown?: ((event: PhotoSwipeEvent<"keydown"> & { originalEvent: KeyboardEvent; - }) => void)[]; + }) => void)[] | undefined; moveMainScroll?: ((event: PhotoSwipeEvent<"moveMainScroll"> & { x: number; dragging: boolean; - }) => void)[]; + }) => void)[] | undefined; firstZoomPan?: ((event: PhotoSwipeEvent<"firstZoomPan"> & { slide: import("../slide/slide.js").default; - }) => void)[]; + }) => void)[] | undefined; gettingData?: ((event: PhotoSwipeEvent<"gettingData"> & { - slide: import("../slide/slide.js").default; + slide: import("../slide/slide.js").default | undefined; data: import("../slide/slide.js").SlideData; index: number; - }) => void)[]; - beforeResize?: ((event: PhotoSwipeEvent<"beforeResize">) => void)[]; - resize?: ((event: PhotoSwipeEvent<"resize">) => void)[]; - viewportSize?: ((event: PhotoSwipeEvent<"viewportSize">) => void)[]; - updateScrollOffset?: ((event: PhotoSwipeEvent<"updateScrollOffset">) => void)[]; + }) => void)[] | undefined; + beforeResize?: ((event: PhotoSwipeEvent<"beforeResize">) => void)[] | undefined; + resize?: ((event: PhotoSwipeEvent<"resize">) => void)[] | undefined; + viewportSize?: ((event: PhotoSwipeEvent<"viewportSize">) => void)[] | undefined; + updateScrollOffset?: ((event: PhotoSwipeEvent<"updateScrollOffset">) => void)[] | undefined; slideInit?: ((event: PhotoSwipeEvent<"slideInit"> & { slide: import("../slide/slide.js").default; - }) => void)[]; + }) => void)[] | undefined; afterSetContent?: ((event: PhotoSwipeEvent<"afterSetContent"> & { slide: import("../slide/slide.js").default; - }) => void)[]; + }) => void)[] | undefined; slideLoad?: ((event: PhotoSwipeEvent<"slideLoad"> & { slide: import("../slide/slide.js").default; - }) => void)[]; + }) => void)[] | undefined; /** * can be default prevented */ appendHeavy?: ((event: PhotoSwipeEvent<"appendHeavy"> & { slide: import("../slide/slide.js").default; - }) => void)[]; + }) => void)[] | undefined; appendHeavyContent?: ((event: PhotoSwipeEvent<"appendHeavyContent"> & { slide: import("../slide/slide.js").default; - }) => void)[]; + }) => void)[] | undefined; slideActivate?: ((event: PhotoSwipeEvent<"slideActivate"> & { slide: import("../slide/slide.js").default; - }) => void)[]; + }) => void)[] | undefined; slideDeactivate?: ((event: PhotoSwipeEvent<"slideDeactivate"> & { slide: import("../slide/slide.js").default; - }) => void)[]; + }) => void)[] | undefined; slideDestroy?: ((event: PhotoSwipeEvent<"slideDestroy"> & { slide: import("../slide/slide.js").default; - }) => void)[]; + }) => void)[] | undefined; beforeZoomTo?: ((event: PhotoSwipeEvent<"beforeZoomTo"> & { destZoomLevel: number; - centerPoint: Point; - transitionDuration: number | false; - }) => void)[]; + centerPoint: Point | undefined; + transitionDuration: number | false | undefined; + }) => void)[] | undefined; zoomPanUpdate?: ((event: PhotoSwipeEvent<"zoomPanUpdate"> & { slide: import("../slide/slide.js").default; - }) => void)[]; + }) => void)[] | undefined; initialZoomPan?: ((event: PhotoSwipeEvent<"initialZoomPan"> & { slide: import("../slide/slide.js").default; - }) => void)[]; + }) => void)[] | undefined; calcSlideSize?: ((event: PhotoSwipeEvent<"calcSlideSize"> & { slide: import("../slide/slide.js").default; - }) => void)[]; - resolutionChanged?: ((event: PhotoSwipeEvent<"resolutionChanged">) => void)[]; + }) => void)[] | undefined; + resolutionChanged?: ((event: PhotoSwipeEvent<"resolutionChanged">) => void)[] | undefined; /** * can be default prevented */ wheel?: ((event: PhotoSwipeEvent<"wheel"> & { originalEvent: WheelEvent; - }) => void)[]; + }) => void)[] | undefined; /** * can be default prevented */ contentAppendImage?: ((event: PhotoSwipeEvent<"contentAppendImage"> & { content: Content; - }) => void)[]; + }) => void)[] | undefined; /** * can be default prevented */ lazyLoadSlide?: ((event: PhotoSwipeEvent<"lazyLoadSlide"> & { index: number; itemData: import("../slide/slide.js").SlideData; - }) => void)[]; - lazyLoad?: ((event: PhotoSwipeEvent<"lazyLoad">) => void)[]; + }) => void)[] | undefined; + lazyLoad?: ((event: PhotoSwipeEvent<"lazyLoad">) => void)[] | undefined; calcBounds?: ((event: PhotoSwipeEvent<"calcBounds"> & { slide: import("../slide/slide.js").default; - }) => void)[]; + }) => void)[] | undefined; /** * legacy */ zoomLevelsUpdate?: ((event: PhotoSwipeEvent<"zoomLevelsUpdate"> & { zoomLevels: import("../slide/zoom-level.js").default; slideData: import("../slide/slide.js").SlideData; - }) => void)[]; - init?: ((event: PhotoSwipeEvent<"init">) => void)[]; - initialZoomIn?: ((event: PhotoSwipeEvent<"initialZoomIn">) => void)[]; - initialZoomOut?: ((event: PhotoSwipeEvent<"initialZoomOut">) => void)[]; - initialZoomInEnd?: ((event: PhotoSwipeEvent<"initialZoomInEnd">) => void)[]; - initialZoomOutEnd?: ((event: PhotoSwipeEvent<"initialZoomOutEnd">) => void)[]; + }) => void)[] | undefined; + init?: ((event: PhotoSwipeEvent<"init">) => void)[] | undefined; + initialZoomIn?: ((event: PhotoSwipeEvent<"initialZoomIn">) => void)[] | undefined; + initialZoomOut?: ((event: PhotoSwipeEvent<"initialZoomOut">) => void)[] | undefined; + initialZoomInEnd?: ((event: PhotoSwipeEvent<"initialZoomInEnd">) => void)[] | undefined; + initialZoomOutEnd?: ((event: PhotoSwipeEvent<"initialZoomOutEnd">) => void)[] | undefined; numItems?: ((event: PhotoSwipeEvent<"numItems"> & { dataSource: import("../photoswipe.js").DataSource; numItems: number; - }) => void)[]; + }) => void)[] | undefined; itemData?: ((event: PhotoSwipeEvent<"itemData"> & { itemData: import("../slide/slide.js").SlideData; index: number; - }) => void)[]; + }) => void)[] | undefined; thumbBounds?: ((event: PhotoSwipeEvent<"thumbBounds"> & { index: number; itemData: import("../slide/slide.js").SlideData; instance: import("../photoswipe.js").default; - }) => void)[]; + }) => void)[] | undefined; }; /** * @type {{ [T in keyof PhotoSwipeFiltersMap]?: Filter[] }} @@ -663,68 +859,68 @@ declare class Eventable { * Modify the total amount of slides. Example on Data sources page. * https://photoswipe.com/filters/#numitems */ - numItems?: Filter<"numItems">[]; + numItems?: Filter<"numItems">[] | undefined; /** * Modify slide item data. Example on Data sources page. * https://photoswipe.com/filters/#itemdata */ - itemData?: Filter<"itemData">[]; + itemData?: Filter<"itemData">[] | undefined; /** * Modify item data when it's parsed from DOM element. Example on Data sources page. * https://photoswipe.com/filters/#domitemdata */ - domItemData?: Filter<"domItemData">[]; + domItemData?: Filter<"domItemData">[] | undefined; /** * Modify clicked gallery item index. * https://photoswipe.com/filters/#clickedindex */ - clickedIndex?: Filter<"clickedIndex">[]; + clickedIndex?: Filter<"clickedIndex">[] | undefined; /** * Modify placeholder image source. * https://photoswipe.com/filters/#placeholdersrc */ - placeholderSrc?: Filter<"placeholderSrc">[]; + placeholderSrc?: Filter<"placeholderSrc">[] | undefined; /** * Modify if the content is currently loading. * https://photoswipe.com/filters/#iscontentloading */ - isContentLoading?: Filter<"isContentLoading">[]; + isContentLoading?: Filter<"isContentLoading">[] | undefined; /** * Modify if the content can be zoomed. * https://photoswipe.com/filters/#iscontentzoomable */ - isContentZoomable?: Filter<"isContentZoomable">[]; + isContentZoomable?: Filter<"isContentZoomable">[] | undefined; /** * Modify if the placeholder should be used for the content. * https://photoswipe.com/filters/#usecontentplaceholder */ - useContentPlaceholder?: Filter<"useContentPlaceholder">[]; + useContentPlaceholder?: Filter<"useContentPlaceholder">[] | undefined; /** * Modify if the placeholder should be kept after the content is loaded. * https://photoswipe.com/filters/#iskeepingplaceholder */ - isKeepingPlaceholder?: Filter<"isKeepingPlaceholder">[]; + isKeepingPlaceholder?: Filter<"isKeepingPlaceholder">[] | undefined; /** * Modify an element when the content has error state (for example, if image cannot be loaded). * https://photoswipe.com/filters/#contenterrorelement */ - contentErrorElement?: Filter<"contentErrorElement">[]; + contentErrorElement?: Filter<"contentErrorElement">[] | undefined; /** * Modify a UI element that's being created. * https://photoswipe.com/filters/#uielement */ - uiElement?: Filter<"uiElement">[]; + uiElement?: Filter<"uiElement">[] | undefined; /** * Modify the thubmnail element from which opening zoom animation starts or ends. * https://photoswipe.com/filters/#thumbel */ - thumbEl?: Filter<"thumbEl">[]; + thumbEl?: Filter<"thumbEl">[] | undefined; /** * Modify the thubmnail bounds from which opening zoom animation starts or ends. * https://photoswipe.com/filters/#thumbbounds */ - thumbBounds?: Filter<"thumbBounds">[]; - srcsetSizesWidth?: Filter<"srcsetSizesWidth">[]; + thumbBounds?: Filter<"thumbBounds">[] | undefined; + srcsetSizesWidth?: Filter<"srcsetSizesWidth">[] | undefined; }; /** @type {PhotoSwipe | undefined} */ pswp: PhotoSwipe | undefined; @@ -768,202 +964,8 @@ declare class Eventable { * @param {PhotoSwipeEventsMap[T]} [details] * @returns {AugmentedEvent} */ - dispatch(name: T_5, details?: PhotoSwipeEventsMap[T_5]): AugmentedEvent; + dispatch(name: T_5, details?: PhotoSwipeEventsMap[T_5] | undefined): AugmentedEvent; } -/** @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} 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, 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, transitionDuration: number | false }} 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, 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) => 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, itemData: SlideData, index: number) => HTMLElement} thumbEl - * Modify the thubmnail element from which opening zoom animation starts or ends. - * https://photoswipe.com/filters/#thumbel - * - * @prop {(thumbBounds: Bounds, itemData: SlideData, index: number) => Bounds} thumbBounds - * Modify the thubmnail bounds from which opening zoom animation starts or ends. - * https://photoswipe.com/filters/#thumbbounds - * - * @prop {(srcsetSizesWidth: number, content: Content) => number} srcsetSizesWidth - * - */ -/** - * @template {keyof PhotoSwipeFiltersMap} T - * @typedef {{ fn: PhotoSwipeFiltersMap[T], priority: number }} Filter - */ -/** - * @template {keyof PhotoSwipeEventsMap} T - * @typedef {PhotoSwipeEventsMap[T] extends undefined ? PhotoSwipeEvent : PhotoSwipeEvent & PhotoSwipeEventsMap[T]} AugmentedEvent - */ -/** - * @template {keyof PhotoSwipeEventsMap} T - * @typedef {(event: AugmentedEvent) => void} EventCallback - */ /** * Base PhotoSwipe event object * @@ -974,7 +976,7 @@ declare class PhotoSwipeEvent { * @param {T} type * @param {PhotoSwipeEventsMap[T]} [details] */ - constructor(type: T, details?: PhotoSwipeEventsMap[T]); + constructor(type: T, details?: PhotoSwipeEventsMap[T] | undefined); type: T; defaultPrevented: boolean; preventDefault(): void; diff --git a/dist/types/gestures/zoom-handler.d.ts b/dist/types/gestures/zoom-handler.d.ts index 90f9e106..da529be6 100644 --- a/dist/types/gestures/zoom-handler.d.ts +++ b/dist/types/gestures/zoom-handler.d.ts @@ -44,5 +44,5 @@ declare class ZoomHandler { * @param {boolean} [ignoreGesture] * Wether gesture coordinates should be ignored when calculating destination pan position. */ - correctZoomPan(ignoreGesture?: boolean): void; + correctZoomPan(ignoreGesture?: boolean | undefined): void; } diff --git a/dist/types/lightbox/lightbox.d.ts b/dist/types/lightbox/lightbox.d.ts index d7901c32..2eabcfd4 100644 --- a/dist/types/lightbox/lightbox.d.ts +++ b/dist/types/lightbox/lightbox.d.ts @@ -46,9 +46,9 @@ export type EventCallback = import('../core/eventable.js').EventCallback; */ declare class PhotoSwipeLightbox extends PhotoSwipeBase { /** - * @param {PhotoSwipeOptions} options + * @param {Partial} [options] */ - constructor(options: PhotoSwipeOptions); + constructor(options?: Partial | undefined); _uid: number; /** * Initialize lightbox, should be called only once. @@ -72,8 +72,8 @@ declare class PhotoSwipeLightbox extends PhotoSwipeBase { * @param {DataSource} dataSource * @param {Point | null} [initialPoint] */ - loadAndOpen(index: number, dataSource: DataSource, initialPoint?: Point | null): boolean; - shouldOpen: boolean; + loadAndOpen(index: number, dataSource: DataSource, initialPoint?: import("../photoswipe.js").Point | null | undefined): boolean; + shouldOpen: boolean | undefined; /** * Load the main module and the slide content by index * @@ -81,7 +81,7 @@ declare class PhotoSwipeLightbox extends PhotoSwipeBase { * @param {DataSource=} dataSource */ preload(index: number, dataSource?: DataSource | undefined): void; - _preloadedContent: import("../slide/content.js").default; + _preloadedContent: import("../slide/content.js").default | null | undefined; /** * @private * @param {Type | { default: Type }} module diff --git a/dist/types/main-scroll.d.ts b/dist/types/main-scroll.d.ts index c0555f1a..efc396ed 100644 --- a/dist/types/main-scroll.d.ts +++ b/dist/types/main-scroll.d.ts @@ -33,7 +33,7 @@ declare class MainScroll { * * @param {boolean} [resizeSlides] Whether slides content should resized */ - resize(resizeSlides?: boolean): void; + resize(resizeSlides?: boolean | undefined): void; /** * Reset X position of the main scroller to zero */ @@ -63,7 +63,7 @@ declare class MainScroll { * @param {number} [velocityX] * @returns {boolean} whether index was changed or not */ - moveIndexBy(diff: number, animate?: boolean, velocityX?: number): boolean; + moveIndexBy(diff: number, animate?: boolean | undefined, velocityX?: number | undefined): boolean; /** * X position of the main scroll for the current slide * (ignores position during dragging) @@ -86,5 +86,5 @@ declare class MainScroll { * @param {number} x * @param {boolean} [dragging] */ - moveTo(x: number, dragging?: boolean): void; + moveTo(x: number, dragging?: boolean | undefined): void; } diff --git a/dist/types/opener.d.ts b/dist/types/opener.d.ts index e567ebad..a6548912 100644 --- a/dist/types/opener.d.ts +++ b/dist/types/opener.d.ts @@ -47,7 +47,7 @@ declare class Opener { private _cropContainer1; /** * @private - * @type { HTMLElement | undefined } + * @type { HTMLElement | null | undefined } */ private _cropContainer2; /** @@ -61,7 +61,7 @@ declare class Opener { close(): void; /** @private */ private _applyStartProps; - _animateZoom: boolean; + _animateZoom: boolean | undefined; /** @private */ private _start; /** @private */ diff --git a/dist/types/photoswipe.d.ts b/dist/types/photoswipe.d.ts index 3a77e682..ea79b325 100644 --- a/dist/types/photoswipe.d.ts +++ b/dist/types/photoswipe.d.ts @@ -58,20 +58,20 @@ export type PhotoSwipeOptions = { /** * Background backdrop opacity, always define it via this option and not via CSS rgba color. */ - bgOpacity?: number | undefined; + bgOpacity: number; /** * Spacing between slides. Defined as ratio relative to the viewport width (0.1 = 10% of viewport). */ - spacing?: number | undefined; + spacing: number; /** * Allow swipe navigation to the next slide when the current slide is zoomed. Does not apply to mouse events. */ - allowPanToNext?: boolean | undefined; + allowPanToNext: boolean; /** * If set to true you'll be able to swipe from the last to the first image. * Option is always false when there are less than 3 slides. */ - loop?: boolean | undefined; + loop: boolean; /** * By default PhotoSwipe zooms image with ctrl-wheel, if you enable this option - image will zoom just via wheel. */ @@ -79,11 +79,11 @@ export type PhotoSwipeOptions = { /** * Pinch touch gesture to close the gallery. */ - pinchToClose?: boolean | undefined; + pinchToClose: boolean; /** * Vertical drag gesture to close the PhotoSwipe. */ - closeOnVerticalDrag?: boolean | undefined; + closeOnVerticalDrag: boolean; /** * Slide area padding (in pixels). */ @@ -91,78 +91,78 @@ export type PhotoSwipeOptions = { /** * The option is checked frequently, so make sure it's performant. Overrides padding option if defined. For example: */ - paddingFn?: (viewportSize: Point, itemData: SlideData, index: number) => Padding; + paddingFn?: ((viewportSize: Point, itemData: SlideData, index: number) => Padding) | undefined; /** * Transition duration in milliseconds, can be 0. */ - hideAnimationDuration?: number | false; + hideAnimationDuration: number | false; /** * Transition duration in milliseconds, can be 0. */ - showAnimationDuration?: number | false; + showAnimationDuration: number | false; /** * Transition duration in milliseconds, can be 0. */ - zoomAnimationDuration?: number | false; + zoomAnimationDuration: number | false; /** * String, 'cubic-bezier(.4,0,.22,1)'. CSS easing function for open/close/zoom transitions. */ - easing?: string | undefined; + easing: string; /** * Esc key to close. */ - escKey?: boolean | undefined; + escKey: boolean; /** * Left/right arrow keys for navigation. */ - arrowKeys?: boolean | undefined; + arrowKeys: boolean; /** * Restore focus the last active element after PhotoSwipe is closed. */ - returnFocus?: boolean | undefined; + returnFocus: boolean; /** * If image is not zoomable (for example, smaller than viewport) it can be closed by clicking on it. */ - clickToCloseNonZoomable?: boolean | undefined; + clickToCloseNonZoomable: boolean; /** * Refer to click and tap actions page. */ - imageClickAction?: ActionType | ActionFn | false; + imageClickAction: ActionType | ActionFn | false; /** * Refer to click and tap actions page. */ - bgClickAction?: ActionType | ActionFn | false; + bgClickAction: ActionType | ActionFn | false; /** * Refer to click and tap actions page. */ - tapAction?: ActionType | ActionFn | false; + tapAction: ActionType | ActionFn | false; /** * Refer to click and tap actions page. */ - doubleTapAction?: ActionType | ActionFn | false; + doubleTapAction: ActionType | ActionFn | false; /** * Delay before the loading indicator will be displayed, * if image is loaded during it - the indicator will not be displayed at all. Can be zero. */ - preloaderDelay?: number | undefined; + preloaderDelay: number; /** * Used for slide count indicator ("1 of 10 "). */ - indexIndicatorSep?: string | undefined; + indexIndicatorSep: string; /** * A function that should return slide viewport width and height, in format {x: 100, y: 100}. */ - getViewportSizeFn?: (options: PhotoSwipeOptions, pswp: PhotoSwipe) => Point; + getViewportSizeFn?: ((options: PhotoSwipeOptions, pswp: PhotoSwipe) => Point) | undefined; /** * Message to display when the image wasn't able to load. If you need to display HTML - use contentErrorElement filter. */ - errorMsg?: string | undefined; + errorMsg: string; /** * Lazy loading of nearby slides based on direction of movement. Should be an array with two integers, * first one - number of items to preload before the current image, second one - after the current image. * Two nearby images are always loaded. */ - preload?: [number, number] | undefined; + preload: [number, number]; /** * Class that will be added to the root element of PhotoSwipe, may contain multiple separated by space. * Example on Styling page. @@ -176,7 +176,7 @@ export type PhotoSwipeOptions = { * Maximum width of image to animate, if initial rendered image width * is larger than this value - the opening/closing transition will be automatically disabled. */ - maxWidthToAnimate?: number | undefined; + maxWidthToAnimate: number; /** * Translating */ @@ -199,12 +199,12 @@ export type PhotoSwipeOptions = { * * Animations are automatically disabled if user `(prefers-reduced-motion: reduce)`. */ - showHideAnimationType?: 'zoom' | 'fade' | 'none'; + showHideAnimationType?: "none" | "zoom" | "fade" | undefined; /** * Defines start slide index. */ - index?: number | undefined; - getClickedIndexFn?: (e: MouseEvent) => number; + index: number; + getClickedIndexFn?: ((e: MouseEvent) => number) | undefined; arrowPrev?: boolean | undefined; arrowNext?: boolean | undefined; zoom?: boolean | undefined; @@ -216,38 +216,228 @@ export type PhotoSwipeOptions = { closeSVG?: string | undefined; counterSVG?: string | undefined; counterTitle?: string | undefined; - initialZoomLevel?: ZoomLevelOption | undefined; - secondaryZoomLevel?: ZoomLevelOption | undefined; - maxZoomLevel?: ZoomLevelOption | undefined; + initialZoomLevel?: import("./slide/zoom-level.js").ZoomLevelOption | undefined; + secondaryZoomLevel?: import("./slide/zoom-level.js").ZoomLevelOption | undefined; + maxZoomLevel?: import("./slide/zoom-level.js").ZoomLevelOption | undefined; mouseMovePan?: boolean | undefined; - initialPointerPos?: Point | null; + initialPointerPos?: Point | null | undefined; showHideOpacity?: boolean | undefined; - pswpModule?: PhotoSwipeModuleOption; - openPromise?: () => Promise; + pswpModule?: PhotoSwipeModuleOption | undefined; + openPromise?: (() => Promise) | undefined; preloadFirstSlide?: boolean | undefined; gallery?: ElementProvider | undefined; gallerySelector?: string | undefined; children?: ElementProvider | undefined; childSelector?: string | undefined; - thumbSelector?: string | false; + thumbSelector?: string | false | undefined; }; +/** + * @template T + * @typedef {import('./types.js').Type} Type + */ +/** @typedef {import('./slide/slide.js').SlideData} SlideData */ +/** @typedef {import('./slide/zoom-level.js').ZoomLevelOption} ZoomLevelOption */ +/** @typedef {import('./ui/ui-element.js').UIElementData} UIElementData */ +/** @typedef {import('./main-scroll.js').ItemHolder} ItemHolder */ +/** @typedef {import('./core/eventable.js').PhotoSwipeEventsMap} PhotoSwipeEventsMap */ +/** @typedef {import('./core/eventable.js').PhotoSwipeFiltersMap} PhotoSwipeFiltersMap */ +/** + * @template T + * @typedef {import('./core/eventable.js').EventCallback} EventCallback + */ +/** + * @template T + * @typedef {import('./core/eventable.js').AugmentedEvent} AugmentedEvent + */ +/** @typedef {{ x: number; y: number; id?: string | number }} Point */ +/** @typedef {{ top: number; bottom: number; left: number; right: number }} Padding */ +/** @typedef {SlideData[]} DataSourceArray */ +/** @typedef {{ gallery: HTMLElement; items?: HTMLElement[] }} DataSourceObject */ +/** @typedef {DataSourceArray | DataSourceObject} DataSource */ +/** @typedef {(point: Point, originalEvent: PointerEvent) => void} ActionFn */ +/** @typedef {'close' | 'next' | 'zoom' | 'zoom-or-close' | 'toggle-controls'} ActionType */ +/** @typedef {Type | { default: Type }} PhotoSwipeModule */ +/** @typedef {PhotoSwipeModule | Promise | (() => Promise)} PhotoSwipeModuleOption */ +/** + * @typedef {string | NodeListOf | HTMLElement[] | HTMLElement} ElementProvider + */ +/** + * @typedef {Object} PhotoSwipeOptions https://photoswipe.com/options/ + * + * @prop {DataSource} [dataSource] + * Pass an array of any items via dataSource option. Its length will determine amount of slides + * (which may be modified further from numItems event). + * + * Each item should contain data that you need to generate slide + * (for image slide it would be src (image URL), width (image width), height, srcset, alt). + * + * If these properties are not present in your initial array, you may "pre-parse" each item from itemData filter. + * + * @prop {number} bgOpacity + * Background backdrop opacity, always define it via this option and not via CSS rgba color. + * + * @prop {number} spacing + * Spacing between slides. Defined as ratio relative to the viewport width (0.1 = 10% of viewport). + * + * @prop {boolean} allowPanToNext + * Allow swipe navigation to the next slide when the current slide is zoomed. Does not apply to mouse events. + * + * @prop {boolean} loop + * If set to true you'll be able to swipe from the last to the first image. + * Option is always false when there are less than 3 slides. + * + * @prop {boolean} [wheelToZoom] + * By default PhotoSwipe zooms image with ctrl-wheel, if you enable this option - image will zoom just via wheel. + * + * @prop {boolean} pinchToClose + * Pinch touch gesture to close the gallery. + * + * @prop {boolean} closeOnVerticalDrag + * Vertical drag gesture to close the PhotoSwipe. + * + * @prop {Padding} [padding] + * Slide area padding (in pixels). + * + * @prop {(viewportSize: Point, itemData: SlideData, index: number) => Padding} [paddingFn] + * The option is checked frequently, so make sure it's performant. Overrides padding option if defined. For example: + * + * @prop {number | false} hideAnimationDuration + * Transition duration in milliseconds, can be 0. + * + * @prop {number | false} showAnimationDuration + * Transition duration in milliseconds, can be 0. + * + * @prop {number | false} zoomAnimationDuration + * Transition duration in milliseconds, can be 0. + * + * @prop {string} easing + * String, 'cubic-bezier(.4,0,.22,1)'. CSS easing function for open/close/zoom transitions. + * + * @prop {boolean} escKey + * Esc key to close. + * + * @prop {boolean} arrowKeys + * Left/right arrow keys for navigation. + * + * @prop {boolean} returnFocus + * Restore focus the last active element after PhotoSwipe is closed. + * + * @prop {boolean} clickToCloseNonZoomable + * If image is not zoomable (for example, smaller than viewport) it can be closed by clicking on it. + * + * @prop {ActionType | ActionFn | false} imageClickAction + * Refer to click and tap actions page. + * + * @prop {ActionType | ActionFn | false} bgClickAction + * Refer to click and tap actions page. + * + * @prop {ActionType | ActionFn | false} tapAction + * Refer to click and tap actions page. + * + * @prop {ActionType | ActionFn | false} doubleTapAction + * Refer to click and tap actions page. + * + * @prop {number} preloaderDelay + * Delay before the loading indicator will be displayed, + * if image is loaded during it - the indicator will not be displayed at all. Can be zero. + * + * @prop {string} indexIndicatorSep + * Used for slide count indicator ("1 of 10 "). + * + * @prop {(options: PhotoSwipeOptions, pswp: PhotoSwipe) => Point} [getViewportSizeFn] + * A function that should return slide viewport width and height, in format {x: 100, y: 100}. + * + * @prop {string} errorMsg + * Message to display when the image wasn't able to load. If you need to display HTML - use contentErrorElement filter. + * + * @prop {[number, number]} preload + * Lazy loading of nearby slides based on direction of movement. Should be an array with two integers, + * first one - number of items to preload before the current image, second one - after the current image. + * Two nearby images are always loaded. + * + * @prop {string} [mainClass] + * Class that will be added to the root element of PhotoSwipe, may contain multiple separated by space. + * Example on Styling page. + * + * @prop {HTMLElement} [appendToEl] + * Element to which PhotoSwipe dialog will be appended when it opens. + * + * @prop {number} maxWidthToAnimate + * Maximum width of image to animate, if initial rendered image width + * is larger than this value - the opening/closing transition will be automatically disabled. + * + * @prop {string} [closeTitle] + * Translating + * + * @prop {string} [zoomTitle] + * Translating + * + * @prop {string} [arrowPrevTitle] + * Translating + * + * @prop {string} [arrowNextTitle] + * Translating + * + * @prop {'zoom' | 'fade' | 'none'} [showHideAnimationType] + * To adjust opening or closing transition type use lightbox option `showHideAnimationType` (`String`). + * It supports three values - `zoom` (default), `fade` (default if there is no thumbnail) and `none`. + * + * Animations are automatically disabled if user `(prefers-reduced-motion: reduce)`. + * + * @prop {number} index + * Defines start slide index. + * + * @prop {(e: MouseEvent) => number} [getClickedIndexFn] + * + * @prop {boolean} [arrowPrev] + * @prop {boolean} [arrowNext] + * @prop {boolean} [zoom] + * @prop {boolean} [close] + * @prop {boolean} [counter] + * + * @prop {string} [arrowPrevSVG] + * @prop {string} [arrowNextSVG] + * @prop {string} [zoomSVG] + * @prop {string} [closeSVG] + * @prop {string} [counterSVG] + * + * @prop {string} [arrowPrevTitle] + * @prop {string} [arrowNextTitle] + * @prop {string} [zoomTitle] + * @prop {string} [closeTitle] + * @prop {string} [counterTitle] + * + * @prop {ZoomLevelOption} [initialZoomLevel] + * @prop {ZoomLevelOption} [secondaryZoomLevel] + * @prop {ZoomLevelOption} [maxZoomLevel] + * + * @prop {boolean} [mouseMovePan] + * @prop {Point | null} [initialPointerPos] + * @prop {boolean} [showHideOpacity] + * + * @prop {PhotoSwipeModuleOption} [pswpModule] + * @prop {() => Promise} [openPromise] + * @prop {boolean} [preloadFirstSlide] + * @prop {ElementProvider} [gallery] + * @prop {string} [gallerySelector] + * @prop {ElementProvider} [children] + * @prop {string} [childSelector] + * @prop {string | false} [thumbSelector] + */ /** * PhotoSwipe Core */ declare class PhotoSwipe extends PhotoSwipeBase { /** - * @param {PhotoSwipeOptions} options + * @param {Partial} [options] */ - constructor(options: PhotoSwipeOptions); + constructor(options?: Partial | undefined); /** * offset of viewport relative to document * - * @type {{ x?: number; y?: number }} + * @type {Point} */ - offset: { - x?: number; - y?: number; - }; + offset: Point; /** * @type {Point} * @private @@ -261,12 +451,27 @@ declare class PhotoSwipe extends PhotoSwipeBase { viewportSize: Point; /** * background (backdrop) opacity - * - * @type {number} */ bgOpacity: number; - /** @type {HTMLDivElement} */ - topBar: HTMLDivElement; + currIndex: number; + potentialIndex: number; + /** + * @private + * @type {SlideData} + */ + private _initialItemData; + /** @type {HTMLDivElement | undefined} */ + topBar: HTMLDivElement | undefined; + /** @type {HTMLDivElement | undefined} */ + element: HTMLDivElement | undefined; + /** @type {HTMLDivElement | undefined} */ + template: HTMLDivElement | undefined; + /** @type {HTMLDivElement | undefined} */ + container: HTMLDivElement | undefined; + /** @type {HTMLElement | undefined} */ + scrollWrap: HTMLElement | undefined; + /** @type {Slide | undefined} */ + currSlide: Slide | undefined; events: DOMEvents; /** @type {Animations} */ animations: Animations; @@ -275,13 +480,11 @@ declare class PhotoSwipe extends PhotoSwipeBase { opener: Opener; keyboard: Keyboard; contentLoader: ContentLoader; - init(): boolean; - isOpen: boolean; - currIndex: number; - potentialIndex: number; scrollWheel: ScrollWheel; - _initialItemData: import("./slide/slide.js").SlideData; - _initialThumbBounds: import("./slide/get-thumb-bounds.js").Bounds; + ui: UI; + init(): true | undefined; + isOpen: boolean | undefined; + _initialThumbBounds: import("./slide/get-thumb-bounds.js").Bounds | null | undefined; /** * Get looped slide index * (for example, -1 will return the last slide) @@ -308,7 +511,7 @@ declare class PhotoSwipe extends PhotoSwipeBase { * * @param {Parameters} args */ - zoomTo(destZoomLevel: number, centerPoint?: Point, transitionDuration?: number | false, ignoreBounds?: boolean): void; + zoomTo(destZoomLevel: number, centerPoint?: Point | undefined, transitionDuration?: number | false | undefined, ignoreBounds?: boolean | undefined): void; /** * @see slide/slide.js toggleZoom */ @@ -318,7 +521,7 @@ declare class PhotoSwipe extends PhotoSwipeBase { * After closing transition ends - destroy it */ close(): void; - isDestroying: boolean; + isDestroying: boolean | undefined; /** * Destroys the gallery: * - instantly closes the gallery @@ -327,15 +530,12 @@ declare class PhotoSwipe extends PhotoSwipeBase { * - removes elements from DOM */ destroy(): void; - listeners: any; /** * Refresh/reload content of a slide by its index * * @param {number} slideIndex */ refreshSlideContent(slideIndex: number): void; - /** @type {Slide} */ - currSlide: Slide; /** * Set slide content * @@ -363,7 +563,7 @@ declare class PhotoSwipe extends PhotoSwipeBase { * Whether mouse is detected */ mouseDetected(): void; - hasMouse: boolean; + hasMouse: boolean | undefined; /** * Page resize event handler * @@ -390,31 +590,22 @@ declare class PhotoSwipe extends PhotoSwipeBase { * @private */ private _createMainStructure; - element: HTMLDivElement; - template: HTMLDivElement; - bg: HTMLDivElement; - scrollWrap: HTMLElement; - container: HTMLDivElement; - ui: UI; + bg: HTMLDivElement | undefined; /** * Get position and dimensions of small thumbnail * {x:,y:,w:} * * Height is optional (calculated based on the large image) */ - getThumbBounds(): import("./slide/get-thumb-bounds.js").Bounds; + getThumbBounds(): import("./slide/get-thumb-bounds.js").Bounds | null; /** * If the PhotoSwipe can have continious loop * @returns Boolean */ canLoop(): boolean; - /** - * @param {PhotoSwipeOptions} options - * @private - */ - private _prepareOptions; } import PhotoSwipeBase from "./core/base.js"; +import Slide from "./slide/slide.js"; import DOMEvents from "./util/dom-events.js"; import Animations from "./util/animations.js"; import MainScroll from "./main-scroll.js"; @@ -423,5 +614,4 @@ import Opener from "./opener.js"; import Keyboard from "./keyboard.js"; import ContentLoader from "./slide/loader.js"; import ScrollWheel from "./scroll-wheel.js"; -import Slide from "./slide/slide.js"; import UI from "./ui/ui.js"; diff --git a/dist/types/slide/content.d.ts b/dist/types/slide/content.d.ts index 5ea9328d..196722e7 100644 --- a/dist/types/slide/content.d.ts +++ b/dist/types/slide/content.d.ts @@ -37,10 +37,10 @@ declare class Content { /** * Preload content * - * @param {boolean} [isLazy] + * @param {boolean} isLazy * @param {boolean} [reload] */ - load(isLazy?: boolean, reload?: boolean): void; + load(isLazy: boolean, reload?: boolean | undefined): void; /** * Preload image * diff --git a/dist/types/slide/get-thumb-bounds.d.ts b/dist/types/slide/get-thumb-bounds.d.ts index 4bb3c54f..a7e5ddaa 100644 --- a/dist/types/slide/get-thumb-bounds.d.ts +++ b/dist/types/slide/get-thumb-bounds.d.ts @@ -5,9 +5,9 @@ * @param {number} index * @param {SlideData} itemData * @param {PhotoSwipe} instance PhotoSwipe instance - * @returns {Bounds | undefined} + * @returns {Bounds | null} */ -export function getThumbBounds(index: number, itemData: SlideData, instance: PhotoSwipe): Bounds | undefined; +export function getThumbBounds(index: number, itemData: SlideData, instance: PhotoSwipe): Bounds | null; export type SlideData = import('./slide.js').SlideData; export type PhotoSwipe = import('../photoswipe.js').default; export type Bounds = { diff --git a/dist/types/slide/loader.d.ts b/dist/types/slide/loader.d.ts index bddf9049..25e55880 100644 --- a/dist/types/slide/loader.d.ts +++ b/dist/types/slide/loader.d.ts @@ -41,7 +41,7 @@ declare class ContentLoader { * * @param {number} [diff] Difference between slide indexes that was changed recently, or 0. */ - updateLazy(diff?: number): void; + updateLazy(diff?: number | undefined): void; /** * @param {number} initialIndex */ diff --git a/dist/types/slide/slide.d.ts b/dist/types/slide/slide.d.ts index fbe55a3c..2335609d 100644 --- a/dist/types/slide/slide.d.ts +++ b/dist/types/slide/slide.d.ts @@ -6,51 +6,51 @@ export type _SlideData = { /** * thumbnail element */ - element?: HTMLElement; + element?: HTMLElement | undefined; /** * image URL */ - src?: string; + src?: string | undefined; /** * image srcset */ - srcset?: string; + srcset?: string | undefined; /** * image width (deprecated) */ - w?: number; + w?: number | undefined; /** * image height (deprecated) */ - h?: number; + h?: number | undefined; /** * image width */ - width?: number; + width?: number | undefined; /** * image height */ - height?: number; + height?: number | undefined; /** * placeholder image URL that's displayed before large image is loaded */ - msrc?: string; + msrc?: string | undefined; /** * image alt text */ - alt?: string; + alt?: string | undefined; /** * whether thumbnail is cropped client-side or not */ - thumbCropped?: boolean; + thumbCropped?: boolean | undefined; /** * html content of a slide */ - html?: string; + html?: string | undefined; /** * slide type */ - type?: 'image' | 'html' | string; + type?: string | undefined; }; /** * Renders and allows to control a single slide @@ -67,12 +67,12 @@ declare class Slide { pswp: import("../photoswipe.js").default; isActive: boolean; currentResolution: number; - /** @type {Point | null} */ - panAreaSize: Point | null; - isFirstSlide: boolean; - zoomLevels: ZoomLevel; + /** @type {Point} */ + panAreaSize: Point; /** @type {Point} */ pan: Point; + isFirstSlide: boolean; + zoomLevels: ZoomLevel; content: import("./content.js").default; container: HTMLDivElement; /** @type {HTMLElement | null} */ @@ -131,7 +131,7 @@ declare class Slide { * * @param {boolean} [force] if size should be updated even if dimensions weren't changed */ - updateContentSize(force?: boolean): void; + updateContentSize(force?: boolean | undefined): void; /** * @param {number} width * @param {number} height @@ -148,11 +148,11 @@ declare class Slide { * @param {number | false} [transitionDuration] Transition duration, may be set to 0. * @param {boolean} [ignoreBounds] Minimum and maximum zoom levels will be ignored. */ - zoomTo(destZoomLevel: number, centerPoint?: Point, transitionDuration?: number | false, ignoreBounds?: boolean): void; + zoomTo(destZoomLevel: number, centerPoint?: import("../photoswipe.js").Point | undefined, transitionDuration?: number | false | undefined, ignoreBounds?: boolean | undefined): void; /** * @param {Point} [centerPoint] */ - toggleZoom(centerPoint?: Point): void; + toggleZoom(centerPoint?: import("../photoswipe.js").Point | undefined): void; /** * Updates zoom level property and recalculates new pan bounds, * unlike zoomTo it does not apply transform (use applyCurrentZoomPan) @@ -173,7 +173,7 @@ declare class Slide { * @param {number} [prevZoomLevel] Zoom level before new zoom was applied. * @returns {number} */ - calculateZoomToPanOffset(axis: 'x' | 'y', point?: Point, prevZoomLevel?: number): number; + calculateZoomToPanOffset(axis: 'x' | 'y', point?: import("../photoswipe.js").Point | undefined, prevZoomLevel?: number | undefined): number; /** * Apply pan and keep it within bounds. * diff --git a/dist/types/slide/zoom-level.d.ts b/dist/types/slide/zoom-level.d.ts index 0e2f3610..4baff8b4 100644 --- a/dist/types/slide/zoom-level.d.ts +++ b/dist/types/slide/zoom-level.d.ts @@ -20,8 +20,8 @@ declare class ZoomLevel { * @param {number} index Slide index * @param {PhotoSwipe} [pswp] PhotoSwipe instance, can be undefined if not initialized yet */ - constructor(options: PhotoSwipeOptions, itemData: SlideData, index: number, pswp?: PhotoSwipe); - pswp: import("../photoswipe.js").default; + constructor(options: PhotoSwipeOptions, itemData: SlideData, index: number, pswp?: import("../photoswipe.js").default | undefined); + pswp: import("../photoswipe.js").default | undefined; options: import("../photoswipe.js").PhotoSwipeOptions; itemData: import("../slide/slide.js").SlideData; index: number; diff --git a/dist/types/ui/ui-element.d.ts b/dist/types/ui/ui-element.d.ts index 2f1cede3..11437e53 100644 --- a/dist/types/ui/ui-element.d.ts +++ b/dist/types/ui/ui-element.d.ts @@ -5,23 +5,23 @@ export type PhotoSwipe = import('../photoswipe.js').default; */ export type Methods = import('../types.js').Methods; export type UIElementMarkupProps = { - isCustomSVG?: boolean; + isCustomSVG?: boolean | undefined; inner: string; - outlineID?: string; - size?: number | string; + outlineID?: string | undefined; + size?: string | number | undefined; }; export type UIElementData = { - name?: DefaultUIElements | string; - className?: string; - html?: UIElementMarkup; - isButton?: boolean; - tagName?: keyof HTMLElementTagNameMap; - title?: string; - ariaLabel?: string; - onInit?: (element: HTMLElement, pswp: PhotoSwipe) => void; - onClick?: import("../types.js").Methods | ((e: MouseEvent, element: HTMLElement, pswp: PhotoSwipe) => void); - appendTo?: 'bar' | 'wrapper' | 'root'; - order?: number; + name?: string | undefined; + className?: string | undefined; + html?: UIElementMarkup | undefined; + isButton?: boolean | undefined; + tagName?: keyof HTMLElementTagNameMap | undefined; + title?: string | undefined; + ariaLabel?: string | undefined; + onInit?: ((element: HTMLElement, pswp: PhotoSwipe) => void) | undefined; + onClick?: import("../types.js").Methods | ((e: MouseEvent, element: HTMLElement, pswp: PhotoSwipe) => void) | undefined; + appendTo?: "bar" | "wrapper" | "root" | undefined; + order?: number | undefined; }; export type DefaultUIElements = 'arrowPrev' | 'arrowNext' | 'close' | 'zoom' | 'counter'; export type UIElementMarkup = string | UIElementMarkupProps; diff --git a/dist/types/util/animations.d.ts b/dist/types/util/animations.d.ts index 7a5d06a6..3883a874 100644 --- a/dist/types/util/animations.d.ts +++ b/dist/types/util/animations.d.ts @@ -2,11 +2,11 @@ export default Animations; export type CssAnimationProps = import('./css-animation.js').CssAnimationProps; export type SpringAnimationProps = import('./spring-animation.js').SpringAnimationProps; export type SharedAnimationProps = { - name?: string; - isPan?: boolean; - isMainScroll?: boolean; - onComplete?: VoidFunction; - onFinish?: VoidFunction; + name?: string | undefined; + isPan?: boolean | undefined; + isMainScroll?: boolean | undefined; + onComplete?: VoidFunction | undefined; + onFinish?: VoidFunction | undefined; }; export type Animation = SpringAnimation | CSSAnimation; export type AnimationProps = SpringAnimationProps | CssAnimationProps; diff --git a/dist/types/util/css-animation.d.ts b/dist/types/util/css-animation.d.ts index bdd04f90..fd873c3f 100644 --- a/dist/types/util/css-animation.d.ts +++ b/dist/types/util/css-animation.d.ts @@ -2,10 +2,10 @@ export default CSSAnimation; export type SharedAnimationProps = import('./animations.js').SharedAnimationProps; export type DefaultCssAnimationProps = { target: HTMLElement; - duration?: number; - easing?: string; - transform?: string; - opacity?: string; + duration?: number | undefined; + easing?: string | undefined; + transform?: string | undefined; + opacity?: string | undefined; }; export type CssAnimationProps = SharedAnimationProps & DefaultCssAnimationProps; /** @typedef {import('./animations.js').SharedAnimationProps} SharedAnimationProps */ diff --git a/dist/types/util/dom-events.d.ts b/dist/types/util/dom-events.d.ts index 207db6ef..419eb2e3 100644 --- a/dist/types/util/dom-events.d.ts +++ b/dist/types/util/dom-events.d.ts @@ -3,14 +3,14 @@ export type PoolItem = { target: HTMLElement | Window | Document | undefined | null; type: string; listener: EventListenerOrEventListenerObject; - passive: boolean; + passive?: boolean | undefined; }; /** * @typedef {Object} PoolItem * @prop {HTMLElement | Window | Document | undefined | null} target * @prop {string} type * @prop {EventListenerOrEventListenerObject} listener - * @prop {boolean} passive + * @prop {boolean} [passive] */ declare class DOMEvents { /** diff --git a/dist/types/util/spring-animation.d.ts b/dist/types/util/spring-animation.d.ts index 04d7c743..da1e29fe 100644 --- a/dist/types/util/spring-animation.d.ts +++ b/dist/types/util/spring-animation.d.ts @@ -4,8 +4,8 @@ export type DefaultSpringAnimationProps = { start: number; end: number; velocity: number; - dampingRatio?: number; - naturalFrequency?: number; + dampingRatio?: number | undefined; + naturalFrequency?: number | undefined; onUpdate: (end: number) => void; }; export type SpringAnimationProps = SharedAnimationProps & DefaultSpringAnimationProps; @@ -27,7 +27,7 @@ declare class SpringAnimation { */ constructor(props: SpringAnimationProps); props: SpringAnimationProps; - onFinish: VoidFunction; _raf: number; + onFinish: VoidFunction; destroy(): void; } diff --git a/dist/types/util/spring-easer.d.ts b/dist/types/util/spring-easer.d.ts index 951d41f2..779ff7bf 100644 --- a/dist/types/util/spring-easer.d.ts +++ b/dist/types/util/spring-easer.d.ts @@ -18,7 +18,7 @@ declare class SpringEaser { * and the faster it will slow down. * Recommended value from 10 to 50 */ - constructor(initialVelocity: number, dampingRatio?: number, naturalFrequency?: number); + constructor(initialVelocity: number, dampingRatio?: number | undefined, naturalFrequency?: number | undefined); velocity: number; _dampingRatio: number; _naturalFrequency: number; diff --git a/dist/types/util/util.d.ts b/dist/types/util/util.d.ts index 3d9fd6ee..16b73358 100644 --- a/dist/types/util/util.d.ts +++ b/dist/types/util/util.d.ts @@ -6,7 +6,7 @@ * @param {Node} [appendToEl] * @returns {HTMLElementTagNameMap[T]} */ -export function createElement(className: string, tagName: T, appendToEl?: Node): HTMLElementTagNameMap[T]; +export function createElement(className: string, tagName: T, appendToEl?: Node | undefined): HTMLElementTagNameMap[T]; /** * @param {Point} p1 * @param {Point} p2 @@ -15,8 +15,9 @@ export function createElement(className: export function equalizePoints(p1: Point, p2: Point): Point; /** * @param {Point} p + * @returns {Point} */ -export function roundPoint(p: Point): void; +export function roundPoint(p: Point): Point; /** * Returns distance between two points. * @@ -50,7 +51,7 @@ export function clamp(val: number, min: number, max: number): number; * @param {number} [scale] * @returns {string} */ -export function toTransformString(x: number, y?: number, scale?: number): string; +export function toTransformString(x: number, y?: number | undefined, scale?: number | undefined): string; /** * Apply transform:translate(x, y) scale(scale) to element * @@ -59,7 +60,7 @@ export function toTransformString(x: number, y?: number, scale?: number): string * @param {number} [y] * @param {number} [scale] */ -export function setTransform(el: HTMLElement, x: number, y?: number, scale?: number): void; +export function setTransform(el: HTMLElement, x: number, y?: number | undefined, scale?: number | undefined): void; /** * Apply CSS transition to element * @@ -68,7 +69,7 @@ export function setTransform(el: HTMLElement, x: number, y?: number, scale?: num * @param {number} [duration] in ms * @param {string} [ease] CSS easing function */ -export function setTransitionStyle(el: HTMLElement, prop?: string, duration?: number, ease?: string): void; +export function setTransitionStyle(el: HTMLElement, prop?: string | undefined, duration?: number | undefined, ease?: string | undefined): void; /** * Apply width and height CSS properties to element * @@ -97,12 +98,12 @@ export function specialKeyUsed(e: MouseEvent | KeyboardEvent): boolean; /** * Parse `gallery` or `children` options. * - * @param {import('../photoswipe.js').ElementProvider} option + * @param {import('../photoswipe.js').ElementProvider} [option] * @param {string} [legacySelector] * @param {HTMLElement | Document} [parent] * @returns HTMLElement[] */ -export function getElementsFromOption(option: import('../photoswipe.js').ElementProvider, legacySelector?: string, parent?: HTMLElement | Document): HTMLElement[]; +export function getElementsFromOption(option?: import("../photoswipe.js").ElementProvider | undefined, legacySelector?: string | undefined, parent?: Document | HTMLElement | undefined): HTMLElement[]; /** * Check if variable is PhotoSwipe class * diff --git a/src/js/core/base.js b/src/js/core/base.js index f338e01e..499fcdfb 100644 --- a/src/js/core/base.js +++ b/src/js/core/base.js @@ -1,4 +1,4 @@ -import Eventable from './eventable.js'; +import Eventable, { defaultOptions } from './eventable.js'; import { getElementsFromOption } from '../util/util.js'; import Content from '../slide/content.js'; import { lazyLoadData } from '../slide/loader.js'; @@ -18,11 +18,12 @@ class PhotoSwipeBase extends Eventable { * @returns {number} */ getNumItems() { - let numItems; - const { dataSource } = this.options; + let numItems = 0; + let { dataSource } = this.options; if (!dataSource) { - numItems = 0; - } else if ('length' in dataSource) { + dataSource = []; + } + if ('length' in dataSource) { // may be an array or just object with length property numItems = dataSource.length; } else if ('gallery' in dataSource) { @@ -65,11 +66,12 @@ class PhotoSwipeBase extends Eventable { */ getItemData(index) { const { dataSource } = this.options; - let dataSourceItem; + /** @type {SlideData | HTMLElement} */ + let dataSourceItem = {}; if (Array.isArray(dataSource)) { // Datasource is an array of elements dataSourceItem = dataSource[index]; - } else if (dataSource && dataSource.gallery) { + } else if (dataSource && 'gallery' in dataSource) { // dataSource has gallery property, // thus it was created by Lightbox, based on // gallery and children options @@ -144,8 +146,8 @@ class PhotoSwipeBase extends Eventable { itemData.srcset = linkEl.dataset.pswpSrcset; } - itemData.width = parseInt(linkEl.dataset.pswpWidth, 10); - itemData.height = parseInt(linkEl.dataset.pswpHeight, 10); + 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; @@ -161,7 +163,7 @@ class PhotoSwipeBase extends Eventable { // 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.getAttribute('alt'); + itemData.alt = thumbnailEl.getAttribute('alt') ?? ''; } if (linkEl.dataset.pswpCropped || linkEl.dataset.cropped) { @@ -182,6 +184,24 @@ class PhotoSwipeBase extends Eventable { lazyLoadData(itemData, index) { return lazyLoadData(itemData, this, index); } + + /** + * @protected + * @param {Partial} options + * @returns {PhotoSwipeOptions} + */ + _prepareOptions(options) { + if (window.matchMedia('(prefers-reduced-motion), (update: slow)').matches) { + options.showHideAnimationType = 'none'; + options.zoomAnimationDuration = 0; + } + + /** @type {PhotoSwipeOptions}*/ + return { + ...defaultOptions, + ...options + }; + } } export default PhotoSwipeBase; diff --git a/src/js/core/eventable.js b/src/js/core/eventable.js index 4f07a95f..a6d050d4 100644 --- a/src/js/core/eventable.js +++ b/src/js/core/eventable.js @@ -86,7 +86,7 @@ * @prop {{ originalEvent: KeyboardEvent }} keydown can be default prevented * @prop {{ x: number; dragging: boolean }} moveMainScroll * @prop {{ slide: Slide }} firstZoomPan - * @prop {{ slide: Slide, data: SlideData, index: number }} gettingData + * @prop {{ slide: Slide | undefined, data: SlideData, index: number }} gettingData * @prop {undefined} beforeResize * @prop {undefined} resize * @prop {undefined} viewportSize @@ -99,7 +99,7 @@ * @prop {{ slide: Slide }} slideActivate * @prop {{ slide: Slide }} slideDeactivate * @prop {{ slide: Slide }} slideDestroy - * @prop {{ destZoomLevel: number, centerPoint: Point, transitionDuration: number | false }} beforeZoomTo + * @prop {{ destZoomLevel: number, centerPoint: Point | undefined, transitionDuration: number | false | undefined }} beforeZoomTo * @prop {{ slide: Slide }} zoomPanUpdate * @prop {{ slide: Slide }} initialZoomPan * @prop {{ slide: Slide }} calcSlideSize @@ -199,6 +199,35 @@ * @typedef {(event: AugmentedEvent) => void} EventCallback */ +/** @type {PhotoSwipeOptions} */ +export const defaultOptions = { + allowPanToNext: true, + spacing: 0.1, + loop: true, + pinchToClose: true, + closeOnVerticalDrag: true, + hideAnimationDuration: 333, + showAnimationDuration: 333, + zoomAnimationDuration: 333, + escKey: true, + arrowKeys: true, + returnFocus: true, + maxWidthToAnimate: 4000, + clickToCloseNonZoomable: true, + imageClickAction: 'zoom-or-close', + bgClickAction: 'close', + tapAction: 'toggle-controls', + doubleTapAction: 'zoom', + indexIndicatorSep: ' / ', + preloaderDelay: 2000, + bgOpacity: 0.8, + + index: 0, + errorMsg: 'The image cannot be loaded', + preload: [1, 2], + easing: 'cubic-bezier(.4,0,.22,1)' +}; + /** * Base PhotoSwipe event object * @@ -242,7 +271,7 @@ class Eventable { this.pswp = undefined; /** @type {PhotoSwipeOptions} */ - this.options = {}; + this.options = defaultOptions; } /** @@ -256,12 +285,10 @@ class Eventable { this._filters[name] = []; } - this._filters[name].push({ fn, priority }); - this._filters[name].sort((f1, f2) => f1.priority - f2.priority); + this._filters[name]?.push({ fn, priority }); + this._filters[name]?.sort((f1, f2) => f1.priority - f2.priority); - if (this.pswp) { - this.pswp.addFilter(name, fn, priority); - } + this.pswp?.addFilter(name, fn, priority); } /** @@ -287,12 +314,10 @@ class Eventable { * @returns {Parameters[0]} */ applyFilters(name, ...args) { - if (this._filters[name]) { - this._filters[name].forEach((filter) => { - // @ts-expect-error - args[0] = filter.fn.apply(this, args); - }); - } + this._filters[name]?.forEach((filter) => { + // @ts-expect-error + args[0] = filter.fn.apply(this, args); + }); return args[0]; } @@ -305,14 +330,12 @@ class Eventable { if (!this._listeners[name]) { this._listeners[name] = []; } - this._listeners[name].push(fn); + this._listeners[name]?.push(fn); // When binding events to lightbox, // also bind events to PhotoSwipe Core, // if it's open. - if (this.pswp) { - this.pswp.on(name, fn); - } + this.pswp?.on(name, fn); } /** @@ -326,9 +349,7 @@ class Eventable { this._listeners[name] = this._listeners[name].filter(listener => (fn !== listener)); } - if (this.pswp) { - this.pswp.off(name, fn); - } + this.pswp?.off(name, fn); } /** @@ -344,11 +365,9 @@ class Eventable { const event = /** @type {AugmentedEvent} */ (new PhotoSwipeEvent(name, details)); - if (this._listeners[name]) { - this._listeners[name].forEach((listener) => { - listener.call(this, event); - }); - } + this._listeners[name]?.forEach((listener) => { + listener.call(this, event); + }); return event; } diff --git a/src/js/gestures/drag-handler.js b/src/js/gestures/drag-handler.js index 4f4ea748..1e15f67f 100644 --- a/src/js/gestures/drag-handler.js +++ b/src/js/gestures/drag-handler.js @@ -38,7 +38,9 @@ class DragHandler { } start() { - equalizePoints(this.startPan, this.gestures.pswp.currSlide.pan); + if (this.gestures.pswp.currSlide) { + this.startPan = equalizePoints(this.startPan, this.gestures.pswp.currSlide.pan); + } this.gestures.pswp.animations.stopAll(); } @@ -48,7 +50,7 @@ class DragHandler { if (dragAxis === 'y' && pswp.options.closeOnVerticalDrag - && currSlide.currZoomLevel <= currSlide.zoomLevels.fit + && (currSlide && currSlide.currZoomLevel <= currSlide.zoomLevels.fit) && !this.gestures.isMultitouch) { // Handle vertical drag to close const panY = currSlide.pan.y + (p1.y - prevP1.y); @@ -63,15 +65,17 @@ class DragHandler { if (!mainScrollChanged) { this._panOrMoveMainScroll('y'); - roundPoint(currSlide.pan); - currSlide.applyCurrentZoomPan(); + if (currSlide) { + currSlide.pan = roundPoint(currSlide.pan); + currSlide.applyCurrentZoomPan(); + } } } } end() { const { pswp, velocity } = this.gestures; - const { mainScroll } = pswp; + const { mainScroll, currSlide } = pswp; let indexDiff = 0; pswp.animations.stopAll(); @@ -111,7 +115,7 @@ class DragHandler { } // Restore zoom level - if (pswp.currSlide.currZoomLevel > pswp.currSlide.zoomLevels.max + if ((currSlide && currSlide.currZoomLevel > currSlide.zoomLevels.max) || this.gestures.isMultitouch) { this.gestures.zoomLevels.correctZoomPan(true); } else { @@ -131,6 +135,11 @@ class DragHandler { _finishPanGestureForAxis(axis) { const { pswp, velocity } = this.gestures; const { currSlide } = pswp; + + if (!currSlide) { + return; + } + const { pan, bounds } = currSlide; const panPos = pan[axis]; const restoreBgOpacity = (pswp.bgOpacity < 1 && axis === 'y'); @@ -215,7 +224,7 @@ class DragHandler { const delta = (p1[axis] - prevP1[axis]); const newMainScrollX = mainScroll.x + delta; - if (!delta) { + if (!delta || !currSlide) { return false; } @@ -314,7 +323,7 @@ class DragHandler { * @returns {number} */ _getVerticalDragRatio(panY) { - return (panY - this.gestures.pswp.currSlide.bounds.center.y) + return (panY - (this.gestures.pswp.currSlide?.bounds.center.y ?? 0)) / (this.gestures.pswp.viewportSize.y / 3); } @@ -329,7 +338,13 @@ class DragHandler { * @param {number} [customFriction] (0.1 - 1) */ _setPanWithFriction(axis, potentialPan, customFriction) { - const { pan, bounds } = this.gestures.pswp.currSlide; + const { currSlide } = this.gestures.pswp; + + if (!currSlide) { + return; + } + + const { pan, bounds } = currSlide; const correctedPan = bounds.correctPan(axis, potentialPan); // If we are out of pan bounds if (correctedPan !== potentialPan || customFriction) { diff --git a/src/js/gestures/gestures.js b/src/js/gestures/gestures.js index 37d6f608..1226cca7 100644 --- a/src/js/gestures/gestures.js +++ b/src/js/gestures/gestures.js @@ -118,8 +118,10 @@ class Gestures { // and you don't preventDefault touchstart (which PhotoSwipe does), // preventDefault will have no effect on touchmove and touchend. // Unless you bind it previously. - pswp.scrollWrap.ontouchmove = () => {}; // eslint-disable-line - pswp.scrollWrap.ontouchend = () => {}; // eslint-disable-line + if (pswp.scrollWrap) { + pswp.scrollWrap.ontouchmove = () => {}; + pswp.scrollWrap.ontouchend = () => {}; + } } else { this._bindEvents('mouse', 'down', 'up'); } @@ -202,7 +204,7 @@ class Gestures { this.dragAxis = null; // we need to store initial point to determine the main axis, // drag is activated only after the axis is determined - equalizePoints(this.startP1, this.p1); + this.startP1 = equalizePoints(this.startP1, this.p1); } if (this._numActivePoints > 1) { @@ -250,7 +252,7 @@ class Gestures { this._intervalTime = Date.now(); //this._startTime = this._intervalTime; this._velocityCalculated = false; - equalizePoints(this._intervalP1, this.p1); + this._intervalP1 = equalizePoints(this._intervalP1, this.p1); this.velocity.x = 0; this.velocity.y = 0; this.drag.start(); @@ -371,7 +373,7 @@ class Gestures { this.velocity.y = this._getVelocity('y', duration); this._intervalTime = time; - equalizePoints(this._intervalP1, this.p1); + this._intervalP1 = equalizePoints(this._intervalP1, this.p1); this._velocityCalculated = true; } @@ -414,7 +416,7 @@ class Gestures { this.tapHandler.doubleTap(this.startP1, e); } } else { - equalizePoints(this._lastStartP1, this.startP1); + this._lastStartP1 = equalizePoints(this._lastStartP1, this.startP1); this._tapTimer = setTimeout(() => { this.tapHandler.tap(this.startP1, e); this._clearTapTimer(); @@ -503,11 +505,11 @@ class Gestures { // update points that PhotoSwipe uses // to calculate position and scale if (this._numActivePoints > 0) { - equalizePoints(this.p1, this._ongoingPointers[0]); + this.p1 = equalizePoints(this.p1, this._ongoingPointers[0]); } if (this._numActivePoints > 1) { - equalizePoints(this.p2, this._ongoingPointers[1]); + this.p2 = equalizePoints(this.p2, this._ongoingPointers[1]); } } else { const touchEvent = /** @type {TouchEvent} */ (e); @@ -541,16 +543,16 @@ class Gestures { * @private */ _updatePrevPoints() { - equalizePoints(this.prevP1, this.p1); - equalizePoints(this.prevP2, this.p2); + this.prevP1 = equalizePoints(this.prevP1, this.p1); + this.prevP2 = equalizePoints(this.prevP2, this.p2); } /** update points at the start of gesture * @private */ _updateStartPoints() { - equalizePoints(this.startP1, this.p1); - equalizePoints(this.startP2, this.p2); + this.startP1 = equalizePoints(this.startP1, this.p1); + this.startP2 = equalizePoints(this.startP2, this.p2); this._updatePrevPoints(); } diff --git a/src/js/gestures/tap-handler.js b/src/js/gestures/tap-handler.js index c5227440..f2138ed4 100644 --- a/src/js/gestures/tap-handler.js +++ b/src/js/gestures/tap-handler.js @@ -94,12 +94,12 @@ class TapHandler { pswp[optionValue](); break; case 'zoom': - currSlide.toggleZoom(point); + currSlide?.toggleZoom(point); break; case 'zoom-or-close': // by default click zooms current image, // if it can not be zoomed - gallery will be closed - if (currSlide.isZoomable() + if (currSlide?.isZoomable() && currSlide.zoomLevels.secondary !== currSlide.zoomLevels.initial) { currSlide.toggleZoom(point); } else if (pswp.options.clickToCloseNonZoomable) { @@ -107,7 +107,7 @@ class TapHandler { } break; case 'toggle-controls': - this.gestures.pswp.element.classList.toggle('pswp--ui-visible'); + this.gestures.pswp.element?.classList.toggle('pswp--ui-visible'); // if (_controlsVisible) { // _ui.hideControls(); // } else { diff --git a/src/js/gestures/zoom-handler.js b/src/js/gestures/zoom-handler.js index c146acbf..72f6bf67 100644 --- a/src/js/gestures/zoom-handler.js +++ b/src/js/gestures/zoom-handler.js @@ -51,8 +51,12 @@ class ZoomHandler { } start() { - this._startZoomLevel = this.gestures.pswp.currSlide.currZoomLevel; - equalizePoints(this._startPan, this.gestures.pswp.currSlide.pan); + const { currSlide } = this.gestures.pswp; + if (currSlide) { + this._startZoomLevel = currSlide.currZoomLevel; + this._startPan = equalizePoints(this._startPan, currSlide.pan); + } + this.gestures.pswp.animations.stopAllPan(); this._wasOverFitZoomLevel = false; } @@ -60,6 +64,11 @@ class ZoomHandler { change() { const { p1, startP1, p2, startP2, pswp } = this.gestures; const { currSlide } = pswp; + + if (!currSlide) { + return; + } + const minZoomLevel = currSlide.zoomLevels.min; const maxZoomLevel = currSlide.zoomLevels.max; @@ -107,7 +116,7 @@ class ZoomHandler { end() { const { pswp } = this.gestures; const { currSlide } = pswp; - if (currSlide.currZoomLevel < currSlide.zoomLevels.initial + if ((!currSlide || currSlide.currZoomLevel < currSlide.zoomLevels.initial) && !this._wasOverFitZoomLevel && pswp.options.pinchToClose) { pswp.close(); @@ -140,7 +149,7 @@ class ZoomHandler { const { pswp } = this.gestures; const { currSlide } = pswp; - if (!currSlide.isZoomable()) { + if (!currSlide?.isZoomable()) { return; } @@ -177,7 +186,7 @@ class ZoomHandler { this._startZoomPoint.x = 0; this._startZoomPoint.y = 0; this._startZoomLevel = prevZoomLevel; - equalizePoints(this._startPan, initialPan); + this._startPan = equalizePoints(this._startPan, initialPan); } if (currZoomLevelNeedsChange) { diff --git a/src/js/keyboard.js b/src/js/keyboard.js index 1f655ee2..3b701acc 100644 --- a/src/js/keyboard.js +++ b/src/js/keyboard.js @@ -65,7 +65,7 @@ class Keyboard { /** @private */ _focusRoot() { - if (!this._wasFocused) { + if (!this._wasFocused && this.pswp.element) { this.pswp.element.focus(); this._wasFocused = true; } @@ -148,6 +148,7 @@ class Keyboard { if (keydownAction) { e.preventDefault(); + // @ts-ignore pswp[keydownAction](); } } @@ -160,7 +161,8 @@ class Keyboard { */ _onFocusIn(e) { const { template } = this.pswp; - if (document !== e.target + if (template + && document !== e.target && template !== e.target && !template.contains(/** @type {Node} */ (e.target))) { // focus root element diff --git a/src/js/lightbox/lightbox.js b/src/js/lightbox/lightbox.js index 0ecd8b21..75dd588d 100644 --- a/src/js/lightbox/lightbox.js +++ b/src/js/lightbox/lightbox.js @@ -42,12 +42,12 @@ import { lazyLoadSlide } from '../slide/loader.js'; */ class PhotoSwipeLightbox extends PhotoSwipeBase { /** - * @param {PhotoSwipeOptions} options + * @param {Partial} [options] */ constructor(options) { super(); /** @type {PhotoSwipeOptions} */ - this.options = options || {}; + this.options = this._prepareOptions(options || {}); this._uid = 0; } @@ -83,6 +83,7 @@ class PhotoSwipeLightbox extends PhotoSwipeBase { // Note that some screen readers emulate the mouse position, // so it's not ideal way to detect them. // + /** @type {Point | null} */ let initialPoint = { x: e.clientX, y: e.clientY }; if (!initialPoint.x && !initialPoint.y) { @@ -242,7 +243,7 @@ class PhotoSwipeLightbox extends PhotoSwipeBase { // map listeners from Lightbox to PhotoSwipe Core /** @type {(keyof PhotoSwipeEventsMap)[]} */ (Object.keys(this._listeners)).forEach((name) => { - this._listeners[name].forEach((fn) => { + this._listeners[name]?.forEach((fn) => { pswp.on(name, /** @type {EventCallback} */(fn)); }); }); @@ -250,7 +251,7 @@ class PhotoSwipeLightbox extends PhotoSwipeBase { // same with filters /** @type {(keyof PhotoSwipeFiltersMap)[]} */ (Object.keys(this._filters)).forEach((name) => { - this._filters[name].forEach((filter) => { + this._filters[name]?.forEach((filter) => { pswp.addFilter(name, filter.fn, filter.priority); }); }); @@ -262,8 +263,8 @@ class PhotoSwipeLightbox extends PhotoSwipeBase { pswp.on('destroy', () => { // clean up public variables - this.pswp = null; - window.pswp = null; + this.pswp = undefined; + delete window.pswp; }); pswp.init(); @@ -278,7 +279,7 @@ class PhotoSwipeLightbox extends PhotoSwipeBase { } this.shouldOpen = false; - this._listeners = null; + this._listeners = {}; getElementsFromOption(this.options.gallery, this.options.gallerySelector) .forEach((galleryElement) => { diff --git a/src/js/main-scroll.js b/src/js/main-scroll.js index a24cad43..9bcfb176 100644 --- a/src/js/main-scroll.js +++ b/src/js/main-scroll.js @@ -328,9 +328,12 @@ class MainScroll { } this.x = x; - setTransform(this.pswp.container, x); - this.pswp.dispatch('moveMainScroll', { x, dragging }); + if (this.pswp.container) { + setTransform(this.pswp.container, x); + } + + this.pswp.dispatch('moveMainScroll', { x, dragging: dragging ?? false }); } } diff --git a/src/js/opener.js b/src/js/opener.js index f5810e37..24636642 100644 --- a/src/js/opener.js +++ b/src/js/opener.js @@ -60,7 +60,7 @@ class Opener { this._cropContainer1 = undefined; /** * @private - * @type { HTMLElement | undefined } + * @type { HTMLElement | null | undefined } */ this._cropContainer2 = undefined; @@ -142,24 +142,24 @@ class Opener { this._thumbBounds = this.pswp.getThumbBounds(); } - this._placeholder = slide.getPlaceholderElement(); + this._placeholder = slide?.getPlaceholderElement(); pswp.animations.stopAll(); // Discard animations when duration is less than 50ms - this._useAnimation = (this._duration > 50); + this._useAnimation = Boolean(this._duration && this._duration > 50); this._animateZoom = Boolean(this._thumbBounds) - && (slide.content && slide.content.usePlaceholder()) + && slide?.content.usePlaceholder() && (!this.isClosing || !pswp.mainScroll.isShifted()); if (!this._animateZoom) { this._animateRootOpacity = true; - if (this.isOpening) { + if (this.isOpening && slide) { slide.zoomAndPanToInitial(); slide.applyCurrentZoomPan(); } } else { - this._animateRootOpacity = options.showHideOpacity; + this._animateRootOpacity = options.showHideOpacity ?? false; } this._animateBgOpacity = !this._animateRootOpacity && this.pswp.options.bgOpacity > MIN_OPACITY; this._opacityElement = this._animateRootOpacity ? pswp.element : pswp.bg; @@ -170,7 +170,9 @@ class Opener { this._animateBgOpacity = false; this._animateRootOpacity = true; if (this.isOpening) { - pswp.element.style.opacity = String(MIN_OPACITY); + if (pswp.element) { + pswp.element.style.opacity = String(MIN_OPACITY); + } pswp.applyBgOpacity(1); } return; @@ -180,10 +182,12 @@ class Opener { // Properties are used when animation from cropped thumbnail this._croppedZoom = true; this._cropContainer1 = this.pswp.container; - this._cropContainer2 = this.pswp.currSlide.holderElement; + this._cropContainer2 = this.pswp.currSlide?.holderElement; - pswp.container.style.overflow = 'hidden'; - pswp.container.style.width = pswp.viewportSize.x + 'px'; + if (pswp.container) { + pswp.container.style.overflow = 'hidden'; + pswp.container.style.width = pswp.viewportSize.x + 'px'; + } } else { this._croppedZoom = false; } @@ -191,13 +195,17 @@ class Opener { if (this.isOpening) { // Apply styles before opening transition if (this._animateRootOpacity) { - pswp.element.style.opacity = String(MIN_OPACITY); + if (pswp.element) { + pswp.element.style.opacity = String(MIN_OPACITY); + } pswp.applyBgOpacity(1); } else { - if (this._animateBgOpacity) { + if (this._animateBgOpacity && pswp.bg) { pswp.bg.style.opacity = String(MIN_OPACITY); } - pswp.element.style.opacity = '1'; + if (pswp.element) { + pswp.element.style.opacity = '1'; + } } if (this._animateZoom) { @@ -214,8 +222,12 @@ class Opener { } else if (this.isClosing) { // hide nearby slides to make sure that // they are not painted during the transition - pswp.mainScroll.itemHolders[0].el.style.display = 'none'; - pswp.mainScroll.itemHolders[2].el.style.display = 'none'; + if (pswp.mainScroll.itemHolders[0]) { + pswp.mainScroll.itemHolders[0].el.style.display = 'none'; + } + if (pswp.mainScroll.itemHolders[2]) { + pswp.mainScroll.itemHolders[2].el.style.display = 'none'; + } if (this._croppedZoom) { if (pswp.mainScroll.x !== 0) { @@ -245,13 +257,13 @@ class Opener { decodeImage(/** @type {HTMLImageElement} */ (this._placeholder)).finally(() => { decoded = true; if (!isDelaying) { - resolve(); + resolve(true); } }); setTimeout(() => { isDelaying = false; if (decoded) { - resolve(); + resolve(true); } }, 50); setTimeout(resolve, 250); @@ -263,7 +275,7 @@ class Opener { /** @private */ _initiate() { - this.pswp.element.style.setProperty('--pswp-transition-duration', this._duration + 'ms'); + this.pswp.element?.style.setProperty('--pswp-transition-duration', this._duration + 'ms'); this.pswp.dispatch( this.isOpening ? 'openingAnimationStart' : 'closingAnimationStart' @@ -275,7 +287,7 @@ class Opener { ('initialZoom' + (this.isOpening ? 'In' : 'Out')) ); - this.pswp.element.classList[this.isOpening ? 'add' : 'remove']('pswp--ui-visible'); + this.pswp.element?.classList[this.isOpening ? 'add' : 'remove']('pswp--ui-visible'); if (this.isOpening) { if (this._placeholder) { @@ -313,11 +325,11 @@ class Opener { if (this.isClosed) { pswp.destroy(); } else if (this.isOpen) { - if (this._animateZoom) { + if (this._animateZoom && pswp.container) { pswp.container.style.overflow = 'visible'; pswp.container.style.width = '100%'; } - pswp.currSlide.applyCurrentZoomPan(); + pswp.currSlide?.applyCurrentZoomPan(); } } @@ -325,24 +337,26 @@ class Opener { _animateToOpenState() { const { pswp } = this; if (this._animateZoom) { - if (this._croppedZoom) { + if (this._croppedZoom && this._cropContainer1 && this._cropContainer2) { this._animateTo(this._cropContainer1, 'transform', 'translate3d(0,0,0)'); this._animateTo(this._cropContainer2, 'transform', 'none'); } - pswp.currSlide.zoomAndPanToInitial(); - this._animateTo( - pswp.currSlide.container, - 'transform', - pswp.currSlide.getCurrentTransform() - ); + if (pswp.currSlide) { + pswp.currSlide.zoomAndPanToInitial(); + this._animateTo( + pswp.currSlide.container, + 'transform', + pswp.currSlide.getCurrentTransform() + ); + } } - if (this._animateBgOpacity) { + if (this._animateBgOpacity && pswp.bg) { this._animateTo(pswp.bg, 'opacity', String(pswp.options.bgOpacity)); } - if (this._animateRootOpacity) { + if (this._animateRootOpacity && pswp.element) { this._animateTo(pswp.element, 'opacity', '1'); } } @@ -355,12 +369,12 @@ class Opener { this._setClosedStateZoomPan(true); } - if (this._animateBgOpacity - && pswp.bgOpacity > 0.01) { // do not animate opacity if it's already at 0 + // do not animate opacity if it's already at 0 + if (this._animateBgOpacity && pswp.bgOpacity > 0.01 && pswp.bg) { this._animateTo(pswp.bg, 'opacity', '0'); } - if (this._animateRootOpacity) { + if (this._animateRootOpacity && pswp.element) { this._animateTo(pswp.element, 'opacity', '0'); } } @@ -376,7 +390,7 @@ class Opener { const { innerRect } = this._thumbBounds; const { currSlide, viewportSize } = pswp; - if (this._croppedZoom) { + if (this._croppedZoom && innerRect && this._cropContainer1 && this._cropContainer2) { const containerOnePanX = -viewportSize.x + (this._thumbBounds.x - innerRect.x) + innerRect.w; const containerOnePanY = -viewportSize.y + (this._thumbBounds.y - innerRect.y) + innerRect.h; const containerTwoPanX = viewportSize.x - innerRect.w; @@ -401,13 +415,14 @@ class Opener { } } - equalizePoints(currSlide.pan, innerRect || this._thumbBounds); - currSlide.currZoomLevel = this._thumbBounds.w / currSlide.width; - - if (animate) { - this._animateTo(currSlide.container, 'transform', currSlide.getCurrentTransform()); - } else { - currSlide.applyCurrentZoomPan(); + if (currSlide) { + currSlide.pan = equalizePoints(currSlide.pan, innerRect || this._thumbBounds); + currSlide.currZoomLevel = this._thumbBounds.w / currSlide.width; + if (animate) { + this._animateTo(currSlide.container, 'transform', currSlide.getCurrentTransform()); + } else { + currSlide.applyCurrentZoomPan(); + } } } diff --git a/src/js/photoswipe.js b/src/js/photoswipe.js index d3c698cd..f99a64dc 100644 --- a/src/js/photoswipe.js +++ b/src/js/photoswipe.js @@ -57,7 +57,7 @@ import ContentLoader from './slide/loader.js'; /** * @typedef {Object} PhotoSwipeOptions https://photoswipe.com/options/ * - * @prop {DataSource=} dataSource + * @prop {DataSource} [dataSource] * Pass an array of any items via dataSource option. Its length will determine amount of slides * (which may be modified further from numItems event). * @@ -66,109 +66,109 @@ import ContentLoader from './slide/loader.js'; * * If these properties are not present in your initial array, you may "pre-parse" each item from itemData filter. * - * @prop {number=} bgOpacity + * @prop {number} bgOpacity * Background backdrop opacity, always define it via this option and not via CSS rgba color. * - * @prop {number=} spacing + * @prop {number} spacing * Spacing between slides. Defined as ratio relative to the viewport width (0.1 = 10% of viewport). * - * @prop {boolean=} allowPanToNext + * @prop {boolean} allowPanToNext * Allow swipe navigation to the next slide when the current slide is zoomed. Does not apply to mouse events. * - * @prop {boolean=} loop + * @prop {boolean} loop * If set to true you'll be able to swipe from the last to the first image. * Option is always false when there are less than 3 slides. * - * @prop {boolean=} wheelToZoom + * @prop {boolean} [wheelToZoom] * By default PhotoSwipe zooms image with ctrl-wheel, if you enable this option - image will zoom just via wheel. * - * @prop {boolean=} pinchToClose + * @prop {boolean} pinchToClose * Pinch touch gesture to close the gallery. * - * @prop {boolean=} closeOnVerticalDrag + * @prop {boolean} closeOnVerticalDrag * Vertical drag gesture to close the PhotoSwipe. * - * @prop {Padding=} padding + * @prop {Padding} [padding] * Slide area padding (in pixels). * * @prop {(viewportSize: Point, itemData: SlideData, index: number) => Padding} [paddingFn] * The option is checked frequently, so make sure it's performant. Overrides padding option if defined. For example: * - * @prop {number | false} [hideAnimationDuration] + * @prop {number | false} hideAnimationDuration * Transition duration in milliseconds, can be 0. * - * @prop {number | false} [showAnimationDuration] + * @prop {number | false} showAnimationDuration * Transition duration in milliseconds, can be 0. * - * @prop {number | false} [zoomAnimationDuration] + * @prop {number | false} zoomAnimationDuration * Transition duration in milliseconds, can be 0. * - * @prop {string=} easing + * @prop {string} easing * String, 'cubic-bezier(.4,0,.22,1)'. CSS easing function for open/close/zoom transitions. * - * @prop {boolean=} escKey + * @prop {boolean} escKey * Esc key to close. * - * @prop {boolean=} arrowKeys + * @prop {boolean} arrowKeys * Left/right arrow keys for navigation. * - * @prop {boolean=} returnFocus + * @prop {boolean} returnFocus * Restore focus the last active element after PhotoSwipe is closed. * - * @prop {boolean=} clickToCloseNonZoomable + * @prop {boolean} clickToCloseNonZoomable * If image is not zoomable (for example, smaller than viewport) it can be closed by clicking on it. * - * @prop {ActionType | ActionFn | false} [imageClickAction] + * @prop {ActionType | ActionFn | false} imageClickAction * Refer to click and tap actions page. * - * @prop {ActionType | ActionFn | false} [bgClickAction] + * @prop {ActionType | ActionFn | false} bgClickAction * Refer to click and tap actions page. * - * @prop {ActionType | ActionFn | false} [tapAction] + * @prop {ActionType | ActionFn | false} tapAction * Refer to click and tap actions page. * - * @prop {ActionType | ActionFn | false} [doubleTapAction] + * @prop {ActionType | ActionFn | false} doubleTapAction * Refer to click and tap actions page. * - * @prop {number=} preloaderDelay + * @prop {number} preloaderDelay * Delay before the loading indicator will be displayed, * if image is loaded during it - the indicator will not be displayed at all. Can be zero. * - * @prop {string=} indexIndicatorSep + * @prop {string} indexIndicatorSep * Used for slide count indicator ("1 of 10 "). * * @prop {(options: PhotoSwipeOptions, pswp: PhotoSwipe) => Point} [getViewportSizeFn] * A function that should return slide viewport width and height, in format {x: 100, y: 100}. * - * @prop {string=} errorMsg + * @prop {string} errorMsg * Message to display when the image wasn't able to load. If you need to display HTML - use contentErrorElement filter. * - * @prop {[number, number]=} preload + * @prop {[number, number]} preload * Lazy loading of nearby slides based on direction of movement. Should be an array with two integers, * first one - number of items to preload before the current image, second one - after the current image. * Two nearby images are always loaded. * - * @prop {string=} mainClass + * @prop {string} [mainClass] * Class that will be added to the root element of PhotoSwipe, may contain multiple separated by space. * Example on Styling page. * - * @prop {HTMLElement=} appendToEl + * @prop {HTMLElement} [appendToEl] * Element to which PhotoSwipe dialog will be appended when it opens. * - * @prop {number=} maxWidthToAnimate + * @prop {number} maxWidthToAnimate * Maximum width of image to animate, if initial rendered image width * is larger than this value - the opening/closing transition will be automatically disabled. * - * @prop {string=} closeTitle + * @prop {string} [closeTitle] * Translating * - * @prop {string=} zoomTitle + * @prop {string} [zoomTitle] * Translating * - * @prop {string=} arrowPrevTitle + * @prop {string} [arrowPrevTitle] * Translating * - * @prop {string=} arrowNextTitle + * @prop {string} [arrowNextTitle] * Translating * * @prop {'zoom' | 'fade' | 'none'} [showHideAnimationType] @@ -177,94 +177,65 @@ import ContentLoader from './slide/loader.js'; * * Animations are automatically disabled if user `(prefers-reduced-motion: reduce)`. * - * @prop {number=} index + * @prop {number} index * Defines start slide index. * * @prop {(e: MouseEvent) => number} [getClickedIndexFn] * - * @prop {boolean=} arrowPrev - * @prop {boolean=} arrowNext - * @prop {boolean=} zoom - * @prop {boolean=} close - * @prop {boolean=} counter - * - * @prop {string=} arrowPrevSVG - * @prop {string=} arrowNextSVG - * @prop {string=} zoomSVG - * @prop {string=} closeSVG - * @prop {string=} counterSVG - * - * @prop {string=} arrowPrevTitle - * @prop {string=} arrowNextTitle - * @prop {string=} zoomTitle - * @prop {string=} closeTitle - * @prop {string=} counterTitle - * - * @prop {ZoomLevelOption=} initialZoomLevel - * @prop {ZoomLevelOption=} secondaryZoomLevel - * @prop {ZoomLevelOption=} maxZoomLevel - * - * @prop {boolean=} mouseMovePan + * @prop {boolean} [arrowPrev] + * @prop {boolean} [arrowNext] + * @prop {boolean} [zoom] + * @prop {boolean} [close] + * @prop {boolean} [counter] + * + * @prop {string} [arrowPrevSVG] + * @prop {string} [arrowNextSVG] + * @prop {string} [zoomSVG] + * @prop {string} [closeSVG] + * @prop {string} [counterSVG] + * + * @prop {string} [arrowPrevTitle] + * @prop {string} [arrowNextTitle] + * @prop {string} [zoomTitle] + * @prop {string} [closeTitle] + * @prop {string} [counterTitle] + * + * @prop {ZoomLevelOption} [initialZoomLevel] + * @prop {ZoomLevelOption} [secondaryZoomLevel] + * @prop {ZoomLevelOption} [maxZoomLevel] + * + * @prop {boolean} [mouseMovePan] * @prop {Point | null} [initialPointerPos] - * @prop {boolean=} showHideOpacity + * @prop {boolean} [showHideOpacity] * * @prop {PhotoSwipeModuleOption} [pswpModule] * @prop {() => Promise} [openPromise] - * @prop {boolean=} preloadFirstSlide - * @prop {ElementProvider=} gallery - * @prop {string=} gallerySelector - * @prop {ElementProvider=} children - * @prop {string=} childSelector + * @prop {boolean} [preloadFirstSlide] + * @prop {ElementProvider} [gallery] + * @prop {string} [gallerySelector] + * @prop {ElementProvider} [children] + * @prop {string} [childSelector] * @prop {string | false} [thumbSelector] */ -/** @type {PhotoSwipeOptions} */ -const defaultOptions = { - allowPanToNext: true, - spacing: 0.1, - loop: true, - pinchToClose: true, - closeOnVerticalDrag: true, - hideAnimationDuration: 333, - showAnimationDuration: 333, - zoomAnimationDuration: 333, - escKey: true, - arrowKeys: true, - returnFocus: true, - maxWidthToAnimate: 4000, - clickToCloseNonZoomable: true, - imageClickAction: 'zoom-or-close', - bgClickAction: 'close', - tapAction: 'toggle-controls', - doubleTapAction: 'zoom', - indexIndicatorSep: ' / ', - preloaderDelay: 2000, - bgOpacity: 0.8, - - index: 0, - errorMsg: 'The image cannot be loaded', - preload: [1, 2], - easing: 'cubic-bezier(.4,0,.22,1)' -}; - /** * PhotoSwipe Core */ class PhotoSwipe extends PhotoSwipeBase { /** - * @param {PhotoSwipeOptions} options + * @param {Partial} [options] */ constructor(options) { super(); - this._prepareOptions(options); + this.options = this._prepareOptions(options || {}); /** * offset of viewport relative to document * - * @type {{ x?: number; y?: number }} + * @type {Point} */ - this.offset = {}; + this.offset = { x: 0, y: 0 }; /** * @type {Point} @@ -281,13 +252,28 @@ class PhotoSwipe extends PhotoSwipeBase { /** * background (backdrop) opacity - * - * @type {number} */ this.bgOpacity = 1; + this.currIndex = 0; + this.potentialIndex = 0; + /** + * @private + * @type {SlideData} + */ + this._initialItemData = {}; - /** @type {HTMLDivElement} */ + /** @type {HTMLDivElement | undefined} */ this.topBar = undefined; + /** @type {HTMLDivElement | undefined} */ + this.element = undefined; + /** @type {HTMLDivElement | undefined} */ + this.template = undefined; + /** @type {HTMLDivElement | undefined} */ + this.container = undefined; + /** @type {HTMLElement | undefined} */ + this.scrollWrap = undefined; + /** @type {Slide | undefined} */ + this.currSlide = undefined; this.events = new DOMEvents(); @@ -299,6 +285,9 @@ class PhotoSwipe extends PhotoSwipeBase { this.opener = new Opener(this); this.keyboard = new Keyboard(this); this.contentLoader = new ContentLoader(this); + // initialize scroll wheel handler to block the scroll + this.scrollWheel = new ScrollWheel(this); + this.ui = new UI(this); } init() { @@ -320,15 +309,14 @@ class PhotoSwipe extends PhotoSwipeBase { if (this.options.mainClass) { rootClasses += ' ' + this.options.mainClass; } - this.element.className += ' ' + rootClasses; + if (this.element) { + this.element.className += ' ' + rootClasses; + } this.currIndex = this.options.index || 0; this.potentialIndex = this.currIndex; this.dispatch('firstUpdate'); // starting index can be modified here - // initialize scroll wheel handler to block the scroll - this.scrollWheel = new ScrollWheel(this); - // sanitize index if (Number.isNaN(this.currIndex) || this.currIndex < 0 @@ -411,9 +399,7 @@ class PhotoSwipe extends PhotoSwipeBase { appendHeavy() { this.mainScroll.itemHolders.forEach((itemHolder) => { - if (itemHolder.slide) { - itemHolder.slide.appendHeavy(); - } + itemHolder.slide?.appendHeavy(); }); } @@ -447,14 +433,14 @@ class PhotoSwipe extends PhotoSwipeBase { * @param {Parameters} args */ zoomTo(...args) { - this.currSlide.zoomTo(...args); + this.currSlide?.zoomTo(...args); } /** * @see slide/slide.js toggleZoom */ toggleZoom() { - this.currSlide.toggleZoom(); + this.currSlide?.toggleZoom(); } /** @@ -490,17 +476,17 @@ class PhotoSwipe extends PhotoSwipeBase { this.dispatch('destroy'); - this.listeners = null; + this._listeners = {}; - this.scrollWrap.ontouchmove = null; - this.scrollWrap.ontouchend = null; + if (this.scrollWrap) { + this.scrollWrap.ontouchmove = null; + this.scrollWrap.ontouchend = null; + } - this.element.remove(); + this.element?.remove(); this.mainScroll.itemHolders.forEach((itemHolder) => { - if (itemHolder.slide) { - itemHolder.slide.destroy(); - } + itemHolder.slide?.destroy(); }); this.contentLoader.destroy(); @@ -515,7 +501,7 @@ class PhotoSwipe extends PhotoSwipeBase { refreshSlideContent(slideIndex) { this.contentLoader.removeByIndex(slideIndex); this.mainScroll.itemHolders.forEach((itemHolder, i) => { - let potentialHolderIndex = this.currSlide.index - 1 + i; + let potentialHolderIndex = (this.currSlide?.index ?? 0) - 1 + i; if (this.canLoop()) { potentialHolderIndex = this.getLoopedIndex(potentialHolderIndex); } @@ -525,9 +511,8 @@ class PhotoSwipe extends PhotoSwipeBase { // activate the new slide if it's current if (i === 1) { - /** @type {Slide} */ this.currSlide = itemHolder.slide; - itemHolder.slide.setIsActive(true); + itemHolder.slide?.setIsActive(true); } } }); @@ -557,7 +542,7 @@ class PhotoSwipe extends PhotoSwipeBase { // destroy previous slide holder.slide.destroy(); - holder.slide = null; + holder.slide = undefined; } // exit if no loop and index is out of bounds @@ -611,11 +596,11 @@ class PhotoSwipe extends PhotoSwipeBase { //this._prevViewportSize.x = newWidth; //this._prevViewportSize.y = newHeight; - equalizePoints(this._prevViewportSize, newViewportSize); + this._prevViewportSize = equalizePoints(this._prevViewportSize, newViewportSize); this.dispatch('beforeResize'); - equalizePoints(this.viewportSize, this._prevViewportSize); + this.viewportSize = equalizePoints(this.viewportSize, this._prevViewportSize); this._updatePageScrollOffset(); @@ -637,7 +622,7 @@ class PhotoSwipe extends PhotoSwipeBase { */ applyBgOpacity(opacity) { this.bgOpacity = Math.max(opacity, 0); - this.bg.style.opacity = String(this.bgOpacity * this.options.bgOpacity); + if (this.bg) this.bg.style.opacity = String(this.bgOpacity * this.options.bgOpacity); } /** @@ -646,7 +631,7 @@ class PhotoSwipe extends PhotoSwipeBase { mouseDetected() { if (!this.hasMouse) { this.hasMouse = true; - this.element.classList.add('pswp--has_mouse'); + this.element?.classList.add('pswp--has_mouse'); } } @@ -719,7 +704,6 @@ class PhotoSwipe extends PhotoSwipeBase { this.mainScroll.appendHolders(); - this.ui = new UI(this); this.ui.init(); // append to DOM @@ -748,23 +732,6 @@ class PhotoSwipe extends PhotoSwipeBase { canLoop() { return (this.options.loop && this.getNumItems() > 2); } - - /** - * @param {PhotoSwipeOptions} options - * @private - */ - _prepareOptions(options) { - if (window.matchMedia('(prefers-reduced-motion), (update: slow)').matches) { - options.showHideAnimationType = 'none'; - options.zoomAnimationDuration = 0; - } - - /** @type {PhotoSwipeOptions}*/ - this.options = { - ...defaultOptions, - ...options - }; - } } export default PhotoSwipe; diff --git a/src/js/slide/content.js b/src/js/slide/content.js index e494cf12..ce37a3de 100644 --- a/src/js/slide/content.js +++ b/src/js/slide/content.js @@ -62,7 +62,7 @@ class Content { /** * Preload content * - * @param {boolean} [isLazy] + * @param {boolean} isLazy * @param {boolean} [reload] */ load(isLazy, reload) { @@ -133,9 +133,8 @@ class Content { imageElement.srcset = this.data.srcset; } - imageElement.src = this.data.src; - - imageElement.alt = this.data.alt || ''; + imageElement.src = this.data.src ?? ''; + imageElement.alt = this.data.alt ?? ''; this.state = LOAD_STATE.LOADING; diff --git a/src/js/slide/get-thumb-bounds.js b/src/js/slide/get-thumb-bounds.js index 9270a10f..1f44b8c9 100644 --- a/src/js/slide/get-thumb-bounds.js +++ b/src/js/slide/get-thumb-bounds.js @@ -66,7 +66,7 @@ function getCroppedBoundsByElement(el, imageWidth, imageHeight) { * @param {number} index * @param {SlideData} itemData * @param {PhotoSwipe} instance PhotoSwipe instance - * @returns {Bounds | undefined} + * @returns {Bounds | null} */ export function getThumbBounds(index, itemData, instance) { // legacy event, before filters were introduced @@ -93,19 +93,21 @@ export function getThumbBounds(index, itemData, instance) { ? element : element.querySelector(thumbSelector); } - thumbnail = instance.applyFilters('thumbEl', thumbnail, itemData, index); - if (thumbnail) { + thumbnail = instance.applyFilters('thumbEl', thumbnail, itemData, index); + if (!itemData.thumbCropped) { thumbBounds = getBoundsByElement(thumbnail); } else { thumbBounds = getCroppedBoundsByElement( thumbnail, - itemData.width || itemData.w, - itemData.height || itemData.h + itemData.width || itemData.w || 0, + itemData.height || itemData.h || 0 ); } } - return instance.applyFilters('thumbBounds', thumbBounds, itemData, index); + return thumbBounds + ? instance.applyFilters('thumbBounds', thumbBounds, itemData, index) + : null; } diff --git a/src/js/slide/slide.js b/src/js/slide/slide.js index 02a6aa16..7c912617 100644 --- a/src/js/slide/slide.js +++ b/src/js/slide/slide.js @@ -46,8 +46,10 @@ class Slide { this.pswp = pswp; this.isActive = (index === pswp.currIndex); this.currentResolution = 0; - /** @type {Point | null} */ - this.panAreaSize = null; + /** @type {Point} */ + this.panAreaSize = { x: 0, y: 0 }; + /** @type {Point} */ + this.pan = { x: 0, y: 0 }; this.isFirstSlide = (this.isActive && !pswp.opener.isOpen); @@ -58,8 +60,6 @@ class Slide { data: this.data, index }); - /** @type {Point} */ - this.pan = { x: 0, y: 0 }; this.content = this.pswp.contentLoader.getContentBySlide(this); this.container = createElement('pswp__zoom-wrap', 'div'); @@ -132,7 +132,7 @@ class Slide { } load() { - this.content.load(); + this.content.load(false); this.pswp.dispatch('slideLoad', { slide: this }); } @@ -316,7 +316,7 @@ class Slide { this.setZoomLevel(destZoomLevel); this.pan.x = this.calculateZoomToPanOffset('x', centerPoint, prevZoomLevel); this.pan.y = this.calculateZoomToPanOffset('y', centerPoint, prevZoomLevel); - roundPoint(this.pan); + this.pan = roundPoint(this.pan); const finishTransition = () => { this.setResolution(destZoomLevel); @@ -384,6 +384,10 @@ class Slide { point = this.pswp.getViewportCenterPoint(); } + if (!prevZoomLevel) { + prevZoomLevel = this.zoomLevels.initial; + } + const zoomFactor = this.currZoomLevel / prevZoomLevel; return this.bounds.correctPan( axis, @@ -408,7 +412,7 @@ class Slide { * @returns {boolean} */ isPannable() { - return this.width && (this.currZoomLevel > this.zoomLevels.fit); + return Boolean(this.width) && (this.currZoomLevel > this.zoomLevels.fit); } /** @@ -416,7 +420,7 @@ class Slide { * @returns {boolean} */ isZoomable() { - return this.width && this.content.isZoomable(); + return Boolean(this.width) && this.content.isZoomable(); } /** @@ -435,7 +439,7 @@ class Slide { // pan according to the zoom level this.bounds.update(this.currZoomLevel); - equalizePoints(this.pan, this.bounds.center); + this.pan = equalizePoints(this.pan, this.bounds.center); this.pswp.dispatch('initialZoomPan', { slide: this }); } @@ -455,14 +459,12 @@ class Slide { calculateSize() { const { pswp } = this; - if (this.panAreaSize) { - equalizePoints( - this.panAreaSize, - getPanAreaSize(pswp.options, pswp.viewportSize, this.data, this.index) - ); + this.panAreaSize = equalizePoints( + this.panAreaSize, + getPanAreaSize(pswp.options, pswp.viewportSize, this.data, this.index) + ); - this.zoomLevels.update(this.width, this.height, this.panAreaSize); - } + this.zoomLevels.update(this.width, this.height, this.panAreaSize); pswp.dispatch('calcSlideSize', { slide: this diff --git a/src/js/ui/loading-indicator.js b/src/js/ui/loading-indicator.js index 6602e70b..0be7f903 100644 --- a/src/js/ui/loading-indicator.js +++ b/src/js/ui/loading-indicator.js @@ -34,7 +34,7 @@ export const loadingIndicator = { }; const updatePreloaderVisibility = () => { - if (!pswp.currSlide.content.isLoading()) { + if (!pswp.currSlide?.content.isLoading()) { setIndicatorVisibility(false); if (delayTimeout) { clearTimeout(delayTimeout); @@ -46,7 +46,7 @@ export const loadingIndicator = { if (!delayTimeout) { // display loading indicator with delay delayTimeout = setTimeout(() => { - setIndicatorVisibility(pswp.currSlide.content.isLoading()); + setIndicatorVisibility(Boolean(pswp.currSlide?.content.isLoading())); delayTimeout = null; }, pswp.options.preloaderDelay); } diff --git a/src/js/ui/ui-element.js b/src/js/ui/ui-element.js index 422eb8a3..8f96c695 100644 --- a/src/js/ui/ui-element.js +++ b/src/js/ui/ui-element.js @@ -134,8 +134,9 @@ class UIElement { element.title = title; } - if (ariaLabel || title) { - element.setAttribute('aria-label', ariaLabel || title); + const ariaText = ariaLabel || title; + if (ariaText) { + element.setAttribute('aria-label', ariaText); } } @@ -148,8 +149,9 @@ class UIElement { if (data.onClick) { element.onclick = (e) => { if (typeof data.onClick === 'string') { + // @ts-ignore pswp[data.onClick](); - } else { + } else if (typeof data.onClick === 'function') { data.onClick(e, element, pswp); } }; @@ -157,7 +159,7 @@ class UIElement { // Top bar is default position const appendTo = data.appendTo || 'bar'; - /** @type {HTMLElement} root element by default */ + /** @type {HTMLElement | undefined} root element by default */ let container = pswp.element; if (appendTo === 'bar') { if (!pswp.topBar) { @@ -174,7 +176,7 @@ class UIElement { } } - container.appendChild(pswp.applyFilters('uiElement', element, data)); + container?.appendChild(pswp.applyFilters('uiElement', element, data)); } } diff --git a/src/js/ui/ui.js b/src/js/ui/ui.js index 88bdc1db..8798b1f1 100644 --- a/src/js/ui/ui.js +++ b/src/js/ui/ui.js @@ -68,7 +68,7 @@ class UI { }); pswp.on('change', () => { - pswp.element.classList[pswp.getNumItems() === 1 ? 'add' : 'remove']('pswp--one-slide'); + pswp.element?.classList[pswp.getNumItems() === 1 ? 'add' : 'remove']('pswp--one-slide'); }); pswp.on('zoomPanUpdate', () => this._onZoomPanUpdate()); @@ -95,12 +95,13 @@ class UI { */ _onZoomPanUpdate() { const { template, currSlide, options } = this.pswp; - let { currZoomLevel } = currSlide; - if (this.pswp.opener.isClosing) { + if (this.pswp.opener.isClosing || !template || !currSlide) { return; } + let { currZoomLevel } = currSlide; + // if not open yet - check against initial zoom level if (!this.pswp.opener.isOpen) { currZoomLevel = currSlide.zoomLevels.initial; diff --git a/src/js/util/css-animation.js b/src/js/util/css-animation.js index 60118dc4..a99b2b28 100644 --- a/src/js/util/css-animation.js +++ b/src/js/util/css-animation.js @@ -39,7 +39,7 @@ class CSSAnimation { // support only transform and opacity const prop = transform ? 'transform' : 'opacity'; - const propValue = props[prop]; + const propValue = props[prop] ?? ''; /** @private */ this._target = target; diff --git a/src/js/util/dom-events.js b/src/js/util/dom-events.js index 11f51145..6ba83a9a 100644 --- a/src/js/util/dom-events.js +++ b/src/js/util/dom-events.js @@ -2,6 +2,7 @@ let supportsPassive = false; /* eslint-disable */ try { + /* @ts-ignore */ window.addEventListener('test', null, Object.defineProperty({}, 'passive', { get: () => { supportsPassive = true; @@ -15,7 +16,7 @@ try { * @prop {HTMLElement | Window | Document | undefined | null} target * @prop {string} type * @prop {EventListenerOrEventListenerObject} listener - * @prop {boolean} passive + * @prop {boolean} [passive] */ class DOMEvents { diff --git a/src/js/util/spring-animation.js b/src/js/util/spring-animation.js index 5c86baa4..abc6609f 100644 --- a/src/js/util/spring-animation.js +++ b/src/js/util/spring-animation.js @@ -21,6 +21,7 @@ class SpringAnimation { */ constructor(props) { this.props = props; + this._raf = 0; const { start, @@ -67,7 +68,7 @@ class SpringAnimation { if (this._raf >= 0) { cancelAnimationFrame(this._raf); } - this._raf = null; + this._raf = 0; } } diff --git a/src/js/util/spring-easer.js b/src/js/util/spring-easer.js index 50d71277..095721e0 100644 --- a/src/js/util/spring-easer.js +++ b/src/js/util/spring-easer.js @@ -29,9 +29,10 @@ class SpringEaser { // https://en.wikipedia.org/wiki/Natural_frequency this._naturalFrequency = naturalFrequency || DEFAULT_NATURAL_FREQUENCY; + this._dampedFrequency = this._naturalFrequency; + if (this._dampingRatio < 1) { - this._dampedFrequency = this._naturalFrequency - * Math.sqrt(1 - this._dampingRatio * this._dampingRatio); + this._dampedFrequency *= Math.sqrt(1 - this._dampingRatio * this._dampingRatio); } } diff --git a/src/js/util/util.js b/src/js/util/util.js index 7ab65c7e..8500f4ff 100644 --- a/src/js/util/util.js +++ b/src/js/util/util.js @@ -24,20 +24,15 @@ export function createElement(className, tagName, appendToEl) { * @returns {Point} */ export function equalizePoints(p1, p2) { - p1.x = p2.x; - p1.y = p2.y; - if (p2.id !== undefined) { - p1.id = p2.id; - } - return p1; + return { ...p1, ...p2 }; } /** * @param {Point} p + * @returns {Point} */ export function roundPoint(p) { - p.x = Math.round(p.x); - p.y = Math.round(p.y); + return { x: Math.round(p.x), y: Math.round(p.y) }; } /** @@ -187,7 +182,7 @@ export function specialKeyUsed(e) { /** * Parse `gallery` or `children` options. * - * @param {import('../photoswipe.js').ElementProvider} option + * @param {import('../photoswipe.js').ElementProvider} [option] * @param {string} [legacySelector] * @param {HTMLElement | Document} [parent] * @returns HTMLElement[] diff --git a/tsconfig.json b/tsconfig.json index 40e87e69..cf23ce1e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "declaration": true, "emitDeclarationOnly": true, "strict": true, - "strictNullChecks": false, + "strictNullChecks": true, "skipLibCheck": true, "sourceMap": true, "allowJs": true, From 07a3d3b45a6803927fa93fec8ed4f94291e9e6af Mon Sep 17 00:00:00 2001 From: mishchuk Date: Fri, 30 Dec 2022 11:04:54 +1000 Subject: [PATCH 26/27] fix photoswipe, lightbox types --- dist/types/core/base.d.ts | 8 +++-- dist/types/core/eventable.d.ts | 12 +++---- dist/types/lightbox/lightbox.d.ts | 24 ++++++++------ dist/types/photoswipe.d.ts | 41 +++++++++++++----------- dist/types/slide/zoom-level.d.ts | 2 +- src/js/core/base.js | 7 +++-- src/js/core/eventable.js | 6 ++-- src/js/lightbox/lightbox.js | 26 ++++++++++------ src/js/photoswipe.js | 52 ++++++++++++++++++++----------- 9 files changed, 106 insertions(+), 72 deletions(-) diff --git a/dist/types/core/base.d.ts b/dist/types/core/base.d.ts index 5687d48d..a3ef465f 100644 --- a/dist/types/core/base.d.ts +++ b/dist/types/core/base.d.ts @@ -1,9 +1,11 @@ export default PhotoSwipeBase; export type PhotoSwipe = import("../photoswipe.js").default; export type PhotoSwipeOptions = import("../photoswipe.js").PhotoSwipeOptions; +export type PreparedPhotoSwipeOptions = import("../photoswipe.js").PreparedPhotoSwipeOptions; export type SlideData = import("../slide/slide.js").SlideData; /** @typedef {import("../photoswipe.js").default} PhotoSwipe */ /** @typedef {import("../photoswipe.js").PhotoSwipeOptions} PhotoSwipeOptions */ +/** @typedef {import("../photoswipe.js").PreparedPhotoSwipeOptions} PreparedPhotoSwipeOptions */ /** @typedef {import("../slide/slide.js").SlideData} SlideData */ /** * PhotoSwipe base class that can retrieve data about every slide. @@ -58,10 +60,10 @@ declare class PhotoSwipeBase extends Eventable { lazyLoadData(itemData: SlideData, index: number): Content; /** * @protected - * @param {Partial} options - * @returns {PhotoSwipeOptions} + * @param {PhotoSwipeOptions} options + * @returns {PreparedPhotoSwipeOptions} */ - protected _prepareOptions(options: Partial): PhotoSwipeOptions; + protected _prepareOptions(options: PhotoSwipeOptions): PreparedPhotoSwipeOptions; } import Eventable from "./eventable.js"; import Content from "../slide/content.js"; diff --git a/dist/types/core/eventable.d.ts b/dist/types/core/eventable.d.ts index 06214e01..e877b392 100644 --- a/dist/types/core/eventable.d.ts +++ b/dist/types/core/eventable.d.ts @@ -1,6 +1,6 @@ /** @typedef {import('../lightbox/lightbox.js').default} PhotoSwipeLightbox */ /** @typedef {import('../photoswipe.js').default} PhotoSwipe */ -/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */ +/** @typedef {import('../photoswipe.js').PreparedPhotoSwipeOptions} PreparedPhotoSwipeOptions */ /** @typedef {import('../photoswipe.js').DataSource} DataSource */ /** @typedef {import('../ui/ui-element.js').UIElementData} UIElementData */ /** @typedef {import('../slide/content.js').default} ContentDefault */ @@ -192,12 +192,12 @@ * @template {keyof PhotoSwipeEventsMap} T * @typedef {(event: AugmentedEvent) => void} EventCallback */ -/** @type {PhotoSwipeOptions} */ -export const defaultOptions: PhotoSwipeOptions; +/** @type {PreparedPhotoSwipeOptions} */ +export const defaultOptions: PreparedPhotoSwipeOptions; export default Eventable; export type PhotoSwipeLightbox = import('../lightbox/lightbox.js').default; export type PhotoSwipe = import('../photoswipe.js').default; -export type PhotoSwipeOptions = import('../photoswipe.js').PhotoSwipeOptions; +export type PreparedPhotoSwipeOptions = import('../photoswipe.js').PreparedPhotoSwipeOptions; export type DataSource = import('../photoswipe.js').DataSource; export type UIElementData = import('../ui/ui-element.js').UIElementData; export type ContentDefault = import('../slide/content.js').default; @@ -924,8 +924,8 @@ declare class Eventable { }; /** @type {PhotoSwipe | undefined} */ pswp: PhotoSwipe | undefined; - /** @type {PhotoSwipeOptions} */ - options: PhotoSwipeOptions; + /** @type {PreparedPhotoSwipeOptions} */ + options: PreparedPhotoSwipeOptions; /** * @template {keyof PhotoSwipeFiltersMap} T * @param {T} name diff --git a/dist/types/lightbox/lightbox.d.ts b/dist/types/lightbox/lightbox.d.ts index 2eabcfd4..0979c47b 100644 --- a/dist/types/lightbox/lightbox.d.ts +++ b/dist/types/lightbox/lightbox.d.ts @@ -46,23 +46,30 @@ export type EventCallback = import('../core/eventable.js').EventCallback; */ declare class PhotoSwipeLightbox extends PhotoSwipeBase { /** - * @param {Partial} [options] + * @param {PhotoSwipeOptions} [options] */ - constructor(options?: Partial | undefined); + constructor(options?: Partial | undefined); _uid: number; + shouldOpen: boolean; /** - * Initialize lightbox, should be called only once. - * It's not included in the main constructor, so you may bind events before it. + * @private + * @type {Content | undefined} */ - init(): void; + private _preloadedContent; /** * @param {MouseEvent} e */ onThumbnailsClick(e: MouseEvent): void; + /** + * Initialize lightbox, should be called only once. + * It's not included in the main constructor, so you may bind events before it. + */ + init(): void; /** * Get index of gallery item that was clicked. * * @param {MouseEvent} e click event + * @returns {number} */ getClickedIndex(e: MouseEvent): number; /** @@ -71,17 +78,16 @@ declare class PhotoSwipeLightbox extends PhotoSwipeBase { * @param {number} index * @param {DataSource} dataSource * @param {Point | null} [initialPoint] + * @returns {boolean} */ loadAndOpen(index: number, dataSource: DataSource, initialPoint?: import("../photoswipe.js").Point | null | undefined): boolean; - shouldOpen: boolean | undefined; /** * Load the main module and the slide content by index * * @param {number} index - * @param {DataSource=} dataSource + * @param {DataSource} [dataSource] */ - preload(index: number, dataSource?: DataSource | undefined): void; - _preloadedContent: import("../slide/content.js").default | null | undefined; + preload(index: number, dataSource?: import("../photoswipe.js").DataSource | undefined): void; /** * @private * @param {Type | { default: Type }} module diff --git a/dist/types/photoswipe.d.ts b/dist/types/photoswipe.d.ts index ea79b325..cf86810e 100644 --- a/dist/types/photoswipe.d.ts +++ b/dist/types/photoswipe.d.ts @@ -9,6 +9,7 @@ export type UIElementData = import('./ui/ui-element.js').UIElementData; export type ItemHolder = import('./main-scroll.js').ItemHolder; export type PhotoSwipeEventsMap = import('./core/eventable.js').PhotoSwipeEventsMap; export type PhotoSwipeFiltersMap = import('./core/eventable.js').PhotoSwipeFiltersMap; +export type Bounds = import('./slide/get-thumb-bounds').Bounds; /** * */ @@ -44,7 +45,8 @@ export type ElementProvider = string | NodeListOf | HTMLElement[] | /** * https://photoswipe.com/options/ */ -export type PhotoSwipeOptions = { +export type PhotoSwipeOptions = Partial; +export type PreparedPhotoSwipeOptions = { /** * Pass an array of any items via dataSource option. Its length will determine amount of slides * (which may be modified further from numItems event). @@ -241,6 +243,7 @@ export type PhotoSwipeOptions = { /** @typedef {import('./main-scroll.js').ItemHolder} ItemHolder */ /** @typedef {import('./core/eventable.js').PhotoSwipeEventsMap} PhotoSwipeEventsMap */ /** @typedef {import('./core/eventable.js').PhotoSwipeFiltersMap} PhotoSwipeFiltersMap */ +/** @typedef {import('./slide/get-thumb-bounds').Bounds} Bounds */ /** * @template T * @typedef {import('./core/eventable.js').EventCallback} EventCallback @@ -261,8 +264,9 @@ export type PhotoSwipeOptions = { /** * @typedef {string | NodeListOf | HTMLElement[] | HTMLElement} ElementProvider */ +/** @typedef {Partial} PhotoSwipeOptions https://photoswipe.com/options/ */ /** - * @typedef {Object} PhotoSwipeOptions https://photoswipe.com/options/ + * @typedef {Object} PreparedPhotoSwipeOptions * * @prop {DataSource} [dataSource] * Pass an array of any items via dataSource option. Its length will determine amount of slides @@ -429,9 +433,9 @@ export type PhotoSwipeOptions = { */ declare class PhotoSwipe extends PhotoSwipeBase { /** - * @param {Partial} [options] + * @param {PhotoSwipeOptions} [options] */ - constructor(options?: Partial | undefined); + constructor(options?: Partial | undefined); /** * offset of viewport relative to document * @@ -455,11 +459,16 @@ declare class PhotoSwipe extends PhotoSwipeBase { bgOpacity: number; currIndex: number; potentialIndex: number; + isOpen: boolean; + isDestroying: boolean; + hasMouse: boolean; /** * @private * @type {SlideData} */ private _initialItemData; + /** @type {Bounds | null} */ + _initialThumbBounds: Bounds | null; /** @type {HTMLDivElement | undefined} */ topBar: HTMLDivElement | undefined; /** @type {HTMLDivElement | undefined} */ @@ -473,7 +482,6 @@ declare class PhotoSwipe extends PhotoSwipeBase { /** @type {Slide | undefined} */ currSlide: Slide | undefined; events: DOMEvents; - /** @type {Animations} */ animations: Animations; mainScroll: MainScroll; gestures: Gestures; @@ -482,14 +490,13 @@ declare class PhotoSwipe extends PhotoSwipeBase { contentLoader: ContentLoader; scrollWheel: ScrollWheel; ui: UI; - init(): true | undefined; - isOpen: boolean | undefined; - _initialThumbBounds: import("./slide/get-thumb-bounds.js").Bounds | null | undefined; + init(): void; /** * Get looped slide index * (for example, -1 will return the last slide) * * @param {number} index + * @returns {number} */ getLoopedIndex(index: number): number; appendHeavy(): void; @@ -521,7 +528,6 @@ declare class PhotoSwipe extends PhotoSwipeBase { * After closing transition ends - destroy it */ close(): void; - isDestroying: boolean | undefined; /** * Destroys the gallery: * - instantly closes the gallery @@ -541,18 +547,16 @@ declare class PhotoSwipe extends PhotoSwipeBase { * * @param {ItemHolder} holder mainScroll.itemHolders array item * @param {number} index Slide index - * @param {boolean=} force If content should be set even if index wasn't changed + * @param {boolean} [force] If content should be set even if index wasn't changed */ setContent(holder: ItemHolder, index: number, force?: boolean | undefined): void; - getViewportCenterPoint(): { - x: number; - y: number; - }; + /** @returns {Point} */ + getViewportCenterPoint(): Point; /** * Update size of all elements. * Executed on init and on page resize. * - * @param {boolean=} force Update size even if size of viewport was not changed. + * @param {boolean} [force] Update size even if size of viewport was not changed. */ updateSize(force?: boolean | undefined): void; /** @@ -563,7 +567,6 @@ declare class PhotoSwipe extends PhotoSwipeBase { * Whether mouse is detected */ mouseDetected(): void; - hasMouse: boolean | undefined; /** * Page resize event handler * @@ -596,10 +599,12 @@ declare class PhotoSwipe extends PhotoSwipeBase { * {x:,y:,w:} * * Height is optional (calculated based on the large image) + * + * @returns {Bounds | null} */ - getThumbBounds(): import("./slide/get-thumb-bounds.js").Bounds | null; + getThumbBounds(): Bounds | null; /** - * If the PhotoSwipe can have continious loop + * If the PhotoSwipe can have continuous loop * @returns Boolean */ canLoop(): boolean; diff --git a/dist/types/slide/zoom-level.d.ts b/dist/types/slide/zoom-level.d.ts index 4baff8b4..85b6099c 100644 --- a/dist/types/slide/zoom-level.d.ts +++ b/dist/types/slide/zoom-level.d.ts @@ -22,7 +22,7 @@ declare class ZoomLevel { */ constructor(options: PhotoSwipeOptions, itemData: SlideData, index: number, pswp?: import("../photoswipe.js").default | undefined); pswp: import("../photoswipe.js").default | undefined; - options: import("../photoswipe.js").PhotoSwipeOptions; + options: Partial; itemData: import("../slide/slide.js").SlideData; index: number; /** @type { Point | null } */ diff --git a/src/js/core/base.js b/src/js/core/base.js index 499fcdfb..48622e67 100644 --- a/src/js/core/base.js +++ b/src/js/core/base.js @@ -5,6 +5,7 @@ import { lazyLoadData } from '../slide/loader.js'; /** @typedef {import("../photoswipe.js").default} PhotoSwipe */ /** @typedef {import("../photoswipe.js").PhotoSwipeOptions} PhotoSwipeOptions */ +/** @typedef {import("../photoswipe.js").PreparedPhotoSwipeOptions} PreparedPhotoSwipeOptions */ /** @typedef {import("../slide/slide.js").SlideData} SlideData */ /** @@ -187,8 +188,8 @@ class PhotoSwipeBase extends Eventable { /** * @protected - * @param {Partial} options - * @returns {PhotoSwipeOptions} + * @param {PhotoSwipeOptions} options + * @returns {PreparedPhotoSwipeOptions} */ _prepareOptions(options) { if (window.matchMedia('(prefers-reduced-motion), (update: slow)').matches) { @@ -196,7 +197,7 @@ class PhotoSwipeBase extends Eventable { options.zoomAnimationDuration = 0; } - /** @type {PhotoSwipeOptions}*/ + /** @type {PreparedPhotoSwipeOptions} */ return { ...defaultOptions, ...options diff --git a/src/js/core/eventable.js b/src/js/core/eventable.js index a6d050d4..fa4d323f 100644 --- a/src/js/core/eventable.js +++ b/src/js/core/eventable.js @@ -1,6 +1,6 @@ /** @typedef {import('../lightbox/lightbox.js').default} PhotoSwipeLightbox */ /** @typedef {import('../photoswipe.js').default} PhotoSwipe */ -/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */ +/** @typedef {import('../photoswipe.js').PreparedPhotoSwipeOptions} PreparedPhotoSwipeOptions */ /** @typedef {import('../photoswipe.js').DataSource} DataSource */ /** @typedef {import('../ui/ui-element.js').UIElementData} UIElementData */ /** @typedef {import('../slide/content.js').default} ContentDefault */ @@ -199,7 +199,7 @@ * @typedef {(event: AugmentedEvent) => void} EventCallback */ -/** @type {PhotoSwipeOptions} */ +/** @type {PreparedPhotoSwipeOptions} */ export const defaultOptions = { allowPanToNext: true, spacing: 0.1, @@ -270,7 +270,7 @@ class Eventable { /** @type {PhotoSwipe | undefined} */ this.pswp = undefined; - /** @type {PhotoSwipeOptions} */ + /** @type {PreparedPhotoSwipeOptions} */ this.options = defaultOptions; } diff --git a/src/js/lightbox/lightbox.js b/src/js/lightbox/lightbox.js index 75dd588d..a8cd0508 100644 --- a/src/js/lightbox/lightbox.js +++ b/src/js/lightbox/lightbox.js @@ -42,13 +42,20 @@ import { lazyLoadSlide } from '../slide/loader.js'; */ class PhotoSwipeLightbox extends PhotoSwipeBase { /** - * @param {Partial} [options] + * @param {PhotoSwipeOptions} [options] */ constructor(options) { super(); - /** @type {PhotoSwipeOptions} */ this.options = this._prepareOptions(options || {}); this._uid = 0; + this.shouldOpen = false; + /** + * @private + * @type {Content | undefined} + */ + this._preloadedContent = undefined; + + this.onThumbnailsClick = this.onThumbnailsClick.bind(this); } /** @@ -56,8 +63,6 @@ class PhotoSwipeLightbox extends PhotoSwipeBase { * It's not included in the main constructor, so you may bind events before it. */ init() { - this.onThumbnailsClick = this.onThumbnailsClick.bind(this); - // Bind click events to each gallery getElementsFromOption(this.options.gallery, this.options.gallerySelector) .forEach((galleryElement) => { @@ -81,7 +86,7 @@ class PhotoSwipeLightbox extends PhotoSwipeBase { // so we do not pass the initialPoint // // Note that some screen readers emulate the mouse position, - // so it's not ideal way to detect them. + // so it's not the ideal way to detect them. // /** @type {Point | null} */ let initialPoint = { x: e.clientX, y: e.clientY }; @@ -92,6 +97,7 @@ class PhotoSwipeLightbox extends PhotoSwipeBase { let clickedIndex = this.getClickedIndex(e); clickedIndex = this.applyFilters('clickedIndex', clickedIndex, e, this); + /** @type {DataSource} */ const dataSource = { gallery: /** @type {HTMLElement} */ (e.currentTarget) }; @@ -106,6 +112,7 @@ class PhotoSwipeLightbox extends PhotoSwipeBase { * Get index of gallery item that was clicked. * * @param {MouseEvent} e click event + * @returns {number} */ getClickedIndex(e) { // legacy option @@ -140,6 +147,7 @@ class PhotoSwipeLightbox extends PhotoSwipeBase { * @param {number} index * @param {DataSource} dataSource * @param {Point | null} [initialPoint] + * @returns {boolean} */ loadAndOpen(index, dataSource, initialPoint) { // Check if the gallery is already open @@ -162,7 +170,7 @@ class PhotoSwipeLightbox extends PhotoSwipeBase { * Load the main module and the slide content by index * * @param {number} index - * @param {DataSource=} dataSource + * @param {DataSource} [dataSource] */ preload(index, dataSource) { const { options } = this; @@ -258,7 +266,7 @@ class PhotoSwipeLightbox extends PhotoSwipeBase { if (this._preloadedContent) { pswp.contentLoader.addToCache(this._preloadedContent); - this._preloadedContent = null; + this._preloadedContent = undefined; } pswp.on('destroy', () => { @@ -274,9 +282,7 @@ class PhotoSwipeLightbox extends PhotoSwipeBase { * Unbinds all events, closes PhotoSwipe if it's open. */ destroy() { - if (this.pswp) { - this.pswp.destroy(); - } + this.pswp?.destroy(); this.shouldOpen = false; this._listeners = {}; diff --git a/src/js/photoswipe.js b/src/js/photoswipe.js index f99a64dc..24a68d06 100644 --- a/src/js/photoswipe.js +++ b/src/js/photoswipe.js @@ -31,6 +31,7 @@ import ContentLoader from './slide/loader.js'; /** @typedef {import('./main-scroll.js').ItemHolder} ItemHolder */ /** @typedef {import('./core/eventable.js').PhotoSwipeEventsMap} PhotoSwipeEventsMap */ /** @typedef {import('./core/eventable.js').PhotoSwipeFiltersMap} PhotoSwipeFiltersMap */ +/** @typedef {import('./slide/get-thumb-bounds').Bounds} Bounds */ /** * @template T * @typedef {import('./core/eventable.js').EventCallback} EventCallback @@ -54,8 +55,9 @@ import ContentLoader from './slide/loader.js'; * @typedef {string | NodeListOf | HTMLElement[] | HTMLElement} ElementProvider */ +/** @typedef {Partial} PhotoSwipeOptions https://photoswipe.com/options/ */ /** - * @typedef {Object} PhotoSwipeOptions https://photoswipe.com/options/ + * @typedef {Object} PreparedPhotoSwipeOptions * * @prop {DataSource} [dataSource] * Pass an array of any items via dataSource option. Its length will determine amount of slides @@ -223,7 +225,7 @@ import ContentLoader from './slide/loader.js'; */ class PhotoSwipe extends PhotoSwipeBase { /** - * @param {Partial} [options] + * @param {PhotoSwipeOptions} [options] */ constructor(options) { super(); @@ -256,11 +258,17 @@ class PhotoSwipe extends PhotoSwipeBase { this.bgOpacity = 1; this.currIndex = 0; this.potentialIndex = 0; + this.isOpen = false; + this.isDestroying = false; + this.hasMouse = false; + /** * @private * @type {SlideData} */ this._initialItemData = {}; + /** @type {Bounds | null} */ + this._initialThumbBounds = null; /** @type {HTMLDivElement | undefined} */ this.topBar = undefined; @@ -276,10 +284,7 @@ class PhotoSwipe extends PhotoSwipeBase { this.currSlide = undefined; this.events = new DOMEvents(); - - /** @type {Animations} */ this.animations = new Animations(); - this.mainScroll = new MainScroll(this); this.gestures = new Gestures(this); this.opener = new Opener(this); @@ -346,12 +351,17 @@ class PhotoSwipe extends PhotoSwipeBase { this.dispatch('initialLayout'); this.on('openingAnimationEnd', () => { - this.mainScroll.itemHolders[0].el.style.display = 'block'; - this.mainScroll.itemHolders[2].el.style.display = 'block'; + const { itemHolders } = this.mainScroll; // Add content to the previous and next slide - this.setContent(this.mainScroll.itemHolders[0], this.currIndex - 1); - this.setContent(this.mainScroll.itemHolders[2], this.currIndex + 1); + if (itemHolders[0]) { + itemHolders[0].el.style.display = 'block'; + this.setContent(itemHolders[0], this.currIndex - 1); + } + if (itemHolders[2]) { + itemHolders[2].el.style.display = 'block'; + this.setContent(itemHolders[2], this.currIndex + 1); + } this.appendHeavy(); @@ -363,14 +373,14 @@ class PhotoSwipe extends PhotoSwipeBase { }); // set content for center slide (first time) - this.setContent(this.mainScroll.itemHolders[1], this.currIndex); + if (this.mainScroll.itemHolders[1]) { + this.setContent(this.mainScroll.itemHolders[1], this.currIndex); + } this.dispatch('change'); this.opener.open(); this.dispatch('afterInit'); - - return true; } /** @@ -378,6 +388,7 @@ class PhotoSwipe extends PhotoSwipeBase { * (for example, -1 will return the last slide) * * @param {number} index + * @returns {number} */ getLoopedIndex(index) { const numSlides = this.getNumItems(); @@ -392,9 +403,7 @@ class PhotoSwipe extends PhotoSwipeBase { } } - index = clamp(index, 0, numSlides - 1); - - return index; + return clamp(index, 0, numSlides - 1); } appendHeavy() { @@ -526,7 +535,7 @@ class PhotoSwipe extends PhotoSwipeBase { * * @param {ItemHolder} holder mainScroll.itemHolders array item * @param {number} index Slide index - * @param {boolean=} force If content should be set even if index wasn't changed + * @param {boolean} [force] If content should be set even if index wasn't changed */ setContent(holder, index, force) { if (this.canLoop()) { @@ -561,6 +570,7 @@ class PhotoSwipe extends PhotoSwipeBase { holder.slide.append(holder.el); } + /** @returns {Point} */ getViewportCenterPoint() { return { x: this.viewportSize.x / 2, @@ -572,7 +582,7 @@ class PhotoSwipe extends PhotoSwipeBase { * Update size of all elements. * Executed on init and on page resize. * - * @param {boolean=} force Update size even if size of viewport was not changed. + * @param {boolean} [force] Update size even if size of viewport was not changed. */ updateSize(force) { // let item; @@ -622,7 +632,9 @@ class PhotoSwipe extends PhotoSwipeBase { */ applyBgOpacity(opacity) { this.bgOpacity = Math.max(opacity, 0); - if (this.bg) this.bg.style.opacity = String(this.bgOpacity * this.options.bgOpacity); + if (this.bg) { + this.bg.style.opacity = String(this.bgOpacity * this.options.bgOpacity); + } } /** @@ -716,6 +728,8 @@ class PhotoSwipe extends PhotoSwipeBase { * {x:,y:,w:} * * Height is optional (calculated based on the large image) + * + * @returns {Bounds | null} */ getThumbBounds() { return getThumbBounds( @@ -726,7 +740,7 @@ class PhotoSwipe extends PhotoSwipeBase { } /** - * If the PhotoSwipe can have continious loop + * If the PhotoSwipe can have continuous loop * @returns Boolean */ canLoop() { From 696d042fd3b6e0100f0ac6a58e3e8df1e424051f Mon Sep 17 00:00:00 2001 From: mishchuk Date: Mon, 30 Jan 2023 15:00:25 +1000 Subject: [PATCH 27/27] fix code after review --- dist/types/core/base.d.ts | 10 - dist/types/core/eventable.d.ts | 406 ++++++++++++------------- dist/types/gestures/drag-handler.d.ts | 1 + dist/types/gestures/gestures.d.ts | 6 +- dist/types/lightbox/lightbox.d.ts | 2 + dist/types/opener.d.ts | 2 +- dist/types/photoswipe.d.ts | 217 +------------ dist/types/slide/get-thumb-bounds.d.ts | 4 +- dist/types/slide/pan-bounds.d.ts | 18 +- dist/types/slide/slide.d.ts | 2 +- dist/types/ui/ui.d.ts | 2 + dist/types/util/animations.d.ts | 1 + dist/types/util/util.d.ts | 3 +- src/js/core/base.js | 36 +-- src/js/core/eventable.js | 43 +-- src/js/gestures/drag-handler.js | 56 ++-- src/js/gestures/gestures.js | 34 +-- src/js/gestures/zoom-handler.js | 8 +- src/js/lightbox/lightbox.js | 3 +- src/js/opener.js | 10 +- src/js/photoswipe.js | 69 ++++- src/js/slide/content.js | 2 +- src/js/slide/get-thumb-bounds.js | 16 +- src/js/slide/loader.js | 25 +- src/js/slide/pan-bounds.js | 18 +- src/js/slide/slide.js | 12 +- src/js/ui/loading-indicator.js | 5 + src/js/ui/ui.js | 2 + src/js/util/animations.js | 3 + src/js/util/util.js | 11 +- 30 files changed, 431 insertions(+), 596 deletions(-) diff --git a/dist/types/core/base.d.ts b/dist/types/core/base.d.ts index a3ef465f..bbd9a90d 100644 --- a/dist/types/core/base.d.ts +++ b/dist/types/core/base.d.ts @@ -1,11 +1,7 @@ export default PhotoSwipeBase; export type PhotoSwipe = import("../photoswipe.js").default; -export type PhotoSwipeOptions = import("../photoswipe.js").PhotoSwipeOptions; -export type PreparedPhotoSwipeOptions = import("../photoswipe.js").PreparedPhotoSwipeOptions; export type SlideData = import("../slide/slide.js").SlideData; /** @typedef {import("../photoswipe.js").default} PhotoSwipe */ -/** @typedef {import("../photoswipe.js").PhotoSwipeOptions} PhotoSwipeOptions */ -/** @typedef {import("../photoswipe.js").PreparedPhotoSwipeOptions} PreparedPhotoSwipeOptions */ /** @typedef {import("../slide/slide.js").SlideData} SlideData */ /** * PhotoSwipe base class that can retrieve data about every slide. @@ -58,12 +54,6 @@ declare class PhotoSwipeBase extends Eventable { * @returns {Content} Image that is being decoded or false. */ lazyLoadData(itemData: SlideData, index: number): Content; - /** - * @protected - * @param {PhotoSwipeOptions} options - * @returns {PreparedPhotoSwipeOptions} - */ - protected _prepareOptions(options: PhotoSwipeOptions): PreparedPhotoSwipeOptions; } import Eventable from "./eventable.js"; import Content from "../slide/content.js"; diff --git a/dist/types/core/eventable.d.ts b/dist/types/core/eventable.d.ts index e877b392..f7931fa0 100644 --- a/dist/types/core/eventable.d.ts +++ b/dist/types/core/eventable.d.ts @@ -1,203 +1,7 @@ -/** @typedef {import('../lightbox/lightbox.js').default} PhotoSwipeLightbox */ -/** @typedef {import('../photoswipe.js').default} PhotoSwipe */ -/** @typedef {import('../photoswipe.js').PreparedPhotoSwipeOptions} PreparedPhotoSwipeOptions */ -/** @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} 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, 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) => 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, itemData: SlideData, index: number) => HTMLElement} thumbEl - * Modify the thubmnail element from which opening zoom animation starts or ends. - * https://photoswipe.com/filters/#thumbel - * - * @prop {(thumbBounds: Bounds, itemData: SlideData, index: number) => Bounds} thumbBounds - * Modify the thubmnail bounds from which opening zoom animation starts or ends. - * https://photoswipe.com/filters/#thumbbounds - * - * @prop {(srcsetSizesWidth: number, content: Content) => number} srcsetSizesWidth - * - */ -/** - * @template {keyof PhotoSwipeFiltersMap} T - * @typedef {{ fn: PhotoSwipeFiltersMap[T], priority: number }} Filter - */ -/** - * @template {keyof PhotoSwipeEventsMap} T - * @typedef {PhotoSwipeEventsMap[T] extends undefined ? PhotoSwipeEvent : PhotoSwipeEvent & PhotoSwipeEventsMap[T]} AugmentedEvent - */ -/** - * @template {keyof PhotoSwipeEventsMap} T - * @typedef {(event: AugmentedEvent) => void} EventCallback - */ -/** @type {PreparedPhotoSwipeOptions} */ -export const defaultOptions: PreparedPhotoSwipeOptions; export default Eventable; export type PhotoSwipeLightbox = import('../lightbox/lightbox.js').default; export type PhotoSwipe = import('../photoswipe.js').default; -export type PreparedPhotoSwipeOptions = import('../photoswipe.js').PreparedPhotoSwipeOptions; +export type PhotoSwipeOptions = import('../photoswipe.js').PhotoSwipeOptions; export type DataSource = import('../photoswipe.js').DataSource; export type UIElementData = import('../ui/ui-element.js').UIElementData; export type ContentDefault = import('../slide/content.js').default; @@ -480,7 +284,7 @@ export type PhotoSwipeEventsMap = { initialZoomInEnd: undefined; initialZoomOutEnd: undefined; numItems: { - dataSource: DataSource; + dataSource: DataSource | undefined; numItems: number; }; itemData: { @@ -501,7 +305,7 @@ export type PhotoSwipeFiltersMap = { * Modify the total amount of slides. Example on Data sources page. * https://photoswipe.com/filters/#numitems */ - numItems: (numItems: number, dataSource: DataSource) => number; + numItems: (numItems: number, dataSource: DataSource | undefined) => number; /** * Modify slide item data. Example on Data sources page. * https://photoswipe.com/filters/#itemdata @@ -556,12 +360,12 @@ export type PhotoSwipeFiltersMap = { * Modify the thubmnail element from which opening zoom animation starts or ends. * https://photoswipe.com/filters/#thumbel */ - thumbEl: (thumbnail: HTMLElement, itemData: SlideData, index: number) => HTMLElement; + thumbEl: (thumbnail: HTMLElement | null | undefined, itemData: SlideData, index: number) => HTMLElement; /** * Modify the thubmnail bounds from which opening zoom animation starts or ends. * https://photoswipe.com/filters/#thumbbounds */ - thumbBounds: (thumbBounds: Bounds, itemData: SlideData, index: number) => Bounds; + thumbBounds: (thumbBounds: Bounds | undefined, itemData: SlideData, index: number) => Bounds; srcsetSizesWidth: (srcsetSizesWidth: number, content: Content) => number; }; export type Filter = { @@ -838,7 +642,7 @@ declare class Eventable { initialZoomInEnd?: ((event: PhotoSwipeEvent<"initialZoomInEnd">) => void)[] | undefined; initialZoomOutEnd?: ((event: PhotoSwipeEvent<"initialZoomOutEnd">) => void)[] | undefined; numItems?: ((event: PhotoSwipeEvent<"numItems"> & { - dataSource: import("../photoswipe.js").DataSource; + dataSource: import("../photoswipe.js").DataSource | undefined; numItems: number; }) => void)[] | undefined; itemData?: ((event: PhotoSwipeEvent<"itemData"> & { @@ -924,8 +728,8 @@ declare class Eventable { }; /** @type {PhotoSwipe | undefined} */ pswp: PhotoSwipe | undefined; - /** @type {PreparedPhotoSwipeOptions} */ - options: PreparedPhotoSwipeOptions; + /** @type {PhotoSwipeOptions | undefined} */ + options: Partial | undefined; /** * @template {keyof PhotoSwipeFiltersMap} T * @param {T} name @@ -966,6 +770,200 @@ declare class Eventable { */ dispatch(name: T_5, details?: PhotoSwipeEventsMap[T_5] | undefined): AugmentedEvent; } +/** @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} 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 thubmnail 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 thubmnail bounds from which opening zoom animation starts or ends. + * https://photoswipe.com/filters/#thumbbounds + * + * @prop {(srcsetSizesWidth: number, content: Content) => number} srcsetSizesWidth + * + */ +/** + * @template {keyof PhotoSwipeFiltersMap} T + * @typedef {{ fn: PhotoSwipeFiltersMap[T], priority: number }} Filter + */ +/** + * @template {keyof PhotoSwipeEventsMap} T + * @typedef {PhotoSwipeEventsMap[T] extends undefined ? PhotoSwipeEvent : PhotoSwipeEvent & PhotoSwipeEventsMap[T]} AugmentedEvent + */ +/** + * @template {keyof PhotoSwipeEventsMap} T + * @typedef {(event: AugmentedEvent) => void} EventCallback + */ /** * Base PhotoSwipe event object * diff --git a/dist/types/gestures/drag-handler.d.ts b/dist/types/gestures/drag-handler.d.ts index 5152eea6..81f60217 100644 --- a/dist/types/gestures/drag-handler.d.ts +++ b/dist/types/gestures/drag-handler.d.ts @@ -10,6 +10,7 @@ declare class DragHandler { */ constructor(gestures: Gestures); gestures: import("./gestures.js").default; + pswp: import("../photoswipe.js").default; /** @type {Point} */ startPan: Point; start(): void; diff --git a/dist/types/gestures/gestures.d.ts b/dist/types/gestures/gestures.d.ts index 513962f5..fd166889 100644 --- a/dist/types/gestures/gestures.d.ts +++ b/dist/types/gestures/gestures.d.ts @@ -56,10 +56,8 @@ declare class Gestures { isMultitouch: boolean; isDragging: boolean; isZooming: boolean; - /** @type {number | null} - * @private - */ - private _raf; + /** @type {number | null} */ + raf: number | null; /** @type {NodeJS.Timeout | null} * @private */ diff --git a/dist/types/lightbox/lightbox.d.ts b/dist/types/lightbox/lightbox.d.ts index 0979c47b..275c4df0 100644 --- a/dist/types/lightbox/lightbox.d.ts +++ b/dist/types/lightbox/lightbox.d.ts @@ -49,6 +49,8 @@ declare class PhotoSwipeLightbox extends PhotoSwipeBase { * @param {PhotoSwipeOptions} [options] */ constructor(options?: Partial | undefined); + /** @type {PhotoSwipeOptions} */ + options: Partial; _uid: number; shouldOpen: boolean; /** diff --git a/dist/types/opener.d.ts b/dist/types/opener.d.ts index a6548912..42a11e26 100644 --- a/dist/types/opener.d.ts +++ b/dist/types/opener.d.ts @@ -52,7 +52,7 @@ declare class Opener { private _cropContainer2; /** * @private - * @type {Bounds | null} + * @type {Bounds | undefined} */ private _thumbBounds; /** @private */ diff --git a/dist/types/photoswipe.d.ts b/dist/types/photoswipe.d.ts index cf86810e..558dadb1 100644 --- a/dist/types/photoswipe.d.ts +++ b/dist/types/photoswipe.d.ts @@ -233,201 +233,6 @@ export type PreparedPhotoSwipeOptions = { childSelector?: string | undefined; thumbSelector?: string | false | undefined; }; -/** - * @template T - * @typedef {import('./types.js').Type} Type - */ -/** @typedef {import('./slide/slide.js').SlideData} SlideData */ -/** @typedef {import('./slide/zoom-level.js').ZoomLevelOption} ZoomLevelOption */ -/** @typedef {import('./ui/ui-element.js').UIElementData} UIElementData */ -/** @typedef {import('./main-scroll.js').ItemHolder} ItemHolder */ -/** @typedef {import('./core/eventable.js').PhotoSwipeEventsMap} PhotoSwipeEventsMap */ -/** @typedef {import('./core/eventable.js').PhotoSwipeFiltersMap} PhotoSwipeFiltersMap */ -/** @typedef {import('./slide/get-thumb-bounds').Bounds} Bounds */ -/** - * @template T - * @typedef {import('./core/eventable.js').EventCallback} EventCallback - */ -/** - * @template T - * @typedef {import('./core/eventable.js').AugmentedEvent} AugmentedEvent - */ -/** @typedef {{ x: number; y: number; id?: string | number }} Point */ -/** @typedef {{ top: number; bottom: number; left: number; right: number }} Padding */ -/** @typedef {SlideData[]} DataSourceArray */ -/** @typedef {{ gallery: HTMLElement; items?: HTMLElement[] }} DataSourceObject */ -/** @typedef {DataSourceArray | DataSourceObject} DataSource */ -/** @typedef {(point: Point, originalEvent: PointerEvent) => void} ActionFn */ -/** @typedef {'close' | 'next' | 'zoom' | 'zoom-or-close' | 'toggle-controls'} ActionType */ -/** @typedef {Type | { default: Type }} PhotoSwipeModule */ -/** @typedef {PhotoSwipeModule | Promise | (() => Promise)} PhotoSwipeModuleOption */ -/** - * @typedef {string | NodeListOf | HTMLElement[] | HTMLElement} ElementProvider - */ -/** @typedef {Partial} PhotoSwipeOptions https://photoswipe.com/options/ */ -/** - * @typedef {Object} PreparedPhotoSwipeOptions - * - * @prop {DataSource} [dataSource] - * Pass an array of any items via dataSource option. Its length will determine amount of slides - * (which may be modified further from numItems event). - * - * Each item should contain data that you need to generate slide - * (for image slide it would be src (image URL), width (image width), height, srcset, alt). - * - * If these properties are not present in your initial array, you may "pre-parse" each item from itemData filter. - * - * @prop {number} bgOpacity - * Background backdrop opacity, always define it via this option and not via CSS rgba color. - * - * @prop {number} spacing - * Spacing between slides. Defined as ratio relative to the viewport width (0.1 = 10% of viewport). - * - * @prop {boolean} allowPanToNext - * Allow swipe navigation to the next slide when the current slide is zoomed. Does not apply to mouse events. - * - * @prop {boolean} loop - * If set to true you'll be able to swipe from the last to the first image. - * Option is always false when there are less than 3 slides. - * - * @prop {boolean} [wheelToZoom] - * By default PhotoSwipe zooms image with ctrl-wheel, if you enable this option - image will zoom just via wheel. - * - * @prop {boolean} pinchToClose - * Pinch touch gesture to close the gallery. - * - * @prop {boolean} closeOnVerticalDrag - * Vertical drag gesture to close the PhotoSwipe. - * - * @prop {Padding} [padding] - * Slide area padding (in pixels). - * - * @prop {(viewportSize: Point, itemData: SlideData, index: number) => Padding} [paddingFn] - * The option is checked frequently, so make sure it's performant. Overrides padding option if defined. For example: - * - * @prop {number | false} hideAnimationDuration - * Transition duration in milliseconds, can be 0. - * - * @prop {number | false} showAnimationDuration - * Transition duration in milliseconds, can be 0. - * - * @prop {number | false} zoomAnimationDuration - * Transition duration in milliseconds, can be 0. - * - * @prop {string} easing - * String, 'cubic-bezier(.4,0,.22,1)'. CSS easing function for open/close/zoom transitions. - * - * @prop {boolean} escKey - * Esc key to close. - * - * @prop {boolean} arrowKeys - * Left/right arrow keys for navigation. - * - * @prop {boolean} returnFocus - * Restore focus the last active element after PhotoSwipe is closed. - * - * @prop {boolean} clickToCloseNonZoomable - * If image is not zoomable (for example, smaller than viewport) it can be closed by clicking on it. - * - * @prop {ActionType | ActionFn | false} imageClickAction - * Refer to click and tap actions page. - * - * @prop {ActionType | ActionFn | false} bgClickAction - * Refer to click and tap actions page. - * - * @prop {ActionType | ActionFn | false} tapAction - * Refer to click and tap actions page. - * - * @prop {ActionType | ActionFn | false} doubleTapAction - * Refer to click and tap actions page. - * - * @prop {number} preloaderDelay - * Delay before the loading indicator will be displayed, - * if image is loaded during it - the indicator will not be displayed at all. Can be zero. - * - * @prop {string} indexIndicatorSep - * Used for slide count indicator ("1 of 10 "). - * - * @prop {(options: PhotoSwipeOptions, pswp: PhotoSwipe) => Point} [getViewportSizeFn] - * A function that should return slide viewport width and height, in format {x: 100, y: 100}. - * - * @prop {string} errorMsg - * Message to display when the image wasn't able to load. If you need to display HTML - use contentErrorElement filter. - * - * @prop {[number, number]} preload - * Lazy loading of nearby slides based on direction of movement. Should be an array with two integers, - * first one - number of items to preload before the current image, second one - after the current image. - * Two nearby images are always loaded. - * - * @prop {string} [mainClass] - * Class that will be added to the root element of PhotoSwipe, may contain multiple separated by space. - * Example on Styling page. - * - * @prop {HTMLElement} [appendToEl] - * Element to which PhotoSwipe dialog will be appended when it opens. - * - * @prop {number} maxWidthToAnimate - * Maximum width of image to animate, if initial rendered image width - * is larger than this value - the opening/closing transition will be automatically disabled. - * - * @prop {string} [closeTitle] - * Translating - * - * @prop {string} [zoomTitle] - * Translating - * - * @prop {string} [arrowPrevTitle] - * Translating - * - * @prop {string} [arrowNextTitle] - * Translating - * - * @prop {'zoom' | 'fade' | 'none'} [showHideAnimationType] - * To adjust opening or closing transition type use lightbox option `showHideAnimationType` (`String`). - * It supports three values - `zoom` (default), `fade` (default if there is no thumbnail) and `none`. - * - * Animations are automatically disabled if user `(prefers-reduced-motion: reduce)`. - * - * @prop {number} index - * Defines start slide index. - * - * @prop {(e: MouseEvent) => number} [getClickedIndexFn] - * - * @prop {boolean} [arrowPrev] - * @prop {boolean} [arrowNext] - * @prop {boolean} [zoom] - * @prop {boolean} [close] - * @prop {boolean} [counter] - * - * @prop {string} [arrowPrevSVG] - * @prop {string} [arrowNextSVG] - * @prop {string} [zoomSVG] - * @prop {string} [closeSVG] - * @prop {string} [counterSVG] - * - * @prop {string} [arrowPrevTitle] - * @prop {string} [arrowNextTitle] - * @prop {string} [zoomTitle] - * @prop {string} [closeTitle] - * @prop {string} [counterTitle] - * - * @prop {ZoomLevelOption} [initialZoomLevel] - * @prop {ZoomLevelOption} [secondaryZoomLevel] - * @prop {ZoomLevelOption} [maxZoomLevel] - * - * @prop {boolean} [mouseMovePan] - * @prop {Point | null} [initialPointerPos] - * @prop {boolean} [showHideOpacity] - * - * @prop {PhotoSwipeModuleOption} [pswpModule] - * @prop {() => Promise} [openPromise] - * @prop {boolean} [preloadFirstSlide] - * @prop {ElementProvider} [gallery] - * @prop {string} [gallerySelector] - * @prop {ElementProvider} [children] - * @prop {string} [childSelector] - * @prop {string | false} [thumbSelector] - */ /** * PhotoSwipe Core */ @@ -436,6 +241,7 @@ declare class PhotoSwipe extends PhotoSwipeBase { * @param {PhotoSwipeOptions} [options] */ constructor(options?: Partial | undefined); + options: PreparedPhotoSwipeOptions; /** * offset of viewport relative to document * @@ -467,8 +273,8 @@ declare class PhotoSwipe extends PhotoSwipeBase { * @type {SlideData} */ private _initialItemData; - /** @type {Bounds | null} */ - _initialThumbBounds: Bounds | null; + /** @type {Bounds | undefined} */ + _initialThumbBounds: Bounds | undefined; /** @type {HTMLDivElement | undefined} */ topBar: HTMLDivElement | undefined; /** @type {HTMLDivElement | undefined} */ @@ -488,9 +294,9 @@ declare class PhotoSwipe extends PhotoSwipeBase { opener: Opener; keyboard: Keyboard; contentLoader: ContentLoader; - scrollWheel: ScrollWheel; - ui: UI; - init(): void; + /** @returns {boolean} */ + init(): boolean; + scrollWheel: ScrollWheel | undefined; /** * Get looped slide index * (for example, -1 will return the last slide) @@ -594,20 +400,27 @@ declare class PhotoSwipe extends PhotoSwipeBase { */ private _createMainStructure; bg: HTMLDivElement | undefined; + ui: UI | undefined; /** * Get position and dimensions of small thumbnail * {x:,y:,w:} * * Height is optional (calculated based on the large image) * - * @returns {Bounds | null} + * @returns {Bounds | undefined} */ - getThumbBounds(): Bounds | null; + getThumbBounds(): Bounds | undefined; /** * If the PhotoSwipe can have continuous loop * @returns Boolean */ canLoop(): boolean; + /** + * @private + * @param {PhotoSwipeOptions} options + * @returns {PreparedPhotoSwipeOptions} + */ + private _prepareOptions; } import PhotoSwipeBase from "./core/base.js"; import Slide from "./slide/slide.js"; diff --git a/dist/types/slide/get-thumb-bounds.d.ts b/dist/types/slide/get-thumb-bounds.d.ts index a7e5ddaa..4bb3c54f 100644 --- a/dist/types/slide/get-thumb-bounds.d.ts +++ b/dist/types/slide/get-thumb-bounds.d.ts @@ -5,9 +5,9 @@ * @param {number} index * @param {SlideData} itemData * @param {PhotoSwipe} instance PhotoSwipe instance - * @returns {Bounds | null} + * @returns {Bounds | undefined} */ -export function getThumbBounds(index: number, itemData: SlideData, instance: PhotoSwipe): Bounds | null; +export function getThumbBounds(index: number, itemData: SlideData, instance: PhotoSwipe): Bounds | undefined; export type SlideData = import('./slide.js').SlideData; export type PhotoSwipe = import('../photoswipe.js').default; export type Bounds = { diff --git a/dist/types/slide/pan-bounds.d.ts b/dist/types/slide/pan-bounds.d.ts index 9916c0b6..36c03a68 100644 --- a/dist/types/slide/pan-bounds.d.ts +++ b/dist/types/slide/pan-bounds.d.ts @@ -2,6 +2,9 @@ export default PanBounds; export type Slide = import('./slide.js').default; export type Point = Record; export type Axis = 'x' | 'y'; +/** @typedef {import('./slide.js').default} Slide */ +/** @typedef {Record} Point */ +/** @typedef {'x' | 'y'} Axis */ /** * Calculates minimum, maximum and initial (center) bounds of a slide */ @@ -12,9 +15,18 @@ declare class PanBounds { constructor(slide: Slide); slide: import("./slide.js").default; currZoomLevel: number; - center: Point; - max: Point; - min: Point; + center: { + x: number; + y: number; + }; + max: { + x: number; + y: number; + }; + min: { + x: number; + y: number; + }; /** * _getItemBounds * diff --git a/dist/types/slide/slide.d.ts b/dist/types/slide/slide.d.ts index 2335609d..6147c586 100644 --- a/dist/types/slide/slide.d.ts +++ b/dist/types/slide/slide.d.ts @@ -224,7 +224,7 @@ declare class Slide { * * @param {number} newResolution */ - setResolution(newResolution: number): void; + _setResolution(newResolution: number): void; } import ZoomLevel from "./zoom-level.js"; import PanBounds from "./pan-bounds.js"; diff --git a/dist/types/ui/ui.d.ts b/dist/types/ui/ui.d.ts index 616e240f..76d2bc00 100644 --- a/dist/types/ui/ui.d.ts +++ b/dist/types/ui/ui.d.ts @@ -12,6 +12,8 @@ declare class UI { uiElementsData: UIElementData[]; /** @type {(UIElement | UIElementData)[]} */ items: (UIElement | UIElementData)[]; + /** @type {() => void} */ + updatePreloaderVisibility: () => void; /** * @private * @type {number | undefined} diff --git a/dist/types/util/animations.d.ts b/dist/types/util/animations.d.ts index 3883a874..e95eebe0 100644 --- a/dist/types/util/animations.d.ts +++ b/dist/types/util/animations.d.ts @@ -39,6 +39,7 @@ declare class Animations { * @private * @param {AnimationProps} props * @param {boolean} [isSpring] + * @returns {Animation} */ private _start; /** diff --git a/dist/types/util/util.d.ts b/dist/types/util/util.d.ts index 16b73358..85f711ce 100644 --- a/dist/types/util/util.d.ts +++ b/dist/types/util/util.d.ts @@ -15,9 +15,8 @@ export function createElement(className: export function equalizePoints(p1: Point, p2: Point): Point; /** * @param {Point} p - * @returns {Point} */ -export function roundPoint(p: Point): Point; +export function roundPoint(p: Point): void; /** * Returns distance between two points. * diff --git a/src/js/core/base.js b/src/js/core/base.js index 48622e67..87e609db 100644 --- a/src/js/core/base.js +++ b/src/js/core/base.js @@ -1,11 +1,9 @@ -import Eventable, { defaultOptions } from './eventable.js'; +import Eventable from './eventable.js'; import { getElementsFromOption } from '../util/util.js'; import Content from '../slide/content.js'; import { lazyLoadData } from '../slide/loader.js'; /** @typedef {import("../photoswipe.js").default} PhotoSwipe */ -/** @typedef {import("../photoswipe.js").PhotoSwipeOptions} PhotoSwipeOptions */ -/** @typedef {import("../photoswipe.js").PreparedPhotoSwipeOptions} PreparedPhotoSwipeOptions */ /** @typedef {import("../slide/slide.js").SlideData} SlideData */ /** @@ -20,14 +18,12 @@ class PhotoSwipeBase extends Eventable { */ getNumItems() { let numItems = 0; - let { dataSource } = this.options; - if (!dataSource) { - dataSource = []; - } - if ('length' in dataSource) { + const dataSource = this.options?.dataSource; + + if (dataSource && 'length' in dataSource) { // may be an array or just object with length property numItems = dataSource.length; - } else if ('gallery' in dataSource) { + } else if (dataSource && 'gallery' in dataSource) { // query DOM elements if (!dataSource.items) { dataSource.items = this._getGalleryDOMElements(dataSource.gallery); @@ -66,7 +62,7 @@ class PhotoSwipeBase extends Eventable { * @returns {SlideData} */ getItemData(index) { - const { dataSource } = this.options; + const dataSource = this.options?.dataSource; /** @type {SlideData | HTMLElement} */ let dataSourceItem = {}; if (Array.isArray(dataSource)) { @@ -109,7 +105,7 @@ class PhotoSwipeBase extends Eventable { * @returns {HTMLElement[]} */ _getGalleryDOMElements(galleryElement) { - if (this.options.children || this.options.childSelector) { + if (this.options?.children || this.options?.childSelector) { return getElementsFromOption( this.options.children, this.options.childSelector, @@ -185,24 +181,6 @@ class PhotoSwipeBase extends Eventable { lazyLoadData(itemData, index) { return lazyLoadData(itemData, this, index); } - - /** - * @protected - * @param {PhotoSwipeOptions} options - * @returns {PreparedPhotoSwipeOptions} - */ - _prepareOptions(options) { - if (window.matchMedia('(prefers-reduced-motion), (update: slow)').matches) { - options.showHideAnimationType = 'none'; - options.zoomAnimationDuration = 0; - } - - /** @type {PreparedPhotoSwipeOptions} */ - return { - ...defaultOptions, - ...options - }; - } } export default PhotoSwipeBase; diff --git a/src/js/core/eventable.js b/src/js/core/eventable.js index fa4d323f..d52b0dc6 100644 --- a/src/js/core/eventable.js +++ b/src/js/core/eventable.js @@ -1,6 +1,6 @@ /** @typedef {import('../lightbox/lightbox.js').default} PhotoSwipeLightbox */ /** @typedef {import('../photoswipe.js').default} PhotoSwipe */ -/** @typedef {import('../photoswipe.js').PreparedPhotoSwipeOptions} PreparedPhotoSwipeOptions */ +/** @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 */ @@ -119,7 +119,7 @@ * @prop {undefined} initialZoomOut * @prop {undefined} initialZoomInEnd * @prop {undefined} initialZoomOutEnd - * @prop {{ dataSource: DataSource, numItems: number }} numItems + * @prop {{ dataSource: DataSource | undefined, numItems: number }} numItems * @prop {{ itemData: SlideData; index: number }} itemData * @prop {{ index: number, itemData: SlideData, instance: PhotoSwipe }} thumbBounds */ @@ -127,7 +127,7 @@ /** * @typedef {Object} PhotoSwipeFiltersMap https://photoswipe.com/filters/ * - * @prop {(numItems: number, dataSource: DataSource) => number} numItems + * @prop {(numItems: number, dataSource: DataSource | undefined) => number} numItems * Modify the total amount of slides. Example on Data sources page. * https://photoswipe.com/filters/#numitems * @@ -172,11 +172,11 @@ * Modify a UI element that's being created. * https://photoswipe.com/filters/#uielement * - * @prop {(thumbnail: HTMLElement, itemData: SlideData, index: number) => HTMLElement} thumbEl + * @prop {(thumbnail: HTMLElement | null | undefined, itemData: SlideData, index: number) => HTMLElement} thumbEl * Modify the thubmnail element from which opening zoom animation starts or ends. * https://photoswipe.com/filters/#thumbel * - * @prop {(thumbBounds: Bounds, itemData: SlideData, index: number) => Bounds} thumbBounds + * @prop {(thumbBounds: Bounds | undefined, itemData: SlideData, index: number) => Bounds} thumbBounds * Modify the thubmnail bounds from which opening zoom animation starts or ends. * https://photoswipe.com/filters/#thumbbounds * @@ -199,35 +199,6 @@ * @typedef {(event: AugmentedEvent) => void} EventCallback */ -/** @type {PreparedPhotoSwipeOptions} */ -export const defaultOptions = { - allowPanToNext: true, - spacing: 0.1, - loop: true, - pinchToClose: true, - closeOnVerticalDrag: true, - hideAnimationDuration: 333, - showAnimationDuration: 333, - zoomAnimationDuration: 333, - escKey: true, - arrowKeys: true, - returnFocus: true, - maxWidthToAnimate: 4000, - clickToCloseNonZoomable: true, - imageClickAction: 'zoom-or-close', - bgClickAction: 'close', - tapAction: 'toggle-controls', - doubleTapAction: 'zoom', - indexIndicatorSep: ' / ', - preloaderDelay: 2000, - bgOpacity: 0.8, - - index: 0, - errorMsg: 'The image cannot be loaded', - preload: [1, 2], - easing: 'cubic-bezier(.4,0,.22,1)' -}; - /** * Base PhotoSwipe event object * @@ -270,8 +241,8 @@ class Eventable { /** @type {PhotoSwipe | undefined} */ this.pswp = undefined; - /** @type {PreparedPhotoSwipeOptions} */ - this.options = defaultOptions; + /** @type {PhotoSwipeOptions | undefined} */ + this.options = undefined; } /** diff --git a/src/js/gestures/drag-handler.js b/src/js/gestures/drag-handler.js index 1e15f67f..b19723a5 100644 --- a/src/js/gestures/drag-handler.js +++ b/src/js/gestures/drag-handler.js @@ -33,31 +33,32 @@ class DragHandler { */ constructor(gestures) { this.gestures = gestures; + this.pswp = gestures.pswp; /** @type {Point} */ this.startPan = { x: 0, y: 0 }; } start() { - if (this.gestures.pswp.currSlide) { - this.startPan = equalizePoints(this.startPan, this.gestures.pswp.currSlide.pan); + if (this.pswp.currSlide) { + equalizePoints(this.startPan, this.pswp.currSlide.pan); } - this.gestures.pswp.animations.stopAll(); + this.pswp.animations.stopAll(); } change() { - const { p1, prevP1, dragAxis, pswp } = this.gestures; - const { currSlide } = pswp; + const { p1, prevP1, dragAxis } = this.gestures; + const { currSlide } = this.pswp; if (dragAxis === 'y' - && pswp.options.closeOnVerticalDrag + && this.pswp.options.closeOnVerticalDrag && (currSlide && currSlide.currZoomLevel <= currSlide.zoomLevels.fit) && !this.gestures.isMultitouch) { // Handle vertical drag to close const panY = currSlide.pan.y + (p1.y - prevP1.y); - if (!pswp.dispatch('verticalDrag', { panY }).defaultPrevented) { + if (!this.pswp.dispatch('verticalDrag', { panY }).defaultPrevented) { this._setPanWithFriction('y', panY, VERTICAL_DRAG_FRICTION); const bgOpacity = 1 - Math.abs(this._getVerticalDragRatio(currSlide.pan.y)); - pswp.applyBgOpacity(bgOpacity); + this.pswp.applyBgOpacity(bgOpacity); currSlide.applyCurrentZoomPan(); } } else { @@ -66,7 +67,7 @@ class DragHandler { this._panOrMoveMainScroll('y'); if (currSlide) { - currSlide.pan = roundPoint(currSlide.pan); + roundPoint(currSlide.pan); currSlide.applyCurrentZoomPan(); } } @@ -74,11 +75,11 @@ class DragHandler { } end() { - const { pswp, velocity } = this.gestures; - const { mainScroll, currSlide } = pswp; + const { velocity } = this.gestures; + const { mainScroll, currSlide } = this.pswp; let indexDiff = 0; - pswp.animations.stopAll(); + this.pswp.animations.stopAll(); // Handle main scroll if it's shifted if (mainScroll.isShifted()) { @@ -87,9 +88,9 @@ class DragHandler { // Ratio between 0 and 1: // 0 - slide is not visible at all, - // 0.5 - half of the slide is vicible + // 0.5 - half of the slide is visible // 1 - slide is fully visible - const currentSlideVisibilityRatio = (mainScrollShiftDiff / pswp.viewportSize.x); + const currentSlideVisibilityRatio = (mainScrollShiftDiff / this.pswp.viewportSize.x); // Go next slide. // @@ -133,8 +134,8 @@ class DragHandler { * @param {'x' | 'y'} axis */ _finishPanGestureForAxis(axis) { - const { pswp, velocity } = this.gestures; - const { currSlide } = pswp; + const { velocity } = this.gestures; + const { currSlide } = this.pswp; if (!currSlide) { return; @@ -142,7 +143,7 @@ class DragHandler { const { pan, bounds } = currSlide; const panPos = pan[axis]; - const restoreBgOpacity = (pswp.bgOpacity < 1 && axis === 'y'); + const restoreBgOpacity = (this.pswp.bgOpacity < 1 && axis === 'y'); // 0.995 means - scroll view loses 0.5% of its velocity per millisecond // Increasing this number will reduce travel distance @@ -159,7 +160,7 @@ class DragHandler { // or if we are below and moving downwards if ((vDragRatio < 0 && projectedVDragRatio < -MIN_RATIO_TO_CLOSE) || (vDragRatio > 0 && projectedVDragRatio > MIN_RATIO_TO_CLOSE)) { - pswp.close(); + this.pswp.close(); return; } } @@ -176,10 +177,10 @@ class DragHandler { // Overshoot if the final position is out of pan bounds const dampingRatio = (correctedPanPosition === projectedPosition) ? 1 : 0.82; - const initialBgOpacity = pswp.bgOpacity; + const initialBgOpacity = this.pswp.bgOpacity; const totalPanDist = correctedPanPosition - panPos; - pswp.animations.startSpring({ + this.pswp.animations.startSpring({ name: 'panGesture' + axis, isPan: true, start: panPos, @@ -188,14 +189,14 @@ class DragHandler { dampingRatio, onUpdate: (pos) => { // Animate opacity of background relative to Y pan position of an image - if (restoreBgOpacity && pswp.bgOpacity < 1) { + if (restoreBgOpacity && this.pswp.bgOpacity < 1) { // 0 - start of animation, 1 - end of animation const animationProgressRatio = 1 - (correctedPanPosition - pos) / totalPanDist; // We clamp opacity to keep it between 0 and 1. // As progress ratio can be larger than 1 due to overshoot, // and we do not want to bounce opacity. - pswp.applyBgOpacity(clamp( + this.pswp.applyBgOpacity(clamp( initialBgOpacity + (1 - initialBgOpacity) * animationProgressRatio, 0, 1 @@ -219,8 +220,8 @@ class DragHandler { * @returns {boolean} */ _panOrMoveMainScroll(axis) { - const { p1, pswp, dragAxis, prevP1, isMultitouch } = this.gestures; - const { currSlide, mainScroll } = pswp; + const { p1, dragAxis, prevP1, isMultitouch } = this.gestures; + const { currSlide, mainScroll } = this.pswp; const delta = (p1[axis] - prevP1[axis]); const newMainScrollX = mainScroll.x + delta; @@ -237,7 +238,7 @@ class DragHandler { const { bounds } = currSlide; const newPan = currSlide.pan[axis] + delta; - if (pswp.options.allowPanToNext + if (this.pswp.options.allowPanToNext && dragAxis === 'x' && axis === 'x' && !isMultitouch) { @@ -323,8 +324,7 @@ class DragHandler { * @returns {number} */ _getVerticalDragRatio(panY) { - return (panY - (this.gestures.pswp.currSlide?.bounds.center.y ?? 0)) - / (this.gestures.pswp.viewportSize.y / 3); + return (panY - (this.pswp.currSlide?.bounds.center.y ?? 0)) / (this.pswp.viewportSize.y / 3); } /** @@ -338,7 +338,7 @@ class DragHandler { * @param {number} [customFriction] (0.1 - 1) */ _setPanWithFriction(axis, potentialPan, customFriction) { - const { currSlide } = this.gestures.pswp; + const { currSlide } = this.pswp; if (!currSlide) { return; diff --git a/src/js/gestures/gestures.js b/src/js/gestures/gestures.js index 1226cca7..07116a36 100644 --- a/src/js/gestures/gestures.js +++ b/src/js/gestures/gestures.js @@ -80,10 +80,8 @@ class Gestures { this.isMultitouch = false; this.isDragging = false; this.isZooming = false; - /** @type {number | null} - * @private - */ - this._raf = null; + /** @type {number | null} */ + this.raf = null; /** @type {NodeJS.Timeout | null} * @private */ @@ -204,7 +202,7 @@ class Gestures { this.dragAxis = null; // we need to store initial point to determine the main axis, // drag is activated only after the axis is determined - this.startP1 = equalizePoints(this.startP1, this.p1); + equalizePoints(this.startP1, this.p1); } if (this._numActivePoints > 1) { @@ -252,7 +250,7 @@ class Gestures { this._intervalTime = Date.now(); //this._startTime = this._intervalTime; this._velocityCalculated = false; - this._intervalP1 = equalizePoints(this._intervalP1, this.p1); + equalizePoints(this._intervalP1, this.p1); this.velocity.x = 0; this.velocity.y = 0; this.drag.start(); @@ -350,7 +348,7 @@ class Gestures { } this._updatePrevPoints(); - this._raf = requestAnimationFrame(this._rafRenderLoop.bind(this)); + this.raf = requestAnimationFrame(this._rafRenderLoop.bind(this)); } } @@ -373,7 +371,7 @@ class Gestures { this.velocity.y = this._getVelocity('y', duration); this._intervalTime = time; - this._intervalP1 = equalizePoints(this._intervalP1, this.p1); + equalizePoints(this._intervalP1, this.p1); this._velocityCalculated = true; } @@ -416,7 +414,7 @@ class Gestures { this.tapHandler.doubleTap(this.startP1, e); } } else { - this._lastStartP1 = equalizePoints(this._lastStartP1, this.startP1); + equalizePoints(this._lastStartP1, this.startP1); this._tapTimer = setTimeout(() => { this.tapHandler.tap(this.startP1, e); this._clearTapTimer(); @@ -457,9 +455,9 @@ class Gestures { * @private */ _rafStopLoop() { - if (this._raf) { - cancelAnimationFrame(this._raf); - this._raf = null; + if (this.raf) { + cancelAnimationFrame(this.raf); + this.raf = null; } } @@ -505,11 +503,11 @@ class Gestures { // update points that PhotoSwipe uses // to calculate position and scale if (this._numActivePoints > 0) { - this.p1 = equalizePoints(this.p1, this._ongoingPointers[0]); + equalizePoints(this.p1, this._ongoingPointers[0]); } if (this._numActivePoints > 1) { - this.p2 = equalizePoints(this.p2, this._ongoingPointers[1]); + equalizePoints(this.p2, this._ongoingPointers[1]); } } else { const touchEvent = /** @type {TouchEvent} */ (e); @@ -543,16 +541,16 @@ class Gestures { * @private */ _updatePrevPoints() { - this.prevP1 = equalizePoints(this.prevP1, this.p1); - this.prevP2 = equalizePoints(this.prevP2, this.p2); + equalizePoints(this.prevP1, this.p1); + equalizePoints(this.prevP2, this.p2); } /** update points at the start of gesture * @private */ _updateStartPoints() { - this.startP1 = equalizePoints(this.startP1, this.p1); - this.startP2 = equalizePoints(this.startP2, this.p2); + equalizePoints(this.startP1, this.p1); + equalizePoints(this.startP2, this.p2); this._updatePrevPoints(); } diff --git a/src/js/gestures/zoom-handler.js b/src/js/gestures/zoom-handler.js index 72f6bf67..4703733c 100644 --- a/src/js/gestures/zoom-handler.js +++ b/src/js/gestures/zoom-handler.js @@ -54,7 +54,7 @@ class ZoomHandler { const { currSlide } = this.gestures.pswp; if (currSlide) { this._startZoomLevel = currSlide.currZoomLevel; - this._startPan = equalizePoints(this._startPan, currSlide.pan); + equalizePoints(this._startPan, currSlide.pan); } this.gestures.pswp.animations.stopAllPan(); @@ -186,7 +186,7 @@ class ZoomHandler { this._startZoomPoint.x = 0; this._startZoomPoint.y = 0; this._startZoomLevel = prevZoomLevel; - this._startPan = equalizePoints(this._startPan, initialPan); + equalizePoints(this._startPan, initialPan); } if (currZoomLevelNeedsChange) { @@ -211,7 +211,7 @@ class ZoomHandler { if (!panNeedsChange && !currZoomLevelNeedsChange && !restoreBgOpacity) { // update resolution after gesture - currSlide.setResolution(destinationZoomLevel); + currSlide._setResolution(destinationZoomLevel); currSlide.applyCurrentZoomPan(); // nothing to animate @@ -257,7 +257,7 @@ class ZoomHandler { }, onComplete: () => { // update resolution after transition ends - currSlide.setResolution(destinationZoomLevel); + currSlide._setResolution(destinationZoomLevel); currSlide.applyCurrentZoomPan(); } }); diff --git a/src/js/lightbox/lightbox.js b/src/js/lightbox/lightbox.js index a8cd0508..e8434294 100644 --- a/src/js/lightbox/lightbox.js +++ b/src/js/lightbox/lightbox.js @@ -46,7 +46,8 @@ class PhotoSwipeLightbox extends PhotoSwipeBase { */ constructor(options) { super(); - this.options = this._prepareOptions(options || {}); + /** @type {PhotoSwipeOptions} */ + this.options = options || {}; this._uid = 0; this.shouldOpen = false; /** diff --git a/src/js/opener.js b/src/js/opener.js index 24636642..76458524 100644 --- a/src/js/opener.js +++ b/src/js/opener.js @@ -66,9 +66,9 @@ class Opener { /** * @private - * @type {Bounds | null} + * @type {Bounds | undefined} */ - this._thumbBounds = null; + this._thumbBounds = undefined; this._prepareOpen = this._prepareOpen.bind(this); @@ -130,11 +130,11 @@ class Opener { if (options.showHideAnimationType === 'fade') { options.showHideOpacity = true; - this._thumbBounds = null; + this._thumbBounds = undefined; } else if (options.showHideAnimationType === 'none') { options.showHideOpacity = false; this._duration = 0; - this._thumbBounds = null; + this._thumbBounds = undefined; } else if (this.isOpening && pswp._initialThumbBounds) { // Use initial bounds if defined this._thumbBounds = pswp._initialThumbBounds; @@ -416,7 +416,7 @@ class Opener { } if (currSlide) { - currSlide.pan = equalizePoints(currSlide.pan, innerRect || this._thumbBounds); + equalizePoints(currSlide.pan, innerRect || this._thumbBounds); currSlide.currZoomLevel = this._thumbBounds.w / currSlide.width; if (animate) { this._animateTo(currSlide.container, 'transform', currSlide.getCurrentTransform()); diff --git a/src/js/photoswipe.js b/src/js/photoswipe.js index 24a68d06..51d75221 100644 --- a/src/js/photoswipe.js +++ b/src/js/photoswipe.js @@ -220,6 +220,35 @@ import ContentLoader from './slide/loader.js'; * @prop {string | false} [thumbSelector] */ +/** @type {PreparedPhotoSwipeOptions} */ +const defaultOptions = { + allowPanToNext: true, + spacing: 0.1, + loop: true, + pinchToClose: true, + closeOnVerticalDrag: true, + hideAnimationDuration: 333, + showAnimationDuration: 333, + zoomAnimationDuration: 333, + escKey: true, + arrowKeys: true, + returnFocus: true, + maxWidthToAnimate: 4000, + clickToCloseNonZoomable: true, + imageClickAction: 'zoom-or-close', + bgClickAction: 'close', + tapAction: 'toggle-controls', + doubleTapAction: 'zoom', + indexIndicatorSep: ' / ', + preloaderDelay: 2000, + bgOpacity: 0.8, + + index: 0, + errorMsg: 'The image cannot be loaded', + preload: [1, 2], + easing: 'cubic-bezier(.4,0,.22,1)' +}; + /** * PhotoSwipe Core */ @@ -267,8 +296,8 @@ class PhotoSwipe extends PhotoSwipeBase { * @type {SlideData} */ this._initialItemData = {}; - /** @type {Bounds | null} */ - this._initialThumbBounds = null; + /** @type {Bounds | undefined} */ + this._initialThumbBounds = undefined; /** @type {HTMLDivElement | undefined} */ this.topBar = undefined; @@ -290,14 +319,12 @@ class PhotoSwipe extends PhotoSwipeBase { this.opener = new Opener(this); this.keyboard = new Keyboard(this); this.contentLoader = new ContentLoader(this); - // initialize scroll wheel handler to block the scroll - this.scrollWheel = new ScrollWheel(this); - this.ui = new UI(this); } + /** @returns {boolean} */ init() { if (this.isOpen || this.isDestroying) { - return; + return false; } this.isOpen = true; @@ -322,6 +349,9 @@ class PhotoSwipe extends PhotoSwipeBase { this.potentialIndex = this.currIndex; this.dispatch('firstUpdate'); // starting index can be modified here + // initialize scroll wheel handler to block the scroll + this.scrollWheel = new ScrollWheel(this); + // sanitize index if (Number.isNaN(this.currIndex) || this.currIndex < 0 @@ -381,6 +411,8 @@ class PhotoSwipe extends PhotoSwipeBase { this.opener.open(); this.dispatch('afterInit'); + + return true; } /** @@ -606,11 +638,11 @@ class PhotoSwipe extends PhotoSwipeBase { //this._prevViewportSize.x = newWidth; //this._prevViewportSize.y = newHeight; - this._prevViewportSize = equalizePoints(this._prevViewportSize, newViewportSize); + equalizePoints(this._prevViewportSize, newViewportSize); this.dispatch('beforeResize'); - this.viewportSize = equalizePoints(this.viewportSize, this._prevViewportSize); + equalizePoints(this.viewportSize, this._prevViewportSize); this._updatePageScrollOffset(); @@ -716,6 +748,7 @@ class PhotoSwipe extends PhotoSwipeBase { this.mainScroll.appendHolders(); + this.ui = new UI(this); this.ui.init(); // append to DOM @@ -729,7 +762,7 @@ class PhotoSwipe extends PhotoSwipeBase { * * Height is optional (calculated based on the large image) * - * @returns {Bounds | null} + * @returns {Bounds | undefined} */ getThumbBounds() { return getThumbBounds( @@ -746,6 +779,24 @@ class PhotoSwipe extends PhotoSwipeBase { canLoop() { return (this.options.loop && this.getNumItems() > 2); } + + /** + * @private + * @param {PhotoSwipeOptions} options + * @returns {PreparedPhotoSwipeOptions} + */ + _prepareOptions(options) { + if (window.matchMedia('(prefers-reduced-motion), (update: slow)').matches) { + options.showHideAnimationType = 'none'; + options.zoomAnimationDuration = 0; + } + + /** @type {PreparedPhotoSwipeOptions} */ + return { + ...defaultOptions, + ...options + }; + } } export default PhotoSwipe; diff --git a/src/js/slide/content.js b/src/js/slide/content.js index ce37a3de..67635010 100644 --- a/src/js/slide/content.js +++ b/src/js/slide/content.js @@ -374,7 +374,7 @@ class Content { displayError() { if (this.slide) { let errorMsgEl = createElement('pswp__error-msg', 'div'); - errorMsgEl.innerText = this.instance.options.errorMsg; + errorMsgEl.innerText = this.instance.options?.errorMsg ?? ''; errorMsgEl = /** @type {HTMLDivElement} */ (this.instance.applyFilters( 'contentErrorElement', errorMsgEl, diff --git a/src/js/slide/get-thumb-bounds.js b/src/js/slide/get-thumb-bounds.js index 1f44b8c9..d9024868 100644 --- a/src/js/slide/get-thumb-bounds.js +++ b/src/js/slide/get-thumb-bounds.js @@ -66,7 +66,7 @@ function getCroppedBoundsByElement(el, imageWidth, imageHeight) { * @param {number} index * @param {SlideData} itemData * @param {PhotoSwipe} instance PhotoSwipe instance - * @returns {Bounds | null} + * @returns {Bounds | undefined} */ export function getThumbBounds(index, itemData, instance) { // legacy event, before filters were introduced @@ -84,18 +84,18 @@ export function getThumbBounds(index, itemData, instance) { const { element } = itemData; /** @type {Bounds | undefined} */ let thumbBounds; - /** @type {HTMLElement | null} */ - let thumbnail = null; + /** @type {HTMLElement | null | undefined} */ + let thumbnail; if (element && instance.options.thumbSelector !== false) { const thumbSelector = instance.options.thumbSelector || 'img'; thumbnail = element.matches(thumbSelector) - ? element : element.querySelector(thumbSelector); + ? element : /** @type {HTMLElement | null} */ (element.querySelector(thumbSelector)); } - if (thumbnail) { - thumbnail = instance.applyFilters('thumbEl', thumbnail, itemData, index); + thumbnail = instance.applyFilters('thumbEl', thumbnail, itemData, index); + if (thumbnail) { if (!itemData.thumbCropped) { thumbBounds = getBoundsByElement(thumbnail); } else { @@ -107,7 +107,5 @@ export function getThumbBounds(index, itemData, instance) { } } - return thumbBounds - ? instance.applyFilters('thumbBounds', thumbBounds, itemData, index) - : null; + return instance.applyFilters('thumbBounds', thumbBounds, itemData, index); } diff --git a/src/js/slide/loader.js b/src/js/slide/loader.js index fd3c6655..c09c5e88 100644 --- a/src/js/slide/loader.js +++ b/src/js/slide/loader.js @@ -21,23 +21,30 @@ const MIN_SLIDES_TO_CACHE = 5; */ export 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 - const zoomLevel = new ZoomLevel(options, itemData, -1); - if (instance.pswp) { - const viewportSize = instance.pswp.viewportSize || getViewportSize(options, instance.pswp); - const panAreaSize = getPanAreaSize(options, viewportSize, itemData, index); - zoomLevel.update(content.width, content.height, panAreaSize); + if (options) { + zoomLevel = new ZoomLevel(options, itemData, -1); + if (instance.pswp) { + const viewportSize = instance.pswp.viewportSize || getViewportSize(options, instance.pswp); + const panAreaSize = getPanAreaSize(options, viewportSize, itemData, index); + zoomLevel.update(content.width, content.height, panAreaSize); + } } content.lazyLoad(); - content.setDisplayedSize( - Math.ceil(content.width * zoomLevel.initial), - Math.ceil(content.height * zoomLevel.initial) - ); + + if (zoomLevel) { + content.setDisplayedSize( + Math.ceil(content.width * zoomLevel.initial), + Math.ceil(content.height * zoomLevel.initial) + ); + } return content; } diff --git a/src/js/slide/pan-bounds.js b/src/js/slide/pan-bounds.js index bef3aeba..93243add 100644 --- a/src/js/slide/pan-bounds.js +++ b/src/js/slide/pan-bounds.js @@ -5,9 +5,6 @@ import { parsePaddingOption } from '../util/viewport-size.js'; /** @typedef {Record} Point */ /** @typedef {'x' | 'y'} Axis */ -/** @type {Point} */ -const defaultPoint = { x: 0, y: 0 }; - /** * Calculates minimum, maximum and initial (center) bounds of a slide */ @@ -18,9 +15,9 @@ class PanBounds { constructor(slide) { this.slide = slide; this.currZoomLevel = 1; - this.center = /** @type {Point} */ ({ ...defaultPoint }); - this.max = /** @type {Point} */ ({ ...defaultPoint }); - this.min = /** @type {Point} */ ({ ...defaultPoint }); + this.center = /** @type {Point} */ { x: 0, y: 0 }; + this.max = /** @type {Point} */ { x: 0, y: 0 }; + this.min = /** @type {Point} */ { x: 0, y: 0 }; } /** @@ -76,9 +73,12 @@ class PanBounds { // _getZeroBounds reset() { - this.center = { ...defaultPoint }; - this.max = { ...defaultPoint }; - this.min = { ...defaultPoint }; + this.center.x = 0; + this.center.y = 0; + this.max.x = 0; + this.max.y = 0; + this.min.x = 0; + this.min.y = 0; } /** diff --git a/src/js/slide/slide.js b/src/js/slide/slide.js index 7c912617..42ebe936 100644 --- a/src/js/slide/slide.js +++ b/src/js/slide/slide.js @@ -116,7 +116,7 @@ class Slide { this.updateContentSize(); this.appendHeavy(); - holderElement.appendChild(this.container); + this.holderElement.appendChild(this.container); this.zoomAndPanToInitial(); @@ -316,10 +316,10 @@ class Slide { this.setZoomLevel(destZoomLevel); this.pan.x = this.calculateZoomToPanOffset('x', centerPoint, prevZoomLevel); this.pan.y = this.calculateZoomToPanOffset('y', centerPoint, prevZoomLevel); - this.pan = roundPoint(this.pan); + roundPoint(this.pan); const finishTransition = () => { - this.setResolution(destZoomLevel); + this._setResolution(destZoomLevel); this.applyCurrentZoomPan(); }; @@ -439,7 +439,7 @@ class Slide { // pan according to the zoom level this.bounds.update(this.currZoomLevel); - this.pan = equalizePoints(this.pan, this.bounds.center); + equalizePoints(this.pan, this.bounds.center); this.pswp.dispatch('initialZoomPan', { slide: this }); } @@ -459,7 +459,7 @@ class Slide { calculateSize() { const { pswp } = this; - this.panAreaSize = equalizePoints( + equalizePoints( this.panAreaSize, getPanAreaSize(pswp.options, pswp.viewportSize, this.data, this.index) ); @@ -492,7 +492,7 @@ class Slide { * * @param {number} newResolution */ - setResolution(newResolution) { + _setResolution(newResolution) { if (newResolution === this.currentResolution) { return; } diff --git a/src/js/ui/loading-indicator.js b/src/js/ui/loading-indicator.js index 0be7f903..2f2c4d74 100644 --- a/src/js/ui/loading-indicator.js +++ b/src/js/ui/loading-indicator.js @@ -59,5 +59,10 @@ export const loadingIndicator = { updatePreloaderVisibility(); } }); + + // expose the method + if (pswp.ui) { + pswp.ui.updatePreloaderVisibility = updatePreloaderVisibility; + } } }; diff --git a/src/js/ui/ui.js b/src/js/ui/ui.js index 8798b1f1..eec2cbc0 100644 --- a/src/js/ui/ui.js +++ b/src/js/ui/ui.js @@ -32,6 +32,8 @@ class UI { this.uiElementsData = []; /** @type {(UIElement | UIElementData)[]} */ this.items = []; + /** @type {() => void} */ + this.updatePreloaderVisibility = () => {}; /** * @private diff --git a/src/js/util/animations.js b/src/js/util/animations.js index a070f4cb..b5a9aa2b 100644 --- a/src/js/util/animations.js +++ b/src/js/util/animations.js @@ -42,6 +42,7 @@ class Animations { * @private * @param {AnimationProps} props * @param {boolean} [isSpring] + * @returns {Animation} */ _start(props, isSpring) { const animation = isSpring @@ -50,6 +51,8 @@ class Animations { this.activeAnimations.push(animation); animation.onFinish = () => this.stop(animation); + + return animation; } /** diff --git a/src/js/util/util.js b/src/js/util/util.js index 8500f4ff..8b3a3932 100644 --- a/src/js/util/util.js +++ b/src/js/util/util.js @@ -24,15 +24,20 @@ export function createElement(className, tagName, appendToEl) { * @returns {Point} */ export function equalizePoints(p1, p2) { - return { ...p1, ...p2 }; + p1.x = p2.x; + p1.y = p2.y; + if (p2.id !== undefined) { + p1.id = p2.id; + } + return p1; } /** * @param {Point} p - * @returns {Point} */ export function roundPoint(p) { - return { x: Math.round(p.x), y: Math.round(p.y) }; + p.x = Math.round(p.x); + p.y = Math.round(p.y); } /**