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.
- package/LICENSE +21 -21
- package/README.md +55 -91
- package/components/appbar/_navbar.old.scss +223 -0
- package/components/appbar/appbar.css +235 -0
- package/components/appbar/appbar.mjs +54 -0
- package/components/appbar/appbar.test.mjs +9 -0
- package/components/atomic/atomic.mjs +19 -0
- package/components/atomic/component.mjs +67 -0
- package/components/badge/_badges.scss +75 -0
- package/components/badge/badge.mjs +11 -0
- package/components/breadcrumb/_breadcrumb.scss +27 -0
- package/components/button/_buttons.scss +352 -0
- package/components/button/button.mjs +11 -0
- package/components/button/buttons.stories.ts +150 -0
- package/components/button/buttons.ts +282 -0
- package/components/button/examples.iso.js +18 -0
- package/components/button/fabSpec.js +65 -0
- package/components/button/test-button.mjs +16 -0
- package/components/card/_cards.scss +229 -0
- package/components/card/card.mjs +10 -0
- package/components/card/cards.ts +185 -0
- package/components/card/cardsSpec.js +269 -0
- package/components/carousel/_carousel.scss +93 -0
- package/components/carousel/_slider.scss +119 -0
- package/components/carousel/carousel.ts +693 -0
- package/components/carousel/carouselSpec.js +61 -0
- package/components/carousel/slider.ts +431 -0
- package/components/carousel/sliderSpec.js +99 -0
- package/components/checkbox/_checkboxes.scss +281 -0
- package/components/checkbox/checkbox.mdx +24 -0
- package/components/checkbox/checkbox.stories.ts +120 -0
- package/components/chip/chip.mjs +37 -0
- package/components/chip/chip.test.mjs +10 -0
- package/components/chip/chips.css +126 -0
- package/components/chip/chips.mdx +33 -0
- package/components/chip/chips.ts +440 -0
- package/components/chip/chipsSpec.js +165 -0
- package/components/chip/readme.md +323 -0
- package/components/collapsible/_collapsible.scss +82 -0
- package/components/collapsible/collapsible.ts +230 -0
- package/components/collapsible/collapsibleSpec.js +240 -0
- package/components/datepicker/_datepicker.scss +268 -0
- package/components/datepicker/datepicker.ts +1450 -0
- package/components/datepicker/datepickerSpec.js +162 -0
- package/components/dialog/_materialbox.scss +42 -0
- package/components/dialog/_modal.scss +79 -0
- package/components/dialog/materialbox.ts +446 -0
- package/components/dialog/materialboxSpec.js +37 -0
- package/components/dialog/modal.ts +111 -0
- package/components/dialog/modalSpec.js +83 -0
- package/components/dialog/readme.md +5 -0
- package/components/divider/readme.md +15 -0
- package/components/dropdown/_dropdown.scss +83 -0
- package/components/dropdown/dropdown.ts +653 -0
- package/components/dropdown/dropdownSpec.js +108 -0
- package/components/list/_collection.scss +114 -0
- package/components/list/_list.scss +6 -0
- package/components/list/list.mjs +58 -0
- package/components/list/list.test.mjs +16 -0
- package/components/loading/loading.mjs +26 -0
- package/components/loading/loading.test.mjs +10 -0
- package/components/navigation-bar/_old +582 -0
- package/components/navigation-bar/navigation-bar.mdx +20 -0
- package/components/navigation-bar/navigation-bar.stories.ts +39 -0
- package/components/navigation-drawer/_sidenav.scss +247 -0
- package/components/navigation-drawer/sidenav.ts +546 -0
- package/components/navigation-drawer/sidenavSpec.js +124 -0
- package/components/pagination/_pagination.scss +47 -0
- package/components/progress/_preloader.scss +416 -0
- package/components/radio-button/_radio-buttons.scss +112 -0
- package/components/radio-button/radio-buttons.mdx +28 -0
- package/components/radio-button/radio-buttons.stories.ts +51 -0
- package/components/search/autocomplete.ts +620 -0
- package/components/search/autocompleteSpec.js +321 -0
- package/components/slider/_range.scss +157 -0
- package/components/slider/range.ts +216 -0
- package/components/snackbar/_toast.scss +68 -0
- package/components/snackbar/toastSpec.js +132 -0
- package/components/snackbar/toasts.ts +312 -0
- package/components/switch/_switches.scss +121 -0
- package/components/tabs/_tabs.scss +171 -0
- package/components/tabs/tabs.mjs +31 -0
- package/components/tabs/tabs.ts +358 -0
- package/components/tabs/tabsSpec.js +180 -0
- package/components/textfield/_file-input.scss +41 -0
- package/components/textfield/_input-fields.scss +368 -0
- package/components/textfield/_select.scss +104 -0
- package/components/textfield/formfields.css +311 -0
- package/components/textfield/forms.ts +182 -0
- package/components/textfield/formsSpec.js +142 -0
- package/components/textfield/select.ts +472 -0
- package/components/textfield/selectSpec.js +390 -0
- package/components/textfield/textfield.mjs +29 -0
- package/components/textfield/textfield.test.mjs +10 -0
- package/components/timepicker/_timepicker.scss +362 -0
- package/components/timepicker/timepicker.ts +905 -0
- package/components/tooltip/_tooltip.scss +40 -0
- package/components/tooltip/tooltip.ts +380 -0
- package/components/tooltip/tooltipSpec.js +145 -0
- package/dist/css/materialize.colors.min.css +6 -0
- package/dist/css/materialize.css +7578 -6326
- package/dist/css/materialize.min.css +6 -13
- package/dist/css/materialize.min.css.map +1 -0
- package/dist/js/materialize.cjs.js +8410 -0
- package/dist/js/materialize.d.ts +2625 -0
- package/dist/js/materialize.js +8415 -12374
- package/dist/js/materialize.min.js +6 -6
- package/dist/js/materialize.mjs +8382 -0
- package/package.json +82 -47
- package/sass/_colors.scss +417 -0
- package/sass/_global.scss +490 -0
- package/sass/_grid.scss +172 -0
- package/sass/_table_of_contents.scss +28 -0
- package/sass/_tapTarget.scss +102 -0
- package/sass/_typography.scss +58 -0
- package/sass/_variables.scss +56 -0
- package/sass/materialize.scss +51 -0
- package/sass/mixins.module.scss +168 -0
- package/extras/noUiSlider/nouislider.css +0 -406
- package/extras/noUiSlider/nouislider.js +0 -2147
- package/extras/noUiSlider/nouislider.min.js +0 -1
- package/js/anime.min.js +0 -34
- package/js/autocomplete.js +0 -448
- package/js/buttons.js +0 -354
- package/js/cards.js +0 -40
- package/js/carousel.js +0 -717
- package/js/cash.js +0 -960
- package/js/characterCounter.js +0 -136
- package/js/chips.js +0 -481
- package/js/collapsible.js +0 -275
- package/js/component.js +0 -44
- package/js/datepicker.js +0 -975
- package/js/dropdown.js +0 -635
- package/js/forms.js +0 -275
- package/js/global.js +0 -444
- package/js/materialbox.js +0 -453
- package/js/modal.js +0 -382
- package/js/parallax.js +0 -138
- package/js/pushpin.js +0 -148
- package/js/range.js +0 -263
- package/js/scrollspy.js +0 -295
- package/js/select.js +0 -450
- package/js/sidenav.js +0 -580
- package/js/slider.js +0 -359
- package/js/tabs.js +0 -402
- package/js/tapTarget.js +0 -314
- package/js/timepicker.js +0 -647
- package/js/toasts.js +0 -310
- package/js/tooltip.js +0 -303
- package/js/waves.js +0 -335
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
import { Utils } from '../../src/utils';
|
|
2
|
+
import { Dropdown, DropdownOptions } from '../dropdown/dropdown';
|
|
3
|
+
import { Component, BaseOptions, InitElements, MElement } from '../../src/component';
|
|
4
|
+
|
|
5
|
+
export interface FormSelectOptions extends BaseOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Classes to be added to the select wrapper element.
|
|
8
|
+
* @default ""
|
|
9
|
+
*/
|
|
10
|
+
classes: string;
|
|
11
|
+
/**
|
|
12
|
+
* Pass options object to select dropdown initialization.
|
|
13
|
+
* @default {}
|
|
14
|
+
*/
|
|
15
|
+
dropdownOptions: Partial<DropdownOptions>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const _defaults: FormSelectOptions = {
|
|
19
|
+
classes: '',
|
|
20
|
+
dropdownOptions: {}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type ValueStruct = {
|
|
24
|
+
el: HTMLOptionElement;
|
|
25
|
+
optionEl: HTMLElement;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export class FormSelect extends Component<FormSelectOptions> {
|
|
29
|
+
declare el: HTMLSelectElement;
|
|
30
|
+
/** If this is a multiple select. */
|
|
31
|
+
isMultiple: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Label associated with the current select element.
|
|
34
|
+
* Is "null", if not detected.
|
|
35
|
+
*/
|
|
36
|
+
labelEl: HTMLLabelElement;
|
|
37
|
+
/** Dropdown UL element. */
|
|
38
|
+
dropdownOptions: HTMLUListElement;
|
|
39
|
+
/** Text input that shows current selected option. */
|
|
40
|
+
input: HTMLInputElement;
|
|
41
|
+
/** Instance of the dropdown plugin for this select. */
|
|
42
|
+
dropdown: Dropdown;
|
|
43
|
+
/** The select wrapper element. */
|
|
44
|
+
wrapper: HTMLDivElement;
|
|
45
|
+
selectOptions: (HTMLOptionElement | HTMLOptGroupElement)[];
|
|
46
|
+
private _values: ValueStruct[];
|
|
47
|
+
nativeTabIndex: number;
|
|
48
|
+
|
|
49
|
+
constructor(el: HTMLSelectElement, options: FormSelectOptions) {
|
|
50
|
+
super(el, options, FormSelect);
|
|
51
|
+
if (this.el.classList.contains('browser-default')) return;
|
|
52
|
+
this.el['M_FormSelect'] = this;
|
|
53
|
+
|
|
54
|
+
this.options = {
|
|
55
|
+
...FormSelect.defaults,
|
|
56
|
+
...options
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
this.isMultiple = this.el.multiple;
|
|
60
|
+
this.nativeTabIndex = this.el.tabIndex ?? -1;
|
|
61
|
+
this.el.tabIndex = -1;
|
|
62
|
+
this._values = [];
|
|
63
|
+
this._setupDropdown();
|
|
64
|
+
this._setupEventHandlers();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
static get defaults(): FormSelectOptions {
|
|
68
|
+
return _defaults;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Initializes instance of FormSelect.
|
|
73
|
+
* @param el HTML element.
|
|
74
|
+
* @param options Component options.
|
|
75
|
+
*/
|
|
76
|
+
static init(el: HTMLSelectElement, options?: Partial<FormSelectOptions>): FormSelect;
|
|
77
|
+
/**
|
|
78
|
+
* Initializes instances of FormSelect.
|
|
79
|
+
* @param els HTML elements.
|
|
80
|
+
* @param options Component options.
|
|
81
|
+
*/
|
|
82
|
+
static init(
|
|
83
|
+
els: InitElements<HTMLSelectElement | MElement>,
|
|
84
|
+
options?: Partial<FormSelectOptions>
|
|
85
|
+
): FormSelect[];
|
|
86
|
+
/**
|
|
87
|
+
* Initializes instances of FormSelect.
|
|
88
|
+
* @param els HTML elements.
|
|
89
|
+
* @param options Component options.
|
|
90
|
+
*/
|
|
91
|
+
static init(
|
|
92
|
+
els: HTMLSelectElement | InitElements<HTMLSelectElement | MElement>,
|
|
93
|
+
options: Partial<FormSelectOptions> = {}
|
|
94
|
+
): FormSelect | FormSelect[] {
|
|
95
|
+
return super.init(els, options, FormSelect);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
static getInstance(el: HTMLElement): FormSelect {
|
|
99
|
+
return el['M_FormSelect'];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
destroy() {
|
|
103
|
+
this._removeEventHandlers();
|
|
104
|
+
this._removeDropdown();
|
|
105
|
+
this.el['M_FormSelect'] = undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
_setupEventHandlers() {
|
|
109
|
+
this.dropdownOptions.querySelectorAll('li:not(.optgroup)').forEach((el) => {
|
|
110
|
+
el.addEventListener('click', this._handleOptionClick);
|
|
111
|
+
el.addEventListener('keydown', (e: KeyboardEvent) => {
|
|
112
|
+
if (e.key === ' ' || e.key === 'Enter') this._handleOptionClick(e);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
this.el.addEventListener('change', this._handleSelectChange);
|
|
116
|
+
this.input.addEventListener('click', this._handleInputClick);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
_removeEventHandlers() {
|
|
120
|
+
this.dropdownOptions.querySelectorAll('li:not(.optgroup)').forEach((el) => {
|
|
121
|
+
el.removeEventListener('click', this._handleOptionClick);
|
|
122
|
+
});
|
|
123
|
+
this.el.removeEventListener('change', this._handleSelectChange);
|
|
124
|
+
this.input.removeEventListener('click', this._handleInputClick);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
_handleSelectChange = () => {
|
|
128
|
+
this._setValueToInput();
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
_handleOptionClick = (e: MouseEvent | KeyboardEvent) => {
|
|
132
|
+
e.preventDefault();
|
|
133
|
+
const virtualOption = (e.target as HTMLLIElement).closest('li');
|
|
134
|
+
this._selectOptionElement(virtualOption);
|
|
135
|
+
e.stopPropagation();
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
_arraysEqual<T, E>(a: T[], b: (E | T)[]) {
|
|
139
|
+
if (a === b) return true;
|
|
140
|
+
if (a == null || b == null) return false;
|
|
141
|
+
if (a.length !== b.length) return false;
|
|
142
|
+
for (let i = 0; i < a.length; ++i) if (a[i] !== b[i]) return false;
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
_selectOptionElement(virtualOption: HTMLElement) {
|
|
147
|
+
if (
|
|
148
|
+
!virtualOption.classList.contains('disabled') &&
|
|
149
|
+
!virtualOption.classList.contains('optgroup')
|
|
150
|
+
) {
|
|
151
|
+
const value = this._values.find((value) => value.optionEl === virtualOption);
|
|
152
|
+
const previousSelectedValues = this.getSelectedValues();
|
|
153
|
+
if (this.isMultiple) {
|
|
154
|
+
// Multi-Select
|
|
155
|
+
this._toggleEntryFromArray(value);
|
|
156
|
+
} else {
|
|
157
|
+
// Single-Select
|
|
158
|
+
this._deselectAll();
|
|
159
|
+
this._selectValue(value);
|
|
160
|
+
}
|
|
161
|
+
// Refresh Input-Text
|
|
162
|
+
this._setValueToInput();
|
|
163
|
+
// Trigger Change-Event only when data is different
|
|
164
|
+
const actualSelectedValues = this.getSelectedValues();
|
|
165
|
+
const selectionHasChanged = !this._arraysEqual(previousSelectedValues, actualSelectedValues);
|
|
166
|
+
if (selectionHasChanged)
|
|
167
|
+
this.el.dispatchEvent(
|
|
168
|
+
new Event('change', { bubbles: true, cancelable: true, composed: true })
|
|
169
|
+
); // trigger('change');
|
|
170
|
+
}
|
|
171
|
+
if (!this.isMultiple) this.dropdown.close();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
_handleInputClick = () => {
|
|
175
|
+
if (this.dropdown && this.dropdown.isOpen) {
|
|
176
|
+
this._setValueToInput();
|
|
177
|
+
this._setSelectedStates();
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
_setupDropdown() {
|
|
182
|
+
this.labelEl = document.querySelector('[for="' + this.el.id + '"]');
|
|
183
|
+
|
|
184
|
+
this.wrapper = document.createElement('div');
|
|
185
|
+
this.wrapper.classList.add('select-wrapper', 'input-field');
|
|
186
|
+
if (this.options.classes.length > 0) {
|
|
187
|
+
this.wrapper.classList.add(...this.options.classes.split(' '));
|
|
188
|
+
}
|
|
189
|
+
this.el.before(this.wrapper);
|
|
190
|
+
|
|
191
|
+
// Move actual select element into overflow hidden wrapper
|
|
192
|
+
const hiddenDiv = document.createElement('div');
|
|
193
|
+
hiddenDiv.classList.add('hide-select');
|
|
194
|
+
this.wrapper.append(hiddenDiv);
|
|
195
|
+
hiddenDiv.appendChild(this.el);
|
|
196
|
+
|
|
197
|
+
if (this.el.disabled) this.wrapper.classList.add('disabled');
|
|
198
|
+
|
|
199
|
+
this.selectOptions = <(HTMLOptGroupElement | HTMLOptionElement)[]>(
|
|
200
|
+
Array.from(this.el.children).filter((el) => ['OPTION', 'OPTGROUP'].includes(el.tagName))
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
// Create dropdown
|
|
204
|
+
this.dropdownOptions = document.createElement('ul');
|
|
205
|
+
this.dropdownOptions.id = `select-options-${Utils.guid()}`;
|
|
206
|
+
this.dropdownOptions.setAttribute('popover', 'auto');
|
|
207
|
+
this.dropdownOptions.classList.add('dropdown-content', 'select-dropdown');
|
|
208
|
+
this.dropdownOptions.setAttribute('role', 'listbox');
|
|
209
|
+
this.dropdownOptions.ariaMultiSelectable = this.isMultiple.toString();
|
|
210
|
+
if (this.isMultiple) this.dropdownOptions.classList.add('multiple-select-dropdown');
|
|
211
|
+
|
|
212
|
+
// Create dropdown structure
|
|
213
|
+
if (this.selectOptions.length > 0) {
|
|
214
|
+
this.selectOptions.forEach((realOption) => {
|
|
215
|
+
if (realOption.tagName === 'OPTION') {
|
|
216
|
+
// Option
|
|
217
|
+
const virtualOption = this._createAndAppendOptionWithIcon(
|
|
218
|
+
realOption,
|
|
219
|
+
this.isMultiple ? 'multiple' : undefined
|
|
220
|
+
);
|
|
221
|
+
this._addOptionToValues(realOption as HTMLOptionElement, virtualOption);
|
|
222
|
+
} else if (realOption.tagName === 'OPTGROUP') {
|
|
223
|
+
// Optgroup
|
|
224
|
+
const groupId = 'opt-group-' + Utils.guid();
|
|
225
|
+
const groupParent = document.createElement('li');
|
|
226
|
+
groupParent.classList.add('optgroup');
|
|
227
|
+
groupParent.tabIndex = -1;
|
|
228
|
+
groupParent.setAttribute('role', 'group');
|
|
229
|
+
groupParent.setAttribute('aria-labelledby', groupId);
|
|
230
|
+
groupParent.innerHTML = `<span id="${groupId}" role="presentation">${realOption.getAttribute(
|
|
231
|
+
'label'
|
|
232
|
+
)}</span>`;
|
|
233
|
+
this.dropdownOptions.append(groupParent);
|
|
234
|
+
|
|
235
|
+
const groupChildren = [];
|
|
236
|
+
const selectOptions = <HTMLOptionElement[]>(
|
|
237
|
+
Array.from(realOption.children).filter((el) => el.tagName === 'OPTION')
|
|
238
|
+
);
|
|
239
|
+
selectOptions.forEach((realOption) => {
|
|
240
|
+
const virtualOption = this._createAndAppendOptionWithIcon(
|
|
241
|
+
realOption,
|
|
242
|
+
'optgroup-option'
|
|
243
|
+
);
|
|
244
|
+
const childId = 'opt-child-' + Utils.guid();
|
|
245
|
+
virtualOption.id = childId;
|
|
246
|
+
groupChildren.push(childId);
|
|
247
|
+
this._addOptionToValues(realOption, virtualOption);
|
|
248
|
+
});
|
|
249
|
+
groupParent.setAttribute('aria-owns', groupChildren.join(' '));
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
this.wrapper.append(this.dropdownOptions);
|
|
254
|
+
|
|
255
|
+
// Add input dropdown
|
|
256
|
+
this.input = document.createElement('input');
|
|
257
|
+
this.input.id = 'm_select-input-' + Utils.guid();
|
|
258
|
+
this.input.classList.add('select-dropdown', 'dropdown-trigger');
|
|
259
|
+
this.input.type = 'text';
|
|
260
|
+
this.input.readOnly = true;
|
|
261
|
+
this.input.setAttribute('data-target', this.dropdownOptions.id);
|
|
262
|
+
this.input.ariaReadOnly = 'true';
|
|
263
|
+
this.input.ariaRequired = this.el.hasAttribute('required').toString(); //setAttribute("aria-required", this.el.hasAttribute("required"));
|
|
264
|
+
if (this.el.disabled) this.input.disabled = true; // 'true');
|
|
265
|
+
this.input.setAttribute('tabindex', this.nativeTabIndex.toString());
|
|
266
|
+
|
|
267
|
+
const attrs = this.el.attributes;
|
|
268
|
+
for (let i = 0; i < attrs.length; ++i) {
|
|
269
|
+
const attr = attrs[i];
|
|
270
|
+
if (attr.name.startsWith('aria-')) this.input.setAttribute(attr.name, attr.value);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Adds aria-attributes to input element
|
|
274
|
+
this.input.setAttribute('role', 'combobox');
|
|
275
|
+
this.input.ariaExpanded = 'false';
|
|
276
|
+
this.input.setAttribute('aria-owns', this.dropdownOptions.id);
|
|
277
|
+
this.input.setAttribute('aria-controls', this.dropdownOptions.id);
|
|
278
|
+
this.input.placeholder = ' ';
|
|
279
|
+
|
|
280
|
+
this.wrapper.prepend(this.input);
|
|
281
|
+
this._setValueToInput();
|
|
282
|
+
|
|
283
|
+
// Add caret
|
|
284
|
+
const dropdownIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); //document.createElement('svg')
|
|
285
|
+
dropdownIcon.classList.add('caret');
|
|
286
|
+
dropdownIcon.setAttribute('height', '24');
|
|
287
|
+
dropdownIcon.setAttribute('width', '24');
|
|
288
|
+
dropdownIcon.setAttribute('viewBox', '0 0 24 24');
|
|
289
|
+
dropdownIcon.ariaHidden = 'true';
|
|
290
|
+
dropdownIcon.innerHTML = `<path d="M7 10l5 5 5-5z"/><path d="M0 0h24v24H0z" fill="none"/>`;
|
|
291
|
+
this.wrapper.prepend(dropdownIcon);
|
|
292
|
+
|
|
293
|
+
// Initialize dropdown
|
|
294
|
+
if (!this.el.disabled) {
|
|
295
|
+
const dropdownOptions = { ...this.options.dropdownOptions };
|
|
296
|
+
dropdownOptions.coverTrigger = false;
|
|
297
|
+
const userOnOpenEnd = dropdownOptions.onOpenEnd;
|
|
298
|
+
const userOnCloseEnd = dropdownOptions.onCloseEnd;
|
|
299
|
+
// Add callback for centering selected option when dropdown content is scrollable
|
|
300
|
+
dropdownOptions.onOpenEnd = () => {
|
|
301
|
+
const selectedOption = this.dropdownOptions.querySelector('.selected');
|
|
302
|
+
if (selectedOption) {
|
|
303
|
+
// Focus selected option in dropdown
|
|
304
|
+
Utils.keyDown = true;
|
|
305
|
+
this.dropdown.focusedIndex = [...selectedOption.parentNode.children].indexOf(
|
|
306
|
+
selectedOption
|
|
307
|
+
);
|
|
308
|
+
this.dropdown._focusFocusedItem();
|
|
309
|
+
Utils.keyDown = false;
|
|
310
|
+
// Handle scrolling to selected option
|
|
311
|
+
if (this.dropdown.isScrollable) {
|
|
312
|
+
let scrollOffset =
|
|
313
|
+
selectedOption.getBoundingClientRect().top -
|
|
314
|
+
(this.dropdownOptions as HTMLElement).getBoundingClientRect().top; // scroll to selected option
|
|
315
|
+
scrollOffset -= this.dropdownOptions.clientHeight / 2; // center in dropdown
|
|
316
|
+
this.dropdownOptions.scrollTop = scrollOffset;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
this.input.ariaExpanded = 'true';
|
|
320
|
+
// Handle user declared onOpenEnd if needed
|
|
321
|
+
if (userOnOpenEnd && typeof userOnOpenEnd === 'function')
|
|
322
|
+
userOnOpenEnd.call(this.dropdown, this.el);
|
|
323
|
+
};
|
|
324
|
+
// Add callback for reseting "expanded" state
|
|
325
|
+
dropdownOptions.onCloseEnd = () => {
|
|
326
|
+
this.input.ariaExpanded = 'false';
|
|
327
|
+
// Handle user declared onOpenEnd if needed
|
|
328
|
+
if (userOnCloseEnd && typeof userOnCloseEnd === 'function')
|
|
329
|
+
userOnCloseEnd.call(this.dropdown, this.el);
|
|
330
|
+
};
|
|
331
|
+
// Prevent dropdown from closing too early
|
|
332
|
+
dropdownOptions.closeOnClick = false;
|
|
333
|
+
this.dropdown = Dropdown.init(this.input, dropdownOptions);
|
|
334
|
+
}
|
|
335
|
+
// Add initial selections
|
|
336
|
+
this._setSelectedStates();
|
|
337
|
+
// move label
|
|
338
|
+
if (this.labelEl) this.input.after(this.labelEl);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
_addOptionToValues(realOption: HTMLOptionElement, virtualOption: HTMLElement) {
|
|
342
|
+
this._values.push({ el: realOption, optionEl: virtualOption });
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
_removeDropdown() {
|
|
346
|
+
this.wrapper.querySelector('.caret').remove();
|
|
347
|
+
this.input.remove();
|
|
348
|
+
this.dropdownOptions.remove();
|
|
349
|
+
this.wrapper.before(this.el);
|
|
350
|
+
this.wrapper.remove();
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
_createAndAppendOptionWithIcon(
|
|
354
|
+
realOption: HTMLOptionElement | HTMLOptGroupElement,
|
|
355
|
+
type: string
|
|
356
|
+
) {
|
|
357
|
+
const li = document.createElement('li');
|
|
358
|
+
li.setAttribute('role', 'option');
|
|
359
|
+
if (realOption.disabled) {
|
|
360
|
+
li.classList.add('disabled');
|
|
361
|
+
li.ariaDisabled = 'true';
|
|
362
|
+
}
|
|
363
|
+
if (type === 'optgroup-option') li.classList.add(type);
|
|
364
|
+
|
|
365
|
+
// Text / Checkbox
|
|
366
|
+
const span = document.createElement('span');
|
|
367
|
+
span.innerHTML = realOption.innerHTML;
|
|
368
|
+
if (this.isMultiple && !realOption.disabled) {
|
|
369
|
+
span.innerHTML = `<label><input type="checkbox"><span>${realOption.innerHTML}</span></label>`;
|
|
370
|
+
}
|
|
371
|
+
li.appendChild(span);
|
|
372
|
+
|
|
373
|
+
// add Icon
|
|
374
|
+
const iconUrl = realOption.getAttribute('data-icon');
|
|
375
|
+
const classes = realOption.getAttribute('class')?.split(' ');
|
|
376
|
+
if (iconUrl) {
|
|
377
|
+
const img = document.createElement('img');
|
|
378
|
+
if (classes) img.classList.add(...classes);
|
|
379
|
+
img.src = iconUrl;
|
|
380
|
+
img.ariaHidden = 'true';
|
|
381
|
+
li.prepend(img);
|
|
382
|
+
}
|
|
383
|
+
// Check for multiple type
|
|
384
|
+
this.dropdownOptions.append(li);
|
|
385
|
+
return li;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
_selectValue(value: ValueStruct) {
|
|
389
|
+
value.el.selected = true;
|
|
390
|
+
value.optionEl.classList.add('selected');
|
|
391
|
+
value.optionEl.ariaSelected = 'true'; // setAttribute("aria-selected", true);
|
|
392
|
+
const checkbox = <HTMLInputElement>value.optionEl.querySelector('input[type="checkbox"]');
|
|
393
|
+
if (checkbox) checkbox.checked = true;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
_deselectValue(value: ValueStruct) {
|
|
397
|
+
value.el.selected = false;
|
|
398
|
+
value.optionEl.classList.remove('selected');
|
|
399
|
+
value.optionEl.ariaSelected = 'false'; //setAttribute("aria-selected", false);
|
|
400
|
+
const checkbox = <HTMLInputElement>value.optionEl.querySelector('input[type="checkbox"]');
|
|
401
|
+
if (checkbox) checkbox.checked = false;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
_deselectAll() {
|
|
405
|
+
this._values.forEach((value) => this._deselectValue(value));
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
_isValueSelected(value: ValueStruct) {
|
|
409
|
+
const realValues = this.getSelectedValues();
|
|
410
|
+
return realValues.some((realValue) => realValue === value.el.value);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
_toggleEntryFromArray(value: ValueStruct) {
|
|
414
|
+
if (this._isValueSelected(value)) this._deselectValue(value);
|
|
415
|
+
else this._selectValue(value);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
_getSelectedOptions(): HTMLOptionElement[] {
|
|
419
|
+
// remove null, false, ... values
|
|
420
|
+
return Array.prototype.filter.call(
|
|
421
|
+
this.el.selectedOptions,
|
|
422
|
+
(realOption: HTMLOptionElement) => realOption
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
_setValueToInput() {
|
|
427
|
+
const selectedRealOptions = this._getSelectedOptions();
|
|
428
|
+
const selectedOptionPairs = this._values.filter(
|
|
429
|
+
(value) => selectedRealOptions.indexOf(value.el) >= 0
|
|
430
|
+
);
|
|
431
|
+
// Filter not disabled
|
|
432
|
+
const notDisabledOptionPairs = selectedOptionPairs.filter((op) => !op.el.disabled);
|
|
433
|
+
const texts = notDisabledOptionPairs.map((value) =>
|
|
434
|
+
value.optionEl.querySelector('span').innerText.trim()
|
|
435
|
+
);
|
|
436
|
+
// Set input-text to first Option with empty value which indicates a description like "choose your option"
|
|
437
|
+
if (texts.length === 0) {
|
|
438
|
+
const firstDisabledOption = <HTMLOptionElement>this.el.querySelector('option:disabled');
|
|
439
|
+
if (firstDisabledOption && firstDisabledOption.value === '') {
|
|
440
|
+
this.input.value = firstDisabledOption.innerText;
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
this.input.value = texts.join(', ');
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
_setSelectedStates() {
|
|
448
|
+
this._values.forEach((value) => {
|
|
449
|
+
const optionIsSelected = value.el.selected;
|
|
450
|
+
const cb = <HTMLInputElement>value.optionEl.querySelector('input[type="checkbox"]');
|
|
451
|
+
if (cb) cb.checked = optionIsSelected;
|
|
452
|
+
if (optionIsSelected) {
|
|
453
|
+
this._activateOption(this.dropdownOptions, value.optionEl);
|
|
454
|
+
} else {
|
|
455
|
+
value.optionEl.classList.remove('selected');
|
|
456
|
+
value.optionEl.ariaSelected = 'false'; // attr("aria-selected", 'false');
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
_activateOption(ul: HTMLElement, li: HTMLElement) {
|
|
462
|
+
if (!li) return;
|
|
463
|
+
if (!this.isMultiple)
|
|
464
|
+
ul.querySelectorAll('li.selected').forEach((li) => li.classList.remove('selected'));
|
|
465
|
+
li.classList.add('selected');
|
|
466
|
+
li.ariaSelected = 'true';
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
getSelectedValues() {
|
|
470
|
+
return this._getSelectedOptions().map((realOption) => realOption.value);
|
|
471
|
+
}
|
|
472
|
+
}
|