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