@shower/core 3.3.0 → 3.4.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.
- package/LICENSE.md +5 -17
- package/README.md +7 -3
- package/dist/index.js +860 -0
- package/lib/default-options.js +11 -11
- package/lib/{start.js → index.js} +5 -5
- package/lib/modules/a11y.js +31 -29
- package/lib/modules/fullscreen.js +22 -0
- package/lib/modules/install.js +26 -24
- package/lib/modules/keys.js +96 -96
- package/lib/modules/location.js +51 -51
- package/lib/modules/mouse.js +39 -39
- package/lib/modules/next.js +39 -39
- package/lib/modules/parse-timing.js +28 -0
- package/lib/modules/progress.js +16 -16
- package/lib/modules/timer.js +25 -25
- package/lib/modules/title.js +15 -15
- package/lib/modules/touch.js +30 -30
- package/lib/modules/view.js +52 -52
- package/lib/shower.js +164 -164
- package/lib/slide.js +94 -94
- package/lib/utils.js +14 -14
- package/package.json +38 -61
- package/CHANGELOG.md +0 -13
- package/dist/shower.js +0 -836
- package/lib/modules/parse-timing.mjs +0 -28
package/dist/shower.js
DELETED
|
@@ -1,836 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Core for Shower HTML presentation engine
|
|
3
|
-
* @shower/core v3.2.0, https://github.com/shower/core
|
|
4
|
-
* @copyright 2010–2021 Vadim Makeev, https://pepelsbey.net
|
|
5
|
-
* @license MIT
|
|
6
|
-
*/
|
|
7
|
-
(function () {
|
|
8
|
-
'use strict';
|
|
9
|
-
|
|
10
|
-
const isInteractiveElement = (element) => element.tabIndex !== -1;
|
|
11
|
-
|
|
12
|
-
const contentLoaded = (callback) => {
|
|
13
|
-
if (document.currentScript.async) {
|
|
14
|
-
callback();
|
|
15
|
-
} else {
|
|
16
|
-
document.addEventListener('DOMContentLoaded', callback);
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const defineReadOnly = (target, props) => {
|
|
21
|
-
for (const [key, value] of Object.entries(props)) {
|
|
22
|
-
Object.defineProperty(target, key, {
|
|
23
|
-
value,
|
|
24
|
-
writable: false,
|
|
25
|
-
enumerable: true,
|
|
26
|
-
configurable: true,
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
class ShowerError extends Error {}
|
|
32
|
-
|
|
33
|
-
var defaultOptions = {
|
|
34
|
-
containerSelector: '.shower',
|
|
35
|
-
progressSelector: '.progress',
|
|
36
|
-
stepSelector: '.next',
|
|
37
|
-
fullModeClass: 'full',
|
|
38
|
-
listModeClass: 'list',
|
|
39
|
-
mouseHiddenClass: 'pointless',
|
|
40
|
-
mouseInactivityTimeout: 5000,
|
|
41
|
-
|
|
42
|
-
slideSelector: '.slide',
|
|
43
|
-
slideTitleSelector: 'h2',
|
|
44
|
-
activeSlideClass: 'active',
|
|
45
|
-
visitedSlideClass: 'visited',
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
class Slide extends EventTarget {
|
|
49
|
-
/**
|
|
50
|
-
* @param {Shower} shower
|
|
51
|
-
* @param {HTMLElement} element
|
|
52
|
-
*/
|
|
53
|
-
constructor(shower, element) {
|
|
54
|
-
super();
|
|
55
|
-
|
|
56
|
-
defineReadOnly(this, {
|
|
57
|
-
shower,
|
|
58
|
-
element,
|
|
59
|
-
state: {
|
|
60
|
-
visitCount: 0,
|
|
61
|
-
innerStepCount: 0,
|
|
62
|
-
},
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
this._isActive = false;
|
|
66
|
-
this._options = this.shower.options;
|
|
67
|
-
|
|
68
|
-
this.element.addEventListener('click', (event) => {
|
|
69
|
-
if (event.defaultPrevented) return;
|
|
70
|
-
|
|
71
|
-
this.activate();
|
|
72
|
-
this.shower.enterFullMode();
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
get isActive() {
|
|
77
|
-
return this._isActive;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
get isVisited() {
|
|
81
|
-
return this.state.visitCount > 0;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
get id() {
|
|
85
|
-
return this.element.id;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
get title() {
|
|
89
|
-
const titleElement = this.element.querySelector(this._options.slideTitleSelector);
|
|
90
|
-
return titleElement ? titleElement.innerText : '';
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Deactivates currently active slide (if any) and activates itself.
|
|
95
|
-
* @emits Slide#deactivate
|
|
96
|
-
* @emits Slide#activate
|
|
97
|
-
* @emits Shower#slidechange
|
|
98
|
-
*/
|
|
99
|
-
activate() {
|
|
100
|
-
if (this._isActive) return;
|
|
101
|
-
|
|
102
|
-
const prev = this.shower.activeSlide;
|
|
103
|
-
if (prev) {
|
|
104
|
-
prev._deactivate();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
this.state.visitCount++;
|
|
108
|
-
this.element.classList.add(this._options.activeSlideClass);
|
|
109
|
-
|
|
110
|
-
this._isActive = true;
|
|
111
|
-
this.dispatchEvent(new Event('activate'));
|
|
112
|
-
this.shower.dispatchEvent(
|
|
113
|
-
new CustomEvent('slidechange', {
|
|
114
|
-
detail: { prev },
|
|
115
|
-
}),
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* @throws {ShowerError}
|
|
121
|
-
* @emits Slide#deactivate
|
|
122
|
-
*/
|
|
123
|
-
deactivate() {
|
|
124
|
-
if (this.shower.isFullMode) {
|
|
125
|
-
throw new ShowerError('In full mode, another slide should be activated instead.');
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (this._isActive) {
|
|
129
|
-
this._deactivate();
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
_deactivate() {
|
|
134
|
-
this.element.classList.replace(
|
|
135
|
-
this._options.activeSlideClass,
|
|
136
|
-
this._options.visitedSlideClass,
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
this._isActive = false;
|
|
140
|
-
this.dispatchEvent(new Event('deactivate'));
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const createLiveRegion = () => {
|
|
145
|
-
const liveRegion = document.createElement('section');
|
|
146
|
-
liveRegion.className = 'region';
|
|
147
|
-
liveRegion.setAttribute('role', 'region');
|
|
148
|
-
liveRegion.setAttribute('aria-live', 'assertive');
|
|
149
|
-
liveRegion.setAttribute('aria-relevant', 'all');
|
|
150
|
-
liveRegion.setAttribute('aria-label', 'Slide Content: Auto-updating');
|
|
151
|
-
return liveRegion;
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
var a11y = (shower) => {
|
|
155
|
-
const { container } = shower;
|
|
156
|
-
const liveRegion = createLiveRegion();
|
|
157
|
-
container.appendChild(liveRegion);
|
|
158
|
-
|
|
159
|
-
const updateDocumentRole = () => {
|
|
160
|
-
if (shower.isFullMode) {
|
|
161
|
-
container.setAttribute('role', 'application');
|
|
162
|
-
} else {
|
|
163
|
-
container.removeAttribute('role');
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
const updateLiveRegion = () => {
|
|
168
|
-
const slide = shower.activeSlide;
|
|
169
|
-
if (slide) {
|
|
170
|
-
liveRegion.innerHTML = slide.element.innerHTML;
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
shower.addEventListener('start', () => {
|
|
175
|
-
updateDocumentRole();
|
|
176
|
-
updateLiveRegion();
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
shower.addEventListener('modechange', updateDocumentRole);
|
|
180
|
-
shower.addEventListener('slidechange', updateLiveRegion);
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
var keys = (shower) => {
|
|
184
|
-
const doSlideActions = (event) => {
|
|
185
|
-
const isShowerAction = !(event.ctrlKey || event.altKey || event.metaKey);
|
|
186
|
-
|
|
187
|
-
switch (event.key.toUpperCase()) {
|
|
188
|
-
case 'ENTER':
|
|
189
|
-
if (event.metaKey && shower.isListMode) {
|
|
190
|
-
if (event.shiftKey) {
|
|
191
|
-
event.preventDefault();
|
|
192
|
-
shower.first();
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
break;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
event.preventDefault();
|
|
199
|
-
if (event.shiftKey) {
|
|
200
|
-
shower.prev();
|
|
201
|
-
} else {
|
|
202
|
-
shower.next();
|
|
203
|
-
}
|
|
204
|
-
break;
|
|
205
|
-
|
|
206
|
-
case 'BACKSPACE':
|
|
207
|
-
case 'PAGEUP':
|
|
208
|
-
case 'ARROWUP':
|
|
209
|
-
case 'ARROWLEFT':
|
|
210
|
-
case 'H':
|
|
211
|
-
case 'K':
|
|
212
|
-
case 'P':
|
|
213
|
-
if (isShowerAction) {
|
|
214
|
-
event.preventDefault();
|
|
215
|
-
shower.prev(event.shiftKey);
|
|
216
|
-
}
|
|
217
|
-
break;
|
|
218
|
-
|
|
219
|
-
case 'PAGEDOWN':
|
|
220
|
-
case 'ARROWDOWN':
|
|
221
|
-
case 'ARROWRIGHT':
|
|
222
|
-
case 'L':
|
|
223
|
-
case 'J':
|
|
224
|
-
case 'N':
|
|
225
|
-
if (isShowerAction) {
|
|
226
|
-
event.preventDefault();
|
|
227
|
-
shower.next(event.shiftKey);
|
|
228
|
-
}
|
|
229
|
-
break;
|
|
230
|
-
|
|
231
|
-
case ' ':
|
|
232
|
-
if (isShowerAction && shower.isFullMode) {
|
|
233
|
-
event.preventDefault();
|
|
234
|
-
if (event.shiftKey) {
|
|
235
|
-
shower.prev();
|
|
236
|
-
} else {
|
|
237
|
-
shower.next();
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
break;
|
|
241
|
-
|
|
242
|
-
case 'HOME':
|
|
243
|
-
event.preventDefault();
|
|
244
|
-
shower.first();
|
|
245
|
-
break;
|
|
246
|
-
|
|
247
|
-
case 'END':
|
|
248
|
-
event.preventDefault();
|
|
249
|
-
shower.last();
|
|
250
|
-
break;
|
|
251
|
-
}
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
const doModeActions = (event) => {
|
|
255
|
-
switch (event.key.toUpperCase()) {
|
|
256
|
-
case 'ESCAPE':
|
|
257
|
-
if (shower.isFullMode) {
|
|
258
|
-
event.preventDefault();
|
|
259
|
-
shower.exitFullMode();
|
|
260
|
-
}
|
|
261
|
-
break;
|
|
262
|
-
|
|
263
|
-
case 'ENTER':
|
|
264
|
-
if (event.metaKey && shower.isListMode) {
|
|
265
|
-
event.preventDefault();
|
|
266
|
-
shower.enterFullMode();
|
|
267
|
-
}
|
|
268
|
-
break;
|
|
269
|
-
|
|
270
|
-
case 'P':
|
|
271
|
-
if (event.metaKey && event.altKey && shower.isListMode) {
|
|
272
|
-
event.preventDefault();
|
|
273
|
-
shower.enterFullMode();
|
|
274
|
-
}
|
|
275
|
-
break;
|
|
276
|
-
|
|
277
|
-
case 'F5':
|
|
278
|
-
if (event.shiftKey && shower.isListMode) {
|
|
279
|
-
event.preventDefault();
|
|
280
|
-
shower.enterFullMode();
|
|
281
|
-
}
|
|
282
|
-
break;
|
|
283
|
-
}
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
shower.container.addEventListener('keydown', (event) => {
|
|
287
|
-
if (event.defaultPrevented) return;
|
|
288
|
-
if (isInteractiveElement(event.target)) return;
|
|
289
|
-
|
|
290
|
-
doSlideActions(event);
|
|
291
|
-
doModeActions(event);
|
|
292
|
-
});
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
var location$1 = (shower) => {
|
|
296
|
-
const composeURL = () => {
|
|
297
|
-
const search = shower.isFullMode ? '?full' : '';
|
|
298
|
-
const slide = shower.activeSlide;
|
|
299
|
-
const hash = slide ? `#${slide.id}` : '';
|
|
300
|
-
|
|
301
|
-
return location.pathname + search + hash; // path is required to clear search params
|
|
302
|
-
};
|
|
303
|
-
|
|
304
|
-
const applyURLMode = () => {
|
|
305
|
-
const isFull = new URLSearchParams(location.search).has('full');
|
|
306
|
-
if (isFull) {
|
|
307
|
-
shower.enterFullMode();
|
|
308
|
-
} else {
|
|
309
|
-
shower.exitFullMode();
|
|
310
|
-
}
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
const applyURLSlide = () => {
|
|
314
|
-
const id = location.hash.slice(1);
|
|
315
|
-
if (!id) return;
|
|
316
|
-
|
|
317
|
-
const target = shower.slides.find((slide) => slide.id === id);
|
|
318
|
-
if (target) {
|
|
319
|
-
target.activate();
|
|
320
|
-
} else if (!shower.activeSlide) {
|
|
321
|
-
shower.first(); // invalid hash
|
|
322
|
-
}
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
const applyURL = () => {
|
|
326
|
-
applyURLMode();
|
|
327
|
-
applyURLSlide();
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
applyURL();
|
|
331
|
-
window.addEventListener('popstate', applyURL);
|
|
332
|
-
|
|
333
|
-
shower.addEventListener('start', () => {
|
|
334
|
-
history.replaceState(null, document.title, composeURL());
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
shower.addEventListener('modechange', () => {
|
|
338
|
-
history.replaceState(null, document.title, composeURL());
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
shower.addEventListener('slidechange', () => {
|
|
342
|
-
const url = composeURL();
|
|
343
|
-
if (!location.href.endsWith(url)) {
|
|
344
|
-
history.pushState(null, document.title, url);
|
|
345
|
-
}
|
|
346
|
-
});
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
var next = (shower) => {
|
|
350
|
-
const { stepSelector, activeSlideClass, visitedSlideClass } = shower.options;
|
|
351
|
-
|
|
352
|
-
let innerSteps;
|
|
353
|
-
let activeIndex;
|
|
354
|
-
|
|
355
|
-
const isActive = (step) => step.classList.contains(activeSlideClass);
|
|
356
|
-
const isVisited = (step) => step.classList.contains(visitedSlideClass);
|
|
357
|
-
|
|
358
|
-
const setInnerStepsState = () => {
|
|
359
|
-
if (shower.isListMode) return;
|
|
360
|
-
|
|
361
|
-
const slide = shower.activeSlide;
|
|
362
|
-
|
|
363
|
-
innerSteps = [...slide.element.querySelectorAll(stepSelector)];
|
|
364
|
-
activeIndex =
|
|
365
|
-
innerSteps.length && innerSteps.every(isVisited)
|
|
366
|
-
? innerSteps.length
|
|
367
|
-
: innerSteps.filter(isActive).length - 1;
|
|
368
|
-
|
|
369
|
-
slide.state.innerStepCount = innerSteps.length;
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
shower.addEventListener('start', setInnerStepsState);
|
|
373
|
-
shower.addEventListener('modechange', setInnerStepsState);
|
|
374
|
-
shower.addEventListener('slidechange', setInnerStepsState);
|
|
375
|
-
|
|
376
|
-
shower.addEventListener('next', (event) => {
|
|
377
|
-
if (shower.isListMode || event.defaultPrevented || !event.cancelable) return;
|
|
378
|
-
|
|
379
|
-
activeIndex++;
|
|
380
|
-
innerSteps.forEach((step, index) => {
|
|
381
|
-
step.classList.toggle(visitedSlideClass, index < activeIndex);
|
|
382
|
-
step.classList.toggle(activeSlideClass, index === activeIndex);
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
if (activeIndex < innerSteps.length) {
|
|
386
|
-
event.preventDefault();
|
|
387
|
-
}
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
shower.addEventListener('prev', (event) => {
|
|
391
|
-
if (shower.isListMode || event.defaultPrevented || !event.cancelable) return;
|
|
392
|
-
if (activeIndex === -1 || activeIndex === innerSteps.length) return;
|
|
393
|
-
|
|
394
|
-
activeIndex--;
|
|
395
|
-
innerSteps.forEach((step, index) => {
|
|
396
|
-
step.classList.toggle(visitedSlideClass, index < activeIndex + 1);
|
|
397
|
-
step.classList.toggle(activeSlideClass, index === activeIndex);
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
event.preventDefault();
|
|
401
|
-
});
|
|
402
|
-
};
|
|
403
|
-
|
|
404
|
-
var progress = (shower) => {
|
|
405
|
-
const { progressSelector } = shower.options;
|
|
406
|
-
const bar = shower.container.querySelector(progressSelector);
|
|
407
|
-
if (!bar) return;
|
|
408
|
-
|
|
409
|
-
bar.setAttribute('role', 'progressbar');
|
|
410
|
-
bar.setAttribute('aria-valuemin', 0);
|
|
411
|
-
bar.setAttribute('aria-valuemax', 100);
|
|
412
|
-
|
|
413
|
-
const updateProgress = () => {
|
|
414
|
-
const index = shower.activeSlideIndex;
|
|
415
|
-
const { length } = shower.slides;
|
|
416
|
-
const progress = (index / (length - 1)) * 100;
|
|
417
|
-
|
|
418
|
-
bar.style.width = `${progress}%`;
|
|
419
|
-
bar.setAttribute('aria-valuenow', progress);
|
|
420
|
-
bar.setAttribute('aria-valuetext', `Slideshow progress: ${progress}%`);
|
|
421
|
-
};
|
|
422
|
-
|
|
423
|
-
shower.addEventListener('start', updateProgress);
|
|
424
|
-
shower.addEventListener('slidechange', updateProgress);
|
|
425
|
-
};
|
|
426
|
-
|
|
427
|
-
const units = ['s', 'm', 'h'];
|
|
428
|
-
const hasUnits = (timing) => {
|
|
429
|
-
return units.some((unit) => timing.includes(unit));
|
|
430
|
-
};
|
|
431
|
-
|
|
432
|
-
const parseUnits = (timing) => {
|
|
433
|
-
return units.map((unit) => timing.match(`(\\S+)${unit}`)).map((match) => match && match[1]);
|
|
434
|
-
};
|
|
435
|
-
|
|
436
|
-
const parseColons = (timing) => {
|
|
437
|
-
return `::${timing}`.split(':').reverse();
|
|
438
|
-
};
|
|
439
|
-
|
|
440
|
-
const SEC_IN_MIN = 60;
|
|
441
|
-
const SEC_IN_HOUR = SEC_IN_MIN * 60;
|
|
442
|
-
|
|
443
|
-
var parseTiming = (timing) => {
|
|
444
|
-
if (!timing) return 0;
|
|
445
|
-
|
|
446
|
-
const parsed = hasUnits(timing) ? parseUnits(timing) : parseColons(timing);
|
|
447
|
-
|
|
448
|
-
let [sec, min, hour] = parsed.map(Number);
|
|
449
|
-
|
|
450
|
-
sec += min * SEC_IN_MIN;
|
|
451
|
-
sec += hour * SEC_IN_HOUR;
|
|
452
|
-
|
|
453
|
-
return Math.max(sec * 1000, 0);
|
|
454
|
-
};
|
|
455
|
-
|
|
456
|
-
var timer = (shower) => {
|
|
457
|
-
let id;
|
|
458
|
-
|
|
459
|
-
const resetTimer = () => {
|
|
460
|
-
clearTimeout(id);
|
|
461
|
-
if (shower.isListMode) return;
|
|
462
|
-
|
|
463
|
-
const slide = shower.activeSlide;
|
|
464
|
-
const { visitCount, innerStepCount } = slide.state;
|
|
465
|
-
if (visitCount > 1) return;
|
|
466
|
-
|
|
467
|
-
const timing = parseTiming(slide.element.dataset.timing);
|
|
468
|
-
if (!timing) return;
|
|
469
|
-
|
|
470
|
-
if (innerStepCount) {
|
|
471
|
-
const stepTiming = timing / (innerStepCount + 1);
|
|
472
|
-
id = setInterval(() => shower.next(), stepTiming);
|
|
473
|
-
} else {
|
|
474
|
-
id = setTimeout(() => shower.next(), timing);
|
|
475
|
-
}
|
|
476
|
-
};
|
|
477
|
-
|
|
478
|
-
shower.addEventListener('start', resetTimer);
|
|
479
|
-
shower.addEventListener('modechange', resetTimer);
|
|
480
|
-
shower.addEventListener('slidechange', resetTimer);
|
|
481
|
-
|
|
482
|
-
shower.container.addEventListener('keydown', (event) => {
|
|
483
|
-
if (!event.defaultPrevented) {
|
|
484
|
-
clearTimeout(id);
|
|
485
|
-
}
|
|
486
|
-
});
|
|
487
|
-
};
|
|
488
|
-
|
|
489
|
-
const mdash = '\u2014';
|
|
490
|
-
|
|
491
|
-
var title = (shower) => {
|
|
492
|
-
const { title } = document;
|
|
493
|
-
const updateTitle = () => {
|
|
494
|
-
if (shower.isFullMode) {
|
|
495
|
-
const slide = shower.activeSlide;
|
|
496
|
-
const slideTitle = slide.title;
|
|
497
|
-
if (slideTitle) {
|
|
498
|
-
document.title = `${slideTitle} ${mdash} ${title}`;
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
document.title = title;
|
|
504
|
-
};
|
|
505
|
-
|
|
506
|
-
shower.addEventListener('start', updateTitle);
|
|
507
|
-
shower.addEventListener('modechange', updateTitle);
|
|
508
|
-
shower.addEventListener('slidechange', updateTitle);
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
var view = (shower) => {
|
|
512
|
-
const { container } = shower;
|
|
513
|
-
const { fullModeClass, listModeClass } = shower.options;
|
|
514
|
-
|
|
515
|
-
if (container.classList.contains(fullModeClass)) {
|
|
516
|
-
shower.enterFullMode();
|
|
517
|
-
} else {
|
|
518
|
-
container.classList.add(listModeClass);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
const updateScale = () => {
|
|
522
|
-
const firstSlide = shower.slides[0];
|
|
523
|
-
if (!firstSlide) return;
|
|
524
|
-
|
|
525
|
-
const { innerWidth, innerHeight } = window;
|
|
526
|
-
const { offsetWidth, offsetHeight } = firstSlide.element;
|
|
527
|
-
|
|
528
|
-
const listScale = 1 / (offsetWidth / innerWidth);
|
|
529
|
-
const fullScale = 1 / Math.max(offsetWidth / innerWidth, offsetHeight / innerHeight);
|
|
530
|
-
|
|
531
|
-
container.style.setProperty('--shower-list-scale', listScale);
|
|
532
|
-
container.style.setProperty('--shower-full-scale', fullScale);
|
|
533
|
-
};
|
|
534
|
-
|
|
535
|
-
const updateModeView = () => {
|
|
536
|
-
if (shower.isFullMode) {
|
|
537
|
-
container.classList.remove(listModeClass);
|
|
538
|
-
container.classList.add(fullModeClass);
|
|
539
|
-
} else {
|
|
540
|
-
container.classList.remove(fullModeClass);
|
|
541
|
-
container.classList.add(listModeClass);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
updateScale();
|
|
545
|
-
|
|
546
|
-
if (shower.isFullMode) return;
|
|
547
|
-
|
|
548
|
-
const slide = shower.activeSlide;
|
|
549
|
-
if (slide) {
|
|
550
|
-
slide.element.scrollIntoView({ block: 'center' });
|
|
551
|
-
}
|
|
552
|
-
};
|
|
553
|
-
|
|
554
|
-
shower.addEventListener('start', updateModeView);
|
|
555
|
-
shower.addEventListener('modechange', updateModeView);
|
|
556
|
-
shower.addEventListener('slidechange', () => {
|
|
557
|
-
if (shower.isFullMode) return;
|
|
558
|
-
|
|
559
|
-
const slide = shower.activeSlide;
|
|
560
|
-
slide.element.scrollIntoView({ block: 'nearest' });
|
|
561
|
-
});
|
|
562
|
-
|
|
563
|
-
window.addEventListener('resize', updateScale);
|
|
564
|
-
};
|
|
565
|
-
|
|
566
|
-
var touch = (shower) => {
|
|
567
|
-
let exitFullScreen = false;
|
|
568
|
-
let clickable = false;
|
|
569
|
-
|
|
570
|
-
document.addEventListener('touchstart', (event) => {
|
|
571
|
-
if (event.touches.length === 1) {
|
|
572
|
-
const touch = event.touches[0];
|
|
573
|
-
const x = touch.clientX;
|
|
574
|
-
const { target } = touch;
|
|
575
|
-
clickable = target.tabIndex !== -1;
|
|
576
|
-
if (!clickable) {
|
|
577
|
-
if (shower.isFullMode) {
|
|
578
|
-
if (event.cancelable) event.preventDefault();
|
|
579
|
-
if (window.innerWidth / 2 < x) {
|
|
580
|
-
shower.next();
|
|
581
|
-
} else {
|
|
582
|
-
shower.prev();
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
} else if (event.touches.length === 3) {
|
|
587
|
-
exitFullScreen = true;
|
|
588
|
-
}
|
|
589
|
-
});
|
|
590
|
-
|
|
591
|
-
shower.container.addEventListener('touchend', (event) => {
|
|
592
|
-
if (exitFullScreen) {
|
|
593
|
-
event.preventDefault();
|
|
594
|
-
exitFullScreen = false;
|
|
595
|
-
shower.exitFullMode();
|
|
596
|
-
} else if (event.touches.length === 1 && !clickable && shower.isFullMode)
|
|
597
|
-
event.preventDefault();
|
|
598
|
-
});
|
|
599
|
-
};
|
|
600
|
-
|
|
601
|
-
var mouse = (shower) => {
|
|
602
|
-
const { mouseHiddenClass, mouseInactivityTimeout } = shower.options;
|
|
603
|
-
|
|
604
|
-
let hideMouseTimeoutId = null;
|
|
605
|
-
|
|
606
|
-
const cleanUp = () => {
|
|
607
|
-
shower.container.classList.remove(mouseHiddenClass);
|
|
608
|
-
clearTimeout(hideMouseTimeoutId);
|
|
609
|
-
hideMouseTimeoutId = null;
|
|
610
|
-
};
|
|
611
|
-
|
|
612
|
-
const hideMouseIfInactive = () => {
|
|
613
|
-
if (hideMouseTimeoutId !== null) {
|
|
614
|
-
cleanUp();
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
hideMouseTimeoutId = setTimeout(() => {
|
|
618
|
-
shower.container.classList.add(mouseHiddenClass);
|
|
619
|
-
}, mouseInactivityTimeout);
|
|
620
|
-
};
|
|
621
|
-
|
|
622
|
-
const initHideMouseIfInactiveModule = () => {
|
|
623
|
-
shower.container.addEventListener('mousemove', hideMouseIfInactive);
|
|
624
|
-
};
|
|
625
|
-
|
|
626
|
-
const destroyHideMouseIfInactiveModule = () => {
|
|
627
|
-
shower.container.removeEventListener('mousemove', hideMouseIfInactive);
|
|
628
|
-
cleanUp();
|
|
629
|
-
};
|
|
630
|
-
|
|
631
|
-
const handleModeChange = () => {
|
|
632
|
-
if (shower.isFullMode) {
|
|
633
|
-
initHideMouseIfInactiveModule();
|
|
634
|
-
} else {
|
|
635
|
-
destroyHideMouseIfInactiveModule();
|
|
636
|
-
}
|
|
637
|
-
};
|
|
638
|
-
|
|
639
|
-
shower.addEventListener('start', handleModeChange);
|
|
640
|
-
shower.addEventListener('modechange', handleModeChange);
|
|
641
|
-
};
|
|
642
|
-
|
|
643
|
-
var installModules = (shower) => {
|
|
644
|
-
a11y(shower);
|
|
645
|
-
progress(shower);
|
|
646
|
-
keys(shower);
|
|
647
|
-
next(shower);
|
|
648
|
-
timer(shower); // should come after `keys` and `next`
|
|
649
|
-
title(shower);
|
|
650
|
-
location$1(shower); // should come after `title`
|
|
651
|
-
view(shower);
|
|
652
|
-
touch(shower);
|
|
653
|
-
mouse(shower);
|
|
654
|
-
|
|
655
|
-
// maintains invariant: active slide always exists in `full` mode
|
|
656
|
-
if (shower.isFullMode && !shower.activeSlide) {
|
|
657
|
-
shower.first();
|
|
658
|
-
}
|
|
659
|
-
};
|
|
660
|
-
|
|
661
|
-
class Shower extends EventTarget {
|
|
662
|
-
/**
|
|
663
|
-
* @param {object=} options
|
|
664
|
-
*/
|
|
665
|
-
constructor(options) {
|
|
666
|
-
super();
|
|
667
|
-
|
|
668
|
-
defineReadOnly(this, {
|
|
669
|
-
options: { ...defaultOptions, ...options },
|
|
670
|
-
});
|
|
671
|
-
|
|
672
|
-
this._mode = 'list';
|
|
673
|
-
this._isStarted = false;
|
|
674
|
-
this._container = null;
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
/**
|
|
678
|
-
* @param {object=} options
|
|
679
|
-
* @throws {ShowerError}
|
|
680
|
-
*/
|
|
681
|
-
configure(options) {
|
|
682
|
-
if (this._isStarted) {
|
|
683
|
-
throw new ShowerError('Shower should be configured before it is started.');
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
Object.assign(this.options, options);
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
/**
|
|
690
|
-
* @throws {ShowerError}
|
|
691
|
-
* @emits Shower#start
|
|
692
|
-
*/
|
|
693
|
-
start() {
|
|
694
|
-
if (this._isStarted) return;
|
|
695
|
-
|
|
696
|
-
const { containerSelector } = this.options;
|
|
697
|
-
this._container = document.querySelector(containerSelector);
|
|
698
|
-
if (!this._container) {
|
|
699
|
-
throw new ShowerError(
|
|
700
|
-
`Shower container with selector '${containerSelector}' was not found.`,
|
|
701
|
-
);
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
this._initSlides();
|
|
705
|
-
installModules(this);
|
|
706
|
-
|
|
707
|
-
this._isStarted = true;
|
|
708
|
-
this.dispatchEvent(new Event('start'));
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
_initSlides() {
|
|
712
|
-
const visibleSlideSelector = `${this.options.slideSelector}:not([hidden])`;
|
|
713
|
-
const visibleSlideElements = this._container.querySelectorAll(visibleSlideSelector);
|
|
714
|
-
|
|
715
|
-
this.slides = Array.from(visibleSlideElements, (slideElement, index) => {
|
|
716
|
-
if (!slideElement.id) {
|
|
717
|
-
slideElement.id = index + 1;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
return new Slide(this, slideElement);
|
|
721
|
-
});
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
_setMode(mode) {
|
|
725
|
-
if (mode === this._mode) return;
|
|
726
|
-
|
|
727
|
-
this._mode = mode;
|
|
728
|
-
this.dispatchEvent(new Event('modechange'));
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
/**
|
|
732
|
-
* @param {Event} event
|
|
733
|
-
*/
|
|
734
|
-
dispatchEvent(event) {
|
|
735
|
-
if (!this._isStarted) return false;
|
|
736
|
-
|
|
737
|
-
return super.dispatchEvent(event);
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
get container() {
|
|
741
|
-
return this._container;
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
get isFullMode() {
|
|
745
|
-
return this._mode === 'full';
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
get isListMode() {
|
|
749
|
-
return this._mode === 'list';
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
get activeSlide() {
|
|
753
|
-
return this.slides.find((slide) => slide.isActive);
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
get activeSlideIndex() {
|
|
757
|
-
return this.slides.findIndex((slide) => slide.isActive);
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
/**
|
|
761
|
-
* Slide fills the maximum area.
|
|
762
|
-
* @emits Shower#modechange
|
|
763
|
-
*/
|
|
764
|
-
enterFullMode() {
|
|
765
|
-
this._setMode('full');
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
/**
|
|
769
|
-
* Shower returns into list mode.
|
|
770
|
-
* @emits Shower#modechange
|
|
771
|
-
*/
|
|
772
|
-
exitFullMode() {
|
|
773
|
-
this._setMode('list');
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
/**
|
|
777
|
-
* @param {number} index
|
|
778
|
-
*/
|
|
779
|
-
goTo(index) {
|
|
780
|
-
const slide = this.slides[index];
|
|
781
|
-
if (slide) {
|
|
782
|
-
slide.activate();
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
/**
|
|
787
|
-
* @param {number} delta
|
|
788
|
-
*/
|
|
789
|
-
goBy(delta) {
|
|
790
|
-
this.goTo(this.activeSlideIndex + delta);
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
/**
|
|
794
|
-
* @param {boolean} [isForce=false]
|
|
795
|
-
* @emits Shower#prev
|
|
796
|
-
*/
|
|
797
|
-
prev(isForce) {
|
|
798
|
-
const prev = new Event('prev', { cancelable: !isForce });
|
|
799
|
-
if (this.dispatchEvent(prev)) {
|
|
800
|
-
this.goBy(-1);
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
/**
|
|
805
|
-
* @param {boolean} [isForce=false]
|
|
806
|
-
* @emits Shower#next
|
|
807
|
-
*/
|
|
808
|
-
next(isForce) {
|
|
809
|
-
const next = new Event('next', { cancelable: !isForce });
|
|
810
|
-
if (this.dispatchEvent(next)) {
|
|
811
|
-
this.goBy(1);
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
first() {
|
|
816
|
-
this.goTo(0);
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
last() {
|
|
820
|
-
this.goTo(this.slides.length - 1);
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
const options = document.currentScript.dataset;
|
|
825
|
-
const shower = new Shower(options);
|
|
826
|
-
|
|
827
|
-
Object.defineProperty(window, 'shower', {
|
|
828
|
-
value: shower,
|
|
829
|
-
configurable: true,
|
|
830
|
-
});
|
|
831
|
-
|
|
832
|
-
contentLoaded(() => {
|
|
833
|
-
shower.start();
|
|
834
|
-
});
|
|
835
|
-
|
|
836
|
-
})();
|