{ "version": 3, "sources": ["../src/index.ts", "../src/services/binding-event.service.ts", "../src/locales/multiple-select-en-US.ts", "../src/constants.ts", "../src/services/virtual-scroll.ts", "../src/utils/domUtils.ts", "../src/utils/utils.ts", "../src/MultipleSelectInstance.ts", "../src/multiple-select.ts"], "sourcesContent": ["export * from './services';\nexport * from './constants';\nexport * from './interfaces';\nexport * from './utils';\nexport * from './multiple-select';\nexport * from './MultipleSelectInstance';\n", "export interface ElementEventListener {\n element: Element;\n eventName: string;\n listener: EventListenerOrEventListenerObject;\n}\n\nexport class BindingEventService {\n protected _distinctEvent: boolean;\n protected _boundedEvents: ElementEventListener[] = [];\n\n get boundedEvents(): ElementEventListener[] {\n return this._boundedEvents;\n }\n\n constructor(options?: { distinctEvent: boolean }) {\n this._distinctEvent = options?.distinctEvent ?? false;\n }\n\n dispose() {\n this.unbindAll();\n this._boundedEvents = [];\n }\n\n /** Bind an event listener to any element */\n bind(\n elementOrElements: Element | NodeListOf,\n eventNameOrNames: string | string[],\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions\n ) {\n const eventNames = Array.isArray(eventNameOrNames) ? eventNameOrNames : [eventNameOrNames];\n\n if ((elementOrElements as NodeListOf)?.forEach) {\n (elementOrElements as NodeListOf)?.forEach((element) => {\n for (const eventName of eventNames) {\n if (!this._distinctEvent || (this._distinctEvent && !this.hasBinding(element, eventName))) {\n element.addEventListener(eventName, listener, options);\n this._boundedEvents.push({ element, eventName, listener });\n }\n }\n });\n } else {\n for (const eventName of eventNames) {\n if (!this._distinctEvent || (this._distinctEvent && !this.hasBinding(elementOrElements as Element, eventName))) {\n (elementOrElements as Element).addEventListener(eventName, listener, options);\n this._boundedEvents.push({ element: elementOrElements as Element, eventName, listener });\n }\n }\n }\n }\n\n hasBinding(elm: Element, eventNameOrNames?: string | string[]): boolean {\n return this._boundedEvents.some((f) => f.element === elm && (!eventNameOrNames || f.eventName === eventNameOrNames));\n }\n\n /** Unbind all will remove every every event handlers that were bounded earlier */\n unbind(\n elementOrElements?: Element | NodeListOf | null,\n eventNameOrNames?: string | string[],\n listener?: EventListenerOrEventListenerObject | null\n ) {\n if (elementOrElements) {\n const elements = Array.isArray(elementOrElements) ? elementOrElements : [elementOrElements];\n const eventNames = Array.isArray(eventNameOrNames) ? eventNameOrNames || '' : [eventNameOrNames || ''];\n\n for (const element of elements) {\n if (!listener) {\n listener = this._boundedEvents.find((f) => {\n if (f.element === element && (!eventNameOrNames || f.eventName === eventNameOrNames)) {\n return f.listener;\n }\n return undefined;\n }) as EventListener | undefined;\n }\n\n for (const eventName of eventNames) {\n element?.removeEventListener?.(eventName, listener);\n }\n }\n }\n }\n\n /** Unbind all will remove every every event handlers that were bounded earlier */\n unbindAll() {\n while (this._boundedEvents.length > 0) {\n const boundedEvent = this._boundedEvents.pop() as ElementEventListener;\n const { element, eventName, listener } = boundedEvent;\n this.unbind(element, eventName, listener);\n }\n }\n}\n", "/**\n * Multiple Select en-US translation\n * Author: Zhixin Wen\n */\n\nimport { MultipleSelectLocale, MultipleSelectLocales } from '../interfaces';\nimport { MultipleSelectInstance } from '../MultipleSelectInstance';\n\nconst ms =\n typeof window !== 'undefined' && window.multipleSelect !== undefined\n ? window.multipleSelect\n : ({ locales: {} as MultipleSelectLocales } as Partial);\n\n(ms.locales as MultipleSelectLocales) = {\n 'en-US': {\n formatSelectAll() {\n return '[Select all]';\n },\n formatAllSelected() {\n return 'All selected';\n },\n formatCountSelected(count: number, total: number) {\n return `${count} of ${total} selected`;\n },\n formatNoMatchesFound() {\n return 'No matches found';\n },\n formatOkButton() {\n return 'OK';\n },\n } as MultipleSelectLocale,\n};\n\nexport default ms.locales;\n", "import { LabelFilter, MultipleSelectOption, TextFilter } from './interfaces';\nimport English from './locales/multiple-select-en-US';\n\nconst BLOCK_ROWS = 50;\nconst CLUSTER_BLOCKS = 4;\n\nconst DEFAULTS: Partial = {\n name: '',\n placeholder: '',\n classes: '',\n classPrefix: '',\n data: undefined,\n locale: undefined,\n\n selectAll: true,\n single: undefined,\n singleRadio: false,\n multiple: false,\n hideOptgroupCheckboxes: false,\n multipleWidth: 80,\n width: undefined,\n dropWidth: undefined,\n maxHeight: 250,\n maxHeightUnit: 'px',\n position: 'bottom',\n\n displayValues: false,\n displayTitle: false,\n displayDelimiter: ', ',\n minimumCountSelected: 3,\n ellipsis: false,\n\n isOpen: false,\n keepOpen: false,\n openOnHover: false,\n container: null,\n\n filter: false,\n filterGroup: false,\n filterPlaceholder: '',\n filterAcceptOnEnter: false,\n filterByDataLength: undefined,\n customFilter(filterOptions) {\n const { text, label, search } = filterOptions as LabelFilter & TextFilter;\n return (label || text || '').includes(search);\n },\n\n showClear: false,\n\n // auto-position the drop\n autoAdjustDropHeight: false,\n autoAdjustDropPosition: false,\n autoAdjustDropWidthByTextSize: false,\n adjustedHeightPadding: 10,\n useSelectOptionLabel: false,\n useSelectOptionLabelToHtml: false,\n\n styler: () => false,\n textTemplate: (elm: HTMLOptionElement) => elm.innerHTML.trim(),\n labelTemplate: (elm: HTMLOptionElement) => elm.label,\n\n onOpen: () => false,\n onClose: () => false,\n onCheckAll: () => false,\n onUncheckAll: () => false,\n onFocus: () => false,\n onBlur: () => false,\n onOptgroupClick: () => false,\n onClick: () => false,\n onFilter: () => false,\n onClear: () => false,\n onAfterCreate: () => false,\n onDestroy: () => false,\n onAfterDestroy: () => false,\n onDestroyed: () => false,\n};\n\nconst METHODS = [\n 'init',\n 'getOptions',\n 'refreshOptions',\n 'getSelects',\n 'setSelects',\n 'enable',\n 'disable',\n 'open',\n 'close',\n 'check',\n 'uncheck',\n 'checkAll',\n 'uncheckAll',\n 'checkInvert',\n 'focus',\n 'blur',\n 'refresh',\n 'destroy',\n];\n\nObject.assign(DEFAULTS, English!['en-US']); // load English as default locale\n\nconst Constants = {\n BLOCK_ROWS,\n CLUSTER_BLOCKS,\n DEFAULTS,\n METHODS,\n};\n\nexport default Constants;\n", "import Constants from '../constants';\nimport { VirtualScrollOption } from '../interfaces';\n\nexport class VirtualScroll {\n cache: any;\n clusterRows?: number;\n dataStart!: number;\n dataEnd!: number;\n rows: string[];\n scrollEl: HTMLElement;\n blockHeight?: number;\n clusterHeight?: number;\n contentEl: HTMLElement;\n parentEl: HTMLElement | null;\n itemHeight?: number;\n lastCluster: number;\n scrollTop: number;\n destroy: () => void;\n callback: () => void;\n sanitizer?: (dirtyHtml: string) => string;\n\n constructor(options: VirtualScrollOption) {\n this.rows = options.rows;\n this.scrollEl = options.scrollEl;\n this.contentEl = options.contentEl;\n this.parentEl = options.contentEl.parentElement;\n this.callback = options.callback;\n\n this.cache = {};\n this.scrollTop = this.scrollEl.scrollTop;\n\n this.initDOM(this.rows);\n\n this.scrollEl.scrollTop = this.scrollTop;\n this.lastCluster = 0;\n\n const onScroll = () => {\n if (this.lastCluster !== (this.lastCluster = this.getNum())) {\n this.initDOM(this.rows);\n this.callback();\n }\n };\n\n this.scrollEl.addEventListener('scroll', onScroll, false);\n this.destroy = () => {\n this.contentEl.innerHTML = '';\n this.scrollEl.removeEventListener('scroll', onScroll, false);\n };\n }\n\n initDOM(rows: string[]) {\n if (typeof this.clusterHeight === 'undefined') {\n this.cache.scrollTop = this.scrollEl.scrollTop;\n const data = rows[0] + rows[0] + rows[0];\n this.contentEl.innerHTML = this.sanitizer ? this.sanitizer(`${data}`) : `${data}`;\n this.cache.data = data;\n this.getRowsHeight();\n }\n\n const data = this.initData(rows, this.getNum());\n const thisRows = data.rows.join('');\n const dataChanged = this.checkChanges('data', thisRows);\n const topOffsetChanged = this.checkChanges('top', data.topOffset);\n const bottomOffsetChanged = this.checkChanges('bottom', data.bottomOffset);\n const html = [];\n\n if (dataChanged && topOffsetChanged) {\n if (data.topOffset) {\n html.push(this.getExtra('top', data.topOffset));\n }\n html.push(thisRows);\n if (data.bottomOffset) {\n html.push(this.getExtra('bottom', data.bottomOffset));\n }\n this.contentEl.innerHTML = this.sanitizer ? this.sanitizer(html.join('')) : html.join('');\n } else if (bottomOffsetChanged && this.contentEl.lastChild) {\n (this.contentEl.lastChild as HTMLElement).style.height = `${data.bottomOffset}px`;\n }\n }\n\n getRowsHeight() {\n if (typeof this.itemHeight === 'undefined') {\n // make sure parent is not hidden before reading item list height\n const prevParentDisplay = this.parentEl?.style.display || '';\n if (this.parentEl && (prevParentDisplay === '' || prevParentDisplay === 'none')) {\n this.parentEl.style.display = 'block';\n }\n const nodes = this.contentEl.children;\n const node = nodes[Math.floor(nodes.length / 2)];\n this.itemHeight = (node as HTMLElement).offsetHeight;\n if (this.parentEl) {\n this.parentEl.style.display = prevParentDisplay;\n }\n }\n this.blockHeight = this.itemHeight * Constants.BLOCK_ROWS;\n this.clusterRows = Constants.BLOCK_ROWS * Constants.CLUSTER_BLOCKS;\n this.clusterHeight = this.blockHeight * Constants.CLUSTER_BLOCKS;\n }\n\n getNum() {\n this.scrollTop = this.scrollEl.scrollTop;\n const blockSize = (this.clusterHeight || 0) - (this.blockHeight || 0);\n if (blockSize) {\n return Math.floor(this.scrollTop / blockSize) || 0;\n }\n return 0;\n }\n\n initData(rows: string[], num: number) {\n if (rows.length < Constants.BLOCK_ROWS) {\n return {\n topOffset: 0,\n bottomOffset: 0,\n rowsAbove: 0,\n rows,\n };\n }\n const start = Math.max((this.clusterRows! - Constants.BLOCK_ROWS) * num, 0);\n const end = start + this.clusterRows!;\n const topOffset = Math.max(start * this.itemHeight!, 0);\n const bottomOffset = Math.max((rows.length - end) * this.itemHeight!, 0);\n const thisRows = [];\n let rowsAbove = start;\n if (topOffset < 1) {\n rowsAbove++;\n }\n for (let i = start; i < end; i++) {\n rows[i] && thisRows.push(rows[i]);\n }\n\n this.dataStart = start;\n this.dataEnd = end;\n\n return {\n topOffset,\n bottomOffset,\n rowsAbove,\n rows: thisRows,\n };\n }\n\n checkChanges(type: string, value: number | string) {\n const changed = value !== this.cache[type];\n this.cache[type] = value;\n return changed;\n }\n\n getExtra(className: string, height: number) {\n const tag = document.createElement('li');\n tag.className = `virtual-scroll-${className}`;\n if (height) {\n tag.style.height = `${height}px`;\n }\n return tag.outerHTML;\n }\n}\n", "export type InferType = T extends infer R ? R : any;\n\n/* eslint-disable @typescript-eslint/indent */\nexport type InferDOMType = T extends CSSStyleDeclaration ? Partial : T extends infer R ? R : any;\n/* eslint-enable @typescript-eslint/indent */\n\nexport interface HtmlElementPosition {\n top: number;\n bottom: number;\n left: number;\n right: number;\n}\n\n/** calculate available space for each side of the DOM element */\nexport function calculateAvailableSpace(element: HTMLElement): { top: number; bottom: number; left: number; right: number } {\n let bottom = 0;\n let top = 0;\n let left = 0;\n let right = 0;\n\n const windowHeight = window.innerHeight ?? 0;\n const windowWidth = window.innerWidth ?? 0;\n const scrollPosition = windowScrollPosition();\n const pageScrollTop = scrollPosition.top;\n const pageScrollLeft = scrollPosition.left;\n const elmOffset = getElementOffset(element);\n\n if (elmOffset) {\n const elementOffsetTop = elmOffset.top ?? 0;\n const elementOffsetLeft = elmOffset.left ?? 0;\n top = elementOffsetTop - pageScrollTop;\n bottom = windowHeight - (elementOffsetTop - pageScrollTop);\n left = elementOffsetLeft - pageScrollLeft;\n right = windowWidth - (elementOffsetLeft - pageScrollLeft);\n }\n\n return { top, bottom, left, right };\n}\n\n/**\n * Create a DOM Element with any optional attributes or properties.\n * It will only accept valid DOM element properties that `createElement` would accept.\n * For example: `createDomElement('div', { className: 'my-css-class' })`,\n * for style or dataset you need to use nested object `{ style: { display: 'none' }}\n * The last argument is to optionally append the created element to a parent container element.\n * @param {String} tagName - html tag\n * @param {Object} options - element properties\n * @param {[HTMLElement]} appendToParent - parent element to append to\n */\nexport function createDomElement(\n tagName: T,\n elementOptions?: { [P in K]: InferDOMType },\n appendToParent?: HTMLElement\n): HTMLElementTagNameMap[T] {\n const elm = document.createElement(tagName);\n\n if (elementOptions) {\n Object.keys(elementOptions).forEach((elmOptionKey) => {\n const elmValue = elementOptions[elmOptionKey as keyof typeof elementOptions];\n if (typeof elmValue === 'object') {\n Object.assign(elm[elmOptionKey as K] as object, elmValue);\n } else {\n elm[elmOptionKey as K] = (elementOptions as any)[elmOptionKey as keyof typeof elementOptions];\n }\n });\n }\n if (appendToParent && appendToParent.appendChild) {\n appendToParent.appendChild(elm);\n }\n return elm;\n}\n\n/** Get HTML element offset with pure JS */\nexport function getElementOffset(element?: HTMLElement): HtmlElementPosition | undefined {\n if (!element) {\n return undefined;\n }\n const rect = element?.getBoundingClientRect?.();\n let top = 0;\n let left = 0;\n let bottom = 0;\n let right = 0;\n\n if (rect?.top !== undefined && rect.left !== undefined) {\n top = rect.top + window.pageYOffset;\n left = rect.left + window.pageXOffset;\n right = rect.right;\n bottom = rect.bottom;\n }\n return { top, left, bottom, right };\n}\n\nexport function getElementSize(elm: HTMLElement, mode: 'inner' | 'outer' | 'scroll', type: 'height' | 'width') {\n // first try defined style width or offsetWidth (which include scroll & padding)\n let size = parseFloat(elm.style[type]);\n if (!size || isNaN(size)) {\n switch (mode) {\n case 'outer':\n size = elm[type === 'width' ? 'offsetWidth' : 'offsetHeight'];\n break;\n case 'scroll':\n size = elm[type === 'width' ? 'scrollWidth' : 'scrollHeight'];\n break;\n case 'inner':\n default:\n size = elm[type === 'width' ? 'clientWidth' : 'clientHeight'];\n break;\n }\n size = elm.getBoundingClientRect()[type];\n }\n\n // when 0 width, we'll try different ways\n if (!size || isNaN(size)) {\n // when element is auto or 0, we'll keep previous style values to get width and then reapply original values\n const prevDisplay = elm.style.display;\n const prevPosition = elm.style.position;\n elm.style.display = 'block';\n elm.style.position = 'absolute';\n const widthStr = window.getComputedStyle(elm)[type];\n size = parseFloat(widthStr);\n if (isNaN(size)) {\n size = 0;\n }\n\n // reapply original values\n elm.style.display = prevDisplay;\n elm.style.position = prevPosition;\n }\n\n return size || 0;\n}\n\n/**\n * Find a single parent by a simple selector, it only works with a simple selector\n * for example: \"input.some-class\", \".some-class\", \"input#some-id\"\n * Note: it won't work with complex selector like \"div.some-class input.my-class\"\n * @param elm\n * @param selector\n * @returns\n */\nexport function findParent(elm: HTMLElement, selector: string) {\n let targetElm: HTMLElement | null = null;\n let parentElm = elm.parentElement;\n\n while (parentElm) {\n // query selector id (#some-id) or class (.some-class other-class)\n const [_, nodeType, selectorType, classOrIdName] = selector.match(/^([a-z]*)([#.]{1})([a-z\\-]+)$/i) || [];\n if (selectorType && classOrIdName) {\n // class or id selector type\n for (const q of classOrIdName.replace(selectorType, '').split(' ')) {\n if (parentElm.classList.contains(q)) {\n if (nodeType) {\n if (parentElm?.tagName.toLowerCase() === nodeType) {\n targetElm = parentElm;\n }\n } else {\n targetElm = parentElm;\n }\n }\n }\n }\n parentElm = parentElm.parentElement;\n }\n\n return targetElm;\n}\n\nexport function insertAfter(referenceNode: HTMLElement, newNode: HTMLElement) {\n referenceNode.parentNode?.insertBefore(newNode, referenceNode.nextSibling);\n}\n\n/**\n * HTML encode using a plain
\n * Create a in-memory div, set it's inner text(which a div can encode)\n * then grab the encoded contents back out. The div never exists on the page.\n * @param {String} inputValue - input value to be encoded\n * @return {String}\n */\nexport function htmlEncode(inputValue: string): string {\n const val = typeof inputValue === 'string' ? inputValue : String(inputValue);\n const entityMap: { [char: string]: string } = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n };\n return (val || '').toString().replace(/[&<>\"']/g, (s) => entityMap[s as keyof { [char: string]: string }]);\n}\n\n/** Display or hide matched element */\nexport function toggleElement(elm?: HTMLElement | null, display?: boolean) {\n if (elm?.style) {\n elm.style.display = (elm.style.display === 'none' && display !== false) || display === true ? 'block' : 'none';\n }\n}\n\nexport function toggleElementClass(elm?: HTMLElement | null, state?: boolean) {\n if (elm?.classList) {\n const adding = state === true || !elm.classList.contains('selected');\n const action = adding ? 'add' : 'remove';\n elm.classList[action]('selected');\n }\n}\n\n/**\n * Get the Window Scroll top/left Position\n * @returns\n */\nexport function windowScrollPosition(): { left: number; top: number } {\n return {\n left: window.pageXOffset || document.documentElement.scrollLeft || 0,\n top: window.pageYOffset || document.documentElement.scrollTop || 0,\n };\n}\n", "/** Compare two objects */\nexport function compareObjects(objectA: any, objectB: any, compareLength = false) {\n const aKeys = Object.keys(objectA);\n const bKeys = Object.keys(objectB);\n\n if (compareLength && aKeys.length !== bKeys.length) {\n return false;\n }\n\n for (const key of aKeys) {\n if (bKeys.includes(key) && objectA[key] !== objectB[key]) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Create an immutable clone of an array or object\n * (c) 2019 Chris Ferdinandi, MIT License, https://gomakethings.com\n * @param {Array|Object} objectOrArray - the array or object to copy\n * @return {Array|Object} - the clone of the array or object\n */\nexport function deepCopy(objectOrArray: any | any[]): any | any[] {\n const cloneObj = () => {\n const clone = {}; // create new object\n\n // Loop through each item in the original, recursively copy it's value and add to the clone\n // eslint-disable-next-line no-restricted-syntax\n for (const key in objectOrArray) {\n if (Object.prototype.hasOwnProperty.call(objectOrArray, key)) {\n (clone as any)[key] = deepCopy(objectOrArray[key]);\n }\n }\n return clone;\n };\n\n // Create an immutable copy of an array\n const cloneArr = () => objectOrArray.map((item: any) => deepCopy(item));\n\n // Get object type\n const type = Object.prototype.toString.call(objectOrArray).slice(8, -1).toLowerCase();\n if (type === 'object') {\n return cloneObj(); // if it's an object\n }\n if (type === 'array') {\n return cloneArr(); // if it's an array\n }\n return objectOrArray; // otherwise, return it as-is, could be primitive or else\n}\n\nexport function setDataKeys(data: any[]) {\n let total = 0;\n\n data.forEach((row, i) => {\n if (row.type === 'optgroup') {\n row._key = `group_${i}`;\n row.visible = typeof row.visible === 'undefined' ? true : row.visible;\n\n row.children.forEach((child: any, j: number) => {\n if (child) {\n child.visible = typeof child?.visible === 'undefined' ? true : child.visible;\n\n if (!child.divider) {\n child._key = `option_${i}_${j}`;\n total += 1;\n }\n }\n });\n } else {\n row.visible = typeof row.visible === 'undefined' ? true : row.visible;\n\n if (!row.divider) {\n row._key = `option_${i}`;\n total += 1;\n }\n }\n });\n\n return total;\n}\n\nexport function findByParam(data: any, param: any, value: any) {\n if (Array.isArray(data)) {\n for (const row of data) {\n if (row[param] === value || (row[param] === `${+row[param]}` && +row[param] === value)) {\n return row;\n }\n if (row.type === 'optgroup') {\n for (const child of row.children) {\n if (child && (child[param] === value || (child[param] === `${+child[param]}` && +child[param] === value))) {\n return child;\n }\n }\n }\n }\n }\n}\n\nexport function stripScripts(str: string) {\n const div = document.createElement('div');\n div.innerHTML = str;\n const scripts = div.getElementsByTagName('script');\n let i = scripts.length;\n while (i--) {\n scripts[i].parentNode?.removeChild(scripts[i]);\n }\n return div.innerHTML;\n}\n\nexport function removeUndefined(obj: any) {\n Object.keys(obj).forEach((key) => (obj[key] === undefined ? delete obj[key] : ''));\n return obj;\n}\n\nexport function removeDiacritics(str: string): string {\n if (typeof str !== 'string') {\n return str;\n }\n if (str.normalize) {\n return str.normalize('NFD').replace(/[\\u0300-\\u036F]/g, '');\n }\n const defaultDiacriticsRemovalMap = [\n {\n base: 'A',\n letters:\n /[\\u0041\\u24B6\\uFF21\\u00C0\\u00C1\\u00C2\\u1EA6\\u1EA4\\u1EAA\\u1EA8\\u00C3\\u0100\\u0102\\u1EB0\\u1EAE\\u1EB4\\u1EB2\\u0226\\u01E0\\u00C4\\u01DE\\u1EA2\\u00C5\\u01FA\\u01CD\\u0200\\u0202\\u1EA0\\u1EAC\\u1EB6\\u1E00\\u0104\\u023A\\u2C6F]/g,\n },\n { base: 'AA', letters: /[\\uA732]/g },\n { base: 'AE', letters: /[\\u00C6\\u01FC\\u01E2]/g },\n { base: 'AO', letters: /[\\uA734]/g },\n { base: 'AU', letters: /[\\uA736]/g },\n { base: 'AV', letters: /[\\uA738\\uA73A]/g },\n { base: 'AY', letters: /[\\uA73C]/g },\n { base: 'B', letters: /[\\u0042\\u24B7\\uFF22\\u1E02\\u1E04\\u1E06\\u0243\\u0182\\u0181]/g },\n { base: 'C', letters: /[\\u0043\\u24B8\\uFF23\\u0106\\u0108\\u010A\\u010C\\u00C7\\u1E08\\u0187\\u023B\\uA73E]/g },\n { base: 'D', letters: /[\\u0044\\u24B9\\uFF24\\u1E0A\\u010E\\u1E0C\\u1E10\\u1E12\\u1E0E\\u0110\\u018B\\u018A\\u0189\\uA779]/g },\n { base: 'DZ', letters: /[\\u01F1\\u01C4]/g },\n { base: 'Dz', letters: /[\\u01F2\\u01C5]/g },\n {\n base: 'E',\n letters:\n /[\\u0045\\u24BA\\uFF25\\u00C8\\u00C9\\u00CA\\u1EC0\\u1EBE\\u1EC4\\u1EC2\\u1EBC\\u0112\\u1E14\\u1E16\\u0114\\u0116\\u00CB\\u1EBA\\u011A\\u0204\\u0206\\u1EB8\\u1EC6\\u0228\\u1E1C\\u0118\\u1E18\\u1E1A\\u0190\\u018E]/g,\n },\n { base: 'F', letters: /[\\u0046\\u24BB\\uFF26\\u1E1E\\u0191\\uA77B]/g },\n {\n base: 'G',\n letters: /[\\u0047\\u24BC\\uFF27\\u01F4\\u011C\\u1E20\\u011E\\u0120\\u01E6\\u0122\\u01E4\\u0193\\uA7A0\\uA77D\\uA77E]/g,\n },\n { base: 'H', letters: /[\\u0048\\u24BD\\uFF28\\u0124\\u1E22\\u1E26\\u021E\\u1E24\\u1E28\\u1E2A\\u0126\\u2C67\\u2C75\\uA78D]/g },\n {\n base: 'I',\n letters:\n /[\\u0049\\u24BE\\uFF29\\u00CC\\u00CD\\u00CE\\u0128\\u012A\\u012C\\u0130\\u00CF\\u1E2E\\u1EC8\\u01CF\\u0208\\u020A\\u1ECA\\u012E\\u1E2C\\u0197]/g,\n },\n { base: 'J', letters: /[\\u004A\\u24BF\\uFF2A\\u0134\\u0248]/g },\n { base: 'K', letters: /[\\u004B\\u24C0\\uFF2B\\u1E30\\u01E8\\u1E32\\u0136\\u1E34\\u0198\\u2C69\\uA740\\uA742\\uA744\\uA7A2]/g },\n {\n base: 'L',\n letters: /[\\u004C\\u24C1\\uFF2C\\u013F\\u0139\\u013D\\u1E36\\u1E38\\u013B\\u1E3C\\u1E3A\\u0141\\u023D\\u2C62\\u2C60\\uA748\\uA746\\uA780]/g,\n },\n { base: 'LJ', letters: /[\\u01C7]/g },\n { base: 'Lj', letters: /[\\u01C8]/g },\n { base: 'M', letters: /[\\u004D\\u24C2\\uFF2D\\u1E3E\\u1E40\\u1E42\\u2C6E\\u019C]/g },\n {\n base: 'N',\n letters: /[\\u004E\\u24C3\\uFF2E\\u01F8\\u0143\\u00D1\\u1E44\\u0147\\u1E46\\u0145\\u1E4A\\u1E48\\u0220\\u019D\\uA790\\uA7A4]/g,\n },\n { base: 'NJ', letters: /[\\u01CA]/g },\n { base: 'Nj', letters: /[\\u01CB]/g },\n {\n base: 'O',\n letters:\n /[\\u004F\\u24C4\\uFF2F\\u00D2\\u00D3\\u00D4\\u1ED2\\u1ED0\\u1ED6\\u1ED4\\u00D5\\u1E4C\\u022C\\u1E4E\\u014C\\u1E50\\u1E52\\u014E\\u022E\\u0230\\u00D6\\u022A\\u1ECE\\u0150\\u01D1\\u020C\\u020E\\u01A0\\u1EDC\\u1EDA\\u1EE0\\u1EDE\\u1EE2\\u1ECC\\u1ED8\\u01EA\\u01EC\\u00D8\\u01FE\\u0186\\u019F\\uA74A\\uA74C]/g,\n },\n { base: 'OI', letters: /[\\u01A2]/g },\n { base: 'OO', letters: /[\\uA74E]/g },\n { base: 'OU', letters: /[\\u0222]/g },\n { base: 'P', letters: /[\\u0050\\u24C5\\uFF30\\u1E54\\u1E56\\u01A4\\u2C63\\uA750\\uA752\\uA754]/g },\n { base: 'Q', letters: /[\\u0051\\u24C6\\uFF31\\uA756\\uA758\\u024A]/g },\n {\n base: 'R',\n letters: /[\\u0052\\u24C7\\uFF32\\u0154\\u1E58\\u0158\\u0210\\u0212\\u1E5A\\u1E5C\\u0156\\u1E5E\\u024C\\u2C64\\uA75A\\uA7A6\\uA782]/g,\n },\n {\n base: 'S',\n letters: /[\\u0053\\u24C8\\uFF33\\u1E9E\\u015A\\u1E64\\u015C\\u1E60\\u0160\\u1E66\\u1E62\\u1E68\\u0218\\u015E\\u2C7E\\uA7A8\\uA784]/g,\n },\n {\n base: 'T',\n letters: /[\\u0054\\u24C9\\uFF34\\u1E6A\\u0164\\u1E6C\\u021A\\u0162\\u1E70\\u1E6E\\u0166\\u01AC\\u01AE\\u023E\\uA786]/g,\n },\n { base: 'TZ', letters: /[\\uA728]/g },\n {\n base: 'U',\n letters:\n /[\\u0055\\u24CA\\uFF35\\u00D9\\u00DA\\u00DB\\u0168\\u1E78\\u016A\\u1E7A\\u016C\\u00DC\\u01DB\\u01D7\\u01D5\\u01D9\\u1EE6\\u016E\\u0170\\u01D3\\u0214\\u0216\\u01AF\\u1EEA\\u1EE8\\u1EEE\\u1EEC\\u1EF0\\u1EE4\\u1E72\\u0172\\u1E76\\u1E74\\u0244]/g,\n },\n { base: 'V', letters: /[\\u0056\\u24CB\\uFF36\\u1E7C\\u1E7E\\u01B2\\uA75E\\u0245]/g },\n { base: 'VY', letters: /[\\uA760]/g },\n { base: 'W', letters: /[\\u0057\\u24CC\\uFF37\\u1E80\\u1E82\\u0174\\u1E86\\u1E84\\u1E88\\u2C72]/g },\n { base: 'X', letters: /[\\u0058\\u24CD\\uFF38\\u1E8A\\u1E8C]/g },\n {\n base: 'Y',\n letters: /[\\u0059\\u24CE\\uFF39\\u1EF2\\u00DD\\u0176\\u1EF8\\u0232\\u1E8E\\u0178\\u1EF6\\u1EF4\\u01B3\\u024E\\u1EFE]/g,\n },\n { base: 'Z', letters: /[\\u005A\\u24CF\\uFF3A\\u0179\\u1E90\\u017B\\u017D\\u1E92\\u1E94\\u01B5\\u0224\\u2C7F\\u2C6B\\uA762]/g },\n {\n base: 'a',\n letters:\n /[\\u0061\\u24D0\\uFF41\\u1E9A\\u00E0\\u00E1\\u00E2\\u1EA7\\u1EA5\\u1EAB\\u1EA9\\u00E3\\u0101\\u0103\\u1EB1\\u1EAF\\u1EB5\\u1EB3\\u0227\\u01E1\\u00E4\\u01DF\\u1EA3\\u00E5\\u01FB\\u01CE\\u0201\\u0203\\u1EA1\\u1EAD\\u1EB7\\u1E01\\u0105\\u2C65\\u0250]/g,\n },\n { base: 'aa', letters: /[\\uA733]/g },\n { base: 'ae', letters: /[\\u00E6\\u01FD\\u01E3]/g },\n { base: 'ao', letters: /[\\uA735]/g },\n { base: 'au', letters: /[\\uA737]/g },\n { base: 'av', letters: /[\\uA739\\uA73B]/g },\n { base: 'ay', letters: /[\\uA73D]/g },\n { base: 'b', letters: /[\\u0062\\u24D1\\uFF42\\u1E03\\u1E05\\u1E07\\u0180\\u0183\\u0253]/g },\n { base: 'c', letters: /[\\u0063\\u24D2\\uFF43\\u0107\\u0109\\u010B\\u010D\\u00E7\\u1E09\\u0188\\u023C\\uA73F\\u2184]/g },\n { base: 'd', letters: /[\\u0064\\u24D3\\uFF44\\u1E0B\\u010F\\u1E0D\\u1E11\\u1E13\\u1E0F\\u0111\\u018C\\u0256\\u0257\\uA77A]/g },\n { base: 'dz', letters: /[\\u01F3\\u01C6]/g },\n {\n base: 'e',\n letters:\n /[\\u0065\\u24D4\\uFF45\\u00E8\\u00E9\\u00EA\\u1EC1\\u1EBF\\u1EC5\\u1EC3\\u1EBD\\u0113\\u1E15\\u1E17\\u0115\\u0117\\u00EB\\u1EBB\\u011B\\u0205\\u0207\\u1EB9\\u1EC7\\u0229\\u1E1D\\u0119\\u1E19\\u1E1B\\u0247\\u025B\\u01DD]/g,\n },\n { base: 'f', letters: /[\\u0066\\u24D5\\uFF46\\u1E1F\\u0192\\uA77C]/g },\n {\n base: 'g',\n letters: /[\\u0067\\u24D6\\uFF47\\u01F5\\u011D\\u1E21\\u011F\\u0121\\u01E7\\u0123\\u01E5\\u0260\\uA7A1\\u1D79\\uA77F]/g,\n },\n {\n base: 'h',\n letters: /[\\u0068\\u24D7\\uFF48\\u0125\\u1E23\\u1E27\\u021F\\u1E25\\u1E29\\u1E2B\\u1E96\\u0127\\u2C68\\u2C76\\u0265]/g,\n },\n { base: 'hv', letters: /[\\u0195]/g },\n {\n base: 'i',\n letters:\n /[\\u0069\\u24D8\\uFF49\\u00EC\\u00ED\\u00EE\\u0129\\u012B\\u012D\\u00EF\\u1E2F\\u1EC9\\u01D0\\u0209\\u020B\\u1ECB\\u012F\\u1E2D\\u0268\\u0131]/g,\n },\n { base: 'j', letters: /[\\u006A\\u24D9\\uFF4A\\u0135\\u01F0\\u0249]/g },\n { base: 'k', letters: /[\\u006B\\u24DA\\uFF4B\\u1E31\\u01E9\\u1E33\\u0137\\u1E35\\u0199\\u2C6A\\uA741\\uA743\\uA745\\uA7A3]/g },\n {\n base: 'l',\n letters:\n /[\\u006C\\u24DB\\uFF4C\\u0140\\u013A\\u013E\\u1E37\\u1E39\\u013C\\u1E3D\\u1E3B\\u017F\\u0142\\u019A\\u026B\\u2C61\\uA749\\uA781\\uA747]/g,\n },\n { base: 'lj', letters: /[\\u01C9]/g },\n { base: 'm', letters: /[\\u006D\\u24DC\\uFF4D\\u1E3F\\u1E41\\u1E43\\u0271\\u026F]/g },\n {\n base: 'n',\n letters: /[\\u006E\\u24DD\\uFF4E\\u01F9\\u0144\\u00F1\\u1E45\\u0148\\u1E47\\u0146\\u1E4B\\u1E49\\u019E\\u0272\\u0149\\uA791\\uA7A5]/g,\n },\n { base: 'nj', letters: /[\\u01CC]/g },\n {\n base: 'o',\n letters:\n /[\\u006F\\u24DE\\uFF4F\\u00F2\\u00F3\\u00F4\\u1ED3\\u1ED1\\u1ED7\\u1ED5\\u00F5\\u1E4D\\u022D\\u1E4F\\u014D\\u1E51\\u1E53\\u014F\\u022F\\u0231\\u00F6\\u022B\\u1ECF\\u0151\\u01D2\\u020D\\u020F\\u01A1\\u1EDD\\u1EDB\\u1EE1\\u1EDF\\u1EE3\\u1ECD\\u1ED9\\u01EB\\u01ED\\u00F8\\u01FF\\u0254\\uA74B\\uA74D\\u0275]/g,\n },\n { base: 'oi', letters: /[\\u01A3]/g },\n { base: 'ou', letters: /[\\u0223]/g },\n { base: 'oo', letters: /[\\uA74F]/g },\n { base: 'p', letters: /[\\u0070\\u24DF\\uFF50\\u1E55\\u1E57\\u01A5\\u1D7D\\uA751\\uA753\\uA755]/g },\n { base: 'q', letters: /[\\u0071\\u24E0\\uFF51\\u024B\\uA757\\uA759]/g },\n {\n base: 'r',\n letters: /[\\u0072\\u24E1\\uFF52\\u0155\\u1E59\\u0159\\u0211\\u0213\\u1E5B\\u1E5D\\u0157\\u1E5F\\u024D\\u027D\\uA75B\\uA7A7\\uA783]/g,\n },\n {\n base: 's',\n letters: /[\\u0073\\u24E2\\uFF53\\u00DF\\u015B\\u1E65\\u015D\\u1E61\\u0161\\u1E67\\u1E63\\u1E69\\u0219\\u015F\\u023F\\uA7A9\\uA785\\u1E9B]/g,\n },\n {\n base: 't',\n letters: /[\\u0074\\u24E3\\uFF54\\u1E6B\\u1E97\\u0165\\u1E6D\\u021B\\u0163\\u1E71\\u1E6F\\u0167\\u01AD\\u0288\\u2C66\\uA787]/g,\n },\n { base: 'tz', letters: /[\\uA729]/g },\n {\n base: 'u',\n letters:\n /[\\u0075\\u24E4\\uFF55\\u00F9\\u00FA\\u00FB\\u0169\\u1E79\\u016B\\u1E7B\\u016D\\u00FC\\u01DC\\u01D8\\u01D6\\u01DA\\u1EE7\\u016F\\u0171\\u01D4\\u0215\\u0217\\u01B0\\u1EEB\\u1EE9\\u1EEF\\u1EED\\u1EF1\\u1EE5\\u1E73\\u0173\\u1E77\\u1E75\\u0289]/g,\n },\n { base: 'v', letters: /[\\u0076\\u24E5\\uFF56\\u1E7D\\u1E7F\\u028B\\uA75F\\u028C]/g },\n { base: 'vy', letters: /[\\uA761]/g },\n { base: 'w', letters: /[\\u0077\\u24E6\\uFF57\\u1E81\\u1E83\\u0175\\u1E87\\u1E85\\u1E98\\u1E89\\u2C73]/g },\n { base: 'x', letters: /[\\u0078\\u24E7\\uFF58\\u1E8B\\u1E8D]/g },\n {\n base: 'y',\n letters: /[\\u0079\\u24E8\\uFF59\\u1EF3\\u00FD\\u0177\\u1EF9\\u0233\\u1E8F\\u00FF\\u1EF7\\u1E99\\u1EF5\\u01B4\\u024F\\u1EFF]/g,\n },\n { base: 'z', letters: /[\\u007A\\u24E9\\uFF5A\\u017A\\u1E91\\u017C\\u017E\\u1E93\\u1E95\\u01B6\\u0225\\u0240\\u2C6C\\uA763]/g },\n ];\n\n return defaultDiacriticsRemovalMap.reduce((string, { letters, base }) => {\n return string.replace(letters, base);\n }, str);\n}\n", "/**\n * @author zhixin wen \n */\nimport Constants from './constants';\nimport { compareObjects, deepCopy, findByParam, removeDiacritics, removeUndefined, setDataKeys, stripScripts } from './utils';\nimport {\n calculateAvailableSpace,\n createDomElement,\n findParent,\n getElementOffset,\n getElementSize,\n htmlEncode,\n insertAfter,\n toggleElement,\n} from './utils/domUtils';\nimport type { HtmlElementPosition } from './utils/domUtils';\nimport type { MultipleSelectOption } from './interfaces/multipleSelectOption.interface';\nimport type { MultipleSelectLocales, OptGroupRowData, OptionDataObject, OptionRowData } from './interfaces';\nimport { BindingEventService, VirtualScroll } from './services';\n\nexport class MultipleSelectInstance {\n protected _bindEventService: BindingEventService;\n protected allSelected = false;\n protected fromHtml = false;\n protected choiceElm!: HTMLButtonElement;\n protected closeElm?: HTMLElement | null;\n protected filterText = '';\n protected updateData: any[] = [];\n protected data?: Array = [];\n protected dataTotal?: any;\n protected dropElm!: HTMLDivElement;\n protected okButtonElm?: HTMLButtonElement;\n protected filterParentElm?: HTMLDivElement | null;\n protected ulElm?: HTMLUListElement | null;\n protected parentElm!: HTMLDivElement;\n protected labelElm?: HTMLLabelElement | null;\n protected selectAllParentElm?: HTMLDivElement | null;\n protected selectAllElm?: HTMLInputElement | null;\n protected searchInputElm?: HTMLInputElement | null;\n protected selectGroupElms?: NodeListOf;\n protected selectItemElms?: NodeListOf;\n protected disableItemElms?: NodeListOf;\n protected noResultsElm?: HTMLDivElement | null;\n protected options: MultipleSelectOption;\n protected selectAllName = '';\n protected selectGroupName = '';\n protected selectItemName = '';\n protected tabIndex?: string | null;\n protected updateDataStart?: number;\n protected updateDataEnd?: number;\n protected virtualScroll?: VirtualScroll | null;\n locales: MultipleSelectLocales = {};\n\n constructor(\n protected elm: HTMLSelectElement,\n options?: Partial>\n ) {\n this.options = Object.assign({}, Constants.DEFAULTS, this.elm.dataset, options) as MultipleSelectOption;\n this._bindEventService = new BindingEventService({ distinctEvent: true });\n }\n\n init() {\n this.initLocale();\n this.initContainer();\n this.initData();\n this.initSelected(true);\n this.initFilter();\n this.initDrop();\n this.initView();\n this.options.onAfterCreate();\n }\n\n /**\n * destroy the element, if a hard destroy is enabled then we'll also nullify it on the multipleSelect instance array.\n * When a soft destroy is called, we'll only remove it from the DOM but we'll keep all multipleSelect instances\n */\n destroy(hardDestroy = true) {\n if (this.parentElm) {\n this.options.onDestroy({ hardDestroy });\n if (hardDestroy) {\n this.options.onHardDestroy();\n }\n if (this.elm.parentElement && this.parentElm.parentElement) {\n this.elm.parentElement.insertBefore(this.elm, this.parentElm.parentElement!.firstChild);\n }\n this.elm.classList.remove('ms-offscreen');\n this._bindEventService.unbindAll();\n\n if (this.tabIndex) {\n this.elm.tabIndex = +this.tabIndex;\n }\n\n this.virtualScroll?.destroy();\n this.dropElm?.remove();\n this.parentElm.parentNode?.removeChild(this.parentElm);\n\n if (this.fromHtml) {\n delete this.options.data;\n this.fromHtml = false;\n }\n this.options.onAfterDestroy({ hardDestroy });\n\n // on a hard destroy, we will also nullify all variables & call event so that _multipleSelect can also nullify its own instance\n if (hardDestroy) {\n this.options.onAfterHardDestroy?.();\n Object.keys(this.options).forEach((o) => delete (this as any)[o]);\n }\n }\n }\n\n protected initLocale() {\n if (this.options.locale) {\n const locales = window.multipleSelect.locales;\n const parts = this.options.locale.split(/-|_/);\n\n parts[0] = parts[0].toLowerCase();\n if (parts[1]) {\n parts[1] = parts[1].toUpperCase();\n }\n\n if (locales[this.options.locale]) {\n Object.assign(this.options, locales[this.options.locale]);\n } else if (locales[parts.join('-')]) {\n Object.assign(this.options, locales[parts.join('-')]);\n } else if (locales[parts[0]]) {\n Object.assign(this.options, locales[parts[0]]);\n } else {\n throw new Error(\n `[multiple-select-vanilla] invalid locales \"${this.options.locale}\", make sure to import it before using it`\n );\n }\n }\n }\n\n protected initContainer() {\n const name = this.elm.getAttribute('name') || this.options.name || '';\n\n if (this.options.classes) {\n this.elm.classList.add(this.options.classes);\n }\n if (this.options.classPrefix) {\n this.elm.classList.add(this.options.classPrefix);\n\n if (this.options.size) {\n this.elm.classList.add(`${this.options.classPrefix}-${this.options.size}`);\n }\n }\n\n // hide select element\n this.elm.style.display = 'none';\n\n // label element\n this.labelElm = this.elm.closest('label');\n if (!this.labelElm && this.elm.id) {\n this.labelElm = document.createElement('label');\n this.labelElm.htmlFor = this.elm.id;\n }\n if (this.labelElm?.querySelector('input')) {\n this.labelElm = null;\n }\n\n // single or multiple\n if (typeof this.options.single === 'undefined') {\n this.options.single = !this.elm.multiple;\n }\n\n // restore class and title from select element\n this.parentElm = createDomElement('div', {\n className: `ms-parent ${this.elm.className || ''} ${this.options.classes}`,\n dataset: { test: 'sel' },\n });\n\n // add tooltip title only when provided\n const parentTitle = this.elm.getAttribute('title') || '';\n if (parentTitle) {\n this.parentElm.title = parentTitle;\n }\n\n // add placeholder to choice button\n this.options.placeholder = this.options.placeholder || this.elm.getAttribute('placeholder') || '';\n\n this.tabIndex = this.elm.getAttribute('tabindex');\n let tabIndex = '';\n if (this.tabIndex !== null) {\n this.elm.tabIndex = -1;\n tabIndex = this.tabIndex && `tabindex=\"${this.tabIndex}\"`;\n }\n\n this.choiceElm = createDomElement('button', { className: 'ms-choice', type: 'button' }, this.parentElm);\n\n if (isNaN(tabIndex as any)) {\n this.choiceElm.tabIndex = +tabIndex;\n }\n\n this.choiceElm.appendChild(createDomElement('span', { className: 'ms-placeholder', textContent: this.options.placeholder }));\n\n if (this.options.showClear) {\n this.choiceElm.appendChild(createDomElement('div', { className: 'icon-close' }));\n }\n\n this.choiceElm.appendChild(createDomElement('div', { className: 'icon-caret' }));\n\n // default position is bottom\n this.dropElm = createDomElement('div', { className: `ms-drop ${this.options.position}` }, this.parentElm);\n\n // add data-name attribute when name option is defined\n if (name) {\n this.dropElm.dataset.name = name;\n }\n\n // add [data-test=\"name\"] attribute to both ms-parent & ms-drop\n const dataTest = this.elm.getAttribute('data-test') || this.options.dataTest;\n if (dataTest) {\n this.parentElm.dataset.test = dataTest;\n this.dropElm.dataset.test = dataTest;\n }\n\n this.closeElm = this.choiceElm.querySelector('.icon-close');\n\n if (this.options.dropWidth) {\n this.dropElm.style.width =\n typeof this.options.dropWidth === 'string' ? this.options.dropWidth : `${this.options.dropWidth}px`;\n }\n\n insertAfter(this.elm, this.parentElm);\n\n if (this.elm.disabled) {\n this.choiceElm.classList.add('disabled');\n }\n\n this.selectAllName = `data-name=\"selectAll${name}\"`;\n this.selectGroupName = `data-name=\"selectGroup${name}\"`;\n this.selectItemName = `data-name=\"selectItem${name}\"`;\n\n if (!this.options.keepOpen) {\n this._bindEventService.unbind(document.body, 'click');\n this._bindEventService.bind(document.body, 'click', ((e: MouseEvent & { target: HTMLElement }) => {\n if (e.target === this.choiceElm || findParent(e.target, '.ms-choice') === this.choiceElm) {\n return;\n }\n\n if (\n (e.target === this.dropElm || (findParent(e.target, '.ms-drop') !== this.dropElm && e.target !== this.elm)) &&\n this.options.isOpen\n ) {\n this.close();\n }\n }) as EventListener);\n }\n }\n\n protected initData() {\n const data: Array = [];\n\n if (this.options.data) {\n if (Array.isArray(this.options.data)) {\n this.data = this.options.data.map((it: any) => {\n if (typeof it === 'string' || typeof it === 'number') {\n return {\n text: it,\n value: it,\n };\n }\n return it;\n });\n } else if (typeof this.options.data === 'object') {\n for (const [value, text] of Object.entries(this.options.data as OptionDataObject)) {\n data.push({\n value,\n text: `${text}`,\n });\n }\n this.data = data;\n }\n } else {\n this.elm.childNodes.forEach((elm) => {\n const row = this.initRow(elm as HTMLOptionElement);\n if (row) {\n data.push(this.initRow(elm as HTMLOptionElement));\n }\n });\n\n this.options.data = data;\n this.data = data;\n this.fromHtml = true;\n }\n\n this.dataTotal = setDataKeys(this.data || []);\n }\n\n protected initRow(elm: HTMLOptionElement, groupDisabled?: boolean) {\n const row: any = {};\n if (elm.tagName?.toLowerCase() === 'option') {\n row.type = 'option';\n row.text = this.options.textTemplate(elm);\n row.value = elm.value;\n row.visible = true;\n row.selected = !!elm.selected;\n row.disabled = groupDisabled || elm.disabled;\n row.classes = elm.getAttribute('class') || '';\n row.title = elm.getAttribute('title') || '';\n\n if (elm.dataset.value) {\n row._value = elm.dataset.value; // value for object\n }\n if (Object.keys(elm.dataset).length) {\n row._data = elm.dataset;\n\n if (row._data.divider) {\n row.divider = row._data.divider;\n }\n }\n\n return row;\n }\n\n if (elm.tagName?.toLowerCase() === 'optgroup') {\n row.type = 'optgroup';\n row.label = this.options.labelTemplate(elm);\n row.visible = true;\n row.selected = !!elm.selected;\n row.disabled = elm.disabled;\n (row as OptGroupRowData).children = [];\n if (Object.keys(elm.dataset).length) {\n row._data = elm.dataset;\n }\n\n elm.childNodes.forEach((childNode) => {\n (row as OptGroupRowData).children.push(this.initRow(childNode as HTMLOptionElement, row.disabled));\n });\n\n return row;\n }\n\n return null;\n }\n\n protected initDrop() {\n this.initList();\n this.update(true);\n\n if (this.options.isOpen) {\n this.open(10);\n }\n\n if (this.options.openOnHover && this.parentElm) {\n this._bindEventService.bind(this.parentElm, 'mouseover', () => this.open(null));\n this._bindEventService.bind(this.parentElm, 'mouseout', () => this.close());\n }\n }\n\n protected initFilter() {\n this.filterText = '';\n\n if (this.options.filter || !this.options.filterByDataLength) {\n return;\n }\n\n let length = 0;\n for (const option of this.data || []) {\n if ((option as OptGroupRowData).type === 'optgroup') {\n length += (option as OptGroupRowData).children.length;\n } else {\n length += 1;\n }\n }\n this.options.filter = length > this.options.filterByDataLength;\n }\n\n protected initList() {\n if (this.options.filter) {\n this.filterParentElm = createDomElement('div', { className: 'ms-search' }, this.dropElm);\n this.filterParentElm.appendChild(\n createDomElement('input', {\n autocomplete: 'off',\n autocapitalize: 'off',\n spellcheck: false,\n type: 'text',\n placeholder: this.options.filterPlaceholder || '\uD83D\uDD0E\uFE0E',\n })\n );\n }\n\n if (this.options.selectAll && !this.options.single) {\n const selectName = this.elm.getAttribute('name') || this.options.name || '';\n this.selectAllParentElm = createDomElement('div', { className: 'ms-select-all' });\n const saLabelElm = document.createElement('label');\n createDomElement(\n 'input',\n { type: 'checkbox', checked: this.allSelected, dataset: { name: `selectAll${selectName}` } },\n saLabelElm\n );\n saLabelElm.appendChild(createDomElement('span', { textContent: this.formatSelectAll() }));\n this.selectAllParentElm.appendChild(saLabelElm);\n this.dropElm.appendChild(this.selectAllParentElm);\n }\n\n this.ulElm = document.createElement('ul');\n this.dropElm.appendChild(this.ulElm);\n\n if (this.options.showOkButton && !this.options.single) {\n this.okButtonElm = createDomElement(\n 'button',\n { className: 'ms-ok-button', type: 'button', textContent: this.formatOkButton() },\n this.dropElm\n );\n }\n\n this.initListItems();\n }\n\n protected initListItems() {\n const rows = this.getListRows();\n let offset = 0;\n\n if (this.options.selectAll && !this.options.single) {\n offset = -1;\n }\n\n if (rows.length > Constants.BLOCK_ROWS * Constants.CLUSTER_BLOCKS) {\n this.virtualScroll?.destroy();\n\n const dropVisible = this.dropElm.style.display !== 'none';\n if (!dropVisible) {\n this.dropElm.style.left = '-10000';\n this.dropElm.style.display = 'block';\n }\n\n const updateDataOffset = () => {\n if (this.virtualScroll) {\n this.updateDataStart = this.virtualScroll.dataStart + offset;\n this.updateDataEnd = this.virtualScroll.dataEnd + offset;\n if (this.updateDataStart < 0) {\n this.updateDataStart = 0;\n }\n if (this.updateDataEnd > (this.data?.length ?? 0)) {\n this.updateDataEnd = this.data?.length ?? 0;\n }\n }\n };\n\n if (this.ulElm) {\n this.virtualScroll = new VirtualScroll({\n rows,\n scrollEl: this.ulElm,\n contentEl: this.ulElm,\n sanitizer: this.options.sanitizer,\n callback: () => {\n updateDataOffset();\n this.events();\n },\n });\n }\n\n updateDataOffset();\n\n if (!dropVisible) {\n this.dropElm.style.left = '0';\n this.dropElm.style.display = 'none';\n }\n } else {\n if (this.ulElm) {\n this.ulElm.innerHTML = this.options.sanitizer ? this.options.sanitizer(rows.join('')) : rows.join('');\n }\n this.updateDataStart = 0;\n this.updateDataEnd = this.updateData.length;\n this.virtualScroll = null;\n }\n this.events();\n }\n\n protected getListRows() {\n const rows = [];\n\n this.updateData = [];\n this.data?.forEach((row) => {\n rows.push(...this.initListItem(row));\n });\n\n rows.push(`
  • ${this.formatNoMatchesFound()}
  • `);\n\n return rows;\n }\n\n protected initListItem(row: any, level = 0) {\n const isRenderAsHtml = this.options.renderOptionLabelAsHtml || this.options.useSelectOptionLabelToHtml;\n const title = row?.title ? `title=\"${row.title}\"` : '';\n const multiple = this.options.multiple ? 'multiple' : '';\n const type = this.options.single ? 'radio' : 'checkbox';\n let classes = '';\n\n if (!row?.visible) {\n return [];\n }\n\n this.updateData.push(row);\n\n if (this.options.single && !this.options.singleRadio) {\n classes = 'hide-radio ';\n }\n\n if (row.selected) {\n classes += 'selected ';\n }\n\n if (row.type === 'optgroup') {\n const customStyle = this.options.styler(row);\n const style = customStyle ? `style=\"${customStyle}\"` : '';\n const html = [];\n const group =\n this.options.hideOptgroupCheckboxes || this.options.single\n ? ``\n : ``;\n\n if (!classes.includes('hide-radio') && (this.options.hideOptgroupCheckboxes || this.options.single)) {\n classes += 'hide-radio ';\n }\n\n html.push(`\n
  • \n \n
  • \n `);\n\n (row as OptGroupRowData).children.forEach((child: any) => {\n html.push(...this.initListItem(child, 1));\n });\n\n return html;\n }\n\n const customStyle = this.options.styler(row);\n const style = customStyle ? `style=\"${customStyle}\"` : '';\n classes += row.classes || '';\n\n if (level && this.options.single) {\n classes += `option-level-${level} `;\n }\n\n if (row.divider) {\n return '
  • ';\n }\n\n return [\n `\n
  • \n \n
  • \n `,\n ];\n }\n\n protected initSelected(ignoreTrigger = false) {\n let selectedTotal = 0;\n\n for (const row of this.data || []) {\n if ((row as OptGroupRowData).type === 'optgroup') {\n const selectedCount = (row as OptGroupRowData).children.filter((child) => {\n return child && child.selected && !child.disabled && child.visible;\n }).length;\n\n if ((row as OptGroupRowData).children.length) {\n row.selected =\n !this.options.single &&\n selectedCount &&\n selectedCount ===\n (row as OptGroupRowData).children.filter(\n (child: any) => child && !child.disabled && child.visible && !child.divider\n ).length;\n }\n selectedTotal += selectedCount;\n } else {\n selectedTotal += row.selected && !row.disabled && row.visible ? 1 : 0;\n }\n }\n\n this.allSelected =\n this.data?.filter((row: OptionRowData | OptGroupRowData) => {\n return row.selected && !row.disabled && row.visible;\n }).length === this.data?.filter((row) => !row.disabled && row.visible && !row.divider).length;\n\n if (!ignoreTrigger) {\n if (this.allSelected) {\n this.options.onCheckAll();\n } else if (selectedTotal === 0) {\n this.options.onUncheckAll();\n }\n }\n }\n\n protected initView() {\n let computedWidth;\n\n if (window.getComputedStyle) {\n computedWidth = window.getComputedStyle(this.elm).width;\n\n if (computedWidth === 'auto') {\n computedWidth = getElementSize(this.dropElm, 'outer', 'width') + 20;\n }\n } else {\n computedWidth = getElementSize(this.elm, 'outer', 'width') + 20;\n }\n\n this.parentElm.style.width = `${this.options.width || computedWidth}px`;\n // this.elm.style.display = 'inline-block';\n this.elm.classList.add('ms-offscreen');\n }\n\n protected events() {\n this._bindEventService.unbind(this.okButtonElm);\n this._bindEventService.unbind(this.searchInputElm);\n this._bindEventService.unbind(this.selectAllElm);\n this._bindEventService.unbind(this.selectGroupElms);\n this._bindEventService.unbind(this.selectItemElms);\n this._bindEventService.unbind(this.disableItemElms);\n this._bindEventService.unbind(this.noResultsElm);\n\n this.searchInputElm = this.dropElm.querySelector('.ms-search input');\n this.selectAllElm = this.dropElm.querySelector(`input[${this.selectAllName}]`);\n this.selectGroupElms = this.dropElm.querySelectorAll(\n `input[${this.selectGroupName}],span[${this.selectGroupName}]`\n );\n this.selectItemElms = this.dropElm.querySelectorAll(`input[${this.selectItemName}]:enabled`);\n this.disableItemElms = this.dropElm.querySelectorAll(`input[${this.selectItemName}]:disabled`);\n this.noResultsElm = this.dropElm.querySelector('.ms-no-results');\n\n const toggleOpen = (e: MouseEvent & { target: HTMLElement }) => {\n e.preventDefault();\n if (e.target.classList.contains('icon-close')) {\n return;\n }\n this[this.options.isOpen ? 'close' : 'open']();\n };\n\n if (this.labelElm) {\n this._bindEventService.bind(this.labelElm, 'click', ((e: MouseEvent & { target: HTMLElement }) => {\n if (e.target.nodeName.toLowerCase() !== 'label') {\n return;\n }\n toggleOpen(e);\n if (!this.options.filter || !this.options.isOpen) {\n this.focus();\n }\n e.stopPropagation(); // Causes lost focus otherwise\n }) as EventListener);\n }\n\n this._bindEventService.bind(this.choiceElm, 'click', toggleOpen as EventListener);\n if (this.options.onFocus) {\n this._bindEventService.bind(this.choiceElm, 'focus', this.options.onFocus as EventListener);\n }\n if (this.options.onBlur) {\n this._bindEventService.bind(this.choiceElm, 'blur', this.options.onBlur as EventListener);\n }\n\n this._bindEventService.bind(this.parentElm, 'keydown', ((e: KeyboardEvent) => {\n // esc key\n if (e.code === 'Escape' && !this.options.keepOpen) {\n this.close();\n this.choiceElm.focus();\n }\n }) as EventListener);\n\n if (this.closeElm) {\n this._bindEventService.bind(this.closeElm, 'click', ((e: MouseEvent) => {\n e.preventDefault();\n this._checkAll(false, true);\n this.initSelected(false);\n this.updateSelected();\n this.update();\n this.options.onClear();\n }) as EventListener);\n }\n\n if (this.searchInputElm) {\n this._bindEventService.bind(this.searchInputElm, 'keydown', ((e: KeyboardEvent) => {\n // Ensure shift-tab causes lost focus from filter as with clicking away\n if (e.code === 'Tab' && e.shiftKey) {\n this.close();\n }\n }) as EventListener);\n\n this._bindEventService.bind(this.searchInputElm, 'keyup', ((e: KeyboardEvent) => {\n // enter or space\n // Avoid selecting/deselecting if no choices made\n if (this.options.filterAcceptOnEnter && ['Enter', 'Space'].includes(e.code) && this.searchInputElm?.value) {\n if (this.options.single) {\n const visibleLiElms: HTMLInputElement[] = [];\n this.selectItemElms?.forEach((selectedElm) => {\n if (selectedElm.closest('li')?.style.display !== 'none') {\n visibleLiElms.push(selectedElm);\n }\n });\n if (visibleLiElms.length) {\n const [selectItemAttrName] = this.selectItemName.split('='); // [data-name=\"selectItem\"], we want \"data-name\" attribute\n if (visibleLiElms[0].hasAttribute(selectItemAttrName)) {\n this.setSelects([visibleLiElms[0].value]);\n }\n }\n } else {\n this.selectAllElm?.click();\n }\n this.close();\n this.focus();\n return;\n }\n this.filter();\n }) as EventListener);\n }\n\n if (this.selectAllElm) {\n this._bindEventService.unbind(this.selectAllElm, 'click');\n this._bindEventService.bind(this.selectAllElm, 'click', ((e: MouseEvent & { currentTarget: HTMLInputElement }) => {\n this._checkAll(e.currentTarget?.checked);\n }) as EventListener);\n }\n\n if (this.okButtonElm) {\n this._bindEventService.unbind(this.okButtonElm, 'click');\n this._bindEventService.bind(this.okButtonElm, 'click', ((e: MouseEvent & { target: HTMLElement }) => {\n toggleOpen(e);\n e.stopPropagation(); // Causes lost focus otherwise\n }) as EventListener);\n }\n\n this._bindEventService.bind(this.selectGroupElms, 'click', ((e: MouseEvent & { currentTarget: HTMLInputElement }) => {\n const selectElm = e.currentTarget;\n const checked = selectElm.checked;\n const group = findByParam(this.data, '_key', selectElm.dataset.key);\n\n this._checkGroup(group, checked);\n this.options.onOptgroupClick(\n removeUndefined({\n label: group.label,\n selected: group.selected,\n data: group._data,\n children: group.children.map((child: any) => {\n if (child) {\n return removeUndefined({\n text: child.text,\n value: child.value,\n selected: child.selected,\n disabled: child.disabled,\n data: child._data,\n });\n }\n }),\n })\n );\n }) as EventListener);\n\n this._bindEventService.bind(this.selectItemElms, 'click', ((e: MouseEvent & { currentTarget: HTMLInputElement }) => {\n const selectElm = e.currentTarget;\n const checked = selectElm.checked;\n const option = findByParam(this.data, '_key', selectElm.dataset.key);\n\n this._check(option, checked);\n this.options.onClick(\n removeUndefined({\n text: option.text,\n value: option.value,\n selected: option.selected,\n data: option._data,\n })\n );\n\n if (this.options.single && this.options.isOpen && !this.options.keepOpen) {\n this.close();\n }\n }) as EventListener);\n }\n\n /**\n * Open the drop method, user could optionally provide a delay in ms to open the drop.\n * The default delay is 0ms (which is 1 CPU cycle) when nothing is provided, to avoid a delay we can pass `-1` or `null`\n * @param {number} [openDelay=0] - provide an optional delay, defaults to 0\n */\n open(openDelay: number | null = 0) {\n if (openDelay !== null && openDelay >= 0) {\n // eslint-disable-next-line prefer-const\n let timer: NodeJS.Timeout | undefined;\n clearTimeout(timer);\n timer = setTimeout(() => this.openDrop(), openDelay);\n } else {\n this.openDrop();\n }\n }\n\n protected openDrop() {\n if (this.choiceElm?.classList.contains('disabled')) {\n return;\n }\n this.options.isOpen = true;\n this.parentElm.classList.add('ms-parent-open');\n this.choiceElm?.querySelector('div')?.classList.add('open');\n this.dropElm.style.display = 'block';\n\n if (this.selectAllElm?.parentElement) {\n this.selectAllElm.parentElement.style.display = 'block';\n }\n\n if (this.noResultsElm) {\n this.noResultsElm.style.display = 'none';\n }\n\n if (!this.data?.length) {\n if (this.selectAllElm?.parentElement) {\n this.selectAllElm.parentElement.style.display = 'none';\n }\n if (this.noResultsElm) {\n this.noResultsElm.style.display = 'block';\n }\n }\n\n if (this.options.container) {\n const offset = getElementOffset(this.dropElm);\n let container: HTMLElement;\n if (this.options.container instanceof Node) {\n container = this.options.container as HTMLElement;\n } else if (typeof this.options.container === 'string') {\n // prettier-ignore\n container = this.options.container === 'body'\n ? document.body\n : document.querySelector(this.options.container) as HTMLElement;\n }\n container!.appendChild(this.dropElm);\n this.dropElm.style.top = `${offset?.top ?? 0}px`;\n this.dropElm.style.left = `${offset?.left ?? 0}px`;\n this.dropElm.style.minWidth = 'auto';\n this.dropElm.style.width = `${getElementSize(this.parentElm, 'outer', 'width')}px`;\n }\n\n const minHeight = this.options.minHeight;\n let maxHeight = this.options.maxHeight;\n if (this.options.maxHeightUnit === 'row') {\n const liElm = this.dropElm.querySelector('ul>li');\n maxHeight = getElementSize(liElm!, 'outer', 'height') * this.options.maxHeight;\n }\n const ulElm = this.dropElm.querySelector('ul');\n if (ulElm) {\n if (minHeight) {\n ulElm.style.minHeight = `${minHeight}px`;\n }\n ulElm.style.maxHeight = `${maxHeight}px`;\n }\n const multElms = this.dropElm.querySelectorAll('.multiple');\n multElms.forEach((multElm) => (multElm.style.width = `${this.options.multipleWidth}px`));\n\n if (this.data?.length && this.options.filter) {\n if (this.searchInputElm) {\n this.searchInputElm.value = '';\n this.searchInputElm.focus();\n }\n this.filter(true);\n }\n\n if (this.options.autoAdjustDropWidthByTextSize) {\n this.adjustDropWidthByText();\n }\n\n let newPosition = this.options.position;\n if (this.options.autoAdjustDropHeight) {\n // if autoAdjustDropPosition is enable, we 1st need to see what position the drop will be located\n // without necessary toggling it's position just yet, we just want to know the future position for calculation\n if (this.options.autoAdjustDropPosition) {\n const { bottom: spaceBottom, top: spaceTop } = calculateAvailableSpace(this.dropElm);\n const msDropHeight = this.dropElm.getBoundingClientRect().height;\n newPosition = spaceBottom < msDropHeight && spaceTop > spaceBottom ? 'top' : 'bottom';\n }\n\n // now that we know which drop position will be used, let's adjust the drop height\n this.adjustDropHeight(newPosition);\n }\n\n if (this.options.autoAdjustDropPosition) {\n this.adjustDropPosition(true);\n }\n\n this.options.onOpen();\n }\n\n close() {\n this.options.isOpen = false;\n this.parentElm.classList.remove('ms-parent-open');\n this.choiceElm?.querySelector('div')?.classList.remove('open');\n this.dropElm.style.display = 'none';\n\n if (this.options.container) {\n this.parentElm.appendChild(this.dropElm);\n this.dropElm.style.top = 'auto';\n this.dropElm.style.left = 'auto';\n }\n this.options.onClose();\n }\n\n protected update(ignoreTrigger = false) {\n const valueSelects = this.getSelects();\n let textSelects = this.getSelects('text');\n\n if (this.options.displayValues) {\n textSelects = valueSelects;\n }\n\n const spanElm = this.choiceElm?.querySelector('span');\n const sl = valueSelects.length;\n let html = null;\n\n const getSelectOptionHtml = () => {\n if (this.options.useSelectOptionLabel || this.options.useSelectOptionLabelToHtml) {\n const labels = valueSelects.join(this.options.displayDelimiter);\n return this.options.useSelectOptionLabelToHtml ? stripScripts(labels) : labels;\n }\n return textSelects.join(this.options.displayDelimiter);\n };\n\n if (spanElm) {\n if (sl === 0) {\n const placeholder = this.options.placeholder || '';\n spanElm.classList.add('ms-placeholder');\n spanElm.innerHTML = this.options.sanitizer ? this.options.sanitizer(placeholder) : placeholder;\n } else if (sl < this.options.minimumCountSelected) {\n html = getSelectOptionHtml();\n } else if (this.formatAllSelected() && sl === this.dataTotal) {\n html = this.formatAllSelected();\n } else if (this.options.ellipsis && sl > this.options.minimumCountSelected) {\n html = `${textSelects.slice(0, this.options.minimumCountSelected).join(this.options.displayDelimiter)}...`;\n } else if (this.formatCountSelected(sl, this.dataTotal) && sl > this.options.minimumCountSelected) {\n html = this.formatCountSelected(sl, this.dataTotal);\n } else {\n html = getSelectOptionHtml();\n }\n if (html !== null) {\n spanElm?.classList.remove('ms-placeholder');\n if (this.options.renderOptionLabelAsHtml || this.options.useSelectOptionLabelToHtml) {\n spanElm.innerHTML = this.options.sanitizer ? this.options.sanitizer(html) : html;\n } else {\n spanElm.textContent = html;\n }\n }\n\n if (this.options.displayTitle || this.options.addTitle) {\n if (this.options.addTitle) {\n console.warn('[Multiple-Select-Vanilla] Please note that the `addTitle` option was replaced with `displayTitle`.');\n }\n const selectType = this.options.useSelectOptionLabel || this.options.useSelectOptionLabelToHtml ? 'value' : 'text';\n spanElm.title = this.getSelects(selectType).join(this.options.displayDelimiter);\n }\n }\n\n // set selects to select\n const selectedValues = this.getSelects().join('');\n if (this.options.single) {\n this.elm.value = selectedValues;\n } else {\n // when multiple values could be set, so we need to loop through each\n const selectOptions = this.elm.options;\n for (let i = 0, ln = selectOptions.length; i < ln; i++) {\n const isSelected = selectedValues.indexOf(selectOptions[i].value) >= 0;\n selectOptions[i].selected = isSelected;\n }\n }\n\n // trigger