kmaterialize 2.3.3-1.0.21 → 2.3.3-1.0.23
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/components/air-datepicker/air-datepicker.js +152 -0
- package/components/alert/alert.js +54 -0
- package/components/button/buttons.js +220 -0
- package/components/card/cards.js +149 -0
- package/components/carousel/carousel.js +574 -0
- package/components/carousel/slider.js +340 -0
- package/components/chip/chips.js +341 -0
- package/components/collapsible/collapsible.js +156 -0
- package/components/color-input/color-input.js +166 -0
- package/components/datepicker/datepicker.js +1035 -0
- package/components/dialog/materialbox.js +377 -0
- package/components/dialog/modal.js +72 -0
- package/components/dropdown/dropdown.js +505 -0
- package/components/file-input/file-input.js +169 -0
- package/components/kanban/_kanban.scss +69 -0
- package/components/kanban/kanban.js +168 -0
- package/components/kanban/kanban.ts +57 -8
- package/components/loading/_loading.scss +68 -0
- package/components/loading/loading.ts +104 -0
- package/components/loading/loadingSpec.js +102 -0
- package/components/navigation-drawer/sidenav.js +454 -0
- package/components/number-input/number-input.js +68 -0
- package/components/password-input/password-input.js +49 -0
- package/components/popup/_popup.scss +106 -0
- package/components/popup/popup.ts +113 -0
- package/components/popup/popupSpec.js +140 -0
- package/components/search/autocomplete.js +470 -0
- package/components/slider/range.js +169 -0
- package/components/snackbar/toasts.js +244 -0
- package/components/tabs/tabs.js +289 -0
- package/components/textfield/forms.js +172 -0
- package/components/textfield/select.js +385 -0
- package/components/timepicker/timepicker.js +698 -0
- package/components/tom-select/tom-select-field.js +130 -0
- package/components/toolbar/toolbar.js +134 -0
- package/components/tooltip/tooltip.js +270 -0
- package/dist/css/materialize.colors.min.css +1 -1
- package/dist/css/materialize.css +322 -1
- package/dist/css/materialize.min.css +2 -2
- package/dist/css/materialize.min.css.map +1 -1
- package/dist/js/materialize.cjs.js +258 -42
- package/dist/js/materialize.d.ts +50 -3
- package/dist/js/materialize.js +258 -42
- package/dist/js/materialize.min.js +2 -2
- package/dist/js/materialize.mjs +257 -43
- package/package.json +6 -1
- package/sass/materialize.scss +2 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { Utils } from '../../src/utils';
|
|
2
|
+
export class Forms {
|
|
3
|
+
/**
|
|
4
|
+
* Checks if the label has validation and apply
|
|
5
|
+
* the correct class and styles
|
|
6
|
+
* @param textfield
|
|
7
|
+
*/
|
|
8
|
+
static validateField(textfield) {
|
|
9
|
+
if (!textfield) {
|
|
10
|
+
console.error('No text field element found');
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const hasLength = textfield.getAttribute('data-length') !== null;
|
|
14
|
+
const lenAttr = parseInt(textfield.getAttribute('data-length'));
|
|
15
|
+
const len = textfield.value.length;
|
|
16
|
+
if (len === 0 &&
|
|
17
|
+
textfield.validity.badInput === false &&
|
|
18
|
+
!textfield.required &&
|
|
19
|
+
textfield.classList.contains('validate')) {
|
|
20
|
+
textfield.classList.remove('invalid');
|
|
21
|
+
}
|
|
22
|
+
else if (textfield.classList.contains('validate')) {
|
|
23
|
+
// Check for character counter attributes
|
|
24
|
+
if ((textfield.validity.valid && hasLength && len <= lenAttr) ||
|
|
25
|
+
(textfield.validity.valid && !hasLength)) {
|
|
26
|
+
textfield.classList.remove('invalid');
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
textfield.classList.add('invalid');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Resizes the given TextArea after updating the
|
|
35
|
+
* value content dynamically.
|
|
36
|
+
* @param e EventTarget
|
|
37
|
+
*/
|
|
38
|
+
static textareaAutoResize(e) {
|
|
39
|
+
const textarea = e;
|
|
40
|
+
// if (!textarea) {
|
|
41
|
+
// console.error('No textarea element found');
|
|
42
|
+
// return;
|
|
43
|
+
// }
|
|
44
|
+
// Textarea Auto Resize
|
|
45
|
+
let hiddenDiv = document.querySelector('.hiddendiv');
|
|
46
|
+
if (!hiddenDiv) {
|
|
47
|
+
hiddenDiv = document.createElement('div');
|
|
48
|
+
hiddenDiv.classList.add('hiddendiv', 'common');
|
|
49
|
+
document.body.append(hiddenDiv);
|
|
50
|
+
}
|
|
51
|
+
const style = getComputedStyle(textarea);
|
|
52
|
+
// Set font properties of hiddenDiv
|
|
53
|
+
const fontFamily = style.fontFamily; //textarea.css('font-family');
|
|
54
|
+
const fontSize = style.fontSize; //textarea.css('font-size');
|
|
55
|
+
const lineHeight = style.lineHeight; //textarea.css('line-height');
|
|
56
|
+
// Firefox can't handle padding shorthand.
|
|
57
|
+
const paddingTop = style.paddingTop; //getComputedStyle(textarea).css('padding-top');
|
|
58
|
+
const paddingRight = style.paddingRight; //textarea.css('padding-right');
|
|
59
|
+
const paddingBottom = style.paddingBottom; //textarea.css('padding-bottom');
|
|
60
|
+
const paddingLeft = style.paddingLeft; //textarea.css('padding-left');
|
|
61
|
+
if (fontSize)
|
|
62
|
+
hiddenDiv.style.fontSize = fontSize; //('font-size', fontSize);
|
|
63
|
+
if (fontFamily)
|
|
64
|
+
hiddenDiv.style.fontFamily = fontFamily; //css('font-family', fontFamily);
|
|
65
|
+
if (lineHeight)
|
|
66
|
+
hiddenDiv.style.lineHeight = lineHeight; //css('line-height', lineHeight);
|
|
67
|
+
if (paddingTop)
|
|
68
|
+
hiddenDiv.style.paddingTop = paddingTop; //ss('padding-top', paddingTop);
|
|
69
|
+
if (paddingRight)
|
|
70
|
+
hiddenDiv.style.paddingRight = paddingRight; //css('padding-right', paddingRight);
|
|
71
|
+
if (paddingBottom)
|
|
72
|
+
hiddenDiv.style.paddingBottom = paddingBottom; //css('padding-bottom', paddingBottom);
|
|
73
|
+
if (paddingLeft)
|
|
74
|
+
hiddenDiv.style.paddingLeft = paddingLeft; //css('padding-left', paddingLeft);
|
|
75
|
+
// Set original-height, if none
|
|
76
|
+
if (!textarea.hasAttribute('original-height'))
|
|
77
|
+
textarea.setAttribute('original-height', textarea.getBoundingClientRect().height.toString());
|
|
78
|
+
if (textarea.getAttribute('wrap') === 'off') {
|
|
79
|
+
hiddenDiv.style.overflowWrap = 'normal'; // ('overflow-wrap', 'normal')
|
|
80
|
+
hiddenDiv.style.whiteSpace = 'pre'; //.css('white-space', 'pre');
|
|
81
|
+
}
|
|
82
|
+
hiddenDiv.innerText = textarea.value + '\n';
|
|
83
|
+
hiddenDiv.innerHTML = hiddenDiv.innerHTML.replace(/\n/g, '<br>');
|
|
84
|
+
// When textarea is hidden, width goes crazy.
|
|
85
|
+
// Approximate with half of window size
|
|
86
|
+
if (textarea.offsetWidth > 0 && textarea.offsetHeight > 0) {
|
|
87
|
+
hiddenDiv.style.width = textarea.getBoundingClientRect().width + 'px'; // ('width', textarea.width() + 'px');
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
hiddenDiv.style.width = window.innerWidth / 2 + 'px'; //css('width', window.innerWidth / 2 + 'px');
|
|
91
|
+
}
|
|
92
|
+
// Resize if the new height is greater than the
|
|
93
|
+
// original height of the textarea
|
|
94
|
+
const originalHeight = parseInt(textarea.getAttribute('original-height'));
|
|
95
|
+
const prevLength = parseInt(textarea.getAttribute('previous-length'));
|
|
96
|
+
if (isNaN(originalHeight))
|
|
97
|
+
return;
|
|
98
|
+
if (originalHeight <= hiddenDiv.clientHeight) {
|
|
99
|
+
textarea.style.height = hiddenDiv.clientHeight + 'px'; //css('height', hiddenDiv.innerHeight() + 'px');
|
|
100
|
+
}
|
|
101
|
+
else if (textarea.value.length < prevLength) {
|
|
102
|
+
// In case the new height is less than original height, it
|
|
103
|
+
// means the textarea has less text than before
|
|
104
|
+
// So we set the height to the original one
|
|
105
|
+
textarea.style.height = originalHeight + 'px';
|
|
106
|
+
}
|
|
107
|
+
textarea.setAttribute('previous-length', (textarea.value || '').length.toString());
|
|
108
|
+
}
|
|
109
|
+
static Init() {
|
|
110
|
+
if (typeof document !== 'undefined')
|
|
111
|
+
document?.addEventListener('DOMContentLoaded', () => {
|
|
112
|
+
document.addEventListener('change', (e) => {
|
|
113
|
+
const target = e.target;
|
|
114
|
+
if (target instanceof HTMLInputElement) {
|
|
115
|
+
if (target.value.length !== 0 || target.getAttribute('placeholder') !== null) {
|
|
116
|
+
for (const child of target.parentNode.children) {
|
|
117
|
+
if (child.tagName == 'label') {
|
|
118
|
+
child.classList.add('active');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
Forms.validateField(target);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
document.addEventListener('keyup', (e) => {
|
|
126
|
+
const target = e.target;
|
|
127
|
+
// Radio and Checkbox focus class
|
|
128
|
+
if (target instanceof HTMLInputElement && ['radio', 'checkbox'].includes(target.type)) {
|
|
129
|
+
// TAB, check if tabbing to radio or checkbox.
|
|
130
|
+
if (Utils.keys.TAB.includes(e.key)) {
|
|
131
|
+
target.classList.add('tabbed');
|
|
132
|
+
target.addEventListener('blur', () => target.classList.remove('tabbed'), {
|
|
133
|
+
once: true
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
document
|
|
139
|
+
.querySelectorAll('.materialize-textarea')
|
|
140
|
+
.forEach((textArea) => {
|
|
141
|
+
Forms.InitTextarea(textArea);
|
|
142
|
+
});
|
|
143
|
+
// File Input Path
|
|
144
|
+
document
|
|
145
|
+
.querySelectorAll('.file-field input[type="file"]')
|
|
146
|
+
.forEach((fileInput) => {
|
|
147
|
+
Forms.InitFileInputPath(fileInput);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
static InitTextarea(textarea) {
|
|
152
|
+
// Save Data in Element
|
|
153
|
+
textarea.setAttribute('original-height', textarea.getBoundingClientRect().height.toString());
|
|
154
|
+
textarea.setAttribute('previous-length', (textarea.value || '').length.toString());
|
|
155
|
+
Forms.textareaAutoResize(textarea);
|
|
156
|
+
textarea.addEventListener('keyup', (e) => Forms.textareaAutoResize(e.target));
|
|
157
|
+
textarea.addEventListener('keydown', (e) => Forms.textareaAutoResize(e.target));
|
|
158
|
+
}
|
|
159
|
+
static InitFileInputPath(fileInput) {
|
|
160
|
+
fileInput.addEventListener('change', () => {
|
|
161
|
+
const fileField = fileInput.closest('.file-field');
|
|
162
|
+
const pathInput = fileField.querySelector('input.file-path');
|
|
163
|
+
const files = fileInput.files;
|
|
164
|
+
const filenames = [];
|
|
165
|
+
for (let i = 0; i < files.length; i++) {
|
|
166
|
+
filenames.push(files[i].name);
|
|
167
|
+
}
|
|
168
|
+
pathInput.value = filenames.join(', ');
|
|
169
|
+
pathInput.dispatchEvent(new Event('change', { bubbles: true, cancelable: true, composed: true }));
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { Utils } from '../../src/utils';
|
|
2
|
+
import { Dropdown } from '../dropdown/dropdown';
|
|
3
|
+
import { Component } from '../../src/component';
|
|
4
|
+
const _defaults = {
|
|
5
|
+
classes: '',
|
|
6
|
+
dropdownOptions: {}
|
|
7
|
+
};
|
|
8
|
+
export class FormSelect extends Component {
|
|
9
|
+
/** If this is a multiple select. */
|
|
10
|
+
isMultiple;
|
|
11
|
+
/**
|
|
12
|
+
* Label associated with the current select element.
|
|
13
|
+
* Is "null", if not detected.
|
|
14
|
+
*/
|
|
15
|
+
labelEl;
|
|
16
|
+
/** Dropdown UL element. */
|
|
17
|
+
dropdownOptions;
|
|
18
|
+
/** Text input that shows current selected option. */
|
|
19
|
+
input;
|
|
20
|
+
/** Instance of the dropdown plugin for this select. */
|
|
21
|
+
dropdown;
|
|
22
|
+
/** The select wrapper element. */
|
|
23
|
+
wrapper;
|
|
24
|
+
selectOptions;
|
|
25
|
+
_values;
|
|
26
|
+
nativeTabIndex;
|
|
27
|
+
constructor(el, options) {
|
|
28
|
+
super(el, options, FormSelect);
|
|
29
|
+
if (this.el.classList.contains('browser-default'))
|
|
30
|
+
return;
|
|
31
|
+
this.el['M_FormSelect'] = this;
|
|
32
|
+
this.options = {
|
|
33
|
+
...FormSelect.defaults,
|
|
34
|
+
...options
|
|
35
|
+
};
|
|
36
|
+
this.isMultiple = this.el.multiple;
|
|
37
|
+
this.nativeTabIndex = this.el.tabIndex ?? -1;
|
|
38
|
+
this.el.tabIndex = -1;
|
|
39
|
+
this._values = [];
|
|
40
|
+
this._setupDropdown();
|
|
41
|
+
this._setupEventHandlers();
|
|
42
|
+
}
|
|
43
|
+
static get defaults() {
|
|
44
|
+
return _defaults;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Initializes instances of FormSelect.
|
|
48
|
+
* @param els HTML elements.
|
|
49
|
+
* @param options Component options.
|
|
50
|
+
*/
|
|
51
|
+
static init(els, options = {}) {
|
|
52
|
+
return super.init(els, options, FormSelect);
|
|
53
|
+
}
|
|
54
|
+
static getInstance(el) {
|
|
55
|
+
return el['M_FormSelect'];
|
|
56
|
+
}
|
|
57
|
+
destroy() {
|
|
58
|
+
this._removeEventHandlers();
|
|
59
|
+
this._removeDropdown();
|
|
60
|
+
this.el['M_FormSelect'] = undefined;
|
|
61
|
+
}
|
|
62
|
+
_setupEventHandlers() {
|
|
63
|
+
this.dropdownOptions.querySelectorAll('li:not(.optgroup)').forEach((el) => {
|
|
64
|
+
el.addEventListener('click', this._handleOptionClick);
|
|
65
|
+
el.addEventListener('keydown', (e) => {
|
|
66
|
+
if (e.key === ' ' || e.key === 'Enter')
|
|
67
|
+
this._handleOptionClick(e);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
this.el.addEventListener('change', this._handleSelectChange);
|
|
71
|
+
this.input.addEventListener('click', this._handleInputClick);
|
|
72
|
+
}
|
|
73
|
+
_removeEventHandlers() {
|
|
74
|
+
this.dropdownOptions.querySelectorAll('li:not(.optgroup)').forEach((el) => {
|
|
75
|
+
el.removeEventListener('click', this._handleOptionClick);
|
|
76
|
+
});
|
|
77
|
+
this.el.removeEventListener('change', this._handleSelectChange);
|
|
78
|
+
this.input.removeEventListener('click', this._handleInputClick);
|
|
79
|
+
}
|
|
80
|
+
_handleSelectChange = () => {
|
|
81
|
+
this._setValueToInput();
|
|
82
|
+
};
|
|
83
|
+
_handleOptionClick = (e) => {
|
|
84
|
+
e.preventDefault();
|
|
85
|
+
const virtualOption = e.target.closest('li');
|
|
86
|
+
this._selectOptionElement(virtualOption);
|
|
87
|
+
e.stopPropagation();
|
|
88
|
+
};
|
|
89
|
+
_arraysEqual(a, b) {
|
|
90
|
+
if (a === b)
|
|
91
|
+
return true;
|
|
92
|
+
if (a == null || b == null)
|
|
93
|
+
return false;
|
|
94
|
+
if (a.length !== b.length)
|
|
95
|
+
return false;
|
|
96
|
+
for (let i = 0; i < a.length; ++i)
|
|
97
|
+
if (a[i] !== b[i])
|
|
98
|
+
return false;
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
_selectOptionElement(virtualOption) {
|
|
102
|
+
if (!virtualOption.classList.contains('disabled') &&
|
|
103
|
+
!virtualOption.classList.contains('optgroup')) {
|
|
104
|
+
const value = this._values.find((value) => value.optionEl === virtualOption);
|
|
105
|
+
const previousSelectedValues = this.getSelectedValues();
|
|
106
|
+
if (this.isMultiple) {
|
|
107
|
+
// Multi-Select
|
|
108
|
+
this._toggleEntryFromArray(value);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// Single-Select
|
|
112
|
+
this._deselectAll();
|
|
113
|
+
this._selectValue(value);
|
|
114
|
+
}
|
|
115
|
+
// Refresh Input-Text
|
|
116
|
+
this._setValueToInput();
|
|
117
|
+
// Trigger Change-Event only when data is different
|
|
118
|
+
const actualSelectedValues = this.getSelectedValues();
|
|
119
|
+
const selectionHasChanged = !this._arraysEqual(previousSelectedValues, actualSelectedValues);
|
|
120
|
+
if (selectionHasChanged)
|
|
121
|
+
this.el.dispatchEvent(new Event('change', { bubbles: true, cancelable: true, composed: true })); // trigger('change');
|
|
122
|
+
}
|
|
123
|
+
if (!this.isMultiple)
|
|
124
|
+
this.dropdown.close();
|
|
125
|
+
}
|
|
126
|
+
_handleInputClick = () => {
|
|
127
|
+
if (this.dropdown && this.dropdown.isOpen) {
|
|
128
|
+
this._setValueToInput();
|
|
129
|
+
this._setSelectedStates();
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
_setupDropdown() {
|
|
133
|
+
this.labelEl = document.querySelector('[for="' + this.el.id + '"]');
|
|
134
|
+
this.wrapper = document.createElement('div');
|
|
135
|
+
this.wrapper.classList.add('select-wrapper', 'input-field');
|
|
136
|
+
if (this.options.classes.length > 0) {
|
|
137
|
+
this.wrapper.classList.add(...this.options.classes.split(' '));
|
|
138
|
+
}
|
|
139
|
+
this.el.before(this.wrapper);
|
|
140
|
+
// Move actual select element into overflow hidden wrapper
|
|
141
|
+
const hiddenDiv = document.createElement('div');
|
|
142
|
+
hiddenDiv.classList.add('hide-select');
|
|
143
|
+
this.wrapper.append(hiddenDiv);
|
|
144
|
+
hiddenDiv.appendChild(this.el);
|
|
145
|
+
if (this.el.disabled)
|
|
146
|
+
this.wrapper.classList.add('disabled');
|
|
147
|
+
this.selectOptions = (Array.from(this.el.children).filter((el) => ['OPTION', 'OPTGROUP'].includes(el.tagName)));
|
|
148
|
+
// Create dropdown
|
|
149
|
+
this.dropdownOptions = document.createElement('ul');
|
|
150
|
+
this.dropdownOptions.id = `select-options-${Utils.guid()}`;
|
|
151
|
+
this.dropdownOptions.setAttribute('popover', 'auto');
|
|
152
|
+
this.dropdownOptions.classList.add('dropdown-content', 'select-dropdown');
|
|
153
|
+
this.dropdownOptions.setAttribute('role', 'listbox');
|
|
154
|
+
this.dropdownOptions.ariaMultiSelectable = this.isMultiple.toString();
|
|
155
|
+
if (this.isMultiple)
|
|
156
|
+
this.dropdownOptions.classList.add('multiple-select-dropdown');
|
|
157
|
+
// Create dropdown structure
|
|
158
|
+
if (this.selectOptions.length > 0) {
|
|
159
|
+
this.selectOptions.forEach((realOption) => {
|
|
160
|
+
if (realOption.tagName === 'OPTION') {
|
|
161
|
+
// Option
|
|
162
|
+
const virtualOption = this._createAndAppendOptionWithIcon(realOption, this.isMultiple ? 'multiple' : undefined);
|
|
163
|
+
this._addOptionToValues(realOption, virtualOption);
|
|
164
|
+
}
|
|
165
|
+
else if (realOption.tagName === 'OPTGROUP') {
|
|
166
|
+
// Optgroup
|
|
167
|
+
const groupId = 'opt-group-' + Utils.guid();
|
|
168
|
+
const groupParent = document.createElement('li');
|
|
169
|
+
groupParent.classList.add('optgroup');
|
|
170
|
+
groupParent.tabIndex = -1;
|
|
171
|
+
groupParent.setAttribute('role', 'group');
|
|
172
|
+
groupParent.setAttribute('aria-labelledby', groupId);
|
|
173
|
+
groupParent.innerHTML = `<span id="${groupId}" role="presentation">${realOption.getAttribute('label')}</span>`;
|
|
174
|
+
this.dropdownOptions.append(groupParent);
|
|
175
|
+
const groupChildren = [];
|
|
176
|
+
const selectOptions = (Array.from(realOption.children).filter((el) => el.tagName === 'OPTION'));
|
|
177
|
+
selectOptions.forEach((realOption) => {
|
|
178
|
+
const virtualOption = this._createAndAppendOptionWithIcon(realOption, 'optgroup-option');
|
|
179
|
+
const childId = 'opt-child-' + Utils.guid();
|
|
180
|
+
virtualOption.id = childId;
|
|
181
|
+
groupChildren.push(childId);
|
|
182
|
+
this._addOptionToValues(realOption, virtualOption);
|
|
183
|
+
});
|
|
184
|
+
groupParent.setAttribute('aria-owns', groupChildren.join(' '));
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
this.wrapper.append(this.dropdownOptions);
|
|
189
|
+
// Add input dropdown
|
|
190
|
+
this.input = document.createElement('input');
|
|
191
|
+
this.input.id = 'm_select-input-' + Utils.guid();
|
|
192
|
+
this.input.classList.add('select-dropdown', 'dropdown-trigger');
|
|
193
|
+
this.input.type = 'text';
|
|
194
|
+
this.input.readOnly = true;
|
|
195
|
+
this.input.setAttribute('data-target', this.dropdownOptions.id);
|
|
196
|
+
this.input.ariaReadOnly = 'true';
|
|
197
|
+
this.input.ariaRequired = this.el.hasAttribute('required').toString(); //setAttribute("aria-required", this.el.hasAttribute("required"));
|
|
198
|
+
if (this.el.disabled)
|
|
199
|
+
this.input.disabled = true; // 'true');
|
|
200
|
+
this.input.setAttribute('tabindex', this.nativeTabIndex.toString());
|
|
201
|
+
const attrs = this.el.attributes;
|
|
202
|
+
for (let i = 0; i < attrs.length; ++i) {
|
|
203
|
+
const attr = attrs[i];
|
|
204
|
+
if (attr.name.startsWith('aria-'))
|
|
205
|
+
this.input.setAttribute(attr.name, attr.value);
|
|
206
|
+
}
|
|
207
|
+
// Adds aria-attributes to input element
|
|
208
|
+
this.input.setAttribute('role', 'combobox');
|
|
209
|
+
this.input.ariaExpanded = 'false';
|
|
210
|
+
this.input.setAttribute('aria-owns', this.dropdownOptions.id);
|
|
211
|
+
this.input.setAttribute('aria-controls', this.dropdownOptions.id);
|
|
212
|
+
this.input.placeholder = ' ';
|
|
213
|
+
this.wrapper.prepend(this.input);
|
|
214
|
+
this._setValueToInput();
|
|
215
|
+
// Add caret
|
|
216
|
+
const dropdownIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); //document.createElement('svg')
|
|
217
|
+
dropdownIcon.classList.add('caret');
|
|
218
|
+
dropdownIcon.setAttribute('height', '24');
|
|
219
|
+
dropdownIcon.setAttribute('width', '24');
|
|
220
|
+
dropdownIcon.setAttribute('viewBox', '0 0 24 24');
|
|
221
|
+
dropdownIcon.ariaHidden = 'true';
|
|
222
|
+
dropdownIcon.innerHTML = `<path d="M7 10l5 5 5-5z"/><path d="M0 0h24v24H0z" fill="none"/>`;
|
|
223
|
+
this.wrapper.prepend(dropdownIcon);
|
|
224
|
+
// Initialize dropdown
|
|
225
|
+
if (!this.el.disabled) {
|
|
226
|
+
const dropdownOptions = { ...this.options.dropdownOptions };
|
|
227
|
+
dropdownOptions.coverTrigger = false;
|
|
228
|
+
const userOnOpenEnd = dropdownOptions.onOpenEnd;
|
|
229
|
+
const userOnCloseEnd = dropdownOptions.onCloseEnd;
|
|
230
|
+
// Add callback for centering selected option when dropdown content is scrollable
|
|
231
|
+
dropdownOptions.onOpenEnd = () => {
|
|
232
|
+
const selectedOption = this.dropdownOptions.querySelector('.selected');
|
|
233
|
+
if (selectedOption) {
|
|
234
|
+
// Focus selected option in dropdown
|
|
235
|
+
Utils.keyDown = true;
|
|
236
|
+
this.dropdown.focusedIndex = [...selectedOption.parentNode.children].indexOf(selectedOption);
|
|
237
|
+
this.dropdown._focusFocusedItem();
|
|
238
|
+
Utils.keyDown = false;
|
|
239
|
+
// Handle scrolling to selected option
|
|
240
|
+
if (this.dropdown.isScrollable) {
|
|
241
|
+
let scrollOffset = selectedOption.getBoundingClientRect().top -
|
|
242
|
+
this.dropdownOptions.getBoundingClientRect().top; // scroll to selected option
|
|
243
|
+
scrollOffset -= this.dropdownOptions.clientHeight / 2; // center in dropdown
|
|
244
|
+
this.dropdownOptions.scrollTop = scrollOffset;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
this.input.ariaExpanded = 'true';
|
|
248
|
+
// Handle user declared onOpenEnd if needed
|
|
249
|
+
if (userOnOpenEnd && typeof userOnOpenEnd === 'function')
|
|
250
|
+
userOnOpenEnd.call(this.dropdown, this.el);
|
|
251
|
+
};
|
|
252
|
+
// Add callback for reseting "expanded" state
|
|
253
|
+
dropdownOptions.onCloseEnd = () => {
|
|
254
|
+
this.input.ariaExpanded = 'false';
|
|
255
|
+
// Handle user declared onOpenEnd if needed
|
|
256
|
+
if (userOnCloseEnd && typeof userOnCloseEnd === 'function')
|
|
257
|
+
userOnCloseEnd.call(this.dropdown, this.el);
|
|
258
|
+
};
|
|
259
|
+
// Prevent dropdown from closing too early
|
|
260
|
+
dropdownOptions.closeOnClick = false;
|
|
261
|
+
this.dropdown = Dropdown.init(this.input, dropdownOptions);
|
|
262
|
+
}
|
|
263
|
+
// Add initial selections
|
|
264
|
+
this._setSelectedStates();
|
|
265
|
+
// move label
|
|
266
|
+
if (this.labelEl)
|
|
267
|
+
this.input.after(this.labelEl);
|
|
268
|
+
}
|
|
269
|
+
_addOptionToValues(realOption, virtualOption) {
|
|
270
|
+
this._values.push({ el: realOption, optionEl: virtualOption });
|
|
271
|
+
}
|
|
272
|
+
_removeDropdown() {
|
|
273
|
+
this.wrapper.querySelector('.caret').remove();
|
|
274
|
+
this.input.remove();
|
|
275
|
+
this.dropdownOptions.remove();
|
|
276
|
+
this.wrapper.before(this.el);
|
|
277
|
+
this.wrapper.remove();
|
|
278
|
+
}
|
|
279
|
+
_createAndAppendOptionWithIcon(realOption, type) {
|
|
280
|
+
const li = document.createElement('li');
|
|
281
|
+
li.setAttribute('role', 'option');
|
|
282
|
+
if (realOption.disabled) {
|
|
283
|
+
li.classList.add('disabled');
|
|
284
|
+
li.ariaDisabled = 'true';
|
|
285
|
+
}
|
|
286
|
+
if (type === 'optgroup-option')
|
|
287
|
+
li.classList.add(type);
|
|
288
|
+
// Text / Checkbox
|
|
289
|
+
const span = document.createElement('span');
|
|
290
|
+
span.innerHTML = realOption.innerHTML;
|
|
291
|
+
if (this.isMultiple && !realOption.disabled) {
|
|
292
|
+
span.innerHTML = `<label><input type="checkbox"><span>${realOption.innerHTML}</span></label>`;
|
|
293
|
+
}
|
|
294
|
+
li.appendChild(span);
|
|
295
|
+
// add Icon
|
|
296
|
+
const iconUrl = realOption.getAttribute('data-icon');
|
|
297
|
+
const classes = realOption.getAttribute('class')?.split(' ');
|
|
298
|
+
if (iconUrl) {
|
|
299
|
+
const img = document.createElement('img');
|
|
300
|
+
if (classes)
|
|
301
|
+
img.classList.add(...classes);
|
|
302
|
+
img.src = iconUrl;
|
|
303
|
+
img.ariaHidden = 'true';
|
|
304
|
+
li.prepend(img);
|
|
305
|
+
}
|
|
306
|
+
// Check for multiple type
|
|
307
|
+
this.dropdownOptions.append(li);
|
|
308
|
+
return li;
|
|
309
|
+
}
|
|
310
|
+
_selectValue(value) {
|
|
311
|
+
value.el.selected = true;
|
|
312
|
+
value.optionEl.classList.add('selected');
|
|
313
|
+
value.optionEl.ariaSelected = 'true'; // setAttribute("aria-selected", true);
|
|
314
|
+
const checkbox = value.optionEl.querySelector('input[type="checkbox"]');
|
|
315
|
+
if (checkbox)
|
|
316
|
+
checkbox.checked = true;
|
|
317
|
+
}
|
|
318
|
+
_deselectValue(value) {
|
|
319
|
+
value.el.selected = false;
|
|
320
|
+
value.optionEl.classList.remove('selected');
|
|
321
|
+
value.optionEl.ariaSelected = 'false'; //setAttribute("aria-selected", false);
|
|
322
|
+
const checkbox = value.optionEl.querySelector('input[type="checkbox"]');
|
|
323
|
+
if (checkbox)
|
|
324
|
+
checkbox.checked = false;
|
|
325
|
+
}
|
|
326
|
+
_deselectAll() {
|
|
327
|
+
this._values.forEach((value) => this._deselectValue(value));
|
|
328
|
+
}
|
|
329
|
+
_isValueSelected(value) {
|
|
330
|
+
const realValues = this.getSelectedValues();
|
|
331
|
+
return realValues.some((realValue) => realValue === value.el.value);
|
|
332
|
+
}
|
|
333
|
+
_toggleEntryFromArray(value) {
|
|
334
|
+
if (this._isValueSelected(value))
|
|
335
|
+
this._deselectValue(value);
|
|
336
|
+
else
|
|
337
|
+
this._selectValue(value);
|
|
338
|
+
}
|
|
339
|
+
_getSelectedOptions() {
|
|
340
|
+
// remove null, false, ... values
|
|
341
|
+
return Array.prototype.filter.call(this.el.selectedOptions, (realOption) => realOption);
|
|
342
|
+
}
|
|
343
|
+
_setValueToInput() {
|
|
344
|
+
const selectedRealOptions = this._getSelectedOptions();
|
|
345
|
+
const selectedOptionPairs = this._values.filter((value) => selectedRealOptions.indexOf(value.el) >= 0);
|
|
346
|
+
// Filter not disabled
|
|
347
|
+
const notDisabledOptionPairs = selectedOptionPairs.filter((op) => !op.el.disabled);
|
|
348
|
+
const texts = notDisabledOptionPairs.map((value) => value.optionEl.querySelector('span').innerText.trim());
|
|
349
|
+
// Set input-text to first Option with empty value which indicates a description like "choose your option"
|
|
350
|
+
if (texts.length === 0) {
|
|
351
|
+
const firstDisabledOption = this.el.querySelector('option:disabled');
|
|
352
|
+
if (firstDisabledOption && firstDisabledOption.value === '') {
|
|
353
|
+
this.input.value = firstDisabledOption.innerText;
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
this.input.value = texts.join(', ');
|
|
358
|
+
}
|
|
359
|
+
_setSelectedStates() {
|
|
360
|
+
this._values.forEach((value) => {
|
|
361
|
+
const optionIsSelected = value.el.selected;
|
|
362
|
+
const cb = value.optionEl.querySelector('input[type="checkbox"]');
|
|
363
|
+
if (cb)
|
|
364
|
+
cb.checked = optionIsSelected;
|
|
365
|
+
if (optionIsSelected) {
|
|
366
|
+
this._activateOption(this.dropdownOptions, value.optionEl);
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
value.optionEl.classList.remove('selected');
|
|
370
|
+
value.optionEl.ariaSelected = 'false'; // attr("aria-selected", 'false');
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
_activateOption(ul, li) {
|
|
375
|
+
if (!li)
|
|
376
|
+
return;
|
|
377
|
+
if (!this.isMultiple)
|
|
378
|
+
ul.querySelectorAll('li.selected').forEach((li) => li.classList.remove('selected'));
|
|
379
|
+
li.classList.add('selected');
|
|
380
|
+
li.ariaSelected = 'true';
|
|
381
|
+
}
|
|
382
|
+
getSelectedValues() {
|
|
383
|
+
return this._getSelectedOptions().map((realOption) => realOption.value);
|
|
384
|
+
}
|
|
385
|
+
}
|