Merge branch 'develop' of https://github.com/jshjohnson/Choices into develop

This commit is contained in:
Egon Richárd Tőrös 2018-05-28 16:13:55 +02:00
commit 0cbf951845
77 changed files with 4060 additions and 4264 deletions

View file

@ -1,32 +1,24 @@
{ {
"extends": "airbnb", "extends": ["prettier"],
"ecmaFeatures": { "plugins": ["prettier"],
"modules": true
},
"env": { "env": {
"browser": true, "browser": true,
"node": true, "node": true,
"mocha": true, "mocha": true
"jasmine": true
}, },
"globals": { "globals": {
"__DEV__": true,
"describe": true, "describe": true,
"it": true, "it": true,
"before": true, "before": true,
"after": true, "after": true,
"beforeEach": true, "beforeEach": true,
"afterEach": true, "afterEach": true
"expect": true,
"browser": true,
"by": true,
"element": true
}, },
"parser": "babel-eslint", "parser": "babel-eslint",
"rules": { "rules": {
"strict": 0, "prettier/prettier": ["error", {
"no-underscore-dangle": 0, "singleQuote": true,
"no-console": ["warn", { allow: ["warn", "error"] }], "trailingComma": "all"
"space-before-function-paren": 0 }]
} }
} }

2
.gitignore vendored
View file

@ -11,3 +11,5 @@ tests/reports
tests/results tests/results
.nyc_output .nyc_output
coverage coverage
cypress/videos
cypress/screenshots

View file

@ -1,6 +1,6 @@
language: node_js language: node_js
node_js: node_js:
- "6" - "8"
before_install: before_install:
- '[ "${TRAVIS_NODE_VERSION}" != "0.8" ] || npm install -g npm@1.4.28' - '[ "${TRAVIS_NODE_VERSION}" != "0.8" ] || npm install -g npm@1.4.28'
- npm install -g npm@latest - npm install -g npm@latest

View file

@ -36,11 +36,11 @@ Or include Choices directly:
```html ```html
<!-- Include base CSS (optional) --> <!-- Include base CSS (optional) -->
<link rel="stylesheet" href="assets/styles/css/base.min.css"> <link rel="stylesheet" href="public/styles/base.min.css">
<!-- Include Choices CSS --> <!-- Include Choices CSS -->
<link rel="stylesheet" href="assets/styles/css/choices.min.css"> <link rel="stylesheet" href="public/styles/choices.min.css">
<!-- Include Choices JavaScript --> <!-- Include Choices JavaScript -->
<script src="/assets/scripts/dist/choices.min.js"></script> <script src="/public/scripts/choices.min.js"></script>
``` ```
## Setup ## Setup

View file

@ -3,8 +3,8 @@
"version": "3.0.2", "version": "3.0.2",
"description": "A vanilla JS customisable text input/select box plugin", "description": "A vanilla JS customisable text input/select box plugin",
"main": [ "main": [
"./assets/scripts/dist/choices.js", "./public/assets/scripts/choices.min.js",
"./assets/styles/css/choices.css" "./public/assets/styles/choices.min.css"
], ],
"authors": [ "authors": [
"Josh Johnson" "Josh Johnson"
@ -16,6 +16,7 @@
"node_modules", "node_modules",
"bower_components", "bower_components",
"test", "test",
"cypress",
"tests" "tests"
], ],
"keywords": [ "keywords": [

1
cypress.json Normal file
View file

@ -0,0 +1 @@
{}

View file

@ -1,25 +1,25 @@
{ {
"name": "choices.js", "name": "choices.js",
"version": "3.0.2", "version": "4.0.0",
"description": "A vanilla JS customisable text input/select box plugin", "description": "A vanilla JS customisable text input/select box plugin",
"main": "./src/scripts/dist/choices.min.js", "main": "./public/assets/scripts/choices.min.js",
"types": "./types/index.d.ts", "types": "./types/index.d.ts",
"scripts": { "scripts": {
"start": "node server.js", "start": "NODE_ENV=development node server.js",
"build": "npm run js:build && npm run css:build",
"lint": "eslint assets/**/*.js", "lint": "eslint assets/**/*.js",
"coverage": "nyc npm run test",
"test": "mocha --require ./config/test.js --compilers js:babel-core/register \"./src/**/**/**/**/*.test.js\"", "test": "mocha --require ./config/test.js --compilers js:babel-core/register \"./src/**/**/**/**/*.test.js\"",
"test:watch": "npm run test -- --watch --inspect=5556", "test:watch": "npm run test -- --watch --inspect=5556",
"coverage": "nyc npm run test",
"css:watch": "nodemon -e scss -x \"npm run css:build\"", "css:watch": "nodemon -e scss -x \"npm run css:build\"",
"css:build": "npm run css:sass -s && npm run css:prefix -s && npm run css:min -s", "css:build": "npm run css:sass -s && npm run css:prefix -s && npm run css:min -s",
"css:sass": "node-sass --output-style expanded --include-path scss src/styles/scss/base.scss src/styles/css/base.css && node-sass --output-style expanded --include-path scss src/styles/scss/choices.scss src/styles/css/choices.css", "css:sass": "node-sass --output-style expanded --include-path scss src/styles/base.scss public/assets/styles/base.css && node-sass --output-style expanded --include-path scss src/styles/choices.scss public/assets/styles/choices.css",
"css:prefix": "postcss --use autoprefixer -b 'last 2 versions' src/styles/css/*.css -d src/styles/css/", "css:prefix": "postcss --use autoprefixer -b 'last 2 versions' public/assets/styles/*.css -d public/assets/styles",
"css:min": "csso src/styles/css/base.css src/styles/css/base.min.css && csso src/styles/css/choices.css src/styles/css/choices.min.css", "css:min": "csso public/assets/styles/base.css public/assets/styles/base.min.css && csso public/assets/styles/choices.css public/assets/styles/choices.min.css",
"js:build": "concurrently --prefix-colors yellow,green \"webpack --env.minimize --config webpack.config.prod.js\" \"webpack --config webpack.config.prod.js\"", "js:build": "concurrently --prefix-colors yellow,green \"webpack --env.minimize --config webpack.config.prod.js\" \"webpack --config webpack.config.prod.js\"",
"version": "node version.js --current $npm_package_version --new $npm_config_newVersion", "version": "node version.js --current $npm_package_version --new $npm_config_newVersion",
"postversion": "npm run js:build", "postversion": "npm run js:build",
"prepush": "npm run lint && npm run test", "prepush": "npm run lint && npm run test"
"dev": "dev"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -40,35 +40,35 @@
"babel-preset-stage-2": "^6.24.1", "babel-preset-stage-2": "^6.24.1",
"chai": "^4.1.0", "chai": "^4.1.0",
"concurrently": "^3.1.0", "concurrently": "^3.1.0",
"core-js": "^2.4.1",
"csso": "^1.8.2", "csso": "^1.8.2",
"custom-event-autopolyfill": "^0.1.3",
"es6-promise": "^3.2.1",
"eslint": "^3.19.0", "eslint": "^3.19.0",
"eslint-config-airbnb": "^15.1.0", "eslint-config-airbnb": "^15.1.0",
"eslint-config-prettier": "^2.9.0",
"eslint-loader": "^1.5.0", "eslint-loader": "^1.5.0",
"eslint-plugin-import": "^2.7.0", "eslint-plugin-import": "^2.7.0",
"eslint-plugin-jsx-a11y": "^5.1.1", "eslint-plugin-jsx-a11y": "^5.1.1",
"eslint-plugin-prettier": "^2.6.0",
"eslint-plugin-react": "^7.2.1", "eslint-plugin-react": "^7.2.1",
"express": "^4.16.3",
"husky": "^0.14.3", "husky": "^0.14.3",
"jasmine-core": "2.4.1",
"jsdom": "^11.5.1", "jsdom": "^11.5.1",
"mocha": "^3.4.2", "mocha": "^3.4.2",
"node-sass": "^3.4.2", "node-sass": "^3.4.2",
"nodemon": "^1.9.1", "nodemon": "^1.9.1",
"nyc": "^11.0.3", "nyc": "^11.0.3",
"open": "0.0.5",
"opn-cli": "^3.1.0",
"postcss-cli": "^2.5.1", "postcss-cli": "^2.5.1",
"prettier": "^1.13.0",
"sinon": "^2.4.0", "sinon": "^2.4.0",
"webpack": "^3.8.1", "webpack": "^3.8.1",
"webpack-dashboard": "^0.1.8", "webpack-dev-middleware": "^2.0.0",
"webpack-dev-server": "^1.14.1", "webpack-hot-middleware": "^2.22.2",
"whatwg-fetch": "^1.0.0", "whatwg-fetch": "^1.0.0",
"wrapper-webpack-plugin": "^0.1.7" "wrapper-webpack-plugin": "^0.1.7"
}, },
"dependencies": { "dependencies": {
"classnames": "^2.2.5", "classnames": "^2.2.5",
"core-js": "^2.5.6",
"custom-event-polyfill": "^0.3.0",
"fuse.js": "^3.1.0", "fuse.js": "^3.1.0",
"opn": "^5.1.0", "opn": "^5.1.0",
"redux": "^3.3.1" "redux": "^3.3.1"
@ -78,9 +78,9 @@
{ {
"basePath": "src", "basePath": "src",
"files": [ "files": [
"scripts/dist/*", "public/scripts/*",
"styles/css/*", "public/styles/*",
"icons/*" "src/icons/*"
] ]
} }
], ],

View file

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

Before

Width:  |  Height:  |  Size: 574 B

After

Width:  |  Height:  |  Size: 574 B

View file

Before

Width:  |  Height:  |  Size: 887 B

After

Width:  |  Height:  |  Size: 887 B

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 6 KiB

After

Width:  |  Height:  |  Size: 6 KiB

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 3 KiB

View file

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

File diff suppressed because one or more lines are too long

2
public/assets/scripts/choices.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -6,11 +6,14 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
*, *:before, *:after { *,
*:before,
*:after {
box-sizing: border-box; box-sizing: border-box;
} }
html, body { html,
body {
position: relative; position: relative;
margin: 0; margin: 0;
width: 100%; width: 100%;
@ -46,14 +49,21 @@ hr {
height: 1px; height: 1px;
} }
h1, h2, h3, h4, h5, h6 { h1,
h2,
h3,
h4,
h5,
h6 {
margin-top: 0; margin-top: 0;
margin-bottom: 12px; margin-bottom: 12px;
font-weight: 400; font-weight: 400;
line-height: 1.2; line-height: 1.2;
} }
a, a:visited, a:focus { a,
a:visited,
a:focus {
color: #FFFFFF; color: #FFFFFF;
text-decoration: none; text-decoration: none;
font-weight: 600; font-weight: 600;
@ -73,27 +83,33 @@ a, a:visited, a:focus {
margin-bottom: 24px; margin-bottom: 24px;
} }
h1, .h1 { h1,
.h1 {
font-size: 32px; font-size: 32px;
} }
h2, .h2 { h2,
.h2 {
font-size: 24px; font-size: 24px;
} }
h3, .h3 { h3,
.h3 {
font-size: 20px; font-size: 20px;
} }
h4, .h4 { h4,
.h4 {
font-size: 18px; font-size: 18px;
} }
h5, .h5 { h5,
.h5 {
font-size: 16px; font-size: 16px;
} }
h6, .h6 { h6,
.h6 {
font-size: 14px; font-size: 14px;
} }
@ -116,7 +132,9 @@ h6, .h6 {
color: #333; color: #333;
} }
.section a, .section a:visited, .section a:focus { .section a,
.section a:visited,
.section a:focus {
color: #00bcd4; color: #00bcd4;
} }

View file

@ -15,7 +15,8 @@
margin-bottom: 0; margin-bottom: 0;
} }
.choices.is-disabled .choices__inner, .choices.is-disabled .choices__input { .choices.is-disabled .choices__inner,
.choices.is-disabled .choices__input {
background-color: #EAEAEA; background-color: #EAEAEA;
cursor: not-allowed; cursor: not-allowed;
-webkit-user-select: none; -webkit-user-select: none;
@ -99,11 +100,13 @@
margin-right: 0; margin-right: 0;
} }
.choices[data-type*="select-multiple"] .choices__inner, .choices[data-type*="text"] .choices__inner { .choices[data-type*="select-multiple"] .choices__inner,
.choices[data-type*="text"] .choices__inner {
cursor: text; cursor: text;
} }
.choices[data-type*="select-multiple"] .choices__button, .choices[data-type*="text"] .choices__button { .choices[data-type*="select-multiple"] .choices__button,
.choices[data-type*="text"] .choices__button {
position: relative; position: relative;
display: inline-block; display: inline-block;
margin-top: 0; margin-top: 0;
@ -120,7 +123,9 @@
border-radius: 0; border-radius: 0;
} }
.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 { .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 {
opacity: 1; opacity: 1;
} }
@ -137,7 +142,8 @@
overflow: hidden; overflow: hidden;
} }
.is-focused .choices__inner, .is-open .choices__inner { .is-focused .choices__inner,
.is-open .choices__inner {
border-color: #b7b7b7; border-color: #b7b7b7;
} }
@ -268,8 +274,7 @@
position: absolute; position: absolute;
right: 10px; right: 10px;
top: 50%; top: 50%;
-webkit-transform: translateY(-50%); transform: translateY(-50%);
transform: translateY(-50%);
} }
[dir="rtl"] .choices__list--dropdown .choices__item--selectable { [dir="rtl"] .choices__list--dropdown .choices__item--selectable {
text-align: right; text-align: right;

File diff suppressed because one or more lines are too long

View file

@ -6,17 +6,17 @@
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no"> <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
<title>Choices</title> <title>Choices</title>
<meta name=description itemprop=description content="A lightweight, configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency."> <meta name=description itemprop=description content="A lightweight, configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency.">
<link rel="apple-touch-icon" sizes="180x180" href="src/images/apple-touch-icon.png"> <link rel="apple-touch-icon" sizes="180x180" href="assets/images/apple-touch-icon.png">
<link rel="icon" type="image/png" href="src/images/favicon-32x32.png" sizes="32x32"> <link rel="icon" type="image/png" href="assets/images/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="src/images/favicon-16x16.png" sizes="16x16"> <link rel="icon" type="image/png" href="assets/images/favicon-16x16.png" sizes="16x16">
<link rel="manifest" href="src/images/manifest.json"> <link rel="manifest" href="assets/images/manifest.json">
<link rel="mask-icon" href="src/images/safari-pinned-tab.svg" color="#00bcd4"> <link rel="mask-icon" href="assets/images/safari-pinned-tab.svg" color="#00bcd4">
<link rel="shortcut icon" href="src/images/favicon.ico"> <link rel="shortcut icon" href="assets/images/favicon.ico">
<meta name="msapplication-config" content="/src/images/browserconfig.xml"> <meta name="msapplication-config" content="/assets/images/browserconfig.xml">
<meta name="theme-color" content="#ffffff"> <meta name="theme-color" content="#ffffff">
<!-- Ignore these --> <!-- Ignore these -->
<link rel="stylesheet" href="src/styles/css/base.min.css?version=3.0.2"> <link rel="stylesheet" href="assets/styles/base.min.css?version=3.0.2">
<!-- End ignore these --> <!-- End ignore these -->
<!-- Optional includes --> <!-- Optional includes -->
@ -24,8 +24,8 @@
<!-- End optional includes --> <!-- End optional includes -->
<!-- Choices includes --> <!-- Choices includes -->
<link rel="stylesheet" href="src/styles/css/choices.min.css?version=3.0.2"> <link rel="stylesheet" href="assets/styles/choices.min.css?version=3.0.2">
<script src="src/scripts/dist/choices.min.js?version=2.8.8"></script> <script src="assets/scripts/choices.min.js?version=2.8.8"></script>
<!-- End Choices includes --> <!-- End Choices includes -->
<!--[if lt IE 9]> <!--[if lt IE 9]>
@ -40,7 +40,7 @@
<div class="container"> <div class="container">
<div class="section"> <div class="section">
<a href="https://github.com/jshjohnson/Choices" class="logo"> <a href="https://github.com/jshjohnson/Choices" class="logo">
<img src="src/images/logo.svg" alt="Choices.js logo" class="logo__img hidden-ie"> <img src="assets/images/logo.svg" alt="Choices.js logo" class="logo__img hidden-ie">
<h1 class="visible-ie">Choices.js</h1> <h1 class="visible-ie">Choices.js</h1>
</a> </a>
<p>Choices.js is a lightweight, configurable select box/text input plugin. Similar to Select2 and Selectize but without <p>Choices.js is a lightweight, configurable select box/text input plugin. Similar to Select2 and Selectize but without

View file

@ -1,14 +1,36 @@
const webpack = require('webpack'); /* eslint-disable no-console,global-require,import/no-extraneous-dependencies */
const WebpackDevServer = require('webpack-dev-server'); const express = require('express');
const config = require('./webpack.config.dev'); const path = require('path');
const opn = require('opn');
new WebpackDevServer(webpack(config), { const PORT = 3001;
publicPath: config.output.publicPath, const DIST_DIR = path.join(__dirname, 'public');
historyApiFallback: true,
quiet: true, // lets WebpackDashboard do its thing const app = express();
}).listen(3001, 'localhost', (err) => {
if (err) console.log(err); if (process.env.NODE_ENV !== 'production') {
opn('http://localhost:3001'); const webpack = require('webpack');
console.log('Listening at localhost:3001'); const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const config = require('./webpack.config.dev');
console.log('Compiling bundle... 👷🏽');
const compiler = webpack(config);
app.use(webpackDevMiddleware(compiler, {
publicPath: '/assets/scripts/',
stats: {
colors: true,
},
}));
app.use(webpackHotMiddleware(compiler));
}
app.use(express.static(DIST_DIR));
app.listen(PORT, (err) => {
if (err) {
console.log(err);
}
console.log(`Listening at http://localhost:${PORT} 👂`);
}); });

View file

@ -27,17 +27,19 @@ describe('actions/choices', () => {
keyCode, keyCode,
}; };
expect(actions.addChoice( expect(
value, actions.addChoice(
label, value,
id, label,
groupId, id,
disabled, groupId,
elementId, disabled,
customProperties, elementId,
placeholder, customProperties,
keyCode, placeholder,
)).to.eql(expectedAction); keyCode,
),
).to.eql(expectedAction);
}); });
}); });
@ -88,4 +90,3 @@ describe('actions/choices', () => {
}); });
}); });
}); });

View file

@ -16,12 +16,9 @@ describe('actions/groups', () => {
disabled, disabled,
}; };
expect(actions.addGroup( expect(actions.addGroup(value, id, active, disabled)).to.eql(
value, expectedAction,
id, );
active,
disabled,
)).to.eql(expectedAction);
}); });
}); });
}); });

View file

@ -25,16 +25,18 @@ describe('actions/items', () => {
keyCode, keyCode,
}; };
expect(actions.addItem( expect(
value, actions.addItem(
label, value,
id, label,
choiceId, id,
groupId, choiceId,
customProperties, groupId,
placeholder, customProperties,
keyCode, placeholder,
)).to.eql(expectedAction); keyCode,
),
).to.eql(expectedAction);
}); });
}); });

File diff suppressed because it is too large Load diff

View file

@ -45,7 +45,7 @@ describe('choices', () => {
instance.init(); instance.init();
}); });
it('doesn\'t set initialise flag', () => { it("doesn't set initialise flag", () => {
expect(instance.initialised).to.not.equal(false); expect(instance.initialised).to.not.equal(false);
}); });
}); });
@ -60,7 +60,7 @@ describe('choices', () => {
beforeEach(() => { beforeEach(() => {
createTemplatesSpy = spy(instance, '_createTemplates'); createTemplatesSpy = spy(instance, '_createTemplates');
createInputSpy = spy(instance, '_createStructure'); createInputSpy = spy(instance, '_createStructure');
storeSubscribeSpy = spy(instance.store, 'subscribe'); storeSubscribeSpy = spy(instance._store, 'subscribe');
renderSpy = spy(instance, 'render'); renderSpy = spy(instance, 'render');
addEventListenersSpy = spy(instance, '_addEventListeners'); addEventListenersSpy = spy(instance, '_addEventListeners');
@ -123,7 +123,7 @@ describe('choices', () => {
instance.destroy(); instance.destroy();
}); });
it('doesn\'t set initialise flag', () => { it("doesn't set initialise flag", () => {
expect(instance.initialised).to.not.equal(true); expect(instance.initialised).to.not.equal(true);
}); });
}); });
@ -161,7 +161,9 @@ describe('choices', () => {
it('reverts outer container', () => { it('reverts outer container', () => {
expect(containerOuterUnwrapSpy.called).to.equal(true); expect(containerOuterUnwrapSpy.called).to.equal(true);
expect(containerOuterUnwrapSpy.lastCall.args[0]).to.equal(instance.passedElement.element); expect(containerOuterUnwrapSpy.lastCall.args[0]).to.equal(
instance.passedElement.element,
);
}); });
it('clears store', () => { it('clears store', () => {
@ -263,7 +265,6 @@ describe('choices', () => {
inputDisableSpy = spy(instance.input, 'disable'); inputDisableSpy = spy(instance.input, 'disable');
}); });
afterEach(() => { afterEach(() => {
removeEventListenersSpy.restore(); removeEventListenersSpy.restore();
passedElementDisableSpy.restore(); passedElementDisableSpy.restore();
@ -374,29 +375,43 @@ describe('choices', () => {
expect(output).to.eql(instance); expect(output).to.eql(instance);
}); });
it('opens containerOuter', () => { it('opens containerOuter', done => {
expect(containerOuterOpenSpy.called).to.equal(true); requestAnimationFrame(() => {
expect(containerOuterOpenSpy.called).to.equal(true);
done();
});
}); });
it('shows dropdown with blurInput flag', () => { it('shows dropdown with blurInput flag', done => {
expect(dropdownShowSpy.called).to.equal(true); requestAnimationFrame(() => {
expect(dropdownShowSpy.called).to.equal(true);
done();
});
}); });
it('triggers event on passedElement', () => { it('triggers event on passedElement', done => {
expect(passedElementTriggerEventStub.called).to.equal(true); requestAnimationFrame(() => {
expect(passedElementTriggerEventStub.lastCall.args[0]).to.eql(EVENTS.showDropdown); expect(passedElementTriggerEventStub.called).to.equal(true);
expect(passedElementTriggerEventStub.lastCall.args[1]).to.eql({}); expect(passedElementTriggerEventStub.lastCall.args[0]).to.eql(
EVENTS.showDropdown,
);
expect(passedElementTriggerEventStub.lastCall.args[1]).to.eql({});
done();
});
}); });
describe('passing true focusInput flag with canSearch set to true', () => { describe('passing true focusInput flag with canSearch set to true', () => {
beforeEach(() => { beforeEach(() => {
instance.dropdown.isActive = false; instance.dropdown.isActive = false;
instance.canSearch = true; instance._canSearch = true;
output = instance.showDropdown(true); output = instance.showDropdown(true);
}); });
it('focuses input', () => { it('focuses input', done => {
expect(inputFocusSpy.called).to.equal(true); requestAnimationFrame(() => {
expect(inputFocusSpy.called).to.equal(true);
done();
});
}); });
}); });
}); });
@ -413,7 +428,10 @@ describe('choices', () => {
containerOuterCloseSpy = spy(instance.containerOuter, 'close'); containerOuterCloseSpy = spy(instance.containerOuter, 'close');
dropdownHideSpy = spy(instance.dropdown, 'hide'); dropdownHideSpy = spy(instance.dropdown, 'hide');
inputBlurSpy = spy(instance.input, 'blur'); inputBlurSpy = spy(instance.input, 'blur');
inputRemoveActiveDescendantSpy = spy(instance.input, 'removeActiveDescendant'); inputRemoveActiveDescendantSpy = spy(
instance.input,
'removeActiveDescendant',
);
passedElementTriggerEventStub = stub(); passedElementTriggerEventStub = stub();
instance.passedElement.triggerEvent = passedElementTriggerEventStub; instance.passedElement.triggerEvent = passedElementTriggerEventStub;
@ -453,33 +471,50 @@ describe('choices', () => {
expect(output).to.eql(instance); expect(output).to.eql(instance);
}); });
it('closes containerOuter', () => { it('closes containerOuter', done => {
expect(containerOuterCloseSpy.called).to.equal(true); requestAnimationFrame(() => {
expect(containerOuterCloseSpy.called).to.equal(true);
done();
});
}); });
it('hides dropdown with blurInput flag', () => { it('hides dropdown with blurInput flag', done => {
expect(dropdownHideSpy.called).to.equal(true); requestAnimationFrame(() => {
expect(dropdownHideSpy.called).to.equal(true);
done();
});
}); });
it('triggers event on passedElement', () => { it('triggers event on passedElement', done => {
expect(passedElementTriggerEventStub.called).to.equal(true); requestAnimationFrame(() => {
expect(passedElementTriggerEventStub.lastCall.args[0]).to.eql(EVENTS.hideDropdown); expect(passedElementTriggerEventStub.called).to.equal(true);
expect(passedElementTriggerEventStub.lastCall.args[1]).to.eql({}); expect(passedElementTriggerEventStub.lastCall.args[0]).to.eql(
EVENTS.hideDropdown,
);
expect(passedElementTriggerEventStub.lastCall.args[1]).to.eql({});
done();
});
}); });
describe('passing true blurInput flag with canSearch set to true', () => { describe('passing true blurInput flag with canSearch set to true', () => {
beforeEach(() => { beforeEach(() => {
instance.dropdown.isActive = true; instance.dropdown.isActive = true;
instance.canSearch = true; instance._canSearch = true;
output = instance.hideDropdown(true); output = instance.hideDropdown(true);
}); });
it('removes active descendants', () => { it('removes active descendants', done => {
expect(inputRemoveActiveDescendantSpy.called).to.equal(true); requestAnimationFrame(() => {
expect(inputRemoveActiveDescendantSpy.called).to.equal(true);
done();
});
}); });
it('blurs input', () => { it('blurs input', done => {
expect(inputBlurSpy.called).to.equal(true); requestAnimationFrame(() => {
expect(inputBlurSpy.called).to.equal(true);
done();
});
}); });
}); });
}); });
@ -540,15 +575,15 @@ describe('choices', () => {
storeGetGroupByIdStub = stub().returns({ storeGetGroupByIdStub = stub().returns({
value: groupIdValue, value: groupIdValue,
}); });
storeDispatchSpy = spy(instance.store, 'dispatch'); storeDispatchSpy = spy(instance._store, 'dispatch');
instance.store.getGroupById = storeGetGroupByIdStub; instance._store.getGroupById = storeGetGroupByIdStub;
instance.passedElement.triggerEvent = passedElementTriggerEventStub; instance.passedElement.triggerEvent = passedElementTriggerEventStub;
}); });
afterEach(() => { afterEach(() => {
storeDispatchSpy.restore(); storeDispatchSpy.restore();
instance.store.getGroupById.reset(); instance._store.getGroupById.reset();
instance.passedElement.triggerEvent.reset(); instance.passedElement.triggerEvent.reset();
}); });
@ -597,7 +632,9 @@ describe('choices', () => {
it('triggers event with null groupValue', () => { it('triggers event with null groupValue', () => {
expect(passedElementTriggerEventStub.called).to.equal(true); expect(passedElementTriggerEventStub.called).to.equal(true);
expect(passedElementTriggerEventStub.lastCall.args[0]).to.equal(EVENTS.highlightItem); expect(passedElementTriggerEventStub.lastCall.args[0]).to.equal(
EVENTS.highlightItem,
);
expect(passedElementTriggerEventStub.lastCall.args[1]).to.eql({ expect(passedElementTriggerEventStub.lastCall.args[1]).to.eql({
id: item.id, id: item.id,
value: item.value, value: item.value,
@ -615,7 +652,9 @@ describe('choices', () => {
it('triggers event with groupValue', () => { it('triggers event with groupValue', () => {
expect(passedElementTriggerEventStub.called).to.equal(true); expect(passedElementTriggerEventStub.called).to.equal(true);
expect(passedElementTriggerEventStub.lastCall.args[0]).to.equal(EVENTS.highlightItem); expect(passedElementTriggerEventStub.lastCall.args[0]).to.equal(
EVENTS.highlightItem,
);
expect(passedElementTriggerEventStub.lastCall.args[1]).to.eql({ expect(passedElementTriggerEventStub.lastCall.args[1]).to.eql({
id: item.id, id: item.id,
value: item.value, value: item.value,
@ -631,7 +670,7 @@ describe('choices', () => {
output = instance.highlightItem(item, false); output = instance.highlightItem(item, false);
}); });
it('doesn\'t trigger event', () => { it("doesn't trigger event", () => {
expect(passedElementTriggerEventStub.called).to.equal(false); expect(passedElementTriggerEventStub.called).to.equal(false);
}); });
@ -651,15 +690,15 @@ describe('choices', () => {
storeGetGroupByIdStub = stub().returns({ storeGetGroupByIdStub = stub().returns({
value: groupIdValue, value: groupIdValue,
}); });
storeDispatchSpy = spy(instance.store, 'dispatch'); storeDispatchSpy = spy(instance._store, 'dispatch');
instance.store.getGroupById = storeGetGroupByIdStub; instance._store.getGroupById = storeGetGroupByIdStub;
instance.passedElement.triggerEvent = passedElementTriggerEventStub; instance.passedElement.triggerEvent = passedElementTriggerEventStub;
}); });
afterEach(() => { afterEach(() => {
storeDispatchSpy.restore(); storeDispatchSpy.restore();
instance.store.getGroupById.reset(); instance._store.getGroupById.reset();
instance.passedElement.triggerEvent.reset(); instance.passedElement.triggerEvent.reset();
}); });
@ -708,7 +747,9 @@ describe('choices', () => {
it('triggers event with null groupValue', () => { it('triggers event with null groupValue', () => {
expect(passedElementTriggerEventStub.called).to.equal(true); expect(passedElementTriggerEventStub.called).to.equal(true);
expect(passedElementTriggerEventStub.lastCall.args[0]).to.equal(EVENTS.highlightItem); expect(passedElementTriggerEventStub.lastCall.args[0]).to.equal(
EVENTS.highlightItem,
);
expect(passedElementTriggerEventStub.lastCall.args[1]).to.eql({ expect(passedElementTriggerEventStub.lastCall.args[1]).to.eql({
id: item.id, id: item.id,
value: item.value, value: item.value,
@ -726,7 +767,9 @@ describe('choices', () => {
it('triggers event with groupValue', () => { it('triggers event with groupValue', () => {
expect(passedElementTriggerEventStub.called).to.equal(true); expect(passedElementTriggerEventStub.called).to.equal(true);
expect(passedElementTriggerEventStub.lastCall.args[0]).to.equal(EVENTS.highlightItem); expect(passedElementTriggerEventStub.lastCall.args[0]).to.equal(
EVENTS.highlightItem,
);
expect(passedElementTriggerEventStub.lastCall.args[1]).to.eql({ expect(passedElementTriggerEventStub.lastCall.args[1]).to.eql({
id: item.id, id: item.id,
value: item.value, value: item.value,
@ -742,7 +785,7 @@ describe('choices', () => {
output = instance.highlightItem(item, false); output = instance.highlightItem(item, false);
}); });
it('doesn\'t trigger event', () => { it("doesn't trigger event", () => {
expect(passedElementTriggerEventStub.called).to.equal(false); expect(passedElementTriggerEventStub.called).to.equal(false);
}); });
@ -767,7 +810,7 @@ describe('choices', () => {
]; ];
beforeEach(() => { beforeEach(() => {
storeGetItemsStub = stub(instance.store, 'items').get(() => items); storeGetItemsStub = stub(instance._store, 'items').get(() => items);
highlightItemStub = stub(); highlightItemStub = stub();
instance.highlightItem = highlightItemStub; instance.highlightItem = highlightItemStub;
@ -805,7 +848,7 @@ describe('choices', () => {
]; ];
beforeEach(() => { beforeEach(() => {
storeGetItemsStub = stub(instance.store, 'items').get(() => items); storeGetItemsStub = stub(instance._store, 'items').get(() => items);
unhighlightItemStub = stub(); unhighlightItemStub = stub();
instance.unhighlightItem = unhighlightItemStub; instance.unhighlightItem = unhighlightItemStub;
@ -832,13 +875,13 @@ describe('choices', () => {
beforeEach(() => { beforeEach(() => {
storeDispatchStub = stub(); storeDispatchStub = stub();
instance.store.dispatch = storeDispatchStub; instance._store.dispatch = storeDispatchStub;
output = instance.clearStore(); output = instance.clearStore();
}); });
afterEach(() => { afterEach(() => {
instance.store.dispatch.reset(); instance._store.dispatch.reset();
}); });
returnsInstance(output); returnsInstance(output);
@ -857,21 +900,21 @@ describe('choices', () => {
beforeEach(() => { beforeEach(() => {
inputClearSpy = spy(instance.input, 'clear'); inputClearSpy = spy(instance.input, 'clear');
storeDispatchStub = stub(); storeDispatchStub = stub();
instance.store.dispatch = storeDispatchStub; instance._store.dispatch = storeDispatchStub;
output = instance.clearInput(); output = instance.clearInput();
}); });
afterEach(() => { afterEach(() => {
inputClearSpy.restore(); inputClearSpy.restore();
instance.store.dispatch.reset(); instance._store.dispatch.reset();
}); });
returnsInstance(output); returnsInstance(output);
describe('text element', () => { describe('text element', () => {
beforeEach(() => { beforeEach(() => {
instance.isSelectOneElement = false; instance._isSelectOneElement = false;
instance.isTextElement = false; instance._isTextElement = false;
output = instance.clearInput(); output = instance.clearInput();
}); });
@ -884,8 +927,8 @@ describe('choices', () => {
describe('select element with search enabled', () => { describe('select element with search enabled', () => {
beforeEach(() => { beforeEach(() => {
instance.isSelectOneElement = true; instance._isSelectOneElement = true;
instance.isTextElement = false; instance._isTextElement = false;
instance.config.searchEnabled = true; instance.config.searchEnabled = true;
output = instance.clearInput(); output = instance.clearInput();
@ -897,7 +940,7 @@ describe('choices', () => {
}); });
it('resets search flag', () => { it('resets search flag', () => {
expect(instance.isSearching).to.equal(false); expect(instance._isSearching).to.equal(false);
}); });
it('dispatches activateChoices action', () => { it('dispatches activateChoices action', () => {
@ -948,7 +991,7 @@ describe('choices', () => {
describe('text element', () => { describe('text element', () => {
beforeEach(() => { beforeEach(() => {
instance.isSelectElement = false; instance._isSelectElement = false;
output = instance.ajax(() => {}); output = instance.ajax(() => {});
}); });
@ -970,7 +1013,7 @@ describe('choices', () => {
beforeEach(() => { beforeEach(() => {
instance.initialised = true; instance.initialised = true;
instance.isSelectElement = true; instance._isSelectElement = true;
ajaxCallbackStub = stub(); ajaxCallbackStub = stub();
callback = stub(); callback = stub();
output = instance.ajax(callback); output = instance.ajax(callback);
@ -978,7 +1021,7 @@ describe('choices', () => {
returnsInstance(output); returnsInstance(output);
it('sets loading state', (done) => { it('sets loading state', done => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
expect(handleLoadingStateStub.called).to.equal(true); expect(handleLoadingStateStub.called).to.equal(true);
done(); done();
@ -1067,7 +1110,7 @@ describe('choices', () => {
describe('when already initialised and not text element', () => { describe('when already initialised and not text element', () => {
beforeEach(() => { beforeEach(() => {
instance.initialised = true; instance.initialised = true;
instance.isTextElement = false; instance._isTextElement = false;
}); });
describe('passing a string value', () => { describe('passing a string value', () => {
@ -1081,15 +1124,14 @@ describe('choices', () => {
it('sets each choice with same value', () => { it('sets each choice with same value', () => {
expect(findAndSelectChoiceByValueStub.called).to.equal(true); expect(findAndSelectChoiceByValueStub.called).to.equal(true);
expect(findAndSelectChoiceByValueStub.firstCall.args[0]).to.equal(value); expect(findAndSelectChoiceByValueStub.firstCall.args[0]).to.equal(
value,
);
}); });
}); });
describe('passing an array of values', () => { describe('passing an array of values', () => {
const values = [ const values = ['Value 1', 'Value 2'];
'Value 1',
'Value 2',
];
beforeEach(() => { beforeEach(() => {
output = instance.setChoiceByValue(values); output = instance.setChoiceByValue(values);
@ -1099,8 +1141,12 @@ describe('choices', () => {
it('sets each choice with same value', () => { it('sets each choice with same value', () => {
expect(findAndSelectChoiceByValueStub.callCount).to.equal(2); expect(findAndSelectChoiceByValueStub.callCount).to.equal(2);
expect(findAndSelectChoiceByValueStub.firstCall.args[0]).to.equal(values[0]); expect(findAndSelectChoiceByValueStub.firstCall.args[0]).to.equal(
expect(findAndSelectChoiceByValueStub.secondCall.args[0]).to.equal(values[1]); values[0],
);
expect(findAndSelectChoiceByValueStub.secondCall.args[0]).to.equal(
values[1],
);
}); });
}); });
}); });
@ -1120,7 +1166,7 @@ describe('choices', () => {
]; ];
beforeEach(() => { beforeEach(() => {
activeItemsStub = stub(instance.store, 'activeItems').get(() => items); activeItemsStub = stub(instance._store, 'activeItems').get(() => items);
}); });
afterEach(() => { afterEach(() => {
@ -1130,7 +1176,7 @@ describe('choices', () => {
describe('passing true valueOnly flag', () => { describe('passing true valueOnly flag', () => {
describe('select one input', () => { describe('select one input', () => {
beforeEach(() => { beforeEach(() => {
instance.isSelectOneElement = true; instance._isSelectOneElement = true;
output = instance.getValue(true); output = instance.getValue(true);
}); });
@ -1141,12 +1187,12 @@ describe('choices', () => {
describe('non select one input', () => { describe('non select one input', () => {
beforeEach(() => { beforeEach(() => {
instance.isSelectOneElement = false; instance._isSelectOneElement = false;
output = instance.getValue(true); output = instance.getValue(true);
}); });
it('returns all active item values', () => { it('returns all active item values', () => {
expect(output).to.eql(items.map((item => item.value))); expect(output).to.eql(items.map(item => item.value));
}); });
}); });
}); });
@ -1154,7 +1200,7 @@ describe('choices', () => {
describe('passing false valueOnly flag', () => { describe('passing false valueOnly flag', () => {
describe('select one input', () => { describe('select one input', () => {
beforeEach(() => { beforeEach(() => {
instance.isSelectOneElement = true; instance._isSelectOneElement = true;
output = instance.getValue(false); output = instance.getValue(false);
}); });
@ -1165,7 +1211,7 @@ describe('choices', () => {
describe('non select one input', () => { describe('non select one input', () => {
beforeEach(() => { beforeEach(() => {
instance.isSelectOneElement = false; instance._isSelectOneElement = false;
output = instance.getValue(false); output = instance.getValue(false);
}); });
@ -1177,51 +1223,41 @@ describe('choices', () => {
}); });
describe('removeActiveItemsByValue', () => { describe('removeActiveItemsByValue', () => {
describe('passing invalid value', () => { let activeItemsStub;
beforeEach(() => { let removeItemStub;
output = instance.removeActiveItemsByValue(null); const value = 'Removed';
}); const items = [
{
id: '1',
value: 'Not removed',
},
{
id: '2',
value: 'Removed',
},
{
id: '3',
value: 'Removed',
},
];
returnsInstance(output); beforeEach(() => {
removeItemStub = stub();
activeItemsStub = stub(instance._store, 'activeItems').get(() => items);
instance._removeItem = removeItemStub;
output = instance.removeActiveItemsByValue(value);
}); });
describe('passing valid value', () => { afterEach(() => {
let activeItemsStub; activeItemsStub.reset();
let removeItemStub; instance._removeItem.reset();
const value = 'Removed'; });
const items = [
{
id: '1',
value: 'Not removed',
},
{
id: '2',
value: 'Removed',
},
{
id: '3',
value: 'Removed',
},
];
beforeEach(() => { it('removes each active item in store with matching value', () => {
removeItemStub = stub(); expect(removeItemStub.callCount).to.equal(2);
activeItemsStub = stub(instance.store, 'activeItems').get(() => items); expect(removeItemStub.firstCall.args[0]).to.equal(items[1]);
instance._removeItem = removeItemStub; expect(removeItemStub.secondCall.args[0]).to.equal(items[2]);
output = instance.removeActiveItemsByValue(value);
});
afterEach(() => {
activeItemsStub.reset();
instance._removeItem.reset();
});
it('removes each active item in store with matching value', () => {
expect(removeItemStub.callCount).to.equal(2);
expect(removeItemStub.firstCall.args[0]).to.equal(items[1]);
expect(removeItemStub.secondCall.args[0]).to.equal(items[2]);
});
}); });
}); });
@ -1245,7 +1281,7 @@ describe('choices', () => {
beforeEach(() => { beforeEach(() => {
removeItemStub = stub(); removeItemStub = stub();
activeItemsStub = stub(instance.store, 'activeItems').get(() => items); activeItemsStub = stub(instance._store, 'activeItems').get(() => items);
instance._removeItem = removeItemStub; instance._removeItem = removeItemStub;
}); });
@ -1298,9 +1334,11 @@ describe('choices', () => {
}, },
]; ];
beforeEach(() => { beforeEach(() => {
highlightedActiveItemsStub = stub(instance.store, 'highlightedActiveItems').get(() => items); highlightedActiveItemsStub = stub(
instance._store,
'highlightedActiveItems',
).get(() => items);
removeItemStub = stub(); removeItemStub = stub();
triggerChangeStub = stub(); triggerChangeStub = stub();
@ -1397,7 +1435,7 @@ describe('choices', () => {
describe('when element is not select element', () => { describe('when element is not select element', () => {
beforeEach(() => { beforeEach(() => {
instance.isSelectElement = false; instance._isSelectElement = false;
instance.setChoices(choices, value, label, false); instance.setChoices(choices, value, label, false);
}); });
@ -1407,7 +1445,7 @@ describe('choices', () => {
describe('passing invalid arguments', () => { describe('passing invalid arguments', () => {
describe('passing an empty array', () => { describe('passing an empty array', () => {
beforeEach(() => { beforeEach(() => {
instance.isSelectElement = true; instance._isSelectElement = true;
instance.setChoices([], value, label, false); instance.setChoices([], value, label, false);
}); });
@ -1416,7 +1454,7 @@ describe('choices', () => {
describe('passing no value', () => { describe('passing no value', () => {
beforeEach(() => { beforeEach(() => {
instance.isSelectElement = true; instance._isSelectElement = true;
instance.setChoices(choices, undefined, 'label', false); instance.setChoices(choices, undefined, 'label', false);
}); });
@ -1426,7 +1464,7 @@ describe('choices', () => {
describe('passing valid arguments', () => { describe('passing valid arguments', () => {
beforeEach(() => { beforeEach(() => {
instance.isSelectElement = true; instance._isSelectElement = true;
}); });
it('removes loading state', () => { it('removes loading state', () => {
@ -1477,8 +1515,8 @@ describe('choices', () => {
}); });
}); });
describe('createGroupsFragment', () => { describe('_createGroupsFragment', () => {
let createChoicesFragmentStub; let _createChoicesFragmentStub;
const choices = [ const choices = [
{ {
id: 1, id: 1,
@ -1519,12 +1557,12 @@ describe('choices', () => {
]; ];
beforeEach(() => { beforeEach(() => {
createChoicesFragmentStub = stub(); _createChoicesFragmentStub = stub();
instance.createChoicesFragment = createChoicesFragmentStub; instance._createChoicesFragment = _createChoicesFragmentStub;
}); });
afterEach(() => { afterEach(() => {
instance.createChoicesFragment.reset(); instance._createChoicesFragment.reset();
}); });
describe('returning a fragment of groups', () => { describe('returning a fragment of groups', () => {
@ -1534,24 +1572,28 @@ describe('choices', () => {
const childElement = document.createElement('div'); const childElement = document.createElement('div');
fragment.appendChild(childElement); fragment.appendChild(childElement);
output = instance.createGroupsFragment(groups, choices, fragment); output = instance._createGroupsFragment(groups, choices, fragment);
const elementToWrapFragment = document.createElement('div'); const elementToWrapFragment = document.createElement('div');
elementToWrapFragment.appendChild(output); elementToWrapFragment.appendChild(output);
expect(output).to.be.instanceOf(DocumentFragment); expect(output).to.be.instanceOf(DocumentFragment);
expect(elementToWrapFragment.children[0]).to.eql(childElement); expect(elementToWrapFragment.children[0]).to.eql(childElement);
expect(elementToWrapFragment.querySelectorAll('[data-group]').length).to.equal(2); expect(
elementToWrapFragment.querySelectorAll('[data-group]').length,
).to.equal(2);
}); });
}); });
describe('not passing fragment argument', () => { describe('not passing fragment argument', () => {
it('returns new groups fragment', () => { it('returns new groups fragment', () => {
output = instance.createGroupsFragment(groups, choices); output = instance._createGroupsFragment(groups, choices);
const elementToWrapFragment = document.createElement('div'); const elementToWrapFragment = document.createElement('div');
elementToWrapFragment.appendChild(output); elementToWrapFragment.appendChild(output);
expect(output).to.be.instanceOf(DocumentFragment); expect(output).to.be.instanceOf(DocumentFragment);
expect(elementToWrapFragment.querySelectorAll('[data-group]').length).to.equal(2); expect(
elementToWrapFragment.querySelectorAll('[data-group]').length,
).to.equal(2);
}); });
}); });
@ -1570,7 +1612,7 @@ describe('choices', () => {
it('sorts groups by config.sortFn', () => { it('sorts groups by config.sortFn', () => {
expect(sortFnStub.called).to.equal(false); expect(sortFnStub.called).to.equal(false);
instance.createGroupsFragment(groups, choices); instance._createGroupsFragment(groups, choices);
expect(sortFnStub.called).to.equal(true); expect(sortFnStub.called).to.equal(true);
}); });
}); });
@ -1589,21 +1631,21 @@ describe('choices', () => {
}); });
it('does not sort groups', () => { it('does not sort groups', () => {
instance.createGroupsFragment(groups, choices); instance._createGroupsFragment(groups, choices);
expect(sortFnStub.called).to.equal(false); expect(sortFnStub.called).to.equal(false);
}); });
}); });
describe('select-one element', () => { describe('select-one element', () => {
beforeEach(() => { beforeEach(() => {
instance.isSelectOneElement = true; instance._isSelectOneElement = true;
}); });
it('calls createChoicesFragment with choices that belong to each group', () => { it('calls _createChoicesFragment with choices that belong to each group', () => {
expect(createChoicesFragmentStub.called).to.equal(false); expect(_createChoicesFragmentStub.called).to.equal(false);
instance.createGroupsFragment(groups, choices); instance._createGroupsFragment(groups, choices);
expect(createChoicesFragmentStub.called).to.equal(true); expect(_createChoicesFragmentStub.called).to.equal(true);
expect(createChoicesFragmentStub.firstCall.args[0]).to.eql([ expect(_createChoicesFragmentStub.firstCall.args[0]).to.eql([
{ {
id: 1, id: 1,
selected: true, selected: true,
@ -1619,7 +1661,7 @@ describe('choices', () => {
label: 'Choice 3', label: 'Choice 3',
}, },
]); ]);
expect(createChoicesFragmentStub.secondCall.args[0]).to.eql([ expect(_createChoicesFragmentStub.secondCall.args[0]).to.eql([
{ {
id: 2, id: 2,
selected: false, selected: false,
@ -1634,15 +1676,15 @@ describe('choices', () => {
describe('text/select-multiple element', () => { describe('text/select-multiple element', () => {
describe('renderSelectedChoices set to "always"', () => { describe('renderSelectedChoices set to "always"', () => {
beforeEach(() => { beforeEach(() => {
instance.isSelectOneElement = false; instance._isSelectOneElement = false;
instance.config.renderSelectedChoices = 'always'; instance.config.renderSelectedChoices = 'always';
}); });
it('calls createChoicesFragment with choices that belong to each group', () => { it('calls _createChoicesFragment with choices that belong to each group', () => {
expect(createChoicesFragmentStub.called).to.equal(false); expect(_createChoicesFragmentStub.called).to.equal(false);
instance.createGroupsFragment(groups, choices); instance._createGroupsFragment(groups, choices);
expect(createChoicesFragmentStub.called).to.equal(true); expect(_createChoicesFragmentStub.called).to.equal(true);
expect(createChoicesFragmentStub.firstCall.args[0]).to.eql([ expect(_createChoicesFragmentStub.firstCall.args[0]).to.eql([
{ {
id: 1, id: 1,
selected: true, selected: true,
@ -1658,7 +1700,7 @@ describe('choices', () => {
label: 'Choice 3', label: 'Choice 3',
}, },
]); ]);
expect(createChoicesFragmentStub.secondCall.args[0]).to.eql([ expect(_createChoicesFragmentStub.secondCall.args[0]).to.eql([
{ {
id: 2, id: 2,
selected: false, selected: false,
@ -1672,15 +1714,15 @@ describe('choices', () => {
describe('renderSelectedChoices not set to "always"', () => { describe('renderSelectedChoices not set to "always"', () => {
beforeEach(() => { beforeEach(() => {
instance.isSelectOneElement = false; instance._isSelectOneElement = false;
instance.config.renderSelectedChoices = false; instance.config.renderSelectedChoices = false;
}); });
it('calls createChoicesFragment with choices that belong to each group that are not already selected', () => { it('calls _createChoicesFragment with choices that belong to each group that are not already selected', () => {
expect(createChoicesFragmentStub.called).to.equal(false); expect(_createChoicesFragmentStub.called).to.equal(false);
instance.createGroupsFragment(groups, choices); instance._createGroupsFragment(groups, choices);
expect(createChoicesFragmentStub.called).to.equal(true); expect(_createChoicesFragmentStub.called).to.equal(true);
expect(createChoicesFragmentStub.firstCall.args[0]).to.eql([ expect(_createChoicesFragmentStub.firstCall.args[0]).to.eql([
{ {
id: 3, id: 3,
selected: false, selected: false,
@ -1689,7 +1731,7 @@ describe('choices', () => {
label: 'Choice 3', label: 'Choice 3',
}, },
]); ]);
expect(createChoicesFragmentStub.secondCall.args[0]).to.eql([ expect(_createChoicesFragmentStub.secondCall.args[0]).to.eql([
{ {
id: 2, id: 2,
selected: false, selected: false,
@ -1704,41 +1746,31 @@ describe('choices', () => {
}); });
}); });
describe('createChoicesFragment', () => { // describe('render', () => {
beforeEach(() => {}); // beforeEach(() => {});
it('returns a fragment of choices', () => {});
});
describe('createItemsFragment', () => { // describe('no change to state', () => {
beforeEach(() => {}); // it('returns early', () => {});
it('returns a fragment of items', () => {}); // });
});
describe('render', () => { // describe('change to state', () => {
beforeEach(() => {}); // it('updates previous state to current state', () => {});
describe('no change to state', () => { // describe('select element', () => {
it('returns early', () => {}); // it('clears choice list', () => {});
});
describe('change to state', () => { // describe('when resetScrollPosition config option is set to true', () => {
it('updates previous state to current state', () => {}); // it('scrolls to top of choice list', () => {});
// });
// });
describe('select element', () => { // describe('text element', () => {
it('clears choice list', () => {}); // describe('active items in store', () => {
// it('clears item list', () => {});
describe('when resetScrollPosition config option is set to true', () => { // it('renders active items inside item list', () => {});
it('scrolls to top of choice list', () => {}); // });
}); // });
}); // });
// });
describe('text element', () => {
describe('active items in store', () => {
it('clears item list', () => {});
it('renders active items inside item list', () => {});
});
});
});
});
}); });
}); });

View file

@ -1,50 +1,35 @@
import { getWindowHeight, wrap } from '../lib/utils'; import { getWindowHeight, wrap } from '../lib/utils';
export default class Container { export default class Container {
constructor(instance, element, classNames) { constructor({ element, type, classNames, position }) {
this.parentInstance = instance; Object.assign(this, { element, classNames, type, position });
this.element = element;
this.classNames = classNames;
this.config = instance.config;
this.isOpen = false; this.isOpen = false;
this.isFlipped = false; this.isFlipped = false;
this.isFocussed = false; this.isFocussed = false;
this.isDisabled = false; this.isDisabled = false;
this.isLoading = false; this.isLoading = false;
this.onFocus = this.onFocus.bind(this);
this.onBlur = this.onBlur.bind(this); this._onFocus = this._onFocus.bind(this);
this._onBlur = this._onBlur.bind(this);
} }
/** /**
* Add event listeners * Add event listeners
*/ */
addEventListeners() { addEventListeners() {
this.element.addEventListener('focus', this.onFocus); this.element.addEventListener('focus', this._onFocus);
this.element.addEventListener('blur', this.onBlur); this.element.addEventListener('blur', this._onBlur);
} }
/** /**
* Remove event listeners * Remove event listeners
*/ */
/** */ /** */
removeEventListeners() { removeEventListeners() {
this.element.removeEventListener('focus', this.onFocus); this.element.removeEventListener('focus', this._onFocus);
this.element.removeEventListener('blur', this.onBlur); this.element.removeEventListener('blur', this._onBlur);
}
/**
* Set focussed state
*/
onFocus() {
this.isFocussed = true;
}
/**
* Remove blurred state
*/
onBlur() {
this.isFocussed = false;
} }
/** /**
@ -57,16 +42,16 @@ export default class Container {
if (dropdownPos === undefined) { if (dropdownPos === undefined) {
return false; return false;
} }
// If flip is enabled and the dropdown bottom position is // If flip is enabled and the dropdown bottom position is
// greater than the window height flip the dropdown. // greater than the window height flip the dropdown.
let shouldFlip = false; let shouldFlip = false;
if (this.config.position === 'auto') { if (this.position === 'auto') {
shouldFlip = dropdownPos >= windowHeight; shouldFlip = dropdownPos >= windowHeight;
} else if (this.config.position === 'top') { } else if (this.position === 'top') {
shouldFlip = true; shouldFlip = true;
} }
return shouldFlip; return shouldFlip;
} }
@ -129,7 +114,7 @@ export default class Container {
enable() { enable() {
this.element.classList.remove(this.classNames.disabledState); this.element.classList.remove(this.classNames.disabledState);
this.element.removeAttribute('aria-disabled'); this.element.removeAttribute('aria-disabled');
if (this.parentInstance.isSelectOneElement) { if (this.type === 'select-one') {
this.element.setAttribute('tabindex', '0'); this.element.setAttribute('tabindex', '0');
} }
this.isDisabled = false; this.isDisabled = false;
@ -141,7 +126,7 @@ export default class Container {
disable() { disable() {
this.element.classList.add(this.classNames.disabledState); this.element.classList.add(this.classNames.disabledState);
this.element.setAttribute('aria-disabled', 'true'); this.element.setAttribute('aria-disabled', 'true');
if (this.parentInstance.isSelectOneElement) { if (this.type === 'select-one') {
this.element.setAttribute('tabindex', '-1'); this.element.setAttribute('tabindex', '-1');
} }
this.isDisabled = true; this.isDisabled = true;
@ -153,10 +138,7 @@ export default class Container {
unwrap(element) { unwrap(element) {
// Move passed element outside this element // Move passed element outside this element
this.element.parentNode.insertBefore( this.element.parentNode.insertBefore(element, this.element);
element,
this.element,
);
// Remove this element // Remove this element
this.element.parentNode.removeChild(this.element); this.element.parentNode.removeChild(this.element);
} }
@ -178,4 +160,18 @@ export default class Container {
this.element.removeAttribute('aria-busy'); this.element.removeAttribute('aria-busy');
this.isLoading = false; this.isLoading = false;
} }
/**
* Set focussed state
*/
_onFocus() {
this.isFocussed = true;
}
/**
* Remove blurred state
*/
_onBlur() {
this.isFocussed = false;
}
} }

View file

@ -1,23 +1,23 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { stub } from 'sinon'; import { stub } from 'sinon';
import Container from './container'; import Container from './container';
import { DEFAULT_CLASSNAMES, DEFAULT_CONFIG } from '../constants'; import { DEFAULT_CLASSNAMES } from '../constants';
describe('components/container', () => { describe('components/container', () => {
let instance; let instance;
let choicesInstance;
let element; let element;
beforeEach(() => { beforeEach(() => {
choicesInstance = {
config: {
...DEFAULT_CONFIG,
},
};
element = document.createElement('div'); element = document.createElement('div');
element.id = 'container'; element.id = 'container';
document.body.appendChild(element); document.body.appendChild(element);
instance = new Container(choicesInstance, document.getElementById('container'), DEFAULT_CLASSNAMES); instance = new Container({
element: document.getElementById('container'),
classNames: DEFAULT_CLASSNAMES,
position: 'auto',
type: 'text',
});
}); });
afterEach(() => { afterEach(() => {
@ -27,10 +27,6 @@ describe('components/container', () => {
}); });
describe('constructor', () => { describe('constructor', () => {
it('assigns choices instance to class', () => {
expect(instance.parentInstance).to.eql(choicesInstance);
});
it('assigns choices element to class', () => { it('assigns choices element to class', () => {
expect(instance.element).to.eql(element); expect(instance.element).to.eql(element);
}); });
@ -81,7 +77,7 @@ describe('components/container', () => {
describe('onFocus', () => { describe('onFocus', () => {
it('sets isFocussed flag to true', () => { it('sets isFocussed flag to true', () => {
expect(instance.isFocussed).to.equal(false); expect(instance.isFocussed).to.equal(false);
instance.onFocus(); instance._onFocus();
expect(instance.isFocussed).to.equal(true); expect(instance.isFocussed).to.equal(true);
}); });
}); });
@ -89,7 +85,7 @@ describe('components/container', () => {
describe('onBlur', () => { describe('onBlur', () => {
it('sets isFocussed flag to false', () => { it('sets isFocussed flag to false', () => {
instance.isFocussed = true; instance.isFocussed = true;
instance.onBlur(); instance._onBlur();
expect(instance.isFocussed).to.equal(false); expect(instance.isFocussed).to.equal(false);
}); });
}); });
@ -104,7 +100,7 @@ describe('components/container', () => {
describe('passing dropdownPos', () => { describe('passing dropdownPos', () => {
describe('position config option set to "auto"', () => { describe('position config option set to "auto"', () => {
beforeEach(() => { beforeEach(() => {
instance.config.position = 'auto'; instance.position = 'auto';
}); });
describe('dropdownPos is greater than window height', () => { describe('dropdownPos is greater than window height', () => {
@ -122,7 +118,7 @@ describe('components/container', () => {
describe('position config option set to "top"', () => { describe('position config option set to "top"', () => {
beforeEach(() => { beforeEach(() => {
instance.config.position = 'top'; instance.position = 'top';
}); });
it('returns true', () => { it('returns true', () => {
@ -132,7 +128,7 @@ describe('components/container', () => {
describe('position config option set to "bottom"', () => { describe('position config option set to "bottom"', () => {
beforeEach(() => { beforeEach(() => {
instance.config.position = 'bottom'; instance.position = 'bottom';
}); });
it('returns false', () => { it('returns false', () => {
@ -143,21 +139,32 @@ describe('components/container', () => {
}); });
describe('setActiveDescendant', () => { describe('setActiveDescendant', () => {
it('sets element\'s aria-activedescendant attribute with passed descendant ID', () => { it("sets element's aria-activedescendant attribute with passed descendant ID", () => {
const activeDescendantID = '1234'; const activeDescendantID = '1234';
expect(instance.element.getAttribute('aria-activedescendant')).to.equal(null); expect(instance.element.getAttribute('aria-activedescendant')).to.equal(
null,
);
instance.setActiveDescendant(activeDescendantID); instance.setActiveDescendant(activeDescendantID);
expect(instance.element.getAttribute('aria-activedescendant')).to.equal(activeDescendantID); expect(instance.element.getAttribute('aria-activedescendant')).to.equal(
activeDescendantID,
);
}); });
}); });
describe('removeActiveDescendant', () => { describe('removeActiveDescendant', () => {
it('remove elememnt\'s aria-activedescendant attribute', () => { it("remove elememnt's aria-activedescendant attribute", () => {
const activeDescendantID = '1234'; const activeDescendantID = '1234';
instance.element.setAttribute('aria-activedescendant', activeDescendantID); instance.element.setAttribute(
expect(instance.element.getAttribute('aria-activedescendant')).to.equal(activeDescendantID); 'aria-activedescendant',
activeDescendantID,
);
expect(instance.element.getAttribute('aria-activedescendant')).to.equal(
activeDescendantID,
);
instance.removeActiveDescendant(); instance.removeActiveDescendant();
expect(instance.element.getAttribute('aria-activedescendant')).to.equal(null); expect(instance.element.getAttribute('aria-activedescendant')).to.equal(
null,
);
}); });
}); });
@ -167,7 +174,9 @@ describe('components/container', () => {
}); });
it('adds open state class', () => { it('adds open state class', () => {
expect(instance.element.classList.contains(DEFAULT_CLASSNAMES.openState)).to.equal(true); expect(
instance.element.classList.contains(DEFAULT_CLASSNAMES.openState),
).to.equal(true);
}); });
it('sets aria-expanded attribute to true', () => { it('sets aria-expanded attribute to true', () => {
@ -192,7 +201,9 @@ describe('components/container', () => {
}); });
it('adds adds flipped state class', () => { it('adds adds flipped state class', () => {
expect(instance.element.classList.contains(DEFAULT_CLASSNAMES.flippedState)).to.equal(true); expect(
instance.element.classList.contains(DEFAULT_CLASSNAMES.flippedState),
).to.equal(true);
}); });
it('sets isFlipped flag to true', () => { it('sets isFlipped flag to true', () => {
@ -207,7 +218,9 @@ describe('components/container', () => {
}); });
it('adds open state class', () => { it('adds open state class', () => {
expect(instance.element.classList.contains(DEFAULT_CLASSNAMES.openState)).to.equal(false); expect(
instance.element.classList.contains(DEFAULT_CLASSNAMES.openState),
).to.equal(false);
}); });
it('sets aria-expanded attribute to true', () => { it('sets aria-expanded attribute to true', () => {
@ -225,7 +238,9 @@ describe('components/container', () => {
}); });
it('removes adds flipped state class', () => { it('removes adds flipped state class', () => {
expect(instance.element.classList.contains(DEFAULT_CLASSNAMES.flippedState)).to.equal(false); expect(
instance.element.classList.contains(DEFAULT_CLASSNAMES.flippedState),
).to.equal(false);
}); });
it('sets isFlipped flag to false', () => { it('sets isFlipped flag to false', () => {
@ -268,9 +283,13 @@ describe('components/container', () => {
}); });
it('adds focus state class', () => { it('adds focus state class', () => {
expect(instance.element.classList.contains(DEFAULT_CLASSNAMES.focusState)).to.equal(false); expect(
instance.element.classList.contains(DEFAULT_CLASSNAMES.focusState),
).to.equal(false);
instance.addFocusState(); instance.addFocusState();
expect(instance.element.classList.contains(DEFAULT_CLASSNAMES.focusState)).to.equal(true); expect(
instance.element.classList.contains(DEFAULT_CLASSNAMES.focusState),
).to.equal(true);
}); });
}); });
@ -280,9 +299,13 @@ describe('components/container', () => {
}); });
it('removes focus state class', () => { it('removes focus state class', () => {
expect(instance.element.classList.contains(DEFAULT_CLASSNAMES.focusState)).to.equal(true); expect(
instance.element.classList.contains(DEFAULT_CLASSNAMES.focusState),
).to.equal(true);
instance.removeFocusState(); instance.removeFocusState();
expect(instance.element.classList.contains(DEFAULT_CLASSNAMES.focusState)).to.equal(false); expect(
instance.element.classList.contains(DEFAULT_CLASSNAMES.focusState),
).to.equal(false);
}); });
}); });
@ -292,9 +315,13 @@ describe('components/container', () => {
}); });
it('removes disabled state class', () => { it('removes disabled state class', () => {
expect(instance.element.classList.contains(DEFAULT_CLASSNAMES.disabledState)).to.equal(true); expect(
instance.element.classList.contains(DEFAULT_CLASSNAMES.disabledState),
).to.equal(true);
instance.enable(); instance.enable();
expect(instance.element.classList.contains(DEFAULT_CLASSNAMES.disabledState)).to.equal(false); expect(
instance.element.classList.contains(DEFAULT_CLASSNAMES.disabledState),
).to.equal(false);
}); });
it('removes aria-disabled attribute', () => { it('removes aria-disabled attribute', () => {
@ -310,7 +337,7 @@ describe('components/container', () => {
describe('select one element', () => { describe('select one element', () => {
beforeEach(() => { beforeEach(() => {
instance.parentInstance.isSelectOneElement = true; instance.type = 'select-one';
instance.enable(); instance.enable();
}); });
@ -326,9 +353,13 @@ describe('components/container', () => {
}); });
it('removes disabled state class', () => { it('removes disabled state class', () => {
expect(instance.element.classList.contains(DEFAULT_CLASSNAMES.disabledState)).to.equal(false); expect(
instance.element.classList.contains(DEFAULT_CLASSNAMES.disabledState),
).to.equal(false);
instance.disable(); instance.disable();
expect(instance.element.classList.contains(DEFAULT_CLASSNAMES.disabledState)).to.equal(true); expect(
instance.element.classList.contains(DEFAULT_CLASSNAMES.disabledState),
).to.equal(true);
}); });
it('removes aria-disabled attribute', () => { it('removes aria-disabled attribute', () => {
@ -344,7 +375,7 @@ describe('components/container', () => {
describe('select one element', () => { describe('select one element', () => {
beforeEach(() => { beforeEach(() => {
instance.parentInstance.isSelectOneElement = true; instance.type = 'select-one';
instance.disable(); instance.disable();
}); });
@ -370,7 +401,9 @@ describe('components/container', () => {
it('wraps passed element inside element', () => { it('wraps passed element inside element', () => {
expect(instance.element.querySelector('div#wrap-test')).to.equal(null); expect(instance.element.querySelector('div#wrap-test')).to.equal(null);
instance.wrap(document.querySelector('div#wrap-test')); instance.wrap(document.querySelector('div#wrap-test'));
expect(instance.element.querySelector('div#wrap-test')).to.equal(elementToWrap); expect(instance.element.querySelector('div#wrap-test')).to.equal(
elementToWrap,
);
}); });
}); });
@ -389,10 +422,14 @@ describe('components/container', () => {
}); });
it('moves wrapped element outside of element', () => { it('moves wrapped element outside of element', () => {
expect(instance.element.querySelector('div#unwrap-test')).to.be.instanceof(HTMLElement); expect(
instance.element.querySelector('div#unwrap-test'),
).to.be.instanceof(HTMLElement);
instance.unwrap(elementToUnwrap); instance.unwrap(elementToUnwrap);
expect(instance.element.querySelector('div#unwrap-test')).to.equal(null); expect(instance.element.querySelector('div#unwrap-test')).to.equal(null);
expect(document.querySelector('div#unwrap-test')).to.be.instanceof(HTMLElement); expect(document.querySelector('div#unwrap-test')).to.be.instanceof(
HTMLElement,
);
}); });
it('removes element from DOM', () => { it('removes element from DOM', () => {
@ -408,9 +445,13 @@ describe('components/container', () => {
}); });
it('adds loading state class', () => { it('adds loading state class', () => {
expect(instance.element.classList.contains(DEFAULT_CLASSNAMES.loadingState)).to.equal(false); expect(
instance.element.classList.contains(DEFAULT_CLASSNAMES.loadingState),
).to.equal(false);
instance.addLoadingState(); instance.addLoadingState();
expect(instance.element.classList.contains(DEFAULT_CLASSNAMES.loadingState)).to.equal(true); expect(
instance.element.classList.contains(DEFAULT_CLASSNAMES.loadingState),
).to.equal(true);
}); });
it('sets aria-busy attribute to true', () => { it('sets aria-busy attribute to true', () => {
@ -432,9 +473,13 @@ describe('components/container', () => {
}); });
it('removes loading state class', () => { it('removes loading state class', () => {
expect(instance.element.classList.contains(DEFAULT_CLASSNAMES.loadingState)).to.equal(true); expect(
instance.element.classList.contains(DEFAULT_CLASSNAMES.loadingState),
).to.equal(true);
instance.removeLoadingState(); instance.removeLoadingState();
expect(instance.element.classList.contains(DEFAULT_CLASSNAMES.loadingState)).to.equal(false); expect(
instance.element.classList.contains(DEFAULT_CLASSNAMES.loadingState),
).to.equal(false);
}); });
it('removes aria-busy attribute', () => { it('removes aria-busy attribute', () => {

View file

@ -1,10 +1,7 @@
export default class Dropdown { export default class Dropdown {
constructor(instance, element, classNames) { constructor({ element, type, classNames }) {
this.parentInstance = instance; Object.assign(this, { element, type, classNames });
this.element = element;
this.classNames = classNames;
this.dimensions = null;
this.position = null;
this.isActive = false; this.isActive = false;
} }
@ -15,7 +12,9 @@ export default class Dropdown {
*/ */
distanceFromTopWindow() { distanceFromTopWindow() {
this.dimensions = this.element.getBoundingClientRect(); this.dimensions = this.element.getBoundingClientRect();
this.position = Math.ceil(this.dimensions.top + window.pageYOffset + this.element.offsetHeight); this.position = Math.ceil(
this.dimensions.top + window.pageYOffset + this.element.offsetHeight,
);
return this.position; return this.position;
} }
@ -36,7 +35,7 @@ export default class Dropdown {
this.element.classList.add(this.classNames.activeState); this.element.classList.add(this.classNames.activeState);
this.element.setAttribute('aria-expanded', 'true'); this.element.setAttribute('aria-expanded', 'true');
this.isActive = true; this.isActive = true;
return this.parentInstance; return this;
} }
/** /**
@ -48,6 +47,6 @@ export default class Dropdown {
this.element.classList.remove(this.classNames.activeState); this.element.classList.remove(this.classNames.activeState);
this.element.setAttribute('aria-expanded', 'false'); this.element.setAttribute('aria-expanded', 'false');
this.isActive = false; this.isActive = false;
return this.parentInstance; return this;
} }
} }

View file

@ -1,23 +1,20 @@
import { expect } from 'chai'; import { expect } from 'chai';
import sinon from 'sinon'; import sinon from 'sinon';
import Dropdown from './dropdown'; import Dropdown from './dropdown';
import { DEFAULT_CLASSNAMES, DEFAULT_CONFIG } from '../constants'; import { DEFAULT_CLASSNAMES } from '../constants';
describe('components/dropdown', () => { describe('components/dropdown', () => {
let instance; let instance;
let choicesInstance;
let choicesElement; let choicesElement;
beforeEach(() => { beforeEach(() => {
choicesInstance = {
config: {
...DEFAULT_CONFIG,
},
};
choicesElement = document.createElement('div'); choicesElement = document.createElement('div');
document.body.appendChild(choicesElement); document.body.appendChild(choicesElement);
instance = new Dropdown(choicesInstance, choicesElement, DEFAULT_CLASSNAMES); instance = new Dropdown({
element: choicesElement,
type: 'text',
classNames: DEFAULT_CLASSNAMES,
});
}); });
afterEach(() => { afterEach(() => {
@ -26,10 +23,6 @@ describe('components/dropdown', () => {
}); });
describe('constructor', () => { describe('constructor', () => {
it('assigns choices instance to instance', () => {
expect(instance.parentInstance).to.eql(choicesInstance);
});
it('assigns choices element to instance', () => { it('assigns choices element to instance', () => {
expect(instance.element).to.eql(choicesElement); expect(instance.element).to.eql(choicesElement);
}); });
@ -57,7 +50,9 @@ describe('components/dropdown', () => {
width: 0, width: 0,
}; };
getBoundingClientRectStub = sinon.stub(instance.element, 'getBoundingClientRect').returns(dimensions); getBoundingClientRectStub = sinon
.stub(instance.element, 'getBoundingClientRect')
.returns(dimensions);
window.pageYOffset = 50; window.pageYOffset = 50;
}); });
@ -114,7 +109,9 @@ describe('components/dropdown', () => {
}); });
it('adds active class', () => { it('adds active class', () => {
expect(instance.element.classList.contains(DEFAULT_CLASSNAMES.activeState)).to.equal(true); expect(
instance.element.classList.contains(DEFAULT_CLASSNAMES.activeState),
).to.equal(true);
}); });
it('sets expanded attribute', () => { it('sets expanded attribute', () => {
@ -125,8 +122,8 @@ describe('components/dropdown', () => {
expect(instance.isActive).to.equal(true); expect(instance.isActive).to.equal(true);
}); });
it('returns parent instance', () => { it('returns instance', () => {
expect(actualResponse).to.eql(choicesInstance); expect(actualResponse).to.eql(instance);
}); });
}); });
@ -142,7 +139,9 @@ describe('components/dropdown', () => {
}); });
it('adds active class', () => { it('adds active class', () => {
expect(instance.element.classList.contains(DEFAULT_CLASSNAMES.activeState)).to.equal(false); expect(
instance.element.classList.contains(DEFAULT_CLASSNAMES.activeState),
).to.equal(false);
}); });
it('sets expanded attribute', () => { it('sets expanded attribute', () => {
@ -153,8 +152,8 @@ describe('components/dropdown', () => {
expect(instance.isActive).to.equal(false); expect(instance.isActive).to.equal(false);
}); });
it('returns parent instance', () => { it('returns instance', () => {
expect(actualResponse).to.eql(choicesInstance); expect(actualResponse).to.eql(instance);
}); });
}); });
}); });

View file

@ -0,0 +1,8 @@
import Dropdown from './dropdown';
import Container from './container';
import Input from './input';
import List from './list';
import WrappedInput from './wrapped-input';
import WrappedSelect from './wrapped-select';
export { Dropdown, Container, Input, List, WrappedInput, WrappedSelect };

View file

@ -1,18 +1,19 @@
import { calcWidthOfInput } from '../lib/utils'; import { calcWidthOfInput } from '../lib/utils';
export default class Input { export default class Input {
constructor(instance, element, classNames) { constructor({ element, type, classNames, placeholderValue }) {
this.parentInstance = instance; Object.assign(this, { element, type, classNames, placeholderValue });
this.element = element; this.element = element;
this.classNames = classNames; this.classNames = classNames;
this.isFocussed = this.element === document.activeElement; this.isFocussed = this.element === document.activeElement;
this.isDisabled = false; this.isDisabled = false;
// Bind event listeners // Bind event listeners
this.onPaste = this.onPaste.bind(this); this._onPaste = this._onPaste.bind(this);
this.onInput = this.onInput.bind(this); this._onInput = this._onInput.bind(this);
this.onFocus = this.onFocus.bind(this); this._onFocus = this._onFocus.bind(this);
this.onBlur = this.onBlur.bind(this); this._onBlur = this._onBlur.bind(this);
} }
set placeholder(placeholder) { set placeholder(placeholder) {
@ -28,55 +29,17 @@ export default class Input {
} }
addEventListeners() { addEventListeners() {
this.element.addEventListener('input', this.onInput); this.element.addEventListener('input', this._onInput);
this.element.addEventListener('paste', this.onPaste); this.element.addEventListener('paste', this._onPaste);
this.element.addEventListener('focus', this.onFocus); this.element.addEventListener('focus', this._onFocus);
this.element.addEventListener('blur', this.onBlur); this.element.addEventListener('blur', this._onBlur);
} }
removeEventListeners() { removeEventListeners() {
this.element.removeEventListener('input', this.onInput); this.element.removeEventListener('input', this._onInput);
this.element.removeEventListener('paste', this.onPaste); this.element.removeEventListener('paste', this._onPaste);
this.element.removeEventListener('focus', this.onFocus); this.element.removeEventListener('focus', this._onFocus);
this.element.removeEventListener('blur', this.onBlur); this.element.removeEventListener('blur', this._onBlur);
}
/**
* Input event
* @return
* @private
*/
onInput() {
if (!this.parentInstance.isSelectOneElement) {
this.setWidth();
}
}
/**
* Paste event
* @param {Object} e Event
* @return
* @private
*/
onPaste(e) {
// Disable pasting into the input if option has been set
if (e.target === this.element && !this.parentInstance.config.paste) {
e.preventDefault();
}
}
/**
* Set focussed state
*/
onFocus() {
this.isFocussed = true;
}
/**
* Remove focussed state
*/
onBlur() {
this.isFocussed = false;
} }
enable() { enable() {
@ -115,7 +78,7 @@ export default class Input {
this.setWidth(); this.setWidth();
} }
return this.parentInstance; return this;
} }
/** /**
@ -124,12 +87,12 @@ export default class Input {
* @return * @return
*/ */
setWidth(enforceWidth) { setWidth(enforceWidth) {
if (this.parentInstance.placeholder) { if (this._placeholderValue) {
// If there is a placeholder, we only want to set the width of the input when it is a greater // If there is a placeholder, we only want to set the width of the input when it is a greater
// length than 75% of the placeholder. This stops the input jumping around. // length than 75% of the placeholder. This stops the input jumping around.
if ( if (
(this.element.value && (this.element.value &&
this.element.value.length >= (this.parentInstance.placeholder.length / 1.25)) || this.element.value.length >= this._placeholderValue.length / 1.25) ||
enforceWidth enforceWidth
) { ) {
this.element.style.width = this.calcWidth(); this.element.style.width = this.calcWidth();
@ -151,4 +114,36 @@ export default class Input {
removeActiveDescendant() { removeActiveDescendant() {
this.element.removeAttribute('aria-activedescendant'); this.element.removeAttribute('aria-activedescendant');
} }
/**
* Input event
* @return
* @private
*/
_onInput() {
if (this.type !== 'select-one') {
this.setWidth();
}
}
/**
* Paste event
* @param {Object} e Event
* @return
* @private
*/
_onPaste(e) {
// Disable pasting into the input if option has been set
if (e.target === this.element && this.preventPaste) {
e.preventDefault();
}
}
_onFocus() {
this.isFocussed = true;
}
_onBlur() {
this.isFocussed = false;
}
} }

View file

@ -1,21 +1,21 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { stub } from 'sinon'; import { stub } from 'sinon';
import Input from './input'; import Input from './input';
import { DEFAULT_CLASSNAMES, DEFAULT_CONFIG } from '../constants'; import { DEFAULT_CLASSNAMES } from '../constants';
describe('components/input', () => { describe('components/input', () => {
let instance; let instance;
let choicesInstance;
let choicesElement; let choicesElement;
beforeEach(() => { beforeEach(() => {
choicesInstance = {
config: {
...DEFAULT_CONFIG,
},
};
choicesElement = document.createElement('input'); choicesElement = document.createElement('input');
instance = new Input(choicesInstance, choicesElement, DEFAULT_CLASSNAMES); instance = new Input({
element: choicesElement,
type: 'text',
classNames: DEFAULT_CLASSNAMES,
placeholderValue: null,
preventPaste: false,
});
}); });
afterEach(() => { afterEach(() => {
@ -24,10 +24,6 @@ describe('components/input', () => {
}); });
describe('constructor', () => { describe('constructor', () => {
it('assigns choices instance to class', () => {
expect(instance.parentInstance).to.eql(choicesInstance);
});
it('assigns choices element to class', () => { it('assigns choices element to class', () => {
expect(instance.element).to.eql(choicesElement); expect(instance.element).to.eql(choicesElement);
}); });
@ -90,19 +86,18 @@ describe('components/input', () => {
setWidthStub.restore(); setWidthStub.restore();
}); });
describe('when element is select one', () => { describe('when element is select one', () => {
it('does not set input width', () => { it('does not set input width', () => {
instance.parentInstance.isSelectOneElement = true; instance.type = 'select-one';
instance.onInput(); instance._onInput();
expect(setWidthStub.callCount).to.equal(0); expect(setWidthStub.callCount).to.equal(0);
}); });
}); });
describe('when element is not a select one', () => { describe('when element is not a select one', () => {
it('sets input width', () => { it('sets input width', () => {
instance.parentInstance.isSelectOneElement = false; instance.type = 'text';
instance.onInput(); instance._onInput();
expect(setWidthStub.callCount).to.equal(1); expect(setWidthStub.callCount).to.equal(1);
}); });
}); });
@ -120,16 +115,16 @@ describe('components/input', () => {
describe('when pasting is disabled and target is the element', () => { describe('when pasting is disabled and target is the element', () => {
it('prevents default pasting behaviour', () => { it('prevents default pasting behaviour', () => {
instance.parentInstance.config.paste = false; instance.preventPaste = true;
instance.onPaste(eventMock); instance._onPaste(eventMock);
expect(eventMock.preventDefault.callCount).to.equal(1); expect(eventMock.preventDefault.callCount).to.equal(1);
}); });
}); });
describe('when pasting is enabled', () => { describe('when pasting is enabled', () => {
it('does not prevent default pasting behaviour', () => { it('does not prevent default pasting behaviour', () => {
instance.parentInstance.config.paste = true; instance.preventPaste = false;
instance.onPaste(eventMock); instance._onPaste(eventMock);
expect(eventMock.preventDefault.callCount).to.equal(0); expect(eventMock.preventDefault.callCount).to.equal(0);
}); });
}); });
@ -138,7 +133,7 @@ describe('components/input', () => {
describe('onFocus', () => { describe('onFocus', () => {
it('sets isFocussed flag to true', () => { it('sets isFocussed flag to true', () => {
expect(instance.isFocussed).to.equal(false); expect(instance.isFocussed).to.equal(false);
instance.onFocus(); instance._onFocus();
expect(instance.isFocussed).to.equal(true); expect(instance.isFocussed).to.equal(true);
}); });
}); });
@ -146,7 +141,7 @@ describe('components/input', () => {
describe('onBlur', () => { describe('onBlur', () => {
it('sets isFocussed flag to false', () => { it('sets isFocussed flag to false', () => {
instance.isFocussed = true; instance.isFocussed = true;
instance.onBlur(); instance._onBlur();
expect(instance.isFocussed).to.equal(false); expect(instance.isFocussed).to.equal(false);
}); });
}); });
@ -211,7 +206,6 @@ describe('components/input', () => {
}); });
}); });
describe('blur', () => { describe('blur', () => {
let blurStub; let blurStub;
@ -224,7 +218,7 @@ describe('components/input', () => {
}); });
describe('when element is not focussed', () => { describe('when element is not focussed', () => {
it('doesn\'t blur element', () => { it("doesn't blur element", () => {
instance.isFocussed = false; instance.isFocussed = false;
instance.blur(); instance.blur();
expect(blurStub.callCount).to.equal(0); expect(blurStub.callCount).to.equal(0);
@ -251,23 +245,22 @@ describe('components/input', () => {
setWidthStub.restore(); setWidthStub.restore();
}); });
it('removes the element\'s value if it has one', () => { it("removes the element's value if it has one", () => {
instance.element.value = 'test'; instance.element.value = 'test';
expect(instance.element.value).to.equal('test'); expect(instance.element.value).to.equal('test');
instance.clear(); instance.clear();
expect(instance.element.value).to.equal(''); expect(instance.element.value).to.equal('');
}); });
it('sets the element\'s width if flag passed', () => { it("sets the element's width if flag passed", () => {
expect(setWidthStub.callCount).to.equal(0); expect(setWidthStub.callCount).to.equal(0);
instance.clear(true); instance.clear(true);
expect(setWidthStub.callCount).to.equal(1); expect(setWidthStub.callCount).to.equal(1);
}); });
it('returns parent instance', () => { it('returns instance', () => {
const actualResponse = instance.clear(); const response = instance.clear();
const expectedResponse = choicesInstance; expect(response).to.eql(instance);
expect(actualResponse).to.eql(expectedResponse);
}); });
}); });
@ -286,7 +279,7 @@ describe('components/input', () => {
describe('with a placeholder', () => { describe('with a placeholder', () => {
describe('when value length is greater or equal to 75% of the placeholder length', () => { describe('when value length is greater or equal to 75% of the placeholder length', () => {
it('sets the width of the element based on input value', () => { it('sets the width of the element based on input value', () => {
instance.parentInstance.placeholder = 'This is a test'; instance._placeholderValue = 'This is a test';
instance.element.value = 'This is a test'; instance.element.value = 'This is a test';
expect(instance.element.style.width).to.not.equal(inputWidth); expect(instance.element.style.width).to.not.equal(inputWidth);
instance.setWidth(); instance.setWidth();
@ -297,7 +290,7 @@ describe('components/input', () => {
describe('when width is enforced', () => { describe('when width is enforced', () => {
it('sets the width of the element based on input value', () => { it('sets the width of the element based on input value', () => {
instance.parentInstance.placeholder = 'This is a test'; instance._placeholderValue = 'This is a test';
instance.element.value = ''; instance.element.value = '';
expect(instance.element.style.width).to.not.equal(inputWidth); expect(instance.element.style.width).to.not.equal(inputWidth);
instance.setWidth(true); instance.setWidth(true);
@ -308,7 +301,7 @@ describe('components/input', () => {
describe('when value length is less than 75% of the placeholder length', () => { describe('when value length is less than 75% of the placeholder length', () => {
it('does not set the width of the element', () => { it('does not set the width of the element', () => {
instance.parentInstance.placeholder = 'This is a test'; instance._placeholderValue = 'This is a test';
instance.element.value = 'Test'; instance.element.value = 'Test';
instance.setWidth(); instance.setWidth();
expect(calcWidthStub.callCount).to.equal(0); expect(calcWidthStub.callCount).to.equal(0);
@ -355,21 +348,32 @@ describe('components/input', () => {
}); });
describe('setActiveDescendant', () => { describe('setActiveDescendant', () => {
it('sets element\'s aria-activedescendant attribute with passed descendant ID', () => { it("sets element's aria-activedescendant attribute with passed descendant ID", () => {
const activeDescendantID = '1234'; const activeDescendantID = '1234';
expect(instance.element.getAttribute('aria-activedescendant')).to.equal(null); expect(instance.element.getAttribute('aria-activedescendant')).to.equal(
null,
);
instance.setActiveDescendant(activeDescendantID); instance.setActiveDescendant(activeDescendantID);
expect(instance.element.getAttribute('aria-activedescendant')).to.equal(activeDescendantID); expect(instance.element.getAttribute('aria-activedescendant')).to.equal(
activeDescendantID,
);
}); });
}); });
describe('removeActiveDescendant', () => { describe('removeActiveDescendant', () => {
it('remove elememnt\'s aria-activedescendant attribute', () => { it("remove elememnt's aria-activedescendant attribute", () => {
const activeDescendantID = '1234'; const activeDescendantID = '1234';
instance.element.setAttribute('aria-activedescendant', activeDescendantID); instance.element.setAttribute(
expect(instance.element.getAttribute('aria-activedescendant')).to.equal(activeDescendantID); 'aria-activedescendant',
activeDescendantID,
);
expect(instance.element.getAttribute('aria-activedescendant')).to.equal(
activeDescendantID,
);
instance.removeActiveDescendant(); instance.removeActiveDescendant();
expect(instance.element.getAttribute('aria-activedescendant')).to.equal(null); expect(instance.element.getAttribute('aria-activedescendant')).to.equal(
null,
);
}); });
}); });
}); });

View file

@ -0,0 +1,89 @@
import { SCROLLING_SPEED } from '../constants';
export default class List {
constructor({ element }) {
Object.assign(this, { element });
this.scrollPos = this.element.scrollTop;
this.height = this.element.offsetHeight;
this.hasChildren = !!this.element.children;
}
clear() {
this.element.innerHTML = '';
}
append(node) {
this.element.appendChild(node);
}
getChild(selector) {
return this.element.querySelector(selector);
}
scrollToTop() {
this.element.scrollTop = 0;
}
scrollToChoice(choice, direction) {
if (!choice) {
return;
}
const dropdownHeight = this.element.offsetHeight;
const choiceHeight = choice.offsetHeight;
// Distance from bottom of element to top of parent
const choicePos = choice.offsetTop + choiceHeight;
// Scroll position of dropdown
const containerScrollPos = this.element.scrollTop + dropdownHeight;
// Difference between the choice and scroll position
const endpoint =
direction > 0
? this.element.scrollTop + choicePos - containerScrollPos
: choice.offsetTop;
requestAnimationFrame(time => {
this._animateScroll(time, endpoint, direction);
});
}
_scrollDown(scrollPos, strength, endpoint) {
const easing = (endpoint - scrollPos) / strength;
const distance = easing > 1 ? easing : 1;
this.element.scrollTop = scrollPos + distance;
}
_scrollUp(scrollPos, strength, endpoint) {
const easing = (scrollPos - endpoint) / strength;
const distance = easing > 1 ? easing : 1;
this.element.scrollTop = scrollPos - distance;
}
_animateScroll(time, endpoint, direction) {
const strength = SCROLLING_SPEED;
const choiceListScrollTop = this.element.scrollTop;
let continueAnimation = false;
if (direction > 0) {
this._scrollDown(choiceListScrollTop, strength, endpoint);
if (choiceListScrollTop < endpoint) {
continueAnimation = true;
}
} else {
this._scrollUp(choiceListScrollTop, strength, endpoint);
if (choiceListScrollTop > endpoint) {
continueAnimation = true;
}
}
if (continueAnimation) {
requestAnimationFrame(time => {
this._animateScroll(time, endpoint, direction);
});
}
}
}

View file

@ -1,20 +1,15 @@
import { expect } from 'chai'; import { expect } from 'chai';
import List from './list'; import List from './list';
import { DEFAULT_CLASSNAMES, DEFAULT_CONFIG } from '../constants';
describe('components/list', () => { describe('components/list', () => {
let instance; let instance;
let choicesInstance;
let choicesElement; let choicesElement;
beforeEach(() => { beforeEach(() => {
choicesInstance = {
config: {
...DEFAULT_CONFIG,
},
};
choicesElement = document.createElement('div'); choicesElement = document.createElement('div');
instance = new List(choicesInstance, choicesElement, DEFAULT_CLASSNAMES); instance = new List({
element: choicesElement,
});
}); });
afterEach(() => { afterEach(() => {
@ -23,21 +18,13 @@ describe('components/list', () => {
}); });
describe('constructor', () => { describe('constructor', () => {
it('assigns choices instance to class', () => {
expect(instance.parentInstance).to.eql(choicesInstance);
});
it('assigns choices element to class', () => { it('assigns choices element to class', () => {
expect(instance.element).to.eql(choicesElement); expect(instance.element).to.eql(choicesElement);
}); });
it('assigns classnames to class', () => {
expect(instance.classNames).to.eql(DEFAULT_CLASSNAMES);
});
}); });
describe('clear', () => { describe('clear', () => {
it('clears element\'s inner HTML', () => { it("clears element's inner HTML", () => {
const innerHTML = 'test'; const innerHTML = 'test';
instance.element.innerHTML = innerHTML; instance.element.innerHTML = innerHTML;
expect(instance.element.innerHTML).to.equal(innerHTML); expect(instance.element.innerHTML).to.equal(innerHTML);
@ -46,25 +33,6 @@ describe('components/list', () => {
}); });
}); });
describe('scrollTo', () => {
describe('passing position', () => {
it('scrolls element to passed position', () => {
const scrollPosition = 20;
expect(instance.element.scrollTop).to.equal(0);
instance.scrollTo(scrollPosition);
expect(instance.element.scrollTop).to.equal(scrollPosition);
});
});
describe('not passing position', () => {
it('scrolls element to default position', () => {
expect(instance.element.scrollTop).to.equal(0);
instance.scrollTo();
expect(instance.element.scrollTop).to.equal(0);
});
});
});
describe('append', () => { describe('append', () => {
it('appends passed node to element', () => { it('appends passed node to element', () => {
const elementToAppend = document.createElement('span'); const elementToAppend = document.createElement('span');
@ -72,11 +40,12 @@ describe('components/list', () => {
elementToAppend.classList.add(childClass); elementToAppend.classList.add(childClass);
expect(instance.element.querySelector(`.${childClass}`)).to.equal(null); expect(instance.element.querySelector(`.${childClass}`)).to.equal(null);
instance.append(elementToAppend); instance.append(elementToAppend);
expect(instance.element.querySelector(`.${childClass}`)).to.equal(elementToAppend); expect(instance.element.querySelector(`.${childClass}`)).to.equal(
elementToAppend,
);
}); });
}); });
describe('getChild', () => { describe('getChild', () => {
let childElement; let childElement;
const childClass = 'test-element'; const childClass = 'test-element';

View file

@ -1,10 +1,13 @@
import { dispatchEvent } from '../lib/utils'; import { dispatchEvent, isElement } from '../lib/utils';
export default class WrappedElement { export default class WrappedElement {
constructor(instance, element, classNames) { constructor({ element, classNames }) {
this.parentInstance = instance; Object.assign(this, { element, classNames });
this.element = element;
this.classNames = classNames; if (!isElement(element)) {
throw new TypeError('Invalid element passed');
}
this.isDisabled = false; this.isDisabled = false;
} }

View file

@ -1,21 +1,17 @@
import { expect } from 'chai'; import { expect } from 'chai';
import WrappedElement from './wrapped-element'; import WrappedElement from './wrapped-element';
import { DEFAULT_CLASSNAMES, DEFAULT_CONFIG } from '../constants'; import { DEFAULT_CLASSNAMES } from '../constants';
describe('components/wrappedElement', () => { describe('components/wrappedElement', () => {
let instance; let instance;
let choicesInstance;
let element; let element;
beforeEach(() => { beforeEach(() => {
choicesInstance = {
config: {
...DEFAULT_CONFIG,
},
};
element = document.createElement('select'); element = document.createElement('select');
instance = new WrappedElement(choicesInstance, element, DEFAULT_CLASSNAMES); instance = new WrappedElement({
element,
classNames: DEFAULT_CLASSNAMES
});
}); });
afterEach(() => { afterEach(() => {
@ -24,10 +20,6 @@ describe('components/wrappedElement', () => {
}); });
describe('constructor', () => { describe('constructor', () => {
it('assigns choices instance to class', () => {
expect(instance.parentInstance).to.eql(choicesInstance);
});
it('assigns choices element to class', () => { it('assigns choices element to class', () => {
expect(instance.element).to.eql(element); expect(instance.element).to.eql(element);
}); });
@ -58,11 +50,17 @@ describe('components/wrappedElement', () => {
it('hides element', () => { it('hides element', () => {
instance.conceal(); instance.conceal();
expect(instance.element.tabIndex).to.equal(-1); expect(instance.element.tabIndex).to.equal(-1);
expect(instance.element.classList.contains(instance.classNames.input)).to.equal(true); expect(
expect(instance.element.classList.contains(instance.classNames.hiddenState)).to.equal(true); instance.element.classList.contains(instance.classNames.input)
).to.equal(true);
expect(
instance.element.classList.contains(instance.classNames.hiddenState)
).to.equal(true);
expect(instance.element.getAttribute('aria-hidden')).to.equal('true'); expect(instance.element.getAttribute('aria-hidden')).to.equal('true');
expect(instance.element.getAttribute('data-choice')).to.equal('active'); expect(instance.element.getAttribute('data-choice')).to.equal('active');
expect(instance.element.getAttribute('data-choice-orig-style')).to.equal(originalStyling); expect(instance.element.getAttribute('data-choice-orig-style')).to.equal(
originalStyling
);
}); });
}); });
@ -77,12 +75,18 @@ describe('components/wrappedElement', () => {
it('shows element', () => { it('shows element', () => {
instance.reveal(); instance.reveal();
expect(instance.element.tabIndex).to.equal(0); expect(instance.element.tabIndex).to.equal(0);
expect(instance.element.classList.contains(instance.classNames.input)).to.equal(false); expect(
expect(instance.element.classList.contains(instance.classNames.hiddenState)).to.equal(false); instance.element.classList.contains(instance.classNames.input)
).to.equal(false);
expect(
instance.element.classList.contains(instance.classNames.hiddenState)
).to.equal(false);
expect(instance.element.getAttribute('style')).to.equal(originalStyling); expect(instance.element.getAttribute('style')).to.equal(originalStyling);
expect(instance.element.getAttribute('aria-hidden')).to.equal(null); expect(instance.element.getAttribute('aria-hidden')).to.equal(null);
expect(instance.element.getAttribute('data-choice')).to.equal(null); expect(instance.element.getAttribute('data-choice')).to.equal(null);
expect(instance.element.getAttribute('data-choice-orig-style')).to.equal(null); expect(instance.element.getAttribute('data-choice-orig-style')).to.equal(
null
);
}); });
}); });
@ -135,9 +139,9 @@ describe('components/wrappedElement', () => {
}); });
describe('triggerEvent', () => { describe('triggerEvent', () => {
it('fires event on element using passed eventType and data', (done) => { it('fires event on element using passed eventType and data', done => {
const data = { const data = {
test: true, test: true
}; };
instance.element.addEventListener('testEvent', ({ detail }) => { instance.element.addEventListener('testEvent', ({ detail }) => {

View file

@ -2,16 +2,14 @@ import WrappedElement from './wrapped-element';
import { reduceToValues } from './../lib/utils'; import { reduceToValues } from './../lib/utils';
export default class WrappedInput extends WrappedElement { export default class WrappedInput extends WrappedElement {
constructor(instance, element, classNames) { constructor({ element, classNames, delimiter }) {
super(instance, element, classNames); super({ element, classNames });
this.parentInstance = instance; this.delimiter = delimiter;
this.element = element;
this.classNames = classNames;
} }
set value(items) { set value(items) {
const itemsFiltered = reduceToValues(items); const itemsFiltered = reduceToValues(items);
const itemsFilteredString = itemsFiltered.join(this.parentInstance.config.delimiter); const itemsFilteredString = itemsFiltered.join(this.delimiter);
this.element.setAttribute('value', itemsFilteredString); this.element.setAttribute('value', itemsFilteredString);
this.element.value = itemsFilteredString; this.element.value = itemsFilteredString;

View file

@ -2,21 +2,20 @@ import { expect } from 'chai';
import { stub } from 'sinon'; import { stub } from 'sinon';
import WrappedElement from './wrapped-element'; import WrappedElement from './wrapped-element';
import WrappedInput from './wrapped-input'; import WrappedInput from './wrapped-input';
import { DEFAULT_CLASSNAMES, DEFAULT_CONFIG } from '../constants'; import { DEFAULT_CLASSNAMES } from '../constants';
describe('components/wrappedInput', () => { describe('components/wrappedInput', () => {
let instance; let instance;
let choicesInstance;
let element; let element;
const delimiter = '-';
beforeEach(() => { beforeEach(() => {
choicesInstance = {
config: {
...DEFAULT_CONFIG,
},
};
element = document.createElement('input'); element = document.createElement('input');
instance = new WrappedInput(choicesInstance, element, DEFAULT_CLASSNAMES); instance = new WrappedInput({
element,
classNames: DEFAULT_CLASSNAMES,
delimiter,
});
}); });
afterEach(() => { afterEach(() => {
@ -25,10 +24,6 @@ describe('components/wrappedInput', () => {
}); });
describe('constructor', () => { describe('constructor', () => {
it('assigns choices instance to class', () => {
expect(instance.parentInstance).to.eql(choicesInstance);
});
it('assigns choices element to class', () => { it('assigns choices element to class', () => {
expect(instance.element).to.eql(element); expect(instance.element).to.eql(element);
}); });
@ -39,7 +34,7 @@ describe('components/wrappedInput', () => {
}); });
describe('inherited methods', () => { describe('inherited methods', () => {
['conceal', 'reveal', 'enable', 'disable'].forEach((method) => { ['conceal', 'reveal', 'enable', 'disable'].forEach(method => {
describe(method, () => { describe(method, () => {
beforeEach(() => { beforeEach(() => {
stub(WrappedElement.prototype, method); stub(WrappedElement.prototype, method);
@ -77,7 +72,9 @@ describe('components/wrappedInput', () => {
it('sets delimited value of element based on passed data', () => { it('sets delimited value of element based on passed data', () => {
expect(instance.element.value).to.equal(''); expect(instance.element.value).to.equal('');
instance.value = data; instance.value = data;
expect(instance.value).to.equal('Value 1,Value 2,Value 3'); expect(instance.value).to.equal(
`Value 1${delimiter}Value 2${delimiter}Value 3`,
);
}); });
}); });
}); });

View file

@ -2,11 +2,8 @@ import WrappedElement from './wrapped-element';
import templates from './../templates'; import templates from './../templates';
export default class WrappedSelect extends WrappedElement { export default class WrappedSelect extends WrappedElement {
constructor(instance, element, classNames) { constructor({ element, classNames }) {
super(instance, element, classNames); super({ element, classNames });
this.parentInstance = instance;
this.element = element;
this.classNames = classNames;
} }
get placeholderOption() { get placeholderOption() {
@ -23,7 +20,7 @@ export default class WrappedSelect extends WrappedElement {
set options(options) { set options(options) {
const fragment = document.createDocumentFragment(); const fragment = document.createDocumentFragment();
const addOptionToFragment = (data) => { const addOptionToFragment = data => {
// Create a standard select option // Create a standard select option
const template = templates.option(data); const template = templates.option(data);
// Append it to fragment // Append it to fragment

View file

@ -2,20 +2,13 @@ import { expect } from 'chai';
import { stub } from 'sinon'; import { stub } from 'sinon';
import WrappedElement from './wrapped-element'; import WrappedElement from './wrapped-element';
import WrappedSelect from './wrapped-select'; import WrappedSelect from './wrapped-select';
import { DEFAULT_CLASSNAMES, DEFAULT_CONFIG } from '../constants'; import { DEFAULT_CLASSNAMES } from '../constants';
describe('components/wrappedSelect', () => { describe('components/wrappedSelect', () => {
let instance; let instance;
let choicesInstance;
let element; let element;
beforeEach(() => { beforeEach(() => {
choicesInstance = {
config: {
...DEFAULT_CONFIG,
},
};
element = document.createElement('select'); element = document.createElement('select');
element.id = 'target'; element.id = 'target';
for (let i = 1; i <= 4; i++) { for (let i = 1; i <= 4; i++) {
@ -32,7 +25,10 @@ describe('components/wrappedSelect', () => {
} }
document.body.appendChild(element); document.body.appendChild(element);
instance = new WrappedSelect(choicesInstance, document.getElementById('target'), DEFAULT_CLASSNAMES); instance = new WrappedSelect({
element: document.getElementById('target'),
classNames: DEFAULT_CLASSNAMES,
});
}); });
afterEach(() => { afterEach(() => {
@ -41,10 +37,6 @@ describe('components/wrappedSelect', () => {
}); });
describe('constructor', () => { describe('constructor', () => {
it('assigns choices instance to class', () => {
expect(instance.parentInstance).to.eql(choicesInstance);
});
it('assigns choices element to class', () => { it('assigns choices element to class', () => {
expect(instance.element).to.eql(element); expect(instance.element).to.eql(element);
}); });
@ -55,7 +47,7 @@ describe('components/wrappedSelect', () => {
}); });
describe('inherited methods', () => { describe('inherited methods', () => {
['conceal', 'reveal', 'enable', 'disable'].forEach((method) => { ['conceal', 'reveal', 'enable', 'disable'].forEach(method => {
beforeEach(() => { beforeEach(() => {
stub(WrappedElement.prototype, method); stub(WrappedElement.prototype, method);
}); });
@ -84,7 +76,7 @@ describe('components/wrappedSelect', () => {
it('returns all option elements', () => { it('returns all option elements', () => {
const { options } = instance; const { options } = instance;
expect(options).to.be.an('array'); expect(options).to.be.an('array');
options.forEach((option) => { options.forEach(option => {
expect(option).to.be.instanceOf(HTMLOptionElement); expect(option).to.be.instanceOf(HTMLOptionElement);
}); });
}); });
@ -99,7 +91,7 @@ describe('components/wrappedSelect', () => {
const { optionGroups } = instance; const { optionGroups } = instance;
expect(optionGroups.length).to.equal(3); expect(optionGroups.length).to.equal(3);
optionGroups.forEach((option) => { optionGroups.forEach(option => {
expect(option).to.be.instanceOf(HTMLOptGroupElement); expect(option).to.be.instanceOf(HTMLOptGroupElement);
}); });
}); });
@ -149,9 +141,13 @@ describe('components/wrappedSelect', () => {
describe('appendDocFragment', () => { describe('appendDocFragment', () => {
it('empties contents of element', () => { it('empties contents of element', () => {
expect(instance.element.getElementsByTagName('option').length).to.equal(4); expect(instance.element.getElementsByTagName('option').length).to.equal(
4,
);
instance.appendDocFragment(document.createDocumentFragment()); instance.appendDocFragment(document.createDocumentFragment());
expect(instance.element.getElementsByTagName('option').length).to.equal(0); expect(instance.element.getElementsByTagName('option').length).to.equal(
0,
);
}); });
it('appends passed fragment to element', () => { it('appends passed fragment to element', () => {
@ -161,7 +157,9 @@ describe('components/wrappedSelect', () => {
fragment.appendChild(elementToAppend); fragment.appendChild(elementToAppend);
expect(instance.element.querySelector('#fragment-target')).to.equal(null); expect(instance.element.querySelector('#fragment-target')).to.equal(null);
instance.appendDocFragment(fragment); instance.appendDocFragment(fragment);
expect(instance.element.querySelector('#fragment-target')).to.eql(elementToAppend); expect(instance.element.querySelector('#fragment-target')).to.eql(
elementToAppend,
);
}); });
}); });
}); });

View file

@ -65,7 +65,7 @@ export const DEFAULT_CONFIG = {
uniqueItemText: 'Only unique values can be added.', uniqueItemText: 'Only unique values can be added.',
addItemText: value => `Press Enter to add <b>"${stripHTML(value)}"</b>`, addItemText: value => `Press Enter to add <b>"${stripHTML(value)}"</b>`,
maxItemText: maxItemCount => `Only ${maxItemCount} values can be added.`, maxItemText: maxItemCount => `Only ${maxItemCount} values can be added.`,
itemComparer: (choice, item) => (choice === item), itemComparer: (choice, item) => choice === item,
fuseOptions: { fuseOptions: {
includeScore: true, includeScore: true,
}, },

View file

@ -140,7 +140,7 @@ describe('constants', () => {
}); });
it('exports each value as a number', () => { it('exports each value as a number', () => {
Object.keys(KEY_CODES).forEach((key) => { Object.keys(KEY_CODES).forEach(key => {
expect(KEY_CODES[key]).to.be.a('number'); expect(KEY_CODES[key]).to.be.a('number');
}); });
}); });

View file

@ -1 +0,0 @@
{"version":3,"file":"choices.min.js","sources":[],"mappings":";;;","sourceRoot":""}

View file

@ -0,0 +1,2 @@
import 'core-js/fn/array/from';
import 'custom-event-polyfill';

View file

@ -1,14 +1,5 @@
/* eslint-disable */ /* eslint-disable */
/**
* Capitalises the first letter of each word in a string
* @param {String} str String to capitalise
* @return {String} Capitalised string
*/
export const capitalise = function(str) {
return str.replace(/\w\S*/g, txt => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase());
};
/** /**
* Generates a string of random chars * Generates a string of random chars
* @param {Number} length Length of the string to generate * @param {Number} length Length of the string to generate
@ -39,7 +30,6 @@ export const generateId = function(element, prefix) {
return id; return id;
}; };
/** /**
* Tests the type of an object * Tests the type of an object
* @param {String} type Type to test object against * @param {String} type Type to test object against
@ -61,25 +51,15 @@ export const isType = function(type, obj) {
return obj !== undefined && obj !== null && clas === type; return obj !== undefined && obj !== null && clas === type;
}; };
/**
* Tests to see if a passed object is a node
* @param {Object} obj Object to be tested
* @return {Boolean}
*/
export const isNode = o => (
typeof Node === 'object' ? o instanceof Node :
o && typeof o === 'object' && typeof o.nodeType === 'number' && typeof o.nodeName === 'string'
);
/** /**
* Tests to see if a passed object is an element * Tests to see if a passed object is an element
* @param {Object} obj Object to be tested * @param {Object} obj Object to be tested
* @return {Boolean} * @return {Boolean}
*/ */
export const isElement = o => ( export const isElement = o => (
typeof HTMLElement === 'object' ? o instanceof HTMLElement : // DOM2 typeof HTMLElement === 'object' ? o instanceof HTMLElement : // DOM2
o && typeof o === 'object' && o !== null && o.nodeType === 1 && typeof o.nodeName === 'string' o && typeof o === 'object' && o !== null && o.nodeType === 1 && typeof o.nodeName === 'string'
); );
/** /**
* Merges unspecified amount of objects into new object * Merges unspecified amount of objects into new object
@ -121,132 +101,6 @@ export const extend = function() {
return extended; return extended;
}; };
/**
* CSS transition end event listener
* @return
*/
export const whichTransitionEvent = function() {
let t,
el = document.createElement('fakeelement');
const transitions = {
transition: 'transitionend',
OTransition: 'oTransitionEnd',
MozTransition: 'transitionend',
WebkitTransition: 'webkitTransitionEnd',
};
for (t in transitions) {
if (el.style[t] !== undefined) {
return transitions[t];
}
}
};
/**
* CSS animation end event listener
* @return
*/
export const whichAnimationEvent = function() {
let t,
el = document.createElement('fakeelement');
const animations = {
animation: 'animationend',
OAnimation: 'oAnimationEnd',
MozAnimation: 'animationend',
WebkitAnimation: 'webkitAnimationEnd',
};
for (t in animations) {
if (el.style[t] !== undefined) {
return animations[t];
}
}
};
/**
* Get the ancestors of each element in the current set of matched elements,
* up to but not including the element matched by the selector
* @param {NodeElement} elem Element to begin search from
* @param {NodeElement} parent Parent to find
* @param {String} selector Class to find
* @return {Array} Array of parent elements
*/
export const getParentsUntil = function(elem, parent, selector) {
const parents = [];
// Get matches
for (; elem && elem !== document; elem = elem.parentNode) {
// Check if parent has been reached
if (parent) {
const parentType = parent.charAt(0);
// If parent is a class
if (parentType === '.') {
if (elem.classList.contains(parent.substr(1))) {
break;
}
}
// If parent is an ID
if (parentType === '#') {
if (elem.id === parent.substr(1)) {
break;
}
}
// If parent is a data attribute
if (parentType === '[') {
if (elem.hasAttribute(parent.substr(1, parent.length - 1))) {
break;
}
}
// If parent is a tag
if (elem.tagName.toLowerCase() === parent) {
break;
}
}
if (selector) {
const selectorType = selector.charAt(0);
// If selector is a class
if (selectorType === '.') {
if (elem.classList.contains(selector.substr(1))) {
parents.push(elem);
}
}
// If selector is an ID
if (selectorType === '#') {
if (elem.id === selector.substr(1)) {
parents.push(elem);
}
}
// If selector is a data attribute
if (selectorType === '[') {
if (elem.hasAttribute(selector.substr(1, selector.length - 1))) {
parents.push(elem);
}
}
// If selector is a tag
if (elem.tagName.toLowerCase() === selector) {
parents.push(elem);
}
} else {
parents.push(elem);
}
}
// Return parents if any exist
if (parents.length === 0) {
return null;
}
return parents;
};
export const wrap = function(element, wrapper) { export const wrap = function(element, wrapper) {
wrapper = wrapper || document.createElement('div'); wrapper = wrapper || document.createElement('div');
if (element.nextSibling) { if (element.nextSibling) {
@ -257,17 +111,6 @@ export const wrap = function(element, wrapper) {
return wrapper.appendChild(element); return wrapper.appendChild(element);
}; };
export const getSiblings = function(elem) {
const siblings = [];
let sibling = elem.parentNode.firstChild;
for (; sibling; sibling = sibling.nextSibling) {
if (sibling.nodeType === 1 && sibling !== elem) {
siblings.push(sibling);
}
}
return siblings;
};
/** /**
* Find ancestor in DOM tree * Find ancestor in DOM tree
* @param {NodeElement} el Element to start search from * @param {NodeElement} el Element to start search from
@ -299,60 +142,6 @@ export const findAncestorByAttrName = function(el, attr) {
return null; return null;
}; };
/**
* Debounce an event handler.
* @param {Function} func Function to run after wait
* @param {Number} wait The delay before the function is executed
* @param {Boolean} immediate If passed, trigger the function on the leading edge, instead of the trailing.
* @return {Function} A function will be called after it stops being called for a given delay
*/
export const debounce = function(func, wait, immediate) {
let timeout;
return function() {
let context = this,
args = arguments;
const later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
/**
* Get an element's distance from the top of the page
* @private
* @param {NodeElement} el Element to test for
* @return {Number} Elements Distance from top of page
*/
export const getElemDistance = function(el) {
let location = 0;
if (el.offsetParent) {
do {
location += el.offsetTop;
el = el.offsetParent;
} while (el);
}
return location >= 0 ? location : 0;
};
/**
* Determine element height multiplied by any offsets
* @private
* @param {HTMLElement} el Element to test for
* @return {Number} Height of element
*/
export const getElementOffset = function(el, offset) {
let elOffset = offset;
if (elOffset > 1) elOffset = 1;
if (elOffset > 0) elOffset = 0;
return Math.max(el.offsetHeight * elOffset);
};
/** /**
* Get the next or previous element from a given start point * Get the next or previous element from a given start point
* @param {HTMLElement} startEl Element to start position from * @param {HTMLElement} startEl Element to start position from
@ -372,31 +161,6 @@ export const getAdjacentEl = (startEl, className, direction = 1) => {
return children[startPos + operatorDirection]; return children[startPos + operatorDirection];
}; };
/**
* Get scroll position based on top/bottom position
* @private
* @return {String} Position of scroll
*/
export const getScrollPosition = function(position) {
if (position === 'bottom') {
// Scroll position from the bottom of the viewport
return Math.max((window.scrollY || window.pageYOffset) + (window.innerHeight || document.documentElement.clientHeight));
}
// Scroll position from the top of the viewport
return (window.scrollY || window.pageYOffset);
};
/**
* Determine whether an element is within the viewport
* @param {HTMLElement} el Element to test
* @return {String} Position of scroll
* @return {Boolean}
*/
export const isInView = function(el, position, offset) {
// If the user has scrolled further than the distance from the element to the top of its parent
return this.getScrollPosition(position) > (this.getElemDistance(el) + this.getElementOffset(el, offset));
};
/** /**
* Determine whether an element is within * Determine whether an element is within
* @param {HTMLElement} el Element to test * @param {HTMLElement} el Element to test
@ -431,25 +195,6 @@ export const stripHTML = html =>
.replace(/</g, '&lt;') .replace(/</g, '&lt;')
.replace(/"/g, '&quot;'); .replace(/"/g, '&quot;');
/**
* Adds animation to an element and removes it upon animation completion
* @param {Element} el Element to add animation to
* @param {String} animation Animation class to add to element
* @return
*/
export const addAnimation = (el, animation) => {
const animationEvent = whichAnimationEvent();
const removeAnimation = () => {
el.classList.remove(animation);
el.removeEventListener(animationEvent, removeAnimation, false);
};
el.classList.add(animation);
el.addEventListener(animationEvent, removeAnimation, false);
};
/** /**
* Get a random number between a range * Get a random number between a range
* @param {Number} min Minimum range * @param {Number} min Minimum range
@ -617,4 +362,18 @@ export const fetchFromObject = function (object, properties){
} }
return object[properties]; return object[properties];
};
export const isIE11 = () => {
return !!(navigator.userAgent.match(/Trident/) && navigator.userAgent.match(/rv[ :]11/));
};
export const existsInArray = (array, value) => {
return array.some((item) => {
if (isType('String', value)) {
return item.value === value.trim();
}
return item.value === value;
})
}; };

View file

@ -43,11 +43,7 @@ describe('utils', () => {
]; ];
it('returns an array of item values', () => { it('returns an array of item values', () => {
const expectedResponse = [ const expectedResponse = [items[0].value, items[1].value, items[2].value];
items[0].value,
items[1].value,
items[2].value,
];
const actualResponse = reduceToValues(items); const actualResponse = reduceToValues(items);
expect(actualResponse).to.eql(expectedResponse); expect(actualResponse).to.eql(expectedResponse);

View file

@ -8,26 +8,29 @@ export default function choices(state = defaultState, action) {
A selected choice has been added to the passed input's value (added as an item) A selected choice has been added to the passed input's value (added as an item)
An active choice appears within the choice dropdown An active choice appears within the choice dropdown
*/ */
return [...state, { return [
id: action.id, ...state,
elementId: action.elementId, {
groupId: action.groupId, id: action.id,
value: action.value, elementId: action.elementId,
label: (action.label || action.value), groupId: action.groupId,
disabled: (action.disabled || false), value: action.value,
selected: false, label: action.label || action.value,
active: true, disabled: action.disabled || false,
score: 9999, selected: false,
customProperties: action.customProperties, active: true,
placeholder: (action.placeholder || false), score: 9999,
keyCode: null, customProperties: action.customProperties,
}]; placeholder: action.placeholder || false,
keyCode: null,
},
];
} }
case 'ADD_ITEM': { case 'ADD_ITEM': {
// If all choices need to be activated // If all choices need to be activated
if (action.activateOptions) { if (action.activateOptions) {
return state.map((obj) => { return state.map(obj => {
const choice = obj; const choice = obj;
choice.active = action.active; choice.active = action.active;
return choice; return choice;
@ -37,7 +40,7 @@ export default function choices(state = defaultState, action) {
// When an item is added and it has an associated choice, // When an item is added and it has an associated choice,
// we want to disable it so it can't be chosen again // we want to disable it so it can't be chosen again
if (action.choiceId > -1) { if (action.choiceId > -1) {
return state.map((obj) => { return state.map(obj => {
const choice = obj; const choice = obj;
if (choice.id === parseInt(action.choiceId, 10)) { if (choice.id === parseInt(action.choiceId, 10)) {
choice.selected = true; choice.selected = true;
@ -53,7 +56,7 @@ export default function choices(state = defaultState, action) {
// When an item is removed and it has an associated choice, // When an item is removed and it has an associated choice,
// we want to re-enable it so it can be chosen again // we want to re-enable it so it can be chosen again
if (action.choiceId > -1) { if (action.choiceId > -1) {
return state.map((obj) => { return state.map(obj => {
const choice = obj; const choice = obj;
if (choice.id === parseInt(action.choiceId, 10)) { if (choice.id === parseInt(action.choiceId, 10)) {
choice.selected = false; choice.selected = false;
@ -66,7 +69,7 @@ export default function choices(state = defaultState, action) {
} }
case 'FILTER_CHOICES': { case 'FILTER_CHOICES': {
return state.map((obj) => { return state.map(obj => {
const choice = obj; const choice = obj;
// Set active state based on whether choice is // Set active state based on whether choice is
// within filtered results // within filtered results
@ -83,7 +86,7 @@ export default function choices(state = defaultState, action) {
} }
case 'ACTIVATE_CHOICES': { case 'ACTIVATE_CHOICES': {
return state.map((obj) => { return state.map(obj => {
const choice = obj; const choice = obj;
choice.active = action.active; choice.active = action.active;
return choice; return choice;

View file

@ -174,15 +174,16 @@ describe('reducers/choices', () => {
score, score,
}; };
const actualResponse = choices(state, { const actualResponse = choices(state, {
type: 'FILTER_CHOICES', type: 'FILTER_CHOICES',
results: [{ results: [
item: { {
id, item: {
id,
},
score,
}, },
score, ],
}],
}).find(choice => choice.id === id); }).find(choice => choice.id === id);
expect(actualResponse).to.eql(expectedResponse); expect(actualResponse).to.eql(expectedResponse);

View file

@ -3,12 +3,15 @@ export const defaultState = [];
export default function groups(state = defaultState, action) { export default function groups(state = defaultState, action) {
switch (action.type) { switch (action.type) {
case 'ADD_GROUP': { case 'ADD_GROUP': {
return [...state, { return [
id: action.id, ...state,
value: action.value, {
active: action.active, id: action.id,
disabled: action.disabled, value: action.value,
}]; active: action.active,
disabled: action.disabled,
},
];
} }
case 'CLEAR_CHOICES': { case 'CLEAR_CHOICES': {

View file

@ -4,20 +4,23 @@ export default function items(state = defaultState, action) {
switch (action.type) { switch (action.type) {
case 'ADD_ITEM': { case 'ADD_ITEM': {
// Add object to items array // Add object to items array
const newState = [...state, { const newState = [
id: action.id, ...state,
choiceId: action.choiceId, {
groupId: action.groupId, id: action.id,
value: action.value, choiceId: action.choiceId,
label: action.label, groupId: action.groupId,
active: true, value: action.value,
highlighted: false, label: action.label,
customProperties: action.customProperties, active: true,
placeholder: (action.placeholder || false), highlighted: false,
keyCode: null, customProperties: action.customProperties,
}]; placeholder: action.placeholder || false,
keyCode: null,
},
];
return newState.map((obj) => { return newState.map(obj => {
const item = obj; const item = obj;
item.highlighted = false; item.highlighted = false;
return item; return item;
@ -26,7 +29,7 @@ export default function items(state = defaultState, action) {
case 'REMOVE_ITEM': { case 'REMOVE_ITEM': {
// Set item to inactive // Set item to inactive
return state.map((obj) => { return state.map(obj => {
const item = obj; const item = obj;
if (item.id === action.id) { if (item.id === action.id) {
item.active = false; item.active = false;
@ -36,7 +39,7 @@ export default function items(state = defaultState, action) {
} }
case 'HIGHLIGHT_ITEM': { case 'HIGHLIGHT_ITEM': {
return state.map((obj) => { return state.map(obj => {
const item = obj; const item = obj;
if (item.id === action.id) { if (item.id === action.id) {
item.highlighted = action.highlighted; item.highlighted = action.highlighted;

View file

@ -56,7 +56,7 @@ describe('reducers/items', () => {
}); });
it('unhighlights all highlighted items', () => { it('unhighlights all highlighted items', () => {
actualResponse.forEach((item) => { actualResponse.forEach(item => {
expect(item.highlighted).to.equal(false); expect(item.highlighted).to.equal(false);
}); });
}); });

View file

@ -1,38 +0,0 @@
export default class List {
constructor(instance, element, classNames) {
this.parentInstance = instance;
this.element = element;
this.classNames = classNames;
this.scrollPos = this.element.scrollTop;
this.height = this.element.offsetHeight;
this.hasChildren = !!this.element.children;
}
/**
* Clear List contents
*/
clear() {
this.element.innerHTML = '';
}
/**
* Scroll to passed position on Y axis
*/
scrollTo(scrollPos = 0) {
this.element.scrollTop = scrollPos;
}
/**
* Append node to element
*/
append(node) {
this.element.appendChild(node);
}
/**
* Find element that matches passed selector
* @return {HTMLElement}
*/
getChild(selector) {
return this.element.querySelector(selector);
}
}

View file

@ -1,130 +0,0 @@
/* eslint-disable */
(function () {
// Production steps of ECMA-262, Edition 6, 22.1.2.1
// Reference: https://people.mozilla.org/~jorendorff/es6-draft.html#sec-array.from
if (!Array.from) {
Array.from = (function() {
let toStr = Object.prototype.toString;
let isCallable = function(fn) {
return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
};
let toInteger = function(value) {
let number = Number(value);
if (isNaN(number)) {
return 0;
}
if (number === 0 || !isFinite(number)) {
return number;
}
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
};
let maxSafeInteger = Math.pow(2, 53) - 1;
let toLength = function(value) {
let len = toInteger(value);
return Math.min(Math.max(len, 0), maxSafeInteger);
};
// The length property of the from method is 1.
return function from(arrayLike /* , mapFn, thisArg */) {
// 1. Let C be the this value.
let C = this;
// 2. Let items be ToObject(arrayLike).
let items = Object(arrayLike);
// 3. ReturnIfAbrupt(items).
if (arrayLike == null) {
throw new TypeError('Array.from requires an array-like object - not null or undefined');
}
// 4. If mapfn is undefined, then let mapping be false.
let mapFn = arguments.length > 1 ? arguments[1] : void undefined;
let T;
if (typeof mapFn !== 'undefined') {
// 5. else
// 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
if (!isCallable(mapFn)) {
throw new TypeError('Array.from: when provided, the second argument must be a function');
}
// 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (arguments.length > 2) {
T = arguments[2];
}
}
// 10. Let lenValue be Get(items, "length").
// 11. Let len be ToLength(lenValue).
let len = toLength(items.length);
// 13. If IsConstructor(C) is true, then
// 13. a. Let A be the result of calling the [[Construct]] internal method of C with an argument list containing the single item len.
// 14. a. Else, Let A be ArrayCreate(len).
let A = isCallable(C) ? Object(new C(len)) : new Array(len);
// 16. Let k be 0.
let k = 0;
// 17. Repeat, while k < len… (also steps a - h)
let kValue;
while (k < len) {
kValue = items[k];
if (mapFn) {
A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
} else {
A[k] = kValue;
}
k += 1;
}
// 18. Let putStatus be Put(A, "length", len, true).
A.length = len;
// 20. Return A.
return A;
};
}());
}
// Reference: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/find
if (!Array.prototype.find) {
Array.prototype.find = function(predicate) {
'use strict';
if (this == null) {
throw new TypeError('Array.prototype.find called on null or undefined');
}
if (typeof predicate !== 'function') {
throw new TypeError('predicate must be a function');
}
let list = Object(this);
let length = list.length >>> 0;
let thisArg = arguments[1];
let value;
for (let i = 0; i < length; i++) {
value = list[i];
if (predicate.call(thisArg, value, i, list)) {
return value;
}
}
return undefined;
};
}
function CustomEvent (event, params) {
params = params || {
bubbles: false,
cancelable: false,
detail: undefined,
};
let evt = document.createEvent('CustomEvent');
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
}
CustomEvent.prototype = window.Event.prototype;
window.CustomEvent = CustomEvent;
}());

View file

@ -3,11 +3,9 @@ import rootReducer from './../reducers/index';
export default class Store { export default class Store {
constructor() { constructor() {
this.store = createStore( this._store = createStore(
rootReducer, rootReducer,
window.devToolsExtension ? window.devToolsExtension ? window.devToolsExtension() : undefined,
window.devToolsExtension() :
undefined,
); );
} }
@ -17,7 +15,7 @@ export default class Store {
* @return * @return
*/ */
subscribe(onChange) { subscribe(onChange) {
this.store.subscribe(onChange); this._store.subscribe(onChange);
} }
/** /**
@ -26,7 +24,7 @@ export default class Store {
* @return * @return
*/ */
dispatch(action) { dispatch(action) {
this.store.dispatch(action); this._store.dispatch(action);
} }
/** /**
@ -34,7 +32,7 @@ export default class Store {
* @return {Object} State * @return {Object} State
*/ */
get state() { get state() {
return this.store.getState(); return this._store.getState();
} }
/** /**
@ -54,9 +52,9 @@ export default class Store {
} }
/** /**
* Get highlighted items from store * Get highlighted items from store
* @return {Array} Item objects * @return {Array} Item objects
*/ */
get highlightedActiveItems() { get highlightedActiveItems() {
return this.items.filter(item => item.active && item.highlighted); return this.items.filter(item => item.active && item.highlighted);
} }
@ -122,10 +120,10 @@ export default class Store {
const groups = this.groups; const groups = this.groups;
const choices = this.choices; const choices = this.choices;
return groups.filter((group) => { return groups.filter(group => {
const isActive = group.active === true && group.disabled === false; const isActive = group.active === true && group.disabled === false;
const hasActiveOptions = choices.some(choice => const hasActiveOptions = choices.some(
choice.active === true && choice.disabled === false, choice => choice.active === true && choice.disabled === false,
); );
return isActive && hasActiveOptions; return isActive && hasActiveOptions;
}, []); }, []);
@ -138,7 +136,9 @@ export default class Store {
getChoiceById(id) { getChoiceById(id) {
if (id) { if (id) {
const choices = this.activeChoices; const choices = this.activeChoices;
const foundChoice = choices.find(choice => choice.id === parseInt(id, 10)); const foundChoice = choices.find(
choice => choice.id === parseInt(id, 10),
);
return foundChoice; return foundChoice;
} }
return false; return false;

View file

@ -10,9 +10,9 @@ describe('reducers/store', () => {
beforeEach(() => { beforeEach(() => {
instance = new Store(); instance = new Store();
subscribeStub = sinon.stub(instance.store, 'subscribe'); subscribeStub = sinon.stub(instance._store, 'subscribe');
dispatchStub = sinon.stub(instance.store, 'dispatch'); dispatchStub = sinon.stub(instance._store, 'dispatch');
getStateStub = sinon.stub(instance.store, 'getState'); getStateStub = sinon.stub(instance._store, 'getState');
}); });
afterEach(() => { afterEach(() => {
@ -21,10 +21,9 @@ describe('reducers/store', () => {
getStateStub.restore(); getStateStub.restore();
}); });
describe('constructor', () => { describe('constructor', () => {
it('creates redux store', () => { it('creates redux store', () => {
expect(instance.store).to.contain.keys([ expect(instance._store).to.contain.keys([
'subscribe', 'subscribe',
'dispatch', 'dispatch',
'getState', 'getState',
@ -32,7 +31,6 @@ describe('reducers/store', () => {
}); });
}); });
describe('subscribe', () => { describe('subscribe', () => {
it('wraps redux subscribe method', () => { it('wraps redux subscribe method', () => {
const onChange = () => {}; const onChange = () => {};
@ -163,14 +161,16 @@ describe('reducers/store', () => {
describe('activeItems getter', () => { describe('activeItems getter', () => {
it('returns items that are active', () => { it('returns items that are active', () => {
const expectedResponse = state.items.filter((item => item.active)); const expectedResponse = state.items.filter(item => item.active);
expect(instance.activeItems).to.eql(expectedResponse); expect(instance.activeItems).to.eql(expectedResponse);
}); });
}); });
describe('highlightedActiveItems getter', () => { describe('highlightedActiveItems getter', () => {
it('returns items that are active and highlighted', () => { it('returns items that are active and highlighted', () => {
const expectedResponse = state.items.filter((item => item.highlighted && item.active)); const expectedResponse = state.items.filter(
item => item.highlighted && item.active,
);
expect(instance.highlightedActiveItems).to.eql(expectedResponse); expect(instance.highlightedActiveItems).to.eql(expectedResponse);
}); });
}); });
@ -184,21 +184,25 @@ describe('reducers/store', () => {
describe('activeChoices getter', () => { describe('activeChoices getter', () => {
it('returns choices that are active', () => { it('returns choices that are active', () => {
const expectedResponse = state.choices.filter((choice => choice.active)); const expectedResponse = state.choices.filter(choice => choice.active);
expect(instance.activeChoices).to.eql(expectedResponse); expect(instance.activeChoices).to.eql(expectedResponse);
}); });
}); });
describe('selectableChoices getter', () => { describe('selectableChoices getter', () => {
it('returns choices that are not disabled', () => { it('returns choices that are not disabled', () => {
const expectedResponse = state.choices.filter((choice => !choice.disabled)); const expectedResponse = state.choices.filter(
choice => !choice.disabled,
);
expect(instance.selectableChoices).to.eql(expectedResponse); expect(instance.selectableChoices).to.eql(expectedResponse);
}); });
}); });
describe('searchableChoices getter', () => { describe('searchableChoices getter', () => {
it('returns choices that are not placeholders and are selectable', () => { it('returns choices that are not placeholders and are selectable', () => {
const expectedResponse = state.choices.filter((choice => !choice.disabled && !choice.placeholder)); const expectedResponse = state.choices.filter(
choice => !choice.disabled && !choice.placeholder,
);
expect(instance.searchableChoices).to.eql(expectedResponse); expect(instance.searchableChoices).to.eql(expectedResponse);
}); });
}); });
@ -207,7 +211,9 @@ describe('reducers/store', () => {
describe('passing id', () => { describe('passing id', () => {
it('returns active choice by passed id', () => { it('returns active choice by passed id', () => {
const id = '1'; const id = '1';
const expectedResponse = state.choices.find((choice => choice.id === parseInt(id, 10))); const expectedResponse = state.choices.find(
choice => choice.id === parseInt(id, 10),
);
const actualResponse = instance.getChoiceById(id); const actualResponse = instance.getChoiceById(id);
expect(actualResponse).to.eql(expectedResponse); expect(actualResponse).to.eql(expectedResponse);
}); });
@ -223,7 +229,9 @@ describe('reducers/store', () => {
describe('placeholderChoice getter', () => { describe('placeholderChoice getter', () => {
it('returns placeholder choice', () => { it('returns placeholder choice', () => {
const expectedResponse = state.choices.reverse().find(choice => choice.placeholder); const expectedResponse = state.choices
.reverse()
.find(choice => choice.placeholder);
expect(instance.getPlaceholderChoice).to.eql(expectedResponse); expect(instance.getPlaceholderChoice).to.eql(expectedResponse);
}); });
}); });
@ -245,7 +253,9 @@ describe('reducers/store', () => {
describe('getGroupById', () => { describe('getGroupById', () => {
it('returns group by id', () => { it('returns group by id', () => {
const id = '1'; const id = '1';
const expectedResponse = state.groups.find((group => group.id === parseInt(id, 10))); const expectedResponse = state.groups.find(
group => group.id === parseInt(id, 10),
);
const actualResponse = instance.getGroupById(id); const actualResponse = instance.getGroupById(id);
expect(actualResponse).to.eql(expectedResponse); expect(actualResponse).to.eql(expectedResponse);
}); });

View file

@ -39,13 +39,10 @@ export const TEMPLATES = {
`); `);
}, },
itemList(globalClasses, isSelectOneElement) { itemList(globalClasses, isSelectOneElement) {
const localClasses = classNames( const localClasses = classNames(globalClasses.list, {
globalClasses.list, [globalClasses.listSingle]: isSelectOneElement,
{ [globalClasses.listItems]: !isSelectOneElement,
[globalClasses.listSingle]: (isSelectOneElement), });
[globalClasses.listItems]: (!isSelectOneElement),
},
);
return strToEl(` return strToEl(`
<div class="${localClasses}"></div> <div class="${localClasses}"></div>
@ -62,22 +59,18 @@ export const TEMPLATES = {
const ariaSelected = data.active ? 'aria-selected="true"' : ''; const ariaSelected = data.active ? 'aria-selected="true"' : '';
const ariaDisabled = data.disabled ? 'aria-disabled="true"' : ''; const ariaDisabled = data.disabled ? 'aria-disabled="true"' : '';
let localClasses = classNames( let localClasses = classNames(globalClasses.item, {
globalClasses.item, { [globalClasses.highlightedState]: data.highlighted,
[globalClasses.highlightedState]: data.highlighted, [globalClasses.itemSelectable]: !data.highlighted,
[globalClasses.itemSelectable]: !data.highlighted, [globalClasses.placeholder]: data.placeholder,
[globalClasses.placeholder]: data.placeholder, });
},
);
if (removeItemButton) { if (removeItemButton) {
localClasses = classNames( localClasses = classNames(globalClasses.item, {
globalClasses.item, { [globalClasses.highlightedState]: data.highlighted,
[globalClasses.highlightedState]: data.highlighted, [globalClasses.itemSelectable]: !data.disabled,
[globalClasses.itemSelectable]: !data.disabled, [globalClasses.placeholder]: data.placeholder,
[globalClasses.placeholder]: data.placeholder, });
},
);
return strToEl(` return strToEl(`
<div <div
@ -116,9 +109,9 @@ export const TEMPLATES = {
`); `);
}, },
choiceList(globalClasses, isSelectOneElement) { choiceList(globalClasses, isSelectOneElement) {
const ariaMultiSelectable = !isSelectOneElement ? const ariaMultiSelectable = !isSelectOneElement
'aria-multiselectable="true"' : ? 'aria-multiselectable="true"'
''; : '';
return strToEl(` return strToEl(`
<div <div
@ -132,11 +125,9 @@ export const TEMPLATES = {
}, },
choiceGroup(globalClasses, data) { choiceGroup(globalClasses, data) {
const ariaDisabled = data.disabled ? 'aria-disabled="true"' : ''; const ariaDisabled = data.disabled ? 'aria-disabled="true"' : '';
const localClasses = classNames( const localClasses = classNames(globalClasses.group, {
globalClasses.group, { [globalClasses.itemDisabled]: data.disabled,
[globalClasses.itemDisabled]: data.disabled, });
},
);
return strToEl(` return strToEl(`
<div <div
@ -155,7 +146,8 @@ export const TEMPLATES = {
const role = data.groupId > 0 ? 'role="treeitem"' : 'role="option"'; const role = data.groupId > 0 ? 'role="treeitem"' : 'role="option"';
const localClasses = classNames( const localClasses = classNames(
globalClasses.item, globalClasses.item,
globalClasses.itemChoice, { globalClasses.itemChoice,
{
[globalClasses.itemDisabled]: data.disabled, [globalClasses.itemDisabled]: data.disabled,
[globalClasses.itemSelectable]: !data.disabled, [globalClasses.itemSelectable]: !data.disabled,
[globalClasses.placeholder]: data.placeholder, [globalClasses.placeholder]: data.placeholder,
@ -169,9 +161,10 @@ export const TEMPLATES = {
data-choice data-choice
data-id="${data.id}" data-id="${data.id}"
data-value="${data.value}" data-value="${data.value}"
${data.disabled ? ${
'data-choice-disabled aria-disabled="true"' : data.disabled
'data-choice-selectable' ? 'data-choice-disabled aria-disabled="true"'
: 'data-choice-selectable'
} }
id="${data.elementId}" id="${data.elementId}"
${role} ${role}
@ -217,8 +210,8 @@ export const TEMPLATES = {
globalClasses.item, globalClasses.item,
globalClasses.itemChoice, globalClasses.itemChoice,
{ {
[globalClasses.noResults]: (type === 'no-results'), [globalClasses.noResults]: type === 'no-results',
[globalClasses.noChoices]: (type === 'no-choices'), [globalClasses.noChoices]: type === 'no-choices',
}, },
); );
@ -230,7 +223,9 @@ export const TEMPLATES = {
}, },
option(data) { option(data) {
return strToEl(` return strToEl(`
<option value="${data.value}" ${data.selected ? 'selected' : ''} ${data.disabled ? 'disabled' : ''}>${data.label}</option> <option value="${data.value}" ${data.selected ? 'selected' : ''} ${
data.disabled ? 'disabled' : ''
}>${data.label}</option>
`); `);
}, },
}; };

View file

@ -2,7 +2,8 @@ import { expect } from 'chai';
import templates from './templates'; import templates from './templates';
import { getType, strToEl } from './lib/utils'; import { getType, strToEl } from './lib/utils';
const stripElement = element => element.outerHTML.replace(/(^|>)\s+|\s+(?=<|$)/g, '$1'); const stripElement = element =>
element.outerHTML.replace(/(^|>)\s+|\s+(?=<|$)/g, '$1');
describe('templates', () => { describe('templates', () => {
describe('containerOuter', () => { describe('containerOuter', () => {
@ -41,7 +42,9 @@ describe('templates', () => {
); );
expect(getType(actualOutput)).to.equal('HTMLDivElement'); expect(getType(actualOutput)).to.equal('HTMLDivElement');
expect(stripElement(actualOutput)).to.equal(stripElement(expectedOutput)); expect(stripElement(actualOutput)).to.equal(
stripElement(expectedOutput),
);
}); });
}); });
@ -73,7 +76,9 @@ describe('templates', () => {
); );
expect(getType(actualOutput)).to.equal('HTMLDivElement'); expect(getType(actualOutput)).to.equal('HTMLDivElement');
expect(stripElement(actualOutput)).to.equal(stripElement(expectedOutput)); expect(stripElement(actualOutput)).to.equal(
stripElement(expectedOutput),
);
}); });
}); });
@ -106,7 +111,9 @@ describe('templates', () => {
); );
expect(getType(actualOutput)).to.equal('HTMLDivElement'); expect(getType(actualOutput)).to.equal('HTMLDivElement');
expect(stripElement(actualOutput)).to.equal(stripElement(expectedOutput)); expect(stripElement(actualOutput)).to.equal(
stripElement(expectedOutput),
);
}); });
}); });
}); });
@ -138,7 +145,9 @@ describe('templates', () => {
); );
expect(getType(actualOutput)).to.equal('HTMLDivElement'); expect(getType(actualOutput)).to.equal('HTMLDivElement');
expect(stripElement(actualOutput)).to.equal(stripElement(expectedOutput)); expect(stripElement(actualOutput)).to.equal(
stripElement(expectedOutput),
);
}); });
}); });
}); });
@ -148,7 +157,9 @@ describe('templates', () => {
const classes = { const classes = {
containerInner: 'test', containerInner: 'test',
}; };
const expectedOutput = strToEl(`<div class="${classes.containerInner}"></div>`); const expectedOutput = strToEl(
`<div class="${classes.containerInner}"></div>`,
);
const actualOutput = templates.containerInner(classes); const actualOutput = templates.containerInner(classes);
expect(getType(actualOutput)).to.equal('HTMLDivElement'); expect(getType(actualOutput)).to.equal('HTMLDivElement');
@ -165,21 +176,29 @@ describe('templates', () => {
describe('select one element', () => { describe('select one element', () => {
it('returns expected html', () => { it('returns expected html', () => {
const expectedOutput = strToEl(`<div class="${classes.list} ${classes.listSingle}"></div>`); const expectedOutput = strToEl(
`<div class="${classes.list} ${classes.listSingle}"></div>`,
);
const actualOutput = templates.itemList(classes, true); const actualOutput = templates.itemList(classes, true);
expect(getType(actualOutput)).to.equal('HTMLDivElement'); expect(getType(actualOutput)).to.equal('HTMLDivElement');
expect(stripElement(actualOutput)).to.equal(stripElement(expectedOutput)); expect(stripElement(actualOutput)).to.equal(
stripElement(expectedOutput),
);
}); });
}); });
describe('non select one element', () => { describe('non select one element', () => {
it('returns expected html', () => { it('returns expected html', () => {
const expectedOutput = strToEl(`<div class="${classes.list} ${classes.listItems}"></div>`); const expectedOutput = strToEl(
`<div class="${classes.list} ${classes.listItems}"></div>`,
);
const actualOutput = templates.itemList(classes, false); const actualOutput = templates.itemList(classes, false);
expect(getType(actualOutput)).to.equal('HTMLDivElement'); expect(getType(actualOutput)).to.equal('HTMLDivElement');
expect(stripElement(actualOutput)).to.equal(stripElement(expectedOutput)); expect(stripElement(actualOutput)).to.equal(
stripElement(expectedOutput),
);
}); });
}); });
}); });
@ -191,8 +210,7 @@ describe('templates', () => {
}; };
const value = 'test'; const value = 'test';
const expectedOutput = strToEl(` const expectedOutput = strToEl(`
<div class="${classes.placeholder}">${value}</div>`, <div class="${classes.placeholder}">${value}</div>`);
);
const actualOutput = templates.placeholder(classes, value); const actualOutput = templates.placeholder(classes, value);
expect(getType(actualOutput)).to.equal('HTMLDivElement'); expect(getType(actualOutput)).to.equal('HTMLDivElement');
@ -222,7 +240,9 @@ describe('templates', () => {
const actualOutput = templates.choiceList(classes, true); const actualOutput = templates.choiceList(classes, true);
expect(getType(actualOutput)).to.equal('HTMLDivElement'); expect(getType(actualOutput)).to.equal('HTMLDivElement');
expect(stripElement(actualOutput)).to.equal(stripElement(expectedOutput)); expect(stripElement(actualOutput)).to.equal(
stripElement(expectedOutput),
);
}); });
}); });
@ -240,7 +260,9 @@ describe('templates', () => {
const actualOutput = templates.choiceList(classes, false); const actualOutput = templates.choiceList(classes, false);
expect(getType(actualOutput)).to.equal('HTMLDivElement'); expect(getType(actualOutput)).to.equal('HTMLDivElement');
expect(stripElement(actualOutput)).to.equal(stripElement(expectedOutput)); expect(stripElement(actualOutput)).to.equal(
stripElement(expectedOutput),
);
}); });
}); });
}); });
@ -278,7 +300,9 @@ describe('templates', () => {
const actualOutput = templates.choiceGroup(classes, data); const actualOutput = templates.choiceGroup(classes, data);
expect(getType(actualOutput)).to.equal('HTMLDivElement'); expect(getType(actualOutput)).to.equal('HTMLDivElement');
expect(stripElement(actualOutput)).to.equal(stripElement(expectedOutput)); expect(stripElement(actualOutput)).to.equal(
stripElement(expectedOutput),
);
}); });
}); });
@ -306,7 +330,9 @@ describe('templates', () => {
const actualOutput = templates.choiceGroup(classes, data); const actualOutput = templates.choiceGroup(classes, data);
expect(getType(actualOutput)).to.equal('HTMLDivElement'); expect(getType(actualOutput)).to.equal('HTMLDivElement');
expect(stripElement(actualOutput)).to.equal(stripElement(expectedOutput)); expect(stripElement(actualOutput)).to.equal(
stripElement(expectedOutput),
);
}); });
}); });
}); });
@ -339,7 +365,9 @@ describe('templates', () => {
it('returns expected html', () => { it('returns expected html', () => {
const expectedOutput = strToEl(` const expectedOutput = strToEl(`
<div <div
class="${classes.item} ${classes.itemChoice} ${classes.itemSelectable}" class="${classes.item} ${classes.itemChoice} ${
classes.itemSelectable
}"
data-select-text="${itemSelectText}" data-select-text="${itemSelectText}"
data-choice data-choice
data-id="${data.id}" data-id="${data.id}"
@ -354,7 +382,9 @@ describe('templates', () => {
const actualOutput = templates.choice(classes, data, itemSelectText); const actualOutput = templates.choice(classes, data, itemSelectText);
expect(getType(actualOutput)).to.equal('HTMLDivElement'); expect(getType(actualOutput)).to.equal('HTMLDivElement');
expect(stripElement(actualOutput)).to.equal(stripElement(expectedOutput)); expect(stripElement(actualOutput)).to.equal(
stripElement(expectedOutput),
);
}); });
}); });
@ -369,7 +399,9 @@ describe('templates', () => {
it('returns expected html', () => { it('returns expected html', () => {
const expectedOutput = strToEl(` const expectedOutput = strToEl(`
<div <div
class="${classes.item} ${classes.itemChoice} ${classes.itemDisabled}" class="${classes.item} ${classes.itemChoice} ${
classes.itemDisabled
}"
data-select-text="${itemSelectText}" data-select-text="${itemSelectText}"
data-choice data-choice
data-id="${data.id}" data-id="${data.id}"
@ -385,7 +417,9 @@ describe('templates', () => {
const actualOutput = templates.choice(classes, data, itemSelectText); const actualOutput = templates.choice(classes, data, itemSelectText);
expect(getType(actualOutput)).to.equal('HTMLDivElement'); expect(getType(actualOutput)).to.equal('HTMLDivElement');
expect(stripElement(actualOutput)).to.equal(stripElement(expectedOutput)); expect(stripElement(actualOutput)).to.equal(
stripElement(expectedOutput),
);
}); });
}); });
@ -400,7 +434,9 @@ describe('templates', () => {
it('returns expected html', () => { it('returns expected html', () => {
const expectedOutput = strToEl(` const expectedOutput = strToEl(`
<div <div
class="${classes.item} ${classes.itemChoice} ${classes.itemSelectable} ${classes.placeholder}" class="${classes.item} ${classes.itemChoice} ${
classes.itemSelectable
} ${classes.placeholder}"
data-select-text="${itemSelectText}" data-select-text="${itemSelectText}"
data-choice data-choice
data-id="${data.id}" data-id="${data.id}"
@ -415,7 +451,9 @@ describe('templates', () => {
const actualOutput = templates.choice(classes, data, itemSelectText); const actualOutput = templates.choice(classes, data, itemSelectText);
expect(getType(actualOutput)).to.equal('HTMLDivElement'); expect(getType(actualOutput)).to.equal('HTMLDivElement');
expect(stripElement(actualOutput)).to.equal(stripElement(expectedOutput)); expect(stripElement(actualOutput)).to.equal(
stripElement(expectedOutput),
);
}); });
}); });
@ -423,14 +461,16 @@ describe('templates', () => {
beforeEach(() => { beforeEach(() => {
data = { data = {
...data, ...data,
groupId: 1 groupId: 1,
}; };
}); });
it('returns expected html', () => { it('returns expected html', () => {
const expectedOutput = strToEl(` const expectedOutput = strToEl(`
<div <div
class="${classes.item} ${classes.itemChoice} ${classes.itemSelectable}" class="${classes.item} ${classes.itemChoice} ${
classes.itemSelectable
}"
data-select-text="${itemSelectText}" data-select-text="${itemSelectText}"
data-choice data-choice
data-id="${data.id}" data-id="${data.id}"
@ -445,7 +485,9 @@ describe('templates', () => {
const actualOutput = templates.choice(classes, data, itemSelectText); const actualOutput = templates.choice(classes, data, itemSelectText);
expect(getType(actualOutput)).to.equal('HTMLDivElement'); expect(getType(actualOutput)).to.equal('HTMLDivElement');
expect(stripElement(actualOutput)).to.equal(stripElement(expectedOutput)); expect(stripElement(actualOutput)).to.equal(
stripElement(expectedOutput),
);
}); });
}); });
}); });
@ -479,11 +521,14 @@ describe('templates', () => {
const classes = { const classes = {
list: 'test 1', list: 'test 1',
listDropdown: 'test 2', listDropdown: 'test 2',
} };
;
it('returns expected html', () => { it('returns expected html', () => {
const value = 'test'; const value = 'test';
const expectedOutput = strToEl(`<div class="${classes.list} ${classes.listDropdown}" aria-expanded="false"></div>`); const expectedOutput = strToEl(
`<div class="${classes.list} ${
classes.listDropdown
}" aria-expanded="false"></div>`,
);
const actualOutput = templates.dropdown(classes, value); const actualOutput = templates.dropdown(classes, value);
expect(getType(actualOutput)).to.equal('HTMLDivElement'); expect(getType(actualOutput)).to.equal('HTMLDivElement');
@ -517,26 +562,34 @@ describe('templates', () => {
describe('no results', () => { describe('no results', () => {
it('adds no results classname', () => { it('adds no results classname', () => {
const expectedOutput = strToEl(` const expectedOutput = strToEl(`
<div class="${classes.item} ${classes.itemChoice} ${classes.noResults}"> <div class="${classes.item} ${classes.itemChoice} ${
classes.noResults
}">
${label} ${label}
</div> </div>
`); `);
const actualOutput = templates.notice(classes, label, 'no-results'); const actualOutput = templates.notice(classes, label, 'no-results');
expect(stripElement(actualOutput)).to.equal(stripElement(expectedOutput)); expect(stripElement(actualOutput)).to.equal(
stripElement(expectedOutput),
);
}); });
}); });
describe('no choices', () => { describe('no choices', () => {
it('adds no choices classname', () => { it('adds no choices classname', () => {
const expectedOutput = strToEl(` const expectedOutput = strToEl(`
<div class="${classes.item} ${classes.itemChoice} ${classes.noChoices}"> <div class="${classes.item} ${classes.itemChoice} ${
classes.noChoices
}">
${label} ${label}
</div> </div>
`); `);
const actualOutput = templates.notice(classes, label, 'no-choices'); const actualOutput = templates.notice(classes, label, 'no-choices');
expect(stripElement(actualOutput)).to.equal(stripElement(expectedOutput)); expect(stripElement(actualOutput)).to.equal(
stripElement(expectedOutput),
);
}); });
}); });
}); });
@ -555,7 +608,11 @@ describe('templates', () => {
}); });
it('returns expected html', () => { it('returns expected html', () => {
const expectedOutput = strToEl(`<option value="${data.value}" ${data.selected ? 'selected' : ''} ${data.disabled ? 'disabled' : ''}>${data.label}</option>`); const expectedOutput = strToEl(
`<option value="${data.value}" ${data.selected ? 'selected' : ''} ${
data.disabled ? 'disabled' : ''
}>${data.label}</option>`,
);
const actualOutput = templates.option(data); const actualOutput = templates.option(data);
expect(getType(actualOutput)).to.equal('HTMLOptionElement'); expect(getType(actualOutput)).to.equal('HTMLOptionElement');

View file

@ -1,27 +1,28 @@
$global-guttering: 24px;
$global-font-size-h1: 32px;
$global-font-size-h2: 24px;
$global-font-size-h3: 20px;
$global-font-size-h4: 18px;
$global-font-size-h5: 16px;
$global-font-size-h6: 14px;
/*============================================= /*=============================================
= Generic styling = = Generic styling =
=============================================*/ =============================================*/
$global-guttering : 24px;
$global-font-size-h1 : 32px;
$global-font-size-h2 : 24px;
$global-font-size-h3 : 20px;
$global-font-size-h4 : 18px;
$global-font-size-h5 : 16px;
$global-font-size-h6 : 14px;
* { * {
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale -moz-osx-font-smoothing: grayscale
} }
*,
*, *:before, *:after { *:before,
*:after {
box-sizing: border-box box-sizing: border-box
} }
html,
html, body { body {
position: relative; position: relative;
margin: 0; margin: 0;
width: 100%; width: 100%;
@ -45,7 +46,9 @@ label {
cursor: pointer; cursor: pointer;
} }
p { margin-top: 0; } p {
margin-top: 0;
}
hr { hr {
display: block; display: block;
@ -55,14 +58,21 @@ hr {
height: 1px; height: 1px;
} }
h1, h2, h3, h4, h5, h6 { h1,
h2,
h3,
h4,
h5,
h6 {
margin-top: 0; margin-top: 0;
margin-bottom: $global-guttering/2; margin-bottom: $global-guttering/2;
font-weight: 400; font-weight: 400;
line-height: 1.2; line-height: 1.2;
} }
a, a:visited, a:focus { a,
a:visited,
a:focus {
color: #FFFFFF; color: #FFFFFF;
text-decoration: none; text-decoration: none;
font-weight: 600; font-weight: 600;
@ -81,26 +91,55 @@ a, a:visited, a:focus {
margin-bottom: $global-guttering; margin-bottom: $global-guttering;
} }
h1, .h1 { font-size: $global-font-size-h1; } h1,
h2, .h2 { font-size: $global-font-size-h2; } .h1 {
h3, .h3 { font-size: $global-font-size-h3; } font-size: $global-font-size-h1;
h4, .h4 { font-size: $global-font-size-h4; } }
h5, .h5 { font-size: $global-font-size-h5; }
h6, .h6 { font-size: $global-font-size-h6; } h2,
.h2 {
font-size: $global-font-size-h2;
}
h3,
.h3 {
font-size: $global-font-size-h3;
}
h4,
.h4 {
font-size: $global-font-size-h4;
}
h5,
.h5 {
font-size: $global-font-size-h5;
}
h6,
.h6 {
font-size: $global-font-size-h6;
}
.container { .container {
display: block; display: block;
margin: auto; margin: auto;
max-width: 40em; max-width: 40em;
padding: $global-guttering*2; padding: $global-guttering*2;
@media (max-width: 620px) { padding: 0; } @media (max-width: 620px) {
padding: 0;
}
} }
.section { .section {
background-color: #FFFFFF; background-color: #FFFFFF;
padding: $global-guttering; padding: $global-guttering;
color: #333; color: #333;
a, a:visited, a:focus { color: #00bcd4; } a,
a:visited,
a:focus {
color: #00bcd4;
}
} }
.logo { .logo {
@ -117,10 +156,24 @@ h6, .h6 { font-size: $global-font-size-h6; }
padding: $global-guttering/4 0; padding: $global-guttering/4 0;
} }
.visible-ie { display: none; } .visible-ie {
.zero-bottom { margin-bottom: 0; } display: none;
.zero-top { margin-top: 0; } }
.text-center { text-align: center; }
.is-hidden { display: none; }
/*===== End of Section comment block ======*/ .zero-bottom {
margin-bottom: 0;
}
.zero-top {
margin-top: 0;
}
.text-center {
text-align: center;
}
.is-hidden {
display: none;
}
/*===== End of Section comment block ======*/

View file

@ -26,21 +26,30 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
position: relative; position: relative;
margin-bottom: $choices-guttering; margin-bottom: $choices-guttering;
font-size: $choices-font-size-lg; font-size: $choices-font-size-lg;
&:focus { outline: none; } &:focus {
&:last-child { margin-bottom: 0; } outline: none;
}
&:last-child {
margin-bottom: 0;
}
&.is-disabled { &.is-disabled {
.#{$choices-selector}__inner, .#{$choices-selector}__input { .#{$choices-selector}__inner,
.#{$choices-selector}__input {
background-color: $choices-bg-color-disabled; background-color: $choices-bg-color-disabled;
cursor: not-allowed; cursor: not-allowed;
user-select: none; user-select: none;
} }
.#{$choices-selector}__item { cursor: not-allowed; } .#{$choices-selector}__item {
cursor: not-allowed;
}
} }
} }
.#{$choices-selector}[data-type*="select-one"] { .#{$choices-selector}[data-type*="select-one"] {
cursor: pointer; cursor: pointer;
.#{$choices-selector}__inner { padding-bottom: 7.5px; } .#{$choices-selector}__inner {
padding-bottom: 7.5px;
}
.#{$choices-selector}__input { .#{$choices-selector}__input {
display: block; display: block;
width: 100%; width: 100%;
@ -62,8 +71,13 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
width: 20px; width: 20px;
border-radius: 10em; border-radius: 10em;
opacity: .5; opacity: .5;
&:hover, &:focus { opacity: 1; } &:hover,
&:focus { box-shadow: 0px 0px 0px 2px $choices-highlight-color; } &:focus {
opacity: 1;
}
&:focus {
box-shadow: 0px 0px 0px 2px $choices-highlight-color;
}
} }
&:after { &:after {
content: ""; content: "";
@ -96,8 +110,11 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
} }
} }
.#{$choices-selector}[data-type*="select-multiple"], .#{$choices-selector}[data-type*="text"] { .#{$choices-selector}[data-type*="select-multiple"],
.#{$choices-selector}__inner { cursor: text; } .#{$choices-selector}[data-type*="text"] {
.#{$choices-selector}__inner {
cursor: text;
}
.#{$choices-selector}__button { .#{$choices-selector}__button {
position: relative; position: relative;
display: inline-block; display: inline-block;
@ -113,7 +130,10 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
line-height: 1; line-height: 1;
opacity: .75; opacity: .75;
border-radius: 0; border-radius: 0;
&:hover, &:focus { opacity: 1; } &:hover,
&:focus {
opacity: 1;
}
} }
} }
@ -128,9 +148,16 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
font-size: $choices-font-size-md; font-size: $choices-font-size-md;
min-height: 44px; min-height: 44px;
overflow: hidden; overflow: hidden;
.is-focused &, .is-open & { border-color: darken($choices-keyline-color, 15%); } .is-focused &,
.is-open & { border-radius: $choices-border-radius $choices-border-radius 0 0; } .is-open & {
.is-flipped.is-open & { border-radius: 0 0 $choices-border-radius $choices-border-radius; } border-color: darken($choices-keyline-color, 15%);
}
.is-open & {
border-radius: $choices-border-radius $choices-border-radius 0 0;
}
.is-flipped.is-open & {
border-radius: 0 0 $choices-border-radius $choices-border-radius;
}
} }
.#{$choices-selector}__list { .#{$choices-selector}__list {
@ -147,7 +174,9 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
padding-right: 4px; padding-right: 4px;
padding-left: 16px; padding-left: 16px;
} }
.#{$choices-selector}__item { width: 100%; } .#{$choices-selector}__item {
width: 100%;
}
} }
.#{$choices-selector}__list--multiple { .#{$choices-selector}__list--multiple {
@ -165,7 +194,9 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
border: 1px solid darken($choices-primary-color, 5%); border: 1px solid darken($choices-primary-color, 5%);
color: #FFFFFF; color: #FFFFFF;
word-break: break-all; word-break: break-all;
&[data-deletable] { padding-right: 5px; } &[data-deletable] {
padding-right: 5px;
}
[dir="rtl"] & { [dir="rtl"] & {
margin-right: 0; margin-right: 0;
margin-left: 3.75px; margin-left: 3.75px;
@ -194,8 +225,12 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
border-bottom-right-radius: $choices-border-radius; border-bottom-right-radius: $choices-border-radius;
overflow: hidden; overflow: hidden;
word-break: break-all; word-break: break-all;
&.is-active { display: block; } &.is-active {
.is-open & { border-color: darken($choices-keyline-color, 15%); } display: block;
}
.is-open & {
border-color: darken($choices-keyline-color, 15%);
}
.is-flipped & { .is-flipped & {
top: auto; top: auto;
bottom: 100%; bottom: 100%;
@ -214,7 +249,9 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
position: relative; position: relative;
padding: 10px; padding: 10px;
font-size: $choices-font-size-md; font-size: $choices-font-size-md;
[dir="rtl"] & { text-align: right; } [dir="rtl"] & {
text-align: right;
}
} }
.#{$choices-selector}__item--selectable { .#{$choices-selector}__item--selectable {
@media (min-width: 640px) { @media (min-width: 640px) {
@ -240,13 +277,21 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
} }
&.is-highlighted { &.is-highlighted {
background-color: mix(#000000, #FFFFFF, 5%); background-color: mix(#000000, #FFFFFF, 5%);
&:after { opacity: .5; } &:after {
opacity: .5;
}
} }
} }
} }
.#{$choices-selector}__item { cursor: default; } .#{$choices-selector}__item {
.#{$choices-selector}__item--selectable { cursor: pointer; } cursor: default;
}
.#{$choices-selector}__item--selectable {
cursor: pointer;
}
.#{$choices-selector}__item--disabled { .#{$choices-selector}__item--disabled {
cursor: not-allowed; cursor: not-allowed;
user-select: none; user-select: none;
@ -270,7 +315,9 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center; background-position: center;
cursor: pointer; cursor: pointer;
&:focus { outline: none; } &:focus {
outline: none;
}
} }
.#{$choices-selector}__input { .#{$choices-selector}__input {
@ -283,14 +330,18 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
border-radius: 0; border-radius: 0;
max-width: 100%; max-width: 100%;
padding: 4px 0 4px 2px; padding: 4px 0 4px 2px;
&:focus { outline: 0; } &:focus {
outline: 0;
}
[dir="rtl"] & { [dir="rtl"] & {
padding-right: 2px; padding-right: 2px;
padding-left: 0; padding-left: 0;
} }
} }
.#{$choices-selector}__placeholder { opacity: .5; } .#{$choices-selector}__placeholder {
opacity: .5;
}
.#{$choices-selector}__input.is-hidden, .#{$choices-selector}__input.is-hidden,
.#{$choices-selector}[data-type*="select-one"] .#{$choices-selector}__input.is-hidden, .#{$choices-selector}[data-type*="select-one"] .#{$choices-selector}__input.is-hidden,
@ -298,4 +349,4 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
display: none; display: none;
} }
/*===== End of Choices ======*/ /*===== End of Choices ======*/

2
types/index.d.ts vendored
View file

@ -917,7 +917,7 @@ export default class Choices {
private createChoicesFragment(choices: any[], fragment: DocumentFragment, withinGroup?: boolean): DocumentFragment; private createChoicesFragment(choices: any[], fragment: DocumentFragment, withinGroup?: boolean): DocumentFragment;
/** Render items into a DOM fragment and append to items list */ /** Render items into a DOM fragment and append to items list */
private createItemsFragment(items: any[], fragment?: DocumentFragment): void; private _createItemsFragment(items: any[], fragment?: DocumentFragment): void;
/** Render DOM with values */ /** Render DOM with values */
private render(): void; private render(): void;

View file

@ -1,25 +1,21 @@
const path = require('path'); const path = require('path');
const webpack = require('webpack'); const webpack = require('webpack');
const Dashboard = require('webpack-dashboard');
const DashboardPlugin = require('webpack-dashboard/plugin');
const dashboard = new Dashboard();
module.exports = { module.exports = {
devtool: 'eval', devtool: 'eval',
entry: [ entry: [
'webpack-dev-server/client?http://localhost:3000', 'webpack/hot/dev-server',
'./src/scripts/src/choices', 'webpack-hot-middleware/client',
'./src/scripts/choices',
], ],
output: { output: {
path: path.join(__dirname, 'dist'), path: path.resolve('public'),
filename: 'choices.min.js', filename: 'choices.min.js',
publicPath: '/src/scripts/dist/', publicPath: 'http://localhost:3001/assets/scripts/',
library: 'Choices', library: 'Choices',
libraryTarget: 'umd', libraryTarget: 'umd',
}, },
plugins: [ plugins: [
new DashboardPlugin(dashboard.setData),
new webpack.HotModuleReplacementPlugin(), new webpack.HotModuleReplacementPlugin(),
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env': { 'process.env': {
@ -32,7 +28,7 @@ module.exports = {
{ {
enforce: 'pre', enforce: 'pre',
test: /\.js?$/, test: /\.js?$/,
include: path.join(__dirname, 'src/scripts/src'), include: path.join(__dirname, 'src/scripts'),
exclude: /(node_modules|bower_components)/, exclude: /(node_modules|bower_components)/,
loader: 'eslint-loader', loader: 'eslint-loader',
query: { query: {
@ -41,7 +37,7 @@ module.exports = {
}, },
{ {
test: /\.js?$/, test: /\.js?$/,
include: path.join(__dirname, 'src/scripts/src'), include: path.join(__dirname, 'src/scripts'),
exclude: /(node_modules|bower_components)/, exclude: /(node_modules|bower_components)/,
loader: 'babel-loader', loader: 'babel-loader',
}, },

View file

@ -11,12 +11,12 @@ module.exports = (env) => {
const config = { const config = {
devtool: minimize ? false : 'cheap-module-source-map', devtool: minimize ? false : 'cheap-module-source-map',
entry: [ entry: [
'./src/scripts/src/choices', './src/scripts/choices',
], ],
output: { output: {
path: path.join(__dirname, '/src/scripts/dist'), path: path.join(__dirname, '/public/assets/scripts'),
filename: minimize ? 'choices.min.js' : 'choices.js', filename: minimize ? 'choices.min.js' : 'choices.js',
publicPath: '/src/scripts/dist/', publicPath: '/public/assets/scripts/',
library: 'Choices', library: 'Choices',
libraryTarget: 'umd', libraryTarget: 'umd',
auxiliaryComment: { auxiliaryComment: {
@ -43,7 +43,7 @@ module.exports = (env) => {
{ {
enforce: 'pre', enforce: 'pre',
test: /\.js?$/, test: /\.js?$/,
include: path.join(__dirname, 'src/scripts/src'), include: path.join(__dirname, 'src/scripts'),
exclude: /(node_modules|bower_components)/, exclude: /(node_modules|bower_components)/,
loader: 'eslint-loader', loader: 'eslint-loader',
query: { query: {
@ -52,7 +52,7 @@ module.exports = (env) => {
}, },
{ {
test: /\.js?$/, test: /\.js?$/,
include: path.join(__dirname, 'src/scripts/src'), include: path.join(__dirname, 'src/scripts'),
exclude: /(node_modules|bower_components)/, exclude: /(node_modules|bower_components)/,
loader: 'babel-loader', loader: 'babel-loader',
}, },