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",
"ecmaFeatures": {
"modules": true
},
"extends": ["prettier"],
"plugins": ["prettier"],
"env": {
"browser": true,
"node": true,
"mocha": true,
"jasmine": true
"mocha": true
},
"globals": {
"__DEV__": true,
"describe": true,
"it": true,
"before": true,
"after": true,
"beforeEach": true,
"afterEach": true,
"expect": true,
"browser": true,
"by": true,
"element": true
"afterEach": true
},
"parser": "babel-eslint",
"rules": {
"strict": 0,
"no-underscore-dangle": 0,
"no-console": ["warn", { allow: ["warn", "error"] }],
"space-before-function-paren": 0
"prettier/prettier": ["error", {
"singleQuote": true,
"trailingComma": "all"
}]
}
}

2
.gitignore vendored
View file

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

View file

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

View file

@ -36,11 +36,11 @@ Or include Choices directly:
```html
<!-- 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 -->
<link rel="stylesheet" href="assets/styles/css/choices.min.css">
<link rel="stylesheet" href="public/styles/choices.min.css">
<!-- Include Choices JavaScript -->
<script src="/assets/scripts/dist/choices.min.js"></script>
<script src="/public/scripts/choices.min.js"></script>
```
## Setup

View file

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

1
cypress.json Normal file
View file

@ -0,0 +1 @@
{}

View file

@ -1,25 +1,25 @@
{
"name": "choices.js",
"version": "3.0.2",
"version": "4.0.0",
"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",
"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",
"coverage": "nyc npm run test",
"test": "mocha --require ./config/test.js --compilers js:babel-core/register \"./src/**/**/**/**/*.test.js\"",
"test:watch": "npm run test -- --watch --inspect=5556",
"coverage": "nyc npm run test",
"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: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:prefix": "postcss --use autoprefixer -b 'last 2 versions' src/styles/css/*.css -d src/styles/css/",
"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: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' public/assets/styles/*.css -d public/assets/styles",
"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\"",
"version": "node version.js --current $npm_package_version --new $npm_config_newVersion",
"postversion": "npm run js:build",
"prepush": "npm run lint && npm run test",
"dev": "dev"
"prepush": "npm run lint && npm run test"
},
"repository": {
"type": "git",
@ -40,35 +40,35 @@
"babel-preset-stage-2": "^6.24.1",
"chai": "^4.1.0",
"concurrently": "^3.1.0",
"core-js": "^2.4.1",
"csso": "^1.8.2",
"custom-event-autopolyfill": "^0.1.3",
"es6-promise": "^3.2.1",
"eslint": "^3.19.0",
"eslint-config-airbnb": "^15.1.0",
"eslint-config-prettier": "^2.9.0",
"eslint-loader": "^1.5.0",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-jsx-a11y": "^5.1.1",
"eslint-plugin-prettier": "^2.6.0",
"eslint-plugin-react": "^7.2.1",
"express": "^4.16.3",
"husky": "^0.14.3",
"jasmine-core": "2.4.1",
"jsdom": "^11.5.1",
"mocha": "^3.4.2",
"node-sass": "^3.4.2",
"nodemon": "^1.9.1",
"nyc": "^11.0.3",
"open": "0.0.5",
"opn-cli": "^3.1.0",
"postcss-cli": "^2.5.1",
"prettier": "^1.13.0",
"sinon": "^2.4.0",
"webpack": "^3.8.1",
"webpack-dashboard": "^0.1.8",
"webpack-dev-server": "^1.14.1",
"webpack-dev-middleware": "^2.0.0",
"webpack-hot-middleware": "^2.22.2",
"whatwg-fetch": "^1.0.0",
"wrapper-webpack-plugin": "^0.1.7"
},
"dependencies": {
"classnames": "^2.2.5",
"core-js": "^2.5.6",
"custom-event-polyfill": "^0.3.0",
"fuse.js": "^3.1.0",
"opn": "^5.1.0",
"redux": "^3.3.1"
@ -78,9 +78,9 @@
{
"basePath": "src",
"files": [
"scripts/dist/*",
"styles/css/*",
"icons/*"
"public/scripts/*",
"public/styles/*",
"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;
}
*, *:before, *:after {
*,
*:before,
*:after {
box-sizing: border-box;
}
html, body {
html,
body {
position: relative;
margin: 0;
width: 100%;
@ -46,14 +49,21 @@ hr {
height: 1px;
}
h1, h2, h3, h4, h5, h6 {
h1,
h2,
h3,
h4,
h5,
h6 {
margin-top: 0;
margin-bottom: 12px;
font-weight: 400;
line-height: 1.2;
}
a, a:visited, a:focus {
a,
a:visited,
a:focus {
color: #FFFFFF;
text-decoration: none;
font-weight: 600;
@ -73,27 +83,33 @@ a, a:visited, a:focus {
margin-bottom: 24px;
}
h1, .h1 {
h1,
.h1 {
font-size: 32px;
}
h2, .h2 {
h2,
.h2 {
font-size: 24px;
}
h3, .h3 {
h3,
.h3 {
font-size: 20px;
}
h4, .h4 {
h4,
.h4 {
font-size: 18px;
}
h5, .h5 {
h5,
.h5 {
font-size: 16px;
}
h6, .h6 {
h6,
.h6 {
font-size: 14px;
}
@ -116,7 +132,9 @@ h6, .h6 {
color: #333;
}
.section a, .section a:visited, .section a:focus {
.section a,
.section a:visited,
.section a:focus {
color: #00bcd4;
}

View file

@ -15,7 +15,8 @@
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;
cursor: not-allowed;
-webkit-user-select: none;
@ -99,11 +100,13 @@
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;
}
.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;
display: inline-block;
margin-top: 0;
@ -120,7 +123,9 @@
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;
}
@ -137,7 +142,8 @@
overflow: hidden;
}
.is-focused .choices__inner, .is-open .choices__inner {
.is-focused .choices__inner,
.is-open .choices__inner {
border-color: #b7b7b7;
}
@ -268,8 +274,7 @@
position: absolute;
right: 10px;
top: 50%;
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
transform: translateY(-50%);
}
[dir="rtl"] .choices__list--dropdown .choices__item--selectable {
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">
<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.">
<link rel="apple-touch-icon" sizes="180x180" href="src/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="src/images/favicon-16x16.png" sizes="16x16">
<link rel="manifest" href="src/images/manifest.json">
<link rel="mask-icon" href="src/images/safari-pinned-tab.svg" color="#00bcd4">
<link rel="shortcut icon" href="src/images/favicon.ico">
<meta name="msapplication-config" content="/src/images/browserconfig.xml">
<link rel="apple-touch-icon" sizes="180x180" href="assets/images/apple-touch-icon.png">
<link rel="icon" type="image/png" href="assets/images/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="assets/images/favicon-16x16.png" sizes="16x16">
<link rel="manifest" href="assets/images/manifest.json">
<link rel="mask-icon" href="assets/images/safari-pinned-tab.svg" color="#00bcd4">
<link rel="shortcut icon" href="assets/images/favicon.ico">
<meta name="msapplication-config" content="/assets/images/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<!-- 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 -->
<!-- Optional includes -->
@ -24,8 +24,8 @@
<!-- End optional includes -->
<!-- Choices includes -->
<link rel="stylesheet" href="src/styles/css/choices.min.css?version=3.0.2">
<script src="src/scripts/dist/choices.min.js?version=2.8.8"></script>
<link rel="stylesheet" href="assets/styles/choices.min.css?version=3.0.2">
<script src="assets/scripts/choices.min.js?version=2.8.8"></script>
<!-- End Choices includes -->
<!--[if lt IE 9]>
@ -40,7 +40,7 @@
<div class="container">
<div class="section">
<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>
</a>
<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');
const WebpackDevServer = require('webpack-dev-server');
const config = require('./webpack.config.dev');
const opn = require('opn');
/* eslint-disable no-console,global-require,import/no-extraneous-dependencies */
const express = require('express');
const path = require('path');
new WebpackDevServer(webpack(config), {
publicPath: config.output.publicPath,
historyApiFallback: true,
quiet: true, // lets WebpackDashboard do its thing
}).listen(3001, 'localhost', (err) => {
if (err) console.log(err);
opn('http://localhost:3001');
console.log('Listening at localhost:3001');
const PORT = 3001;
const DIST_DIR = path.join(__dirname, 'public');
const app = express();
if (process.env.NODE_ENV !== 'production') {
const webpack = require('webpack');
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,
};
expect(actions.addChoice(
value,
label,
id,
groupId,
disabled,
elementId,
customProperties,
placeholder,
keyCode,
)).to.eql(expectedAction);
expect(
actions.addChoice(
value,
label,
id,
groupId,
disabled,
elementId,
customProperties,
placeholder,
keyCode,
),
).to.eql(expectedAction);
});
});
@ -88,4 +90,3 @@ describe('actions/choices', () => {
});
});
});

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -45,7 +45,7 @@ describe('choices', () => {
instance.init();
});
it('doesn\'t set initialise flag', () => {
it("doesn't set initialise flag", () => {
expect(instance.initialised).to.not.equal(false);
});
});
@ -60,7 +60,7 @@ describe('choices', () => {
beforeEach(() => {
createTemplatesSpy = spy(instance, '_createTemplates');
createInputSpy = spy(instance, '_createStructure');
storeSubscribeSpy = spy(instance.store, 'subscribe');
storeSubscribeSpy = spy(instance._store, 'subscribe');
renderSpy = spy(instance, 'render');
addEventListenersSpy = spy(instance, '_addEventListeners');
@ -123,7 +123,7 @@ describe('choices', () => {
instance.destroy();
});
it('doesn\'t set initialise flag', () => {
it("doesn't set initialise flag", () => {
expect(instance.initialised).to.not.equal(true);
});
});
@ -161,7 +161,9 @@ describe('choices', () => {
it('reverts outer container', () => {
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', () => {
@ -263,7 +265,6 @@ describe('choices', () => {
inputDisableSpy = spy(instance.input, 'disable');
});
afterEach(() => {
removeEventListenersSpy.restore();
passedElementDisableSpy.restore();
@ -374,29 +375,43 @@ describe('choices', () => {
expect(output).to.eql(instance);
});
it('opens containerOuter', () => {
expect(containerOuterOpenSpy.called).to.equal(true);
it('opens containerOuter', done => {
requestAnimationFrame(() => {
expect(containerOuterOpenSpy.called).to.equal(true);
done();
});
});
it('shows dropdown with blurInput flag', () => {
expect(dropdownShowSpy.called).to.equal(true);
it('shows dropdown with blurInput flag', done => {
requestAnimationFrame(() => {
expect(dropdownShowSpy.called).to.equal(true);
done();
});
});
it('triggers event on passedElement', () => {
expect(passedElementTriggerEventStub.called).to.equal(true);
expect(passedElementTriggerEventStub.lastCall.args[0]).to.eql(EVENTS.showDropdown);
expect(passedElementTriggerEventStub.lastCall.args[1]).to.eql({});
it('triggers event on passedElement', done => {
requestAnimationFrame(() => {
expect(passedElementTriggerEventStub.called).to.equal(true);
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', () => {
beforeEach(() => {
instance.dropdown.isActive = false;
instance.canSearch = true;
instance._canSearch = true;
output = instance.showDropdown(true);
});
it('focuses input', () => {
expect(inputFocusSpy.called).to.equal(true);
it('focuses input', done => {
requestAnimationFrame(() => {
expect(inputFocusSpy.called).to.equal(true);
done();
});
});
});
});
@ -413,7 +428,10 @@ describe('choices', () => {
containerOuterCloseSpy = spy(instance.containerOuter, 'close');
dropdownHideSpy = spy(instance.dropdown, 'hide');
inputBlurSpy = spy(instance.input, 'blur');
inputRemoveActiveDescendantSpy = spy(instance.input, 'removeActiveDescendant');
inputRemoveActiveDescendantSpy = spy(
instance.input,
'removeActiveDescendant',
);
passedElementTriggerEventStub = stub();
instance.passedElement.triggerEvent = passedElementTriggerEventStub;
@ -453,33 +471,50 @@ describe('choices', () => {
expect(output).to.eql(instance);
});
it('closes containerOuter', () => {
expect(containerOuterCloseSpy.called).to.equal(true);
it('closes containerOuter', done => {
requestAnimationFrame(() => {
expect(containerOuterCloseSpy.called).to.equal(true);
done();
});
});
it('hides dropdown with blurInput flag', () => {
expect(dropdownHideSpy.called).to.equal(true);
it('hides dropdown with blurInput flag', done => {
requestAnimationFrame(() => {
expect(dropdownHideSpy.called).to.equal(true);
done();
});
});
it('triggers event on passedElement', () => {
expect(passedElementTriggerEventStub.called).to.equal(true);
expect(passedElementTriggerEventStub.lastCall.args[0]).to.eql(EVENTS.hideDropdown);
expect(passedElementTriggerEventStub.lastCall.args[1]).to.eql({});
it('triggers event on passedElement', done => {
requestAnimationFrame(() => {
expect(passedElementTriggerEventStub.called).to.equal(true);
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', () => {
beforeEach(() => {
instance.dropdown.isActive = true;
instance.canSearch = true;
instance._canSearch = true;
output = instance.hideDropdown(true);
});
it('removes active descendants', () => {
expect(inputRemoveActiveDescendantSpy.called).to.equal(true);
it('removes active descendants', done => {
requestAnimationFrame(() => {
expect(inputRemoveActiveDescendantSpy.called).to.equal(true);
done();
});
});
it('blurs input', () => {
expect(inputBlurSpy.called).to.equal(true);
it('blurs input', done => {
requestAnimationFrame(() => {
expect(inputBlurSpy.called).to.equal(true);
done();
});
});
});
});
@ -540,15 +575,15 @@ describe('choices', () => {
storeGetGroupByIdStub = stub().returns({
value: groupIdValue,
});
storeDispatchSpy = spy(instance.store, 'dispatch');
storeDispatchSpy = spy(instance._store, 'dispatch');
instance.store.getGroupById = storeGetGroupByIdStub;
instance._store.getGroupById = storeGetGroupByIdStub;
instance.passedElement.triggerEvent = passedElementTriggerEventStub;
});
afterEach(() => {
storeDispatchSpy.restore();
instance.store.getGroupById.reset();
instance._store.getGroupById.reset();
instance.passedElement.triggerEvent.reset();
});
@ -597,7 +632,9 @@ describe('choices', () => {
it('triggers event with null groupValue', () => {
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({
id: item.id,
value: item.value,
@ -615,7 +652,9 @@ describe('choices', () => {
it('triggers event with groupValue', () => {
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({
id: item.id,
value: item.value,
@ -631,7 +670,7 @@ describe('choices', () => {
output = instance.highlightItem(item, false);
});
it('doesn\'t trigger event', () => {
it("doesn't trigger event", () => {
expect(passedElementTriggerEventStub.called).to.equal(false);
});
@ -651,15 +690,15 @@ describe('choices', () => {
storeGetGroupByIdStub = stub().returns({
value: groupIdValue,
});
storeDispatchSpy = spy(instance.store, 'dispatch');
storeDispatchSpy = spy(instance._store, 'dispatch');
instance.store.getGroupById = storeGetGroupByIdStub;
instance._store.getGroupById = storeGetGroupByIdStub;
instance.passedElement.triggerEvent = passedElementTriggerEventStub;
});
afterEach(() => {
storeDispatchSpy.restore();
instance.store.getGroupById.reset();
instance._store.getGroupById.reset();
instance.passedElement.triggerEvent.reset();
});
@ -708,7 +747,9 @@ describe('choices', () => {
it('triggers event with null groupValue', () => {
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({
id: item.id,
value: item.value,
@ -726,7 +767,9 @@ describe('choices', () => {
it('triggers event with groupValue', () => {
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({
id: item.id,
value: item.value,
@ -742,7 +785,7 @@ describe('choices', () => {
output = instance.highlightItem(item, false);
});
it('doesn\'t trigger event', () => {
it("doesn't trigger event", () => {
expect(passedElementTriggerEventStub.called).to.equal(false);
});
@ -767,7 +810,7 @@ describe('choices', () => {
];
beforeEach(() => {
storeGetItemsStub = stub(instance.store, 'items').get(() => items);
storeGetItemsStub = stub(instance._store, 'items').get(() => items);
highlightItemStub = stub();
instance.highlightItem = highlightItemStub;
@ -805,7 +848,7 @@ describe('choices', () => {
];
beforeEach(() => {
storeGetItemsStub = stub(instance.store, 'items').get(() => items);
storeGetItemsStub = stub(instance._store, 'items').get(() => items);
unhighlightItemStub = stub();
instance.unhighlightItem = unhighlightItemStub;
@ -832,13 +875,13 @@ describe('choices', () => {
beforeEach(() => {
storeDispatchStub = stub();
instance.store.dispatch = storeDispatchStub;
instance._store.dispatch = storeDispatchStub;
output = instance.clearStore();
});
afterEach(() => {
instance.store.dispatch.reset();
instance._store.dispatch.reset();
});
returnsInstance(output);
@ -857,21 +900,21 @@ describe('choices', () => {
beforeEach(() => {
inputClearSpy = spy(instance.input, 'clear');
storeDispatchStub = stub();
instance.store.dispatch = storeDispatchStub;
instance._store.dispatch = storeDispatchStub;
output = instance.clearInput();
});
afterEach(() => {
inputClearSpy.restore();
instance.store.dispatch.reset();
instance._store.dispatch.reset();
});
returnsInstance(output);
describe('text element', () => {
beforeEach(() => {
instance.isSelectOneElement = false;
instance.isTextElement = false;
instance._isSelectOneElement = false;
instance._isTextElement = false;
output = instance.clearInput();
});
@ -884,8 +927,8 @@ describe('choices', () => {
describe('select element with search enabled', () => {
beforeEach(() => {
instance.isSelectOneElement = true;
instance.isTextElement = false;
instance._isSelectOneElement = true;
instance._isTextElement = false;
instance.config.searchEnabled = true;
output = instance.clearInput();
@ -897,7 +940,7 @@ describe('choices', () => {
});
it('resets search flag', () => {
expect(instance.isSearching).to.equal(false);
expect(instance._isSearching).to.equal(false);
});
it('dispatches activateChoices action', () => {
@ -948,7 +991,7 @@ describe('choices', () => {
describe('text element', () => {
beforeEach(() => {
instance.isSelectElement = false;
instance._isSelectElement = false;
output = instance.ajax(() => {});
});
@ -970,7 +1013,7 @@ describe('choices', () => {
beforeEach(() => {
instance.initialised = true;
instance.isSelectElement = true;
instance._isSelectElement = true;
ajaxCallbackStub = stub();
callback = stub();
output = instance.ajax(callback);
@ -978,7 +1021,7 @@ describe('choices', () => {
returnsInstance(output);
it('sets loading state', (done) => {
it('sets loading state', done => {
requestAnimationFrame(() => {
expect(handleLoadingStateStub.called).to.equal(true);
done();
@ -1067,7 +1110,7 @@ describe('choices', () => {
describe('when already initialised and not text element', () => {
beforeEach(() => {
instance.initialised = true;
instance.isTextElement = false;
instance._isTextElement = false;
});
describe('passing a string value', () => {
@ -1081,15 +1124,14 @@ describe('choices', () => {
it('sets each choice with same value', () => {
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', () => {
const values = [
'Value 1',
'Value 2',
];
const values = ['Value 1', 'Value 2'];
beforeEach(() => {
output = instance.setChoiceByValue(values);
@ -1099,8 +1141,12 @@ describe('choices', () => {
it('sets each choice with same value', () => {
expect(findAndSelectChoiceByValueStub.callCount).to.equal(2);
expect(findAndSelectChoiceByValueStub.firstCall.args[0]).to.equal(values[0]);
expect(findAndSelectChoiceByValueStub.secondCall.args[0]).to.equal(values[1]);
expect(findAndSelectChoiceByValueStub.firstCall.args[0]).to.equal(
values[0],
);
expect(findAndSelectChoiceByValueStub.secondCall.args[0]).to.equal(
values[1],
);
});
});
});
@ -1120,7 +1166,7 @@ describe('choices', () => {
];
beforeEach(() => {
activeItemsStub = stub(instance.store, 'activeItems').get(() => items);
activeItemsStub = stub(instance._store, 'activeItems').get(() => items);
});
afterEach(() => {
@ -1130,7 +1176,7 @@ describe('choices', () => {
describe('passing true valueOnly flag', () => {
describe('select one input', () => {
beforeEach(() => {
instance.isSelectOneElement = true;
instance._isSelectOneElement = true;
output = instance.getValue(true);
});
@ -1141,12 +1187,12 @@ describe('choices', () => {
describe('non select one input', () => {
beforeEach(() => {
instance.isSelectOneElement = false;
instance._isSelectOneElement = false;
output = instance.getValue(true);
});
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('select one input', () => {
beforeEach(() => {
instance.isSelectOneElement = true;
instance._isSelectOneElement = true;
output = instance.getValue(false);
});
@ -1165,7 +1211,7 @@ describe('choices', () => {
describe('non select one input', () => {
beforeEach(() => {
instance.isSelectOneElement = false;
instance._isSelectOneElement = false;
output = instance.getValue(false);
});
@ -1177,51 +1223,41 @@ describe('choices', () => {
});
describe('removeActiveItemsByValue', () => {
describe('passing invalid value', () => {
beforeEach(() => {
output = instance.removeActiveItemsByValue(null);
});
let activeItemsStub;
let removeItemStub;
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', () => {
let activeItemsStub;
let removeItemStub;
const value = 'Removed';
const items = [
{
id: '1',
value: 'Not removed',
},
{
id: '2',
value: 'Removed',
},
{
id: '3',
value: 'Removed',
},
];
afterEach(() => {
activeItemsStub.reset();
instance._removeItem.reset();
});
beforeEach(() => {
removeItemStub = stub();
activeItemsStub = stub(instance.store, 'activeItems').get(() => items);
instance._removeItem = removeItemStub;
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]);
});
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(() => {
removeItemStub = stub();
activeItemsStub = stub(instance.store, 'activeItems').get(() => items);
activeItemsStub = stub(instance._store, 'activeItems').get(() => items);
instance._removeItem = removeItemStub;
});
@ -1298,9 +1334,11 @@ describe('choices', () => {
},
];
beforeEach(() => {
highlightedActiveItemsStub = stub(instance.store, 'highlightedActiveItems').get(() => items);
highlightedActiveItemsStub = stub(
instance._store,
'highlightedActiveItems',
).get(() => items);
removeItemStub = stub();
triggerChangeStub = stub();
@ -1397,7 +1435,7 @@ describe('choices', () => {
describe('when element is not select element', () => {
beforeEach(() => {
instance.isSelectElement = false;
instance._isSelectElement = false;
instance.setChoices(choices, value, label, false);
});
@ -1407,7 +1445,7 @@ describe('choices', () => {
describe('passing invalid arguments', () => {
describe('passing an empty array', () => {
beforeEach(() => {
instance.isSelectElement = true;
instance._isSelectElement = true;
instance.setChoices([], value, label, false);
});
@ -1416,7 +1454,7 @@ describe('choices', () => {
describe('passing no value', () => {
beforeEach(() => {
instance.isSelectElement = true;
instance._isSelectElement = true;
instance.setChoices(choices, undefined, 'label', false);
});
@ -1426,7 +1464,7 @@ describe('choices', () => {
describe('passing valid arguments', () => {
beforeEach(() => {
instance.isSelectElement = true;
instance._isSelectElement = true;
});
it('removes loading state', () => {
@ -1477,8 +1515,8 @@ describe('choices', () => {
});
});
describe('createGroupsFragment', () => {
let createChoicesFragmentStub;
describe('_createGroupsFragment', () => {
let _createChoicesFragmentStub;
const choices = [
{
id: 1,
@ -1519,12 +1557,12 @@ describe('choices', () => {
];
beforeEach(() => {
createChoicesFragmentStub = stub();
instance.createChoicesFragment = createChoicesFragmentStub;
_createChoicesFragmentStub = stub();
instance._createChoicesFragment = _createChoicesFragmentStub;
});
afterEach(() => {
instance.createChoicesFragment.reset();
instance._createChoicesFragment.reset();
});
describe('returning a fragment of groups', () => {
@ -1534,24 +1572,28 @@ describe('choices', () => {
const childElement = document.createElement('div');
fragment.appendChild(childElement);
output = instance.createGroupsFragment(groups, choices, fragment);
output = instance._createGroupsFragment(groups, choices, fragment);
const elementToWrapFragment = document.createElement('div');
elementToWrapFragment.appendChild(output);
expect(output).to.be.instanceOf(DocumentFragment);
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', () => {
it('returns new groups fragment', () => {
output = instance.createGroupsFragment(groups, choices);
output = instance._createGroupsFragment(groups, choices);
const elementToWrapFragment = document.createElement('div');
elementToWrapFragment.appendChild(output);
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', () => {
expect(sortFnStub.called).to.equal(false);
instance.createGroupsFragment(groups, choices);
instance._createGroupsFragment(groups, choices);
expect(sortFnStub.called).to.equal(true);
});
});
@ -1589,21 +1631,21 @@ describe('choices', () => {
});
it('does not sort groups', () => {
instance.createGroupsFragment(groups, choices);
instance._createGroupsFragment(groups, choices);
expect(sortFnStub.called).to.equal(false);
});
});
describe('select-one element', () => {
beforeEach(() => {
instance.isSelectOneElement = true;
instance._isSelectOneElement = true;
});
it('calls createChoicesFragment with choices that belong to each group', () => {
expect(createChoicesFragmentStub.called).to.equal(false);
instance.createGroupsFragment(groups, choices);
expect(createChoicesFragmentStub.called).to.equal(true);
expect(createChoicesFragmentStub.firstCall.args[0]).to.eql([
it('calls _createChoicesFragment with choices that belong to each group', () => {
expect(_createChoicesFragmentStub.called).to.equal(false);
instance._createGroupsFragment(groups, choices);
expect(_createChoicesFragmentStub.called).to.equal(true);
expect(_createChoicesFragmentStub.firstCall.args[0]).to.eql([
{
id: 1,
selected: true,
@ -1619,7 +1661,7 @@ describe('choices', () => {
label: 'Choice 3',
},
]);
expect(createChoicesFragmentStub.secondCall.args[0]).to.eql([
expect(_createChoicesFragmentStub.secondCall.args[0]).to.eql([
{
id: 2,
selected: false,
@ -1634,15 +1676,15 @@ describe('choices', () => {
describe('text/select-multiple element', () => {
describe('renderSelectedChoices set to "always"', () => {
beforeEach(() => {
instance.isSelectOneElement = false;
instance._isSelectOneElement = false;
instance.config.renderSelectedChoices = 'always';
});
it('calls createChoicesFragment with choices that belong to each group', () => {
expect(createChoicesFragmentStub.called).to.equal(false);
instance.createGroupsFragment(groups, choices);
expect(createChoicesFragmentStub.called).to.equal(true);
expect(createChoicesFragmentStub.firstCall.args[0]).to.eql([
it('calls _createChoicesFragment with choices that belong to each group', () => {
expect(_createChoicesFragmentStub.called).to.equal(false);
instance._createGroupsFragment(groups, choices);
expect(_createChoicesFragmentStub.called).to.equal(true);
expect(_createChoicesFragmentStub.firstCall.args[0]).to.eql([
{
id: 1,
selected: true,
@ -1658,7 +1700,7 @@ describe('choices', () => {
label: 'Choice 3',
},
]);
expect(createChoicesFragmentStub.secondCall.args[0]).to.eql([
expect(_createChoicesFragmentStub.secondCall.args[0]).to.eql([
{
id: 2,
selected: false,
@ -1672,15 +1714,15 @@ describe('choices', () => {
describe('renderSelectedChoices not set to "always"', () => {
beforeEach(() => {
instance.isSelectOneElement = false;
instance._isSelectOneElement = false;
instance.config.renderSelectedChoices = false;
});
it('calls createChoicesFragment with choices that belong to each group that are not already selected', () => {
expect(createChoicesFragmentStub.called).to.equal(false);
instance.createGroupsFragment(groups, choices);
expect(createChoicesFragmentStub.called).to.equal(true);
expect(createChoicesFragmentStub.firstCall.args[0]).to.eql([
it('calls _createChoicesFragment with choices that belong to each group that are not already selected', () => {
expect(_createChoicesFragmentStub.called).to.equal(false);
instance._createGroupsFragment(groups, choices);
expect(_createChoicesFragmentStub.called).to.equal(true);
expect(_createChoicesFragmentStub.firstCall.args[0]).to.eql([
{
id: 3,
selected: false,
@ -1689,7 +1731,7 @@ describe('choices', () => {
label: 'Choice 3',
},
]);
expect(createChoicesFragmentStub.secondCall.args[0]).to.eql([
expect(_createChoicesFragmentStub.secondCall.args[0]).to.eql([
{
id: 2,
selected: false,
@ -1704,41 +1746,31 @@ describe('choices', () => {
});
});
describe('createChoicesFragment', () => {
beforeEach(() => {});
it('returns a fragment of choices', () => {});
});
// describe('render', () => {
// beforeEach(() => {});
describe('createItemsFragment', () => {
beforeEach(() => {});
it('returns a fragment of items', () => {});
});
// describe('no change to state', () => {
// it('returns early', () => {});
// });
describe('render', () => {
beforeEach(() => {});
// describe('change to state', () => {
// it('updates previous state to current state', () => {});
describe('no change to state', () => {
it('returns early', () => {});
});
// describe('select element', () => {
// it('clears choice list', () => {});
describe('change to state', () => {
it('updates previous state to current state', () => {});
// describe('when resetScrollPosition config option is set to true', () => {
// it('scrolls to top of choice list', () => {});
// });
// });
describe('select element', () => {
it('clears choice list', () => {});
describe('when resetScrollPosition config option is set to true', () => {
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', () => {});
});
});
});
});
// 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';
export default class Container {
constructor(instance, element, classNames) {
this.parentInstance = instance;
this.element = element;
this.classNames = classNames;
this.config = instance.config;
constructor({ element, type, classNames, position }) {
Object.assign(this, { element, classNames, type, position });
this.isOpen = false;
this.isFlipped = false;
this.isFocussed = false;
this.isDisabled = 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
*/
*/
addEventListeners() {
this.element.addEventListener('focus', this.onFocus);
this.element.addEventListener('blur', this.onBlur);
this.element.addEventListener('focus', this._onFocus);
this.element.addEventListener('blur', this._onBlur);
}
/**
* Remove event listeners
*/
*/
/** */
removeEventListeners() {
this.element.removeEventListener('focus', this.onFocus);
this.element.removeEventListener('blur', this.onBlur);
}
/**
* Set focussed state
*/
onFocus() {
this.isFocussed = true;
}
/**
* Remove blurred state
*/
onBlur() {
this.isFocussed = false;
this.element.removeEventListener('focus', this._onFocus);
this.element.removeEventListener('blur', this._onBlur);
}
/**
@ -57,16 +42,16 @@ export default class Container {
if (dropdownPos === undefined) {
return false;
}
// If flip is enabled and the dropdown bottom position is
// greater than the window height flip the dropdown.
let shouldFlip = false;
if (this.config.position === 'auto') {
if (this.position === 'auto') {
shouldFlip = dropdownPos >= windowHeight;
} else if (this.config.position === 'top') {
} else if (this.position === 'top') {
shouldFlip = true;
}
return shouldFlip;
}
@ -129,7 +114,7 @@ export default class Container {
enable() {
this.element.classList.remove(this.classNames.disabledState);
this.element.removeAttribute('aria-disabled');
if (this.parentInstance.isSelectOneElement) {
if (this.type === 'select-one') {
this.element.setAttribute('tabindex', '0');
}
this.isDisabled = false;
@ -141,7 +126,7 @@ export default class Container {
disable() {
this.element.classList.add(this.classNames.disabledState);
this.element.setAttribute('aria-disabled', 'true');
if (this.parentInstance.isSelectOneElement) {
if (this.type === 'select-one') {
this.element.setAttribute('tabindex', '-1');
}
this.isDisabled = true;
@ -153,10 +138,7 @@ export default class Container {
unwrap(element) {
// Move passed element outside this element
this.element.parentNode.insertBefore(
element,
this.element,
);
this.element.parentNode.insertBefore(element, this.element);
// Remove this element
this.element.parentNode.removeChild(this.element);
}
@ -178,4 +160,18 @@ export default class Container {
this.element.removeAttribute('aria-busy');
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 { stub } from 'sinon';
import Container from './container';
import { DEFAULT_CLASSNAMES, DEFAULT_CONFIG } from '../constants';
import { DEFAULT_CLASSNAMES } from '../constants';
describe('components/container', () => {
let instance;
let choicesInstance;
let element;
beforeEach(() => {
choicesInstance = {
config: {
...DEFAULT_CONFIG,
},
};
element = document.createElement('div');
element.id = 'container';
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(() => {
@ -27,10 +27,6 @@ describe('components/container', () => {
});
describe('constructor', () => {
it('assigns choices instance to class', () => {
expect(instance.parentInstance).to.eql(choicesInstance);
});
it('assigns choices element to class', () => {
expect(instance.element).to.eql(element);
});
@ -81,7 +77,7 @@ describe('components/container', () => {
describe('onFocus', () => {
it('sets isFocussed flag to true', () => {
expect(instance.isFocussed).to.equal(false);
instance.onFocus();
instance._onFocus();
expect(instance.isFocussed).to.equal(true);
});
});
@ -89,7 +85,7 @@ describe('components/container', () => {
describe('onBlur', () => {
it('sets isFocussed flag to false', () => {
instance.isFocussed = true;
instance.onBlur();
instance._onBlur();
expect(instance.isFocussed).to.equal(false);
});
});
@ -104,7 +100,7 @@ describe('components/container', () => {
describe('passing dropdownPos', () => {
describe('position config option set to "auto"', () => {
beforeEach(() => {
instance.config.position = 'auto';
instance.position = 'auto';
});
describe('dropdownPos is greater than window height', () => {
@ -122,7 +118,7 @@ describe('components/container', () => {
describe('position config option set to "top"', () => {
beforeEach(() => {
instance.config.position = 'top';
instance.position = 'top';
});
it('returns true', () => {
@ -132,7 +128,7 @@ describe('components/container', () => {
describe('position config option set to "bottom"', () => {
beforeEach(() => {
instance.config.position = 'bottom';
instance.position = 'bottom';
});
it('returns false', () => {
@ -143,21 +139,32 @@ describe('components/container', () => {
});
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';
expect(instance.element.getAttribute('aria-activedescendant')).to.equal(null);
expect(instance.element.getAttribute('aria-activedescendant')).to.equal(
null,
);
instance.setActiveDescendant(activeDescendantID);
expect(instance.element.getAttribute('aria-activedescendant')).to.equal(activeDescendantID);
expect(instance.element.getAttribute('aria-activedescendant')).to.equal(
activeDescendantID,
);
});
});
describe('removeActiveDescendant', () => {
it('remove elememnt\'s aria-activedescendant attribute', () => {
it("remove elememnt's aria-activedescendant attribute", () => {
const activeDescendantID = '1234';
instance.element.setAttribute('aria-activedescendant', activeDescendantID);
expect(instance.element.getAttribute('aria-activedescendant')).to.equal(activeDescendantID);
instance.element.setAttribute(
'aria-activedescendant',
activeDescendantID,
);
expect(instance.element.getAttribute('aria-activedescendant')).to.equal(
activeDescendantID,
);
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', () => {
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', () => {
@ -192,7 +201,9 @@ describe('components/container', () => {
});
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', () => {
@ -207,7 +218,9 @@ describe('components/container', () => {
});
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', () => {
@ -225,7 +238,9 @@ describe('components/container', () => {
});
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', () => {
@ -268,9 +283,13 @@ describe('components/container', () => {
});
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();
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', () => {
expect(instance.element.classList.contains(DEFAULT_CLASSNAMES.focusState)).to.equal(true);
expect(
instance.element.classList.contains(DEFAULT_CLASSNAMES.focusState),
).to.equal(true);
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', () => {
expect(instance.element.classList.contains(DEFAULT_CLASSNAMES.disabledState)).to.equal(true);
expect(
instance.element.classList.contains(DEFAULT_CLASSNAMES.disabledState),
).to.equal(true);
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', () => {
@ -310,7 +337,7 @@ describe('components/container', () => {
describe('select one element', () => {
beforeEach(() => {
instance.parentInstance.isSelectOneElement = true;
instance.type = 'select-one';
instance.enable();
});
@ -326,9 +353,13 @@ describe('components/container', () => {
});
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();
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', () => {
@ -344,7 +375,7 @@ describe('components/container', () => {
describe('select one element', () => {
beforeEach(() => {
instance.parentInstance.isSelectOneElement = true;
instance.type = 'select-one';
instance.disable();
});
@ -370,7 +401,9 @@ describe('components/container', () => {
it('wraps passed element inside element', () => {
expect(instance.element.querySelector('div#wrap-test')).to.equal(null);
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', () => {
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);
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', () => {
@ -408,9 +445,13 @@ describe('components/container', () => {
});
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();
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', () => {
@ -432,9 +473,13 @@ describe('components/container', () => {
});
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();
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', () => {

View file

@ -1,10 +1,7 @@
export default class Dropdown {
constructor(instance, element, classNames) {
this.parentInstance = instance;
this.element = element;
this.classNames = classNames;
this.dimensions = null;
this.position = null;
constructor({ element, type, classNames }) {
Object.assign(this, { element, type, classNames });
this.isActive = false;
}
@ -15,7 +12,9 @@ export default class Dropdown {
*/
distanceFromTopWindow() {
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;
}
@ -36,7 +35,7 @@ export default class Dropdown {
this.element.classList.add(this.classNames.activeState);
this.element.setAttribute('aria-expanded', '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.setAttribute('aria-expanded', 'false');
this.isActive = false;
return this.parentInstance;
return this;
}
}

View file

@ -1,23 +1,20 @@
import { expect } from 'chai';
import sinon from 'sinon';
import Dropdown from './dropdown';
import { DEFAULT_CLASSNAMES, DEFAULT_CONFIG } from '../constants';
import { DEFAULT_CLASSNAMES } from '../constants';
describe('components/dropdown', () => {
let instance;
let choicesInstance;
let choicesElement;
beforeEach(() => {
choicesInstance = {
config: {
...DEFAULT_CONFIG,
},
};
choicesElement = document.createElement('div');
document.body.appendChild(choicesElement);
instance = new Dropdown(choicesInstance, choicesElement, DEFAULT_CLASSNAMES);
instance = new Dropdown({
element: choicesElement,
type: 'text',
classNames: DEFAULT_CLASSNAMES,
});
});
afterEach(() => {
@ -26,10 +23,6 @@ describe('components/dropdown', () => {
});
describe('constructor', () => {
it('assigns choices instance to instance', () => {
expect(instance.parentInstance).to.eql(choicesInstance);
});
it('assigns choices element to instance', () => {
expect(instance.element).to.eql(choicesElement);
});
@ -57,7 +50,9 @@ describe('components/dropdown', () => {
width: 0,
};
getBoundingClientRectStub = sinon.stub(instance.element, 'getBoundingClientRect').returns(dimensions);
getBoundingClientRectStub = sinon
.stub(instance.element, 'getBoundingClientRect')
.returns(dimensions);
window.pageYOffset = 50;
});
@ -114,7 +109,9 @@ describe('components/dropdown', () => {
});
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', () => {
@ -125,8 +122,8 @@ describe('components/dropdown', () => {
expect(instance.isActive).to.equal(true);
});
it('returns parent instance', () => {
expect(actualResponse).to.eql(choicesInstance);
it('returns instance', () => {
expect(actualResponse).to.eql(instance);
});
});
@ -142,7 +139,9 @@ describe('components/dropdown', () => {
});
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', () => {
@ -153,8 +152,8 @@ describe('components/dropdown', () => {
expect(instance.isActive).to.equal(false);
});
it('returns parent instance', () => {
expect(actualResponse).to.eql(choicesInstance);
it('returns instance', () => {
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';
export default class Input {
constructor(instance, element, classNames) {
this.parentInstance = instance;
constructor({ element, type, classNames, placeholderValue }) {
Object.assign(this, { element, type, classNames, placeholderValue });
this.element = element;
this.classNames = classNames;
this.isFocussed = this.element === document.activeElement;
this.isDisabled = false;
// Bind event listeners
this.onPaste = this.onPaste.bind(this);
this.onInput = this.onInput.bind(this);
this.onFocus = this.onFocus.bind(this);
this.onBlur = this.onBlur.bind(this);
this._onPaste = this._onPaste.bind(this);
this._onInput = this._onInput.bind(this);
this._onFocus = this._onFocus.bind(this);
this._onBlur = this._onBlur.bind(this);
}
set placeholder(placeholder) {
@ -28,55 +29,17 @@ export default class Input {
}
addEventListeners() {
this.element.addEventListener('input', this.onInput);
this.element.addEventListener('paste', this.onPaste);
this.element.addEventListener('focus', this.onFocus);
this.element.addEventListener('blur', this.onBlur);
this.element.addEventListener('input', this._onInput);
this.element.addEventListener('paste', this._onPaste);
this.element.addEventListener('focus', this._onFocus);
this.element.addEventListener('blur', this._onBlur);
}
removeEventListeners() {
this.element.removeEventListener('input', this.onInput);
this.element.removeEventListener('paste', this.onPaste);
this.element.removeEventListener('focus', this.onFocus);
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;
this.element.removeEventListener('input', this._onInput);
this.element.removeEventListener('paste', this._onPaste);
this.element.removeEventListener('focus', this._onFocus);
this.element.removeEventListener('blur', this._onBlur);
}
enable() {
@ -115,7 +78,7 @@ export default class Input {
this.setWidth();
}
return this.parentInstance;
return this;
}
/**
@ -124,12 +87,12 @@ export default class Input {
* @return
*/
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
// length than 75% of the placeholder. This stops the input jumping around.
if (
(this.element.value &&
this.element.value.length >= (this.parentInstance.placeholder.length / 1.25)) ||
this.element.value.length >= this._placeholderValue.length / 1.25) ||
enforceWidth
) {
this.element.style.width = this.calcWidth();
@ -151,4 +114,36 @@ export default class Input {
removeActiveDescendant() {
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 { stub } from 'sinon';
import Input from './input';
import { DEFAULT_CLASSNAMES, DEFAULT_CONFIG } from '../constants';
import { DEFAULT_CLASSNAMES } from '../constants';
describe('components/input', () => {
let instance;
let choicesInstance;
let choicesElement;
beforeEach(() => {
choicesInstance = {
config: {
...DEFAULT_CONFIG,
},
};
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(() => {
@ -24,10 +24,6 @@ describe('components/input', () => {
});
describe('constructor', () => {
it('assigns choices instance to class', () => {
expect(instance.parentInstance).to.eql(choicesInstance);
});
it('assigns choices element to class', () => {
expect(instance.element).to.eql(choicesElement);
});
@ -90,19 +86,18 @@ describe('components/input', () => {
setWidthStub.restore();
});
describe('when element is select one', () => {
it('does not set input width', () => {
instance.parentInstance.isSelectOneElement = true;
instance.onInput();
instance.type = 'select-one';
instance._onInput();
expect(setWidthStub.callCount).to.equal(0);
});
});
describe('when element is not a select one', () => {
it('sets input width', () => {
instance.parentInstance.isSelectOneElement = false;
instance.onInput();
instance.type = 'text';
instance._onInput();
expect(setWidthStub.callCount).to.equal(1);
});
});
@ -120,16 +115,16 @@ describe('components/input', () => {
describe('when pasting is disabled and target is the element', () => {
it('prevents default pasting behaviour', () => {
instance.parentInstance.config.paste = false;
instance.onPaste(eventMock);
instance.preventPaste = true;
instance._onPaste(eventMock);
expect(eventMock.preventDefault.callCount).to.equal(1);
});
});
describe('when pasting is enabled', () => {
it('does not prevent default pasting behaviour', () => {
instance.parentInstance.config.paste = true;
instance.onPaste(eventMock);
instance.preventPaste = false;
instance._onPaste(eventMock);
expect(eventMock.preventDefault.callCount).to.equal(0);
});
});
@ -138,7 +133,7 @@ describe('components/input', () => {
describe('onFocus', () => {
it('sets isFocussed flag to true', () => {
expect(instance.isFocussed).to.equal(false);
instance.onFocus();
instance._onFocus();
expect(instance.isFocussed).to.equal(true);
});
});
@ -146,7 +141,7 @@ describe('components/input', () => {
describe('onBlur', () => {
it('sets isFocussed flag to false', () => {
instance.isFocussed = true;
instance.onBlur();
instance._onBlur();
expect(instance.isFocussed).to.equal(false);
});
});
@ -211,7 +206,6 @@ describe('components/input', () => {
});
});
describe('blur', () => {
let blurStub;
@ -224,7 +218,7 @@ describe('components/input', () => {
});
describe('when element is not focussed', () => {
it('doesn\'t blur element', () => {
it("doesn't blur element", () => {
instance.isFocussed = false;
instance.blur();
expect(blurStub.callCount).to.equal(0);
@ -251,23 +245,22 @@ describe('components/input', () => {
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';
expect(instance.element.value).to.equal('test');
instance.clear();
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);
instance.clear(true);
expect(setWidthStub.callCount).to.equal(1);
});
it('returns parent instance', () => {
const actualResponse = instance.clear();
const expectedResponse = choicesInstance;
expect(actualResponse).to.eql(expectedResponse);
it('returns instance', () => {
const response = instance.clear();
expect(response).to.eql(instance);
});
});
@ -286,7 +279,7 @@ describe('components/input', () => {
describe('with a placeholder', () => {
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', () => {
instance.parentInstance.placeholder = 'This is a test';
instance._placeholderValue = 'This is a test';
instance.element.value = 'This is a test';
expect(instance.element.style.width).to.not.equal(inputWidth);
instance.setWidth();
@ -297,7 +290,7 @@ describe('components/input', () => {
describe('when width is enforced', () => {
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 = '';
expect(instance.element.style.width).to.not.equal(inputWidth);
instance.setWidth(true);
@ -308,7 +301,7 @@ describe('components/input', () => {
describe('when value length is less than 75% of the placeholder length', () => {
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.setWidth();
expect(calcWidthStub.callCount).to.equal(0);
@ -355,21 +348,32 @@ describe('components/input', () => {
});
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';
expect(instance.element.getAttribute('aria-activedescendant')).to.equal(null);
expect(instance.element.getAttribute('aria-activedescendant')).to.equal(
null,
);
instance.setActiveDescendant(activeDescendantID);
expect(instance.element.getAttribute('aria-activedescendant')).to.equal(activeDescendantID);
expect(instance.element.getAttribute('aria-activedescendant')).to.equal(
activeDescendantID,
);
});
});
describe('removeActiveDescendant', () => {
it('remove elememnt\'s aria-activedescendant attribute', () => {
it("remove elememnt's aria-activedescendant attribute", () => {
const activeDescendantID = '1234';
instance.element.setAttribute('aria-activedescendant', activeDescendantID);
expect(instance.element.getAttribute('aria-activedescendant')).to.equal(activeDescendantID);
instance.element.setAttribute(
'aria-activedescendant',
activeDescendantID,
);
expect(instance.element.getAttribute('aria-activedescendant')).to.equal(
activeDescendantID,
);
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 List from './list';
import { DEFAULT_CLASSNAMES, DEFAULT_CONFIG } from '../constants';
describe('components/list', () => {
let instance;
let choicesInstance;
let choicesElement;
beforeEach(() => {
choicesInstance = {
config: {
...DEFAULT_CONFIG,
},
};
choicesElement = document.createElement('div');
instance = new List(choicesInstance, choicesElement, DEFAULT_CLASSNAMES);
instance = new List({
element: choicesElement,
});
});
afterEach(() => {
@ -23,21 +18,13 @@ describe('components/list', () => {
});
describe('constructor', () => {
it('assigns choices instance to class', () => {
expect(instance.parentInstance).to.eql(choicesInstance);
});
it('assigns choices element to class', () => {
expect(instance.element).to.eql(choicesElement);
});
it('assigns classnames to class', () => {
expect(instance.classNames).to.eql(DEFAULT_CLASSNAMES);
});
});
describe('clear', () => {
it('clears element\'s inner HTML', () => {
it("clears element's inner HTML", () => {
const innerHTML = 'test';
instance.element.innerHTML = 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', () => {
it('appends passed node to element', () => {
const elementToAppend = document.createElement('span');
@ -72,11 +40,12 @@ describe('components/list', () => {
elementToAppend.classList.add(childClass);
expect(instance.element.querySelector(`.${childClass}`)).to.equal(null);
instance.append(elementToAppend);
expect(instance.element.querySelector(`.${childClass}`)).to.equal(elementToAppend);
expect(instance.element.querySelector(`.${childClass}`)).to.equal(
elementToAppend,
);
});
});
describe('getChild', () => {
let childElement;
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 {
constructor(instance, element, classNames) {
this.parentInstance = instance;
this.element = element;
this.classNames = classNames;
constructor({ element, classNames }) {
Object.assign(this, { element, classNames });
if (!isElement(element)) {
throw new TypeError('Invalid element passed');
}
this.isDisabled = false;
}

View file

@ -1,21 +1,17 @@
import { expect } from 'chai';
import WrappedElement from './wrapped-element';
import { DEFAULT_CLASSNAMES, DEFAULT_CONFIG } from '../constants';
import { DEFAULT_CLASSNAMES } from '../constants';
describe('components/wrappedElement', () => {
let instance;
let choicesInstance;
let element;
beforeEach(() => {
choicesInstance = {
config: {
...DEFAULT_CONFIG,
},
};
element = document.createElement('select');
instance = new WrappedElement(choicesInstance, element, DEFAULT_CLASSNAMES);
instance = new WrappedElement({
element,
classNames: DEFAULT_CLASSNAMES
});
});
afterEach(() => {
@ -24,10 +20,6 @@ describe('components/wrappedElement', () => {
});
describe('constructor', () => {
it('assigns choices instance to class', () => {
expect(instance.parentInstance).to.eql(choicesInstance);
});
it('assigns choices element to class', () => {
expect(instance.element).to.eql(element);
});
@ -58,11 +50,17 @@ describe('components/wrappedElement', () => {
it('hides element', () => {
instance.conceal();
expect(instance.element.tabIndex).to.equal(-1);
expect(instance.element.classList.contains(instance.classNames.input)).to.equal(true);
expect(instance.element.classList.contains(instance.classNames.hiddenState)).to.equal(true);
expect(
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('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', () => {
instance.reveal();
expect(instance.element.tabIndex).to.equal(0);
expect(instance.element.classList.contains(instance.classNames.input)).to.equal(false);
expect(instance.element.classList.contains(instance.classNames.hiddenState)).to.equal(false);
expect(
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('aria-hidden')).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', () => {
it('fires event on element using passed eventType and data', (done) => {
it('fires event on element using passed eventType and data', done => {
const data = {
test: true,
test: true
};
instance.element.addEventListener('testEvent', ({ detail }) => {

View file

@ -2,16 +2,14 @@ import WrappedElement from './wrapped-element';
import { reduceToValues } from './../lib/utils';
export default class WrappedInput extends WrappedElement {
constructor(instance, element, classNames) {
super(instance, element, classNames);
this.parentInstance = instance;
this.element = element;
this.classNames = classNames;
constructor({ element, classNames, delimiter }) {
super({ element, classNames });
this.delimiter = delimiter;
}
set value(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.value = itemsFilteredString;

View file

@ -2,21 +2,20 @@ import { expect } from 'chai';
import { stub } from 'sinon';
import WrappedElement from './wrapped-element';
import WrappedInput from './wrapped-input';
import { DEFAULT_CLASSNAMES, DEFAULT_CONFIG } from '../constants';
import { DEFAULT_CLASSNAMES } from '../constants';
describe('components/wrappedInput', () => {
let instance;
let choicesInstance;
let element;
const delimiter = '-';
beforeEach(() => {
choicesInstance = {
config: {
...DEFAULT_CONFIG,
},
};
element = document.createElement('input');
instance = new WrappedInput(choicesInstance, element, DEFAULT_CLASSNAMES);
instance = new WrappedInput({
element,
classNames: DEFAULT_CLASSNAMES,
delimiter,
});
});
afterEach(() => {
@ -25,10 +24,6 @@ describe('components/wrappedInput', () => {
});
describe('constructor', () => {
it('assigns choices instance to class', () => {
expect(instance.parentInstance).to.eql(choicesInstance);
});
it('assigns choices element to class', () => {
expect(instance.element).to.eql(element);
});
@ -39,7 +34,7 @@ describe('components/wrappedInput', () => {
});
describe('inherited methods', () => {
['conceal', 'reveal', 'enable', 'disable'].forEach((method) => {
['conceal', 'reveal', 'enable', 'disable'].forEach(method => {
describe(method, () => {
beforeEach(() => {
stub(WrappedElement.prototype, method);
@ -77,7 +72,9 @@ describe('components/wrappedInput', () => {
it('sets delimited value of element based on passed data', () => {
expect(instance.element.value).to.equal('');
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';
export default class WrappedSelect extends WrappedElement {
constructor(instance, element, classNames) {
super(instance, element, classNames);
this.parentInstance = instance;
this.element = element;
this.classNames = classNames;
constructor({ element, classNames }) {
super({ element, classNames });
}
get placeholderOption() {
@ -23,7 +20,7 @@ export default class WrappedSelect extends WrappedElement {
set options(options) {
const fragment = document.createDocumentFragment();
const addOptionToFragment = (data) => {
const addOptionToFragment = data => {
// Create a standard select option
const template = templates.option(data);
// Append it to fragment

View file

@ -2,20 +2,13 @@ import { expect } from 'chai';
import { stub } from 'sinon';
import WrappedElement from './wrapped-element';
import WrappedSelect from './wrapped-select';
import { DEFAULT_CLASSNAMES, DEFAULT_CONFIG } from '../constants';
import { DEFAULT_CLASSNAMES } from '../constants';
describe('components/wrappedSelect', () => {
let instance;
let choicesInstance;
let element;
beforeEach(() => {
choicesInstance = {
config: {
...DEFAULT_CONFIG,
},
};
element = document.createElement('select');
element.id = 'target';
for (let i = 1; i <= 4; i++) {
@ -32,7 +25,10 @@ describe('components/wrappedSelect', () => {
}
document.body.appendChild(element);
instance = new WrappedSelect(choicesInstance, document.getElementById('target'), DEFAULT_CLASSNAMES);
instance = new WrappedSelect({
element: document.getElementById('target'),
classNames: DEFAULT_CLASSNAMES,
});
});
afterEach(() => {
@ -41,10 +37,6 @@ describe('components/wrappedSelect', () => {
});
describe('constructor', () => {
it('assigns choices instance to class', () => {
expect(instance.parentInstance).to.eql(choicesInstance);
});
it('assigns choices element to class', () => {
expect(instance.element).to.eql(element);
});
@ -55,7 +47,7 @@ describe('components/wrappedSelect', () => {
});
describe('inherited methods', () => {
['conceal', 'reveal', 'enable', 'disable'].forEach((method) => {
['conceal', 'reveal', 'enable', 'disable'].forEach(method => {
beforeEach(() => {
stub(WrappedElement.prototype, method);
});
@ -84,7 +76,7 @@ describe('components/wrappedSelect', () => {
it('returns all option elements', () => {
const { options } = instance;
expect(options).to.be.an('array');
options.forEach((option) => {
options.forEach(option => {
expect(option).to.be.instanceOf(HTMLOptionElement);
});
});
@ -99,7 +91,7 @@ describe('components/wrappedSelect', () => {
const { optionGroups } = instance;
expect(optionGroups.length).to.equal(3);
optionGroups.forEach((option) => {
optionGroups.forEach(option => {
expect(option).to.be.instanceOf(HTMLOptGroupElement);
});
});
@ -149,9 +141,13 @@ describe('components/wrappedSelect', () => {
describe('appendDocFragment', () => {
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());
expect(instance.element.getElementsByTagName('option').length).to.equal(0);
expect(instance.element.getElementsByTagName('option').length).to.equal(
0,
);
});
it('appends passed fragment to element', () => {
@ -161,7 +157,9 @@ describe('components/wrappedSelect', () => {
fragment.appendChild(elementToAppend);
expect(instance.element.querySelector('#fragment-target')).to.equal(null);
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.',
addItemText: value => `Press Enter to add <b>"${stripHTML(value)}"</b>`,
maxItemText: maxItemCount => `Only ${maxItemCount} values can be added.`,
itemComparer: (choice, item) => (choice === item),
itemComparer: (choice, item) => choice === item,
fuseOptions: {
includeScore: true,
},

View file

@ -140,7 +140,7 @@ describe('constants', () => {
});
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');
});
});

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 */
/**
* 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
* @param {Number} length Length of the string to generate
@ -39,7 +30,6 @@ export const generateId = function(element, prefix) {
return id;
};
/**
* Tests the type of an object
* @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;
};
/**
* 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
* @param {Object} obj Object to be tested
* @return {Boolean}
*/
export const isElement = o => (
typeof HTMLElement === 'object' ? o instanceof HTMLElement : // DOM2
o && typeof o === 'object' && o !== null && o.nodeType === 1 && typeof o.nodeName === 'string'
);
typeof HTMLElement === 'object' ? o instanceof HTMLElement : // DOM2
o && typeof o === 'object' && o !== null && o.nodeType === 1 && typeof o.nodeName === 'string'
);
/**
* Merges unspecified amount of objects into new object
@ -121,132 +101,6 @@ export const extend = function() {
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) {
wrapper = wrapper || document.createElement('div');
if (element.nextSibling) {
@ -257,17 +111,6 @@ export const wrap = function(element, wrapper) {
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
* @param {NodeElement} el Element to start search from
@ -299,60 +142,6 @@ export const findAncestorByAttrName = function(el, attr) {
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
* @param {HTMLElement} startEl Element to start position from
@ -372,31 +161,6 @@ export const getAdjacentEl = (startEl, className, direction = 1) => {
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
* @param {HTMLElement} el Element to test
@ -431,25 +195,6 @@ export const stripHTML = html =>
.replace(/</g, '&lt;')
.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
* @param {Number} min Minimum range
@ -617,4 +362,18 @@ export const fetchFromObject = function (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', () => {
const expectedResponse = [
items[0].value,
items[1].value,
items[2].value,
];
const expectedResponse = [items[0].value, items[1].value, items[2].value];
const actualResponse = reduceToValues(items);
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)
An active choice appears within the choice dropdown
*/
return [...state, {
id: action.id,
elementId: action.elementId,
groupId: action.groupId,
value: action.value,
label: (action.label || action.value),
disabled: (action.disabled || false),
selected: false,
active: true,
score: 9999,
customProperties: action.customProperties,
placeholder: (action.placeholder || false),
keyCode: null,
}];
return [
...state,
{
id: action.id,
elementId: action.elementId,
groupId: action.groupId,
value: action.value,
label: action.label || action.value,
disabled: action.disabled || false,
selected: false,
active: true,
score: 9999,
customProperties: action.customProperties,
placeholder: action.placeholder || false,
keyCode: null,
},
];
}
case 'ADD_ITEM': {
// If all choices need to be activated
if (action.activateOptions) {
return state.map((obj) => {
return state.map(obj => {
const choice = obj;
choice.active = action.active;
return choice;
@ -37,7 +40,7 @@ export default function choices(state = defaultState, action) {
// When an item is added and it has an associated choice,
// we want to disable it so it can't be chosen again
if (action.choiceId > -1) {
return state.map((obj) => {
return state.map(obj => {
const choice = obj;
if (choice.id === parseInt(action.choiceId, 10)) {
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,
// we want to re-enable it so it can be chosen again
if (action.choiceId > -1) {
return state.map((obj) => {
return state.map(obj => {
const choice = obj;
if (choice.id === parseInt(action.choiceId, 10)) {
choice.selected = false;
@ -66,7 +69,7 @@ export default function choices(state = defaultState, action) {
}
case 'FILTER_CHOICES': {
return state.map((obj) => {
return state.map(obj => {
const choice = obj;
// Set active state based on whether choice is
// within filtered results
@ -83,7 +86,7 @@ export default function choices(state = defaultState, action) {
}
case 'ACTIVATE_CHOICES': {
return state.map((obj) => {
return state.map(obj => {
const choice = obj;
choice.active = action.active;
return choice;

View file

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

View file

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

View file

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

View file

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

View file

@ -10,9 +10,9 @@ describe('reducers/store', () => {
beforeEach(() => {
instance = new Store();
subscribeStub = sinon.stub(instance.store, 'subscribe');
dispatchStub = sinon.stub(instance.store, 'dispatch');
getStateStub = sinon.stub(instance.store, 'getState');
subscribeStub = sinon.stub(instance._store, 'subscribe');
dispatchStub = sinon.stub(instance._store, 'dispatch');
getStateStub = sinon.stub(instance._store, 'getState');
});
afterEach(() => {
@ -21,10 +21,9 @@ describe('reducers/store', () => {
getStateStub.restore();
});
describe('constructor', () => {
it('creates redux store', () => {
expect(instance.store).to.contain.keys([
expect(instance._store).to.contain.keys([
'subscribe',
'dispatch',
'getState',
@ -32,7 +31,6 @@ describe('reducers/store', () => {
});
});
describe('subscribe', () => {
it('wraps redux subscribe method', () => {
const onChange = () => {};
@ -163,14 +161,16 @@ describe('reducers/store', () => {
describe('activeItems getter', () => {
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);
});
});
describe('highlightedActiveItems getter', () => {
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);
});
});
@ -184,21 +184,25 @@ describe('reducers/store', () => {
describe('activeChoices getter', () => {
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);
});
});
describe('selectableChoices getter', () => {
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);
});
});
describe('searchableChoices getter', () => {
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);
});
});
@ -207,7 +211,9 @@ describe('reducers/store', () => {
describe('passing id', () => {
it('returns active choice by passed id', () => {
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);
expect(actualResponse).to.eql(expectedResponse);
});
@ -223,7 +229,9 @@ describe('reducers/store', () => {
describe('placeholderChoice getter', () => {
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);
});
});
@ -245,7 +253,9 @@ describe('reducers/store', () => {
describe('getGroupById', () => {
it('returns group by id', () => {
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);
expect(actualResponse).to.eql(expectedResponse);
});

View file

@ -39,13 +39,10 @@ export const TEMPLATES = {
`);
},
itemList(globalClasses, isSelectOneElement) {
const localClasses = classNames(
globalClasses.list,
{
[globalClasses.listSingle]: (isSelectOneElement),
[globalClasses.listItems]: (!isSelectOneElement),
},
);
const localClasses = classNames(globalClasses.list, {
[globalClasses.listSingle]: isSelectOneElement,
[globalClasses.listItems]: !isSelectOneElement,
});
return strToEl(`
<div class="${localClasses}"></div>
@ -62,22 +59,18 @@ export const TEMPLATES = {
const ariaSelected = data.active ? 'aria-selected="true"' : '';
const ariaDisabled = data.disabled ? 'aria-disabled="true"' : '';
let localClasses = classNames(
globalClasses.item, {
[globalClasses.highlightedState]: data.highlighted,
[globalClasses.itemSelectable]: !data.highlighted,
[globalClasses.placeholder]: data.placeholder,
},
);
let localClasses = classNames(globalClasses.item, {
[globalClasses.highlightedState]: data.highlighted,
[globalClasses.itemSelectable]: !data.highlighted,
[globalClasses.placeholder]: data.placeholder,
});
if (removeItemButton) {
localClasses = classNames(
globalClasses.item, {
[globalClasses.highlightedState]: data.highlighted,
[globalClasses.itemSelectable]: !data.disabled,
[globalClasses.placeholder]: data.placeholder,
},
);
localClasses = classNames(globalClasses.item, {
[globalClasses.highlightedState]: data.highlighted,
[globalClasses.itemSelectable]: !data.disabled,
[globalClasses.placeholder]: data.placeholder,
});
return strToEl(`
<div
@ -116,9 +109,9 @@ export const TEMPLATES = {
`);
},
choiceList(globalClasses, isSelectOneElement) {
const ariaMultiSelectable = !isSelectOneElement ?
'aria-multiselectable="true"' :
'';
const ariaMultiSelectable = !isSelectOneElement
? 'aria-multiselectable="true"'
: '';
return strToEl(`
<div
@ -132,11 +125,9 @@ export const TEMPLATES = {
},
choiceGroup(globalClasses, data) {
const ariaDisabled = data.disabled ? 'aria-disabled="true"' : '';
const localClasses = classNames(
globalClasses.group, {
[globalClasses.itemDisabled]: data.disabled,
},
);
const localClasses = classNames(globalClasses.group, {
[globalClasses.itemDisabled]: data.disabled,
});
return strToEl(`
<div
@ -155,7 +146,8 @@ export const TEMPLATES = {
const role = data.groupId > 0 ? 'role="treeitem"' : 'role="option"';
const localClasses = classNames(
globalClasses.item,
globalClasses.itemChoice, {
globalClasses.itemChoice,
{
[globalClasses.itemDisabled]: data.disabled,
[globalClasses.itemSelectable]: !data.disabled,
[globalClasses.placeholder]: data.placeholder,
@ -169,9 +161,10 @@ export const TEMPLATES = {
data-choice
data-id="${data.id}"
data-value="${data.value}"
${data.disabled ?
'data-choice-disabled aria-disabled="true"' :
'data-choice-selectable'
${
data.disabled
? 'data-choice-disabled aria-disabled="true"'
: 'data-choice-selectable'
}
id="${data.elementId}"
${role}
@ -217,8 +210,8 @@ export const TEMPLATES = {
globalClasses.item,
globalClasses.itemChoice,
{
[globalClasses.noResults]: (type === 'no-results'),
[globalClasses.noChoices]: (type === 'no-choices'),
[globalClasses.noResults]: type === 'no-results',
[globalClasses.noChoices]: type === 'no-choices',
},
);
@ -230,7 +223,9 @@ export const TEMPLATES = {
},
option(data) {
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 { 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('containerOuter', () => {
@ -41,7 +42,9 @@ describe('templates', () => {
);
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(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(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(stripElement(actualOutput)).to.equal(stripElement(expectedOutput));
expect(stripElement(actualOutput)).to.equal(
stripElement(expectedOutput),
);
});
});
});
@ -148,7 +157,9 @@ describe('templates', () => {
const classes = {
containerInner: 'test',
};
const expectedOutput = strToEl(`<div class="${classes.containerInner}"></div>`);
const expectedOutput = strToEl(
`<div class="${classes.containerInner}"></div>`,
);
const actualOutput = templates.containerInner(classes);
expect(getType(actualOutput)).to.equal('HTMLDivElement');
@ -165,21 +176,29 @@ describe('templates', () => {
describe('select one element', () => {
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);
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', () => {
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);
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 expectedOutput = strToEl(`
<div class="${classes.placeholder}">${value}</div>`,
);
<div class="${classes.placeholder}">${value}</div>`);
const actualOutput = templates.placeholder(classes, value);
expect(getType(actualOutput)).to.equal('HTMLDivElement');
@ -222,7 +240,9 @@ describe('templates', () => {
const actualOutput = templates.choiceList(classes, true);
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);
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);
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);
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', () => {
const expectedOutput = strToEl(`
<div
class="${classes.item} ${classes.itemChoice} ${classes.itemSelectable}"
class="${classes.item} ${classes.itemChoice} ${
classes.itemSelectable
}"
data-select-text="${itemSelectText}"
data-choice
data-id="${data.id}"
@ -354,7 +382,9 @@ describe('templates', () => {
const actualOutput = templates.choice(classes, data, itemSelectText);
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', () => {
const expectedOutput = strToEl(`
<div
class="${classes.item} ${classes.itemChoice} ${classes.itemDisabled}"
class="${classes.item} ${classes.itemChoice} ${
classes.itemDisabled
}"
data-select-text="${itemSelectText}"
data-choice
data-id="${data.id}"
@ -385,7 +417,9 @@ describe('templates', () => {
const actualOutput = templates.choice(classes, data, itemSelectText);
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', () => {
const expectedOutput = strToEl(`
<div
class="${classes.item} ${classes.itemChoice} ${classes.itemSelectable} ${classes.placeholder}"
class="${classes.item} ${classes.itemChoice} ${
classes.itemSelectable
} ${classes.placeholder}"
data-select-text="${itemSelectText}"
data-choice
data-id="${data.id}"
@ -415,7 +451,9 @@ describe('templates', () => {
const actualOutput = templates.choice(classes, data, itemSelectText);
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(() => {
data = {
...data,
groupId: 1
groupId: 1,
};
});
it('returns expected html', () => {
const expectedOutput = strToEl(`
<div
class="${classes.item} ${classes.itemChoice} ${classes.itemSelectable}"
class="${classes.item} ${classes.itemChoice} ${
classes.itemSelectable
}"
data-select-text="${itemSelectText}"
data-choice
data-id="${data.id}"
@ -445,7 +485,9 @@ describe('templates', () => {
const actualOutput = templates.choice(classes, data, itemSelectText);
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 = {
list: 'test 1',
listDropdown: 'test 2',
}
;
};
it('returns expected html', () => {
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);
expect(getType(actualOutput)).to.equal('HTMLDivElement');
@ -517,26 +562,34 @@ describe('templates', () => {
describe('no results', () => {
it('adds no results classname', () => {
const expectedOutput = strToEl(`
<div class="${classes.item} ${classes.itemChoice} ${classes.noResults}">
<div class="${classes.item} ${classes.itemChoice} ${
classes.noResults
}">
${label}
</div>
`);
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', () => {
it('adds no choices classname', () => {
const expectedOutput = strToEl(`
<div class="${classes.item} ${classes.itemChoice} ${classes.noChoices}">
<div class="${classes.item} ${classes.itemChoice} ${
classes.noChoices
}">
${label}
</div>
`);
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', () => {
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);
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 =
=============================================*/
$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;
-moz-osx-font-smoothing: grayscale
}
*, *:before, *:after {
*,
*:before,
*:after {
box-sizing: border-box
}
html, body {
html,
body {
position: relative;
margin: 0;
width: 100%;
@ -45,7 +46,9 @@ label {
cursor: pointer;
}
p { margin-top: 0; }
p {
margin-top: 0;
}
hr {
display: block;
@ -55,14 +58,21 @@ hr {
height: 1px;
}
h1, h2, h3, h4, h5, h6 {
h1,
h2,
h3,
h4,
h5,
h6 {
margin-top: 0;
margin-bottom: $global-guttering/2;
font-weight: 400;
line-height: 1.2;
}
a, a:visited, a:focus {
a,
a:visited,
a:focus {
color: #FFFFFF;
text-decoration: none;
font-weight: 600;
@ -81,26 +91,55 @@ a, a:visited, a:focus {
margin-bottom: $global-guttering;
}
h1, .h1 { font-size: $global-font-size-h1; }
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; }
h1,
.h1 {
font-size: $global-font-size-h1;
}
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 {
display: block;
margin: auto;
max-width: 40em;
padding: $global-guttering*2;
@media (max-width: 620px) { padding: 0; }
@media (max-width: 620px) {
padding: 0;
}
}
.section {
background-color: #FFFFFF;
padding: $global-guttering;
color: #333;
a, a:visited, a:focus { color: #00bcd4; }
a,
a:visited,
a:focus {
color: #00bcd4;
}
}
.logo {
@ -117,10 +156,24 @@ h6, .h6 { font-size: $global-font-size-h6; }
padding: $global-guttering/4 0;
}
.visible-ie { display: none; }
.zero-bottom { margin-bottom: 0; }
.zero-top { margin-top: 0; }
.text-center { text-align: center; }
.is-hidden { display: none; }
.visible-ie {
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;
margin-bottom: $choices-guttering;
font-size: $choices-font-size-lg;
&:focus { outline: none; }
&:last-child { margin-bottom: 0; }
&:focus {
outline: none;
}
&:last-child {
margin-bottom: 0;
}
&.is-disabled {
.#{$choices-selector}__inner, .#{$choices-selector}__input {
.#{$choices-selector}__inner,
.#{$choices-selector}__input {
background-color: $choices-bg-color-disabled;
cursor: not-allowed;
user-select: none;
}
.#{$choices-selector}__item { cursor: not-allowed; }
.#{$choices-selector}__item {
cursor: not-allowed;
}
}
}
.#{$choices-selector}[data-type*="select-one"] {
cursor: pointer;
.#{$choices-selector}__inner { padding-bottom: 7.5px; }
.#{$choices-selector}__inner {
padding-bottom: 7.5px;
}
.#{$choices-selector}__input {
display: block;
width: 100%;
@ -62,8 +71,13 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
width: 20px;
border-radius: 10em;
opacity: .5;
&:hover, &:focus { opacity: 1; }
&:focus { box-shadow: 0px 0px 0px 2px $choices-highlight-color; }
&:hover,
&:focus {
opacity: 1;
}
&:focus {
box-shadow: 0px 0px 0px 2px $choices-highlight-color;
}
}
&:after {
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}__inner { cursor: text; }
.#{$choices-selector}[data-type*="select-multiple"],
.#{$choices-selector}[data-type*="text"] {
.#{$choices-selector}__inner {
cursor: text;
}
.#{$choices-selector}__button {
position: relative;
display: inline-block;
@ -113,7 +130,10 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
line-height: 1;
opacity: .75;
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;
min-height: 44px;
overflow: hidden;
.is-focused &, .is-open & { 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; }
.is-focused &,
.is-open & {
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 {
@ -147,7 +174,9 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
padding-right: 4px;
padding-left: 16px;
}
.#{$choices-selector}__item { width: 100%; }
.#{$choices-selector}__item {
width: 100%;
}
}
.#{$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%);
color: #FFFFFF;
word-break: break-all;
&[data-deletable] { padding-right: 5px; }
&[data-deletable] {
padding-right: 5px;
}
[dir="rtl"] & {
margin-right: 0;
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;
overflow: hidden;
word-break: break-all;
&.is-active { display: block; }
.is-open & { border-color: darken($choices-keyline-color, 15%); }
&.is-active {
display: block;
}
.is-open & {
border-color: darken($choices-keyline-color, 15%);
}
.is-flipped & {
top: auto;
bottom: 100%;
@ -214,7 +249,9 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
position: relative;
padding: 10px;
font-size: $choices-font-size-md;
[dir="rtl"] & { text-align: right; }
[dir="rtl"] & {
text-align: right;
}
}
.#{$choices-selector}__item--selectable {
@media (min-width: 640px) {
@ -240,13 +277,21 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
}
&.is-highlighted {
background-color: mix(#000000, #FFFFFF, 5%);
&:after { opacity: .5; }
&:after {
opacity: .5;
}
}
}
}
.#{$choices-selector}__item { cursor: default; }
.#{$choices-selector}__item--selectable { cursor: pointer; }
.#{$choices-selector}__item {
cursor: default;
}
.#{$choices-selector}__item--selectable {
cursor: pointer;
}
.#{$choices-selector}__item--disabled {
cursor: not-allowed;
user-select: none;
@ -270,7 +315,9 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
background-repeat: no-repeat;
background-position: center;
cursor: pointer;
&:focus { outline: none; }
&:focus {
outline: none;
}
}
.#{$choices-selector}__input {
@ -283,14 +330,18 @@ $choices-icon-cross-inverse: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiI
border-radius: 0;
max-width: 100%;
padding: 4px 0 4px 2px;
&:focus { outline: 0; }
&:focus {
outline: 0;
}
[dir="rtl"] & {
padding-right: 2px;
padding-left: 0;
}
}
.#{$choices-selector}__placeholder { opacity: .5; }
.#{$choices-selector}__placeholder {
opacity: .5;
}
.#{$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;
}
/*===== 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;
/** 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 */
private render(): void;

View file

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

View file

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