Code refactoring (#735)

* Add placeholder options to demo page

* Use constant types in components

* Refactor adding predefined groups/items/choices

* Add 'highlighted' flag to Item type

* Fix dispatch param type

* Build

* Add jsdoc comments to utils

* Remove unused file

* Add default values to js doc comments

* Use Redux Action type

* Housekeeping

* Increase utils coverage

* Apply suggestions from code review

* Add _getTemplate unit tests
This commit is contained in:
Josh Johnson 2019-11-03 17:45:16 +00:00 committed by GitHub
parent e6882f3e4b
commit ab22347d7b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1198 additions and 807 deletions

66
package-lock.json generated
View file

@ -1944,7 +1944,7 @@
}, },
"browserify-aes": { "browserify-aes": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
"integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
"dev": true, "dev": true,
"requires": { "requires": {
@ -1981,7 +1981,7 @@
}, },
"browserify-rsa": { "browserify-rsa": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
"integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
"dev": true, "dev": true,
"requires": { "requires": {
@ -2026,7 +2026,7 @@
}, },
"buffer": { "buffer": {
"version": "4.9.1", "version": "4.9.1",
"resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
"integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
"dev": true, "dev": true,
"requires": { "requires": {
@ -2213,7 +2213,7 @@
}, },
"camelcase-keys": { "camelcase-keys": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
"integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
"dev": true, "dev": true,
"requires": { "requires": {
@ -2807,7 +2807,7 @@
}, },
"create-hash": { "create-hash": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
"integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
"dev": true, "dev": true,
"requires": { "requires": {
@ -2820,7 +2820,7 @@
}, },
"create-hmac": { "create-hmac": {
"version": "1.1.7", "version": "1.1.7",
"resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
"integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
"dev": true, "dev": true,
"requires": { "requires": {
@ -3350,7 +3350,7 @@
}, },
"diffie-hellman": { "diffie-hellman": {
"version": "5.0.3", "version": "5.0.3",
"resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
"integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
"dev": true, "dev": true,
"requires": { "requires": {
@ -3420,7 +3420,7 @@
}, },
"duplexer": { "duplexer": {
"version": "0.1.1", "version": "0.1.1",
"resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
"integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=",
"dev": true "dev": true
}, },
@ -4092,7 +4092,7 @@
"dependencies": { "dependencies": {
"pify": { "pify": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true "dev": true
} }
@ -5873,7 +5873,7 @@
}, },
"get-stream": { "get-stream": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
"dev": true "dev": true
}, },
@ -6038,7 +6038,7 @@
}, },
"got": { "got": {
"version": "6.7.1", "version": "6.7.1",
"resolved": "http://registry.npmjs.org/got/-/got-6.7.1.tgz", "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz",
"integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=",
"dev": true, "dev": true,
"requires": { "requires": {
@ -6903,7 +6903,7 @@
}, },
"is-obj": { "is-obj": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
"integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
"dev": true "dev": true
}, },
@ -7866,7 +7866,7 @@
}, },
"load-json-file": { "load-json-file": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
"integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
"dev": true, "dev": true,
"requires": { "requires": {
@ -7887,7 +7887,7 @@
}, },
"pify": { "pify": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true "dev": true
} }
@ -8145,7 +8145,7 @@
}, },
"media-typer": { "media-typer": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
"dev": true "dev": true
}, },
@ -8186,7 +8186,7 @@
}, },
"meow": { "meow": {
"version": "3.7.0", "version": "3.7.0",
"resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz", "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
"integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
"dev": true, "dev": true,
"requires": { "requires": {
@ -8214,7 +8214,7 @@
}, },
"load-json-file": { "load-json-file": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"dev": true, "dev": true,
"requires": { "requires": {
@ -8256,7 +8256,7 @@
}, },
"pify": { "pify": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true "dev": true
}, },
@ -8454,7 +8454,7 @@
}, },
"mkdirp": { "mkdirp": {
"version": "0.5.1", "version": "0.5.1",
"resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true, "dev": true,
"requires": { "requires": {
@ -8724,7 +8724,7 @@
"dependencies": { "dependencies": {
"semver": { "semver": {
"version": "5.3.0", "version": "5.3.0",
"resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
"dev": true "dev": true
} }
@ -9603,7 +9603,7 @@
}, },
"onetime": { "onetime": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
"dev": true "dev": true
}, },
@ -9653,7 +9653,7 @@
}, },
"ora": { "ora": {
"version": "0.2.3", "version": "0.2.3",
"resolved": "http://registry.npmjs.org/ora/-/ora-0.2.3.tgz", "resolved": "https://registry.npmjs.org/ora/-/ora-0.2.3.tgz",
"integrity": "sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=", "integrity": "sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=",
"dev": true, "dev": true,
"requires": { "requires": {
@ -9704,7 +9704,7 @@
}, },
"os-locale": { "os-locale": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
"integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
"dev": true, "dev": true,
"requires": { "requires": {
@ -9940,7 +9940,7 @@
"dependencies": { "dependencies": {
"pify": { "pify": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true "dev": true
} }
@ -11770,7 +11770,7 @@
}, },
"safe-regex": { "safe-regex": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
"integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
"dev": true, "dev": true,
"requires": { "requires": {
@ -12134,7 +12134,7 @@
}, },
"sha.js": { "sha.js": {
"version": "2.4.11", "version": "2.4.11",
"resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
"integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
"dev": true, "dev": true,
"requires": { "requires": {
@ -12201,6 +12201,12 @@
"supports-color": "^5.5.0" "supports-color": "^5.5.0"
} }
}, },
"sinon-chai": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.3.0.tgz",
"integrity": "sha512-r2JhDY7gbbmh5z3Q62pNbrjxZdOAjpsqW/8yxAZRSqLZqowmfGZPGUZPFf3UX36NLis0cv8VEM5IJh9HgkSOAA==",
"dev": true
},
"slash": { "slash": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@ -12209,7 +12215,7 @@
}, },
"slice-ansi": { "slice-ansi": {
"version": "0.0.4", "version": "0.0.4",
"resolved": "http://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz",
"integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=",
"dev": true "dev": true
}, },
@ -12629,7 +12635,7 @@
}, },
"strip-ansi": { "strip-ansi": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true, "dev": true,
"requires": { "requires": {
@ -12644,7 +12650,7 @@
}, },
"strip-eof": { "strip-eof": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
"dev": true "dev": true
}, },
@ -12977,7 +12983,7 @@
}, },
"through": { "through": {
"version": "2.3.8", "version": "2.3.8",
"resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true "dev": true
}, },

View file

@ -84,6 +84,7 @@
"postcss-cli": "^6.1.3", "postcss-cli": "^6.1.3",
"prettier": "^1.18.2", "prettier": "^1.18.2",
"sinon": "^7.5.0", "sinon": "^7.5.0",
"sinon-chai": "^3.3.0",
"webpack": "^4.41.2", "webpack": "^4.41.2",
"webpack-cli": "^3.3.9", "webpack-cli": "^3.3.9",
"webpack-dev-middleware": "^3.7.2", "webpack-dev-middleware": "^3.7.2",

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -214,6 +214,7 @@
placeholder="This is a placeholder" placeholder="This is a placeholder"
multiple multiple
> >
<option value="">Choose a city</option>
<optgroup label="UK"> <optgroup label="UK">
<option value="London">London</option> <option value="London">London</option>
<option value="Manchester">Manchester</option> <option value="Manchester">Manchester</option>
@ -336,8 +337,8 @@
data-trigger data-trigger
name="choices-single-groups" name="choices-single-groups"
id="choices-single-groups" id="choices-single-groups"
placeholder="This is a placeholder"
> >
<option value="">Choose a city</option>
<optgroup label="UK"> <optgroup label="UK">
<option value="London">London</option> <option value="London">London</option>
<option value="Manchester">Manchester</option> <option value="Manchester">Manchester</option>
@ -376,7 +377,6 @@
data-trigger data-trigger
name="choices-single-rtl" name="choices-single-rtl"
id="choices-single-rtl" id="choices-single-rtl"
placeholder="This is a placeholder"
dir="rtl" dir="rtl"
> >
<option value="Choice 1">Choice 1</option> <option value="Choice 1">Choice 1</option>
@ -402,7 +402,6 @@
class="form-control" class="form-control"
name="choices-single-preset-options" name="choices-single-preset-options"
id="choices-single-preset-options" id="choices-single-preset-options"
placeholder="This is a placeholder"
></select> ></select>
<label for="choices-single-selected-option" <label for="choices-single-selected-option"
@ -415,7 +414,6 @@
class="form-control" class="form-control"
name="choices-single-selected-option" name="choices-single-selected-option"
id="choices-single-selected-option" id="choices-single-selected-option"
placeholder="This is a placeholder"
></select> ></select>
<label for="choices-with-custom-props-via-html" <label for="choices-with-custom-props-via-html"
@ -440,7 +438,6 @@
class="form-control" class="form-control"
name="choices-single-no-sorting" name="choices-single-no-sorting"
id="choices-single-no-sorting" id="choices-single-no-sorting"
placeholder="This is a placeholder"
> >
<option value="Madrid">Madrid</option> <option value="Madrid">Madrid</option>
<option value="Toronto">Toronto</option> <option value="Toronto">Toronto</option>
@ -467,7 +464,6 @@
class="form-control" class="form-control"
name="choices-single-custom-templates" name="choices-single-custom-templates"
id="choices-single-custom-templates" id="choices-single-custom-templates"
placeholder="This is a placeholder"
> >
<option value="React">React</option> <option value="React">React</option>
<option value="Angular">Angular</option> <option value="Angular">Angular</option>
@ -481,12 +477,8 @@
'Cities' is 'London' 'Cities' is 'London'
</p> </p>
<label for="cities">Cities</label> <label for="cities">Cities</label>
<select <select class="form-control" name="cities" id="cities">
class="form-control" <option value="">Choose a city</option>
name="cities"
id="cities"
placeholder="Choose a city"
>
<option value="Leeds">Leeds</option> <option value="Leeds">Leeds</option>
<option value="Manchester">Manchester</option> <option value="Manchester">Manchester</option>
<option value="London">London</option> <option value="London">London</option>
@ -495,12 +487,8 @@
</select> </select>
<label for="tube-stations">Tube stations</label> <label for="tube-stations">Tube stations</label>
<select <select class="form-control" name="tube-stations" id="tube-stations">
class="form-control" <option value="">Choose a tube station</option>
name="tube-stations"
id="tube-stations"
placeholder="Choose a tube station"
>
<option value="Moorgate">Moorgate</option> <option value="Moorgate">Moorgate</option>
<option value="St Pauls">St Pauls</option> <option value="St Pauls">St Pauls</option>
<option value="Old Street">Old Street</option> <option value="Old Street">Old Street</option>
@ -515,12 +503,7 @@
<p>Change the values and press reset to restore to initial state.</p> <p>Change the values and press reset to restore to initial state.</p>
<form> <form>
<label for="reset-simple">Change me!</label> <label for="reset-simple">Change me!</label>
<select <select class="form-control" name="reset-simple" id="reset-simple">
class="form-control"
name="reset-simple"
id="reset-simple"
placeholder="Choose an option"
>
<option value="Option 1">Option 1</option> <option value="Option 1">Option 1</option>
<option value="Option 2" selected>Option 2</option> <option value="Option 2" selected>Option 2</option>
<option value="Option 3">Option 3</option> <option value="Option 3">Option 3</option>
@ -533,7 +516,6 @@
class="form-control" class="form-control"
name="reset-multiple" name="reset-multiple"
id="reset-multiple" id="reset-multiple"
placeholder="This is a placeholder"
multiple multiple
> >
<option value="Choice 1" selected>Choice 1</option> <option value="Choice 1" selected>Choice 1</option>

View file

@ -1,4 +1,5 @@
/** /**
* @typedef {import('redux').Action} Action
* @typedef {import('../../../types/index').Choices.Choice} Choice * @typedef {import('../../../types/index').Choices.Choice} Choice
*/ */
@ -6,7 +7,7 @@ import { ACTION_TYPES } from '../constants';
/** /**
* @argument {Choice} choice * @argument {Choice} choice
* @returns {{ type: string } & Choice} * @returns {Action & Choice}
*/ */
export const addChoice = ({ export const addChoice = ({
value, value,
@ -33,7 +34,7 @@ export const addChoice = ({
/** /**
* @argument {Choice[]} results * @argument {Choice[]} results
* @returns {{ type: string, results: Choice[] }} * @returns {Action & { results: Choice[] }}
*/ */
export const filterChoices = results => ({ export const filterChoices = results => ({
type: ACTION_TYPES.FILTER_CHOICES, type: ACTION_TYPES.FILTER_CHOICES,
@ -42,7 +43,7 @@ export const filterChoices = results => ({
/** /**
* @argument {boolean} active * @argument {boolean} active
* @returns {{ type: string, active: boolean }} * @returns {Action & { active: boolean }}
*/ */
export const activateChoices = (active = true) => ({ export const activateChoices = (active = true) => ({
type: ACTION_TYPES.ACTIVATE_CHOICES, type: ACTION_TYPES.ACTIVATE_CHOICES,
@ -50,7 +51,7 @@ export const activateChoices = (active = true) => ({
}); });
/** /**
* @returns {{ type: string }} * @returns {Action}
*/ */
export const clearChoices = () => ({ export const clearChoices = () => ({
type: ACTION_TYPES.CLEAR_CHOICES, type: ACTION_TYPES.CLEAR_CHOICES,

View file

@ -1,12 +1,13 @@
import { ACTION_TYPES } from '../constants'; import { ACTION_TYPES } from '../constants';
/** /**
* @typedef {import('redux').Action} Action
* @typedef {import('../../../types/index').Choices.Group} Group * @typedef {import('../../../types/index').Choices.Group} Group
*/ */
/** /**
* @param {Group} group * @param {Group} group
* @returns {{ type: string } & Group} * @returns {Action & Group}
*/ */
export const addGroup = ({ value, id, active, disabled }) => ({ export const addGroup = ({ value, id, active, disabled }) => ({
type: ACTION_TYPES.ADD_GROUP, type: ACTION_TYPES.ADD_GROUP,

View file

@ -1,12 +1,13 @@
import { ACTION_TYPES } from '../constants'; import { ACTION_TYPES } from '../constants';
/** /**
* @typedef {import('redux').Action} Action
* @typedef {import('../../../types/index').Choices.Item} Item * @typedef {import('../../../types/index').Choices.Item} Item
*/ */
/** /**
* @param {Item} item * @param {Item} item
* @returns {{ type: string } & Item} * @returns {Action & Item}
*/ */
export const addItem = ({ export const addItem = ({
value, value,
@ -32,7 +33,7 @@ export const addItem = ({
/** /**
* @param {string} id * @param {string} id
* @param {string} choiceId * @param {string} choiceId
* @returns {{ type: string, id: string, choiceId: string }} * @returns {Action & { id: string, choiceId: string }}
*/ */
export const removeItem = (id, choiceId) => ({ export const removeItem = (id, choiceId) => ({
type: ACTION_TYPES.REMOVE_ITEM, type: ACTION_TYPES.REMOVE_ITEM,
@ -43,7 +44,7 @@ export const removeItem = (id, choiceId) => ({
/** /**
* @param {string} id * @param {string} id
* @param {boolean} highlighted * @param {boolean} highlighted
* @returns {{ type: string, id: string, highlighted: boolean }} * @returns {Action & { id: string, highlighted: boolean }}
*/ */
export const highlightItem = (id, highlighted) => ({ export const highlightItem = (id, highlighted) => ({
type: ACTION_TYPES.HIGHLIGHT_ITEM, type: ACTION_TYPES.HIGHLIGHT_ITEM,

View file

@ -1,5 +1,9 @@
/** /**
* @returns {{ type: string }} * @typedef {import('redux').Action} Action
*/
/**
* @returns {Action}
*/ */
export const clearAll = () => ({ export const clearAll = () => ({
type: 'CLEAR_ALL', type: 'CLEAR_ALL',
@ -7,7 +11,7 @@ export const clearAll = () => ({
/** /**
* @param {any} state * @param {any} state
* @returns {{ type: string, state: object }} * @returns {Action & { state: object }}
*/ */
export const resetTo = state => ({ export const resetTo = state => ({
type: 'RESET_TO', type: 'RESET_TO',
@ -16,7 +20,7 @@ export const resetTo = state => ({
/** /**
* @param {boolean} isLoading * @param {boolean} isLoading
* @returns {{ type: string, isLoading: boolean }} * @returns {Action & { isLoading: boolean }}
*/ */
export const setIsLoading = isLoading => ({ export const setIsLoading = isLoading => ({
type: 'SET_IS_LOADING', type: 'SET_IS_LOADING',

View file

@ -45,6 +45,8 @@ import {
/** /**
* @typedef {import('../../types/index').Choices.Choice} Choice * @typedef {import('../../types/index').Choices.Choice} Choice
* @typedef {import('../../types/index').Choices.Item} Item
* @typedef {import('../../types/index').Choices.Group} Group
* @typedef {import('../../types/index').Choices.Options} Options * @typedef {import('../../types/index').Choices.Options} Options
*/ */
@ -174,16 +176,31 @@ class Choices {
this._idNames = { this._idNames = {
itemChoice: 'item-choice', itemChoice: 'item-choice',
}; };
// Assign preset groups from passed element
this._presetGroups = this.passedElement.optionGroups;
// Assign preset choices from passed object // Assign preset choices from passed object
this._presetChoices = this.config.choices; this._presetChoices = this.config.choices;
// Assign preset items from passed object first // Assign preset items from passed object first
this._presetItems = this.config.items; this._presetItems = this.config.items;
// Then add any values passed from attribute // Add any values passed from attribute
if (this.passedElement.value) { if (this.passedElement.value) {
this._presetItems = this._presetItems.concat( this._presetItems = this._presetItems.concat(
this.passedElement.value.split(this.config.delimiter), this.passedElement.value.split(this.config.delimiter),
); );
} }
// Create array of choices from option elements
if (this.passedElement.options) {
this.passedElement.options.forEach(o => {
this._presetChoices.push({
value: o.value,
label: o.innerHTML,
selected: o.selected,
disabled: o.disabled || o.parentNode.disabled,
placeholder: o.value === '' || o.hasAttribute('placeholder'),
customProperties: o.getAttribute('data-custom-properties'),
});
});
}
this._render = this._render.bind(this); this._render = this._render.bind(this);
this._onFocus = this._onFocus.bind(this); this._onFocus = this._onFocus.bind(this);
@ -466,7 +483,7 @@ class Choices {
* *
* **Input types affected:** select-one, select-multiple * **Input types affected:** select-one, select-multiple
* *
* @template {object[] | ((instance: Choices) => object[] | Promise<object[]>)} T * @template {Choice[] | ((instance: Choices) => object[] | Promise<object[]>)} T
* @param {T} [choicesArrayOrFetcher] * @param {T} [choicesArrayOrFetcher]
* @param {string} [value = 'value'] - name of `value` field * @param {string} [value = 'value'] - name of `value` field
* @param {string} [label = 'label'] - name of 'label' field * @param {string} [label = 'label'] - name of 'label' field
@ -556,6 +573,7 @@ class Choices {
if (typeof choicesArrayOrFetcher === 'function') { if (typeof choicesArrayOrFetcher === 'function') {
// it's a choices fetcher function // it's a choices fetcher function
const fetcher = choicesArrayOrFetcher(this); const fetcher = choicesArrayOrFetcher(this);
if (typeof Promise === 'function' && fetcher instanceof Promise) { if (typeof Promise === 'function' && fetcher instanceof Promise) {
// that's a promise // that's a promise
// eslint-disable-next-line compat/compat // eslint-disable-next-line compat/compat
@ -591,7 +609,9 @@ class Choices {
this.containerOuter.removeLoadingState(); this.containerOuter.removeLoadingState();
const addGroupsAndChoices = groupOrChoice => { this._setLoading(true);
choicesArrayOrFetcher.forEach(groupOrChoice => {
if (groupOrChoice.choices) { if (groupOrChoice.choices) {
this._addGroup({ this._addGroup({
group: groupOrChoice, group: groupOrChoice,
@ -609,10 +629,8 @@ class Choices {
placeholder: groupOrChoice.placeholder, placeholder: groupOrChoice.placeholder,
}); });
} }
}; });
this._setLoading(true);
choicesArrayOrFetcher.forEach(addGroupsAndChoices);
this._setLoading(false); this._setLoading(false);
return this; return this;
@ -2024,7 +2042,7 @@ class Choices {
this.input.placeholder = this.config.searchPlaceholderValue || ''; this.input.placeholder = this.config.searchPlaceholderValue || '';
} else if (this._placeholderValue) { } else if (this._placeholderValue) {
this.input.placeholder = this._placeholderValue; this.input.placeholder = this._placeholderValue;
this.input.setWidth(true); this.input.setWidth();
} }
this.containerOuter.element.appendChild(this.containerInner.element); this.containerOuter.element.appendChild(this.containerInner.element);
@ -2045,116 +2063,105 @@ class Choices {
} }
if (this._isSelectElement) { if (this._isSelectElement) {
this._addPredefinedChoices(); this._highlightPosition = 0;
} else if (this._isTextElement) { this._isSearching = false;
this._addPredefinedItems(); this._setLoading(true);
if (this._presetGroups.length) {
this._addPredefinedGroups(this._presetGroups);
} else {
this._addPredefinedChoices(this._presetChoices);
}
this._setLoading(false);
}
if (this._isTextElement) {
this._addPredefinedItems(this._presetItems);
} }
} }
_addPredefinedChoices() { _addPredefinedGroups(groups) {
const passedGroups = this.passedElement.optionGroups; // If we have a placeholder option
const placeholderChoice = this.passedElement.placeholderOption;
this._highlightPosition = 0; if (
this._isSearching = false; placeholderChoice &&
this._setLoading(true); placeholderChoice.parentNode.tagName === 'SELECT'
) {
if (passedGroups && passedGroups.length) { this._addChoice({
// If we have a placeholder option value: placeholderChoice.value,
const placeholderChoice = this.passedElement.placeholderOption; label: placeholderChoice.innerHTML,
if ( isSelected: placeholderChoice.selected,
placeholderChoice && isDisabled: placeholderChoice.disabled,
placeholderChoice.parentNode.tagName === 'SELECT' placeholder: true,
) {
this._addChoice({
value: placeholderChoice.value,
label: placeholderChoice.innerHTML,
isSelected: placeholderChoice.selected,
isDisabled: placeholderChoice.disabled,
placeholder: true,
});
}
passedGroups.forEach(group =>
this._addGroup({
group,
id: group.id || null,
}),
);
} else {
const passedOptions = this.passedElement.options;
const filter = this.config.sorter;
const allChoices = this._presetChoices;
// Create array of options from option elements
passedOptions.forEach(o => {
allChoices.push({
value: o.value,
label: o.innerHTML,
selected: o.selected,
disabled: o.disabled || o.parentNode.disabled,
placeholder: o.value === '' || o.hasAttribute('placeholder'),
customProperties: o.getAttribute('data-custom-properties'),
});
}); });
}
// If sorting is enabled or the user is searching, filter choices groups.forEach(group =>
if (this.config.shouldSort) { this._addGroup({
allChoices.sort(filter); group,
} id: group.id || null,
}),
);
}
// Determine whether there is a selected choice _addPredefinedChoices(choices) {
const hasSelectedChoice = allChoices.some(choice => choice.selected); // If sorting is enabled or the user is searching, filter choices
const handleChoice = (choice, index) => { if (this.config.shouldSort) {
const { value, label, customProperties, placeholder } = choice; choices.sort(this.config.sorter);
}
if (this._isSelectElement) { // Determine whether there is a selected choice
// If the choice is actually a group const hasSelectedChoice = choices.some(choice => choice.selected);
if (choice.choices) {
this._addGroup({
group: choice,
id: choice.id || null,
});
} else {
// If there is a selected choice already or the choice is not
// the first in the array, add each choice normally
// Otherwise pre-select the first choice in the array if it's a single select
const shouldPreselect =
this._isSelectOneElement && !hasSelectedChoice && index === 0;
const isSelected = shouldPreselect ? true : choice.selected;
const isDisabled = shouldPreselect ? false : choice.disabled;
this._addChoice({ // Add each choice
value, choices.forEach((choice, index) => {
label, const { value, label, customProperties, placeholder } = choice;
isSelected,
isDisabled, if (this._isSelectElement) {
customProperties, // If the choice is actually a group
placeholder, if (choice.choices) {
}); this._addGroup({
} group: choice,
id: choice.id || null,
});
} else { } else {
// If there is a selected choice already or the choice is not
// the first in the array, add each choice normally
// Otherwise pre-select the first choice in the array if it's a single select
const shouldPreselect =
this._isSelectOneElement && !hasSelectedChoice && index === 0;
const isSelected = shouldPreselect ? true : choice.selected;
const isDisabled = shouldPreselect ? false : choice.disabled;
this._addChoice({ this._addChoice({
value, value,
label, label,
isSelected: choice.selected, isSelected,
isDisabled: choice.disabled, isDisabled,
customProperties, customProperties,
placeholder, placeholder,
}); });
} }
}; } else {
this._addChoice({
// Add each choice value,
allChoices.forEach((choice, index) => handleChoice(choice, index)); label,
} isSelected: choice.selected,
isDisabled: choice.disabled,
this._setLoading(false); customProperties,
placeholder,
});
}
});
} }
_addPredefinedItems() { /**
const handlePresetItem = item => { * @param {Item[]} items
const itemType = getType(item); */
if (itemType === 'Object' && item.value) { _addPredefinedItems(items) {
items.forEach(item => {
if (typeof item === 'object' && item.value) {
this._addItem({ this._addItem({
value: item.value, value: item.value,
label: item.label, label: item.label,
@ -2162,14 +2169,14 @@ class Choices {
customProperties: item.customProperties, customProperties: item.customProperties,
placeholder: item.placeholder, placeholder: item.placeholder,
}); });
} else if (itemType === 'String') { }
if (typeof item === 'string') {
this._addItem({ this._addItem({
value: item, value: item,
}); });
} }
}; });
this._presetItems.forEach(item => handlePresetItem(item));
} }
_setChoiceOrItem(item) { _setChoiceOrItem(item) {

View file

@ -1,10 +1,13 @@
import { expect } from 'chai'; import chai, { expect } from 'chai';
import { spy, stub } from 'sinon'; import { spy, stub } from 'sinon';
import sinonChai from 'sinon-chai';
import Choices from './choices'; import Choices from './choices';
import { EVENTS, ACTION_TYPES, DEFAULT_CONFIG } from './constants'; import { EVENTS, ACTION_TYPES, DEFAULT_CONFIG } from './constants';
import { WrappedSelect, WrappedInput } from './components/index'; import { WrappedSelect, WrappedInput } from './components/index';
chai.use(sinonChai);
describe('choices', () => { describe('choices', () => {
let instance; let instance;
let output; let output;
@ -1964,5 +1967,33 @@ describe('choices', () => {
}); });
}); });
}); });
describe('_getTemplate', () => {
describe('when not passing a template key', () => {
it('returns null', () => {
output = instance._getTemplate();
expect(output).to.equal(null);
});
});
describe('when passing a template key', () => {
it('returns the generated template for the given template key', () => {
const templateKey = 'test';
const element = document.createElement('div');
const customArg = { test: true };
instance._templates = {
[templateKey]: stub().returns(element),
};
output = instance._getTemplate(templateKey, customArg);
expect(output).to.deep.equal(element);
expect(instance._templates[templateKey]).to.have.been.calledOnceWith(
instance.config.classNames,
customArg,
);
});
});
});
}); });
}); });

View file

@ -1,4 +1,5 @@
import { wrap } from '../lib/utils'; import { wrap } from '../lib/utils';
import { SELECT_ONE_TYPE } from '../constants';
/** /**
* @typedef {import('../../../types/index').Choices.passedElement} passedElement * @typedef {import('../../../types/index').Choices.passedElement} passedElement
@ -116,7 +117,7 @@ export default class Container {
enable() { enable() {
this.element.classList.remove(this.classNames.disabledState); this.element.classList.remove(this.classNames.disabledState);
this.element.removeAttribute('aria-disabled'); this.element.removeAttribute('aria-disabled');
if (this.type === 'select-one') { if (this.type === SELECT_ONE_TYPE) {
this.element.setAttribute('tabindex', '0'); this.element.setAttribute('tabindex', '0');
} }
this.isDisabled = false; this.isDisabled = false;
@ -125,7 +126,7 @@ export default class Container {
disable() { disable() {
this.element.classList.add(this.classNames.disabledState); this.element.classList.add(this.classNames.disabledState);
this.element.setAttribute('aria-disabled', 'true'); this.element.setAttribute('aria-disabled', 'true');
if (this.type === 'select-one') { if (this.type === SELECT_ONE_TYPE) {
this.element.setAttribute('tabindex', '-1'); this.element.setAttribute('tabindex', '-1');
} }
this.isDisabled = true; this.isDisabled = true;

View file

@ -1,4 +1,5 @@
import { sanitise } from '../lib/utils'; import { sanitise } from '../lib/utils';
import { SELECT_ONE_TYPE } from '../constants';
/** /**
* @typedef {import('../../../types/index').Choices.passedElement} passedElement * @typedef {import('../../../types/index').Choices.passedElement} passedElement
@ -137,7 +138,7 @@ export default class Input {
} }
_onInput() { _onInput() {
if (this.type !== 'select-one') { if (this.type !== SELECT_ONE_TYPE) {
this.setWidth(); this.setWidth();
} }
} }

View file

@ -18,7 +18,7 @@ export default class List {
} }
/** /**
* @param {Element} node * @param {Element | DocumentFragment} node
*/ */
append(node) { append(node) {
this.element.appendChild(node); this.element.appendChild(node);

View file

@ -1,45 +0,0 @@
window.delegateEvent = (function delegateEvent() {
let events;
let addedListenerTypes;
if (typeof events === 'undefined') {
events = new Map();
}
if (typeof addedListenerTypes === 'undefined') {
addedListenerTypes = [];
}
function _callback(event) {
const type = events.get(event.type);
if (!type) {
return;
}
type.forEach(fn => fn(event));
}
return {
add: function add(type, fn) {
// Cache list of events.
if (events.has(type)) {
events.get(type).push(fn);
} else {
events.set(type, [fn]);
}
// Setup events.
if (addedListenerTypes.indexOf(type) === -1) {
document.documentElement.addEventListener(type, _callback, true);
addedListenerTypes.push(type);
}
},
remove: function remove(type, fn) {
if (!events.get(type)) {
return;
}
events.set(type, events.get(type).filter(item => item !== fn));
if (!events.get(type).length) {
addedListenerTypes.splice(addedListenerTypes.indexOf(type), 1);
}
},
};
})();

View file

@ -1,9 +1,23 @@
/**
* @param {number} min
* @param {number} max
* @returns {number}
*/
export const getRandomNumber = (min, max) => export const getRandomNumber = (min, max) =>
Math.floor(Math.random() * (max - min) + min); Math.floor(Math.random() * (max - min) + min);
/**
* @param {number} length
* @returns {string}
*/
export const generateChars = length => export const generateChars = length =>
Array.from({ length }, () => getRandomNumber(0, 36).toString(36)).join(''); Array.from({ length }, () => getRandomNumber(0, 36).toString(36)).join('');
/**
* @param {HTMLInputElement | HTMLSelectElement} element
* @param {string} prefix
* @returns {string}
*/
export const generateId = (element, prefix) => { export const generateId = (element, prefix) => {
let id = let id =
element.id || element.id ||
@ -15,11 +29,25 @@ export const generateId = (element, prefix) => {
return id; return id;
}; };
/**
* @param {any} obj
* @returns {string}
*/
export const getType = obj => Object.prototype.toString.call(obj).slice(8, -1); export const getType = obj => Object.prototype.toString.call(obj).slice(8, -1);
/**
* @param {string} type
* @param {any} obj
* @returns {boolean}
*/
export const isType = (type, obj) => export const isType = (type, obj) =>
obj !== undefined && obj !== null && getType(obj) === type; obj !== undefined && obj !== null && getType(obj) === type;
/**
* @param {HTMLElement} element
* @param {HTMLElement} [wrapper={HTMLDivElement}]
* @returns {HTMLElement}
*/
export const wrap = (element, wrapper = document.createElement('div')) => { export const wrap = (element, wrapper = document.createElement('div')) => {
if (element.nextSibling) { if (element.nextSibling) {
element.parentNode.insertBefore(wrapper, element.nextSibling); element.parentNode.insertBefore(wrapper, element.nextSibling);
@ -36,34 +64,39 @@ export const wrap = (element, wrapper = document.createElement('div')) => {
*/ */
export const findAncestorByAttrName = (el, attr) => el.closest(`[${attr}]`); export const findAncestorByAttrName = (el, attr) => el.closest(`[${attr}]`);
export const getAdjacentEl = /**
/** * @param {Element} startEl
* @param {Element} startEl * @param {string} selector
* @param {string} selector * @param {1 | -1} direction
* @param {1 | -1} direction * @returns {Element | undefined}
* @returns {Element | undefined} */
*/ export const getAdjacentEl = (startEl, selector, direction = 1) => {
(startEl, selector, direction = 1) => { if (!(startEl instanceof Element) || typeof selector !== 'string') {
if (!(startEl instanceof Element) || typeof selector !== 'string') { return undefined;
return undefined; }
const prop = `${direction > 0 ? 'next' : 'previous'}ElementSibling`;
let sibling = startEl[prop];
while (sibling) {
if (sibling.matches(selector)) {
return sibling;
} }
sibling = sibling[prop];
}
const prop = `${direction > 0 ? 'next' : 'previous'}ElementSibling`; return sibling;
};
let sibling = startEl[prop]; /**
while (sibling) { * @param {HTMLElement} element
if (sibling.matches(selector)) { * @param {HTMLElement} parent
return sibling; * @param {-1 | 1} direction
} * @returns {boolean}
sibling = sibling[prop]; */
} export const isScrolledIntoView = (element, parent, direction = 1) => {
if (!element) {
return sibling; return false;
};
export const isScrolledIntoView = (el, parent, direction = 1) => {
if (!el) {
return;
} }
let isVisible; let isVisible;
@ -71,15 +104,20 @@ export const isScrolledIntoView = (el, parent, direction = 1) => {
if (direction > 0) { if (direction > 0) {
// In view from bottom // In view from bottom
isVisible = isVisible =
parent.scrollTop + parent.offsetHeight >= el.offsetTop + el.offsetHeight; parent.scrollTop + parent.offsetHeight >=
element.offsetTop + element.offsetHeight;
} else { } else {
// In view from top // In view from top
isVisible = el.offsetTop >= parent.scrollTop; isVisible = element.offsetTop >= parent.scrollTop;
} }
return isVisible; return isVisible;
}; };
/**
* @param {any} value
* @returns {any}
*/
export const sanitise = value => { export const sanitise = value => {
if (typeof value !== 'string') { if (typeof value !== 'string') {
return value; return value;
@ -92,6 +130,9 @@ export const sanitise = value => {
.replace(/"/g, '&quot;'); .replace(/"/g, '&quot;');
}; };
/**
* @returns {function}
*/
export const strToEl = (() => { export const strToEl = (() => {
const tmpEl = document.createElement('div'); const tmpEl = document.createElement('div');
@ -124,11 +165,16 @@ export const sortByAlpha = (
}); });
/** /**
* @param {object} a * @param {{ score: number }} a
* @param {object} b * @param {{ score: number }} b
*/ */
export const sortByScore = (a, b) => a.score - b.score; export const sortByScore = (a, b) => a.score - b.score;
/**
* @param {HTMLElement} element
* @param {string} type
* @param {object} customArgs
*/
export const dispatchEvent = (element, type, customArgs = null) => { export const dispatchEvent = (element, type, customArgs = null) => {
const event = new CustomEvent(type, { const event = new CustomEvent(type, {
detail: customArgs, detail: customArgs,
@ -139,9 +185,19 @@ export const dispatchEvent = (element, type, customArgs = null) => {
return element.dispatchEvent(event); return element.dispatchEvent(event);
}; };
/**
* @param {string} userAgent
* @returns {boolean}
*/
export const isIE11 = userAgent => export const isIE11 = userAgent =>
!!(userAgent.match(/Trident/) && userAgent.match(/rv[ :]11/)); !!(userAgent.match(/Trident/) && userAgent.match(/rv[ :]11/));
/**
* @param {array} array
* @param {any} value
* @param {string} [key="value"]
* @returns {boolean}
*/
export const existsInArray = (array, value, key = 'value') => export const existsInArray = (array, value, key = 'value') =>
array.some(item => { array.some(item => {
if (typeof value === 'string') { if (typeof value === 'string') {
@ -151,8 +207,18 @@ export const existsInArray = (array, value, key = 'value') =>
return item[key] === value; return item[key] === value;
}); });
/**
* @param {any} obj
* @returns {any}
*/
export const cloneObject = obj => JSON.parse(JSON.stringify(obj)); export const cloneObject = obj => JSON.parse(JSON.stringify(obj));
/**
* Returns an array of keys present on the first but missing on the second object
* @param {object} a
* @param {object} b
* @returns {string[]}
*/
export const diff = (a, b) => { export const diff = (a, b) => {
const aKeys = Object.keys(a).sort(); const aKeys = Object.keys(a).sort();
const bKeys = Object.keys(b).sort(); const bKeys = Object.keys(b).sort();

View file

@ -13,6 +13,7 @@ import {
existsInArray, existsInArray,
cloneObject, cloneObject,
dispatchEvent, dispatchEvent,
diff,
} from './utils'; } from './utils';
describe('utils', () => { describe('utils', () => {
@ -37,7 +38,7 @@ describe('utils', () => {
describe('generateId', () => { describe('generateId', () => {
describe('when given element has id value', () => { describe('when given element has id value', () => {
it('generates a unique prefixed id based on given elements id', () => { it('generates a unique prefixed id based on given elements id', () => {
const element = document.createElement('div'); const element = document.createElement('select');
element.id = 'test-id'; element.id = 'test-id';
const prefix = 'test-prefix'; const prefix = 'test-prefix';
@ -49,7 +50,7 @@ describe('utils', () => {
describe('when given element has no id value but name value', () => { describe('when given element has no id value but name value', () => {
it('generates a unique prefixed id based on given elements name plus 2 random characters', () => { it('generates a unique prefixed id based on given elements name plus 2 random characters', () => {
const element = document.createElement('div'); const element = document.createElement('select');
element.name = 'test-name'; element.name = 'test-name';
const prefix = 'test-prefix'; const prefix = 'test-prefix';
@ -63,7 +64,7 @@ describe('utils', () => {
describe('when given element has no id value and no name value', () => { describe('when given element has no id value and no name value', () => {
it('generates a unique prefixed id based on 4 random characters', () => { it('generates a unique prefixed id based on 4 random characters', () => {
const element = document.createElement('div'); const element = document.createElement('select');
const prefix = 'test-prefix'; const prefix = 'test-prefix';
const output = generateId(element, prefix); const output = generateId(element, prefix);
@ -83,7 +84,7 @@ describe('utils', () => {
expect(getType([])).to.equal('Array'); expect(getType([])).to.equal('Array');
expect(getType(() => {})).to.equal('Function'); expect(getType(() => {})).to.equal('Function');
expect(getType(new Error())).to.equal('Error'); expect(getType(new Error())).to.equal('Error');
expect(getType(new RegExp())).to.equal('RegExp'); expect(getType(new RegExp(/''/g))).to.equal('RegExp');
expect(getType(new String())).to.equal('String'); // eslint-disable-line expect(getType(new String())).to.equal('String'); // eslint-disable-line
expect(getType('')).to.equal('String'); expect(getType('')).to.equal('String');
}); });
@ -97,12 +98,24 @@ describe('utils', () => {
}); });
describe('sanitise', () => { describe('sanitise', () => {
it('strips HTML from value', () => { describe('when passing a parameter that is not a string', () => {
const value = '<script>somethingMalicious();</script>'; it('returns the passed argument', () => {
const output = sanitise(value); const value = {
expect(output).to.equal( test: true,
'&lt;script&rt;somethingMalicious();&lt;/script&rt;', };
); const output = sanitise(value);
expect(output).to.equal(value);
});
});
describe('when passing a string', () => {
it('strips HTML from value', () => {
const value = '<script>somethingMalicious();</script>';
const output = sanitise(value);
expect(output).to.equal(
'&lt;script&rt;somethingMalicious();&lt;/script&rt;',
);
});
}); });
}); });
@ -238,4 +251,20 @@ describe('utils', () => {
expect(output).to.eql(object); expect(output).to.eql(object);
}); });
}); });
describe('diff', () => {
it('returns an array of keys present on the first but missing on the second object', () => {
const obj1 = {
foo: 'bar',
baz: 'foo',
};
const obj2 = {
foo: 'bar',
};
const output = diff(obj1, obj2);
expect(output).to.deep.equal(['baz']);
});
});
}); });

View file

@ -27,7 +27,7 @@ export default class Store {
/** /**
* Dispatch event to store (wrapped Redux method) * Dispatch event to store (wrapped Redux method)
* @param {Function} action Action function to trigger * @param {{ type: string, [x: string]: any }} action Action to trigger
* @return * @return
*/ */
dispatch(action) { dispatch(action) {

5
types/index.d.ts vendored
View file

@ -1,4 +1,4 @@
// Type definitions for Choices.js 7.1.x // Type definitions for Choices.js
// Project: https://github.com/jshjohnson/Choices // Project: https://github.com/jshjohnson/Choices
// Definitions by: // Definitions by:
// Arthur vasconcelos <https://github.com/arthurvasconcelos>, // Arthur vasconcelos <https://github.com/arthurvasconcelos>,
@ -46,6 +46,7 @@ declare namespace Choices {
interface Item extends Choice { interface Item extends Choice {
choiceId?: string; choiceId?: string;
keyCode?: number; keyCode?: number;
highlighted?: boolean;
} }
/** /**
@ -64,7 +65,7 @@ declare namespace Choices {
value: string; value: string;
label: string; label: string;
groupValue: string; groupValue: string;
keyCode: string; keyCode: number;
}>; }>;
/** /**