├── src ├── BsMultiSelect2.js ├── BsMultiSelect2.jquery.js ├── LayoutFactory.js ├── InitialDomFactory.js ├── OptionsAspect.js ├── plugins │ ├── PlaceholderCssPatchPlugin.js │ ├── WarningCssPatchPlugin.js │ ├── WarningBs5Plugin.js │ ├── WarningBs4Plugin.js │ ├── FloatingLabelCssPatchBs5Plugin.js │ ├── BsAppearanceBs4CssPatchPlugin.js │ ├── BsAppearanceBs5CssPatchPlugin.js │ ├── CssPatchBs5Plugin.js │ ├── CssPatchBs4Plugin.js │ ├── JQueryMethodsPlugin.js │ ├── FormResetPlugin.js │ ├── UpdateAppearancePlugin.js │ ├── PicksApiPlugin.js │ ├── FormRestoreOnBackwardPlugin.js │ ├── RtlPlugin.js │ ├── BsAppearanceBs4Plugin.js │ ├── BsAppearanceBs5Plugin.js │ ├── OptionsApiPlugin.js │ ├── LabelForAttributePlugin.js │ ├── CustomPickStylingsPlugin.js │ ├── FloatingLabelPlugin.js │ ├── PicksPlugin.js │ ├── CustomChoiceStylingsPlugin.js │ ├── ChoicesDynamicStylingPlugin.js │ ├── PickButtonPlugin.js │ ├── DisableComponentPlugin.js │ ├── index.js │ ├── WarningPlugin.js │ ├── HighlightPlugin.js │ ├── HiddenOptionAltPlugin.js │ ├── HiddenOptionPlugin.js │ ├── CreatePopperPlugin.js │ ├── PlaceholderPlugin.js │ ├── DisabledOptionPlugin.js │ └── ValidationApiPlugin.js ├── OnChangeAspect.js ├── SpecialPicksEventsAspect.js ├── ResetLayoutAspect.js ├── LoadAspect.js ├── ToolSet.js ├── CreateElementAspect.js ├── CountableChoicesListInsertAspect.js ├── ChoicesEnumerableAspect.js ├── ChoicesVisibilityAspect.js ├── AppendAspect.js ├── ShowErrorAspect.js ├── UpdateDataAspect.js ├── BsMultiSelectElement.js ├── BsMultiSelect.bs4.esm.js ├── NavigateAspect.js ├── AfterInputAspect.js ├── BsMultiSelect.esm.js ├── PicksElementAspect.js ├── OptionsLoopAspect.js ├── ResetFilterListAspect.js ├── DomFactories.js ├── CreateWrapAspect.js ├── Wraps.js ├── ProducePickAspect.js ├── BsMultiSelect.bs4.jquery.js ├── BsMultiSelect.jquery.js ├── PickDomFactory.js ├── InputAspect.js ├── CreateForJQuery.js ├── BsMultiSelect.web.js ├── ChoicesDomFactory.js ├── FilterManagerAspect.js ├── StaticManagerAspect.js ├── index.js ├── ProduceChoiceAspect.js ├── MultiSelectBuilder.js ├── ModuleFactory.js ├── PluginSet.js ├── FilterDomFactory.js ├── StaticDomFactory.js ├── AddToJQueryPrototype.js ├── PicksDomFactory.js ├── BsMultiSelectDepricatedParameters.js ├── ToolsStyling.js ├── PluginManager.js └── ToolsDom.js ├── .stylelintignore ├── BsMultiSelect.code-workspace ├── .vscode ├── notes.txt └── launch.json ├── .eslintignore ├── .gitignore ├── index.d.ts ├── .cache └── .stylelintcache ├── KB.md ├── babel.bundle.umd.config.js ├── babel.bundle.esm.config.js ├── babel.mjs.config.js ├── babel.mjs.ecma5.config.js ├── .stylelintrc ├── debugJsSimplePicks.html ├── dist └── css │ ├── BsMultiSelect.bs4.min.css │ ├── BsMultiSelect.min.css │ ├── BsMultiSelect.bs4.css │ ├── BsMultiSelect.css │ ├── BsMultiSelect.bs4.css.map │ └── BsMultiSelect.css.map └── npm-debug.log /src/BsMultiSelect2.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/BsMultiSelect2.jquery.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | **/*.min.css 2 | **/dist/ 3 | **/node_modules/ 4 | 5 | -------------------------------------------------------------------------------- /BsMultiSelect.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "./" 5 | } 6 | ] 7 | } -------------------------------------------------------------------------------- /.vscode/notes.txt: -------------------------------------------------------------------------------- 1 | Possible: 2 | "url": "file:///D:/cot/DashboardCode/BsMultiSelect/snippetJsAdd.html" -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.min.js 2 | **/dist/ 3 | **/vendor/ 4 | /_gh_pages/ 5 | /package.js 6 | /build/rollup.config.js -------------------------------------------------------------------------------- /src/LayoutFactory.js: -------------------------------------------------------------------------------- 1 | export function LayoutFactory( eventHandlers){ 2 | return { 3 | 4 | } 5 | } -------------------------------------------------------------------------------- /src/InitialDomFactory.js: -------------------------------------------------------------------------------- 1 | // export function InitialDomFactory(initialElement){ 2 | // return { 3 | 4 | // } 5 | // } -------------------------------------------------------------------------------- /src/OptionsAspect.js: -------------------------------------------------------------------------------- 1 | // export function OptionsAspect(options){ 2 | // return { 3 | // getOptions : () => options 4 | // } 5 | // } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directories 2 | node_modules/ 3 | 4 | # Optional npm cache directory 5 | .npm 6 | 7 | # Optional eslint cache 8 | .eslintcache 9 | -------------------------------------------------------------------------------- /src/plugins/PlaceholderCssPatchPlugin.js: -------------------------------------------------------------------------------- 1 | export function PlaceholderCssPatchPlugin(defaults){ 2 | defaults.cssPatch.filterInput_empty = 'form-control' 3 | } -------------------------------------------------------------------------------- /src/OnChangeAspect.js: -------------------------------------------------------------------------------- 1 | export function OnChangeAspect(staticDom, name) { 2 | return { 3 | onChange(){ 4 | staticDom.trigger(name) 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /src/SpecialPicksEventsAspect.js: -------------------------------------------------------------------------------- 1 | export function SpecialPicksEventsAspect(){ 2 | return { 3 | backSpace(pick){ 4 | pick.setSelectedFalse(); 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /src/ResetLayoutAspect.js: -------------------------------------------------------------------------------- 1 | export function ResetLayoutAspect(resetFilterAspect){ 2 | return { 3 | resetLayout(){ 4 | resetFilterAspect.resetFilter(); 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /src/LoadAspect.js: -------------------------------------------------------------------------------- 1 | 2 | export function LoadAspect(optionsLoopAspect){ 3 | return{ 4 | load(){ // redriven in AppearancePlugin, FormRestoreOnBackwardPlugin 5 | optionsLoopAspect.loop(); 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /src/ToolSet.js: -------------------------------------------------------------------------------- 1 | import {composeSync} from './ToolsJs' 2 | import {EventBinder} from './ToolsDom' 3 | import {addStyling, toggleStyling} from './ToolsStyling' 4 | 5 | export let utilities = {composeSync, EventBinder, addStyling, toggleStyling} -------------------------------------------------------------------------------- /src/plugins/WarningCssPatchPlugin.js: -------------------------------------------------------------------------------- 1 | export function WarningCssPatchPlugin(defaults){ 2 | defaults.cssPatch.warning = {paddingLeft: '.25rem', paddingRight: '.25rem', zIndex: 4, fontSize:'small', backgroundColor: 'var(--bs-warning)'} 3 | } -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@dashboardcode/bsmultiselect'{ 2 | // example 3 | export function ModuleFactory(environment:any): any 4 | //export declare type ModuleFactory = (environment: any) => any; 5 | } 6 | 7 | 8 | //export * from './src'; -------------------------------------------------------------------------------- /src/CreateElementAspect.js: -------------------------------------------------------------------------------- 1 | export function CreateElementAspect(createElement, createElementFromHtml, createElementFromHtmlPutAfter){ 2 | return { 3 | createElement, 4 | createElementFromHtml, 5 | createElementFromHtmlPutAfter 6 | } 7 | } -------------------------------------------------------------------------------- /.cache/.stylelintcache: -------------------------------------------------------------------------------- 1 | [{"D:\\cot\\DashboardCode\\BsMultiSelect\\scss\\BsMultiSelect.scss":"1","D:\\cot\\DashboardCode\\BsMultiSelect\\scss\\BsMultiSelect.bs4.scss":"2"},{"size":8172,"mtime":1629851082166,"hashOfConfig":"3"},{"size":8430,"mtime":1638303239148,"hashOfConfig":"3"},"xcst03"] -------------------------------------------------------------------------------- /src/CountableChoicesListInsertAspect.js: -------------------------------------------------------------------------------- 1 | export function CountableChoicesListInsertAspect(wrapsCollection, countableChoicesList){ 2 | return { 3 | countableChoicesListInsert(wrap, key){ 4 | let choiceNext = wrapsCollection.getNext(key); 5 | countableChoicesList.add(wrap, choiceNext) 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /src/plugins/WarningBs5Plugin.js: -------------------------------------------------------------------------------- 1 | import {plug as plug2, preset as preset2} from './WarningPlugin'; 2 | 3 | export function WarningBs5Plugin(defaults){ 4 | preset(defaults); 5 | return {plug:plug2}; 6 | } 7 | 8 | export function preset(defaults){ 9 | defaults.css.warning = 'alert-warning'; 10 | preset2(defaults); 11 | } 12 | -------------------------------------------------------------------------------- /src/ChoicesEnumerableAspect.js: -------------------------------------------------------------------------------- 1 | export function ChoicesEnumerableAspect(countableChoicesList, getNext){ 2 | return { 3 | forEach(f){ 4 | let wrap = countableChoicesList.getHead(); 5 | while(wrap){ 6 | f(wrap); 7 | wrap = getNext(wrap); 8 | } 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/plugins/WarningBs4Plugin.js: -------------------------------------------------------------------------------- 1 | import {plug as plug2, preset as preset2} from './WarningPlugin'; 2 | 3 | export function WarningBs4Plugin(defaults){ 4 | preset(defaults); 5 | return {plug:plug2}; 6 | } 7 | 8 | export function preset(defaults){ 9 | defaults.css.warning = 'alert-warning bg-warning'; 10 | preset2(defaults); 11 | } 12 | -------------------------------------------------------------------------------- /src/plugins/FloatingLabelCssPatchBs5Plugin.js: -------------------------------------------------------------------------------- 1 | export function FloatingLabelCssPatchBs5Plugin(defaults){ 2 | let cssPatch = defaults.cssPatch; 3 | cssPatch.label_floating_lifted = {opacity: '.65', transform : 'scale(.85) translateY(-.5rem) translateX(.15rem)'}; 4 | cssPatch.picks_floating_lifted = {paddingTop: '1.625rem', paddingLeft:'0.8rem', paddingBottom : '0'}; 5 | } -------------------------------------------------------------------------------- /KB.md: -------------------------------------------------------------------------------- 1 | # RTL 2 | https://www.rtlstyling.com/posts/rtl-styling/ (RU: https://habr.com/ru/post/484886/ ) 3 | 4 | # NPM 5 | 6 | npm publish --tag beta 7 | 8 | 9 | # NPM update 10 | npm outdated -g 11 | npm update -g 12 | 13 | # Git 14 | "please check out a branch to push to a remote" 15 | git branch -r // list all remote branches 16 | git branch --show-current // get current branch -------------------------------------------------------------------------------- /src/ChoicesVisibilityAspect.js: -------------------------------------------------------------------------------- 1 | export function ChoicesVisibilityAspect(choicesElement) { 2 | 3 | return { 4 | isChoicesVisible(){ 5 | return choicesElement.style.display != 'none'}, 6 | setChoicesVisible(visible){ 7 | choicesElement.style.display = visible ? 'block' : 'none'; 8 | }, 9 | updatePopupLocation(){ 10 | 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "type": "pwa-chrome", 10 | "request": "launch", 11 | "name": "Launch Chrome against localhost", 12 | "url": "http://127.0.0.1:5500/debugJsSimple.html", 13 | 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /src/plugins/BsAppearanceBs4CssPatchPlugin.js: -------------------------------------------------------------------------------- 1 | export function BsAppearanceBs4CssPatchPlugin(defaults){ 2 | let cssPatch = defaults.cssPatch; 3 | cssPatch.picks_def = {minHeight: 'calc(2.25rem + 2px)'}; 4 | cssPatch.picks_lg = {minHeight: 'calc(2.875rem + 2px)'}; 5 | cssPatch.picks_sm = {minHeight: 'calc(1.8125rem + 2px)'}; 6 | 7 | cssPatch.picks_focus_valid = {borderColor: '', boxShadow: '0 0 0 0.2rem rgba(40, 167, 69, 0.25)'}; 8 | cssPatch.picks_focus_invalid = {borderColor: '', boxShadow: '0 0 0 0.2rem rgba(220, 53, 69, 0.25)'}; 9 | } -------------------------------------------------------------------------------- /src/AppendAspect.js: -------------------------------------------------------------------------------- 1 | export function AppendAspect(){ 2 | return { 3 | appendToContainer: (containerElement, picksDom, filterDom, choicesDom, isDisposablePicksElementFlag)=> { 4 | picksDom.pickFilterElement.appendChild(filterDom.filterInputElement); 5 | picksDom.picksElement.appendChild(picksDom.pickFilterElement); 6 | containerElement.appendChild(choicesDom.choicesElement); 7 | if (isDisposablePicksElementFlag) 8 | containerElement.appendChild(picksDom.picksElement) 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/plugins/BsAppearanceBs5CssPatchPlugin.js: -------------------------------------------------------------------------------- 1 | export function BsAppearanceBs5CssPatchPlugin(defaults){ 2 | let cssPatch = defaults.cssPatch; 3 | cssPatch.picks_def = {minHeight: 'calc(2.25rem + 2px)'}; 4 | cssPatch.picks_lg = {minHeight: 'calc(2.875rem + 2px)'}; 5 | cssPatch.picks_sm = {minHeight: 'calc(1.8125rem + 2px)'}; 6 | cssPatch.picks_floating_def = {minHeight: 'calc(3.5rem + 2px)'}; 7 | 8 | cssPatch.picks_focus_valid = {borderColor: '', boxShadow: '0 0 0 0.2rem rgba(40, 167, 69, 0.25)'}; 9 | cssPatch.picks_focus_invalid = {borderColor: '', boxShadow: '0 0 0 0.2rem rgba(220, 53, 69, 0.25)'}; 10 | } -------------------------------------------------------------------------------- /src/ShowErrorAspect.js: -------------------------------------------------------------------------------- 1 | export function ShowErrorAspect(staticDom){ 2 | return { 3 | showError(error){ 4 | let {createElementAspect, initialElement} = staticDom; 5 | let errorElement = createElementAspect.createElement('SPAN'); 6 | errorElement.style.backgroundColor = 'red'; 7 | errorElement.style.color = 'white'; 8 | errorElement.style.block = 'inline-block'; 9 | errorElement.style.padding = '0.2rem 0.5rem'; 10 | errorElement.textContent = 'BsMultiSelect ' + error.toString(); 11 | initialElement.parentNode.insertBefore(errorElement, initialElement.nextSibling); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/UpdateDataAspect.js: -------------------------------------------------------------------------------- 1 | export function UpdateDataAspect(choicesDom, wraps, picksList, optionsLoopAspect, resetLayoutAspect){ 2 | return { 3 | updateData(){ 4 | // close drop down , remove filter 5 | resetLayoutAspect.resetLayout(); 6 | choicesDom.choicesListElement.innerHTML = ""; // TODO: there should better "optimization" 7 | wraps.clear(); 8 | picksList.forEach(pick=>pick.dispose()); 9 | picksList.reset(); 10 | optionsLoopAspect.loop(); 11 | } 12 | } 13 | } 14 | 15 | export function UpdateAspect(updateDataAspect){ 16 | return { 17 | update(){ 18 | updateDataAspect.updateData(); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /babel.bundle.umd.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | //retainLines: true, 3 | "presets": [ 4 | [ 5 | "@babel/preset-env", 6 | { 7 | "loose": true, // ES6 to ES5 8 | "bugfixes": true, 9 | // "useBuiltIns": "usage", 10 | "modules": false, 11 | "exclude": ["transform-typeof-symbol"], 12 | "targets": { 13 | "browsers": [ 14 | "chrome >= 45", "Firefox >= 38", "Explorer >= 10", "edge >= 12", "iOS >= 9","Safari >= 9","Android >= 4.4","Opera >= 30" 15 | ] 16 | }, 17 | "debug": true 18 | } 19 | ] 20 | ] 21 | } -------------------------------------------------------------------------------- /src/BsMultiSelectElement.js: -------------------------------------------------------------------------------- 1 | 2 | import {BsMultiSelect} from './BsMultiSelect.esm' 3 | 4 | export class BsMultiSelectElement extends HTMLElement { 5 | constructor() { 6 | super(); // must by spec 7 | var shadowRoot = this.attachShadow({mode: 'open'}); 8 | this.multiSelect = BsMultiSelect(this, environment, settings) 9 | shadowRoot.innerHTML = `
stub text
`; // container 10 | // https://developers.google.com/web/fundamentals/web-components/shadowdom 11 | } 12 | // connectedCallback(){ 13 | 14 | // } 15 | // disconnectedCallback(){ 16 | 17 | // } 18 | // attributeChangedCallback(attrName, oldVal, newVal){ 19 | 20 | // } 21 | // adoptedCallback(){ 22 | 23 | // } 24 | } 25 | -------------------------------------------------------------------------------- /src/plugins/CssPatchBs5Plugin.js: -------------------------------------------------------------------------------- 1 | import {CssPatchPlugin} from './CssPatchPlugin'; 2 | 3 | import {PicksDomFactoryPlugCssPatchBs5} from '../PicksDomFactory' // TODO move specific styles to button plugin 4 | import {ChoiceDomFactoryPlugCssPatch} from '../ChoiceDomFactory' 5 | import {ChoicesDomFactoryPlugCssPatch} from '../ChoicesDomFactory' 6 | import {FilterDomFactoryPlugCssPatch} from '../FilterDomFactory' 7 | 8 | export function CssPatchBs5Plugin(defaults){ 9 | var cssPatch = {}; 10 | 11 | PicksDomFactoryPlugCssPatchBs5(cssPatch); 12 | ChoiceDomFactoryPlugCssPatch(cssPatch); 13 | ChoicesDomFactoryPlugCssPatch(cssPatch); 14 | FilterDomFactoryPlugCssPatch(cssPatch); 15 | 16 | defaults.cssPatch = cssPatch; 17 | return CssPatchPlugin(defaults) 18 | } -------------------------------------------------------------------------------- /babel.bundle.esm.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | //retainLines: true, 3 | "presets": [ 4 | [ 5 | "@babel/preset-env", 6 | { 7 | "loose": true, // ES6 to ES5 8 | "bugfixes": true, 9 | // "useBuiltIns": "usage", 10 | "modules": false, 11 | "exclude": ["transform-typeof-symbol"], 12 | "targets": { 13 | "browsers": [ 14 | // where "script module" is possoble 15 | "chrome >= 61", "Firefox >= 60", "edge >= 16", "iOS >= 10.3","Safari >= 10.1","Android >= 93","Opera >= 48" 16 | ] 17 | }, 18 | "debug": true 19 | } 20 | ] 21 | ] 22 | } -------------------------------------------------------------------------------- /src/BsMultiSelect.bs4.esm.js: -------------------------------------------------------------------------------- 1 | import { createDefaultCssBs4 } from './DomFactories' 2 | import { Bs4PluginSet } from './PluginSet' 3 | import { ModuleFactory as ModuleFactoryImpl } from "./ModuleFactory" 4 | 5 | const defaultCss = createDefaultCssBs4(); 6 | 7 | function ModuleFactory(environment){ 8 | return ModuleFactoryImpl(environment, Bs4PluginSet, defaultCss); 9 | } 10 | 11 | function legacyConstructor(element, environment, settings){ 12 | console.log("DashboarCode.BsMultiSelect: 'BsMultiSelect' is depricated, use - ModuleFactory(environment).BsMultiSelect(element, settings)"); 13 | var {BsMultiSelect} = ModuleFactory(environment); 14 | var bsMultiSelect = BsMultiSelect(element, settings); 15 | return bsMultiSelect; 16 | } 17 | 18 | export { 19 | legacyConstructor as BsMultiSelect, 20 | ModuleFactory 21 | } -------------------------------------------------------------------------------- /src/NavigateAspect.js: -------------------------------------------------------------------------------- 1 | export function HoveredChoiceAspect(){ 2 | let hoveredChoice=null; 3 | return { 4 | getHoveredChoice: () => hoveredChoice, 5 | setHoveredChoice: (wrap) => {hoveredChoice = wrap}, 6 | resetHoveredChoice() { 7 | if (hoveredChoice) { 8 | hoveredChoice.choice.setHoverIn(false) 9 | hoveredChoice = null; 10 | } 11 | } 12 | } 13 | } 14 | 15 | export function NavigateAspect(hoveredChoiceAspect, navigate){ 16 | return { 17 | hoverIn(wrap){ 18 | hoveredChoiceAspect.resetHoveredChoice(); 19 | hoveredChoiceAspect.setHoveredChoice(wrap); 20 | wrap.choice.setHoverIn(true); 21 | }, 22 | navigate: (down) => navigate(down, hoveredChoiceAspect.getHoveredChoice()), 23 | } 24 | } -------------------------------------------------------------------------------- /src/AfterInputAspect.js: -------------------------------------------------------------------------------- 1 | export function AfterInputAspect(filterManagerAspect, navigateAspect, choicesVisibilityAspect, hoveredChoiceAspect){ 2 | return { 3 | visible(showChoices, visibleCount){ 4 | let panelIsVisble = choicesVisibilityAspect.isChoicesVisible(); 5 | if (!panelIsVisble) { 6 | showChoices(); 7 | } 8 | if (visibleCount == 1) { 9 | navigateAspect.hoverIn(filterManagerAspect.getNavigateManager().getHead()) 10 | } else { 11 | if (panelIsVisble) 12 | hoveredChoiceAspect.resetHoveredChoice(); 13 | } 14 | }, 15 | notVisible(hideChoices){ 16 | if (choicesVisibilityAspect.isChoicesVisible()){ 17 | hideChoices(); 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /babel.mjs.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | //retainLines: true, 3 | // "presets": [ 4 | // [ 5 | // "@babel/preset-env", 6 | // { 7 | // "loose": false, // ES6 to ES5 8 | // "bugfixes": true, 9 | // // "useBuiltIns": "usage", 10 | // "modules": false, 11 | // "exclude": ["transform-typeof-symbol"], 12 | // // "targets": { 13 | // // "browsers": [ 14 | // // // browsers that can load ESM bundles: https://caniuse.com/es6-module 15 | // // "chrome >= 61", "Firefox >= 60", "edge >= 16", "iOS >= 10.3","Safari >= 10.1","Android >= 93","Opera >= 48"] 16 | // // }, 17 | // "debug": true 18 | // } 19 | // ] 20 | // ] 21 | } -------------------------------------------------------------------------------- /src/BsMultiSelect.esm.js: -------------------------------------------------------------------------------- 1 | import {createDefaultCssBs5} from "./DomFactories"; 2 | import {Bs4PluginSet} from './PluginSet' 3 | import {ModuleFactory as ModuleFactoryImpl} from "./ModuleFactory"; 4 | 5 | const defaultCss = createDefaultCssBs5(); 6 | 7 | function ModuleFactory(environment){ 8 | return ModuleFactoryImpl( 9 | environment, 10 | Bs4PluginSet, 11 | defaultCss 12 | ); 13 | } 14 | 15 | function legacyConstructor(element, environment, settings){ 16 | console.log("DashboarCode.BsMultiSelect: 'BsMultiSelect' is depricated, use - ModuleFactory(environment).BsMultiSelect(element, settings)"); 17 | var {BsMultiSelect} = ModuleFactory(environment); 18 | var bsMultiSelect = BsMultiSelect(element, settings); 19 | return bsMultiSelect; 20 | } 21 | 22 | export { 23 | legacyConstructor as BsMultiSelect, 24 | ModuleFactory 25 | } -------------------------------------------------------------------------------- /src/plugins/CssPatchBs4Plugin.js: -------------------------------------------------------------------------------- 1 | import {CssPatchPlugin} from './CssPatchPlugin'; 2 | 3 | //import {PickDomFactoryPlugCssPatch} from '../PickDomFactory' 4 | import {PicksDomFactoryPlugCssPatchBs4} from '../PicksDomFactory' // TODO move specific styles to button plugin 5 | import {ChoiceDomFactoryPlugCssPatch} from '../ChoiceDomFactory' 6 | import {ChoicesDomFactoryPlugCssPatch} from '../ChoicesDomFactory' 7 | import {FilterDomFactoryPlugCssPatch} from '../FilterDomFactory' 8 | 9 | export function CssPatchBs4Plugin(defaults) { 10 | var cssPatch = {}; 11 | 12 | //PickDomFactoryPlugCssPatch(cssPatch); 13 | PicksDomFactoryPlugCssPatchBs4(cssPatch); 14 | ChoiceDomFactoryPlugCssPatch(cssPatch); 15 | ChoicesDomFactoryPlugCssPatch(cssPatch); 16 | FilterDomFactoryPlugCssPatch(cssPatch); 17 | 18 | defaults.cssPatch = cssPatch; 19 | return CssPatchPlugin(defaults) 20 | } -------------------------------------------------------------------------------- /src/PicksElementAspect.js: -------------------------------------------------------------------------------- 1 | import {EventBinder, containsAndSelf} from './ToolsDom'; 2 | 3 | export function PicksElementAspect(picksElement){ 4 | var componentDisabledEventBinder = EventBinder(); 5 | var skipoutAndResetMousedownEventBinder = EventBinder(); 6 | return { 7 | containsAndSelf(element){ 8 | return containsAndSelf(picksElement, element); 9 | }, 10 | onClickUnbind(){ 11 | componentDisabledEventBinder.unbind(); 12 | }, 13 | onClick(handler){ 14 | componentDisabledEventBinder.bind(picksElement, "click", handler); 15 | }, 16 | onMousedown(handler){ 17 | skipoutAndResetMousedownEventBinder.bind(picksElement, "mousedown", handler); 18 | }, 19 | unbind(){ 20 | skipoutAndResetMousedownEventBinder.unbind(); 21 | componentDisabledEventBinder.unbind(); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/OptionsLoopAspect.js: -------------------------------------------------------------------------------- 1 | export function OptionAttachAspect(createWrapAspect, createChoiceBaseAspect, buildAndAttachChoiceAspect, wraps){ 2 | return { 3 | attach(option){ 4 | let wrap = createWrapAspect.createWrap(option); 5 | wrap.choice = createChoiceBaseAspect.createChoiceBase(option); 6 | 7 | 8 | wraps.push(wrap); // note: before attach because attach need it for navigation management 9 | buildAndAttachChoiceAspect.buildAndAttachChoice(wrap); 10 | //wraps.push(wrap); 11 | } 12 | } 13 | } 14 | 15 | export function OptionsLoopAspect(dataWrap, optionAttachAspect){ 16 | return{ 17 | loop(){ 18 | let options = dataWrap.getOptions(); 19 | for(let i = 0; i { 9 | return { 10 | layout: () => { 11 | let {staticDom, choicesDom, filterDom, picksList, picksDom} = aspects; 12 | return { 13 | buildApi(api){ 14 | api.getContainer = () => staticDom.containerElement; 15 | api.getChoices = () => choicesDom.choicesElement; 16 | api.getChoicesList = () => choicesDom.choicesListElement; 17 | api.getFilterInput = () => filterDom.filterInputElement; 18 | api.getPicks = () => picksDom.picksElement; 19 | api.picksCount = () => picksList.getCount(); 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/ResetFilterListAspect.js: -------------------------------------------------------------------------------- 1 | export function ResetFilterListAspect(filterDom, filterManagerAspect){ 2 | return { 3 | forceResetFilter(){ // over in PlaceholderPlugin 4 | filterDom.setEmpty(); 5 | filterManagerAspect.processEmptyInput(); // over in PlaceholderPlugin 6 | } 7 | } 8 | } 9 | 10 | export function ResetFilterAspect(filterDom, resetFilterListAspect){ 11 | return { 12 | resetFilter(){ // call in OptionsApiPlugin 13 | if (!filterDom.isEmpty()) // call in Placeholder 14 | resetFilterListAspect.forceResetFilter(); // over in Placeholder 15 | } 16 | } 17 | } 18 | 19 | export function FocusInAspect(picksDom){ 20 | return { 21 | setFocusIn(focus){ // call in OptionsApiPlugin 22 | picksDom.setIsFocusIn(focus) // unique call, call BsAppearancePlugin 23 | picksDom.toggleFocusStyling() // over BsAppearancePlugin 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /src/DomFactories.js: -------------------------------------------------------------------------------- 1 | import {PickDomFactoryPlugCss} from './PickDomFactory' 2 | import {PicksDomFactoryPlugCss} from './PicksDomFactory' 3 | import {ChoiceDomFactoryPlugCssBs5, ChoiceDomFactoryPlugCssBs4} from './ChoiceDomFactory' 4 | import {ChoicesDomFactoryPlugCss} from './ChoicesDomFactory' 5 | import {FilterDomFactoryPlugCss} from './FilterDomFactory' 6 | 7 | export function createDefaultCssBs5(){ 8 | var defaultCss={}; 9 | PickDomFactoryPlugCss(defaultCss) 10 | PicksDomFactoryPlugCss(defaultCss) 11 | ChoiceDomFactoryPlugCssBs5(defaultCss) 12 | ChoicesDomFactoryPlugCss(defaultCss) 13 | FilterDomFactoryPlugCss(defaultCss) 14 | return defaultCss; 15 | } 16 | 17 | export function createDefaultCssBs4(){ 18 | var defaultCss={} 19 | PickDomFactoryPlugCss(defaultCss) 20 | PicksDomFactoryPlugCss(defaultCss) 21 | ChoiceDomFactoryPlugCssBs4(defaultCss) 22 | ChoicesDomFactoryPlugCss(defaultCss) 23 | FilterDomFactoryPlugCss(defaultCss) 24 | return defaultCss; 25 | } -------------------------------------------------------------------------------- /src/plugins/FormResetPlugin.js: -------------------------------------------------------------------------------- 1 | import {EventBinder, closestByTagName} from '../ToolsDom'; 2 | 3 | export function FormResetPlugin(){ 4 | return { 5 | plug 6 | } 7 | } 8 | 9 | export function plug(){ 10 | return (aspects) => { 11 | return { 12 | layout: () => { 13 | var {staticDom, updateDataAspect, environment} = aspects; 14 | 15 | var eventBuilder = EventBinder(); 16 | if (staticDom.selectElement){ 17 | var form = closestByTagName(staticDom.selectElement, 'FORM'); 18 | if (form) { 19 | eventBuilder.bind(form, 20 | 'reset', 21 | () => environment.window.setTimeout( ()=>updateDataAspect.updateData() ) ); 22 | } 23 | } 24 | return { 25 | dispose(){ 26 | eventBuilder.unbind(); 27 | } 28 | } 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/CreateWrapAspect.js: -------------------------------------------------------------------------------- 1 | 2 | // no overrides (not an aspect, just ) 3 | export function CreateChoiceBaseAspect(dataWrap){ 4 | return { 5 | createChoiceBase(option){ 6 | return { 7 | // navigation and filter support 8 | filteredPrev: null, 9 | filteredNext: null, 10 | searchText: dataWrap.getText(option).toLowerCase().trim(), // TODO make an index abstraction 11 | 12 | // internal state handlers, so they do not have "update semantics" 13 | isHoverIn: false, 14 | 15 | setHoverIn: null, 16 | 17 | choiceDom:null, 18 | 19 | itemPrev: null, 20 | itemNext: null, 21 | 22 | dispose: null 23 | } 24 | } 25 | } 26 | } 27 | 28 | export function CreateWrapAspect(){ 29 | return { 30 | createWrap(option){ 31 | return { 32 | option: option 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Wraps.js: -------------------------------------------------------------------------------- 1 | export function Wraps(wrapsCollection, 2 | listFacade_reset, listFacade_remove, listFacade_add) 3 | { 4 | return { 5 | push: (wrap) => push(wrap, wrapsCollection, listFacade_add), 6 | insert: (key, wrap) => insert(key, wrap, wrapsCollection, listFacade_add), 7 | remove: (key) => { 8 | var wrap = wrapsCollection.remove(key); 9 | listFacade_remove(wrap); 10 | return wrap; 11 | }, 12 | clear: () => { 13 | wrapsCollection.reset(); 14 | listFacade_reset(); 15 | }, 16 | dispose: () => wrapsCollection.forLoop(wrap => wrap.dispose()) 17 | } 18 | } 19 | 20 | function push(wrap, wrapsCollection, listFacade_add){ 21 | wrapsCollection.push(wrap); 22 | listFacade_add(wrap); 23 | } 24 | 25 | function insert(key, wrap, wrapsCollection, listFacade_add){ 26 | if (key>=wrapsCollection.getCount()) { 27 | push(wrap, wrapsCollection, listFacade_add); 28 | } 29 | else { 30 | wrapsCollection.add(wrap, key); 31 | listFacade_add(wrap, key); 32 | } 33 | } -------------------------------------------------------------------------------- /src/plugins/UpdateAppearancePlugin.js: -------------------------------------------------------------------------------- 1 | import {composeSync} from '../ToolsJs'; 2 | 3 | export function UpdateAppearancePlugin(){ 4 | return { 5 | plug 6 | } 7 | } 8 | 9 | export function plug(){ 10 | var updateAppearanceAspect = UpdateAppearanceAspect(); 11 | return (aspects) => { 12 | aspects.updateAppearanceAspect = updateAppearanceAspect; 13 | return { 14 | layout: () => { 15 | var {updateAspect, loadAspect} = aspects; 16 | 17 | updateAspect.update = composeSync(updateAspect.update, ()=>updateAppearanceAspect.updateAppearance()) 18 | loadAspect.load = composeSync(loadAspect.load, ()=>updateAppearanceAspect.updateAppearance()) 19 | 20 | return{ 21 | buildApi(api){ 22 | api.updateAppearance = ()=>updateAppearanceAspect.updateAppearance(); 23 | } 24 | } 25 | } 26 | 27 | } 28 | } 29 | } 30 | 31 | function UpdateAppearanceAspect(){ 32 | return { 33 | updateAppearance(){} 34 | } 35 | } -------------------------------------------------------------------------------- /src/ProducePickAspect.js: -------------------------------------------------------------------------------- 1 | export function ProducePickAspect(picksDom, pickDomFactory){ 2 | return { 3 | // overrided by DisableOptionPlugin 4 | producePick(wrap){ 5 | let {pickElement, attach, detach} = picksDom.createPickElement(); 6 | let pickDom = {pickElement}; 7 | let pickDomManagerHandlers = {attach, detach}; 8 | 9 | let pick = { 10 | wrap, 11 | pickDom, 12 | pickDomManagerHandlers, 13 | 14 | dispose: () => { 15 | detach(); 16 | pickDom.pickElement=null; 17 | pickDomManagerHandlers.attach=null; 18 | pick.wrap=null; 19 | pick.pickDom=null; 20 | pick.pickDomManagerHandlers=null; 21 | } 22 | } 23 | 24 | wrap.pick=pick; 25 | pickDomFactory.create(pick); 26 | 27 | pick.pickDomManagerHandlers.attach(); 28 | 29 | return pick; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/plugins/PicksApiPlugin.js: -------------------------------------------------------------------------------- 1 | export function PicksApiPlugin(){ 2 | return { 3 | plug 4 | } 5 | } 6 | 7 | export function plug(){ 8 | return (aspects) => { 9 | return { 10 | buildApi(api){ 11 | let {picksList, createWrapAspect} = aspects; 12 | api.forEachPeak = (f) => 13 | picksList.forEach(wrap=>f(wrap.option)); 14 | // TODO: getHeadPeak 15 | api.getTailPeak = () => picksList.getTail()?.option; 16 | api.countPeaks = () => picksList.getCount(); 17 | api.isEmptyPeaks = () => picksList.isEmpty(); 18 | 19 | api.addPick = (option) => { 20 | let wrap = createWrapAspect.createWrap(option); 21 | // TODO should be moved to specific plugins 22 | wrap.updateDisabled = () => {}; 23 | wrap.updateHidden = () => {}; 24 | 25 | let pickHandlers = wrap.choice.addPickForChoice(); 26 | } 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/BsMultiSelect.bs4.jquery.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery' 2 | import Popper from 'popper.js' 3 | 4 | import {createForJQuery} from './CreateForJQuery' 5 | 6 | import { Bs4PluginSet, multiSelectPlugins, picksPlugins, allPlugins} from './PluginSet' 7 | import {createDefaultCssBs4} from './DomFactories' 8 | 9 | 10 | import {MultiSelectBuilder} from './MultiSelectBuilder' 11 | import {utilities} from './ToolSet' 12 | 13 | import {shallowClearClone} from './ToolsJs' 14 | 15 | const defaultCss = createDefaultCssBs4(); 16 | 17 | const BsMultiSelect = ( 18 | (window, jQuery, createPopper) => { 19 | let plugins = shallowClearClone(Bs4PluginSet, multiSelectPlugins); 20 | return createForJQuery(window, jQuery, createPopper, 'BsMultiSelect', plugins, defaultCss ) 21 | } 22 | )(window, $, Popper) 23 | 24 | const BsPicks = ( 25 | (window, jQuery, createPopper) => { 26 | let plugins = shallowClearClone(Bs4PluginSet, picksPlugins); 27 | return createForJQuery(window, jQuery, createPopper, 'BsPicks', plugins, defaultCss) 28 | } 29 | )(window, $, Popper) 30 | 31 | export default {BsMultiSelect, BsPicks , MultiSelectTools: {MultiSelectBuilder, plugins: shallowClearClone(Bs4PluginSet, allPlugins), defaultCss, utilities} } -------------------------------------------------------------------------------- /src/BsMultiSelect.jquery.js: -------------------------------------------------------------------------------- 1 | import Popper from '@popperjs/core' 2 | 3 | import {createForJQuery} from './CreateForJQuery' 4 | 5 | import {Bs5PluginSet, multiSelectPlugins, picksPlugins, allPlugins} from './PluginSet' 6 | import {createDefaultCssBs5} from './DomFactories' 7 | 8 | import {MultiSelectBuilder} from './MultiSelectBuilder' 9 | import {utilities} from './ToolSet' 10 | 11 | import {shallowClearClone} from './ToolsJs' 12 | 13 | const defaultCss = createDefaultCssBs5(); 14 | 15 | const BsMultiSelect = ( 16 | (window, jQuery, globalPopper) => { 17 | let plugins = shallowClearClone(Bs5PluginSet, multiSelectPlugins); 18 | return createForJQuery(window, jQuery, globalPopper, 'BsMultiSelect', plugins, defaultCss) 19 | } 20 | )(window, window.jQuery, Popper) 21 | 22 | const BsPicks = ( 23 | (window, jQuery, globalPopper) => { 24 | let plugins = shallowClearClone(Bs5PluginSet, picksPlugins); 25 | return createForJQuery(window, jQuery, globalPopper, 'BsPicks', plugins, defaultCss) 26 | } 27 | )(window, window.jQuery, Popper) 28 | 29 | export default { BsMultiSelect, BsPicks, 30 | MultiSelectTools: {MultiSelectBuilder, plugins: shallowClearClone(Bs5PluginSet, allPlugins), defaultCss, utilities} 31 | } -------------------------------------------------------------------------------- /src/PickDomFactory.js: -------------------------------------------------------------------------------- 1 | import {composeSync} from './ToolsJs'; 2 | import {addStyling} from './ToolsStyling'; 3 | 4 | // empty but can be usefull in custom development 5 | export function PickDomFactoryPlugCss(css){ 6 | css.pickContent = ''; 7 | } 8 | 9 | export function PickDomFactory(css, createElementAspect, dataWrap){ 10 | return { 11 | create(pick){ 12 | let wrap = pick.wrap; 13 | let {pickDom, pickDomManagerHandlers} = pick; 14 | let pickElement = pickDom.pickElement; 15 | 16 | let pickContentElement = createElementAspect.createElement('SPAN'); 17 | 18 | pickElement.appendChild(pickContentElement); 19 | pickDom.pickContentElement=pickContentElement; 20 | pickDomManagerHandlers.updateData = () => { 21 | // this is not a generic because there could be more then one text field. 22 | pickContentElement.textContent = dataWrap.getText(wrap.option); 23 | } 24 | addStyling(pickContentElement, css.pickContent); 25 | pick.dispose=composeSync(pick.dispose, ()=>{ 26 | pickDom.pickContentElement=null; 27 | pickDomManagerHandlers.updateData=null; 28 | }) 29 | pickDomManagerHandlers.updateData(); // set visual text 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/plugins/FormRestoreOnBackwardPlugin.js: -------------------------------------------------------------------------------- 1 | import {composeSync} from '../ToolsJs'; 2 | 3 | export function FormRestoreOnBackwardPlugin(){ 4 | return { 5 | plug 6 | } 7 | } 8 | 9 | export function plug(){ 10 | return (aspects) => { 11 | return { 12 | layout: () => { 13 | let {staticDom, environment, loadAspect, updateOptionsSelectedAspect} = aspects; 14 | let window = environment.window; 15 | 16 | if (staticDom.selectElement && updateOptionsSelectedAspect){ 17 | loadAspect.load = composeSync(loadAspect.load, 18 | function(){ 19 | // support browser's "step backward" and form's values restore 20 | if (window.document.readyState !="complete"){ 21 | window.setTimeout(function(){ 22 | updateOptionsSelectedAspect.updateOptionsSelected(); 23 | // there are no need to add more updates as api.updateWasValidated() because backward never trigger .was-validate 24 | // also backward never set the state to invalid 25 | }); 26 | } 27 | }) 28 | } 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/InputAspect.js: -------------------------------------------------------------------------------- 1 | export function InputAspect( 2 | filterDom, 3 | filterManagerAspect 4 | ){ 5 | 6 | return { 7 | // overrided in SelectedOptionPlugin 8 | processInput(){ 9 | let filterInputValue = filterDom.getValue(); 10 | let text = filterInputValue.trim(); 11 | var isEmpty=false; 12 | if (text == '') 13 | isEmpty=true; 14 | else 15 | { 16 | filterManagerAspect.setFilter(text.toLowerCase()); 17 | } 18 | 19 | if (!isEmpty) 20 | { 21 | if ( filterManagerAspect.getNavigateManager().getCount() == 1) 22 | { 23 | // todo: move exact match to filterManager 24 | let fullMatchWrap = filterManagerAspect.getNavigateManager().getHead(); 25 | let text = filterManagerAspect.getFilter(); 26 | if (fullMatchWrap.choice.searchText == text) 27 | { 28 | let success = fullMatchWrap.choice.fullMatch(); 29 | if (success) { 30 | filterDom.setEmpty(); 31 | isEmpty = true; 32 | } 33 | } 34 | } 35 | } 36 | 37 | return {filterInputValue, isEmpty}; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/plugins/RtlPlugin.js: -------------------------------------------------------------------------------- 1 | 2 | import {getIsRtl, AttributeBackup} from '../ToolsDom'; 3 | import {isBoolean} from '../ToolsJs'; 4 | 5 | export function RtlPlugin(){ 6 | return { 7 | plug 8 | } 9 | } 10 | 11 | export function plug(configuration){ 12 | return (aspects) => { 13 | return { 14 | layout: () => { 15 | let {popperRtlAspect, staticDom} = aspects; 16 | let {isRtl} = configuration; 17 | let forceRtlOnContainer = false; 18 | if (isBoolean(isRtl)) 19 | forceRtlOnContainer = true; 20 | else 21 | isRtl = getIsRtl(staticDom.initialElement); 22 | 23 | var attributeBackup = AttributeBackup(); 24 | if (forceRtlOnContainer){ 25 | attributeBackup.set(staticDom.containerElement, "dir", "rtl"); 26 | } 27 | else if (staticDom.selectElement){ 28 | var dirAttributeValue = staticDom.selectElement.getAttribute("dir"); 29 | if (dirAttributeValue){ 30 | attributeBackup.set(staticDom.containerElement, "dir", dirAttributeValue); 31 | } 32 | } 33 | 34 | if (popperRtlAspect) 35 | popperRtlAspect.getIsRtl = () => isRtl; 36 | 37 | return { 38 | dispose(){ 39 | attributeBackup.restore(); 40 | } 41 | } 42 | } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/CreateForJQuery.js: -------------------------------------------------------------------------------- 1 | import {addToJQueryPrototype} from './AddToJQueryPrototype' 2 | 3 | import {composeSync, ObjectValuesEx, isString} from './ToolsJs' 4 | import {composeEventTrigger} from './ToolsDom' 5 | 6 | import {MultiSelectBuilder} from './MultiSelectBuilder' 7 | 8 | export function createForJQuery(window, $, globalPopper, name, plugins, defaultCss){ 9 | let trigger = null; 10 | let isJQyery = $ && !window.document.body.hasAttribute('data-bs-no-jquery'); 11 | if (isJQyery) { 12 | trigger = (e, eventName) => $(e).trigger(eventName); 13 | } else { 14 | trigger = composeEventTrigger(window); 15 | } 16 | 17 | var isIE11 = !!window.MSInputMethodContext && !!window.document.documentMode; 18 | 19 | let environment = {trigger, window, globalPopper, isIE11} 20 | let pluginsArray = ObjectValuesEx(plugins) 21 | let {create, defaultSettings} = MultiSelectBuilder(environment, pluginsArray, defaultCss); 22 | let createForUmd = (element, settings) => { 23 | if (isString(element)) 24 | element = window.document.querySelector(element); 25 | return create(element, settings); 26 | } 27 | createForUmd.Default = defaultSettings; 28 | 29 | if (isJQyery) { 30 | let constructorForJquery = (element, settings, removeInstanceData) => {let multiSelect = create(element, settings); multiSelect.dispose = composeSync(multiSelect.dispose, removeInstanceData); return multiSelect;} 31 | let prototypable = addToJQueryPrototype(name, constructorForJquery, $); 32 | 33 | prototypable.defaults = defaultSettings; 34 | } 35 | return createForUmd; 36 | } 37 | -------------------------------------------------------------------------------- /src/plugins/BsAppearanceBs4Plugin.js: -------------------------------------------------------------------------------- 1 | import {closestByClassName} from '../ToolsDom' 2 | import {BsAppearancePlugin} from './BsAppearancePlugin' 3 | 4 | export function BsAppearanceBs4Plugin(defaults) { 5 | defaults.composeGetSize = composeGetSize; // BsAppearancePlugin 6 | defaults.getDefaultLabel = getDefaultLabel; // FloatingLabelPlugin, BsAppearancePlugin 7 | return BsAppearancePlugin(); 8 | } 9 | 10 | function composeGetSize(element){ 11 | let inputGroupElement = closestByClassName(element, 'input-group'); 12 | let getSize = null; 13 | if (inputGroupElement){ 14 | getSize = function(){ 15 | var value = null; 16 | if (inputGroupElement.classList.contains('input-group-lg')) 17 | value = 'lg'; 18 | else if (inputGroupElement.classList.contains('input-group-sm')) 19 | value = 'sm'; 20 | return value; 21 | } 22 | } 23 | else{ 24 | getSize = function(){ 25 | var value = null; 26 | if (element.classList.contains('custom-select-lg') || element.classList.contains('form-control-lg')) 27 | value = 'lg'; 28 | else if (element.classList.contains('custom-select-sm') || element.classList.contains('form-control-sm')) 29 | value = 'sm'; 30 | return value; 31 | } 32 | } 33 | return getSize; 34 | } 35 | 36 | function getDefaultLabel(element){ 37 | let value = null; 38 | let formGroup = closestByClassName(element,'form-group'); 39 | if (formGroup) 40 | value = formGroup.querySelector(`label[for="${element.id}"]`); 41 | return value; 42 | } -------------------------------------------------------------------------------- /src/BsMultiSelect.web.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery' 2 | import Popper from 'popper.js' 3 | import {BsMultiSelect} from './BsMultiSelect.esm' 4 | 5 | import {BsMultiSelectElement} from './BsMultiSelectElement'; 6 | 7 | // import {FormResetPlugin} from './plugins/FormResetPlugin'; 8 | // import {ValidationApiPlugin} from './plugins/ValidationApiPlugin'; 9 | // import {BsAppearancePlugin} from './plugins/BsAppearancePlugin'; 10 | // import {HiddenOptionPlugin} from './plugins/HiddenOptionPlugin'; 11 | 12 | /* 13 | 1. 14 | 15 | 16 | 2. 17 | 18 | */ 19 | 20 | ( 21 | (window, createPopper) => { 22 | // was: 23 | // var elementPrototype = Object.create(HTMLElement.prototype); 24 | // elementPrototype.createdCallback = function() { var shadowRoot = this.createShadowRoot();... shadowRoot.appendChild(elem); } 25 | // elementPrototype.attributeChangedCallback = ... 26 | // document.registerElement('element-multiplier', {prototype: elementPrototype }); 27 | window.customElements.define('dashboardcode-bsmultiselect', BsMultiSelectElement); 28 | // let createPlugin = (element, settings, onDispose) => { 29 | // let trigger = (e, eventName) => $(e).trigger(eventName); 30 | // let environment = {trigger, window, Popper} 31 | // let multiSelect = BsMultiSelect(element, environment, settings); 32 | // multiSelect.onDispose = composeSync(multiSelect.onDispose, onDispose); 33 | // return multiSelect; 34 | // } 35 | // addToJQueryPrototype('BsMultiSelect', createPlugin, defaults, $); 36 | } 37 | )(window, createPopper) 38 | -------------------------------------------------------------------------------- /src/plugins/BsAppearanceBs5Plugin.js: -------------------------------------------------------------------------------- 1 | import {closestByClassName} from '../ToolsDom' 2 | import {BsAppearancePlugin} from './BsAppearancePlugin' 3 | 4 | export function BsAppearanceBs5Plugin(defaults) { 5 | defaults.composeGetSize = composeGetSize; // BsAppearancePlugin 6 | defaults.getDefaultLabel = getDefaultLabel; // FloatingLabelPlugin, BsAppearancePlugin 7 | return BsAppearancePlugin(); 8 | } 9 | 10 | function composeGetSize(element){ 11 | let inputGroupElement = closestByClassName(element, 'input-group'); 12 | let getSize = null; 13 | if (inputGroupElement){ 14 | getSize = function(){ 15 | var value = null; 16 | if (inputGroupElement.classList.contains('input-group-lg')) 17 | value = 'lg'; 18 | else if (inputGroupElement.classList.contains('input-group-sm')) 19 | value = 'sm'; 20 | return value; 21 | } 22 | } 23 | else{ 24 | getSize = function(){ 25 | var value = null; 26 | if (element.classList.contains('form-select-lg') || element.classList.contains('form-control-lg')) // changed for BS 27 | value = 'lg'; 28 | else if (element.classList.contains('form-select-sm') || element.classList.contains('form-control-sm')) 29 | value = 'sm'; 30 | return value; 31 | } 32 | } 33 | return getSize; 34 | } 35 | 36 | function getDefaultLabel(element){ 37 | let value = null; 38 | let query = `label[for="${element.id}"]`; 39 | let p1 = element.parentElement; 40 | value = p1.querySelector(query); // label can be wrapped into col-auto 41 | if (!value){ 42 | let p2 = p1.parentElement; 43 | value = p2.querySelector(query); 44 | } 45 | return value; 46 | } -------------------------------------------------------------------------------- /src/ChoicesDomFactory.js: -------------------------------------------------------------------------------- 1 | import {addStyling} from './ToolsStyling'; 2 | 3 | export function ChoicesDomFactory(staticDom) { 4 | return { 5 | create(){ 6 | let {css, createElementAspect} = staticDom; 7 | var choicesElement = createElementAspect.createElement('DIV'); 8 | var choicesListElement = createElementAspect.createElement('UL'); 9 | 10 | choicesElement.appendChild(choicesListElement); 11 | choicesElement.style.display = 'none'; 12 | 13 | addStyling(choicesElement, css.choices); 14 | addStyling(choicesListElement, css.choicesList); 15 | 16 | return { 17 | choicesElement, 18 | choicesListElement, 19 | createChoiceElement(){ 20 | var choiceElement = createElementAspect.createElement('LI'); 21 | addStyling(choiceElement, css.choice); 22 | return { 23 | choiceElement, 24 | setVisible: (isVisible) => choiceElement.style.display = isVisible ? 'block': 'none', 25 | attach: (beforeElement) => choicesListElement.insertBefore(choiceElement, beforeElement), 26 | detach: () => choicesListElement.removeChild(choiceElement) 27 | } 28 | } 29 | } 30 | } 31 | } 32 | } 33 | 34 | export function ChoicesDomFactoryPlugCss(css){ 35 | css.choices = 'dropdown-menu'; 36 | css.choicesList = ''; 37 | css.choice = ''; 38 | } 39 | 40 | export function ChoicesDomFactoryPlugCssPatch(cssPatch){ 41 | cssPatch.choicesList = {listStyleType:'none', paddingLeft:'0', paddingRight:'0', marginBottom:'0'}; 42 | cssPatch.choice = {classes:'px-md-2 px-1', styles:{cursor:'pointer'}}; 43 | } -------------------------------------------------------------------------------- /src/plugins/OptionsApiPlugin.js: -------------------------------------------------------------------------------- 1 | export function OptionsApiPlugin(){ 2 | return { 3 | plug 4 | } 5 | } 6 | 7 | export function plug(){ 8 | return (aspects) => { 9 | return { 10 | buildApi(api){ 11 | let {buildAndAttachChoiceAspect, wraps, wrapsCollection, createWrapAspect, createChoiceBaseAspect, 12 | dataWrap, resetLayoutAspect} = aspects; 13 | 14 | api.updateOptionAdded = (key) => { // TODO: generalize index as key 15 | let options = dataWrap.getOptions(); 16 | let option = options[key]; 17 | 18 | let wrap = createWrapAspect.createWrap(option); 19 | wrap.choice= createChoiceBaseAspect.createChoiceBase(option); 20 | wraps.insert(key, wrap); 21 | let nextChoice = ()=> wrapsCollection.getNext(key, c => c.choice.choiceDom.choiceElement); 22 | 23 | buildAndAttachChoiceAspect.buildAndAttachChoice( 24 | wrap, 25 | () => nextChoice()?.choice.choiceDom.choiceElement 26 | ) 27 | } 28 | 29 | api.updateOptionRemoved = (key) => { // TODO: generalize index as key 30 | resetLayoutAspect.resetLayout(); // always hide 1st, then reset filter 31 | 32 | var wrap = wraps.remove(key); 33 | wrap.choice.choiceDomManagerHandlers.detach?.(); 34 | wrap.dispose?.(); 35 | } 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /babel.mjs.ecma5.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | //retainLines: true, 3 | "presets": [ 4 | [ 5 | "@babel/preset-env", 6 | { 7 | "loose": true, // ES6 to EcmaScript5 8 | "bugfixes": true, 9 | // "useBuiltIns": "usage", 10 | "modules": false, 11 | "exclude": ["transform-typeof-symbol"], 12 | "targets": { 13 | "browsers": [ 14 | "chrome >= 45", "Firefox >= 38", "Explorer >= 10", "edge >= 12", "iOS >= 9","Safari >= 9","Android >= 4.4","Opera >= 30" 15 | ] 16 | }, 17 | "debug": true 18 | } 19 | ] 20 | ] 21 | } 22 | 23 | 24 | // const BROWSER_COMPAT = process.env.BROWSER_COMPAT === 'true'; 25 | 26 | // module.exports = { 27 | // // presets: [ 28 | // // [ 29 | // // '@babel/env', 30 | // // { 31 | // // loose: true, 32 | // // modules: false, 33 | // // }, 34 | // // ], 35 | // // ], 36 | // plugins: [ 37 | // //'@babel/plugin-transform-flow-strip-types', 38 | // 'babel-plugin-add-import-extension', 39 | // [ 40 | // '@babel/plugin-proposal-object-rest-spread', 41 | // { 42 | // loose: true, 43 | // useBuiltIns: true, 44 | // }, 45 | // ], 46 | // ...(BROWSER_COMPAT 47 | // ? [ 48 | // [ 49 | // 'inline-replace-variables', 50 | // { 51 | // __DEV__: false, 52 | // }, 53 | // ], 54 | // ] 55 | // : ['dev-expression']), 56 | // 'annotate-pure-calls', 57 | // ], 58 | // env: { 59 | // test: { 60 | // presets: ['@babel/env'], 61 | // plugins: ['@babel/plugin-transform-runtime'], 62 | // }, 63 | // dev: { 64 | // plugins: [ 65 | // [ 66 | // 'transform-inline-environment-variables', 67 | // { 68 | // include: ['NODE_ENV'], 69 | // }, 70 | // ], 71 | // ], 72 | // }, 73 | // }, 74 | // }; -------------------------------------------------------------------------------- /src/plugins/LabelForAttributePlugin.js: -------------------------------------------------------------------------------- 1 | import {defCall, composeSync} from '../ToolsJs'; 2 | 3 | export function LabelForAttributePlugin(defaults){ 4 | defaults.label = null; 5 | return { 6 | plug 7 | } 8 | } 9 | 10 | export function plug(configuration){ 11 | var getLabelAspect = {getLabel : ()=>defCall(configuration.label)} 12 | var createFilterInputElementIdAspect = { 13 | createFilterInputElementId : ()=>defCall(configuration.filterInputElementId), 14 | }; 15 | return (aspects) => { 16 | aspects.getLabelAspect = getLabelAspect; 17 | aspects.createFilterInputElementIdAspect = createFilterInputElementIdAspect; 18 | return { 19 | layout: () => { 20 | var {filterDom, loadAspect, disposeAspect, staticDom} = aspects; 21 | 22 | loadAspect.load = composeSync(loadAspect.load, ()=>{ 23 | let {filterInputElement} = filterDom; 24 | 25 | let labelElement = getLabelAspect.getLabel(); 26 | if (labelElement){ 27 | let backupedForAttribute = labelElement.getAttribute('for'); 28 | var inputId = createFilterInputElementIdAspect.createFilterInputElementId(); 29 | 30 | if (!inputId){ 31 | let {containerClass} = configuration; 32 | let {containerElement} = staticDom; 33 | inputId =`${containerClass}-generated-filter-${containerElement.id}` 34 | } 35 | filterInputElement.setAttribute('id', inputId); 36 | labelElement.setAttribute('for',inputId); 37 | if (backupedForAttribute){ 38 | disposeAspect.dispose = composeSync( 39 | disposeAspect.dispose, 40 | () =>labelElement.setAttribute('for', backupedForAttribute) 41 | ) 42 | } 43 | } 44 | }) 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/FilterManagerAspect.js: -------------------------------------------------------------------------------- 1 | export function NavigateManager( 2 | list, getPrev, getNext 3 | ){ 4 | return { 5 | navigate(down, wrap /* hoveredChoice */){ 6 | if (down) { 7 | return wrap?getNext(wrap): list.getHead(); 8 | } else { 9 | return wrap?getPrev(wrap): list.getTail(); 10 | } 11 | }, 12 | getCount(){ 13 | return list.getCount() 14 | }, 15 | getHead(){ 16 | return list.getHead() 17 | } 18 | } 19 | } 20 | 21 | export function FilterPredicateAspect(){ 22 | return { 23 | filterPredicate: (wrap, text) => 24 | wrap.choice.searchText.indexOf(text) >= 0 25 | } 26 | } 27 | 28 | export function FilterManagerAspect( 29 | emptyNavigateManager, 30 | filteredNavigateManager, 31 | filteredChoicesList, 32 | choicesEnumerableAspect, 33 | filterPredicateAspect 34 | ) { 35 | let showEmptyFilter=true; 36 | let filterText = ""; 37 | return { 38 | getNavigateManager(){ 39 | return (showEmptyFilter)?emptyNavigateManager:filteredNavigateManager; 40 | }, 41 | processEmptyInput(){ // redefined in PlaceholderPulgin, HighlightPlugin 42 | showEmptyFilter =true; 43 | filterText =""; 44 | choicesEnumerableAspect.forEach( (wrap)=>{ 45 | wrap.choice.choiceDomManagerHandlers.setVisible(true); 46 | }); 47 | }, 48 | getFilter(){ 49 | return filterText; 50 | }, 51 | setFilter(text){ // redefined in HighlightPlugin 52 | showEmptyFilter =false; 53 | filterText = text; 54 | filteredChoicesList.reset(); 55 | choicesEnumerableAspect.forEach( (wrap)=>{ 56 | wrap.choice.filteredPrev = wrap.choice.filteredNext = null; 57 | var v = filterPredicateAspect.filterPredicate(wrap, text) 58 | if (v) 59 | filteredChoicesList.add(wrap); 60 | wrap.choice.choiceDomManagerHandlers.setVisible(v); 61 | }); 62 | } 63 | } 64 | } 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/StaticManagerAspect.js: -------------------------------------------------------------------------------- 1 | export function StaticManagerAspect(staticDom, picksDom, filterDom){ 2 | return { 3 | appendToContainer(){ 4 | picksDom.pickFilterElement.appendChild(filterDom.filterInputElement); 5 | picksDom.picksElement.appendChild(picksDom.pickFilterElement); 6 | containerElement.appendChild(choicesDom.choicesElement); 7 | if (isDisposablePicksElementFlag) 8 | containerElement.appendChild(configDom.picksElement); 9 | }, 10 | 11 | dispose(){ 12 | containerElement.removeChild(choicesDom.choicesElement); 13 | if (removableContainerClass) 14 | containerElement.classList.remove(containerClass); 15 | if (isDisposablePicksElementFlag) 16 | containerElement.removeChild(configDom.picksElement); 17 | picksDom.dispose(); 18 | filterDom.dispose(); 19 | } 20 | 21 | 22 | // ------------------------------- 23 | 24 | // appendToContainer(){ 25 | // let {selectElement} = staticDom; 26 | // picksDom.pickFilterElement.appendChild(filterDom.filterInputElement); 27 | // picksDom.picksElement.appendChild(picksDom.pickFilterElement); 28 | // if (isDisposableContainerElementFlag){ 29 | // selectElement.parentNode.insertBefore(containerElement, selectElement.nextSibling) 30 | // containerElement.appendChild(choicesDom.choicesElement) 31 | // }else { 32 | // selectElement.parentNode.insertBefore(choicesDom.choicesElement, selectElement.nextSibling) 33 | // } 34 | // if (isDisposablePicksElementFlag) 35 | // containerElement.appendChild(picksDom.picksElement) 36 | // }, 37 | 38 | // dispose(){ 39 | // let {selectElement} = staticDom; 40 | // choicesDom.choicesElement.parentNode.removeChild(choicesDom.choicesElement); 41 | // if (isDisposableContainerElementFlag) 42 | // selectElement.parentNode.removeChild(containerElement) 43 | // if (isDisposablePicksElementFlag) 44 | // containerElement.removeChild(picksDom.picksElement) 45 | // picksDom.dispose(); 46 | // filterDom.dispose(); 47 | // } 48 | } 49 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { shallowClearClone, ObjectValuesEx } from "./ToolsJs.js"; 2 | 3 | import { composeSync } from "./ToolsJs.js"; 4 | import { EventBinder } from "./ToolsDom.js"; 5 | import { addStyling, toggleStyling } from "./ToolsStyling.js"; 6 | 7 | import { MultiSelectBuilder } from "./MultiSelectBuilder.js"; 8 | import { BsMultiSelect, ModuleFactory } from "./BsMultiSelect.esm.js"; 9 | 10 | import { BsAppearanceBs5Plugin, BsAppearanceBs4Plugin, CssPatchBs4Plugin, CssPatchBs5Plugin, SelectElementPlugin, LabelForAttributePlugin, ValidationApiPlugin, UpdateAppearancePlugin, DisableComponentPlugin, 11 | FormResetPlugin, CreatePopperPlugin, WarningCssPatchPlugin, RtlPlugin, PlaceholderPlugin, PlaceholderCssPatchPlugin, 12 | FloatingLabelPlugin, FloatingLabelCssPatchBs5Plugin, OptionsApiPlugin, JQueryMethodsPlugin, SelectedOptionPlugin, 13 | FormRestoreOnBackwardPlugin, DisabledOptionPlugin, DisabledOptionCssPatchPlugin, PicksApiPlugin, HighlightPlugin, ChoicesDynamicStylingPlugin, CustomPickStylingsPlugin, CustomChoiceStylingsPlugin, 14 | PicksPlugin, HiddenOptionPlugin, HiddenOptionAltPlugin, 15 | BsAppearanceBs4CssPatchPlugin, BsAppearanceBs5CssPatchPlugin, 16 | WarningBs4Plugin, WarningBs5Plugin, 17 | PickButtonBs4Plugin, PickButtonBs5Plugin, PickButtonPlugCssPatchBs4, PickButtonPlugCssPatchBs5, 18 | /*,SelectedPicksPlugin*/ 19 | } from "./plugins/index.js"; 20 | 21 | export { 22 | BsAppearanceBs4Plugin, WarningBs4Plugin, 23 | BsAppearanceBs5Plugin, WarningBs5Plugin, 24 | 25 | CssPatchBs4Plugin, BsAppearanceBs4CssPatchPlugin, 26 | CssPatchBs5Plugin, BsAppearanceBs5CssPatchPlugin, FloatingLabelCssPatchBs5Plugin, PlaceholderCssPatchPlugin, WarningCssPatchPlugin, 27 | SelectElementPlugin, PicksPlugin, 28 | 29 | LabelForAttributePlugin, ValidationApiPlugin, UpdateAppearancePlugin, 30 | 31 | PickButtonBs4Plugin, PickButtonBs5Plugin, PickButtonPlugCssPatchBs4, PickButtonPlugCssPatchBs5, 32 | 33 | DisableComponentPlugin, 34 | FormResetPlugin, CreatePopperPlugin, RtlPlugin, PlaceholderPlugin, 35 | FloatingLabelPlugin, OptionsApiPlugin, JQueryMethodsPlugin, SelectedOptionPlugin, 36 | FormRestoreOnBackwardPlugin, 37 | DisabledOptionPlugin, DisabledOptionCssPatchPlugin, 38 | PicksApiPlugin, HighlightPlugin, ChoicesDynamicStylingPlugin, CustomPickStylingsPlugin, CustomChoiceStylingsPlugin, 39 | 40 | HiddenOptionPlugin, HiddenOptionAltPlugin, 41 | 42 | shallowClearClone, ObjectValuesEx, composeSync, EventBinder, addStyling, toggleStyling, 43 | MultiSelectBuilder, BsMultiSelect, ModuleFactory}; -------------------------------------------------------------------------------- /src/ProduceChoiceAspect.js: -------------------------------------------------------------------------------- 1 | import {composeSync} from './ToolsJs'; 2 | export function BuildAndAttachChoiceAspect( 3 | produceChoiceAspect, 4 | ){ 5 | return { 6 | buildAndAttachChoice( 7 | wrap, 8 | getNextElement 9 | ) 10 | { 11 | produceChoiceAspect.produceChoice(wrap); 12 | wrap.choice.choiceDomManagerHandlers.attach(getNextElement?.()); 13 | } 14 | } 15 | } 16 | 17 | export function ProduceChoiceAspect(choicesDom, choiceDomFactory) { 18 | return { 19 | // 1 overrided in highlight and option disable plugins 20 | // 2 call in HiddenPlugin (create) 21 | // 3 overrided in layout: pick created, choice.choiceDomManagerHandlers.detach updated to remove pick 22 | produceChoice(wrap) { 23 | var {choiceElement, attach, detach, setVisible} = choicesDom.createChoiceElement(); 24 | 25 | let choice = wrap.choice; 26 | choice.wrap = wrap; 27 | 28 | choice.choiceDom={ 29 | choiceElement 30 | }; 31 | 32 | let choiceDomManagerHandlers = { 33 | attach, 34 | detach, 35 | setVisible // TODO: refactor it there should be 3 types of not visibility: for hidden, for filtered out, for optgroup, for message item 36 | } 37 | choice.choiceDomManagerHandlers = choiceDomManagerHandlers 38 | choiceDomFactory.create(choice); 39 | 40 | // added by "navigation (by mouse and arrows) plugin" 41 | choice.isHoverIn = false; // internal state 42 | choice.setHoverIn = (v) => { 43 | choice.isHoverIn =v ; 44 | choiceDomManagerHandlers.updateHoverIn(); 45 | } 46 | 47 | choice.dispose = composeSync(() =>{ 48 | choice.choiceDom.choiceElement = null; 49 | choice.choiceDom = null; 50 | choiceDomManagerHandlers.attach=null; 51 | choiceDomManagerHandlers.detach=null; 52 | choiceDomManagerHandlers.setVisible=null; 53 | choice.choiceDomManagerHandlers = null; 54 | choice.choiсeClick=null; 55 | 56 | choice.setHoverIn = null; 57 | 58 | choice.wrap = null; 59 | choice.dispose = null; 60 | }, choice.dispose) 61 | 62 | wrap.dispose = () => { 63 | choice.dispose(); 64 | wrap.dispose = null; 65 | } 66 | return choice; 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/MultiSelectBuilder.js: -------------------------------------------------------------------------------- 1 | import {BsMultiSelect} from './BsMultiSelect' 2 | import {ComposePluginManagerFactory} from './PluginManager' 3 | 4 | import {adjustLegacySettings} from './BsMultiSelectDepricatedParameters' 5 | 6 | import {extendIfUndefined} from './ToolsJs' 7 | 8 | import {createCss} from './ToolsStyling' 9 | 10 | // TODO: remove environment - replace it with plugins 11 | // TODO: defaultCss should come together with DomFactories and Layout 12 | export function MultiSelectBuilder(environment, plugins, defaultCss) 13 | { 14 | const defaults = {containerClass: "dashboardcode-bsmultiselect", css: defaultCss} 15 | 16 | var pluginManagerFactory = ComposePluginManagerFactory(plugins, defaults, environment); 17 | 18 | /* NOTE: about namings 19 | defaults - defaults for module 20 | setting - object that could modify defaults (not just overwrite) 21 | options - configuration "generalization": can be buildConfiguration function or settings 22 | configuration - for control instance 23 | */ 24 | let create = (element, options) => { 25 | if (options && options.plugins) 26 | console.log("DashboarCode.BsMultiSelect: 'options.plugins' is depricated, use - MultiSelectBuilder(environment, plugins) instead"); 27 | 28 | let buildConfiguration; 29 | let settings; 30 | if (options instanceof Function) { 31 | buildConfiguration = options; 32 | settings = null; 33 | } else { 34 | buildConfiguration = options?.buildConfiguration; 35 | settings = options; 36 | } 37 | if (settings){ 38 | adjustLegacySettings(settings); 39 | } 40 | let configuration = {}; 41 | 42 | configuration.css = createCss(defaults.css, settings?.css); 43 | 44 | extendIfUndefined(configuration, settings); 45 | // next line: merging of cssPatch will be delayed to the CssPatchPlugin merge handler 46 | extendIfUndefined(configuration, defaults); 47 | let inlineBuildAspectsList = buildConfiguration?.(element, configuration); 48 | // next line merges settings.cssPatch and defaults.cssPatch also merge defaults.css and defaults.cssPatch 49 | var pluginManager = pluginManagerFactory(configuration, settings, inlineBuildAspectsList); 50 | // now we can freeze configuration object 51 | Object.freeze(configuration); 52 | let multiSelect = BsMultiSelect(element, environment, pluginManager, configuration); 53 | return multiSelect; 54 | } 55 | 56 | return {create, defaultSettings: defaults} 57 | } -------------------------------------------------------------------------------- /src/ModuleFactory.js: -------------------------------------------------------------------------------- 1 | import {multiSelectPlugins, picksPlugins, allPlugins} from './PluginSet' 2 | import {shallowClearClone, ObjectValuesEx} from './ToolsJs' 3 | import {utilities} from './ToolSet' 4 | import {MultiSelectBuilder} from './MultiSelectBuilder' 5 | 6 | export function ModuleFactory(environment, customizationPlugins, defaultCss){ 7 | if (!environment.trigger) 8 | environment.trigger = (e, name) => e.dispatchEvent(new environment.window.Event(name)) 9 | 10 | if (!environment.isIE11) 11 | environment.isIE11 = !!environment.window.MSInputMethodContext && !!environment.window.document.documentMode; 12 | 13 | let multiSelectPluginsObj = shallowClearClone(customizationPlugins, multiSelectPlugins); 14 | let pluginsArray = ObjectValuesEx(multiSelectPluginsObj); 15 | let {create: BsMultiSelect, BsMultiSelectDefault} = MultiSelectBuilder(environment, pluginsArray, defaultCss) 16 | BsMultiSelect.Default = BsMultiSelectDefault; 17 | 18 | let picksPluginsObj = shallowClearClone(customizationPlugins, picksPlugins); 19 | let picksPluginsArray = ObjectValuesEx(picksPluginsObj); 20 | let {create: BsPicks, BsPicksDefault} = MultiSelectBuilder(environment, picksPluginsArray, defaultCss) 21 | BsPicks.Default = BsPicksDefault; 22 | 23 | return { 24 | BsMultiSelect, 25 | BsPicks, 26 | MultiSelectTools: {MultiSelectBuilder, plugins: shallowClearClone(customizationPlugins, allPlugins), defaultCss, utilities} 27 | } 28 | } 29 | 30 | 31 | // TEST 32 | // function areValidElements(...args) { 33 | // const result = Object.values(obj); 34 | // return !args.some( 35 | // (element) => 36 | // !(element && typeof element.getBoundingClientRect === 'function') 37 | // ); 38 | // } 39 | 40 | // function ModuleFactory(environment) { 41 | // if (!environment.trigger) 42 | // environment.trigger = (e, name) => e.dispatchEvent(new environment.window.Event(name)) 43 | 44 | // let pluginsArray = ObjectValues(shallowClearClone({Bs5Plugin}, multiSelectPlugins)); 45 | // let {create: BsMultiSelect, BsMultiSelectDefault} = MultiSelectBuilder(environment, pluginsArray) 46 | // BsMultiSelect.Default = BsMultiSelectDefault; 47 | 48 | // let picksPluginsArray = ObjectValues(shallowClearClone({Bs5Plugin}, picksPlugins)); 49 | // let {create: BsPicks, BsPicksDefault} = MultiSelectBuilder(environment, picksPluginsArray) 50 | // BsPicks.Default = BsPicksDefault; 51 | 52 | // return { 53 | // BsMultiSelect, 54 | // BsPicks, 55 | // MultiSelectTools: {MultiSelectBuilder, plugins: shallowClearClone({Bs5Plugin}, allPlugins), utilities} 56 | // } 57 | // } 58 | -------------------------------------------------------------------------------- /src/plugins/CustomPickStylingsPlugin.js: -------------------------------------------------------------------------------- 1 | import { composeSync } from "../ToolsJs"; 2 | 3 | export function CustomPickStylingsPlugin(defaults){ 4 | defaults.customPickStylings = null; 5 | return { 6 | plug 7 | } 8 | } 9 | 10 | export function plug(configuration){ 11 | return (aspects) => { 12 | return { 13 | plugStaticDom: ()=> { 14 | let {disabledComponentAspect, pickDomFactory} = aspects; 15 | let customPickStylings = configuration.customPickStylings; 16 | let customPickStylingsAspect = CustomPickStylingsAspect(disabledComponentAspect, customPickStylings); 17 | ExtendPickDomFactory(pickDomFactory, customPickStylingsAspect); 18 | } 19 | } 20 | } 21 | } 22 | 23 | function ExtendPickDomFactory(pickDomFactory, customPickStylingsAspect){ 24 | let origCreatePickDomFactory = pickDomFactory.create; 25 | pickDomFactory.create = function(pick){ 26 | origCreatePickDomFactory(pick); 27 | customPickStylingsAspect.customize(pick); 28 | } 29 | } 30 | 31 | function CustomPickStylingsAspect(disabledComponentAspect, customPickStylings){ 32 | return { 33 | customize(pick){ 34 | if (customPickStylings){ 35 | var handlers = customPickStylings(pick.pickDom, pick.wrap.option); 36 | 37 | if (handlers){ 38 | function customPickStylingsClosure(custom){ 39 | return function() { 40 | custom({ 41 | isOptionDisabled: pick.wrap.isOptionDisabled, 42 | // wrap.component.getDisabled(); 43 | // wrap.group.getDisabled(); 44 | isComponentDisabled: disabledComponentAspect.getDisabled() 45 | }); 46 | } 47 | } 48 | let pickDomManagerHandlers = pick.pickDomManagerHandlers; 49 | // TODO: automate it 50 | if (pickDomManagerHandlers.updateDisabled && handlers.updateDisabled) 51 | pickDomManagerHandlers.updateDisabled 52 | = composeSync(pickDomManagerHandlers.updateDisabled, customPickStylingsClosure(handlers.updateDisabled)); 53 | if (pickDomManagerHandlers.updateComponentDisabled && handlers.updateComponentDisabled) 54 | pickDomManagerHandlers.updateComponentDisabled 55 | = composeSync(pickDomManagerHandlers.updateComponentDisabled, customPickStylingsClosure(handlers.updateComponentDisabled)); 56 | } 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/PluginSet.js: -------------------------------------------------------------------------------- 1 | import { 2 | BsAppearanceBs4Plugin, BsAppearanceBs5Plugin, 3 | 4 | CssPatchBs4Plugin, CssPatchBs5Plugin, 5 | BsAppearanceBs4CssPatchPlugin, BsAppearanceBs5CssPatchPlugin, 6 | 7 | SelectElementPlugin, 8 | LabelForAttributePlugin, ValidationApiPlugin, 9 | UpdateAppearancePlugin, 10 | 11 | DisableComponentPlugin, 12 | FormResetPlugin, CreatePopperPlugin, RtlPlugin, PlaceholderPlugin, PlaceholderCssPatchPlugin, 13 | OptionsApiPlugin, 14 | JQueryMethodsPlugin, 15 | SelectedOptionPlugin, FormRestoreOnBackwardPlugin, 16 | DisabledOptionPlugin, DisabledOptionCssPatchPlugin, 17 | PicksApiPlugin, HighlightPlugin, 18 | ChoicesDynamicStylingPlugin, CustomPickStylingsPlugin, CustomChoiceStylingsPlugin, 19 | 20 | FloatingLabelPlugin, FloatingLabelCssPatchBs5Plugin, 21 | 22 | WarningCssPatchPlugin, WarningBs4Plugin, WarningBs5Plugin, 23 | 24 | PicksPlugin, 25 | PickButtonBs4Plugin, PickButtonBs5Plugin, PickButtonPlugCssPatchBs4, PickButtonPlugCssPatchBs5, 26 | HiddenOptionPlugin, 27 | /*HiddenOptionAltPlugin as HiddenOptionPlugin*/} from './plugins/index' 28 | 29 | import {shallowClearClone} from './ToolsJs' 30 | 31 | 32 | export let Bs4PluginSet = {BsAppearanceBs4Plugin, PickButtonBs4Plugin, WarningBs4Plugin, CssPatchBs4Plugin, BsAppearanceBs4CssPatchPlugin, PickButtonPlugCssPatchBs4} 33 | 34 | export let Bs5PluginSet = {BsAppearanceBs5Plugin, PickButtonBs5Plugin, WarningBs5Plugin, CssPatchBs5Plugin, BsAppearanceBs5CssPatchPlugin, PickButtonPlugCssPatchBs5, FloatingLabelCssPatchBs5Plugin} 35 | 36 | export let multiSelectPlugins = {SelectElementPlugin, 37 | LabelForAttributePlugin, HiddenOptionPlugin, ValidationApiPlugin, 38 | UpdateAppearancePlugin, 39 | DisableComponentPlugin, 40 | FormResetPlugin, CreatePopperPlugin, WarningCssPatchPlugin, RtlPlugin, PlaceholderPlugin, PlaceholderCssPatchPlugin, FloatingLabelPlugin, OptionsApiPlugin, 41 | JQueryMethodsPlugin, 42 | SelectedOptionPlugin, FormRestoreOnBackwardPlugin, 43 | DisabledOptionPlugin, DisabledOptionCssPatchPlugin, PicksApiPlugin, HighlightPlugin, 44 | ChoicesDynamicStylingPlugin, CustomPickStylingsPlugin, CustomChoiceStylingsPlugin}; 45 | 46 | export let picksPlugins = {PicksPlugin, 47 | LabelForAttributePlugin, ValidationApiPlugin, 48 | UpdateAppearancePlugin, 49 | DisableComponentPlugin, 50 | CreatePopperPlugin, WarningCssPatchPlugin, RtlPlugin, PlaceholderPlugin, PlaceholderCssPatchPlugin, FloatingLabelPlugin, OptionsApiPlugin, 51 | JQueryMethodsPlugin, PicksApiPlugin, HighlightPlugin, 52 | ChoicesDynamicStylingPlugin, CustomPickStylingsPlugin, CustomChoiceStylingsPlugin}; 53 | 54 | export let allPlugins = shallowClearClone(multiSelectPlugins, {PicksPlugin}); 55 | 56 | 57 | 58 | 59 | // var defaultConfig = { 60 | // plugins: multiSelectPlugins 61 | // } 62 | 63 | // var picksConfig = { 64 | // plugins: picksPlugins 65 | // } 66 | 67 | // export function createConfig(arg){ 68 | // return config; 69 | // } -------------------------------------------------------------------------------- /src/FilterDomFactory.js: -------------------------------------------------------------------------------- 1 | import {addStyling} from './ToolsStyling'; 2 | import {EventBinder} from './ToolsDom'; 3 | 4 | export function FilterDomFactory(staticDom){ 5 | return { 6 | create(){ 7 | let {isDisposablePicksElementFlag, css, createElementAspect} = staticDom; 8 | var filterInputElement = createElementAspect.createElement('INPUT'); 9 | addStyling(filterInputElement, css.filterInput); 10 | 11 | filterInputElement.setAttribute("type","search"); 12 | filterInputElement.setAttribute("autocomplete","off"); 13 | var eventBinder = EventBinder(); 14 | 15 | return { 16 | filterInputElement, 17 | isEmpty(){return filterInputElement.value ? false : true}, 18 | setEmpty(){ 19 | filterInputElement.value =''; 20 | }, 21 | getValue(){ 22 | return filterInputElement.value; 23 | }, 24 | setFocus(){ 25 | filterInputElement.focus(); 26 | }, 27 | setWidth(text){ 28 | filterInputElement.style.width = text.length * 1.3 + 2 + "ch" 29 | }, 30 | // TODO: check why I need this comparision? 31 | setFocusIfNotTarget(target){ 32 | if (target != filterInputElement) 33 | filterInputElement.focus(); 34 | }, 35 | onInput(onFilterInputInput){ 36 | eventBinder.bind(filterInputElement,'input', onFilterInputInput); 37 | }, 38 | onFocusIn(onFocusIn){ 39 | eventBinder.bind(filterInputElement,'focusin', onFocusIn); 40 | }, 41 | onFocusOut(onFocusOut){ 42 | eventBinder.bind(filterInputElement,'focusout', onFocusOut); 43 | }, 44 | onKeyDown(onfilterInputKeyDown){ 45 | eventBinder.bind(filterInputElement,'keydown', onfilterInputKeyDown); 46 | }, 47 | onKeyUp(onFilterInputKeyUp){ 48 | eventBinder.bind(filterInputElement,'keyup', onFilterInputKeyUp); 49 | }, 50 | dispose(){ 51 | eventBinder.unbind(); 52 | if (!isDisposablePicksElementFlag){ 53 | if (filterInputElement.parentNode) 54 | filterInputElement.parentNode.removeChild(filterInputElement) 55 | } 56 | } 57 | } 58 | } 59 | } 60 | } 61 | 62 | export function FilterDomFactoryPlugCss(css){ 63 | css.filterInput = ''; 64 | } 65 | 66 | export function FilterDomFactoryPlugCssPatch(cssPatch){ 67 | cssPatch.filterInput = { 68 | border:'0px', height: 'auto', boxShadow:'none', 69 | padding:'0', margin:'0', 70 | outline:'none', backgroundColor:'transparent', 71 | backgroundImage: 'none' // otherwise BS .was-validated set its image 72 | }; 73 | } 74 | -------------------------------------------------------------------------------- /src/plugins/FloatingLabelPlugin.js: -------------------------------------------------------------------------------- 1 | import {composeSync} from '../ToolsJs'; 2 | import {toggleStyling} from '../ToolsStyling'; 3 | 4 | export function FloatingLabelPlugin(defaults){ 5 | defaults.css.label_floating_lifted = 'floating-lifted'; 6 | defaults.css.picks_floating_lifted = 'floating-lifted'; 7 | return { 8 | plug 9 | } 10 | } 11 | 12 | export function plug(configuration){ 13 | return (aspects) => { 14 | return { 15 | plugStaticDom: ()=> { 16 | aspects.floatingLabelAspect = FloatingLabelAspect(); 17 | }, 18 | layout: () => { 19 | let {picksList, picksDom, filterDom, 20 | updateDataAspect, resetFilterListAspect, floatingLabelAspect, getLabelAspect} = aspects; 21 | let {css} = configuration; 22 | 23 | if (floatingLabelAspect.isFloatingLabel() ){ 24 | let labelElement = getLabelAspect.getLabel(); 25 | let picksElement = picksDom.picksElement; 26 | 27 | var liftToggleStyling1 = toggleStyling(labelElement, css.label_floating_lifted); 28 | var liftToggleStyling2 = toggleStyling(picksElement, css.picks_floating_lifted); 29 | 30 | function liftedLabel(isEmpty){ 31 | liftToggleStyling1(isEmpty); 32 | liftToggleStyling2(isEmpty); 33 | } 34 | 35 | let isEmpty = () => picksList.isEmpty() && filterDom.isEmpty() && !picksDom.getIsFocusIn();; 36 | 37 | function updateLiftedLabel(){ 38 | liftedLabel(!isEmpty()); 39 | }; 40 | 41 | updateLiftedLabel(); 42 | 43 | resetFilterListAspect.forceResetFilter = composeSync(resetFilterListAspect.forceResetFilter, updateLiftedLabel); 44 | 45 | let origAdd = picksList.add; 46 | picksList.add = (pick) => { 47 | let returnValue = origAdd(pick); 48 | if (picksList.getCount()==1) 49 | updateLiftedLabel() 50 | pick.dispose = composeSync(pick.dispose, ()=> 51 | { 52 | if (picksList.getCount()==0) 53 | updateLiftedLabel() 54 | }) 55 | return returnValue; 56 | }; 57 | 58 | var origToggleFocusStyling = picksDom.toggleFocusStyling; 59 | picksDom.toggleFocusStyling = () => { 60 | var isFocusIn = picksDom.getIsFocusIn(); 61 | origToggleFocusStyling(isFocusIn); 62 | updateLiftedLabel(); 63 | } 64 | 65 | updateDataAspect.updateData = composeSync(updateDataAspect.updateData, updateLiftedLabel); 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | function FloatingLabelAspect() { 73 | return { 74 | isFloatingLabel(){}, 75 | } 76 | } -------------------------------------------------------------------------------- /src/StaticDomFactory.js: -------------------------------------------------------------------------------- 1 | import {findDirectChildByTagName, closestByClassName} from './ToolsDom'; 2 | 3 | export function StaticDomFactory(staticDom){ 4 | return { 5 | createStaticDom(){ 6 | let {createElementAspect, initialElement, containerClass} = staticDom; 7 | 8 | let containerElement, picksElement; 9 | let removableContainerClass= false; 10 | if (initialElement.tagName == 'DIV') { 11 | containerElement = initialElement; 12 | if (!containerElement.classList.contains(containerClass)){ 13 | containerElement.classList.add(containerClass); 14 | removableContainerClass = true; 15 | } 16 | picksElement = findDirectChildByTagName(containerElement, 'UL'); 17 | } 18 | else if (initialElement.tagName == 'UL') { 19 | picksElement = initialElement; 20 | containerElement = closestByClassName(initialElement, containerClass); 21 | if (!containerElement){ 22 | throw new Error('BsMultiSelect: defined on UL but precedentant DIV for container not found; class='+containerClass); 23 | } 24 | } 25 | else if (initialElement.tagName=="INPUT") { 26 | throw new Error('BsMultiSelect: INPUT element is not supported'); 27 | } 28 | 29 | 30 | let isDisposablePicksElementFlag=false; 31 | if (!picksElement) { 32 | picksElement = createElementAspect.createElement('UL'); 33 | isDisposablePicksElementFlag = true; 34 | } 35 | staticDom.containerElement = containerElement; 36 | staticDom.isDisposablePicksElementFlag = isDisposablePicksElementFlag; 37 | staticDom.picksElement = picksElement; 38 | 39 | return { 40 | staticManager: { 41 | appendToContainer(){ 42 | let {containerElement, isDisposablePicksElementFlag, choicesDom, picksDom, filterDom} = staticDom; 43 | picksDom.pickFilterElement.appendChild(filterDom.filterInputElement); 44 | picksDom.picksElement.appendChild(picksDom.pickFilterElement); 45 | containerElement.appendChild(choicesDom.choicesElement); 46 | if (isDisposablePicksElementFlag) 47 | containerElement.appendChild(picksDom.picksElement) 48 | }, 49 | dispose(){ 50 | let {containerElement, containerClass, isDisposablePicksElementFlag, choicesDom, picksDom, filterDom} = staticDom; 51 | containerElement.removeChild(choicesDom.choicesElement); 52 | if (removableContainerClass) 53 | containerElement.classList.remove(containerClass); 54 | if (isDisposablePicksElementFlag) 55 | containerElement.removeChild(picksDom.picksElement) 56 | picksDom.dispose(); 57 | filterDom.dispose(); 58 | } 59 | } 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/plugins/PicksPlugin.js: -------------------------------------------------------------------------------- 1 | import {composeSync} from '../ToolsJs'; 2 | 3 | export function PicksPlugin(){ 4 | return { 5 | plug 6 | } 7 | } 8 | 9 | export function plug(configuration){ 10 | return (aspects) => { 11 | return { 12 | plugStaticDom: ()=> { 13 | let {picksList} = aspects; 14 | let {picks} = configuration; 15 | if (picks) { 16 | let {add: origAdd, reset: origReset} = picksList; 17 | 18 | picksList.add = (e) => { 19 | let {remove, index} = origAdd(e); 20 | picks.push(e); 21 | return { remove: composeSync(remove, () => void picks.splice(index(), 1)), index}; 22 | } 23 | 24 | picksList.reset = () => { 25 | origReset(); 26 | picks.length = 0; 27 | } 28 | } 29 | }, 30 | layout: () => { 31 | let {inputAspect, filterDom, filterManagerAspect} = aspects; 32 | let {picks, addOptionPicked} = configuration; 33 | /* 34 | if (!addOptionPicked){ 35 | addOptionPicked = (option, index, value) => { 36 | if (value) 37 | picks.push(option); 38 | else 39 | picks.splice(index, 1); 40 | return true; 41 | }; 42 | } 43 | 44 | function trySetWrapSelected(option, updateSelected, booleanValue){ 45 | let success = false; 46 | var confirmed = setIsOptionSelected(option, booleanValue); 47 | if (!(confirmed===false)) { 48 | updateSelected(); 49 | success = true; 50 | } 51 | return success; 52 | } 53 | 54 | let origProcessInput = inputAspect.processInput; 55 | inputAspect.processInput = () => { 56 | let origResult = origProcessInput(); 57 | if (!origResult.isEmpty) 58 | { 59 | if ( filterManagerAspect.getNavigateManager().getCount() == 1) 60 | { 61 | // todo: move exact match to filterManager 62 | let fullMatchWrap = filterManagerAspect.getNavigateManager().getHead(); 63 | let text = filterManagerAspect.getFilter(); 64 | if (fullMatchWrap.choice.searchText == text) 65 | { 66 | let success = trySetWrapSelected(fullMatchWrap, true); 67 | if (success) { 68 | filterDom.setEmpty(); 69 | origResult.isEmpty = true; 70 | } 71 | } 72 | } 73 | } 74 | return origResult; 75 | }*/ 76 | } 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /src/plugins/CustomChoiceStylingsPlugin.js: -------------------------------------------------------------------------------- 1 | import { composeSync } from "../ToolsJs"; 2 | 3 | export function CustomChoiceStylingsPlugin(defaults){ 4 | defaults.customChoiceStylings = null; 5 | return { 6 | plug 7 | } 8 | } 9 | 10 | export function plug(configuration){ 11 | return (aspects) => { 12 | return { 13 | plugStaticDom: ()=> { 14 | let {choiceDomFactory} = aspects; 15 | let customChoiceStylings = configuration.customChoiceStylings; 16 | if (customChoiceStylings) { 17 | 18 | let customChoiceStylingsAspect = CustomChoiceStylingsAspect(customChoiceStylings); 19 | ExtendChoiceDomFactory(choiceDomFactory, customChoiceStylingsAspect); 20 | } 21 | } 22 | } 23 | } 24 | } 25 | 26 | function ExtendChoiceDomFactory(choiceDomFactory, customChoiceStylingsAspect){ 27 | let origChoiceDomFactoryCreate = choiceDomFactory.create; 28 | choiceDomFactory.create = function(choice){ 29 | origChoiceDomFactoryCreate(choice); 30 | customChoiceStylingsAspect.customize(choice.wrap, choice.choiceDom, choice.choiceDomManagerHandlers); 31 | } 32 | } 33 | 34 | function CustomChoiceStylingsAspect(customChoiceStylings){ 35 | return { 36 | customize(choice){ 37 | var handlers = customChoiceStylings(choice.choiceDom, choice.wrap.option); 38 | 39 | if (handlers){ 40 | function customChoiceStylingsClosure(custom){ 41 | return function() { 42 | custom({ 43 | isOptionSelected: choice.wrap.isOptionSelected, 44 | isOptionDisabled: choice.wrap.isOptionDisabled, 45 | isHoverIn: choice.isHoverIn, 46 | //isHighlighted: wrap.choice.isHighlighted // TODO isHighlighted should be developed 47 | }); 48 | } 49 | } 50 | let choiceDomManagerHandlers = choice.choiceDomManagerHandlers; 51 | if (choiceDomManagerHandlers.updateHoverIn && handlers.updateHoverIn) 52 | choiceDomManagerHandlers.updateHoverIn 53 | = composeSync(choiceDomManagerHandlers.updateHoverIn, customChoiceStylingsClosure(handlers.updateHoverIn) ); 54 | if (choiceDomManagerHandlers.updateSelected && handlers.updateSelected) 55 | choiceDomManagerHandlers.updateSelected 56 | = composeSync(choiceDomManagerHandlers.updateSelected, customChoiceStylingsClosure(handlers.updateSelected)); 57 | if (choiceDomManagerHandlers.updateDisabled && handlers.updateDisabled) 58 | choiceDomManagerHandlers.updateDisabled 59 | = composeSync(choiceDomManagerHandlers.updateDisabled, customChoiceStylingsClosure(handlers.updateDisabled)); 60 | if (choiceDomManagerHandlers.updateHighlighted && handlers.updateHighlighted) 61 | choiceDomManagerHandlers.updateHighlighted 62 | = composeSync(choiceDomManagerHandlers.updateHighlighted, customChoiceStylingsClosure(handlers.updateHighlighted)); 63 | } 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /src/AddToJQueryPrototype.js: -------------------------------------------------------------------------------- 1 | export function addToJQueryPrototype(pluginName, createPlugin, $){ 2 | const firstChar = pluginName.charAt(0); 3 | const firstCharLower = firstChar.toLowerCase(); 4 | if (firstCharLower == firstChar) { 5 | throw new Error(`Plugin name '${pluginName}' should be started from upper case char`) 6 | } 7 | const prototypableName = firstCharLower + pluginName.slice(1) 8 | const noConflictPrototypable = $.fn[prototypableName]; 9 | const noConflictPrototypableForInstance = $.fn[pluginName]; 10 | const dataKey = `DashboardCode.${pluginName}`; 11 | 12 | function createInstance(options, e, $e){ 13 | const optionsRef = (typeof options === 'object') || options instanceof Function ? options:null; 14 | let instance = createPlugin(e, optionsRef, 15 | () => { 16 | $e.removeData(dataKey) 17 | }); 18 | $e.data(dataKey, instance); 19 | return instance; 20 | } 21 | 22 | function prototypable(options){ 23 | let output=[]; 24 | this.each( function (i, e) { 25 | let $e = $(e); 26 | let instance = $e.data(dataKey) 27 | let isMethodName = typeof options === 'string'; 28 | if (!instance) { 29 | if (isMethodName && /Dispose/.test(options)) 30 | return; 31 | instance = createInstance(options, e, $e); 32 | } 33 | if (isMethodName) { 34 | let methodName = options; 35 | if (typeof instance[methodName] === 'undefined') { 36 | let lMethodName = methodName.charAt(0).toLowerCase() + methodName.slice(1) 37 | if ( typeof instance[lMethodName] === 'undefined') { 38 | throw new Error(`No method named '${methodName}'`) 39 | } else { 40 | methodName = lMethodName; 41 | } 42 | } 43 | let result = instance[methodName](); 44 | if (result !== undefined){ 45 | output.push(result); 46 | } 47 | } 48 | }) 49 | if (output.length==0) 50 | return this; 51 | else if (output.length==1) 52 | return output[0]; 53 | else 54 | return output; 55 | } 56 | 57 | function prototypableForInstance(options){ 58 | let instance = this.data(dataKey); 59 | if (instance) 60 | return instance; 61 | else if (this.length === 1){ 62 | return createInstance(options, this.get(0), this); 63 | } else if (this.length > 1){ 64 | let output=[]; 65 | this.each(function(i, e){ 66 | output.push(createInstance(options, e, $(e))); 67 | }) 68 | return output; 69 | } 70 | } 71 | 72 | $.fn[prototypableName] = prototypable; 73 | $.fn[prototypableName].noConflict = function () { 74 | $.fn[prototypableName] = noConflictPrototypable 75 | return prototypable; 76 | } 77 | 78 | $.fn[pluginName] = prototypableForInstance; 79 | $.fn[pluginName].noConflict = function () { 80 | $.fn[pluginName] = noConflictPrototypableForInstance 81 | return prototypableForInstance; 82 | } 83 | 84 | return prototypable; 85 | } -------------------------------------------------------------------------------- /src/plugins/ChoicesDynamicStylingPlugin.js: -------------------------------------------------------------------------------- 1 | // aka auto height and scrolling 2 | export function ChoicesDynamicStylingPlugin(defaults, environment){ 3 | preset(defaults) 4 | return { 5 | plug 6 | } 7 | } 8 | 9 | export function preset(o){ 10 | o.useChoicesDynamicStyling = false; 11 | o.choicesDynamicStyling = (aspects)=>choicesDynamicStyling(aspects, window); 12 | o.minimalChoicesDynamicStylingMaxHeight = 20; 13 | } 14 | 15 | export function plug(configuration){ 16 | let {choicesDynamicStyling, useChoicesDynamicStyling} = configuration; 17 | return (aspects) => { 18 | return { 19 | layout: () => { 20 | 21 | if (useChoicesDynamicStyling) { 22 | let {choicesVisibilityAspect, specialPicksEventsAspect} = aspects; 23 | var origSetChoicesVisible = choicesVisibilityAspect.setChoicesVisible; 24 | choicesVisibilityAspect.setChoicesVisible = 25 | function(visible){ 26 | if (visible) 27 | choicesDynamicStyling(aspects); 28 | origSetChoicesVisible(visible); 29 | }; 30 | var origBackSpace = specialPicksEventsAspect.backSpace; 31 | specialPicksEventsAspect.backSpace = (pick) => {origBackSpace(pick); choicesDynamicStyling(aspects);}; 32 | } 33 | } 34 | } 35 | } 36 | } 37 | 38 | function choicesDynamicStyling(aspects, window){ 39 | let {choicesDom, navigateAspect, configuration} = aspects; 40 | let choicesElement = choicesDom.choicesElement; 41 | let minimalChoicesDynamicStylingMaxHeight = configuration.minimalChoicesDynamicStylingMaxHeight; 42 | 43 | //find height of the browser window 44 | var g = window.document.getElementsByTagName('body')[0], 45 | e = window.document.documentElement, 46 | y = window.innerHeight || e.clientHeight || g.clientHeight; 47 | 48 | //find position of choicesElement, if it's at the bottom of the page make the choicesElement shorter 49 | var pos = choicesElement.parentNode.getBoundingClientRect(); 50 | var new_y = y - pos.top; 51 | 52 | //calculate multi select max-height 53 | var msHeight = Math.max(minimalChoicesDynamicStylingMaxHeight, Math.round((new_y * 0.85))); // Michalek: 0.85 is empiric value, without it list was longer than footer height ; TODO: propose better way 54 | 55 | //add css height value 56 | choicesElement.style.setProperty("max-height", msHeight+"px"); 57 | choicesElement.style.setProperty("overflow-y", "auto"); 58 | 59 | if (!choicesDom.ChoicesDynamicStylingPlugin_scrollHandle){ 60 | choicesDom.ChoicesDynamicStylingPlugin_scrollHandle = true; 61 | var origNavigateAspectNavigate = navigateAspect.navigate; 62 | navigateAspect.navigate = function(down){ 63 | var wrap = origNavigateAspectNavigate(down); 64 | if (wrap!= null && wrap.choice!=null && wrap.choice.choiceDom.choiceElement!=null) 65 | wrap.choice.choiceDom.choiceElement.scrollIntoView(false); // alignTo false - scroll to the top bottom of dropdown first 66 | // TODO: BUG if mouse left on the dropdow scroll to bottom and one after doesn't work properly 67 | return wrap; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["stylelint-config-standard", "stylelint-config-recommended-scss"], 3 | "plugins": [ 4 | "stylelint-order" 5 | ], 6 | "rules": { 7 | "selector-combinator-space-before" : null, 8 | "block-closing-brace-newline-before": null, 9 | "no-invalid-position-at-import-rule": null, 10 | "selector-descendant-combinator-no-non-space":null, 11 | "declaration-colon-space-after":null, 12 | "at-rule-empty-line-before": null, 13 | "at-rule-name-space-after": "always", 14 | "at-rule-no-vendor-prefix": true, 15 | "at-rule-semicolon-space-before": "never", 16 | "block-closing-brace-empty-line-before": null, 17 | "block-closing-brace-newline-after": null, 18 | "block-opening-brace-space-before": null, 19 | "color-named": null, 20 | "declaration-block-semicolon-newline-after": null, 21 | "declaration-block-semicolon-newline-before": "never-multi-line", 22 | "declaration-block-semicolon-space-after": "always-single-line", 23 | "declaration-empty-line-before": null, 24 | "declaration-no-important": true, 25 | "font-family-name-quotes": "always-where-recommended", 26 | "font-weight-notation": "numeric", 27 | "function-url-no-scheme-relative": true, 28 | "function-url-quotes": "always", 29 | "length-zero-no-unit": true, 30 | "max-empty-lines": 2, 31 | "max-line-length": null, 32 | "media-feature-name-no-vendor-prefix": true, 33 | "media-feature-parentheses-space-inside": "never", 34 | "media-feature-range-operator-space-after": "always", 35 | "media-feature-range-operator-space-before": "never", 36 | "no-descending-specificity": null, 37 | "no-duplicate-selectors": true, 38 | "number-leading-zero": null, 39 | "indentation": null, 40 | "media-feature-name-no-unknown": [true, { 41 | "ignoreMediaFeatureNames": ["prefers-reduced-motion"] 42 | }], 43 | "order/properties-order": null, 44 | "property-no-vendor-prefix": true, 45 | "rule-empty-line-before": null, 46 | "scss/dollar-variable-default": [true, { "ignore": "local" }], 47 | "selector-attribute-quotes": "always", 48 | "selector-list-comma-newline-after": null, 49 | "selector-list-comma-newline-before": "never-multi-line", 50 | "selector-list-comma-space-after": "always-single-line", 51 | "selector-list-comma-space-before": "never-single-line", 52 | "selector-max-attribute": 2, 53 | "selector-max-class": 7, 54 | "selector-max-combinators": 7, 55 | "selector-max-compound-selectors": 7, 56 | "selector-max-empty-lines": 1, 57 | "selector-max-id": 0, 58 | "selector-max-specificity": null, 59 | "selector-max-type": 3, 60 | "selector-max-universal": 1, 61 | "selector-no-qualifying-type": [true, {"ignore": ["attribute", "class", "id"]}], 62 | "selector-no-vendor-prefix": true, 63 | "string-quotes": "double", 64 | "value-keyword-case": "lower", 65 | "value-list-comma-newline-after": "never-multi-line", 66 | "value-list-comma-newline-before": "never-multi-line", 67 | "value-list-comma-space-after": "always", 68 | "value-no-vendor-prefix": true, 69 | "no-eol-whitespace": null, 70 | "block-opening-brace-newline-after":null, 71 | "no-missing-end-of-source-newline":null, 72 | "comment-empty-line-before": null, 73 | "comment-whitespace-inside": null, 74 | "scss/at-import-no-partial-leading-underscore": null, 75 | "color-function-notation": null 76 | } 77 | } -------------------------------------------------------------------------------- /src/plugins/PickButtonPlugin.js: -------------------------------------------------------------------------------- 1 | import {EventBinder} from '../ToolsDom'; 2 | import {addStyling} from '../ToolsStyling'; 3 | import {composeSync} from '../ToolsJs'; 4 | 5 | export function PickButtonPlugCssPatchBs4(defaults){ 6 | // increase font and limit the line 7 | defaults.cssPatch.pickButton = {float : "none", verticalAlign: "text-top", fontSize:'1.8em', lineHeight: '0.5em', fontWeight:'400' }; 8 | 9 | } 10 | 11 | export function PickButtonPlugCssPatchBs5(defaults){ 12 | defaults.cssPatch.pickButton = {float : "none", verticalAlign: "text-top", fontSize:'0.8em'}; 13 | } 14 | 15 | export function PickButtonBs4Plugin(defaults){ 16 | defaults.pickButtonHTML = ''; 17 | defaults.css.pickButton = 'close'; 18 | return PickButtonPlugin() 19 | } 20 | 21 | export function PickButtonBs5Plugin(defaults){ 22 | defaults.pickButtonHTML = ''; 23 | defaults.css.pickButton = 'btn-close'; 24 | return PickButtonPlugin() 25 | } 26 | 27 | export function PickButtonPlugin(){ 28 | return {plug} 29 | } 30 | 31 | export function plug(configuration){ 32 | return (aspects) => { 33 | return { 34 | plugStaticDom: () => { 35 | var {pickDomFactory, staticDom} = aspects; 36 | ExtendPickDomFactory(pickDomFactory, staticDom.createElementAspect, configuration.pickButtonHTML, configuration.css); 37 | }, 38 | layout: ()=>{ 39 | var {producePickAspect} = aspects; 40 | ExtendProducePickAspect(producePickAspect); 41 | 42 | } 43 | } 44 | } 45 | } 46 | 47 | function ExtendProducePickAspect(producePickAspect){ 48 | let origProducePickPickAspect = producePickAspect.producePick; 49 | producePickAspect.producePick = (wrap)=>{ 50 | let pick = origProducePickPickAspect(wrap) 51 | pick.removeOnButton = (event) => { 52 | pick.setSelectedFalse(); 53 | } 54 | pick.dispose = composeSync( 55 | pick.dispose, 56 | ()=>{pick.removeOnButton=null} 57 | ); 58 | return pick; 59 | } 60 | } 61 | 62 | function ExtendPickDomFactory(pickDomFactory, createElementAspect, pickButtonHTML, css){ 63 | var origCreatePickDomFactory = pickDomFactory.create; 64 | pickDomFactory.create = (pick) => { 65 | 66 | origCreatePickDomFactory(pick); 67 | let {pickDom,pickDomManagerHandlers} = pick; 68 | createElementAspect.createElementFromHtmlPutAfter(pickDom.pickContentElement, pickButtonHTML); 69 | let pickButtonElement = pickDom.pickElement.querySelector('BUTTON'); 70 | pickDom.pickButtonElement=pickButtonElement; 71 | pickDomManagerHandlers.disableButton = (val)=> { 72 | pickButtonElement.disabled = val; 73 | } 74 | 75 | let eventBinder = EventBinder(); 76 | eventBinder.bind(pickButtonElement, "click", (event)=>pick.removeOnButton(event)); 77 | 78 | addStyling(pickButtonElement, css.pickButton); 79 | 80 | pick.dispose = composeSync( 81 | pick.dispose, 82 | ()=>{ 83 | eventBinder.unbind(); 84 | pickDom.pickButtonElement=null; 85 | pickDomManagerHandlers.disableButton = null; 86 | } 87 | ) 88 | } 89 | } -------------------------------------------------------------------------------- /src/plugins/DisableComponentPlugin.js: -------------------------------------------------------------------------------- 1 | import {composeSync} from '../ToolsJs'; 2 | 3 | export function DisableComponentPlugin(defaults){ 4 | preset(defaults) 5 | return { 6 | plug 7 | } 8 | } 9 | 10 | export function preset(defaults){ 11 | defaults.getDisabled = () => null; 12 | } 13 | 14 | export function plug(configuration){ 15 | let disabledComponentAspect = DisabledComponentAspect(configuration.getDisabled); 16 | return (aspects) => { 17 | aspects.disabledComponentAspect=disabledComponentAspect; 18 | return { 19 | plugStaticDom: () => { 20 | var {pickDomFactory} = aspects; 21 | ExtendPickDomFactory(pickDomFactory, disabledComponentAspect); 22 | }, 23 | layout: () => { 24 | var {updateAppearanceAspect, picksList, picksDom, picksElementAspect} = aspects; 25 | 26 | var disableComponent = (isComponentDisabled)=>{ 27 | picksList.forEach(pick=>pick.pickDomManagerHandlers.updateComponentDisabled()) 28 | picksDom.disable(isComponentDisabled); 29 | } 30 | 31 | var origOnClick = picksElementAspect.onClick; 32 | picksElementAspect.onClick = (handler)=>{ 33 | disableComponent = (isComponentDisabled)=>{ 34 | picksList.forEach(pick=>pick.pickDomManagerHandlers.updateComponentDisabled()) 35 | picksDom.disable(isComponentDisabled); 36 | if (isComponentDisabled) 37 | picksElementAspect.onClickUnbind(); //componentDisabledEventBinder.unbind(); 38 | else 39 | origOnClick(handler); //componentDisabledEventBinder.bind(picksElement, "click", handler); 40 | } 41 | } 42 | 43 | let isComponentDisabled; // state! 44 | function updateDisabled(){ 45 | let newIsComponentDisabled = disabledComponentAspect.getDisabled()??false; 46 | if (isComponentDisabled!==newIsComponentDisabled){ 47 | isComponentDisabled=newIsComponentDisabled; 48 | disableComponent(newIsComponentDisabled); 49 | } 50 | } 51 | 52 | updateAppearanceAspect.updateAppearance = composeSync(updateAppearanceAspect.updateAppearance, updateDisabled); 53 | 54 | return{ 55 | buildApi(api){ 56 | api.updateDisabled = updateDisabled; 57 | } 58 | } 59 | } 60 | } 61 | } 62 | } 63 | 64 | export function DisabledComponentAspect(getDisabled) { 65 | return { 66 | getDisabled 67 | } 68 | } 69 | 70 | function ExtendPickDomFactory(pickDomFactory, disabledComponentAspect){ 71 | var origCreatePickDomFactory = pickDomFactory.create; 72 | pickDomFactory.create = (pick) => { 73 | origCreatePickDomFactory(pick); 74 | let pickDomManagerHandlers = pick.pickDomManagerHandlers; 75 | pickDomManagerHandlers.updateComponentDisabled = () => { 76 | if (pickDomManagerHandlers.disableButton) 77 | pickDomManagerHandlers.disableButton(disabledComponentAspect.getDisabled()??false) 78 | }; 79 | pickDomManagerHandlers.updateComponentDisabled(); 80 | } 81 | } -------------------------------------------------------------------------------- /src/plugins/index.js: -------------------------------------------------------------------------------- 1 | //import {BsAppearancePlugin} from './BsAppearancePlugin' 2 | import {BsAppearanceBs4Plugin} from './BsAppearanceBs4Plugin' 3 | import {BsAppearanceBs5Plugin} from './BsAppearanceBs5Plugin' 4 | import {BsAppearanceBs4CssPatchPlugin} from './BsAppearanceBs4CssPatchPlugin' 5 | import {BsAppearanceBs5CssPatchPlugin} from './BsAppearanceBs5CssPatchPlugin' 6 | 7 | import {LabelForAttributePlugin} from './LabelForAttributePlugin' 8 | import {RtlPlugin} from './RtlPlugin' 9 | import {FormResetPlugin} from './FormResetPlugin' 10 | import {ValidationApiPlugin} from './ValidationApiPlugin' 11 | 12 | import {HiddenOptionPlugin} from './HiddenOptionPlugin' 13 | import {HiddenOptionAltPlugin} from './HiddenOptionAltPlugin' 14 | 15 | import {CssPatchBs4Plugin} from './CssPatchBs4Plugin' 16 | import {CssPatchBs5Plugin} from './CssPatchBs5Plugin' 17 | 18 | import {JQueryMethodsPlugin} from './JQueryMethodsPlugin' 19 | import {OptionsApiPlugin} from './OptionsApiPlugin' 20 | import {FormRestoreOnBackwardPlugin} from './FormRestoreOnBackwardPlugin' 21 | import {SelectElementPlugin} from './SelectElementPlugin' 22 | import {SelectedOptionPlugin} from './SelectedOptionPlugin' 23 | import {DisabledOptionPlugin, DisabledOptionCssPatchPlugin} from './DisabledOptionPlugin' 24 | import {PicksApiPlugin} from './PicksApiPlugin' 25 | import {PicksPlugin} from './PicksPlugin' 26 | 27 | import {CreatePopperPlugin} from './CreatePopperPlugin' 28 | import {ChoicesDynamicStylingPlugin} from './ChoicesDynamicStylingPlugin' 29 | 30 | import {HighlightPlugin} from './HighlightPlugin' 31 | 32 | import {CustomChoiceStylingsPlugin} from './CustomChoiceStylingsPlugin' 33 | import {CustomPickStylingsPlugin} from './CustomPickStylingsPlugin' 34 | 35 | import {UpdateAppearancePlugin} from './UpdateAppearancePlugin' 36 | import {DisableComponentPlugin} from './DisableComponentPlugin' 37 | 38 | import {PlaceholderPlugin} from './PlaceholderPlugin' 39 | import {PlaceholderCssPatchPlugin} from './PlaceholderCssPatchPlugin' 40 | import {FloatingLabelPlugin} from './FloatingLabelPlugin' 41 | import {FloatingLabelCssPatchBs5Plugin} from './FloatingLabelCssPatchBs5Plugin' 42 | 43 | import {WarningCssPatchPlugin} from './WarningCssPatchPlugin' 44 | import {WarningBs4Plugin} from './WarningBs4Plugin' 45 | import {WarningBs5Plugin} from './WarningBs5Plugin' 46 | 47 | import {PickButtonBs4Plugin, PickButtonBs5Plugin, PickButtonPlugCssPatchBs4, PickButtonPlugCssPatchBs5} from './PickButtonPlugin' 48 | 49 | 50 | export { 51 | BsAppearanceBs4Plugin, BsAppearanceBs5Plugin, 52 | BsAppearanceBs4CssPatchPlugin, BsAppearanceBs5CssPatchPlugin, 53 | 54 | CssPatchBs4Plugin, CssPatchBs5Plugin, 55 | 56 | SelectElementPlugin, 57 | LabelForAttributePlugin, HiddenOptionPlugin, ValidationApiPlugin, 58 | UpdateAppearancePlugin, 59 | 60 | PickButtonBs4Plugin, PickButtonBs5Plugin, PickButtonPlugCssPatchBs4, PickButtonPlugCssPatchBs5, 61 | 62 | DisableComponentPlugin, 63 | FormResetPlugin, CreatePopperPlugin, RtlPlugin, PlaceholderPlugin, PlaceholderCssPatchPlugin, 64 | OptionsApiPlugin, 65 | JQueryMethodsPlugin, 66 | SelectedOptionPlugin, FormRestoreOnBackwardPlugin, 67 | DisabledOptionPlugin, DisabledOptionCssPatchPlugin, 68 | PicksApiPlugin, HighlightPlugin, 69 | ChoicesDynamicStylingPlugin, CustomPickStylingsPlugin, CustomChoiceStylingsPlugin, 70 | 71 | FloatingLabelPlugin, FloatingLabelCssPatchBs5Plugin, 72 | WarningBs5Plugin, WarningBs4Plugin, WarningCssPatchPlugin, 73 | 74 | PicksPlugin, 75 | 76 | HiddenOptionAltPlugin 77 | } -------------------------------------------------------------------------------- /src/PicksDomFactory.js: -------------------------------------------------------------------------------- 1 | import {addStyling, toggleStyling} from './ToolsStyling'; 2 | 3 | export function PicksDomFactory(staticDom){ 4 | return { 5 | create(){ 6 | var {picksElement, isDisposablePicksElementFlag, css, createElementAspect} = staticDom; 7 | var pickFilterElement = createElementAspect.createElement('LI'); 8 | 9 | addStyling(picksElement, css.picks); 10 | addStyling(pickFilterElement, css.pickFilter); 11 | 12 | let disableToggleStyling = toggleStyling(picksElement, css.picks_disabled); 13 | let focusToggleStyling = toggleStyling(picksElement, css.picks_focus); 14 | let isFocusIn = false; 15 | 16 | return { 17 | picksElement, 18 | isDisposablePicksElementFlag, 19 | pickFilterElement, 20 | 21 | createPickElement(){ 22 | var pickElement = createElementAspect.createElement('LI'); 23 | addStyling(pickElement, css.pick); 24 | return { 25 | pickElement, 26 | attach: (beforeElement) => picksElement.insertBefore(pickElement, beforeElement??pickFilterElement), 27 | detach: () => picksElement.removeChild(pickElement) 28 | }; 29 | }, 30 | disable(isComponentDisabled){ 31 | disableToggleStyling(isComponentDisabled) 32 | }, 33 | toggleFocusStyling(){ 34 | focusToggleStyling(isFocusIn) 35 | }, 36 | getIsFocusIn(){ 37 | return isFocusIn; 38 | }, 39 | setIsFocusIn(newIsFocusIn){ 40 | isFocusIn = newIsFocusIn 41 | }, 42 | dispose(){ 43 | if (!isDisposablePicksElementFlag){ 44 | disableToggleStyling(false) 45 | focusToggleStyling(false) 46 | 47 | if (pickFilterElement.parentNode) 48 | pickFilterElement.parentNode.removeChild(pickFilterElement) 49 | } 50 | } 51 | } 52 | } 53 | } 54 | } 55 | 56 | export function PicksDomFactoryPlugCss(css){ 57 | css.picks = 'form-control'; 58 | css.pickFilter = ''; 59 | css.picks_disabled = 'disabled'; 60 | css.picks_focus = 'focus'; 61 | css.pick = 'badge'; 62 | } 63 | 64 | function PicksDomFactoryPlugCssPatch(cssPatch){ 65 | cssPatch.picks = {listStyleType:'none', display:'flex', flexWrap:'wrap', height: 'auto', marginBottom: '0',cursor: 'text'}, 66 | cssPatch.picks_disabled = {backgroundColor: '#e9ecef'}; 67 | cssPatch.picks_focus = {borderColor: '#80bdff', boxShadow: '0 0 0 0.2rem rgba(0, 123, 255, 0.25)'}; 68 | cssPatch.pick = {paddingLeft: '0', paddingRight: '.5rem', paddingInlineStart:'0', paddingInlineEnd:'0.5rem'} 69 | } 70 | 71 | export function PicksDomFactoryPlugCssPatchBs4(cssPatch){ 72 | PicksDomFactoryPlugCssPatch(cssPatch) 73 | // TODO: this is done for button and should be moved to button plugin 74 | //cssPatch.pick.lineHeight = '1.5em'; 75 | cssPatch.pick.paddingTop = '0.35em'; 76 | cssPatch.pick.paddingBottom = '0.35em'; 77 | } 78 | 79 | export function PicksDomFactoryPlugCssPatchBs5(cssPatch){ 80 | PicksDomFactoryPlugCssPatch(cssPatch) 81 | // TODO: this is done for button and should be moved to button plugin 82 | cssPatch.pick.color ='var(--bs-dark)'; 83 | } -------------------------------------------------------------------------------- /src/plugins/WarningPlugin.js: -------------------------------------------------------------------------------- 1 | import {addStyling} from "../ToolsStyling"; 2 | import {composeSync} from '../ToolsJs'; 3 | 4 | const defNoResultsWarningMessage = 'No results found'; 5 | 6 | export function preset(o){o.noResultsWarning=defNoResultsWarningMessage; o.isNoResultsWarningEnabled=false;} 7 | 8 | export function plug(configuration){ 9 | return (aspects) => { 10 | return { 11 | layout: () => { 12 | let {choicesDom, staticManager, afterInputAspect, filterManagerAspect, resetLayoutAspect, staticDom} = aspects; 13 | let {createElementAspect} = staticDom; 14 | let {css, noResultsWarning} = configuration; 15 | 16 | if (configuration.isNoResultsWarningEnabled){ 17 | let warningAspect = WarningAspect(choicesDom, createElementAspect, staticManager, css); 18 | aspects.warningAspect = warningAspect; 19 | 20 | ExtendAfterInputAspect(afterInputAspect, warningAspect, filterManagerAspect, noResultsWarning); 21 | 22 | resetLayoutAspect.resetLayout = composeSync(() => warningAspect.hide(), resetLayoutAspect.resetLayout); 23 | } 24 | }, 25 | append: ()=> { 26 | let {createPopperAspect, filterDom, warningAspect, staticManager, disposeAspect} = aspects; 27 | if (warningAspect){ 28 | let filterInputElement = filterDom.filterInputElement; 29 | 30 | let pop2 = createPopperAspect.createPopper(warningAspect.warningElement, filterInputElement, false); 31 | staticManager.appendToContainer = composeSync(staticManager.appendToContainer, pop2.init); 32 | 33 | var origWarningAspectShow = warningAspect.show; 34 | warningAspect.show = (msg) => { 35 | pop2.update(); 36 | origWarningAspectShow(msg); 37 | } 38 | disposeAspect.dispose = composeSync(disposeAspect.dispose, pop2.dispose); 39 | } 40 | } 41 | } 42 | } 43 | } 44 | 45 | function ExtendAfterInputAspect(afterInputAspect, warningAspect, filterManagerAspect, noResultsWarning){ 46 | var origVisible = afterInputAspect.visible; 47 | afterInputAspect.visible = (showChoices, visibleCount) => { 48 | warningAspect.hide(); 49 | origVisible(showChoices, visibleCount) 50 | } 51 | 52 | var origNotVisible = afterInputAspect.notVisible; 53 | afterInputAspect.notVisible = (hideChoices) => { 54 | origNotVisible(hideChoices); 55 | 56 | if (filterManagerAspect.getFilter()) 57 | warningAspect.show(noResultsWarning); 58 | else 59 | warningAspect.hide(); 60 | } 61 | } 62 | 63 | function WarningAspect(choicesDom, createElementAspect, staticManager, css){ 64 | let choicesElement = choicesDom.choicesElement; 65 | 66 | var warningElement = createElementAspect.createElement('DIV'); 67 | staticManager.appendToContainer = composeSync(staticManager.appendToContainer, ()=> 68 | choicesElement.parentNode.insertBefore(warningElement, choicesElement.nextSibling)); 69 | 70 | warningElement.style.display = 'none'; 71 | addStyling(warningElement, css.warning); 72 | 73 | 74 | return { 75 | warningElement, 76 | show(message){ 77 | warningElement.style.display = 'block'; 78 | warningElement.innerHTML = message; 79 | }, 80 | hide(){ 81 | warningElement.style.display = 'none'; 82 | warningElement.innerHTML = ""; 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /src/plugins/HighlightPlugin.js: -------------------------------------------------------------------------------- 1 | export function HighlightPlugin(defaults){ 2 | defaults.useHighlighting = false; 3 | return { 4 | plug 5 | } 6 | } 7 | 8 | function ExtendChoiceDomFactory(choiceDomFactory, dataWrap){ 9 | var origChoiceDomFactoryCreate = choiceDomFactory.create; 10 | choiceDomFactory.create = (choice) => { 11 | origChoiceDomFactoryCreate(choice); 12 | let choiceElement = choice.choiceDom.choiceElement; 13 | choice.choiceDomManagerHandlers.updateHighlighted = () => { 14 | var text = dataWrap.getText(choice.wrap.option); 15 | var highlighter = aspects.highlightAspect.getHighlighter(); 16 | if (highlighter) 17 | highlighter(choiceElement, choice.choiceDom, text); 18 | else 19 | choiceElement.textContent = text; 20 | }; 21 | } 22 | } 23 | 24 | export function plug(configuration){ 25 | return (aspects) => { 26 | if (configuration.useHighlighting) 27 | aspects.highlightAspect = HighlightAspect(); 28 | return { 29 | plugStaticDom(){ 30 | var {choiceDomFactory, dataWrap} = aspects; 31 | ExtendChoiceDomFactory(choiceDomFactory, dataWrap); 32 | }, 33 | layout(){ 34 | let {highlightAspect, filterManagerAspect, produceChoiceAspect} = aspects; 35 | if (highlightAspect){ 36 | let origProcessEmptyInput = filterManagerAspect.processEmptyInput; 37 | filterManagerAspect.processEmptyInput = function(){ 38 | highlightAspect.reset(); 39 | origProcessEmptyInput(); 40 | } 41 | let origSetFilter = filterManagerAspect.setFilter; 42 | filterManagerAspect.setFilter = function(text){ 43 | highlightAspect.set(text); 44 | origSetFilter(text); 45 | } 46 | let origProduceChoice = produceChoiceAspect.produceChoice; 47 | produceChoiceAspect.produceChoice = function(wrap){ 48 | origProduceChoice(wrap); 49 | let origSetVisible = wrap.choice.choiceDomManagerHandlers.setVisible; 50 | wrap.choice.choiceDomManagerHandlers.setVisible = function(v){ 51 | origSetVisible(v); 52 | wrap.choice.choiceDomManagerHandlers.updateHighlighted(); 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } 59 | } 60 | 61 | function HighlightAspect(){ 62 | let highlighter = null; 63 | return { 64 | getHighlighter(){ 65 | return highlighter; 66 | }, 67 | set(filter){ 68 | var guarded = filter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); 69 | var regex = new RegExp("("+guarded+")","gi"); 70 | highlighter = function(e, choiceDom, text){ 71 | // TODO replace with 72 | // var pos = text.indexOf(filter); 73 | e.innerHTML = text.replace(regex,"$1"); 74 | // TODO to method 75 | // var nodes = e.querySelectorAll('u'); 76 | // var array = Array.prototype.slice.call(nodes); 77 | // if (choiceDom.highlightedElements) 78 | // choiceDom.highlightedElements.concat(array); 79 | // else 80 | // choiceDom.highlightedElements = array; 81 | } 82 | }, 83 | reset(){ 84 | highlighter = null; 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /src/plugins/HiddenOptionAltPlugin.js: -------------------------------------------------------------------------------- 1 | export function HiddenOptionAltPlugin(){ 2 | return { 3 | plug 4 | } 5 | } 6 | 7 | export function plug(configuration){ 8 | return (aspects) => { 9 | return { 10 | layout: () => { 11 | let {createWrapAspect, isChoiceSelectableAspect, 12 | wrapsCollection, buildAndAttachChoiceAspect, countableChoicesListInsertAspect, countableChoicesList 13 | } = aspects; 14 | 15 | countableChoicesListInsertAspect.countableChoicesListInsert = (wrap, key) => { 16 | if ( !wrap.isOptionHidden ){ 17 | let choiceNext = wrapsCollection.getNext(key, c=>!c.isOptionHidden ); 18 | countableChoicesList.add(wrap, choiceNext) 19 | } 20 | } 21 | 22 | let origBuildAndAttachChoice = buildAndAttachChoiceAspect.buildAndAttachChoice; 23 | buildAndAttachChoiceAspect.buildAndAttachChoice=(wrap, getNextElement) => { 24 | origBuildAndAttachChoice(wrap, getNextElement); 25 | wrap.choice.choiceDomManagerHandlers.setVisible(!wrap.isOptionHidden) 26 | } 27 | 28 | var origIsSelectable = isChoiceSelectableAspect.isSelectable; 29 | isChoiceSelectableAspect.isSelectable = (wrap) => origIsSelectable(wrap) && !wrap.isOptionHidden; 30 | 31 | let {getIsOptionHidden, options} = configuration; 32 | if (options) { 33 | if (!getIsOptionHidden) 34 | getIsOptionHidden = (option) => (option.hidden===undefined)?false:option.hidden; 35 | } else { 36 | if (!getIsOptionHidden) 37 | getIsOptionHidden = (option) => { 38 | return option.hidden; 39 | } 40 | } 41 | 42 | var origCreateWrap = createWrapAspect.createWrap; 43 | createWrapAspect.createWrap = (option) => { 44 | let wrap = origCreateWrap(option); 45 | wrap.isOptionHidden = getIsOptionHidden(option); 46 | return wrap; 47 | }; 48 | 49 | return { 50 | buildApi(api){ 51 | let getNextNonHidden = (key) => wrapsCollection.getNext(key, c => !c.isOptionHidden ); 52 | api.updateOptionsHidden = () => 53 | wrapsCollection.forLoop( (wrap, key) => 54 | updateChoiceHidden(wrap, key, getNextNonHidden, countableChoicesList, getIsOptionHidden) 55 | ); 56 | api.updateOptionHidden = (key) => updateChoiceHidden(wrapsCollection.get(key), key, getNextNonHidden, countableChoicesList, getIsOptionHidden); 57 | } 58 | } 59 | } 60 | } 61 | } 62 | } 63 | 64 | function updateChoiceHidden(wrap, key, getNextNonHidden, countableChoicesList, getIsOptionHidden){ 65 | let newIsOptionHidden = getIsOptionHidden(wrap.option); 66 | if (newIsOptionHidden != wrap.isOptionHidden) 67 | { 68 | wrap.isOptionHidden= newIsOptionHidden; 69 | if (wrap.isOptionHidden) 70 | countableChoicesList.remove(wrap) 71 | else{ 72 | let nextChoice = getNextNonHidden(key); // TODO: should not rely on element but do 73 | countableChoicesList.add(wrap, nextChoice); 74 | } 75 | wrap.choice.choiceDomManagerHandlers.setVisible(!wrap.isOptionHidden) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/BsMultiSelectDepricatedParameters.js: -------------------------------------------------------------------------------- 1 | const transformStyles = [ 2 | {old:'selectedPanelDisabledBackgroundColor', opt:'picks_disabled', style:"backgroundColor"}, 3 | {old:'selectedPanelFocusValidBoxShadow', opt:'picks_focus_valid', style:"boxShadow"}, 4 | {old:'selectedPanelFocusInvalidBoxShadow', opt:'picks_focus_invalid', style:"boxShadow"}, 5 | {old:'selectedPanelDefMinHeight', opt:'picks_def', style:"minHeight"}, 6 | {old:'selectedPanelLgMinHeight', opt:'picks_lg', style:"minHeight"}, 7 | {old:'selectedPanelSmMinHeight', opt:'picks_sm', style:"minHeight"}, 8 | {old:'selectedItemContentDisabledOpacity', opt:'choiceLabel_disabled', style:"opacity"} 9 | ] 10 | 11 | const transformClasses = [ 12 | {old:'dropDownMenuClass', opt:'choices'}, 13 | {old:'dropDownItemClass', opt:'choice'}, 14 | {old:'dropDownItemHoverClass', opt:'choice_hover'}, 15 | {old:'selectedPanelClass', opt:'picks'}, 16 | {old:'selectedItemClass', opt:'pick'}, 17 | {old:'removeSelectedItemButtonClass', opt:'pickButton'}, 18 | {old:'filterInputItemClass', opt:'pickFilter'}, 19 | {old:'filterInputClass', opt:'filterInput'}, 20 | {old:'selectedPanelFocusClass', opt:'picks_focus'}, 21 | {old:'selectedPanelDisabledClass', opt:'picks_disabled'}, 22 | {old:'selectedItemContentDisabledClass', opt:'pick_disabled'} 23 | ] 24 | 25 | export function adjustLegacySettings(settings){ 26 | if (!settings.css) 27 | settings.css={} 28 | var css =settings.css; 29 | 30 | if (!settings.cssPatch) 31 | settings.cssPatch={} 32 | var cssPatch =settings.cssPatch; 33 | 34 | if (settings.selectedPanelFocusBorderColor || settings.selectedPanelFocusBoxShadow){ 35 | console.log("DashboarCode.BsMultiSelect: selectedPanelFocusBorderColor and selectedPanelFocusBoxShadow are depricated, use - cssPatch:{picks_focus:{borderColor:'myValue', boxShadow:'myValue'}}"); 36 | if(!cssPatch.picks_focus){ 37 | cssPatch.picks_focus = {boxShadow: settings.selectedPanelFocusBoxShadow, borderColor: settings.selectedPanelFocusBorderColor} 38 | } 39 | delete settings.selectedPanelFocusBorderColor; 40 | delete settings.selectedPanelFocusBoxShadow; 41 | } 42 | 43 | transformStyles.forEach( 44 | (i)=>{ 45 | if (settings[i.old]){ 46 | console.log(`DashboarCode.BsMultiSelect: ${i.old} is depricated, use - cssPatch:{${i.opt}:{${i.style}:'myValue'}}`); 47 | if(!settings[i.opt]){ 48 | let opt = {} 49 | opt[i.style] = settings[i.old] 50 | settings.cssPatch[i.opt]=opt; 51 | } 52 | delete settings[i.old]; 53 | } 54 | } 55 | ) 56 | 57 | transformClasses.forEach( (i) => { 58 | if (settings[i.old]){ 59 | console.log(`DashboarCode.BsMultiSelect: ${i.old} is depricated, use - css:{${i.opt}:'myValue'}`); 60 | if(!css[i.opt]){ 61 | css[i.opt]= settings[i.old] 62 | } 63 | delete settings[i.old]; 64 | } 65 | }) 66 | 67 | if (settings.inputColor){ 68 | console.log("DashboarCode.BsMultiSelect: inputColor is depricated, remove parameter"); 69 | delete settings.inputColor; 70 | } 71 | 72 | if (settings.useCss){ 73 | console.log("DashboarCode.BsMultiSelect: 'useCss: true' is depricated, use - 'useCssPatch: false'"); 74 | if(!css.pick_disabled){ 75 | settings.useCssPatch = !settings.useCss 76 | } 77 | delete settings.useCss; 78 | } 79 | 80 | if (settings.getIsValid || settings.getIsInValid){ 81 | throw "DashboarCode.BsMultiSelect: parameters getIsValid and getIsInValid are depricated and removed, use - getValidity that should return (true|false|null) " 82 | } 83 | } -------------------------------------------------------------------------------- /debugJsSimplePicks.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | DashboardCode Multiselect Plugin for Bootstrap 5 10 | 11 | 12 |
13 |
14 | 15 | 16 | 17 | 25 |
26 |
27 |
28 | 29 |
30 |
31 |
32 |
33 | 34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /dist/css/BsMultiSelect.bs4.min.css: -------------------------------------------------------------------------------- 1 | .dashboardcode-bsmultiselect ul.form-control{display:flex;flex-wrap:wrap;height:auto;min-height:calc(1.5em + .75rem + 2px);margin-bottom:0;cursor:text;list-style-type:none}.dashboardcode-bsmultiselect ul.form-control input{height:auto;padding:0;margin:0;font-weight:inherit;color:inherit;background-color:transparent;border:0;outline:0;box-shadow:none}.dashboardcode-bsmultiselect ul.form-control.disabled{background-color:#e9ecef}.dashboardcode-bsmultiselect ul.form-control::-moz-placeholder{color:#6c757d;opacity:1}.dashboardcode-bsmultiselect ul.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.dashboardcode-bsmultiselect ul.form-control::placeholder{color:#6c757d;opacity:1}.dashboardcode-bsmultiselect ul.form-control>li.badge{padding-left:0;-webkit-padding-start:0;padding-inline-start:0;-webkit-padding-end:0.5rem;padding-inline-end:0.5rem;padding-top:.35em;padding-bottom:.35em;line-height:1.5em}.dashboardcode-bsmultiselect ul.form-control>li.badge button.close{float:none;font-size:1.8em;line-height:.5em;font-weight:400}.dashboardcode-bsmultiselect ul.form-control>li.badge span.disabled{opacity:.65}.dashboardcode-bsmultiselect ul.form-control.focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.dashboardcode-bsmultiselect ul.form-control.form-control-sm{min-height:calc(1.5em + .5rem + 2px)}.dashboardcode-bsmultiselect ul.form-control.form-control-sm input{font-size:.875rem}.dashboardcode-bsmultiselect ul.form-control.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.dashboardcode-bsmultiselect ul.form-control.form-control-lg input{font-size:1.25rem}.dashboardcode-bsmultiselect ul.form-control.is-valid,.was-validated .dashboardcode-bsmultiselect ul.form-control:valid{border-color:#28a745}.dashboardcode-bsmultiselect ul.form-control.is-valid.focus,.was-validated .dashboardcode-bsmultiselect ul.form-control:valid.focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.dashboardcode-bsmultiselect ul.form-control.is-invalid,.was-validated .dashboardcode-bsmultiselect ul.form-control:invalid{border-color:#dc3545}.dashboardcode-bsmultiselect ul.form-control.is-invalid.focus,.was-validated .dashboardcode-bsmultiselect ul.form-control:invalid.focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.dashboardcode-bsmultiselect div.dropdown-menu{list-style-type:none}.dashboardcode-bsmultiselect div.dropdown-menu>ul{padding-left:0;padding-right:0;margin-bottom:0}.dashboardcode-bsmultiselect div.dropdown-menu>ul>li{display:block;width:100%;padding:0 .5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0;cursor:pointer}.dashboardcode-bsmultiselect div.dropdown-menu>ul>li .custom-control{justify-content:flex-start}.dashboardcode-bsmultiselect div.dropdown-menu>ul>li .custom-control .custom-control-input,.dashboardcode-bsmultiselect div.dropdown-menu>ul>li .custom-control .custom-control-label{cursor:inherit}.dashboardcode-bsmultiselect div.dropdown-menu>ul>li.disabled .custom-control-label{color:#6c757d}.dashboardcode-bsmultiselect div.dropdown-menu>ul>li.hover{color:var(--primary);background-color:#e9ecef}.dashboardcode-bsmultiselect div.dropdown-menu>ul>li.hover:not(.disabled){color:var(--primary)}.dashboardcode-bsmultiselect div.dropdown-menu>ul>li.hover.selected{color:var(--primary)}.dashboardcode-bsmultiselect div.dropdown-menu+div.alert-warning{padding-left:.25rem;padding-right:.25rem;z-index:4;font-size:small}.dashboardcode-bsmultiselect.input-group.input-group-sm ul.form-control{min-height:calc(1.5em + .5rem + 2px)}.dashboardcode-bsmultiselect.input-group.input-group-sm ul.form-control input{font-size:.875rem}.dashboardcode-bsmultiselect.input-group.input-group-lg ul.form-control{min-height:calc(1.5em + 1rem + 2px)}.dashboardcode-bsmultiselect.input-group.input-group-lg ul.form-control input{font-size:1.25rem}.was-validated .dashboardcode-bsmultiselect div.dropdown-menu>ul>li .custom-control-input:valid:checked~.custom-control-label{color:#212529}.was-validated .dashboardcode-bsmultiselect div.dropdown-menu>ul>li .custom-control-input:valid:not(:checked)~.custom-control-label{color:#212529}.was-validated .dashboardcode-bsmultiselect div.dropdown-menu>ul>li.hover .custom-control-input:valid:checked~.custom-control-label{color:var(--primary)}.was-validated .dashboardcode-bsmultiselect div.dropdown-menu>ul>li.hover .custom-control-input:valid:not(:checked)~.custom-control-label{color:var(--primary)}.was-validated .dashboardcode-bsmultiselect div.dropdown-menu>ul>li .custom-control-input:valid:checked~.custom-control-label::before{color:#fff;border-color:#007bff;background-color:#007bff}.was-validated .dashboardcode-bsmultiselect div.dropdown-menu>ul>li .custom-control-input:valid:not(:checked)~.custom-control-label::before{border:#adb5bd solid 1px} 2 | /*# sourceMappingURL=BsMultiSelect.bs4.min.css.map */ -------------------------------------------------------------------------------- /dist/css/BsMultiSelect.min.css: -------------------------------------------------------------------------------- 1 | .dashboardcode-bsmultiselect ul.form-control{display:flex;flex-wrap:wrap;height:auto;min-height:calc(1.5em + .75rem + 2px);margin-bottom:0;cursor:text;list-style-type:none}.dashboardcode-bsmultiselect ul.form-control input{height:auto;padding:0;margin:0;font-weight:inherit;color:inherit;background-color:transparent;border:0;outline:0;box-shadow:none}.dashboardcode-bsmultiselect ul.form-control.disabled{background-color:#e9ecef}.dashboardcode-bsmultiselect ul.form-control::-moz-placeholder{color:#6c757d;opacity:1}.dashboardcode-bsmultiselect ul.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.dashboardcode-bsmultiselect ul.form-control::placeholder{color:#6c757d;opacity:1}.dashboardcode-bsmultiselect ul.form-control>li.badge{padding-left:0;-webkit-padding-start:0;padding-inline-start:0;-webkit-padding-end:0.5rem;padding-inline-end:0.5rem;color:var(--bs-dark)}.dashboardcode-bsmultiselect ul.form-control>li.badge button.btn-close{float:none;font-size:.8em}.dashboardcode-bsmultiselect ul.form-control>li.badge span.disabled{opacity:.65}.dashboardcode-bsmultiselect ul.form-control.focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.dashboardcode-bsmultiselect ul.form-control.form-control-sm{min-height:calc(1.5em + .5rem + 2px)}.dashboardcode-bsmultiselect ul.form-control.form-control-sm input{font-size:.875rem}.dashboardcode-bsmultiselect ul.form-control.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.dashboardcode-bsmultiselect ul.form-control.form-control-lg input{font-size:1.25rem}.dashboardcode-bsmultiselect ul.form-control.is-valid,.was-validated .dashboardcode-bsmultiselect ul.form-control:valid{border-color:#198754}.dashboardcode-bsmultiselect ul.form-control.is-valid.focus,.was-validated .dashboardcode-bsmultiselect ul.form-control:valid.focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.dashboardcode-bsmultiselect ul.form-control.is-invalid,.was-validated .dashboardcode-bsmultiselect ul.form-control:invalid{border-color:#dc3545}.dashboardcode-bsmultiselect ul.form-control.is-invalid.focus,.was-validated .dashboardcode-bsmultiselect ul.form-control:invalid.focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.dashboardcode-bsmultiselect div.dropdown-menu>ul{list-style-type:none;padding-left:0;padding-right:0;margin-bottom:0}.dashboardcode-bsmultiselect div.dropdown-menu>ul>li{display:block;width:100%;padding:0 .5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0;cursor:pointer}.dashboardcode-bsmultiselect div.dropdown-menu>ul>li .form-check{cursor:inherit;justify-content:flex-start}.dashboardcode-bsmultiselect div.dropdown-menu>ul>li .form-check .form-check-input,.dashboardcode-bsmultiselect div.dropdown-menu>ul>li .form-check .form-check-label{cursor:inherit}.dashboardcode-bsmultiselect div.dropdown-menu>ul>li.disabled .form-check-label{opacity:.5}.dashboardcode-bsmultiselect div.dropdown-menu>ul>li.hover{background-color:#e9ecef}.dashboardcode-bsmultiselect div.dropdown-menu>ul>li.hover:not(.disabled){color:var(--bs-primary)}.dashboardcode-bsmultiselect div.dropdown-menu>ul>li.hover.selected{color:var(--bs-primary)}.dashboardcode-bsmultiselect div.dropdown-menu+div.alert-warning{padding-left:.25rem;padding-right:.25rem;z-index:4;font-size:small}.dashboardcode-bsmultiselect.input-group.input-group-sm ul.form-control{min-height:calc(1.5em + .5rem + 2px)}.dashboardcode-bsmultiselect.input-group.input-group-sm ul.form-control input{font-size:.875rem}.dashboardcode-bsmultiselect.input-group.input-group-lg ul.form-control{min-height:calc(1.5em + 1rem + 2px)}.dashboardcode-bsmultiselect.input-group.input-group-lg ul.form-control input{font-size:1.25rem}.form-floating .dashboardcode-bsmultiselect ul.form-control{min-height:calc(3.5rem + 2px)}.form-floating .dashboardcode-bsmultiselect ul.form-control.floating-lifted{padding-top:1.625rem;padding-left:.7rem;padding-bottom:0}.form-floating .dashboardcode-bsmultiselect+label.floating-lifted{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.was-validated .dashboardcode-bsmultiselect div.dropdown-menu>ul>li .form-check-input:valid:checked~.form-check-label,.was-validated .dashboardcode-bsmultiselect div.dropdown-menu>ul>li .form-check-input:valid:not(:checked)~.form-check-label{color:#212529}.was-validated .dashboardcode-bsmultiselect div.dropdown-menu>ul>li.hover .form-check-input:valid:checked~.form-check-label,.was-validated .dashboardcode-bsmultiselect div.dropdown-menu>ul>li.hover .form-check-input:valid:not(:checked)~.form-check-label{color:var(--bs-primary)}.was-validated .dashboardcode-bsmultiselect div.dropdown-menu>ul>li .form-check-input:valid:checked{border-color:var(--bs-primary);background-color:var(--bs-primary)}.was-validated .dashboardcode-bsmultiselect div.dropdown-menu>ul>li .form-check-input:valid:not(:checked){border:1px solid rgba(0,0,0,.25)} 2 | /*# sourceMappingURL=BsMultiSelect.min.css.map */ -------------------------------------------------------------------------------- /src/plugins/HiddenOptionPlugin.js: -------------------------------------------------------------------------------- 1 | export function HiddenOptionPlugin(){ 2 | return { 3 | plug 4 | } 5 | } 6 | 7 | export function plug(configuration){ 8 | return (aspects) => { 9 | return { 10 | layout: () => { 11 | let {createWrapAspect, isChoiceSelectableAspect, 12 | wrapsCollection, produceChoiceAspect, buildAndAttachChoiceAspect, 13 | countableChoicesListInsertAspect, countableChoicesList} = aspects; 14 | 15 | countableChoicesListInsertAspect.countableChoicesListInsert = (wrap, key) => { 16 | if ( !wrap.isOptionHidden ){ 17 | let choiceNext = wrapsCollection.getNext(key, c=>!c.isOptionHidden ); 18 | countableChoicesList.add(wrap, choiceNext) 19 | } 20 | } 21 | 22 | let origBuildAndAttachChoice = buildAndAttachChoiceAspect.buildAndAttachChoice; 23 | buildAndAttachChoiceAspect.buildAndAttachChoice=(wrap, getNextElement)=>{ 24 | if (wrap.isOptionHidden){ 25 | buildHiddenChoice(wrap); 26 | } 27 | else{ 28 | origBuildAndAttachChoice(wrap, getNextElement); 29 | } 30 | } 31 | 32 | var origIsSelectable = isChoiceSelectableAspect.isSelectable; 33 | isChoiceSelectableAspect.isSelectable = (wrap) => origIsSelectable(wrap) && !wrap.isOptionHidden; 34 | 35 | let {getIsOptionHidden, options} = configuration; 36 | if (options) { 37 | if (!getIsOptionHidden) 38 | getIsOptionHidden = (option) => (option.hidden===undefined)?false:option.hidden; 39 | } else { 40 | if (!getIsOptionHidden) 41 | getIsOptionHidden = (option) => { 42 | return option.hidden; 43 | } 44 | } 45 | 46 | var origCreateWrap = createWrapAspect.createWrap; 47 | createWrapAspect.createWrap = (option) => { 48 | let wrap = origCreateWrap(option); 49 | wrap.isOptionHidden = getIsOptionHidden(option); 50 | return wrap; 51 | }; 52 | 53 | return { 54 | buildApi(api){ 55 | let getNextNonHidden = (key) => wrapsCollection.getNext(key, c => !c.isOptionHidden ); 56 | 57 | api.updateOptionsHidden = () => 58 | wrapsCollection.forLoop( (wrap, key) => 59 | updateChoiceHidden(wrap, key, getNextNonHidden, countableChoicesList, getIsOptionHidden, produceChoiceAspect) 60 | ); 61 | 62 | api.updateOptionHidden = (key) => 63 | updateChoiceHidden(wrapsCollection.get(key), key, getNextNonHidden, countableChoicesList, getIsOptionHidden, produceChoiceAspect); 64 | // TODO create updateHidden ? 65 | // it is too complex since we need to find the next non hidden, when this depends on key 66 | // there should be the backreference "wrap -> index" invited before 67 | // api.updateOptionHidden = (key) => wrapsCollection.get(key).updateHidden(); 68 | } 69 | } 70 | } 71 | } 72 | } 73 | } 74 | 75 | function buildHiddenChoice(wrap){ 76 | wrap.updateSelected = () => void 0; 77 | 78 | wrap.choice.choicesDom = {}; 79 | wrap.choice.choiceDomManagerHandlers ={} 80 | wrap.choice.choiceDomManagerHandlers.setVisible = null; 81 | wrap.choice.setHoverIn = null; 82 | 83 | wrap.choice.dispose = () => { 84 | wrap.choice.dispose = null; 85 | }; 86 | 87 | wrap.dispose = () => { 88 | wrap.choice.dispose(); 89 | wrap.dispose = null; 90 | }; 91 | } 92 | 93 | function updateChoiceHidden(wrap, key, getNextNonHidden, countableChoicesList, getIsOptionHidden, produceChoiceAspect){ 94 | let newIsOptionHidden = getIsOptionHidden(wrap.option); 95 | if (newIsOptionHidden != wrap.isOptionHidden) 96 | { 97 | wrap.isOptionHidden= newIsOptionHidden; 98 | if (wrap.isOptionHidden) { 99 | 100 | countableChoicesList.remove(wrap); 101 | wrap.choice.choiceDomManagerHandlers.detach(); 102 | buildHiddenChoice(wrap); 103 | } else { 104 | let nextChoice = getNextNonHidden(key); 105 | countableChoicesList.add(wrap, nextChoice); 106 | produceChoiceAspect.produceChoice(wrap); 107 | wrap.choice.choiceDomManagerHandlers.attach(nextChoice?.choice.choiceDom.choiceElement); 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /src/ToolsStyling.js: -------------------------------------------------------------------------------- 1 | import {shallowClearClone, isString} from './ToolsJs'; 2 | 3 | export function addStyling(element, styling){ 4 | var backupStyling = {classes:[], styles:{}} 5 | if (styling) { 6 | var {classes, styles} = styling; 7 | classes.forEach(e => element.classList.add(e)) // todo use add(classes) 8 | backupStyling.classes = classes.slice(); 9 | for (let property in styles){ 10 | backupStyling.styles[property] = element.style[property]; 11 | element.style[property] = styles[property]; // todo use Object.assign (need polyfill for IE11) 12 | } 13 | } 14 | return backupStyling; 15 | } 16 | 17 | function removeStyling(element, styling){ 18 | if (styling) { 19 | var {classes, styles} = styling; 20 | classes.forEach(e=>element.classList.remove(e)) // todo use remove(classes) 21 | for (let property in styles) 22 | element.style[property] = styles[property]; // todo use Object.assign (need polyfill for IE11) 23 | } 24 | } 25 | 26 | export function toggleStyling(element, styling){ 27 | var backupStyling = {classes:[], styles:{}}; 28 | var isOn=false; 29 | var isF = styling instanceof Function; 30 | return (value, force)=>{ 31 | if (value) { 32 | if (isOn===false){ 33 | backupStyling = addStyling(element, isF?styling():styling) 34 | isOn=true; 35 | } else if (force){ 36 | removeStyling(element, backupStyling); 37 | backupStyling =addStyling(element, isF?styling():styling) 38 | } 39 | } else { 40 | if (isOn===true || force===true){ 41 | removeStyling(element, backupStyling); 42 | isOn=false; 43 | } 44 | } 45 | } 46 | } 47 | 48 | function extendClasses(out, param, actionStr, actionArr, isRemoveEmptyClasses){ 49 | if (isString(param)){ 50 | if (param === ""){ 51 | if (isRemoveEmptyClasses){ 52 | out.classes = []; 53 | } 54 | } else { 55 | let c = param.split(' '); 56 | out.classes = actionStr(c); 57 | } 58 | return true; 59 | } else if (param instanceof Array){ 60 | if (param.length==0){ 61 | if (isRemoveEmptyClasses){ 62 | out.classes = []; 63 | } 64 | } 65 | else{ 66 | out.classes = actionArr(param); 67 | } 68 | return true; 69 | } 70 | return false; 71 | } 72 | 73 | function extend(value, param, actionStr, actionArr, actionObj, isRemoveEmptyClasses){ 74 | var success = extendClasses(value, param, actionStr, actionArr, isRemoveEmptyClasses); 75 | if (success === false){ 76 | if (param instanceof Object){ 77 | var {classes, styles} = param; 78 | extendClasses(value, classes, actionStr, actionArr, isRemoveEmptyClasses); 79 | 80 | if (styles) { 81 | value.styles = actionObj(styles); 82 | } else if (!classes) { 83 | value.styles = actionObj(param) 84 | } 85 | } 86 | } 87 | } 88 | 89 | export function Styling(param){ 90 | var value = {classes:[], styles:{}}; 91 | if (param){ 92 | extend(value, param, a=>a, a=>a.slice(), o=>shallowClearClone(o), true); 93 | } 94 | return Object.freeze(value); 95 | } 96 | 97 | function createStyling(isReplace, param, ...params){ 98 | var value = {classes:[], styles:{}}; 99 | if (param){ 100 | extend(value, param, a=>a, a=>a.slice(), o=>shallowClearClone(o),true); 101 | if (params){ 102 | var {classes, styles} = value; 103 | var extendInt = isReplace? (p)=>extend(value, p, s=>s, a=>a.slice(), o=> shallowClearClone(styles, o),true): 104 | (p)=>extend(value, p, a=>classes.concat(a), a=>classes.concat(a), o=>shallowClearClone(styles, o),false) 105 | params.forEach(p=> extendInt(p)); 106 | } 107 | } 108 | return Styling(value); 109 | } 110 | 111 | export function createCss(stylings1, stylings2){ 112 | var destination = {}; 113 | for (let property in stylings1) { 114 | let param1 = stylings1[property]; 115 | 116 | let param2 = stylings2?stylings2[property]:undefined; 117 | if (param2===undefined) 118 | destination[property] = Styling(param1) 119 | else{ 120 | destination[property] = createStyling(true, param1, param2); 121 | } 122 | } 123 | if (stylings2) 124 | for (let property in stylings2) { 125 | if (!stylings1[property]) 126 | destination[property] = Styling(stylings2[property]) 127 | } 128 | return destination; 129 | } 130 | 131 | export function extendCss(stylings1, stylings2){ 132 | for (let property in stylings2) { 133 | let param2 = stylings2[property]; 134 | let param1 = stylings1[property]; 135 | if (param1 === undefined) 136 | stylings1[property] = Styling(param2) 137 | else{ 138 | stylings1[property] = createStyling(false, param1, param2); 139 | } 140 | } 141 | } -------------------------------------------------------------------------------- /npm-debug.log: -------------------------------------------------------------------------------- 1 | 0 info it worked if it ends with ok 2 | 1 verbose cli [ 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\Web\\External\\Node.exe', 3 | 1 verbose cli 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\Web\\External\\node_modules\\npm\\bin\\npm-cli.js', 4 | 1 verbose cli 'run', 5 | 1 verbose cli 'report' ] 6 | 2 info using npm@3.3.4 7 | 3 info using node@v5.4.1 8 | 4 verbose run-script [ 'prereport', 'report', 'postreport' ] 9 | 5 info lifecycle dashboardcode.bsmutltiselect@0.1.15~prereport: dashboardcode.bsmutltiselect@0.1.15 10 | 6 silly lifecycle dashboardcode.bsmutltiselect@0.1.15~prereport: no script for prereport, continuing 11 | 7 info lifecycle dashboardcode.bsmutltiselect@0.1.15~report: dashboardcode.bsmutltiselect@0.1.15 12 | 8 verbose lifecycle dashboardcode.bsmutltiselect@0.1.15~report: unsafe-perm in lifecycle true 13 | 9 verbose lifecycle dashboardcode.bsmutltiselect@0.1.15~report: PATH: C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Web\External\node_modules\npm\bin\node-gyp-bin;C:\cot\DashboardCode\BsMultiSelect\node_modules\.bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\Program Files\Microsoft SQL Server\130\Tools\Binn\;C:\Program Files\dotnet\;C:\Program Files (x86)\Skype\Phone\;C:\Program Files\Git\cmd;C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Web\External;C:\Program Files\nodejs\;C:\Users\pokrorom\.dnx\bin;C:\Program Files\Microsoft VS Code\bin;C:\Users\pokrorom\AppData\Roaming\npm 14 | 10 verbose lifecycle dashboardcode.bsmutltiselect@0.1.15~report: CWD: C:\cot\DashboardCode\BsMultiSelect 15 | 11 silly lifecycle dashboardcode.bsmutltiselect@0.1.15~report: Args: [ '/d /s /c', 16 | 11 silly lifecycle 'echo.cd & cd & echo. & where node & echo.node -v & node -v & echo. & where npm & echo.npm -version & npm -version & echo. & where eslint & echo.eslint -v & eslint -v & echo. & where rollup & echo.rollup -version & rollup -version & echo. & echo.babel -version & node ./node_modules/@babel/cli/bin/babel --version' ] 17 | 12 silly lifecycle dashboardcode.bsmutltiselect@0.1.15~report: Returned: code: 1 signal: null 18 | 13 info lifecycle dashboardcode.bsmutltiselect@0.1.15~report: Failed to exec report script 19 | 14 verbose stack Error: dashboardcode.bsmutltiselect@0.1.15 report: `echo.cd & cd & echo. & where node & echo.node -v & node -v & echo. & where npm & echo.npm -version & npm -version & echo. & where eslint & echo.eslint -v & eslint -v & echo. & where rollup & echo.rollup -version & rollup -version & echo. & echo.babel -version & node ./node_modules/@babel/cli/bin/babel --version` 20 | 14 verbose stack Exit status 1 21 | 14 verbose stack at EventEmitter. (C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Web\External\node_modules\npm\lib\utils\lifecycle.js:233:16) 22 | 14 verbose stack at emitTwo (events.js:87:13) 23 | 14 verbose stack at EventEmitter.emit (events.js:172:7) 24 | 14 verbose stack at ChildProcess. (C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Web\External\node_modules\npm\lib\utils\spawn.js:24:14) 25 | 14 verbose stack at emitTwo (events.js:87:13) 26 | 14 verbose stack at ChildProcess.emit (events.js:172:7) 27 | 14 verbose stack at maybeClose (internal/child_process.js:821:16) 28 | 14 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:211:5) 29 | 15 verbose pkgid dashboardcode.bsmutltiselect@0.1.15 30 | 16 verbose cwd C:\cot\DashboardCode\BsMultiSelect 31 | 17 error Windows_NT 6.1.7601 32 | 18 error argv "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\Web\\External\\Node.exe" "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\Web\\External\\node_modules\\npm\\bin\\npm-cli.js" "run" "report" 33 | 19 error node v5.4.1 34 | 20 error npm v3.3.4 35 | 21 error code ELIFECYCLE 36 | 22 error dashboardcode.bsmutltiselect@0.1.15 report: `echo.cd & cd & echo. & where node & echo.node -v & node -v & echo. & where npm & echo.npm -version & npm -version & echo. & where eslint & echo.eslint -v & eslint -v & echo. & where rollup & echo.rollup -version & rollup -version & echo. & echo.babel -version & node ./node_modules/@babel/cli/bin/babel --version` 37 | 22 error Exit status 1 38 | 23 error Failed at the dashboardcode.bsmutltiselect@0.1.15 report script 'echo.cd & cd & echo. & where node & echo.node -v & node -v & echo. & where npm & echo.npm -version & npm -version & echo. & where eslint & echo.eslint -v & eslint -v & echo. & where rollup & echo.rollup -version & rollup -version & echo. & echo.babel -version & node ./node_modules/@babel/cli/bin/babel --version'. 39 | 23 error This is most likely a problem with the dashboardcode.bsmutltiselect package, 40 | 23 error not with npm itself. 41 | 23 error Tell the author that this fails on your system: 42 | 23 error echo.cd & cd & echo. & where node & echo.node -v & node -v & echo. & where npm & echo.npm -version & npm -version & echo. & where eslint & echo.eslint -v & eslint -v & echo. & where rollup & echo.rollup -version & rollup -version & echo. & echo.babel -version & node ./node_modules/@babel/cli/bin/babel --version 43 | 23 error You can get their info via: 44 | 23 error npm owner ls dashboardcode.bsmutltiselect 45 | 23 error There is likely additional logging output above. 46 | 24 verbose exit [ 1, true ] 47 | -------------------------------------------------------------------------------- /src/PluginManager.js: -------------------------------------------------------------------------------- 1 | import {extendIfUndefined} from './ToolsJs'; 2 | 3 | function parseEventHandler(key, eventHandler, doms, plugStaticDoms, preLayouts, layouts, appends, buildApis, disposes){ 4 | if (eventHandler) { 5 | if (eventHandler.dom) 6 | doms.push({key, value:eventHandler.dom}); 7 | if (eventHandler.plugStaticDom) 8 | plugStaticDoms.push({key, value:eventHandler.plugStaticDom}); 9 | if (eventHandler.preLayout) 10 | preLayouts.push({key, value:eventHandler.preLayout}); 11 | if (eventHandler.layout) 12 | layouts.push({key, value:eventHandler.layout}); 13 | if (eventHandler.append) 14 | appends.push({key, value:eventHandler.append}); 15 | if (eventHandler.buildApi) 16 | buildApis.push({key, value:eventHandler.buildApi}); 17 | if (eventHandler.dispose) 18 | disposes.push({key, value:eventHandler.dispose}); 19 | } 20 | } 21 | 22 | export function ComposePluginManagerFactory(plugins, defaults, environment){ 23 | let plugedList = []; 24 | let mergeList = []; 25 | for(let i = 0; i { 36 | let buildAspectsList = []; 37 | for(let i = 0; i { 58 | extendIfUndefined(aspects, newAspects) 59 | 60 | var doms = []; 61 | var plugStaticDoms = []; 62 | var preLayouts = []; 63 | var layouts = []; 64 | var appends = []; 65 | var buildApis = []; 66 | let disposes = []; 67 | for(let k = 0; k { 12 | aspects.popperRtlAspect = popperRtlAspect; 13 | 14 | let {environment} = aspects; 15 | 16 | let {createPopper, Popper, globalPopper} = environment; 17 | let createModifiersVX = null; 18 | let createPopperVX = null; 19 | if (Popper) { // V2 20 | createPopperVX = createPopper = (function(createPopperConstructor) { 21 | return function(anchorElement, element, popperConfiguration) { 22 | return new createPopperConstructor(anchorElement, element, popperConfiguration); 23 | } 24 | })(Popper);; 25 | createModifiersVX = CreateModifiersV1; 26 | } else if (createPopper) { 27 | createPopperVX = createPopper; 28 | createModifiersVX = CreateModifiersV2; 29 | } else if (globalPopper) { 30 | if (globalPopper.createPopper) { 31 | createPopperVX = globalPopper.createPopper; 32 | createModifiersVX = CreateModifiersV2; 33 | } else { 34 | createPopperVX = createPopper = (function(createPopperConstructor) { 35 | return function(anchorElement, element, popperConfiguration) { 36 | return new createPopperConstructor(anchorElement, element, popperConfiguration); 37 | } 38 | })(globalPopper); 39 | createModifiersVX = CreateModifiersV1; 40 | } 41 | } else { 42 | throw new Error("BsMultiSelect: Popper component (https://popper.js.org) is required"); 43 | } 44 | var createPopperConfigurationAspect = CreatePopperConfigurationAspect(createModifiersVX); 45 | var createPopperAspect = CreatePopperAspect(createPopperVX, popperRtlAspect, createPopperConfigurationAspect); 46 | aspects.createPopperAspect = createPopperAspect; 47 | 48 | return { 49 | append(){ 50 | let {filterDom, choicesDom, disposeAspect, staticManager, choicesVisibilityAspect, specialPicksEventsAspect} = aspects; 51 | 52 | let filterInputElement = filterDom.filterInputElement; 53 | let choicesElement = choicesDom.choicesElement; 54 | 55 | let pop = createPopperAspect.createPopper(choicesElement, filterInputElement, true); 56 | 57 | staticManager.appendToContainer = composeSync(staticManager.appendToContainer, pop.init); 58 | 59 | var origBackSpace = specialPicksEventsAspect.backSpace; 60 | specialPicksEventsAspect.backSpace = (pick) => {origBackSpace(pick); pop.update();}; 61 | 62 | disposeAspect.dispose = composeSync(disposeAspect.dispose, pop.dispose); 63 | 64 | choicesVisibilityAspect.updatePopupLocation = composeSync( 65 | choicesVisibilityAspect.updatePopupLocation, 66 | function(){pop.update();} 67 | ); 68 | } 69 | } 70 | } 71 | } 72 | 73 | function PopperRtlAspect(){ 74 | return { 75 | getIsRtl(){ 76 | return false; 77 | } 78 | } 79 | } 80 | 81 | function CreateModifiersV1(preventOverflow){ 82 | return { 83 | preventOverflow: {enabled:preventOverflow}, 84 | hide: {enabled:false}, 85 | flip: {enabled:false} 86 | }; 87 | } 88 | 89 | function CreateModifiersV2(preventOverflow){ 90 | var modifiers = [{ 91 | name: 'flip', 92 | options: { 93 | fallbackPlacements: ['bottom'], 94 | }, 95 | } 96 | ]; 97 | if (preventOverflow) { 98 | modifiers.push({name: 'preventOverflow'}); 99 | } 100 | return modifiers; 101 | } 102 | 103 | function CreatePopperAspect(createPopperVX, popperRtlAspect, createPopperConfigurationAspect){ 104 | return { 105 | createPopper(element, anchorElement, preventOverflow){ 106 | let popper = null; 107 | return { 108 | init(){ 109 | var isRtl = popperRtlAspect.getIsRtl(); 110 | var popperConfiguration = createPopperConfigurationAspect.createConfiguration(preventOverflow, isRtl); 111 | popper = createPopperVX(anchorElement, element, popperConfiguration); 112 | }, 113 | update(){ 114 | popper.update(); // become async in popper 2; use forceUpdate if sync is needed? 115 | }, 116 | dispose(){ 117 | popper.destroy(); 118 | } 119 | } 120 | } 121 | } 122 | } 123 | 124 | function CreatePopperConfigurationAspect(createModifiersVX){ 125 | return { 126 | createConfiguration(preventOverflow, isRtl){ 127 | let modifiers = createModifiersVX(preventOverflow); 128 | 129 | let popperConfiguration = { 130 | placement: 'bottom-start', 131 | modifiers: modifiers 132 | }; 133 | 134 | if (isRtl){ 135 | popperConfiguration.placement = 'bottom-end'; 136 | } 137 | return popperConfiguration; 138 | } 139 | } 140 | } -------------------------------------------------------------------------------- /src/ToolsDom.js: -------------------------------------------------------------------------------- 1 | export function findDirectChildByTagName(element, tagName){ 2 | let value = null; 3 | for (let i = 0; i{ 19 | var event = new window.Event(eventName); 20 | e.dispatchEvent(event); 21 | } 22 | } 23 | else 24 | trigger = (e, eventName) => { // IE 11 polyfill 25 | let event = window.document.createEvent("CustomEvent"); 26 | event.initCustomEvent(eventName, false, false, undefined); 27 | e.dispatchEvent(event); 28 | } 29 | return trigger; 30 | } 31 | 32 | export function closestByTagName(element, tagName){ 33 | return closest(element, e => e.tagName === tagName) // TODO support xhtml? e.tagName.toUpperCase() ? 34 | } 35 | 36 | export function closestByClassName(element, className){ 37 | return closest(element, e => e.classList.contains(className)) 38 | } 39 | 40 | export function closestByAttribute(element, attributeName, attribute){ 41 | return closest(element, e => e.getAttribute(attributeName)===attribute ) 42 | } 43 | 44 | export function containsAndSelf(node, otherNode ){ 45 | return node === otherNode || node.contains(otherNode); 46 | } 47 | 48 | export function getDataGuardedWithPrefix(element, prefix, name){ 49 | var tmp1 = element.getAttribute('data-' + prefix + '-' + name); 50 | if (tmp1) { 51 | return tmp1; 52 | } else { 53 | var tmp2 = element.getAttribute('data-' + name); 54 | if (tmp2) 55 | return tmp2; 56 | } 57 | return null; 58 | } 59 | 60 | function closest(element, predicate){ 61 | if (!element || !(element instanceof Element)) return null; // should be element, not document (TODO: check iframe) 62 | 63 | if (predicate(element)) return element; 64 | return closest(element.parentNode, predicate); 65 | } 66 | 67 | export function siblingsAsArray(element){ 68 | var value = [] 69 | if (element.parentNode){ 70 | var children = element.parentNode.children; 71 | var l = element.parentNode.children.length; 72 | if (children.length>1){ 73 | for (var i=0; i < l; ++i){ 74 | var e = children[i]; 75 | if (e!=element) 76 | value.push(e); 77 | 78 | } 79 | } 80 | } 81 | return value; 82 | } 83 | 84 | export function getIsRtl(element){ 85 | var isRtl = false; 86 | var e = closestByAttribute(element,"dir","rtl"); 87 | if (e) 88 | isRtl = true; 89 | return isRtl; 90 | } 91 | 92 | export function EventBinder(){ 93 | var list = []; 94 | return { 95 | bind(element, eventName, handler) { 96 | element.addEventListener(eventName, handler) 97 | list.push( {element, eventName, handler} ) 98 | }, 99 | unbind() { 100 | list.forEach( e=> { 101 | let {element, eventName, handler}=e; 102 | element.removeEventListener(eventName, handler) 103 | }) 104 | } 105 | } 106 | } 107 | 108 | export function EventTumbler(element, eventName, handler){ 109 | return { 110 | on() { 111 | element.addEventListener(eventName, handler) 112 | }, 113 | off() { 114 | element.removeEventListener(eventName, handler) 115 | } 116 | } 117 | } 118 | 119 | export function AttributeBackup(){ 120 | var list = []; 121 | return { 122 | set(element, attributeName, attribute){ 123 | var currentAtribute = element.getAttribute(attributeName); 124 | list.push( {element, currentAtribute, attribute} ) 125 | element.setAttribute(attributeName, attribute) 126 | }, 127 | restore(){ 128 | list.forEach(e=>{ 129 | let {element, attributeName, attribute} = e; 130 | if (attributeName) 131 | element.setAttribute(attributeName, attribute) 132 | else 133 | element.removeAttribute(attributeName) 134 | }) 135 | } 136 | } 137 | } 138 | 139 | export function EventLoopFlag(window) { 140 | var flag = false; 141 | return { 142 | get(){ 143 | return flag; 144 | }, 145 | set(){ 146 | flag = true; 147 | pr = window.setTimeout( 148 | () => { 149 | flag = false; 150 | }) 151 | 152 | } 153 | } 154 | } 155 | 156 | export function EventLoopProlongableFlag(window) { 157 | var flag = false; 158 | var pr = null; 159 | return { 160 | get(){ 161 | return flag; 162 | }, 163 | set(timeout){ 164 | if (flag && pr){ 165 | window.clearTimeout(pr); 166 | } 167 | flag = true; 168 | pr = window.setTimeout( 169 | () => { 170 | flag = false; 171 | pr=null; 172 | }, timeout?timeout:0) 173 | 174 | } 175 | } 176 | } 177 | 178 | export function ResetableFlag() { 179 | var flag = false; 180 | return { 181 | get(){ 182 | return flag; 183 | }, 184 | set(){ 185 | flag = true; 186 | }, 187 | reset(){ 188 | flag = false; 189 | } 190 | } 191 | } -------------------------------------------------------------------------------- /src/plugins/PlaceholderPlugin.js: -------------------------------------------------------------------------------- 1 | import {composeSync} from '../ToolsJs'; 2 | import {getDataGuardedWithPrefix} from '../ToolsDom'; 3 | import {toggleStyling} from '../ToolsStyling'; 4 | import {ResetableFlag} from '../ToolsDom' 5 | 6 | export function PlaceholderPlugin(){ 7 | return { 8 | plug 9 | } 10 | } 11 | 12 | export function plug(configuration){ 13 | return (aspects) => { 14 | return { 15 | layout: () => { 16 | let {staticManager, picksList, picksDom, filterDom, updateDataAspect, 17 | resetFilterListAspect, filterManagerAspect, environment, staticDom} = aspects; 18 | let isIE11 = environment.isIE11; 19 | let {placeholder, css} = configuration; 20 | let {picksElement} = picksDom; 21 | let filterInputElement = filterDom.filterInputElement; 22 | 23 | function setPlaceholder(placeholder){ 24 | filterInputElement.placeholder = placeholder; 25 | } 26 | if (isIE11){ 27 | var ignoreNextInputResetableFlag = ResetableFlag(); 28 | let placeholderStopInputAspect = PlaceholderStopInputAspect(ignoreNextInputResetableFlag); 29 | var setPlaceholderOrig = setPlaceholder; 30 | setPlaceholder = function(placeholder){ 31 | ignoreNextInputResetableFlag.set(); 32 | setPlaceholderOrig(placeholder); 33 | } 34 | var origOnInput = filterDom.onInput; 35 | filterDom.onInput = (handler) => { 36 | origOnInput( 37 | ()=>{if (placeholderStopInputAspect.get()){ 38 | placeholderStopInputAspect.reset(); 39 | } else { 40 | handler(); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | if (!placeholder){ 47 | placeholder = getDataGuardedWithPrefix(staticDom.initialElement,"bsmultiselect","placeholder"); 48 | } 49 | 50 | function setEmptyInputWidth(isVisible){ 51 | if(isVisible) 52 | filterInputElement.style.width ='100%'; 53 | else 54 | filterInputElement.style.width ='2ch'; 55 | } 56 | var emptyToggleStyling = toggleStyling(filterInputElement, css.filterInput_empty); 57 | function showPlacehodler(isVisible){ 58 | if (isVisible) 59 | { 60 | setPlaceholder(placeholder?placeholder:''); 61 | picksElement.style.display = 'block'; 62 | } 63 | else 64 | { 65 | setPlaceholder(''); 66 | picksElement.style.display = 'flex'; 67 | } 68 | emptyToggleStyling(isVisible); 69 | setEmptyInputWidth(isVisible); 70 | } 71 | showPlacehodler(true); 72 | 73 | function setDisabled(isComponentDisabled){ 74 | filterInputElement.disabled = isComponentDisabled; 75 | }; 76 | let isEmpty = () => picksList.isEmpty() && filterDom.isEmpty() 77 | 78 | function updatePlacehodlerVisibility(){ 79 | showPlacehodler(isEmpty()); 80 | }; 81 | function updateEmptyInputWidth(){ 82 | setEmptyInputWidth(isEmpty()) 83 | }; 84 | 85 | let origDisable = picksDom.disable; 86 | picksDom.disable = (isComponentDisabled)=>{ 87 | setDisabled(isComponentDisabled); 88 | origDisable(isComponentDisabled); 89 | }; 90 | 91 | staticManager.appendToContainer = composeSync(staticManager.appendToContainer, updateEmptyInputWidth); 92 | 93 | filterManagerAspect.processEmptyInput = composeSync(updateEmptyInputWidth, filterManagerAspect.processEmptyInput); 94 | resetFilterListAspect.forceResetFilter = composeSync(resetFilterListAspect.forceResetFilter, updatePlacehodlerVisibility); 95 | 96 | let origAdd = picksList.add; 97 | picksList.add = (pick) => { 98 | let returnValue = origAdd(pick); 99 | if (picksList.getCount()==1 ){ // make flex 100 | if (filterDom.isEmpty()){ 101 | setPlaceholder(''); 102 | picksElement.style.display = 'flex'; 103 | emptyToggleStyling(false); 104 | filterInputElement.style.width ='2ch'; 105 | } else { 106 | picksElement.style.display = 'flex'; 107 | } 108 | } 109 | pick.dispose = composeSync(pick.dispose, function() 110 | { 111 | if (isEmpty()) { 112 | showPlacehodler(true); 113 | } 114 | }); 115 | return returnValue; 116 | }; 117 | 118 | updateDataAspect.updateData = composeSync(updateDataAspect.updateData, updatePlacehodlerVisibility); 119 | } 120 | } 121 | } 122 | } 123 | 124 | // ie11 support 125 | function PlaceholderStopInputAspect(resetableFlag){ 126 | return{ 127 | get(){ 128 | return resetableFlag.get(); 129 | }, 130 | reset(){ 131 | return resetableFlag.reset(); 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /src/plugins/DisabledOptionPlugin.js: -------------------------------------------------------------------------------- 1 | import {composeSync} from '../ToolsJs'; 2 | import {toggleStyling} from '../ToolsStyling'; 3 | 4 | export function DisabledOptionCssPatchPlugin(defaults){ 5 | defaults.cssPatch.pickContent_disabled = {opacity: '.65'}; 6 | } 7 | 8 | export function DisabledOptionPlugin(defaults){ 9 | defaults.css.pickContent_disabled = 'disabled'; 10 | return { 11 | plug 12 | } 13 | } 14 | 15 | export function plug(configuration){ 16 | return (aspects) => { 17 | return { 18 | layout: () => { 19 | let {isChoiceSelectableAspect, createWrapAspect, produceChoiceAspect, 20 | filterPredicateAspect, wrapsCollection, producePickAspect, pickDomFactory } = aspects; 21 | 22 | let {getIsOptionDisabled, options, css} = configuration; 23 | if (options) { 24 | if (!getIsOptionDisabled) 25 | getIsOptionDisabled = (option) => (option.disabled===undefined) ? false : option.disabled; 26 | } else { // selectElement 27 | if (!getIsOptionDisabled) 28 | getIsOptionDisabled = (option) => option.disabled; 29 | } 30 | 31 | // TODO check this instead of wrap.updateDisabled 32 | // function updateDisabled(wrap){ 33 | // wrap?.choice?.choiceDomManagerHandlers?.updateDisabled?.(); 34 | // wrap?.pick?.pickDomManagerHandlers?.updateDisabled?.(); 35 | // } 36 | 37 | let origCreateWrap = createWrapAspect.createWrap; 38 | createWrapAspect.createWrap = (option) => { 39 | let wrap = origCreateWrap(option); 40 | wrap.isOptionDisabled = getIsOptionDisabled(option); // TODO: remove usage wrap.isOptionDisabled 41 | wrap.updateDisabled = null; 42 | return wrap; 43 | }; 44 | 45 | 46 | 47 | let origIsSelectable = isChoiceSelectableAspect.isSelectable; 48 | isChoiceSelectableAspect.isSelectable = (wrap) => { 49 | return origIsSelectable(wrap) && !wrap.isOptionDisabled; 50 | }; 51 | 52 | let origFilterPredicate = filterPredicateAspect.filterPredicate; 53 | filterPredicateAspect.filterPredicate = (wrap, text) => { 54 | return !wrap.isOptionDisabled && origFilterPredicate(wrap, text) ; 55 | }; 56 | 57 | ExtendProduceChoiceAspectProduceChoice(produceChoiceAspect); 58 | 59 | ExtendProducePickAspectProducePick(producePickAspect); 60 | 61 | ExtendPickDomFactoryCreate(pickDomFactory, css); 62 | 63 | return { 64 | buildApi(api){ 65 | api.updateOptionsDisabled = () => wrapsCollection.forLoop( wrap => updateChoiceDisabled(wrap, getIsOptionDisabled)) 66 | api.updateOptionDisabled = (key) => updateChoiceDisabled(wrapsCollection.get(key), getIsOptionDisabled) 67 | } 68 | }; 69 | } 70 | } 71 | } 72 | } 73 | 74 | function ExtendProduceChoiceAspectProduceChoice(produceChoiceAspect){ 75 | let orig = produceChoiceAspect.produceChoice; 76 | produceChoiceAspect.produceChoice = (wrap) => { 77 | let val = orig(wrap); 78 | wrap.choice.choiceDomManagerHandlers.updateDisabled(); 79 | wrap.updateDisabled = wrap.choice.choiceDomManagerHandlers.updateDisabled 80 | wrap.choice.dispose = composeSync(()=>{wrap.updateDisabled=null;}, wrap.choice.dispose); 81 | 82 | let origToggle = wrap.choice.tryToggleChoice; 83 | wrap.choice.tryToggleChoice = () => { 84 | let success = false; 85 | if (wrap.isOptionSelected!==undefined){ 86 | if (wrap.isOptionSelected || !wrap.isOptionDisabled) // TODO: declare dependency on SelectedOptionPlugin 87 | success = origToggle(wrap); 88 | } 89 | else{ 90 | if (!wrap.isOptionDisabled) { 91 | success = origToggle(wrap); 92 | } 93 | } 94 | return success; 95 | }; 96 | 97 | return val; 98 | } 99 | } 100 | 101 | function ExtendProducePickAspectProducePick(producePickAspect){ 102 | let orig = producePickAspect.producePick; 103 | producePickAspect.producePick = (wrap) => { 104 | let val = orig(wrap); 105 | let pick = wrap.pick; 106 | let choiceUpdateDisabledBackup = wrap.updateDisabled; // backup disable only choice 107 | wrap.updateDisabled = composeSync(choiceUpdateDisabledBackup, () => pick.pickDomManagerHandlers.updateDisabled()); // add pickDisabled 108 | pick.dispose = composeSync(pick.dispose, 109 | ()=>{ 110 | wrap.updateDisabled = choiceUpdateDisabledBackup; // remove pickDisabled 111 | wrap.updateDisabled(); // make "true disabled" without it checkbox only looks disabled 112 | } 113 | ) 114 | return val; 115 | } 116 | } 117 | 118 | function ExtendPickDomFactoryCreate(pickDomFactory, css){ 119 | let orig = pickDomFactory.create; 120 | pickDomFactory.create = (pick) => { 121 | orig(pick); 122 | let {pickDom, pickDomManagerHandlers} = pick; 123 | let disableToggle = toggleStyling(pickDom.pickContentElement, css.pickContent_disabled); 124 | pickDomManagerHandlers.updateDisabled = ()=>{ 125 | disableToggle(pick.wrap.isOptionDisabled); 126 | } 127 | pickDomManagerHandlers.updateDisabled(); 128 | } 129 | } 130 | 131 | function updateChoiceDisabled(wrap, getIsOptionDisabled){ 132 | let newIsDisabled = getIsOptionDisabled(wrap.option); 133 | if (newIsDisabled != wrap.isOptionDisabled) 134 | { 135 | wrap.isOptionDisabled= newIsDisabled; 136 | wrap.updateDisabled?.(); // some hidden oesn't have element (and need to be updated) 137 | } 138 | } -------------------------------------------------------------------------------- /src/plugins/ValidationApiPlugin.js: -------------------------------------------------------------------------------- 1 | import {ObservableValue, ObservableLambda, defCall, isBoolean, composeSync} from '../ToolsJs'; 2 | import {getDataGuardedWithPrefix} from '../ToolsDom'; 3 | 4 | const defValueMissingMessage = 'Please select an item in the list' 5 | 6 | export function ValidationApiPlugin(defaults){ 7 | preset(defaults); 8 | return { 9 | plug 10 | } 11 | } 12 | 13 | export function preset(defaults){defaults.getValueRequired=() => false; defaults.valueMissingMessage='';} 14 | 15 | export function plug(configuration){ 16 | let {required, getValueRequired, getIsValueMissing, valueMissingMessage} = configuration; 17 | var getValueRequiredAspect = GetValueRequiredAspect(required, getValueRequired); 18 | return (aspects) => { 19 | aspects.getValueRequiredAspect = getValueRequiredAspect; 20 | return { 21 | plugStaticDom: ()=>{ 22 | var {dataWrap, staticDom} = aspects; 23 | 24 | var valueMissingMessageEx = defCall(valueMissingMessage, 25 | ()=> getDataGuardedWithPrefix(staticDom.initialElement,"bsmultiselect","value-missing-message"), 26 | defValueMissingMessage) 27 | 28 | if (!getIsValueMissing) { 29 | getIsValueMissing = () => { 30 | let count = 0; 31 | let optionsArray = dataWrap.getOptions(); 32 | for (var i=0; i < optionsArray.length; i++) { 33 | if (optionsArray[i].selected) 34 | count++; 35 | } 36 | return count===0; 37 | } 38 | } 39 | 40 | return { 41 | preLayout() { 42 | // getValueRequiredAspect redefined on appendToContainer, so this can't be called on prelayout and layout 43 | var isValueMissingObservable = ObservableLambda(()=>getValueRequiredAspect.getValueRequired() && getIsValueMissing()); 44 | var validationApiObservable = ObservableValue(!isValueMissingObservable.getValue()); 45 | aspects.validationApiAspect = ValidationApiAspect(validationApiObservable); // used in BsAppearancePlugin layout, possible races 46 | 47 | return { 48 | layout: () => { 49 | var {onChangeAspect, updateDataAspect} = aspects; 50 | // TODO: required could be a function 51 | //let {valueMissingMessage} = configuration; 52 | 53 | onChangeAspect.onChange = composeSync(isValueMissingObservable.call, onChangeAspect.onChange); 54 | updateDataAspect.updateData = composeSync(isValueMissingObservable.call, updateDataAspect.updateData); 55 | 56 | return { 57 | buildApi(api){ 58 | var {staticDom, filterDom} = aspects; 59 | api.validationApi = ValidityApi( 60 | filterDom.filterInputElement, // !! 61 | isValueMissingObservable, 62 | valueMissingMessageEx, 63 | (isValid)=>validationApiObservable.setValue(isValid), 64 | staticDom.trigger 65 | ); 66 | } 67 | } 68 | }, 69 | dispose(){ 70 | isValueMissingObservable.detachAll(); 71 | validationApiObservable.detachAll(); 72 | } 73 | } 74 | } 75 | } 76 | } 77 | } 78 | } 79 | } 80 | 81 | function GetValueRequiredAspect(required, getValueRequiredCfg){ 82 | return { 83 | getValueRequired(){ 84 | let value = false; 85 | if (!isBoolean(required)) 86 | if (getValueRequiredCfg) 87 | value = getValueRequiredCfg(); 88 | return value; 89 | } 90 | } 91 | } 92 | 93 | function ValidationApiAspect(validationApiObservable){ 94 | return { 95 | getValue(){ 96 | return validationApiObservable.getValue(); 97 | }, 98 | attach(f){ 99 | validationApiObservable.attach(f); 100 | } 101 | } 102 | } 103 | 104 | function ValidityApi(visibleElement, isValueMissingObservable, valueMissingMessage, onValid, trigger){ 105 | var customValidationMessage = ""; 106 | var validationMessage = ""; 107 | var validity = null; 108 | var willValidate = true; 109 | 110 | function setMessage(valueMissing, customError){ 111 | validity = Object.freeze({ 112 | valueMissing, 113 | customError, 114 | valid: !(valueMissing || customError), 115 | }); 116 | validationMessage = customError?customValidationMessage:(valueMissing?valueMissingMessage:"") 117 | visibleElement.setCustomValidity(validationMessage); 118 | onValid(validity.valid); 119 | } 120 | 121 | setMessage(isValueMissingObservable.getValue(), false); 122 | 123 | isValueMissingObservable.attach( 124 | (value) => { 125 | setMessage(value, validity.customError); 126 | } 127 | ); 128 | var checkValidity = () => { 129 | if (!validity.valid) 130 | trigger('dashboardcode.multiselect:invalid') 131 | return validity.valid; 132 | } 133 | return { 134 | validationMessage, 135 | willValidate, 136 | validity, 137 | setCustomValidity(message){ 138 | customValidationMessage = message; 139 | setMessage(validity.valueMissing, customValidationMessage?true:false); 140 | }, 141 | checkValidity, 142 | reportValidity(){ 143 | visibleElement.reportValidity(); 144 | return checkValidity(); 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /dist/css/BsMultiSelect.bs4.css: -------------------------------------------------------------------------------- 1 | .dashboardcode-bsmultiselect ul.form-control { 2 | display: flex; 3 | flex-wrap: wrap; 4 | height: auto; 5 | min-height: calc(1.5em + 0.75rem + 2px); 6 | margin-bottom: 0; 7 | cursor: text; 8 | list-style-type: none; 9 | } 10 | .dashboardcode-bsmultiselect ul.form-control input { 11 | height: auto; 12 | padding: 0; 13 | margin: 0; 14 | font-weight: inherit; 15 | color: inherit; 16 | background-color: transparent; 17 | border: 0; 18 | outline: none; 19 | box-shadow: none; 20 | } 21 | .dashboardcode-bsmultiselect ul.form-control.disabled { 22 | background-color: #e9ecef; 23 | } 24 | .dashboardcode-bsmultiselect ul.form-control::-moz-placeholder { 25 | color: #6c757d; 26 | opacity: 1; 27 | } 28 | .dashboardcode-bsmultiselect ul.form-control:-ms-input-placeholder { 29 | color: #6c757d; 30 | opacity: 1; 31 | } 32 | .dashboardcode-bsmultiselect ul.form-control::placeholder { 33 | color: #6c757d; 34 | opacity: 1; 35 | } 36 | .dashboardcode-bsmultiselect ul.form-control > li.badge { 37 | padding-left: 0; 38 | -webkit-padding-start: 0; 39 | padding-inline-start: 0; 40 | -webkit-padding-end: 0.5rem; 41 | padding-inline-end: 0.5rem; 42 | padding-top: 0.35em; 43 | padding-bottom: 0.35em; 44 | line-height: 1.5em; 45 | } 46 | .dashboardcode-bsmultiselect ul.form-control > li.badge button.close { 47 | float: none; 48 | font-size: 1.8em; 49 | line-height: 0.5em; 50 | font-weight: 400; 51 | } 52 | .dashboardcode-bsmultiselect ul.form-control > li.badge span.disabled { 53 | opacity: 0.65; 54 | } 55 | .dashboardcode-bsmultiselect ul.form-control.focus { 56 | color: #495057; 57 | background-color: #fff; 58 | border-color: #80bdff; 59 | outline: 0; 60 | box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); 61 | } 62 | .dashboardcode-bsmultiselect ul.form-control.form-control-sm { 63 | min-height: calc(1.5em + 0.5rem + 2px); 64 | } 65 | .dashboardcode-bsmultiselect ul.form-control.form-control-sm input { 66 | font-size: 0.875rem; 67 | } 68 | .dashboardcode-bsmultiselect ul.form-control.form-control-lg { 69 | min-height: calc(1.5em + 1rem + 2px); 70 | } 71 | .dashboardcode-bsmultiselect ul.form-control.form-control-lg input { 72 | font-size: 1.25rem; 73 | } 74 | .was-validated .dashboardcode-bsmultiselect ul.form-control:valid, .dashboardcode-bsmultiselect ul.form-control.is-valid { 75 | border-color: #28a745; 76 | } 77 | .was-validated .dashboardcode-bsmultiselect ul.form-control:valid.focus, .dashboardcode-bsmultiselect ul.form-control.is-valid.focus { 78 | border-color: #28a745; 79 | box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); 80 | } 81 | .was-validated .dashboardcode-bsmultiselect ul.form-control:invalid, .dashboardcode-bsmultiselect ul.form-control.is-invalid { 82 | border-color: #dc3545; 83 | } 84 | .was-validated .dashboardcode-bsmultiselect ul.form-control:invalid.focus, .dashboardcode-bsmultiselect ul.form-control.is-invalid.focus { 85 | border-color: #dc3545; 86 | box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); 87 | } 88 | .dashboardcode-bsmultiselect div.dropdown-menu { 89 | list-style-type: none; 90 | } 91 | .dashboardcode-bsmultiselect div.dropdown-menu > ul { 92 | padding-left: 0; 93 | padding-right: 0; 94 | margin-bottom: 0; 95 | } 96 | .dashboardcode-bsmultiselect div.dropdown-menu > ul > li { 97 | display: block; 98 | width: 100%; 99 | padding: 0 0.5rem; 100 | clear: both; 101 | font-weight: 400; 102 | color: #212529; 103 | text-align: inherit; 104 | white-space: nowrap; 105 | background-color: transparent; 106 | border: 0; 107 | cursor: pointer; 108 | } 109 | .dashboardcode-bsmultiselect div.dropdown-menu > ul > li .custom-control { 110 | justify-content: flex-start; 111 | } 112 | .dashboardcode-bsmultiselect div.dropdown-menu > ul > li .custom-control .custom-control-input, .dashboardcode-bsmultiselect div.dropdown-menu > ul > li .custom-control .custom-control-label { 113 | cursor: inherit; 114 | } 115 | .dashboardcode-bsmultiselect div.dropdown-menu > ul > li.disabled .custom-control-label { 116 | color: #6c757d; 117 | } 118 | .dashboardcode-bsmultiselect div.dropdown-menu > ul > li.hover { 119 | color: var(--primary); 120 | background-color: #e9ecef; 121 | } 122 | .dashboardcode-bsmultiselect div.dropdown-menu > ul > li.hover:not(.disabled) { 123 | color: var(--primary); 124 | } 125 | .dashboardcode-bsmultiselect div.dropdown-menu > ul > li.hover.selected { 126 | color: var(--primary); 127 | } 128 | .dashboardcode-bsmultiselect div.dropdown-menu + div.alert-warning { 129 | padding-left: 0.25rem; 130 | padding-right: 0.25rem; 131 | z-index: 4; 132 | font-size: small; 133 | } 134 | .dashboardcode-bsmultiselect.input-group.input-group-sm ul.form-control { 135 | min-height: calc(1.5em + 0.5rem + 2px); 136 | } 137 | .dashboardcode-bsmultiselect.input-group.input-group-sm ul.form-control input { 138 | font-size: 0.875rem; 139 | } 140 | .dashboardcode-bsmultiselect.input-group.input-group-lg ul.form-control { 141 | min-height: calc(1.5em + 1rem + 2px); 142 | } 143 | .dashboardcode-bsmultiselect.input-group.input-group-lg ul.form-control input { 144 | font-size: 1.25rem; 145 | } 146 | 147 | .was-validated .dashboardcode-bsmultiselect div.dropdown-menu > ul > li .custom-control-input:valid:checked ~ .custom-control-label { 148 | color: #212529; 149 | } 150 | 151 | .was-validated .dashboardcode-bsmultiselect div.dropdown-menu > ul > li .custom-control-input:valid:not(:checked) ~ .custom-control-label { 152 | color: #212529; 153 | } 154 | 155 | .was-validated .dashboardcode-bsmultiselect div.dropdown-menu > ul > li.hover .custom-control-input:valid:checked ~ .custom-control-label { 156 | color: var(--primary); 157 | } 158 | 159 | .was-validated .dashboardcode-bsmultiselect div.dropdown-menu > ul > li.hover .custom-control-input:valid:not(:checked) ~ .custom-control-label { 160 | color: var(--primary); 161 | } 162 | 163 | .was-validated .dashboardcode-bsmultiselect div.dropdown-menu > ul > li .custom-control-input:valid:checked ~ .custom-control-label::before { 164 | color: #fff; 165 | border-color: #007bff; 166 | background-color: #007bff; 167 | } 168 | 169 | .was-validated .dashboardcode-bsmultiselect div.dropdown-menu > ul > li .custom-control-input:valid:not(:checked) ~ .custom-control-label::before { 170 | border: #adb5bd solid 1px; 171 | } 172 | 173 | /*# sourceMappingURL=BsMultiSelect.bs4.css.map */ -------------------------------------------------------------------------------- /dist/css/BsMultiSelect.css: -------------------------------------------------------------------------------- 1 | .dashboardcode-bsmultiselect ul.form-control { 2 | display: flex; 3 | flex-wrap: wrap; 4 | height: auto; 5 | min-height: calc(1.5em + 0.75rem + 2px); 6 | margin-bottom: 0; 7 | cursor: text; 8 | list-style-type: none; 9 | } 10 | .dashboardcode-bsmultiselect ul.form-control input { 11 | height: auto; 12 | padding: 0; 13 | margin: 0; 14 | font-weight: inherit; 15 | color: inherit; 16 | background-color: transparent; 17 | border: 0; 18 | outline: none; 19 | box-shadow: none; 20 | } 21 | .dashboardcode-bsmultiselect ul.form-control.disabled { 22 | background-color: #e9ecef; 23 | } 24 | .dashboardcode-bsmultiselect ul.form-control::-moz-placeholder { 25 | color: #6c757d; 26 | opacity: 1; 27 | } 28 | .dashboardcode-bsmultiselect ul.form-control:-ms-input-placeholder { 29 | color: #6c757d; 30 | opacity: 1; 31 | } 32 | .dashboardcode-bsmultiselect ul.form-control::placeholder { 33 | color: #6c757d; 34 | opacity: 1; 35 | } 36 | .dashboardcode-bsmultiselect ul.form-control > li.badge { 37 | padding-left: 0; 38 | -webkit-padding-start: 0; 39 | padding-inline-start: 0; 40 | -webkit-padding-end: 0.5rem; 41 | padding-inline-end: 0.5rem; 42 | color: var(--bs-dark); 43 | } 44 | .dashboardcode-bsmultiselect ul.form-control > li.badge button.btn-close { 45 | float: none; 46 | font-size: 0.8em; 47 | } 48 | .dashboardcode-bsmultiselect ul.form-control > li.badge span.disabled { 49 | opacity: 0.65; 50 | } 51 | .dashboardcode-bsmultiselect ul.form-control.focus { 52 | color: #212529; 53 | background-color: #fff; 54 | border-color: #86b7fe; 55 | outline: 0; 56 | box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); 57 | } 58 | .dashboardcode-bsmultiselect ul.form-control.form-control-sm { 59 | min-height: calc(1.5em + 0.5rem + 2px); 60 | } 61 | .dashboardcode-bsmultiselect ul.form-control.form-control-sm input { 62 | font-size: 0.875rem; 63 | } 64 | .dashboardcode-bsmultiselect ul.form-control.form-control-lg { 65 | min-height: calc(1.5em + 1rem + 2px); 66 | } 67 | .dashboardcode-bsmultiselect ul.form-control.form-control-lg input { 68 | font-size: 1.25rem; 69 | } 70 | .was-validated .dashboardcode-bsmultiselect ul.form-control:valid, .dashboardcode-bsmultiselect ul.form-control.is-valid { 71 | border-color: #198754; 72 | } 73 | .was-validated .dashboardcode-bsmultiselect ul.form-control:valid.focus, .dashboardcode-bsmultiselect ul.form-control.is-valid.focus { 74 | border-color: #198754; 75 | box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, 0.25); 76 | } 77 | .was-validated .dashboardcode-bsmultiselect ul.form-control:invalid, .dashboardcode-bsmultiselect ul.form-control.is-invalid { 78 | border-color: #dc3545; 79 | } 80 | .was-validated .dashboardcode-bsmultiselect ul.form-control:invalid.focus, .dashboardcode-bsmultiselect ul.form-control.is-invalid.focus { 81 | border-color: #dc3545; 82 | box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25); 83 | } 84 | .dashboardcode-bsmultiselect div.dropdown-menu > ul { 85 | list-style-type: none; 86 | padding-left: 0; 87 | padding-right: 0; 88 | margin-bottom: 0; 89 | } 90 | .dashboardcode-bsmultiselect div.dropdown-menu > ul > li { 91 | display: block; 92 | width: 100%; 93 | padding: 0 0.5rem; 94 | clear: both; 95 | font-weight: 400; 96 | color: #212529; 97 | text-align: inherit; 98 | white-space: nowrap; 99 | background-color: transparent; 100 | border: 0; 101 | cursor: pointer; 102 | } 103 | .dashboardcode-bsmultiselect div.dropdown-menu > ul > li .form-check { 104 | cursor: inherit; 105 | justify-content: flex-start; 106 | } 107 | .dashboardcode-bsmultiselect div.dropdown-menu > ul > li .form-check .form-check-label, .dashboardcode-bsmultiselect div.dropdown-menu > ul > li .form-check .form-check-input { 108 | cursor: inherit; 109 | } 110 | .dashboardcode-bsmultiselect div.dropdown-menu > ul > li.disabled .form-check-label { 111 | opacity: 0.5; 112 | } 113 | .dashboardcode-bsmultiselect div.dropdown-menu > ul > li.hover { 114 | background-color: #e9ecef; 115 | } 116 | .dashboardcode-bsmultiselect div.dropdown-menu > ul > li.hover:not(.disabled) { 117 | color: var(--bs-primary); 118 | } 119 | .dashboardcode-bsmultiselect div.dropdown-menu > ul > li.hover.selected { 120 | color: var(--bs-primary); 121 | } 122 | .dashboardcode-bsmultiselect div.dropdown-menu + div.alert-warning { 123 | padding-left: 0.25rem; 124 | padding-right: 0.25rem; 125 | z-index: 4; 126 | font-size: small; 127 | } 128 | .dashboardcode-bsmultiselect.input-group.input-group-sm ul.form-control { 129 | min-height: calc(1.5em + 0.5rem + 2px); 130 | } 131 | .dashboardcode-bsmultiselect.input-group.input-group-sm ul.form-control input { 132 | font-size: 0.875rem; 133 | } 134 | .dashboardcode-bsmultiselect.input-group.input-group-lg ul.form-control { 135 | min-height: calc(1.5em + 1rem + 2px); 136 | } 137 | .dashboardcode-bsmultiselect.input-group.input-group-lg ul.form-control input { 138 | font-size: 1.25rem; 139 | } 140 | 141 | .form-floating .dashboardcode-bsmultiselect ul.form-control { 142 | min-height: calc(3.5rem + 2px); 143 | } 144 | .form-floating .dashboardcode-bsmultiselect ul.form-control.floating-lifted { 145 | padding-top: 1.625rem; 146 | padding-left: 0.7rem; 147 | padding-bottom: 0; 148 | } 149 | .form-floating .dashboardcode-bsmultiselect + label.floating-lifted { 150 | opacity: 0.65; 151 | transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); 152 | } 153 | 154 | .was-validated .dashboardcode-bsmultiselect div.dropdown-menu > ul > li .form-check-input:valid:checked ~ .form-check-label, 155 | .was-validated .dashboardcode-bsmultiselect div.dropdown-menu > ul > li .form-check-input:valid:not(:checked) ~ .form-check-label { 156 | color: #212529; 157 | } 158 | 159 | .was-validated .dashboardcode-bsmultiselect div.dropdown-menu > ul > li.hover .form-check-input:valid:checked ~ .form-check-label, 160 | .was-validated .dashboardcode-bsmultiselect div.dropdown-menu > ul > li.hover .form-check-input:valid:not(:checked) ~ .form-check-label { 161 | color: var(--bs-primary); 162 | } 163 | 164 | .was-validated .dashboardcode-bsmultiselect div.dropdown-menu > ul > li .form-check-input:valid:checked { 165 | border-color: var(--bs-primary); 166 | background-color: var(--bs-primary); 167 | } 168 | 169 | .was-validated .dashboardcode-bsmultiselect div.dropdown-menu > ul > li .form-check-input:valid:not(:checked) { 170 | border: 1px solid rgba(0, 0, 0, 0.25); 171 | } 172 | 173 | /*# sourceMappingURL=BsMultiSelect.css.map */ -------------------------------------------------------------------------------- /dist/css/BsMultiSelect.bs4.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../scss/BsMultiSelect.bs4.scss","BsMultiSelect.bs4.css","../../node_modules/bootstrap-4/scss/_variables.scss","../../node_modules/bootstrap-4/scss/mixins/_gradients.scss"],"names":[],"mappings":"AA2CI;EACI,aAAA;EACA,eAAA;EACA,YAAA;EACA,uCAAA;EACA,gBAAA;EACA,YAAA;EACA,qBAAA;AC1CR;AD4CQ;EACI,YAAA;EACA,UAAA;EACA,SAAA;EACA,oBAAA;EACA,cAAA;EACA,6BAAA;EACA,SAAA;EACA,aAAA;EACA,gBAAA;AC1CZ;AD4CQ;EACI,yBEvDD;ADaX;AD4CQ;EACI,cEtDD;EFwDC,UAAA;AC3CZ;ADwCQ;EACI,cEtDD;EFwDC,UAAA;AC3CZ;ADwCQ;EACI,cEtDD;EFwDC,UAAA;AC3CZ;AD+CQ;EACI,eAAA;EACA,wBAAA;EAAA,uBAAA;EACA,2BAAA;EAAA,0BAAA;EACA,mBAAA;EACA,sBAAA;EACA,kBAAA;AC7CZ;AD8CY;EACI,WAAA;EACA,gBAAA;EACA,kBAAA;EACA,gBAAA;AC5ChB;ADgDY;EACI,aE2Vc;ADzY9B;AD9BI;EACI,cAAA;EACA,sBERG;EFSH,qBEqdgC;EFpdhC,UAAA;EAII,gDEqXkB;ADxV9B;AD6CQ;EACQ,sCAAA;AC3ChB;AD4CgB;EACI,mBEyLU;ADnO9B;AD8CQ;EACQ,oCAAA;AC5ChB;AD6CgB;EACI,kBEiLU;AD5N9B;ADhCQ;EAEI,qBEgnBwB;AD/kBpC;ADhCY;EACI,qBE8mBoB;EF7mBpB,gDAAA;ACkChB;ADvCQ;EAEI,qBEinBwB;ADzkBpC;ADvCY;EACI,qBE+mBoB;EF9mBpB,gDAAA;ACyChB;ADqCI;EACI,qBAAA;ACnCR;ADoCQ;EACI,eAAA;EACA,gBAAA;EACA,gBAAA;AClCZ;ADoCY;EAEI,cAAA;EACA,WAAA;EACA,iBAAA;EACA,WAAA;EACA,gBEiKc;EFhKd,cE/GL;EFgHK,mBAAA;EAEA,mBAAA;EACA,6BAAA;EACA,SAAA;EACA,eAAA;ACpChB;ADqCgB;EACI,2BAAA;ACnCpB;ADoCoB;EACI,eAAA;AClCxB;ADsCgB;EACI,cEjIT;AD6FX;ADsCgB;EACI,qBAAA;EG3IhB,yBDGO;ADqGX;ADsCgB;EACI,qBAAA;ACpCpB;ADsCgB;EACI,qBAAA;ACpCpB;AD0CQ;EACI,qBAAA;EACA,sBAAA;EACA,UAAA;EACA,gBAAA;ACxCZ;AD6CQ;EACI,sCAAA;AC3CZ;AD4CY;EACI,mBEiHc;AD3J9B;ADgDQ;EACI,oCAAA;AC9CZ;AD+CY;EACI,kBEuGc;ADpJ9B;;ADwDA;EACI,cE/KO;AD0HX;;ADuDA;EACI,cElLO;AD8HX;;ADwDA;EACI,qBAAA;ACrDJ;;ADuDA;EACI,qBAAA;ACpDJ;;ADwDA;EACI,WExMO;EFyMP,qBE0C0B;ECpP1B,yBDoP0B;AD9F9B;;ADwDA;EACI,yBAAA;ACrDJ","file":"BsMultiSelect.bs4.css","sourcesContent":[null,".dashboardcode-bsmultiselect ul.form-control {\n display: flex;\n flex-wrap: wrap;\n height: auto;\n min-height: calc(1.5em + 0.75rem + 2px);\n margin-bottom: 0;\n cursor: text;\n list-style-type: none;\n}\n.dashboardcode-bsmultiselect ul.form-control input {\n height: auto;\n padding: 0;\n margin: 0;\n font-weight: inherit;\n color: inherit;\n background-color: transparent;\n border: 0;\n outline: none;\n box-shadow: none;\n}\n.dashboardcode-bsmultiselect ul.form-control.disabled {\n background-color: #e9ecef;\n}\n.dashboardcode-bsmultiselect ul.form-control::placeholder {\n color: #6c757d;\n opacity: 1;\n}\n.dashboardcode-bsmultiselect ul.form-control > li.badge {\n padding-left: 0;\n padding-inline-start: 0;\n padding-inline-end: 0.5rem;\n padding-top: 0.35em;\n padding-bottom: 0.35em;\n line-height: 1.5em;\n}\n.dashboardcode-bsmultiselect ul.form-control > li.badge button.close {\n float: none;\n font-size: 1.8em;\n line-height: 0.5em;\n font-weight: 400;\n}\n.dashboardcode-bsmultiselect ul.form-control > li.badge span.disabled {\n opacity: 0.65;\n}\n.dashboardcode-bsmultiselect ul.form-control.focus {\n color: #495057;\n background-color: #fff;\n border-color: #80bdff;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n.dashboardcode-bsmultiselect ul.form-control.form-control-sm {\n min-height: calc(1.5em + 0.5rem + 2px);\n}\n.dashboardcode-bsmultiselect ul.form-control.form-control-sm input {\n font-size: 0.875rem;\n}\n.dashboardcode-bsmultiselect ul.form-control.form-control-lg {\n min-height: calc(1.5em + 1rem + 2px);\n}\n.dashboardcode-bsmultiselect ul.form-control.form-control-lg input {\n font-size: 1.25rem;\n}\n.was-validated .dashboardcode-bsmultiselect ul.form-control:valid, .dashboardcode-bsmultiselect ul.form-control.is-valid {\n border-color: #28a745;\n}\n.was-validated .dashboardcode-bsmultiselect ul.form-control:valid.focus, .dashboardcode-bsmultiselect ul.form-control.is-valid.focus {\n border-color: #28a745;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n.was-validated .dashboardcode-bsmultiselect ul.form-control:invalid, .dashboardcode-bsmultiselect ul.form-control.is-invalid {\n border-color: #dc3545;\n}\n.was-validated .dashboardcode-bsmultiselect ul.form-control:invalid.focus, .dashboardcode-bsmultiselect ul.form-control.is-invalid.focus {\n border-color: #dc3545;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n.dashboardcode-bsmultiselect div.dropdown-menu {\n list-style-type: none;\n}\n.dashboardcode-bsmultiselect div.dropdown-menu > ul {\n padding-left: 0;\n padding-right: 0;\n margin-bottom: 0;\n}\n.dashboardcode-bsmultiselect div.dropdown-menu > ul > li {\n display: block;\n width: 100%;\n padding: 0 0.5rem;\n clear: both;\n font-weight: 400;\n color: #212529;\n text-align: inherit;\n white-space: nowrap;\n background-color: transparent;\n border: 0;\n cursor: pointer;\n}\n.dashboardcode-bsmultiselect div.dropdown-menu > ul > li .custom-control {\n justify-content: flex-start;\n}\n.dashboardcode-bsmultiselect div.dropdown-menu > ul > li .custom-control .custom-control-input, .dashboardcode-bsmultiselect div.dropdown-menu > ul > li .custom-control .custom-control-label {\n cursor: inherit;\n}\n.dashboardcode-bsmultiselect div.dropdown-menu > ul > li.disabled .custom-control-label {\n color: #6c757d;\n}\n.dashboardcode-bsmultiselect div.dropdown-menu > ul > li.hover {\n color: var(--primary);\n background-color: #e9ecef;\n}\n.dashboardcode-bsmultiselect div.dropdown-menu > ul > li.hover:not(.disabled) {\n color: var(--primary);\n}\n.dashboardcode-bsmultiselect div.dropdown-menu > ul > li.hover.selected {\n color: var(--primary);\n}\n.dashboardcode-bsmultiselect div.dropdown-menu + div.alert-warning {\n padding-left: 0.25rem;\n padding-right: 0.25rem;\n z-index: 4;\n font-size: small;\n}\n.dashboardcode-bsmultiselect.input-group.input-group-sm ul.form-control {\n min-height: calc(1.5em + 0.5rem + 2px);\n}\n.dashboardcode-bsmultiselect.input-group.input-group-sm ul.form-control input {\n font-size: 0.875rem;\n}\n.dashboardcode-bsmultiselect.input-group.input-group-lg ul.form-control {\n min-height: calc(1.5em + 1rem + 2px);\n}\n.dashboardcode-bsmultiselect.input-group.input-group-lg ul.form-control input {\n font-size: 1.25rem;\n}\n\n.was-validated .dashboardcode-bsmultiselect div.dropdown-menu > ul > li .custom-control-input:valid:checked ~ .custom-control-label {\n color: #212529;\n}\n\n.was-validated .dashboardcode-bsmultiselect div.dropdown-menu > ul > li .custom-control-input:valid:not(:checked) ~ .custom-control-label {\n color: #212529;\n}\n\n.was-validated .dashboardcode-bsmultiselect div.dropdown-menu > ul > li.hover .custom-control-input:valid:checked ~ .custom-control-label {\n color: var(--primary);\n}\n\n.was-validated .dashboardcode-bsmultiselect div.dropdown-menu > ul > li.hover .custom-control-input:valid:not(:checked) ~ .custom-control-label {\n color: var(--primary);\n}\n\n.was-validated .dashboardcode-bsmultiselect div.dropdown-menu > ul > li .custom-control-input:valid:checked ~ .custom-control-label::before {\n color: #fff;\n border-color: #007bff;\n background-color: #007bff;\n}\n\n.was-validated .dashboardcode-bsmultiselect div.dropdown-menu > ul > li .custom-control-input:valid:not(:checked) ~ .custom-control-label::before {\n border: #adb5bd solid 1px;\n}\n\n/*# sourceMappingURL=BsMultiSelect.bs4.css.map */\n",null,null]} -------------------------------------------------------------------------------- /dist/css/BsMultiSelect.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../scss/BsMultiSelect.scss","BsMultiSelect.css","../../node_modules/bootstrap/scss/_variables.scss","../../node_modules/bootstrap/scss/mixins/_gradients.scss"],"names":[],"mappings":"AA2CI;EACI,aAAA;EACA,eAAA;EACA,YAAA;EACA,uCAAA;EACA,gBAAA;EACA,YAAA;EACA,qBAAA;AC1CR;AD4CQ;EACI,YAAA;EACA,UAAA;EACA,SAAA;EACA,oBAAA;EACA,cAAA;EACA,6BAAA;EACA,SAAA;EACA,aAAA;EACA,gBAAA;AC1CZ;AD4CQ;EACI,yBEtDD;ADYX;AD4CQ;EACI,cErDD;EFuDC,UAAA;AC3CZ;ADwCQ;EACI,cErDD;EFuDC,UAAA;AC3CZ;ADwCQ;EACI,cErDD;EFuDC,UAAA;AC3CZ;ADgDQ;EACI,eAAA;EACA,wBAAA;EAAA,uBAAA;EACA,2BAAA;EAAA,0BAAA;EACA,qBAAA;AC9CZ;AD+CY;EACI,WAAA;EACA,gBAAA;AC7ChB;AD+CY;EACI,aEqsBc;ADlvB9B;AD1BI;EACI,cEGG;EFFH,sBEPG;EFQH,qBEk1BgC;EFj1BhC,UAAA;EAII,kDE4tBoB;ADnsBhC;AD4CQ;EACI,sCAAA;AC1CZ;AD2CY;EACI,mBE6ec;ADthB9B;AD6CQ;EACI,oCAAA;AC3CZ;AD4CY;EACI,kBEuec;ADjhB9B;AD5BQ;EAEI,qBEaF;ADgBV;AD5BY;EACI,qBEWN;EFVM,iDAAA;AC8BhB;ADnCQ;EAEI,qBEUF;AD0BV;ADnCY;EACI,qBEQN;EFPM,iDAAA;ACqChB;ADqCQ;EACI,qBAAA;EACA,eAAA;EACA,gBAAA;EACA,gBAAA;ACnCZ;ADqCY;EAEI,cAAA;EACA,WAAA;EACA,iBAAA;EACA,WAAA;EACA,gBEsdc;EFrdd,cEzGL;EF0GK,mBAAA;EAEA,mBAAA;EACA,6BAAA;EACA,SAAA;EACA,eAAA;ACrChB;ADsCgB;EACI,eAAA;EACA,2BAAA;ACpCpB;ADqCoB;EACI,eAAA;ACnCxB;ADsCgB;EACI,YE6wBuB;ADjzB3C;ADsCgB;EGvId,yBDMS;AD8FX;ADsCgB;EACI,wBAAA;ACpCpB;ADsCgB;EACI,wBAAA;ACpCpB;ADyCQ;EACI,qBAAA;EACA,sBAAA;EACA,UAAA;EACA,gBAAA;ACvCZ;AD4CQ;EACI,sCAAA;AC1CZ;AD2CY;EACI,mBEuac;ADhd9B;AD+CQ;EACI,oCAAA;AC7CZ;AD8CY;EACI,kBE+Zc;AD3c9B;;ADoDQ;EACI,8BAAA;ACjDZ;ADmDY;EACI,qBAAA;EACA,oBAAA;EACA,iBAAA;ACjDhB;ADqDQ;EACI,aAAA;EACA,8DAAA;ACnDZ;;AD6DA;;EAEI,cE3LO;ADiIX;;AD8DA;;EAEI,wBAAA;AC3DJ;;AD+DA;EACI,+BAAA;EACA,mCAAA;AC5DJ;;AD8DA;EACI,qCE0qBsC;ADruB1C","file":"BsMultiSelect.css","sourcesContent":[null,".dashboardcode-bsmultiselect ul.form-control {\n display: flex;\n flex-wrap: wrap;\n height: auto;\n min-height: calc(1.5em + 0.75rem + 2px);\n margin-bottom: 0;\n cursor: text;\n list-style-type: none;\n}\n.dashboardcode-bsmultiselect ul.form-control input {\n height: auto;\n padding: 0;\n margin: 0;\n font-weight: inherit;\n color: inherit;\n background-color: transparent;\n border: 0;\n outline: none;\n box-shadow: none;\n}\n.dashboardcode-bsmultiselect ul.form-control.disabled {\n background-color: #e9ecef;\n}\n.dashboardcode-bsmultiselect ul.form-control::placeholder {\n color: #6c757d;\n opacity: 1;\n}\n.dashboardcode-bsmultiselect ul.form-control > li.badge {\n padding-left: 0;\n padding-inline-start: 0;\n padding-inline-end: 0.5rem;\n color: var(--bs-dark);\n}\n.dashboardcode-bsmultiselect ul.form-control > li.badge button.btn-close {\n float: none;\n font-size: 0.8em;\n}\n.dashboardcode-bsmultiselect ul.form-control > li.badge span.disabled {\n opacity: 0.65;\n}\n.dashboardcode-bsmultiselect ul.form-control.focus {\n color: #212529;\n background-color: #fff;\n border-color: #86b7fe;\n outline: 0;\n box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n}\n.dashboardcode-bsmultiselect ul.form-control.form-control-sm {\n min-height: calc(1.5em + 0.5rem + 2px);\n}\n.dashboardcode-bsmultiselect ul.form-control.form-control-sm input {\n font-size: 0.875rem;\n}\n.dashboardcode-bsmultiselect ul.form-control.form-control-lg {\n min-height: calc(1.5em + 1rem + 2px);\n}\n.dashboardcode-bsmultiselect ul.form-control.form-control-lg input {\n font-size: 1.25rem;\n}\n.was-validated .dashboardcode-bsmultiselect ul.form-control:valid, .dashboardcode-bsmultiselect ul.form-control.is-valid {\n border-color: #198754;\n}\n.was-validated .dashboardcode-bsmultiselect ul.form-control:valid.focus, .dashboardcode-bsmultiselect ul.form-control.is-valid.focus {\n border-color: #198754;\n box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, 0.25);\n}\n.was-validated .dashboardcode-bsmultiselect ul.form-control:invalid, .dashboardcode-bsmultiselect ul.form-control.is-invalid {\n border-color: #dc3545;\n}\n.was-validated .dashboardcode-bsmultiselect ul.form-control:invalid.focus, .dashboardcode-bsmultiselect ul.form-control.is-invalid.focus {\n border-color: #dc3545;\n box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);\n}\n.dashboardcode-bsmultiselect div.dropdown-menu > ul {\n list-style-type: none;\n padding-left: 0;\n padding-right: 0;\n margin-bottom: 0;\n}\n.dashboardcode-bsmultiselect div.dropdown-menu > ul > li {\n display: block;\n width: 100%;\n padding: 0 0.5rem;\n clear: both;\n font-weight: 400;\n color: #212529;\n text-align: inherit;\n white-space: nowrap;\n background-color: transparent;\n border: 0;\n cursor: pointer;\n}\n.dashboardcode-bsmultiselect div.dropdown-menu > ul > li .form-check {\n cursor: inherit;\n justify-content: flex-start;\n}\n.dashboardcode-bsmultiselect div.dropdown-menu > ul > li .form-check .form-check-label, .dashboardcode-bsmultiselect div.dropdown-menu > ul > li .form-check .form-check-input {\n cursor: inherit;\n}\n.dashboardcode-bsmultiselect div.dropdown-menu > ul > li.disabled .form-check-label {\n opacity: 0.5;\n}\n.dashboardcode-bsmultiselect div.dropdown-menu > ul > li.hover {\n background-color: #e9ecef;\n}\n.dashboardcode-bsmultiselect div.dropdown-menu > ul > li.hover:not(.disabled) {\n color: var(--bs-primary);\n}\n.dashboardcode-bsmultiselect div.dropdown-menu > ul > li.hover.selected {\n color: var(--bs-primary);\n}\n.dashboardcode-bsmultiselect div.dropdown-menu + div.alert-warning {\n padding-left: 0.25rem;\n padding-right: 0.25rem;\n z-index: 4;\n font-size: small;\n}\n.dashboardcode-bsmultiselect.input-group.input-group-sm ul.form-control {\n min-height: calc(1.5em + 0.5rem + 2px);\n}\n.dashboardcode-bsmultiselect.input-group.input-group-sm ul.form-control input {\n font-size: 0.875rem;\n}\n.dashboardcode-bsmultiselect.input-group.input-group-lg ul.form-control {\n min-height: calc(1.5em + 1rem + 2px);\n}\n.dashboardcode-bsmultiselect.input-group.input-group-lg ul.form-control input {\n font-size: 1.25rem;\n}\n\n.form-floating .dashboardcode-bsmultiselect ul.form-control {\n min-height: calc(3.5rem + 2px);\n}\n.form-floating .dashboardcode-bsmultiselect ul.form-control.floating-lifted {\n padding-top: 1.625rem;\n padding-left: 0.7rem;\n padding-bottom: 0;\n}\n.form-floating .dashboardcode-bsmultiselect + label.floating-lifted {\n opacity: 0.65;\n transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem);\n}\n\n.was-validated .dashboardcode-bsmultiselect div.dropdown-menu > ul > li .form-check-input:valid:checked ~ .form-check-label,\n.was-validated .dashboardcode-bsmultiselect div.dropdown-menu > ul > li .form-check-input:valid:not(:checked) ~ .form-check-label {\n color: #212529;\n}\n\n.was-validated .dashboardcode-bsmultiselect div.dropdown-menu > ul > li.hover .form-check-input:valid:checked ~ .form-check-label,\n.was-validated .dashboardcode-bsmultiselect div.dropdown-menu > ul > li.hover .form-check-input:valid:not(:checked) ~ .form-check-label {\n color: var(--bs-primary);\n}\n\n.was-validated .dashboardcode-bsmultiselect div.dropdown-menu > ul > li .form-check-input:valid:checked {\n border-color: var(--bs-primary);\n background-color: var(--bs-primary);\n}\n\n.was-validated .dashboardcode-bsmultiselect div.dropdown-menu > ul > li .form-check-input:valid:not(:checked) {\n border: 1px solid rgba(0, 0, 0, 0.25);\n}\n\n/*# sourceMappingURL=BsMultiSelect.css.map */\n",null,null]} --------------------------------------------------------------------------------