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