intranet-pictures 1.0.6 → 1.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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/lib/intranet/pictures/responder.rb +27 -18
  3. data/lib/intranet/pictures/version.rb +1 -1
  4. data/lib/intranet/resources/haml/pictures_browse.haml +0 -1
  5. data/lib/intranet/resources/haml/pictures_home.haml +0 -3
  6. data/lib/intranet/resources/locales/en.yml +0 -1
  7. data/lib/intranet/resources/locales/fr.yml +0 -1
  8. data/lib/intranet/resources/www/jpictures.js +32 -14
  9. data/lib/intranet/resources/www/photoswipe/photoswipe-dynamic-caption-plugin.css +47 -0
  10. data/lib/intranet/resources/www/photoswipe/photoswipe-dynamic-caption-plugin.esm.js +400 -0
  11. data/lib/intranet/resources/www/photoswipe/photoswipe-lightbox.esm.js +1382 -0
  12. data/lib/intranet/resources/www/photoswipe/photoswipe-lightbox.esm.js.map +1 -0
  13. data/lib/intranet/resources/www/photoswipe/photoswipe-lightbox.esm.min.js +5 -0
  14. data/lib/intranet/resources/www/photoswipe/photoswipe.css +383 -142
  15. data/lib/intranet/resources/www/photoswipe/photoswipe.esm.js +5279 -0
  16. data/lib/intranet/resources/www/photoswipe/photoswipe.esm.js.map +1 -0
  17. data/lib/intranet/resources/www/photoswipe/photoswipe.esm.min.js +5 -0
  18. data/lib/intranet/resources/www/style.css +13 -0
  19. data/spec/intranet/pictures/responder_spec.rb +78 -58
  20. metadata +26 -28
  21. data/lib/intranet/resources/haml/pictures_photoswipe.haml +0 -23
  22. data/lib/intranet/resources/www/photoswipe/LICENSE +0 -21
  23. data/lib/intranet/resources/www/photoswipe/default-skin/default-skin.css +0 -484
  24. data/lib/intranet/resources/www/photoswipe/default-skin/default-skin.png +0 -0
  25. data/lib/intranet/resources/www/photoswipe/default-skin/default-skin.svg +0 -1
  26. data/lib/intranet/resources/www/photoswipe/default-skin/preloader.gif +0 -0
  27. data/lib/intranet/resources/www/photoswipe/photoswipe-ui-default.js +0 -861
  28. data/lib/intranet/resources/www/photoswipe/photoswipe-ui-default.min.js +0 -4
  29. data/lib/intranet/resources/www/photoswipe/photoswipe.js +0 -3734
  30. data/lib/intranet/resources/www/photoswipe/photoswipe.min.js +0 -4
@@ -0,0 +1,1382 @@
1
+ /*!
2
+ * PhotoSwipe Lightbox 5.2.4 - https://photoswipe.com
3
+ * (c) 2022 Dmytro Semenov
4
+ */
5
+ /**
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
+ */
12
+ function createElement(className, tagName, appendToEl) {
13
+ const el = document.createElement(tagName || 'div');
14
+ if (className) {
15
+ el.className = className;
16
+ }
17
+ if (appendToEl) {
18
+ appendToEl.appendChild(el);
19
+ }
20
+ return el;
21
+ }
22
+
23
+ /**
24
+ * Get transform string
25
+ *
26
+ * @param {Number} x
27
+ * @param {Number|null} y
28
+ * @param {Number|null} scale
29
+ */
30
+ function toTransformString(x, y, scale) {
31
+ let propValue = 'translate3d('
32
+ + x + 'px,' + (y || 0) + 'px'
33
+ + ',0)';
34
+
35
+ if (scale !== undefined) {
36
+ propValue += ' scale3d('
37
+ + scale + ',' + scale
38
+ + ',1)';
39
+ }
40
+
41
+ return propValue;
42
+ }
43
+
44
+ /**
45
+ * Apply width and height CSS properties to element
46
+ */
47
+ 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;
50
+ }
51
+
52
+ const LOAD_STATE = {
53
+ IDLE: 'idle',
54
+ LOADING: 'loading',
55
+ LOADED: 'loaded',
56
+ ERROR: 'error',
57
+ };
58
+
59
+
60
+ /**
61
+ * Check if click or keydown event was dispatched
62
+ * with a special key or via mouse wheel.
63
+ *
64
+ * @param {Event} e
65
+ */
66
+ function specialKeyUsed(e) {
67
+ if (e.which === 2 || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey) {
68
+ return true;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Parse `gallery` or `children` options.
74
+ *
75
+ * @param {Element|NodeList|String} option
76
+ * @param {String|null} legacySelector
77
+ * @param {Element|null} parent
78
+ * @returns Element[]
79
+ */
80
+ function getElementsFromOption(option, legacySelector, parent = document) {
81
+ let elements = [];
82
+
83
+ if (option instanceof Element) {
84
+ elements = [option];
85
+ } else if (option instanceof NodeList || Array.isArray(option)) {
86
+ elements = Array.from(option);
87
+ } else {
88
+ const selector = typeof option === 'string' ? option : legacySelector;
89
+ if (selector) {
90
+ elements = Array.from(parent.querySelectorAll(selector));
91
+ }
92
+ }
93
+
94
+ return elements;
95
+ }
96
+
97
+ /**
98
+ * Check if variable is PhotoSwipe class
99
+ *
100
+ * @param {*} fn
101
+ * @returns Boolean
102
+ */
103
+ function isPswpClass(fn) {
104
+ return typeof fn === 'function'
105
+ && fn.prototype
106
+ && fn.prototype.goTo;
107
+ }
108
+
109
+ /**
110
+ * Base PhotoSwipe event object
111
+ */
112
+ class PhotoSwipeEvent {
113
+ constructor(type, details) {
114
+ this.type = type;
115
+ if (details) {
116
+ Object.assign(this, details);
117
+ }
118
+ }
119
+
120
+ preventDefault() {
121
+ this.defaultPrevented = true;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * PhotoSwipe base class that can listen and dispatch for events.
127
+ * Shared by PhotoSwipe Core and PhotoSwipe Lightbox, extended by base.js
128
+ */
129
+ class Eventable {
130
+ constructor() {
131
+ this._listeners = {};
132
+ this._filters = {};
133
+ }
134
+
135
+ addFilter(name, fn, priority = 100) {
136
+ if (!this._filters[name]) {
137
+ this._filters[name] = [];
138
+ }
139
+
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
+ }
146
+ }
147
+
148
+ removeFilter(name, fn) {
149
+ if (this._filters[name]) {
150
+ this._filters[name] = this._filters[name].filter(filter => (filter.fn !== fn));
151
+ }
152
+
153
+ if (this.pswp) {
154
+ this.pswp.removeFilter(name, fn);
155
+ }
156
+ }
157
+
158
+ applyFilters(name, ...args) {
159
+ if (this._filters[name]) {
160
+ this._filters[name].forEach((filter) => {
161
+ args[0] = filter.fn.apply(this, args);
162
+ });
163
+ }
164
+ return args[0];
165
+ }
166
+
167
+ on(name, fn) {
168
+ if (!this._listeners[name]) {
169
+ this._listeners[name] = [];
170
+ }
171
+ this._listeners[name].push(fn);
172
+
173
+ // When binding events to lightbox,
174
+ // also bind events to PhotoSwipe Core,
175
+ // if it's open.
176
+ if (this.pswp) {
177
+ this.pswp.on(name, fn);
178
+ }
179
+ }
180
+
181
+ off(name, fn) {
182
+ if (this._listeners[name]) {
183
+ this._listeners[name] = this._listeners[name].filter(listener => (fn !== listener));
184
+ }
185
+
186
+ if (this.pswp) {
187
+ this.pswp.off(name, fn);
188
+ }
189
+ }
190
+
191
+ dispatch(name, details) {
192
+ if (this.pswp) {
193
+ return this.pswp.dispatch(name, details);
194
+ }
195
+
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
+
208
+ return event;
209
+ }
210
+ }
211
+
212
+ class Placeholder {
213
+ /**
214
+ * @param {String|false} imageSrc
215
+ * @param {Element} container
216
+ */
217
+ constructor(imageSrc, container) {
218
+ // Create placeholder
219
+ // (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
+ );
225
+
226
+ if (imageSrc) {
227
+ this.element.decoding = 'async';
228
+ this.element.alt = '';
229
+ this.element.src = imageSrc;
230
+ this.element.setAttribute('role', 'presentation');
231
+ }
232
+
233
+ this.element.setAttribute('aria-hiden', 'true');
234
+ }
235
+
236
+ setDisplayedSize(width, height) {
237
+ if (!this.element) {
238
+ return;
239
+ }
240
+
241
+ if (this.element.tagName === 'IMG') {
242
+ // Use transform scale() to modify img placeholder size
243
+ // (instead of changing width/height directly).
244
+ // This helps with performance, specifically in iOS15 Safari.
245
+ setWidthHeight(this.element, 250, 'auto');
246
+ this.element.style.transformOrigin = '0 0';
247
+ this.element.style.transform = toTransformString(0, 0, width / 250);
248
+ } else {
249
+ setWidthHeight(this.element, width, height);
250
+ }
251
+ }
252
+
253
+ destroy() {
254
+ if (this.element.parentNode) {
255
+ this.element.remove();
256
+ }
257
+ this.element = null;
258
+ }
259
+ }
260
+
261
+ class Content {
262
+ /**
263
+ * @param {Object} itemData Slide data
264
+ * @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)
268
+ */
269
+ constructor(itemData, instance, index) {
270
+ this.instance = instance;
271
+ this.data = itemData;
272
+ this.index = index;
273
+
274
+ this.width = Number(this.data.w) || Number(this.data.width) || 0;
275
+ this.height = Number(this.data.h) || Number(this.data.height) || 0;
276
+
277
+ this.isAttached = false;
278
+ this.hasSlide = false;
279
+ this.state = LOAD_STATE.IDLE;
280
+
281
+ if (this.data.type) {
282
+ this.type = this.data.type;
283
+ } else if (this.data.src) {
284
+ this.type = 'image';
285
+ } else {
286
+ this.type = 'html';
287
+ }
288
+
289
+ this.instance.dispatch('contentInit', { content: this });
290
+ }
291
+
292
+ removePlaceholder() {
293
+ if (this.placeholder && !this.keepPlaceholder()) {
294
+ // With delay, as image might be loaded, but not rendered
295
+ setTimeout(() => {
296
+ if (this.placeholder) {
297
+ this.placeholder.destroy();
298
+ this.placeholder = null;
299
+ }
300
+ }, 500);
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Preload content
306
+ *
307
+ * @param {Boolean} isLazy
308
+ */
309
+ 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
+ );
322
+ }
323
+
324
+ if (this.element && !reload) {
325
+ return;
326
+ }
327
+
328
+ if (this.instance.dispatch('contentLoad', { content: this, isLazy }).defaultPrevented) {
329
+ return;
330
+ }
331
+
332
+ if (this.isImageContent()) {
333
+ this.loadImage(isLazy);
334
+ } else {
335
+ this.element = createElement('pswp__content');
336
+ this.element.innerHTML = this.data.html || '';
337
+ }
338
+
339
+ if (reload && this.slide) {
340
+ this.slide.updateContentSize(true);
341
+ }
342
+ }
343
+
344
+ /**
345
+ * Preload image
346
+ *
347
+ * @param {Boolean} isLazy
348
+ */
349
+ loadImage(isLazy) {
350
+ this.element = createElement('pswp__img', 'img');
351
+
352
+ if (this.instance.dispatch('contentLoadImage', { content: this, isLazy }).defaultPrevented) {
353
+ return;
354
+ }
355
+
356
+ if (this.data.srcset) {
357
+ this.element.srcset = this.data.srcset;
358
+ }
359
+
360
+ this.element.src = this.data.src;
361
+
362
+ this.element.alt = this.data.alt || '';
363
+
364
+ this.state = LOAD_STATE.LOADING;
365
+
366
+ if (this.element.complete) {
367
+ this.onLoaded();
368
+ } else {
369
+ this.element.onload = () => {
370
+ this.onLoaded();
371
+ };
372
+
373
+ this.element.onerror = () => {
374
+ this.onError();
375
+ };
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Assign slide to content
381
+ *
382
+ * @param {Slide} slide
383
+ */
384
+ setSlide(slide) {
385
+ this.slide = slide;
386
+ this.hasSlide = true;
387
+ this.instance = slide.pswp;
388
+
389
+ // todo: do we need to unset slide?
390
+ }
391
+
392
+ /**
393
+ * Content load success handler
394
+ */
395
+ onLoaded() {
396
+ this.state = LOAD_STATE.LOADED;
397
+
398
+ if (this.slide) {
399
+ this.instance.dispatch('loadComplete', { slide: this.slide, content: this });
400
+
401
+ // if content is reloaded
402
+ if (this.slide.isActive
403
+ && this.slide.heavyAppended
404
+ && !this.element.parentNode) {
405
+ this.slide.container.innerHTML = '';
406
+ this.append();
407
+ this.slide.updateContentSize(true);
408
+ }
409
+ }
410
+ }
411
+
412
+ /**
413
+ * Content load error handler
414
+ */
415
+ onError() {
416
+ this.state = LOAD_STATE.ERROR;
417
+
418
+ if (this.slide) {
419
+ this.displayError();
420
+ this.instance.dispatch('loadComplete', { slide: this.slide, isError: true, content: this });
421
+ this.instance.dispatch('loadError', { slide: this.slide, content: this });
422
+ }
423
+ }
424
+
425
+ /**
426
+ * @returns {Boolean} If the content is currently loading
427
+ */
428
+ isLoading() {
429
+ return this.instance.applyFilters(
430
+ 'isContentLoading',
431
+ this.state === LOAD_STATE.LOADING,
432
+ this
433
+ );
434
+ }
435
+
436
+ isError() {
437
+ return this.state === LOAD_STATE.ERROR;
438
+ }
439
+
440
+ /**
441
+ * @returns {Boolean} If the content is image
442
+ */
443
+ isImageContent() {
444
+ return this.type === 'image';
445
+ }
446
+
447
+ /**
448
+ * Update content size
449
+ *
450
+ * @param {Number} width
451
+ * @param {Number} height
452
+ */
453
+ setDisplayedSize(width, height) {
454
+ if (!this.element) {
455
+ return;
456
+ }
457
+
458
+ if (this.placeholder) {
459
+ this.placeholder.setDisplayedSize(width, height);
460
+ }
461
+
462
+ if (this.instance.dispatch('contentResize', { content: this, width, height }).defaultPrevented) {
463
+ return;
464
+ }
465
+
466
+ setWidthHeight(this.element, width, height);
467
+
468
+ 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;
479
+ }
480
+
481
+ if (this.slide) {
482
+ this.instance.dispatch('imageSizeChange', { slide: this.slide, width, height, content: this });
483
+ }
484
+ }
485
+ }
486
+
487
+ /**
488
+ * @returns {Boolean} If the content can be zoomed
489
+ */
490
+ isZoomable() {
491
+ return this.instance.applyFilters(
492
+ 'isContentZoomable',
493
+ this.isImageContent() && (this.state !== LOAD_STATE.ERROR),
494
+ this
495
+ );
496
+ }
497
+
498
+ /**
499
+ * @returns {Boolean} If content should use a placeholder (from msrc by default)
500
+ */
501
+ usePlaceholder() {
502
+ return this.instance.applyFilters(
503
+ 'useContentPlaceholder',
504
+ this.isImageContent(),
505
+ this
506
+ );
507
+ }
508
+
509
+ /**
510
+ * Preload content with lazy-loading param
511
+ *
512
+ * @param {Boolean} isLazy
513
+ */
514
+ lazyLoad() {
515
+ if (this.instance.dispatch('contentLazyLoad', { content: this }).defaultPrevented) {
516
+ return;
517
+ }
518
+
519
+ this.load(true);
520
+ }
521
+
522
+ /**
523
+ * @returns {Boolean} If placeholder should be kept after content is loaded
524
+ */
525
+ keepPlaceholder() {
526
+ return this.instance.applyFilters(
527
+ 'isKeepingPlaceholder',
528
+ this.isLoading(),
529
+ this
530
+ );
531
+ }
532
+
533
+ /**
534
+ * Destroy the content
535
+ */
536
+ destroy() {
537
+ this.hasSlide = false;
538
+ this.slide = null;
539
+
540
+ if (this.instance.dispatch('contentDestroy', { content: this }).defaultPrevented) {
541
+ return;
542
+ }
543
+
544
+ this.remove();
545
+
546
+ if (this.isImageContent() && this.element) {
547
+ this.element.onload = null;
548
+ this.element.onerror = null;
549
+ this.element = null;
550
+ }
551
+ }
552
+
553
+ /**
554
+ * Display error message
555
+ */
556
+ displayError() {
557
+ 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');
566
+ this.element.appendChild(errorMsgEl);
567
+ this.slide.container.innerHTML = '';
568
+ this.slide.container.appendChild(this.element);
569
+ this.slide.updateContentSize(true);
570
+ this.removePlaceholder();
571
+ }
572
+ }
573
+
574
+ /**
575
+ * Append the content
576
+ */
577
+ append() {
578
+ this.isAttached = true;
579
+
580
+ if (this.state === LOAD_STATE.ERROR) {
581
+ this.displayError();
582
+ return;
583
+ }
584
+
585
+ if (this.instance.dispatch('contentAppend', { content: this }).defaultPrevented) {
586
+ return;
587
+ }
588
+
589
+ if (this.isImageContent()) {
590
+ // Use decode() on nearby slides
591
+ //
592
+ // Nearby slide images are in DOM and not hidden via display:none.
593
+ // However, they are placed offscreen (to the left and right side).
594
+ //
595
+ // Some browsers do not composite the image until it's actually visible,
596
+ // using decode() helps.
597
+ //
598
+ // You might ask "why dont you just decode() and then append all images",
599
+ // that's because I want to show image before it's fully loaded,
600
+ // 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
+ });
619
+ } else {
620
+ if (this.placeholder
621
+ && (this.state === LOAD_STATE.LOADED || this.state === LOAD_STATE.ERROR)) {
622
+ this.removePlaceholder();
623
+ }
624
+ this.appendImage();
625
+ }
626
+ } else if (this.element && !this.element.parentNode) {
627
+ this.slide.container.appendChild(this.element);
628
+ }
629
+ }
630
+
631
+ /**
632
+ * Activate the slide,
633
+ * active slide is generally the current one,
634
+ * meaning the user can see it.
635
+ */
636
+ activate() {
637
+ if (this.instance.dispatch('contentActivate', { content: this }).defaultPrevented) {
638
+ return;
639
+ }
640
+
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
+ }
649
+ }
650
+ }
651
+
652
+ /**
653
+ * Deactivate the content
654
+ */
655
+ deactivate() {
656
+ this.instance.dispatch('contentDeactivate', { content: this });
657
+ }
658
+
659
+
660
+ /**
661
+ * Remove the content from DOM
662
+ */
663
+ remove() {
664
+ this.isAttached = false;
665
+
666
+ if (this.instance.dispatch('contentRemove', { content: this }).defaultPrevented) {
667
+ return;
668
+ }
669
+
670
+ if (this.element && this.element.parentNode) {
671
+ this.element.remove();
672
+ }
673
+ }
674
+
675
+ /**
676
+ * Append the image content to slide container
677
+ */
678
+ appendImage() {
679
+ if (!this.isAttached) {
680
+ return;
681
+ }
682
+
683
+ if (this.instance.dispatch('contentAppendImage', { content: this }).defaultPrevented) {
684
+ return;
685
+ }
686
+
687
+ // ensure that element exists and is not already appended
688
+ if (this.slide && this.element && !this.element.parentNode) {
689
+ 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
+ }
767
+
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
+ ) || [];
797
+ }
798
+
799
+ return [galleryElement];
800
+ }
801
+
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');
836
+
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
+ }
843
+
844
+ if (linkEl.dataset.pswpCropped || linkEl.dataset.cropped) {
845
+ itemData.thumbCropped = true;
846
+ }
847
+ }
848
+
849
+ this.applyFilters('domItemData', itemData, element, linkEl);
850
+
851
+ return itemData;
852
+ }
853
+ }
854
+
855
+ function getViewportSize(options, pswp) {
856
+ if (options.getViewportSizeFn) {
857
+ const newViewportSize = options.getViewportSizeFn(options, pswp);
858
+ if (newViewportSize) {
859
+ return newViewportSize;
860
+ }
861
+ }
862
+
863
+ return {
864
+ x: document.documentElement.clientWidth,
865
+
866
+ // TODO: height on mobile is very incosistent due to toolbar
867
+ // find a way to improve this
868
+ //
869
+ // document.documentElement.clientHeight - doesn't seem to work well
870
+ y: window.innerHeight
871
+ };
872
+ }
873
+
874
+ /**
875
+ * Parses padding option.
876
+ * Supported formats:
877
+ *
878
+ * // Object
879
+ * padding: {
880
+ * top: 0,
881
+ * bottom: 0,
882
+ * left: 0,
883
+ * right: 0
884
+ * }
885
+ *
886
+ * // A function that returns the object
887
+ * paddingFn: (viewportSize, itemData, index) => {
888
+ * return {
889
+ * top: 0,
890
+ * bottom: 0,
891
+ * left: 0,
892
+ * right: 0
893
+ * };
894
+ * }
895
+ *
896
+ * // Legacy variant
897
+ * paddingLeft: 0,
898
+ * paddingRight: 0,
899
+ * paddingTop: 0,
900
+ * paddingBottom: 0,
901
+ *
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}
908
+ */
909
+ function parsePaddingOption(prop, options, viewportSize, itemData, index) {
910
+ let paddingValue;
911
+
912
+ if (options.paddingFn) {
913
+ paddingValue = options.paddingFn(viewportSize, itemData, index)[prop];
914
+ } else if (options.padding) {
915
+ paddingValue = options.padding[prop];
916
+ } else {
917
+ const legacyPropName = 'padding' + prop[0].toUpperCase() + prop.slice(1);
918
+ if (options[legacyPropName]) {
919
+ paddingValue = options[legacyPropName];
920
+ }
921
+ }
922
+
923
+ return paddingValue || 0;
924
+ }
925
+
926
+
927
+ function getPanAreaSize(options, viewportSize, itemData, index) {
928
+ 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)
935
+ };
936
+ }
937
+
938
+ /**
939
+ * Calculates zoom levels for specific slide.
940
+ * Depends on viewport size and image size.
941
+ */
942
+
943
+ const MAX_IMAGE_WIDTH = 4000;
944
+
945
+ class ZoomLevel {
946
+ /**
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
951
+ */
952
+ constructor(options, itemData, index, pswp) {
953
+ this.pswp = pswp;
954
+ this.options = options;
955
+ this.itemData = itemData;
956
+ this.index = index;
957
+ }
958
+
959
+ /**
960
+ * Calculate initial, secondary and maximum zoom level for the specified slide.
961
+ *
962
+ * It should be called when either image or viewport size changes.
963
+ *
964
+ * @param {Slide} slide
965
+ */
966
+ update(maxWidth, maxHeight, panAreaSize) {
967
+ this.elementSize = {
968
+ x: maxWidth,
969
+ y: maxHeight
970
+ };
971
+
972
+ this.panAreaSize = panAreaSize;
973
+
974
+ const hRatio = this.panAreaSize.x / this.elementSize.x;
975
+ const vRatio = this.panAreaSize.y / this.elementSize.y;
976
+
977
+ 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
981
+ // when it has 100% of viewport vertical space (height)
982
+ this.vFill = Math.min(1, vRatio);
983
+
984
+ this.initial = this._getInitial();
985
+ 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
+ );
997
+
998
+ if (this.pswp) {
999
+ this.pswp.dispatch('zoomLevelsUpdate', { zoomLevels: this, slideData: this.itemData });
1000
+ }
1001
+ }
1002
+
1003
+ /**
1004
+ * Parses user-defined zoom option.
1005
+ *
1006
+ * @param {Mixed} optionPrefix Zoom level option prefix (initial, secondary, max)
1007
+ */
1008
+ _parseZoomLevelOption(optionPrefix) {
1009
+ // zoom.initial
1010
+ // zoom.secondary
1011
+ // zoom.max
1012
+ const optionValue = this.options[optionPrefix + 'ZoomLevel'];
1013
+
1014
+ if (!optionValue) {
1015
+ return;
1016
+ }
1017
+
1018
+ if (typeof optionValue === 'function') {
1019
+ return optionValue(this);
1020
+ }
1021
+
1022
+ if (optionValue === 'fill') {
1023
+ return this.fill;
1024
+ }
1025
+
1026
+ if (optionValue === 'fit') {
1027
+ return this.fit;
1028
+ }
1029
+
1030
+ return Number(optionValue);
1031
+ }
1032
+
1033
+ /**
1034
+ * Get zoom level to which image will be zoomed after double-tap gesture,
1035
+ * or when user clicks on zoom icon,
1036
+ * or mouse-click on image itself.
1037
+ * If you return 1 image will be zoomed to its original size.
1038
+ *
1039
+ * @return {Number}
1040
+ */
1041
+ _getSecondary() {
1042
+ let currZoomLevel = this._parseZoomLevelOption('secondary');
1043
+
1044
+ if (currZoomLevel) {
1045
+ return currZoomLevel;
1046
+ }
1047
+
1048
+ // 3x of "fit" state, but not larger than original
1049
+ currZoomLevel = Math.min(1, this.fit * 3);
1050
+
1051
+ if (currZoomLevel * this.elementSize.x > MAX_IMAGE_WIDTH) {
1052
+ currZoomLevel = MAX_IMAGE_WIDTH / this.elementSize.x;
1053
+ }
1054
+
1055
+ return currZoomLevel;
1056
+ }
1057
+
1058
+ /**
1059
+ * Get initial image zoom level.
1060
+ *
1061
+ * @return {Number}
1062
+ */
1063
+ _getInitial() {
1064
+ return this._parseZoomLevelOption('initial') || this.fit;
1065
+ }
1066
+
1067
+ /**
1068
+ * Maximum zoom level when user zooms
1069
+ * via zoom/pinch gesture,
1070
+ * via cmd/ctrl-wheel or via trackpad.
1071
+ *
1072
+ * @return {Number}
1073
+ */
1074
+ _getMax() {
1075
+ const currZoomLevel = this._parseZoomLevelOption('max');
1076
+
1077
+ if (currZoomLevel) {
1078
+ return currZoomLevel;
1079
+ }
1080
+
1081
+ // max zoom level is x4 from "fit state",
1082
+ // used for zoom gesture and ctrl/trackpad zoom
1083
+ return Math.max(1, this.fit * 4);
1084
+ }
1085
+ }
1086
+
1087
+ /**
1088
+ * Lazy-load an image
1089
+ * This function is used both by Lightbox and PhotoSwipe core,
1090
+ * thus it can be called before dialog is opened.
1091
+ *
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.
1096
+ */
1097
+ function lazyLoadData(itemData, instance, index) {
1098
+ // src/slide/content/content.js
1099
+ const content = instance.createContentFromData(itemData, index);
1100
+
1101
+ if (!content || !content.lazyLoad) {
1102
+ return;
1103
+ }
1104
+
1105
+ const { options } = instance;
1106
+
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);
1111
+
1112
+ const zoomLevel = new ZoomLevel(options, itemData, -1);
1113
+ zoomLevel.update(content.width, content.height, panAreaSize);
1114
+
1115
+ content.lazyLoad();
1116
+ content.setDisplayedSize(
1117
+ Math.ceil(content.width * zoomLevel.initial),
1118
+ Math.ceil(content.height * zoomLevel.initial)
1119
+ );
1120
+
1121
+ return content;
1122
+ }
1123
+
1124
+
1125
+ /**
1126
+ * Lazy-loads specific slide.
1127
+ * This function is used both by Lightbox and PhotoSwipe core,
1128
+ * thus it can be called before dialog is opened.
1129
+ *
1130
+ * By default it loads image based on viewport size and initial zoom level.
1131
+ *
1132
+ * @param {Integer} index Slide index
1133
+ * @param {Object} instance PhotoSwipe or PhotoSwipeLightbox eventable instance
1134
+ */
1135
+ function lazyLoadSlide(index, instance) {
1136
+ const itemData = instance.getItemData(index);
1137
+
1138
+ if (instance.dispatch('lazyLoadSlide', { index, itemData }).defaultPrevented) {
1139
+ return;
1140
+ }
1141
+
1142
+ return lazyLoadData(itemData, instance, index);
1143
+ }
1144
+
1145
+ /**
1146
+ * PhotoSwipe lightbox
1147
+ *
1148
+ * - If user has unsupported browser it falls back to default browser action (just opens URL)
1149
+ * - Binds click event to links that should open PhotoSwipe
1150
+ * - parses DOM strcture for PhotoSwipe (retrieves large image URLs and sizes)
1151
+ * - Initializes PhotoSwipe
1152
+ *
1153
+ *
1154
+ * Loader options use the same object as PhotoSwipe, and supports such options:
1155
+ *
1156
+ * gallery - Element | Element[] | NodeList | string selector for the gallery element
1157
+ * children - Element | Element[] | NodeList | string selector for the gallery children
1158
+ *
1159
+ */
1160
+
1161
+ class PhotoSwipeLightbox extends PhotoSwipeBase {
1162
+ constructor(options) {
1163
+ super();
1164
+ this.options = options || {};
1165
+ this._uid = 0;
1166
+ }
1167
+
1168
+ init() {
1169
+ this.onThumbnailsClick = this.onThumbnailsClick.bind(this);
1170
+
1171
+ // 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
+ });
1176
+ }
1177
+
1178
+ onThumbnailsClick(e) {
1179
+ // Exit and allow default browser action if:
1180
+ 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
1183
+ return;
1184
+ }
1185
+
1186
+ // If both clientX and clientY are 0 or not defined,
1187
+ // the event is likely triggered by keyboard,
1188
+ // so we do not pass the initialPoint
1189
+ //
1190
+ // Note that some screen readers emulate the mouse position,
1191
+ // so it's not ideal way to detect them.
1192
+ //
1193
+ let initialPoint = { x: e.clientX, y: e.clientY };
1194
+
1195
+ if (!initialPoint.x && !initialPoint.y) {
1196
+ initialPoint = null;
1197
+ }
1198
+
1199
+ let clickedIndex = this.getClickedIndex(e);
1200
+ clickedIndex = this.applyFilters('clickedIndex', clickedIndex, e, this);
1201
+ const dataSource = {
1202
+ gallery: e.currentTarget
1203
+ };
1204
+
1205
+ if (clickedIndex >= 0) {
1206
+ e.preventDefault();
1207
+ this.loadAndOpen(clickedIndex, dataSource, initialPoint);
1208
+ }
1209
+ }
1210
+
1211
+ /**
1212
+ * Get index of gallery item that was clicked.
1213
+ *
1214
+ * @param {Event} e click event
1215
+ */
1216
+ getClickedIndex(e) {
1217
+ // legacy option
1218
+ if (this.options.getClickedIndexFn) {
1219
+ return this.options.getClickedIndexFn.call(this, e);
1220
+ }
1221
+
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
+ );
1231
+
1232
+ if (clickedChildIndex !== -1) {
1233
+ return clickedChildIndex;
1234
+ } else if (this.options.children || this.options.childSelector) {
1235
+ // click wasn't on a child element
1236
+ return -1;
1237
+ }
1238
+
1239
+ // There is only one item (which is the gallery)
1240
+ return 0;
1241
+ }
1242
+
1243
+ /**
1244
+ * Load and open PhotoSwipe
1245
+ *
1246
+ * @param {Integer} index
1247
+ * @param {Array|Object|null} dataSource
1248
+ * @param {Point|null} initialPoint
1249
+ */
1250
+ loadAndOpen(index, dataSource, initialPoint) {
1251
+ // Check if the gallery is already open
1252
+ if (window.pswp) {
1253
+ return false;
1254
+ }
1255
+
1256
+ // set initial index
1257
+ this.options.index = index;
1258
+
1259
+ // define options for PhotoSwipe constructor
1260
+ this.options.initialPointerPos = initialPoint;
1261
+
1262
+ this.shouldOpen = true;
1263
+ this.preload(index, dataSource);
1264
+ return true;
1265
+ }
1266
+
1267
+ /**
1268
+ * Load the main module and the slide content by index
1269
+ *
1270
+ * @param {Integer} index
1271
+ */
1272
+ preload(index, dataSource) {
1273
+ const { options } = this;
1274
+
1275
+ if (dataSource) {
1276
+ options.dataSource = dataSource;
1277
+ }
1278
+
1279
+ // Add the main module
1280
+ const promiseArray = [];
1281
+
1282
+ const pswpModuleType = typeof options.pswpModule;
1283
+ if (isPswpClass(options.pswpModule)) {
1284
+ promiseArray.push(options.pswpModule);
1285
+ } else if (pswpModuleType === 'string') {
1286
+ throw new Error('pswpModule as string is no longer supported');
1287
+ } else if (pswpModuleType === 'function') {
1288
+ promiseArray.push(options.pswpModule());
1289
+ } else {
1290
+ throw new Error('pswpModule is not valid');
1291
+ }
1292
+
1293
+ // Add custom-defined promise, if any
1294
+ if (typeof options.openPromise === 'function') {
1295
+ // allow developers to perform some task before opening
1296
+ promiseArray.push(options.openPromise());
1297
+ }
1298
+
1299
+ if (options.preloadFirstSlide !== false && index >= 0) {
1300
+ this._preloadedContent = lazyLoadSlide(index, this);
1301
+ }
1302
+
1303
+ // Wait till all promises resolve and open PhotoSwipe
1304
+ const uid = ++this._uid;
1305
+ Promise.all(promiseArray).then((iterableModules) => {
1306
+ if (this.shouldOpen) {
1307
+ const mainModule = iterableModules[0];
1308
+ this._openPhotoswipe(mainModule, uid);
1309
+ }
1310
+ });
1311
+ }
1312
+
1313
+ _openPhotoswipe(module, uid) {
1314
+ // Cancel opening if UID doesn't match the current one
1315
+ // (if user clicked on another gallery item before current was loaded).
1316
+ //
1317
+ // Or if shouldOpen flag is set to false
1318
+ // (developer may modify it via public API)
1319
+ if (uid !== this._uid && this.shouldOpen) {
1320
+ return;
1321
+ }
1322
+
1323
+ this.shouldOpen = false;
1324
+
1325
+ // PhotoSwipe is already open
1326
+ if (window.pswp) {
1327
+ return;
1328
+ }
1329
+
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
1334
+
1335
+ this.pswp = pswp;
1336
+ window.pswp = pswp;
1337
+
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);
1342
+ });
1343
+ });
1344
+
1345
+ // same with filters
1346
+ Object.keys(this._filters).forEach((name) => {
1347
+ this._filters[name].forEach((filter) => {
1348
+ pswp.addFilter(name, filter.fn, filter.priority);
1349
+ });
1350
+ });
1351
+
1352
+ if (this._preloadedContent) {
1353
+ pswp.contentLoader.addToCache(this._preloadedContent);
1354
+ this._preloadedContent = null;
1355
+ }
1356
+
1357
+ pswp.on('destroy', () => {
1358
+ // clean up public variables
1359
+ this.pswp = null;
1360
+ window.pswp = null;
1361
+ });
1362
+
1363
+ pswp.init();
1364
+ }
1365
+
1366
+ destroy() {
1367
+ if (this.pswp) {
1368
+ this.pswp.destroy();
1369
+ }
1370
+
1371
+ 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
+ });
1378
+ }
1379
+ }
1380
+
1381
+ export default PhotoSwipeLightbox;
1382
+ //# sourceMappingURL=photoswipe-lightbox.esm.js.map