UNPKG

slim-select

Version:

Slim advanced select dropdown

1,118 lines (1,113 loc) 87.9 kB
var SlimSelect = (function () { 'use strict'; class CssClasses { constructor(classes) { if (!classes) { classes = {}; } this.main = classes.main || 'ss-main'; this.placeholder = classes.placeholder || 'ss-placeholder'; this.values = classes.values || 'ss-values'; this.single = classes.single || 'ss-single'; this.max = classes.max || 'ss-max'; this.value = classes.value || 'ss-value'; this.valueText = classes.valueText || 'ss-value-text'; this.valueDelete = classes.valueDelete || 'ss-value-delete'; this.valueOut = classes.valueOut || 'ss-value-out'; this.deselect = classes.deselect || 'ss-deselect'; this.deselectPath = classes.deselectPath || 'M10,10 L90,90 M10,90 L90,10'; this.arrow = classes.arrow || 'ss-arrow'; this.arrowClose = classes.arrowClose || 'M10,30 L50,70 L90,30'; this.arrowOpen = classes.arrowOpen || 'M10,70 L50,30 L90,70'; this.content = classes.content || 'ss-content'; this.openAbove = classes.openAbove || 'ss-open-above'; this.openBelow = classes.openBelow || 'ss-open-below'; this.search = classes.search || 'ss-search'; this.searchHighlighter = classes.searchHighlighter || 'ss-search-highlight'; this.searching = classes.searching || 'ss-searching'; this.addable = classes.addable || 'ss-addable'; this.addablePath = classes.addablePath || 'M50,10 L50,90 M10,50 L90,50'; this.list = classes.list || 'ss-list'; this.optgroup = classes.optgroup || 'ss-optgroup'; this.optgroupLabel = classes.optgroupLabel || 'ss-optgroup-label'; this.optgroupLabelText = classes.optgroupLabelText || 'ss-optgroup-label-text'; this.optgroupActions = classes.optgroupActions || 'ss-optgroup-actions'; this.optgroupSelectAll = classes.optgroupSelectAll || 'ss-selectall'; this.optgroupSelectAllBox = classes.optgroupSelectAllBox || 'M60,10 L10,10 L10,90 L90,90 L90,50'; this.optgroupSelectAllCheck = classes.optgroupSelectAllCheck || 'M30,45 L50,70 L90,10'; this.optgroupClosable = classes.optgroupClosable || 'ss-closable'; this.option = classes.option || 'ss-option'; this.optionDelete = classes.optionDelete || 'M10,10 L90,90 M10,90 L90,10'; this.highlighted = classes.highlighted || 'ss-highlighted'; this.open = classes.open || 'ss-open'; this.close = classes.close || 'ss-close'; this.selected = classes.selected || 'ss-selected'; this.error = classes.error || 'ss-error'; this.disabled = classes.disabled || 'ss-disabled'; this.hide = classes.hide || 'ss-hide'; } } function generateID() { return Math.random().toString(36).substring(2, 10); } function hasClassInTree(element, className) { function hasClass(e, c) { if (c && e && e.classList && e.classList.contains(c)) { return e; } if (c && e && e.dataset && e.dataset.id && e.dataset.id === className) { return e; } return null; } function parentByClass(e, c) { if (!e || e === document) { return null; } else if (hasClass(e, c)) { return e; } else { return parentByClass(e.parentNode, c); } } return hasClass(element, className) || parentByClass(element, className); } function debounce(func, wait = 50, immediate = false) { let timeout; return function (...args) { const context = self; const later = () => { timeout = null; if (!immediate) { func.apply(context, args); } }; const callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) { func.apply(context, args); } }; } function isEqual(a, b) { return JSON.stringify(a) === JSON.stringify(b); } function kebabCase(str) { const result = str.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g, (match) => '-' + match.toLowerCase()); return str[0] === str[0].toUpperCase() ? result.substring(1) : result; } class Optgroup { constructor(optgroup) { this.id = !optgroup.id || optgroup.id === '' ? generateID() : optgroup.id; this.label = optgroup.label || ''; this.selectAll = optgroup.selectAll === undefined ? false : optgroup.selectAll; this.selectAllText = optgroup.selectAllText || 'Select All'; this.closable = optgroup.closable || 'off'; this.options = []; if (optgroup.options) { for (const o of optgroup.options) { this.options.push(new Option(o)); } } } } class Option { constructor(option) { this.id = !option.id || option.id === '' ? generateID() : option.id; this.value = option.value === undefined ? option.text : option.value; this.text = option.text || ''; this.html = option.html || ''; this.selected = option.selected !== undefined ? option.selected : false; this.display = option.display !== undefined ? option.display : true; this.disabled = option.disabled !== undefined ? option.disabled : false; this.mandatory = option.mandatory !== undefined ? option.mandatory : false; this.placeholder = option.placeholder !== undefined ? option.placeholder : false; this.class = option.class || ''; this.style = option.style || ''; this.data = option.data || {}; } } class Store { constructor(type, data) { this.selectType = 'single'; this.data = []; this.selectedOrder = []; this.selectType = type; this.setData(data); } validateDataArray(data) { if (!Array.isArray(data)) { return new Error('Data must be an array'); } for (let dataObj of data) { if (dataObj instanceof Optgroup || 'label' in dataObj) { if (!('label' in dataObj)) { return new Error('Optgroup must have a label'); } if ('options' in dataObj && dataObj.options) { for (let option of dataObj.options) { const validationError = this.validateOption(option); if (validationError) { return validationError; } } } } else if (dataObj instanceof Option || 'text' in dataObj) { const validationError = this.validateOption(dataObj); if (validationError) { return validationError; } } else { return new Error('Data object must be a valid optgroup or option'); } } return null; } validateOption(option) { if (!('text' in option)) { return new Error('Option must have a text'); } return null; } partialToFullData(data) { let dataFinal = []; data.forEach((dataObj) => { if (dataObj instanceof Optgroup || 'label' in dataObj) { let optOptions = []; if ('options' in dataObj && dataObj.options) { dataObj.options.forEach((option) => { optOptions.push(new Option(option)); }); } if (optOptions.length > 0) { dataFinal.push(new Optgroup(dataObj)); } } if (dataObj instanceof Option || 'text' in dataObj) { dataFinal.push(new Option(dataObj)); } }); return dataFinal; } setData(data) { this.data = this.partialToFullData(data); if (this.selectType === 'single') { this.setSelectedBy('id', this.getSelected()); } } getData() { return this.filter(null, true); } getDataOptions() { return this.filter(null, false); } addOption(option, addToStart = false) { if (addToStart) { let data = [new Option(option)]; this.setData(data.concat(this.getData())); } else { this.setData(this.getData().concat(new Option(option))); } } setSelectedBy(selectedType, selectedValues) { let firstOption = null; let hasSelected = false; const selectedObjects = []; for (let dataObj of this.data) { if (dataObj instanceof Optgroup) { for (let option of dataObj.options) { if (!firstOption) { firstOption = option; } option.selected = hasSelected ? false : selectedValues.includes(option[selectedType]); if (option.selected) { selectedObjects.push(option); if (this.selectType === 'single') { hasSelected = true; } } } } if (dataObj instanceof Option) { if (!firstOption) { firstOption = dataObj; } dataObj.selected = hasSelected ? false : selectedValues.includes(dataObj[selectedType]); if (dataObj.selected) { selectedObjects.push(dataObj); if (this.selectType === 'single') { hasSelected = true; } } } } if (this.selectType === 'single' && firstOption && !hasSelected) { firstOption.selected = true; selectedObjects.push(firstOption); } const selectedIds = selectedValues.map((value) => { var _a; return ((_a = selectedObjects.find((option) => option[selectedType] === value)) === null || _a === void 0 ? void 0 : _a.id) || ''; }); this.selectedOrder = selectedIds; } getSelected() { return this.getSelectedOptions().map((option) => option.id); } getSelectedValues() { return this.getSelectedOptions().map((option) => option.value); } getSelectedOptions() { return this.filter((opt) => { return opt.selected; }, false); } getOptgroupByID(id) { for (let dataObj of this.data) { if (dataObj instanceof Optgroup && dataObj.id === id) { return dataObj; } } return null; } getOptionByID(id) { let options = this.filter((opt) => { return opt.id === id; }, false); return options.length ? options[0] : null; } getSelectType() { return this.selectType; } getFirstOption() { let option = null; for (let dataObj of this.data) { if (dataObj instanceof Optgroup) { option = dataObj.options[0]; } else if (dataObj instanceof Option) { option = dataObj; } if (option) { break; } } return option; } search(search, searchFilter) { search = search.trim(); if (search === '') { return this.getData(); } return this.filter((opt) => { return searchFilter(opt, search); }, true); } filter(filter, includeOptgroup) { const dataSearch = []; this.data.forEach((dataObj) => { if (dataObj instanceof Optgroup) { let optOptions = []; dataObj.options.forEach((option) => { if (!filter || filter(option)) { if (!includeOptgroup) { dataSearch.push(new Option(option)); } else { optOptions.push(new Option(option)); } } }); if (optOptions.length > 0) { let optgroup = new Optgroup(dataObj); optgroup.options = optOptions; dataSearch.push(optgroup); } } if (dataObj instanceof Option) { if (!filter || filter(dataObj)) { dataSearch.push(new Option(dataObj)); } } }); return dataSearch; } selectedOrderOptions(options) { const newOrder = []; this.selectedOrder.forEach((id) => { const option = options.find((opt) => opt.id === id); if (option) { newOrder.push(option); } }); options.forEach((option) => { let isIn = false; newOrder.forEach((selectedOption) => { if (option.id === selectedOption.id) { isIn = true; return; } }); if (!isIn) { newOrder.push(option); } }); return newOrder; } } class Render { constructor(settings, classes, store, callbacks) { this.store = store; this.settings = settings; this.classes = classes; this.callbacks = callbacks; this.main = this.mainDiv(); this.content = this.contentDiv(); this.updateClassStyles(); this.updateAriaAttributes(); if (this.settings.contentLocation) { this.settings.contentLocation.appendChild(this.content.main); } } enable() { this.main.main.classList.remove(this.classes.disabled); this.content.search.input.disabled = false; } disable() { this.main.main.classList.add(this.classes.disabled); this.content.search.input.disabled = true; } open() { this.main.arrow.path.setAttribute('d', this.classes.arrowOpen); this.main.main.classList.add(this.settings.openPosition === 'up' ? this.classes.openAbove : this.classes.openBelow); this.main.main.setAttribute('aria-expanded', 'true'); this.moveContent(); const selectedOptions = this.store.getSelectedOptions(); if (selectedOptions.length) { const selectedId = selectedOptions[selectedOptions.length - 1].id; const selectedOption = this.content.list.querySelector('[data-id="' + selectedId + '"]'); if (selectedOption) { this.ensureElementInView(this.content.list, selectedOption); } } } close() { this.main.main.classList.remove(this.classes.openAbove); this.main.main.classList.remove(this.classes.openBelow); this.main.main.setAttribute('aria-expanded', 'false'); this.content.main.classList.remove(this.classes.openAbove); this.content.main.classList.remove(this.classes.openBelow); this.main.arrow.path.setAttribute('d', this.classes.arrowClose); } updateClassStyles() { this.main.main.className = ''; this.main.main.removeAttribute('style'); this.content.main.className = ''; this.content.main.removeAttribute('style'); this.main.main.classList.add(this.classes.main); this.content.main.classList.add(this.classes.content); if (this.settings.style !== '') { this.main.main.style.cssText = this.settings.style; this.content.main.style.cssText = this.settings.style; } if (this.settings.class.length) { for (const c of this.settings.class) { if (c.trim() !== '') { this.main.main.classList.add(c.trim()); this.content.main.classList.add(c.trim()); } } } if (this.settings.contentPosition === 'relative' || this.settings.contentPosition === 'fixed') { this.content.main.classList.add('ss-' + this.settings.contentPosition); } } updateAriaAttributes() { this.main.main.role = 'combobox'; this.main.main.setAttribute('aria-haspopup', 'listbox'); this.main.main.setAttribute('aria-controls', this.content.main.id); this.main.main.setAttribute('aria-expanded', 'false'); this.content.main.setAttribute('role', 'listbox'); } mainDiv() { var _a; const main = document.createElement('div'); main.dataset.id = this.settings.id; main.setAttribute('aria-label', this.settings.ariaLabel); main.tabIndex = 0; main.onkeydown = (e) => { switch (e.key) { case 'ArrowUp': case 'ArrowDown': this.callbacks.open(); e.key === 'ArrowDown' ? this.highlight('down') : this.highlight('up'); return false; case 'Tab': this.callbacks.close(); return true; case 'Enter': case ' ': this.callbacks.open(); const highlighted = this.content.list.querySelector('.' + this.classes.highlighted); if (highlighted) { highlighted.click(); } return false; case 'Escape': this.callbacks.close(); return false; } if (e.key.length === 1) { this.callbacks.open(); } return true; }; main.onclick = (e) => { if (this.settings.disabled) { return; } this.settings.isOpen ? this.callbacks.close() : this.callbacks.open(); }; const values = document.createElement('div'); values.classList.add(this.classes.values); main.appendChild(values); const deselect = document.createElement('div'); deselect.classList.add(this.classes.deselect); const selectedOptions = (_a = this.store) === null || _a === void 0 ? void 0 : _a.getSelectedOptions(); if (!this.settings.allowDeselect || (this.settings.isMultiple && selectedOptions && selectedOptions.length <= 0)) { deselect.classList.add(this.classes.hide); } else { deselect.classList.remove(this.classes.hide); } deselect.onclick = (e) => { e.stopPropagation(); if (this.settings.disabled) { return; } let shouldDelete = true; const before = this.store.getSelectedOptions(); const after = []; if (this.callbacks.beforeChange) { shouldDelete = this.callbacks.beforeChange(after, before) === true; } if (shouldDelete) { if (this.settings.isMultiple) { this.callbacks.setSelected([], false); this.updateDeselectAll(); } else { const firstOption = this.store.getFirstOption(); const id = firstOption ? firstOption.id : ''; this.callbacks.setSelected(id, false); } if (this.settings.closeOnSelect) { this.callbacks.close(); } if (this.callbacks.afterChange) { this.callbacks.afterChange(this.store.getSelectedOptions()); } } }; const deselectSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); deselectSvg.setAttribute('viewBox', '0 0 100 100'); const deselectPath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); deselectPath.setAttribute('d', this.classes.deselectPath); deselectSvg.appendChild(deselectPath); deselect.appendChild(deselectSvg); main.appendChild(deselect); const arrow = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); arrow.classList.add(this.classes.arrow); arrow.setAttribute('viewBox', '0 0 100 100'); const arrowPath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); arrowPath.setAttribute('d', this.classes.arrowClose); if (this.settings.alwaysOpen) { arrow.classList.add(this.classes.hide); } arrow.appendChild(arrowPath); main.appendChild(arrow); return { main: main, values: values, deselect: { main: deselect, svg: deselectSvg, path: deselectPath }, arrow: { main: arrow, path: arrowPath } }; } mainFocus(eventType) { if (eventType !== 'click') { this.main.main.focus({ preventScroll: true }); } } placeholder() { const placeholderOption = this.store.filter((o) => o.placeholder, false); let placeholderText = this.settings.placeholderText; if (placeholderOption.length) { if (placeholderOption[0].html !== '') { placeholderText = placeholderOption[0].html; } else if (placeholderOption[0].text !== '') { placeholderText = placeholderOption[0].text; } } const placeholder = document.createElement('div'); placeholder.classList.add(this.classes.placeholder); placeholder.innerHTML = placeholderText; return placeholder; } renderValues() { if (!this.settings.isMultiple) { this.renderSingleValue(); return; } this.renderMultipleValues(); this.updateDeselectAll(); } renderSingleValue() { const selected = this.store.filter((o) => { return o.selected && !o.placeholder; }, false); const selectedSingle = selected.length > 0 ? selected[0] : null; if (!selectedSingle) { this.main.values.innerHTML = this.placeholder().outerHTML; } else { const singleValue = document.createElement('div'); singleValue.classList.add(this.classes.single); if (selectedSingle.html) { singleValue.innerHTML = selectedSingle.html; } else { singleValue.innerText = selectedSingle.text; } this.main.values.innerHTML = singleValue.outerHTML; } if (!this.settings.allowDeselect || !selected.length) { this.main.deselect.main.classList.add(this.classes.hide); } else { this.main.deselect.main.classList.remove(this.classes.hide); } } renderMultipleValues() { let currentNodes = this.main.values.childNodes; let selectedOptions = this.store.filter((opt) => { return opt.selected && opt.display; }, false); if (selectedOptions.length === 0) { this.main.values.innerHTML = this.placeholder().outerHTML; return; } else { const placeholder = this.main.values.querySelector('.' + this.classes.placeholder); if (placeholder) { placeholder.remove(); } } if (selectedOptions.length > this.settings.maxValuesShown) { const singleValue = document.createElement('div'); singleValue.classList.add(this.classes.max); singleValue.textContent = this.settings.maxValuesMessage.replace('{number}', selectedOptions.length.toString()); this.main.values.innerHTML = singleValue.outerHTML; return; } else { const maxValuesMessage = this.main.values.querySelector('.' + this.classes.max); if (maxValuesMessage) { maxValuesMessage.remove(); } } if (this.settings.keepOrder) { selectedOptions = this.store.selectedOrderOptions(selectedOptions); } let removeNodes = []; for (let i = 0; i < currentNodes.length; i++) { const node = currentNodes[i]; const id = node.getAttribute('data-id'); if (id) { const found = selectedOptions.filter((opt) => { return opt.id === id; }, false); if (!found.length) { removeNodes.push(node); } } } for (const n of removeNodes) { n.classList.add(this.classes.valueOut); setTimeout(() => { if (this.main.values.hasChildNodes() && this.main.values.contains(n)) { this.main.values.removeChild(n); } }, 100); } currentNodes = this.main.values.childNodes; for (let d = 0; d < selectedOptions.length; d++) { let shouldAdd = true; for (let i = 0; i < currentNodes.length; i++) { if (selectedOptions[d].id === String(currentNodes[i].dataset.id)) { shouldAdd = false; } } if (shouldAdd) { if (this.settings.keepOrder) { this.main.values.appendChild(this.multipleValue(selectedOptions[d])); } else { if (currentNodes.length === 0) { this.main.values.appendChild(this.multipleValue(selectedOptions[d])); } else if (d === 0) { this.main.values.insertBefore(this.multipleValue(selectedOptions[d]), currentNodes[d]); } else { currentNodes[d - 1].insertAdjacentElement('afterend', this.multipleValue(selectedOptions[d])); } } } } } multipleValue(option) { const value = document.createElement('div'); value.classList.add(this.classes.value); value.dataset.id = option.id; const text = document.createElement('div'); text.classList.add(this.classes.valueText); text.textContent = option.text; value.appendChild(text); if (!option.mandatory) { const deleteDiv = document.createElement('div'); deleteDiv.classList.add(this.classes.valueDelete); deleteDiv.onclick = (e) => { e.preventDefault(); e.stopPropagation(); if (this.settings.disabled) { return; } let shouldDelete = true; const before = this.store.getSelectedOptions(); const after = before.filter((o) => { return o.selected && o.id !== option.id; }, true); if (this.settings.minSelected && after.length < this.settings.minSelected) { return; } if (this.callbacks.beforeChange) { shouldDelete = this.callbacks.beforeChange(after, before) === true; } if (shouldDelete) { let selectedIds = []; for (const o of after) { if (o instanceof Optgroup) { for (const c of o.options) { selectedIds.push(c.id); } } if (o instanceof Option) { selectedIds.push(o.id); } } this.callbacks.setSelected(selectedIds, false); if (this.settings.closeOnSelect) { this.callbacks.close(); } if (this.callbacks.afterChange) { this.callbacks.afterChange(after); } this.updateDeselectAll(); } }; const deleteSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); deleteSvg.setAttribute('viewBox', '0 0 100 100'); const deletePath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); deletePath.setAttribute('d', this.classes.optionDelete); deleteSvg.appendChild(deletePath); deleteDiv.appendChild(deleteSvg); value.appendChild(deleteDiv); } return value; } contentDiv() { const main = document.createElement('div'); main.dataset.id = this.settings.id; const search = this.searchDiv(); main.appendChild(search.main); const list = this.listDiv(); main.appendChild(list); return { main: main, search: search, list: list }; } moveContent() { if (this.settings.contentPosition === 'relative') { this.moveContentBelow(); return; } if (this.settings.openPosition === 'down') { this.moveContentBelow(); return; } else if (this.settings.openPosition === 'up') { this.moveContentAbove(); return; } if (this.putContent() === 'up') { this.moveContentAbove(); } else { this.moveContentBelow(); } } searchDiv() { const main = document.createElement('div'); const input = document.createElement('input'); const addable = document.createElement('div'); main.classList.add(this.classes.search); const searchReturn = { main, input }; if (!this.settings.showSearch) { main.classList.add(this.classes.hide); input.readOnly = true; } input.type = 'search'; input.placeholder = this.settings.searchPlaceholder; input.tabIndex = -1; input.setAttribute('aria-label', this.settings.searchPlaceholder); input.setAttribute('autocapitalize', 'off'); input.setAttribute('autocomplete', 'off'); input.setAttribute('autocorrect', 'off'); input.oninput = debounce((e) => { this.callbacks.search(e.target.value); }, 100); input.onkeydown = (e) => { switch (e.key) { case 'ArrowUp': case 'ArrowDown': e.key === 'ArrowDown' ? this.highlight('down') : this.highlight('up'); return false; case 'Tab': this.callbacks.close(); return true; case 'Escape': this.callbacks.close(); return false; case ' ': const highlighted = this.content.list.querySelector('.' + this.classes.highlighted); if (highlighted) { highlighted.click(); return false; } return true; case 'Enter': if (this.callbacks.addable) { addable.click(); return false; } else { const highlighted = this.content.list.querySelector('.' + this.classes.highlighted); if (highlighted) { highlighted.click(); return false; } } return true; } return true; }; main.appendChild(input); if (this.callbacks.addable) { addable.classList.add(this.classes.addable); const plus = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); plus.setAttribute('viewBox', '0 0 100 100'); const plusPath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); plusPath.setAttribute('d', this.classes.addablePath); plus.appendChild(plusPath); addable.appendChild(plus); addable.onclick = (e) => { e.preventDefault(); e.stopPropagation(); if (!this.callbacks.addable) { return; } const inputValue = this.content.search.input.value.trim(); if (inputValue === '') { this.content.search.input.focus(); return; } const runFinish = (oo) => { let newOption = new Option(oo); this.callbacks.addOption(newOption); if (this.settings.isMultiple) { let ids = this.store.getSelected(); ids.push(newOption.id); this.callbacks.setSelected(ids, true); } else { this.callbacks.setSelected([newOption.id], true); } this.callbacks.search(''); if (this.settings.closeOnSelect) { setTimeout(() => { this.callbacks.close(); }, 100); } }; const addableValue = this.callbacks.addable(inputValue); if (addableValue === false || addableValue === undefined || addableValue === null) { return; } if (addableValue instanceof Promise) { addableValue.then((value) => { if (typeof value === 'string') { runFinish({ text: value, value: value }); } else if (addableValue instanceof Error) { this.renderError(addableValue.message); } else { runFinish(value); } }); } else if (typeof addableValue === 'string') { runFinish({ text: addableValue, value: addableValue }); } else if (addableValue instanceof Error) { this.renderError(addableValue.message); } else { runFinish(addableValue); } return; }; main.appendChild(addable); searchReturn.addable = { main: addable, svg: plus, path: plusPath }; } return searchReturn; } searchFocus() { this.content.search.input.focus(); } getOptions(notPlaceholder = false, notDisabled = false, notHidden = false) { let query = '.' + this.classes.option; if (notPlaceholder) { query += ':not(.' + this.classes.placeholder + ')'; } if (notDisabled) { query += ':not(.' + this.classes.disabled + ')'; } if (notHidden) { query += ':not(.' + this.classes.hide + ')'; } return Array.from(this.content.list.querySelectorAll(query)); } highlight(dir) { const options = this.getOptions(true, true, true); if (options.length === 0) { return; } if (options.length === 1) { if (!options[0].classList.contains(this.classes.highlighted)) { options[0].classList.add(this.classes.highlighted); return; } } let highlighted = false; for (const o of options) { if (o.classList.contains(this.classes.highlighted)) { highlighted = true; } } if (!highlighted) { for (const o of options) { if (o.classList.contains(this.classes.selected)) { o.classList.add(this.classes.highlighted); break; } } } for (let i = 0; i < options.length; i++) { if (options[i].classList.contains(this.classes.highlighted)) { const prevOption = options[i]; prevOption.classList.remove(this.classes.highlighted); const prevParent = prevOption.parentElement; if (prevParent && prevParent.classList.contains(this.classes.open)) { const optgroupLabel = prevParent.querySelector('.' + this.classes.optgroupLabel); if (optgroupLabel) { optgroupLabel.click(); } } let selectOption = options[dir === 'down' ? (i + 1 < options.length ? i + 1 : 0) : i - 1 >= 0 ? i - 1 : options.length - 1]; selectOption.classList.add(this.classes.highlighted); this.ensureElementInView(this.content.list, selectOption); const selectParent = selectOption.parentElement; if (selectParent && selectParent.classList.contains(this.classes.close)) { const optgroupLabel = selectParent.querySelector('.' + this.classes.optgroupLabel); if (optgroupLabel) { optgroupLabel.click(); } } return; } } options[dir === 'down' ? 0 : options.length - 1].classList.add(this.classes.highlighted); this.ensureElementInView(this.content.list, options[dir === 'down' ? 0 : options.length - 1]); } listDiv() { const options = document.createElement('div'); options.classList.add(this.classes.list); return options; } renderError(error) { this.content.list.innerHTML = ''; const errorDiv = document.createElement('div'); errorDiv.classList.add(this.classes.error); errorDiv.textContent = error; this.content.list.appendChild(errorDiv); } renderSearching() { this.content.list.innerHTML = ''; const searchingDiv = document.createElement('div'); searchingDiv.classList.add(this.classes.searching); searchingDiv.textContent = this.settings.searchingText; this.content.list.appendChild(searchingDiv); } renderOptions(data) { this.content.list.innerHTML = ''; if (data.length === 0) { const noResults = document.createElement('div'); noResults.classList.add(this.classes.search); if (this.callbacks.addable) { noResults.innerHTML = this.settings.addableText.replace('{value}', this.content.search.input.value); } else { noResults.innerHTML = this.settings.searchText; } this.content.list.appendChild(noResults); return; } if (this.settings.allowDeselect && !this.settings.isMultiple) { const placeholderOption = this.store.filter((o) => o.placeholder, false); if (!placeholderOption.length) { this.store.addOption(new Option({ text: '', value: '', selected: false, placeholder: true }), true); } } for (const d of data) { if (d instanceof Optgroup) { const optgroupEl = document.createElement('div'); optgroupEl.classList.add(this.classes.optgroup); const optgroupLabel = document.createElement('div'); optgroupLabel.classList.add(this.classes.optgroupLabel); optgroupEl.appendChild(optgroupLabel); const optgroupLabelText = document.createElement('div'); optgroupLabelText.classList.add(this.classes.optgroupLabelText); optgroupLabelText.textContent = d.label; optgroupLabel.appendChild(optgroupLabelText); const optgroupActions = document.createElement('div'); optgroupActions.classList.add(this.classes.optgroupActions); optgroupLabel.appendChild(optgroupActions); if (this.settings.isMultiple && d.selectAll) { const selectAll = document.createElement('div'); selectAll.classList.add(this.classes.optgroupSelectAll); let allSelected = true; for (const o of d.options) { if (!o.selected) { allSelected = false; break; } } if (allSelected) { selectAll.classList.add(this.classes.selected); } const selectAllText = document.createElement('span'); selectAllText.textContent = d.selectAllText; selectAll.appendChild(selectAllText); const selectAllSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); selectAllSvg.setAttribute('viewBox', '0 0 100 100'); selectAll.appendChild(selectAllSvg); const selectAllBox = document.createElementNS('http://www.w3.org/2000/svg', 'path'); selectAllBox.setAttribute('d', this.classes.optgroupSelectAllBox); selectAllSvg.appendChild(selectAllBox); const selectAllCheck = document.createElementNS('http://www.w3.org/2000/svg', 'path'); selectAllCheck.setAttribute('d', this.classes.optgroupSelectAllCheck); selectAllSvg.appendChild(selectAllCheck); selectAll.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const currentSelected = this.store.getSelected(); if (allSelected) { const newSelected = currentSelected.filter((s) => { for (const o of d.options) { if (s === o.id) { return false; } } return true; }); this.callbacks.setSelected(newSelected, true); return; } else { const newSelected = currentSelected.concat(d.options.map((o) => o.id)); for (const o of d.options) { if (!this.store.getOptionByID(o.id)) { this.callbacks.addOption(o); } } this.callbacks.setSelected(newSelected, true); return; } }); optgroupActions.appendChild(selectAll); } if (d.closable !== 'off') { const optgroupClosable = document.createElement('div'); optgroupClosable.classList.add(this.classes.optgroupClosable); const optgroupClosableSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); optgroupClosableSvg.setAttribute('viewBox', '0 0 100 100'); optgroupClosableSvg.classList.add(this.classes.arrow); optgroupClosable.appendChild(optgroupClosableSvg); const optgroupClosableArrow = document.createElementNS('http://www.w3.org/2000/svg', 'path'); optgroupClosableSvg.appendChild(optgroupClosableArrow); if (d.options.some((o) => o.selected) || this.content.search.input.value.trim() !== '') { optgroupClosable.classList.add(this.classes.open); optgroupClosableArrow.setAttribute('d', this.classes.arrowOpen); } else if (d.closable === 'open') { optgroupEl.classList.add(this.classes.open); optgroupClosableArrow.setAttribute('d', this.classes.arrowOpen); } else if (d.closable === 'close') { optgroupEl.classList.add(this.classes.close); optgroupClosableArrow.setAttribute('d', this.classes.arrowClose); }