@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/lib/shower.js
CHANGED
|
@@ -1,169 +1,169 @@
|
|
|
1
|
-
import defaultOptions from './default-options';
|
|
2
|
-
import Slide from './slide';
|
|
3
|
-
import { defineReadOnly, ShowerError } from './utils';
|
|
4
|
-
import installModules from './modules/install';
|
|
1
|
+
import defaultOptions from './default-options.js';
|
|
2
|
+
import Slide from './slide.js';
|
|
3
|
+
import { defineReadOnly, ShowerError } from './utils.js';
|
|
4
|
+
import installModules from './modules/install.js';
|
|
5
5
|
|
|
6
6
|
class Shower extends EventTarget {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
7
|
+
/**
|
|
8
|
+
* @param {object=} options
|
|
9
|
+
*/
|
|
10
|
+
constructor(options) {
|
|
11
|
+
super();
|
|
12
|
+
|
|
13
|
+
defineReadOnly(this, {
|
|
14
|
+
options: { ...defaultOptions, ...options },
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
this._mode = 'list';
|
|
18
|
+
this._isStarted = false;
|
|
19
|
+
this._container = null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {object=} options
|
|
24
|
+
* @throws {ShowerError}
|
|
25
|
+
*/
|
|
26
|
+
configure(options) {
|
|
27
|
+
if (this._isStarted) {
|
|
28
|
+
throw new ShowerError('Shower should be configured before it is started.');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
Object.assign(this.options, options);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @throws {ShowerError}
|
|
36
|
+
* @emits Shower#start
|
|
37
|
+
*/
|
|
38
|
+
start() {
|
|
39
|
+
if (this._isStarted) return;
|
|
40
|
+
|
|
41
|
+
const { containerSelector } = this.options;
|
|
42
|
+
this._container = document.querySelector(containerSelector);
|
|
43
|
+
if (!this._container) {
|
|
44
|
+
throw new ShowerError(
|
|
45
|
+
`Shower container with selector '${containerSelector}' was not found.`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this._initSlides();
|
|
50
|
+
installModules(this);
|
|
51
|
+
|
|
52
|
+
this._isStarted = true;
|
|
53
|
+
this.dispatchEvent(new Event('start'));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
_initSlides() {
|
|
57
|
+
const visibleSlideSelector = `${this.options.slideSelector}:not([hidden])`;
|
|
58
|
+
const visibleSlideElements = this._container.querySelectorAll(visibleSlideSelector);
|
|
59
|
+
|
|
60
|
+
this.slides = Array.from(visibleSlideElements, (slideElement, index) => {
|
|
61
|
+
if (!slideElement.id) {
|
|
62
|
+
slideElement.id = index + 1;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return new Slide(this, slideElement);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
_setMode(mode) {
|
|
70
|
+
if (mode === this._mode) return;
|
|
71
|
+
|
|
72
|
+
this._mode = mode;
|
|
73
|
+
this.dispatchEvent(new Event('modechange'));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @param {Event} event
|
|
78
|
+
*/
|
|
79
|
+
dispatchEvent(event) {
|
|
80
|
+
if (!this._isStarted) return false;
|
|
81
|
+
|
|
82
|
+
return super.dispatchEvent(event);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
get container() {
|
|
86
|
+
return this._container;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
get isFullMode() {
|
|
90
|
+
return this._mode === 'full';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
get isListMode() {
|
|
94
|
+
return this._mode === 'list';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
get activeSlide() {
|
|
98
|
+
return this.slides.find((slide) => slide.isActive);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get activeSlideIndex() {
|
|
102
|
+
return this.slides.findIndex((slide) => slide.isActive);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Slide fills the maximum area.
|
|
107
|
+
* @emits Shower#modechange
|
|
108
|
+
*/
|
|
109
|
+
enterFullMode() {
|
|
110
|
+
this._setMode('full');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Shower returns into list mode.
|
|
115
|
+
* @emits Shower#modechange
|
|
116
|
+
*/
|
|
117
|
+
exitFullMode() {
|
|
118
|
+
this._setMode('list');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* @param {number} index
|
|
123
|
+
*/
|
|
124
|
+
goTo(index) {
|
|
125
|
+
const slide = this.slides[index];
|
|
126
|
+
if (slide) {
|
|
127
|
+
slide.activate();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @param {number} delta
|
|
133
|
+
*/
|
|
134
|
+
goBy(delta) {
|
|
135
|
+
this.goTo(this.activeSlideIndex + delta);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @param {boolean} [isForce=false]
|
|
140
|
+
* @emits Shower#prev
|
|
141
|
+
*/
|
|
142
|
+
prev(isForce) {
|
|
143
|
+
const prev = new Event('prev', { cancelable: !isForce });
|
|
144
|
+
if (this.dispatchEvent(prev)) {
|
|
145
|
+
this.goBy(-1);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @param {boolean} [isForce=false]
|
|
151
|
+
* @emits Shower#next
|
|
152
|
+
*/
|
|
153
|
+
next(isForce) {
|
|
154
|
+
const next = new Event('next', { cancelable: !isForce });
|
|
155
|
+
if (this.dispatchEvent(next)) {
|
|
156
|
+
this.goBy(1);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
first() {
|
|
161
|
+
this.goTo(0);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
last() {
|
|
165
|
+
this.goTo(this.slides.length - 1);
|
|
166
|
+
}
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
export default Shower;
|
package/lib/slide.js
CHANGED
|
@@ -1,99 +1,99 @@
|
|
|
1
|
-
import { defineReadOnly, ShowerError } from './utils';
|
|
1
|
+
import { defineReadOnly, ShowerError } from './utils.js';
|
|
2
2
|
|
|
3
3
|
class Slide extends EventTarget {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
4
|
+
/**
|
|
5
|
+
* @param {Shower} shower
|
|
6
|
+
* @param {HTMLElement} element
|
|
7
|
+
*/
|
|
8
|
+
constructor(shower, element) {
|
|
9
|
+
super();
|
|
10
|
+
|
|
11
|
+
defineReadOnly(this, {
|
|
12
|
+
shower,
|
|
13
|
+
element,
|
|
14
|
+
state: {
|
|
15
|
+
visitCount: 0,
|
|
16
|
+
innerStepCount: 0,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
this._isActive = false;
|
|
21
|
+
this._options = this.shower.options;
|
|
22
|
+
|
|
23
|
+
this.element.addEventListener('click', (event) => {
|
|
24
|
+
if (event.defaultPrevented) return;
|
|
25
|
+
|
|
26
|
+
this.activate();
|
|
27
|
+
this.shower.enterFullMode();
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get isActive() {
|
|
32
|
+
return this._isActive;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get isVisited() {
|
|
36
|
+
return this.state.visitCount > 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get id() {
|
|
40
|
+
return this.element.id;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get title() {
|
|
44
|
+
const titleElement = this.element.querySelector(this._options.slideTitleSelector);
|
|
45
|
+
return titleElement ? titleElement.innerText : '';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Deactivates currently active slide (if any) and activates itself.
|
|
50
|
+
* @emits Slide#deactivate
|
|
51
|
+
* @emits Slide#activate
|
|
52
|
+
* @emits Shower#slidechange
|
|
53
|
+
*/
|
|
54
|
+
activate() {
|
|
55
|
+
if (this._isActive) return;
|
|
56
|
+
|
|
57
|
+
const prev = this.shower.activeSlide;
|
|
58
|
+
if (prev) {
|
|
59
|
+
prev._deactivate();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.state.visitCount++;
|
|
63
|
+
this.element.classList.add(this._options.activeSlideClass);
|
|
64
|
+
|
|
65
|
+
this._isActive = true;
|
|
66
|
+
this.dispatchEvent(new Event('activate'));
|
|
67
|
+
this.shower.dispatchEvent(
|
|
68
|
+
new CustomEvent('slidechange', {
|
|
69
|
+
detail: { prev },
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @throws {ShowerError}
|
|
76
|
+
* @emits Slide#deactivate
|
|
77
|
+
*/
|
|
78
|
+
deactivate() {
|
|
79
|
+
if (this.shower.isFullMode) {
|
|
80
|
+
throw new ShowerError('In full mode, another slide should be activated instead.');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (this._isActive) {
|
|
84
|
+
this._deactivate();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
_deactivate() {
|
|
89
|
+
this.element.classList.replace(
|
|
90
|
+
this._options.activeSlideClass,
|
|
91
|
+
this._options.visitedSlideClass,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
this._isActive = false;
|
|
95
|
+
this.dispatchEvent(new Event('deactivate'));
|
|
96
|
+
}
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
export default Slide;
|
package/lib/utils.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
export const isInteractiveElement = (element) => element.tabIndex !== -1;
|
|
1
|
+
export const isInteractiveElement = (element) => element.tabIndex !== -1 || element.isContentEditable;
|
|
2
2
|
|
|
3
3
|
export const contentLoaded = (callback) => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
if (document.currentScript.async) {
|
|
5
|
+
callback();
|
|
6
|
+
} else {
|
|
7
|
+
document.addEventListener('DOMContentLoaded', callback);
|
|
8
|
+
}
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
export const defineReadOnly = (target, props) => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
for (const [key, value] of Object.entries(props)) {
|
|
13
|
+
Object.defineProperty(target, key, {
|
|
14
|
+
value,
|
|
15
|
+
writable: false,
|
|
16
|
+
enumerable: true,
|
|
17
|
+
configurable: true,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
export class ShowerError extends Error {}
|
package/package.json
CHANGED
|
@@ -1,63 +1,40 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"puppeteer": "^3.3.0",
|
|
41
|
-
"rollup": "^2.58.0",
|
|
42
|
-
"sauce-connect-launcher": "^1.3.2",
|
|
43
|
-
"serve": "^11.3.2",
|
|
44
|
-
"serve-handler": "^6.1.3"
|
|
45
|
-
},
|
|
46
|
-
"scripts": {
|
|
47
|
-
"build": "rollup --config",
|
|
48
|
-
"build:watch": "rollup --config --watch",
|
|
49
|
-
"serve": "serve dist",
|
|
50
|
-
"lint": "eslint '**/*.{js,mjs}'",
|
|
51
|
-
"lint:fix": "eslint '**/*.{js,mjs}' --fix",
|
|
52
|
-
"format": "npm run format:js && npm run format:etc",
|
|
53
|
-
"format:js": "prettier '**/*.{js,mjs}' --write",
|
|
54
|
-
"format:etc": "prettier '**/*.{json,md,yml}' --write",
|
|
55
|
-
"test": "npm run lint && npm run test:unit && npm run test:local",
|
|
56
|
-
"test:unit": "mocha test/unit --require chai/register-should",
|
|
57
|
-
"test:local": "nightwatch --env chrome-local --timeout 500",
|
|
58
|
-
"test:sauce": "nightwatch --env chrome,firefox,safari"
|
|
59
|
-
},
|
|
60
|
-
"config": {
|
|
61
|
-
"test_port": 8031
|
|
62
|
-
}
|
|
2
|
+
"type": "module",
|
|
3
|
+
"name": "@shower/core",
|
|
4
|
+
"description": "Core for Shower HTML presentation engine",
|
|
5
|
+
"version": "3.4.0",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"author": {
|
|
10
|
+
"name": "Vadim Makeev",
|
|
11
|
+
"email": "hi@pepelsbey.dev",
|
|
12
|
+
"url": "https://pepelsbey.dev"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/shower/shower/tree/main/packages/core",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git://github.com/shower/shower.git",
|
|
18
|
+
"directory": "packages/core"
|
|
19
|
+
},
|
|
20
|
+
"bugs": "https://github.com/shower/shower/issues",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"keywords": [
|
|
23
|
+
"shower",
|
|
24
|
+
"presentation",
|
|
25
|
+
"core"
|
|
26
|
+
],
|
|
27
|
+
"main": "lib/index.js",
|
|
28
|
+
"files": [
|
|
29
|
+
"lib",
|
|
30
|
+
"dist/index.js",
|
|
31
|
+
"LICENSE.md",
|
|
32
|
+
"README.md"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "rollup --config",
|
|
36
|
+
"build:watch": "rollup --config --watch",
|
|
37
|
+
"test": "npm run test:e2e",
|
|
38
|
+
"test:e2e": "playwright test"
|
|
39
|
+
}
|
|
63
40
|
}
|