├── .babelrc ├── .gitignore ├── assets ├── images │ ├── favicon.ico │ ├── favicon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── mstile-150x150.png │ ├── apple-touch-icon.png │ ├── android-chrome-192x192.png │ ├── manifest.json │ ├── browserconfig.xml │ ├── safari-pinned-tab.svg │ └── logo.svg ├── scripts │ ├── dist │ │ ├── choices.min.js.map │ │ └── choices.min.js │ └── src │ │ ├── reducers │ │ ├── groups.js │ │ ├── index.js │ │ ├── items.js │ │ └── choices.js │ │ ├── actions │ │ └── index.js │ │ ├── lib │ │ ├── polyfills.js │ │ └── utils.js │ │ └── store │ │ └── index.js ├── icons │ ├── cross.svg │ └── cross-inverse.svg └── styles │ ├── css │ ├── base.min.css │ ├── base.css │ ├── choices.min.css │ └── choices.css │ └── scss │ ├── base.scss │ └── choices.scss ├── .editorconfig ├── .travis.yml ├── server.js ├── .eslintrc ├── bower.json ├── LICENSE ├── webpack.config.dev.js ├── tests ├── karma.config.js └── spec │ └── choices_spec.js ├── webpack.config.prod.js ├── package.json ├── index.html └── README.md /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .DS_Store 4 | 5 | # Test 6 | tests/reports 7 | tests/results -------------------------------------------------------------------------------- /assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstacruz/Choices/master/assets/images/favicon.ico -------------------------------------------------------------------------------- /assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstacruz/Choices/master/assets/images/favicon.png -------------------------------------------------------------------------------- /assets/images/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstacruz/Choices/master/assets/images/favicon-16x16.png -------------------------------------------------------------------------------- /assets/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstacruz/Choices/master/assets/images/favicon-32x32.png -------------------------------------------------------------------------------- /assets/images/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstacruz/Choices/master/assets/images/mstile-150x150.png -------------------------------------------------------------------------------- /assets/images/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstacruz/Choices/master/assets/images/apple-touch-icon.png -------------------------------------------------------------------------------- /assets/scripts/dist/choices.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"choices.min.js","sources":[],"mappings":";;","sourceRoot":""} -------------------------------------------------------------------------------- /assets/images/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rstacruz/Choices/master/assets/images/android-chrome-192x192.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | before_install: 5 | - '[ "${TRAVIS_NODE_VERSION}" != "0.8" ] || npm install -g npm@1.4.28' 6 | - npm install -g npm@latest 7 | install: 8 | - npm install 9 | script: npm run js:test -------------------------------------------------------------------------------- /assets/icons/cross.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/cross-inverse.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Choices.js", 3 | "icons": [ 4 | { 5 | "src": "\/assets\/images\/android-chrome-192x192.png", 6 | "sizes": "192x192", 7 | "type": "image\/png" 8 | } 9 | ], 10 | "theme_color": "#ffffff", 11 | "display": "standalone" 12 | } 13 | -------------------------------------------------------------------------------- /assets/images/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #ffffff 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /assets/scripts/src/reducers/groups.js: -------------------------------------------------------------------------------- 1 | const groups = (state = [], action) => { 2 | switch (action.type) { 3 | case 'ADD_GROUP': { 4 | return [...state, { 5 | id: action.id, 6 | value: action.value, 7 | active: action.active, 8 | disabled: action.disabled, 9 | }]; 10 | } 11 | 12 | case 'CLEAR_CHOICES': { 13 | return state.groups = []; 14 | } 15 | 16 | default: { 17 | return state; 18 | } 19 | } 20 | }; 21 | 22 | export default groups; 23 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var WebpackDevServer = require('webpack-dev-server'); 3 | var config = require('./webpack.config.dev'); 4 | var opn = require('opn'); 5 | 6 | new WebpackDevServer(webpack(config), { 7 | publicPath: config.output.publicPath, 8 | historyApiFallback: true, 9 | quiet: true, // lets WebpackDashboard do its thing 10 | }).listen(3000, 'localhost', function(err, result) { 11 | if (err) console.log(err); 12 | opn('http://localhost:3000'); 13 | console.log('Listening at localhost:3000'); 14 | }); -------------------------------------------------------------------------------- /assets/scripts/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import items from './items'; 3 | import groups from './groups'; 4 | import choices from './choices'; 5 | 6 | const appReducer = combineReducers({ 7 | items, 8 | groups, 9 | choices, 10 | }); 11 | 12 | const rootReducer = (passedState, action) => { 13 | let state = passedState; 14 | // If we are clearing all items, groups and options we reassign 15 | // state and then pass that state to our proper reducer. This isn't 16 | // mutating our actual state 17 | // See: http://stackoverflow.com/a/35641992 18 | if (action.type === 'CLEAR_ALL') { 19 | state = undefined; 20 | } 21 | 22 | return appReducer(state, action); 23 | }; 24 | 25 | export default rootReducer; 26 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "ecmaFeatures": { 3 | "modules": true 4 | }, 5 | "env": { 6 | "browser": true, 7 | "node": true 8 | }, 9 | "parser": "babel-eslint", 10 | "rules": { 11 | "quotes": [2, "single"], 12 | "strict": [2, "never"], 13 | "indent": ["error", 2, {"SwitchCase": 1}], 14 | "eol-last": "off", 15 | "arrow-body-style": "off", 16 | "no-underscore-dangle": "off", 17 | "no-new": 0, 18 | "max-len": "off", 19 | "no-console": "off", 20 | "consistent-return": "off", 21 | "no-param-reassign": ["error", { "props": false }], 22 | "no-unused-vars": ["error", { "args": "none" }], 23 | "no-lonely-if": "off", 24 | "class-methods-use-this": "error", 25 | "react/require-extension": "off", 26 | }, 27 | } 28 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "choices.js", 3 | "version": "2.5.1", 4 | "description": "A vanilla JS customisable text input/select box plugin", 5 | "main": [ 6 | "./assets/scripts/dist/choices.js", 7 | "./assets/styles/css/choices.css" 8 | ], 9 | "authors": [ 10 | "Josh Johnson" 11 | ], 12 | "license": "MIT", 13 | "homepage": "https://joshuajohnson.co.uk/Choices/", 14 | "ignore": [ 15 | "**/.*", 16 | "node_modules", 17 | "bower_components", 18 | "test", 19 | "tests", 20 | "assets/images", 21 | "webpack.config.dev.js", 22 | "webpack.config.prod.js", 23 | "server.js" 24 | ], 25 | "keywords": [ 26 | "customisable", 27 | "input", 28 | "select", 29 | "vanilla", 30 | "plugin", 31 | "js" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Josh Johnson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var Dashboard = require('webpack-dashboard'); 4 | var DashboardPlugin = require('webpack-dashboard/plugin'); 5 | var dashboard = new Dashboard(); 6 | 7 | module.exports = { 8 | devtool: 'eval', 9 | entry: [ 10 | 'webpack-dev-server/client?http://localhost:3000', 11 | './assets/scripts/src/choices' 12 | ], 13 | output: { 14 | path: path.join(__dirname, 'dist'), 15 | filename: 'choices.min.js', 16 | publicPath: '/assets/scripts/dist/', 17 | library: 'Choices', 18 | libraryTarget: 'umd', 19 | }, 20 | eslint: { 21 | configFile: '.eslintrc' 22 | }, 23 | plugins: [ 24 | new DashboardPlugin(dashboard.setData), 25 | new webpack.HotModuleReplacementPlugin(), 26 | new webpack.DefinePlugin({ 27 | 'process.env': { 28 | 'NODE_ENV': JSON.stringify('development') 29 | } 30 | }) 31 | ], 32 | module: { 33 | loaders: [{ 34 | test: /\.js$/, 35 | exclude: /(node_modules|bower_components)/, 36 | loaders: ['babel', 'eslint-loader'], 37 | include: path.join(__dirname, 'assets/scripts/src') 38 | }] 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /tests/karma.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('karma-webpack'); 2 | 3 | module.exports = function(config) { 4 | config.set({ 5 | frameworks: ['jasmine'], 6 | files: [ 7 | '../tests/**/*_spec.js', 8 | ], 9 | plugins: [ 10 | webpack, 11 | 'karma-jasmine', 12 | 'karma-phantomjs-launcher', 13 | 'karma-coverage', 14 | 'karma-spec-reporter', 15 | 'karma-htmlfile-reporter', 16 | 'es6-shim' 17 | ], 18 | browsers: ['PhantomJS'], 19 | preprocessors: { 20 | '**/*_spec.js': ['webpack'], 21 | 'src/**/*.js': ['webpack'] 22 | }, 23 | reporters: ['spec', 'coverage', 'html'], 24 | coverageReporter: { 25 | dir: '../tests/reports/coverage', 26 | reporters: [{ 27 | type: 'html', 28 | }] 29 | }, 30 | webpack: { 31 | module: { 32 | loaders: [{ 33 | test: /\.(js|jsx)$/, 34 | exclude: /(bower_components|node_modules)/, 35 | loader: 'babel-loader' 36 | }], 37 | } 38 | }, 39 | colors: true, 40 | webpackMiddleware: { 41 | noInfo: true 42 | }, 43 | htmlReporter: { 44 | outputFile: 'results/unit-tests.html' 45 | } 46 | }); 47 | }; -------------------------------------------------------------------------------- /assets/scripts/src/reducers/items.js: -------------------------------------------------------------------------------- 1 | const items = (state = [], action) => { 2 | switch (action.type) { 3 | case 'ADD_ITEM': { 4 | // Add object to items array 5 | const newState = [...state, { 6 | id: action.id, 7 | choiceId: action.choiceId, 8 | groupId: action.groupId, 9 | value: action.value, 10 | label: action.label, 11 | active: true, 12 | highlighted: false, 13 | }]; 14 | 15 | return newState.map((item) => { 16 | if (item.highlighted) { 17 | item.highlighted = false; 18 | } 19 | return item; 20 | }); 21 | } 22 | 23 | case 'REMOVE_ITEM': { 24 | // Set item to inactive 25 | return state.map((item) => { 26 | if (item.id === action.id) { 27 | item.active = false; 28 | } 29 | return item; 30 | }); 31 | } 32 | 33 | case 'HIGHLIGHT_ITEM': { 34 | return state.map((item) => { 35 | if (item.id === action.id) { 36 | item.highlighted = action.highlighted; 37 | } 38 | return item; 39 | }); 40 | } 41 | 42 | default: { 43 | return state; 44 | } 45 | } 46 | }; 47 | 48 | export default items; 49 | -------------------------------------------------------------------------------- /assets/styles/css/base.min.css: -------------------------------------------------------------------------------- 1 | *{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}*,:after,:before{box-sizing:border-box}body,html{position:relative;margin:0;width:100%;height:100%}body{font-family:"Helvetica Neue",Helvetica,Arial,"Lucida Grande",sans-serif;font-size:16px;line-height:1.4;color:#fff;background-color:#333;overflow-x:hidden}hr,label{display:block}label{margin-bottom:8px;font-size:14px;font-weight:500;cursor:pointer}p{margin-top:0}hr{margin:36px 0;border:0;border-bottom:1px solid #eaeaea;height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:12px;font-weight:400;line-height:1.2}a,a:focus,a:visited{color:#fff;text-decoration:none;font-weight:600}.form-control{display:block;width:100%;background-color:#f9f9f9;padding:12px;border:1px solid #ddd;border-radius:2.5px;font-size:14px;-webkit-appearance:none;-moz-appearance:none;appearance:none;margin-bottom:24px}.h1,h1{font-size:32px}.h2,h2{font-size:24px}.h3,h3{font-size:20px}.h4,h4{font-size:18px}.h5,h5{font-size:16px}.h6,h6{font-size:14px}.container{display:block;margin:auto;max-width:40em;padding:48px}@media (max-width:620px){.container{padding:0}}.section{background-color:#fff;padding:24px;color:#333}.section a,.section a:focus,.section a:visited{color:#00bcd4}.logo{display:block;margin-bottom:12px}.logo__img{width:100%;height:auto;display:inline-block;max-width:100%;vertical-align:top;padding:6px 0}.visible-ie{display:none} -------------------------------------------------------------------------------- /assets/scripts/src/actions/index.js: -------------------------------------------------------------------------------- 1 | export const addItem = (value, label, id, choiceId, groupId) => { 2 | return { 3 | type: 'ADD_ITEM', 4 | value, 5 | label, 6 | id, 7 | choiceId, 8 | groupId, 9 | }; 10 | }; 11 | 12 | export const removeItem = (id, choiceId) => { 13 | return { 14 | type: 'REMOVE_ITEM', 15 | id, 16 | choiceId, 17 | }; 18 | }; 19 | 20 | export const highlightItem = (id, highlighted) => { 21 | return { 22 | type: 'HIGHLIGHT_ITEM', 23 | id, 24 | highlighted, 25 | }; 26 | }; 27 | 28 | export const addChoice = (value, label, id, groupId, disabled) => { 29 | return { 30 | type: 'ADD_CHOICE', 31 | value, 32 | label, 33 | id, 34 | groupId, 35 | disabled, 36 | }; 37 | }; 38 | 39 | export const filterChoices = (results) => { 40 | return { 41 | type: 'FILTER_CHOICES', 42 | results, 43 | }; 44 | }; 45 | 46 | export const activateChoices = (active = true) => { 47 | return { 48 | type: 'ACTIVATE_CHOICES', 49 | active, 50 | }; 51 | }; 52 | 53 | export const clearChoices = () => { 54 | return { 55 | type: 'CLEAR_CHOICES', 56 | }; 57 | }; 58 | 59 | export const addGroup = (value, id, active, disabled) => { 60 | return { 61 | type: 'ADD_GROUP', 62 | value, 63 | id, 64 | active, 65 | disabled, 66 | }; 67 | }; 68 | 69 | export const clearAll = () => { 70 | return { 71 | type: 'CLEAR_ALL', 72 | }; 73 | }; 74 | -------------------------------------------------------------------------------- /webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var package = require('./package.json'); 3 | var webpack = require('webpack'); 4 | var wrapperPlugin = require('wrapper-webpack-plugin'); 5 | var banner = `/*! ${ package.name } v${ package.version } | (c) ${ new Date().getFullYear() } ${ package.author } | ${ package.homepage } */ \n`; 6 | var minimize = process.argv.indexOf('--minimize') !== -1; 7 | 8 | var config = { 9 | devtool: 'cheap-module-source-map', 10 | entry: [ 11 | './assets/scripts/src/choices' 12 | ], 13 | output: { 14 | path: path.join(__dirname, '/assets/scripts/dist'), 15 | filename: minimize ? 'choices.min.js' : 'choices.js', 16 | publicPath: '/assets/scripts/dist/', 17 | library: 'Choices', 18 | libraryTarget: 'umd', 19 | }, 20 | libraryTarget: 'umd', 21 | plugins: [ 22 | new webpack.DefinePlugin({ 23 | 'process.env': { 24 | // This has effect on the react lib size 25 | 'NODE_ENV': JSON.stringify('production'), 26 | } 27 | }), 28 | new wrapperPlugin({ 29 | header: banner, 30 | }), 31 | ], 32 | module: { 33 | loaders: [{ 34 | test: /\.js$/, 35 | exclude: /(node_modules|bower_components)/, 36 | loaders: ['babel'], 37 | include: path.join(__dirname, 'assets/scripts/src') 38 | }] 39 | } 40 | }; 41 | 42 | if (minimize) { 43 | config.plugins.unshift(new webpack.optimize.UglifyJsPlugin({ 44 | sourceMap: false, 45 | mangle: true, 46 | output: { 47 | comments: false 48 | }, 49 | compress: { 50 | warnings: false, 51 | screw_ie8: true 52 | } 53 | })); 54 | } 55 | 56 | module.exports = config; 57 | -------------------------------------------------------------------------------- /assets/images/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /assets/styles/css/base.css: -------------------------------------------------------------------------------- 1 | /*============================================= 2 | = Generic styling = 3 | =============================================*/ 4 | * { 5 | -webkit-font-smoothing: antialiased; 6 | -moz-osx-font-smoothing: grayscale; 7 | } 8 | 9 | *, *:before, *:after { 10 | box-sizing: border-box; 11 | } 12 | 13 | html, body { 14 | position: relative; 15 | margin: 0; 16 | width: 100%; 17 | height: 100%; 18 | } 19 | 20 | body { 21 | font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; 22 | font-size: 16px; 23 | line-height: 1.4; 24 | color: #FFFFFF; 25 | background-color: #333; 26 | overflow-x: hidden; 27 | } 28 | 29 | label { 30 | display: block; 31 | margin-bottom: 8px; 32 | font-size: 14px; 33 | font-weight: 500; 34 | cursor: pointer; 35 | } 36 | 37 | p { 38 | margin-top: 0; 39 | } 40 | 41 | hr { 42 | display: block; 43 | margin: 36px 0; 44 | border: 0; 45 | border-bottom: 1px solid #eaeaea; 46 | height: 1px; 47 | } 48 | 49 | h1, h2, h3, h4, h5, h6 { 50 | margin-top: 0; 51 | margin-bottom: 12px; 52 | font-weight: 400; 53 | line-height: 1.2; 54 | } 55 | 56 | a, a:visited, a:focus { 57 | color: #FFFFFF; 58 | text-decoration: none; 59 | font-weight: 600; 60 | } 61 | 62 | .form-control { 63 | display: block; 64 | width: 100%; 65 | background-color: #f9f9f9; 66 | padding: 12px; 67 | border: 1px solid #ddd; 68 | border-radius: 2.5px; 69 | font-size: 14px; 70 | -webkit-appearance: none; 71 | -moz-appearance: none; 72 | appearance: none; 73 | margin-bottom: 24px; 74 | } 75 | 76 | h1, .h1 { 77 | font-size: 32px; 78 | } 79 | 80 | h2, .h2 { 81 | font-size: 24px; 82 | } 83 | 84 | h3, .h3 { 85 | font-size: 20px; 86 | } 87 | 88 | h4, .h4 { 89 | font-size: 18px; 90 | } 91 | 92 | h5, .h5 { 93 | font-size: 16px; 94 | } 95 | 96 | h6, .h6 { 97 | font-size: 14px; 98 | } 99 | 100 | .container { 101 | display: block; 102 | margin: auto; 103 | max-width: 40em; 104 | padding: 48px; 105 | } 106 | 107 | @media (max-width: 620px) { 108 | .container { 109 | padding: 0; 110 | } 111 | } 112 | 113 | .section { 114 | background-color: #FFFFFF; 115 | padding: 24px; 116 | color: #333; 117 | } 118 | 119 | .section a, .section a:visited, .section a:focus { 120 | color: #00bcd4; 121 | } 122 | 123 | .logo { 124 | display: block; 125 | margin-bottom: 12px; 126 | } 127 | 128 | .logo__img { 129 | width: 100%; 130 | height: auto; 131 | display: inline-block; 132 | max-width: 100%; 133 | vertical-align: top; 134 | padding: 6px 0; 135 | } 136 | 137 | .visible-ie { 138 | display: none; 139 | } 140 | 141 | /*===== End of Section comment block ======*/ 142 | -------------------------------------------------------------------------------- /assets/scripts/src/reducers/choices.js: -------------------------------------------------------------------------------- 1 | const choices = (state = [], action) => { 2 | switch (action.type) { 3 | case 'ADD_CHOICE': { 4 | /* 5 | A disabled choice appears in the choice dropdown but cannot be selected 6 | A selected choice has been added to the passed input's value (added as an item) 7 | An active choice appears within the choice dropdown 8 | */ 9 | return [...state, { 10 | id: action.id, 11 | groupId: action.groupId, 12 | value: action.value, 13 | label: action.label, 14 | disabled: action.disabled, 15 | selected: false, 16 | active: true, 17 | score: 9999, 18 | }]; 19 | } 20 | 21 | case 'ADD_ITEM': { 22 | let newState = state; 23 | 24 | // If all choices need to be activated 25 | if (action.activateOptions) { 26 | newState = state.map((choice) => { 27 | choice.active = action.active; 28 | return choice; 29 | }); 30 | } 31 | // When an item is added and it has an associated choice, 32 | // we want to disable it so it can't be chosen again 33 | if (action.choiceId > -1) { 34 | newState = state.map((choice) => { 35 | if (choice.id === parseInt(action.choiceId, 10)) { 36 | choice.selected = true; 37 | } 38 | return choice; 39 | }); 40 | } 41 | 42 | return newState; 43 | } 44 | 45 | case 'REMOVE_ITEM': { 46 | // When an item is removed and it has an associated choice, 47 | // we want to re-enable it so it can be chosen again 48 | if (action.choiceId > -1) { 49 | return state.map((choice) => { 50 | if (choice.id === parseInt(action.choiceId, 10)) { 51 | choice.selected = false; 52 | } 53 | return choice; 54 | }); 55 | } 56 | 57 | return state; 58 | } 59 | 60 | case 'FILTER_CHOICES': { 61 | const filteredResults = action.results; 62 | const filteredState = state.map((choice) => { 63 | // Set active state based on whether choice is 64 | // within filtered results 65 | 66 | choice.active = filteredResults.some((result) => { 67 | if (result.item.id === choice.id) { 68 | choice.score = result.score; 69 | return true; 70 | } 71 | return false; 72 | }); 73 | 74 | return choice; 75 | }); 76 | 77 | return filteredState; 78 | } 79 | 80 | case 'ACTIVATE_CHOICES': { 81 | return state.map((choice) => { 82 | choice.active = action.active; 83 | return choice; 84 | }); 85 | } 86 | 87 | case 'CLEAR_CHOICES': { 88 | return state.choices = []; 89 | } 90 | 91 | default: { 92 | return state; 93 | } 94 | } 95 | }; 96 | 97 | export default choices; 98 | -------------------------------------------------------------------------------- /assets/styles/scss/base.scss: -------------------------------------------------------------------------------- 1 | $global-guttering: 24px; 2 | $global-font-size-h1: 32px; 3 | $global-font-size-h2: 24px; 4 | $global-font-size-h3: 20px; 5 | $global-font-size-h4: 18px; 6 | $global-font-size-h5: 16px; 7 | $global-font-size-h6: 14px; 8 | 9 | /*============================================= 10 | = Generic styling = 11 | =============================================*/ 12 | 13 | * { 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale 16 | } 17 | 18 | 19 | *, *:before, *:after { 20 | box-sizing: border-box 21 | } 22 | 23 | 24 | html, body { 25 | position: relative; 26 | margin: 0; 27 | width: 100%; 28 | height: 100%; 29 | } 30 | 31 | body { 32 | font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; 33 | font-size: 16px; 34 | line-height: 1.4; 35 | color: #FFFFFF; 36 | background-color: #333; 37 | overflow-x: hidden; 38 | } 39 | 40 | label { 41 | display: block; 42 | margin-bottom: 8px; 43 | font-size: 14px; 44 | font-weight: 500; 45 | cursor: pointer; 46 | } 47 | 48 | p { margin-top: 0; } 49 | 50 | hr { 51 | display: block; 52 | margin: $global-guttering*1.5 0; 53 | border: 0; 54 | border-bottom: 1px solid #eaeaea; 55 | height: 1px; 56 | } 57 | 58 | h1, h2, h3, h4, h5, h6 { 59 | margin-top: 0; 60 | margin-bottom: $global-guttering/2; 61 | font-weight: 400; 62 | line-height: 1.2; 63 | } 64 | 65 | a, a:visited, a:focus { 66 | color: #FFFFFF; 67 | text-decoration: none; 68 | font-weight: 600; 69 | } 70 | 71 | .form-control { 72 | display: block; 73 | width: 100%; 74 | background-color: #f9f9f9; 75 | padding: 12px; 76 | border: 1px solid #ddd; 77 | border-radius: 2.5px; 78 | font-size: 14px; 79 | -webkit-appearance: none; 80 | appearance: none; 81 | margin-bottom: $global-guttering; 82 | } 83 | 84 | h1, .h1 { font-size: $global-font-size-h1; } 85 | h2, .h2 { font-size: $global-font-size-h2; } 86 | h3, .h3 { font-size: $global-font-size-h3; } 87 | h4, .h4 { font-size: $global-font-size-h4; } 88 | h5, .h5 { font-size: $global-font-size-h5; } 89 | h6, .h6 { font-size: $global-font-size-h6; } 90 | 91 | .container { 92 | display: block; 93 | margin: auto; 94 | max-width: 40em; 95 | padding: $global-guttering*2; 96 | @media (max-width: 620px) { padding: 0; } 97 | } 98 | 99 | .section { 100 | background-color: #FFFFFF; 101 | padding: $global-guttering; 102 | color: #333; 103 | a, a:visited, a:focus { color: #00bcd4; } 104 | } 105 | 106 | .logo { 107 | display: block; 108 | margin-bottom: $global-guttering/2; 109 | } 110 | 111 | .logo__img { 112 | width: 100%; 113 | height: auto; 114 | display: inline-block; 115 | max-width: 100%; 116 | vertical-align: top; 117 | padding: $global-guttering/4 0; 118 | } 119 | 120 | .visible-ie { display: none; } 121 | 122 | /*===== End of Section comment block ======*/ 123 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "choices.js", 3 | "version": "2.5.1", 4 | "description": "A vanilla JS customisable text input/select box plugin", 5 | "main": "./assets/scripts/dist/choices.min.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "css:watch": "nodemon -e scss -x \"npm run css:build\"", 9 | "css:build": "npm run css:sass -s && npm run css:prefix -s && npm run css:min -s", 10 | "css:sass": "node-sass --output-style expanded --include-path scss assets/styles/scss/base.scss assets/styles/css/base.css && node-sass --output-style expanded --include-path scss assets/styles/scss/choices.scss assets/styles/css/choices.css", 11 | "css:prefix": "postcss --use autoprefixer -b 'last 2 versions' assets/styles/css/*.css -d assets/styles/css/", 12 | "css:min": "csso assets/styles/css/base.css assets/styles/css/base.min.css && csso assets/styles/css/choices.css assets/styles/css/choices.min.css", 13 | "js:build": "concurrently --prefix-colors yellow,green \"webpack --minimize --config webpack.config.prod.js\" \"webpack --config webpack.config.prod.js\"", 14 | "js:test": "./node_modules/karma/bin/karma start --single-run --no-auto-watch tests/karma.config.js", 15 | "js:test:watch": "./node_modules/karma/bin/karma start --auto-watch --no-single-run tests/karma.config.js" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/jshjohnson/Choices.git" 20 | }, 21 | "author": "Josh Johnson", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/jshjohnson/Choices/issues" 25 | }, 26 | "homepage": "https://github.com/jshjohnson/Choices#readme", 27 | "devDependencies": { 28 | "autoprefixer": "^6.3.3", 29 | "babel-core": "^6.7.2", 30 | "babel-eslint": "^6.1.2", 31 | "babel-loader": "^6.2.4", 32 | "babel-preset-es2015": "^6.6.0", 33 | "concurrently": "^3.1.0", 34 | "csso": "^1.8.2", 35 | "es6-promise": "^3.2.1", 36 | "eslint": "^3.3.0", 37 | "eslint-config-airbnb": "^10.0.1", 38 | "eslint-loader": "^1.5.0", 39 | "eslint-plugin-import": "^1.13.0", 40 | "eslint-plugin-jsx-a11y": "^2.2.3", 41 | "eslint-plugin-react": "^6.4.1", 42 | "jasmine-core": "2.4.1", 43 | "karma": "^1.1.0", 44 | "karma-coverage": "^1.0.0", 45 | "karma-es6-shim": "^1.0.0", 46 | "karma-htmlfile-reporter": "^0.3.4", 47 | "karma-jasmine": "^1.0.2", 48 | "karma-phantomjs-launcher": "^1.0.1", 49 | "karma-spec-reporter": "0.0.26", 50 | "karma-webpack": "^1.7.0", 51 | "node-sass": "^3.4.2", 52 | "nodemon": "^1.9.1", 53 | "opn-cli": "^3.1.0", 54 | "postcss-cli": "^2.5.1", 55 | "webpack": "^1.12.14", 56 | "webpack-dashboard": "^0.1.8", 57 | "webpack-dev-server": "^1.14.1", 58 | "whatwg-fetch": "^1.0.0", 59 | "wrapper-webpack-plugin": "^0.1.7" 60 | }, 61 | "dependencies": { 62 | "redux": "^3.3.1", 63 | "fuse.js": "^2.2.2" 64 | }, 65 | "npmName": "choices.js", 66 | "npmFileMap": [ 67 | { 68 | "basePath": "assets", 69 | "files": [ 70 | "scripts/dist/*", 71 | "styles/css/*", 72 | "icons/*" 73 | ] 74 | } 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /assets/scripts/src/lib/polyfills.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // Production steps of ECMA-262, Edition 6, 22.1.2.1 3 | // Reference: https://people.mozilla.org/~jorendorff/es6-draft.html#sec-array.from 4 | if (!Array.from) { 5 | Array.from = (function() { 6 | var toStr = Object.prototype.toString; 7 | 8 | var isCallable = function(fn) { 9 | return typeof fn === 'function' || toStr.call(fn) === '[object Function]'; 10 | }; 11 | 12 | var toInteger = function(value) { 13 | var number = Number(value); 14 | if (isNaN(number)) { 15 | return 0; 16 | } 17 | if (number === 0 || !isFinite(number)) { 18 | return number; 19 | } 20 | return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number)); 21 | }; 22 | 23 | var maxSafeInteger = Math.pow(2, 53) - 1; 24 | 25 | var toLength = function(value) { 26 | var len = toInteger(value); 27 | return Math.min(Math.max(len, 0), maxSafeInteger); 28 | }; 29 | 30 | // The length property of the from method is 1. 31 | return function from(arrayLike /*, mapFn, thisArg */ ) { 32 | // 1. Let C be the this value. 33 | var C = this; 34 | 35 | // 2. Let items be ToObject(arrayLike). 36 | var items = Object(arrayLike); 37 | 38 | // 3. ReturnIfAbrupt(items). 39 | if (arrayLike == null) { 40 | throw new TypeError("Array.from requires an array-like object - not null or undefined"); 41 | } 42 | 43 | // 4. If mapfn is undefined, then let mapping be false. 44 | var mapFn = arguments.length > 1 ? arguments[1] : void undefined; 45 | var T; 46 | if (typeof mapFn !== 'undefined') { 47 | // 5. else 48 | // 5. a If IsCallable(mapfn) is false, throw a TypeError exception. 49 | if (!isCallable(mapFn)) { 50 | throw new TypeError('Array.from: when provided, the second argument must be a function'); 51 | } 52 | 53 | // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined. 54 | if (arguments.length > 2) { 55 | T = arguments[2]; 56 | } 57 | } 58 | 59 | // 10. Let lenValue be Get(items, "length"). 60 | // 11. Let len be ToLength(lenValue). 61 | var len = toLength(items.length); 62 | 63 | // 13. If IsConstructor(C) is true, then 64 | // 13. a. Let A be the result of calling the [[Construct]] internal method of C with an argument list containing the single item len. 65 | // 14. a. Else, Let A be ArrayCreate(len). 66 | var A = isCallable(C) ? Object(new C(len)) : new Array(len); 67 | 68 | // 16. Let k be 0. 69 | var k = 0; 70 | // 17. Repeat, while k < len… (also steps a - h) 71 | var kValue; 72 | while (k < len) { 73 | kValue = items[k]; 74 | if (mapFn) { 75 | A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k); 76 | } else { 77 | A[k] = kValue; 78 | } 79 | k += 1; 80 | } 81 | // 18. Let putStatus be Put(A, "length", len, true). 82 | A.length = len; 83 | // 20. Return A. 84 | return A; 85 | }; 86 | }()); 87 | } 88 | 89 | // Reference: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/find 90 | if (!Array.prototype.find) { 91 | Array.prototype.find = function(predicate) { 92 | 'use strict'; 93 | if (this == null) { 94 | throw new TypeError('Array.prototype.find called on null or undefined'); 95 | } 96 | if (typeof predicate !== 'function') { 97 | throw new TypeError('predicate must be a function'); 98 | } 99 | var list = Object(this); 100 | var length = list.length >>> 0; 101 | var thisArg = arguments[1]; 102 | var value; 103 | 104 | for (var i = 0; i < length; i++) { 105 | value = list[i]; 106 | if (predicate.call(thisArg, value, i, list)) { 107 | return value; 108 | } 109 | } 110 | return undefined; 111 | }; 112 | } -------------------------------------------------------------------------------- /assets/scripts/src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux'; 2 | import rootReducer from './../reducers/index.js'; 3 | 4 | export default class Store { 5 | constructor() { 6 | this.store = createStore( 7 | rootReducer 8 | , window.devToolsExtension ? window.devToolsExtension() : undefined 9 | ); 10 | } 11 | 12 | /** 13 | * Get store object (wrapping Redux method) 14 | * @return {Object} State 15 | */ 16 | getState() { 17 | return this.store.getState(); 18 | } 19 | 20 | /** 21 | * Dispatch event to store (wrapped Redux method) 22 | * @param {Function} action Action function to trigger 23 | * @return 24 | */ 25 | dispatch(action) { 26 | this.store.dispatch(action); 27 | } 28 | 29 | /** 30 | * Subscribe store to function call (wrapped Redux method) 31 | * @param {Function} onChange Function to trigger when state changes 32 | * @return 33 | */ 34 | subscribe(onChange) { 35 | this.store.subscribe(onChange); 36 | } 37 | 38 | /** 39 | * Get items from store 40 | * @return {Array} Item objects 41 | */ 42 | getItems() { 43 | const state = this.store.getState(); 44 | return state.items; 45 | } 46 | 47 | /** 48 | * Get active items from store 49 | * @return {Array} Item objects 50 | */ 51 | getItemsFilteredByActive() { 52 | const items = this.getItems(); 53 | const values = items.filter((item) => { 54 | return item.active === true; 55 | }, []); 56 | 57 | return values; 58 | } 59 | 60 | /** 61 | * Get items from store reduced to just their values 62 | * @return {Array} Item objects 63 | */ 64 | getItemsReducedToValues(items = this.getItems()) { 65 | const values = items.reduce((prev, current) => { 66 | prev.push(current.value); 67 | return prev; 68 | }, []); 69 | 70 | return values; 71 | } 72 | 73 | /** 74 | * Get choices from store 75 | * @return {Array} Option objects 76 | */ 77 | getChoices() { 78 | const state = this.store.getState(); 79 | return state.choices; 80 | } 81 | 82 | /** 83 | * Get active choices from store 84 | * @return {Array} Option objects 85 | */ 86 | getChoicesFilteredByActive() { 87 | const choices = this.getChoices(); 88 | const values = choices.filter((choice) => { 89 | return choice.active === true; 90 | }, []); 91 | 92 | return values; 93 | } 94 | 95 | /** 96 | * Get selectable choices from store 97 | * @return {Array} Option objects 98 | */ 99 | getChoicesFilteredBySelectable() { 100 | const choices = this.getChoices(); 101 | const values = choices.filter((choice) => { 102 | return choice.disabled !== true; 103 | }, []); 104 | 105 | return values; 106 | } 107 | 108 | /** 109 | * Get single choice by it's ID 110 | * @return {Object} Found choice 111 | */ 112 | getChoiceById(id) { 113 | if (id) { 114 | const choices = this.getChoicesFilteredByActive(); 115 | const foundChoice = choices.find((choice) => choice.id === parseInt(id, 10)); 116 | return foundChoice; 117 | } 118 | return false; 119 | } 120 | 121 | /** 122 | * Get groups from store 123 | * @return {Array} Group objects 124 | */ 125 | getGroups() { 126 | const state = this.store.getState(); 127 | return state.groups; 128 | } 129 | 130 | /** 131 | * Get active groups from store 132 | * @return {Array} Group objects 133 | */ 134 | getGroupsFilteredByActive() { 135 | const groups = this.getGroups(); 136 | const choices = this.getChoices(); 137 | 138 | const values = groups.filter((group) => { 139 | const isActive = group.active === true && group.disabled === false; 140 | const hasActiveOptions = choices.some((choice) => { 141 | return choice.active === true && choice.disabled === false; 142 | }); 143 | return isActive && hasActiveOptions; 144 | }, []); 145 | 146 | return values; 147 | } 148 | 149 | /** 150 | * Get group by group id 151 | * @param {Number} id Group ID 152 | * @return {Object} Group data 153 | */ 154 | getGroupById(id) { 155 | const groups = this.getGroups(); 156 | const foundGroup = groups.find((group) => { 157 | return group.id === id; 158 | }); 159 | 160 | return foundGroup; 161 | } 162 | } 163 | 164 | module.exports = Store; 165 | -------------------------------------------------------------------------------- /assets/styles/css/choices.min.css: -------------------------------------------------------------------------------- 1 | .choices{position:relative;margin-bottom:24px;font-size:16px}.choices:focus{outline:none}.choices:last-child{margin-bottom:0}.choices.is-disabled .choices__inner,.choices.is-disabled .choices__input{background-color:#eaeaea;cursor:not-allowed;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.choices.is-disabled .choices__item{cursor:not-allowed}.choices[data-type*=select-one]{cursor:pointer}.choices[data-type*=select-one] .choices__inner{padding-bottom:7.5px}.choices[data-type*=select-one] .choices__input{display:block;width:100%;padding:10px;border-bottom:1px solid #ddd;background-color:#fff;margin:0}.choices[data-type*=select-one] .choices__button{background-image:url(../../icons//cross-inverse.svg);padding:0;background-size:8px;position:absolute;top:50%;right:0;margin-top:-10px;margin-right:25px;height:20px;width:20px;border-radius:10em;opacity:.5}.choices[data-type*=select-one] .choices__button:focus,.choices[data-type*=select-one] .choices__button:hover{opacity:1}.choices[data-type*=select-one] .choices__button:focus{box-shadow:0 0 0 2px #00bcd4}.choices[data-type*=select-one]:after{content:"";height:0;width:0;border-style:solid;border-color:#333 transparent transparent transparent;border-width:5px;position:absolute;right:11.5px;top:50%;margin-top:-2.5px;pointer-events:none}.choices[data-type*=select-one].is-open:after{border-color:transparent transparent #333 transparent;margin-top:-7.5px}.choices[data-type*=select-one][dir=rtl]:after{left:11.5px;right:auto}.choices[data-type*=select-one][dir=rtl] .choices__button{right:auto;left:0;margin-left:25px;margin-right:0}.choices[data-type*=select-multiple] .choices__inner,.choices[data-type*=text] .choices__inner{cursor:text}.choices[data-type*=select-multiple] .choices__button,.choices[data-type*=text] .choices__button{position:relative;display:inline-block;margin:0 -4px 0 8px;padding-left:16px;border-left:1px solid #008fa1;background-image:url(../../icons//cross.svg);background-size:8px;width:8px;line-height:1;opacity:.75}.choices[data-type*=select-multiple] .choices__button:focus,.choices[data-type*=select-multiple] .choices__button:hover,.choices[data-type*=text] .choices__button:focus,.choices[data-type*=text] .choices__button:hover{opacity:1}.choices__inner{display:inline-block;vertical-align:top;width:100%;background-color:#f9f9f9;padding:7.5px 7.5px 3.75px;border:1px solid #ddd;border-radius:2.5px;font-size:14px;min-height:44px;overflow:hidden}.is-focused .choices__inner,.is-open .choices__inner{border-color:#b7b7b7}.is-open .choices__inner{border-radius:2.5px 2.5px 0 0}.is-flipped.is-open .choices__inner{border-radius:0 0 2.5px 2.5px}.choices__list{margin:0;padding-left:0;list-style:none}.choices__list--single{display:inline-block;padding:4px 16px 4px 4px;width:100%}[dir=rtl] .choices__list--single{padding-right:4px;padding-left:16px}.choices__list--single .choices__item{width:100%}.choices__list--multiple{display:inline}.choices__list--multiple .choices__item{display:inline-block;vertical-align:middle;border-radius:20px;padding:4px 10px;font-size:12px;font-weight:500;margin-right:3.75px;margin-bottom:3.75px;background-color:#00bcd4;border:1px solid #00a5bb;color:#fff;word-break:break-all}.choices__list--multiple .choices__item[data-deletable]{padding-right:5px}[dir=rtl] .choices__list--multiple .choices__item{margin-right:0;margin-left:3.75px}.choices__list--multiple .choices__item.is-highlighted{background-color:#00a5bb;border:1px solid #008fa1}.is-disabled .choices__list--multiple .choices__item{background-color:#aaa;border:1px solid #919191}.choices__list--dropdown{display:none;z-index:1;position:absolute;width:100%;background-color:#fff;border:1px solid #ddd;top:100%;margin-top:-1px;border-bottom-left-radius:2.5px;border-bottom-right-radius:2.5px;overflow:hidden;word-break:break-all}.choices__list--dropdown.is-active{display:block}.is-open .choices__list--dropdown{border-color:#b7b7b7}.is-flipped .choices__list--dropdown{top:auto;bottom:100%;margin-top:0;margin-bottom:-1px;border-radius:.25rem .25rem 0 0}.choices__list--dropdown .choices__list{position:relative;max-height:300px;overflow:auto;-webkit-overflow-scrolling:touch;will-change:scroll-position}.choices__list--dropdown .choices__item{position:relative;padding:10px;font-size:14px}[dir=rtl] .choices__list--dropdown .choices__item{text-align:right}@media (min-width:640px){.choices__list--dropdown .choices__item--selectable{padding-right:100px}.choices__list--dropdown .choices__item--selectable:after{content:attr(data-select-text);font-size:12px;opacity:0;position:absolute;right:10px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}[dir=rtl] .choices__list--dropdown .choices__item--selectable{text-align:right;padding-left:100px;padding-right:10px}[dir=rtl] .choices__list--dropdown .choices__item--selectable:after{right:auto;left:10px}}.choices__list--dropdown .choices__item--selectable.is-highlighted{background-color:#f2f2f2}.choices__list--dropdown .choices__item--selectable.is-highlighted:after{opacity:.5}.choices__item{cursor:default}.choices__item--selectable{cursor:pointer}.choices__item--disabled{cursor:not-allowed;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;opacity:.5}.choices__heading{font-weight:600;font-size:12px;padding:10px;border-bottom:1px solid #f7f7f7;color:gray}.choices__button{text-indent:-9999px;-webkit-appearance:none;-moz-appearance:none;appearance:none;border:0;background-color:transparent;background-repeat:no-repeat;background-position:center;cursor:pointer}.choices__button:focus{outline:none}.choices__input{display:inline-block;vertical-align:baseline;background-color:#f9f9f9;font-size:14px;margin-bottom:5px;border:0;border-radius:0;max-width:100%;padding:4px 0 4px 2px}.choices__input:focus{outline:0}[dir=rtl] .choices__input{padding-right:2px;padding-left:0}.choices__placeholder{opacity:.5} -------------------------------------------------------------------------------- /assets/styles/css/choices.css: -------------------------------------------------------------------------------- 1 | /*=============================== 2 | = Choices = 3 | ===============================*/ 4 | .choices { 5 | position: relative; 6 | margin-bottom: 24px; 7 | font-size: 16px; 8 | } 9 | 10 | .choices:focus { 11 | outline: none; 12 | } 13 | 14 | .choices:last-child { 15 | margin-bottom: 0; 16 | } 17 | 18 | .choices.is-disabled .choices__inner, .choices.is-disabled .choices__input { 19 | background-color: #EAEAEA; 20 | cursor: not-allowed; 21 | -webkit-user-select: none; 22 | -moz-user-select: none; 23 | -ms-user-select: none; 24 | user-select: none; 25 | } 26 | 27 | .choices.is-disabled .choices__item { 28 | cursor: not-allowed; 29 | } 30 | 31 | .choices[data-type*="select-one"] { 32 | cursor: pointer; 33 | } 34 | 35 | .choices[data-type*="select-one"] .choices__inner { 36 | padding-bottom: 7.5px; 37 | } 38 | 39 | .choices[data-type*="select-one"] .choices__input { 40 | display: block; 41 | width: 100%; 42 | padding: 10px; 43 | border-bottom: 1px solid #DDDDDD; 44 | background-color: #FFFFFF; 45 | margin: 0; 46 | } 47 | 48 | .choices[data-type*="select-one"] .choices__button { 49 | background-image: url("../../icons//cross-inverse.svg"); 50 | padding: 0; 51 | background-size: 8px; 52 | height: 100%; 53 | position: absolute; 54 | top: 50%; 55 | right: 0; 56 | margin-top: -10px; 57 | margin-right: 25px; 58 | height: 20px; 59 | width: 20px; 60 | border-radius: 10em; 61 | opacity: .5; 62 | } 63 | 64 | .choices[data-type*="select-one"] .choices__button:hover, .choices[data-type*="select-one"] .choices__button:focus { 65 | opacity: 1; 66 | } 67 | 68 | .choices[data-type*="select-one"] .choices__button:focus { 69 | box-shadow: 0px 0px 0px 2px #00BCD4; 70 | } 71 | 72 | .choices[data-type*="select-one"]:after { 73 | content: ""; 74 | height: 0; 75 | width: 0; 76 | border-style: solid; 77 | border-color: #333333 transparent transparent transparent; 78 | border-width: 5px; 79 | position: absolute; 80 | right: 11.5px; 81 | top: 50%; 82 | margin-top: -2.5px; 83 | pointer-events: none; 84 | } 85 | 86 | .choices[data-type*="select-one"].is-open:after { 87 | border-color: transparent transparent #333333 transparent; 88 | margin-top: -7.5px; 89 | } 90 | 91 | .choices[data-type*="select-one"][dir="rtl"]:after { 92 | left: 11.5px; 93 | right: auto; 94 | } 95 | 96 | .choices[data-type*="select-one"][dir="rtl"] .choices__button { 97 | right: auto; 98 | left: 0; 99 | margin-left: 25px; 100 | margin-right: 0; 101 | } 102 | 103 | .choices[data-type*="select-multiple"] .choices__inner, .choices[data-type*="text"] .choices__inner { 104 | cursor: text; 105 | } 106 | 107 | .choices[data-type*="select-multiple"] .choices__button, .choices[data-type*="text"] .choices__button { 108 | position: relative; 109 | display: inline-block; 110 | margin-top: 0; 111 | margin-right: -4px; 112 | margin-bottom: 0; 113 | margin-left: 8px; 114 | padding-left: 16px; 115 | border-left: 1px solid #008fa1; 116 | background-image: url("../../icons//cross.svg"); 117 | background-size: 8px; 118 | width: 8px; 119 | line-height: 1; 120 | opacity: .75; 121 | } 122 | 123 | .choices[data-type*="select-multiple"] .choices__button:hover, .choices[data-type*="select-multiple"] .choices__button:focus, .choices[data-type*="text"] .choices__button:hover, .choices[data-type*="text"] .choices__button:focus { 124 | opacity: 1; 125 | } 126 | 127 | .choices__inner { 128 | display: inline-block; 129 | vertical-align: top; 130 | width: 100%; 131 | background-color: #f9f9f9; 132 | padding: 7.5px 7.5px 3.75px; 133 | border: 1px solid #DDDDDD; 134 | border-radius: 2.5px; 135 | font-size: 14px; 136 | min-height: 44px; 137 | overflow: hidden; 138 | } 139 | 140 | .is-focused .choices__inner, .is-open .choices__inner { 141 | border-color: #b7b7b7; 142 | } 143 | 144 | .is-open .choices__inner { 145 | border-radius: 2.5px 2.5px 0 0; 146 | } 147 | 148 | .is-flipped.is-open .choices__inner { 149 | border-radius: 0 0 2.5px 2.5px; 150 | } 151 | 152 | .choices__list { 153 | margin: 0; 154 | padding-left: 0; 155 | list-style: none; 156 | } 157 | 158 | .choices__list--single { 159 | display: inline-block; 160 | padding: 4px 16px 4px 4px; 161 | width: 100%; 162 | } 163 | 164 | [dir="rtl"] .choices__list--single { 165 | padding-right: 4px; 166 | padding-left: 16px; 167 | } 168 | 169 | .choices__list--single .choices__item { 170 | width: 100%; 171 | } 172 | 173 | .choices__list--multiple { 174 | display: inline; 175 | } 176 | 177 | .choices__list--multiple .choices__item { 178 | display: inline-block; 179 | vertical-align: middle; 180 | border-radius: 20px; 181 | padding: 4px 10px; 182 | font-size: 12px; 183 | font-weight: 500; 184 | margin-right: 3.75px; 185 | margin-bottom: 3.75px; 186 | background-color: #00BCD4; 187 | border: 1px solid #00a5bb; 188 | color: #FFFFFF; 189 | word-break: break-all; 190 | } 191 | 192 | .choices__list--multiple .choices__item[data-deletable] { 193 | padding-right: 5px; 194 | } 195 | 196 | [dir="rtl"] .choices__list--multiple .choices__item { 197 | margin-right: 0; 198 | margin-left: 3.75px; 199 | } 200 | 201 | .choices__list--multiple .choices__item.is-highlighted { 202 | background-color: #00a5bb; 203 | border: 1px solid #008fa1; 204 | } 205 | 206 | .is-disabled .choices__list--multiple .choices__item { 207 | background-color: #aaaaaa; 208 | border: 1px solid #919191; 209 | } 210 | 211 | .choices__list--dropdown { 212 | display: none; 213 | z-index: 1; 214 | position: absolute; 215 | width: 100%; 216 | background-color: #FFFFFF; 217 | border: 1px solid #DDDDDD; 218 | top: 100%; 219 | margin-top: -1px; 220 | border-bottom-left-radius: 2.5px; 221 | border-bottom-right-radius: 2.5px; 222 | overflow: hidden; 223 | word-break: break-all; 224 | } 225 | 226 | .choices__list--dropdown.is-active { 227 | display: block; 228 | } 229 | 230 | .is-open .choices__list--dropdown { 231 | border-color: #b7b7b7; 232 | } 233 | 234 | .is-flipped .choices__list--dropdown { 235 | top: auto; 236 | bottom: 100%; 237 | margin-top: 0; 238 | margin-bottom: -1px; 239 | border-radius: .25rem .25rem 0 0; 240 | } 241 | 242 | .choices__list--dropdown .choices__list { 243 | position: relative; 244 | max-height: 300px; 245 | overflow: auto; 246 | -webkit-overflow-scrolling: touch; 247 | will-change: scroll-position; 248 | } 249 | 250 | .choices__list--dropdown .choices__item { 251 | position: relative; 252 | padding: 10px; 253 | font-size: 14px; 254 | } 255 | 256 | [dir="rtl"] .choices__list--dropdown .choices__item { 257 | text-align: right; 258 | } 259 | 260 | @media (min-width: 640px) { 261 | .choices__list--dropdown .choices__item--selectable { 262 | padding-right: 100px; 263 | } 264 | .choices__list--dropdown .choices__item--selectable:after { 265 | content: attr(data-select-text); 266 | font-size: 12px; 267 | opacity: 0; 268 | position: absolute; 269 | right: 10px; 270 | top: 50%; 271 | -webkit-transform: translateY(-50%); 272 | transform: translateY(-50%); 273 | } 274 | [dir="rtl"] .choices__list--dropdown .choices__item--selectable { 275 | text-align: right; 276 | padding-left: 100px; 277 | padding-right: 10px; 278 | } 279 | [dir="rtl"] .choices__list--dropdown .choices__item--selectable:after { 280 | right: auto; 281 | left: 10px; 282 | } 283 | } 284 | 285 | .choices__list--dropdown .choices__item--selectable.is-highlighted { 286 | background-color: #f2f2f2; 287 | } 288 | 289 | .choices__list--dropdown .choices__item--selectable.is-highlighted:after { 290 | opacity: .5; 291 | } 292 | 293 | .choices__item { 294 | cursor: default; 295 | } 296 | 297 | .choices__item--selectable { 298 | cursor: pointer; 299 | } 300 | 301 | .choices__item--disabled { 302 | cursor: not-allowed; 303 | -webkit-user-select: none; 304 | -moz-user-select: none; 305 | -ms-user-select: none; 306 | user-select: none; 307 | opacity: .5; 308 | } 309 | 310 | .choices__heading { 311 | font-weight: 600; 312 | font-size: 12px; 313 | padding: 10px; 314 | border-bottom: 1px solid #f7f7f7; 315 | color: gray; 316 | } 317 | 318 | .choices__button { 319 | text-indent: -9999px; 320 | -webkit-appearance: none; 321 | -moz-appearance: none; 322 | appearance: none; 323 | border: 0; 324 | background-color: transparent; 325 | background-repeat: no-repeat; 326 | background-position: center; 327 | cursor: pointer; 328 | } 329 | 330 | .choices__button:focus { 331 | outline: none; 332 | } 333 | 334 | .choices__input { 335 | display: inline-block; 336 | vertical-align: baseline; 337 | background-color: #f9f9f9; 338 | font-size: 14px; 339 | margin-bottom: 5px; 340 | border: 0; 341 | border-radius: 0; 342 | max-width: 100%; 343 | padding: 4px 0 4px 2px; 344 | } 345 | 346 | .choices__input:focus { 347 | outline: 0; 348 | } 349 | 350 | [dir="rtl"] .choices__input { 351 | padding-right: 2px; 352 | padding-left: 0; 353 | } 354 | 355 | .choices__placeholder { 356 | opacity: .5; 357 | } 358 | 359 | /*===== End of Choices ======*/ 360 | -------------------------------------------------------------------------------- /assets/styles/scss/choices.scss: -------------------------------------------------------------------------------- 1 | /*=============================== 2 | = Choices = 3 | ===============================*/ 4 | 5 | $choices-selector: 'choices' !default; 6 | $choices-font-size-lg: 16px !default; 7 | $choices-font-size-md: 14px !default; 8 | $choices-font-size-sm: 12px !default; 9 | $choices-guttering: 24px !default; 10 | $choices-border-radius: 2.5px !default; 11 | $choices-border-radius-item: 20px !default; 12 | $choices-bg-color: #f9f9f9 !default; 13 | $choices-bg-color-disabled: #EAEAEA !default; 14 | $choices-bg-color-dropdown: #FFFFFF !default; 15 | $choices-text-color: #333333 !default; 16 | $choices-keyline-color: #DDDDDD !default; 17 | $choices-primary-color: #00BCD4 !default; 18 | $choices-disabled-color: #eaeaea !default; 19 | $choices-highlight-color: $choices-primary-color !default; 20 | $choices-button-icon-path: '../../icons/' !default; 21 | $choices-button-dimension: 8px !default; 22 | $choices-button-offset: 8px !default; 23 | 24 | .#{$choices-selector} { 25 | position: relative; 26 | margin-bottom: $choices-guttering; 27 | font-size: $choices-font-size-lg; 28 | &:focus { outline: none; } 29 | &:last-child { margin-bottom: 0; } 30 | &.is-disabled { 31 | .#{$choices-selector}__inner, .#{$choices-selector}__input { 32 | background-color: $choices-bg-color-disabled; 33 | cursor: not-allowed; 34 | user-select: none; 35 | } 36 | .#{$choices-selector}__item { cursor: not-allowed; } 37 | } 38 | } 39 | 40 | .#{$choices-selector}[data-type*="select-one"] { 41 | cursor: pointer; 42 | .#{$choices-selector}__inner { padding-bottom: 7.5px; } 43 | .#{$choices-selector}__input { 44 | display: block; 45 | width: 100%; 46 | padding: 10px; 47 | border-bottom: 1px solid $choices-keyline-color; 48 | background-color: #FFFFFF; 49 | margin: 0; 50 | } 51 | .#{$choices-selector}__button { 52 | background-image: url($choices-button-icon-path + '/cross-inverse.svg'); 53 | padding: 0; 54 | background-size: 8px; 55 | height: 100%; 56 | position: absolute; 57 | top: 50%; 58 | right: 0; 59 | margin-top: -10px; 60 | margin-right: 25px; 61 | height: 20px; 62 | width: 20px; 63 | border-radius: 10em; 64 | opacity: .5; 65 | &:hover, &:focus { opacity: 1; } 66 | &:focus { box-shadow: 0px 0px 0px 2px $choices-highlight-color; } 67 | } 68 | &:after { 69 | content: ""; 70 | height: 0; 71 | width: 0; 72 | border-style: solid; 73 | border-color: $choices-text-color transparent transparent transparent; 74 | border-width: 5px; 75 | position: absolute; 76 | right: 11.5px; 77 | top: 50%; 78 | margin-top: -2.5px; 79 | pointer-events: none; 80 | } 81 | &.is-open:after { 82 | border-color: transparent transparent $choices-text-color transparent; 83 | margin-top: -7.5px; 84 | } 85 | &[dir="rtl"] { 86 | &:after { 87 | left: 11.5px; 88 | right: auto; 89 | } 90 | .#{$choices-selector}__button { 91 | right: auto; 92 | left: 0; 93 | margin-left: 25px; 94 | margin-right: 0; 95 | } 96 | } 97 | } 98 | 99 | .#{$choices-selector}[data-type*="select-multiple"], .#{$choices-selector}[data-type*="text"] { 100 | .#{$choices-selector}__inner { cursor: text; } 101 | .#{$choices-selector}__button { 102 | position: relative; 103 | display: inline-block; 104 | margin-top: 0; 105 | margin-right: -$choices-button-offset/2; 106 | margin-bottom: 0; 107 | margin-left: $choices-button-offset; 108 | padding-left: $choices-button-offset*2; 109 | border-left: 1px solid darken($choices-primary-color, 10%); 110 | background-image: url($choices-button-icon-path + '/cross.svg'); 111 | background-size: $choices-button-dimension; 112 | width: $choices-button-dimension; 113 | line-height: 1; 114 | opacity: .75; 115 | &:hover, &:focus { opacity: 1; } 116 | } 117 | } 118 | 119 | .#{$choices-selector}__inner { 120 | display: inline-block; 121 | vertical-align: top; 122 | width: 100%; 123 | background-color: $choices-bg-color; 124 | padding: 7.5px 7.5px 3.75px; 125 | border: 1px solid $choices-keyline-color; 126 | border-radius: $choices-border-radius; 127 | font-size: $choices-font-size-md; 128 | min-height: 44px; 129 | overflow: hidden; 130 | .is-focused &, .is-open & { border-color: darken($choices-keyline-color, 15%); } 131 | .is-open & { border-radius: $choices-border-radius $choices-border-radius 0 0; } 132 | .is-flipped.is-open & { border-radius: 0 0 $choices-border-radius $choices-border-radius; } 133 | } 134 | 135 | .#{$choices-selector}__list { 136 | margin: 0; 137 | padding-left: 0; 138 | list-style: none; 139 | } 140 | 141 | .#{$choices-selector}__list--single { 142 | display: inline-block; 143 | padding: 4px 16px 4px 4px; 144 | width: 100%; 145 | [dir="rtl"] & { 146 | padding-right: 4px; 147 | padding-left: 16px; 148 | } 149 | .#{$choices-selector}__item { width: 100%; } 150 | } 151 | 152 | .#{$choices-selector}__list--multiple { 153 | display: inline; 154 | .#{$choices-selector}__item { 155 | display: inline-block; 156 | vertical-align: middle; 157 | border-radius: $choices-border-radius-item; 158 | padding: 4px 10px; 159 | font-size: $choices-font-size-sm; 160 | font-weight: 500; 161 | margin-right: 3.75px; 162 | margin-bottom: 3.75px; 163 | background-color: $choices-primary-color; 164 | border: 1px solid darken($choices-primary-color, 5%); 165 | color: #FFFFFF; 166 | word-break: break-all; 167 | &[data-deletable] { padding-right: 5px; } 168 | [dir="rtl"] & { 169 | margin-right: 0; 170 | margin-left: 3.75px; 171 | } 172 | &.is-highlighted { 173 | background-color: darken($choices-primary-color, 5%); 174 | border: 1px solid darken($choices-primary-color, 10%); 175 | } 176 | .is-disabled & { 177 | background-color: darken($choices-disabled-color, 25%); 178 | border: 1px solid darken($choices-disabled-color, 35%); 179 | } 180 | } 181 | } 182 | 183 | .#{$choices-selector}__list--dropdown { 184 | display: none; 185 | z-index: 1; 186 | position: absolute; 187 | width: 100%; 188 | background-color: $choices-bg-color-dropdown; 189 | border: 1px solid $choices-keyline-color; 190 | top: 100%; 191 | margin-top: -1px; 192 | border-bottom-left-radius: $choices-border-radius; 193 | border-bottom-right-radius: $choices-border-radius; 194 | overflow: hidden; 195 | word-break: break-all; 196 | &.is-active { display: block; } 197 | .is-open & { border-color: darken($choices-keyline-color, 15%); } 198 | .is-flipped & { 199 | top: auto; 200 | bottom: 100%; 201 | margin-top: 0; 202 | margin-bottom: -1px; 203 | border-radius: .25rem .25rem 0 0; 204 | } 205 | .#{$choices-selector}__list { 206 | position: relative; 207 | max-height: 300px; 208 | overflow: auto; 209 | -webkit-overflow-scrolling: touch; 210 | will-change: scroll-position; 211 | } 212 | .#{$choices-selector}__item { 213 | position: relative; 214 | padding: 10px; 215 | font-size: $choices-font-size-md; 216 | [dir="rtl"] & { text-align: right; } 217 | } 218 | .#{$choices-selector}__item--selectable { 219 | @media (min-width: 640px) { 220 | padding-right: 100px; 221 | &:after { 222 | content: attr(data-select-text); 223 | font-size: $choices-font-size-sm; 224 | opacity: 0; 225 | position: absolute; 226 | right: 10px; 227 | top: 50%; 228 | transform: translateY(-50%); 229 | } 230 | [dir="rtl"] & { 231 | text-align: right; 232 | padding-left: 100px; 233 | padding-right: 10px; 234 | &:after { 235 | right: auto; 236 | left: 10px; 237 | } 238 | } 239 | } 240 | &.is-highlighted { 241 | background-color: mix(#000000, #FFFFFF, 5%); 242 | &:after { opacity: .5; } 243 | } 244 | } 245 | } 246 | 247 | .#{$choices-selector}__item { cursor: default; } 248 | .#{$choices-selector}__item--selectable { cursor: pointer; } 249 | .#{$choices-selector}__item--disabled { 250 | cursor: not-allowed; 251 | user-select: none; 252 | opacity: .5; 253 | } 254 | 255 | .#{$choices-selector}__heading { 256 | font-weight: 600; 257 | font-size: $choices-font-size-sm; 258 | padding: 10px; 259 | border-bottom: 1px solid lighten($choices-keyline-color, 10%); 260 | color: lighten(#333, 30%); 261 | } 262 | 263 | .#{$choices-selector}__button { 264 | text-indent: -9999px; 265 | -webkit-appearance: none; 266 | appearance: none; 267 | border: 0; 268 | background-color: transparent; 269 | background-repeat: no-repeat; 270 | background-position: center; 271 | cursor: pointer; 272 | &:focus { outline: none; } 273 | } 274 | 275 | .#{$choices-selector}__input { 276 | display: inline-block; 277 | vertical-align: baseline; 278 | background-color: mix(#000000, #FFFFFF, 2.5%); 279 | font-size: $choices-font-size-md; 280 | margin-bottom: 5px; 281 | border: 0; 282 | border-radius: 0; 283 | max-width: 100%; 284 | padding: 4px 0 4px 2px; 285 | &:focus { outline: 0; } 286 | [dir="rtl"] & { 287 | padding-right: 2px; 288 | padding-left: 0; 289 | } 290 | } 291 | 292 | .#{$choices-selector}__placeholder { opacity: .5; } 293 | 294 | /*===== End of Choices ======*/ 295 | -------------------------------------------------------------------------------- /assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Slice 1 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /assets/scripts/src/lib/utils.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Capitalises the first letter of each word in a string 4 | * @param {String} str String to capitalise 5 | * @return {String} Capitalised string 6 | */ 7 | export const capitalise = function(str) { 8 | return str.replace(/\w\S*/g, function(txt) { 9 | return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); 10 | }); 11 | }; 12 | 13 | /** 14 | * Tests the type of an object 15 | * @param {String} type Type to test object against 16 | * @param {Object} obj Object to be tested 17 | * @return {Boolean} 18 | */ 19 | export const isType = function(type, obj) { 20 | var clas = Object.prototype.toString.call(obj).slice(8, -1); 21 | return obj !== undefined && obj !== null && clas === type; 22 | }; 23 | 24 | /** 25 | * Tests to see if a passed object is a node 26 | * @param {Object} obj Object to be tested 27 | * @return {Boolean} 28 | */ 29 | export const isNode = (o) => { 30 | return ( 31 | typeof Node === "object" ? o instanceof Node : 32 | o && typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName === "string" 33 | ); 34 | }; 35 | 36 | /** 37 | * Tests to see if a passed object is an element 38 | * @param {Object} obj Object to be tested 39 | * @return {Boolean} 40 | */ 41 | export const isElement = (o) => { 42 | return ( 43 | typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2 44 | o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName === "string" 45 | ); 46 | }; 47 | 48 | /** 49 | * Merges unspecified amount of objects into new object 50 | * @private 51 | * @return {Object} Merged object of arguments 52 | */ 53 | export const extend = function() { 54 | let extended = {}; 55 | let length = arguments.length; 56 | 57 | /** 58 | * Merge one object into another 59 | * @param {Object} obj Object to merge into extended object 60 | */ 61 | let merge = function(obj) { 62 | for (let prop in obj) { 63 | if (Object.prototype.hasOwnProperty.call(obj, prop)) { 64 | // If deep merge and property is an object, merge properties 65 | if (isType('Object', obj[prop])) { 66 | extended[prop] = extend(true, extended[prop], obj[prop]); 67 | } else { 68 | extended[prop] = obj[prop]; 69 | } 70 | } 71 | } 72 | }; 73 | 74 | // Loop through each passed argument 75 | for (let i = 0; i < length; i++) { 76 | // store argument at position i 77 | let obj = arguments[i]; 78 | 79 | // If we are in fact dealing with an object, merge it. 80 | if (isType('Object', obj)) { 81 | merge(obj); 82 | } 83 | } 84 | 85 | return extended; 86 | }; 87 | 88 | /** 89 | * CSS transition end event listener 90 | * @return 91 | */ 92 | export const whichTransitionEvent = function() { 93 | var t, 94 | el = document.createElement("fakeelement"); 95 | 96 | var transitions = { 97 | "transition": "transitionend", 98 | "OTransition": "oTransitionEnd", 99 | "MozTransition": "transitionend", 100 | "WebkitTransition": "webkitTransitionEnd" 101 | } 102 | 103 | for (t in transitions) { 104 | if (el.style[t] !== undefined) { 105 | return transitions[t]; 106 | } 107 | } 108 | }; 109 | 110 | /** 111 | * CSS animation end event listener 112 | * @return 113 | */ 114 | export const whichAnimationEvent = function() { 115 | var t, 116 | el = document.createElement('fakeelement'); 117 | 118 | var animations = { 119 | 'animation': 'animationend', 120 | 'OAnimation': 'oAnimationEnd', 121 | 'MozAnimation': 'animationend', 122 | 'WebkitAnimation': 'webkitAnimationEnd' 123 | }; 124 | 125 | for (t in animations) { 126 | if (el.style[t] !== undefined) { 127 | return animations[t]; 128 | } 129 | } 130 | }; 131 | 132 | /** 133 | * Get the ancestors of each element in the current set of matched elements, 134 | * up to but not including the element matched by the selector 135 | * @param {NodeElement} elem Element to begin search from 136 | * @param {NodeElement} parent Parent to find 137 | * @param {String} selector Class to find 138 | * @return {Array} Array of parent elements 139 | */ 140 | export const getParentsUntil = function(elem, parent, selector) { 141 | var parents = []; 142 | // Get matches 143 | for (; elem && elem !== document; elem = elem.parentNode) { 144 | 145 | // Check if parent has been reached 146 | if (parent) { 147 | 148 | var parentType = parent.charAt(0); 149 | 150 | // If parent is a class 151 | if (parentType === '.') { 152 | if (elem.classList.contains(parent.substr(1))) { 153 | break; 154 | } 155 | } 156 | 157 | // If parent is an ID 158 | if (parentType === '#') { 159 | if (elem.id === parent.substr(1)) { 160 | break; 161 | } 162 | } 163 | 164 | // If parent is a data attribute 165 | if (parentType === '[') { 166 | if (elem.hasAttribute(parent.substr(1, parent.length - 1))) { 167 | break; 168 | } 169 | } 170 | 171 | // If parent is a tag 172 | if (elem.tagName.toLowerCase() === parent) { 173 | break; 174 | } 175 | 176 | } 177 | if (selector) { 178 | var selectorType = selector.charAt(0); 179 | 180 | // If selector is a class 181 | if (selectorType === '.') { 182 | if (elem.classList.contains(selector.substr(1))) { 183 | parents.push(elem); 184 | } 185 | } 186 | 187 | // If selector is an ID 188 | if (selectorType === '#') { 189 | if (elem.id === selector.substr(1)) { 190 | parents.push(elem); 191 | } 192 | } 193 | 194 | // If selector is a data attribute 195 | if (selectorType === '[') { 196 | if (elem.hasAttribute(selector.substr(1, selector.length - 1))) { 197 | parents.push(elem); 198 | } 199 | } 200 | 201 | // If selector is a tag 202 | if (elem.tagName.toLowerCase() === selector) { 203 | parents.push(elem); 204 | } 205 | 206 | } else { 207 | parents.push(elem); 208 | } 209 | } 210 | 211 | // Return parents if any exist 212 | if (parents.length === 0) { 213 | return null; 214 | } else { 215 | return parents; 216 | } 217 | }; 218 | 219 | export const wrap = function(element, wrapper) { 220 | wrapper = wrapper || document.createElement('div'); 221 | if (element.nextSibling) { 222 | element.parentNode.insertBefore(wrapper, element.nextSibling); 223 | } else { 224 | element.parentNode.appendChild(wrapper); 225 | } 226 | return wrapper.appendChild(element); 227 | }; 228 | 229 | export const getSiblings = function(elem) { 230 | var siblings = []; 231 | var sibling = elem.parentNode.firstChild; 232 | for (; sibling; sibling = sibling.nextSibling) { 233 | if (sibling.nodeType === 1 && sibling !== elem) { 234 | siblings.push(sibling); 235 | } 236 | } 237 | return siblings; 238 | }; 239 | 240 | /** 241 | * Find ancestor in DOM tree 242 | * @param {NodeElement} el Element to start search from 243 | * @param {[type]} cls Class of parent 244 | * @return {NodeElement} Found parent element 245 | */ 246 | export const findAncestor = function(el, cls) { 247 | while ((el = el.parentElement) && !el.classList.contains(cls)); 248 | return el; 249 | }; 250 | 251 | /** 252 | * Debounce an event handler. 253 | * @param {Function} func Function to run after wait 254 | * @param {Number} wait The delay before the function is executed 255 | * @param {Boolean} immediate If passed, trigger the function on the leading edge, instead of the trailing. 256 | * @return {Function} A function will be called after it stops being called for a given delay 257 | */ 258 | export const debounce = function(func, wait, immediate) { 259 | var timeout; 260 | return function() { 261 | var context = this, 262 | args = arguments; 263 | var later = function() { 264 | timeout = null; 265 | if (!immediate) func.apply(context, args); 266 | }; 267 | var callNow = immediate && !timeout; 268 | clearTimeout(timeout); 269 | timeout = setTimeout(later, wait); 270 | if (callNow) func.apply(context, args); 271 | }; 272 | }; 273 | 274 | /** 275 | * Get an element's distance from the top of the page 276 | * @private 277 | * @param {NodeElement} el Element to test for 278 | * @return {Number} Elements Distance from top of page 279 | */ 280 | export const getElemDistance = function(el) { 281 | var location = 0; 282 | if (el.offsetParent) { 283 | do { 284 | location += el.offsetTop; 285 | el = el.offsetParent; 286 | } while (el); 287 | } 288 | return location >= 0 ? location : 0; 289 | }; 290 | 291 | /** 292 | * Determine element height multiplied by any offsets 293 | * @private 294 | * @param {HTMLElement} el Element to test for 295 | * @return {Number} Height of element 296 | */ 297 | export const getElementOffset = function(el, offset) { 298 | var elOffset = offset; 299 | if (elOffset > 1) elOffset = 1; 300 | if (elOffset > 0) elOffset = 0; 301 | 302 | return Math.max(el.offsetHeight * elOffset); 303 | }; 304 | 305 | /** 306 | * Get the next or previous element from a given start point 307 | * @param {HTMLElement} startEl Element to start position from 308 | * @param {String} className The class we will look through 309 | * @param {Number} direction Positive next element, negative previous element 310 | * @return {[HTMLElement} Found element 311 | */ 312 | export const getAdjacentEl = (startEl, className, direction = 1) => { 313 | if (!startEl || !className) return; 314 | 315 | const parent = startEl.parentNode.parentNode; 316 | const children = Array.from(parent.querySelectorAll(className)); 317 | 318 | const startPos = children.indexOf(startEl); 319 | const operatorDirection = direction > 0 ? 1 : -1; 320 | 321 | return children[startPos + operatorDirection]; 322 | }; 323 | 324 | /** 325 | * Get scroll position based on top/bottom position 326 | * @private 327 | * @return {String} Position of scroll 328 | */ 329 | export const getScrollPosition = function(position) { 330 | if (position === 'bottom') { 331 | // Scroll position from the bottom of the viewport 332 | return Math.max((window.scrollY || window.pageYOffset) + (window.innerHeight || document.documentElement.clientHeight)); 333 | } else { 334 | // Scroll position from the top of the viewport 335 | return (window.scrollY || window.pageYOffset); 336 | } 337 | }; 338 | 339 | /** 340 | * Determine whether an element is within the viewport 341 | * @param {HTMLElement} el Element to test 342 | * @return {String} Position of scroll 343 | * @return {Boolean} 344 | */ 345 | export const isInView = function(el, position, offset) { 346 | // If the user has scrolled further than the distance from the element to the top of its parent 347 | return this.getScrollPosition(position) > (this.getElemDistance(el) + this.getElementOffset(el, offset)) ? true : false; 348 | }; 349 | 350 | /** 351 | * Determine whether an element is within 352 | * @param {HTMLElement} el Element to test 353 | * @param {HTMLElement} parent Scrolling parent 354 | * @param {Number} direction Whether element is visible from above or below 355 | * @return {Boolean} 356 | */ 357 | export const isScrolledIntoView = (el, parent, direction = 1) => { 358 | if (!el) return; 359 | 360 | let isVisible; 361 | 362 | if (direction > 0) { 363 | // In view from bottom 364 | isVisible = (parent.scrollTop + parent.offsetHeight) >= (el.offsetTop + el.offsetHeight); 365 | } else { 366 | // In view from top 367 | isVisible = el.offsetTop >= parent.scrollTop; 368 | } 369 | 370 | return isVisible; 371 | }; 372 | 373 | /** 374 | * Remove html tags from a string 375 | * @param {String} Initial string/html 376 | * @return {String} Sanitised string 377 | */ 378 | export const stripHTML = function(html) { 379 | let el = document.createElement("DIV"); 380 | el.innerHTML = html; 381 | return el.textContent || el.innerText || ""; 382 | }; 383 | 384 | /** 385 | * Adds animation to an element and removes it upon animation completion 386 | * @param {Element} el Element to add animation to 387 | * @param {String} animation Animation class to add to element 388 | * @return 389 | */ 390 | export const addAnimation = (el, animation) => { 391 | let animationEvent = whichAnimationEvent(); 392 | 393 | let removeAnimation = () => { 394 | el.classList.remove(animation); 395 | el.removeEventListener(animationEvent, removeAnimation, false); 396 | }; 397 | 398 | el.classList.add(animation); 399 | el.addEventListener(animationEvent, removeAnimation, false); 400 | }; 401 | 402 | 403 | /** 404 | * Get a random number between a range 405 | * @param {Number} min Minimum range 406 | * @param {Number} max Maximum range 407 | * @return {Number} Random number 408 | */ 409 | export const getRandomNumber = function(min, max) { 410 | return Math.floor(Math.random() * (max - min) + min); 411 | }; 412 | 413 | /** 414 | * Turn a string into a node 415 | * @param {String} String to convert 416 | * @return {HTMLElement} Converted node element 417 | */ 418 | export const strToEl = (function() { 419 | var tmpEl = document.createElement('div'); 420 | return function(str) { 421 | var r; 422 | tmpEl.innerHTML = str; 423 | r = tmpEl.children[0]; 424 | 425 | while (tmpEl.firstChild) { 426 | tmpEl.removeChild(tmpEl.firstChild); 427 | } 428 | 429 | return r; 430 | }; 431 | }()); 432 | 433 | /** 434 | * Sets the width of a passed input based on its value 435 | * @return {Number} Width of input 436 | */ 437 | export const getWidthOfInput = (input) => { 438 | const value = input.value || input.placeholder; 439 | let width = input.offsetWidth; 440 | 441 | if (value) { 442 | const testEl = strToEl(`${ value }`); 443 | testEl.style.position = 'absolute'; 444 | testEl.style.padding = '0'; 445 | testEl.style.top = '-9999px'; 446 | testEl.style.left = '-9999px'; 447 | testEl.style.width = 'auto'; 448 | testEl.style.whiteSpace = 'pre'; 449 | 450 | document.body.appendChild(testEl); 451 | 452 | if (value && testEl.offsetWidth !== input.offsetWidth) { 453 | width = testEl.offsetWidth + 4; 454 | } 455 | 456 | document.body.removeChild(testEl); 457 | } 458 | 459 | return `${width}px`; 460 | }; 461 | 462 | export const sortByAlpha = (a, b) => { 463 | const labelA = (a.label || a.value).toLowerCase(); 464 | const labelB = (b.label || b.value).toLowerCase(); 465 | 466 | if (labelA < labelB) return -1; 467 | if (labelA > labelB) return 1; 468 | return 0; 469 | }; 470 | 471 | export const sortByScore = (a, b) => { 472 | return a.score - b.score; 473 | }; 474 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Choices 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 36 | 37 | 38 |
39 |
40 | 44 |

Choices.js is a lightweight, configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency.

45 |

For all config options, visit the GitHub repo.

46 | 47 |
48 | 49 |

Text inputs

50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
75 | 76 |

Multiple select input

77 | 78 | 84 | 85 | 86 | 92 | 93 | 94 | 126 | 127 |

If the following example do not load, the Discogs rate limit has probably been reached. Try again later!

128 | 129 | 130 | 131 | 132 | 133 | 139 | 140 |
141 | 142 |

Single select input

143 | 144 | 150 | 151 |

If the following two examples do not load, the Discogs rate limit has probably been reached. Try again later!

152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 192 | 193 | 194 | 199 | 200 | 201 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 232 | 233 | 234 | 240 | 241 |

Below is an example of how you could have two select inputs depend on eachother. 'Boroughs' will only be enabled if the value of 'States' is 'New York'

242 | 243 | 250 | 251 | 252 | 259 |
260 |
261 | 472 | 473 | 474 | 479 | 480 | 481 | 482 | 483 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Choices.js [![Build Status](https://travis-ci.org/jshjohnson/Choices.svg?branch=master)](https://travis-ci.org/jshjohnson/Choices) 2 | A vanilla, lightweight (~15kb gzipped 🎉), configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency. 3 | 4 | [Demo](https://joshuajohnson.co.uk/Choices/) 5 | 6 | ## TL;DR 7 | * Lightweight 8 | * No jQuery dependency 9 | * Configurable sorting 10 | * Flexible styling 11 | * Fast search/filtering 12 | * Clean API 13 | * Right-to-left support 14 | * Custom templates 15 | 16 | ## Installation 17 | With [NPM](https://www.npmjs.com/package/choices.js): 18 | ```zsh 19 | npm install choices.js --save 20 | ``` 21 | With [Bower](https://bower.io/): 22 | ```zsh 23 | bower install choices.js --save 24 | ``` 25 | Or include Choices directly: 26 | ```html 27 | 28 | 29 | 30 | 31 | 32 | 33 | ``` 34 | ## Setup 35 | 36 | ```js 37 | // Pass multiple elements: 38 | const choices = new Choices(elements); 39 | 40 | // Pass single element: 41 | const choices = new Choices(element); 42 | 43 | // Pass reference 44 | const choices = new Choices('[data-trigger']); 45 | const choices = new Choices('.js-choice'); 46 | 47 | // Pass jQuery element 48 | const choices = new Choices($('.js-choice')[0]); 49 | 50 | // Passing options (with default options) 51 | const choices = new Choices(elements, { 52 | items: [], 53 | choices: [], 54 | maxItemCount: -1, 55 | addItems: true, 56 | removeItems: true, 57 | removeItemButton: false, 58 | editItems: false, 59 | duplicateItems: true, 60 | delimiter: ',', 61 | paste: true, 62 | search: true, 63 | searchFloor: 1, 64 | flip: true, 65 | regexFilter: null, 66 | shouldSort: true, 67 | sortFilter: () => {...}, 68 | sortFields: ['label', 'value'], 69 | placeholder: true, 70 | placeholderValue: null, 71 | prependValue: null, 72 | appendValue: null, 73 | loadingText: 'Loading...', 74 | noResultsText: 'No results found', 75 | noChoicesText: 'No choices to choose from', 76 | itemSelectText: 'Press to select', 77 | addItemText: (value) => { 78 | return `Press Enter to add "${value}"`; 79 | }, 80 | maxItemText: (maxItemCount) => { 81 | return `Only ${maxItemCount} values can be added.`; 82 | }, 83 | classNames: { 84 | containerOuter: 'choices', 85 | containerInner: 'choices__inner', 86 | input: 'choices__input', 87 | inputCloned: 'choices__input--cloned', 88 | list: 'choices__list', 89 | listItems: 'choices__list--multiple', 90 | listSingle: 'choices__list--single', 91 | listDropdown: 'choices__list--dropdown', 92 | item: 'choices__item', 93 | itemSelectable: 'choices__item--selectable', 94 | itemDisabled: 'choices__item--disabled', 95 | itemChoice: 'choices__item--choice', 96 | group: 'choices__group', 97 | groupHeading : 'choices__heading', 98 | button: 'choices__button', 99 | activeState: 'is-active', 100 | focusState: 'is-focused', 101 | openState: 'is-open', 102 | disabledState: 'is-disabled', 103 | highlightedState: 'is-highlighted', 104 | hiddenState: 'is-hidden', 105 | flippedState: 'is-flipped', 106 | loadingState: 'is-loading', 107 | }, 108 | callbackOnInit: null, 109 | callbackOnAddItem: null, 110 | callbackOnRemoveItem: null, 111 | callbackOnHighlightItem: null, 112 | callbackOnUnhighlightItem: null, 113 | callbackOnCreateTemplates: null, 114 | callbackOnChange: null, 115 | callbackOnSearch: null, 116 | }); 117 | ``` 118 | 119 | ## Terminology 120 | | Word | Definition | 121 | | ------ | ---------- | 122 | | Choice | A choice is a value a user can select. A choice would be equivelant to the `` element within a select input. | 123 | | Group | A group is a collection of choices. A group should be seen as equivalent to a `` element within a select input.| 124 | | Item | An item is an inputted value (text input) or a selected choice (select element). In the context of a select element, an item is equivelent to a selected option element: `` whereas in the context of a text input an item is equivelant to ``| 125 | 126 | 127 | ## Configuration options 128 | ### items 129 | **Type:** `Array` **Default:** `[]` 130 | 131 | **Input types affected:** `text` 132 | 133 | **Usage:** Add pre-selected items (see terminology) to text input. 134 | 135 | Pass an array of strings: 136 | 137 | `['value 1', 'value 2', 'value 3']` 138 | 139 | Pass an array of objects: 140 | 141 | ``` 142 | [{ 143 | value: 'Value 1', 144 | label: 'Label 1', 145 | id: 1 146 | }, 147 | { 148 | value: 'Value 2', 149 | label: 'Label 2', 150 | id: 2 151 | }] 152 | ``` 153 | 154 | ### choices 155 | **Type:** `Array` **Default:** `[]` 156 | 157 | **Input types affected:** `select-one`, `select-multiple` 158 | 159 | **Usage:** Add choices (see terminology) to select input. 160 | 161 | Pass an array of objects: 162 | 163 | ``` 164 | [{ 165 | value: 'Option 1', 166 | label: 'Option 1', 167 | selected: true, 168 | disabled: false, 169 | }, 170 | { 171 | value: 'Option 2', 172 | label: 'Option 2', 173 | selected: false, 174 | disabled: true, 175 | }] 176 | ``` 177 | 178 | ### maxItemCount 179 | **Type:** `Number` **Default:** `-1` 180 | 181 | **Input types affected:** `text`, `select-multiple` 182 | 183 | **Usage:** The amount of items a user can input/select ("-1" indicates no limit). 184 | 185 | ### addItems 186 | **Type:** `Boolean` **Default:** `true` 187 | 188 | **Input types affected:** `text` 189 | 190 | **Usage:** Whether a user can add items. 191 | 192 | ### removeItems 193 | **Type:** `Boolean` **Default:** `true` 194 | 195 | **Input types affected:** `text`, `select-multiple` 196 | 197 | **Usage:** Whether a user can remove items. 198 | 199 | ### removeItemButton 200 | **Type:** `Boolean` **Default:** `false` 201 | 202 | **Input types affected:** `text`, `select-one`, `select-multiple` 203 | 204 | **Usage:** Whether each item should have a remove button. 205 | 206 | ### editItems 207 | **Type:** `Boolean` **Default:** `false` 208 | 209 | **Input types affected:** `text` 210 | 211 | **Usage:** Whether a user can edit items. An item's value can be edited by pressing the backspace. 212 | 213 | ### duplicateItems 214 | **Type:** `Boolean` **Default:** `true` 215 | 216 | **Input types affected:** `text`, `select-multiple` 217 | 218 | **Usage:** Whether each inputted/chosen item should be unique. 219 | 220 | ### delimiter 221 | **Type:** `String` **Default:** `,` 222 | 223 | **Input types affected:** `text` 224 | 225 | **Usage:** What divides each value. The default delimiter seperates each value with a comma: `"Value 1, Value 2, Value 3"`. 226 | 227 | ### paste 228 | **Type:** `Boolean` **Default:** `true` 229 | 230 | **Input types affected:** `text`, `select-multiple` 231 | 232 | **Usage:** Whether a user can paste into the input. 233 | 234 | ### search 235 | **Type:** `Boolean` **Default:** `true` 236 | 237 | **Input types affected:** `select-one` 238 | 239 | **Usage:** Whether a user should be allowed to search avaiable choices. Note that multiple select boxes will always show search inputs. 240 | 241 | ### searchFloor 242 | **Type:** `Number` **Default:** `1` 243 | 244 | **Input types affected:** `select-one`, `select-multiple` 245 | 246 | **Usage:** The minimum length a search value should be before choices are searched. 247 | 248 | ### flip 249 | **Type:** `Boolean` **Default:** `true` 250 | 251 | **Input types affected:** `select-one`, `select-multiple` 252 | 253 | **Usage:** Whether the dropdown should appear above the input (rather than beneath) if there is not enough space within the window. 254 | 255 | ### regexFilter 256 | **Type:** `Regex` **Default:** `null` 257 | 258 | **Input types affected:** `text` 259 | 260 | **Usage:** A filter that will need to pass for a user to successfully add an item. 261 | 262 | ### shouldSort 263 | **Type:** `Boolean` **Default:** `true` 264 | 265 | **Input types affected:** `select-one`, `select-multiple` 266 | 267 | **Usage:** Whether choices should be sorted. If false, choices will appear in the order they were given. 268 | 269 | ### sortFilter 270 | **Type:** `Function` **Default:** sortByAlpha 271 | 272 | **Input types affected:** `select-one`, `select-multiple` 273 | 274 | **Usage:** The function that will sort choices before they are displayed (unless a user is searching). By default choices are sorted by alphabetical order. 275 | 276 | **Example:** 277 | 278 | ```js 279 | // Sorting via length of label from largest to smallest 280 | const example = new Choices(element, { 281 | sortFilter: function(a, b) { 282 | return b.label.length - a.label.length; 283 | }, 284 | }; 285 | ``` 286 | 287 | ### sortFields 288 | **Type:** `Array/String` **Default:** `['label', 'value']` 289 | 290 | **Input types affected:**`select-one`, `select-multiple` 291 | 292 | **Usage:** Specify which fields should be used for sorting when a user is searching. If a user is not searching and sorting is enabled, only the choice's label will be sorted. 293 | 294 | ### placeholder 295 | **Type:** `Boolean` **Default:** `true` 296 | 297 | **Input types affected:** `text`, `select-one`, `select-multiple` 298 | 299 | **Usage:** Whether the input should show a placeholder. Used in conjunction with `placeholderValue`. If `placeholder` is set to true and no value is passed to `placeholderValue`, the passed input's placeholder attribute will be used as the placeholder value. 300 | 301 | ### placeholderValue 302 | **Type:** `String` **Default:** `null` 303 | 304 | **Input types affected:** `text`, `select-one`, `select-multiple` 305 | 306 | **Usage:** The value of the inputs placeholder. 307 | 308 | ### prependValue 309 | **Type:** `String` **Default:** `null` 310 | 311 | **Input types affected:** `text`, `select-one`, `select-multiple` 312 | 313 | **Usage:** Prepend a value to each item added/selected. 314 | 315 | ### appendValue 316 | **Type:** `String` **Default:** `null` 317 | 318 | **Input types affected:** `text`, `select-one`, `select-multiple` 319 | 320 | **Usage:** Append a value to each item added/selected. 321 | 322 | ### loadingText 323 | **Type:** `String` **Default:** `Loading...` 324 | 325 | **Input types affected:** `select-one`, `select-multiple` 326 | 327 | **Usage:** The text that is shown whilst choices are being populated via AJAX. 328 | 329 | ### noResultsText 330 | **Type:** `String` **Default:** `No results found` 331 | 332 | **Input types affected:** `select-one`, `select-multiple` 333 | 334 | **Usage:** The text that is shown when a user's search has returned no results. 335 | 336 | ### noChoicesText 337 | **Type:** `String` **Default:** `No choices to choose from` 338 | 339 | **Input types affected:** `select-multiple` 340 | 341 | **Usage:** The text that is shown when a user has selected all possible choices. 342 | 343 | ### itemSelectText 344 | **Type:** `String` **Default:** `Press to select` 345 | 346 | **Input types affected:** `select-multiple`, `select-one` 347 | 348 | **Usage:** The text that is shown when a user hovers over a selectable choice. 349 | 350 | ### addItemText 351 | **Type:** `String/Function` **Default:** `Press Enter to add "${value}"` 352 | 353 | **Input types affected:** `text` 354 | 355 | **Usage:** The text that is shown when a user has inputted a new item but has not pressed the enter key. To access the current input value, pass a function with a `value` argument (see the [default config](https://github.com/jshjohnson/Choices#setup) for an example), otherwise pass a string. 356 | 357 | ### maxItemText 358 | **Type:** `String/Function` **Default:** `Only ${maxItemCount} values can be added.` 359 | 360 | **Input types affected:** `text` 361 | 362 | **Usage:** The text that is shown when a user has focus on the input but has already reached the [max item count](https://github.com/jshjohnson/Choices#maxitemcount). To access the max item count, pass a function with a `maxItemCount` argument (see the [default config](https://github.com/jshjohnson/Choices#setup) for an example), otherwise pass a string. 363 | 364 | ### classNames 365 | **Type:** `Object` **Default:** 366 | 367 | ``` 368 | classNames: { 369 | containerOuter: 'choices', 370 | containerInner: 'choices__inner', 371 | input: 'choices__input', 372 | inputCloned: 'choices__input--cloned', 373 | list: 'choices__list', 374 | listItems: 'choices__list--multiple', 375 | listSingle: 'choices__list--single', 376 | listDropdown: 'choices__list--dropdown', 377 | item: 'choices__item', 378 | itemSelectable: 'choices__item--selectable', 379 | itemDisabled: 'choices__item--disabled', 380 | itemOption: 'choices__item--choice', 381 | group: 'choices__group', 382 | groupHeading : 'choices__heading', 383 | button: 'choices__button', 384 | activeState: 'is-active', 385 | focusState: 'is-focused', 386 | openState: 'is-open', 387 | disabledState: 'is-disabled', 388 | highlightedState: 'is-highlighted', 389 | hiddenState: 'is-hidden', 390 | flippedState: 'is-flipped', 391 | selectedState: 'is-highlighted', 392 | } 393 | ``` 394 | 395 | **Input types affected:** `text`, `select-one`, `select-multiple` 396 | 397 | **Usage:** Classes added to HTML generated by Choices. By default classnames follow the [BEM](http://csswizardry.com/2013/01/mindbemding-getting-your-head-round-bem-syntax/) notation. 398 | 399 | ## Callbacks 400 | **Note:** For each callback, `this` refers to the current instance of Choices. This can be useful if you need access to methods (`this.disable()`) or the config object (`this.config`). 401 | 402 | ### callbackOnInit 403 | **Type:** `Function` **Default:** `null` 404 | 405 | **Input types affected:** `text`, `select-one`, `select-multiple` 406 | 407 | **Usage:** Function to run once Choices initialises. 408 | 409 | ### callbackOnAddItem 410 | **Type:** `Function` **Default:** `null` **Arguments:** `id, value, groupValue` 411 | 412 | **Input types affected:** `text`, `select-one`, `select-multiple` 413 | 414 | **Usage:** Function to run each time an item is added (programmatically or by the user). 415 | 416 | **Example:** 417 | 418 | ```js 419 | const example = new Choices(element, { 420 | callbackOnAddItem: (id, value, groupValue) => { 421 | // do something creative here... 422 | }, 423 | }; 424 | ``` 425 | 426 | ### callbackOnRemoveItem 427 | **Type:** `Function` **Default:** `null` **Arguments:** `id, value, groupValue` 428 | 429 | **Input types affected:** `text`, `select-one`, `select-multiple` 430 | 431 | **Usage:** Function to run each time an item is removed (programmatically or by the user). 432 | 433 | ### callbackOnHighlightItem 434 | **Type:** `Function` **Default:** `null` **Arguments:** `id, value, groupValue` 435 | 436 | **Input types affected:** `text`, `select-multiple` 437 | 438 | **Usage:** Function to run each time an item is highlighted. 439 | 440 | ### callbackOnUnhighlightItem 441 | **Type:** `Function` **Default:** `null` **Arguments:** `id, value, groupValue` 442 | 443 | **Input types affected:** `text`, `select-multiple` 444 | 445 | **Usage:** Function to run each time an item is unhighlighted. 446 | 447 | ### callbackOnCreateTemplates 448 | **Type:** `Function` **Default:** `null` **Arguments:** `template` 449 | 450 | **Input types affected:** `text`, `select-one`, `select-multiple` 451 | 452 | **Usage:** Function to run on template creation. Through this callback it is possible to provide custom templates for the various components of Choices (see terminology). For Choices to work with custom templates, it is important you maintain the various data attributes defined [here](https://github.com/jshjohnson/Choices/blob/67f29c286aa21d88847adfcd6304dc7d068dc01f/assets/scripts/src/choices.js#L1993-L2067). 453 | 454 | **Example:** 455 | 456 | ```js 457 | const example = new Choices(element, { 458 | callbackOnCreateTemplates: function (template) { 459 | var classNames = this.config.classNames; 460 | return { 461 | item: (data) => { 462 | return template(` 463 |
464 | ${data.label} 465 |
466 | `); 467 | }, 468 | choice: (data) => { 469 | return template(` 470 |
0 ? 'role="treeitem"' : 'role="option"'}> 471 | ${data.label} 472 |
473 | `); 474 | }, 475 | }; 476 | } 477 | }); 478 | ``` 479 | 480 | ### callbackOnChange 481 | **Type:** `Function` **Default:** `null` **Arguments:** `value` 482 | 483 | **Input types affected:** `text`, `select-one`, `select-multiple` 484 | 485 | **Usage:** Function to run each time an item is added/removed by a user. 486 | 487 | ### callbackOnSearch 488 | **Type:** `Function` **Default:** `null` **Arguments:** `value` 489 | 490 | **Input types affected:** `select-one`, `select-multiple` 491 | 492 | **Usage:** Function to run when a user types into an input to search choices. 493 | 494 | ## Methods 495 | Methods can be called either directly or by chaining: 496 | 497 | ```js 498 | // Calling a method by chaining 499 | const choices = new Choices(element, { 500 | addItems: false, 501 | removeItems: false, 502 | }).setValue(['Set value 1', 'Set value 2']).disable(); 503 | 504 | // Calling a method directly 505 | const choices = new Choices(element, { 506 | addItems: false, 507 | removeItems: false, 508 | }); 509 | 510 | choices.setValue(['Set value 1', 'Set value 2']) 511 | choices.disable(); 512 | ``` 513 | 514 | ### destroy(); 515 | **Input types affected:** `text`, `select-multiple`, `select-one` 516 | 517 | **Usage:** Kills the instance of Choices, removes all event listeners and returns passed input to its initial state. 518 | 519 | ### init(); 520 | **Input types affected:** `text`, `select-multiple`, `select-one` 521 | 522 | **Usage:** Creates a new instance of Choices, adds event listeners, creates templates and renders a Choices element to the DOM. 523 | 524 | **Note:** This is called implicitly when a new instance of Choices is created. This would be used after a Choices instance had already been destroyed (using `destroy()`). 525 | 526 | ### highlightAll(); 527 | **Input types affected:** `text`, `select-multiple` 528 | 529 | **Usage:** Highlight each chosen item (selected items can be removed). 530 | 531 | 532 | ### unhighlightAll(); 533 | **Input types affected:** `text`, `select-multiple` 534 | 535 | **Usage:** Un-highlight each chosen item. 536 | 537 | 538 | ### removeItemsByValue(value); 539 | **Input types affected:** `text`, `select-multiple` 540 | 541 | **Usage:** Remove each item by a given value. 542 | 543 | 544 | ### removeActiveItems(excludedId); 545 | **Input types affected:** `text`, `select-multiple` 546 | 547 | **Usage:** Remove each selectable item. 548 | 549 | 550 | ### removeHighlightedItems(); 551 | **Input types affected:** `text`, `select-multiple` 552 | 553 | **Usage:** Remove each item the user has selected. 554 | 555 | 556 | ### showDropdown(); 557 | **Input types affected:** `select-one`, `select-multiple` 558 | 559 | **Usage:** Show option list dropdown (only affects select inputs). 560 | 561 | 562 | ### hideDropdown(); 563 | **Input types affected:** `text`, `select-multiple` 564 | 565 | **Usage:** Hide option list dropdown (only affects select inputs). 566 | 567 | 568 | ### toggleDropdown(); 569 | **Input types affected:** `text`, `select-multiple` 570 | 571 | **Usage:** Toggle dropdown between showing/hidden. 572 | 573 | ### setChoices(choices, value, label, replaceChoices); 574 | **Input types affected:** `select-one`, `select-multiple` 575 | 576 | **Usage:** Set choices of select input via an array of objects, a value name and a label name. This behaves the same as passing items via the `choices` option but can be called after initialising Choices. This can also be used to add groups of choices (see example 2); Optionally pass a true `replaceChoices` value to remove any existing choices. 577 | 578 | **Example 1:** 579 | 580 | ```js 581 | const example = new Choices(element); 582 | 583 | example.setChoices([ 584 | {value: 'One', label: 'Label One', disabled: true}, 585 | {value: 'Two', label: 'Label Two' selected: true}, 586 | {value: 'Three', label: 'Label Three'}, 587 | ], 'value', 'label', false); 588 | ``` 589 | 590 | **Example 2:** 591 | 592 | ```js 593 | const example = new Choices(element); 594 | 595 | example.setChoices([{ 596 | label: 'Group one', 597 | id: 1, 598 | disabled: false, 599 | choices: [ 600 | {value: 'Child One', label: 'Child One', selected: true}, 601 | {value: 'Child Two', label: 'Child Two', disabled: true}, 602 | {value: 'Child Three', label: 'Child Three'}, 603 | ] 604 | }, 605 | { 606 | label: 'Group two', 607 | id: 2, 608 | disabled: false, 609 | choices: [ 610 | {value: 'Child Four', label: 'Child Four', disabled: true}, 611 | {value: 'Child Five', label: 'Child Five'}, 612 | {value: 'Child Six', label: 'Child Six'}, 613 | ] 614 | }], 'value', 'label', false); 615 | ``` 616 | 617 | ### getValue(valueOnly) 618 | **Input types affected:** `text`, `select-one`, `select-multiple` 619 | 620 | **Usage:** Get value(s) of input (i.e. inputted items (text) or selected choices (select)). Optionally pass an argument of `true` to only return values rather than value objects. 621 | 622 | **Example:** 623 | 624 | ```js 625 | const example = new Choices(element); 626 | const values = example.getValue(true); // returns ['value 1', 'value 2']; 627 | const valueArray = example.getValue(); // returns [{ active: true, choiceId: 1, highlighted: false, id: 1, label: 'Label 1', value: 'Value 1'}, { active: true, choiceId: 2, highlighted: false, id: 2, label: 'Label 2', value: 'Value 2'}]; 628 | ``` 629 | 630 | ### setValue(args); 631 | **Input types affected:** `text` 632 | 633 | **Usage:** Set value of input based on an array of objects or strings. This behaves exactly the same as passing items via the `items` option but can be called after initialising Choices. 634 | 635 | **Example:** 636 | 637 | ```js 638 | const example = new Choices(element); 639 | 640 | // via an array of objects 641 | example.setValue([ 642 | {value: 'One', label: 'Label One'}, 643 | {value: 'Two', label: 'Label Two'}, 644 | {value: 'Three', label: 'Label Three'}, 645 | ]); 646 | 647 | // or via an array of strings 648 | example.setValue(['Four','Five','Six']); 649 | ``` 650 | 651 | ### setValueByChoice(value); 652 | **Input types affected:** `select-one`, `select-multiple` 653 | 654 | **Usage:** Set value of input based on existing Choice. 655 | 656 | **Example:** 657 | 658 | ```js 659 | const example = new Choices(element, { 660 | choices: [ 661 | {value: 'One', label: 'Label One'}, 662 | {value: 'Two', label: 'Label Two', disabled: true}, 663 | {value: 'Three', label: 'Label Three'}, 664 | ], 665 | }); 666 | 667 | example.setValueByChoice('Two'); // Choice with value of 'Two' has now been selected. 668 | ``` 669 | 670 | ### clearStore(); 671 | **Input types affected:** `text`, `select-one`, `select-multiple` 672 | 673 | **Usage:** Removes all items, choices and groups. Use with caution. 674 | 675 | 676 | ### clearInput(); 677 | **Input types affected:** `text` 678 | 679 | **Usage:** Clear input of any user inputted text. 680 | 681 | 682 | ### disable(); 683 | **Input types affected:** `text`, `select-one`, `select-multiple` 684 | 685 | **Usage:** Disables input from accepting new value/sselecting further choices. 686 | 687 | ### enable(); 688 | **Input types affected:** `text`, `select-one`, `select-multiple` 689 | 690 | **Usage:** Enables input to accept new values/select further choices. 691 | 692 | 693 | ### ajax(fn); 694 | **Input types affected:** `select-one`, `select-multiple` 695 | 696 | **Usage:** Populate choices/groups via a callback. 697 | 698 | **Example:** 699 | 700 | ```js 701 | var example = new Choices(element); 702 | 703 | example.ajax(function(callback) { 704 | fetch(url) 705 | .then(function(response) { 706 | response.json().then(function(data) { 707 | callback(data, 'value', 'label'); 708 | }); 709 | }) 710 | .catch(function(error) { 711 | console.log(error); 712 | }); 713 | }); 714 | ``` 715 | 716 | 717 | ## Browser compatibility 718 | Choices is compiled using [Babel](https://babeljs.io/) to enable support for [ES5 browsers](http://caniuse.com/#feat=es5). If you need to support a browser that does not support one of the features listed below, I suggest including a polyfill from the very good [polyfill.io](https://cdn.polyfill.io/v2/docs/): 719 | 720 | **Polyfill example used for the demo:** 721 | 722 | ```html 723 | 724 | ``` 725 | 726 | **Features used in Choices:** 727 | 728 | * Array.prototype.forEach 729 | * Array.prototype.map 730 | * Array.prototype.find 731 | * Array.prototype.some 732 | * Array.prototype.reduce 733 | * Array.prototype.indexOf 734 | * Element.prototype.classList 735 | * window.requestAnimationFrame 736 | 737 | ## Development 738 | To setup a local environment: clone this repo, navigate into it's directory in a terminal window and run the following command: 739 | 740 | ```npm install``` 741 | 742 | ### NPM tasks 743 | | Task | Usage | 744 | | ------------------- | ------------------------------------------------------------ | 745 | | `npm start` | Fire up local server for development | 746 | | `npm run js:build` | Compile Choices to an uglified JavaScript file | 747 | | `npm run css:watch` | Watch SCSS files for changes. On a change, run build process | 748 | | `npm run css:build` | Compile, minify and prefix SCSS files to CSS | 749 | 750 | ## Contributions 751 | In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using npm scripts...bla bla bla 752 | 753 | ## License 754 | MIT License 755 | 756 | ## Misc 757 | Thanks to [@mikefrancis](https://github.com/mikefrancis/) for [sending me on a hunt](https://twitter.com/_mikefrancis/status/701797835826667520) for a non-jQuery solution for select boxes that eventually led to this being built! 758 | -------------------------------------------------------------------------------- /tests/spec/choices_spec.js: -------------------------------------------------------------------------------- 1 | import 'whatwg-fetch'; 2 | import 'es6-promise'; 3 | import Choices from '../../assets/scripts/src/choices.js'; 4 | 5 | describe('Choices', () => { 6 | 7 | afterEach(function() { 8 | this.choices.destroy(); 9 | }); 10 | 11 | describe('should initialize Choices', () => { 12 | 13 | beforeEach(function() { 14 | this.input = document.createElement('input'); 15 | this.input.type = "text"; 16 | this.input.className = 'js-choices'; 17 | 18 | document.body.appendChild(this.input); 19 | this.choices = new Choices(this.input); 20 | }); 21 | 22 | it('should be defined', function() { 23 | expect(this.choices).toBeDefined(); 24 | }); 25 | 26 | it('should have initalised', function() { 27 | expect(this.choices.initialised).toBe(true); 28 | }); 29 | 30 | it('should not re-initialise if passed element again', function() { 31 | const reinitialise = new Choices(this.choices.passedElement); 32 | spyOn(reinitialise, '_createTemplates'); 33 | expect(reinitialise._createTemplates).not.toHaveBeenCalled(); 34 | }) 35 | 36 | it('should have a blank state', function() { 37 | expect(this.choices.currentState.items.length).toEqual(0); 38 | expect(this.choices.currentState.groups.length).toEqual(0); 39 | expect(this.choices.currentState.choices.length).toEqual(0); 40 | }); 41 | 42 | it('should have config options', function() { 43 | expect(this.choices.config.items).toEqual(jasmine.any(Array)); 44 | expect(this.choices.config.choices).toEqual(jasmine.any(Array)); 45 | expect(this.choices.config.maxItemCount).toEqual(jasmine.any(Number)); 46 | expect(this.choices.config.addItems).toEqual(jasmine.any(Boolean)); 47 | expect(this.choices.config.removeItems).toEqual(jasmine.any(Boolean)); 48 | expect(this.choices.config.removeItemButton).toEqual(jasmine.any(Boolean)); 49 | expect(this.choices.config.editItems).toEqual(jasmine.any(Boolean)); 50 | expect(this.choices.config.duplicateItems).toEqual(jasmine.any(Boolean)); 51 | expect(this.choices.config.delimiter).toEqual(jasmine.any(String)); 52 | expect(this.choices.config.paste).toEqual(jasmine.any(Boolean)); 53 | expect(this.choices.config.search).toEqual(jasmine.any(Boolean)); 54 | expect(this.choices.config.searchFloor).toEqual(jasmine.any(Number)); 55 | expect(this.choices.config.flip).toEqual(jasmine.any(Boolean)); 56 | expect(this.choices.config.regexFilter).toEqual(null); 57 | expect(this.choices.config.sortFilter).toEqual(jasmine.any(Function)); 58 | expect(this.choices.config.sortFields).toEqual(jasmine.any(Array) || jasmine.any(String)); 59 | expect(this.choices.config.shouldSort).toEqual(jasmine.any(Boolean)); 60 | expect(this.choices.config.placeholder).toEqual(jasmine.any(Boolean)); 61 | expect(this.choices.config.placeholderValue).toEqual(null); 62 | expect(this.choices.config.prependValue).toEqual(null); 63 | expect(this.choices.config.appendValue).toEqual(null); 64 | expect(this.choices.config.loadingText).toEqual(jasmine.any(String)); 65 | expect(this.choices.config.noResultsText).toEqual(jasmine.any(String)); 66 | expect(this.choices.config.noChoicesText).toEqual(jasmine.any(String)); 67 | expect(this.choices.config.itemSelectText).toEqual(jasmine.any(String)); 68 | expect(this.choices.config.classNames).toEqual(jasmine.any(Object)); 69 | expect(this.choices.config.callbackOnInit).toEqual(null); 70 | expect(this.choices.config.callbackOnAddItem).toEqual(null); 71 | expect(this.choices.config.callbackOnRemoveItem).toEqual(null); 72 | expect(this.choices.config.callbackOnHighlightItem).toEqual(null); 73 | expect(this.choices.config.callbackOnUnhighlightItem).toEqual(null); 74 | expect(this.choices.config.callbackOnChange).toEqual(null); 75 | expect(this.choices.config.callbackOnSearch).toEqual(null); 76 | }); 77 | 78 | it('should expose public methods', function() { 79 | expect(this.choices.init).toEqual(jasmine.any(Function)); 80 | expect(this.choices.destroy).toEqual(jasmine.any(Function)); 81 | expect(this.choices.render).toEqual(jasmine.any(Function)); 82 | expect(this.choices.renderGroups).toEqual(jasmine.any(Function)); 83 | expect(this.choices.renderItems).toEqual(jasmine.any(Function)); 84 | expect(this.choices.renderChoices).toEqual(jasmine.any(Function)); 85 | expect(this.choices.highlightItem).toEqual(jasmine.any(Function)); 86 | expect(this.choices.unhighlightItem).toEqual(jasmine.any(Function)); 87 | expect(this.choices.highlightAll).toEqual(jasmine.any(Function)); 88 | expect(this.choices.unhighlightAll).toEqual(jasmine.any(Function)); 89 | expect(this.choices.removeItemsByValue).toEqual(jasmine.any(Function)); 90 | expect(this.choices.removeActiveItems).toEqual(jasmine.any(Function)); 91 | expect(this.choices.removeHighlightedItems).toEqual(jasmine.any(Function)); 92 | expect(this.choices.showDropdown).toEqual(jasmine.any(Function)); 93 | expect(this.choices.hideDropdown).toEqual(jasmine.any(Function)); 94 | expect(this.choices.toggleDropdown).toEqual(jasmine.any(Function)); 95 | expect(this.choices.getValue).toEqual(jasmine.any(Function)); 96 | expect(this.choices.setValue).toEqual(jasmine.any(Function)); 97 | expect(this.choices.setValueByChoice).toEqual(jasmine.any(Function)); 98 | expect(this.choices.setChoices).toEqual(jasmine.any(Function)); 99 | expect(this.choices.disable).toEqual(jasmine.any(Function)); 100 | expect(this.choices.enable).toEqual(jasmine.any(Function)); 101 | expect(this.choices.ajax).toEqual(jasmine.any(Function)); 102 | expect(this.choices.clearStore).toEqual(jasmine.any(Function)); 103 | expect(this.choices.clearInput).toEqual(jasmine.any(Function)); 104 | }); 105 | 106 | it('should hide passed input', function() { 107 | expect(this.choices.passedElement.style.display).toEqual('none'); 108 | }); 109 | 110 | it('should create an outer container', function() { 111 | expect(this.choices.containerOuter).toEqual(jasmine.any(HTMLElement)); 112 | }); 113 | 114 | it('should create an inner container', function() { 115 | expect(this.choices.containerInner).toEqual(jasmine.any(HTMLElement)); 116 | }); 117 | 118 | it('should create a choice list', function() { 119 | expect(this.choices.choiceList).toEqual(jasmine.any(HTMLElement)); 120 | }); 121 | 122 | it('should create an item list', function() { 123 | expect(this.choices.itemList).toEqual(jasmine.any(HTMLElement)); 124 | }); 125 | 126 | it('should create an input', function() { 127 | expect(this.choices.input).toEqual(jasmine.any(HTMLElement)); 128 | }); 129 | 130 | // it('should create a dropdown', function() { 131 | // expect(this.choices.dropdown).toEqual(jasmine.any(HTMLElement)); 132 | // }); 133 | }); 134 | 135 | describe('should accept text inputs', function() { 136 | beforeEach(function() { 137 | this.input = document.createElement('input'); 138 | this.input.type = "text"; 139 | this.input.className = 'js-choices'; 140 | this.input.placeholder = 'Placeholder text'; 141 | 142 | document.body.appendChild(this.input); 143 | }); 144 | 145 | it('should accept a user inputted value', function() { 146 | this.choices = new Choices(this.input); 147 | 148 | this.choices.input.focus(); 149 | this.choices.input.value = 'test'; 150 | 151 | this.choices._onKeyDown({ 152 | target: this.choices.input, 153 | keyCode: 13, 154 | ctrlKey: false 155 | }); 156 | 157 | expect(this.choices.currentState.items[0].value).toContain(this.choices.input.value); 158 | }); 159 | 160 | it('should copy the passed placeholder to the cloned input', function() { 161 | this.choices = new Choices(this.input); 162 | 163 | expect(this.choices.input.placeholder).toEqual(this.input.placeholder); 164 | }); 165 | 166 | it('should not allow duplicates if duplicateItems is false', function() { 167 | this.choices = new Choices(this.input, { 168 | duplicateItems: false, 169 | items: ['test 1'], 170 | }); 171 | 172 | this.choices.input.focus(); 173 | this.choices.input.value = 'test 1'; 174 | 175 | this.choices._onKeyDown({ 176 | target: this.choices.input, 177 | keyCode: 13, 178 | ctrlKey: false 179 | }); 180 | 181 | expect(this.choices.currentState.items[this.choices.currentState.items.length - 1]).not.toContain(this.choices.input.value); 182 | }); 183 | 184 | it('should filter input if regexFilter is passed', function() { 185 | this.choices = new Choices(this.input, { 186 | regexFilter: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, 187 | }); 188 | 189 | this.choices.input.focus(); 190 | this.choices.input.value = 'josh@joshuajohnson.co.uk'; 191 | 192 | this.choices._onKeyDown({ 193 | target: this.choices.input, 194 | keyCode: 13, 195 | ctrlKey: false 196 | }); 197 | 198 | this.choices.input.focus(); 199 | this.choices.input.value = 'not an email address'; 200 | 201 | this.choices._onKeyDown({ 202 | target: this.choices.input, 203 | keyCode: 13, 204 | ctrlKey: false 205 | }); 206 | 207 | const lastItem = this.choices.currentState.items[this.choices.currentState.items.length - 1]; 208 | 209 | expect(lastItem.value).toEqual('josh@joshuajohnson.co.uk'); 210 | expect(lastItem.value).not.toEqual('not an email address'); 211 | }); 212 | 213 | it('should prepend and append values if passed', function() { 214 | this.choices = new Choices(this.input, { 215 | prependValue: 'item-', 216 | appendValue: '-value', 217 | }); 218 | 219 | this.choices.input.focus(); 220 | this.choices.input.value = 'test'; 221 | 222 | this.choices._onKeyDown({ 223 | target: this.choices.input, 224 | keyCode: 13, 225 | ctrlKey: false 226 | }); 227 | 228 | const lastItem = this.choices.currentState.items[this.choices.currentState.items.length - 1]; 229 | 230 | expect(lastItem.value).not.toEqual('test'); 231 | expect(lastItem.value).toEqual('item-test-value'); 232 | }); 233 | }); 234 | 235 | describe('should accept single select inputs', function() { 236 | beforeEach(function() { 237 | this.input = document.createElement('select'); 238 | this.input.className = 'js-choices'; 239 | this.input.placeholder = 'Placeholder text'; 240 | 241 | for (let i = 1; i < 4; i++) { 242 | const option = document.createElement('option'); 243 | 244 | option.value = `Value ${i}`; 245 | option.innerHTML = `Label ${i}`; 246 | 247 | this.input.appendChild(option); 248 | } 249 | 250 | document.body.appendChild(this.input); 251 | }); 252 | 253 | it('should open the choice list on focussing', function() { 254 | this.choices = new Choices(this.input); 255 | this.choices.input.focus(); 256 | expect(this.choices.dropdown.classList).toContain(this.choices.config.classNames.activeState); 257 | }); 258 | 259 | it('should select the first choice', function() { 260 | this.choices = new Choices(this.input); 261 | expect(this.choices.currentState.items[0].value).toContain('Value 1'); 262 | }); 263 | 264 | it('should highlight the choices on keydown', function() { 265 | this.choices = new Choices(this.input); 266 | this.choices.input.focus(); 267 | 268 | for (var i = 0; i < 2; i++) { 269 | // Key down to third choice 270 | this.choices._onKeyDown({ 271 | target: this.choices.input, 272 | keyCode: 40, 273 | ctrlKey: false, 274 | preventDefault: () => {} 275 | }); 276 | } 277 | 278 | expect(this.choices.highlightPosition).toBe(2); 279 | }); 280 | 281 | it('should select choice on enter key press', function() { 282 | this.choices = new Choices(this.input); 283 | this.choices.input.focus(); 284 | 285 | // Key down to second choice 286 | this.choices._onKeyDown({ 287 | target: this.choices.input, 288 | keyCode: 40, 289 | ctrlKey: false, 290 | preventDefault: () => {} 291 | }); 292 | 293 | // Key down to select choice 294 | this.choices._onKeyDown({ 295 | target: this.choices.input, 296 | keyCode: 13, 297 | ctrlKey: false, 298 | preventDefault: () => {} 299 | }); 300 | 301 | expect(this.choices.currentState.items.length).toBe(2); 302 | }); 303 | 304 | it('should trigger a change callback on selection', function() { 305 | this.choices = new Choices(this.input); 306 | spyOn(this.choices.config, 'callbackOnChange'); 307 | this.choices.input.focus(); 308 | 309 | // Key down to second choice 310 | this.choices._onKeyDown({ 311 | target: this.choices.input, 312 | keyCode: 40, 313 | ctrlKey: false, 314 | preventDefault: () => {} 315 | }); 316 | 317 | // Key down to select choice 318 | this.choices._onKeyDown({ 319 | target: this.choices.input, 320 | keyCode: 13, 321 | ctrlKey: false, 322 | preventDefault: () => {} 323 | }); 324 | 325 | expect(this.choices.config.callbackOnChange).toHaveBeenCalledWith(jasmine.any(String)); 326 | }); 327 | 328 | it('should open the dropdown on click', function() { 329 | this.choices = new Choices(this.input); 330 | const container = this.choices.containerOuter; 331 | this.choices._onClick({ 332 | target: container, 333 | ctrlKey: false, 334 | preventDefault: () => {} 335 | }); 336 | 337 | expect(document.activeElement === this.choices.input && container.classList.contains('is-open')).toBe(true); 338 | }); 339 | 340 | it('should close the dropdown on double click', function() { 341 | this.choices = new Choices(this.input); 342 | const container = this.choices.containerOuter; 343 | 344 | this.choices._onClick({ 345 | target: container, 346 | ctrlKey: false, 347 | preventDefault: () => {} 348 | }); 349 | 350 | this.choices._onClick({ 351 | target: container, 352 | ctrlKey: false, 353 | preventDefault: () => {} 354 | }); 355 | 356 | expect(document.activeElement === this.choices.input && container.classList.contains('is-open')).toBe(false); 357 | }); 358 | 359 | it('should filter choices when searching', function() { 360 | this.choices = new Choices(this.input); 361 | this.choices.input.focus(); 362 | this.choices.input.value = 'Value 3'; 363 | 364 | // Key down to search 365 | this.choices._onKeyUp({ 366 | target: this.choices.input, 367 | keyCode: 13, 368 | ctrlKey: false 369 | }); 370 | 371 | const mostAccurateResult = this.choices.currentState.choices[0]; 372 | 373 | expect(this.choices.isSearching && mostAccurateResult.value === 'Value 3').toBeTruthy; 374 | }); 375 | 376 | it('shouldn\'t sort choices if shouldSort is false', function() { 377 | this.choices = new Choices(this.input, { 378 | shouldSort: false, 379 | choices: [{ 380 | value: 'Value 5', 381 | label: 'Label Five' 382 | }, { 383 | value: 'Value 6', 384 | label: 'Label Six' 385 | }, { 386 | value: 'Value 7', 387 | label: 'Label Seven' 388 | }, ], 389 | }); 390 | 391 | expect(this.choices.currentState.choices[0].value).toEqual('Value 5'); 392 | }); 393 | 394 | it('should sort choices if shouldSort is false', function() { 395 | this.choices = new Choices(this.input, { 396 | shouldSort: true, 397 | choices: [{ 398 | value: 'Value 5', 399 | label: 'Label Five' 400 | }, { 401 | value: 'Value 6', 402 | label: 'Label Six' 403 | }, { 404 | value: 'Value 7', 405 | label: 'Label Seven' 406 | }, ], 407 | }); 408 | 409 | expect(this.choices.currentState.choices[0].value).toEqual('Value 1'); 410 | }); 411 | }); 412 | 413 | describe('should accept multiple select inputs', function() { 414 | beforeEach(function() { 415 | this.input = document.createElement('select'); 416 | this.input.className = 'js-choices'; 417 | this.input.setAttribute('multiple', ''); 418 | 419 | for (let i = 1; i < 4; i++) { 420 | const option = document.createElement('option'); 421 | 422 | option.value = `Value ${i}`; 423 | option.innerHTML = `Value ${i}`; 424 | 425 | if (i % 2) { 426 | option.selected = true; 427 | } 428 | 429 | this.input.appendChild(option); 430 | } 431 | 432 | document.body.appendChild(this.input); 433 | 434 | this.choices = new Choices(this.input, { 435 | placeholderValue: 'Placeholder text', 436 | choices: [{ 437 | value: 'One', 438 | label: 'Label One', 439 | selected: true, 440 | disabled: false 441 | }, { 442 | value: 'Two', 443 | label: 'Label Two', 444 | disabled: true 445 | }, { 446 | value: 'Three', 447 | label: 'Label Three' 448 | }, ], 449 | }); 450 | }); 451 | 452 | it('should add any pre-defined values', function() { 453 | expect(this.choices.currentState.items.length).toBeGreaterThan(1); 454 | }); 455 | 456 | it('should add options defined in the config + pre-defined options', function() { 457 | expect(this.choices.currentState.choices.length).toEqual(6); 458 | }); 459 | 460 | it('should add a placeholder defined in the config to the search input', function() { 461 | expect(this.choices.input.placeholder).toEqual('Placeholder text'); 462 | }); 463 | }); 464 | 465 | describe('should handle public methods on select input types', function() { 466 | beforeEach(function() { 467 | this.input = document.createElement('select'); 468 | this.input.className = 'js-choices'; 469 | this.input.multiple = true; 470 | this.input.placeholder = 'Placeholder text'; 471 | 472 | for (let i = 1; i < 10; i++) { 473 | const option = document.createElement('option'); 474 | 475 | option.value = `Value ${i}`; 476 | option.innerHTML = `Value ${i}`; 477 | 478 | if (i % 2) { 479 | option.selected = true; 480 | } 481 | 482 | this.input.appendChild(option); 483 | } 484 | 485 | document.body.appendChild(this.input); 486 | this.choices = new Choices(this.input); 487 | }); 488 | 489 | it('should handle highlightItem()', function() { 490 | const items = this.choices.currentState.items; 491 | const randomItem = items[Math.floor(Math.random() * items.length)]; 492 | 493 | this.choices.highlightItem(randomItem); 494 | 495 | expect(randomItem.highlighted).toBe(true); 496 | }); 497 | 498 | it('should handle unhighlightItem()', function() { 499 | const items = this.choices.currentState.items; 500 | const randomItem = items[Math.floor(Math.random() * items.length)]; 501 | 502 | this.choices.unhighlightItem(randomItem); 503 | 504 | expect(randomItem.highlighted).toBe(false); 505 | }); 506 | 507 | it('should handle highlightAll()', function() { 508 | const items = this.choices.currentState.items; 509 | 510 | this.choices.highlightAll(); 511 | 512 | const unhighlightedItems = items.some((item) => item.highlighted === false); 513 | 514 | expect(unhighlightedItems).toBe(false); 515 | }); 516 | 517 | it('should handle unhighlightAll()', function() { 518 | const items = this.choices.currentState.items; 519 | 520 | this.choices.unhighlightAll(); 521 | 522 | const highlightedItems = items.some((item) => item.highlighted === true); 523 | 524 | expect(highlightedItems).toBe(false); 525 | }); 526 | 527 | it('should handle removeHighlightedItems()', function() { 528 | const items = this.choices.currentState.items; 529 | this.choices.highlightAll(); 530 | this.choices.removeHighlightedItems(); 531 | 532 | const activeItems = items.some((item) => item.active === true); 533 | 534 | expect(activeItems).toBe(false); 535 | }); 536 | 537 | it('should handle showDropdown()', function() { 538 | this.choices.showDropdown(); 539 | const hasOpenState = this.choices.containerOuter.classList.contains(this.choices.config.classNames.openState); 540 | const hasAttr = this.choices.containerOuter.getAttribute('aria-expanded') === 'true'; 541 | const hasActiveState = this.choices.dropdown.classList.contains(this.choices.config.classNames.activeState); 542 | expect(hasOpenState && hasAttr && hasActiveState).toBe(true); 543 | }); 544 | 545 | it('should handle hideDropdown()', function() { 546 | this.choices.showDropdown(); 547 | this.choices.hideDropdown(); 548 | const hasOpenState = this.choices.containerOuter.classList.contains(this.choices.config.classNames.openState); 549 | const hasAttr = this.choices.containerOuter.getAttribute('aria-expanded') === 'true'; 550 | const hasActiveState = this.choices.dropdown.classList.contains(this.choices.config.classNames.activeState); 551 | 552 | expect(hasOpenState && hasAttr && hasActiveState).toBe(false); 553 | }); 554 | 555 | it('should handle toggleDropdown()', function() { 556 | spyOn(this.choices, 'hideDropdown'); 557 | this.choices.showDropdown(); 558 | this.choices.toggleDropdown(); 559 | expect(this.choices.hideDropdown).toHaveBeenCalled(); 560 | }); 561 | 562 | it('should handle hideDropdown()', function() { 563 | this.choices.showDropdown(); 564 | expect(this.choices.containerOuter.classList).toContain(this.choices.config.classNames.openState); 565 | }); 566 | 567 | it('should handle getValue()', function() { 568 | const valueObjects = this.choices.getValue(); 569 | const valueStrings = this.choices.getValue(true); 570 | 571 | expect(valueStrings[0]).toEqual(jasmine.any(String)); 572 | expect(valueObjects[0]).toEqual(jasmine.any(Object)); 573 | expect(valueObjects).toEqual(jasmine.any(Array)); 574 | expect(valueObjects.length).toEqual(5); 575 | }); 576 | 577 | it('should handle setValue()', function() { 578 | this.choices.setValue(['Set value 1', 'Set value 2', 'Set value 3']); 579 | const valueStrings = this.choices.getValue(true); 580 | 581 | expect(valueStrings[valueStrings.length - 1]).toBe('Set value 3'); 582 | expect(valueStrings[valueStrings.length - 2]).toBe('Set value 2'); 583 | expect(valueStrings[valueStrings.length - 3]).toBe('Set value 1'); 584 | }); 585 | 586 | it('should handle setValueByChoice()', function() { 587 | const choices = this.choices.store.getChoicesFilteredByActive(); 588 | const randomChoice = choices[Math.floor(Math.random() * choices.length)]; 589 | 590 | this.choices.highlightAll(); 591 | this.choices.removeHighlightedItems(); 592 | this.choices.setValueByChoice(randomChoice.value); 593 | 594 | const value = this.choices.getValue(true); 595 | 596 | expect(value[0]).toBe(randomChoice.value); 597 | }); 598 | 599 | it('should handle setChoices()', function() { 600 | this.choices.setChoices([{ 601 | label: 'Group one', 602 | id: 1, 603 | disabled: false, 604 | choices: [{ 605 | value: 'Child One', 606 | label: 'Child One', 607 | selected: true 608 | }, { 609 | value: 'Child Two', 610 | label: 'Child Two', 611 | disabled: true 612 | }, { 613 | value: 'Child Three', 614 | label: 'Child Three' 615 | }, ] 616 | }, { 617 | label: 'Group two', 618 | id: 2, 619 | disabled: false, 620 | choices: [{ 621 | value: 'Child Four', 622 | label: 'Child Four', 623 | disabled: true 624 | }, { 625 | value: 'Child Five', 626 | label: 'Child Five' 627 | }, { 628 | value: 'Child Six', 629 | label: 'Child Six' 630 | }, ] 631 | }], 'value', 'label'); 632 | 633 | 634 | const groups = this.choices.currentState.groups; 635 | const choices = this.choices.currentState.choices; 636 | 637 | expect(groups[groups.length - 1].value).toEqual('Group two'); 638 | expect(groups[groups.length - 2].value).toEqual('Group one'); 639 | expect(choices[choices.length - 1].value).toEqual('Child Six'); 640 | expect(choices[choices.length - 2].value).toEqual('Child Five'); 641 | }); 642 | 643 | it('should handle setChoices() with blank values', function() { 644 | this.choices.setChoices([{ 645 | label: 'Choice one', 646 | value: 'one' 647 | }, { 648 | label: 'Choice two', 649 | value: '' 650 | }], 'value', 'label', true); 651 | 652 | 653 | const choices = this.choices.currentState.choices; 654 | expect(choices[0].value).toEqual('one'); 655 | expect(choices[1].value).toEqual(''); 656 | }); 657 | 658 | it('should handle clearStore()', function() { 659 | this.choices.clearStore(); 660 | 661 | expect(this.choices.currentState.items).toEqual([]); 662 | expect(this.choices.currentState.choices).toEqual([]); 663 | expect(this.choices.currentState.groups).toEqual([]); 664 | }); 665 | 666 | it('should handle disable()', function() { 667 | this.choices.disable(); 668 | 669 | expect(this.choices.input.disabled).toBe(true); 670 | expect(this.choices.containerOuter.classList.contains(this.choices.config.classNames.disabledState)).toBe(true); 671 | expect(this.choices.containerOuter.getAttribute('aria-disabled')).toBe('true'); 672 | }); 673 | 674 | it('should handle enable()', function() { 675 | this.choices.enable(); 676 | 677 | expect(this.choices.input.disabled).toBe(false); 678 | expect(this.choices.containerOuter.classList.contains(this.choices.config.classNames.disabledState)).toBe(false); 679 | expect(this.choices.containerOuter.hasAttribute('aria-disabled')).toBe(false); 680 | }); 681 | 682 | it('should handle ajax()', function() { 683 | spyOn(this.choices, 'ajax'); 684 | 685 | this.choices.ajax((callback) => { 686 | fetch('https://restcountries.eu/rest/v1/all') 687 | .then((response) => { 688 | response.json().then((data) => { 689 | callback(data, 'alpha2Code', 'name'); 690 | }); 691 | }) 692 | .catch((error) => { 693 | console.log(error); 694 | }); 695 | }); 696 | 697 | expect(this.choices.ajax).toHaveBeenCalledWith(jasmine.any(Function)); 698 | }); 699 | }); 700 | 701 | describe('should handle public methods on text input types', function() { 702 | beforeEach(function() { 703 | this.input = document.createElement('input'); 704 | this.input.type = "text"; 705 | this.input.className = 'js-choices'; 706 | this.input.value = "Value 1, Value 2, Value 3, Value 4"; 707 | 708 | document.body.appendChild(this.input); 709 | this.choices = new Choices(this.input); 710 | }); 711 | 712 | it('should handle clearInput()', function() { 713 | this.choices.clearInput(); 714 | expect(this.choices.input.value).toBe(''); 715 | }); 716 | 717 | it('should handle removeItemsByValue()', function() { 718 | const items = this.choices.currentState.items; 719 | const randomItem = items[Math.floor(Math.random() * items.length)]; 720 | 721 | this.choices.removeItemsByValue(randomItem.value); 722 | expect(randomItem.active).toBe(false); 723 | }); 724 | }); 725 | }); 726 | -------------------------------------------------------------------------------- /assets/scripts/dist/choices.min.js: -------------------------------------------------------------------------------- 1 | /*! choices.js v2.5.1 | (c) 2016 Josh Johnson | https://github.com/jshjohnson/Choices#readme */ 2 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Choices=t():e.Choices=t()}(this,function(){return function(e){function t(n){if(i[n])return i[n].exports;var s=i[n]={exports:{},id:n,loaded:!1};return e[n].call(s.exports,s,s.exports,t),s.loaded=!0,s.exports}var i={};return t.m=e,t.c=i,t.p="/assets/scripts/dist/",t(0)}([function(e,t,i){e.exports=i(1)},function(e,t,i){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}function s(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}function o(e){if(Array.isArray(e)){for(var t=0,i=Array(e.length);t0&&void 0!==arguments[0]?arguments[0]:"[data-choice]",n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(r(this,e),(0,f.isType)("String",i)){var s=document.querySelectorAll(i);if(s.length>1)for(var o=1;o"'+e+'"'},maxItemText:function(e){return"Only "+e+" values can be added."},uniqueItemText:"Only unique values can be added.",classNames:{containerOuter:"choices",containerInner:"choices__inner",input:"choices__input",inputCloned:"choices__input--cloned",list:"choices__list",listItems:"choices__list--multiple",listSingle:"choices__list--single",listDropdown:"choices__list--dropdown",item:"choices__item",itemSelectable:"choices__item--selectable",itemDisabled:"choices__item--disabled",itemChoice:"choices__item--choice",placeholder:"choices__placeholder",group:"choices__group",groupHeading:"choices__heading",button:"choices__button",activeState:"is-active",focusState:"is-focused",openState:"is-open",disabledState:"is-disabled",highlightedState:"is-highlighted",hiddenState:"is-hidden",flippedState:"is-flipped",loadingState:"is-loading"},callbackOnInit:null,callbackOnAddItem:null,callbackOnRemoveItem:null,callbackOnHighlightItem:null,callbackOnUnhighlightItem:null,callbackOnCreateTemplates:null,callbackOnChange:null,callbackOnSearch:null};if(this.config=(0,f.extend)(c,n),this.store=new h.default(this.render),this.initialised=!1,this.currentState={},this.prevState={},this.currentValue="",this.element=i,this.passedElement=(0,f.isType)("String",i)?document.querySelector(i):i,this.isSelectElement="select-one"===this.passedElement.type||"select-multiple"===this.passedElement.type,this.isTextElement="text"===this.passedElement.type,!this.passedElement)return void console.error("Passed element not found");this.highlightPosition=0,this.canSearch=this.config.search,this.presetChoices=this.config.choices,this.presetItems=this.config.items,this.passedElement.value&&(this.presetItems=this.presetItems.concat(this.passedElement.value.split(this.config.delimiter))),this.init=this.init.bind(this),this.render=this.render.bind(this),this.destroy=this.destroy.bind(this),this.disable=this.disable.bind(this),this._onFocus=this._onFocus.bind(this),this._onBlur=this._onBlur.bind(this),this._onKeyUp=this._onKeyUp.bind(this),this._onKeyDown=this._onKeyDown.bind(this),this._onClick=this._onClick.bind(this),this._onTouchMove=this._onTouchMove.bind(this),this._onTouchEnd=this._onTouchEnd.bind(this),this._onMouseDown=this._onMouseDown.bind(this),this._onMouseOver=this._onMouseOver.bind(this),this._onPaste=this._onPaste.bind(this),this._onInput=this._onInput.bind(this),this.wasTap=!0;var l="classList"in document.documentElement;l||console.error("Choices: Your browser doesn't support Choices");var u=["select-one","select-multiple","text"].some(function(e){return e===t.passedElement.type}),d=(0,f.isElement)(this.passedElement)&&u;if(d){if("active"===this.passedElement.getAttribute("data-choice"))return;this.init()}else console.error("Incompatible input passed")}return a(e,[{key:"init",value:function(){if(this.initialised!==!0){var e=this.config.callbackOnInit;this.initialised=!0,this._createTemplates(),this._createInput(),this.store.subscribe(this.render),this.render(),this._addEventListeners(),e&&((0,f.isType)("Function",e)?e.call(this):console.error("callbackOnInit: Callback is not a function"))}}},{key:"destroy",value:function(){this.initialised!==!1&&(this._removeEventListeners(),this.passedElement.classList.remove(this.config.classNames.input,this.config.classNames.hiddenState),this.passedElement.removeAttribute("tabindex"),this.passedElement.removeAttribute("style","display:none;"),this.passedElement.removeAttribute("aria-hidden"),this.passedElement.removeAttribute("data-choice","active"),this.passedElement.value=this.passedElement.value,this.containerOuter.parentNode.insertBefore(this.passedElement,this.containerOuter),this.containerOuter.parentNode.removeChild(this.containerOuter),this.clearStore(),this.config.templates=null,this.initialised=!1)}},{key:"renderGroups",value:function(e,t,i){var n=this,s=i||document.createDocumentFragment(),o=this.config.sortFilter;return this.config.shouldSort&&e.sort(o),e.forEach(function(e){var i=t.filter(function(t){return"select-one"===n.passedElement.type?t.groupId===e.id:t.groupId===e.id&&!t.selected});if(i.length>=1){var o=n._getTemplate("choiceGroup",e);s.appendChild(o),n.renderChoices(i,s)}}),s}},{key:"renderChoices",value:function(e,t){var i=this,n=t||document.createDocumentFragment(),s=this.isSearching?f.sortByScore:this.config.sortFilter;return(this.config.shouldSort||this.isSearching)&&e.sort(s),e.forEach(function(e){var t=i._getTemplate("choice",e),s="select-one"===i.passedElement.type||!e.selected;s&&n.appendChild(t)}),n}},{key:"renderItems",value:function(e,t){var i=this,n=t||document.createDocumentFragment(),s=this.store.getItemsReducedToValues(e);return this.isTextElement?this.passedElement.setAttribute("value",s.join(this.config.delimiter)):!function(){var t=document.createDocumentFragment();e.forEach(function(e){var n=i._getTemplate("option",e);t.appendChild(n)}),i.passedElement.innerHTML="",i.passedElement.appendChild(t)}(),e.forEach(function(e){var t=i._getTemplate("item",e);n.appendChild(t)}),n}},{key:"render",value:function(){if(this.currentState=this.store.getState(),this.currentState!==this.prevState){if(!(this.currentState.choices===this.prevState.choices&&this.currentState.groups===this.prevState.groups||"select-multiple"!==this.passedElement.type&&"select-one"!==this.passedElement.type)){var e=this.store.getGroupsFilteredByActive(),t=this.store.getChoicesFilteredByActive(),i=document.createDocumentFragment();if(this.choiceList.innerHTML="",this.choiceList.scrollTop=0,e.length>=1&&this.isSearching!==!0?i=this.renderGroups(e,t,i):t.length>=1&&(i=this.renderChoices(t,i)),i.childNodes&&i.childNodes.length>0)this.choiceList.appendChild(i),this._highlightChoice();else{var n=this.isSearching?this._getTemplate("notice",this.config.noResultsText):this._getTemplate("notice",this.config.noChoicesText);this.choiceList.appendChild(n)}}if(this.currentState.items!==this.prevState.items){var s=this.store.getItemsFilteredByActive();if(s){var o=this.renderItems(s);this.itemList.innerHTML="",o.childNodes&&this.itemList.appendChild(o)}}this.prevState=this.currentState}}},{key:"highlightItem",value:function(e){if(e){var t=e.id,i=e.groupId,n=this.config.callbackOnHighlightItem;if(this.store.dispatch((0,d.highlightItem)(t,!0)),n)if((0,f.isType)("Function",n)){var s=i>=0?this.store.getGroupById(i):null;s&&s.value?n.call(this,t,e.value,s.value):n.call(this,t,e.value)}else console.error("callbackOnHighlightItem: Callback is not a function");return this}}},{key:"unhighlightItem",value:function(e){if(e){var t=e.id,i=e.groupId,n=this.config.callbackOnUnhighlightItem;if(this.store.dispatch((0,d.highlightItem)(t,!1)),n)if((0,f.isType)("Function",n)){var s=i>=0?this.store.getGroupById(i):null;s&&s.value?n.call(this,t,e.value,s.value):n.call(this,t,e.value)}else console.error("callbackOnUnhighlightItem: Callback is not a function");return this}}},{key:"highlightAll",value:function(){var e=this,t=this.store.getItems();return t.forEach(function(t){e.highlightItem(t)}),this}},{key:"unhighlightAll",value:function(){var e=this,t=this.store.getItems();return t.forEach(function(t){e.unhighlightItem(t)}),this}},{key:"removeItemsByValue",value:function(e){var t=this;if(!e||!(0,f.isType)("String",e))return void console.error("removeItemsByValue: No value was passed to be removed");var i=this.store.getItemsFilteredByActive();return i.forEach(function(i){i.value===e&&t._removeItem(i)}),this}},{key:"removeActiveItems",value:function(e){var t=this,i=this.store.getItemsFilteredByActive();return i.forEach(function(i){i.active&&e!==i.id&&t._removeItem(i)}),this}},{key:"removeHighlightedItems",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]&&arguments[0],i=this.store.getItemsFilteredByActive();return i.forEach(function(i){i.highlighted&&i.active&&(e._removeItem(i),t&&e._triggerChange(i.value))}),this}},{key:"showDropdown",value:function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0],t=document.body,i=document.documentElement,n=Math.max(t.scrollHeight,t.offsetHeight,i.clientHeight,i.scrollHeight,i.offsetHeight);this.containerOuter.classList.add(this.config.classNames.openState),this.containerOuter.setAttribute("aria-expanded","true"),this.dropdown.classList.add(this.config.classNames.activeState);var s=this.dropdown.getBoundingClientRect(),o=Math.ceil(s.top+window.scrollY+s.height),r=!!this.config.flip&&o>=n;return r?this.containerOuter.classList.add(this.config.classNames.flippedState):this.containerOuter.classList.remove(this.config.classNames.flippedState),e&&this.canSearch&&document.activeElement!==this.input&&this.input.focus(),this}},{key:"hideDropdown",value:function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0],t=this.containerOuter.classList.contains(this.config.classNames.flippedState);return this.containerOuter.classList.remove(this.config.classNames.openState),this.containerOuter.setAttribute("aria-expanded","false"),this.dropdown.classList.remove(this.config.classNames.activeState),t&&this.containerOuter.classList.remove(this.config.classNames.flippedState),e&&this.canSearch&&document.activeElement===this.input&&this.input.blur(),this}},{key:"toggleDropdown",value:function(){var e=this.dropdown.classList.contains(this.config.classNames.activeState);return e?this.hideDropdown():this.showDropdown(!0),this}},{key:"getValue",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]&&arguments[0],i=this.store.getItemsFilteredByActive(),n=[];return i.forEach(function(i){e.isTextElement?n.push(t?i.value:i):i.active&&n.push(t?i.value:i)}),"select-one"===this.passedElement.type?n[0]:n}},{key:"setValue",value:function(e){var t=this;return this.initialised===!0&&!function(){var i=[].concat(o(e)),n=t.passedElement.type;i.forEach(function(e){if((0,f.isType)("Object",e)){if(!e.value)return;"text"!==n?t._addChoice(!0,!1,e.value,e.label,-1):t._addItem(e.value,e.label,e.id)}else(0,f.isType)("String",e)&&("text"!==n?t._addChoice(!0,!1,e,e,-1):t._addItem(e))})}(),this}},{key:"setValueByChoice",value:function(e){var t=this;return"text"!==this.passedElement.type&&!function(){var i=t.store.getChoices(),n=(0,f.isType)("Array",e)?e:[e];n.forEach(function(e){var n=i.find(function(t){return t.value===e});n?n.selected?console.warn("Attempting to select choice already selected"):t._addItem(n.value,n.label,n.id,n.groupId):console.warn("Attempting to select choice that does not exist")})}(),this}},{key:"setChoices",value:function(e,t,i){var n=this,s=arguments.length>3&&void 0!==arguments[3]&&arguments[3];if(this.initialised===!0&&this.isSelectElement){if(!(0,f.isType)("Array",e)||!t)return;s&&this._clearChoices(),e&&e.length&&(this.containerOuter.classList.remove(this.config.classNames.loadingState),e.forEach(function(e,s){var o=!!e.selected&&e.selected,r=!!e.disabled&&e.disabled;e.choices?n._addGroup(e,s,t,i):n._addChoice(o,r,e[t],e[i])}))}return this}},{key:"clearStore",value:function(){return this.store.dispatch((0,d.clearAll)()),this}},{key:"clearInput",value:function(){return this.input.value&&(this.input.value=""),"select-one"!==this.passedElement.type&&this._setInputWidth(),"text"!==this.passedElement.type&&this.config.search&&(this.isSearching=!1,this.store.dispatch((0,d.activateChoices)(!0))),this}},{key:"enable",value:function(){this.passedElement.disabled=!1;var e=this.containerOuter.classList.contains(this.config.classNames.disabledState);return this.initialised&&e&&(this._addEventListeners(),this.passedElement.removeAttribute("disabled"),this.input.removeAttribute("disabled"),this.containerOuter.classList.remove(this.config.classNames.disabledState),this.containerOuter.removeAttribute("aria-disabled")),this}},{key:"disable",value:function(){this.passedElement.disabled=!0;var e=!this.containerOuter.classList.contains(this.config.classNames.disabledState);return this.initialised&&e&&(this._removeEventListeners(),this.passedElement.setAttribute("disabled",""),this.input.setAttribute("disabled",""),this.containerOuter.classList.add(this.config.classNames.disabledState),this.containerOuter.setAttribute("aria-disabled","true")),this}},{key:"ajax",value:function(e){return this.initialised===!0&&this.isSelectElement&&(this._handleLoadingState(!0),e(this._ajaxCallback())),this}},{key:"_triggerChange",value:function(e){if(e){var t=this.config.callbackOnChange;t&&((0,f.isType)("Function",t)?t.call(this,e):console.error("callbackOnChange: Callback is not a function"))}}},{key:"_handleButtonAction",value:function(e,t){var i=this;e&&t&&this.config.removeItems&&this.config.removeItemButton&&!function(){var n=t.parentNode.getAttribute("data-id"),s=e.find(function(e){return e.id===parseInt(n,10)});if(i._removeItem(s),i._triggerChange(s.value),"select-one"===i.passedElement.type){var o=!!i.config.placeholder&&(i.config.placeholderValue||i.passedElement.getAttribute("placeholder"));if(o){var r=i._getTemplate("placeholder",o);i.itemList.appendChild(r)}}}()}},{key:"_handleItemAction",value:function(e,t){var i=this,n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];e&&t&&this.config.removeItems&&"select-one"!==this.passedElement.type&&!function(){var s=t.getAttribute("data-id");e.forEach(function(e){e.id!==parseInt(s,10)||e.highlighted?n||e.highlighted&&i.unhighlightItem(e):i.highlightItem(e)}),document.activeElement!==i.input&&i.input.focus()}()}},{key:"_handleChoiceAction",value:function(e,t){if(e&&t){var i=t.getAttribute("data-id"),n=this.store.getChoiceById(i),s=this.dropdown.classList.contains(this.config.classNames.activeState);if(n&&!n.selected&&!n.disabled){var o=this._canAddItem(e,n.value);o.response&&(this._addItem(n.value,n.label,n.id,n.groupId),this._triggerChange(n.value))}this.clearInput(this.passedElement),s&&"select-one"===this.passedElement.type&&(this.hideDropdown(),this.containerOuter.focus())}}},{key:"_handleBackspace",value:function(e){if(this.config.removeItems&&e){var t=e[e.length-1],i=e.some(function(e){return e.highlighted===!0});this.config.editItems&&!i&&t?(this.input.value=t.value,this._setInputWidth(),this._removeItem(t),this._triggerChange(t.value)):(i||this.highlightItem(t),this.removeHighlightedItems(!0))}}},{key:"_canAddItem",value:function(e,t){var i=!0,n=(0,f.isType)("Function",this.config.addItemText)?this.config.addItemText(t):this.config.addItemText;if("select-multiple"!==this.passedElement.type&&"text"!==this.passedElement.type||this.config.maxItemCount>0&&this.config.maxItemCount<=this.itemList.children.length&&(i=!1,n=(0,f.isType)("Function",this.config.maxItemText)?this.config.maxItemText(this.config.maxItemCount):this.config.maxItemText),"text"===this.passedElement.type&&this.config.addItems){var s=!e.some(function(e){return e.value===t.trim()});this.config.regexFilter&&(i=this._regexFilter(t)),this.config.duplicateItems!==!1||s||(i=!1,n=(0,f.isType)("Function",this.config.uniqueItemText)?this.config.uniqueItemText(t):this.config.uniqueItemText)}return{response:i,notice:n}}},{key:"_handleLoadingState",value:function(){var e=!(arguments.length>0&&void 0!==arguments[0])||arguments[0],t=this.itemList.querySelector("."+this.config.classNames.placeholder);if(e)this.containerOuter.classList.add(this.config.classNames.loadingState),this.containerOuter.setAttribute("aria-busy","true"),"select-one"===this.passedElement.type?t?t.innerHTML=this.config.loadingText:(t=this._getTemplate("placeholder",this.config.loadingText),this.itemList.appendChild(t)):this.input.placeholder=this.config.loadingText;else{this.containerOuter.classList.remove(this.config.classNames.loadingState);var i=!!this.config.placeholder&&(this.config.placeholderValue||this.passedElement.getAttribute("placeholder"));"select-one"===this.passedElement.type?t.innerHTML=i||"":this.input.placeholder=i||""}}},{key:"_ajaxCallback",value:function(){var e=this;return function(t,i,n){if(t&&i){var s=(0,f.isType)("Object",t)?[t]:t;s&&(0,f.isType)("Array",s)&&s.length&&(e._handleLoadingState(!1),s.forEach(function(t,s){var o=!!t.selected&&t.selected,r=!!t.disabled&&t.disabled;t.choices?e._addGroup(t,s,i,n):e._addChoice(o,r,t[i],t[n])})),e.containerOuter.removeAttribute("aria-busy")}}}},{key:"_searchChoices",value:function(e){var t=(0,f.isType)("String",e)?e.trim():e,i=(0,f.isType)("String",this.currentValue)?this.currentValue.trim():this.currentValue;if(t.length>=1&&t!==i+" "){var n=this.store.getChoicesFilteredBySelectable(),s=t,o=(0,f.isType)("Array",this.config.sortFields)?this.config.sortFields:[this.config.sortFields],r=new l.default(n,{keys:o,shouldSort:!0,include:"score"}),a=r.search(s);this.currentValue=t,this.highlightPosition=0,this.isSearching=!0,this.store.dispatch((0,d.filterChoices)(a))}}},{key:"_handleSearch",value:function(e){if(e){var t=this.store.getChoices(),i=t.some(function(e){return e.active!==!0}),n=this.config.callbackOnSearch;this.input===document.activeElement&&(e&&e.length>this.config.searchFloor?(this._searchChoices(e),n&&((0,f.isType)("Function",n)?n.call(this,e):console.error("callbackOnSearch: Callback is not a function"))):i&&(this.isSearching=!1,this.store.dispatch((0,d.activateChoices)(!0))))}}},{key:"_addEventListeners",value:function(){document.addEventListener("keyup",this._onKeyUp),document.addEventListener("keydown",this._onKeyDown),document.addEventListener("click",this._onClick),document.addEventListener("touchmove",this._onTouchMove),document.addEventListener("touchend",this._onTouchEnd),document.addEventListener("mousedown",this._onMouseDown),document.addEventListener("mouseover",this._onMouseOver),this.passedElement.type&&"select-one"===this.passedElement.type&&(this.containerOuter.addEventListener("focus",this._onFocus),this.containerOuter.addEventListener("blur",this._onBlur)),this.input.addEventListener("input",this._onInput),this.input.addEventListener("paste",this._onPaste),this.input.addEventListener("focus",this._onFocus),this.input.addEventListener("blur",this._onBlur)}},{key:"_removeEventListeners",value:function(){document.removeEventListener("keyup",this._onKeyUp),document.removeEventListener("keydown",this._onKeyDown),document.removeEventListener("click",this._onClick),document.removeEventListener("touchmove",this._onTouchMove),document.removeEventListener("touchend",this._onTouchEnd),document.removeEventListener("mousedown",this._onMouseDown),document.removeEventListener("mouseover",this._onMouseOver),this.passedElement.type&&"select-one"===this.passedElement.type&&(this.containerOuter.removeEventListener("focus",this._onFocus),this.containerOuter.removeEventListener("blur",this._onBlur)),this.input.removeEventListener("input",this._onInput),this.input.removeEventListener("paste",this._onPaste),this.input.removeEventListener("focus",this._onFocus),this.input.removeEventListener("blur",this._onBlur)}},{key:"_setInputWidth",value:function(){if(this.config.placeholder&&(this.config.placeholderValue||this.passedElement.getAttribute("placeholder"))){var e=!!this.config.placeholder&&(this.config.placeholderValue||this.passedElement.getAttribute("placeholder"));this.input.value&&this.input.value.length>=e.length/1.25&&(this.input.style.width=(0,f.getWidthOfInput)(this.input))}else this.input.style.width=(0,f.getWidthOfInput)(this.input)}},{key:"_onKeyDown",value:function(e){var t,i=this;if(e.target===this.input||this.containerOuter.contains(e.target)){var n=e.target,o=this.passedElement.type,r=this.store.getItemsFilteredByActive(),a=this.input===document.activeElement,c=this.dropdown.classList.contains(this.config.classNames.activeState),l=this.itemList&&this.itemList.children,u=String.fromCharCode(e.keyCode),h=46,d=8,p=13,v=65,m=27,g=38,y=40,b=e.ctrlKey||e.metaKey;"text"!==o&&/[a-zA-Z0-9-_ ]/.test(u)&&!c&&this.showDropdown(!0),this.canSearch=this.config.search;var _=function(){b&&l&&(i.canSearch=!1,i.config.removeItems&&!i.input.value&&i.input===document.activeElement&&i.highlightAll(i.itemList.children))},E=function(){if("text"===o&&n.value){var t=i.input.value,s=i._canAddItem(r,t);s.response&&(c&&i.hideDropdown(),i._addItem(t),i._triggerChange(t),i.clearInput(i.passedElement))}if(n.hasAttribute("data-button")&&(i._handleButtonAction(r,n),e.preventDefault()),c){e.preventDefault();var a=i.dropdown.querySelector("."+i.config.classNames.highlightedState);a&&i._handleChoiceAction(r,a)}else"select-one"===o&&(c||(i.showDropdown(!0),e.preventDefault()))},I=function(){c&&i.toggleDropdown()},S=function(){if(c||"select-one"===o){c||i.showDropdown(!0);var t=i.dropdown.querySelector("."+i.config.classNames.highlightedState),n=e.keyCode===y?1:-1,s=void 0;i.canSearch=!1,s=t?(0,f.getAdjacentEl)(t,"[data-choice-selectable]",n):i.dropdown.querySelector("[data-choice-selectable]"),s&&((0,f.isScrolledIntoView)(s,i.choiceList,n)||i._scrollToChoice(s,n),i._highlightChoice(s)),e.preventDefault()}},w=function(){a&&!e.target.value&&"select-one"!==o&&(i._handleBackspace(r),e.preventDefault())},T=(t={},s(t,v,_),s(t,p,E),s(t,m,I),s(t,g,S),s(t,y,S),s(t,d,w),s(t,h,w),t);T[e.keyCode]&&T[e.keyCode]()}}},{key:"_onKeyUp",value:function(e){if(e.target===this.input)if(this.isTextElement){var t=this.dropdown.classList.contains(this.config.classNames.activeState),i=this.input.value;if(i){var n=this.store.getItemsFilteredByActive(),s=this._canAddItem(n,i);if(s.notice){var o=this._getTemplate("notice",s.notice);this.dropdown.innerHTML=o.outerHTML}s.response===!0?t||this.showDropdown():!s.notice&&t&&this.hideDropdown()}else t&&this.hideDropdown()}else{var r=46,a=8;e.keyCode!==r&&e.keyCode!==a||e.target.value?this.canSearch&&this._handleSearch(this.input.value):"text"!==this.passedElement.type&&this.isSearching&&(this.isSearching=!1,this.store.dispatch((0,d.activateChoices)(!0)))}}},{key:"_onInput",value:function(){"select-one"!==this.passedElement.type&&this._setInputWidth()}},{key:"_onTouchMove",value:function(){this.wasTap===!0&&(this.wasTap=!1)}},{key:"_onTouchEnd",value:function(e){var t=e.target||e.touches[0].target,i=this.dropdown.classList.contains(this.config.classNames.activeState);this.wasTap===!0&&this.containerOuter.contains(t)&&(t!==this.containerOuter&&t!==this.containerInner||"select-one"===this.passedElement.type||(this.isTextElement?document.activeElement!==this.input&&this.input.focus():i||this.showDropdown(!0)),e.stopPropagation()),this.wasTap=!0}},{key:"_onMouseDown",value:function(e){var t=e.target;if(this.containerOuter.contains(t)&&t!==this.input){var i=this.store.getItemsFilteredByActive(),n=e.shiftKey;t.hasAttribute("data-item")?this._handleItemAction(i,t,n):t.hasAttribute("data-choice")&&this._handleChoiceAction(i,t),e.preventDefault()}}},{key:"_onClick",value:function(e){var t=e.target,i=this.dropdown.classList.contains(this.config.classNames.activeState),n=this.store.getItemsFilteredByActive();if(this.containerOuter.contains(t))t.hasAttribute("data-button")&&this._handleButtonAction(n,t),i?"select-one"!==this.passedElement.type||t===this.input||this.dropdown.contains(t)||this.hideDropdown(!0):this.isTextElement?document.activeElement!==this.input&&this.input.focus():this.canSearch?this.showDropdown(!0):(this.showDropdown(),this.containerOuter.focus());else{var s=n.some(function(e){return e.highlighted===!0});s&&this.unhighlightAll(),this.containerOuter.classList.remove(this.config.classNames.focusState),i&&this.hideDropdown()}}},{key:"_onMouseOver",value:function(e){(e.target===this.dropdown||this.dropdown.contains(e.target))&&e.target.hasAttribute("data-choice")&&this._highlightChoice(e.target)}},{key:"_onPaste",value:function(e){e.target!==this.input||this.config.paste||e.preventDefault()}},{key:"_onFocus",value:function(e){var t=this,i=e.target;this.containerOuter.contains(i)&&!function(){var e=t.dropdown.classList.contains(t.config.classNames.activeState),n={text:function(){i===t.input&&t.containerOuter.classList.add(t.config.classNames.focusState)},"select-one":function(){t.containerOuter.classList.add(t.config.classNames.focusState),i===t.input&&(e||t.showDropdown())},"select-multiple":function(){i===t.input&&(t.containerOuter.classList.add(t.config.classNames.focusState),e||t.showDropdown(!0))}};n[t.passedElement.type]()}()}},{key:"_onBlur",value:function(e){var t=this,i=e.target;this.containerOuter.contains(i)&&!function(){var e=t.store.getItemsFilteredByActive(),n=t.dropdown.classList.contains(t.config.classNames.activeState),s=e.some(function(e){return e.highlighted===!0}),o={text:function(){i===t.input&&(t.containerOuter.classList.remove(t.config.classNames.focusState),s&&t.unhighlightAll(),n&&t.hideDropdown())},"select-one":function(){t.containerOuter.classList.remove(t.config.classNames.focusState),i===t.containerOuter&&n&&!t.canSearch&&t.hideDropdown(),i===t.input&&n&&t.hideDropdown()},"select-multiple":function(){i===t.input&&(t.containerOuter.classList.remove(t.config.classNames.focusState),n&&t.hideDropdown(),s&&t.unhighlightAll())}};o[t.passedElement.type]()}()}},{key:"_regexFilter",value:function(e){if(e){var t=this.config.regexFilter,i=new RegExp(t.source,"i");return i.test(e)}}},{key:"_scrollToChoice",value:function(e,t){var i=this;if(e){var n=this.choiceList.offsetHeight,s=e.offsetHeight,o=e.offsetTop+s,r=this.choiceList.scrollTop+n,a=t>0?this.choiceList.scrollTop+o-r:e.offsetTop,c=function e(){var n=4,s=i.choiceList.scrollTop,o=!1,r=void 0,c=void 0;t>0?(r=(a-s)/n,c=r>1?r:1,i.choiceList.scrollTop=s+c,s1?r:1,i.choiceList.scrollTop=s-c,s>a&&(o=!0)),o&&requestAnimationFrame(function(i){e(i,a,t)})};requestAnimationFrame(function(e){c(e,a,t)})}}},{key:"_highlightChoice",value:function(e){var t=this,i=Array.from(this.dropdown.querySelectorAll("[data-choice-selectable]"));if(i&&i.length){var n=Array.from(this.dropdown.querySelectorAll("."+this.config.classNames.highlightedState));if(n.forEach(function(e){e.classList.remove(t.config.classNames.highlightedState),e.setAttribute("aria-selected","false")}),e)e.classList.add(this.config.classNames.highlightedState),this.highlightPosition=i.indexOf(e);else{var s=void 0;s=i.length>this.highlightPosition?i[this.highlightPosition]:i[i.length-1],s||(s=i[0]),s.classList.add(this.config.classNames.highlightedState),s.setAttribute("aria-selected","true")}}}},{key:"_addItem",value:function(e,t){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:-1,n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:-1,s=(0,f.isType)("String",e)?e.trim():e,o=this.store.getItems(),r=t||s,a=parseInt(i,10)||-1,c=this.config.callbackOnAddItem;this.config.prependValue&&(s=this.config.prependValue+s.toString()),this.config.appendValue&&(s+=this.config.appendValue.toString());var l=o?o.length+1:1;if(this.store.dispatch((0,d.addItem)(s,r,l,a,n)),"select-one"===this.passedElement.type&&this.removeActiveItems(l),c){var u=n>=0?this.store.getGroupById(n):null;(0,f.isType)("Function",c)?u&&u.value?c.call(this,l,s,u.value):c.call(this,l,s):console.error("callbackOnAddItem: Callback is not a function")}return this}},{key:"_removeItem",value:function(e){if(!e||!(0,f.isType)("Object",e))return void console.error("removeItem: No item object was passed to be removed");var t=e.id,i=e.value,n=e.choiceId,s=e.groupId,o=this.config.callbackOnRemoveItem;if(this.store.dispatch((0,d.removeItem)(t,n)),o)if((0,f.isType)("Function",o)){var r=s>=0?this.store.getGroupById(s):null;r&&r.value?o.call(this,t,i,r.value):o.call(this,t,i)}else console.error("callbackOnRemoveItem: Callback is not a function");return this}},{key:"_addChoice",value:function(e,t,i,n){var s=arguments.length>4&&void 0!==arguments[4]?arguments[4]:-1;if("undefined"!=typeof i&&null!==i){var o=this.store.getChoices(),r=n||i,a=o?o.length+1:1;this.store.dispatch((0,d.addChoice)(i,r,a,s,t)),e&&this._addItem(i,r,a)}}},{key:"_clearChoices",value:function(){this.store.dispatch((0,d.clearChoices)())}},{key:"_addGroup",value:function(e,t){var i=this,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"value",s=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"label",o=(0,f.isType)("Object",e)?e.choices:Array.from(e.getElementsByTagName("OPTION")),r=t,a=!!e.disabled&&e.disabled;o?(this.store.dispatch((0,d.addGroup)(e.label,r,!0,a)),o.forEach(function(e){var t=e.disabled||e.parentNode&&e.parentNode.disabled||!1,o=!!e.selected&&e.selected,a=void 0;a=(0,f.isType)("Object",e)?e[s]||e[n]:e.innerHTML,i._addChoice(o,t,e[n],a,r)})):this.store.dispatch((0,d.addGroup)(e.label,e.id,!1,e.disabled))}},{key:"_getTemplate",value:function(e){if(e){for(var t=this.config.templates,i=arguments.length,n=Array(i>1?i-1:0),s=1;s