kmaterialize 2.0.0 → 2.3.3-1.0.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 (150) 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/materialize.scss +51 -0
  118. package/sass/mixins.module.scss +168 -0
  119. package/extras/noUiSlider/nouislider.css +0 -406
  120. package/extras/noUiSlider/nouislider.js +0 -2147
  121. package/extras/noUiSlider/nouislider.min.js +0 -1
  122. package/js/anime.min.js +0 -34
  123. package/js/autocomplete.js +0 -448
  124. package/js/buttons.js +0 -354
  125. package/js/cards.js +0 -40
  126. package/js/carousel.js +0 -717
  127. package/js/cash.js +0 -960
  128. package/js/characterCounter.js +0 -136
  129. package/js/chips.js +0 -481
  130. package/js/collapsible.js +0 -275
  131. package/js/component.js +0 -44
  132. package/js/datepicker.js +0 -975
  133. package/js/dropdown.js +0 -635
  134. package/js/forms.js +0 -275
  135. package/js/global.js +0 -444
  136. package/js/materialbox.js +0 -453
  137. package/js/modal.js +0 -382
  138. package/js/parallax.js +0 -138
  139. package/js/pushpin.js +0 -148
  140. package/js/range.js +0 -263
  141. package/js/scrollspy.js +0 -295
  142. package/js/select.js +0 -450
  143. package/js/sidenav.js +0 -580
  144. package/js/slider.js +0 -359
  145. package/js/tabs.js +0 -402
  146. package/js/tapTarget.js +0 -314
  147. package/js/timepicker.js +0 -647
  148. package/js/toasts.js +0 -310
  149. package/js/tooltip.js +0 -303
  150. 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
+ }