@tomasvn/glightbox 0.0.0-stage → 3.3.4

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 (46) hide show
  1. package/.editorconfig +16 -0
  2. package/.eslintignore +3 -0
  3. package/.eslintrc.json +94 -0
  4. package/.github/FUNDING.yml +3 -0
  5. package/.github/ISSUE_TEMPLATE/bug_report.md +41 -0
  6. package/.github/ISSUE_TEMPLATE/feature_request.md +23 -0
  7. package/.github/stale.yml +21 -0
  8. package/.github/workflows/greetings.yml +13 -0
  9. package/.husky/pre-commit +3 -0
  10. package/.lando.yml +10 -0
  11. package/.prettierignore +3 -0
  12. package/.prettierrc.json +30 -0
  13. package/CHANGELOG.md +259 -0
  14. package/CONTRIBUTING.md +29 -0
  15. package/LICENSE.md +21 -0
  16. package/README.md +714 -2
  17. package/bower.json +29 -0
  18. package/development/build.js +44 -0
  19. package/development/jscompiler.js +90 -0
  20. package/development/notifications.js +15 -0
  21. package/development/package.js +116 -0
  22. package/development/postcss.js +63 -0
  23. package/development/watcher.js +105 -0
  24. package/dist/css/glightbox.css +939 -0
  25. package/dist/css/glightbox.min.css +1 -0
  26. package/dist/js/glightbox.js +3109 -0
  27. package/dist/js/glightbox.min.js +1 -0
  28. package/index.d.ts +586 -0
  29. package/package.json +60 -4
  30. package/src/js/.jshintrc +6 -0
  31. package/src/js/core/drag.js +203 -0
  32. package/src/js/core/keyboard-navigation.js +101 -0
  33. package/src/js/core/slide-parser.js +236 -0
  34. package/src/js/core/slide.js +251 -0
  35. package/src/js/core/touch-events.js +378 -0
  36. package/src/js/core/touch-navigation.js +291 -0
  37. package/src/js/core/zoom.js +156 -0
  38. package/src/js/glightbox.js +1351 -0
  39. package/src/js/slides/iframe.js +24 -0
  40. package/src/js/slides/image.js +56 -0
  41. package/src/js/slides/inline.js +71 -0
  42. package/src/js/slides/video.js +108 -0
  43. package/src/js/utils/helpers.js +541 -0
  44. package/src/postcss/glightbox.css +725 -0
  45. package/tests/slide.test.mjs +130 -0
  46. package/tests/touch-navigation.test.mjs +13 -0
@@ -0,0 +1,1351 @@
1
+ /**
2
+ * GLightbox
3
+ * Awesome pure javascript lightbox
4
+ * made by https://www.biati.digital
5
+ * Github: https://github.com/biati-digital/glightbox
6
+ */
7
+
8
+ import keyboardNavigation from './core/keyboard-navigation.js';
9
+ import Slide from './core/slide.js';
10
+ import touchNavigation from './core/touch-navigation.js';
11
+ import * as _ from './utils/helpers.js';
12
+
13
+ const version = '3.3.4';
14
+ const isMobile = _.isMobile();
15
+ const isTouch = _.isTouch();
16
+ const html = document.getElementsByTagName('html')[0];
17
+
18
+ const defaults = {
19
+ selector: '.glightbox',
20
+ elements: null,
21
+ skin: 'clean',
22
+ theme: 'clean',
23
+ closeButton: true,
24
+ closeButtonText: 'Close',
25
+ prevButtonText: 'Previous',
26
+ nextButtonText: 'Next',
27
+ startAt: null,
28
+ autoplayVideos: true,
29
+ autofocusVideos: true,
30
+ descPosition: 'bottom',
31
+ width: '900px',
32
+ height: '506px',
33
+ videosWidth: '960px',
34
+ beforeSlideChange: null,
35
+ afterSlideChange: null,
36
+ beforeSlideLoad: null,
37
+ afterSlideLoad: null,
38
+ slideInserted: null,
39
+ slideRemoved: null,
40
+ slideExtraAttributes: null,
41
+ onOpen: null,
42
+ onClose: null,
43
+ loop: false,
44
+ zoomable: true,
45
+ draggable: true,
46
+ dragAutoSnap: false,
47
+ dragToleranceX: 40,
48
+ dragToleranceY: 65,
49
+ preload: true,
50
+ oneSlidePerOpen: false,
51
+ touchNavigation: true,
52
+ touchFollowAxis: true,
53
+ keyboardNavigation: true,
54
+ closeOnOutsideClick: true,
55
+ plugins: false,
56
+ plyr: {
57
+ css: 'https://cdn.plyr.io/3.6.12/plyr.css',
58
+ js: 'https://cdn.plyr.io/3.6.12/plyr.js',
59
+ config: {
60
+ ratio: '16:9', // or '4:3'
61
+ fullscreen: { enabled: true, iosNative: true },
62
+ youtube: {
63
+ noCookie: true,
64
+ rel: 0,
65
+ showinfo: 0,
66
+ iv_load_policy: 3 // eslint-disable-line camelcase
67
+ },
68
+ vimeo: {
69
+ byline: false,
70
+ portrait: false,
71
+ title: false,
72
+ transparent: false
73
+ }
74
+ }
75
+ },
76
+ openEffect: 'zoom', // fade, zoom, none
77
+ closeEffect: 'zoom', // fade, zoom, none
78
+ slideEffect: 'slide', // fade, slide, zoom, none
79
+ moreText: 'See more',
80
+ lessText: 'See less',
81
+ moreLength: 60,
82
+ cssEfects: {
83
+ fade: { in: 'fadeIn', out: 'fadeOut' },
84
+ zoom: { in: 'zoomIn', out: 'zoomOut' },
85
+ slide: { in: 'slideInRight', out: 'slideOutLeft' },
86
+ slideBack: { in: 'slideInLeft', out: 'slideOutRight' },
87
+ none: { in: 'none', out: 'none' }
88
+ },
89
+ svg: {
90
+ close: '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" xml:space="preserve"><g><g><path d="M505.943,6.058c-8.077-8.077-21.172-8.077-29.249,0L6.058,476.693c-8.077,8.077-8.077,21.172,0,29.249C10.096,509.982,15.39,512,20.683,512c5.293,0,10.586-2.019,14.625-6.059L505.943,35.306C514.019,27.23,514.019,14.135,505.943,6.058z"/></g></g><g><g><path d="M505.942,476.694L35.306,6.059c-8.076-8.077-21.172-8.077-29.248,0c-8.077,8.076-8.077,21.171,0,29.248l470.636,470.636c4.038,4.039,9.332,6.058,14.625,6.058c5.293,0,10.587-2.019,14.624-6.057C514.018,497.866,514.018,484.771,505.942,476.694z"/></g></g></svg>',
91
+ next: '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 477.175 477.175" xml:space="preserve"> <g><path d="M360.731,229.075l-225.1-225.1c-5.3-5.3-13.8-5.3-19.1,0s-5.3,13.8,0,19.1l215.5,215.5l-215.5,215.5c-5.3,5.3-5.3,13.8,0,19.1c2.6,2.6,6.1,4,9.5,4c3.4,0,6.9-1.3,9.5-4l225.1-225.1C365.931,242.875,365.931,234.275,360.731,229.075z"/></g></svg>',
92
+ prev: '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 477.175 477.175" xml:space="preserve"><g><path d="M145.188,238.575l215.5-215.5c5.3-5.3,5.3-13.8,0-19.1s-13.8-5.3-19.1,0l-225.1,225.1c-5.3,5.3-5.3,13.8,0,19.1l225.1,225c2.6,2.6,6.1,4,9.5,4s6.9-1.3,9.5-4c5.3-5.3,5.3-13.8,0-19.1L145.188,238.575z"/></g></svg>'
93
+ }
94
+ };
95
+
96
+ // You can pass your own slide structure
97
+ // just make sure that add the same classes so they are populated
98
+ // title class = gslide-title
99
+ // desc class = gslide-desc
100
+ // prev arrow class = gnext
101
+ // next arrow id = gprev
102
+ // close id = gclose
103
+ defaults.slideHTML = `<div class="gslide">
104
+ <div class="gslide-inner-content">
105
+ <div class="ginner-container">
106
+ <div class="gslide-media">
107
+ </div>
108
+ <div class="gslide-description">
109
+ <div class="gdesc-inner">
110
+ <h4 class="gslide-title"></h4>
111
+ <div class="gslide-desc"></div>
112
+ </div>
113
+ </div>
114
+ </div>
115
+ </div>
116
+ </div>`;
117
+
118
+ defaults.lightboxHTML = `<div id="glightbox-body" class="glightbox-container" tabindex="-1" role="dialog" aria-hidden="false">
119
+ <div class="gloader visible"></div>
120
+ <div class="goverlay"></div>
121
+ <div class="gcontainer">
122
+ <div id="glightbox-slider" class="gslider"></div>
123
+ <button class="gclose gbtn" aria-label="{closeButtonText}" data-taborder="3">{closeSVG}</button>
124
+ <button class="gprev gbtn" aria-label="{prevButtonText}" data-taborder="2">{prevSVG}</button>
125
+ <button class="gnext gbtn" aria-label="{nextButtonText}" data-taborder="1">{nextSVG}</button>
126
+ </div>
127
+ </div>`;
128
+
129
+ /**
130
+ * GLightbox Class
131
+ * Class and public methods
132
+ */
133
+ class GlightboxInit {
134
+ constructor(options = {}) {
135
+ this.customOptions = options;
136
+ this.settings = _.extend(defaults, options);
137
+ this.effectsClasses = this.getAnimationClasses();
138
+ this.videoPlayers = {};
139
+ this.apiEvents = [];
140
+ this.fullElementsList = false;
141
+ }
142
+
143
+ init() {
144
+ const selector = this.getSelector();
145
+
146
+ if (selector) {
147
+ this.baseEvents = _.addEvent('click', {
148
+ onElement: selector,
149
+ withCallback: (e, target) => {
150
+ e.preventDefault();
151
+ this.open(target);
152
+ }
153
+ });
154
+ }
155
+
156
+ this.elements = this.getElements();
157
+ }
158
+
159
+ open(element = null, startAt = null) {
160
+ if (this.elements.length === 0) {
161
+ return false;
162
+ }
163
+
164
+ this.activeSlide = null;
165
+ this.prevActiveSlideIndex = null;
166
+ this.prevActiveSlide = null;
167
+ let index = _.isNumber(startAt) ? startAt : this.settings.startAt;
168
+
169
+ if (_.isNode(element)) {
170
+ const gallery = element.getAttribute('data-gallery');
171
+ if (gallery) {
172
+ this.fullElementsList = this.elements;
173
+ this.elements = this.getGalleryElements(this.elements, gallery);
174
+ }
175
+ if (_.isNil(index)) {
176
+ // get the index of the element
177
+ index = this.getElementIndex(element);
178
+ if (index < 0) {
179
+ index = 0;
180
+ }
181
+ }
182
+ }
183
+
184
+ if (!_.isNumber(index)) {
185
+ index = 0;
186
+ }
187
+
188
+ this.build();
189
+
190
+ _.animateElement(this.overlay, this.settings.openEffect === 'none' ? 'none' : this.settings.cssEfects.fade.in);
191
+
192
+ const body = document.body;
193
+
194
+ const scrollBar = window.innerWidth - document.documentElement.clientWidth;
195
+ if (scrollBar > 0) {
196
+ var styleSheet = document.createElement('style');
197
+ styleSheet.type = 'text/css';
198
+ styleSheet.className = 'gcss-styles';
199
+ styleSheet.innerText = `.gscrollbar-fixer {margin-right: ${scrollBar}px}`;
200
+ document.head.appendChild(styleSheet);
201
+ _.addClass(body, 'gscrollbar-fixer');
202
+ }
203
+
204
+ _.addClass(body, 'glightbox-open');
205
+ _.addClass(html, 'glightbox-open');
206
+ if (isMobile) {
207
+ _.addClass(document.body, 'glightbox-mobile');
208
+ this.settings.slideEffect = 'slide';
209
+ }
210
+
211
+ this.showSlide(index, true);
212
+
213
+ if (this.elements.length === 1) {
214
+ _.addClass(this.prevButton, 'glightbox-button-hidden');
215
+ _.addClass(this.nextButton, 'glightbox-button-hidden');
216
+ } else {
217
+ _.removeClass(this.prevButton, 'glightbox-button-hidden');
218
+ _.removeClass(this.nextButton, 'glightbox-button-hidden');
219
+ }
220
+ this.lightboxOpen = true;
221
+
222
+ this.trigger('open');
223
+
224
+ // settings.onOpen is deprecated and will be removed in a future update
225
+ if (_.isFunction(this.settings.onOpen)) {
226
+ this.settings.onOpen();
227
+ }
228
+ if (isTouch && this.settings.touchNavigation) {
229
+ touchNavigation(this);
230
+ }
231
+ if (this.settings.keyboardNavigation) {
232
+ keyboardNavigation(this);
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Open at specific index
238
+ * @param {int} index
239
+ */
240
+ openAt(index = 0) {
241
+ this.open(null, index);
242
+ }
243
+
244
+ /**
245
+ * Set Slide
246
+ */
247
+ showSlide(index = 0, first = false) {
248
+ _.show(this.loader);
249
+ this.index = parseInt(index);
250
+
251
+ let current = this.slidesContainer.querySelector('.current');
252
+ if (current) {
253
+ _.removeClass(current, 'current');
254
+ }
255
+
256
+ // hide prev slide
257
+ this.slideAnimateOut();
258
+
259
+ let slideNode = this.slidesContainer.querySelectorAll('.gslide')[index];
260
+
261
+ // Check if slide's content is alreay loaded
262
+ if (_.hasClass(slideNode, 'loaded')) {
263
+ this.slideAnimateIn(slideNode, first);
264
+ _.hide(this.loader);
265
+ } else {
266
+ // If not loaded add the slide content
267
+ _.show(this.loader);
268
+
269
+ const slide = this.elements[index];
270
+ const slideData = {
271
+ index: this.index,
272
+ slide: slideNode, //this will be removed in the future
273
+ slideNode: slideNode,
274
+ slideConfig: slide.slideConfig,
275
+ slideIndex: this.index,
276
+ trigger: slide.node,
277
+ player: null
278
+ };
279
+
280
+ this.trigger('slide_before_load', slideData);
281
+
282
+ slide.instance.setContent(slideNode, () => {
283
+ _.hide(this.loader);
284
+ this.resize();
285
+ this.slideAnimateIn(slideNode, first);
286
+ this.trigger('slide_after_load', slideData);
287
+ });
288
+ }
289
+
290
+ this.slideDescription = slideNode.querySelector('.gslide-description');
291
+ this.slideDescriptionContained = this.slideDescription && _.hasClass(this.slideDescription.parentNode, 'gslide-media');
292
+
293
+ // Preload subsequent slides
294
+ if (this.settings.preload) {
295
+ this.preloadSlide(index + 1);
296
+ this.preloadSlide(index - 1);
297
+ }
298
+
299
+ // Handle navigation arrows
300
+ this.updateNavigationClasses();
301
+
302
+ this.activeSlide = slideNode;
303
+ }
304
+
305
+ /**
306
+ * Preload slides
307
+ * @param {Int} index slide index
308
+ * @return {null}
309
+ */
310
+ preloadSlide(index) {
311
+ // Verify slide index, it can not be lower than 0
312
+ // and it can not be greater than the total elements
313
+ if (index < 0 || index > this.elements.length - 1) {
314
+ return false;
315
+ }
316
+
317
+ if (_.isNil(this.elements[index])) {
318
+ return false;
319
+ }
320
+
321
+ let slideNode = this.slidesContainer.querySelectorAll('.gslide')[index];
322
+ if (_.hasClass(slideNode, 'loaded')) {
323
+ return false;
324
+ }
325
+
326
+ const slide = this.elements[index];
327
+ const type = slide.type;
328
+ const slideData = {
329
+ index: index,
330
+ slide: slideNode, //this will be removed in the future
331
+ slideNode: slideNode,
332
+ slideConfig: slide.slideConfig,
333
+ slideIndex: index,
334
+ trigger: slide.node,
335
+ player: null
336
+ };
337
+
338
+ this.trigger('slide_before_load', slideData);
339
+
340
+ if (type === 'video' || type === 'external') {
341
+ setTimeout(() => {
342
+ slide.instance.setContent(slideNode, () => {
343
+ this.trigger('slide_after_load', slideData);
344
+ });
345
+ }, 200);
346
+ } else {
347
+ slide.instance.setContent(slideNode, () => {
348
+ this.trigger('slide_after_load', slideData);
349
+ });
350
+ }
351
+ }
352
+
353
+ /**
354
+ * Load previous slide
355
+ * calls goToslide
356
+ */
357
+ prevSlide() {
358
+ this.goToSlide(this.index - 1);
359
+ }
360
+
361
+ /**
362
+ * Load next slide
363
+ * calls goToslide
364
+ */
365
+ nextSlide() {
366
+ this.goToSlide(this.index + 1);
367
+ }
368
+
369
+ /**
370
+ * Go to sldei
371
+ * calls set slide
372
+ * @param {Int} - index
373
+ */
374
+ goToSlide(index = false) {
375
+ this.prevActiveSlide = this.activeSlide;
376
+ this.prevActiveSlideIndex = this.index;
377
+
378
+ if (!this.loop() && (index < 0 || index > this.elements.length - 1)) {
379
+ return false;
380
+ }
381
+ if (index < 0) {
382
+ index = this.elements.length - 1;
383
+ } else if (index >= this.elements.length) {
384
+ index = 0;
385
+ }
386
+ this.showSlide(index);
387
+ }
388
+
389
+ /**
390
+ * Insert slide
391
+ *
392
+ * @param { object } data
393
+ * @param { numeric } position
394
+ */
395
+ insertSlide(config = {}, index = -1) {
396
+ // Append at the end
397
+ if (index < 0) {
398
+ index = this.elements.length;
399
+ }
400
+
401
+ const slide = new Slide(config, this, index);
402
+ const data = slide.getConfig();
403
+ const slideInfo = _.extend({}, data);
404
+ const newSlide = slide.create();
405
+ const totalSlides = this.elements.length - 1;
406
+
407
+ slideInfo.index = index;
408
+ slideInfo.node = false;
409
+ slideInfo.instance = slide;
410
+ slideInfo.slideConfig = data;
411
+ this.elements.splice(index, 0, slideInfo);
412
+
413
+ let addedSlideNode = null;
414
+ let addedSlidePlayer = null;
415
+
416
+ if (this.slidesContainer) {
417
+ // Append at the end
418
+ if (index > totalSlides) {
419
+ this.slidesContainer.appendChild(newSlide);
420
+ } else {
421
+ // A current slide must exist in the position specified
422
+ // we need tp get that slide and insder the new slide before
423
+ let existingSlide = this.slidesContainer.querySelectorAll('.gslide')[index];
424
+ this.slidesContainer.insertBefore(newSlide, existingSlide);
425
+ }
426
+
427
+ if ((this.settings.preload && this.index == 0 && index == 0) || this.index - 1 == index || this.index + 1 == index) {
428
+ this.preloadSlide(index);
429
+ }
430
+
431
+ if (this.index === 0 && index === 0) {
432
+ this.index = 1;
433
+ }
434
+
435
+ this.updateNavigationClasses();
436
+
437
+ addedSlideNode = this.slidesContainer.querySelectorAll('.gslide')[index];
438
+ addedSlidePlayer = this.getSlidePlayerInstance(index);
439
+ slideInfo.slideNode = addedSlideNode;
440
+ }
441
+
442
+ this.trigger('slide_inserted', {
443
+ index: index,
444
+ slide: addedSlideNode,
445
+ slideNode: addedSlideNode,
446
+ slideConfig: data,
447
+ slideIndex: index,
448
+ trigger: null,
449
+ player: addedSlidePlayer
450
+ });
451
+
452
+ // Deprecated and will be removed in a future update
453
+ if (_.isFunction(this.settings.slideInserted)) {
454
+ this.settings.slideInserted({
455
+ index: index,
456
+ slide: addedSlideNode,
457
+ player: addedSlidePlayer
458
+ });
459
+ }
460
+ }
461
+
462
+ /**
463
+ * Remove slide
464
+ *
465
+ * @param { numeric } position
466
+ */
467
+ removeSlide(index = -1) {
468
+ if (index < 0 || index > this.elements.length - 1) {
469
+ return false;
470
+ }
471
+
472
+ const slide = this.slidesContainer && this.slidesContainer.querySelectorAll('.gslide')[index];
473
+
474
+ if (slide) {
475
+ if (this.getActiveSlideIndex() == index) {
476
+ if (index == this.elements.length - 1) {
477
+ this.prevSlide();
478
+ } else {
479
+ this.nextSlide();
480
+ }
481
+ }
482
+ slide.parentNode.removeChild(slide);
483
+ }
484
+ this.elements.splice(index, 1);
485
+
486
+ this.trigger('slide_removed', index);
487
+
488
+ // Deprecated and will be removed in a future update
489
+ if (_.isFunction(this.settings.slideRemoved)) {
490
+ this.settings.slideRemoved(index);
491
+ }
492
+ }
493
+
494
+ /**
495
+ * Slide In
496
+ * @return {null}
497
+ */
498
+ slideAnimateIn(slide, first) {
499
+ let slideMedia = slide.querySelector('.gslide-media');
500
+ let slideDesc = slide.querySelector('.gslide-description');
501
+ let prevData = {
502
+ index: this.prevActiveSlideIndex,
503
+ slide: this.prevActiveSlide, //this will be removed in the future
504
+ slideNode: this.prevActiveSlide,
505
+ slideIndex: this.prevActiveSlide,
506
+ slideConfig: _.isNil(this.prevActiveSlideIndex) ? null : this.elements[this.prevActiveSlideIndex].slideConfig,
507
+ trigger: _.isNil(this.prevActiveSlideIndex) ? null : this.elements[this.prevActiveSlideIndex].node,
508
+ player: this.getSlidePlayerInstance(this.prevActiveSlideIndex)
509
+ };
510
+
511
+ let nextData = {
512
+ index: this.index,
513
+ slide: this.activeSlide, //this will be removed in the future
514
+ slideNode: this.activeSlide,
515
+ slideConfig: this.elements[this.index].slideConfig,
516
+ slideIndex: this.index,
517
+ trigger: this.elements[this.index].node,
518
+ player: this.getSlidePlayerInstance(this.index)
519
+ };
520
+ if (slideMedia.offsetWidth > 0 && slideDesc) {
521
+ _.hide(slideDesc);
522
+ slideDesc.style.display = '';
523
+ }
524
+
525
+ _.removeClass(slide, this.effectsClasses);
526
+
527
+ if (first) {
528
+ _.animateElement(slide, this.settings.cssEfects[this.settings.openEffect].in, () => {
529
+ if (this.settings.autoplayVideos) {
530
+ this.slidePlayerPlay(slide);
531
+ }
532
+
533
+ this.trigger('slide_changed', {
534
+ prev: prevData,
535
+ current: nextData
536
+ });
537
+
538
+ // settings.afterSlideChange is deprecated and will be removed in a future update
539
+ if (_.isFunction(this.settings.afterSlideChange)) {
540
+ this.settings.afterSlideChange.apply(this, [prevData, nextData]);
541
+ }
542
+ });
543
+ } else {
544
+ let effectName = this.settings.slideEffect;
545
+ let animIn = effectName !== 'none' ? this.settings.cssEfects[effectName].in : effectName;
546
+ if (this.prevActiveSlideIndex > this.index) {
547
+ if (this.settings.slideEffect == 'slide') {
548
+ animIn = this.settings.cssEfects.slideBack.in;
549
+ }
550
+ }
551
+ _.animateElement(slide, animIn, () => {
552
+ if (this.settings.autoplayVideos) {
553
+ this.slidePlayerPlay(slide);
554
+ }
555
+
556
+ this.trigger('slide_changed', {
557
+ prev: prevData,
558
+ current: nextData
559
+ });
560
+
561
+ // settings.afterSlideChange is deprecated and will be removed in a future update
562
+ if (_.isFunction(this.settings.afterSlideChange)) {
563
+ this.settings.afterSlideChange.apply(this, [prevData, nextData]);
564
+ }
565
+ });
566
+ }
567
+
568
+ setTimeout(() => {
569
+ this.resize(slide);
570
+ }, 100);
571
+ _.addClass(slide, 'current');
572
+ }
573
+
574
+ /**
575
+ * Slide out
576
+ */
577
+ slideAnimateOut() {
578
+ if (!this.prevActiveSlide) {
579
+ return false;
580
+ }
581
+
582
+ let prevSlide = this.prevActiveSlide;
583
+ _.removeClass(prevSlide, this.effectsClasses);
584
+ _.addClass(prevSlide, 'prev');
585
+
586
+ let animation = this.settings.slideEffect;
587
+ let animOut = animation !== 'none' ? this.settings.cssEfects[animation].out : animation;
588
+
589
+ this.slidePlayerPause(prevSlide);
590
+
591
+ this.trigger('slide_before_change', {
592
+ prev: {
593
+ index: this.prevActiveSlideIndex, //this will be removed in the future
594
+ slide: this.prevActiveSlide, //this will be removed in the future
595
+ slideNode: this.prevActiveSlide,
596
+ slideIndex: this.prevActiveSlideIndex,
597
+ slideConfig: _.isNil(this.prevActiveSlideIndex) ? null : this.elements[this.prevActiveSlideIndex].slideConfig,
598
+ trigger: _.isNil(this.prevActiveSlideIndex) ? null : this.elements[this.prevActiveSlideIndex].node,
599
+ player: this.getSlidePlayerInstance(this.prevActiveSlideIndex)
600
+ },
601
+ current: {
602
+ index: this.index, //this will be removed in the future
603
+ slide: this.activeSlide, //this will be removed in the future
604
+ slideNode: this.activeSlide,
605
+ slideIndex: this.index,
606
+ slideConfig: this.elements[this.index].slideConfig,
607
+ trigger: this.elements[this.index].node,
608
+ player: this.getSlidePlayerInstance(this.index)
609
+ }
610
+ });
611
+
612
+ // settings.beforeSlideChange is deprecated and will be removed in a future update
613
+ if (_.isFunction(this.settings.beforeSlideChange)) {
614
+ this.settings.beforeSlideChange.apply(this, [
615
+ {
616
+ index: this.prevActiveSlideIndex,
617
+ slide: this.prevActiveSlide,
618
+ player: this.getSlidePlayerInstance(this.prevActiveSlideIndex)
619
+ },
620
+ {
621
+ index: this.index,
622
+ slide: this.activeSlide,
623
+ player: this.getSlidePlayerInstance(this.index)
624
+ }
625
+ ]);
626
+ }
627
+ if (this.prevActiveSlideIndex > this.index && this.settings.slideEffect == 'slide') {
628
+ // going back
629
+ animOut = this.settings.cssEfects.slideBack.out;
630
+ }
631
+ _.animateElement(prevSlide, animOut, () => {
632
+ let container = prevSlide.querySelector('.ginner-container');
633
+ let media = prevSlide.querySelector('.gslide-media');
634
+ let desc = prevSlide.querySelector('.gslide-description');
635
+
636
+ container.style.transform = '';
637
+ media.style.transform = '';
638
+ _.removeClass(media, 'greset');
639
+ media.style.opacity = '';
640
+ if (desc) {
641
+ desc.style.opacity = '';
642
+ }
643
+ _.removeClass(prevSlide, 'prev');
644
+ });
645
+ }
646
+
647
+ /**
648
+ * Get all defined players
649
+ */
650
+ getAllPlayers() {
651
+ return this.videoPlayers;
652
+ }
653
+
654
+ /**
655
+ * Get player at index
656
+ *
657
+ * @param index
658
+ * @return bool|object
659
+ */
660
+ getSlidePlayerInstance(index) {
661
+ const id = 'gvideo' + index;
662
+ const videoPlayers = this.getAllPlayers();
663
+
664
+ if (_.has(videoPlayers, id) && videoPlayers[id]) {
665
+ return videoPlayers[id];
666
+ }
667
+
668
+ return false;
669
+ }
670
+
671
+ /**
672
+ * Stop video at specified
673
+ * node or index
674
+ *
675
+ * @param slide node or index
676
+ * @return void
677
+ */
678
+ stopSlideVideo(slide) {
679
+ if (_.isNode(slide)) {
680
+ let node = slide.querySelector('.gvideo-wrapper');
681
+ if (node) {
682
+ slide = node.getAttribute('data-index');
683
+ }
684
+ }
685
+ console.log('stopSlideVideo is deprecated, use slidePlayerPause');
686
+ const player = this.getSlidePlayerInstance(slide);
687
+ if (player && player.playing) {
688
+ player.pause();
689
+ }
690
+ }
691
+
692
+ /**
693
+ * Stop player at specified index
694
+ *
695
+ * @param slide node or index
696
+ * @return void
697
+ */
698
+ slidePlayerPause(slide) {
699
+ if (_.isNode(slide)) {
700
+ let node = slide.querySelector('.gvideo-wrapper');
701
+ if (node) {
702
+ slide = node.getAttribute('data-index');
703
+ }
704
+ }
705
+ const player = this.getSlidePlayerInstance(slide);
706
+ if (player && player.playing) {
707
+ player.pause();
708
+ }
709
+ }
710
+
711
+ /**
712
+ * Play video at specified
713
+ * node or index
714
+ *
715
+ * @param slide node or index
716
+ * @return void
717
+ */
718
+ playSlideVideo(slide) {
719
+ if (_.isNode(slide)) {
720
+ let node = slide.querySelector('.gvideo-wrapper');
721
+ if (node) {
722
+ slide = node.getAttribute('data-index');
723
+ }
724
+ }
725
+ console.log('playSlideVideo is deprecated, use slidePlayerPlay');
726
+ const player = this.getSlidePlayerInstance(slide);
727
+ if (player && !player.playing) {
728
+ player.play();
729
+ }
730
+ }
731
+
732
+ /**
733
+ * Play media player at specified
734
+ * node or index
735
+ *
736
+ * @param slide node or index
737
+ * @return void
738
+ */
739
+ slidePlayerPlay(slide) {
740
+ // Do not autoplay on mobile
741
+ // plyr does not handle well the errors
742
+ // and the player becomes unplayable
743
+ if (isMobile && !this.settings.plyr.config?.muted) {
744
+ return;
745
+ }
746
+
747
+ if (_.isNode(slide)) {
748
+ let node = slide.querySelector('.gvideo-wrapper');
749
+ if (node) {
750
+ slide = node.getAttribute('data-index');
751
+ }
752
+ }
753
+
754
+ const player = this.getSlidePlayerInstance(slide);
755
+
756
+ if (player && !player.playing) {
757
+ player.play();
758
+ if (this.settings.autofocusVideos) {
759
+ player.elements.container.focus();
760
+ }
761
+ }
762
+ }
763
+
764
+ /**
765
+ * Set the entire elements
766
+ * in the gallery, it replaces all
767
+ * the existing elements
768
+ * with the specified list
769
+ *
770
+ * @param {array} elements
771
+ */
772
+ setElements(elements) {
773
+ this.settings.elements = false;
774
+
775
+ const newElements = [];
776
+
777
+ if (elements && elements.length) {
778
+ _.each(elements, (el, i) => {
779
+ const slide = new Slide(el, this, i);
780
+ const data = slide.getConfig();
781
+ const slideInfo = _.extend({}, data);
782
+
783
+ slideInfo.slideConfig = data;
784
+ slideInfo.instance = slide;
785
+ slideInfo.index = i;
786
+ newElements.push(slideInfo);
787
+ });
788
+ }
789
+
790
+ this.elements = newElements;
791
+
792
+ if (this.lightboxOpen) {
793
+ this.slidesContainer.innerHTML = '';
794
+
795
+ if (this.elements.length) {
796
+ _.each(this.elements, () => {
797
+ let slide = _.createHTML(this.settings.slideHTML);
798
+ this.slidesContainer.appendChild(slide);
799
+ });
800
+ this.showSlide(0, true);
801
+ }
802
+ }
803
+ }
804
+
805
+ /**
806
+ * Return the index
807
+ * of the specified node,
808
+ * this node is for example an image, link, etc.
809
+ * that when clicked it opens the lightbox
810
+ * its position in the elements array can change
811
+ * when using insertSlide or removeSlide so we
812
+ * need to find it in the elements list
813
+ *
814
+ * @param {node} node
815
+ * @return bool|int
816
+ */
817
+ getElementIndex(node) {
818
+ let index = false;
819
+ _.each(this.elements, (el, i) => {
820
+ if (_.has(el, 'node') && el.node == node) {
821
+ index = i;
822
+ return true; // exit loop
823
+ }
824
+ });
825
+
826
+ return index;
827
+ }
828
+
829
+ /**
830
+ * Get elements
831
+ * returns an array containing all
832
+ * the elements that must be displayed in the
833
+ * lightbox
834
+ *
835
+ * @return { array }
836
+ */
837
+ getElements() {
838
+ let list = [];
839
+ this.elements = this.elements ? this.elements : [];
840
+
841
+ if (!_.isNil(this.settings.elements) && _.isArray(this.settings.elements) && this.settings.elements.length) {
842
+ _.each(this.settings.elements, (el, i) => {
843
+ const slide = new Slide(el, this, i);
844
+ const elData = slide.getConfig();
845
+ const slideInfo = _.extend({}, elData);
846
+
847
+ slideInfo.node = false;
848
+ slideInfo.index = i;
849
+ slideInfo.instance = slide;
850
+ slideInfo.slideConfig = elData;
851
+ list.push(slideInfo);
852
+ });
853
+ }
854
+
855
+ let nodes = false;
856
+ let selector = this.getSelector();
857
+
858
+ if (selector) {
859
+ nodes = document.querySelectorAll(this.getSelector());
860
+ }
861
+
862
+ if (!nodes) {
863
+ return list;
864
+ }
865
+
866
+ _.each(nodes, (el, i) => {
867
+ const slide = new Slide(el, this, i);
868
+ const elData = slide.getConfig();
869
+ const slideInfo = _.extend({}, elData);
870
+
871
+ slideInfo.node = el;
872
+ slideInfo.index = i;
873
+ slideInfo.instance = slide;
874
+ slideInfo.slideConfig = elData;
875
+ slideInfo.gallery = el.getAttribute('data-gallery');
876
+ list.push(slideInfo);
877
+ });
878
+
879
+ return list;
880
+ }
881
+
882
+ /**
883
+ * Return only the elements
884
+ * from a specific gallery
885
+ *
886
+ * @return array
887
+ */
888
+ getGalleryElements(list, gallery) {
889
+ return list.filter((el) => {
890
+ return el.gallery == gallery;
891
+ });
892
+ }
893
+
894
+ /**
895
+ * Get selector
896
+ */
897
+ getSelector() {
898
+ if (this.settings.elements) {
899
+ return false;
900
+ }
901
+ if (this.settings.selector && this.settings.selector.substring(0, 5) == 'data-') {
902
+ return `*[${this.settings.selector}]`;
903
+ }
904
+ return this.settings.selector;
905
+ }
906
+
907
+ /**
908
+ * Get the active slide
909
+ */
910
+ getActiveSlide() {
911
+ return this.slidesContainer.querySelectorAll('.gslide')[this.index];
912
+ }
913
+
914
+ /**
915
+ * Get the active index
916
+ */
917
+ getActiveSlideIndex() {
918
+ return this.index;
919
+ }
920
+
921
+ /**
922
+ * Get the defined
923
+ * effects as string
924
+ */
925
+ getAnimationClasses() {
926
+ let effects = [];
927
+ for (let key in this.settings.cssEfects) {
928
+ if (this.settings.cssEfects.hasOwnProperty(key)) {
929
+ let effect = this.settings.cssEfects[key];
930
+ effects.push(`g${effect.in}`);
931
+ effects.push(`g${effect.out}`);
932
+ }
933
+ }
934
+ return effects.join(' ');
935
+ }
936
+
937
+ /**
938
+ * Build the structure
939
+ * @return {null}
940
+ */
941
+ build() {
942
+ if (this.built) {
943
+ return false;
944
+ }
945
+
946
+ // TODO: :scope is not supported on IE or first Edge. so we'll
947
+ // update this when IE support is removed to use newer code
948
+ //const children = document.body.querySelectorAll(':scope > *');
949
+ const children = document.body.childNodes;
950
+ const bodyChildElms = [];
951
+ _.each(children, (el) => {
952
+ if (el.parentNode == document.body && el.nodeName.charAt(0) !== '#' && el.hasAttribute && !el.hasAttribute('aria-hidden')) {
953
+ bodyChildElms.push(el);
954
+ el.setAttribute('aria-hidden', 'true');
955
+ }
956
+ });
957
+
958
+ const nextSVG = _.has(this.settings.svg, 'next') ? this.settings.svg.next : '';
959
+ const prevSVG = _.has(this.settings.svg, 'prev') ? this.settings.svg.prev : '';
960
+ const closeSVG = _.has(this.settings.svg, 'close') ? this.settings.svg.close : '';
961
+ const closeButtonText = this.settings.closeButtonText ?? 'Close';
962
+ const prevButtonText = this.settings.prevButtonText ?? 'Previous';
963
+ const nextButtonText = this.settings.nextButtonText ?? 'Next';
964
+
965
+ let lightboxHTML = this.settings.lightboxHTML;
966
+ lightboxHTML = lightboxHTML.replace(/{nextSVG}/g, nextSVG);
967
+ lightboxHTML = lightboxHTML.replace(/{prevSVG}/g, prevSVG);
968
+ lightboxHTML = lightboxHTML.replace(/{closeSVG}/g, closeSVG);
969
+ lightboxHTML = lightboxHTML.replace(/{closeButtonText}/g, closeButtonText);
970
+ lightboxHTML = lightboxHTML.replace(/{prevButtonText}/g, prevButtonText);
971
+ lightboxHTML = lightboxHTML.replace(/{nextButtonText}/g, nextButtonText);
972
+
973
+ lightboxHTML = _.createHTML(lightboxHTML);
974
+ document.body.appendChild(lightboxHTML);
975
+
976
+ const modal = document.getElementById('glightbox-body');
977
+ this.modal = modal;
978
+ let closeButton = modal.querySelector('.gclose');
979
+ this.prevButton = modal.querySelector('.gprev');
980
+ this.nextButton = modal.querySelector('.gnext');
981
+ this.overlay = modal.querySelector('.goverlay');
982
+ this.loader = modal.querySelector('.gloader');
983
+ this.slidesContainer = document.getElementById('glightbox-slider');
984
+ this.bodyHiddenChildElms = bodyChildElms;
985
+ this.events = {};
986
+
987
+ _.addClass(this.modal, 'glightbox-' + this.settings.skin);
988
+
989
+ if (this.settings.closeButton && closeButton) {
990
+ this.events['close'] = _.addEvent('click', {
991
+ onElement: closeButton,
992
+ withCallback: (e, target) => {
993
+ e.preventDefault();
994
+ this.close();
995
+ }
996
+ });
997
+ }
998
+ if (closeButton && !this.settings.closeButton) {
999
+ closeButton.parentNode.removeChild(closeButton);
1000
+ }
1001
+
1002
+ if (this.nextButton) {
1003
+ this.events['next'] = _.addEvent('click', {
1004
+ onElement: this.nextButton,
1005
+ withCallback: (e, target) => {
1006
+ e.preventDefault();
1007
+ this.nextSlide();
1008
+ }
1009
+ });
1010
+ }
1011
+
1012
+ if (this.prevButton) {
1013
+ this.events['prev'] = _.addEvent('click', {
1014
+ onElement: this.prevButton,
1015
+ withCallback: (e, target) => {
1016
+ e.preventDefault();
1017
+ this.prevSlide();
1018
+ }
1019
+ });
1020
+ }
1021
+ if (this.settings.closeOnOutsideClick) {
1022
+ this.events['outClose'] = _.addEvent('click', {
1023
+ onElement: modal,
1024
+ withCallback: (e, target) => {
1025
+ if (!this.preventOutsideClick && !_.hasClass(document.body, 'glightbox-mobile') && !_.closest(e.target, '.ginner-container')) {
1026
+ if (!_.closest(e.target, '.gbtn') && !_.hasClass(e.target, 'gnext') && !_.hasClass(e.target, 'gprev')) {
1027
+ this.close();
1028
+ }
1029
+ }
1030
+ }
1031
+ });
1032
+ }
1033
+
1034
+ _.each(this.elements, (slide, i) => {
1035
+ this.slidesContainer.appendChild(slide.instance.create());
1036
+ slide.slideNode = this.slidesContainer.querySelectorAll('.gslide')[i];
1037
+ });
1038
+ if (isTouch) {
1039
+ _.addClass(document.body, 'glightbox-touch');
1040
+ }
1041
+
1042
+ this.events['resize'] = _.addEvent('resize', {
1043
+ onElement: window,
1044
+ withCallback: () => {
1045
+ this.resize();
1046
+ }
1047
+ });
1048
+
1049
+ this.built = true;
1050
+ }
1051
+
1052
+ /**
1053
+ * Handle resize
1054
+ * Create only to handle
1055
+ * when the height of the screen
1056
+ * is lower than the slide content
1057
+ * this helps to resize videos vertically
1058
+ * and images with description
1059
+ */
1060
+ resize(slide = null) {
1061
+ slide = !slide ? this.activeSlide : slide;
1062
+
1063
+ if (!slide || _.hasClass(slide, 'zoomed')) {
1064
+ return;
1065
+ }
1066
+
1067
+ const winSize = _.windowSize();
1068
+ const video = slide.querySelector('.gvideo-wrapper');
1069
+ const image = slide.querySelector('.gslide-image');
1070
+ const description = this.slideDescription;
1071
+
1072
+ let winWidth = winSize.width;
1073
+ let winHeight = winSize.height;
1074
+
1075
+ if (winWidth <= 768) {
1076
+ _.addClass(document.body, 'glightbox-mobile');
1077
+ } else {
1078
+ _.removeClass(document.body, 'glightbox-mobile');
1079
+ }
1080
+
1081
+ if (!video && !image) {
1082
+ return;
1083
+ }
1084
+
1085
+ let descriptionResize = false;
1086
+ if (description && (_.hasClass(description, 'description-bottom') || _.hasClass(description, 'description-top')) && !_.hasClass(description, 'gabsolute')) {
1087
+ descriptionResize = true;
1088
+ }
1089
+
1090
+ if (image) {
1091
+ if (winWidth <= 768) {
1092
+ let imgNode = image.querySelector('img');
1093
+ //imgNode.setAttribute('style', '');
1094
+ } else if (descriptionResize) {
1095
+ let descHeight = description.offsetHeight;
1096
+ let imgNode = image.querySelector('img');
1097
+
1098
+ // if a slide height is set via data-height, we want to use that
1099
+ // if not, we fall back to 100vh
1100
+ const slideTriggerNode = this.elements[this.index]?.node;
1101
+ let maxHeightValue = '100vh';
1102
+ if (slideTriggerNode) {
1103
+ maxHeightValue = slideTriggerNode.getAttribute('data-height') ?? maxHeightValue;
1104
+ }
1105
+
1106
+ imgNode.setAttribute('style', `max-height: calc(${maxHeightValue} - ${descHeight}px)`);
1107
+ description.setAttribute('style', `max-width: ${imgNode.offsetWidth}px;`);
1108
+ }
1109
+ }
1110
+
1111
+ if (video) {
1112
+ let ratio = _.has(this.settings.plyr.config, 'ratio') ? this.settings.plyr.config.ratio : '';
1113
+
1114
+ if (!ratio) {
1115
+ // If no ratio passed, calculate it using the video width and height
1116
+ // generated by Plyr
1117
+ const containerWidth = video.clientWidth;
1118
+ const containerHeight = video.clientHeight;
1119
+ const divisor = containerWidth / containerHeight;
1120
+ ratio = `${containerWidth / divisor}:${containerHeight / divisor}`;
1121
+ }
1122
+
1123
+ let videoRatio = ratio.split(':');
1124
+ let videoWidth = this.settings.videosWidth;
1125
+ let maxWidth = this.settings.videosWidth;
1126
+
1127
+ if (_.isNumber(videoWidth) || videoWidth.indexOf('px') !== -1) {
1128
+ maxWidth = parseInt(videoWidth);
1129
+ } else {
1130
+ // If video size is vw, vh or % convert it to pixels,
1131
+ // fallback to the current video size
1132
+ if (videoWidth.indexOf('vw') !== -1) {
1133
+ maxWidth = (winWidth * parseInt(videoWidth)) / 100;
1134
+ } else if (videoWidth.indexOf('vh') !== -1) {
1135
+ maxWidth = (winHeight * parseInt(videoWidth)) / 100;
1136
+ } else if (videoWidth.indexOf('%') !== -1) {
1137
+ maxWidth = (winWidth * parseInt(videoWidth)) / 100;
1138
+ } else {
1139
+ maxWidth = parseInt(video.clientWidth);
1140
+ }
1141
+ }
1142
+
1143
+ let maxHeight = maxWidth / (parseInt(videoRatio[0]) / parseInt(videoRatio[1]));
1144
+ maxHeight = Math.floor(maxHeight);
1145
+
1146
+ if (descriptionResize) {
1147
+ winHeight = winHeight - description.offsetHeight;
1148
+ }
1149
+
1150
+ if (maxWidth > winWidth || maxHeight > winHeight || (winHeight < maxHeight && winWidth > maxWidth)) {
1151
+ let vwidth = video.offsetWidth;
1152
+ let vheight = video.offsetHeight;
1153
+ let ratio = winHeight / vheight;
1154
+ let vsize = { width: vwidth * ratio, height: vheight * ratio };
1155
+ video.parentNode.setAttribute('style', `max-width: ${vsize.width}px`);
1156
+
1157
+ if (descriptionResize) {
1158
+ description.setAttribute('style', `max-width: ${vsize.width}px;`);
1159
+ }
1160
+ } else {
1161
+ video.parentNode.style.maxWidth = `${videoWidth}`;
1162
+ if (descriptionResize) {
1163
+ description.setAttribute('style', `max-width: ${videoWidth};`);
1164
+ }
1165
+ }
1166
+ }
1167
+ }
1168
+
1169
+ /**
1170
+ * Reload Lightbox
1171
+ * reload and apply events to nodes
1172
+ */
1173
+ reload() {
1174
+ this.init();
1175
+ }
1176
+
1177
+ /**
1178
+ * Update navigation classes on slide change
1179
+ */
1180
+ updateNavigationClasses() {
1181
+ const loop = this.loop();
1182
+ // Handle navigation arrows
1183
+ _.removeClass(this.nextButton, 'disabled');
1184
+ _.removeClass(this.prevButton, 'disabled');
1185
+
1186
+ if (this.index == 0 && this.elements.length - 1 == 0) {
1187
+ _.addClass(this.prevButton, 'disabled');
1188
+ _.addClass(this.nextButton, 'disabled');
1189
+ } else if (this.index === 0 && !loop) {
1190
+ _.addClass(this.prevButton, 'disabled');
1191
+ } else if (this.index === this.elements.length - 1 && !loop) {
1192
+ _.addClass(this.nextButton, 'disabled');
1193
+ }
1194
+ }
1195
+
1196
+ /**
1197
+ * Handle loop config
1198
+ */
1199
+ loop() {
1200
+ let loop = _.has(this.settings, 'loopAtEnd') ? this.settings.loopAtEnd : null;
1201
+ loop = _.has(this.settings, 'loop') ? this.settings.loop : loop;
1202
+
1203
+ return loop;
1204
+ }
1205
+
1206
+ /**
1207
+ * Close Lightbox
1208
+ * closes the lightbox and removes the slides
1209
+ * and some classes
1210
+ */
1211
+ close() {
1212
+ if (!this.lightboxOpen) {
1213
+ if (this.events) {
1214
+ for (let key in this.events) {
1215
+ if (this.events.hasOwnProperty(key)) {
1216
+ this.events[key].destroy();
1217
+ }
1218
+ }
1219
+ this.events = null;
1220
+ }
1221
+ return false;
1222
+ }
1223
+
1224
+ if (this.closing) {
1225
+ return false;
1226
+ }
1227
+ this.closing = true;
1228
+ this.slidePlayerPause(this.activeSlide);
1229
+
1230
+ if (this.fullElementsList) {
1231
+ this.elements = this.fullElementsList;
1232
+ }
1233
+
1234
+ if (this.bodyHiddenChildElms.length) {
1235
+ _.each(this.bodyHiddenChildElms, (el) => {
1236
+ el.removeAttribute('aria-hidden');
1237
+ });
1238
+ }
1239
+
1240
+ _.addClass(this.modal, 'glightbox-closing');
1241
+ _.animateElement(this.overlay, this.settings.openEffect == 'none' ? 'none' : this.settings.cssEfects.fade.out);
1242
+ _.animateElement(this.activeSlide, this.settings.cssEfects[this.settings.closeEffect].out, () => {
1243
+ this.activeSlide = null;
1244
+ this.prevActiveSlideIndex = null;
1245
+ this.prevActiveSlide = null;
1246
+ this.built = false;
1247
+
1248
+ if (this.events) {
1249
+ for (let key in this.events) {
1250
+ if (this.events.hasOwnProperty(key)) {
1251
+ this.events[key].destroy();
1252
+ }
1253
+ }
1254
+ this.events = null;
1255
+ }
1256
+
1257
+ const body = document.body;
1258
+ _.removeClass(html, 'glightbox-open');
1259
+ _.removeClass(body, 'glightbox-open touching gdesc-open glightbox-touch glightbox-mobile gscrollbar-fixer');
1260
+ this.modal.parentNode.removeChild(this.modal);
1261
+
1262
+ this.trigger('close');
1263
+
1264
+ // settings.onClose is deprecated and will be removed in a future update
1265
+ if (_.isFunction(this.settings.onClose)) {
1266
+ this.settings.onClose();
1267
+ }
1268
+
1269
+ const styles = document.querySelector('.gcss-styles');
1270
+ if (styles) {
1271
+ styles.parentNode.removeChild(styles);
1272
+ }
1273
+ this.lightboxOpen = false;
1274
+ this.closing = null;
1275
+ });
1276
+ }
1277
+
1278
+ /**
1279
+ * Destroy lightbox
1280
+ * and all events
1281
+ */
1282
+ destroy() {
1283
+ this.close();
1284
+ this.clearAllEvents();
1285
+
1286
+ if (this.baseEvents) {
1287
+ this.baseEvents.destroy();
1288
+ }
1289
+ }
1290
+
1291
+ /**
1292
+ * Set event
1293
+ */
1294
+ on(evt, callback, once = false) {
1295
+ if (!evt || !_.isFunction(callback)) {
1296
+ throw new TypeError('Event name and callback must be defined');
1297
+ }
1298
+ this.apiEvents.push({ evt, once, callback });
1299
+ }
1300
+
1301
+ /**
1302
+ * Set event
1303
+ */
1304
+ once(evt, callback) {
1305
+ this.on(evt, callback, true);
1306
+ }
1307
+
1308
+ /**
1309
+ * Triggers an specific event
1310
+ * with data
1311
+ *
1312
+ * @param string eventName
1313
+ */
1314
+ trigger(eventName, data = null) {
1315
+ const onceTriggered = [];
1316
+ _.each(this.apiEvents, (event, i) => {
1317
+ const { evt, once, callback } = event;
1318
+ if (evt == eventName) {
1319
+ callback(data);
1320
+ if (once) {
1321
+ onceTriggered.push(i);
1322
+ }
1323
+ }
1324
+ });
1325
+ if (onceTriggered.length) {
1326
+ _.each(onceTriggered, (i) => this.apiEvents.splice(i, 1));
1327
+ }
1328
+ }
1329
+
1330
+ /**
1331
+ * Removes all events
1332
+ * set using the API
1333
+ */
1334
+ clearAllEvents() {
1335
+ this.apiEvents.splice(0, this.apiEvents.length);
1336
+ }
1337
+
1338
+ /**
1339
+ * Get Version
1340
+ */
1341
+ version() {
1342
+ return version;
1343
+ }
1344
+ }
1345
+
1346
+ export default function (options = {}) {
1347
+ const instance = new GlightboxInit(options);
1348
+ instance.init();
1349
+
1350
+ return instance;
1351
+ }