@materializecss/materialize 1.2.2 → 2.0.0-alpha
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/Gruntfile.js +68 -313
- package/README.md +2 -2
- package/dist/css/materialize.css +1009 -1822
- package/dist/css/materialize.min.css +2 -8
- package/dist/js/materialize.js +8402 -12300
- package/dist/js/materialize.min.js +3 -2
- package/dist/js/materialize.min.js.map +1 -0
- package/package.json +13 -9
- package/sass/components/_badges.scss +12 -2
- package/sass/components/_buttons.scss +16 -11
- package/sass/components/_cards.scss +14 -9
- package/sass/components/_carousel.scss +5 -2
- package/sass/components/_chips.scss +3 -3
- package/sass/components/_collapsible.scss +22 -8
- package/sass/components/_collection.scss +14 -6
- package/sass/components/_datepicker.scss +30 -11
- package/sass/components/_dropdown.scss +6 -4
- package/sass/components/_global.scss +132 -111
- package/sass/components/_grid.scss +119 -98
- package/sass/components/_modal.scss +3 -3
- package/sass/components/_navbar.scss +31 -17
- package/sass/components/_normalize.scss +26 -124
- package/sass/components/_sidenav.scss +21 -20
- package/sass/components/_slider.scss +27 -7
- package/sass/components/_table_of_contents.scss +12 -12
- package/sass/components/_tabs.scss +47 -16
- package/sass/components/_tapTarget.scss +6 -6
- package/sass/components/_timepicker.scss +54 -46
- package/sass/components/_toast.scss +3 -3
- package/sass/components/_tooltip.scss +4 -5
- package/sass/components/_typography.scss +1 -1
- package/sass/components/_variables.scss +185 -120
- package/sass/components/forms/_checkboxes.scss +9 -9
- package/sass/components/forms/_file-input.scss +9 -7
- package/sass/components/forms/_input-fields.scss +173 -234
- package/sass/components/forms/_radio-buttons.scss +1 -1
- package/sass/components/forms/_range.scss +11 -11
- package/sass/components/forms/_select.scss +29 -19
- package/sass/components/forms/_switches.scss +22 -18
- package/sass/materialize.scss +1 -1
- package/src/autocomplete.ts +459 -0
- package/src/bounding.ts +6 -0
- package/{js/buttons.js → src/buttons.ts} +103 -162
- package/src/cards.ts +54 -0
- package/{js/carousel.js → src/carousel.ts} +137 -262
- package/src/characterCounter.ts +88 -0
- package/src/chips.ts +350 -0
- package/src/collapsible.ts +184 -0
- package/{js/component.js → src/component.ts} +6 -19
- package/{js/datepicker.js → src/datepicker.ts} +213 -299
- package/{js/dropdown.js → src/dropdown.ts} +140 -254
- package/src/edges.ts +6 -0
- package/src/forms.ts +120 -0
- package/src/global.ts +385 -0
- package/src/materialbox.ts +348 -0
- package/src/modal.ts +256 -0
- package/{js/parallax.js → src/parallax.ts} +47 -60
- package/{js/pushpin.js → src/pushpin.ts} +19 -47
- package/{js/range.js → src/range.ts} +58 -139
- package/{js/scrollspy.js → src/scrollspy.ts} +81 -153
- package/src/select.ts +448 -0
- package/{js/sidenav.js → src/sidenav.ts} +96 -202
- package/src/slider.ts +415 -0
- package/src/tabs.ts +290 -0
- package/src/tapTarget.ts +240 -0
- package/{js/timepicker.js → src/timepicker.ts} +268 -272
- package/{js/toasts.js → src/toasts.ts} +75 -134
- package/{js/tooltip.js → src/tooltip.ts} +59 -96
- package/src/waves.ts +70 -0
- package/extras/noUiSlider/nouislider.css +0 -404
- package/extras/noUiSlider/nouislider.js +0 -2147
- package/extras/noUiSlider/nouislider.min.js +0 -1
- package/js/anime.min.js +0 -34
- package/js/autocomplete.js +0 -479
- package/js/cards.js +0 -40
- package/js/cash.js +0 -960
- package/js/characterCounter.js +0 -136
- package/js/chips.js +0 -486
- package/js/collapsible.js +0 -275
- package/js/forms.js +0 -285
- package/js/global.js +0 -428
- package/js/materialbox.js +0 -453
- package/js/modal.js +0 -382
- package/js/select.js +0 -391
- package/js/slider.js +0 -497
- package/js/tabs.js +0 -402
- package/js/tapTarget.js +0 -315
- package/js/waves.js +0 -615
- package/sass/components/_waves.scss +0 -187
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { Component } from "./component";
|
|
2
|
+
import anim from "animejs";
|
|
3
|
+
import { M } from "./global";
|
|
4
|
+
|
|
5
|
+
const _defaults = {
|
|
6
|
+
inDuration: 275,
|
|
7
|
+
outDuration: 200,
|
|
8
|
+
onOpenStart: null,
|
|
9
|
+
onOpenEnd: null,
|
|
10
|
+
onCloseStart: null,
|
|
11
|
+
onCloseEnd: null
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export class Materialbox extends Component {
|
|
15
|
+
el: HTMLElement;
|
|
16
|
+
overlayActive: boolean;
|
|
17
|
+
doneAnimating: boolean;
|
|
18
|
+
caption: string;
|
|
19
|
+
originalWidth: number;
|
|
20
|
+
originalHeight: number;
|
|
21
|
+
private originInlineStyles: string;
|
|
22
|
+
private placeholder: HTMLElement;
|
|
23
|
+
private _changedAncestorList: HTMLElement[];
|
|
24
|
+
private newHeight: number;
|
|
25
|
+
private newWidth: number;
|
|
26
|
+
private windowWidth: number;
|
|
27
|
+
private windowHeight: number;
|
|
28
|
+
private attrWidth: string;
|
|
29
|
+
private attrHeight: string;
|
|
30
|
+
private _overlay: HTMLElement;
|
|
31
|
+
private _photoCaption: HTMLElement;
|
|
32
|
+
private _handleMaterialboxClickBound: any;
|
|
33
|
+
private _handleWindowScrollBound: any;
|
|
34
|
+
private _handleWindowResizeBound: any;
|
|
35
|
+
private _handleWindowEscapeBound: any;
|
|
36
|
+
|
|
37
|
+
constructor(el, options) {
|
|
38
|
+
super(Materialbox, el, options);
|
|
39
|
+
(this.el as any).M_Materialbox = this;
|
|
40
|
+
this.options = {...Materialbox.defaults, ...options};
|
|
41
|
+
this.overlayActive = false;
|
|
42
|
+
this.doneAnimating = true;
|
|
43
|
+
this.placeholder = document.createElement('div');
|
|
44
|
+
this.placeholder.classList.add('material-placeholder');
|
|
45
|
+
this.originalWidth = 0;
|
|
46
|
+
this.originalHeight = 0;
|
|
47
|
+
this.originInlineStyles = this.el.getAttribute('style');
|
|
48
|
+
this.caption = this.el.getAttribute('data-caption') || '';
|
|
49
|
+
// Wrap
|
|
50
|
+
this.el.before(this.placeholder);
|
|
51
|
+
this.placeholder.append(this.el);
|
|
52
|
+
this._setupEventHandlers();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static get defaults() {
|
|
56
|
+
return _defaults;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static init(els, options) {
|
|
60
|
+
return super.init(this, els, options);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
static getInstance(el) {
|
|
64
|
+
const domElem = !!el.jquery ? el[0] : el;
|
|
65
|
+
return domElem.M_Materialbox;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
destroy() {
|
|
69
|
+
this._removeEventHandlers();
|
|
70
|
+
(this.el as any).M_Materialbox = undefined;
|
|
71
|
+
// Unwrap image
|
|
72
|
+
//this.placeholder.after(this.el).remove();
|
|
73
|
+
this.placeholder.remove();
|
|
74
|
+
this.el.removeAttribute('style');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_setupEventHandlers() {
|
|
78
|
+
this._handleMaterialboxClickBound = this._handleMaterialboxClick.bind(this);
|
|
79
|
+
this.el.addEventListener('click', this._handleMaterialboxClickBound);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
_removeEventHandlers() {
|
|
83
|
+
this.el.removeEventListener('click', this._handleMaterialboxClickBound);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
_handleMaterialboxClick(e) {
|
|
87
|
+
// If already modal, return to original
|
|
88
|
+
if (this.doneAnimating === false || (this.overlayActive && this.doneAnimating))
|
|
89
|
+
this.close();
|
|
90
|
+
else
|
|
91
|
+
this.open();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
_handleWindowScroll() {
|
|
95
|
+
if (this.overlayActive) this.close();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
_handleWindowResize() {
|
|
99
|
+
if (this.overlayActive) this.close();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
_handleWindowEscape(e) {
|
|
103
|
+
// ESC key
|
|
104
|
+
if (e.keyCode === 27 && this.doneAnimating && this.overlayActive) this.close();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
_makeAncestorsOverflowVisible() {
|
|
108
|
+
this._changedAncestorList = [];
|
|
109
|
+
let ancestor = this.placeholder.parentNode;
|
|
110
|
+
while (ancestor !== null && ancestor !== document) {
|
|
111
|
+
const curr = <HTMLElement>ancestor;
|
|
112
|
+
if (curr.style.overflow !== 'visible') {
|
|
113
|
+
curr.style.overflow = 'visible';
|
|
114
|
+
this._changedAncestorList.push(curr);
|
|
115
|
+
}
|
|
116
|
+
ancestor = ancestor.parentNode;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private _offset(el) {
|
|
121
|
+
const box = el.getBoundingClientRect();
|
|
122
|
+
const docElem = document.documentElement;
|
|
123
|
+
return {
|
|
124
|
+
top: box.top + window.pageYOffset - docElem.clientTop,
|
|
125
|
+
left: box.left + window.pageXOffset - docElem.clientLeft
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
_animateImageIn() {
|
|
130
|
+
this.el.style.maxHeight = this.newHeight.toString()+'px';
|
|
131
|
+
this.el.style.maxWidth = this.newWidth.toString()+'px';
|
|
132
|
+
|
|
133
|
+
const animOptions = {
|
|
134
|
+
targets: this.el, // image
|
|
135
|
+
height: [this.originalHeight, this.newHeight],
|
|
136
|
+
width: [this.originalWidth, this.newWidth],
|
|
137
|
+
left:
|
|
138
|
+
M.getDocumentScrollLeft() +
|
|
139
|
+
this.windowWidth / 2 -
|
|
140
|
+
this._offset(this.placeholder).left -
|
|
141
|
+
this.newWidth / 2,
|
|
142
|
+
top:
|
|
143
|
+
M.getDocumentScrollTop() +
|
|
144
|
+
this.windowHeight / 2 -
|
|
145
|
+
this._offset(this.placeholder).top -
|
|
146
|
+
this.newHeight / 2,
|
|
147
|
+
duration: this.options.inDuration,
|
|
148
|
+
easing: 'easeOutQuad',
|
|
149
|
+
complete: () => {
|
|
150
|
+
this.doneAnimating = true;
|
|
151
|
+
// onOpenEnd callback
|
|
152
|
+
if (typeof this.options.onOpenEnd === 'function') {
|
|
153
|
+
this.options.onOpenEnd.call(this, this.el);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
// Override max-width or max-height if needed
|
|
158
|
+
//const elStyle = this.el.style;
|
|
159
|
+
//console.log('mh', elStyle.maxHeight, '->', this.newHeight);
|
|
160
|
+
//console.log('mw', elStyle.maxWidth, '->', this.newWidth);
|
|
161
|
+
//if (elStyle.maxWidth !== 'none') animOptions.maxWidth = this.newWidth;
|
|
162
|
+
//if (elStyle.maxHeight !== 'none') animOptions.maxHeight = this.newHeight;
|
|
163
|
+
//console.log('>>> animate');
|
|
164
|
+
//console.log(JSON.stringify(animOptions));
|
|
165
|
+
anim(animOptions);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
_animateImageOut() {
|
|
169
|
+
const animOptions = {
|
|
170
|
+
targets: this.el,
|
|
171
|
+
width: this.originalWidth,
|
|
172
|
+
height: this.originalHeight,
|
|
173
|
+
left: 0,
|
|
174
|
+
top: 0,
|
|
175
|
+
duration: this.options.outDuration,
|
|
176
|
+
easing: 'easeOutQuad',
|
|
177
|
+
complete: () => {
|
|
178
|
+
this.placeholder.style.height = '';
|
|
179
|
+
this.placeholder.style.width = '';
|
|
180
|
+
this.placeholder.style.position = '';
|
|
181
|
+
this.placeholder.style.top = '';
|
|
182
|
+
this.placeholder.style.left = '';
|
|
183
|
+
// Revert to width or height attribute
|
|
184
|
+
if (this.attrWidth) this.el.setAttribute('width', this.attrWidth.toString());
|
|
185
|
+
if (this.attrHeight) this.el.setAttribute('height', this.attrHeight.toString());
|
|
186
|
+
this.el.removeAttribute('style');
|
|
187
|
+
this.originInlineStyles && this.el.setAttribute('style', this.originInlineStyles);
|
|
188
|
+
// Remove class
|
|
189
|
+
this.el.classList.remove('active');
|
|
190
|
+
this.doneAnimating = true;
|
|
191
|
+
// Remove overflow overrides on ancestors
|
|
192
|
+
this._changedAncestorList.forEach(anchestor => anchestor.style.overflow = '');
|
|
193
|
+
// onCloseEnd callback
|
|
194
|
+
if (typeof this.options.onCloseEnd === 'function') {
|
|
195
|
+
this.options.onCloseEnd.call(this, this.el);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
anim(animOptions);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
_updateVars() {
|
|
203
|
+
this.windowWidth = window.innerWidth;
|
|
204
|
+
this.windowHeight = window.innerHeight;
|
|
205
|
+
this.caption = this.el.getAttribute('data-caption') || '';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
open() {
|
|
209
|
+
this._updateVars();
|
|
210
|
+
this.originalWidth = this.el.getBoundingClientRect().width;
|
|
211
|
+
this.originalHeight = this.el.getBoundingClientRect().height;
|
|
212
|
+
// Set states
|
|
213
|
+
this.doneAnimating = false;
|
|
214
|
+
this.el.classList.add('active');
|
|
215
|
+
this.overlayActive = true;
|
|
216
|
+
// onOpenStart callback
|
|
217
|
+
if (typeof this.options.onOpenStart === 'function') {
|
|
218
|
+
this.options.onOpenStart.call(this, this.el);
|
|
219
|
+
}
|
|
220
|
+
// Set positioning for placeholder
|
|
221
|
+
this.placeholder.style.width = this.placeholder.getBoundingClientRect().width+'px';
|
|
222
|
+
this.placeholder.style.height = this.placeholder.getBoundingClientRect().height+'px';
|
|
223
|
+
this.placeholder.style.position = 'relative';
|
|
224
|
+
this.placeholder.style.top = '0';
|
|
225
|
+
this.placeholder.style.left = '0';
|
|
226
|
+
this._makeAncestorsOverflowVisible();
|
|
227
|
+
// Set css on origin
|
|
228
|
+
this.el.style.position = 'absolute';
|
|
229
|
+
this.el.style.zIndex = '1000';
|
|
230
|
+
this.el.style.willChange = 'left, top, width, height';
|
|
231
|
+
// Change from width or height attribute to css
|
|
232
|
+
this.attrWidth = this.el.getAttribute('width');
|
|
233
|
+
this.attrHeight = this.el.getAttribute('height');
|
|
234
|
+
if (this.attrWidth) {
|
|
235
|
+
this.el.style.width = this.attrWidth+'px';
|
|
236
|
+
this.el.removeAttribute('width');
|
|
237
|
+
}
|
|
238
|
+
if (this.attrHeight) {
|
|
239
|
+
this.el.style.width = this.attrHeight+'px';
|
|
240
|
+
this.el.removeAttribute('height');
|
|
241
|
+
}
|
|
242
|
+
// Add overlay
|
|
243
|
+
this._overlay = document.createElement('div');
|
|
244
|
+
this._overlay.id = 'materialbox-overlay';
|
|
245
|
+
this._overlay.style.opacity = '0';
|
|
246
|
+
this._overlay.addEventListener('click', e => {
|
|
247
|
+
if (this.doneAnimating) this.close();
|
|
248
|
+
}, {once: true});
|
|
249
|
+
// Put before in origin image to preserve z-index layering.
|
|
250
|
+
this.el.before(this._overlay);
|
|
251
|
+
// Set dimensions if needed
|
|
252
|
+
const overlayOffset = this._overlay.getBoundingClientRect();
|
|
253
|
+
this._overlay.style.width = this.windowWidth+'px';
|
|
254
|
+
this._overlay.style.height = this.windowHeight+'px';
|
|
255
|
+
this._overlay.style.left = -1 * overlayOffset.left+'px';
|
|
256
|
+
this._overlay.style.top = -1 * overlayOffset.top+'px';
|
|
257
|
+
anim.remove(this.el);
|
|
258
|
+
anim.remove(this._overlay);
|
|
259
|
+
// Animate Overlay
|
|
260
|
+
anim({
|
|
261
|
+
targets: this._overlay,
|
|
262
|
+
opacity: 1,
|
|
263
|
+
duration: this.options.inDuration,
|
|
264
|
+
easing: 'easeOutQuad'
|
|
265
|
+
});
|
|
266
|
+
// Add and animate caption if it exists
|
|
267
|
+
if (this.caption !== '') {
|
|
268
|
+
if (this._photoCaption) anim.remove(this._photoCaption);
|
|
269
|
+
this._photoCaption = document.createElement('div');
|
|
270
|
+
this._photoCaption.classList.add('materialbox-caption');
|
|
271
|
+
this._photoCaption.innerText = this.caption;
|
|
272
|
+
document.body.append(this._photoCaption);
|
|
273
|
+
this._photoCaption.style.display = 'inline';
|
|
274
|
+
anim({
|
|
275
|
+
targets: this._photoCaption,
|
|
276
|
+
opacity: 1,
|
|
277
|
+
duration: this.options.inDuration,
|
|
278
|
+
easing: 'easeOutQuad'
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Resize Image
|
|
283
|
+
const widthPercent = this.originalWidth / this.windowWidth;
|
|
284
|
+
const heightPercent = this.originalHeight / this.windowHeight;
|
|
285
|
+
this.newWidth = 0;
|
|
286
|
+
this.newHeight = 0;
|
|
287
|
+
if (widthPercent > heightPercent) {
|
|
288
|
+
// Width first
|
|
289
|
+
const ratio = this.originalHeight / this.originalWidth;
|
|
290
|
+
this.newWidth = this.windowWidth * 0.9;
|
|
291
|
+
this.newHeight = this.windowWidth * 0.9 * ratio;
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
// Height first
|
|
295
|
+
const ratio = this.originalWidth / this.originalHeight;
|
|
296
|
+
this.newWidth = this.windowHeight * 0.9 * ratio;
|
|
297
|
+
this.newHeight = this.windowHeight * 0.9;
|
|
298
|
+
}
|
|
299
|
+
this._animateImageIn();
|
|
300
|
+
|
|
301
|
+
// Handle Exit triggers
|
|
302
|
+
this._handleWindowScrollBound = this._handleWindowScroll.bind(this);
|
|
303
|
+
this._handleWindowResizeBound = this._handleWindowResize.bind(this);
|
|
304
|
+
this._handleWindowEscapeBound = this._handleWindowEscape.bind(this);
|
|
305
|
+
window.addEventListener('scroll', this._handleWindowScrollBound);
|
|
306
|
+
window.addEventListener('resize', this._handleWindowResizeBound);
|
|
307
|
+
window.addEventListener('keyup', this._handleWindowEscapeBound);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
close() {
|
|
311
|
+
this._updateVars();
|
|
312
|
+
this.doneAnimating = false;
|
|
313
|
+
// onCloseStart callback
|
|
314
|
+
if (typeof this.options.onCloseStart === 'function') {
|
|
315
|
+
this.options.onCloseStart.call(this, this.el);
|
|
316
|
+
}
|
|
317
|
+
anim.remove(this.el);
|
|
318
|
+
anim.remove(this._overlay);
|
|
319
|
+
if (this.caption !== '') anim.remove(this._photoCaption);
|
|
320
|
+
// disable exit handlers
|
|
321
|
+
window.removeEventListener('scroll', this._handleWindowScrollBound);
|
|
322
|
+
window.removeEventListener('resize', this._handleWindowResizeBound);
|
|
323
|
+
window.removeEventListener('keyup', this._handleWindowEscapeBound);
|
|
324
|
+
anim({
|
|
325
|
+
targets: this._overlay,
|
|
326
|
+
opacity: 0,
|
|
327
|
+
duration: this.options.outDuration,
|
|
328
|
+
easing: 'easeOutQuad',
|
|
329
|
+
complete: () => {
|
|
330
|
+
this.overlayActive = false;
|
|
331
|
+
this._overlay.remove();
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
this._animateImageOut();
|
|
335
|
+
// Remove Caption + reset css settings on image
|
|
336
|
+
if (this.caption !== '') {
|
|
337
|
+
anim({
|
|
338
|
+
targets: this._photoCaption,
|
|
339
|
+
opacity: 0,
|
|
340
|
+
duration: this.options.outDuration,
|
|
341
|
+
easing: 'easeOutQuad',
|
|
342
|
+
complete: () => {
|
|
343
|
+
this._photoCaption.remove();
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
package/src/modal.ts
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { Component } from "./component";
|
|
2
|
+
import anim from "animejs";
|
|
3
|
+
import { M } from "./global";
|
|
4
|
+
|
|
5
|
+
const _defaults = {
|
|
6
|
+
opacity: 0.5,
|
|
7
|
+
inDuration: 250,
|
|
8
|
+
outDuration: 250,
|
|
9
|
+
onOpenStart: null,
|
|
10
|
+
onOpenEnd: null,
|
|
11
|
+
onCloseStart: null,
|
|
12
|
+
onCloseEnd: null,
|
|
13
|
+
preventScrolling: true,
|
|
14
|
+
dismissible: true,
|
|
15
|
+
startingTop: '4%',
|
|
16
|
+
endingTop: '10%'
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export class Modal extends Component {
|
|
20
|
+
el: HTMLElement;
|
|
21
|
+
static _modalsOpen: number;
|
|
22
|
+
static _count: number;
|
|
23
|
+
isOpen: boolean;
|
|
24
|
+
id: string;
|
|
25
|
+
private _openingTrigger: any;
|
|
26
|
+
private _overlay: HTMLElement;
|
|
27
|
+
private _nthModalOpened: number;
|
|
28
|
+
private _handleOverlayClickBound: any;
|
|
29
|
+
private _handleModalCloseClickBound: any;
|
|
30
|
+
private _handleKeydownBound: any;
|
|
31
|
+
private _handleFocusBound: any;
|
|
32
|
+
|
|
33
|
+
constructor(el, options) {
|
|
34
|
+
super(Modal, el, options);
|
|
35
|
+
(this.el as any).M_Modal = this;
|
|
36
|
+
this.options = {...Modal.defaults, ...options};
|
|
37
|
+
this.isOpen = false;
|
|
38
|
+
this.id = this.el.id;
|
|
39
|
+
this._openingTrigger = undefined;
|
|
40
|
+
this._overlay = document.createElement('div');
|
|
41
|
+
this._overlay.classList.add('modal-overlay');
|
|
42
|
+
this.el.tabIndex = 0;
|
|
43
|
+
this._nthModalOpened = 0;
|
|
44
|
+
Modal._count++;
|
|
45
|
+
this._setupEventHandlers();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static get defaults() {
|
|
49
|
+
return _defaults;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static init(els, options) {
|
|
53
|
+
return super.init(this, els, options);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static getInstance(el) {
|
|
57
|
+
const domElem = !!el.jquery ? el[0] : el;
|
|
58
|
+
return domElem.M_Modal;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
destroy() {
|
|
62
|
+
Modal._count--;
|
|
63
|
+
this._removeEventHandlers();
|
|
64
|
+
this.el.removeAttribute('style');
|
|
65
|
+
this._overlay.remove();
|
|
66
|
+
(this.el as any).M_Modal = undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
_setupEventHandlers() {
|
|
70
|
+
this._handleOverlayClickBound = this._handleOverlayClick.bind(this);
|
|
71
|
+
this._handleModalCloseClickBound = this._handleModalCloseClick.bind(this);
|
|
72
|
+
if (Modal._count === 1) {
|
|
73
|
+
document.body.addEventListener('click', this._handleTriggerClick);
|
|
74
|
+
}
|
|
75
|
+
this._overlay.addEventListener('click', this._handleOverlayClickBound);
|
|
76
|
+
this.el.addEventListener('click', this._handleModalCloseClickBound);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
_removeEventHandlers() {
|
|
80
|
+
if (Modal._count === 0) {
|
|
81
|
+
document.body.removeEventListener('click', this._handleTriggerClick);
|
|
82
|
+
}
|
|
83
|
+
this._overlay.removeEventListener('click', this._handleOverlayClickBound);
|
|
84
|
+
this.el.removeEventListener('click', this._handleModalCloseClickBound);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
_handleTriggerClick(e) {
|
|
88
|
+
const trigger = e.target.closest('.modal-trigger');
|
|
89
|
+
if (!trigger) return;
|
|
90
|
+
const modalId = M.getIdFromTrigger(trigger);
|
|
91
|
+
const modalInstance = (document.getElementById(modalId) as any).M_Modal;
|
|
92
|
+
if (modalInstance) modalInstance.open(trigger);
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
_handleOverlayClick() {
|
|
97
|
+
if (this.options.dismissible) this.close();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
_handleModalCloseClick(e) {
|
|
101
|
+
const closeTrigger = e.target.closest('.modal-close');
|
|
102
|
+
if (closeTrigger) this.close();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
_handleKeydown(e) {
|
|
106
|
+
// ESC key
|
|
107
|
+
if (e.keyCode === 27 && this.options.dismissible) this.close();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
_handleFocus(e) {
|
|
111
|
+
// Only trap focus if this modal is the last model opened (prevents loops in nested modals).
|
|
112
|
+
if (!this.el.contains(e.target) && this._nthModalOpened === Modal._modalsOpen) {
|
|
113
|
+
this.el.focus();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
_animateIn() {
|
|
118
|
+
// Set initial styles
|
|
119
|
+
this.el.style.display = 'block';
|
|
120
|
+
this.el.style.opacity = '0';
|
|
121
|
+
this._overlay.style.display = 'block';
|
|
122
|
+
this._overlay.style.opacity = '0';
|
|
123
|
+
// Animate overlay
|
|
124
|
+
anim({
|
|
125
|
+
targets: this._overlay,
|
|
126
|
+
opacity: this.options.opacity,
|
|
127
|
+
duration: this.options.inDuration,
|
|
128
|
+
easing: 'easeOutQuad'
|
|
129
|
+
});
|
|
130
|
+
// Define modal animation options
|
|
131
|
+
const enterAnimOptions = {
|
|
132
|
+
targets: this.el,
|
|
133
|
+
duration: this.options.inDuration,
|
|
134
|
+
easing: 'easeOutCubic',
|
|
135
|
+
// Handle modal onOpenEnd callback
|
|
136
|
+
complete: () => {
|
|
137
|
+
if (typeof this.options.onOpenEnd === 'function') {
|
|
138
|
+
this.options.onOpenEnd.call(this, this.el, this._openingTrigger);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
// Bottom sheet animation
|
|
143
|
+
if (this.el.classList.contains('bottom-sheet')) {
|
|
144
|
+
enterAnimOptions['bottom'] = 0;
|
|
145
|
+
enterAnimOptions['opacity'] = 1;
|
|
146
|
+
}
|
|
147
|
+
// Normal modal animation
|
|
148
|
+
else {
|
|
149
|
+
enterAnimOptions['top'] = [this.options.startingTop, this.options.endingTop];
|
|
150
|
+
enterAnimOptions['opacity'] = 1;
|
|
151
|
+
enterAnimOptions['scaleX'] = [0.8, 1];
|
|
152
|
+
enterAnimOptions['scaleY'] = [0.8, 1];
|
|
153
|
+
}
|
|
154
|
+
anim(enterAnimOptions);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
_animateOut() {
|
|
158
|
+
// Animate overlay
|
|
159
|
+
anim({
|
|
160
|
+
targets: this._overlay,
|
|
161
|
+
opacity: 0,
|
|
162
|
+
duration: this.options.outDuration,
|
|
163
|
+
easing: 'easeOutQuart'
|
|
164
|
+
});
|
|
165
|
+
// Define modal animation options
|
|
166
|
+
const exitAnimOptions = {
|
|
167
|
+
targets: this.el,
|
|
168
|
+
duration: this.options.outDuration,
|
|
169
|
+
easing: 'easeOutCubic',
|
|
170
|
+
// Handle modal ready callback
|
|
171
|
+
complete: () => {
|
|
172
|
+
this.el.style.display = 'none';
|
|
173
|
+
this._overlay.remove();
|
|
174
|
+
// Call onCloseEnd callback
|
|
175
|
+
if (typeof this.options.onCloseEnd === 'function') {
|
|
176
|
+
this.options.onCloseEnd.call(this, this.el);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
// Bottom sheet animation
|
|
181
|
+
if (this.el.classList.contains('bottom-sheet')) {
|
|
182
|
+
exitAnimOptions['bottom'] = '-100%';
|
|
183
|
+
exitAnimOptions['opacity'] = 0;
|
|
184
|
+
}
|
|
185
|
+
// Normal modal animation
|
|
186
|
+
else {
|
|
187
|
+
exitAnimOptions['top'] = [this.options.endingTop, this.options.startingTop];
|
|
188
|
+
exitAnimOptions['opacity'] = 0;
|
|
189
|
+
exitAnimOptions['scaleX'] = 0.8;
|
|
190
|
+
exitAnimOptions['scaleY'] = 0.8;
|
|
191
|
+
}
|
|
192
|
+
anim(exitAnimOptions);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
open(trigger: HTMLElement|undefined): Modal {
|
|
196
|
+
if (this.isOpen) return;
|
|
197
|
+
this.isOpen = true;
|
|
198
|
+
Modal._modalsOpen++;
|
|
199
|
+
this._nthModalOpened = Modal._modalsOpen;
|
|
200
|
+
// Set Z-Index based on number of currently open modals
|
|
201
|
+
this._overlay.style.zIndex = (1000 + Modal._modalsOpen * 2).toString();
|
|
202
|
+
this.el.style.zIndex = (1000 + Modal._modalsOpen * 2 + 1).toString();
|
|
203
|
+
// Set opening trigger, undefined indicates modal was opened by javascript
|
|
204
|
+
this._openingTrigger = !!trigger ? trigger : undefined;
|
|
205
|
+
// onOpenStart callback
|
|
206
|
+
if (typeof this.options.onOpenStart === 'function') {
|
|
207
|
+
this.options.onOpenStart.call(this, this.el, this._openingTrigger);
|
|
208
|
+
}
|
|
209
|
+
if (this.options.preventScrolling) {
|
|
210
|
+
document.body.style.overflow = 'hidden';
|
|
211
|
+
}
|
|
212
|
+
this.el.classList.add('open');
|
|
213
|
+
this.el.insertAdjacentElement('afterend', this._overlay);
|
|
214
|
+
if (this.options.dismissible) {
|
|
215
|
+
this._handleKeydownBound = this._handleKeydown.bind(this);
|
|
216
|
+
this._handleFocusBound = this._handleFocus.bind(this);
|
|
217
|
+
document.addEventListener('keydown', this._handleKeydownBound);
|
|
218
|
+
document.addEventListener('focus', this._handleFocusBound, true);
|
|
219
|
+
}
|
|
220
|
+
anim.remove(this.el);
|
|
221
|
+
anim.remove(this._overlay);
|
|
222
|
+
this._animateIn();
|
|
223
|
+
// Focus modal
|
|
224
|
+
this.el.focus();
|
|
225
|
+
return this;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
close() {
|
|
229
|
+
if (!this.isOpen) return;
|
|
230
|
+
this.isOpen = false;
|
|
231
|
+
Modal._modalsOpen--;
|
|
232
|
+
this._nthModalOpened = 0;
|
|
233
|
+
// Call onCloseStart callback
|
|
234
|
+
if (typeof this.options.onCloseStart === 'function') {
|
|
235
|
+
this.options.onCloseStart.call(this, this.el);
|
|
236
|
+
}
|
|
237
|
+
this.el.classList.remove('open');
|
|
238
|
+
// Enable body scrolling only if there are no more modals open.
|
|
239
|
+
if (Modal._modalsOpen === 0) {
|
|
240
|
+
document.body.style.overflow = '';
|
|
241
|
+
}
|
|
242
|
+
if (this.options.dismissible) {
|
|
243
|
+
document.removeEventListener('keydown', this._handleKeydownBound);
|
|
244
|
+
document.removeEventListener('focus', this._handleFocusBound, true);
|
|
245
|
+
}
|
|
246
|
+
anim.remove(this.el);
|
|
247
|
+
anim.remove(this._overlay);
|
|
248
|
+
this._animateOut();
|
|
249
|
+
return this;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
static{
|
|
253
|
+
Modal._modalsOpen = 0;
|
|
254
|
+
Modal._count = 0;
|
|
255
|
+
}
|
|
256
|
+
}
|