@ecl/gallery 5.0.0-RC1
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/LICENSE +296 -0
- package/README.md +154 -0
- package/gallery-item.html.twig +211 -0
- package/gallery-overlay.html.twig +174 -0
- package/gallery-print.scss +365 -0
- package/gallery.html.twig +213 -0
- package/gallery.js +779 -0
- package/gallery.scss +698 -0
- package/package.json +37 -0
package/gallery.js
ADDED
|
@@ -0,0 +1,779 @@
|
|
|
1
|
+
import { queryOne, queryAll } from '@ecl/dom-utils';
|
|
2
|
+
import { createFocusTrap } from 'focus-trap';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {HTMLElement} element DOM element for component instantiation and scope
|
|
6
|
+
* @param {Object} options
|
|
7
|
+
* @param {String} options.galleryItemSelector Selector for gallery element
|
|
8
|
+
* @param {String} options.descriptionSelector Selector for gallery description element
|
|
9
|
+
* @param {String} options.titleSelector Selector for gallery title element
|
|
10
|
+
* @param {String} options.closeButtonSelector Selector for close button element
|
|
11
|
+
* @param {String} options.allButtonSelector Selector for view all button element
|
|
12
|
+
* @param {String} options.overlaySelector Selector for gallery overlay element
|
|
13
|
+
* @param {String} options.overlayHeaderSelector Selector for gallery overlay header element
|
|
14
|
+
* @param {String} options.overlayFooterSelector Selector for gallery overlay footer element
|
|
15
|
+
* @param {String} options.overlayMediaSelector Selector for gallery overlay media element
|
|
16
|
+
* @param {String} options.overlayCounterCurrentSelector Selector for gallery overlay current number element
|
|
17
|
+
* @param {String} options.overlayCounterMaxSelector Selector for display of number of elements in the gallery overlay
|
|
18
|
+
* @param {String} options.overlayDownloadSelector Selector for gallery overlay download element
|
|
19
|
+
* @param {String} options.overlayShareSelector Selector for gallery overlay share element
|
|
20
|
+
* @param {String} options.overlayDescriptionSelector Selector for gallery overlay description element
|
|
21
|
+
* @param {String} options.overlayPreviousSelector Selector for gallery overlay previous link element
|
|
22
|
+
* @param {String} options.overlayNextSelector Selector for gallery overlay next link element
|
|
23
|
+
* @param {String} options.videoTitleSelector Selector for video title
|
|
24
|
+
* @param {Boolean} options.attachClickListener Whether or not to bind click events
|
|
25
|
+
* @param {Boolean} options.attachKeyListener Whether or not to bind keyup events
|
|
26
|
+
*/
|
|
27
|
+
export class Gallery {
|
|
28
|
+
/**
|
|
29
|
+
* @static
|
|
30
|
+
* Shorthand for instance creation and initialisation.
|
|
31
|
+
*
|
|
32
|
+
* @param {HTMLElement} root DOM element for component instantiation and scope
|
|
33
|
+
*
|
|
34
|
+
* @return {Gallery} An instance of Gallery.
|
|
35
|
+
*/
|
|
36
|
+
static autoInit(root, { GALLERY: defaultOptions = {} } = {}) {
|
|
37
|
+
const gallery = new Gallery(root, defaultOptions);
|
|
38
|
+
gallery.init();
|
|
39
|
+
root.ECLGallery = gallery;
|
|
40
|
+
return gallery;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
constructor(
|
|
44
|
+
element,
|
|
45
|
+
{
|
|
46
|
+
expandableSelector = 'data-ecl-gallery-not-expandable',
|
|
47
|
+
galleryItemSelector = '[data-ecl-gallery-item]',
|
|
48
|
+
descriptionSelector = '[data-ecl-gallery-description]',
|
|
49
|
+
titleSelector = '[data-ecl-gallery-title]',
|
|
50
|
+
noOverlaySelector = 'data-ecl-gallery-no-overlay',
|
|
51
|
+
itemsLimitSelector = 'data-ecl-gallery-visible-items',
|
|
52
|
+
closeButtonSelector = '[data-ecl-gallery-close]',
|
|
53
|
+
viewAllSelector = '[data-ecl-gallery-all]',
|
|
54
|
+
viewAllLabelSelector = 'data-ecl-gallery-collapsed-label',
|
|
55
|
+
viewAllExpandedLabelSelector = 'data-ecl-gallery-expanded-label',
|
|
56
|
+
countSelector = '[data-ecl-gallery-count]',
|
|
57
|
+
overlaySelector = '[data-ecl-gallery-overlay]',
|
|
58
|
+
overlayHeaderSelector = '[data-ecl-gallery-overlay-header]',
|
|
59
|
+
overlayFooterSelector = '[data-ecl-gallery-overlay-footer]',
|
|
60
|
+
overlayMediaSelector = '[data-ecl-gallery-overlay-media]',
|
|
61
|
+
overlayCounterCurrentSelector = '[data-ecl-gallery-overlay-counter-current]',
|
|
62
|
+
overlayCounterMaxSelector = '[data-ecl-gallery-overlay-counter-max]',
|
|
63
|
+
overlayDownloadSelector = '[data-ecl-gallery-overlay-download]',
|
|
64
|
+
overlayShareSelector = '[data-ecl-gallery-overlay-share]',
|
|
65
|
+
overlayDescriptionSelector = '[data-ecl-gallery-overlay-description]',
|
|
66
|
+
overlayPreviousSelector = '[data-ecl-gallery-overlay-previous]',
|
|
67
|
+
overlayNextSelector = '[data-ecl-gallery-overlay-next]',
|
|
68
|
+
videoTitleSelector = 'data-ecl-gallery-item-video-title',
|
|
69
|
+
attachClickListener = true,
|
|
70
|
+
attachKeyListener = true,
|
|
71
|
+
attachResizeListener = true,
|
|
72
|
+
} = {},
|
|
73
|
+
) {
|
|
74
|
+
// Check element
|
|
75
|
+
if (!element || element.nodeType !== Node.ELEMENT_NODE) {
|
|
76
|
+
throw new TypeError(
|
|
77
|
+
'DOM element should be given to initialize this widget.',
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.element = element;
|
|
82
|
+
|
|
83
|
+
// Options
|
|
84
|
+
this.galleryItemSelector = galleryItemSelector;
|
|
85
|
+
this.descriptionSelector = descriptionSelector;
|
|
86
|
+
this.titleSelector = titleSelector;
|
|
87
|
+
this.closeButtonSelector = closeButtonSelector;
|
|
88
|
+
this.viewAllSelector = viewAllSelector;
|
|
89
|
+
this.countSelector = countSelector;
|
|
90
|
+
this.itemsLimitSelector = itemsLimitSelector;
|
|
91
|
+
this.overlaySelector = overlaySelector;
|
|
92
|
+
this.noOverlaySelector = noOverlaySelector;
|
|
93
|
+
this.overlayHeaderSelector = overlayHeaderSelector;
|
|
94
|
+
this.overlayFooterSelector = overlayFooterSelector;
|
|
95
|
+
this.overlayMediaSelector = overlayMediaSelector;
|
|
96
|
+
this.overlayCounterCurrentSelector = overlayCounterCurrentSelector;
|
|
97
|
+
this.overlayCounterMaxSelector = overlayCounterMaxSelector;
|
|
98
|
+
this.overlayDownloadSelector = overlayDownloadSelector;
|
|
99
|
+
this.overlayShareSelector = overlayShareSelector;
|
|
100
|
+
this.overlayDescriptionSelector = overlayDescriptionSelector;
|
|
101
|
+
this.overlayPreviousSelector = overlayPreviousSelector;
|
|
102
|
+
this.overlayNextSelector = overlayNextSelector;
|
|
103
|
+
this.attachClickListener = attachClickListener;
|
|
104
|
+
this.attachKeyListener = attachKeyListener;
|
|
105
|
+
this.attachResizeListener = attachResizeListener;
|
|
106
|
+
this.viewAllLabelSelector = viewAllLabelSelector;
|
|
107
|
+
this.viewAllExpandedLabelSelector = viewAllExpandedLabelSelector;
|
|
108
|
+
this.expandableSelector = expandableSelector;
|
|
109
|
+
this.videoTitleSelector = videoTitleSelector;
|
|
110
|
+
|
|
111
|
+
// Private variables
|
|
112
|
+
this.galleryItems = null;
|
|
113
|
+
this.closeButton = null;
|
|
114
|
+
this.viewAll = null;
|
|
115
|
+
this.count = null;
|
|
116
|
+
this.overlay = null;
|
|
117
|
+
this.overlayHeader = null;
|
|
118
|
+
this.overlayFooter = null;
|
|
119
|
+
this.overlayMedia = null;
|
|
120
|
+
this.overlayCounterCurrent = null;
|
|
121
|
+
this.overlayCounterMax = null;
|
|
122
|
+
this.overlayDownload = null;
|
|
123
|
+
this.overlayShare = null;
|
|
124
|
+
this.overlayDescription = null;
|
|
125
|
+
this.overlayPrevious = null;
|
|
126
|
+
this.overlayNext = null;
|
|
127
|
+
this.selectedItem = null;
|
|
128
|
+
this.focusTrap = null;
|
|
129
|
+
this.isDesktop = false;
|
|
130
|
+
this.resizeTimer = null;
|
|
131
|
+
this.visibleItems = 0;
|
|
132
|
+
this.breakpointMd = 768;
|
|
133
|
+
this.breakpointLg = 996;
|
|
134
|
+
this.imageHeight = 185;
|
|
135
|
+
this.imageHeightBig = 260;
|
|
136
|
+
|
|
137
|
+
// Bind `this` for use in callbacks
|
|
138
|
+
this.iframeResize = this.iframeResize.bind(this);
|
|
139
|
+
this.handleClickOnCloseButton = this.handleClickOnCloseButton.bind(this);
|
|
140
|
+
this.handleClickOnViewAll = this.handleClickOnViewAll.bind(this);
|
|
141
|
+
this.handleClickOnItem = this.handleClickOnItem.bind(this);
|
|
142
|
+
this.preventClickOnItem = this.preventClickOnItem.bind(this);
|
|
143
|
+
this.handleKeyPressOnItem = this.handleKeyPressOnItem.bind(this);
|
|
144
|
+
this.handleClickOnPreviousButton =
|
|
145
|
+
this.handleClickOnPreviousButton.bind(this);
|
|
146
|
+
this.handleClickOnNextButton = this.handleClickOnNextButton.bind(this);
|
|
147
|
+
this.handleKeyboard = this.handleKeyboard.bind(this);
|
|
148
|
+
this.handleResize = this.handleResize.bind(this);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Initialise component.
|
|
153
|
+
*/
|
|
154
|
+
init() {
|
|
155
|
+
if (!ECL) {
|
|
156
|
+
throw new TypeError('Called init but ECL is not present');
|
|
157
|
+
}
|
|
158
|
+
ECL.components = ECL.components || new Map();
|
|
159
|
+
// Query elements
|
|
160
|
+
this.expandable = !this.element.hasAttribute(this.expandableSelector);
|
|
161
|
+
this.visibleItems = this.element.getAttribute(this.itemsLimitSelector);
|
|
162
|
+
this.galleryItems = queryAll(this.galleryItemSelector, this.element);
|
|
163
|
+
this.closeButton = queryOne(this.closeButtonSelector, this.element);
|
|
164
|
+
this.noOverlay = this.element.hasAttribute(this.noOverlaySelector);
|
|
165
|
+
if (this.expandable) {
|
|
166
|
+
this.viewAll = queryOne(this.viewAllSelector, this.element);
|
|
167
|
+
this.viewAllLabel =
|
|
168
|
+
this.viewAll.getAttribute(this.viewAllLabelSelector) ||
|
|
169
|
+
this.viewAll.innerText;
|
|
170
|
+
this.viewAllLabelExpanded =
|
|
171
|
+
this.viewAll.getAttribute(this.viewAllExpandedLabelSelector) ||
|
|
172
|
+
this.viewAllLabel;
|
|
173
|
+
}
|
|
174
|
+
this.count = queryOne(this.countSelector, this.element);
|
|
175
|
+
|
|
176
|
+
// Bind click event on view all (open first item)
|
|
177
|
+
if (this.attachClickListener && this.viewAll) {
|
|
178
|
+
this.viewAll.addEventListener('click', this.handleClickOnViewAll);
|
|
179
|
+
}
|
|
180
|
+
if (!this.noOverlay) {
|
|
181
|
+
this.overlay = queryOne(this.overlaySelector, this.element);
|
|
182
|
+
this.overlayHeader = queryOne(this.overlayHeaderSelector, this.overlay);
|
|
183
|
+
this.overlayFooter = queryOne(this.overlayFooterSelector, this.overlay);
|
|
184
|
+
this.overlayMedia = queryOne(this.overlayMediaSelector, this.overlay);
|
|
185
|
+
this.overlayCounterCurrent = queryOne(
|
|
186
|
+
this.overlayCounterCurrentSelector,
|
|
187
|
+
this.overlay,
|
|
188
|
+
);
|
|
189
|
+
this.overlayCounterMax = queryOne(
|
|
190
|
+
this.overlayCounterMaxSelector,
|
|
191
|
+
this.overlay,
|
|
192
|
+
);
|
|
193
|
+
this.overlayDownload = queryOne(
|
|
194
|
+
this.overlayDownloadSelector,
|
|
195
|
+
this.overlay,
|
|
196
|
+
);
|
|
197
|
+
this.overlayShare = queryOne(this.overlayShareSelector, this.overlay);
|
|
198
|
+
this.overlayDescription = queryOne(
|
|
199
|
+
this.overlayDescriptionSelector,
|
|
200
|
+
this.overlay,
|
|
201
|
+
);
|
|
202
|
+
this.overlayPrevious = queryOne(
|
|
203
|
+
this.overlayPreviousSelector,
|
|
204
|
+
this.overlay,
|
|
205
|
+
);
|
|
206
|
+
this.overlayNext = queryOne(this.overlayNextSelector, this.overlay);
|
|
207
|
+
|
|
208
|
+
// Create focus trap
|
|
209
|
+
this.focusTrap = createFocusTrap(this.overlay, {
|
|
210
|
+
escapeDeactivates: false,
|
|
211
|
+
returnFocusOnDeactivate: false,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Polyfill to support <dialog>
|
|
215
|
+
this.isDialogSupported = true;
|
|
216
|
+
if (!window.HTMLDialogElement) {
|
|
217
|
+
this.isDialogSupported = false;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Bind click event on close button
|
|
221
|
+
if (this.attachClickListener && this.closeButton) {
|
|
222
|
+
this.closeButton.addEventListener(
|
|
223
|
+
'click',
|
|
224
|
+
this.handleClickOnCloseButton,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Bind click event on gallery items
|
|
229
|
+
if (this.attachClickListener && this.galleryItems) {
|
|
230
|
+
this.galleryItems.forEach((galleryItem) => {
|
|
231
|
+
if (this.attachClickListener) {
|
|
232
|
+
galleryItem.addEventListener('click', this.handleClickOnItem);
|
|
233
|
+
}
|
|
234
|
+
if (this.attachKeyListener) {
|
|
235
|
+
galleryItem.addEventListener('keyup', this.handleKeyPressOnItem);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Bind click event on previous button
|
|
241
|
+
if (this.attachClickListener && this.overlayPrevious) {
|
|
242
|
+
this.overlayPrevious.addEventListener(
|
|
243
|
+
'click',
|
|
244
|
+
this.handleClickOnPreviousButton,
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Bind click event on next button
|
|
249
|
+
if (this.attachClickListener && this.overlayNext) {
|
|
250
|
+
this.overlayNext.addEventListener(
|
|
251
|
+
'click',
|
|
252
|
+
this.handleClickOnNextButton,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Bind other close event
|
|
257
|
+
if (!this.isDialogSupported && this.attachKeyListener && this.overlay) {
|
|
258
|
+
this.overlay.addEventListener('keyup', this.handleKeyboard);
|
|
259
|
+
}
|
|
260
|
+
if (this.isDialogSupported && this.overlay) {
|
|
261
|
+
this.overlay.addEventListener('close', this.handleClickOnCloseButton);
|
|
262
|
+
}
|
|
263
|
+
} else {
|
|
264
|
+
this.galleryItems.forEach((galleryItem) => {
|
|
265
|
+
galleryItem.classList.add('ecl-gallery__item__link--frozen');
|
|
266
|
+
galleryItem.addEventListener('click', this.preventClickOnItem);
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Bind resize events
|
|
271
|
+
if (this.attachResizeListener) {
|
|
272
|
+
window.addEventListener('resize', this.handleResize);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Init display of gallery items
|
|
276
|
+
if (this.expandable) {
|
|
277
|
+
this.checkScreen();
|
|
278
|
+
this.hideItems();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Add number to gallery items
|
|
282
|
+
this.galleryItems.forEach((galleryItem, key) => {
|
|
283
|
+
galleryItem.setAttribute('data-ecl-gallery-item-id', key);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// Update counter
|
|
287
|
+
if (this.count) {
|
|
288
|
+
this.count.innerHTML = this.galleryItems.length;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Set ecl initialized attribute
|
|
292
|
+
this.element.setAttribute('data-ecl-auto-initialized', 'true');
|
|
293
|
+
ECL.components.set(this.element, this);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Destroy component.
|
|
298
|
+
*/
|
|
299
|
+
destroy() {
|
|
300
|
+
if (this.attachClickListener && this.closeButton) {
|
|
301
|
+
this.closeButton.removeEventListener(
|
|
302
|
+
'click',
|
|
303
|
+
this.handleClickOnCloseButton,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (this.attachClickListener && this.viewAll) {
|
|
308
|
+
this.viewAll.removeEventListener('click', this.handleClickOnViewAll);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (this.attachClickListener && this.galleryItems) {
|
|
312
|
+
this.galleryItems.forEach((galleryItem) => {
|
|
313
|
+
galleryItem.removeEventListener('click', this.handleClickOnItem);
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (this.attachClickListener && this.overlayPrevious) {
|
|
318
|
+
this.overlayPrevious.removeEventListener(
|
|
319
|
+
'click',
|
|
320
|
+
this.handleClickOnPreviousButton,
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (this.attachClickListener && this.overlayNext) {
|
|
325
|
+
this.overlayNext.removeEventListener(
|
|
326
|
+
'click',
|
|
327
|
+
this.handleClickOnNextButton,
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (!this.isDialogSupported && this.attachKeyListener && this.overlay) {
|
|
332
|
+
this.overlay.removeEventListener('keyup', this.handleKeyboard);
|
|
333
|
+
}
|
|
334
|
+
if (this.isDialogSupported && this.overlay) {
|
|
335
|
+
this.overlay.removeEventListener('close', this.handleClickOnCloseButton);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (this.attachResizeListener) {
|
|
339
|
+
window.removeEventListener('resize', this.handleResize);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (this.element) {
|
|
343
|
+
this.element.removeAttribute('data-ecl-auto-initialized');
|
|
344
|
+
ECL.components.delete(this.element);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Check if current display is desktop or mobile
|
|
350
|
+
*/
|
|
351
|
+
checkScreen() {
|
|
352
|
+
if (window.innerWidth > this.breakpointMd) {
|
|
353
|
+
this.isDesktop = true;
|
|
354
|
+
} else {
|
|
355
|
+
this.isDesktop = false;
|
|
356
|
+
}
|
|
357
|
+
if (window.innerWidth > this.breakpointLg) {
|
|
358
|
+
this.isLarge = true;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
iframeResize(iframe) {
|
|
363
|
+
if (!iframe && this.overlay) {
|
|
364
|
+
iframe = queryOne('iframe', this.overlay);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (iframe) {
|
|
368
|
+
const width = window.innerWidth;
|
|
369
|
+
|
|
370
|
+
setTimeout(() => {
|
|
371
|
+
const height =
|
|
372
|
+
this.overlay.clientHeight -
|
|
373
|
+
this.overlayHeader.clientHeight -
|
|
374
|
+
this.overlayFooter.clientHeight;
|
|
375
|
+
|
|
376
|
+
if (width > height) {
|
|
377
|
+
iframe.setAttribute('height', `${height}px`);
|
|
378
|
+
|
|
379
|
+
if ((height * 16) / 9 > width) {
|
|
380
|
+
iframe.setAttribute('width', `${width - 0.05 * width}px`);
|
|
381
|
+
} else {
|
|
382
|
+
iframe.setAttribute('width', `${(height * 16) / 9}px`);
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
iframe.setAttribute('width', `${width}px`);
|
|
386
|
+
if ((width * 4) / 3 > height) {
|
|
387
|
+
iframe.setAttribute('height', `${height - 0.05 * height}px`);
|
|
388
|
+
} else {
|
|
389
|
+
iframe.setAttribute('height', `${(width * 4) / 3}px`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}, 0);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* @param {Int} rows/item number
|
|
398
|
+
*
|
|
399
|
+
* Hide several gallery items by default
|
|
400
|
+
* - 2 "lines" of items on desktop
|
|
401
|
+
* - only 3 items on mobile or the desired rows or items
|
|
402
|
+
* when using the view more button.
|
|
403
|
+
*/
|
|
404
|
+
hideItems(plus = 0) {
|
|
405
|
+
if (!this.viewAll || this.viewAll.expanded) return;
|
|
406
|
+
|
|
407
|
+
if (this.isDesktop) {
|
|
408
|
+
let hiddenItemIds = [];
|
|
409
|
+
// We should browse the list first to mark the items to be hidden, and hide them later
|
|
410
|
+
// otherwise, it will interfer with the calculus
|
|
411
|
+
this.galleryItems.forEach((galleryItem, key) => {
|
|
412
|
+
galleryItem.parentNode.classList.remove('ecl-gallery__item--hidden');
|
|
413
|
+
if (key >= Number(this.visibleItems) + Number(plus)) {
|
|
414
|
+
hiddenItemIds = [...hiddenItemIds, key];
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
hiddenItemIds.forEach((id) => {
|
|
418
|
+
this.galleryItems[id].parentNode.classList.add(
|
|
419
|
+
'ecl-gallery__item--hidden',
|
|
420
|
+
);
|
|
421
|
+
});
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
this.galleryItems.forEach((galleryItem, key) => {
|
|
426
|
+
if (key > 2 + Number(plus)) {
|
|
427
|
+
galleryItem.parentNode.classList.add('ecl-gallery__item--hidden');
|
|
428
|
+
} else {
|
|
429
|
+
galleryItem.parentNode.classList.remove('ecl-gallery__item--hidden');
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Trigger events on resize
|
|
436
|
+
* Uses a debounce, for performance
|
|
437
|
+
*/
|
|
438
|
+
handleResize() {
|
|
439
|
+
clearTimeout(this.resizeTimer);
|
|
440
|
+
this.resizeTimer = setTimeout(() => {
|
|
441
|
+
this.checkScreen();
|
|
442
|
+
this.hideItems();
|
|
443
|
+
this.iframeResize();
|
|
444
|
+
}, 200);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* @param {HTMLElement} selectedItem Media element
|
|
449
|
+
*/
|
|
450
|
+
updateOverlay(selectedItem) {
|
|
451
|
+
this.selectedItem = selectedItem;
|
|
452
|
+
const embeddedVideo = selectedItem.getAttribute(
|
|
453
|
+
'data-ecl-gallery-item-embed-src',
|
|
454
|
+
);
|
|
455
|
+
const embeddedVideoAudio = selectedItem.getAttribute(
|
|
456
|
+
'data-ecl-gallery-item-embed-audio',
|
|
457
|
+
);
|
|
458
|
+
const video = queryOne('video', selectedItem);
|
|
459
|
+
let mediaElement = null;
|
|
460
|
+
|
|
461
|
+
// Update media
|
|
462
|
+
if (embeddedVideo != null) {
|
|
463
|
+
// Media is a embedded video
|
|
464
|
+
mediaElement = document.createElement('div');
|
|
465
|
+
mediaElement.classList.add('ecl-gallery__slider-embed');
|
|
466
|
+
|
|
467
|
+
const mediaAudio = document.createElement('div');
|
|
468
|
+
mediaAudio.classList.add('ecl-gallery__slider-embed-audio');
|
|
469
|
+
if (embeddedVideoAudio) {
|
|
470
|
+
mediaAudio.innerHTML = embeddedVideoAudio;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const iframeUrl = new URL(embeddedVideo);
|
|
474
|
+
|
|
475
|
+
const mediaIframe = document.createElement('iframe');
|
|
476
|
+
mediaIframe.setAttribute('src', iframeUrl);
|
|
477
|
+
mediaIframe.setAttribute('frameBorder', '0');
|
|
478
|
+
|
|
479
|
+
// Update iframe title
|
|
480
|
+
const videoTitle = selectedItem.getAttribute(this.videoTitleSelector);
|
|
481
|
+
if (videoTitle) {
|
|
482
|
+
mediaIframe.setAttribute('title', videoTitle);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (this.overlayMedia) {
|
|
486
|
+
if (embeddedVideoAudio) {
|
|
487
|
+
mediaElement.appendChild(mediaAudio);
|
|
488
|
+
}
|
|
489
|
+
mediaElement.appendChild(mediaIframe);
|
|
490
|
+
this.overlayMedia.innerHTML = '';
|
|
491
|
+
this.overlayMedia.appendChild(mediaElement);
|
|
492
|
+
}
|
|
493
|
+
this.iframeResize(mediaIframe);
|
|
494
|
+
} else if (video != null) {
|
|
495
|
+
// Media is a video
|
|
496
|
+
mediaElement = document.createElement('video');
|
|
497
|
+
mediaElement.setAttribute('poster', video.poster);
|
|
498
|
+
mediaElement.setAttribute('controls', 'controls');
|
|
499
|
+
mediaElement.classList.add('ecl-gallery__slider-video');
|
|
500
|
+
|
|
501
|
+
// Update video title
|
|
502
|
+
const videoTitle = selectedItem.getAttribute(this.videoTitleSelector);
|
|
503
|
+
if (videoTitle) {
|
|
504
|
+
mediaElement.setAttribute('aria-label', videoTitle);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (this.overlayMedia) {
|
|
508
|
+
this.overlayMedia.innerHTML = '';
|
|
509
|
+
this.overlayMedia.appendChild(mediaElement);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Get sources
|
|
513
|
+
const sources = queryAll('source', video);
|
|
514
|
+
sources.forEach((source) => {
|
|
515
|
+
const sourceTag = document.createElement('source');
|
|
516
|
+
sourceTag.setAttribute('src', source.getAttribute('src'));
|
|
517
|
+
sourceTag.setAttribute('type', source.getAttribute('type'));
|
|
518
|
+
mediaElement.appendChild(sourceTag);
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
// Get tracks
|
|
522
|
+
const tracks = queryAll('track', video);
|
|
523
|
+
tracks.forEach((track) => {
|
|
524
|
+
const trackTag = document.createElement('track');
|
|
525
|
+
trackTag.setAttribute('src', track.getAttribute('src'));
|
|
526
|
+
trackTag.setAttribute('kind', track.getAttribute('kind'));
|
|
527
|
+
trackTag.setAttribute('srclang', track.getAttribute('srcLang'));
|
|
528
|
+
trackTag.setAttribute('label', track.getAttribute('label'));
|
|
529
|
+
mediaElement.appendChild(trackTag);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
mediaElement.load();
|
|
533
|
+
} else {
|
|
534
|
+
// Media is an image
|
|
535
|
+
const picture = queryOne('.ecl-gallery__picture', selectedItem);
|
|
536
|
+
const image = queryOne('img', picture);
|
|
537
|
+
if (picture) {
|
|
538
|
+
image.classList.remove('ecl-gallery__image');
|
|
539
|
+
mediaElement = picture.cloneNode(true);
|
|
540
|
+
} else {
|
|
541
|
+
// backward compatibility
|
|
542
|
+
mediaElement = document.createElement('img');
|
|
543
|
+
mediaElement.setAttribute('src', image.getAttribute('src'));
|
|
544
|
+
mediaElement.setAttribute('alt', image.getAttribute('alt'));
|
|
545
|
+
}
|
|
546
|
+
mediaElement.classList.add('ecl-gallery__slider-image');
|
|
547
|
+
|
|
548
|
+
if (this.overlayMedia) {
|
|
549
|
+
this.overlayMedia.innerHTML = '';
|
|
550
|
+
this.overlayMedia.appendChild(mediaElement);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Get id
|
|
555
|
+
const id = selectedItem.getAttribute('id');
|
|
556
|
+
|
|
557
|
+
// Update counter
|
|
558
|
+
this.overlayCounterCurrent.innerHTML =
|
|
559
|
+
+selectedItem.getAttribute('data-ecl-gallery-item-id') + 1;
|
|
560
|
+
this.overlayCounterMax.innerHTML = this.galleryItems.length;
|
|
561
|
+
|
|
562
|
+
// Prepare display of links for mobile
|
|
563
|
+
const actionMobile = document.createElement('div');
|
|
564
|
+
actionMobile.classList.add('ecl-gallery__detail-actions-mobile');
|
|
565
|
+
|
|
566
|
+
// Update download link
|
|
567
|
+
if (this.overlayDownload !== null && embeddedVideo === null) {
|
|
568
|
+
this.overlayDownload.href = this.selectedItem.href;
|
|
569
|
+
if (id) {
|
|
570
|
+
this.overlayDownload.setAttribute('aria-describedby', `${id}-title`);
|
|
571
|
+
}
|
|
572
|
+
this.overlayDownload.hidden = false;
|
|
573
|
+
actionMobile.appendChild(this.overlayDownload.cloneNode(true));
|
|
574
|
+
} else if (this.overlayDownload !== null) {
|
|
575
|
+
this.overlayDownload.hidden = true;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Update share link
|
|
579
|
+
const shareHref = this.selectedItem.getAttribute(
|
|
580
|
+
'data-ecl-gallery-item-share',
|
|
581
|
+
);
|
|
582
|
+
if (shareHref != null) {
|
|
583
|
+
this.overlayShare.href = shareHref;
|
|
584
|
+
if (id) {
|
|
585
|
+
this.overlayShare.setAttribute('aria-describedby', `${id}-title`);
|
|
586
|
+
}
|
|
587
|
+
this.overlayShare.hidden = false;
|
|
588
|
+
actionMobile.appendChild(this.overlayShare.cloneNode(true));
|
|
589
|
+
} else {
|
|
590
|
+
this.overlayShare.hidden = true;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Update description
|
|
594
|
+
const description = queryOne(this.descriptionSelector, selectedItem);
|
|
595
|
+
if (description) {
|
|
596
|
+
this.overlayDescription.innerHTML = description.innerHTML;
|
|
597
|
+
}
|
|
598
|
+
if (actionMobile.childNodes.length > 0) {
|
|
599
|
+
this.overlayDescription.prepend(actionMobile);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Handles keyboard events such as Escape and navigation.
|
|
605
|
+
*
|
|
606
|
+
* @param {Event} e
|
|
607
|
+
*/
|
|
608
|
+
handleKeyboard(e) {
|
|
609
|
+
// Detect press on Escape
|
|
610
|
+
// Only used if the browser do not support <dialog>
|
|
611
|
+
if (e.key === 'Escape' || e.key === 'Esc') {
|
|
612
|
+
this.handleClickOnCloseButton();
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Invoke listeners for close events.
|
|
618
|
+
*/
|
|
619
|
+
handleClickOnCloseButton() {
|
|
620
|
+
if (this.isDialogSupported) {
|
|
621
|
+
this.overlay.close();
|
|
622
|
+
} else {
|
|
623
|
+
this.overlay.removeAttribute('open');
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Remove iframe
|
|
627
|
+
const embeddedVideo = queryOne('iframe', this.overlayMedia);
|
|
628
|
+
if (embeddedVideo) embeddedVideo.remove();
|
|
629
|
+
|
|
630
|
+
// Stop video
|
|
631
|
+
const video = queryOne('video', this.overlayMedia);
|
|
632
|
+
if (video) video.pause();
|
|
633
|
+
|
|
634
|
+
// Untrap focus
|
|
635
|
+
this.focusTrap.deactivate();
|
|
636
|
+
|
|
637
|
+
// Restore css class on items
|
|
638
|
+
this.galleryItems.forEach((galleryItem) => {
|
|
639
|
+
const image = queryOne('img', galleryItem);
|
|
640
|
+
if (image) {
|
|
641
|
+
image.classList.add('ecl-gallery__image');
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
// Focus item
|
|
646
|
+
this.selectedItem.focus();
|
|
647
|
+
|
|
648
|
+
// Enable scroll on body
|
|
649
|
+
document.body.classList.remove('ecl-u-disablescroll');
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Invoke listeners for on pressing the spacebar button.
|
|
654
|
+
*
|
|
655
|
+
* @param {Event} e
|
|
656
|
+
*/
|
|
657
|
+
handleKeyPressOnItem(e) {
|
|
658
|
+
if (e.keyCode === 32) {
|
|
659
|
+
// If spacebar trigger the modal
|
|
660
|
+
this.handleClickOnItem(e);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Invoke listeners for on click events on view all.
|
|
666
|
+
*
|
|
667
|
+
* @param {Event} e
|
|
668
|
+
*/
|
|
669
|
+
handleClickOnViewAll(e) {
|
|
670
|
+
e.preventDefault();
|
|
671
|
+
if (!this.viewAll) return;
|
|
672
|
+
|
|
673
|
+
if (this.viewAll.expanded) {
|
|
674
|
+
delete this.viewAll.expanded;
|
|
675
|
+
this.checkScreen();
|
|
676
|
+
this.hideItems();
|
|
677
|
+
this.viewAll.textContent = this.viewAllLabel;
|
|
678
|
+
} else {
|
|
679
|
+
this.viewAll.expanded = true;
|
|
680
|
+
this.viewAll.textContent = this.viewAllLabelExpanded;
|
|
681
|
+
|
|
682
|
+
const hidden = this.galleryItems.filter((item) =>
|
|
683
|
+
item.parentNode.classList.contains('ecl-gallery__item--hidden'),
|
|
684
|
+
);
|
|
685
|
+
if (hidden.length > 0) {
|
|
686
|
+
hidden.forEach((item) => {
|
|
687
|
+
item.parentNode.classList.remove('ecl-gallery__item--hidden');
|
|
688
|
+
});
|
|
689
|
+
hidden[0].focus();
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Invoke listeners for on click events on the given gallery item.
|
|
696
|
+
*
|
|
697
|
+
* @param {Event} e
|
|
698
|
+
*/
|
|
699
|
+
handleClickOnItem(e) {
|
|
700
|
+
e.preventDefault();
|
|
701
|
+
|
|
702
|
+
// Disable scroll on body
|
|
703
|
+
document.body.classList.add('ecl-u-disablescroll');
|
|
704
|
+
|
|
705
|
+
// Display overlay
|
|
706
|
+
if (this.isDialogSupported) {
|
|
707
|
+
this.overlay.showModal();
|
|
708
|
+
} else {
|
|
709
|
+
this.overlay.setAttribute('open', '');
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Update overlay
|
|
713
|
+
this.updateOverlay(e.currentTarget);
|
|
714
|
+
|
|
715
|
+
// Trap focus
|
|
716
|
+
this.focusTrap.activate();
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* handle click event on gallery items when no overlay.
|
|
721
|
+
*
|
|
722
|
+
* @param {Event} e
|
|
723
|
+
*/
|
|
724
|
+
|
|
725
|
+
preventClickOnItem(e) {
|
|
726
|
+
e.preventDefault();
|
|
727
|
+
e.stopPropagation();
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Invoke listeners for on click events on previous navigation link.
|
|
732
|
+
*/
|
|
733
|
+
handleClickOnPreviousButton() {
|
|
734
|
+
// Get current id
|
|
735
|
+
const currentId = this.selectedItem.getAttribute(
|
|
736
|
+
'data-ecl-gallery-item-id',
|
|
737
|
+
);
|
|
738
|
+
|
|
739
|
+
// Get previous id
|
|
740
|
+
let previousId = +currentId - 1;
|
|
741
|
+
if (previousId < 0) previousId = this.galleryItems.length - 1;
|
|
742
|
+
|
|
743
|
+
// Stop video
|
|
744
|
+
const video = queryOne('video', this.selectedItem);
|
|
745
|
+
if (video) video.pause();
|
|
746
|
+
|
|
747
|
+
// Update overlay
|
|
748
|
+
this.updateOverlay(this.galleryItems[previousId]);
|
|
749
|
+
this.selectedItem = this.galleryItems[previousId];
|
|
750
|
+
|
|
751
|
+
return this;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Invoke listeners for on click events on next navigation link.
|
|
756
|
+
*/
|
|
757
|
+
handleClickOnNextButton() {
|
|
758
|
+
// Get current id
|
|
759
|
+
const currentId = this.selectedItem.getAttribute(
|
|
760
|
+
'data-ecl-gallery-item-id',
|
|
761
|
+
);
|
|
762
|
+
|
|
763
|
+
// Get next id
|
|
764
|
+
let nextId = +currentId + 1;
|
|
765
|
+
if (nextId >= this.galleryItems.length) nextId = 0;
|
|
766
|
+
|
|
767
|
+
// Stop video
|
|
768
|
+
const video = queryOne('video', this.selectedItem);
|
|
769
|
+
if (video) video.pause();
|
|
770
|
+
|
|
771
|
+
// Update overlay
|
|
772
|
+
this.updateOverlay(this.galleryItems[nextId]);
|
|
773
|
+
this.selectedItem = this.galleryItems[nextId];
|
|
774
|
+
|
|
775
|
+
return this;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
export default Gallery;
|