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