@tomasvn/glightbox 3.3.3 → 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.
package/package.json CHANGED
@@ -1,10 +1,15 @@
1
1
  {
2
2
  "name": "@tomasvn/glightbox",
3
- "version": "3.3.3",
3
+ "version": "3.3.4",
4
4
  "description": "Pure Javascript lightbox",
5
5
  "main": "dist/js/glightbox.min.js",
6
6
  "scripts": {
7
- "watch": "node development/watcher.js"
7
+ "build": "node development/build.js",
8
+ "test": "node --test",
9
+ "watch": "node development/watcher.js",
10
+ "lint": "eslint \"src/js/**/*.js\"",
11
+ "format": "prettier --write \"src/js/**/*.js\"",
12
+ "prepare": "husky"
8
13
  },
9
14
  "publishConfig": {
10
15
  "access": "public"
@@ -36,8 +41,8 @@
36
41
  "clean-css": "^4.2.3",
37
42
  "css-mqpacker": "^7.0.0",
38
43
  "eslint": "^7.16.0",
39
- "fs-extra": "^8.1.0",
40
44
  "fs-jetpack": "^2.4.0",
45
+ "husky": "^9.1.7",
41
46
  "install": "^0.13.0",
42
47
  "node-notifier": "^5.4.0",
43
48
  "npm": "^6.14.10",
@@ -45,6 +50,7 @@
45
50
  "postcss-nested": "^4.1.1",
46
51
  "postcss-preset-env": "^6.5.0",
47
52
  "postcss-prettify": "^0.3.4",
53
+ "prettier": "^3.10.1",
48
54
  "rollup": "^1.32.1",
49
55
  "rollup-plugin-babel": "^4.4.0",
50
56
  "rollup-plugin-commonjs": "^9.2.0",
@@ -52,6 +58,5 @@
52
58
  "temporary": "^1.1.0",
53
59
  "terser": "^4.8.0",
54
60
  "uninstall": "0.0.0"
55
- },
56
- "dependencies": {}
61
+ }
57
62
  }
package/src/js/.jshintrc CHANGED
@@ -3,4 +3,4 @@
3
3
  "esversion": 6,
4
4
  "maxparams": 4,
5
5
  "asi": false
6
- }
6
+ }
@@ -50,11 +50,7 @@ export default class DragSlides {
50
50
 
51
51
  let clicked = e.target.nodeName.toLowerCase();
52
52
  let exludeClicks = ['input', 'select', 'textarea', 'button', 'a'];
53
- if (
54
- e.target.classList.contains('nodrag') ||
55
- closest(e.target, '.nodrag') ||
56
- exludeClicks.indexOf(clicked) !== -1
57
- ) {
53
+ if (e.target.classList.contains('nodrag') || closest(e.target, '.nodrag') || exludeClicks.indexOf(clicked) !== -1) {
58
54
  this.active = false;
59
55
  return;
60
56
  }
@@ -131,11 +127,7 @@ export default class DragSlides {
131
127
  let currentYInt = Math.abs(this.currentY);
132
128
 
133
129
  // Horizontal drag
134
- if (
135
- currentXInt > 0 &&
136
- currentXInt >= Math.abs(this.currentY) &&
137
- (!this.lastDirection || this.lastDirection == 'x')
138
- ) {
130
+ if (currentXInt > 0 && currentXInt >= Math.abs(this.currentY) && (!this.lastDirection || this.lastDirection == 'x')) {
139
131
  this.yOffset = 0;
140
132
  this.lastDirection = 'x';
141
133
  this.setTranslate(this.dragContainer, this.currentX, 0);
@@ -158,12 +150,7 @@ export default class DragSlides {
158
150
  }
159
151
 
160
152
  // Vertical drag
161
- if (
162
- this.toleranceY > 0 &&
163
- currentYInt > 0 &&
164
- currentYInt >= currentXInt &&
165
- (!this.lastDirection || this.lastDirection == 'y')
166
- ) {
153
+ if (this.toleranceY > 0 && currentYInt > 0 && currentYInt >= currentXInt && (!this.lastDirection || this.lastDirection == 'y')) {
167
154
  this.xOffset = 0;
168
155
  this.lastDirection = 'y';
169
156
  this.setTranslate(this.dragContainer, 0, this.currentY);
@@ -188,10 +175,7 @@ export default class DragSlides {
188
175
  if (currentXInt >= this.toleranceX) {
189
176
  let dragDir = this.currentX > 0 ? 'right' : 'left';
190
177
 
191
- if (
192
- (dragDir == 'left' && this.slide !== this.slide.parentNode.lastChild) ||
193
- (dragDir == 'right' && this.slide !== this.slide.parentNode.firstChild)
194
- ) {
178
+ if ((dragDir == 'left' && this.slide !== this.slide.parentNode.lastChild) || (dragDir == 'right' && this.slide !== this.slide.parentNode.firstChild)) {
195
179
  doChange = dragDir;
196
180
  }
197
181
  }
@@ -25,7 +25,10 @@ function getNextFocusElement(current = -1) {
25
25
  each(btns, (btn) => {
26
26
  orders.push(btn.getAttribute('data-taborder'));
27
27
  });
28
- const highestOrder = Math.max.apply(Math, orders.map((order) => parseInt(order)));
28
+ const highestOrder = Math.max.apply(
29
+ Math,
30
+ orders.map((order) => parseInt(order))
31
+ );
29
32
 
30
33
  let newIndex = current < 0 ? 1 : current + 1;
31
34
  if (newIndex > highestOrder) {
@@ -38,7 +38,12 @@ export default class SlideConfigParser {
38
38
  if (url.match(/\.(jpeg|jpg|jpe|gif|png|apn|webp|avif|svg)/) !== null) {
39
39
  return 'image';
40
40
  }
41
- if (url.match(/(youtube\.com|youtube-nocookie\.com)\/watch\?v=([a-zA-Z0-9\-_]+)/) || url.match(/youtu\.be\/([a-zA-Z0-9\-_]+)/) || url.match(/(youtube\.com|youtube-nocookie\.com)\/embed\/([a-zA-Z0-9\-_]+)/) || url.match(/(youtube\.com|youtube-nocookie\.com)\/shorts\/([a-zA-Z0-9\-_]+)/)) {
41
+ if (
42
+ url.match(/(youtube\.com|youtube-nocookie\.com)\/watch\?v=([a-zA-Z0-9\-_]+)/) ||
43
+ url.match(/youtu\.be\/([a-zA-Z0-9\-_]+)/) ||
44
+ url.match(/(youtube\.com|youtube-nocookie\.com)\/embed\/([a-zA-Z0-9\-_]+)/) ||
45
+ url.match(/(youtube\.com|youtube-nocookie\.com)\/shorts\/([a-zA-Z0-9\-_]+)/)
46
+ ) {
42
47
  return 'video';
43
48
  }
44
49
  if (url.match(/vimeo\.com\/([0-9]*)/)) {
@@ -184,6 +184,7 @@ export default class Slide {
184
184
  onElement: moreLink,
185
185
  withCallback: (event, target) => {
186
186
  event.preventDefault();
187
+ event.stopPropagation();
187
188
  const body = document.body;
188
189
 
189
190
  let desc = closest(target, '.gslide-desc');
@@ -191,13 +192,18 @@ export default class Slide {
191
192
  return false;
192
193
  }
193
194
 
194
- desc.innerHTML = data.description;
195
+ desc.innerHTML = `${data.description} <a href="#" class="desc-less">${this.instance.settings.lessText}</a>`;
195
196
  addClass(body, 'gdesc-open');
196
197
 
197
198
  let shortEvent = addEvent('click', {
198
199
  onElement: [body, closest(desc, '.gslide-description')],
199
200
  withCallback: (event, target) => {
200
- if (event.target.nodeName.toLowerCase() !== 'a') {
201
+ const isLessLink = hasClass(event.target, 'desc-less');
202
+ if (event.target.nodeName.toLowerCase() !== 'a' || isLessLink) {
203
+ event.stopPropagation();
204
+ if (isLessLink) {
205
+ event.preventDefault();
206
+ }
201
207
  removeClass(body, 'gdesc-open');
202
208
  addClass(body, 'gdesc-closed');
203
209
  desc.innerHTML = data.smallDescription;
@@ -8,7 +8,11 @@
8
8
  import TouchEvents from './touch-events.js';
9
9
  import { addEvent, addClass, removeClass, hasClass, closest, whichTransitionEvent, cssTransform, windowSize } from '../utils/helpers.js';
10
10
 
11
- function resetSlideMove(slide) {
11
+ export function resetSlideMove(slide) {
12
+ if (!slide) {
13
+ return;
14
+ }
15
+
12
16
  const transitionEnd = whichTransitionEvent();
13
17
  const windowWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
14
18
 
@@ -10,7 +10,7 @@ import Slide from './core/slide.js';
10
10
  import touchNavigation from './core/touch-navigation.js';
11
11
  import * as _ from './utils/helpers.js';
12
12
 
13
- const version = '3.3.1';
13
+ const version = '3.3.4';
14
14
  const isMobile = _.isMobile();
15
15
  const isTouch = _.isTouch();
16
16
  const html = document.getElementsByTagName('html')[0];
@@ -21,6 +21,9 @@ const defaults = {
21
21
  skin: 'clean',
22
22
  theme: 'clean',
23
23
  closeButton: true,
24
+ closeButtonText: 'Close',
25
+ prevButtonText: 'Previous',
26
+ nextButtonText: 'Next',
24
27
  startAt: null,
25
28
  autoplayVideos: true,
26
29
  autofocusVideos: true,
@@ -74,6 +77,7 @@ const defaults = {
74
77
  closeEffect: 'zoom', // fade, zoom, none
75
78
  slideEffect: 'slide', // fade, slide, zoom, none
76
79
  moreText: 'See more',
80
+ lessText: 'See less',
77
81
  moreLength: 60,
78
82
  cssEfects: {
79
83
  fade: { in: 'fadeIn', out: 'fadeOut' },
@@ -116,9 +120,9 @@ defaults.lightboxHTML = `<div id="glightbox-body" class="glightbox-container" ta
116
120
  <div class="goverlay"></div>
117
121
  <div class="gcontainer">
118
122
  <div id="glightbox-slider" class="gslider"></div>
119
- <button class="gclose gbtn" aria-label="Close" data-taborder="3">{closeSVG}</button>
120
- <button class="gprev gbtn" aria-label="Previous" data-taborder="2">{prevSVG}</button>
121
- <button class="gnext gbtn" aria-label="Next" data-taborder="1">{nextSVG}</button>
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>
122
126
  </div>
123
127
  </div>`;
124
128
 
@@ -954,11 +958,17 @@ class GlightboxInit {
954
958
  const nextSVG = _.has(this.settings.svg, 'next') ? this.settings.svg.next : '';
955
959
  const prevSVG = _.has(this.settings.svg, 'prev') ? this.settings.svg.prev : '';
956
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';
957
964
 
958
965
  let lightboxHTML = this.settings.lightboxHTML;
959
966
  lightboxHTML = lightboxHTML.replace(/{nextSVG}/g, nextSVG);
960
967
  lightboxHTML = lightboxHTML.replace(/{prevSVG}/g, prevSVG);
961
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);
962
972
 
963
973
  lightboxHTML = _.createHTML(lightboxHTML);
964
974
  document.body.appendChild(lightboxHTML);
@@ -37,7 +37,10 @@ export default function slideVideo(slide, data, index, callback) {
37
37
  // Set youtube videos
38
38
  if (
39
39
  !provider &&
40
- (url.match(/(youtube\.com|youtube-nocookie\.com)\/watch\?v=([a-zA-Z0-9\-_]+)/) || url.match(/youtu\.be\/([a-zA-Z0-9\-_]+)/) || url.match(/(youtube\.com|youtube-nocookie\.com)\/embed\/([a-zA-Z0-9\-_]+)/) || url.match(/(youtube\.com|youtube-nocookie\.com)\/shorts\/([a-zA-Z0-9\-_]+)/))
40
+ (url.match(/(youtube\.com|youtube-nocookie\.com)\/watch\?v=([a-zA-Z0-9\-_]+)/) ||
41
+ url.match(/youtu\.be\/([a-zA-Z0-9\-_]+)/) ||
42
+ url.match(/(youtube\.com|youtube-nocookie\.com)\/embed\/([a-zA-Z0-9\-_]+)/) ||
43
+ url.match(/(youtube\.com|youtube-nocookie\.com)\/shorts\/([a-zA-Z0-9\-_]+)/))
41
44
  ) {
42
45
  provider = 'youtube';
43
46
  }
@@ -370,7 +370,7 @@
370
370
  }
371
371
  }
372
372
 
373
- .gdesc-open {
373
+ .glightbox-mobile.gdesc-open {
374
374
  .gslide-media {
375
375
  transition: opacity 0.5s ease;
376
376
  opacity: 0.4;
@@ -379,7 +379,7 @@
379
379
  padding-bottom: 30px;
380
380
  }
381
381
  }
382
- .gdesc-closed {
382
+ .glightbox-mobile.gdesc-closed {
383
383
  .gslide-media {
384
384
  transition: opacity 0.5s ease;
385
385
  opacity: 1;
@@ -524,6 +524,7 @@ iframe.wait-autoplay {
524
524
  .gslide-title {
525
525
  font-size: 1em;
526
526
  font-weight: normal;
527
+ font-family: Arial, sans-serif;
527
528
  color: #000;
528
529
  margin-bottom: 19px;
529
530
  line-height: 1.4em;
@@ -531,6 +532,7 @@ iframe.wait-autoplay {
531
532
 
532
533
  .gslide-desc {
533
534
  font-size: 0.86em;
535
+ font-family: Arial, sans-serif;
534
536
  margin-bottom: 0;
535
537
  line-height: 1.4em;
536
538
  }
@@ -0,0 +1,130 @@
1
+ import assert from 'node:assert/strict';
2
+ import { describe, it } from 'node:test';
3
+ import Slide from '../src/js/core/slide.js';
4
+ import { closest } from '../src/js/utils/helpers.js';
5
+
6
+ class FakeElement {
7
+ constructor(nodeName, classes = [], parentElement = null) {
8
+ this.nodeType = 1;
9
+ this.nodeName = nodeName;
10
+ this.parentElement = parentElement;
11
+ this.listeners = {};
12
+ this.children = [];
13
+ this.innerHTML = '';
14
+ this.classList = {
15
+ contains: (name) => classes.includes(name),
16
+ add: (name) => {
17
+ if (!classes.includes(name)) {
18
+ classes.push(name);
19
+ }
20
+ },
21
+ remove: (name) => {
22
+ const index = classes.indexOf(name);
23
+ if (index >= 0) {
24
+ classes.splice(index, 1);
25
+ }
26
+ }
27
+ };
28
+ if (parentElement) {
29
+ parentElement.children.push(this);
30
+ }
31
+ }
32
+
33
+ addEventListener(name, callback) {
34
+ this.listeners[name] = this.listeners[name] || [];
35
+ this.listeners[name].push(callback);
36
+ }
37
+
38
+ removeEventListener(name, callback) {
39
+ this.listeners[name] = (this.listeners[name] || []).filter((listener) => listener !== callback);
40
+ }
41
+
42
+ matches(selector) {
43
+ return selector[0] === '.' && this.classList.contains(selector.slice(1));
44
+ }
45
+
46
+ querySelector(selector) {
47
+ return this.children.find((child) => child.matches(selector)) || null;
48
+ }
49
+
50
+ set innerHTML(value) {
51
+ this._innerHTML = value;
52
+ for (const child of this.children) {
53
+ child.parentElement = null;
54
+ }
55
+ this.children = [];
56
+
57
+ const linkMatch = value.match(/<a [^>]*class="([^"]+)"[^>]*>(.*?)<\/a>/);
58
+ if (linkMatch) {
59
+ const link = new FakeElement('A', linkMatch[1].split(' '), this);
60
+ link.innerHTML = linkMatch[2];
61
+ }
62
+ }
63
+
64
+ get innerHTML() {
65
+ return this._innerHTML;
66
+ }
67
+
68
+ click() {
69
+ const path = [];
70
+ for (let element = this; element; element = element.parentElement) {
71
+ path.push(element);
72
+ }
73
+
74
+ let propagationStopped = false;
75
+ const event = {
76
+ target: this,
77
+ preventDefault() {},
78
+ stopPropagation() {
79
+ propagationStopped = true;
80
+ }
81
+ };
82
+
83
+ for (const element of path) {
84
+ for (const listener of element.listeners.click || []) {
85
+ listener.call(element, event);
86
+ }
87
+ if (propagationStopped) {
88
+ break;
89
+ }
90
+ }
91
+ }
92
+ }
93
+
94
+ describe('description expansion', () => {
95
+ it('does not reach the outside-click handler after detaching the more link', () => {
96
+ const body = new FakeElement('BODY');
97
+ globalThis.document = { body };
98
+ globalThis.window = {};
99
+
100
+ const modal = new FakeElement('DIV', [], body);
101
+ const innerContainer = new FakeElement('DIV', ['ginner-container'], modal);
102
+ const description = new FakeElement('DIV', ['gslide-description'], innerContainer);
103
+ const desc = new FakeElement('DIV', ['gslide-desc'], description);
104
+ const moreLink = new FakeElement('A', ['desc-more'], desc);
105
+ desc.querySelector = (selector) => desc.children.find((child) => child.matches(selector)) || null;
106
+
107
+ let closeCount = 0;
108
+ modal.addEventListener('click', (event) => {
109
+ if (!closest(event.target, '.ginner-container')) {
110
+ closeCount++;
111
+ }
112
+ });
113
+
114
+ const slide = Object.create(Slide.prototype);
115
+ slide.instance = { settings: { lessText: 'See less' } };
116
+ slide.descriptionEvents(desc, {
117
+ description: 'Full description',
118
+ smallDescription: 'Short description'
119
+ });
120
+ moreLink.click();
121
+
122
+ assert.match(desc.innerHTML, /^Full description <a href="#" class="desc-less">See less<\/a>$/);
123
+ assert.equal(closeCount, 0);
124
+
125
+ desc.querySelector('.desc-less').click();
126
+
127
+ assert.equal(desc.innerHTML, 'Short description');
128
+ assert.equal(closeCount, 0);
129
+ });
130
+ });
@@ -0,0 +1,13 @@
1
+ import assert from 'node:assert/strict';
2
+ import { describe, it } from 'node:test';
3
+ import { resetSlideMove } from '../src/js/core/touch-navigation.js';
4
+
5
+ describe('resetSlideMove', () => {
6
+ it('returns when the slide is null', () => {
7
+ assert.equal(resetSlideMove(null), undefined);
8
+ });
9
+
10
+ it('returns when the slide is undefined', () => {
11
+ assert.equal(resetSlideMove(undefined), undefined);
12
+ });
13
+ });