Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

slim-select

Package Overview
Dependencies
Maintainers
1
Versions
111
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

slim-select - npm Package Compare versions

Comparing version 1.27.1 to 2.0.0

.nvmrc

20

.vscode/settings.json
{
"editor.formatOnSave": false,
"extensions.ignoreRecommendations": false,
"editor.detectIndentation": false,
"editor.formatOnSave": true,
"editor.insertSpaces": true,
"editor.tabSize": 2,
"tslint.autoFixOnSave": true
}
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.organizeImports": false,
"source.sortMembers": true
}
}

@@ -0,7 +1,4 @@

export declare function generateID(): string;
export declare function hasClassInTree(element: HTMLElement, className: string): any;
export declare function ensureElementInView(container: HTMLElement, element: HTMLElement): void;
export declare function putContent(el: HTMLElement, currentPosition: string, isOpen: boolean): string;
export declare function debounce(func: (...params: any[]) => void, wait?: number, immediate?: boolean): () => void;
export declare function isValueInArrayOfObjects(selected: any, key: string, value: string): boolean;
export declare function highlight(str: string, search: any, className: string): any;
export declare function kebabCase(str: string): string;

@@ -1,33 +0,23 @@

import { Config } from './config';
import { Select } from './select';
import { Slim } from './slim';
import { Data, dataArray, Option } from './data';
interface Constructor {
import Render from './render';
import Select from './select';
import Settings, { SettingsPartial } from './settings';
import Store, { DataArray, DataArrayPartial, Option, OptionOptional } from './store';
export * from './helper';
export * from './settings';
export * from './select';
export * from './store';
export * from './render';
export { Settings, Select, Store, Render };
export interface Config {
select: string | Element;
data?: dataArray;
showSearch?: boolean;
searchPlaceholder?: string;
searchText?: string;
searchingText?: string;
searchFocus?: boolean;
searchHighlight?: boolean;
searchFilter?: (opt: Option, search: string) => boolean;
closeOnSelect?: boolean;
showContent?: string;
placeholder?: string;
allowDeselect?: boolean;
allowDeselectOption?: boolean;
hideSelectedOption?: boolean;
deselectLabel?: string;
isEnabled?: boolean;
valuesUseText?: boolean;
showOptionTooltips?: boolean;
selectByGroup?: boolean;
limit?: number;
timeoutDelay?: number;
addToBody?: boolean;
ajax?: (value: string, func: (info: any) => void) => void;
addable?: (value: string) => Option | string;
beforeOnChange?: (info: Option | Option[]) => void | boolean;
onChange?: (info: Option | Option[]) => void;
data?: DataArrayPartial;
settings?: SettingsPartial;
events?: Events;
}
export interface Events {
search?: (searchValue: string, currentData: DataArray) => Promise<DataArrayPartial> | DataArrayPartial;
searchFilter?: (option: Option, search: string) => boolean;
addable?: (value: string) => OptionOptional | string;
beforeChange?: (newVal: Option[], oldVal: Option[]) => boolean | void;
afterChange?: (newVal: Option[]) => void;
beforeOpen?: () => void;

@@ -37,36 +27,26 @@ afterOpen?: () => void;

afterClose?: () => void;
error?: (err: Error) => void;
}
export default class SlimSelect {
config: Config;
selectEl: HTMLSelectElement;
settings: Settings;
select: Select;
data: Data;
slim: Slim;
ajax: ((value: string, func: (info: any) => void) => void) | null;
addable: ((value: string) => Option | string) | null;
beforeOnChange: ((info: Option) => void | boolean) | null;
onChange: ((info: Option) => void) | null;
beforeOpen: (() => void) | null;
afterOpen: (() => void) | null;
beforeClose: (() => void) | null;
afterClose: (() => void) | null;
private windowScroll;
constructor(info: Constructor);
validate(info: Constructor): HTMLSelectElement;
selected(): string | string[];
set(value: string | string[], type?: string, close?: boolean, render?: boolean): void;
setSelected(value: string | string[], type?: string, close?: boolean, render?: boolean): void;
setData(data: dataArray): void;
addData(data: Option): void;
store: Store;
render: Render;
events: Events;
constructor(config: Config);
enable(): void;
disable(): void;
getData(): DataArray;
setData(data: DataArrayPartial): void;
getSelected(): string[];
setSelected(value: string | string[]): void;
addOption(option: OptionOptional): void;
open(): void;
close(): void;
moveContentAbove(): void;
moveContentBelow(): void;
enable(): void;
disable(): void;
search(value: string): void;
setSearchText(text: string): void;
render(): void;
destroy(id?: string | null): void;
destroy(): void;
private windowResize;
private windowScroll;
private documentClick;
}
export {};

@@ -1,22 +0,33 @@

import SlimSelect from './index';
import { dataArray } from './data';
interface Constructor {
import { DataArray, Optgroup, Option } from './store';
export default class Select {
select: HTMLSelectElement;
main: SlimSelect;
listen: boolean;
onSelectChange?: (data: DataArray) => void;
onValueChange?: (value: string[]) => void;
private observer;
constructor(select: HTMLSelectElement);
enable(): void;
disable(): void;
hideUI(): void;
showUI(): void;
changeListen(on: boolean): void;
addSelectChangeListener(func: (data: DataArray) => void): void;
removeSelectChangeListener(): void;
addValueChangeListener(func: (value: string[]) => void): void;
removeValueChangeListener(): void;
valueChange(ev: Event): any;
private observeWrapper;
private addObserver;
private connectObserver;
private disconnectObserver;
getData(): DataArray;
getDataFromOptgroup(optgroup: HTMLOptGroupElement): Optgroup;
getSelectedValues(): string[];
getDataFromOption(option: HTMLOptionElement): Option;
setSelected(value: string[]): void;
updateSelect(id?: string, style?: string, classes?: string[]): void;
updateOptions(data: DataArray): void;
createOptgroup(optgroup: Optgroup): HTMLOptGroupElement;
createOption(info: Option): HTMLOptionElement;
destroy(): void;
}
export declare class Select {
element: HTMLSelectElement;
main: SlimSelect;
mutationObserver: MutationObserver | null;
triggerMutationObserver: boolean;
constructor(info: Constructor);
setValue(): void;
addAttributes(): void;
addEventListeners(): void;
addMutationObserver(): void;
observeMutationObserver(): void;
disconnectMutationObserver(): void;
create(data: dataArray): void;
createOption(info: any): HTMLOptionElement;
}
export {};
{
"name": "slim-select",
"description": "Slim advanced select dropdown",
"version": "1.27.1",
"version": "2.0.0",
"author": "Brian Voelker <brian@webiswhatido.com> (http://webiswhatido.com)",

@@ -11,13 +11,9 @@ "homepage": "https://slimselectjs.com",

},
"engines": {
"node": ">=8"
},
"main": "dist/slimselect.min.js",
"exports": {
"require": "./dist/slimselect.min.js",
"import": "./dist/slimselect.min.mjs"
},
"style": "dist/slimselect.min.css",
"main": "dist/slimselect.js",
"module": "dist/slimselect.es.js",
"unpkg": "dist/slimselect.js",
"types": "dist/index.d.ts",
"typings": "dist/index.d.ts",
"style": "dist/slimselect.css",
"sass": "src/slim-select/slimselect.scss",
"types": "dist/index.d.ts",
"repository": {

@@ -35,29 +31,37 @@ "type": "git",

"scripts": {
"dev": "vue-cli-service serve",
"library": "rm -r dist && cd src/slim-select && webpack && cd ../../ && npm run cleanDist && npm run renameDist && npm run mjs",
"cleanDist": "rm dist/slimselectcss.min.js && rm dist/slimselectcss.js",
"renameDist": "mv 'dist/slimselectcss.css' 'dist/slimselect.css' && mv 'dist/slimselectcss.min.css' 'dist/slimselect.min.css'",
"docs": "vue-cli-service build",
"build": "npm run docs && npm run library",
"mjs": "(printf 'var exports = {};'; cat dist/slimselect.min.js; printf 'export default exports.SlimSelect') > dist/slimselect.min.mjs",
"lint": "vue-cli-service lint"
"jestinit": "ts-jest config:init",
"dev": "vite --port=1111",
"format": "prettier --write --cache --parser typescript \"src/**/*.ts\"",
"build": "npm run build:clean && npm run build:docs && npm run build:library",
"build:clean": "rimraf ./dist/*",
"build:docs": "vite build",
"build:library": "npm run build:library:js && npm run build:library:css",
"build:library:js": "cd src/slim-select && rollup --config ./rollup.config.mjs && cd ../../",
"build:library:css": "cd src/slim-select && sass ./slimselect.scss ../../dist/slimselect.css --style=compressed && cd ../../",
"test": "jest"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.5.15",
"@vue/cli-plugin-typescript": "^4.5.15",
"@vue/cli-service": "^4.5.15",
"chance": "^1.1.8",
"clipboard": "^2.0.8",
"node-sass": "^5.0.0",
"optimize-css-assets-webpack-plugin": "^5.0.4",
"prismjs": "^1.25.0",
"sass-loader": "^10.1.0",
"typescript": "^4.5.4",
"uglifyjs-webpack-plugin": "^2.2.0",
"vue": "^2.6.14",
"vue-router": "^3.5.3",
"vue-template-compiler": "^2.6.14",
"vuex": "^3.6.2",
"webpack-cli": "^4.9.1"
"@jest/globals": "^29.3.1",
"@rollup/plugin-typescript": "^9.0.2",
"@types/downloadjs": "^1.4.3",
"@vitejs/plugin-vue": "^3.2.0",
"clipboard": "^2.0.11",
"downloadjs": "^1.4.7",
"jest": "^29.3.1",
"jest-environment-jsdom": "^29.3.1",
"prettier": "^2.7.1",
"prismjs": "^1.29.0",
"rimraf": "^3.0.2",
"rollup": "^2.79.1",
"rollup-plugin-terser": "^7.0.2",
"sass": "^1.56.1",
"ts-jest": "^29.0.3",
"tslib": "^2.4.1",
"typescript": "^4.9.3",
"vite": "^3.2.4",
"vue": "^3.2.45",
"vue-router": "^4.1.6",
"vue-tsc": "^1.0.9",
"vuex": "^4.0.2"
}
}

@@ -16,3 +16,4 @@ # Slim Select

- No Dependencies
- 20kb - 5kb gzip
- JS: 35.4kb - 9kb gzip
- CSS: 6.09kb - 1kb gzip
- Single Select

@@ -19,0 +20,0 @@ - Multi Select

@@ -1,17 +0,45 @@

import Vue from 'vue'
import Router from 'vue-router'
import { createRouter, createWebHistory } from 'vue-router'
Vue.use(Router)
export default new Router({
mode: 'history',
base: '/',
const router = createRouter({
history: createWebHistory(),
linkActiveClass: 'active',
routes: [
{ path: '/', component: () => import(/* webpackChunkName: "home" */ './pages/home.vue') },
{ path: '/install', component: () => import(/* webpackChunkName: "install" */ './pages/install.vue') },
{ path: '/selects', component: () => import(/* webpackChunkName: "selects" */ './pages/selects.vue') },
{ path: '/options', component: () => import(/* webpackChunkName: "options" */ './pages/options.vue') },
{ path: '/methods', component: () => import(/* webpackChunkName: "methods" */ './pages/methods.vue') }
]
{
path: '/',
name: 'Home',
component: () => import('./pages/home.vue'),
},
{
path: '/install',
name: 'Install',
component: () => import('./pages/install.vue'),
},
{
path: '/selects',
name: 'Selects',
component: () => import('./pages/selects.vue'),
},
{
path: '/data',
name: 'Data',
component: () => import('./pages/data.vue'),
},
{
path: '/settings',
name: 'Settings',
component: () => import('./pages/settings/index.vue'),
},
{
path: '/events',
name: 'Events',
component: () => import('./pages/events/index.vue'),
},
{
path: '/methods',
name: 'Methods',
component: () => import('./pages/methods/index.vue'),
},
],
})
export default router

@@ -0,4 +1,18 @@

// Generate an 8 character random string
export function generateID(): string {
return Math.random().toString(36).substring(2, 10)
}
export function hasClassInTree(element: HTMLElement, className: string) {
function hasClass(e: HTMLElement, c: string) {
if (!(!c || !e || !e.classList || !e.classList.contains(c))) { return e }
// If the element has the class return element
if (c && e && e.classList && e.classList.contains(c)) {
return e
}
// If the element has a dataset id of the class return element
if (c && e && e.dataset && e.dataset.id && e.dataset.id === className) {
return e
}
return null

@@ -8,3 +22,3 @@ }

function parentByClass(e: any, c: string): any {
if (!e || e === document as any) {
if (!e || e === (document as any)) {
return null

@@ -21,37 +35,11 @@ } else if (hasClass(e, c)) {

export function ensureElementInView(container: HTMLElement, element: HTMLElement): void {
// Determine container top and bottom
const cTop = container.scrollTop + container.offsetTop // Make sure to have offsetTop
const cBottom = cTop + container.clientHeight
// Determine element top and bottom
const eTop = element.offsetTop
const eBottom = eTop + element.clientHeight
// Check if out of view
if (eTop < cTop) {
container.scrollTop -= (cTop - eTop)
} else if (eBottom > cBottom) {
container.scrollTop += (eBottom - cBottom)
}
}
export function putContent(el: HTMLElement, currentPosition: string, isOpen: boolean): string {
const height = el.offsetHeight
const rect = el.getBoundingClientRect()
const elemTop = (isOpen ? rect.top : rect.top - height)
const elemBottom = (isOpen ? rect.bottom : rect.bottom + height)
if (elemTop <= 0) { return 'below' }
if (elemBottom >= window.innerHeight) { return 'above' }
return (isOpen ? currentPosition : 'below')
}
export function debounce(func: (...params: any[]) => void, wait = 100, immediate = false): () => void {
export function debounce(func: (...params: any[]) => void, wait = 50, immediate = false): () => void {
let timeout: any
return function(this: any, ...args: any[]) {
return function (this: any, ...args: any[]) {
const context = self
const later = () => {
timeout = null
if (!immediate) { func.apply(context, args) }
if (!immediate) {
func.apply(context, args)
}
}

@@ -61,60 +49,11 @@ const callNow = immediate && !timeout

timeout = setTimeout(later, wait)
if (callNow) { func.apply(context, args) }
}
}
export function isValueInArrayOfObjects(selected: any, key: string, value: string): boolean {
if (!Array.isArray(selected)) {
return selected[key] === value
}
for (const s of selected) {
if (s && s[key] && s[key] === value) {
return true
if (callNow) {
func.apply(context, args)
}
}
return false
}
export function highlight(str: string, search: any, className: string) {
// the completed string will be itself if already set, otherwise, the string that was passed in
let completedString: any = str
const regex = new RegExp('(' + search.trim() + ')(?![^<]*>[^<>]*</)', 'i')
// If the regex doesn't match the string just exit
if (!str.match(regex)) { return str }
// Otherwise, get to highlighting
const matchStartPosition = (str.match(regex) as any).index
const matchEndPosition = matchStartPosition + (str.match(regex) as any)[0].toString().length
const originalTextFoundByRegex = str.substring(matchStartPosition, matchEndPosition)
completedString = completedString.replace(regex, `<mark class="${className}">${originalTextFoundByRegex}</mark>`)
return completedString
}
export function kebabCase(str: string) {
const result = str.replace(
/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g,
(match) => '-' + match.toLowerCase()
)
return (str[0] === str[0].toUpperCase())
? result.substring(1)
: result
const result = str.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g, (match) => '-' + match.toLowerCase())
return str[0] === str[0].toUpperCase() ? result.substring(1) : result
}
// Custom events
(() => {
const w = (window as any)
if (typeof w.CustomEvent === 'function') { return }
function CustomEvent(event: any, params: any) {
params = params || { bubbles: false, cancelable: false, detail: undefined }
const evt = document.createEvent('CustomEvent')
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail)
return evt
}
CustomEvent.prototype = w.Event.prototype
w.CustomEvent = CustomEvent
})()

@@ -1,37 +0,30 @@

import { Config } from './config'
import { Select } from './select'
import { Slim } from './slim'
import { Data, dataArray, Option, validateData } from './data'
import { hasClassInTree, putContent, debounce, ensureElementInView } from './helper'
import { debounce, hasClassInTree } from './helper'
import Render from './render'
import Select from './select'
import Settings, { SettingsPartial } from './settings'
import Store, { DataArray, DataArrayPartial, Option, OptionOptional } from './store'
interface Constructor {
// Export everything except the "export default"
export * from './helper'
export * from './settings'
export * from './select'
export * from './store'
export * from './render'
// Export all "export defaults"
export { Settings, Select, Store, Render }
export interface Config {
select: string | Element
data?: dataArray
showSearch?: boolean
searchPlaceholder?: string
searchText?: string
searchingText?: string
searchFocus?: boolean
searchHighlight?: boolean
searchFilter?: (opt: Option, search: string) => boolean
closeOnSelect?: boolean
showContent?: string
placeholder?: string
allowDeselect?: boolean
allowDeselectOption?: boolean
hideSelectedOption?: boolean
deselectLabel?: string
isEnabled?: boolean
valuesUseText?: boolean // Use text value when showing selected value
showOptionTooltips?: boolean
selectByGroup?: boolean
limit?: number
timeoutDelay?: number
addToBody?: boolean
data?: DataArrayPartial
settings?: SettingsPartial
events?: Events
}
// Events
ajax?: (value: string, func: (info: any) => void) => void
addable?: (value: string) => Option | string
beforeOnChange?: (info: Option | Option[]) => void | boolean
onChange?: (info: Option | Option[]) => void
export interface Events {
search?: (searchValue: string, currentData: DataArray) => Promise<DataArrayPartial> | DataArrayPartial
searchFilter?: (option: Option, search: string) => boolean
addable?: (value: string) => OptionOptional | string
beforeChange?: (newVal: Option[], oldVal: Option[]) => boolean | void
afterChange?: (newVal: Option[]) => void
beforeOpen?: () => void

@@ -41,492 +34,396 @@ afterOpen?: () => void

afterClose?: () => void
error?: (err: Error) => void
}
export default class SlimSelect {
public config: Config
public select: Select
public data: Data
public slim: Slim
public ajax: ((value: string, func: (info: any) => void) => void) | null = null
public addable: ((value: string) => Option | string) | null = null
public beforeOnChange: ((info: Option) => void | boolean) | null = null
public onChange: ((info: Option) => void) | null = null
public beforeOpen: (() => void) | null = null
public afterOpen: (() => void) | null = null
public beforeClose: (() => void) | null = null
public afterClose: (() => void) | null = null
public selectEl: HTMLSelectElement
private windowScroll: (e: Event) => void = debounce((e: Event) => {
if (this.data.contentOpen) {
if (putContent(this.slim.content, this.data.contentPosition, this.data.contentOpen) === 'above') {
this.moveContentAbove()
} else {
this.moveContentBelow()
// Classes
public settings!: Settings
public select!: Select
public store!: Store
public render!: Render
// Events
public events = {
search: undefined,
searchFilter: (opt: Option, search: string) => {
return opt.text.toLowerCase().indexOf(search.toLowerCase()) !== -1
},
addable: undefined,
beforeChange: undefined,
afterChange: undefined,
beforeOpen: undefined,
afterOpen: undefined,
beforeClose: undefined,
afterClose: undefined,
} as Events
constructor(config: Config) {
// Make sure you get the right element
this.selectEl = (
typeof config.select === 'string' ? document.querySelector(config.select) : config.select
) as HTMLSelectElement
if (!this.selectEl) {
if (config.events && config.events.error) {
config.events.error(new Error('Could not find select element'))
}
return
}
})
if (this.selectEl.tagName !== 'SELECT') {
if (config.events && config.events.error) {
config.events.error(new Error('Element isnt of type select'))
}
return
}
constructor(info: Constructor) {
const selectElement = this.validate(info)
// If select already has a slim select id on it lets destroy it first
if (selectElement.dataset.ssid) { this.destroy(selectElement.dataset.ssid) }
if (this.selectEl.dataset.ssid) {
this.destroy()
}
// Set ajax function if passed in
if (info.ajax) { this.ajax = info.ajax }
// Set settings
this.settings = new Settings(config.settings)
// Add addable if option is passed in
if (info.addable) { this.addable = info.addable }
// Set events
for (const key in config.events) {
if (config.events.hasOwnProperty(key)) {
;(this.events as { [key: string]: any })[key] = (config.events as { [key: string]: any })[key]
}
}
this.config = new Config({
select: selectElement,
isAjax: (info.ajax ? true : false),
showSearch: info.showSearch,
searchPlaceholder: info.searchPlaceholder,
searchText: info.searchText,
searchingText: info.searchingText,
searchFocus: info.searchFocus,
searchHighlight: info.searchHighlight,
searchFilter: info.searchFilter,
closeOnSelect: info.closeOnSelect,
showContent: info.showContent,
placeholderText: info.placeholder,
allowDeselect: info.allowDeselect,
allowDeselectOption: info.allowDeselectOption,
hideSelectedOption: info.hideSelectedOption,
deselectLabel: info.deselectLabel,
isEnabled: info.isEnabled,
valuesUseText: info.valuesUseText,
showOptionTooltips: info.showOptionTooltips,
selectByGroup: info.selectByGroup,
limit: info.limit,
timeoutDelay: info.timeoutDelay,
addToBody: info.addToBody
})
// Upate settings with type, style and classname
this.settings.isMultiple = this.selectEl.multiple
this.settings.style = this.selectEl.style.cssText
this.settings.class = this.selectEl.className.split(' ')
this.select = new Select({
select: selectElement,
main: this
// Set select class
this.select = new Select(this.selectEl)
this.select.updateSelect(this.settings.id, this.settings.style, this.settings.class)
this.select.hideUI() // Hide the original select element
// Add select listeners
this.select.addSelectChangeListener((data: DataArray) => {
// Run set data from the values given
this.setData(data)
})
this.select.addValueChangeListener((values: string[]) => {
// Run set selected from the values given
this.setSelected(values)
})
this.data = new Data({ main: this })
this.slim = new Slim({ main: this })
// Set store class
this.store = new Store(
this.settings.isMultiple ? 'multiple' : 'single',
config.data ? config.data : this.select.getData(),
)
// Add after original select element
if (this.select.element.parentNode) {
this.select.element.parentNode.insertBefore(this.slim.container, this.select.element.nextSibling)
// If data is passed update the original select element
if (config.data) {
this.select.updateOptions(this.store.getData())
}
// If data is passed in lets set it
// and thus will start the render
if (info.data) {
this.setData(info.data)
} else {
// Do an initial render on startup
this.render()
// Set render callbacks
const callbacks = {
open: this.open.bind(this),
close: this.close.bind(this),
addable: this.events.addable ? this.events.addable : undefined,
setSelected: this.setSelected.bind(this),
addOption: this.addOption.bind(this),
search: this.search.bind(this),
beforeChange: this.events.beforeChange,
afterChange: this.events.afterChange,
}
// Setup render class
this.render = new Render(this.settings, this.store, callbacks)
// Add render after original select element
if (this.selectEl.parentNode) {
this.selectEl.parentNode.insertBefore(this.render.main.main, this.selectEl.nextSibling)
}
// Add onclick listener to document to closeContent if clicked outside
document.addEventListener('click', this.documentClick)
// Add window resize listener to moveContent if window size changes
window.addEventListener('resize', this.windowResize, false)
// If the user wants to show the content forcibly on a specific side,
// there is no need to listen for scroll events
if (this.config.showContent === 'auto') {
if (this.settings.openPosition === 'auto') {
window.addEventListener('scroll', this.windowScroll, false)
}
// Add event callbacks after everthing has been created
if (info.beforeOnChange) { this.beforeOnChange = info.beforeOnChange }
if (info.onChange) { this.onChange = info.onChange }
if (info.beforeOpen) { this.beforeOpen = info.beforeOpen }
if (info.afterOpen) { this.afterOpen = info.afterOpen }
if (info.beforeClose) { this.beforeClose = info.beforeClose }
if (info.afterClose) { this.afterClose = info.afterClose }
// If disabled lets call it
if (!this.config.isEnabled) { this.disable() }
}
if (!this.settings.isEnabled) {
this.disable()
}
public validate(info: Constructor) {
const select = (typeof info.select === 'string' ? document.querySelector(info.select) : info.select) as HTMLSelectElement
if (!select) { throw new Error('Could not find select element') }
if (select.tagName !== 'SELECT') { throw new Error('Element isnt of type select') }
return select
// If alwaysOpnen then open it
if (this.settings.alwaysOpen) {
this.open()
}
// Add SlimSelect to select element
;(this.selectEl as any).slim = this
}
public selected(): string | string[] {
if (this.config.isMultiple) {
const selected = this.data.getSelected() as Option[]
const outputSelected: string[] = []
for (const s of selected) {
outputSelected.push(s.value as string)
}
return outputSelected
} else {
const selected = this.data.getSelected() as Option
return (selected ? selected.value as string : '')
}
// Set to enabled and remove disabled classes
public enable(): void {
this.settings.isEnabled = true
this.select.enable()
this.render.enable()
}
// Sets value of the select, adds it to data and original select
public set(value: string | string[], type: string = 'value', close: boolean = true, render: boolean = true) {
if (this.config.isMultiple && !Array.isArray(value)) {
this.data.addToSelected(value, type)
} else {
this.data.setSelected(value, type)
}
this.select.setValue()
this.data.onDataChange() // Trigger on change callback
this.render()
// Set to disabled and add disabled classes
public disable(): void {
this.settings.isEnabled = false
// Close when all options are selected and hidden
if (this.config.hideSelectedOption && this.config.isMultiple && (this.data.getSelected() as Option[]).length === this.data.data.length) {
close = true
}
if (close) { this.close() }
this.select.disable()
this.render.disable()
}
// setSelected is just mapped to the set method
public setSelected(value: string | string[], type: string = 'value', close: boolean = true, render: boolean = true) {
this.set(value, type, close, render)
public getData(): DataArray {
return this.store.getData()
}
public setData(data: dataArray) {
// Validate data if passed in
const isValid = validateData(data)
if (!isValid) { console.error('Validation problem on: #' + this.select.element.id); return } // If data passed in is not valid DO NOT parse, set and render
const newData = JSON.parse(JSON.stringify(data))
const selected = this.data.getSelected()
// Check newData to make sure value is set
// If not set from text
for (let i = 0; i < newData.length; i++) {
if (!newData[i].value && !newData[i].placeholder) {
newData[i].value = newData[i].text
public setData(data: DataArrayPartial): void {
// Validate data
const err = this.store.validateDataArray(data)
if (err) {
if (this.events.error) {
this.events.error(err)
}
return
}
// If its an ajax type keep selected values
if (this.config.isAjax && selected) {
if (this.config.isMultiple) {
const reverseSelected = (selected as Option[]).reverse()
for (const r of reverseSelected) {
newData.unshift(r)
}
} else {
newData.unshift(selected)
// Update the store
this.store.setData(data)
const dataClean = this.store.getData()
// Look for duplicate selected if so remove it
for (let i = 0; i < newData.length; i++) {
if (!newData[i].placeholder && newData[i].value === (selected as Option).value && newData[i].text === (selected as Option).text) {
newData.splice(i, 1)
}
}
// Update original select element
this.select.updateOptions(dataClean)
// Add placeholder if it doesnt already have one
let hasPlaceholder = false
for (let i = 0; i < newData.length; i++) {
if (newData[i].placeholder) {
hasPlaceholder = true
}
}
if (!hasPlaceholder) {
newData.unshift({ text: '', placeholder: true })
}
}
}
// Update the render
this.render.renderValues()
this.render.renderOptions(dataClean)
}
this.select.create(newData)
this.data.parseSelectData()
this.data.setSelectedFromSelect()
public getSelected(): string[] {
return this.store.getSelected()
}
// addData will append to the current data set
public addData(data: Option) {
// Validate data if passed in
const isValid = validateData([data])
if (!isValid) { console.error('Validation problem on: #' + this.select.element.id); return } // If data passed in is not valid DO NOT parse, set and render
public setSelected(value: string | string[]): void {
// Update the store
this.store.setSelectedBy('value', Array.isArray(value) ? value : [value])
const data = this.store.getData()
this.data.add(this.data.newOption(data))
this.select.create(this.data.data)
this.data.parseSelectData()
this.data.setSelectedFromSelect()
this.render()
// Update the select element
this.select.updateOptions(data)
// Update the render
this.render.renderValues()
this.render.renderOptions(data)
}
// Open content section
public addOption(option: OptionOptional): void {
// Add option to store
this.store.addOption(option)
const data = this.store.getData()
// Update the select element
this.select.updateOptions(data)
// Update the render
this.render.renderValues()
this.render.renderOptions(data)
}
public open(): void {
// Dont open if disabled
if (!this.config.isEnabled) { return }
// Dont do anything if the content is already open
if (this.data.contentOpen) { return }
if (!this.settings.isEnabled || this.settings.isOpen) {
return
}
// Dont open when all options are selected and hidden
if (this.config.hideSelectedOption && this.config.isMultiple && (this.data.getSelected() as Option[]).length === this.data.data.length) { return }
// Run beforeOpen callback
if (this.beforeOpen) { this.beforeOpen() }
if (this.config.isMultiple && this.slim.multiSelected) {
this.slim.multiSelected.plus.classList.add('ss-cross')
} else if (this.slim.singleSelected) {
this.slim.singleSelected.arrowIcon.arrow.classList.remove('arrow-down')
this.slim.singleSelected.arrowIcon.arrow.classList.add('arrow-up')
if (this.events.beforeOpen) {
this.events.beforeOpen()
}
(this.slim as any)[(this.config.isMultiple ? 'multiSelected' : 'singleSelected')].container.classList.add((this.data.contentPosition === 'above' ? this.config.openAbove : this.config.openBelow))
if (this.config.addToBody) {
// move the content in to the right location
const containerRect = this.slim.container.getBoundingClientRect()
this.slim.content.style.top = (containerRect.top + containerRect.height + window.scrollY) + 'px'
this.slim.content.style.left = (containerRect.left + window.scrollX) + 'px'
this.slim.content.style.width = containerRect.width + 'px'
}
this.slim.content.classList.add(this.config.open)
// Tell render to open
this.render.open()
// Check showContent to see if they want to specifically show in a certain direction
if (this.config.showContent.toLowerCase() === 'up') {
this.moveContentAbove()
} else if (this.config.showContent.toLowerCase() === 'down') {
this.moveContentBelow()
} else {
// Auto identify where to put it
if (putContent(this.slim.content, this.data.contentPosition, this.data.contentOpen) === 'above') {
this.moveContentAbove()
} else {
this.moveContentBelow()
}
// Focus on input field only if search is enabled
if (this.settings.showSearch) {
this.render.searchFocus(false)
}
// Move to selected option for single option
if (!this.config.isMultiple) {
const selected = this.data.getSelected() as Option
if (selected) {
const selectedId = selected.id
const selectedOption = this.slim.list.querySelector('[data-id="' + selectedId + '"]') as HTMLElement
if (selectedOption) {
ensureElementInView(this.slim.list, selectedOption)
}
}
}
// setTimeout is for animation completion
setTimeout(() => {
this.data.contentOpen = true
// Focus on input field
if (this.config.searchFocus) {
this.slim.search.input.focus()
// Run afterOpen callback
if (this.events.afterOpen) {
this.events.afterOpen()
}
// Run afterOpen callback
if (this.afterOpen) {
this.afterOpen()
}
}, this.config.timeoutDelay)
// Update settings
this.settings.isOpen = true
}, this.settings.timeoutDelay)
// Start an interval to check if main has moved
// in order to keep content close to main
if (this.settings.intervalMove) {
clearInterval(this.settings.intervalMove)
}
this.settings.intervalMove = setInterval(this.render.moveContent.bind(this.render), 500)
}
// Close content section
public close(): void {
// Dont do anything if the content is already closed
if (!this.data.contentOpen) { return }
// Dont do anything if alwaysOpen is true
if (!this.settings.isOpen || this.settings.alwaysOpen) {
return
}
// Run beforeClose calback
if (this.beforeClose) { this.beforeClose() }
// this.slim.search.input.blur() // Removed due to safari quirk
if (this.config.isMultiple && this.slim.multiSelected) {
this.slim.multiSelected.container.classList.remove(this.config.openAbove)
this.slim.multiSelected.container.classList.remove(this.config.openBelow)
this.slim.multiSelected.plus.classList.remove('ss-cross')
} else if (this.slim.singleSelected) {
this.slim.singleSelected.container.classList.remove(this.config.openAbove)
this.slim.singleSelected.container.classList.remove(this.config.openBelow)
this.slim.singleSelected.arrowIcon.arrow.classList.add('arrow-down')
this.slim.singleSelected.arrowIcon.arrow.classList.remove('arrow-up')
if (this.events.beforeClose) {
this.events.beforeClose()
}
this.slim.content.classList.remove(this.config.open)
this.data.contentOpen = false
// Tell render to close
this.render.close()
// Clear search
this.search('') // Clear search
// If we arent tabbing focus back on the main element
this.render.mainFocus(false)
// Reset the content below
setTimeout(() => {
this.slim.content.removeAttribute('style')
this.data.contentPosition = 'below'
if (this.config.isMultiple && this.slim.multiSelected) {
this.slim.multiSelected.container.classList.remove(this.config.openAbove)
this.slim.multiSelected.container.classList.remove(this.config.openBelow)
} else if (this.slim.singleSelected) {
this.slim.singleSelected.container.classList.remove(this.config.openAbove)
this.slim.singleSelected.container.classList.remove(this.config.openBelow)
// Run afterClose callback
if (this.events.afterClose) {
this.events.afterClose()
}
// After content is closed lets blur on the input field
this.slim.search.input.blur()
// Update settings
this.settings.isOpen = false
}, this.settings.timeoutDelay)
// Run afterClose callback
if (this.afterClose) { this.afterClose() }
}, this.config.timeoutDelay)
}
public moveContentAbove(): void {
let selectHeight: number = 0
if (this.config.isMultiple && this.slim.multiSelected) {
selectHeight = this.slim.multiSelected.container.offsetHeight
} else if (this.slim.singleSelected) {
selectHeight = this.slim.singleSelected.container.offsetHeight
if (this.settings.intervalMove) {
clearInterval(this.settings.intervalMove)
}
const contentHeight = this.slim.content.offsetHeight
const height = selectHeight + contentHeight - 1
this.slim.content.style.margin = '-' + height + 'px 0 0 0'
this.slim.content.style.height = (height - selectHeight + 1) + 'px'
this.slim.content.style.transformOrigin = 'center bottom'
this.data.contentPosition = 'above'
if (this.config.isMultiple && this.slim.multiSelected) {
this.slim.multiSelected.container.classList.remove(this.config.openBelow)
this.slim.multiSelected.container.classList.add(this.config.openAbove)
} else if (this.slim.singleSelected) {
this.slim.singleSelected.container.classList.remove(this.config.openBelow)
this.slim.singleSelected.container.classList.add(this.config.openAbove)
}
}
public moveContentBelow(): void {
this.data.contentPosition = 'below'
if (this.config.isMultiple && this.slim.multiSelected) {
this.slim.multiSelected.container.classList.remove(this.config.openAbove)
this.slim.multiSelected.container.classList.add(this.config.openBelow)
} else if (this.slim.singleSelected) {
this.slim.singleSelected.container.classList.remove(this.config.openAbove)
this.slim.singleSelected.container.classList.add(this.config.openBelow)
// Take in string value and search current options
public search(value: string): void {
// If the passed in value is not the same as the search input value
// then lets update the search input value
if (this.render.content.search.input.value !== value) {
this.render.content.search.input.value = value
}
}
// Set to enabled, remove disabled classes and removed disabled from original select
public enable(): void {
this.config.isEnabled = true
if (this.config.isMultiple && this.slim.multiSelected) {
this.slim.multiSelected.container.classList.remove(this.config.disabled)
} else if (this.slim.singleSelected) {
this.slim.singleSelected.container.classList.remove(this.config.disabled)
// If no search event run regular search
if (!this.events.search) {
// If value is empty then render all options
this.render.renderOptions(
value === '' ? this.store.getData() : this.store.search(value, this.events.searchFilter!),
)
return
}
// Disable original select but dont trigger observer
this.select.triggerMutationObserver = false
this.select.element.disabled = false
this.slim.search.input.disabled = false
this.select.triggerMutationObserver = true
}
// Search event exists so lets render the searching text
this.render.renderSearching()
// Set to disabled, add disabled classes and add disabled to original select
public disable(): void {
this.config.isEnabled = false
if (this.config.isMultiple && this.slim.multiSelected) {
this.slim.multiSelected.container.classList.add(this.config.disabled)
} else if (this.slim.singleSelected) {
this.slim.singleSelected.container.classList.add(this.config.disabled)
}
// Based upon the search event deal with the response
const searchResp = this.events.search(value, this.store.getSelectedOptions())
// Enable original select but dont trigger observer
this.select.triggerMutationObserver = false
this.select.element.disabled = true
this.slim.search.input.disabled = true
this.select.triggerMutationObserver = true
}
// If the search event returns a promise
if (searchResp instanceof Promise) {
searchResp
.then((data: DataArrayPartial) => {
// Update the render with the new data
this.render.renderOptions(this.store.partialToFullData(data))
})
.catch((err: Error | string) => {
// Update the render with error
this.render.renderError(typeof err === 'string' ? err : err.message)
})
// Take in string value and search current options
public search(value: string): void {
// Only filter data and rerender if value has changed
if (this.data.searchValue === value) { return }
this.slim.search.input.value = value
if (this.config.isAjax) {
const master = this
this.config.isSearching = true
this.render()
// If ajax call it
if (this.ajax) {
this.ajax(value, (info: any) => {
// Only process if return callback is not false
master.config.isSearching = false
if (Array.isArray(info)) {
info.unshift({ text: '', placeholder: true })
master.setData(info)
master.data.search(value)
master.render()
} else if (typeof info === 'string') {
master.slim.options(info)
} else {
master.render()
}
})
}
return
} else if (Array.isArray(searchResp)) {
// Update the render options
this.render.renderOptions(this.store.partialToFullData(searchResp))
} else {
this.data.search(value)
this.render()
// Update the render with error
this.render.renderError('Search event must return a promise or an array of data')
}
}
public setSearchText(text: string): void {
this.config.searchText = text
}
public render(): void {
if (this.config.isMultiple) {
this.slim.values()
} else {
this.slim.placeholder()
this.slim.deselect()
public destroy(): void {
// Remove all event listeners
document.removeEventListener('click', this.documentClick)
window.removeEventListener('resize', this.windowResize, false)
if (this.settings.openPosition === 'auto') {
window.removeEventListener('scroll', this.windowScroll, false)
}
this.slim.options()
}
// Display original select again and remove slim
public destroy(id: string | null = null): void {
const slim = (id ? document.querySelector('.' + id + '.ss-main') : this.slim.container)
const select = (id ? document.querySelector(`[data-ssid=${id}]`) as HTMLSelectElement : this.select.element)
// If there is no slim dont do anything
if (!slim || !select) { return }
// Delete the store data
this.store.setData([])
// Remove the render
this.render.destroy()
document.removeEventListener('click', this.documentClick)
// Show the original select element
this.select.destroy()
}
if (this.config.showContent === 'auto') {
window.removeEventListener('scroll', this.windowScroll, false)
private windowResize: (e: Event) => void = debounce(() => {
if (!this.settings.isOpen) {
return
}
// Show original select
select.style.display = ''
delete select.dataset.ssid
this.render.moveContent()
})
// Remove slim from original select dropdown
const el = select as any
el.slim = null
// Event listener for window scrolling
private windowScroll: (e: Event) => void = debounce(() => {
// If the content is not open, there is no need to move it
if (!this.settings.isOpen) {
return
}
// Remove slim select
if (slim.parentElement) {
slim.parentElement.removeChild(slim)
// If openContent is not auto set content
if (this.settings.openPosition === 'down') {
this.render.moveContentBelow()
return
} else if (this.settings.openPosition === 'up') {
this.render.moveContentAbove()
return
}
// remove the content if it was added to the document body
if (this.config.addToBody) {
const slimContent = (id ? document.querySelector('.' + id + '.ss-content') : this.slim.content)
if (!slimContent) { return }
document.body.removeChild(slimContent)
// Determine where to put the content
if (this.settings.contentPosition === 'relative') {
this.render.moveContentBelow()
} else if (this.render.putContent(this.render.content.main, this.settings.isOpen) === 'up') {
this.render.moveContentAbove()
} else {
this.render.moveContentBelow()
}
}
})
// Event listener for document click
private documentClick: (e: Event) => void = (e: Event) => {
if (e.target && !hasClassInTree(e.target as HTMLElement, this.config.id)) {
// If the content is not open, there is no need to close it
if (!this.settings.isOpen) {
return
}
// Check if the click was on the content by looking at the parents
if (e.target && !hasClassInTree(e.target as HTMLElement, this.settings.id)) {
this.close()
}
}
}

@@ -1,147 +0,309 @@

import SlimSelect from './index'
import { Option, Optgroup, dataArray } from './data'
import { kebabCase } from './helper'
import { generateID, kebabCase } from './helper'
import { DataArray, DataObject, Optgroup, Option } from './store'
interface Constructor {
select: HTMLSelectElement
main: SlimSelect
}
export default class Select {
public select: HTMLSelectElement
public listen: boolean = false
export class Select {
public element: HTMLSelectElement
public main: SlimSelect
public mutationObserver: MutationObserver | null
public triggerMutationObserver: boolean = true
constructor(info: Constructor) {
this.element = info.select
this.main = info.main
// Mutation observer fields
public onSelectChange?: (data: DataArray) => void
public onValueChange?: (value: string[]) => void
private observer: MutationObserver | null = null
// If original select is set to disabled lets make sure slim is too
if (this.element.disabled) { this.main.config.isEnabled = false }
constructor(select: HTMLSelectElement) {
this.select = select
}
this.addAttributes()
this.addEventListeners()
this.mutationObserver = null
this.addMutationObserver()
// Set to enabled
public enable(): void {
// Disable original select but dont trigger observer
this.disconnectObserver()
this.select.disabled = false
this.connectObserver()
}
// Add slim to original select dropdown
const el = this.element as any
el.slim = info.main
// Set to disabled
public disable(): void {
// Enable original select but dont trigger observer
this.disconnectObserver()
this.select.disabled = true
this.connectObserver()
}
public setValue(): void {
if (!this.main.data.getSelected()) { return }
// Set misc attributes on the main select element
public hideUI(): void {
this.select.tabIndex = -1
this.select.style.display = 'none'
this.select.setAttribute('aria-hidden', 'true')
}
if (this.main.config.isMultiple) {
// If multiple loop through options and set selected
const selected = this.main.data.getSelected() as Option[]
const options = this.element.options as any as HTMLOptionElement[]
for (const o of options) {
o.selected = false
for (const s of selected) {
if (s.value === o.value) {
o.selected = true
}
}
}
public showUI(): void {
this.select.removeAttribute('tabindex')
this.select.style.display = ''
this.select.removeAttribute('aria-hidden')
}
public changeListen(on: boolean) {
this.listen = on
// Deal with some observer situations
if (this.listen) {
this.connectObserver()
} else {
// If single select simply set value
const selected = this.main.data.getSelected() as any
this.element.value = (selected ? selected.value : '')
this.disconnectObserver()
}
}
// Do not trigger onChange callbacks for this event listener
this.main.data.isOnChangeEnabled = false
this.element.dispatchEvent(new CustomEvent('change', { bubbles: true }))
this.main.data.isOnChangeEnabled = true
// Add change listener to original select
public addSelectChangeListener(func: (data: DataArray) => void): void {
this.onSelectChange = func
this.addObserver()
this.connectObserver()
this.changeListen(true) // Last start listening
}
public addAttributes() {
this.element.tabIndex = -1
this.element.style.display = 'none'
// remove change listener from original select
public removeSelectChangeListener(): void {
this.changeListen(false) // First stop listening
this.onSelectChange = undefined
}
// Add slim select id
this.element.dataset.ssid = this.main.config.id
this.element.setAttribute('aria-hidden', 'true')
public addValueChangeListener(func: (value: string[]) => void): void {
this.onValueChange = func
this.select.addEventListener('change', this.valueChange.bind(this))
}
// Add onChange listener to original select
public addEventListeners() {
this.element.addEventListener('change', (e: Event) => {
this.main.data.setSelectedFromSelect()
this.main.render()
})
public removeValueChangeListener(): void {
this.onValueChange = undefined
this.select.removeEventListener('change', this.valueChange.bind(this))
}
public valueChange(ev: Event): any {
if (this.onValueChange) {
this.onValueChange(this.getSelectedValues())
}
}
private observeWrapper(mutations: MutationRecord[]): void {
if (this.onSelectChange) {
this.onSelectChange(this.getData())
}
}
// Add MutationObserver to select
public addMutationObserver(): void {
// Only add if not in ajax mode
if (this.main.config.isAjax) { return }
private addObserver(): void {
// If mutation observer already exists then disconnect and
if (this.observer) {
this.disconnectObserver()
this.observer = null
}
this.mutationObserver = new MutationObserver((mutations) => {
if (!this.triggerMutationObserver) {return}
// If anything changes in the select then update the data
this.observer = new MutationObserver(this.observeWrapper)
}
this.main.data.parseSelectData()
this.main.data.setSelectedFromSelect()
this.main.render()
private connectObserver(): void {
if (this.observer) {
this.observer.observe(this.select, {
attributes: true,
childList: true,
characterData: true,
subtree: true,
})
}
}
mutations.forEach((mutation) => {
if (mutation.attributeName === 'class') {
this.main.slim.updateContainerDivClass(this.main.slim.container)
private disconnectObserver(): void {
if (this.observer) {
this.observer.disconnect()
}
}
// From the select element pull optgroup and options into data
public getData(): DataArray {
let data = []
// Loop through nodes and get data
const nodes = this.select.childNodes as any as HTMLOptGroupElement[] | HTMLOptionElement[]
for (const n of nodes) {
// Optgroup
if (n.nodeName === 'OPTGROUP') {
data.push(this.getDataFromOptgroup(n as HTMLOptGroupElement))
}
// Option
if (n.nodeName === 'OPTION') {
data.push(this.getDataFromOption(n as HTMLOptionElement))
}
}
return data
}
public getDataFromOptgroup(optgroup: HTMLOptGroupElement): Optgroup {
let data = {
id: '',
label: optgroup.label,
options: [],
} as Optgroup
const options = optgroup.childNodes as any as HTMLOptionElement[]
for (const o of options) {
if (o.nodeName === 'OPTION') {
data.options.push(this.getDataFromOption(o as HTMLOptionElement))
}
}
return data
}
public getSelectedValues(): string[] {
let values = []
// Loop through options and set selected
const options = this.select.childNodes as any as (HTMLOptGroupElement | HTMLOptionElement)[]
for (const o of options) {
if (o.nodeName === 'OPTGROUP') {
const optgroupOptions = o.childNodes as any as HTMLOptionElement[]
for (const oo of optgroupOptions) {
if (oo.nodeName === 'OPTION') {
const option = oo as HTMLOptionElement
if (option.selected) {
values.push(option.value)
}
}
}
})
})
}
this.observeMutationObserver()
if (o.nodeName === 'OPTION') {
const option = o as HTMLOptionElement
if (option.selected) {
values.push(option.value)
}
}
}
return values
}
public observeMutationObserver(): void {
if (!this.mutationObserver) { return }
// From passed in option pull pieces of usable information
public getDataFromOption(option: HTMLOptionElement): Option {
return {
id: (option.dataset ? option.dataset.id : false) || generateID(),
value: option.value,
text: option.text,
html: option.innerHTML,
selected: option.selected,
display: option.style.display === 'none' ? false : true,
disabled: option.disabled,
mandatory: option.dataset ? option.dataset.mandatory === 'true' : false,
placeholder: option.dataset.placeholder === 'true',
class: option.className,
style: option.style.cssText,
data: option.dataset,
} as Option
}
this.mutationObserver.observe(this.element, {
attributes: true,
childList: true,
characterData: true
})
public setSelected(value: string[]): void {
// Loop through options and set selected
const options = this.select.childNodes as any as (HTMLOptGroupElement | HTMLOptionElement)[]
for (const o of options) {
if (o.nodeName === 'OPTGROUP') {
const optgroup = o as HTMLOptGroupElement
const optgroupOptions = optgroup.childNodes as any as HTMLOptionElement[]
for (const oo of optgroupOptions) {
if (oo.nodeName === 'OPTION') {
const option = oo as HTMLOptionElement
option.selected = value.includes(option.value)
}
}
}
if (o.nodeName === 'OPTION') {
const option = o as HTMLOptionElement
option.selected = value.includes(option.value)
}
}
}
public disconnectMutationObserver(): void {
if (this.mutationObserver) {
this.mutationObserver.disconnect()
public updateSelect(id?: string, style?: string, classes?: string[]): void {
// Stop listening to changes
this.changeListen(false)
// Update id
if (id) {
this.select.id = id
}
// Update style
if (style) {
this.select.style.cssText = style
}
// Update classes
if (classes) {
this.select.className = ''
classes.forEach((c) => {
if (c.trim() !== '') {
this.select.classList.add(c.trim())
}
})
}
// Start listening to changes
this.changeListen(true)
}
// Create select element and optgroup/options
public create(data: dataArray): void {
public updateOptions(data: DataArray): void {
// Stop listening to changes
this.changeListen(false)
// Clear out select
this.element.innerHTML = ''
this.select.innerHTML = ''
for (const d of data) {
if (d.hasOwnProperty('options')) {
const optgroupObject = d as Optgroup
const optgroupEl = document.createElement('optgroup') as HTMLOptGroupElement
optgroupEl.label = optgroupObject.label
if (optgroupObject.options) {
for (const oo of optgroupObject.options) {
optgroupEl.appendChild(this.createOption(oo))
}
}
this.element.appendChild(optgroupEl)
} else {
this.element.appendChild(this.createOption(d))
if (d instanceof Optgroup) {
this.select.appendChild(this.createOptgroup(d))
}
if (d instanceof Option) {
this.select.appendChild(this.createOption(d))
}
}
// Start listening to changes
this.changeListen(true)
}
public createOption(info: any): HTMLOptionElement {
public createOptgroup(optgroup: Optgroup): HTMLOptGroupElement {
const optgroupEl = document.createElement('optgroup')
optgroupEl.id = optgroup.id
optgroupEl.label = optgroup.label
if (optgroup.options) {
for (const o of optgroup.options) {
optgroupEl.appendChild(this.createOption(o))
}
}
return optgroupEl
}
public createOption(info: Option): HTMLOptionElement {
const optionEl = document.createElement('option')
optionEl.value = info.value !== '' ? info.value : info.text
optionEl.innerHTML = info.innerHTML || info.text
if (info.selected) { optionEl.selected = info.selected }
optionEl.innerHTML = info.html || info.text
if (info.selected) {
optionEl.selected = info.selected
}
if (info.disabled) {
optionEl.disabled = true
}
if (info.display === false) {
optionEl.style.display = 'none'
}
if (info.disabled) { optionEl.disabled = true }
if (info.placeholder) { optionEl.setAttribute('data-placeholder', 'true') }
if (info.mandatory) { optionEl.setAttribute('data-mandatory', 'true') }
if (info.placeholder) {
optionEl.setAttribute('data-placeholder', 'true')
}
if (info.mandatory) {
optionEl.setAttribute('data-mandatory', 'true')
}
if (info.class) {

@@ -160,2 +322,12 @@ info.class.split(' ').forEach((optionClass: string) => {

}
public destroy() {
this.changeListen(false)
this.disconnectObserver()
this.removeSelectChangeListener()
this.removeValueChangeListener()
// show the original select
this.showUI()
}
}
{
"compilerOptions": {
"strict": true, /* Enable all strict type-checking options. */
"declaration": true, /* Generates corresponding '.d.ts' file. */
"target": "es2016",
"module": "esnext",
"moduleResolution": "node",
"strict": true /* Enable all strict type-checking options. */,
"declaration": true /* Generates corresponding '.d.ts' file. */,
"outDir": "../../dist", // Output to dist folder
"removeComments": true /* Do not emit comments to output. */
}
}
"removeComments": true /* Do not emit comments to output. */,
"importHelpers": true /* Import emit helpers from 'tslib'. */
},
"include": ["**/*.ts"],
"exclude": ["**/*.test.ts"]
}
{
"compilerOptions": {
"declaration": true,
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"allowJs": false,
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"importHelpers": true,
"moduleResolution": "node",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
"lib": ["esnext", "dom"]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc