slim-select
Version:
Slim advanced select dropdown
1,122 lines (1,119 loc) • 72.3 kB
JavaScript
(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