@materializecss/materialize 1.1.0-alpha → 1.1.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.
Files changed (85) hide show
  1. package/Gruntfile.js +115 -132
  2. package/LICENSE +21 -21
  3. package/README.md +97 -97
  4. package/dist/css/materialize.css +8462 -8987
  5. package/dist/css/materialize.min.css +7 -7
  6. package/dist/js/materialize.js +12669 -12791
  7. package/dist/js/materialize.min.js +6 -6
  8. package/extras/noUiSlider/nouislider.css +406 -406
  9. package/extras/noUiSlider/nouislider.js +2147 -2147
  10. package/extras/noUiSlider/nouislider.min.js +0 -0
  11. package/js/anime.min.js +34 -34
  12. package/js/autocomplete.js +479 -479
  13. package/js/buttons.js +354 -354
  14. package/js/cards.js +40 -40
  15. package/js/carousel.js +732 -717
  16. package/js/cash.js +960 -960
  17. package/js/characterCounter.js +136 -136
  18. package/js/chips.js +486 -486
  19. package/js/collapsible.js +275 -275
  20. package/js/component.js +44 -44
  21. package/js/datepicker.js +983 -976
  22. package/js/dropdown.js +669 -668
  23. package/js/forms.js +275 -275
  24. package/js/global.js +424 -424
  25. package/js/materialbox.js +453 -453
  26. package/js/modal.js +382 -382
  27. package/js/parallax.js +138 -138
  28. package/js/pushpin.js +148 -148
  29. package/js/range.js +263 -263
  30. package/js/scrollspy.js +295 -295
  31. package/js/select.js +310 -451
  32. package/js/sidenav.js +583 -583
  33. package/js/slider.js +359 -359
  34. package/js/tabs.js +402 -402
  35. package/js/tapTarget.js +315 -315
  36. package/js/timepicker.js +648 -647
  37. package/js/toasts.js +322 -322
  38. package/js/tooltip.js +320 -320
  39. package/js/waves.js +614 -614
  40. package/package.json +82 -74
  41. package/sass/_style.scss +929 -0
  42. package/sass/components/_badges.scss +55 -55
  43. package/sass/components/_buttons.scss +322 -322
  44. package/sass/components/_cards.scss +195 -195
  45. package/sass/components/_carousel.scss +90 -90
  46. package/sass/components/_chips.scss +96 -96
  47. package/sass/components/_collapsible.scss +91 -91
  48. package/sass/components/_collection.scss +107 -0
  49. package/sass/components/_color-classes.scss +32 -32
  50. package/sass/components/_color-variables.scss +370 -370
  51. package/sass/components/_datepicker.scss +191 -191
  52. package/sass/components/_dropdown.scss +84 -84
  53. package/sass/components/_global.scss +642 -771
  54. package/sass/components/_grid.scss +158 -156
  55. package/sass/components/_icons-material-design.scss +5 -5
  56. package/sass/components/_materialbox.scss +42 -42
  57. package/sass/components/_modal.scss +97 -97
  58. package/sass/components/_navbar.scss +208 -208
  59. package/sass/components/_normalize.scss +447 -447
  60. package/sass/components/_preloader.scss +334 -334
  61. package/sass/components/_pulse.scss +34 -34
  62. package/sass/components/_sidenav.scss +214 -214
  63. package/sass/components/_slider.scss +91 -91
  64. package/sass/components/_table_of_contents.scss +33 -33
  65. package/sass/components/_tabs.scss +99 -99
  66. package/sass/components/_tapTarget.scss +103 -103
  67. package/sass/components/_timepicker.scss +183 -183
  68. package/sass/components/_toast.scss +58 -58
  69. package/sass/components/_tooltip.scss +32 -32
  70. package/sass/components/_transitions.scss +12 -12
  71. package/sass/components/_typography.scss +62 -60
  72. package/sass/components/_variables.scss +352 -349
  73. package/sass/components/_waves.scss +187 -187
  74. package/sass/components/forms/_checkboxes.scss +200 -200
  75. package/sass/components/forms/_file-input.scss +44 -44
  76. package/sass/components/forms/_forms.scss +22 -22
  77. package/sass/components/forms/_input-fields.scss +379 -379
  78. package/sass/components/forms/_radio-buttons.scss +115 -115
  79. package/sass/components/forms/_range.scss +161 -161
  80. package/sass/components/forms/_select.scss +199 -199
  81. package/sass/components/forms/_switches.scss +91 -91
  82. package/sass/ghpages-materialize.scss +7 -0
  83. package/sass/materialize.scss +42 -41
  84. package/CHANGELOG.md +0 -76
  85. package/HISTORY.md +0 -527
package/js/dropdown.js CHANGED
@@ -1,668 +1,669 @@
1
- (function($, anim) {
2
- 'use strict';
3
-
4
- let _defaults = {
5
- alignment: 'left',
6
- autoFocus: true,
7
- constrainWidth: true,
8
- container: null,
9
- coverTrigger: true,
10
- closeOnClick: true,
11
- hover: false,
12
- inDuration: 150,
13
- outDuration: 250,
14
- onOpenStart: null,
15
- onOpenEnd: null,
16
- onCloseStart: null,
17
- onCloseEnd: null,
18
- onItemClick: null
19
- };
20
-
21
- /**
22
- * @class
23
- */
24
- class Dropdown extends Component {
25
- constructor(el, options) {
26
- super(Dropdown, el, options);
27
-
28
- this.el.M_Dropdown = this;
29
- Dropdown._dropdowns.push(this);
30
-
31
- this.id = M.getIdFromTrigger(el);
32
- this.dropdownEl = document.getElementById(this.id);
33
- this.$dropdownEl = $(this.dropdownEl);
34
-
35
- /**
36
- * Options for the dropdown
37
- * @member Dropdown#options
38
- * @prop {String} [alignment='left'] - Edge which the dropdown is aligned to
39
- * @prop {Boolean} [autoFocus=true] - Automatically focus dropdown el for keyboard
40
- * @prop {Boolean} [constrainWidth=true] - Constrain width to width of the button
41
- * @prop {Element} container - Container element to attach dropdown to (optional)
42
- * @prop {Boolean} [coverTrigger=true] - Place dropdown over trigger
43
- * @prop {Boolean} [closeOnClick=true] - Close on click of dropdown item
44
- * @prop {Boolean} [hover=false] - Open dropdown on hover
45
- * @prop {Number} [inDuration=150] - Duration of open animation in ms
46
- * @prop {Number} [outDuration=250] - Duration of close animation in ms
47
- * @prop {Function} onOpenStart - Function called when dropdown starts opening
48
- * @prop {Function} onOpenEnd - Function called when dropdown finishes opening
49
- * @prop {Function} onCloseStart - Function called when dropdown starts closing
50
- * @prop {Function} onCloseEnd - Function called when dropdown finishes closing
51
- */
52
- this.options = $.extend({}, Dropdown.defaults, options);
53
-
54
- /**
55
- * Describes open/close state of dropdown
56
- * @type {Boolean}
57
- */
58
- this.isOpen = false;
59
-
60
- /**
61
- * Describes if dropdown content is scrollable
62
- * @type {Boolean}
63
- */
64
- this.isScrollable = false;
65
-
66
- /**
67
- * Describes if touch moving on dropdown content
68
- * @type {Boolean}
69
- */
70
- this.isTouchMoving = false;
71
-
72
- this.focusedIndex = -1;
73
- this.filterQuery = [];
74
-
75
- // Move dropdown-content after dropdown-trigger
76
- this._moveDropdown();
77
-
78
- this._makeDropdownFocusable();
79
- this._resetFilterQueryBound = this._resetFilterQuery.bind(this);
80
- this._handleDocumentClickBound = this._handleDocumentClick.bind(this);
81
- this._handleDocumentTouchmoveBound = this._handleDocumentTouchmove.bind(this);
82
- this._handleDropdownClickBound = this._handleDropdownClick.bind(this);
83
- this._handleDropdownKeydownBound = this._handleDropdownKeydown.bind(this);
84
- this._handleTriggerKeydownBound = this._handleTriggerKeydown.bind(this);
85
- this._setupEventHandlers();
86
- }
87
-
88
- static get defaults() {
89
- return _defaults;
90
- }
91
-
92
- static init(els, options) {
93
- return super.init(this, els, options);
94
- }
95
-
96
- /**
97
- * Get Instance
98
- */
99
- static getInstance(el) {
100
- let domElem = !!el.jquery ? el[0] : el;
101
- return domElem.M_Dropdown;
102
- }
103
-
104
- /**
105
- * Teardown component
106
- */
107
- destroy() {
108
- this._resetDropdownStyles();
109
- this._removeEventHandlers();
110
- Dropdown._dropdowns.splice(Dropdown._dropdowns.indexOf(this), 1);
111
- this.el.M_Dropdown = undefined;
112
- }
113
-
114
- /**
115
- * Setup Event Handlers
116
- */
117
- _setupEventHandlers() {
118
- // Trigger keydown handler
119
- this.el.addEventListener('keydown', this._handleTriggerKeydownBound);
120
-
121
- // Item click handler
122
- this.dropdownEl.addEventListener('click', this._handleDropdownClickBound);
123
-
124
- // Hover event handlers
125
- if (this.options.hover) {
126
- this._handleMouseEnterBound = this._handleMouseEnter.bind(this);
127
- this.el.addEventListener('mouseenter', this._handleMouseEnterBound);
128
- this._handleMouseLeaveBound = this._handleMouseLeave.bind(this);
129
- this.el.addEventListener('mouseleave', this._handleMouseLeaveBound);
130
- this.dropdownEl.addEventListener('mouseleave', this._handleMouseLeaveBound);
131
-
132
- // Click event handlers
133
- } else {
134
- this._handleClickBound = this._handleClick.bind(this);
135
- this.el.addEventListener('click', this._handleClickBound);
136
- }
137
- }
138
-
139
- /**
140
- * Remove Event Handlers
141
- */
142
- _removeEventHandlers() {
143
- this.el.removeEventListener('keydown', this._handleTriggerKeydownBound);
144
- this.dropdownEl.removeEventListener('click', this._handleDropdownClickBound);
145
-
146
- if (this.options.hover) {
147
- this.el.removeEventListener('mouseenter', this._handleMouseEnterBound);
148
- this.el.removeEventListener('mouseleave', this._handleMouseLeaveBound);
149
- this.dropdownEl.removeEventListener('mouseleave', this._handleMouseLeaveBound);
150
- } else {
151
- this.el.removeEventListener('click', this._handleClickBound);
152
- }
153
- }
154
-
155
- _setupTemporaryEventHandlers() {
156
- // Use capture phase event handler to prevent click
157
- document.body.addEventListener('click', this._handleDocumentClickBound, true);
158
- document.body.addEventListener('touchend', this._handleDocumentClickBound);
159
- document.body.addEventListener('touchmove', this._handleDocumentTouchmoveBound);
160
- this.dropdownEl.addEventListener('keydown', this._handleDropdownKeydownBound);
161
- }
162
-
163
- _removeTemporaryEventHandlers() {
164
- // Use capture phase event handler to prevent click
165
- document.body.removeEventListener('click', this._handleDocumentClickBound, true);
166
- document.body.removeEventListener('touchend', this._handleDocumentClickBound);
167
- document.body.removeEventListener('touchmove', this._handleDocumentTouchmoveBound);
168
- this.dropdownEl.removeEventListener('keydown', this._handleDropdownKeydownBound);
169
- }
170
-
171
- _handleClick(e) {
172
- e.preventDefault();
173
- this.open();
174
- }
175
-
176
- _handleMouseEnter() {
177
- this.open();
178
- }
179
-
180
- _handleMouseLeave(e) {
181
- let toEl = e.toElement || e.relatedTarget;
182
- let leaveToDropdownContent = !!$(toEl).closest('.dropdown-content').length;
183
- let leaveToActiveDropdownTrigger = false;
184
-
185
- let $closestTrigger = $(toEl).closest('.dropdown-trigger');
186
- if (
187
- $closestTrigger.length &&
188
- !!$closestTrigger[0].M_Dropdown &&
189
- $closestTrigger[0].M_Dropdown.isOpen
190
- ) {
191
- leaveToActiveDropdownTrigger = true;
192
- }
193
-
194
- // Close hover dropdown if mouse did not leave to either active dropdown-trigger or dropdown-content
195
- if (!leaveToActiveDropdownTrigger && !leaveToDropdownContent) {
196
- this.close();
197
- }
198
- }
199
-
200
- _handleDocumentClick(e) {
201
- let $target = $(e.target);
202
- if (
203
- this.options.closeOnClick &&
204
- $target.closest('.dropdown-content').length &&
205
- !this.isTouchMoving
206
- ) {
207
- // isTouchMoving to check if scrolling on mobile.
208
- setTimeout(() => {
209
- this.close();
210
- }, 0);
211
- } else if (
212
- $target.closest('.dropdown-trigger').length ||
213
- !$target.closest('.dropdown-content').length
214
- ) {
215
- setTimeout(() => {
216
- this.close();
217
- }, 0);
218
- }
219
- this.isTouchMoving = false;
220
- }
221
-
222
- _handleTriggerKeydown(e) {
223
- // ARROW DOWN OR ENTER WHEN SELECT IS CLOSED - open Dropdown
224
- if ((e.which === M.keys.ARROW_DOWN || e.which === M.keys.ENTER) && !this.isOpen) {
225
- e.preventDefault();
226
- this.open();
227
- }
228
- }
229
-
230
- /**
231
- * Handle Document Touchmove
232
- * @param {Event} e
233
- */
234
- _handleDocumentTouchmove(e) {
235
- let $target = $(e.target);
236
- if ($target.closest('.dropdown-content').length) {
237
- this.isTouchMoving = true;
238
- }
239
- }
240
-
241
- /**
242
- * Handle Dropdown Click
243
- * @param {Event} e
244
- */
245
- _handleDropdownClick(e) {
246
- // onItemClick callback
247
- if (typeof this.options.onItemClick === 'function') {
248
- let itemEl = $(e.target).closest('li')[0];
249
- this.options.onItemClick.call(this, itemEl);
250
- }
251
- }
252
-
253
- /**
254
- * Handle Dropdown Keydown
255
- * @param {Event} e
256
- */
257
- _handleDropdownKeydown(e) {
258
- if (e.which === M.keys.TAB) {
259
- e.preventDefault();
260
- this.close();
261
-
262
- // Navigate down dropdown list
263
- } else if ((e.which === M.keys.ARROW_DOWN || e.which === M.keys.ARROW_UP) && this.isOpen) {
264
- e.preventDefault();
265
- let direction = e.which === M.keys.ARROW_DOWN ? 1 : -1;
266
- let newFocusedIndex = this.focusedIndex;
267
- let foundNewIndex = false;
268
- do {
269
- newFocusedIndex = newFocusedIndex + direction;
270
-
271
- if (
272
- !!this.dropdownEl.children[newFocusedIndex] &&
273
- this.dropdownEl.children[newFocusedIndex].tabIndex !== -1
274
- ) {
275
- foundNewIndex = true;
276
- break;
277
- }
278
- } while (newFocusedIndex < this.dropdownEl.children.length && newFocusedIndex >= 0);
279
-
280
- if (foundNewIndex) {
281
- // Remove active class from old element
282
- if (this.focusedIndex >= 0)
283
- this.dropdownEl.children[this.focusedIndex].classList.remove('active');
284
- this.focusedIndex = newFocusedIndex;
285
- this._focusFocusedItem();
286
- }
287
-
288
- // ENTER selects choice on focused item
289
- } else if (e.which === M.keys.ENTER && this.isOpen) {
290
- // Search for <a> and <button>
291
- let focusedElement = this.dropdownEl.children[this.focusedIndex];
292
- let $activatableElement = $(focusedElement)
293
- .find('a, button')
294
- .first();
295
-
296
- // Click a or button tag if exists, otherwise click li tag
297
- if (!!$activatableElement.length) {
298
- $activatableElement[0].click();
299
- } else if (!!focusedElement) {
300
- focusedElement.click();
301
- }
302
-
303
- // Close dropdown on ESC
304
- } else if (e.which === M.keys.ESC && this.isOpen) {
305
- e.preventDefault();
306
- this.close();
307
- }
308
-
309
- // CASE WHEN USER TYPE LETTERS
310
- let letter = String.fromCharCode(e.which).toLowerCase(),
311
- nonLetters = [9, 13, 27, 38, 40];
312
- if (letter && nonLetters.indexOf(e.which) === -1) {
313
- this.filterQuery.push(letter);
314
-
315
- let string = this.filterQuery.join(''),
316
- newOptionEl = $(this.dropdownEl)
317
- .find('li')
318
- .filter((el) => {
319
- return (
320
- $(el)
321
- .text()
322
- .toLowerCase()
323
- .indexOf(string) === 0
324
- );
325
- })[0];
326
-
327
- if (newOptionEl) {
328
- this.focusedIndex = $(newOptionEl).index();
329
- this._focusFocusedItem();
330
- }
331
- }
332
-
333
- this.filterTimeout = setTimeout(this._resetFilterQueryBound, 1000);
334
- }
335
-
336
- /**
337
- * Setup dropdown
338
- */
339
- _resetFilterQuery() {
340
- this.filterQuery = [];
341
- }
342
-
343
- _resetDropdownStyles() {
344
- this.$dropdownEl.css({
345
- display: '',
346
- width: '',
347
- height: '',
348
- left: '',
349
- top: '',
350
- 'transform-origin': '',
351
- transform: '',
352
- opacity: ''
353
- });
354
- }
355
-
356
- // Move dropdown after container or trigger
357
- _moveDropdown(containerEl) {
358
- if (!!this.options.container) {
359
- $(this.options.container).append(this.dropdownEl);
360
- } else if (containerEl) {
361
- if (!containerEl.contains(this.dropdownEl)) {
362
- $(containerEl).append(this.dropdownEl);
363
- }
364
- } else {
365
- this.$el.after(this.dropdownEl);
366
- }
367
- }
368
-
369
- _makeDropdownFocusable() {
370
- // Needed for arrow key navigation
371
- this.dropdownEl.tabIndex = 0;
372
-
373
- // Only set tabindex if it hasn't been set by user
374
- $(this.dropdownEl)
375
- .children()
376
- .each(function(el) {
377
- if (!el.getAttribute('tabindex')) {
378
- el.setAttribute('tabindex', 0);
379
- }
380
- });
381
- }
382
-
383
- _focusFocusedItem() {
384
- if (
385
- this.focusedIndex >= 0 &&
386
- this.focusedIndex < this.dropdownEl.children.length &&
387
- this.options.autoFocus
388
- ) {
389
- this.dropdownEl.children[this.focusedIndex].classList.add('active');
390
- this.dropdownEl.children[this.focusedIndex].scrollIntoView({
391
- behavior: 'smooth',
392
- block: 'nearest',
393
- inline: 'nearest'
394
- });
395
- }
396
- }
397
-
398
- _getDropdownPosition(closestOverflowParent) {
399
- let offsetParentBRect = this.el.offsetParent.getBoundingClientRect();
400
- let triggerBRect = this.el.getBoundingClientRect();
401
- let dropdownBRect = this.dropdownEl.getBoundingClientRect();
402
-
403
- let idealHeight = dropdownBRect.height;
404
- let idealWidth = dropdownBRect.width;
405
- let idealXPos = triggerBRect.left - dropdownBRect.left;
406
- let idealYPos = triggerBRect.top - dropdownBRect.top;
407
-
408
- let dropdownBounds = {
409
- left: idealXPos,
410
- top: idealYPos,
411
- height: idealHeight,
412
- width: idealWidth
413
- };
414
-
415
- let alignments = M.checkPossibleAlignments(
416
- this.el,
417
- closestOverflowParent,
418
- dropdownBounds,
419
- this.options.coverTrigger ? 0 : triggerBRect.height
420
- );
421
-
422
- let verticalAlignment = 'top';
423
- let horizontalAlignment = this.options.alignment;
424
- idealYPos += this.options.coverTrigger ? 0 : triggerBRect.height;
425
-
426
- // Reset isScrollable
427
- this.isScrollable = false;
428
-
429
- if (!alignments.top) {
430
- if (alignments.bottom) {
431
- verticalAlignment = 'bottom';
432
-
433
- if (!this.options.coverTrigger) {
434
- idealYPos -= triggerBRect.height;
435
- }
436
- } else {
437
- this.isScrollable = true;
438
-
439
- // Determine which side has most space and cutoff at correct height
440
- idealHeight -= 20; // Add padding when cutoff
441
- if (alignments.spaceOnTop > alignments.spaceOnBottom) {
442
- verticalAlignment = 'bottom';
443
- idealHeight += alignments.spaceOnTop;
444
- idealYPos -= this.options.coverTrigger
445
- ? alignments.spaceOnTop - 20
446
- : alignments.spaceOnTop - 20 + triggerBRect.height;
447
- } else {
448
- idealHeight += alignments.spaceOnBottom;
449
- }
450
- }
451
- }
452
-
453
- // If preferred horizontal alignment is possible
454
- if (!alignments[horizontalAlignment]) {
455
- let oppositeAlignment = horizontalAlignment === 'left' ? 'right' : 'left';
456
- if (alignments[oppositeAlignment]) {
457
- horizontalAlignment = oppositeAlignment;
458
- } else {
459
- // Determine which side has most space and cutoff at correct height
460
- if (alignments.spaceOnLeft > alignments.spaceOnRight) {
461
- horizontalAlignment = 'right';
462
- idealWidth += alignments.spaceOnLeft;
463
- idealXPos -= alignments.spaceOnLeft;
464
- } else {
465
- horizontalAlignment = 'left';
466
- idealWidth += alignments.spaceOnRight;
467
- }
468
- }
469
- }
470
-
471
- if (verticalAlignment === 'bottom') {
472
- idealYPos =
473
- idealYPos - dropdownBRect.height + (this.options.coverTrigger ? triggerBRect.height : 0);
474
- }
475
- if (horizontalAlignment === 'right') {
476
- idealXPos = idealXPos - dropdownBRect.width + triggerBRect.width;
477
- }
478
- return {
479
- x: idealXPos,
480
- y: idealYPos,
481
- verticalAlignment: verticalAlignment,
482
- horizontalAlignment: horizontalAlignment,
483
- height: idealHeight,
484
- width: idealWidth
485
- };
486
- }
487
-
488
- /**
489
- * Animate in dropdown
490
- */
491
- _animateIn() {
492
- anim.remove(this.dropdownEl);
493
- anim({
494
- targets: this.dropdownEl,
495
- opacity: {
496
- value: [0, 1],
497
- easing: 'easeOutQuad'
498
- },
499
- scaleX: [0.3, 1],
500
- scaleY: [0.3, 1],
501
- duration: this.options.inDuration,
502
- easing: 'easeOutQuint',
503
- complete: (anim) => {
504
- if (this.options.autoFocus) {
505
- this.dropdownEl.focus();
506
- }
507
-
508
- // onOpenEnd callback
509
- if (typeof this.options.onOpenEnd === 'function') {
510
- this.options.onOpenEnd.call(this, this.el);
511
- }
512
- }
513
- });
514
- }
515
-
516
- /**
517
- * Animate out dropdown
518
- */
519
- _animateOut() {
520
- anim.remove(this.dropdownEl);
521
- anim({
522
- targets: this.dropdownEl,
523
- opacity: {
524
- value: 0,
525
- easing: 'easeOutQuint'
526
- },
527
- scaleX: 0.3,
528
- scaleY: 0.3,
529
- duration: this.options.outDuration,
530
- easing: 'easeOutQuint',
531
- complete: (anim) => {
532
- this._resetDropdownStyles();
533
-
534
- // onCloseEnd callback
535
- if (typeof this.options.onCloseEnd === 'function') {
536
- this.options.onCloseEnd.call(this, this.el);
537
- }
538
- }
539
- });
540
- }
541
-
542
- /**
543
- * Place dropdown
544
- */
545
- _placeDropdown() {
546
- /**
547
- * Get closest ancestor that satisfies the condition
548
- * @param {Element} el Element to find ancestors on
549
- * @param {Function} condition Function that given an ancestor element returns true or false
550
- * @returns {Element} Return closest ancestor or null if none satisfies the condition
551
- */
552
- const getClosestAncestor = function(el, condition) {
553
- let ancestor = el.parentNode;
554
- while (ancestor !== null && !$(ancestor).is(document)) {
555
- if (condition(ancestor)) {
556
- return ancestor;
557
- }
558
- ancestor = ancestor.parentNode;
559
- }
560
- return null;
561
- };
562
-
563
- // Container here will be closest ancestor with overflow: hidden
564
- let closestOverflowParent = getClosestAncestor(this.dropdownEl, (ancestor) => {
565
- return $(ancestor).css('overflow') !== 'visible';
566
- });
567
- // Fallback
568
- if (!closestOverflowParent) {
569
- closestOverflowParent = !!this.dropdownEl.offsetParent
570
- ? this.dropdownEl.offsetParent
571
- : this.dropdownEl.parentNode;
572
- }
573
- if ($(closestOverflowParent).css('position') === 'static')
574
- $(closestOverflowParent).css('position', 'relative');
575
-
576
- this._moveDropdown(closestOverflowParent);
577
-
578
- // Set width before calculating positionInfo
579
- let idealWidth = this.options.constrainWidth
580
- ? this.el.getBoundingClientRect().width
581
- : this.dropdownEl.getBoundingClientRect().width;
582
- this.dropdownEl.style.width = idealWidth + 'px';
583
-
584
- let positionInfo = this._getDropdownPosition(closestOverflowParent);
585
- this.dropdownEl.style.left = positionInfo.x + 'px';
586
- this.dropdownEl.style.top = positionInfo.y + 'px';
587
- this.dropdownEl.style.height = positionInfo.height + 'px';
588
- this.dropdownEl.style.width = positionInfo.width + 'px';
589
- this.dropdownEl.style.transformOrigin = `${
590
- positionInfo.horizontalAlignment === 'left' ? '0' : '100%'
591
- } ${positionInfo.verticalAlignment === 'top' ? '0' : '100%'}`;
592
- }
593
-
594
- /**
595
- * Open Dropdown
596
- */
597
- open() {
598
- if (this.isOpen) {
599
- return;
600
- }
601
- this.isOpen = true;
602
-
603
- // onOpenStart callback
604
- if (typeof this.options.onOpenStart === 'function') {
605
- this.options.onOpenStart.call(this, this.el);
606
- }
607
-
608
- // Reset styles
609
- this._resetDropdownStyles();
610
- this.dropdownEl.style.display = 'block';
611
-
612
- this._placeDropdown();
613
- this._animateIn();
614
- this._setupTemporaryEventHandlers();
615
- }
616
-
617
- /**
618
- * Close Dropdown
619
- */
620
- close() {
621
- if (!this.isOpen) {
622
- return;
623
- }
624
- this.isOpen = false;
625
- this.focusedIndex = -1;
626
-
627
- // onCloseStart callback
628
- if (typeof this.options.onCloseStart === 'function') {
629
- this.options.onCloseStart.call(this, this.el);
630
- }
631
-
632
- this._animateOut();
633
- this._removeTemporaryEventHandlers();
634
-
635
- if (this.options.autoFocus) {
636
- this.el.focus();
637
- }
638
- }
639
-
640
- /**
641
- * Recalculate dimensions
642
- */
643
- recalculateDimensions() {
644
- if (this.isOpen) {
645
- this.$dropdownEl.css({
646
- width: '',
647
- height: '',
648
- left: '',
649
- top: '',
650
- 'transform-origin': ''
651
- });
652
- this._placeDropdown();
653
- }
654
- }
655
- }
656
-
657
- /**
658
- * @static
659
- * @memberof Dropdown
660
- */
661
- Dropdown._dropdowns = [];
662
-
663
- M.Dropdown = Dropdown;
664
-
665
- if (M.jQueryLoaded) {
666
- M.initializeJqueryWrapper(Dropdown, 'dropdown', 'M_Dropdown');
667
- }
668
- })(cash, M.anime);
1
+ (function($, anim) {
2
+ 'use strict';
3
+
4
+ let _defaults = {
5
+ alignment: 'left',
6
+ autoFocus: true,
7
+ constrainWidth: true,
8
+ container: null,
9
+ coverTrigger: true,
10
+ closeOnClick: true,
11
+ hover: false,
12
+ inDuration: 150,
13
+ outDuration: 250,
14
+ onOpenStart: null,
15
+ onOpenEnd: null,
16
+ onCloseStart: null,
17
+ onCloseEnd: null,
18
+ onItemClick: null
19
+ };
20
+
21
+ /**
22
+ * @class
23
+ */
24
+ class Dropdown extends Component {
25
+ constructor(el, options) {
26
+ super(Dropdown, el, options);
27
+
28
+ this.el.M_Dropdown = this;
29
+ Dropdown._dropdowns.push(this);
30
+
31
+ this.id = M.getIdFromTrigger(el);
32
+ this.dropdownEl = document.getElementById(this.id);
33
+ this.$dropdownEl = $(this.dropdownEl);
34
+
35
+ /**
36
+ * Options for the dropdown
37
+ * @member Dropdown#options
38
+ * @prop {String} [alignment='left'] - Edge which the dropdown is aligned to
39
+ * @prop {Boolean} [autoFocus=true] - Automatically focus dropdown el for keyboard
40
+ * @prop {Boolean} [constrainWidth=true] - Constrain width to width of the button
41
+ * @prop {Element} container - Container element to attach dropdown to (optional)
42
+ * @prop {Boolean} [coverTrigger=true] - Place dropdown over trigger
43
+ * @prop {Boolean} [closeOnClick=true] - Close on click of dropdown item
44
+ * @prop {Boolean} [hover=false] - Open dropdown on hover
45
+ * @prop {Number} [inDuration=150] - Duration of open animation in ms
46
+ * @prop {Number} [outDuration=250] - Duration of close animation in ms
47
+ * @prop {Function} onOpenStart - Function called when dropdown starts opening
48
+ * @prop {Function} onOpenEnd - Function called when dropdown finishes opening
49
+ * @prop {Function} onCloseStart - Function called when dropdown starts closing
50
+ * @prop {Function} onCloseEnd - Function called when dropdown finishes closing
51
+ */
52
+ this.options = $.extend({}, Dropdown.defaults, options);
53
+
54
+ /**
55
+ * Describes open/close state of dropdown
56
+ * @type {Boolean}
57
+ */
58
+ this.isOpen = false;
59
+
60
+ /**
61
+ * Describes if dropdown content is scrollable
62
+ * @type {Boolean}
63
+ */
64
+ this.isScrollable = false;
65
+
66
+ /**
67
+ * Describes if touch moving on dropdown content
68
+ * @type {Boolean}
69
+ */
70
+ this.isTouchMoving = false;
71
+
72
+ this.focusedIndex = -1;
73
+ this.filterQuery = [];
74
+
75
+ // Move dropdown-content after dropdown-trigger
76
+ this._moveDropdown();
77
+
78
+ this._makeDropdownFocusable();
79
+ this._resetFilterQueryBound = this._resetFilterQuery.bind(this);
80
+ this._handleDocumentClickBound = this._handleDocumentClick.bind(this);
81
+ this._handleDocumentTouchmoveBound = this._handleDocumentTouchmove.bind(this);
82
+ this._handleDropdownClickBound = this._handleDropdownClick.bind(this);
83
+ this._handleDropdownKeydownBound = this._handleDropdownKeydown.bind(this);
84
+ this._handleTriggerKeydownBound = this._handleTriggerKeydown.bind(this);
85
+ this._setupEventHandlers();
86
+ }
87
+
88
+ static get defaults() {
89
+ return _defaults;
90
+ }
91
+
92
+ static init(els, options) {
93
+ return super.init(this, els, options);
94
+ }
95
+
96
+ /**
97
+ * Get Instance
98
+ */
99
+ static getInstance(el) {
100
+ let domElem = !!el.jquery ? el[0] : el;
101
+ return domElem.M_Dropdown;
102
+ }
103
+
104
+ /**
105
+ * Teardown component
106
+ */
107
+ destroy() {
108
+ this._resetDropdownStyles();
109
+ this._removeEventHandlers();
110
+ Dropdown._dropdowns.splice(Dropdown._dropdowns.indexOf(this), 1);
111
+ this.el.M_Dropdown = undefined;
112
+ }
113
+
114
+ /**
115
+ * Setup Event Handlers
116
+ */
117
+ _setupEventHandlers() {
118
+ // Trigger keydown handler
119
+ this.el.addEventListener('keydown', this._handleTriggerKeydownBound);
120
+
121
+ // Item click handler
122
+ this.dropdownEl.addEventListener('click', this._handleDropdownClickBound);
123
+
124
+ // Hover event handlers
125
+ if (this.options.hover) {
126
+ this._handleMouseEnterBound = this._handleMouseEnter.bind(this);
127
+ this.el.addEventListener('mouseenter', this._handleMouseEnterBound);
128
+ this._handleMouseLeaveBound = this._handleMouseLeave.bind(this);
129
+ this.el.addEventListener('mouseleave', this._handleMouseLeaveBound);
130
+ this.dropdownEl.addEventListener('mouseleave', this._handleMouseLeaveBound);
131
+
132
+ // Click event handlers
133
+ } else {
134
+ this._handleClickBound = this._handleClick.bind(this);
135
+ this.el.addEventListener('click', this._handleClickBound);
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Remove Event Handlers
141
+ */
142
+ _removeEventHandlers() {
143
+ this.el.removeEventListener('keydown', this._handleTriggerKeydownBound);
144
+ this.dropdownEl.removeEventListener('click', this._handleDropdownClickBound);
145
+
146
+ if (this.options.hover) {
147
+ this.el.removeEventListener('mouseenter', this._handleMouseEnterBound);
148
+ this.el.removeEventListener('mouseleave', this._handleMouseLeaveBound);
149
+ this.dropdownEl.removeEventListener('mouseleave', this._handleMouseLeaveBound);
150
+ } else {
151
+ this.el.removeEventListener('click', this._handleClickBound);
152
+ }
153
+ }
154
+
155
+ _setupTemporaryEventHandlers() {
156
+ // Use capture phase event handler to prevent click
157
+ document.body.addEventListener('click', this._handleDocumentClickBound, true);
158
+ document.body.addEventListener('touchmove', this._handleDocumentTouchmoveBound);
159
+ this.dropdownEl.addEventListener('keydown', this._handleDropdownKeydownBound);
160
+ }
161
+
162
+ _removeTemporaryEventHandlers() {
163
+ // Use capture phase event handler to prevent click
164
+ document.body.removeEventListener('click', this._handleDocumentClickBound, true);
165
+ document.body.removeEventListener('touchmove', this._handleDocumentTouchmoveBound);
166
+ this.dropdownEl.removeEventListener('keydown', this._handleDropdownKeydownBound);
167
+ }
168
+
169
+ _handleClick(e) {
170
+ e.preventDefault();
171
+ this.open();
172
+ }
173
+
174
+ _handleMouseEnter() {
175
+ this.open();
176
+ }
177
+
178
+ _handleMouseLeave(e) {
179
+ let toEl = e.toElement || e.relatedTarget;
180
+ let leaveToDropdownContent = !!$(toEl).closest('.dropdown-content').length;
181
+ let leaveToActiveDropdownTrigger = false;
182
+
183
+ let $closestTrigger = $(toEl).closest('.dropdown-trigger');
184
+ if (
185
+ $closestTrigger.length &&
186
+ !!$closestTrigger[0].M_Dropdown &&
187
+ $closestTrigger[0].M_Dropdown.isOpen
188
+ ) {
189
+ leaveToActiveDropdownTrigger = true;
190
+ }
191
+
192
+ // Close hover dropdown if mouse did not leave to either active dropdown-trigger or dropdown-content
193
+ if (!leaveToActiveDropdownTrigger && !leaveToDropdownContent) {
194
+ this.close();
195
+ }
196
+ }
197
+
198
+ _handleDocumentClick(e) {
199
+ let $target = $(e.target);
200
+ if (
201
+ this.options.closeOnClick &&
202
+ $target.closest('.dropdown-content').length &&
203
+ !this.isTouchMoving
204
+ ) {
205
+ // isTouchMoving to check if scrolling on mobile.
206
+ setTimeout(() => {
207
+ this.close();
208
+ }, 0);
209
+ } else if (
210
+ $target.closest('.dropdown-trigger').length ||
211
+ !$target.closest('.dropdown-content').length
212
+ ) {
213
+ setTimeout(() => {
214
+ this.close();
215
+ }, 0);
216
+ }
217
+ this.isTouchMoving = false;
218
+ }
219
+
220
+ _handleTriggerKeydown(e) {
221
+ // ARROW DOWN OR ENTER WHEN SELECT IS CLOSED - open Dropdown
222
+ if ((e.which === M.keys.ARROW_DOWN || e.which === M.keys.ENTER) && !this.isOpen) {
223
+ e.preventDefault();
224
+ this.open();
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Handle Document Touchmove
230
+ * @param {Event} e
231
+ */
232
+ _handleDocumentTouchmove(e) {
233
+ let $target = $(e.target);
234
+ if ($target.closest('.dropdown-content').length) {
235
+ this.isTouchMoving = true;
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Handle Dropdown Click
241
+ * @param {Event} e
242
+ */
243
+ _handleDropdownClick(e) {
244
+ // onItemClick callback
245
+ if (typeof this.options.onItemClick === 'function') {
246
+ let itemEl = $(e.target).closest('li')[0];
247
+ this.options.onItemClick.call(this, itemEl);
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Handle Dropdown Keydown
253
+ * @param {Event} e
254
+ */
255
+ _handleDropdownKeydown(e) {
256
+ if (e.which === M.keys.TAB) {
257
+ e.preventDefault();
258
+ this.close();
259
+
260
+ // Navigate down dropdown list
261
+ } else if ((e.which === M.keys.ARROW_DOWN || e.which === M.keys.ARROW_UP) && this.isOpen) {
262
+ e.preventDefault();
263
+ let direction = e.which === M.keys.ARROW_DOWN ? 1 : -1;
264
+ let newFocusedIndex = this.focusedIndex;
265
+ let foundNewIndex = false;
266
+ do {
267
+ newFocusedIndex = newFocusedIndex + direction;
268
+
269
+ if (
270
+ !!this.dropdownEl.children[newFocusedIndex] &&
271
+ this.dropdownEl.children[newFocusedIndex].tabIndex !== -1
272
+ ) {
273
+ foundNewIndex = true;
274
+ break;
275
+ }
276
+ } while (newFocusedIndex < this.dropdownEl.children.length && newFocusedIndex >= 0);
277
+
278
+ if (foundNewIndex) {
279
+ // Remove active class from old element
280
+ if (this.focusedIndex >= 0)
281
+ this.dropdownEl.children[this.focusedIndex].classList.remove('active');
282
+ this.focusedIndex = newFocusedIndex;
283
+ this._focusFocusedItem();
284
+ }
285
+
286
+ // ENTER selects choice on focused item
287
+ } else if (e.which === M.keys.ENTER && this.isOpen) {
288
+ // Search for <a> and <button>
289
+ let focusedElement = this.dropdownEl.children[this.focusedIndex];
290
+ let $activatableElement = $(focusedElement)
291
+ .find('a, button')
292
+ .first();
293
+
294
+ // Click a or button tag if exists, otherwise click li tag
295
+ if (!!$activatableElement.length) {
296
+ $activatableElement[0].click();
297
+ } else if (!!focusedElement) {
298
+ focusedElement.click();
299
+ }
300
+
301
+ // Close dropdown on ESC
302
+ } else if (e.which === M.keys.ESC && this.isOpen) {
303
+ e.preventDefault();
304
+ this.close();
305
+ }
306
+
307
+ // CASE WHEN USER TYPE LETTERS
308
+ let letter = String.fromCharCode(e.which).toLowerCase(),
309
+ nonLetters = [9, 13, 27, 38, 40];
310
+ if (letter && nonLetters.indexOf(e.which) === -1) {
311
+ this.filterQuery.push(letter);
312
+
313
+ let string = this.filterQuery.join(''),
314
+ newOptionEl = $(this.dropdownEl)
315
+ .find('li')
316
+ .filter((el) => {
317
+ return (
318
+ $(el)
319
+ .text()
320
+ .toLowerCase()
321
+ .indexOf(string) === 0
322
+ );
323
+ })[0];
324
+
325
+ if (newOptionEl) {
326
+ this.focusedIndex = $(newOptionEl).index();
327
+ this._focusFocusedItem();
328
+ }
329
+ }
330
+
331
+ this.filterTimeout = setTimeout(this._resetFilterQueryBound, 1000);
332
+ }
333
+
334
+ /**
335
+ * Setup dropdown
336
+ */
337
+ _resetFilterQuery() {
338
+ this.filterQuery = [];
339
+ }
340
+
341
+ _resetDropdownStyles() {
342
+ this.$dropdownEl.css({
343
+ display: '',
344
+ width: '',
345
+ height: '',
346
+ left: '',
347
+ top: '',
348
+ 'transform-origin': '',
349
+ transform: '',
350
+ opacity: ''
351
+ });
352
+ }
353
+
354
+ // Move dropdown after container or trigger
355
+ _moveDropdown(containerEl) {
356
+ if (!!this.options.container) {
357
+ $(this.options.container).append(this.dropdownEl);
358
+ } else if (containerEl) {
359
+ if (!containerEl.contains(this.dropdownEl)) {
360
+ $(containerEl).append(this.dropdownEl);
361
+ }
362
+ } else {
363
+ this.$el.after(this.dropdownEl);
364
+ }
365
+ }
366
+
367
+ _makeDropdownFocusable() {
368
+ // Needed for arrow key navigation
369
+ this.dropdownEl.tabIndex = 0;
370
+
371
+ // Only set tabindex if it hasn't been set by user
372
+ $(this.dropdownEl)
373
+ .children()
374
+ .each(function(el) {
375
+ if (!el.getAttribute('tabindex')) {
376
+ el.setAttribute('tabindex', 0);
377
+ }
378
+ });
379
+ }
380
+
381
+ _focusFocusedItem() {
382
+ if (
383
+ this.focusedIndex >= 0 &&
384
+ this.focusedIndex < this.dropdownEl.children.length &&
385
+ this.options.autoFocus
386
+ ) {
387
+ this.dropdownEl.children[this.focusedIndex].focus({
388
+ preventScroll: true
389
+ });
390
+ this.dropdownEl.children[this.focusedIndex].scrollIntoView({
391
+ behavior: 'smooth',
392
+ block: 'nearest',
393
+ inline: 'nearest'
394
+ });
395
+ }
396
+ }
397
+
398
+ _getDropdownPosition(closestOverflowParent) {
399
+ let offsetParentBRect = this.el.offsetParent.getBoundingClientRect();
400
+ let triggerBRect = this.el.getBoundingClientRect();
401
+ let dropdownBRect = this.dropdownEl.getBoundingClientRect();
402
+
403
+ let idealHeight = dropdownBRect.height;
404
+ let idealWidth = dropdownBRect.width;
405
+ let idealXPos = triggerBRect.left - dropdownBRect.left;
406
+ let idealYPos = triggerBRect.top - dropdownBRect.top;
407
+
408
+ let dropdownBounds = {
409
+ left: idealXPos,
410
+ top: idealYPos,
411
+ height: idealHeight,
412
+ width: idealWidth
413
+ };
414
+
415
+ let alignments = M.checkPossibleAlignments(
416
+ this.el,
417
+ closestOverflowParent,
418
+ dropdownBounds,
419
+ this.options.coverTrigger ? 0 : triggerBRect.height
420
+ );
421
+
422
+ let verticalAlignment = 'top';
423
+ let horizontalAlignment = this.options.alignment;
424
+ idealYPos += this.options.coverTrigger ? 0 : triggerBRect.height;
425
+
426
+ // Reset isScrollable
427
+ this.isScrollable = false;
428
+
429
+ if (!alignments.top) {
430
+ if (alignments.bottom) {
431
+ verticalAlignment = 'bottom';
432
+
433
+ if (!this.options.coverTrigger) {
434
+ idealYPos -= triggerBRect.height;
435
+ }
436
+ } else {
437
+ this.isScrollable = true;
438
+
439
+ // Determine which side has most space and cutoff at correct height
440
+ idealHeight -= 20; // Add padding when cutoff
441
+ if (alignments.spaceOnTop > alignments.spaceOnBottom) {
442
+ verticalAlignment = 'bottom';
443
+ idealHeight += alignments.spaceOnTop;
444
+ idealYPos -= this.options.coverTrigger
445
+ ? alignments.spaceOnTop - 20
446
+ : alignments.spaceOnTop - 20 + triggerBRect.height;
447
+ } else {
448
+ idealHeight += alignments.spaceOnBottom;
449
+ }
450
+ }
451
+ }
452
+
453
+ // If preferred horizontal alignment is possible
454
+ if (!alignments[horizontalAlignment]) {
455
+ let oppositeAlignment = horizontalAlignment === 'left' ? 'right' : 'left';
456
+ if (alignments[oppositeAlignment]) {
457
+ horizontalAlignment = oppositeAlignment;
458
+ } else {
459
+ // Determine which side has most space and cutoff at correct height
460
+ if (alignments.spaceOnLeft > alignments.spaceOnRight) {
461
+ horizontalAlignment = 'right';
462
+ idealWidth += alignments.spaceOnLeft;
463
+ idealXPos -= alignments.spaceOnLeft;
464
+ } else {
465
+ horizontalAlignment = 'left';
466
+ idealWidth += alignments.spaceOnRight;
467
+ }
468
+ }
469
+ }
470
+
471
+ if (verticalAlignment === 'bottom') {
472
+ idealYPos =
473
+ idealYPos - dropdownBRect.height + (this.options.coverTrigger ? triggerBRect.height : 0);
474
+ }
475
+ if (horizontalAlignment === 'right') {
476
+ idealXPos = idealXPos - dropdownBRect.width + triggerBRect.width;
477
+ }
478
+ return {
479
+ x: idealXPos,
480
+ y: idealYPos,
481
+ verticalAlignment: verticalAlignment,
482
+ horizontalAlignment: horizontalAlignment,
483
+ height: idealHeight,
484
+ width: idealWidth
485
+ };
486
+ }
487
+
488
+ /**
489
+ * Animate in dropdown
490
+ */
491
+ _animateIn() {
492
+ anim.remove(this.dropdownEl);
493
+ anim({
494
+ targets: this.dropdownEl,
495
+ opacity: {
496
+ value: [0, 1],
497
+ easing: 'easeOutQuad'
498
+ },
499
+ scaleX: [0.3, 1],
500
+ scaleY: [0.3, 1],
501
+ duration: this.options.inDuration,
502
+ easing: 'easeOutQuint',
503
+ complete: (anim) => {
504
+ if (this.options.autoFocus) {
505
+ this.dropdownEl.focus();
506
+ }
507
+
508
+ // onOpenEnd callback
509
+ if (typeof this.options.onOpenEnd === 'function') {
510
+ this.options.onOpenEnd.call(this, this.el);
511
+ }
512
+ }
513
+ });
514
+ }
515
+
516
+ /**
517
+ * Animate out dropdown
518
+ */
519
+ _animateOut() {
520
+ anim.remove(this.dropdownEl);
521
+ anim({
522
+ targets: this.dropdownEl,
523
+ opacity: {
524
+ value: 0,
525
+ easing: 'easeOutQuint'
526
+ },
527
+ scaleX: 0.3,
528
+ scaleY: 0.3,
529
+ duration: this.options.outDuration,
530
+ easing: 'easeOutQuint',
531
+ complete: (anim) => {
532
+ this._resetDropdownStyles();
533
+
534
+ // onCloseEnd callback
535
+ if (typeof this.options.onCloseEnd === 'function') {
536
+ this.options.onCloseEnd.call(this, this.el);
537
+ }
538
+ }
539
+ });
540
+ }
541
+
542
+ /**
543
+ * Place dropdown
544
+ */
545
+ _placeDropdown() {
546
+ /**
547
+ * Get closest ancestor that satisfies the condition
548
+ * @param {Element} el Element to find ancestors on
549
+ * @param {Function} condition Function that given an ancestor element returns true or false
550
+ * @returns {Element} Return closest ancestor or null if none satisfies the condition
551
+ */
552
+ const getClosestAncestor = function(el, condition) {
553
+ let ancestor = el.parentNode;
554
+ while (ancestor !== null && !$(ancestor).is(document)) {
555
+ if (condition(ancestor)) {
556
+ return ancestor;
557
+ }
558
+ ancestor = ancestor.parentNode;
559
+ }
560
+ return null;
561
+ };
562
+
563
+ // Container here will be closest ancestor with overflow: hidden
564
+ let closestOverflowParent = getClosestAncestor(this.dropdownEl, (ancestor) => {
565
+ return $(ancestor).css('overflow') !== 'visible';
566
+ });
567
+ // Fallback
568
+ if (!closestOverflowParent) {
569
+ closestOverflowParent = !!this.dropdownEl.offsetParent
570
+ ? this.dropdownEl.offsetParent
571
+ : this.dropdownEl.parentNode;
572
+ }
573
+ if ($(closestOverflowParent).css('position') === 'static')
574
+ $(closestOverflowParent).css('position', 'relative');
575
+
576
+ this._moveDropdown(closestOverflowParent);
577
+
578
+ // Set width before calculating positionInfo
579
+ let idealWidth = this.options.constrainWidth
580
+ ? this.el.getBoundingClientRect().width
581
+ : this.dropdownEl.getBoundingClientRect().width;
582
+ this.dropdownEl.style.width = idealWidth + 'px';
583
+
584
+ let positionInfo = this._getDropdownPosition(closestOverflowParent);
585
+ this.dropdownEl.style.left = positionInfo.x + 'px';
586
+ this.dropdownEl.style.top = positionInfo.y + 'px';
587
+ this.dropdownEl.style.height = positionInfo.height + 'px';
588
+ this.dropdownEl.style.width = positionInfo.width + 'px';
589
+ this.dropdownEl.style.transformOrigin = `${
590
+ positionInfo.horizontalAlignment === 'left' ? '0' : '100%'
591
+ } ${positionInfo.verticalAlignment === 'top' ? '0' : '100%'}`;
592
+ }
593
+
594
+ /**
595
+ * Open Dropdown
596
+ */
597
+ open() {
598
+ if (this.isOpen) {
599
+ return;
600
+ }
601
+ this.isOpen = true;
602
+
603
+ // onOpenStart callback
604
+ if (typeof this.options.onOpenStart === 'function') {
605
+ this.options.onOpenStart.call(this, this.el);
606
+ }
607
+
608
+ // Reset styles
609
+ this._resetDropdownStyles();
610
+ this.dropdownEl.style.display = 'block';
611
+
612
+ this._placeDropdown();
613
+ this._animateIn();
614
+ this._setupTemporaryEventHandlers();
615
+ }
616
+
617
+ /**
618
+ * Close Dropdown
619
+ */
620
+ close() {
621
+ if (!this.isOpen) {
622
+ return;
623
+ }
624
+
625
+ this.isOpen = false;
626
+ this.focusedIndex = -1;
627
+
628
+ // onCloseStart callback
629
+ if (typeof this.options.onCloseStart === 'function') {
630
+ this.options.onCloseStart.call(this, this.el);
631
+ }
632
+
633
+ this._animateOut();
634
+ this._removeTemporaryEventHandlers();
635
+
636
+ if (this.options.autoFocus) {
637
+ this.el.focus();
638
+ }
639
+ }
640
+
641
+ /**
642
+ * Recalculate dimensions
643
+ */
644
+ recalculateDimensions() {
645
+ if (this.isOpen) {
646
+ this.$dropdownEl.css({
647
+ width: '',
648
+ height: '',
649
+ left: '',
650
+ top: '',
651
+ 'transform-origin': ''
652
+ });
653
+ this._placeDropdown();
654
+ }
655
+ }
656
+ }
657
+
658
+ /**
659
+ * @static
660
+ * @memberof Dropdown
661
+ */
662
+ Dropdown._dropdowns = [];
663
+
664
+ M.Dropdown = Dropdown;
665
+
666
+ if (M.jQueryLoaded) {
667
+ M.initializeJqueryWrapper(Dropdown, 'dropdown', 'M_Dropdown');
668
+ }
669
+ })(cash, M.anime);