intranet-pictures 2.0.0 → 2.1.0

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