diff --git a/dist/types/core/base.d.ts b/dist/types/core/base.d.ts index 26a52160a..bbd9a90d1 100644 --- a/dist/types/core/base.d.ts +++ b/dist/types/core/base.d.ts @@ -1,9 +1,7 @@ export default PhotoSwipeBase; export type PhotoSwipe = import("../photoswipe.js").default; -export type PhotoSwipeOptions = import("../photoswipe.js").PhotoSwipeOptions; export type SlideData = import("../slide/slide.js").SlideData; /** @typedef {import("../photoswipe.js").default} PhotoSwipe */ -/** @typedef {import("../photoswipe.js").PhotoSwipeOptions} PhotoSwipeOptions */ /** @typedef {import("../slide/slide.js").SlideData} SlideData */ /** * PhotoSwipe base class that can retrieve data about every slide. @@ -19,6 +17,7 @@ declare class PhotoSwipeBase extends Eventable { /** * @param {SlideData} slideData * @param {number} index + * @returns {Content} */ createContentFromData(slideData: SlideData, index: number): Content; /** @@ -29,27 +28,30 @@ 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 * * @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/core/eventable.d.ts b/dist/types/core/eventable.d.ts index 979e26df4..f7931fa0b 100644 --- a/dist/types/core/eventable.d.ts +++ b/dist/types/core/eventable.d.ts @@ -198,7 +198,7 @@ export type PhotoSwipeEventsMap = { slide: Slide; }; gettingData: { - slide: Slide; + slide: Slide | undefined; data: SlideData; index: number; }; @@ -235,8 +235,8 @@ export type PhotoSwipeEventsMap = { }; beforeZoomTo: { destZoomLevel: number; - centerPoint: Point; - transitionDuration: number | false; + centerPoint: Point | undefined; + transitionDuration: number | false | undefined; }; zoomPanUpdate: { slide: Slide; @@ -284,7 +284,7 @@ export type PhotoSwipeEventsMap = { initialZoomInEnd: undefined; initialZoomOutEnd: undefined; numItems: { - dataSource: DataSource; + dataSource: DataSource | undefined; numItems: number; }; itemData: { @@ -305,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 @@ -360,25 +360,19 @@ 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 = { 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. @@ -389,49 +383,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 * @@ -440,33 +434,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 */ @@ -474,43 +468,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 * @@ -519,147 +513,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; + dataSource: import("../photoswipe.js").DataSource | undefined; 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[] }} @@ -669,73 +663,73 @@ 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=} */ + /** @type {PhotoSwipe | undefined} */ pswp: PhotoSwipe | undefined; - /** @type {PhotoSwipeOptions} */ - options: PhotoSwipeOptions; + /** @type {PhotoSwipeOptions | undefined} */ + options: Partial | undefined; /** * @template {keyof PhotoSwipeFiltersMap} T * @param {T} name @@ -774,7 +768,7 @@ 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 */ @@ -862,7 +856,7 @@ declare class Eventable { * @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 @@ -875,7 +869,7 @@ declare class Eventable { * @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 @@ -895,14 +889,14 @@ declare class Eventable { * @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 */ /** * @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 * @@ -947,11 +941,11 @@ declare class Eventable { * 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 * @@ -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 @@ -980,8 +974,8 @@ 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; - preventDefault(): void; defaultPrevented: boolean; + preventDefault(): void; } diff --git a/dist/types/gestures/drag-handler.d.ts b/dist/types/gestures/drag-handler.d.ts index cd9f62668..81f602178 100644 --- a/dist/types/gestures/drag-handler.d.ts +++ b/dist/types/gestures/drag-handler.d.ts @@ -29,6 +29,7 @@ declare class DragHandler { * * @private * @param {'x' | 'y'} axis + * @returns {boolean} */ private _panOrMoveMainScroll; /** @@ -40,6 +41,7 @@ declare class DragHandler { * * @private * @param {number} panY The current pan Y position. + * @returns {number} */ private _getVerticalDragRatio; /** @@ -50,7 +52,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/dist/types/gestures/gestures.d.ts b/dist/types/gestures/gestures.d.ts index 8b2b49604..fd1668891 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,57 @@ 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} */ + raf: number | null; + /** @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 +93,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 +115,7 @@ declare class Gestures { * @private * @param {'x' | 'y'} axis * @param {number} duration + * @returns {number} */ private _getVelocity; /** @@ -120,9 +136,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 +153,7 @@ declare class Gestures { * @private * @param {Touch | PointerEvent} e * @param {Point} p + * @returns {Point} */ private _convertEventPosToPoint; /** diff --git a/dist/types/gestures/tap-handler.d.ts b/dist/types/gestures/tap-handler.d.ts index 50f15721e..74f2cdccc 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. */ @@ -34,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/dist/types/gestures/zoom-handler.d.ts b/dist/types/gestures/zoom-handler.d.ts index b25bfb615..da529be64 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,7 +41,7 @@ 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; diff --git a/dist/types/keyboard.d.ts b/dist/types/keyboard.d.ts index a4b2af225..f290ceb0f 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/dist/types/lightbox/lightbox.d.ts b/dist/types/lightbox/lightbox.d.ts index cb156983e..275c4df09 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 */ @@ -44,45 +46,50 @@ export type EventCallback = import('../core/eventable.js').EventCallback; */ declare class PhotoSwipeLightbox extends PhotoSwipeBase { /** - * @param {PhotoSwipeOptions} options + * @param {PhotoSwipeOptions} [options] */ - constructor(options: PhotoSwipeOptions); + constructor(options?: Partial | undefined); + /** @type {PhotoSwipeOptions} */ + options: Partial; _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; /** * Load and open PhotoSwipe * * @param {number} index - * @param {DataSource=} dataSource - * @param {{ x?: number; y?: number }} [initialPoint] + * @param {DataSource} dataSource + * @param {Point | null} [initialPoint] + * @returns {boolean} */ - loadAndOpen(index: number, dataSource?: DataSource | undefined, initialPoint?: { - x?: number; - y?: number; - }): boolean; - shouldOpen: boolean; + loadAndOpen(index: number, dataSource: DataSource, initialPoint?: import("../photoswipe.js").Point | null | undefined): boolean; /** * 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; + preload(index: number, dataSource?: import("../photoswipe.js").DataSource | undefined): void; /** * @private * @param {Type | { default: Type }} module diff --git a/dist/types/main-scroll.d.ts b/dist/types/main-scroll.d.ts index aed43cd05..efc396ed8 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; /** * 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; /** * 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; } diff --git a/dist/types/opener.d.ts b/dist/types/opener.d.ts index 536c314f2..42a11e26b 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; - _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; + 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 | null | undefined } + */ + private _cropContainer2; + /** + * @private + * @type {Bounds | undefined} + */ + private _thumbBounds; + /** @private */ + private _prepareOpen; + open(): void; + close(): void; + /** @private */ + private _applyStartProps; + _animateZoom: boolean | undefined; + /** @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/dist/types/photoswipe.d.ts b/dist/types/photoswipe.d.ts index 1e5bd5324..558dadb12 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; /** * */ @@ -18,14 +19,10 @@ 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 = { - x?: number; - y?: number; -}; export type Padding = { top: number; bottom: number; @@ -48,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). @@ -62,20 +60,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. */ @@ -83,11 +81,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). */ @@ -95,81 +93,78 @@ 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) | 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) => { - x: number; - y: number; - }; + 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. @@ -183,7 +178,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 */ @@ -206,12 +201,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; @@ -223,80 +218,91 @@ 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; }; /** * PhotoSwipe Core */ declare class PhotoSwipe extends PhotoSwipeBase { /** - * @param {PhotoSwipeOptions} options + * @param {PhotoSwipeOptions} [options] */ - constructor(options: PhotoSwipeOptions); + constructor(options?: Partial | undefined); + options: PreparedPhotoSwipeOptions; /** * offset of viewport relative to document * - * @type {{ x?: number; y?: number }} + * @type {Point} */ - offset: { - x?: number; - y?: number; - }; + offset: Point; /** - * @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 - * - * @type {number} */ bgOpacity: number; - /** @type {HTMLDivElement} */ - topBar: HTMLDivElement; + currIndex: number; + potentialIndex: number; + isOpen: boolean; + isDestroying: boolean; + hasMouse: boolean; + /** + * @private + * @type {SlideData} + */ + private _initialItemData; + /** @type {Bounds | undefined} */ + _initialThumbBounds: Bounds | undefined; + /** @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; mainScroll: MainScroll; gestures: Gestures; opener: Opener; keyboard: Keyboard; contentLoader: ContentLoader; + /** @returns {boolean} */ init(): boolean; - isOpen: boolean; - currIndex: number; - potentialIndex: number; - scrollWheel: ScrollWheel; - _initialItemData: import("./slide/slide.js").SlideData; - _initialThumbBounds: import("./slide/get-thumb-bounds.js").Bounds; + scrollWheel: ScrollWheel | undefined; /** * Get looped slide index * (for example, -1 will return the last slide) * * @param {number} index + * @returns {number} */ getLoopedIndex(index: number): number; appendHeavy(): void; @@ -318,10 +324,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 | undefined, transitionDuration?: number | false | undefined, ignoreBounds?: boolean | undefined): void; /** * @see slide/slide.js toggleZoom */ @@ -331,7 +334,6 @@ declare class PhotoSwipe extends PhotoSwipeBase { * After closing transition ends - destroy it */ close(): void; - isDestroying: boolean; /** * Destroys the gallery: * - instantly closes the gallery @@ -340,32 +342,27 @@ 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 * * @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; /** @@ -376,7 +373,6 @@ declare class PhotoSwipe extends PhotoSwipeBase { * Whether mouse is detected */ mouseDetected(): void; - hasMouse: boolean; /** * Page resize event handler * @@ -403,31 +399,31 @@ declare class PhotoSwipe extends PhotoSwipeBase { * @private */ private _createMainStructure; - element: HTMLDivElement; - template: HTMLDivElement; - bg: HTMLDivElement; - scrollWrap: HTMLElement; - container: HTMLDivElement; - ui: UI; + 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 | undefined} */ - getThumbBounds(): import("./slide/get-thumb-bounds.js").Bounds; + getThumbBounds(): Bounds | undefined; /** - * If the PhotoSwipe can have continious loop + * If the PhotoSwipe can have continuous loop * @returns Boolean */ canLoop(): boolean; /** - * @param {PhotoSwipeOptions} options * @private + * @param {PhotoSwipeOptions} options + * @returns {PreparedPhotoSwipeOptions} */ 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"; @@ -436,5 +432,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 f7a2d413d..196722e73 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 | undefined): 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/dist/types/slide/loader.d.ts b/dist/types/slide/loader.d.ts index 426374dd7..25e55880d 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; /** - * @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/dist/types/slide/pan-bounds.d.ts b/dist/types/slide/pan-bounds.d.ts index 83d502dcc..36c03a680 100644 --- a/dist/types/slide/pan-bounds.d.ts +++ b/dist/types/slide/pan-bounds.d.ts @@ -1,12 +1,9 @@ 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 {Record} Point */ /** @typedef {'x' | 'y'} Axis */ /** * Calculates minimum, maximum and initial (center) bounds of a slide @@ -18,12 +15,18 @@ 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; + center: { + x: number; + y: number; + }; + max: { + x: number; + y: number; + }; + min: { + x: number; + y: number; + }; /** * _getItemBounds * @@ -42,6 +45,7 @@ declare class PanBounds { * * @param {Axis} axis x or y * @param {number} panOffset + * @returns {number} */ correctPan(axis: Axis, panOffset: number): number; } diff --git a/dist/types/slide/placeholder.d.ts b/dist/types/slide/placeholder.d.ts index d0d13230b..f9645dba4 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/dist/types/slide/slide.d.ts b/dist/types/slide/slide.d.ts index 2c2a0bf55..6147c5868 100644 --- a/dist/types/slide/slide.d.ts +++ b/dist/types/slide/slide.d.ts @@ -50,7 +50,7 @@ export type _SlideData = { /** * slide type */ - type?: 'image' | 'html' | string; + type?: string | undefined; }; /** * Renders and allows to control a single slide @@ -69,19 +69,20 @@ declare class Slide { currentResolution: number; /** @type {Point} */ panAreaSize: Point; + /** @type {Point} */ + pan: Point; isFirstSlide: boolean; zoomLevels: ZoomLevel; - pan: { - x: number; - y: number; - }; 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,7 +129,7 @@ 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; /** @@ -138,28 +137,22 @@ declare class Slide { * @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?: import("../photoswipe.js").Point | undefined, transitionDuration?: number | false | undefined, ignoreBounds?: boolean | undefined): void; /** - * @param {{ x?: number, y?: number }} [centerPoint] + * @param {Point} [centerPoint] */ - toggleZoom(centerPoint?: { - x?: number; - y?: number; - }): 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) @@ -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?: import("../photoswipe.js").Point | undefined, prevZoomLevel?: number | undefined): 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,7 +219,7 @@ 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 diff --git a/dist/types/slide/zoom-level.d.ts b/dist/types/slide/zoom-level.d.ts index 1782622e8..85b6099ca 100644 --- a/dist/types/slide/zoom-level.d.ts +++ b/dist/types/slide/zoom-level.d.ts @@ -1,10 +1,12 @@ 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); /** @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 */ /** @@ -16,13 +18,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); - pswp: import("../photoswipe.js").default; - options: import("../photoswipe.js").PhotoSwipeOptions; + constructor(options: PhotoSwipeOptions, itemData: SlideData, index: number, pswp?: import("../photoswipe.js").default | undefined); + pswp: import("../photoswipe.js").default | undefined; + options: Partial; itemData: import("../slide/slide.js").SlideData; index: number; + /** @type { Point | null } */ + panAreaSize: Point | null; + /** @type { Point | null } */ + elementSize: Point | 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 +43,15 @@ declare class ZoomLevel { * * @param {number} maxWidth * @param {number} maxHeight - * @param {{ x?: number; y?: number }} panAreaSize + * @param {Point} 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: Point): 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/dist/types/ui/ui-element.d.ts b/dist/types/ui/ui-element.d.ts index c9ea01cf6..11437e533 100644 --- a/dist/types/ui/ui-element.d.ts +++ b/dist/types/ui/ui-element.d.ts @@ -8,19 +8,19 @@ export type UIElementMarkupProps = { isCustomSVG?: boolean | undefined; inner: string; outlineID?: string | undefined; - size?: number | string; + size?: string | number | undefined; }; export type UIElementData = { - name?: DefaultUIElements | string; + name?: string | undefined; className?: string | undefined; html?: UIElementMarkup | undefined; isButton?: boolean | undefined; - tagName?: keyof HTMLElementTagNameMap; + tagName?: keyof HTMLElementTagNameMap | undefined; title?: string | undefined; ariaLabel?: string | undefined; - onInit?: (element: HTMLElement, pswp: PhotoSwipe) => void; - onClick?: import("../types.js").Methods | ((e: MouseEvent, element: HTMLElement, pswp: PhotoSwipe) => void); - appendTo?: 'bar' | 'wrapper' | 'root'; + 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'; diff --git a/dist/types/ui/ui.d.ts b/dist/types/ui/ui.d.ts index d436a4c61..76d2bc008 100644 --- a/dist/types/ui/ui.d.ts +++ b/dist/types/ui/ui.d.ts @@ -7,16 +7,19 @@ 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)[]; + /** @type {() => void} */ + updatePreloaderVisibility: () => void; + /** + * @private + * @type {number | undefined} + */ + private _lastUpdatedZoomLevel; + init(): void; /** * @param {UIElementData} elementData */ @@ -24,7 +27,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/dist/types/util/animations.d.ts b/dist/types/util/animations.d.ts index eff15e989..e95eebe0c 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; +export type CssAnimationProps = import('./css-animation.js').CssAnimationProps; +export type SpringAnimationProps = import('./spring-animation.js').SpringAnimationProps; +export type SharedAnimationProps = { 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; + onComplete?: VoidFunction | undefined; + onFinish?: VoidFunction | undefined; }; -/** @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,20 @@ 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] + * @returns {Animation} */ - _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 5f2b8ff17..fd873c3fe 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 | undefined; + easing?: string | undefined; + transform?: string | undefined; + opacity?: string | undefined; +}; +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/dom-events.d.ts b/dist/types/util/dom-events.d.ts index 0a74fd58c..419eb2e3b 100644 --- a/dist/types/util/dom-events.d.ts +++ b/dist/types/util/dom-events.d.ts @@ -1,16 +1,16 @@ export default DOMEvents; export type PoolItem = { - target: HTMLElement | Window | Document; + target: HTMLElement | Window | Document | undefined | null; type: string; - listener: (e: any) => void; - passive: boolean; + listener: EventListenerOrEventListenerObject; + passive?: boolean | undefined; }; /** * @typedef {Object} PoolItem - * @prop {HTMLElement | Window | Document} target + * @prop {HTMLElement | Window | Document | undefined | null} target * @prop {string} type - * @prop {(e: any) => void} listener - * @prop {boolean} passive + * @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/dist/types/util/spring-animation.d.ts b/dist/types/util/spring-animation.d.ts index 9d382eced..da1e29fef 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 | undefined; + naturalFrequency?: number | undefined; + 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; _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 4d59365a3..779ff7bfd 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 | 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 157402132..85f711ce0 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 | undefined): 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,14 +39,16 @@ 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; /** @@ -54,17 +56,17 @@ export function toTransformString(x: number, y?: number | undefined, scale?: num * * @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; /** * 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; /** @@ -89,23 +91,25 @@ export function decodeImage(img: HTMLImageElement): Promise 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 * @@ -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,10 +238,10 @@ class Eventable { */ this._filters = {}; - /** @type {PhotoSwipe=} */ + /** @type {PhotoSwipe | undefined} */ this.pswp = undefined; - /** @type {PhotoSwipeOptions} */ + /** @type {PhotoSwipeOptions | undefined} */ this.options = undefined; } @@ -255,12 +256,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); } /** @@ -286,12 +285,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]; } @@ -304,14 +301,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); } /** @@ -325,9 +320,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); } /** @@ -343,15 +336,9 @@ 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); - }); - } + 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 f99e3f638..b19723a54 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); @@ -34,28 +35,30 @@ class DragHandler { this.gestures = gestures; this.pswp = gestures.pswp; /** @type {Point} */ - this.startPan = {}; + this.startPan = { x: 0, y: 0 }; } start() { - equalizePoints(this.startPan, this.pswp.currSlide.pan); + if (this.pswp.currSlide) { + equalizePoints(this.startPan, this.pswp.currSlide.pan); + } 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 - && currSlide.currZoomLevel <= currSlide.zoomLevels.fit + && 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 { @@ -63,18 +66,20 @@ class DragHandler { if (!mainScrollChanged) { this._panOrMoveMainScroll('y'); - roundPoint(currSlide.pan); - currSlide.applyCurrentZoomPan(); + if (currSlide) { + roundPoint(currSlide.pan); + currSlide.applyCurrentZoomPan(); + } } } } end() { - const { pswp, velocity } = this.gestures; - const { mainScroll } = 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()) { @@ -83,13 +88,13 @@ 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. // - // - 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 @@ -111,7 +116,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 { @@ -129,15 +134,19 @@ class DragHandler { * @param {'x' | 'y'} axis */ _finishPanGestureForAxis(axis) { - const { pswp } = this; - const { currSlide } = pswp; const { velocity } = this.gestures; + const { currSlide } = this.pswp; + + if (!currSlide) { + return; + } + 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 - // 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 @@ -151,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; } } @@ -168,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, @@ -180,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 @@ -208,15 +217,16 @@ class DragHandler { * * @private * @param {'x' | 'y'} axis + * @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; - if (!delta) { - return; + if (!delta || !currSlide) { + return false; } // Always move main scroll if image can not be panned @@ -228,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) { @@ -295,8 +305,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 +321,10 @@ 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.pswp.currSlide?.bounds.center.y ?? 0)) / (this.pswp.viewportSize.y / 3); } /** @@ -323,10 +335,16 @@ 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 { currSlide } = this.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 935a2a293..07116a362 100644 --- a/src/js/gestures/gestures.js +++ b/src/js/gestures/gestures.js @@ -31,38 +31,61 @@ 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 /** @type {Point} */ - this.p1 = {}; // the first pressed pointer + this.p1 = { x: 0, y: 0 }; // the first pressed pointer /** @type {Point} */ - this.p2 = {}; // the second pressed pointer + this.p2 = { x: 0, y: 0 }; // the second pressed pointer /** @type {Point} */ - this.prevP1 = {}; + this.prevP1 = { x: 0, y: 0 }; /** @type {Point} */ - this.prevP2 = {}; + this.prevP2 = { x: 0, y: 0 }; /** @type {Point} */ - this.startP1 = {}; + this.startP1 = { x: 0, y: 0 }; /** @type {Point} */ - this.startP2 = {}; + this.startP2 = { x: 0, y: 0 }; /** @type {Point} */ - this.velocity = {}; - - /** @type {Point} */ - this._lastStartP1 = {}; - /** @type {Point} */ - this._intervalP1 = {}; + this.velocity = { x: 0, y: 0 }; + + /** @type {Point} + * @private + */ + this._lastStartP1 = { x: 0, y: 0 }; + /** @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} */ + this.raf = null; + /** @type {NodeJS.Timeout | null} + * @private + */ + this._tapTimer = null; if (!this.supportsTouch) { // disable pan to next slide for non-touch devices @@ -74,7 +97,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'); @@ -89,8 +116,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'); } @@ -98,7 +127,7 @@ class Gestures { } /** - * + * @private * @param {'mouse' | 'touch' | 'pointer'} pref * @param {'down' | 'start'} down * @param {'up' | 'end'} up @@ -110,11 +139,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)) + ); } } @@ -128,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 @@ -164,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, @@ -274,7 +306,6 @@ class Gestures { } if (this._numActivePoints === 0) { - this.pointerDown = false; this._rafStopLoop(); if (this.isDragging) { @@ -324,7 +355,8 @@ class Gestures { /** * Update velocity at 50ms interval * - * @param {boolean=} force + * @private + * @param {boolean} [force] */ _updateVelocity(force) { const time = Date.now(); @@ -406,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. @@ -436,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; } /** @@ -451,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) { @@ -460,7 +492,7 @@ class Gestures { this._ongoingPointers.splice(pointerIndex, 1); } else if (pointerType === 'down' && pointerIndex === -1) { // add new pointer - this._ongoingPointers.push(this._convertEventPosToPoint(pointerEvent, {})); + this._ongoingPointers.push(this._convertEventPosToPoint(pointerEvent, { x: 0, y: 0 })); } else if (pointerIndex > -1) { // update existing pointer this._convertEventPosToPoint(pointerEvent, this._ongoingPointers[pointerIndex]); @@ -505,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 @@ -544,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; diff --git a/src/js/gestures/tap-handler.js b/src/js/gestures/tap-handler.js index 9798503c1..f2138ed4e 100644 --- a/src/js/gestures/tap-handler.js +++ b/src/js/gestures/tap-handler.js @@ -1,19 +1,19 @@ /** - * @template T - * @template P + * @template T, P * @typedef {import('../types.js').AddPostfix} AddPostfix */ /** @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 * (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 @@ -93,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) { @@ -106,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 4622785b9..4703733cd 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,26 +29,46 @@ class ZoomHandler { */ constructor(gestures) { this.gestures = gestures; - this.pswp = this.gestures.pswp; - /** @type {Point} */ - this._startPan = {}; - - /** @type {Point} */ - this._startZoomPoint = {}; - /** @type {Point} */ - this._zoomPoint = {}; + /** + * @private + * @type {Point} + */ + this._startPan = { x: 0, y: 0 }; + /** + * @private + * @type {Point} + */ + this._startZoomPoint = { x: 0, y: 0 }; + /** + * @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(); + const { currSlide } = this.gestures.pswp; + if (currSlide) { + this._startZoomLevel = currSlide.currZoomLevel; + equalizePoints(this._startPan, currSlide.pan); + } + + this.gestures.pswp.animations.stopAllPan(); this._wasOverFitZoomLevel = false; } 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; @@ -93,9 +114,9 @@ class ZoomHandler { } end() { - const { pswp } = this; + 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(); @@ -108,6 +129,7 @@ class ZoomHandler { * @private * @param {'x' | 'y'} axis * @param {number} currZoomLevel + * @returns {number} */ _calculatePanForZoomLevel(axis, currZoomLevel) { const zoomFactor = currZoomLevel / this._startZoomLevel; @@ -120,18 +142,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()) { + if (!currSlide?.isZoomable()) { return; } - if (this._zoomPoint.x === undefined) { + if (this._zoomPoint.x === 0) { ignoreGesture = true; } @@ -155,8 +177,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; @@ -185,10 +207,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 diff --git a/src/js/keyboard.js b/src/js/keyboard.js index d231fdce5..3b701acce 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 @@ -27,8 +49,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); @@ -41,14 +63,16 @@ class Keyboard { }); } + /** @private */ _focusRoot() { - if (!this._wasFocused) { + if (!this._wasFocused && this.pswp.element) { this.pswp.element.focus(); this._wasFocused = true; } } /** + * @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: @@ -123,6 +148,7 @@ class Keyboard { if (keydownAction) { e.preventDefault(); + // @ts-ignore pswp[keydownAction](); } } @@ -130,11 +156,13 @@ class Keyboard { /** * Trap focus inside photoswipe * + * @private * @param {FocusEvent} e */ _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 b2282d4f4..e8434294a 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 */ @@ -41,13 +42,21 @@ import { lazyLoadSlide } from '../slide/loader.js'; */ class PhotoSwipeLightbox extends PhotoSwipeBase { /** - * @param {PhotoSwipeOptions} options + * @param {PhotoSwipeOptions} [options] */ constructor(options) { super(); /** @type {PhotoSwipeOptions} */ this.options = options || {}; this._uid = 0; + this.shouldOpen = false; + /** + * @private + * @type {Content | undefined} + */ + this._preloadedContent = undefined; + + this.onThumbnailsClick = this.onThumbnailsClick.bind(this); } /** @@ -55,8 +64,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) => { @@ -80,8 +87,9 @@ 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 }; if (!initialPoint.x && !initialPoint.y) { @@ -90,6 +98,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) }; @@ -104,6 +113,7 @@ class PhotoSwipeLightbox extends PhotoSwipeBase { * Get index of gallery item that was clicked. * * @param {MouseEvent} e click event + * @returns {number} */ getClickedIndex(e) { // legacy option @@ -136,8 +146,9 @@ 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] + * @returns {boolean} */ loadAndOpen(index, dataSource, initialPoint) { // Check if the gallery is already open @@ -160,7 +171,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; @@ -241,7 +252,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)); }); }); @@ -249,20 +260,20 @@ 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); }); }); if (this._preloadedContent) { pswp.contentLoader.addToCache(this._preloadedContent); - this._preloadedContent = null; + this._preloadedContent = undefined; } pswp.on('destroy', () => { // clean up public variables - this.pswp = null; - window.pswp = null; + this.pswp = undefined; + delete window.pswp; }); pswp.init(); @@ -272,12 +283,10 @@ 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 = 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 e7d898ace..9bcfb1766 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; @@ -97,7 +99,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'); @@ -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)) { @@ -325,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 f6585f50d..76458524a 100644 --- a/src/js/opener.js +++ b/src/js/opener.js @@ -27,11 +27,52 @@ class Opener { constructor(pswp) { this.pswp = pswp; this.isClosed = true; - this._prepareOpen = this._prepareOpen.bind(this); - - /** @type {false | Bounds} */ + 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 | null | undefined } + */ + this._cropContainer2 = undefined; + + /** + * @private + * @type {Bounds | undefined} + */ 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 = undefined; } else if (options.showHideAnimationType === 'none') { options.showHideOpacity = false; this._duration = 0; - this._thumbBounds = false; + this._thumbBounds = undefined; } else if (this.isOpening && pswp._initialThumbBounds) { // Use initial bounds if defined this._thumbBounds = pswp._initialThumbBounds; @@ -101,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; @@ -129,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; @@ -139,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; } @@ -150,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) { @@ -173,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) { @@ -186,6 +239,7 @@ class Opener { } } + /** @private */ _start() { if (this.isOpening && this._useAnimation @@ -203,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); @@ -219,8 +273,9 @@ 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' @@ -232,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) { @@ -249,6 +304,7 @@ class Opener { } } + /** @private */ _onAnimationComplete() { const { pswp } = this; this.isOpen = this.isOpening; @@ -269,39 +325,43 @@ 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(); } } + /** @private */ _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'); } } + /** @private */ _animateToClosedState() { const { pswp } = this; @@ -309,18 +369,19 @@ 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'); } } /** - * @param {boolean=} animate + * @private + * @param {boolean} [animate] */ _setClosedStateZoomPan(animate) { if (!this._thumbBounds) return; @@ -329,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; @@ -354,17 +415,19 @@ 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) { + 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(); + } } } /** + * @private * @param {HTMLElement} target * @param {'transform' | 'opacity'} prop * @param {string} propValue diff --git a/src/js/photoswipe.js b/src/js/photoswipe.js index 68bd618c7..51d75221c 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 @@ -40,8 +41,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 }} Size */ +/** @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 */ @@ -55,10 +55,11 @@ 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 + * @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). * @@ -67,109 +68,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: 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] + * @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) => { 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 + * @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] @@ -178,48 +179,48 @@ 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} */ +/** @type {PreparedPhotoSwipeOptions} */ const defaultOptions = { allowPanToNext: true, spacing: 0.1, @@ -253,48 +254,66 @@ const defaultOptions = { */ class PhotoSwipe extends PhotoSwipeBase { /** - * @param {PhotoSwipeOptions} options + * @param {PhotoSwipeOptions} [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 {{ 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 - * - * @type {number} */ 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 | undefined} */ + this._initialThumbBounds = undefined; - /** @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(); - - /** @type {Animations} */ this.animations = new Animations(); - this.mainScroll = new MainScroll(this); this.gestures = new Gestures(this); this.opener = new Opener(this); @@ -302,9 +321,10 @@ class PhotoSwipe extends PhotoSwipeBase { this.contentLoader = new ContentLoader(this); } + /** @returns {boolean} */ init() { if (this.isOpen || this.isDestroying) { - return; + return false; } this.isOpen = true; @@ -321,7 +341,9 @@ 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; @@ -359,12 +381,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(); @@ -376,7 +403,9 @@ 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(); @@ -391,6 +420,7 @@ class PhotoSwipe extends PhotoSwipeBase { * (for example, -1 will return the last slide) * * @param {number} index + * @returns {number} */ getLoopedIndex(index) { const numSlides = this.getNumItems(); @@ -405,16 +435,12 @@ class PhotoSwipe extends PhotoSwipeBase { } } - index = clamp(index, 0, numSlides - 1); - - return index; + return clamp(index, 0, numSlides - 1); } appendHeavy() { this.mainScroll.itemHolders.forEach((itemHolder) => { - if (itemHolder.slide) { - itemHolder.slide.appendHeavy(); - } + itemHolder.slide?.appendHeavy(); }); } @@ -448,14 +474,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(); } /** @@ -491,17 +517,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(); @@ -516,7 +542,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); } @@ -526,9 +552,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); } } }); @@ -542,7 +567,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()) { @@ -558,7 +583,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 @@ -577,6 +602,7 @@ class PhotoSwipe extends PhotoSwipeBase { holder.slide.append(holder.el); } + /** @returns {Point} */ getViewportCenterPoint() { return { x: this.viewportSize.x / 2, @@ -588,7 +614,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; @@ -638,7 +664,9 @@ 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); + } } /** @@ -647,7 +675,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'); } } @@ -700,7 +728,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 +737,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'); @@ -733,6 +761,8 @@ class PhotoSwipe extends PhotoSwipeBase { * {x:,y:,w:} * * Height is optional (calculated based on the large image) + * + * @returns {Bounds | undefined} */ getThumbBounds() { return getThumbBounds( @@ -743,7 +773,7 @@ class PhotoSwipe extends PhotoSwipeBase { } /** - * If the PhotoSwipe can have continious loop + * If the PhotoSwipe can have continuous loop * @returns Boolean */ canLoop() { @@ -751,8 +781,9 @@ class PhotoSwipe extends PhotoSwipeBase { } /** - * @param {PhotoSwipeOptions} options * @private + * @param {PhotoSwipeOptions} options + * @returns {PreparedPhotoSwipeOptions} */ _prepareOptions(options) { if (window.matchMedia('(prefers-reduced-motion), (update: slow)').matches) { @@ -760,8 +791,8 @@ class PhotoSwipe extends PhotoSwipeBase { options.zoomAnimationDuration = 0; } - /** @type {PhotoSwipeOptions}*/ - this.options = { + /** @type {PreparedPhotoSwipeOptions} */ + return { ...defaultOptions, ...options }; diff --git a/src/js/scroll-wheel.js b/src/js/scroll-wheel.js index 08c05a28d..7afda1825 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/slide/content.js b/src/js/slide/content.js index 9121f9ac0..676350104 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,21 +119,22 @@ 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) { 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; @@ -164,7 +170,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 +211,9 @@ class Content { ); } + /** + * @returns {Boolean} If the content is in error state + */ isError() { return this.state === LOAD_STATE.ERROR; } @@ -231,8 +240,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 +262,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 +290,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 +348,7 @@ class Content { */ destroy() { this.hasSlide = false; - this.slide = null; + this.slide = undefined; if (this.instance.dispatch('contentDestroy', { content: this }).defaultPrevented) { return; @@ -346,13 +358,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 +373,14 @@ class Content { */ displayError() { if (this.slide) { - /** @type {HTMLElement} */ - let errorMsgEl = createElement('pswp__error-msg'); - errorMsgEl.innerText = this.instance.options.errorMsg; - errorMsgEl = this.instance.applyFilters( + let errorMsgEl = createElement('pswp__error-msg', 'div'); + errorMsgEl.innerText = this.instance.options?.errorMsg ?? ''; + 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 +393,7 @@ class Content { * Append the content */ append() { - if (this.isAttached) { + if (this.isAttached || !this.element) { return; } @@ -424,7 +435,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 +446,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'); } } diff --git a/src/js/slide/get-thumb-bounds.js b/src/js/slide/get-thumb-bounds.js index eb9fd29b1..d90248682 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,14 +82,15 @@ export function getThumbBounds(index, itemData, instance) { } const { element } = itemData; + /** @type {Bounds | undefined} */ let thumbBounds; - /** @type {HTMLElement} */ + /** @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)); } thumbnail = instance.applyFilters('thumbEl', thumbnail, itemData, index); @@ -98,8 +101,8 @@ export function getThumbBounds(index, itemData, instance) { } else { thumbBounds = getCroppedBoundsByElement( thumbnail, - itemData.width || itemData.w, - itemData.height || itemData.h + itemData.width || itemData.w || 0, + itemData.height || itemData.h || 0 ); } } diff --git a/src/js/slide/loader.js b/src/js/slide/loader.js index f288f8f09..c09c5e880 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,34 +15,36 @@ 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; - } + /** @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 - // @ts-expect-error should provide pswp instance? - const viewportSize = instance.viewportSize || getViewportSize(options, instance); - const panAreaSize = getPanAreaSize(options, viewportSize, itemData, index); - - const zoomLevel = new ZoomLevel(options, itemData, -1); - zoomLevel.update(content.width, content.height, panAreaSize); + // as it might use srcset, and we need to define sizes + 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; } @@ -54,10 +55,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 +71,6 @@ export function lazyLoadSlide(index, instance) { return lazyLoadData(itemData, instance, index); } - class ContentLoader { /** * @param {PhotoSwipe} pswp @@ -88,7 +89,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 +114,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 +132,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 +182,7 @@ class ContentLoader { /** * @param {number} index + * @returns {Content | undefined} */ getContentByIndex(index) { return this._cachedItems.find(content => content.index === index); @@ -190,7 +190,7 @@ class ContentLoader { destroy() { this._cachedItems.forEach(content => content.destroy()); - this._cachedItems = null; + this._cachedItems = []; } } diff --git a/src/js/slide/pan-bounds.js b/src/js/slide/pan-bounds.js index a482a4e72..93243addf 100644 --- a/src/js/slide/pan-bounds.js +++ b/src/js/slide/pan-bounds.js @@ -1,10 +1,8 @@ -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 */ /** @@ -16,17 +14,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} */ { x: 0, y: 0 }; + this.max = /** @type {Point} */ { x: 0, y: 0 }; + this.min = /** @type {Point} */ { x: 0, y: 0 }; } /** @@ -66,7 +57,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 @@ -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]); diff --git a/src/js/slide/placeholder.js b/src/js/slide/placeholder.js index 732349e5f..631c1fc47 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; diff --git a/src/js/slide/slide.js b/src/js/slide/slide.js index fae577648..42ebe936c 100644 --- a/src/js/slide/slide.js +++ b/src/js/slide/slide.js @@ -4,17 +4,17 @@ /** * @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 */ @@ -47,7 +47,9 @@ class Slide { this.isActive = (index === pswp.currIndex); this.currentResolution = 0; /** @type {Point} */ - this.panAreaSize = {}; + this.panAreaSize = { x: 0, y: 0 }; + /** @type {Point} */ + this.pan = { x: 0, y: 0 }; this.isFirstSlide = (this.isActive && !pswp.opener.isOpen); @@ -59,20 +61,17 @@ class Slide { index }); - this.pan = { - x: 0, - y: 0 - }; - this.content = this.pswp.contentLoader.getContentBySlide(this); - this.container = createElement('pswp__zoom-wrap'); + 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; @@ -133,7 +132,7 @@ class Slide { } load() { - this.content.load(); + this.content.load(false); this.pswp.dispatch('slideLoad', { slide: this }); } @@ -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; @@ -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]; @@ -386,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, @@ -407,16 +409,18 @@ 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); + return Boolean(this.width) && (this.currZoomLevel > this.zoomLevels.fit); } /** * If the slide can be zoomed + * @returns {boolean} */ isZoomable() { - return this.width && this.content.isZoomable(); + return Boolean(this.width) && this.content.isZoomable(); } /** @@ -445,6 +449,7 @@ class Slide { * @param {number} x * @param {number} y * @param {number} zoom + * @private */ _applyZoomTransform(x, y, zoom) { zoom /= this.currentResolution || this.zoomLevels.initial; @@ -466,6 +471,7 @@ class Slide { }); } + /** @returns {string} */ getCurrentTransform() { const scale = this.currZoomLevel / (this.currentResolution || this.zoomLevels.initial); return toTransformString(this.pan.x, this.pan.y, scale); @@ -481,7 +487,7 @@ 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 diff --git a/src/js/slide/zoom-level.js b/src/js/slide/zoom-level.js index 298532434..45630eb2f 100644 --- a/src/js/slide/zoom-level.js +++ b/src/js/slide/zoom-level.js @@ -2,6 +2,7 @@ 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 */ @@ -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 { Point | null } */ + this.panAreaSize = null; + /** @type { Point | null } */ + this.elementSize = null; + this.fit = 1; + this.fill = 1; + this.vFill = 1; + this.initial = 1; + this.secondary = 1; + this.max = 1; + this.min = 1; } /** @@ -31,18 +43,16 @@ class ZoomLevel { * * @param {number} maxWidth * @param {number} maxHeight - * @param {{ x?: number; y?: number }} panAreaSize + * @param {Point} panAreaSize */ update(maxWidth, maxHeight, panAreaSize) { - this.elementSize = { - x: maxWidth, - y: maxHeight - }; - + /** @type {Point} */ + 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); } } diff --git a/src/js/ui/button-arrow.js b/src/js/ui/button-arrow.js index 77f574ce2..3e140d440 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'); diff --git a/src/js/ui/loading-indicator.js b/src/js/ui/loading-indicator.js index 2a5a9a41a..2f2c4d748 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 @@ -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); } @@ -61,6 +61,8 @@ export const loadingIndicator = { }); // expose the method - pswp.ui.updatePreloaderVisibility = updatePreloaderVisibility; + if (pswp.ui) { + pswp.ui.updatePreloaderVisibility = updatePreloaderVisibility; + } } }; diff --git a/src/js/ui/ui-element.js b/src/js/ui/ui-element.js index 0789153e5..8f96c6950 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'; } @@ -136,8 +134,9 @@ class UIElement { element.title = title; } - if (ariaLabel || title) { - /** @type {HTMLElement} */ (element).setAttribute('aria-label', ariaLabel || title); + const ariaText = ariaLabel || title; + if (ariaText) { + element.setAttribute('aria-label', ariaText); } } @@ -150,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); } }; @@ -159,7 +159,8 @@ class UIElement { // Top bar is default position const appendTo = data.appendTo || 'bar'; - let container; + /** @type {HTMLElement | undefined} 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,13 +173,10 @@ class UIElement { if (appendTo === 'wrapper') { container = pswp.scrollWrap; - } else { - // root element - container = pswp.element; } } - 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 23366ccc8..eec2cbc00 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,24 @@ class UI { */ constructor(pswp) { this.pswp = pswp; - + this.isRegistered = false; + /** @type {UIElementData[]} */ + this.uiElementsData = []; + /** @type {(UIElement | UIElementData)[]} */ + this.items = []; /** @type {() => void} */ - this.updatePreloaderVisibility = undefined; + this.updatePreloaderVisibility = () => {}; - /** @type {number} */ + /** + * @private + * @type {number | undefined} + */ this._lastUpdatedZoomLevel = undefined; } init() { const { pswp } = this; this.isRegistered = false; - /** @type {UIElementData[]} */ this.uiElementsData = [ closeButton, arrowPrev, @@ -56,7 +62,6 @@ class UI { return (a.order || 0) - (b.order || 0); }); - /** @type {(UIElement | UIElementData)[]} */ this.items = []; this.isRegistered = true; @@ -65,7 +70,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()); @@ -87,15 +92,18 @@ 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; - 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/animations.js b/src/js/util/animations.js index 345ec4e0a..b5a9aa2be 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,31 +25,29 @@ 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] + * @returns {Animation} */ _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); diff --git a/src/js/util/css-animation.js b/src/js/util/css-animation.js index 4eb3042e5..a99b2b28d 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,36 +30,30 @@ 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 const prop = transform ? 'transform' : 'opacity'; - const propValue = props[prop]; + const propValue = props[prop] ?? ''; /** @private */ 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/dom-events.js b/src/js/util/dom-events.js index 2265c3b2f..6ba83a9a4 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; @@ -10,13 +11,12 @@ 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 {boolean} passive + * @prop {EventListenerOrEventListenerObject} listener + * @prop {boolean} [passive] */ class DOMEvents { @@ -31,10 +31,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 +43,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 +72,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 +110,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; diff --git a/src/js/util/spring-animation.js b/src/js/util/spring-animation.js index 79100b746..abc6609f4 100644 --- a/src/js/util/spring-animation.js +++ b/src/js/util/spring-animation.js @@ -1,13 +1,27 @@ 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; + this._raf = 0; const { start, @@ -15,12 +29,11 @@ class SpringAnimation { velocity, onUpdate, onComplete, - onFinish, + onFinish = () => {}, dampingRatio, naturalFrequency } = props; - /** @type {() => void} */ this.onFinish = onFinish; const easer = new SpringEaser(velocity, dampingRatio, naturalFrequency); @@ -55,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 833dd8bb7..095721e05 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. @@ -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 30c993f95..8b3a39325 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 ('button' in e && e.button === 1) || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey; } /** * Parse `gallery` or `children` options. * - * @param {import('../photoswipe.js').ElementProvider} option - * @param {string=} legacySelector + * @param {import('../photoswipe.js').ElementProvider} [option] + * @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' diff --git a/src/js/util/viewport-size.js b/src/js/util/viewport-size.js index 5657e8cbe..759b00470 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 { diff --git a/tsconfig.json b/tsconfig.json index 40e87e692..cf23ce1eb 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,