UNPKG

slim-select

Version:

Slim advanced select dropdown

1,122 lines (1,119 loc) 72.3 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.SlimSelect = factory()); })(this, (function () { 'use strict'; 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 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.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.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) { return this.validateOption(option); } } } else if (dataObj instanceof Option || 'text' in dataObj) { return this.validateOption(dataObj); } 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('value', this.getSelected()); } } getData() { return this.filter(null, true); } getDataOptions() { return this.filter(null, false); } addOption(option) { this.setData(this.getData().concat(new Option(option))); } setSelectedBy(selectedType, selectedValues) { let firstOption = null; let hasSelected = false; 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 && this.selectType === 'single') { hasSelected = true; } } } if (dataObj instanceof Option) { if (!firstOption) { firstOption = dataObj; } dataObj.selected = hasSelected ? false : selectedValues.includes(dataObj[selectedType]); if (dataObj.selected && this.selectType === 'single') { hasSelected = true; } } } if (this.selectType === 'single' && firstOption && !hasSelected) { firstOption.selected = true; } } getSelected() { let selectedOptions = this.getSelectedOptions(); let selectedValues = []; selectedOptions.forEach((option) => { selectedValues.push(option.value); }); return selectedValues; } getSelectedOptions() { return this.filter((opt) => { return opt.selected; }, false); } getSelectedIDs() { let selectedOptions = this.getSelectedOptions(); let selectedIDs = []; selectedOptions.forEach((op) => { selectedIDs.push(op.id); }); return selectedIDs; } 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; } 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) { if (!includeOptgroup) { dataObj.options.forEach((option) => { if (filter && filter(option)) { dataSearch.push(option); } }); } else { let optOptions = []; dataObj.options.forEach((option) => { if (!filter || filter(option)) { optOptions.push(new Option(option)); } }); if (optOptions.length > 0) { dataSearch.push(new Optgroup(dataObj)); } } } if (dataObj instanceof Option) { if (!filter || filter(dataObj)) { dataSearch.push(new Option(dataObj)); } } }); return dataSearch; } } class Render { constructor(settings, store, callbacks) { this.classes = { main: 'ss-main', placeholder: 'ss-placeholder', values: 'ss-values', single: 'ss-single', value: 'ss-value', valueText: 'ss-value-text', valueDelete: 'ss-value-delete', valueOut: 'ss-value-out', deselect: 'ss-deselect', deselectPath: 'M10,10 L90,90 M10,90 L90,10', arrow: 'ss-arrow', arrowClose: 'M10,30 L50,70 L90,30', arrowOpen: 'M10,70 L50,30 L90,70', content: 'ss-content', openAbove: 'ss-open-above', openBelow: 'ss-open-below', search: 'ss-search', searchHighlighter: 'ss-search-highlight', searching: 'ss-searching', addable: 'ss-addable', addablePath: 'M50,10 L50,90 M10,50 L90,50', list: 'ss-list', optgroup: 'ss-optgroup', optgroupLabel: 'ss-optgroup-label', optgroupLabelText: 'ss-optgroup-label-text', optgroupActions: 'ss-optgroup-actions', optgroupSelectAll: 'ss-selectall', optgroupSelectAllBox: 'M60,10 L10,10 L10,90 L90,90 L90,50', optgroupSelectAllCheck: 'M30,45 L50,70 L90,10', optgroupClosable: 'ss-closable', option: 'ss-option', optionDelete: 'M10,10 L90,90 M10,90 L90,10', highlighted: 'ss-highlighted', open: 'ss-open', close: 'ss-close', selected: 'ss-selected', error: 'ss-error', disabled: 'ss-disabled', hide: 'ss-hide', }; this.store = store; this.settings = settings; this.callbacks = callbacks; this.main = this.mainDiv(); this.content = this.contentDiv(); 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.moveContent(); this.renderOptions(this.store.getData()); if (this.settings.contentPosition === 'relative') { this.moveContentBelow(); } else if (this.settings.openPosition.toLowerCase() === 'up') { this.moveContentAbove(); } else if (this.settings.openPosition.toLowerCase() === 'down') { this.moveContentBelow(); } else { if (this.putContent(this.content.main, this.settings.isOpen) === 'up') { this.moveContentAbove(); } else { this.moveContentBelow(); } } 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.content.main.classList.remove(this.classes.openAbove); this.content.main.classList.remove(this.classes.openBelow); this.main.arrow.path.setAttribute('d', this.classes.arrowClose); } mainDiv() { const main = document.createElement('div'); main.tabIndex = 0; main.style.cssText = this.settings.style !== '' ? this.settings.style : ''; main.className = ''; main.classList.add(this.settings.id); main.classList.add(this.classes.main); if (this.settings.class) { for (const c of this.settings.class) { if (c.trim() !== '') { main.classList.add(c.trim()); } } } main.onfocus = () => { if (this.settings.triggerFocus) { this.callbacks.open(); } }; 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': const highlighted = this.content.list.querySelector('.' + this.classes.highlighted); if (highlighted) { highlighted.click(); } return false; case 'Escape': this.callbacks.close(); return false; } }; main.onclick = (e) => { if (!this.settings.isEnabled) { 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); if (!this.settings.allowDeselect || this.settings.isMultiple) { deselect.classList.add(this.classes.hide); } deselect.onclick = (e) => { e.stopPropagation(); if (!this.settings.isEnabled) { return; } let shouldDelete = true; const before = this.store.getSelectedOptions(); const after = []; if (this.callbacks.beforeChange) { shouldDelete = this.callbacks.beforeChange(after, before) === true; } if (shouldDelete) { this.callbacks.setSelected(['']); if (this.settings.closeOnSelect) { this.callbacks.close(); } if (this.callbacks.afterChange) { this.callbacks.afterChange(after); } } }; 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(trigger) { if (!trigger) { this.settings.triggerFocus = false; } this.main.main.focus({ preventScroll: true }); this.settings.triggerFocus = 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(); } 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); singleValue.innerHTML = selectedSingle.html ? selectedSingle.html : 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(); } } 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(() => { 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 (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.innerHTML = 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(); 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 selectedValues = []; for (const o of after) { if (o instanceof Optgroup) { for (const c of o.options) { selectedValues.push(c.value); } } if (o instanceof Option) { selectedValues.push(o.value); } } this.callbacks.setSelected(selectedValues); if (this.settings.closeOnSelect) { this.callbacks.close(); } if (this.callbacks.afterChange) { this.callbacks.afterChange(after); } } }; 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.classList.add(this.classes.content); main.dataset.id = this.settings.id; if (this.settings.style !== '') { main.style.cssText = this.settings.style; } if (this.settings.contentPosition === 'relative') { main.classList.add('ss-' + this.settings.contentPosition); } if (this.settings.class.length) { for (const c of this.settings.class) { if (c.trim() !== '') { main.classList.add(c.trim()); } } } 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') { return; } const containerRect = this.main.main.getBoundingClientRect(); this.content.main.style.top = containerRect.top + containerRect.height + window.scrollY + 'px'; this.content.main.style.left = containerRect.left + window.scrollX + 'px'; this.content.main.style.width = containerRect.width + 'px'; } 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': this.callbacks.open(); 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 'Enter': if (this.callbacks.addable && e.ctrlKey) { addable.click(); } else { const highlighted = this.content.list.querySelector('.' + this.classes.highlighted); if (highlighted) { highlighted.click(); } } return false; } }; input.onfocus = () => { if (this.settings.isOpen) { return; } this.callbacks.open(); }; 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 values = this.store.getSelected(); values.push(newOption.value); this.callbacks.setSelected(values); } else { this.callbacks.setSelected([newOption.value]); } this.callbacks.search(''); if (this.settings.closeOnSelect) { setTimeout(() => { this.callbacks.close(); }, 100); } }; const addableValue = this.callbacks.addable(inputValue); if (addableValue instanceof Promise) { addableValue.then((value) => { if (typeof value === 'string') { runFinish({ text: value, value: value, }); } else { runFinish(value); } }); } else if (typeof addableValue === 'string') { runFinish({ text: addableValue, value: addableValue, }); } else { runFinish(addableValue); } return; }; main.appendChild(addable); searchReturn.addable = { main: addable, svg: plus, path: plusPath, }; } return searchReturn; } searchFocus(trigger) { if (!trigger) { this.settings.triggerFocus = false; } this.content.search.input.focus(); this.settings.triggerFocus = true; } 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; } } 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); options.setAttribute('role', 'listbox'); 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.option); noResults.classList.add(this.classes.disabled); noResults.innerHTML = this.settings.searchText; this.content.list.appendChild(noResults); return; } 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 = 'Select All'; 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.value) { return false; } } return true; }); this.callbacks.setSelected(newSelected); return; } else { const newSelected = currentSelected.concat(d.options.map((o) => o.value)); this.callbacks.setSelected(newSelected); } }); 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'); optgroupClosableArrow.setAttribute('d', this.classes.arrow); optgroupClosableSvg.appendChild(optgroupClosableArrow); if (d.options.some((o) => o.selected)) { 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); } optgroupLabel.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); if (optgroupEl.classList.contains(this.classes.close)) { optgroupEl.classList.remove(this.classes.close); optgroupEl.classList.add(this.classes.open); optgroupClosableArrow.setAttribute('d', this.classes.arrowOpen); } else { optgroupEl.classList.remove(this.classes.open); optgroupEl.classList.add(this.classes.close); optgroupClosableArrow.setAttribute('d', this.classes.arrowClose); } }); optgroupActions.appendChild(optgroupClosable); } optgroupEl.appendChild(optgroupLabel); for (const o of d.options) { optgroupEl.appendChild(this.option(o)); } this.content.list.appendChild(optgroupEl); } if (d instanceof Option) { this.content.list.appendChild(this.option(d)); } } } option(option) { if (option.placeholder) { const placeholder = document.createElement('div'); placeholder.classList.add(this.classes.option); placeholder.classList.add(this.classes.hide); return placeholder; } const optionEl = document.createElement('div'); optionEl.dataset.id = option.id; optionEl.classList.add(this.classes.option); optionEl.setAttribute('role', 'option'); if (option.class) { option.class.split(' ').forEach((dataClass) => { optionEl.classList.add(dataClass); }); } if (option.style) { optionEl.style.cssText = option.style; } if (this.settings.searchHighlight && this.content.search.input.value.trim() !== '') { const textOrHtml = option.html !== '' ? option.html : option.text; optionEl.innerHTML = this.highlightText(textOrHtml, this.content.search.input.value, this.classes.searchHighlighter); } else if (option.html !== '') { optionEl.innerHTML = option.html; } else { optionEl.textContent = option.text; } if (this.settings.showOptionTooltips && optionEl.textContent) { optionEl.setAttribute('title', optionEl.textContent); } if ((option.selected && !this.settings.allowDeselect) || (option.disabled && !this.settings.allowDeselect)) { optionEl.classList.add(this.classes.disabled); } if (option.selected && this.settings.hideSelected) { optionEl.classList.add(this.classes.hide); } if (option.selected) { optionEl.classList.add(this.classes.selected); } else { optionEl.classList.remove(this.classes.selected); } optionEl.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const selectedOptions = this.store.getSelected(); const element = e.currentTarget; const elementID = String(element.dataset.id); if (option.disabled || (option.selected && !this.settings.allowDeselect)) { return; } if (this.settings.isMultiple && Array.isArray(selectedOptions) && this.settings.maxSelected <= selectedOptions.length) { return; } let shouldUpdate = false; const before = this.store.getSelectedOptions(); let after = []; if (this.settings.isMultiple) { if (option.selected) { after = before.filter((o) => o.id !== elementID); } else { after = before.concat(option); } } if (!this.settings.isMultiple) { if (option.selected) { after = []; } else { after = [option]; } } if (!this.callbacks.beforeChange) { shouldUpdate = true; } if (this.callbacks.beforeChange) { if (this.callbacks.beforeChange(after, before) === false) { shouldUpdate = false; } else { shouldUpdate = true; } } if (shouldUpdate) { if (!this.store.getOptionByID(elementID)) { this.callbacks.addOption(option); } this.callbacks.setSelected(after.map((o) => o.value)); if (this.settings.closeOnSelect) { this.callbacks.close(); } if (this.callbacks.afterChange) { this.callbacks.afterChange(after); } } }); return optionEl; } destroy() { this.main.main.remove(); this.content.main.remove(); } highlightText(str, search, className) { let completedString = str; const regex = new RegExp('(' + search.trim() + ')(?![^<]*>[^<>]*</)', 'i'); if (!str.match(regex)) { return str; } const matchStartPosition = str.match(regex).index; const matchEndPosition = matchStartPosition + str.match(regex)[0].toString().length; const originalTextFoundByRegex = str.substring(matchStartPosition, matchEndPosition); completedString = completedString.replace(regex, `<mark class="${className}">${originalTextFoundByRegex}</mark>`); return completedString; } moveContentAbove() { let mainHeight = this.main.main.offsetHeight; const contentHeight = this.content.main.offsetHeight; const height = mainHeight + contentHeight - 1; this.content.main.style.margin = '-' + height + 'px 0px 0px 0px'; this.content.main.style.transformOrigin = 'center bottom'; this.main.main.classList.remove(this.classes.openBelow); this.main.main.classList.add(this.classes.openAbove); this.content.main.classList.remove(this.classes.openBelow); this.content.main.classList.add(this.classes.openAbove); } moveContentBelow() { this.content.main.style.margin = '-1px 0px 0px 0px'; this.content