kmaterialize 2.0.0 → 2.3.3-1.0.1

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 (156) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +55 -91
  3. package/components/appbar/_navbar.old.scss +223 -0
  4. package/components/appbar/appbar.css +235 -0
  5. package/components/appbar/appbar.mjs +54 -0
  6. package/components/appbar/appbar.test.mjs +9 -0
  7. package/components/atomic/atomic.mjs +19 -0
  8. package/components/atomic/component.mjs +67 -0
  9. package/components/badge/_badges.scss +75 -0
  10. package/components/badge/badge.mjs +11 -0
  11. package/components/breadcrumb/_breadcrumb.scss +27 -0
  12. package/components/button/_buttons.scss +352 -0
  13. package/components/button/button.mjs +11 -0
  14. package/components/button/buttons.stories.ts +150 -0
  15. package/components/button/buttons.ts +282 -0
  16. package/components/button/examples.iso.js +18 -0
  17. package/components/button/fabSpec.js +65 -0
  18. package/components/button/test-button.mjs +16 -0
  19. package/components/card/_cards.scss +229 -0
  20. package/components/card/card.mjs +10 -0
  21. package/components/card/cards.ts +185 -0
  22. package/components/card/cardsSpec.js +269 -0
  23. package/components/carousel/_carousel.scss +93 -0
  24. package/components/carousel/_slider.scss +119 -0
  25. package/components/carousel/carousel.ts +693 -0
  26. package/components/carousel/carouselSpec.js +61 -0
  27. package/components/carousel/slider.ts +431 -0
  28. package/components/carousel/sliderSpec.js +99 -0
  29. package/components/checkbox/_checkboxes.scss +281 -0
  30. package/components/checkbox/checkbox.mdx +24 -0
  31. package/components/checkbox/checkbox.stories.ts +120 -0
  32. package/components/chip/chip.mjs +37 -0
  33. package/components/chip/chip.test.mjs +10 -0
  34. package/components/chip/chips.css +126 -0
  35. package/components/chip/chips.mdx +33 -0
  36. package/components/chip/chips.ts +440 -0
  37. package/components/chip/chipsSpec.js +165 -0
  38. package/components/chip/readme.md +323 -0
  39. package/components/collapsible/_collapsible.scss +82 -0
  40. package/components/collapsible/collapsible.ts +230 -0
  41. package/components/collapsible/collapsibleSpec.js +240 -0
  42. package/components/datepicker/_datepicker.scss +268 -0
  43. package/components/datepicker/datepicker.ts +1450 -0
  44. package/components/datepicker/datepickerSpec.js +162 -0
  45. package/components/dialog/_materialbox.scss +42 -0
  46. package/components/dialog/_modal.scss +79 -0
  47. package/components/dialog/materialbox.ts +446 -0
  48. package/components/dialog/materialboxSpec.js +37 -0
  49. package/components/dialog/modal.ts +111 -0
  50. package/components/dialog/modalSpec.js +83 -0
  51. package/components/dialog/readme.md +5 -0
  52. package/components/divider/readme.md +15 -0
  53. package/components/dropdown/_dropdown.scss +83 -0
  54. package/components/dropdown/dropdown.ts +653 -0
  55. package/components/dropdown/dropdownSpec.js +108 -0
  56. package/components/list/_collection.scss +114 -0
  57. package/components/list/_list.scss +6 -0
  58. package/components/list/list.mjs +58 -0
  59. package/components/list/list.test.mjs +16 -0
  60. package/components/loading/loading.mjs +26 -0
  61. package/components/loading/loading.test.mjs +10 -0
  62. package/components/navigation-bar/_old +582 -0
  63. package/components/navigation-bar/navigation-bar.mdx +20 -0
  64. package/components/navigation-bar/navigation-bar.stories.ts +39 -0
  65. package/components/navigation-drawer/_sidenav.scss +247 -0
  66. package/components/navigation-drawer/sidenav.ts +546 -0
  67. package/components/navigation-drawer/sidenavSpec.js +124 -0
  68. package/components/pagination/_pagination.scss +47 -0
  69. package/components/progress/_preloader.scss +416 -0
  70. package/components/radio-button/_radio-buttons.scss +112 -0
  71. package/components/radio-button/radio-buttons.mdx +28 -0
  72. package/components/radio-button/radio-buttons.stories.ts +51 -0
  73. package/components/search/autocomplete.ts +620 -0
  74. package/components/search/autocompleteSpec.js +321 -0
  75. package/components/slider/_range.scss +157 -0
  76. package/components/slider/range.ts +216 -0
  77. package/components/snackbar/_toast.scss +68 -0
  78. package/components/snackbar/toastSpec.js +132 -0
  79. package/components/snackbar/toasts.ts +312 -0
  80. package/components/switch/_switches.scss +121 -0
  81. package/components/tabs/_tabs.scss +171 -0
  82. package/components/tabs/tabs.mjs +31 -0
  83. package/components/tabs/tabs.ts +358 -0
  84. package/components/tabs/tabsSpec.js +180 -0
  85. package/components/textfield/_file-input.scss +41 -0
  86. package/components/textfield/_input-fields.scss +368 -0
  87. package/components/textfield/_select.scss +104 -0
  88. package/components/textfield/formfields.css +311 -0
  89. package/components/textfield/forms.ts +182 -0
  90. package/components/textfield/formsSpec.js +142 -0
  91. package/components/textfield/select.ts +472 -0
  92. package/components/textfield/selectSpec.js +390 -0
  93. package/components/textfield/textfield.mjs +29 -0
  94. package/components/textfield/textfield.test.mjs +10 -0
  95. package/components/timepicker/_timepicker.scss +362 -0
  96. package/components/timepicker/timepicker.ts +905 -0
  97. package/components/tooltip/_tooltip.scss +40 -0
  98. package/components/tooltip/tooltip.ts +380 -0
  99. package/components/tooltip/tooltipSpec.js +145 -0
  100. package/dist/css/materialize.colors.min.css +6 -0
  101. package/dist/css/materialize.css +7578 -6326
  102. package/dist/css/materialize.min.css +6 -13
  103. package/dist/css/materialize.min.css.map +1 -0
  104. package/dist/js/materialize.cjs.js +8410 -0
  105. package/dist/js/materialize.d.ts +2625 -0
  106. package/dist/js/materialize.js +8415 -12374
  107. package/dist/js/materialize.min.js +6 -6
  108. package/dist/js/materialize.mjs +8382 -0
  109. package/package.json +82 -47
  110. package/sass/_colors.scss +417 -0
  111. package/sass/_global.scss +490 -0
  112. package/sass/_grid.scss +172 -0
  113. package/sass/_table_of_contents.scss +28 -0
  114. package/sass/_tapTarget.scss +102 -0
  115. package/sass/_typography.scss +58 -0
  116. package/sass/_variables.scss +56 -0
  117. package/sass/colors.module.css +74 -0
  118. package/sass/materialize.scss +51 -0
  119. package/sass/mixins.module.scss +168 -0
  120. package/sass/pulse.css +35 -0
  121. package/sass/theme.module.css +140 -0
  122. package/sass/tokens.module.css +272 -0
  123. package/sass/transitions.css +10 -0
  124. package/sass/typography.module.css +150 -0
  125. package/extras/noUiSlider/nouislider.css +0 -406
  126. package/extras/noUiSlider/nouislider.js +0 -2147
  127. package/extras/noUiSlider/nouislider.min.js +0 -1
  128. package/js/anime.min.js +0 -34
  129. package/js/autocomplete.js +0 -448
  130. package/js/buttons.js +0 -354
  131. package/js/cards.js +0 -40
  132. package/js/carousel.js +0 -717
  133. package/js/cash.js +0 -960
  134. package/js/characterCounter.js +0 -136
  135. package/js/chips.js +0 -481
  136. package/js/collapsible.js +0 -275
  137. package/js/component.js +0 -44
  138. package/js/datepicker.js +0 -975
  139. package/js/dropdown.js +0 -635
  140. package/js/forms.js +0 -275
  141. package/js/global.js +0 -444
  142. package/js/materialbox.js +0 -453
  143. package/js/modal.js +0 -382
  144. package/js/parallax.js +0 -138
  145. package/js/pushpin.js +0 -148
  146. package/js/range.js +0 -263
  147. package/js/scrollspy.js +0 -295
  148. package/js/select.js +0 -450
  149. package/js/sidenav.js +0 -580
  150. package/js/slider.js +0 -359
  151. package/js/tabs.js +0 -402
  152. package/js/tapTarget.js +0 -314
  153. package/js/timepicker.js +0 -647
  154. package/js/toasts.js +0 -310
  155. package/js/tooltip.js +0 -303
  156. package/js/waves.js +0 -335
@@ -0,0 +1,653 @@
1
+ import { Utils } from '../../src/utils';
2
+ import { Component, BaseOptions, InitElements, MElement, Openable } from '../../src/component';
3
+
4
+ export interface DropdownOptions extends BaseOptions {
5
+ /**
6
+ * Defines the edge the menu is aligned to.
7
+ * @default 'left'
8
+ */
9
+ alignment: 'left' | 'right';
10
+ /**
11
+ * If true, automatically focus dropdown el for keyboard.
12
+ * @default true
13
+ */
14
+ autoFocus: boolean;
15
+ /**
16
+ * If true, constrainWidth to the size of the dropdown activator.
17
+ * @default true
18
+ */
19
+ constrainWidth: boolean;
20
+ /**
21
+ * Provide an element that will be the bounding container of the dropdown.
22
+ * @default null
23
+ */
24
+ container: Element;
25
+ /**
26
+ * If false, the dropdown will show below the trigger.
27
+ * @default true
28
+ */
29
+ coverTrigger: boolean;
30
+ /**
31
+ * If true, close dropdown on item click.
32
+ * @default true
33
+ */
34
+ closeOnClick: boolean;
35
+ /**
36
+ * If true, the dropdown will open on hover.
37
+ * @default false
38
+ */
39
+ hover: boolean;
40
+ /**
41
+ * The duration of the transition enter in milliseconds.
42
+ * @default 150
43
+ */
44
+ inDuration: number;
45
+ /**
46
+ * The duration of the transition out in milliseconds.
47
+ * @default 250
48
+ */
49
+ outDuration: number;
50
+ /**
51
+ * Function called when dropdown starts entering.
52
+ * @default null
53
+ */
54
+ onOpenStart: (el: HTMLElement) => void;
55
+ /**
56
+ * Function called when dropdown finishes entering.
57
+ * @default null
58
+ */
59
+ onOpenEnd: (el: HTMLElement) => void;
60
+ /**
61
+ * Function called when dropdown starts exiting.
62
+ * @default null
63
+ */
64
+ onCloseStart: (el: HTMLElement) => void;
65
+ /**
66
+ * Function called when dropdown finishes exiting.
67
+ * @default null
68
+ */
69
+ onCloseEnd: (el: HTMLElement) => void;
70
+ /**
71
+ * Function called when item is clicked.
72
+ * @default null
73
+ */
74
+ onItemClick: (el: HTMLLIElement) => void;
75
+ }
76
+
77
+ const _defaults: DropdownOptions = {
78
+ alignment: 'left',
79
+ autoFocus: true,
80
+ constrainWidth: true,
81
+ container: null,
82
+ coverTrigger: true,
83
+ closeOnClick: true,
84
+ hover: false,
85
+ inDuration: 150,
86
+ outDuration: 250,
87
+ onOpenStart: null,
88
+ onOpenEnd: null,
89
+ onCloseStart: null,
90
+ onCloseEnd: null,
91
+ onItemClick: null
92
+ };
93
+
94
+ export class Dropdown extends Component<DropdownOptions> implements Openable {
95
+ static _dropdowns: Dropdown[] = [];
96
+ /** ID of the dropdown element. */
97
+ id: string;
98
+ /** The DOM element of the dropdown. */
99
+ dropdownEl: HTMLElement;
100
+ /** If the dropdown is open. */
101
+ isOpen: boolean;
102
+ /** If the dropdown content is scrollable. */
103
+ isScrollable: boolean;
104
+ isTouchMoving: boolean;
105
+ /** The index of the item focused. */
106
+ focusedIndex: number;
107
+ filterQuery: string[];
108
+ filterTimeout: NodeJS.Timeout | number;
109
+
110
+ constructor(el: HTMLElement, options: Partial<DropdownOptions>) {
111
+ super(el, options, Dropdown);
112
+ this.el['M_Dropdown'] = this;
113
+
114
+ Dropdown._dropdowns.push(this);
115
+ this.id = Utils.getIdFromTrigger(el);
116
+ this.dropdownEl = document.getElementById(this.id);
117
+
118
+ this.options = {
119
+ ...Dropdown.defaults,
120
+ ...options
121
+ };
122
+
123
+ this.isOpen = false;
124
+ this.isScrollable = false;
125
+ this.isTouchMoving = false;
126
+ this.focusedIndex = -1;
127
+ this.filterQuery = [];
128
+ this.el.ariaExpanded = 'false';
129
+
130
+ // Move dropdown-content after dropdown-trigger
131
+ this._moveDropdownToElement();
132
+ this._makeDropdownFocusable();
133
+ this._setupEventHandlers();
134
+ }
135
+
136
+ static get defaults(): DropdownOptions {
137
+ return _defaults;
138
+ }
139
+
140
+ /**
141
+ * Initializes instance of Dropdown.
142
+ * @param el HTML element.
143
+ * @param options Component options.
144
+ */
145
+ static init(el: HTMLElement, options?: Partial<DropdownOptions>): Dropdown;
146
+ /**
147
+ * Initializes instances of Dropdown.
148
+ * @param els HTML elements.
149
+ * @param options Component options.
150
+ */
151
+ static init(els: InitElements<MElement>, options?: Partial<DropdownOptions>): Dropdown[];
152
+ /**
153
+ * Initializes instances of Dropdown.
154
+ * @param els HTML elements.
155
+ * @param options Component options.
156
+ */
157
+ static init(
158
+ els: HTMLElement | InitElements<MElement>,
159
+ options: Partial<DropdownOptions> = {}
160
+ ): Dropdown | Dropdown[] {
161
+ return super.init(els, options, Dropdown);
162
+ }
163
+
164
+ static getInstance(el: HTMLElement): Dropdown {
165
+ return el['M_Dropdown'];
166
+ }
167
+
168
+ destroy() {
169
+ this._resetDropdownStyles();
170
+ this._removeEventHandlers();
171
+ Dropdown._dropdowns.splice(Dropdown._dropdowns.indexOf(this), 1);
172
+ this.el['M_Dropdown'] = undefined;
173
+ }
174
+
175
+ _setupEventHandlers() {
176
+ // Trigger keydown handler
177
+ this.el.addEventListener('keydown', this._handleTriggerKeydown);
178
+ // Item click handler
179
+ this.dropdownEl?.addEventListener('click', this._handleDropdownClick);
180
+ // Hover event handlers
181
+ if (this.options.hover) {
182
+ this.el.addEventListener('mouseenter', this._handleMouseEnter);
183
+ this.el.addEventListener('mouseleave', this._handleMouseLeave);
184
+ this.dropdownEl.addEventListener('mouseleave', this._handleMouseLeave);
185
+ // Click event handlers
186
+ } else {
187
+ this.el.addEventListener('click', this._handleClick);
188
+ }
189
+ }
190
+
191
+ _removeEventHandlers() {
192
+ this.el.removeEventListener('keydown', this._handleTriggerKeydown);
193
+ this.dropdownEl.removeEventListener('click', this._handleDropdownClick);
194
+ if (this.options.hover) {
195
+ this.el.removeEventListener('mouseenter', this._handleMouseEnter);
196
+ this.el.removeEventListener('mouseleave', this._handleMouseLeave);
197
+ this.dropdownEl.removeEventListener('mouseleave', this._handleMouseLeave);
198
+ } else {
199
+ this.el.removeEventListener('click', this._handleClick);
200
+ }
201
+ }
202
+
203
+ _setupTemporaryEventHandlers() {
204
+ document.body.addEventListener('click', this._handleDocumentClick);
205
+ document.body.addEventListener('touchmove', this._handleDocumentTouchmove);
206
+ this.dropdownEl.addEventListener('keydown', this._handleDropdownKeydown);
207
+ window.addEventListener('resize', this._handleWindowResize);
208
+ }
209
+
210
+ _removeTemporaryEventHandlers() {
211
+ document.body.removeEventListener('click', this._handleDocumentClick);
212
+ document.body.removeEventListener('touchmove', this._handleDocumentTouchmove);
213
+ this.dropdownEl.removeEventListener('keydown', this._handleDropdownKeydown);
214
+ window.removeEventListener('resize', this._handleWindowResize);
215
+ }
216
+
217
+ _handleClick = (e: MouseEvent) => {
218
+ e.preventDefault();
219
+ //this._moveDropdown((<HTMLElement>e.target).closest('li'));
220
+ if (this.isOpen) {
221
+ this.close();
222
+ } else {
223
+ this.open();
224
+ }
225
+ };
226
+
227
+ _handleMouseEnter = () => {
228
+ //this._moveDropdown((<HTMLElement>e.target).closest('li'));
229
+ this.open();
230
+ };
231
+
232
+ _handleMouseLeave = (e: MouseEvent) => {
233
+ const toEl = e.relatedTarget as HTMLElement;
234
+ const leaveToDropdownContent = !!toEl.closest('.dropdown-content');
235
+ let leaveToActiveDropdownTrigger = false;
236
+ const closestTrigger = toEl.closest('.dropdown-trigger');
237
+ if (closestTrigger && !!closestTrigger['M_Dropdown'] && closestTrigger['M_Dropdown'].isOpen) {
238
+ leaveToActiveDropdownTrigger = true;
239
+ }
240
+ // Close hover dropdown if mouse did not leave to either active dropdown-trigger or dropdown-content
241
+ if (!leaveToActiveDropdownTrigger && !leaveToDropdownContent) {
242
+ this.close();
243
+ }
244
+ };
245
+
246
+ _handleDocumentClick = (e: MouseEvent) => {
247
+ const target = <HTMLElement>e.target;
248
+ if (this.options.closeOnClick && target.closest('.dropdown-content') && !this.isTouchMoving) {
249
+ // isTouchMoving to check if scrolling on mobile.
250
+ this.close();
251
+ } else if (!target.closest('.dropdown-content')) {
252
+ // Do this one frame later so that if the element clicked also triggers _handleClick
253
+ // For example, if a label for a select was clicked, that we don't close/open the dropdown
254
+ setTimeout(() => {
255
+ if (this.isOpen) {
256
+ this.close();
257
+ }
258
+ }, 0);
259
+ }
260
+ this.isTouchMoving = false;
261
+ };
262
+
263
+ _handleTriggerKeydown = (e: KeyboardEvent) => {
264
+ // ARROW DOWN OR ENTER WHEN SELECT IS CLOSED - open Dropdown
265
+ const arrowDownOrEnter =
266
+ Utils.keys.ARROW_DOWN.includes(e.key) || Utils.keys.ENTER.includes(e.key);
267
+ if (arrowDownOrEnter && !this.isOpen) {
268
+ e.preventDefault();
269
+ this.open();
270
+ }
271
+ };
272
+
273
+ _handleDocumentTouchmove = (e: TouchEvent) => {
274
+ const target = <HTMLElement>e.target;
275
+ if (target.closest('.dropdown-content')) {
276
+ this.isTouchMoving = true;
277
+ }
278
+ };
279
+
280
+ _handleDropdownClick = (e: MouseEvent) => {
281
+ // onItemClick callback
282
+ if (typeof this.options.onItemClick === 'function') {
283
+ const itemEl = (<HTMLElement>e.target).closest('li');
284
+ this.options.onItemClick.call(this, itemEl);
285
+ }
286
+ };
287
+
288
+ _handleDropdownKeydown = (e: KeyboardEvent) => {
289
+ const arrowUpOrDown =
290
+ Utils.keys.ARROW_DOWN.includes(e.key) || Utils.keys.ARROW_UP.includes(e.key);
291
+ if (Utils.keys.TAB.includes(e.key)) {
292
+ e.preventDefault();
293
+ this.close();
294
+ }
295
+ // Navigate down dropdown list
296
+ else if (arrowUpOrDown && this.isOpen) {
297
+ e.preventDefault();
298
+ const direction = Utils.keys.ARROW_DOWN.includes(e.key) ? 1 : -1;
299
+ let newFocusedIndex = this.focusedIndex;
300
+ let hasFoundNewIndex = false;
301
+ do {
302
+ newFocusedIndex = newFocusedIndex + direction;
303
+ if (
304
+ !!this.dropdownEl.children[newFocusedIndex] &&
305
+ (<HTMLLIElement>this.dropdownEl.children[newFocusedIndex]).tabIndex !== -1
306
+ ) {
307
+ hasFoundNewIndex = true;
308
+ break;
309
+ }
310
+ } while (newFocusedIndex < this.dropdownEl.children.length && newFocusedIndex >= 0);
311
+
312
+ if (hasFoundNewIndex) {
313
+ // Remove active class from old element
314
+ if (this.focusedIndex >= 0)
315
+ this.dropdownEl.children[this.focusedIndex].classList.remove('active');
316
+ this.focusedIndex = newFocusedIndex;
317
+ this._focusFocusedItem();
318
+ }
319
+ }
320
+ // ENTER selects choice on focused item
321
+ else if (Utils.keys.ENTER.includes(e.key) && this.isOpen) {
322
+ // Search for <a> and <button>
323
+ const focusedElement = this.dropdownEl.children[this.focusedIndex];
324
+ const activatableElement = <HTMLElement>focusedElement?.querySelector('a, button');
325
+ // Click a or button tag if exists, otherwise click li tag
326
+ if (!!activatableElement) {
327
+ activatableElement.click();
328
+ } else if (!!focusedElement) {
329
+ if (focusedElement instanceof HTMLElement) {
330
+ focusedElement.click();
331
+ }
332
+ }
333
+ }
334
+ // Close dropdown on ESC
335
+ else if (Utils.keys.ESC.includes(e.key) && this.isOpen) {
336
+ e.preventDefault();
337
+ this.close();
338
+ }
339
+
340
+ // CASE WHEN USER TYPE LTTERS
341
+ const keyText = e.key.toLowerCase();
342
+ const isLetter = /[a-zA-Z0-9-_]/.test(keyText);
343
+ const specialKeys = [
344
+ ...Utils.keys.ARROW_DOWN,
345
+ ...Utils.keys.ARROW_UP,
346
+ ...Utils.keys.ENTER,
347
+ ...Utils.keys.ESC,
348
+ ...Utils.keys.TAB
349
+ ];
350
+ if (isLetter && !specialKeys.includes(e.key)) {
351
+ this.filterQuery.push(keyText);
352
+ const string = this.filterQuery.join('');
353
+ const newOptionEl = Array.from(this.dropdownEl.querySelectorAll('li')).find(
354
+ (el) => el.innerText.toLowerCase().indexOf(string) === 0
355
+ );
356
+ if (newOptionEl) {
357
+ this.focusedIndex = [...newOptionEl.parentNode.children].indexOf(newOptionEl);
358
+ this._focusFocusedItem();
359
+ }
360
+ }
361
+ this.filterTimeout = setTimeout(this._resetFilterQuery, 1000);
362
+ };
363
+
364
+ _handleWindowResize = () => {
365
+ // Only re-place the dropdown if it's still visible
366
+ // Accounts for elements hiding via media queries
367
+ if (this.el.offsetParent) {
368
+ this.recalculateDimensions();
369
+ }
370
+ };
371
+
372
+ _resetFilterQuery = () => {
373
+ this.filterQuery = [];
374
+ };
375
+
376
+ _resetDropdownStyles() {
377
+ this.dropdownEl.style.display = '';
378
+ this._resetDropdownPositioningStyles();
379
+ this.dropdownEl.style.transform = '';
380
+ this.dropdownEl.style.opacity = '';
381
+ }
382
+
383
+ _resetDropdownPositioningStyles() {
384
+ this.dropdownEl.style.width = '';
385
+ this.dropdownEl.style.height = '';
386
+ this.dropdownEl.style.left = '';
387
+ this.dropdownEl.style.top = '';
388
+ this.dropdownEl.style.transformOrigin = '';
389
+ }
390
+
391
+ _moveDropdownToElement(containerEl: HTMLElement = null) {
392
+ if (this.options.container) {
393
+ this.options.container.append(this.dropdownEl);
394
+ return;
395
+ }
396
+ if (containerEl) {
397
+ if (!containerEl.contains(this.dropdownEl)) containerEl.append(this.dropdownEl);
398
+ return;
399
+ }
400
+ this.el.after(this.dropdownEl);
401
+ }
402
+
403
+ _makeDropdownFocusable() {
404
+ if (!this.dropdownEl) return;
405
+ this.dropdownEl.popover = '';
406
+ // Needed for arrow key navigation
407
+ this.dropdownEl.tabIndex = 0;
408
+ // Only set tabindex if it hasn't been set by user
409
+ Array.from(this.dropdownEl.children).forEach((el) => {
410
+ if (!el.getAttribute('tabindex')) el.setAttribute('tabindex', '0');
411
+ });
412
+ }
413
+
414
+ _focusFocusedItem() {
415
+ if (
416
+ this.focusedIndex >= 0 &&
417
+ this.focusedIndex < this.dropdownEl.children.length &&
418
+ this.options.autoFocus
419
+ ) {
420
+ (this.dropdownEl.children[this.focusedIndex] as HTMLElement).focus({
421
+ preventScroll: true
422
+ });
423
+ this.dropdownEl.children[this.focusedIndex].scrollIntoView({
424
+ behavior: 'smooth',
425
+ block: 'nearest',
426
+ inline: 'nearest'
427
+ });
428
+ }
429
+ }
430
+
431
+ _getDropdownPosition(closestOverflowParent: HTMLElement) {
432
+ // const offsetParentBRect = this.el.offsetParent.getBoundingClientRect();
433
+ const triggerBRect = this.el.getBoundingClientRect();
434
+ const dropdownBRect = this.dropdownEl.getBoundingClientRect();
435
+
436
+ let idealHeight = dropdownBRect.height;
437
+ let idealWidth = dropdownBRect.width;
438
+ let idealXPos = triggerBRect.left - dropdownBRect.left;
439
+ let idealYPos = triggerBRect.top - dropdownBRect.top;
440
+
441
+ const dropdownBounds = {
442
+ left: idealXPos,
443
+ top: idealYPos,
444
+ height: idealHeight,
445
+ width: idealWidth
446
+ };
447
+
448
+ const alignments = Utils.checkPossibleAlignments(
449
+ this.el,
450
+ closestOverflowParent,
451
+ dropdownBounds,
452
+ this.options.coverTrigger ? 0 : triggerBRect.height
453
+ );
454
+
455
+ let verticalAlignment = 'top';
456
+ let horizontalAlignment = this.options.alignment;
457
+ idealYPos += this.options.coverTrigger ? 0 : triggerBRect.height;
458
+
459
+ // Reset isScrollable
460
+ this.isScrollable = false;
461
+
462
+ if (!alignments.top) {
463
+ if (alignments.bottom) {
464
+ verticalAlignment = 'bottom';
465
+
466
+ if (!this.options.coverTrigger) {
467
+ idealYPos -= triggerBRect.height;
468
+ }
469
+ } else {
470
+ this.isScrollable = true;
471
+
472
+ // Determine which side has most space and cutoff at correct height
473
+ idealHeight -= 20; // Add padding when cutoff
474
+ if (alignments.spaceOnTop > alignments.spaceOnBottom) {
475
+ verticalAlignment = 'bottom';
476
+ idealHeight += alignments.spaceOnTop;
477
+ idealYPos -= this.options.coverTrigger
478
+ ? alignments.spaceOnTop - 20
479
+ : alignments.spaceOnTop - 20 + triggerBRect.height;
480
+ } else {
481
+ idealHeight += alignments.spaceOnBottom;
482
+ }
483
+ }
484
+ }
485
+
486
+ // If preferred horizontal alignment is possible
487
+ if (!alignments[horizontalAlignment]) {
488
+ const oppositeAlignment = horizontalAlignment === 'left' ? 'right' : 'left';
489
+ if (alignments[oppositeAlignment]) {
490
+ horizontalAlignment = oppositeAlignment;
491
+ } else {
492
+ // Determine which side has most space and cutoff at correct height
493
+ if (alignments.spaceOnLeft > alignments.spaceOnRight) {
494
+ horizontalAlignment = 'right';
495
+ idealWidth += alignments.spaceOnLeft;
496
+ idealXPos -= alignments.spaceOnLeft;
497
+ } else {
498
+ horizontalAlignment = 'left';
499
+ idealWidth += alignments.spaceOnRight;
500
+ }
501
+ }
502
+ }
503
+
504
+ if (verticalAlignment === 'bottom') {
505
+ idealYPos =
506
+ idealYPos - dropdownBRect.height + (this.options.coverTrigger ? triggerBRect.height : 0);
507
+ }
508
+ if (horizontalAlignment === 'right') {
509
+ idealXPos = idealXPos - dropdownBRect.width + triggerBRect.width;
510
+ }
511
+ return {
512
+ x: idealXPos,
513
+ y: idealYPos,
514
+ verticalAlignment: verticalAlignment,
515
+ horizontalAlignment: horizontalAlignment,
516
+ height: idealHeight,
517
+ width: idealWidth
518
+ };
519
+ }
520
+
521
+ _animateIn() {
522
+ const duration = this.options.inDuration;
523
+ this.dropdownEl.style.transition = 'none';
524
+ // from
525
+ this.dropdownEl.style.opacity = '0';
526
+ this.dropdownEl.style.transform = 'scale(0.3, 0.3)';
527
+ setTimeout(() => {
528
+ // easeOutQuad (opacity) & easeOutQuint
529
+ this.dropdownEl.style.transition = `opacity ${duration}ms ease, transform ${duration}ms ease`;
530
+ // to
531
+ this.dropdownEl.style.opacity = '1';
532
+ this.dropdownEl.style.transform = 'scale(1, 1)';
533
+ }, 1);
534
+ setTimeout(() => {
535
+ if (this.options.autoFocus) this.dropdownEl.focus();
536
+ if (typeof this.options.onOpenEnd === 'function') this.options.onOpenEnd.call(this, this.el);
537
+ }, duration);
538
+ }
539
+
540
+ _animateOut() {
541
+ const duration = this.options.outDuration;
542
+ // easeOutQuad (opacity) & easeOutQuint
543
+ this.dropdownEl.style.transition = `opacity ${duration}ms ease, transform ${duration}ms ease`;
544
+ // to
545
+ this.dropdownEl.style.opacity = '0';
546
+ this.dropdownEl.style.transform = 'scale(0.3, 0.3)';
547
+ setTimeout(() => {
548
+ this._resetDropdownStyles();
549
+ if (typeof this.options.onCloseEnd === 'function')
550
+ this.options.onCloseEnd.call(this, this.el);
551
+ }, duration);
552
+ }
553
+
554
+ private _getClosestAncestor(el: HTMLElement, condition: (Function) => boolean): HTMLElement {
555
+ let ancestor = el.parentNode;
556
+ while (ancestor !== null && ancestor !== document) {
557
+ if (condition(ancestor)) {
558
+ return <HTMLElement>ancestor;
559
+ }
560
+ ancestor = ancestor.parentElement;
561
+ }
562
+ return null;
563
+ }
564
+
565
+ _placeDropdown() {
566
+ // Container here will be closest ancestor with overflow: hidden
567
+ let closestOverflowParent: HTMLElement = this._getClosestAncestor(
568
+ this.dropdownEl,
569
+ (ancestor: HTMLElement) => {
570
+ return (
571
+ !['HTML', 'BODY'].includes(ancestor.tagName) &&
572
+ getComputedStyle(ancestor).overflow !== 'visible'
573
+ );
574
+ }
575
+ );
576
+ // Fallback
577
+ if (!closestOverflowParent) {
578
+ closestOverflowParent = <HTMLElement>(
579
+ (!!this.dropdownEl.offsetParent ? this.dropdownEl.offsetParent : this.dropdownEl.parentNode)
580
+ );
581
+ }
582
+
583
+ if (getComputedStyle(closestOverflowParent).position === 'static')
584
+ closestOverflowParent.style.position = 'relative';
585
+
586
+ //this._moveDropdown(closestOverflowParent);
587
+
588
+ // Set width before calculating positionInfo
589
+ const idealWidth = this.options.constrainWidth
590
+ ? this.el.getBoundingClientRect().width
591
+ : this.dropdownEl.getBoundingClientRect().width;
592
+ this.dropdownEl.style.width = idealWidth + 'px';
593
+
594
+ const positionInfo = this._getDropdownPosition(closestOverflowParent);
595
+ this.dropdownEl.style.left = positionInfo.x + 'px';
596
+ this.dropdownEl.style.top = positionInfo.y + 'px';
597
+ this.dropdownEl.style.height = positionInfo.height + 'px';
598
+ this.dropdownEl.style.width = positionInfo.width + 'px';
599
+ this.dropdownEl.style.transformOrigin = `${
600
+ positionInfo.horizontalAlignment === 'left' ? '0' : '100%'
601
+ } ${positionInfo.verticalAlignment === 'top' ? '0' : '100%'}`;
602
+ }
603
+
604
+ /**
605
+ * Open dropdown.
606
+ */
607
+ open = () => {
608
+ if (this.isOpen) return;
609
+ this.isOpen = true;
610
+ // onOpenStart callback
611
+ if (typeof this.options.onOpenStart === 'function') {
612
+ this.options.onOpenStart.call(this, this.el);
613
+ }
614
+ // Reset styles
615
+ this._resetDropdownStyles();
616
+ this.dropdownEl.style.display = 'block';
617
+ this._placeDropdown();
618
+ this._animateIn();
619
+ // Do this one frame later so that we don't bind an event handler that's immediately
620
+ // called when the event bubbles up to the document and closes the dropdown
621
+ setTimeout(() => this._setupTemporaryEventHandlers(), 0);
622
+ this.el.ariaExpanded = 'true';
623
+ };
624
+
625
+ /**
626
+ * Close dropdown.
627
+ */
628
+ close = () => {
629
+ if (!this.isOpen) return;
630
+ this.isOpen = false;
631
+ this.focusedIndex = -1;
632
+ // onCloseStart callback
633
+ if (typeof this.options.onCloseStart === 'function') {
634
+ this.options.onCloseStart.call(this, this.el);
635
+ }
636
+ this._animateOut();
637
+ this._removeTemporaryEventHandlers();
638
+ if (this.options.autoFocus) {
639
+ this.el.focus();
640
+ }
641
+ this.el.ariaExpanded = 'false';
642
+ };
643
+
644
+ /**
645
+ * While dropdown is open, you can recalculate its dimensions if its contents have changed.
646
+ */
647
+ recalculateDimensions = () => {
648
+ if (this.isOpen) {
649
+ this._resetDropdownPositioningStyles();
650
+ this._placeDropdown();
651
+ }
652
+ };
653
+ }