slim-select
Version:
Slim advanced select dropdown
1,115 lines (1,111 loc) • 88.2 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';
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.arrowOpe