multiple-select-vanilla
Version:
4 lines • 138 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../src/index.ts", "../../src/services/binding-event.service.ts", "../../src/locales/multiple-select-en-US.ts", "../../src/constants.ts", "../../src/utils/utils.ts", "../../src/utils/domUtils.ts", "../../src/services/virtual-scroll.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<Element>,\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<HTMLElement>)?.forEach) {\n (elementOrElements as NodeListOf<HTMLElement>)?.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<Element> | 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<wenzhixin2010@gmail.com>\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<MultipleSelectInstance>);\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<MultipleSelectOption> = {\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 cssStyler: () => null,\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 onBeforeClick: () => true,\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", "/** 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 isDefined(val: any) {\n return val !== undefined && val !== null && val !== '';\n}\n\n/**\n * Remove all empty props from an object,\n * we can optionally provide a fixed list of props to consider for removal (anything else will be excluded)\n * @param {*} obj\n * @param {Array<String>} [clearProps] - optional list of props to consider for removal (anything else will be excluded)\n * @returns cleaned object\n */\nexport function objectRemoveEmptyProps(obj: any, clearProps?: string[]) {\n if (typeof obj === 'object') {\n if (clearProps) {\n return Object.fromEntries(\n Object.entries(obj).filter(([name, val]) => (!isDefined(val) && !clearProps.includes(name)) || isDefined(val))\n );\n }\n return Object.fromEntries(Object.entries(obj).filter(([_, v]) => isDefined(v)));\n }\n return obj;\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(dirtyHtml: string) {\n return dirtyHtml.replace(\n /(\\b)(on[a-z]+)(\\s*)=([^>]*)|javascript:([^>]*)[^>]*|(<\\s*)(\\/*)script([<>]*).*(<\\s*)(\\/*)script(>*)|(<|<)(\\/*)(script|script defer)(.*)(>|>|>\">)/gi,\n ''\n );\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 toCamelCase(str: string) {\n return str.replace(/[\\W_]+(.)/g, (_match, char) => char.toUpperCase());\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", "import { CSSStyleDeclarationWritable, HtmlStruct, InferDOMType } from '../interfaces';\nimport { objectRemoveEmptyProps, toCamelCase } from './utils';\n\nexport interface HtmlElementPosition {\n top: number;\n bottom: number;\n left: number;\n right: number;\n}\n\nexport function convertStringStyleToElementStyle(styleStr: string): CSSStyleDeclaration {\n const style = {} as CSSStyleDeclaration;\n if (styleStr) {\n const cstyles = styleStr.replace(/\\s/g, '').split(';');\n for (const cstyle of cstyles) {\n const [styleProp, styleVal] = cstyle.trim().split(':');\n if (styleProp) {\n (style as any)[toCamelCase(styleProp) as CSSStyleDeclarationWritable] = styleVal.trim() as CSSStyleDeclarationWritable;\n }\n }\n\n console.warn(\n '[multiple-select-vanilla] Please note that `styler` is deprecated, please migrate to `cssStyler` when possible.'\n );\n }\n return style;\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<T extends keyof HTMLElementTagNameMap, K extends keyof HTMLElementTagNameMap[T]>(\n tagName: T,\n elementOptions?: { [P in K]: InferDOMType<HTMLElementTagNameMap[T][P]> },\n appendToParent?: HTMLElement\n): HTMLElementTagNameMap[T] {\n const elm = document.createElement<T>(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?.appendChild) {\n appendToParent.appendChild(elm);\n }\n return elm;\n}\n\n/**\n * From an HtmlBlock, create the DOM structure and append it to dedicated DOM element, for example:\n * `{ tagName: 'li', props: { className: 'some-class' }, attrs: { 'aria-label': 'some label' }, children: [] }`\n * @param item\n * @param appendToElm\n */\nexport function createDomStructure(item: HtmlStruct, appendToElm?: HTMLElement, parentElm?: HTMLElement): HTMLElement {\n // innerHTML needs to be applied separately\n let innerHTMLStr = '';\n if (item.props?.innerHTML) {\n innerHTMLStr = item.props.innerHTML;\n delete item.props.innerHTML;\n }\n\n const elm = createDomElement(item.tagName, objectRemoveEmptyProps(item.props, ['class', 'title', 'style']), appendToElm);\n let parent: HTMLElement | null | undefined = parentElm;\n if (!parent) {\n parent = elm;\n }\n\n if (innerHTMLStr) {\n elm.innerHTML = innerHTMLStr; // type should already be as TrustedHTML\n }\n\n // add all custom DOM element attributes\n if (item.attrs) {\n for (const attrName of Object.keys(item.attrs)) {\n elm.setAttribute(attrName, item.attrs[attrName]);\n }\n }\n\n // use recursion when finding item children\n if (item.children) {\n for (const childItem of item.children) {\n createDomStructure(childItem, elm, parent);\n }\n }\n\n appendToElm?.appendChild(elm);\n return elm;\n}\n\n/** takes an html block object and converts to a real HTMLElement */\nexport function convertItemRowToHtml(item: HtmlStruct): HTMLElement {\n if (item.hasOwnProperty('tagName')) {\n return createDomStructure(item);\n }\n return document.createElement('li');\n}\n\n/**\n * Empty a DOM element by removing all of its DOM element children leaving with an empty element (basically an empty shell)\n * @return {object} element - updated element\n */\nexport function emptyElement<T extends Element = Element>(element?: T | null): T | undefined | null {\n while (element?.firstChild) {\n if (element.lastChild) {\n element.removeChild(element.lastChild);\n }\n }\n return element;\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/** 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", "import Constants from '../constants';\nimport { HtmlStruct, VirtualCache, VirtualScrollOption } from '../interfaces';\nimport { convertItemRowToHtml, emptyElement } from '../utils';\n\nexport class VirtualScroll {\n cache: VirtualCache;\n clusterRows?: number;\n dataStart!: number;\n dataEnd!: number;\n rows: HtmlStruct[];\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 = {} as VirtualCache;\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 emptyElement(this.contentEl);\n this.scrollEl.removeEventListener('scroll', onScroll, false);\n };\n }\n\n initDOM(rows: HtmlStruct[]) {\n if (typeof this.clusterHeight === 'undefined') {\n this.cache.scrollTop = this.scrollEl.scrollTop;\n const firstRowElm = convertItemRowToHtml(rows[0]);\n\n this.contentEl.appendChild(firstRowElm);\n this.contentEl.appendChild(firstRowElm);\n this.contentEl.appendChild(firstRowElm);\n this.cache.data = [rows[0]];\n this.getRowsHeight();\n }\n\n const data = this.initData(rows, this.getNum());\n const dataChanged = this.checkChanges('data', data.rows);\n const topOffsetChanged = this.checkChanges('top', data.topOffset);\n const bottomOffsetChanged = this.checkChanges('bottom', data.bottomOffset);\n\n emptyElement(this.contentEl);\n\n if (dataChanged && topOffsetChanged) {\n if (data.topOffset) {\n this.contentEl.appendChild(this.getExtra('top', data.topOffset));\n }\n data.rows.forEach((h) => this.contentEl.appendChild(convertItemRowToHtml(h)));\n\n if (data.bottomOffset) {\n this.contentEl.appendChild(this.getExtra('bottom', data.bottomOffset));\n }\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: HtmlStruct[], 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: HtmlStruct[] = [];\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<T extends keyof VirtualCache>(type: T, value: VirtualCache[T]) {\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;\n }\n}\n", "/**\n * @author zhixin wen <wenzhixin2010@gmail.com>\n */\nimport Constants from './constants';\nimport { compareObjects, deepCopy, findByParam, removeDiacritics, removeUndefined, setDataKeys, stripScripts } from './utils';\nimport {\n calculateAvailableSpace,\n convertItemRowToHtml,\n convertStringStyleToElementStyle,\n createDomElement,\n emptyElement,\n findParent,\n getElementOffset,\n getElementSize,\n insertAfter,\n toggleElement,\n} from './utils/domUtils';\nimport type { HtmlElementPosition } from './utils/domUtils';\nimport type { MultipleSelectOption } from './interfaces/multipleSelectOption.interface';\nimport type { HtmlStruct, 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 closeSearchElm?: HTMLElement | null;\n protected filterText = '';\n protected updateData: any[] = [];\n protected data?: Array<OptionRowData | OptGroupRowData> = [];\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<HTMLInputElement>;\n protected selectItemElms?: NodeListOf<HTMLInputElement>;\n protected disableItemElms?: NodeListOf<HTMLInputElement>;\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 get isRenderAsHtml() {\n return this.options.renderOptionLabelAsHtml || this.options.useSelectOptionLabelToHtml;\n }\n\n constructor(\n protected elm: HTMLSelectElement,\n options?: Partial<Omit<MultipleSelectOption, 'onHardDestroy' | 'onAfterHardDestroy'>>\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.elm && 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 = `selectAll${name}`;\n this.selectGroupName = `selectGroup${name}`;\n this.selectItemName = `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<OptionRowData> = [];\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(row as OptionRowData);\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 = {} as OptionRowData | OptGroupRowData;\n if (elm.tagName?.toLowerCase() === 'option') {\n row.type = 'option';\n (row as OptionRowData).text = this.options.textTemplate(elm);\n row.value = elm.value;\n row.visible = true;\n row.selected = Boolean(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 as OptGroupRowData).label = this.options.labelTemplate(elm);\n row.visible = true;\n row.selected = Boolean(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) as OptionRowData);\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 if (this.options.showSearchClear) {\n this.filterParentElm.appendChild(createDomElement('span', { className: 'icon-close' }));\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 {