intranet-pictures 2.0.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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