kmaterialize 2.3.3-1.0.21 → 2.3.3-1.0.23
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/components/air-datepicker/air-datepicker.js +152 -0
- package/components/alert/alert.js +54 -0
- package/components/button/buttons.js +220 -0
- package/components/card/cards.js +149 -0
- package/components/carousel/carousel.js +574 -0
- package/components/carousel/slider.js +340 -0
- package/components/chip/chips.js +341 -0
- package/components/collapsible/collapsible.js +156 -0
- package/components/color-input/color-input.js +166 -0
- package/components/datepicker/datepicker.js +1035 -0
- package/components/dialog/materialbox.js +377 -0
- package/components/dialog/modal.js +72 -0
- package/components/dropdown/dropdown.js +505 -0
- package/components/file-input/file-input.js +169 -0
- package/components/kanban/_kanban.scss +69 -0
- package/components/kanban/kanban.js +168 -0
- package/components/kanban/kanban.ts +57 -8
- package/components/loading/_loading.scss +68 -0
- package/components/loading/loading.ts +104 -0
- package/components/loading/loadingSpec.js +102 -0
- package/components/navigation-drawer/sidenav.js +454 -0
- package/components/number-input/number-input.js +68 -0
- package/components/password-input/password-input.js +49 -0
- package/components/popup/_popup.scss +106 -0
- package/components/popup/popup.ts +113 -0
- package/components/popup/popupSpec.js +140 -0
- package/components/search/autocomplete.js +470 -0
- package/components/slider/range.js +169 -0
- package/components/snackbar/toasts.js +244 -0
- package/components/tabs/tabs.js +289 -0
- package/components/textfield/forms.js +172 -0
- package/components/textfield/select.js +385 -0
- package/components/timepicker/timepicker.js +698 -0
- package/components/tom-select/tom-select-field.js +130 -0
- package/components/toolbar/toolbar.js +134 -0
- package/components/tooltip/tooltip.js +270 -0
- package/dist/css/materialize.colors.min.css +1 -1
- package/dist/css/materialize.css +322 -1
- package/dist/css/materialize.min.css +2 -2
- package/dist/css/materialize.min.css.map +1 -1
- package/dist/js/materialize.cjs.js +258 -42
- package/dist/js/materialize.d.ts +50 -3
- package/dist/js/materialize.js +258 -42
- package/dist/js/materialize.min.js +2 -2
- package/dist/js/materialize.mjs +257 -43
- package/package.json +6 -1
- package/sass/materialize.scss +2 -0
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
import { Utils } from '../../src/utils';
|
|
2
|
+
import { Component } from '../../src/component';
|
|
3
|
+
const _defaults = {
|
|
4
|
+
edge: 'left',
|
|
5
|
+
draggable: true,
|
|
6
|
+
dragTargetWidth: '10px',
|
|
7
|
+
inDuration: 250,
|
|
8
|
+
outDuration: 200,
|
|
9
|
+
onOpenStart: null,
|
|
10
|
+
onOpenEnd: null,
|
|
11
|
+
onCloseStart: null,
|
|
12
|
+
onCloseEnd: null,
|
|
13
|
+
preventScrolling: true
|
|
14
|
+
};
|
|
15
|
+
export class Sidenav extends Component {
|
|
16
|
+
id;
|
|
17
|
+
/** Describes open/close state of Sidenav. */
|
|
18
|
+
isOpen;
|
|
19
|
+
/** Describes if sidenav is fixed. */
|
|
20
|
+
isFixed;
|
|
21
|
+
/** Describes if Sidenav is being dragged. */
|
|
22
|
+
isDragged;
|
|
23
|
+
lastWindowWidth;
|
|
24
|
+
lastWindowHeight;
|
|
25
|
+
static _sidenavs;
|
|
26
|
+
_overlay;
|
|
27
|
+
dragTarget;
|
|
28
|
+
_startingXpos;
|
|
29
|
+
_xPos;
|
|
30
|
+
_time;
|
|
31
|
+
_width;
|
|
32
|
+
_initialScrollTop;
|
|
33
|
+
_verticallyScrolling;
|
|
34
|
+
deltaX;
|
|
35
|
+
velocityX;
|
|
36
|
+
percentOpen;
|
|
37
|
+
constructor(el, options) {
|
|
38
|
+
super(el, options, Sidenav);
|
|
39
|
+
this.el['M_Sidenav'] = this;
|
|
40
|
+
this.options = {
|
|
41
|
+
...Sidenav.defaults,
|
|
42
|
+
...options
|
|
43
|
+
};
|
|
44
|
+
this.id = this.el.id;
|
|
45
|
+
this.isOpen = false;
|
|
46
|
+
this.isFixed = this.el.classList.contains('sidenav-fixed');
|
|
47
|
+
this.isDragged = false;
|
|
48
|
+
// Window size variables for window resize checks
|
|
49
|
+
this.lastWindowWidth = window.innerWidth;
|
|
50
|
+
this.lastWindowHeight = window.innerHeight;
|
|
51
|
+
this._createOverlay();
|
|
52
|
+
this._createDragTarget();
|
|
53
|
+
this._setupEventHandlers();
|
|
54
|
+
this._setupClasses();
|
|
55
|
+
this._setupFixed();
|
|
56
|
+
Sidenav._sidenavs.push(this);
|
|
57
|
+
}
|
|
58
|
+
static get defaults() {
|
|
59
|
+
return _defaults;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Initializes instances of Sidenav.
|
|
63
|
+
* @param els HTML elements.
|
|
64
|
+
* @param options Component options.
|
|
65
|
+
*/
|
|
66
|
+
static init(els, options = {}) {
|
|
67
|
+
return super.init(els, options, Sidenav);
|
|
68
|
+
}
|
|
69
|
+
static getInstance(el) {
|
|
70
|
+
return el['M_Sidenav'];
|
|
71
|
+
}
|
|
72
|
+
destroy() {
|
|
73
|
+
this._removeEventHandlers();
|
|
74
|
+
this._enableBodyScrolling();
|
|
75
|
+
this._overlay.parentNode.removeChild(this._overlay);
|
|
76
|
+
this.dragTarget.parentNode.removeChild(this.dragTarget);
|
|
77
|
+
this.el['M_Sidenav'] = undefined;
|
|
78
|
+
this.el.style.transform = '';
|
|
79
|
+
const index = Sidenav._sidenavs.indexOf(this);
|
|
80
|
+
if (index >= 0) {
|
|
81
|
+
Sidenav._sidenavs.splice(index, 1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
_createOverlay() {
|
|
85
|
+
this._overlay = document.createElement('div');
|
|
86
|
+
this._overlay.classList.add('sidenav-overlay');
|
|
87
|
+
this._overlay.addEventListener('click', this.close);
|
|
88
|
+
document.body.appendChild(this._overlay);
|
|
89
|
+
}
|
|
90
|
+
_setupEventHandlers() {
|
|
91
|
+
if (Sidenav._sidenavs.length === 0) {
|
|
92
|
+
document.body.addEventListener('click', this._handleTriggerClick);
|
|
93
|
+
}
|
|
94
|
+
const passiveIfSupported = null;
|
|
95
|
+
this.dragTarget.addEventListener('touchmove', this._handleDragTargetDrag, passiveIfSupported);
|
|
96
|
+
this.dragTarget.addEventListener('touchend', this._handleDragTargetRelease);
|
|
97
|
+
this._overlay.addEventListener('touchmove', this._handleCloseDrag, passiveIfSupported);
|
|
98
|
+
this._overlay.addEventListener('touchend', this._handleCloseRelease);
|
|
99
|
+
this.el.addEventListener('touchmove', this._handleCloseDrag); // , passiveIfSupported);
|
|
100
|
+
this.el.addEventListener('touchend', this._handleCloseRelease);
|
|
101
|
+
this.el.addEventListener('click', this._handleCloseTriggerClick);
|
|
102
|
+
// Add resize for side nav fixed
|
|
103
|
+
if (this.isFixed) {
|
|
104
|
+
window.addEventListener('resize', this._handleWindowResize);
|
|
105
|
+
}
|
|
106
|
+
/* Set aria-hidden state */
|
|
107
|
+
this._setAriaHidden();
|
|
108
|
+
this._setTabIndex();
|
|
109
|
+
}
|
|
110
|
+
_removeEventHandlers() {
|
|
111
|
+
if (Sidenav._sidenavs.length === 1) {
|
|
112
|
+
document.body.removeEventListener('click', this._handleTriggerClick);
|
|
113
|
+
}
|
|
114
|
+
this.dragTarget.removeEventListener('touchmove', this._handleDragTargetDrag);
|
|
115
|
+
this.dragTarget.removeEventListener('touchend', this._handleDragTargetRelease);
|
|
116
|
+
this._overlay.removeEventListener('touchmove', this._handleCloseDrag);
|
|
117
|
+
this._overlay.removeEventListener('touchend', this._handleCloseRelease);
|
|
118
|
+
this.el.removeEventListener('touchmove', this._handleCloseDrag);
|
|
119
|
+
this.el.removeEventListener('touchend', this._handleCloseRelease);
|
|
120
|
+
this.el.removeEventListener('click', this._handleCloseTriggerClick);
|
|
121
|
+
// Remove resize for side nav fixed
|
|
122
|
+
if (this.isFixed) {
|
|
123
|
+
window.removeEventListener('resize', this._handleWindowResize);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
_handleTriggerClick(e) {
|
|
127
|
+
const trigger = e.target.closest('.sidenav-trigger');
|
|
128
|
+
if (e.target && trigger) {
|
|
129
|
+
const sidenavId = Utils.getIdFromTrigger(trigger);
|
|
130
|
+
const sidenavInstance = document.getElementById(sidenavId)['M_Sidenav'];
|
|
131
|
+
if (sidenavInstance) {
|
|
132
|
+
sidenavInstance.open();
|
|
133
|
+
}
|
|
134
|
+
e.preventDefault();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Set variables needed at the beginning of drag and stop any current transition.
|
|
138
|
+
_startDrag(e) {
|
|
139
|
+
const clientX = e.targetTouches[0].clientX;
|
|
140
|
+
this.isDragged = true;
|
|
141
|
+
this._startingXpos = clientX;
|
|
142
|
+
this._xPos = this._startingXpos;
|
|
143
|
+
this._time = Date.now();
|
|
144
|
+
this._width = this.el.getBoundingClientRect().width;
|
|
145
|
+
this._overlay.style.display = 'block';
|
|
146
|
+
this._initialScrollTop = this.isOpen ? this.el.scrollTop : Utils.getDocumentScrollTop();
|
|
147
|
+
this._verticallyScrolling = false;
|
|
148
|
+
}
|
|
149
|
+
//Set variables needed at each drag move update tick
|
|
150
|
+
_dragMoveUpdate(e) {
|
|
151
|
+
const clientX = e.targetTouches[0].clientX;
|
|
152
|
+
const currentScrollTop = this.isOpen ? this.el.scrollTop : Utils.getDocumentScrollTop();
|
|
153
|
+
this.deltaX = Math.abs(this._xPos - clientX);
|
|
154
|
+
this._xPos = clientX;
|
|
155
|
+
this.velocityX = this.deltaX / (Date.now() - this._time);
|
|
156
|
+
this._time = Date.now();
|
|
157
|
+
if (this._initialScrollTop !== currentScrollTop) {
|
|
158
|
+
this._verticallyScrolling = true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
_handleDragTargetDrag = (e) => {
|
|
162
|
+
// Check if draggable
|
|
163
|
+
if (!this._isDraggable())
|
|
164
|
+
return;
|
|
165
|
+
let totalDeltaX = this._calculateDelta(e);
|
|
166
|
+
const dragDirection = totalDeltaX > 0 ? 'right' : 'left';
|
|
167
|
+
// Don't allow totalDeltaX to exceed Sidenav width or be dragged in the opposite direction
|
|
168
|
+
totalDeltaX = Math.min(this._width, Math.abs(totalDeltaX));
|
|
169
|
+
if (this.options.edge === dragDirection) {
|
|
170
|
+
totalDeltaX = 0;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* transformX is the drag displacement
|
|
174
|
+
* transformPrefix is the initial transform placement
|
|
175
|
+
* Invert values if Sidenav is right edge
|
|
176
|
+
*/
|
|
177
|
+
let transformX = totalDeltaX;
|
|
178
|
+
let transformPrefix = 'translateX(-100%)';
|
|
179
|
+
if (this.options.edge === 'right') {
|
|
180
|
+
transformPrefix = 'translateX(100%)';
|
|
181
|
+
transformX = -transformX;
|
|
182
|
+
}
|
|
183
|
+
// Calculate open/close percentage of sidenav, with open = 1 and close = 0
|
|
184
|
+
this.percentOpen = Math.min(1, totalDeltaX / this._width);
|
|
185
|
+
// Set transform and opacity styles
|
|
186
|
+
this.el.style.transform = `${transformPrefix} translateX(${transformX}px)`;
|
|
187
|
+
this._overlay.style.opacity = this.percentOpen.toString();
|
|
188
|
+
};
|
|
189
|
+
_handleDragTargetRelease = () => {
|
|
190
|
+
if (this.isDragged) {
|
|
191
|
+
if (this.percentOpen > 0.2) {
|
|
192
|
+
this.open();
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
this._animateOut();
|
|
196
|
+
}
|
|
197
|
+
this.isDragged = false;
|
|
198
|
+
this._verticallyScrolling = false;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
_handleCloseDrag = (e) => {
|
|
202
|
+
// Check if open and draggable
|
|
203
|
+
if (!this.isOpen || !this._isDraggable())
|
|
204
|
+
return;
|
|
205
|
+
let totalDeltaX = this._calculateDelta(e);
|
|
206
|
+
// dragDirection is the attempted user drag direction
|
|
207
|
+
const dragDirection = totalDeltaX > 0 ? 'right' : 'left';
|
|
208
|
+
totalDeltaX = Math.min(this._width, Math.abs(totalDeltaX));
|
|
209
|
+
if (this.options.edge !== dragDirection) {
|
|
210
|
+
totalDeltaX = 0;
|
|
211
|
+
}
|
|
212
|
+
let transformX = -totalDeltaX;
|
|
213
|
+
if (this.options.edge === 'right') {
|
|
214
|
+
transformX = -transformX;
|
|
215
|
+
}
|
|
216
|
+
// Calculate open/close percentage of sidenav, with open = 1 and close = 0
|
|
217
|
+
this.percentOpen = Math.min(1, 1 - totalDeltaX / this._width);
|
|
218
|
+
// Set transform and opacity styles
|
|
219
|
+
this.el.style.transform = `translateX(${transformX}px)`;
|
|
220
|
+
this._overlay.style.opacity = this.percentOpen.toString();
|
|
221
|
+
};
|
|
222
|
+
_calculateDelta = (e) => {
|
|
223
|
+
// If not being dragged, set initial drag start variables
|
|
224
|
+
if (!this.isDragged) {
|
|
225
|
+
this._startDrag(e);
|
|
226
|
+
}
|
|
227
|
+
// Run touchmove updates
|
|
228
|
+
this._dragMoveUpdate(e);
|
|
229
|
+
// Calculate raw deltaX
|
|
230
|
+
return this._xPos - this._startingXpos;
|
|
231
|
+
};
|
|
232
|
+
_handleCloseRelease = () => {
|
|
233
|
+
if (this.isOpen && this.isDragged) {
|
|
234
|
+
if (this.percentOpen > 0.8) {
|
|
235
|
+
this._animateIn();
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
this.close();
|
|
239
|
+
}
|
|
240
|
+
this.isDragged = false;
|
|
241
|
+
this._verticallyScrolling = false;
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
// Handles closing of Sidenav when element with class .sidenav-close
|
|
245
|
+
_handleCloseTriggerClick = (e) => {
|
|
246
|
+
const closeTrigger = e.target.closest('.sidenav-close');
|
|
247
|
+
if (closeTrigger && !this._isCurrentlyFixed()) {
|
|
248
|
+
this.close();
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
_handleWindowResize = () => {
|
|
252
|
+
// Only handle horizontal resizes
|
|
253
|
+
if (this.lastWindowWidth !== window.innerWidth) {
|
|
254
|
+
if (window.innerWidth > 992) {
|
|
255
|
+
this.open();
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
this.close();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
this.lastWindowWidth = window.innerWidth;
|
|
262
|
+
this.lastWindowHeight = window.innerHeight;
|
|
263
|
+
};
|
|
264
|
+
_setupClasses() {
|
|
265
|
+
if (this.options.edge === 'right') {
|
|
266
|
+
this.el.classList.add('right-aligned');
|
|
267
|
+
this.dragTarget.classList.add('right-aligned');
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
_removeClasses() {
|
|
271
|
+
this.el.classList.remove('right-aligned');
|
|
272
|
+
this.dragTarget.classList.remove('right-aligned');
|
|
273
|
+
}
|
|
274
|
+
_setupFixed() {
|
|
275
|
+
if (this._isCurrentlyFixed())
|
|
276
|
+
this.open();
|
|
277
|
+
}
|
|
278
|
+
_isDraggable() {
|
|
279
|
+
return this.options.draggable && !this._isCurrentlyFixed() && !this._verticallyScrolling;
|
|
280
|
+
}
|
|
281
|
+
_isCurrentlyFixed() {
|
|
282
|
+
return this.isFixed && window.innerWidth > 992;
|
|
283
|
+
}
|
|
284
|
+
_createDragTarget() {
|
|
285
|
+
const dragTarget = document.createElement('div');
|
|
286
|
+
dragTarget.classList.add('drag-target');
|
|
287
|
+
dragTarget.style.width = this.options.dragTargetWidth;
|
|
288
|
+
document.body.appendChild(dragTarget);
|
|
289
|
+
this.dragTarget = dragTarget;
|
|
290
|
+
}
|
|
291
|
+
_preventBodyScrolling() {
|
|
292
|
+
document.body.style.overflow = 'hidden';
|
|
293
|
+
}
|
|
294
|
+
_enableBodyScrolling() {
|
|
295
|
+
document.body.style.overflow = '';
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Opens Sidenav.
|
|
299
|
+
*/
|
|
300
|
+
open = () => {
|
|
301
|
+
if (this.isOpen === true)
|
|
302
|
+
return;
|
|
303
|
+
this.isOpen = true;
|
|
304
|
+
// Run onOpenStart callback
|
|
305
|
+
if (typeof this.options.onOpenStart === 'function') {
|
|
306
|
+
this.options.onOpenStart.call(this, this.el);
|
|
307
|
+
}
|
|
308
|
+
// Handle fixed Sidenav
|
|
309
|
+
if (this._isCurrentlyFixed()) {
|
|
310
|
+
// Show if fixed
|
|
311
|
+
this.el.style.transform = 'translateX(0)';
|
|
312
|
+
this._enableBodyScrolling();
|
|
313
|
+
this._overlay.style.display = 'none';
|
|
314
|
+
}
|
|
315
|
+
// Handle non-fixed Sidenav
|
|
316
|
+
else {
|
|
317
|
+
if (this.options.preventScrolling)
|
|
318
|
+
this._preventBodyScrolling();
|
|
319
|
+
if (!this.isDragged || this.percentOpen != 1)
|
|
320
|
+
this._animateIn();
|
|
321
|
+
/* Set aria-hidden state */
|
|
322
|
+
this._setAriaHidden();
|
|
323
|
+
this._setTabIndex();
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
/**
|
|
327
|
+
* Closes Sidenav.
|
|
328
|
+
*/
|
|
329
|
+
close = () => {
|
|
330
|
+
if (this.isOpen === false)
|
|
331
|
+
return;
|
|
332
|
+
this.isOpen = false;
|
|
333
|
+
// Run onCloseStart callback
|
|
334
|
+
if (typeof this.options.onCloseStart === 'function') {
|
|
335
|
+
this.options.onCloseStart.call(this, this.el);
|
|
336
|
+
}
|
|
337
|
+
// Handle fixed Sidenav
|
|
338
|
+
if (this._isCurrentlyFixed()) {
|
|
339
|
+
const transformX = this.options.edge === 'left' ? '-105%' : '105%';
|
|
340
|
+
this.el.style.transform = `translateX(${transformX})`;
|
|
341
|
+
}
|
|
342
|
+
// Handle non-fixed Sidenav
|
|
343
|
+
else {
|
|
344
|
+
this._enableBodyScrolling();
|
|
345
|
+
if (!this.isDragged || this.percentOpen != 0) {
|
|
346
|
+
this._animateOut();
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
this._overlay.style.display = 'none';
|
|
350
|
+
}
|
|
351
|
+
/* Set aria-hidden state */
|
|
352
|
+
this._setAriaHidden();
|
|
353
|
+
this._setTabIndex();
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
_animateIn() {
|
|
357
|
+
this._animateSidenavIn();
|
|
358
|
+
this._animateOverlayIn();
|
|
359
|
+
}
|
|
360
|
+
_animateOut() {
|
|
361
|
+
this._animateSidenavOut();
|
|
362
|
+
this._animateOverlayOut();
|
|
363
|
+
}
|
|
364
|
+
_animateSidenavIn() {
|
|
365
|
+
let slideOutPercent = this.options.edge === 'left' ? -1 : 1;
|
|
366
|
+
if (this.isDragged) {
|
|
367
|
+
slideOutPercent =
|
|
368
|
+
this.options.edge === 'left'
|
|
369
|
+
? slideOutPercent + this.percentOpen
|
|
370
|
+
: slideOutPercent - this.percentOpen;
|
|
371
|
+
}
|
|
372
|
+
const duration = this.options.inDuration;
|
|
373
|
+
// from
|
|
374
|
+
this.el.style.transition = 'none';
|
|
375
|
+
this.el.style.transform = 'translateX(' + slideOutPercent * 100 + '%)';
|
|
376
|
+
setTimeout(() => {
|
|
377
|
+
this.el.style.transition = `transform ${duration}ms ease`; // easeOutQuad
|
|
378
|
+
// to
|
|
379
|
+
this.el.style.transform = 'translateX(0)';
|
|
380
|
+
}, 1);
|
|
381
|
+
setTimeout(() => {
|
|
382
|
+
if (typeof this.options.onOpenEnd === 'function')
|
|
383
|
+
this.options.onOpenEnd.call(this, this.el);
|
|
384
|
+
}, duration);
|
|
385
|
+
}
|
|
386
|
+
_animateSidenavOut() {
|
|
387
|
+
const endPercent = this.options.edge === 'left' ? -1 : 1;
|
|
388
|
+
// let slideOutPercent = 0;
|
|
389
|
+
// if (this.isDragged) {
|
|
390
|
+
// // @todo unused variable
|
|
391
|
+
// slideOutPercent =
|
|
392
|
+
// this.options.edge === 'left'
|
|
393
|
+
// ? endPercent + this.percentOpen
|
|
394
|
+
// : endPercent - this.percentOpen;
|
|
395
|
+
// }
|
|
396
|
+
const duration = this.options.outDuration;
|
|
397
|
+
this.el.style.transition = `transform ${duration}ms ease`; // easeOutQuad
|
|
398
|
+
// to
|
|
399
|
+
this.el.style.transform = 'translateX(' + endPercent * 100 + '%)';
|
|
400
|
+
setTimeout(() => {
|
|
401
|
+
if (typeof this.options.onCloseEnd === 'function')
|
|
402
|
+
this.options.onCloseEnd.call(this, this.el);
|
|
403
|
+
}, duration);
|
|
404
|
+
}
|
|
405
|
+
_animateOverlayIn() {
|
|
406
|
+
let start = 0;
|
|
407
|
+
if (this.isDragged)
|
|
408
|
+
start = this.percentOpen;
|
|
409
|
+
else
|
|
410
|
+
this._overlay.style.display = 'block';
|
|
411
|
+
// Animation
|
|
412
|
+
const duration = this.options.inDuration;
|
|
413
|
+
// from
|
|
414
|
+
this._overlay.style.transition = 'none';
|
|
415
|
+
this._overlay.style.opacity = start.toString();
|
|
416
|
+
// easeOutQuad
|
|
417
|
+
setTimeout(() => {
|
|
418
|
+
this._overlay.style.transition = `opacity ${duration}ms ease`;
|
|
419
|
+
// to
|
|
420
|
+
this._overlay.style.opacity = '1';
|
|
421
|
+
}, 1);
|
|
422
|
+
}
|
|
423
|
+
_animateOverlayOut() {
|
|
424
|
+
const duration = this.options.outDuration;
|
|
425
|
+
// easeOutQuad
|
|
426
|
+
this._overlay.style.transition = `opacity ${duration}ms ease`;
|
|
427
|
+
// to
|
|
428
|
+
this._overlay.style.opacity = '0';
|
|
429
|
+
setTimeout(() => {
|
|
430
|
+
this._overlay.style.display = 'none';
|
|
431
|
+
}, duration);
|
|
432
|
+
}
|
|
433
|
+
_setAriaHidden = () => {
|
|
434
|
+
this.el.ariaHidden = this.isOpen ? 'false' : 'true';
|
|
435
|
+
const navWrapper = document.querySelector('.nav-wrapper ul');
|
|
436
|
+
if (navWrapper)
|
|
437
|
+
navWrapper.ariaHidden = this.isOpen.toString();
|
|
438
|
+
};
|
|
439
|
+
_setTabIndex = () => {
|
|
440
|
+
const navLinks = document.querySelectorAll('.nav-wrapper ul li a');
|
|
441
|
+
const sideNavLinks = document.querySelectorAll('.sidenav li a');
|
|
442
|
+
if (navLinks)
|
|
443
|
+
navLinks.forEach((navLink) => {
|
|
444
|
+
navLink.tabIndex = this.isOpen ? -1 : 0;
|
|
445
|
+
});
|
|
446
|
+
if (sideNavLinks)
|
|
447
|
+
sideNavLinks.forEach((sideNavLink) => {
|
|
448
|
+
sideNavLink.tabIndex = this.isOpen ? 0 : -1;
|
|
449
|
+
});
|
|
450
|
+
};
|
|
451
|
+
static {
|
|
452
|
+
Sidenav._sidenavs = [];
|
|
453
|
+
}
|
|
454
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Component } from '../../src/component';
|
|
2
|
+
import { loadPeer } from '../../src/peer-loader';
|
|
3
|
+
const _defaults = {
|
|
4
|
+
thousandsSeparator: ' ',
|
|
5
|
+
radix: '.',
|
|
6
|
+
mapToRadix: [',']
|
|
7
|
+
};
|
|
8
|
+
// @implement /Users/kzarshenas/Sites/CrazyProject/CrazyPHP/src/Front/Library/Utility/Form/Number.ts
|
|
9
|
+
// Masked/formatted numeric input via IMask (thousands separator, decimal
|
|
10
|
+
// scale, min/max) - deliberately opts in on `type="text" data-type="number"`
|
|
11
|
+
// rather than `type="number"`, so IMask can format the display value
|
|
12
|
+
// (thousands separators etc.) that a native number input wouldn't allow.
|
|
13
|
+
//
|
|
14
|
+
// IMask is an optional peerDependency, loaded on demand via peer-loader -
|
|
15
|
+
// see that file for the bundler/no-bundler resolution strategy.
|
|
16
|
+
export class NumberInput extends Component {
|
|
17
|
+
mask;
|
|
18
|
+
ready;
|
|
19
|
+
constructor(el, options) {
|
|
20
|
+
super(el, options, NumberInput);
|
|
21
|
+
this.el.M_NumberInput = this;
|
|
22
|
+
this.options = {
|
|
23
|
+
...NumberInput.defaults,
|
|
24
|
+
...options
|
|
25
|
+
};
|
|
26
|
+
this.ready = this._setup();
|
|
27
|
+
}
|
|
28
|
+
static get defaults() {
|
|
29
|
+
return _defaults;
|
|
30
|
+
}
|
|
31
|
+
static init(els, options = {}) {
|
|
32
|
+
return super.init(els, options, NumberInput);
|
|
33
|
+
}
|
|
34
|
+
static getInstance(el) {
|
|
35
|
+
return el.M_NumberInput;
|
|
36
|
+
}
|
|
37
|
+
destroy() {
|
|
38
|
+
this.mask?.destroy();
|
|
39
|
+
this.el.M_NumberInput = undefined;
|
|
40
|
+
}
|
|
41
|
+
async _setup() {
|
|
42
|
+
const IMask = await loadPeer({
|
|
43
|
+
specifier: 'imask',
|
|
44
|
+
globalName: 'IMask',
|
|
45
|
+
feature: 'Number input (IMask) enhancement',
|
|
46
|
+
cdnHint: '<script src="path/to/imask.min.js"></script> (self-hosted - copy from node_modules/imask/dist/imask.min.js, or a CDN of your choice)'
|
|
47
|
+
}, () => import('imask'));
|
|
48
|
+
const maskOptions = {
|
|
49
|
+
mask: Number,
|
|
50
|
+
skipInvalid: true,
|
|
51
|
+
thousandsSeparator: this.options.thousandsSeparator,
|
|
52
|
+
radix: this.options.radix,
|
|
53
|
+
mapToRadix: this.options.mapToRadix,
|
|
54
|
+
autofix: true
|
|
55
|
+
};
|
|
56
|
+
const max = this.options.max ?? (this.el.hasAttribute('max') ? Number(this.el.getAttribute('max')) : undefined);
|
|
57
|
+
if (max !== undefined && !Number.isNaN(max))
|
|
58
|
+
maskOptions.max = max;
|
|
59
|
+
const min = this.options.min ?? (this.el.hasAttribute('min') ? Number(this.el.getAttribute('min')) : undefined);
|
|
60
|
+
if (min !== undefined && !Number.isNaN(min))
|
|
61
|
+
maskOptions.min = min;
|
|
62
|
+
const step = this.el.getAttribute('step');
|
|
63
|
+
const scale = this.options.scale ?? (step?.includes('.') ? step.split('.').at(-1)?.length : undefined);
|
|
64
|
+
if (scale !== undefined)
|
|
65
|
+
maskOptions.scale = scale;
|
|
66
|
+
this.mask = IMask(this.el, maskOptions);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Component } from '../../src/component';
|
|
2
|
+
const _defaults = {};
|
|
3
|
+
// @implement /Users/kzarshenas/Sites/CrazyProject/CrazyPHP/src/Front/Library/Utility/Form/Password.ts
|
|
4
|
+
// Show/hide toggle for a password input - no third-party dependency, just
|
|
5
|
+
// Materialize's own .prefix/.suffix icon-slot convention (see
|
|
6
|
+
// components/textfield/_input-fields.scss), so unlike the other new form
|
|
7
|
+
// enhancements in this batch this one needs no peer-loader/dynamic import.
|
|
8
|
+
export class PasswordInput extends Component {
|
|
9
|
+
_suffixEl;
|
|
10
|
+
constructor(el, options) {
|
|
11
|
+
super(el, options, PasswordInput);
|
|
12
|
+
this.el.M_PasswordInput = this;
|
|
13
|
+
this.options = {
|
|
14
|
+
...PasswordInput.defaults,
|
|
15
|
+
...options
|
|
16
|
+
};
|
|
17
|
+
this._suffixEl = this.el.parentElement?.querySelector('[data-password-toggle-icon]') ?? null;
|
|
18
|
+
if (!this.el.dataset.passwordVisible)
|
|
19
|
+
this.el.dataset.passwordVisible = '0';
|
|
20
|
+
this._setupEventHandlers();
|
|
21
|
+
}
|
|
22
|
+
static get defaults() {
|
|
23
|
+
return _defaults;
|
|
24
|
+
}
|
|
25
|
+
static init(els, options = {}) {
|
|
26
|
+
return super.init(els, options, PasswordInput);
|
|
27
|
+
}
|
|
28
|
+
static getInstance(el) {
|
|
29
|
+
return el.M_PasswordInput;
|
|
30
|
+
}
|
|
31
|
+
destroy() {
|
|
32
|
+
this._removeEventHandlers();
|
|
33
|
+
this.el.M_PasswordInput = undefined;
|
|
34
|
+
}
|
|
35
|
+
_setupEventHandlers() {
|
|
36
|
+
this._suffixEl?.addEventListener('click', this._handleToggleClick);
|
|
37
|
+
}
|
|
38
|
+
_removeEventHandlers() {
|
|
39
|
+
this._suffixEl?.removeEventListener('click', this._handleToggleClick);
|
|
40
|
+
}
|
|
41
|
+
_handleToggleClick = () => {
|
|
42
|
+
const visible = this.el.dataset.passwordVisible === '1';
|
|
43
|
+
this.el.type = visible ? 'password' : 'text';
|
|
44
|
+
this.el.dataset.passwordVisible = visible ? '0' : '1';
|
|
45
|
+
const iconEl = this._suffixEl?.querySelector('i');
|
|
46
|
+
if (iconEl)
|
|
47
|
+
iconEl.textContent = visible ? 'visibility' : 'visibility_off';
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// SweetAlert2 provides dialog layout, focus management, and behavior.
|
|
2
|
+
// Load its base CSS before materialize.css. Only Popup dialogs are themed.
|
|
3
|
+
.swal2-container.popup-container {
|
|
4
|
+
--swal2-backdrop: color-mix(in srgb, var(--md-sys-color-scrim) 40%, transparent);
|
|
5
|
+
z-index: 1100;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.swal2-popup.popup {
|
|
9
|
+
--swal2-background: var(--md-sys-color-surface);
|
|
10
|
+
--swal2-color: var(--md-sys-color-on-surface);
|
|
11
|
+
--swal2-border: 1px solid var(--md-sys-color-outline-variant);
|
|
12
|
+
--swal2-border-radius: 28px;
|
|
13
|
+
--swal2-input-background: var(--md-sys-color-surface);
|
|
14
|
+
--swal2-input-border: 1px solid var(--md-sys-color-outline);
|
|
15
|
+
--swal2-input-border-radius: 4px;
|
|
16
|
+
--swal2-input-box-shadow: none;
|
|
17
|
+
--swal2-input-hover-box-shadow: none;
|
|
18
|
+
--swal2-input-focus-border: 1px solid var(--md-sys-color-primary);
|
|
19
|
+
--swal2-input-focus-box-shadow: 0 0 0 1px var(--md-sys-color-primary);
|
|
20
|
+
--swal2-validation-message-background: var(--md-sys-color-error-container);
|
|
21
|
+
--swal2-validation-message-color: var(--md-sys-color-on-error-container);
|
|
22
|
+
--swal2-footer-border-color: var(--md-sys-color-outline-variant);
|
|
23
|
+
--swal2-footer-color: var(--md-sys-color-on-surface-variant);
|
|
24
|
+
--swal2-timer-progress-bar-background: var(--md-sys-color-primary);
|
|
25
|
+
--swal2-close-button-color: var(--md-sys-color-on-surface-variant);
|
|
26
|
+
--swal2-close-button-focus-box-shadow: inset 0 0 0 2px var(--md-sys-color-primary);
|
|
27
|
+
--swal2-outline: 0 0 0 2px var(--md-sys-color-primary);
|
|
28
|
+
|
|
29
|
+
font-family: inherit;
|
|
30
|
+
|
|
31
|
+
.swal2-title { font-size: 1.5rem; line-height: 1.3; }
|
|
32
|
+
.swal2-html-container { font-size: 1rem; line-height: 1.5; }
|
|
33
|
+
.swal2-actions { gap: 0.75rem; padding: 0 1.5rem; }
|
|
34
|
+
.swal2-actions .btn { margin: 0; }
|
|
35
|
+
.swal2-close:hover { color: var(--md-sys-color-primary); }
|
|
36
|
+
.swal2-loader { border-color: var(--md-sys-color-primary) transparent; }
|
|
37
|
+
|
|
38
|
+
// Adapted from Rodeo FX's sweetalert_materializecss.scss. Materialize's
|
|
39
|
+
// full-width text fields otherwise extend past SweetAlert's input margins.
|
|
40
|
+
input.swal2-input,
|
|
41
|
+
textarea.swal2-textarea,
|
|
42
|
+
select.swal2-select {
|
|
43
|
+
box-sizing: border-box;
|
|
44
|
+
width: auto !important;
|
|
45
|
+
min-width: 0;
|
|
46
|
+
max-width: calc(100% - 3rem);
|
|
47
|
+
margin: 1rem 1.5rem 0;
|
|
48
|
+
padding: 0.75rem;
|
|
49
|
+
border: var(--swal2-input-border);
|
|
50
|
+
border-radius: var(--swal2-input-border-radius);
|
|
51
|
+
background: var(--swal2-input-background);
|
|
52
|
+
color: var(--swal2-color);
|
|
53
|
+
box-shadow: none;
|
|
54
|
+
font-size: 1rem;
|
|
55
|
+
|
|
56
|
+
&:focus {
|
|
57
|
+
border: var(--swal2-input-focus-border);
|
|
58
|
+
box-shadow: var(--swal2-input-focus-box-shadow);
|
|
59
|
+
outline: none;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
&::placeholder { color: var(--md-sys-color-on-surface-variant); opacity: 1; }
|
|
63
|
+
&.swal2-inputerror {
|
|
64
|
+
border-color: var(--md-sys-color-error) !important;
|
|
65
|
+
box-shadow: 0 0 0 1px var(--md-sys-color-error) !important;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
input.swal2-input, select.swal2-select { height: 3rem; }
|
|
70
|
+
textarea.swal2-textarea { min-height: 7rem; }
|
|
71
|
+
.swal2-input-label { color: var(--md-sys-color-on-surface); font-size: 1rem; }
|
|
72
|
+
|
|
73
|
+
// SweetAlert's generated choice fields use native inputs. Undo the global
|
|
74
|
+
// Materialize rules that hide them in favor of sibling span pseudo-elements.
|
|
75
|
+
.swal2-radio input,
|
|
76
|
+
.swal2-checkbox input {
|
|
77
|
+
appearance: auto;
|
|
78
|
+
position: static;
|
|
79
|
+
opacity: 1;
|
|
80
|
+
pointer-events: auto;
|
|
81
|
+
width: 1.125rem;
|
|
82
|
+
height: 1.125rem;
|
|
83
|
+
accent-color: var(--md-sys-color-primary);
|
|
84
|
+
background: initial;
|
|
85
|
+
border: initial;
|
|
86
|
+
|
|
87
|
+
&::before, &::after { display: none; }
|
|
88
|
+
&:focus-visible { outline: 2px solid var(--md-sys-color-primary); outline-offset: 2px; }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.swal2-radio label, .swal2-checkbox { gap: 0.5rem; color: inherit; }
|
|
92
|
+
.swal2-radio input + span, .swal2-checkbox input + span {
|
|
93
|
+
padding-left: 0;
|
|
94
|
+
font-size: 1rem;
|
|
95
|
+
|
|
96
|
+
&::before, &::after { display: none; }
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@media (prefers-reduced-motion: reduce) {
|
|
101
|
+
.popup-container .swal2-popup.popup,
|
|
102
|
+
.popup-container .swal2-popup.popup * {
|
|
103
|
+
animation: none !important;
|
|
104
|
+
transition: none !important;
|
|
105
|
+
}
|
|
106
|
+
}
|