@materializecss/materialize 1.2.0 → 1.2.2

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 (82) hide show
  1. package/Gruntfile.js +722 -712
  2. package/LICENSE +21 -21
  3. package/README.md +91 -91
  4. package/dist/css/materialize.css +78 -137
  5. package/dist/css/materialize.min.css +12 -12
  6. package/dist/js/materialize.js +1502 -1378
  7. package/dist/js/materialize.min.js +6 -6
  8. package/extras/noUiSlider/nouislider.css +403 -403
  9. package/extras/noUiSlider/nouislider.js +2147 -2147
  10. package/js/anime.min.js +34 -34
  11. package/js/autocomplete.js +479 -479
  12. package/js/buttons.js +354 -354
  13. package/js/cards.js +40 -40
  14. package/js/carousel.js +732 -732
  15. package/js/cash.js +960 -960
  16. package/js/characterCounter.js +136 -136
  17. package/js/chips.js +486 -486
  18. package/js/collapsible.js +275 -275
  19. package/js/component.js +44 -44
  20. package/js/datepicker.js +983 -983
  21. package/js/dropdown.js +669 -669
  22. package/js/forms.js +285 -285
  23. package/js/global.js +428 -428
  24. package/js/materialbox.js +453 -453
  25. package/js/modal.js +382 -382
  26. package/js/parallax.js +138 -138
  27. package/js/pushpin.js +148 -148
  28. package/js/range.js +263 -263
  29. package/js/scrollspy.js +295 -295
  30. package/js/select.js +391 -391
  31. package/js/sidenav.js +583 -583
  32. package/js/slider.js +497 -359
  33. package/js/tabs.js +402 -402
  34. package/js/tapTarget.js +315 -315
  35. package/js/timepicker.js +712 -712
  36. package/js/toasts.js +325 -325
  37. package/js/tooltip.js +320 -320
  38. package/js/waves.js +614 -614
  39. package/package.json +87 -84
  40. package/sass/components/_badges.scss +55 -55
  41. package/sass/components/_buttons.scss +322 -322
  42. package/sass/components/_cards.scss +195 -195
  43. package/sass/components/_carousel.scss +90 -90
  44. package/sass/components/_chips.scss +96 -96
  45. package/sass/components/_collapsible.scss +91 -91
  46. package/sass/components/_collection.scss +106 -106
  47. package/sass/components/_color-classes.scss +32 -32
  48. package/sass/components/_color-variables.scss +370 -370
  49. package/sass/components/_datepicker.scss +191 -191
  50. package/sass/components/_dropdown.scss +84 -84
  51. package/sass/components/_global.scss +646 -646
  52. package/sass/components/_grid.scss +158 -158
  53. package/sass/components/_icons-material-design.scss +5 -5
  54. package/sass/components/_materialbox.scss +42 -42
  55. package/sass/components/_modal.scss +97 -97
  56. package/sass/components/_navbar.scss +208 -208
  57. package/sass/components/_normalize.scss +447 -447
  58. package/sass/components/_preloader.scss +334 -334
  59. package/sass/components/_pulse.scss +34 -34
  60. package/sass/components/_sidenav.scss +214 -214
  61. package/sass/components/_slider.scss +100 -91
  62. package/sass/components/_table_of_contents.scss +33 -33
  63. package/sass/components/_tabs.scss +99 -99
  64. package/sass/components/_tapTarget.scss +103 -103
  65. package/sass/components/_timepicker.scss +199 -199
  66. package/sass/components/_toast.scss +58 -58
  67. package/sass/components/_tooltip.scss +32 -32
  68. package/sass/components/_transitions.scss +12 -12
  69. package/sass/components/_typography.scss +62 -62
  70. package/sass/components/_variables.scss +352 -352
  71. package/sass/components/_waves.scss +187 -187
  72. package/sass/components/forms/_checkboxes.scss +200 -200
  73. package/sass/components/forms/_file-input.scss +44 -44
  74. package/sass/components/forms/_forms.scss +22 -22
  75. package/sass/components/forms/_input-fields.scss +388 -388
  76. package/sass/components/forms/_radio-buttons.scss +115 -115
  77. package/sass/components/forms/_range.scss +161 -161
  78. package/sass/components/forms/_select.scss +199 -199
  79. package/sass/components/forms/_switches.scss +91 -91
  80. package/sass/materialize.scss +42 -42
  81. package/sass/_style.scss +0 -929
  82. package/sass/ghpages-materialize.scss +0 -7
package/js/dropdown.js CHANGED
@@ -1,669 +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('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);
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).is('html,body') && $(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);