@materializecss/materialize 1.1.0-alpha → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/Gruntfile.js +712 -725
  2. package/LICENSE +21 -21
  3. package/README.md +91 -97
  4. package/dist/css/materialize.css +659 -1140
  5. package/dist/css/materialize.min.css +7 -7
  6. package/dist/js/materialize.js +4679 -4654
  7. package/dist/js/materialize.min.js +6 -6
  8. package/extras/noUiSlider/nouislider.css +404 -406
  9. package/extras/noUiSlider/nouislider.js +2147 -2147
  10. package/js/anime.min.js +34 -34
  11. package/js/autocomplete.js +479 -479
  12. package/js/buttons.js +354 -354
  13. package/js/cards.js +40 -40
  14. package/js/carousel.js +732 -717
  15. package/js/cash.js +960 -960
  16. package/js/characterCounter.js +136 -136
  17. package/js/chips.js +486 -486
  18. package/js/collapsible.js +275 -275
  19. package/js/component.js +44 -44
  20. package/js/datepicker.js +983 -976
  21. package/js/dropdown.js +669 -668
  22. package/js/forms.js +285 -275
  23. package/js/global.js +428 -424
  24. package/js/materialbox.js +453 -453
  25. package/js/modal.js +382 -382
  26. package/js/parallax.js +138 -138
  27. package/js/pushpin.js +148 -148
  28. package/js/range.js +263 -263
  29. package/js/scrollspy.js +295 -295
  30. package/js/select.js +391 -451
  31. package/js/sidenav.js +583 -583
  32. package/js/slider.js +359 -359
  33. package/js/tabs.js +402 -402
  34. package/js/tapTarget.js +315 -315
  35. package/js/timepicker.js +712 -647
  36. package/js/toasts.js +325 -322
  37. package/js/tooltip.js +320 -320
  38. package/js/waves.js +614 -614
  39. package/package.json +84 -74
  40. package/sass/_style.scss +929 -0
  41. package/sass/components/_badges.scss +55 -55
  42. package/sass/components/_buttons.scss +322 -322
  43. package/sass/components/_cards.scss +195 -195
  44. package/sass/components/_carousel.scss +90 -90
  45. package/sass/components/_chips.scss +96 -96
  46. package/sass/components/_collapsible.scss +91 -91
  47. package/sass/components/_collection.scss +107 -0
  48. package/sass/components/_color-classes.scss +32 -32
  49. package/sass/components/_color-variables.scss +370 -370
  50. package/sass/components/_datepicker.scss +191 -191
  51. package/sass/components/_dropdown.scss +84 -84
  52. package/sass/components/_global.scss +646 -771
  53. package/sass/components/_grid.scss +158 -156
  54. package/sass/components/_icons-material-design.scss +5 -5
  55. package/sass/components/_materialbox.scss +42 -42
  56. package/sass/components/_modal.scss +97 -97
  57. package/sass/components/_navbar.scss +208 -208
  58. package/sass/components/_normalize.scss +447 -447
  59. package/sass/components/_preloader.scss +334 -334
  60. package/sass/components/_pulse.scss +34 -34
  61. package/sass/components/_sidenav.scss +214 -214
  62. package/sass/components/_slider.scss +91 -91
  63. package/sass/components/_table_of_contents.scss +33 -33
  64. package/sass/components/_tabs.scss +99 -99
  65. package/sass/components/_tapTarget.scss +103 -103
  66. package/sass/components/_timepicker.scss +199 -183
  67. package/sass/components/_toast.scss +58 -58
  68. package/sass/components/_tooltip.scss +32 -32
  69. package/sass/components/_transitions.scss +12 -12
  70. package/sass/components/_typography.scss +62 -60
  71. package/sass/components/_variables.scss +352 -349
  72. package/sass/components/_waves.scss +187 -187
  73. package/sass/components/forms/_checkboxes.scss +200 -200
  74. package/sass/components/forms/_file-input.scss +44 -44
  75. package/sass/components/forms/_forms.scss +22 -22
  76. package/sass/components/forms/_input-fields.scss +388 -379
  77. package/sass/components/forms/_radio-buttons.scss +115 -115
  78. package/sass/components/forms/_range.scss +161 -161
  79. package/sass/components/forms/_select.scss +199 -199
  80. package/sass/components/forms/_switches.scss +91 -91
  81. package/sass/ghpages-materialize.scss +7 -0
  82. package/sass/materialize.scss +42 -41
  83. package/CHANGELOG.md +0 -76
  84. package/HISTORY.md +0 -527
package/js/select.js CHANGED
@@ -1,451 +1,391 @@
1
- (function($) {
2
- 'use strict';
3
-
4
- let _defaults = {
5
- classes: '',
6
- dropdownOptions: {}
7
- };
8
-
9
- /**
10
- * @class
11
- *
12
- */
13
- class FormSelect extends Component {
14
- /**
15
- * Construct FormSelect instance
16
- * @constructor
17
- * @param {Element} el
18
- * @param {Object} options
19
- */
20
- constructor(el, options) {
21
- super(FormSelect, el, options);
22
-
23
- // Don't init if browser default version
24
- if (this.$el.hasClass('browser-default')) {
25
- return;
26
- }
27
-
28
- this.el.M_FormSelect = this;
29
-
30
- /**
31
- * Options for the select
32
- * @member FormSelect#options
33
- */
34
- this.options = $.extend({}, FormSelect.defaults, options);
35
-
36
- this.isMultiple = this.$el.prop('multiple');
37
-
38
- // Setup
39
- this.el.tabIndex = -1;
40
- this._keysSelected = {};
41
- this._valueDict = {}; // Maps key to original and generated option element.
42
- this._setupDropdown();
43
-
44
- this._setupEventHandlers();
45
- }
46
-
47
- static get defaults() {
48
- return _defaults;
49
- }
50
-
51
- static init(els, options) {
52
- return super.init(this, els, options);
53
- }
54
-
55
- /**
56
- * Get Instance
57
- */
58
- static getInstance(el) {
59
- let domElem = !!el.jquery ? el[0] : el;
60
- return domElem.M_FormSelect;
61
- }
62
-
63
- /**
64
- * Teardown component
65
- */
66
- destroy() {
67
- this._removeEventHandlers();
68
- this._removeDropdown();
69
- this.el.M_FormSelect = undefined;
70
- }
71
-
72
- /**
73
- * Setup Event Handlers
74
- */
75
- _setupEventHandlers() {
76
- this._handleSelectChangeBound = this._handleSelectChange.bind(this);
77
- this._handleOptionClickBound = this._handleOptionClick.bind(this);
78
- this._handleInputClickBound = this._handleInputClick.bind(this);
79
-
80
- $(this.dropdownOptions)
81
- .find('li:not(.optgroup)')
82
- .each((el) => {
83
- el.addEventListener('click', this._handleOptionClickBound);
84
- });
85
- this.el.addEventListener('change', this._handleSelectChangeBound);
86
- this.input.addEventListener('click', this._handleInputClickBound);
87
- }
88
-
89
- /**
90
- * Remove Event Handlers
91
- */
92
- _removeEventHandlers() {
93
- $(this.dropdownOptions)
94
- .find('li:not(.optgroup)')
95
- .each((el) => {
96
- el.removeEventListener('click', this._handleOptionClickBound);
97
- });
98
- this.el.removeEventListener('change', this._handleSelectChangeBound);
99
- this.input.removeEventListener('click', this._handleInputClickBound);
100
- }
101
-
102
- /**
103
- * Handle Select Change
104
- * @param {Event} e
105
- */
106
- _handleSelectChange(e) {
107
- this._setValueToInput();
108
- }
109
-
110
- /**
111
- * Handle Option Click
112
- * @param {Event} e
113
- */
114
- _handleOptionClick(e) {
115
- e.preventDefault();
116
- let optionEl = $(e.target).closest('li')[0];
117
- this._selectOption(optionEl);
118
- e.stopPropagation();
119
- }
120
-
121
- _selectOption(optionEl) {
122
- let key = optionEl.id;
123
- if (!$(optionEl).hasClass('disabled') && !$(optionEl).hasClass('optgroup') && key.length) {
124
- let selected = true;
125
-
126
- if (this.isMultiple) {
127
- // Deselect placeholder option if still selected.
128
- let placeholderOption = $(this.dropdownOptions).find('li.disabled.selected');
129
- if (placeholderOption.length) {
130
- placeholderOption.removeClass('selected');
131
- placeholderOption.find('input[type="checkbox"]').prop('checked', false);
132
- this._toggleEntryFromArray(placeholderOption[0].id);
133
- }
134
- selected = this._toggleEntryFromArray(key);
135
- } else {
136
- $(this.dropdownOptions)
137
- .find('li')
138
- .removeClass('selected');
139
- $(optionEl).toggleClass('selected', selected);
140
- this._keysSelected = {};
141
- this._keysSelected[optionEl.id] = true;
142
- }
143
-
144
- // Set selected on original select option
145
- // Only trigger if selected state changed
146
- let prevSelected = $(this._valueDict[key].el).prop('selected');
147
- if (prevSelected !== selected) {
148
- $(this._valueDict[key].el).prop('selected', selected);
149
- this.$el.trigger('change');
150
- }
151
- }
152
-
153
- if (!this.isMultiple) {
154
- this.dropdown.close();
155
- }
156
- }
157
-
158
- /**
159
- * Handle Input Click
160
- */
161
- _handleInputClick() {
162
- if (this.dropdown && this.dropdown.isOpen) {
163
- this._setValueToInput();
164
- this._setSelectedStates();
165
- }
166
- }
167
-
168
- /**
169
- * Setup dropdown
170
- */
171
- _setupDropdown() {
172
- this.wrapper = document.createElement('div');
173
- $(this.wrapper).addClass('select-wrapper ' + this.options.classes);
174
- this.$el.before($(this.wrapper));
175
- // Move actual select element into overflow hidden wrapper
176
- let $hideSelect = $('<div class="hide-select"></div>');
177
- $(this.wrapper).append($hideSelect);
178
- $hideSelect[0].appendChild(this.el);
179
-
180
- if (this.el.disabled) {
181
- this.wrapper.classList.add('disabled');
182
- }
183
-
184
- // Create dropdown
185
- this.$selectOptions = this.$el.children('option, optgroup');
186
- this.dropdownOptions = document.createElement('ul');
187
- this.dropdownOptions.id = `select-options-${M.guid()}`;
188
- $(this.dropdownOptions).addClass(
189
- 'dropdown-content select-dropdown ' + (this.isMultiple ? 'multiple-select-dropdown' : '')
190
- );
191
-
192
- // Create dropdown structure.
193
- if (this.$selectOptions.length) {
194
- this.$selectOptions.each((el) => {
195
- if ($(el).is('option')) {
196
- // Direct descendant option.
197
- let optionEl;
198
- if (this.isMultiple) {
199
- optionEl = this._appendOptionWithIcon(this.$el, el, 'multiple');
200
- } else {
201
- optionEl = this._appendOptionWithIcon(this.$el, el);
202
- }
203
-
204
- this._addOptionToValueDict(el, optionEl);
205
- } else if ($(el).is('optgroup')) {
206
- // Optgroup.
207
- let selectOptions = $(el).children('option');
208
- $(this.dropdownOptions).append(
209
- $('<li class="optgroup"><span>' + el.getAttribute('label') + '</span></li>')[0]
210
- );
211
-
212
- selectOptions.each((el) => {
213
- let optionEl = this._appendOptionWithIcon(this.$el, el, 'optgroup-option');
214
- this._addOptionToValueDict(el, optionEl);
215
- });
216
- }
217
- });
218
- }
219
-
220
- $(this.wrapper).append(this.dropdownOptions);
221
-
222
- // Add input dropdown
223
- this.input = document.createElement('input');
224
- $(this.input).addClass('select-dropdown dropdown-trigger');
225
- this.input.setAttribute('type', 'text');
226
- this.input.setAttribute('readonly', 'true');
227
- this.input.setAttribute('data-target', this.dropdownOptions.id);
228
- if (this.el.disabled) {
229
- $(this.input).prop('disabled', 'true');
230
- }
231
-
232
- $(this.wrapper).prepend(this.input);
233
- this._setValueToInput();
234
-
235
- // Add caret
236
- let dropdownIcon = $(
237
- '<svg class="caret" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M7 10l5 5 5-5z"/><path d="M0 0h24v24H0z" fill="none"/></svg>'
238
- );
239
- $(this.wrapper).prepend(dropdownIcon[0]);
240
-
241
- // Initialize dropdown
242
- if (!this.el.disabled) {
243
- let dropdownOptions = $.extend({}, this.options.dropdownOptions);
244
- let userOnOpenEnd = dropdownOptions.onOpenEnd;
245
-
246
- // Add callback for centering selected option when dropdown content is scrollable
247
- dropdownOptions.onOpenEnd = (el) => {
248
- let selectedOption = $(this.dropdownOptions)
249
- .find('.selected')
250
- .first();
251
-
252
- if (selectedOption.length) {
253
- // Focus selected option in dropdown
254
- M.keyDown = true;
255
- this.dropdown.focusedIndex = selectedOption.index();
256
- this.dropdown._focusFocusedItem();
257
- M.keyDown = false;
258
-
259
- // Handle scrolling to selected option
260
- if (this.dropdown.isScrollable) {
261
- let scrollOffset =
262
- selectedOption[0].getBoundingClientRect().top -
263
- this.dropdownOptions.getBoundingClientRect().top; // scroll to selected option
264
- scrollOffset -= this.dropdownOptions.clientHeight / 2; // center in dropdown
265
- this.dropdownOptions.scrollTop = scrollOffset;
266
- }
267
- }
268
-
269
- // Handle user declared onOpenEnd if needed
270
- if (userOnOpenEnd && typeof userOnOpenEnd === 'function') {
271
- userOnOpenEnd.call(this.dropdown, this.el);
272
- }
273
- };
274
-
275
- // Prevent dropdown from closing too early
276
- dropdownOptions.closeOnClick = false;
277
-
278
- this.dropdown = M.Dropdown.init(this.input, dropdownOptions);
279
- }
280
-
281
- // Add initial selections
282
- this._setSelectedStates();
283
- }
284
-
285
- /**
286
- * Add option to value dict
287
- * @param {Element} el original option element
288
- * @param {Element} optionEl generated option element
289
- */
290
- _addOptionToValueDict(el, optionEl) {
291
- let index = Object.keys(this._valueDict).length;
292
- let key = this.dropdownOptions.id + index;
293
- let obj = {};
294
- optionEl.id = key;
295
-
296
- obj.el = el;
297
- obj.optionEl = optionEl;
298
- this._valueDict[key] = obj;
299
- }
300
-
301
- /**
302
- * Remove dropdown
303
- */
304
- _removeDropdown() {
305
- $(this.wrapper)
306
- .find('.caret')
307
- .remove();
308
- $(this.input).remove();
309
- $(this.dropdownOptions).remove();
310
- $(this.wrapper).before(this.$el);
311
- $(this.wrapper).remove();
312
- }
313
-
314
- /**
315
- * Setup dropdown
316
- * @param {Element} select select element
317
- * @param {Element} option option element from select
318
- * @param {String} type
319
- * @return {Element} option element added
320
- */
321
- _appendOptionWithIcon(select, option, type) {
322
- // Add disabled attr if disabled
323
- let disabledClass = option.disabled ? 'disabled ' : '';
324
- let optgroupClass = type === 'optgroup-option' ? 'optgroup-option ' : '';
325
- let multipleCheckbox = this.isMultiple
326
- ? `<label><input type="checkbox"${disabledClass}"/><span>${option.innerHTML}</span></label>`
327
- : option.innerHTML;
328
- let liEl = $('<li></li>');
329
- let spanEl = $('<span></span>');
330
- spanEl.html(multipleCheckbox);
331
- liEl.addClass(`${disabledClass} ${optgroupClass}`);
332
- liEl.append(spanEl);
333
-
334
- // add icons
335
- let iconUrl = option.getAttribute('data-icon');
336
- let classes = option.getAttribute('class');
337
- if (!!iconUrl) {
338
- let imgEl = $(`<img alt="" class="${classes}" src="${iconUrl}">`);
339
- liEl.prepend(imgEl);
340
- }
341
-
342
- // Check for multiple type.
343
- $(this.dropdownOptions).append(liEl[0]);
344
- return liEl[0];
345
- }
346
-
347
- /**
348
- * Toggle entry from option
349
- * @param {String} key Option key
350
- * @return {Boolean} if entry was added or removed
351
- */
352
- _toggleEntryFromArray(key) {
353
- let notAdded = !this._keysSelected.hasOwnProperty(key);
354
- let $optionLi = $(this._valueDict[key].optionEl);
355
-
356
- if (notAdded) {
357
- this._keysSelected[key] = true;
358
- } else {
359
- delete this._keysSelected[key];
360
- }
361
-
362
- $optionLi.toggleClass('selected', notAdded);
363
-
364
- // Set checkbox checked value
365
- $optionLi.find('input[type="checkbox"]').prop('checked', notAdded);
366
-
367
- // use notAdded instead of true (to detect if the option is selected or not)
368
- $optionLi.prop('selected', notAdded);
369
-
370
- return notAdded;
371
- }
372
-
373
- /**
374
- * Set text value to input
375
- */
376
- _setValueToInput() {
377
- let values = [];
378
- let options = this.$el.find('option');
379
-
380
- options.each((el) => {
381
- if ($(el).prop('selected')) {
382
- let text = $(el).text().trim();
383
- values.push(text);
384
- }
385
- });
386
-
387
- if (!values.length) {
388
- let firstDisabled = this.$el.find('option:disabled').eq(0);
389
- if (firstDisabled.length && firstDisabled[0].value === '') {
390
- values.push(firstDisabled.text());
391
- }
392
- }
393
-
394
- this.input.value = values.join(', ');
395
- }
396
-
397
- /**
398
- * Set selected state of dropdown to match actual select element
399
- */
400
- _setSelectedStates() {
401
- this._keysSelected = {};
402
-
403
- for (let key in this._valueDict) {
404
- let option = this._valueDict[key];
405
- let optionIsSelected = $(option.el).prop('selected');
406
- $(option.optionEl)
407
- .find('input[type="checkbox"]')
408
- .prop('checked', optionIsSelected);
409
- if (optionIsSelected) {
410
- this._activateOption($(this.dropdownOptions), $(option.optionEl));
411
- this._keysSelected[key] = true;
412
- } else {
413
- $(option.optionEl).removeClass('selected');
414
- }
415
- }
416
- }
417
-
418
- /**
419
- * Make option as selected and scroll to selected position
420
- * @param {jQuery} collection Select options jQuery element
421
- * @param {Element} newOption element of the new option
422
- */
423
- _activateOption(collection, newOption) {
424
- if (newOption) {
425
- if (!this.isMultiple) {
426
- collection.find('li.selected').removeClass('selected');
427
- }
428
- let option = $(newOption);
429
- option.addClass('selected');
430
- }
431
- }
432
-
433
- /**
434
- * Get Selected Values
435
- * @return {Array} Array of selected values
436
- */
437
- getSelectedValues() {
438
- let selectedValues = [];
439
- for (let key in this._keysSelected) {
440
- selectedValues.push(this._valueDict[key].el.value);
441
- }
442
- return selectedValues;
443
- }
444
- }
445
-
446
- M.FormSelect = FormSelect;
447
-
448
- if (M.jQueryLoaded) {
449
- M.initializeJqueryWrapper(FormSelect, 'formSelect', 'M_FormSelect');
450
- }
451
- })(cash);
1
+ (function($) {
2
+ 'use strict';
3
+
4
+ let _defaults = {
5
+ classes: '',
6
+ dropdownOptions: {}
7
+ };
8
+
9
+ class FormSelect extends Component {
10
+ constructor(el, options) {
11
+ super(FormSelect, el, options);
12
+ if (this.$el.hasClass('browser-default')) return;
13
+ this.el.M_FormSelect = this;
14
+ this.options = $.extend({}, FormSelect.defaults, options);
15
+ this.isMultiple = this.$el.prop('multiple');
16
+ this.el.tabIndex = -1;
17
+ this._values = [];
18
+ this.labelEl = null;
19
+ this._labelFor = false;
20
+ this._setupDropdown();
21
+ this._setupEventHandlers();
22
+ }
23
+ static get defaults() {
24
+ return _defaults;
25
+ }
26
+ static init(els, options) {
27
+ return super.init(this, els, options);
28
+ }
29
+ static getInstance(el) {
30
+ let domElem = !!el.jquery ? el[0] : el;
31
+ return domElem.M_FormSelect;
32
+ }
33
+ destroy() {
34
+ // Returns label to its original owner
35
+ if (this._labelFor) this.labelEl.setAttribute("for", this.el.id);
36
+ this._removeEventHandlers();
37
+ this._removeDropdown();
38
+ this.el.M_FormSelect = undefined;
39
+ }
40
+ _setupEventHandlers() {
41
+ this._handleSelectChangeBound = this._handleSelectChange.bind(this);
42
+ this._handleOptionClickBound = this._handleOptionClick.bind(this);
43
+ this._handleInputClickBound = this._handleInputClick.bind(this);
44
+ $(this.dropdownOptions)
45
+ .find('li:not(.optgroup)')
46
+ .each((el) => {
47
+ el.addEventListener('click', this._handleOptionClickBound);
48
+ el.addEventListener('keydown', (e) => {
49
+ if (e.key === " " || e.key === "Enter") this._handleOptionClickBound(e);
50
+ });
51
+ });
52
+ this.el.addEventListener('change', this._handleSelectChangeBound);
53
+ this.input.addEventListener('click', this._handleInputClickBound);
54
+ }
55
+ _removeEventHandlers() {
56
+ $(this.dropdownOptions)
57
+ .find('li:not(.optgroup)')
58
+ .each((el) => {
59
+ el.removeEventListener('click', this._handleOptionClickBound);
60
+ });
61
+ this.el.removeEventListener('change', this._handleSelectChangeBound);
62
+ this.input.removeEventListener('click', this._handleInputClickBound);
63
+ }
64
+ _handleSelectChange(e) {
65
+ this._setValueToInput();
66
+ }
67
+ _handleOptionClick(e) {
68
+ e.preventDefault();
69
+ let virtualOption = $(e.target).closest('li')[0];
70
+ this._selectOptionElement(virtualOption);
71
+ e.stopPropagation();
72
+ }
73
+ _arraysEqual(a, b) {
74
+ if (a === b) return true;
75
+ if (a == null || b == null) return false;
76
+ if (a.length !== b.length) return false;
77
+ for (let i = 0; i < a.length; ++i) if (a[i] !== b[i]) return false;
78
+ return true;
79
+ }
80
+ _selectOptionElement(virtualOption) {
81
+ if (!$(virtualOption).hasClass('disabled') && !$(virtualOption).hasClass('optgroup')) {
82
+ const value = this._values.filter((value) => value.optionEl === virtualOption)[0];
83
+ const previousSelectedValues = this.getSelectedValues();
84
+ if (this.isMultiple) {
85
+ // Multi-Select
86
+ this._toggleEntryFromArray(value);
87
+ } else {
88
+ // Single-Select
89
+ this._deselectAll();
90
+ this._selectValue(value);
91
+ }
92
+ // Refresh Input-Text
93
+ this._setValueToInput();
94
+ // Trigger Change-Event only when data is different
95
+ const actualSelectedValues = this.getSelectedValues();
96
+ const selectionHasChanged = !this._arraysEqual(
97
+ previousSelectedValues,
98
+ actualSelectedValues
99
+ );
100
+ if (selectionHasChanged) this.$el.trigger('change');
101
+ }
102
+ if (!this.isMultiple) this.dropdown.close();
103
+ }
104
+ _handleInputClick() {
105
+ if (this.dropdown && this.dropdown.isOpen) {
106
+ this._setValueToInput();
107
+ this._setSelectedStates();
108
+ }
109
+ }
110
+ _setupDropdown() {
111
+ this.wrapper = document.createElement('div');
112
+ $(this.wrapper).addClass('select-wrapper ' + this.options.classes);
113
+ this.$el.before($(this.wrapper));
114
+
115
+ // Move actual select element into overflow hidden wrapper
116
+ let $hideSelect = $('<div class="hide-select"></div>');
117
+ $(this.wrapper).append($hideSelect);
118
+ $hideSelect[0].appendChild(this.el);
119
+
120
+ if (this.el.disabled) this.wrapper.classList.add('disabled');
121
+
122
+ // Create dropdown
123
+ this.$selectOptions = this.$el.children('option, optgroup');
124
+ this.dropdownOptions = document.createElement('ul');
125
+ this.dropdownOptions.id = `select-options-${M.guid()}`;
126
+ $(this.dropdownOptions).addClass(
127
+ 'dropdown-content select-dropdown ' + (this.isMultiple ? 'multiple-select-dropdown' : '')
128
+ );
129
+ this.dropdownOptions.setAttribute("role", "listbox");
130
+ this.dropdownOptions.setAttribute("aria-required", this.el.hasAttribute("required"));
131
+ this.dropdownOptions.setAttribute("aria-multiselectable", this.isMultiple);
132
+
133
+ // Create dropdown structure
134
+ if (this.$selectOptions.length) {
135
+ this.$selectOptions.each((realOption) => {
136
+ if ($(realOption).is('option')) {
137
+ // Option
138
+ const virtualOption = this._createAndAppendOptionWithIcon(
139
+ realOption,
140
+ this.isMultiple ? 'multiple' : undefined
141
+ );
142
+ this._addOptionToValues(realOption, virtualOption);
143
+ } else if ($(realOption).is('optgroup')) {
144
+ // Optgroup
145
+ const selectOptions = $(realOption).children('option');
146
+ let lId = "opt-group-" + M.guid();
147
+ let groupParent = $(
148
+ `<li class="optgroup" role="group" aria-labelledby="${lId}" tabindex="-1"><span id="${lId}" role="presentation">${realOption.getAttribute('label')}</span></li>`
149
+ )[0];
150
+ let groupChildren = [];
151
+ $(this.dropdownOptions).append(groupParent);
152
+ selectOptions.each((realOption) => {
153
+ const virtualOption = this._createAndAppendOptionWithIcon(
154
+ realOption,
155
+ 'optgroup-option'
156
+ );
157
+ let cId = "opt-child-" + M.guid();
158
+ virtualOption.id = cId;
159
+ groupChildren.push(cId);
160
+ this._addOptionToValues(realOption, virtualOption);
161
+ });
162
+ groupParent.setAttribute("aria-owns", groupChildren.join(" "));
163
+ }
164
+ });
165
+ }
166
+ $(this.wrapper).append(this.dropdownOptions);
167
+
168
+ // Add input dropdown
169
+ this.input = document.createElement('input');
170
+ this.input.id = "m_select-input-" + M.guid();
171
+ $(this.input).addClass('select-dropdown dropdown-trigger');
172
+ this.input.setAttribute('type', 'text');
173
+ this.input.setAttribute('readonly', 'true');
174
+ this.input.setAttribute('data-target', this.dropdownOptions.id);
175
+ this.input.setAttribute('aria-readonly', 'true');
176
+ if (this.el.disabled) $(this.input).prop('disabled', 'true');
177
+
178
+ // Makes new element to assume HTML's select label and
179
+ // aria-attributes, if exists
180
+ if (this.el.hasAttribute("aria-labelledby")){
181
+ this.labelEl = document.getElementById(this.el.getAttribute("aria-labelledby"));
182
+ }
183
+ else if (this.el.id != ""){
184
+ let lbl = $(`label[for='${this.el.id}']`);
185
+ if (lbl.length){
186
+ this.labelEl = lbl[0];
187
+ this.labelEl.removeAttribute("for");
188
+ this._labelFor = true;
189
+ }
190
+ }
191
+ // Tries to find a valid label in parent element
192
+ if (!this.labelEl){
193
+ let el = this.el.parentElement;
194
+ if (el) el = el.getElementsByTagName("label")[0];
195
+ if (el) this.labelEl = el;
196
+ }
197
+ if (this.labelEl && this.labelEl.id == ""){
198
+ this.labelEl.id = "m_select-label-" + M.guid();
199
+ }
200
+
201
+ if (this.labelEl){
202
+ this.labelEl.setAttribute("for", this.input.id);
203
+ this.dropdownOptions.setAttribute("aria-labelledby", this.labelEl.id);
204
+ }
205
+ else this.dropdownOptions.setAttribute("aria-label", "");
206
+
207
+ let attrs = this.el.attributes;
208
+ for (let i = 0; i < attrs.length; ++i){
209
+ const attr = attrs[i];
210
+ if (attr.name.startsWith("aria-"))
211
+ this.input.setAttribute(attr.name, attr.value);
212
+ }
213
+
214
+ // Adds aria-attributes to input element
215
+ this.input.setAttribute("role", "combobox");
216
+ this.input.setAttribute("aria-owns", this.dropdownOptions.id);
217
+ this.input.setAttribute("aria-controls", this.dropdownOptions.id);
218
+ this.input.setAttribute("aria-expanded", false);
219
+
220
+ $(this.wrapper).prepend(this.input);
221
+ this._setValueToInput();
222
+
223
+ // Add caret
224
+ let dropdownIcon = $(
225
+ '<svg class="caret" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M7 10l5 5 5-5z"/><path d="M0 0h24v24H0z" fill="none"/></svg>'
226
+ );
227
+ $(this.wrapper).prepend(dropdownIcon[0]);
228
+ // Initialize dropdown
229
+ if (!this.el.disabled) {
230
+ let dropdownOptions = $.extend({}, this.options.dropdownOptions);
231
+ dropdownOptions.coverTrigger = false;
232
+ let userOnOpenEnd = dropdownOptions.onOpenEnd;
233
+ let userOnCloseEnd = dropdownOptions.onCloseEnd;
234
+ // Add callback for centering selected option when dropdown content is scrollable
235
+ dropdownOptions.onOpenEnd = (el) => {
236
+ let selectedOption = $(this.dropdownOptions)
237
+ .find('.selected')
238
+ .first();
239
+ if (selectedOption.length) {
240
+ // Focus selected option in dropdown
241
+ M.keyDown = true;
242
+ this.dropdown.focusedIndex = selectedOption.index();
243
+ this.dropdown._focusFocusedItem();
244
+ M.keyDown = false;
245
+ // Handle scrolling to selected option
246
+ if (this.dropdown.isScrollable) {
247
+ let scrollOffset =
248
+ selectedOption[0].getBoundingClientRect().top -
249
+ this.dropdownOptions.getBoundingClientRect().top; // scroll to selected option
250
+ scrollOffset -= this.dropdownOptions.clientHeight / 2; // center in dropdown
251
+ this.dropdownOptions.scrollTop = scrollOffset;
252
+ }
253
+ }
254
+ // Sets "aria-expanded" to "true"
255
+ this.input.setAttribute("aria-expanded", true);
256
+ // Handle user declared onOpenEnd if needed
257
+ if (userOnOpenEnd && typeof userOnOpenEnd === 'function')
258
+ userOnOpenEnd.call(this.dropdown, this.el);
259
+ };
260
+ // Add callback for reseting "expanded" state
261
+ dropdownOptions.onCloseEnd = (el) => {
262
+ // Sets "aria-expanded" to "false"
263
+ this.input.setAttribute("aria-expanded", false);
264
+ // Handle user declared onOpenEnd if needed
265
+ if (userOnCloseEnd && typeof userOnCloseEnd === 'function')
266
+ userOnCloseEnd.call(this.dropdown, this.el);
267
+ };
268
+ // Prevent dropdown from closing too early
269
+ dropdownOptions.closeOnClick = false;
270
+ this.dropdown = M.Dropdown.init(this.input, dropdownOptions);
271
+ }
272
+ // Add initial selections
273
+ this._setSelectedStates();
274
+ }
275
+ _addOptionToValues(realOption, virtualOption) {
276
+ this._values.push({ el: realOption, optionEl: virtualOption });
277
+ }
278
+ _removeDropdown() {
279
+ $(this.wrapper)
280
+ .find('.caret')
281
+ .remove();
282
+ $(this.input).remove();
283
+ $(this.dropdownOptions).remove();
284
+ $(this.wrapper).before(this.$el);
285
+ $(this.wrapper).remove();
286
+ }
287
+ _createAndAppendOptionWithIcon(realOption, type) {
288
+ const li = document.createElement('li');
289
+ li.setAttribute("role", "option");
290
+ if (realOption.disabled){
291
+ li.classList.add('disabled');
292
+ li.setAttribute("aria-disabled", true);
293
+ }
294
+ if (type === 'optgroup-option') li.classList.add(type);
295
+ // Text / Checkbox
296
+ const span = document.createElement('span');
297
+ if (this.isMultiple)
298
+ span.innerHTML = `<label><input type="checkbox"${
299
+ realOption.disabled ? ' disabled="disabled"' : ''
300
+ }><span>${realOption.innerHTML}</span></label>`;
301
+ else span.innerHTML = realOption.innerHTML;
302
+ li.appendChild(span);
303
+ // add Icon
304
+ const iconUrl = realOption.getAttribute('data-icon');
305
+ const classes = realOption.getAttribute('class');
306
+ if (iconUrl) {
307
+ const img = $(`<img alt="" class="${classes}" src="${iconUrl}">`);
308
+ img[0].setAttribute("aria-hidden", true);
309
+ li.prepend(img[0]);
310
+ }
311
+ // Check for multiple type
312
+ $(this.dropdownOptions).append(li);
313
+ return li;
314
+ }
315
+
316
+ _selectValue(value) {
317
+ value.el.selected = true;
318
+ value.optionEl.classList.add('selected');
319
+ value.optionEl.setAttribute("aria-selected", true);
320
+ const checkbox = value.optionEl.querySelector('input[type="checkbox"]');
321
+ if (checkbox) checkbox.checked = true;
322
+ }
323
+ _deselectValue(value) {
324
+ value.el.selected = false;
325
+ value.optionEl.classList.remove('selected');
326
+ value.optionEl.setAttribute("aria-selected", false);
327
+ const checkbox = value.optionEl.querySelector('input[type="checkbox"]');
328
+ if (checkbox) checkbox.checked = false;
329
+ }
330
+ _deselectAll() {
331
+ this._values.forEach((value) => {
332
+ this._deselectValue(value);
333
+ });
334
+ }
335
+ _isValueSelected(value) {
336
+ const realValues = this.getSelectedValues();
337
+ return realValues.some((realValue) => realValue === value.el.value);
338
+ }
339
+ _toggleEntryFromArray(value) {
340
+ const isSelected = this._isValueSelected(value);
341
+ if (isSelected) this._deselectValue(value);
342
+ else this._selectValue(value);
343
+ }
344
+ _getSelectedOptions() {
345
+ return Array.prototype.filter.call(this.el.selectedOptions, (realOption) => realOption);
346
+ }
347
+
348
+ _setValueToInput() {
349
+ const realOptions = this._getSelectedOptions();
350
+ const values = this._values.filter((value) => realOptions.indexOf(value.el) >= 0);
351
+ const texts = values.map((value) => value.optionEl.querySelector('span').innerText.trim());
352
+ // Set input-text to first Option with empty value which indicates a description like "choose your option"
353
+ if (texts.length === 0) {
354
+ const firstDisabledOption = this.$el.find('option:disabled').eq(0);
355
+ if (firstDisabledOption.length > 0 && firstDisabledOption[0].value === '') {
356
+ this.input.value = firstDisabledOption.text();
357
+ return;
358
+ }
359
+ }
360
+ this.input.value = texts.join(', ');
361
+ }
362
+ _setSelectedStates() {
363
+ this._values.forEach((value) => {
364
+ const optionIsSelected = $(value.el).prop('selected');
365
+ $(value.optionEl)
366
+ .find('input[type="checkbox"]')
367
+ .prop('checked', optionIsSelected);
368
+ if (optionIsSelected) {
369
+ this._activateOption($(this.dropdownOptions), $(value.optionEl));
370
+ } else {
371
+ $(value.optionEl).removeClass('selected');
372
+ $(value.optionEl).attr("aria-selected", false);
373
+ }
374
+ });
375
+ }
376
+ _activateOption(ul, li) {
377
+ if (!li) return;
378
+ if (!this.isMultiple) ul.find('li.selected').removeClass('selected');
379
+ $(li).addClass('selected');
380
+ $(li).attr("aria-selected", true);
381
+ }
382
+
383
+ getSelectedValues() {
384
+ return this._getSelectedOptions().map((realOption) => realOption.value);
385
+ }
386
+ }
387
+
388
+ M.FormSelect = FormSelect;
389
+
390
+ if (M.jQueryLoaded) M.initializeJqueryWrapper(FormSelect, 'formSelect', 'M_FormSelect');
391
+ })(cash);