Compare commits
644 commits
Author | SHA1 | Date | |
---|---|---|---|
5dbea2825a | |||
312971acea | |||
a7ed4d880e | |||
ba27dbb533 | |||
9c6de739c7 | |||
2730d7aad4 | |||
5d17dd8531 | |||
d1f39c6cda | |||
bbdacdfe3b | |||
92eabcbd4a | |||
2a8107f379 | |||
ea45c47d43 | |||
d130beb4e9 | |||
6056cce4e9 | |||
ce590e3785 | |||
30e6dbadda | |||
4da6239a67 | |||
9ac71c4717 | |||
835ea12ac9 | |||
1b67baec25 | |||
624dd797a4 | |||
cbbdbda95b | |||
d796e90955 | |||
06d16cd857 | |||
d26b753f6b | |||
72da548a64 | |||
490e341db5 | |||
76780ccc34 | |||
beb07ebc2c | |||
20d3f5cd55 | |||
f63201d929 | |||
08d4f1fee7 | |||
09eeca0db0 | |||
e2aab31bc0 | |||
c01144ab23 | |||
86694c138e | |||
7c360b44b3 | |||
2099c3dc2e | |||
d25c065210 | |||
eb22895f00 | |||
ac8d27facf | |||
06d4435c82 | |||
bb76a572c0 | |||
6df44b1044 | |||
29097d5457 | |||
c21b998eaa | |||
96e4ac53d9 | |||
770342b7e8 | |||
eef836a037 | |||
8ab8157607 | |||
75caa0b1f3 | |||
3d42067bbd | |||
ce3d1111d8 | |||
7758226d64 | |||
25a7ed0318 | |||
23f0b7cb9d | |||
303d12504c | |||
3b4d7543e5 | |||
781c729e13 | |||
9605f367f9 | |||
1b4d3553ef | |||
f12c62c4b8 | |||
c7976b7acd | |||
4df199e0b9 | |||
0903934848 | |||
64b5df6874 | |||
824eca6f81 | |||
9c9aded8b2 | |||
7e200664c4 | |||
04571e36d3 | |||
11208fe62d | |||
b9acfa773d | |||
6e4903e852 | |||
a8862e78be | |||
bf94386cc5 | |||
0b6973b322 | |||
66c6864267 | |||
391c3e39cb | |||
3d402d4560 | |||
545a442f5c | |||
c989be1491 | |||
3633c4ac0f | |||
859f6262eb | |||
6b16e93977 | |||
7f727480e8 | |||
82b94228f9 | |||
b92823b70c | |||
22f9be0d93 | |||
2222f767a9 | |||
f0ec43fa20 | |||
c4f3a81d41 | |||
fb379408c1 | |||
c26596e9ec | |||
9835eb756a | |||
70397fd89a | |||
59ea75ef14 | |||
8647f36fbe | |||
046afbb614 | |||
de5307ce4d | |||
05a41c5c7d | |||
9e656397cd | |||
20579bad35 | |||
977221650a | |||
e09a342ac8 | |||
e64eface11 | |||
de6c46cdd0 | |||
a1ec9d0de6 | |||
3d921621b7 | |||
46deb9abe5 | |||
6b6fb17bca | |||
b4a6371cc8 | |||
60adc56bbf | |||
ebbea8c3b0 | |||
41f81d22ed | |||
cbaf43232c | |||
825efc6fd0 | |||
68313da412 | |||
3ed027229d | |||
44b4326a52 | |||
b3b7b3f14f | |||
3d099d85fc | |||
9676cff128 | |||
47aba515fb | |||
62e849d1b4 | |||
d116f95c54 | |||
6215ca5e59 | |||
67266a3aae | |||
85f0b5f9be | |||
592c326442 | |||
214b6e80df | |||
9f2661023d | |||
d2516e7de3 | |||
0e8e42e015 | |||
e7b4afd472 | |||
952b3fc146 | |||
760f39dbf3 | |||
573c840de4 | |||
44da784f29 | |||
6be462c80b | |||
e67b8a3e4c | |||
933ea6093f | |||
84b952e115 | |||
f745be449d | |||
8775bacdd9 | |||
fb2310cb56 | |||
68322d9528 | |||
d04031e02f | |||
7dcc155b8f | |||
b5b593a62f | |||
81c44ba8f2 | |||
f30b976424 | |||
ab22347d7b | |||
e6882f3e4b | |||
452c8fa666 | |||
a0fe05f926 | |||
939a73b762 | |||
68f6b8e398 | |||
dddba5b35b | |||
5afe8b5a7f | |||
9e11db8181 | |||
00e53f76ee | |||
5051bf1b10 | |||
034191c78a | |||
c03fcf5d03 | |||
eb17194ff3 | |||
0e44a916e3 | |||
88f63faa0b | |||
172366d6fa | |||
e3cc6eaf1b | |||
1f5192b4ad | |||
2e004015d5 | |||
b080bcda7d | |||
2a03d9be12 | |||
2b8acc5f37 | |||
9504cfcec5 | |||
1c751472b2 | |||
b48395ce36 | |||
a2485392a5 | |||
50fae125ed | |||
264a0cb486 | |||
92d5e49fb6 | |||
64407174e3 | |||
dbd15d7823 | |||
99f945bc03 | |||
312569a734 | |||
15d54c7d34 | |||
ef2c70fb7a | |||
69582349bb | |||
bef6743c3b | |||
472e0dff39 | |||
7887c05249 | |||
9bb0c628b2 | |||
a35c8b9009 | |||
1dbab0f29e | |||
e83f40956b | |||
dc7cf42e90 | |||
4c6acb5de2 | |||
7e38f83b9e | |||
71e64e0f52 | |||
4b9dd1efe3 | |||
4acd6d6ca8 | |||
6848970fd9 | |||
00bf028904 | |||
1285602b2f | |||
8782564ddf | |||
4de6e677d1 | |||
2fca709814 | |||
ffc32df462 | |||
f597bc9aff | |||
bc8a044ab1 | |||
31ef5bb065 | |||
7de0887e7d | |||
4e8842d013 | |||
b8074733c9 | |||
98206e899d | |||
659c3545fb | |||
97f91c240c | |||
7022c238fd | |||
bbeb556a48 | |||
bb0736e8fc | |||
b8f5bd1680 | |||
6c1e02fa82 | |||
f872caf1f8 | |||
585859f4cd | |||
e7d775e2ae | |||
5cf226f166 | |||
37db45e651 | |||
57807e88f6 | |||
589578f420 | |||
39b6eed395 | |||
061219cb00 | |||
62ccd923ff | |||
43e670d02d | |||
0829899284 | |||
7540d39f95 | |||
67382a3f31 | |||
899e4b16bb | |||
63e5b51683 | |||
3c05016920 | |||
0580a0433f | |||
fee575d6d9 | |||
b08a5412f9 | |||
f32995367b | |||
201b3c8ada | |||
9c021408fa | |||
aceb838988 | |||
03a45094b7 | |||
92680c0dc0 | |||
da87fa07cf | |||
2247eff08a | |||
5018e4a7d4 | |||
cc32284e8a | |||
b1005061ff | |||
6486bbbd6d | |||
f9f63e7aba | |||
5c17250e20 | |||
fc9bca2ece | |||
2fa92520ef | |||
bbbc31594c | |||
56845e3897 | |||
9bebf2e99e | |||
4d1c3cd2ae | |||
e8b96b0a6e | |||
879c97f64c | |||
5d4d3be1b5 | |||
a99547c2f4 | |||
a846534ffb | |||
efaf8f8aae | |||
c424ae2f3b | |||
22e1884ad4 | |||
0cf05f4eff | |||
31f9d33327 | |||
8540d5aabd | |||
55b356ec69 | |||
ba09fb00e6 | |||
23e5e7674f | |||
7091daa0d1 | |||
71a3131d3c | |||
caed692440 | |||
c36f76460c | |||
5c9dfdf2db | |||
2f873167f6 | |||
437651411f | |||
6c3bad777d | |||
d02cdffcdb | |||
dc9e7eb44d | |||
5c30a1f0de | |||
48b74a91bc | |||
9c001487ba | |||
826384b9d5 | |||
16e16a181c | |||
490a2bb37b | |||
35d8b6f603 | |||
24d6aea68f | |||
c3e46e55aa | |||
bded79386f | |||
367fee6885 | |||
3182c7f2c2 | |||
28f36bf6f5 | |||
3431d03ef0 | |||
cd4454201b | |||
496db95153 | |||
307881a068 | |||
f3c0abef95 | |||
90a92656be | |||
cd165deb3c | |||
6f2bf5770b | |||
feb2a15edc | |||
2292b5282f | |||
d657b17568 | |||
5fe6e18077 | |||
bd674b5bdf | |||
8d11eae895 | |||
c9d86fd826 | |||
0010afa109 | |||
8d1aa239ac | |||
6192ebd5db | |||
b0f3b28ef4 | |||
798b49d565 | |||
139a756ef9 | |||
11e9719bbb | |||
4d87fa6532 | |||
7e4347b183 | |||
bd44abb629 | |||
293eed2091 | |||
3e0246f2f2 | |||
ff08408c84 | |||
2288d47672 | |||
728b570e57 | |||
58a3d66cd3 | |||
d14f1c503f | |||
88a2bc1b7c | |||
5587ec6063 | |||
9a14daedc3 | |||
e060ceaad4 | |||
f6c77fbac0 | |||
7920f20c98 | |||
608358a9f8 | |||
8e6daa64f0 | |||
a45bcdaf49 | |||
ea9e322931 | |||
d5437413a4 | |||
dad2b345cd | |||
4fdf7b8d1d | |||
de71fb7ef4 | |||
013058dd7f | |||
315b2f8f36 | |||
a06988406a | |||
498b7fe70a | |||
0439728374 | |||
2c434feb43 | |||
e39cb73a70 | |||
62bfde18bc | |||
8149db8436 | |||
15082ccd03 | |||
54c026ec61 | |||
4193422cf6 | |||
620bfeae0d | |||
462d8b764c | |||
17e02df269 | |||
e37b051b5a | |||
69ee88ae88 | |||
544763017e | |||
92cfd989ff | |||
b224a96e08 | |||
e07e14bd8f | |||
1f06a32b30 | |||
0f7cbfb6bf | |||
57f16c23ee | |||
2f96c239ff | |||
5b68505b1a | |||
e53ebdda42 | |||
447f53a078 | |||
af4dd13ffe | |||
81b2e23d0d | |||
71662e0e4b | |||
83bc319801 | |||
3ce59432bc | |||
7d3c8daf07 | |||
3319529b98 | |||
60d52a2d3b | |||
4431a0ec23 | |||
4824958e4d | |||
49c6a648c0 | |||
28da5683e5 | |||
8dedc6f1e0 | |||
5e215907f4 | |||
129d01c396 | |||
3886cbbd5c | |||
5640744bf3 | |||
9ac62aec31 | |||
1b7b3045b8 | |||
38cc568341 | |||
6f1d012ed2 | |||
bc2e0b8be0 | |||
32f32703cb | |||
5a6c8142b9 | |||
138b7772f2 | |||
8a21c12482 | |||
f286dbc653 | |||
bfb6571ce9 | |||
fe6208f427 | |||
056ece0238 | |||
179542897c | |||
67e1940232 | |||
9dbb2e9733 | |||
8214eea7a9 | |||
6e45d3f9b0 | |||
a023d92b35 | |||
0cbf951845 | |||
39beb20245 | |||
804a4a442b | |||
f9455b1a25 | |||
a6bfdc0993 | |||
1df8d81ae4 | |||
ba67ff4a4d | |||
9219552ce3 | |||
a63fae8f5b | |||
9a9e7f7b25 | |||
257a038262 | |||
9f8dfb19ec | |||
831a79ea44 | |||
9eb69a03b1 | |||
8f67de4844 | |||
b3956db628 | |||
579138f756 | |||
478df0fac1 | |||
25309c91fc | |||
1608d815e9 | |||
62389ff744 | |||
109a232171 | |||
a591c32a24 | |||
8ab0387857 | |||
92e968462c | |||
d3a18e255b | |||
ec061adc7c | |||
b3108835bb | |||
18456153ff | |||
85c0a488cd | |||
bf1eb3b8ac | |||
54b8935aee | |||
f02abdaacf | |||
80f4d929ac | |||
152b740670 | |||
c2fccdc398 | |||
d15ab02db8 | |||
275295adc5 | |||
82c389bc56 | |||
38cf04b0d8 | |||
e46ae74209 | |||
00ac0d879e | |||
e60be87a94 | |||
3019d96ad9 | |||
4f5711c7e3 | |||
8a586fc32a | |||
b49980d169 | |||
2514cbcd51 | |||
e5a3b0e308 | |||
3cb2a897fa | |||
56c4888aea | |||
6276d17a2a | |||
a2a9953b1b | |||
1d9fd326f0 | |||
1e6ae4e3f9 | |||
5048379e68 | |||
a5277a49e7 | |||
9c9a4c115a | |||
905f31abf1 | |||
afd5935866 | |||
e3aa6e723e | |||
f54a81f7f4 | |||
9777287b92 | |||
beeeeb87ad | |||
6130ee00d5 | |||
e79699facd | |||
43417510cd | |||
e9469816d5 | |||
053ec770d3 | |||
d97857e013 | |||
a5befca2fe | |||
f3ab15a53c | |||
622d658f6b | |||
40ed67453c | |||
7d96ff2dcf | |||
e18971fd6d | |||
760ecde4c1 | |||
4d5ab91a72 | |||
06e18c528e | |||
7f713ed8e5 | |||
b3486ccfbb | |||
2f094e8e10 | |||
92037ba5b9 | |||
78bfa5dac4 | |||
0f64611057 | |||
a46915718f | |||
3adccb3ce5 | |||
dd0b4bc086 | |||
e8d8f8662e | |||
7c1a18895e | |||
dd0a06a946 | |||
f7e63ce35c | |||
b44c918022 | |||
97e8c04536 | |||
f20b9fc59f | |||
9746d26b97 | |||
fdbc2a890f | |||
1dc040d7f8 | |||
ad797c2b5e | |||
09cffecc69 | |||
487e878b88 | |||
cc11d6bfd6 | |||
8c7fc17811 | |||
a7484fa093 | |||
affd67e542 | |||
71dcb6c140 | |||
a325a75a23 | |||
30c31a406c | |||
920d41d0f5 | |||
5304de02a7 | |||
a77b93b25a | |||
88ccdc6917 | |||
859653ff72 | |||
8b71f277ff | |||
bd77f458b3 | |||
82cb17ec2b | |||
d48fdf4e02 | |||
e79aeaa93e | |||
d5167b0106 | |||
843d1f66bb | |||
48e35a88da | |||
f9303d86b0 | |||
d211cf9507 | |||
806edca24f | |||
f98e79e218 | |||
fb4f80b5f1 | |||
865f96d0da | |||
ff7cadbd85 | |||
af1dc1243d | |||
180e0fc609 | |||
2a8a8340f6 | |||
c63b9ff439 | |||
b9750e3c83 | |||
a9cdbc54c4 | |||
a8cbeb91bc | |||
4666e33845 | |||
6f5673a6a3 | |||
efc87cc6ce | |||
0f3b42ee2c | |||
da133747eb | |||
55fca5d402 | |||
b8c53bdd49 | |||
e96e5fdc74 | |||
498e45f6db | |||
098f71172d | |||
8e703b5bfc | |||
85bd985a5e | |||
4a68f6acf3 | |||
509eed587c | |||
4c080cdc06 | |||
5937afdc48 | |||
92f7263e77 | |||
955716d846 | |||
a048fced4f | |||
020905676a | |||
721441f32d | |||
26d4c2304e | |||
79638ae88d | |||
2c4a74496a | |||
3ab9bca6e6 | |||
671a696962 | |||
f29307ec47 | |||
badb5c3cfe | |||
31396569b7 | |||
c1df7461c0 | |||
971262c4a9 | |||
e6216983f6 | |||
241c9e5dcc | |||
753f163195 | |||
809b62c60d | |||
4c7cc047dc | |||
9c4f1f4963 | |||
c9c1eab014 | |||
fc7b53350a | |||
ebcf6c957d | |||
329bcbd1ce | |||
28f34be704 | |||
dc6921bea2 | |||
852837cd37 | |||
bf5ee6ea5a | |||
336b65fef9 | |||
579fd3e824 | |||
00ad96bfb3 | |||
02f7bdcd2f | |||
edd68171b5 | |||
746c910452 | |||
c7fae9b3ed | |||
b8917d84ba | |||
7aee340a8b | |||
b70f062acd | |||
1e40b60c3f | |||
9b9662ee99 | |||
9eebfc7ad1 | |||
67fe5d7dda | |||
b86b8f0ed0 | |||
0f81a03e19 | |||
364aca20a6 | |||
6261962caf | |||
3b7e49dadb | |||
ed458a61d4 | |||
74703665df | |||
c2c297d986 | |||
f82ddb3530 | |||
6b3437627d | |||
7967874597 | |||
913da73a56 | |||
3c037e0682 | |||
f64369cc9f | |||
e16ddb404f | |||
bfef79d3ee | |||
d0a3c99ef5 | |||
4d2caa129c | |||
dc9d083d89 | |||
5eac8d4bdd | |||
a4999d0286 | |||
bc757a6485 | |||
99461846a0 | |||
8fa852e4b8 | |||
57987c7215 | |||
bfa7b1d97f | |||
bab75d6af1 | |||
899a7f1531 | |||
75a0b2e7b5 | |||
ad476c2480 | |||
a75e7c6a03 | |||
0e307b5bf0 | |||
4d94fa57a6 | |||
565d659c0a | |||
171f59cf65 | |||
c3a7330e5b | |||
3817bb33dd | |||
84f149ee83 | |||
1013f32ce6 | |||
94e3c97b4e | |||
791eb9a95b |
16
.babelrc
|
@ -1,3 +1,17 @@
|
|||
{
|
||||
"presets": ["es2015"]
|
||||
"presets": [["@babel/preset-env", { "loose": true }]],
|
||||
"env": {
|
||||
"test": {
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"node": true
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
1
.browserslistrc
Normal file
|
@ -0,0 +1 @@
|
|||
> 1%
|
3
.codebeatignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
public/**
|
||||
webpack.config.*.js
|
||||
*.js
|
4
.codecov.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
coverage:
|
||||
parsers:
|
||||
javascript:
|
||||
enable_partials: yes
|
3
.eslintignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
node_modules/
|
||||
types/
|
||||
public/
|
27
.eslintrc
|
@ -1,27 +0,0 @@
|
|||
{
|
||||
"ecmaFeatures": {
|
||||
"modules": true
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"rules": {
|
||||
"quotes": [2, "single"],
|
||||
"strict": [2, "never"],
|
||||
"indent": ["error", 2, {"SwitchCase": 1}],
|
||||
"eol-last": "off",
|
||||
"arrow-body-style": "off",
|
||||
"no-underscore-dangle": "off",
|
||||
"no-new": 0,
|
||||
"max-len": "off",
|
||||
"no-console": "off",
|
||||
"consistent-return": "off",
|
||||
"no-param-reassign": ["error", { "props": false }],
|
||||
"no-unused-vars": ["error", { "args": "none" }],
|
||||
"no-lonely-if": "off",
|
||||
"class-methods-use-this": "error",
|
||||
"react/require-extension": "off",
|
||||
},
|
||||
}
|
123
.eslintrc.json
Normal file
|
@ -0,0 +1,123 @@
|
|||
{
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": ["@typescript-eslint", "prettier", "sort-class-members"],
|
||||
"extends": [
|
||||
"airbnb-base",
|
||||
"airbnb-typescript",
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:compat/recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"env": {
|
||||
"es6": true,
|
||||
"browser": true,
|
||||
"mocha": true,
|
||||
"cypress/globals": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json",
|
||||
"ecmaVersion": 2020
|
||||
},
|
||||
"rules": {
|
||||
"import/prefer-default-export": "off",
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
{
|
||||
"devDependencies": true
|
||||
}
|
||||
],
|
||||
"no-console": [
|
||||
"warn",
|
||||
{
|
||||
"allow": ["warn", "error"]
|
||||
}
|
||||
],
|
||||
"no-plusplus": "off",
|
||||
"no-unused-expressions": "off",
|
||||
"no-underscore-dangle": "off",
|
||||
"consistent-return": "off",
|
||||
"import/no-useless-path-segments": "warn",
|
||||
"prefer-destructuring": [
|
||||
"warn",
|
||||
{
|
||||
"array": false,
|
||||
"object": true
|
||||
}
|
||||
],
|
||||
"curly": ["error", "all"],
|
||||
"newline-before-return": "error",
|
||||
"sort-class-members/sort-class-members": [
|
||||
2,
|
||||
{
|
||||
"order": [
|
||||
"[static-properties]",
|
||||
"[static-methods]",
|
||||
"[properties]",
|
||||
"[conventional-private-properties]",
|
||||
"constructor",
|
||||
"[methods]",
|
||||
"[conventional-private-methods]"
|
||||
],
|
||||
"accessorPairPositioning": "getThenSet"
|
||||
}
|
||||
],
|
||||
"lines-between-class-members": "off",
|
||||
"@typescript-eslint/no-namespace": "off",
|
||||
"react/jsx-filename-extension": [0]
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.test.ts"],
|
||||
"env": {
|
||||
"mocha": true
|
||||
},
|
||||
"rules": {
|
||||
"no-restricted-syntax": "off",
|
||||
"compat/compat": "off",
|
||||
"no-new": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/no-unused-expressions": "off",
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"error",
|
||||
{
|
||||
"selector": "default",
|
||||
"format": ["camelCase", "PascalCase", "UPPER_CASE"],
|
||||
"leadingUnderscore": "allow"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["cypress/**"],
|
||||
"plugins": ["cypress"],
|
||||
"rules": {
|
||||
"no-unused-vars": "warn"
|
||||
},
|
||||
"env": {
|
||||
"cypress/globals": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"polyfills": [
|
||||
"Array.from",
|
||||
"Array.prototype.find",
|
||||
"Array.prototype.includes",
|
||||
"Symbol",
|
||||
"Symbol.iterator",
|
||||
"DOMTokenList",
|
||||
"Object.assign",
|
||||
"CustomEvent",
|
||||
"Element.prototype.classList",
|
||||
"Element.prototype.closest",
|
||||
"Element.prototype.dataset"
|
||||
],
|
||||
"import/resolver": {
|
||||
"node": {
|
||||
"extensions": [".js", ".ts"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
64
.gitattributes
vendored
Normal file
|
@ -0,0 +1,64 @@
|
|||
## GITATTRIBUTES FOR WEB PROJECTS
|
||||
#
|
||||
# These settings are for any web project.
|
||||
#
|
||||
# Details per file setting:
|
||||
# text These files should be normalized (i.e. convert CRLF to LF).
|
||||
# binary These files are binary and should be left untouched.
|
||||
#
|
||||
# Note that binary is a macro for -text -diff.
|
||||
######################################################################
|
||||
|
||||
# Auto detect
|
||||
## Handle line endings automatically for files detected as
|
||||
## text and leave all files detected as binary untouched.
|
||||
## This will handle all files NOT defined below.
|
||||
* text eol=lf
|
||||
|
||||
# Source code
|
||||
*.css text eol=lf
|
||||
*.html text diff=html eol=lf
|
||||
*.js text eol=lf
|
||||
*.json text eol=lf
|
||||
*.scss text diff=css eol=lf
|
||||
*.ts text eol=lf
|
||||
|
||||
# Documentation
|
||||
*.md text eol=lf
|
||||
*.txt text eol=lf
|
||||
AUTHORS text eol=lf
|
||||
CHANGELOG text eol=lf
|
||||
CHANGES text eol=lf
|
||||
CONTRIBUTING text eol=lf
|
||||
COPYING text eol=lf
|
||||
copyright text eol=lf
|
||||
*COPYRIGHT* text eol=lf
|
||||
INSTALL text eol=lf
|
||||
license text eol=lf
|
||||
LICENSE text eol=lf
|
||||
NEWS text eol=lf
|
||||
readme text eol=lf
|
||||
*README* text eol=lf
|
||||
TODO text eol=lf
|
||||
|
||||
# Linters
|
||||
.eslintrc text eol=lf
|
||||
.stylelintrc text eol=lf
|
||||
|
||||
# Configs
|
||||
.babelrc text eol=lf
|
||||
.browserslistrc text eol=lf
|
||||
.editorconfig text eol=lf
|
||||
.env text eol=lf
|
||||
.gitattributes text eol=lf
|
||||
.gitconfig text eol=lf
|
||||
package-lock.json text -diff eol=lf
|
||||
*.npmignore text eol=lf
|
||||
*.yaml text eol=lf
|
||||
*.yml text eol=lf
|
||||
browserslist text eol=lf
|
||||
|
||||
# Graphics
|
||||
# SVG treated as an asset (binary) by default.
|
||||
*.svg text eol=lf
|
||||
*.png binary
|
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: feature request
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
28
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
## Description
|
||||
|
||||
<!--- Describe your changes in detail -->
|
||||
<!--- Why is this change required? What problem does it solve? -->
|
||||
<!--- If it fixes an open issue, please link to the issue here. -->
|
||||
|
||||
## Screenshots (if appropriate)
|
||||
|
||||
## Types of changes
|
||||
|
||||
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
|
||||
|
||||
- [ ] Chore (tooling change or documentation change)
|
||||
- [ ] Refactor (non-breaking change which maintains existing functionality)
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
|
||||
## Checklist
|
||||
|
||||
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
||||
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
|
||||
- [ ] My code follows the code style of this project.
|
||||
- [ ] I have added new tests for the bug I fixed/the new feature I added.
|
||||
- [ ] I have modified existing tests for the bug I fixed/the new feature I added.
|
||||
- [ ] My change requires a change to the documentation.
|
||||
- [ ] I have updated the documentation accordingly.
|
BIN
.github/actions-scripts/__snapshots__/chrome-win32.png
vendored
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
.github/actions-scripts/__snapshots__/edge-win32.png
vendored
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
.github/actions-scripts/__snapshots__/firefox-darwin.png
vendored
Executable file
After Width: | Height: | Size: 47 KiB |
BIN
.github/actions-scripts/__snapshots__/firefox-win32.png
vendored
Normal file
After Width: | Height: | Size: 104 KiB |
BIN
.github/actions-scripts/__snapshots__/ie-win32.png
vendored
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
.github/actions-scripts/__snapshots__/puppeteer-darwin.png
vendored
Executable file
After Width: | Height: | Size: 297 KiB |
BIN
.github/actions-scripts/__snapshots__/safari-darwin.png
vendored
Normal file
After Width: | Height: | Size: 284 KiB |
14
.github/actions-scripts/polyfills-sync.js
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
const { readFileSync } = require('fs');
|
||||
const path = require('path');
|
||||
const assert = require('assert');
|
||||
|
||||
const readme = readFileSync(path.resolve(__dirname, '../../README.md'), 'utf8');
|
||||
|
||||
const polyfillsFromDocs = /^```polyfills\s*\n([^`]+)\n^```/m
|
||||
.exec(readme)[1]
|
||||
.split('\n')
|
||||
.map(v => v.trim())
|
||||
.sort();
|
||||
// @ts-ignore
|
||||
const polyfillsFromSettings = require('../../.eslintrc.json').settings.polyfills.sort();
|
||||
assert.deepStrictEqual(polyfillsFromDocs, polyfillsFromSettings);
|
92
.github/actions-scripts/puppeteer.js
vendored
Normal file
|
@ -0,0 +1,92 @@
|
|||
const { readFileSync, writeFileSync, mkdirSync } = require('fs');
|
||||
const path = require('path');
|
||||
const { once } = require('events');
|
||||
|
||||
const puppeteer = require('puppeteer');
|
||||
const pixelmatch = require('pixelmatch');
|
||||
const { PNG } = require('pngjs');
|
||||
|
||||
const server = require('../../server');
|
||||
|
||||
async function test() {
|
||||
const browser = await puppeteer.launch();
|
||||
const page = await browser.newPage();
|
||||
const artifactsPath = 'screenshot';
|
||||
const snapshotName = `puppeteer-${process.platform}.png`;
|
||||
let error;
|
||||
let pixelDifference;
|
||||
let diff;
|
||||
|
||||
if (!server.listening) await once(server, 'listening');
|
||||
|
||||
try {
|
||||
page.on('console', msg => {
|
||||
if (msg.type() === 'error') throw new Error(msg.text());
|
||||
});
|
||||
page.on('pageerror', err => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
await page.goto(`http://127.0.0.1:${server.address().port}`, {
|
||||
waitUntil: 'networkidle2',
|
||||
});
|
||||
await page.setViewport({ width: 640, height: 1000 });
|
||||
await page.waitForTimeout(500); // Wait for resize to complete
|
||||
await page.click('label[for="choices-single-custom-templates"]');
|
||||
await page.keyboard.press('ArrowDown');
|
||||
await page.keyboard.press('ArrowDown');
|
||||
|
||||
mkdirSync(artifactsPath, { recursive: true });
|
||||
const imageBuffer = await page.screenshot({
|
||||
path: path.join(artifactsPath, snapshotName),
|
||||
fullPage: true,
|
||||
});
|
||||
|
||||
// compare with snapshot
|
||||
const screenshot = PNG.sync.read(imageBuffer);
|
||||
const snapshot = PNG.sync.read(
|
||||
readFileSync(path.resolve(__dirname, `./__snapshots__/${snapshotName}`)),
|
||||
);
|
||||
const { width, height } = screenshot;
|
||||
diff = new PNG({ width, height });
|
||||
pixelDifference = pixelmatch(
|
||||
screenshot.data,
|
||||
snapshot.data,
|
||||
diff.data,
|
||||
width,
|
||||
height,
|
||||
{
|
||||
threshold: 0.6,
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
error = err;
|
||||
} finally {
|
||||
if (diff) {
|
||||
writeFileSync(path.join(artifactsPath, 'diff-' + snapshotName), PNG.sync.write(diff));
|
||||
}
|
||||
await Promise.all([
|
||||
browser.close(),
|
||||
new Promise(resolve => server.close(resolve)),
|
||||
]);
|
||||
}
|
||||
|
||||
if (pixelDifference > 200) {
|
||||
console.error(
|
||||
`Snapshot is different from screenshot by ${pixelDifference} pixels`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
if (error) process.exit(1);
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
process.once('uncaughtException', err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
setImmediate(test);
|
155
.github/actions-scripts/selenium.js
vendored
Normal file
|
@ -0,0 +1,155 @@
|
|||
const path = require('path');
|
||||
const { readFileSync, writeFileSync, mkdirSync } = require('fs');
|
||||
const { once } = require('events');
|
||||
|
||||
const pixelmatch = require('pixelmatch');
|
||||
const { PNG } = require('pngjs');
|
||||
const {
|
||||
Builder,
|
||||
By,
|
||||
Key,
|
||||
until,
|
||||
Capabilities,
|
||||
logging,
|
||||
} = require('selenium-webdriver');
|
||||
|
||||
const server = require('../../server');
|
||||
|
||||
async function test() {
|
||||
let pixelDifference;
|
||||
let error;
|
||||
|
||||
let capabilities;
|
||||
switch (process.env.BROWSER) {
|
||||
case 'ie':
|
||||
capabilities = Capabilities.ie();
|
||||
capabilities.set('ignoreProtectedModeSettings', true);
|
||||
capabilities.set('ignoreZoomSetting', true);
|
||||
capabilities.set('ie.options', {
|
||||
enableFullPageScreenshot: true,
|
||||
ensureCleanSession: true,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'edge':
|
||||
capabilities = Capabilities.edge();
|
||||
break;
|
||||
|
||||
case 'safari':
|
||||
capabilities = Capabilities.safari();
|
||||
capabilities.set('safari.options', { technologyPreview: false });
|
||||
break;
|
||||
|
||||
case 'firefox': {
|
||||
capabilities = Capabilities.firefox().setLoggingPrefs({ browser: 'ALL' });
|
||||
break;
|
||||
}
|
||||
case 'chrome': {
|
||||
capabilities = Capabilities.chrome().setLoggingPrefs({ browser: 'ALL' });
|
||||
capabilities.set('chromeOptions', {
|
||||
args: ['--headless', '--no-sandbox', '--disable-gpu'],
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let driver = await new Builder().withCapabilities(capabilities).build();
|
||||
|
||||
if (!server.listening) await once(server, 'listening');
|
||||
|
||||
try {
|
||||
await driver.get(`http://127.0.0.1:${server.address().port}`);
|
||||
|
||||
// wait for last choice to init
|
||||
await driver.wait(
|
||||
until.elementLocated(By.css('#reset-multiple ~ .choices__list')),
|
||||
10000,
|
||||
'waiting for all Choices instances to init',
|
||||
);
|
||||
|
||||
// Resize window
|
||||
await driver
|
||||
.manage()
|
||||
.window()
|
||||
.maximize();
|
||||
await driver
|
||||
.manage()
|
||||
.window()
|
||||
// magic numbers here to make sure all demo page are fit inside
|
||||
.setRect({ x: 0, y: 0, width: 630, height: 4000 });
|
||||
|
||||
// and click on press space on it, so it should open choices
|
||||
await driver
|
||||
.findElement(By.css('#reset-multiple ~ .choices__list button'))
|
||||
.sendKeys(Key.SPACE);
|
||||
await driver.sleep(1000);
|
||||
|
||||
// take screenshot
|
||||
const image = await driver.takeScreenshot();
|
||||
const imageBuffer = Buffer.from(image, 'base64');
|
||||
|
||||
const snapshotName = `${process.env.BROWSER}-${process.platform}.png`;
|
||||
const artifactsPath = 'screenshot';
|
||||
mkdirSync(artifactsPath, { recursive: true });
|
||||
|
||||
writeFileSync(path.join(artifactsPath, snapshotName), imageBuffer);
|
||||
|
||||
// compare with snapshot
|
||||
const screenshot = PNG.sync.read(imageBuffer);
|
||||
const snapshot = PNG.sync.read(
|
||||
readFileSync(path.resolve(__dirname, `./__snapshots__/${snapshotName}`)),
|
||||
);
|
||||
const { width, height } = screenshot;
|
||||
const diff = new PNG({ width, height });
|
||||
pixelDifference = pixelmatch(
|
||||
screenshot.data,
|
||||
snapshot.data,
|
||||
diff.data,
|
||||
width,
|
||||
height,
|
||||
{
|
||||
threshold: 1,
|
||||
},
|
||||
);
|
||||
writeFileSync(path.join(artifactsPath, 'diff.png'), PNG.sync.write(diff));
|
||||
|
||||
// getting console logs
|
||||
// ensure no errors in console (only supported in Chrome currently)
|
||||
if (process.env.BROWSER === 'chrome') {
|
||||
const entries = await driver
|
||||
.manage()
|
||||
.logs()
|
||||
.get(logging.Type.BROWSER);
|
||||
if (
|
||||
Array.isArray(entries) &&
|
||||
entries.some(entry => entry.level.name_ === 'SEVERE')
|
||||
)
|
||||
throw new Error(JSON.stringify(entries));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
error = err;
|
||||
} finally {
|
||||
await Promise.all([
|
||||
driver.quit(),
|
||||
new Promise(resolve => server.close(resolve)),
|
||||
]);
|
||||
}
|
||||
if (pixelDifference > 200) {
|
||||
console.error(
|
||||
`Snapshot is different from screenshot by ${pixelDifference} pixels`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
if (error) process.exit(1);
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
process.once('uncaughtException', err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
setImmediate(test);
|
29
.github/release-drafter.yml
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
name-template: 'Draft (next release)'
|
||||
tag-template: 'v$NEXT_PATCH_VERSION'
|
||||
sort-direction: descending
|
||||
exclude-labels:
|
||||
- 'skip-changelog'
|
||||
- 'release'
|
||||
categories:
|
||||
- title: '🚨 Breaking changes'
|
||||
labels:
|
||||
- 'breaking change'
|
||||
- title: '🚀 Features'
|
||||
labels:
|
||||
- 'feature'
|
||||
- 'enhancement'
|
||||
- title: '🐛 Bug Fixes'
|
||||
labels:
|
||||
- 'bugfix'
|
||||
- title: '🔧 Maintenance'
|
||||
labels:
|
||||
- 'chore'
|
||||
- 'housekeeping'
|
||||
- 'refactor'
|
||||
- 'documentation'
|
||||
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
|
||||
template: |
|
||||
# Changes
|
||||
$CHANGES
|
||||
# Contributors
|
||||
$CONTRIBUTORS
|
131
.github/workflows/browsers.yml
vendored
Normal file
|
@ -0,0 +1,131 @@
|
|||
name: Browsers
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'package-lock.json'
|
||||
- '.browserslistrc'
|
||||
- '.babelrc'
|
||||
- 'webpack.config.*'
|
||||
- 'public/index.html'
|
||||
- '.github/actions-scripts/__snapshots__/**'
|
||||
- '.github/workflows/browsers.yml'
|
||||
|
||||
jobs:
|
||||
selenium:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest, macos-latest]
|
||||
browser: [edge, firefox, safari, chrome]
|
||||
exclude:
|
||||
- os: windows-latest
|
||||
browser: safari
|
||||
- os: macos-latest
|
||||
browser: edge
|
||||
- os: macos-latest
|
||||
browser: chrome
|
||||
# Safari workaround is not working in Catalina
|
||||
- browser: safari
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Dump GitHub context
|
||||
env:
|
||||
GITHUB_CONTEXT: ${{ toJson(github) }}
|
||||
run: echo "$GITHUB_CONTEXT"
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.x
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.OS }}-build-${{ matrix.browser }}
|
||||
restore-keys: |
|
||||
${{ runner.OS }}-build-${{ env.cache-name }}-
|
||||
${{ runner.OS }}-build-
|
||||
${{ runner.OS }}-
|
||||
|
||||
- run: |
|
||||
npm ci
|
||||
npm run build
|
||||
env:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
HUSKY_SKIP_INSTALL: true
|
||||
|
||||
# install drivers
|
||||
- name: Enable Safari Driver
|
||||
run: |
|
||||
# Workaround for `sudo safardriver --enable` not working:
|
||||
# https://github.com/web-platform-tests/wpt/issues/19845
|
||||
# https://github.com/web-platform-tests/wpt/blob/master/tools/ci/azure/install_safari.yml
|
||||
mkdir -p ~/Library/WebDriver/
|
||||
curl https://raw.githubusercontent.com/web-platform-tests/wpt/master/tools/ci/azure/com.apple.Safari.plist -o ~/Library/WebDriver/com.apple.Safari.plist
|
||||
defaults write com.apple.Safari WebKitJavaScriptCanOpenWindowsAutomatically 1
|
||||
# sudo safaridriver --enable
|
||||
if: matrix.browser == 'safari'
|
||||
|
||||
- run: |
|
||||
brew install --cask firefox
|
||||
brew install geckodriver
|
||||
if: matrix.browser == 'firefox' && matrix.os == 'macos-latest'
|
||||
|
||||
- run: echo "$env:GeckoWebDriver" >> $GITHUB_PATH
|
||||
if: matrix.browser == 'firefox' && matrix.os == 'windows-latest'
|
||||
|
||||
- run: echo "$env:EdgeWebDriver" >> $GITHUB_PATH
|
||||
if: matrix.browser == 'edge' && matrix.os == 'windows-latest'
|
||||
|
||||
- run: echo "$env:ChromeWebDriver" >> $GITHUB_PATH
|
||||
if: matrix.browser == 'chrome' && matrix.os == 'windows-latest'
|
||||
|
||||
- run: npm i --no-optional --no-audit selenium-webdriver pixelmatch pngjs
|
||||
- run: node .github/actions-scripts/selenium.js
|
||||
env:
|
||||
BROWSER: ${{ matrix.browser }}
|
||||
PORT: 0
|
||||
NODE_ENV: production # prevent watching
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: failure()
|
||||
with:
|
||||
name: screenshot-${{ matrix.browser }}-${{ matrix.os }}
|
||||
path: screenshot
|
||||
|
||||
puppeteer:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.OS }}-build-puppeteer
|
||||
restore-keys: |
|
||||
${{ runner.OS }}-build-puppeteer
|
||||
- run: |
|
||||
npm ci
|
||||
npm run build
|
||||
env:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
HUSKY_SKIP_INSTALL: true
|
||||
- run: npm i --no-optional --no-audit puppeteer pixelmatch pngjs
|
||||
- run: node .github/actions-scripts/puppeteer.js
|
||||
env:
|
||||
PORT: 0
|
||||
NODE_ENV: production # prevent watching
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: failure()
|
||||
with:
|
||||
name: screenshot-puppeteer-darwin
|
||||
path: screenshot
|
60
.github/workflows/build-and-test.yml
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
name: Build and test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18
|
||||
- name: Build and run all tests
|
||||
run: |
|
||||
npm ci
|
||||
npm run build
|
||||
npx bundlesize
|
||||
npm run test:unit:coverage
|
||||
npm run test:e2e
|
||||
env:
|
||||
CI: true
|
||||
CI_REPO_NAME: ${{ github.event.repository.name }}
|
||||
CI_REPO_OWNER: ${{ github.event.organization.login }}
|
||||
CI_COMMIT_SHA: ${{ github.sha }}
|
||||
GIT_COMMIT: ${{ github.sha }}
|
||||
CI_BRANCH: ${{ github.head_ref }}
|
||||
BUNDLESIZE_GITHUB_TOKEN: ${{secrets.BUNDLESIZE_GITHUB_TOKEN}}
|
||||
FORCE_COLOR: 2
|
||||
HUSKY_SKIP_INSTALL: true
|
||||
##
|
||||
## Disabling for now. There does not appear to be a secure way to do this
|
||||
## with protected branches. See discussion:
|
||||
## https://github.community/t/how-to-push-to-protected-branches-in-a-github-action/16101
|
||||
##
|
||||
# - name: Commit built files
|
||||
# run: |
|
||||
# git config --local user.email "action@github.com"
|
||||
# git config --local user.name "GitHub Action"
|
||||
# git commit -m "Update build files 🏗" -a || echo "No changes to commit" && exit 0
|
||||
# - name: Push changes
|
||||
# uses: ad-m/github-push-action@master
|
||||
# with:
|
||||
# github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
run: bash <(curl -s https://codecov.io/bash)
|
||||
-f ./coverage/lcov.info
|
||||
-B ${{ github.head_ref }}
|
||||
-C ${{ github.sha }}
|
||||
-Z || echo 'Codecov upload failed'
|
||||
env:
|
||||
CI: true
|
||||
GITLAB_CI: true # pretend we are GitLab CI, while Codecov adding support for Github Actions
|
||||
CODECOV_ENV: github-action
|
||||
CODECOV_TOKEN: ${{secrets.CODECOV_TOKEN}}
|
42
.github/workflows/bundlesize.yml
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
name: Bundle size checks
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/scripts/**'
|
||||
- 'src/styles/**'
|
||||
- 'package-lock.json'
|
||||
- '.browserslistrc'
|
||||
|
||||
jobs:
|
||||
measure:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install dependencies and build
|
||||
run: |
|
||||
npm ci
|
||||
npm run build
|
||||
env:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
HUSKY_SKIP_INSTALL: true
|
||||
|
||||
# we don't need to build here, as even minized assets expected to be commited
|
||||
|
||||
- run: npx bundlesize
|
||||
env:
|
||||
CI: true
|
||||
BUNDLESIZE_GITHUB_TOKEN: ${{secrets.BUNDLESIZE_GITHUB_TOKEN}}
|
||||
CI_REPO_NAME: ${{ github.event.repository.name }}
|
||||
CI_REPO_OWNER: ${{ github.event.organization.login }}
|
||||
CI_COMMIT_SHA: ${{ github.event.after }}
|
||||
GIT_COMMIT: ${{ github.event.after }}
|
||||
CI_BRANCH: ${{ github.head_ref }}
|
||||
FORCE_COLOR: 2
|
32
.github/workflows/deploy-pages.yml
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
name: Deploy Pages
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy-gh-pages:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18
|
||||
registry-url: https://registry.npmjs.org/
|
||||
- name: Build
|
||||
run: |
|
||||
npm ci
|
||||
npm run build
|
||||
rm -rf public/test
|
||||
env:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
HUSKY_SKIP_INSTALL: true
|
||||
- name: Deploy
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PUBLISH_BRANCH: gh-pages
|
||||
PUBLISH_DIR: ./public
|
24
.github/workflows/deployment.yml
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
name: Publish to npm
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
publish-npm:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18
|
||||
registry-url: https://registry.npmjs.org/
|
||||
- run: npm ci
|
||||
env:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
HUSKY_SKIP_INSTALL: true
|
||||
- run: npm publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
71
.github/workflows/e2e-tests.yml
vendored
Normal file
|
@ -0,0 +1,71 @@
|
|||
name: End-to-end tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'package-lock.json'
|
||||
- '.browserslistrc'
|
||||
- '.babelrc'
|
||||
- 'webpack.config.*'
|
||||
- 'public/test/**'
|
||||
- 'cypress/**'
|
||||
- '.github/workflows/e2e-tests.yml'
|
||||
|
||||
jobs:
|
||||
test-e2e:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI: true
|
||||
TERM: xterm-256color
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.x
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.OS }}-build-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.OS }}-build-${{ env.cache-name }}-
|
||||
${{ runner.OS }}-build-
|
||||
${{ runner.OS }}-
|
||||
|
||||
- name: Get Cypress info
|
||||
id: cypress-info
|
||||
run: |
|
||||
echo ::set-output name=version::$(jq -r .devDependencies.cypress ./package.json)
|
||||
echo ::set-output name=cache::$(npx cypress cache path)
|
||||
env:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
- name: Cache Cypress cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ steps.cypress-info.outputs.cache }}
|
||||
key: ${{ runner.OS }}-cypress-${{ steps.cypress-info.outputs.version }}
|
||||
restore-keys: |
|
||||
${{ runner.OS }}-cypress-${{ steps.cypress-info.outputs.version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
env:
|
||||
HUSKY_SKIP_INSTALL: true
|
||||
|
||||
- name: run Cypress (with or without recording)
|
||||
# if we have ran out of free Cypress recordings, run Cypress with recording switched off
|
||||
run: npm exec -- run-p --race start cypress:ci || npm exec -- run-p --race start cypress:run
|
||||
env:
|
||||
NODE_ENV: production # prevent watching
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
DEBUG: commit-info,cypress:server:record
|
||||
# https://docs.cypress.io/guides/guides/continuous-integration.html#Environment-variables
|
||||
COMMIT_INFO_BRANCH: ${{ github.head_ref }}
|
||||
COMMIT_INFO_AUTHOR: ${{ github.event.sender.login }}
|
||||
COMMIT_INFO_SHA: ${{ github.event.after }}
|
41
.github/workflows/lint.yml
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
name: Code linting
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/scripts/**'
|
||||
- 'src/styles/**'
|
||||
- package-lock.json
|
||||
- '.browserslistrc'
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
env:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
HUSKY_SKIP_INSTALL: true
|
||||
|
||||
- name: run eslint
|
||||
run: npm run lint:js
|
||||
|
||||
## Can't use same eslint config for TypeScript and JavaScript
|
||||
## TypeScript rules cause rule definition not found errors
|
||||
## Can be re-enabled if this is resolved: https://github.com/eslint/eslint/issues/14851
|
||||
# - name: Lint JS bundle
|
||||
# run: |
|
||||
# npm run js:build
|
||||
# npx eslint --no-ignore ./public/assets/scripts/*.js
|
||||
|
||||
- name: run stylelint
|
||||
run: npm run lint:scss
|
23
.github/workflows/polyfills-sync.yml
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
name: Polyfills documentation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'README.md'
|
||||
- '.browserslistrc'
|
||||
- '.eslintrc.json'
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Check Polyfills documentation and settings sync
|
||||
run: node .github/actions-scripts/polyfills-sync.js
|
14
.github/workflows/release-drafter.yml
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
name: Release drafter
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
update-draft-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: release-drafter/release-drafter@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
42
.github/workflows/unit-tests.yml
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
name: Unit tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/scripts/**'
|
||||
- package-lock.json
|
||||
- '.browserslistrc'
|
||||
|
||||
jobs:
|
||||
test-unit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install --no-optional --no-audit --ignore-scripts
|
||||
env:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
HUSKY_SKIP_INSTALL: true
|
||||
|
||||
- run: npm run test:unit:coverage
|
||||
env:
|
||||
FORCE_COLOR: 2
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
run: bash <(curl -s https://codecov.io/bash)
|
||||
-f ./coverage/lcov.info
|
||||
-B ${{ github.head_ref }}
|
||||
-C ${{ github.sha }}
|
||||
-Z || echo 'Codecov upload failed'
|
||||
env:
|
||||
CI: true
|
||||
GITLAB_CI: true # pretend we are GitLab CI, while Codecov adding support for Github Actions
|
||||
CODECOV_ENV: github-action
|
||||
CODECOV_TOKEN: ${{secrets.CODECOV_TOKEN}}
|
7
.gitignore
vendored
|
@ -1,11 +1,12 @@
|
|||
node_modules
|
||||
npm-debug.log
|
||||
.DS_Store
|
||||
.gitignore
|
||||
.idea
|
||||
.vscode
|
||||
package-lock.json
|
||||
|
||||
# Test
|
||||
tests/reports
|
||||
tests/results
|
||||
.nyc_output
|
||||
coverage
|
||||
cypress/videos
|
||||
cypress/screenshots
|
||||
|
|
6
.huskyrc
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"skipCI": true,
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
}
|
8
.mocharc.yml
Normal file
|
@ -0,0 +1,8 @@
|
|||
require:
|
||||
- 'ts-node/register'
|
||||
- './config/jsdom.js'
|
||||
exit: true
|
||||
spec: src/**/**/*.test.ts
|
||||
extension:
|
||||
- ts
|
||||
- js
|
20
.prettierrc.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"endOfLine": "lf",
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.svg"],
|
||||
"options": {
|
||||
"parser": "html",
|
||||
"htmlWhitespaceSensitivity": "ignore"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["public/*.html"],
|
||||
"options": {
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
6
.stylelintrc.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"extends": "stylelint-config-standard-scss",
|
||||
"rules": {
|
||||
"declaration-block-no-redundant-longhand-properties": null
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "5"
|
||||
before_install:
|
||||
- '[ "${TRAVIS_NODE_VERSION}" != "0.8" ] || npm install -g npm@1.4.28'
|
||||
- npm install -g npm@latest
|
||||
install:
|
||||
- npm install
|
||||
script: npm run test
|
17
.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
// we enforce ESLint rules, so, recommend extension
|
||||
"dbaeumer.vscode-eslint",
|
||||
// we use prettier, so, recommend extension
|
||||
"esbenp.prettier-vscode",
|
||||
// we are on GitHub, so, recommend extension
|
||||
"github.vscode-pull-request-github",
|
||||
// needed for our configured debug configuration with Chrome
|
||||
"msjsdiag.debugger-for-chrome"
|
||||
// Mocha recommended - https://mochajs.org/#mocha-sidebar-vs-code,
|
||||
// but it's buggy
|
||||
// "maty.vscode-mocha-sidebar"
|
||||
]
|
||||
}
|
70
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome",
|
||||
"preLaunchTask": "buildAndWatch",
|
||||
"url": "http://localhost:3001",
|
||||
"webRoot": "${workspaceFolder}",
|
||||
"sourceMapPathOverrides": {
|
||||
"webpack://Choices/*": "${workspaceFolder}/*"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Mocha Current File",
|
||||
"program": "${workspaceFolder}/node_modules/mocha/bin/mocha",
|
||||
"args": ["-u", "bdd", "--timeout", "999999", "--colors", "${file}"],
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"env": {
|
||||
"NODE_ENV": "test"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Mocha All",
|
||||
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
|
||||
"args": ["-u", "bdd", "--timeout", "999999", "--colors"],
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"env": {
|
||||
"NODE_ENV": "test"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Cypress Current File",
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/cypress",
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceFolder}\\node_modules\\.bin\\cypress.cmd"
|
||||
},
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"--headed",
|
||||
"--no-exit",
|
||||
"--browser=electron",
|
||||
"--port",
|
||||
"9898",
|
||||
"--spec"
|
||||
],
|
||||
"protocol": "legacy",
|
||||
"port": 9898,
|
||||
"program": "${file}",
|
||||
"console": "integratedTerminal",
|
||||
"preLaunchTask": "buildAndWatch",
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"timeout": 999999999999999,
|
||||
"autoAttachChildProcesses": false,
|
||||
"env": {
|
||||
"NODE_ENV": "test"
|
||||
// "DEBUG": "cypress:*"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
72
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,72 @@
|
|||
{
|
||||
"eslint.enable": true,
|
||||
// prevent watch task failures on lint errors
|
||||
"eslint.autoFixOnSave": true,
|
||||
// switch off default VSCode formatting rules
|
||||
"javascript.format.enable": false,
|
||||
// Javascript prettier runs via ESLint
|
||||
"prettier.disableLanguages": ["javascript"],
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[html]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[scss]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.formatOnSave": false
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/node_modules": true,
|
||||
"public/assets": true,
|
||||
"**/coverage": true
|
||||
},
|
||||
// Mocha Sidebar settings
|
||||
"mocha.env": {
|
||||
"NODE_ENV": "test"
|
||||
},
|
||||
"mocha.files.glob": "src/scripts/**/*.test.js",
|
||||
"mocha.requires": ["@babel/register", "./config/jsdom.js"],
|
||||
// for Windows collaborators
|
||||
"files.eol": "\n",
|
||||
"files.encoding": "utf8",
|
||||
// associations for some files this project is using
|
||||
"files.associations": {
|
||||
".browserslistrc": "gitignore",
|
||||
".huskyrc": "jsonc",
|
||||
".npmrc": "ini"
|
||||
},
|
||||
// We use NPM as package manager
|
||||
"npm.packageManager": "npm",
|
||||
"npm.autoDetect": "on",
|
||||
"npm.fetchOnlinePackageInfo": true,
|
||||
"eslint.packageManager": "npm",
|
||||
"json.schemas": [
|
||||
// Cypress related settings - https://docs.cypress.io/guides/tooling/intelligent-code-completion.html#Features-1
|
||||
{
|
||||
"fileMatch": ["cypress.json"],
|
||||
"url": "https://on.cypress.io/cypress.schema.json"
|
||||
},
|
||||
// Husky config file
|
||||
{
|
||||
"fileMatch": [".huskyrc"],
|
||||
"url": "http://json.schemastore.org/huskyrc"
|
||||
},
|
||||
// Prettier config
|
||||
{
|
||||
"fileMatch": [".prettierrc.json"],
|
||||
"url": "http://json.schemastore.org/prettierrc"
|
||||
}
|
||||
],
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"stylelint.validate": [
|
||||
"css",
|
||||
"less",
|
||||
"postcss",
|
||||
"scss"
|
||||
]
|
||||
}
|
87
.vscode/tasks.json
vendored
Normal file
|
@ -0,0 +1,87 @@
|
|||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"label": "buildAndWatch",
|
||||
"script": "js:watch",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"isBackground": true,
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "dedicated",
|
||||
"showReuseMessage": true,
|
||||
"clear": false
|
||||
},
|
||||
"problemMatcher": [
|
||||
"$eslint-stylish",
|
||||
{
|
||||
"owner": "webpack",
|
||||
"fileLocation": "absolute",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^Module build failed \\(from (\\.+)\\)",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3
|
||||
},
|
||||
{
|
||||
"regexp": "\\s*TS\\d+:\\s*(.*)",
|
||||
"message": 1
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"source": "webpack",
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": "^Listening at",
|
||||
"endsPattern": "Compiled successfully\\."
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "css:build",
|
||||
"group": "build",
|
||||
"problemMatcher": ["$node-sass"]
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "lint",
|
||||
"problemMatcher": ["$eslint-stylish"]
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "build",
|
||||
"group": "build"
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "test",
|
||||
"group": "test"
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "test:e2e",
|
||||
"group": "test"
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "test:unit",
|
||||
"group": "test"
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "cypress:open",
|
||||
"isBackground": true
|
||||
}
|
||||
]
|
||||
}
|
|
@ -5,7 +5,8 @@ In lieu of a formal styleguide, take care to maintain the existing coding style
|
|||
| Task | Usage |
|
||||
| -------------------- | ------------------------------------------------------------ |
|
||||
| `npm run start` | Fire up local server for development |
|
||||
| `npm run test` | Run sequence of tests once |
|
||||
| `npm run test:unit` | Run sequence of unit tests once |
|
||||
| `npm run test:e2e` | Run sequence of integration tests once |
|
||||
| `npm run test:watch` | Fire up test server and re-test on file change |
|
||||
| `npm run js:build` | Compile Choices to an uglified JavaScript file |
|
||||
| `npm run css:watch` | Watch SCSS files for changes. On a change, run build process |
|
||||
|
|
6030
assets/scripts/dist/choices.js
vendored
1
assets/scripts/dist/choices.js.map
vendored
5
assets/scripts/dist/choices.min.js
vendored
1
assets/scripts/dist/choices.min.js.map
vendored
|
@ -1 +0,0 @@
|
|||
{"version":3,"file":"choices.min.js","sources":[],"mappings":";;;","sourceRoot":""}
|
|
@ -1,78 +0,0 @@
|
|||
export const addItem = (value, label, id, choiceId, groupId, customProperties, keyCode) => {
|
||||
return {
|
||||
type: 'ADD_ITEM',
|
||||
value,
|
||||
label,
|
||||
id,
|
||||
choiceId,
|
||||
groupId,
|
||||
customProperties,
|
||||
keyCode
|
||||
};
|
||||
};
|
||||
|
||||
export const removeItem = (id, choiceId) => {
|
||||
return {
|
||||
type: 'REMOVE_ITEM',
|
||||
id,
|
||||
choiceId,
|
||||
};
|
||||
};
|
||||
|
||||
export const highlightItem = (id, highlighted) => {
|
||||
return {
|
||||
type: 'HIGHLIGHT_ITEM',
|
||||
id,
|
||||
highlighted,
|
||||
};
|
||||
};
|
||||
|
||||
export const addChoice = (value, label, id, groupId, disabled, elementId, customProperties, keyCode) => {
|
||||
return {
|
||||
type: 'ADD_CHOICE',
|
||||
value,
|
||||
label,
|
||||
id,
|
||||
groupId,
|
||||
disabled,
|
||||
elementId,
|
||||
customProperties,
|
||||
keyCode
|
||||
};
|
||||
};
|
||||
|
||||
export const filterChoices = (results) => {
|
||||
return {
|
||||
type: 'FILTER_CHOICES',
|
||||
results,
|
||||
};
|
||||
};
|
||||
|
||||
export const activateChoices = (active = true) => {
|
||||
return {
|
||||
type: 'ACTIVATE_CHOICES',
|
||||
active,
|
||||
};
|
||||
};
|
||||
|
||||
export const clearChoices = () => {
|
||||
return {
|
||||
type: 'CLEAR_CHOICES',
|
||||
};
|
||||
};
|
||||
|
||||
export const addGroup = (value, id, active, disabled) => {
|
||||
return {
|
||||
type: 'ADD_GROUP',
|
||||
value,
|
||||
id,
|
||||
active,
|
||||
disabled,
|
||||
};
|
||||
};
|
||||
|
||||
export const clearAll = () => {
|
||||
return {
|
||||
type: 'CLEAR_ALL',
|
||||
};
|
||||
};
|
|
@ -1,129 +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() {
|
||||
var toStr = Object.prototype.toString;
|
||||
|
||||
var isCallable = function(fn) {
|
||||
return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
|
||||
};
|
||||
|
||||
var toInteger = function(value) {
|
||||
var 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));
|
||||
};
|
||||
|
||||
var maxSafeInteger = Math.pow(2, 53) - 1;
|
||||
|
||||
var toLength = function(value) {
|
||||
var 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.
|
||||
var C = this;
|
||||
|
||||
// 2. Let items be ToObject(arrayLike).
|
||||
var 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.
|
||||
var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
|
||||
var 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).
|
||||
var 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).
|
||||
var A = isCallable(C) ? Object(new C(len)) : new Array(len);
|
||||
|
||||
// 16. Let k be 0.
|
||||
var k = 0;
|
||||
// 17. Repeat, while k < len… (also steps a - h)
|
||||
var 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');
|
||||
}
|
||||
var list = Object(this);
|
||||
var length = list.length >>> 0;
|
||||
var thisArg = arguments[1];
|
||||
var value;
|
||||
|
||||
for (var 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
|
||||
};
|
||||
var evt = document.createEvent('CustomEvent');
|
||||
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
|
||||
return evt;
|
||||
}
|
||||
|
||||
CustomEvent.prototype = window.Event.prototype;
|
||||
|
||||
window.CustomEvent = CustomEvent;
|
||||
})();
|
|
@ -1,568 +0,0 @@
|
|||
/* 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, function(txt) {
|
||||
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a string of random chars
|
||||
* @param {Number} length Length of the string to generate
|
||||
* @return {String} String of random chars
|
||||
*/
|
||||
export const generateChars = function (length) {
|
||||
var chars = '';
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
var randomChar = getRandomNumber(0, 36);
|
||||
chars += randomChar.toString(36);
|
||||
}
|
||||
|
||||
return chars;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a unique id based on an element
|
||||
* @param {HTMLElement} element Element to generate the id from
|
||||
* @param {String} Prefix for the Id
|
||||
* @return {String} Unique Id
|
||||
*/
|
||||
export const generateId = function (element, prefix) {
|
||||
var id = element.id || (element.name && (element.name + '-' + generateChars(2))) || generateChars(4);
|
||||
id = id.replace(/(:|\.|\[|\]|,)/g, '');
|
||||
id = prefix + id;
|
||||
|
||||
return id;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tests the type of an object
|
||||
* @param {String} type Type to test object against
|
||||
* @param {Object} obj Object to be tested
|
||||
* @return {Boolean}
|
||||
*/
|
||||
export const getType = function(obj) {
|
||||
return Object.prototype.toString.call(obj).slice(8, -1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Tests the type of an object
|
||||
* @param {String} type Type to test object against
|
||||
* @param {Object} obj Object to be tested
|
||||
* @return {Boolean}
|
||||
*/
|
||||
export const isType = function(type, obj) {
|
||||
var clas = getType(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) => {
|
||||
return (
|
||||
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) => {
|
||||
return (
|
||||
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
|
||||
* @private
|
||||
* @return {Object} Merged object of arguments
|
||||
*/
|
||||
export const extend = function() {
|
||||
let extended = {};
|
||||
let length = arguments.length;
|
||||
|
||||
/**
|
||||
* Merge one object into another
|
||||
* @param {Object} obj Object to merge into extended object
|
||||
*/
|
||||
let merge = function(obj) {
|
||||
for (let prop in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, prop)) {
|
||||
// If deep merge and property is an object, merge properties
|
||||
if (isType('Object', obj[prop])) {
|
||||
extended[prop] = extend(true, extended[prop], obj[prop]);
|
||||
} else {
|
||||
extended[prop] = obj[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Loop through each passed argument
|
||||
for (let i = 0; i < length; i++) {
|
||||
// store argument at position i
|
||||
let obj = arguments[i];
|
||||
|
||||
// If we are in fact dealing with an object, merge it.
|
||||
if (isType('Object', obj)) {
|
||||
merge(obj);
|
||||
}
|
||||
}
|
||||
|
||||
return extended;
|
||||
};
|
||||
|
||||
/**
|
||||
* CSS transition end event listener
|
||||
* @return
|
||||
*/
|
||||
export const whichTransitionEvent = function() {
|
||||
var t,
|
||||
el = document.createElement("fakeelement");
|
||||
|
||||
var 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() {
|
||||
var t,
|
||||
el = document.createElement('fakeelement');
|
||||
|
||||
var 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) {
|
||||
var parents = [];
|
||||
// Get matches
|
||||
for (; elem && elem !== document; elem = elem.parentNode) {
|
||||
|
||||
// Check if parent has been reached
|
||||
if (parent) {
|
||||
|
||||
var 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) {
|
||||
var 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;
|
||||
} else {
|
||||
return parents;
|
||||
}
|
||||
};
|
||||
|
||||
export const wrap = function(element, wrapper) {
|
||||
wrapper = wrapper || document.createElement('div');
|
||||
if (element.nextSibling) {
|
||||
element.parentNode.insertBefore(wrapper, element.nextSibling);
|
||||
} else {
|
||||
element.parentNode.appendChild(wrapper);
|
||||
}
|
||||
return wrapper.appendChild(element);
|
||||
};
|
||||
|
||||
export const getSiblings = function(elem) {
|
||||
var siblings = [];
|
||||
var 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
|
||||
* @param {[type]} cls Class of parent
|
||||
* @return {NodeElement} Found parent element
|
||||
*/
|
||||
export const findAncestor = function(el, cls) {
|
||||
while ((el = el.parentElement) && !el.classList.contains(cls));
|
||||
return el;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find ancestor in DOM tree by attribute name
|
||||
* @param {NodeElement} el Element to start search from
|
||||
* @param {string} attr Attribute name of parent
|
||||
* @return {?NodeElement} Found parent element or null
|
||||
*/
|
||||
export const findAncestorByAttrName = function(el, attr) {
|
||||
let target = el;
|
||||
|
||||
while (target) {
|
||||
if (target.hasAttribute(attr)) {
|
||||
return target;
|
||||
}
|
||||
|
||||
target = target.parentElement;
|
||||
}
|
||||
|
||||
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) {
|
||||
var timeout;
|
||||
return function() {
|
||||
var context = this,
|
||||
args = arguments;
|
||||
var later = function() {
|
||||
timeout = null;
|
||||
if (!immediate) func.apply(context, args);
|
||||
};
|
||||
var 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) {
|
||||
var 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) {
|
||||
var 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
|
||||
* @param {String} className The class we will look through
|
||||
* @param {Number} direction Positive next element, negative previous element
|
||||
* @return {[HTMLElement} Found element
|
||||
*/
|
||||
export const getAdjacentEl = (startEl, className, direction = 1) => {
|
||||
if (!startEl || !className) return;
|
||||
|
||||
const parent = startEl.parentNode.parentNode;
|
||||
const children = Array.from(parent.querySelectorAll(className));
|
||||
|
||||
const startPos = children.indexOf(startEl);
|
||||
const operatorDirection = direction > 0 ? 1 : -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));
|
||||
} else {
|
||||
// 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)) ? true : false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine whether an element is within
|
||||
* @param {HTMLElement} el Element to test
|
||||
* @param {HTMLElement} parent Scrolling parent
|
||||
* @param {Number} direction Whether element is visible from above or below
|
||||
* @return {Boolean}
|
||||
*/
|
||||
export const isScrolledIntoView = (el, parent, direction = 1) => {
|
||||
if (!el) return;
|
||||
|
||||
let isVisible;
|
||||
|
||||
if (direction > 0) {
|
||||
// In view from bottom
|
||||
isVisible = (parent.scrollTop + parent.offsetHeight) >= (el.offsetTop + el.offsetHeight);
|
||||
} else {
|
||||
// In view from top
|
||||
isVisible = el.offsetTop >= parent.scrollTop;
|
||||
}
|
||||
|
||||
return isVisible;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove html tags from a string
|
||||
* @param {String} Initial string/html
|
||||
* @return {String} Sanitised string
|
||||
*/
|
||||
export const stripHTML = function(html) {
|
||||
let el = document.createElement("DIV");
|
||||
el.innerHTML = html;
|
||||
return el.textContent || el.innerText || "";
|
||||
};
|
||||
|
||||
/**
|
||||
* 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) => {
|
||||
let animationEvent = whichAnimationEvent();
|
||||
|
||||
let 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
|
||||
* @param {Number} max Maximum range
|
||||
* @return {Number} Random number
|
||||
*/
|
||||
export const getRandomNumber = function(min, max) {
|
||||
return Math.floor(Math.random() * (max - min) + min);
|
||||
};
|
||||
|
||||
/**
|
||||
* Turn a string into a node
|
||||
* @param {String} String to convert
|
||||
* @return {HTMLElement} Converted node element
|
||||
*/
|
||||
export const strToEl = (function() {
|
||||
let tmpEl = document.createElement('div');
|
||||
return function(str) {
|
||||
let cleanedInput = str.trim();
|
||||
let r;
|
||||
tmpEl.innerHTML = cleanedInput;
|
||||
r = tmpEl.children[0];
|
||||
|
||||
while (tmpEl.firstChild) {
|
||||
tmpEl.removeChild(tmpEl.firstChild);
|
||||
}
|
||||
|
||||
return r;
|
||||
};
|
||||
}());
|
||||
|
||||
/**
|
||||
* Sets the width of a passed input based on its value
|
||||
* @return {Number} Width of input
|
||||
*/
|
||||
export const getWidthOfInput = (input) => {
|
||||
const value = input.value || input.placeholder;
|
||||
let width = input.offsetWidth;
|
||||
|
||||
if (value) {
|
||||
const testEl = strToEl(`<span>${ value }</span>`);
|
||||
testEl.style.position = 'absolute';
|
||||
testEl.style.padding = '0';
|
||||
testEl.style.top = '-9999px';
|
||||
testEl.style.left = '-9999px';
|
||||
testEl.style.width = 'auto';
|
||||
testEl.style.whiteSpace = 'pre';
|
||||
|
||||
document.body.appendChild(testEl);
|
||||
|
||||
if (value && testEl.offsetWidth !== input.offsetWidth) {
|
||||
width = testEl.offsetWidth + 4;
|
||||
}
|
||||
|
||||
document.body.removeChild(testEl);
|
||||
}
|
||||
|
||||
return `${width}px`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sorting function for current and previous string
|
||||
* @param {String} a Current value
|
||||
* @param {String} b Next value
|
||||
* @return {Number} -1 for after previous,
|
||||
* 1 for before,
|
||||
* 0 for same location
|
||||
*/
|
||||
export const sortByAlpha = (a, b) => {
|
||||
const labelA = (a.label || a.value).toLowerCase();
|
||||
const labelB = (b.label || b.value).toLowerCase();
|
||||
|
||||
if (labelA < labelB) return -1;
|
||||
if (labelA > labelB) return 1;
|
||||
return 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sort by numeric score
|
||||
* @param {Object} a Current value
|
||||
* @param {Object} b Next value
|
||||
* @return {Number} -1 for after previous,
|
||||
* 1 for before,
|
||||
* 0 for same location
|
||||
*/
|
||||
export const sortByScore = (a, b) => {
|
||||
return a.score - b.score;
|
||||
};
|
||||
|
||||
/**
|
||||
* Trigger native event
|
||||
* @param {NodeElement} element Element to trigger event on
|
||||
* @param {String} type Type of event to trigger
|
||||
* @param {Object} customArgs Data to pass with event
|
||||
* @return {Object} Triggered event
|
||||
*/
|
||||
export const triggerEvent = (element, type, customArgs = null) => {
|
||||
const event = new CustomEvent(type, {
|
||||
detail: customArgs,
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
});
|
||||
|
||||
return element.dispatchEvent(event);
|
||||
};
|
|
@ -1,100 +0,0 @@
|
|||
const choices = (state = [], action) => {
|
||||
switch (action.type) {
|
||||
case 'ADD_CHOICE': {
|
||||
/*
|
||||
A disabled choice appears in the choice dropdown but cannot be selected
|
||||
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,
|
||||
keyCode: null
|
||||
}];
|
||||
}
|
||||
|
||||
case 'ADD_ITEM': {
|
||||
let newState = state;
|
||||
|
||||
// If all choices need to be activated
|
||||
if (action.activateOptions) {
|
||||
newState = state.map((choice) => {
|
||||
choice.active = action.active;
|
||||
return choice;
|
||||
});
|
||||
}
|
||||
// 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) {
|
||||
newState = state.map((choice) => {
|
||||
if (choice.id === parseInt(action.choiceId, 10)) {
|
||||
choice.selected = true;
|
||||
}
|
||||
return choice;
|
||||
});
|
||||
}
|
||||
|
||||
return newState;
|
||||
}
|
||||
|
||||
case 'REMOVE_ITEM': {
|
||||
// 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((choice) => {
|
||||
if (choice.id === parseInt(action.choiceId, 10)) {
|
||||
choice.selected = false;
|
||||
}
|
||||
return choice;
|
||||
});
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
case 'FILTER_CHOICES': {
|
||||
const filteredResults = action.results;
|
||||
const filteredState = state.map((choice) => {
|
||||
// Set active state based on whether choice is
|
||||
// within filtered results
|
||||
|
||||
choice.active = filteredResults.some((result) => {
|
||||
if (result.item.id === choice.id) {
|
||||
choice.score = result.score;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return choice;
|
||||
});
|
||||
|
||||
return filteredState;
|
||||
}
|
||||
|
||||
case 'ACTIVATE_CHOICES': {
|
||||
return state.map((choice) => {
|
||||
choice.active = action.active;
|
||||
return choice;
|
||||
});
|
||||
}
|
||||
|
||||
case 'CLEAR_CHOICES': {
|
||||
return state.choices = [];
|
||||
}
|
||||
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default choices;
|
|
@ -1,22 +0,0 @@
|
|||
const groups = (state = [], action) => {
|
||||
switch (action.type) {
|
||||
case 'ADD_GROUP': {
|
||||
return [...state, {
|
||||
id: action.id,
|
||||
value: action.value,
|
||||
active: action.active,
|
||||
disabled: action.disabled,
|
||||
}];
|
||||
}
|
||||
|
||||
case 'CLEAR_CHOICES': {
|
||||
return state.groups = [];
|
||||
}
|
||||
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default groups;
|
|
@ -1,50 +0,0 @@
|
|||
const items = (state = [], 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,
|
||||
keyCode: null
|
||||
}];
|
||||
|
||||
return newState.map((item) => {
|
||||
if (item.highlighted) {
|
||||
item.highlighted = false;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
case 'REMOVE_ITEM': {
|
||||
// Set item to inactive
|
||||
return state.map((item) => {
|
||||
if (item.id === action.id) {
|
||||
item.active = false;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
case 'HIGHLIGHT_ITEM': {
|
||||
return state.map((item) => {
|
||||
if (item.id === action.id) {
|
||||
item.highlighted = action.highlighted;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default items;
|
|
@ -1,164 +0,0 @@
|
|||
import { createStore } from 'redux';
|
||||
import rootReducer from './../reducers/index.js';
|
||||
|
||||
export default class Store {
|
||||
constructor() {
|
||||
this.store = createStore(
|
||||
rootReducer
|
||||
, window.devToolsExtension ? window.devToolsExtension() : undefined
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get store object (wrapping Redux method)
|
||||
* @return {Object} State
|
||||
*/
|
||||
getState() {
|
||||
return this.store.getState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch event to store (wrapped Redux method)
|
||||
* @param {Function} action Action function to trigger
|
||||
* @return
|
||||
*/
|
||||
dispatch(action) {
|
||||
this.store.dispatch(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe store to function call (wrapped Redux method)
|
||||
* @param {Function} onChange Function to trigger when state changes
|
||||
* @return
|
||||
*/
|
||||
subscribe(onChange) {
|
||||
this.store.subscribe(onChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get items from store
|
||||
* @return {Array} Item objects
|
||||
*/
|
||||
getItems() {
|
||||
const state = this.store.getState();
|
||||
return state.items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active items from store
|
||||
* @return {Array} Item objects
|
||||
*/
|
||||
getItemsFilteredByActive() {
|
||||
const items = this.getItems();
|
||||
const values = items.filter((item) => {
|
||||
return item.active === true;
|
||||
}, []);
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get items from store reduced to just their values
|
||||
* @return {Array} Item objects
|
||||
*/
|
||||
getItemsReducedToValues(items = this.getItems()) {
|
||||
const values = items.reduce((prev, current) => {
|
||||
prev.push(current.value);
|
||||
return prev;
|
||||
}, []);
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get choices from store
|
||||
* @return {Array} Option objects
|
||||
*/
|
||||
getChoices() {
|
||||
const state = this.store.getState();
|
||||
return state.choices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active choices from store
|
||||
* @return {Array} Option objects
|
||||
*/
|
||||
getChoicesFilteredByActive() {
|
||||
const choices = this.getChoices();
|
||||
const values = choices.filter((choice) => {
|
||||
return choice.active === true;
|
||||
}, []);
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get selectable choices from store
|
||||
* @return {Array} Option objects
|
||||
*/
|
||||
getChoicesFilteredBySelectable() {
|
||||
const choices = this.getChoices();
|
||||
const values = choices.filter((choice) => {
|
||||
return choice.disabled !== true;
|
||||
}, []);
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get single choice by it's ID
|
||||
* @return {Object} Found choice
|
||||
*/
|
||||
getChoiceById(id) {
|
||||
if (id) {
|
||||
const choices = this.getChoicesFilteredByActive();
|
||||
const foundChoice = choices.find((choice) => choice.id === parseInt(id, 10));
|
||||
return foundChoice;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get groups from store
|
||||
* @return {Array} Group objects
|
||||
*/
|
||||
getGroups() {
|
||||
const state = this.store.getState();
|
||||
return state.groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active groups from store
|
||||
* @return {Array} Group objects
|
||||
*/
|
||||
getGroupsFilteredByActive() {
|
||||
const groups = this.getGroups();
|
||||
const choices = this.getChoices();
|
||||
|
||||
const values = groups.filter((group) => {
|
||||
const isActive = group.active === true && group.disabled === false;
|
||||
const hasActiveOptions = choices.some((choice) => {
|
||||
return choice.active === true && choice.disabled === false;
|
||||
});
|
||||
return isActive && hasActiveOptions;
|
||||
}, []);
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get group by group id
|
||||
* @param {Number} id Group ID
|
||||
* @return {Object} Group data
|
||||
*/
|
||||
getGroupById(id) {
|
||||
const groups = this.getGroups();
|
||||
const foundGroup = groups.find((group) => {
|
||||
return group.id === id;
|
||||
});
|
||||
|
||||
return foundGroup;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Store;
|
1
assets/styles/css/base.min.css
vendored
|
@ -1 +0,0 @@
|
|||
*{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}*,:after,:before{box-sizing:border-box}body,html{position:relative;margin:0;width:100%;height:100%}body{font-family:"Helvetica Neue",Helvetica,Arial,"Lucida Grande",sans-serif;font-size:16px;line-height:1.4;color:#fff;background-color:#333;overflow-x:hidden}hr,label{display:block}label{margin-bottom:8px;font-size:14px;font-weight:500;cursor:pointer}p{margin-top:0}hr{margin:30px 0;border:0;border-bottom:1px solid #eaeaea;height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:12px;font-weight:400;line-height:1.2}a,a:focus,a:visited{color:#fff;text-decoration:none;font-weight:600}.form-control{display:block;width:100%;background-color:#f9f9f9;padding:12px;border:1px solid #ddd;border-radius:2.5px;font-size:14px;-webkit-appearance:none;-moz-appearance:none;appearance:none;margin-bottom:24px}.h1,h1{font-size:32px}.h2,h2{font-size:24px}.h3,h3{font-size:20px}.h4,h4{font-size:18px}.h5,h5{font-size:16px}.h6,h6{font-size:14px}.container{display:block;margin:auto;max-width:40em;padding:48px}@media (max-width:620px){.container{padding:0}}.section{background-color:#fff;padding:24px;color:#333}.section a,.section a:focus,.section a:visited{color:#00bcd4}.logo{display:block;margin-bottom:12px}.logo__img{width:100%;height:auto;display:inline-block;max-width:100%;vertical-align:top;padding:6px 0}.visible-ie{display:none}.zero-bottom{margin-bottom:0}.zero-top{margin-top:0}.text-center{text-align:center}
|
1
assets/styles/css/choices.min.css
vendored
|
@ -1,125 +0,0 @@
|
|||
$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 =
|
||||
=============================================*/
|
||||
|
||||
* {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale
|
||||
}
|
||||
|
||||
|
||||
*, *:before, *:after {
|
||||
box-sizing: border-box
|
||||
}
|
||||
|
||||
|
||||
html, body {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
color: #FFFFFF;
|
||||
background-color: #333;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
p { margin-top: 0; }
|
||||
|
||||
hr {
|
||||
display: block;
|
||||
margin: $global-guttering*1.25 0;
|
||||
border: 0;
|
||||
border-bottom: 1px solid #eaeaea;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
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 {
|
||||
color: #FFFFFF;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
display: block;
|
||||
width: 100%;
|
||||
background-color: #f9f9f9;
|
||||
padding: 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 2.5px;
|
||||
font-size: 14px;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
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; }
|
||||
|
||||
.container {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 40em;
|
||||
padding: $global-guttering*2;
|
||||
@media (max-width: 620px) { padding: 0; }
|
||||
}
|
||||
|
||||
.section {
|
||||
background-color: #FFFFFF;
|
||||
padding: $global-guttering;
|
||||
color: #333;
|
||||
a, a:visited, a:focus { color: #00bcd4; }
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: block;
|
||||
margin-bottom: $global-guttering/2;
|
||||
}
|
||||
|
||||
.logo__img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
vertical-align: top;
|
||||
padding: $global-guttering/4 0;
|
||||
}
|
||||
|
||||
.visible-ie { display: none; }
|
||||
.zero-bottom { margin-bottom: 0; }
|
||||
.zero-top { margin-top: 0; }
|
||||
.text-center { text-align: center; }
|
||||
|
||||
/*===== End of Section comment block ======*/
|
29
bower.json
|
@ -1,29 +0,0 @@
|
|||
{
|
||||
"name": "choices.js",
|
||||
"version": "2.8.12",
|
||||
"description": "A vanilla JS customisable text input/select box plugin",
|
||||
"main": [
|
||||
"./assets/scripts/dist/choices.js",
|
||||
"./assets/styles/css/choices.css"
|
||||
],
|
||||
"authors": [
|
||||
"Josh Johnson"
|
||||
],
|
||||
"license": "MIT",
|
||||
"homepage": "https://joshuajohnson.co.uk/Choices/",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"keywords": [
|
||||
"customisable",
|
||||
"input",
|
||||
"select",
|
||||
"vanilla",
|
||||
"plugin",
|
||||
"js"
|
||||
]
|
||||
}
|
50
config/jsdom.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
|
||||
const { JSDOM } = require('jsdom');
|
||||
|
||||
const jsdom = new JSDOM(
|
||||
'<!doctype html><html><head><meta charset="utf-8"></head><body></body></html>',
|
||||
{
|
||||
pretendToBeVisual: true,
|
||||
},
|
||||
);
|
||||
const { window } = jsdom;
|
||||
|
||||
function copyProps(src, target) {
|
||||
const props = Object.getOwnPropertyNames(src)
|
||||
.filter(prop => typeof target[prop] === 'undefined')
|
||||
.map(prop => Object.getOwnPropertyDescriptor(src, prop));
|
||||
Object.defineProperties(target, props);
|
||||
}
|
||||
|
||||
function ignoreExtensions(extensions = [], returnValue = {}) {
|
||||
function noop() {
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
extensions.forEach(ext => {
|
||||
require.extensions[ext] = noop;
|
||||
});
|
||||
}
|
||||
|
||||
global.window = window;
|
||||
global.document = window.document;
|
||||
global.navigator = {
|
||||
userAgent: 'node.js',
|
||||
};
|
||||
global.CustomEvent = window.CustomEvent;
|
||||
global.Element = window.Element;
|
||||
global.HTMLElement = window.HTMLElement;
|
||||
global.Option = window.Option;
|
||||
global.HTMLOptionElement = window.HTMLOptionElement;
|
||||
global.HTMLOptGroupElement = window.HTMLOptGroupElement;
|
||||
global.HTMLSelectElement = window.HTMLSelectElement;
|
||||
global.HTMLInputElement = window.HTMLInputElement;
|
||||
global.DocumentFragment = window.DocumentFragment;
|
||||
global.requestAnimationFrame = window.requestAnimationFrame;
|
||||
window.matchMedia = () => true;
|
||||
|
||||
copyProps(window, global);
|
||||
|
||||
ignoreExtensions(['.scss', '.css']);
|
||||
ignoreExtensions(['.jpg', '.png', '.svg'], '');
|
15
cypress.config.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { defineConfig } from 'cypress'
|
||||
|
||||
export default defineConfig({
|
||||
video: false,
|
||||
projectId: 'n7g5qp',
|
||||
e2e: {
|
||||
// We've imported your old cypress plugins here.
|
||||
// You may want to clean this up later by importing these.
|
||||
setupNodeEvents(on, config) {
|
||||
return require('./cypress/plugins/index.js')(on, config)
|
||||
},
|
||||
baseUrl: 'http://localhost:3001/test',
|
||||
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
|
||||
},
|
||||
})
|
1011
cypress/e2e/select-multiple.spec.ts
Normal file
1149
cypress/e2e/select-one.spec.ts
Normal file
431
cypress/e2e/text.spec.ts
Normal file
|
@ -0,0 +1,431 @@
|
|||
describe('Choices - text element', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/text', {
|
||||
onBeforeLoad(win) {
|
||||
cy.stub(win.console, 'warn').as('consoleWarn');
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('scenarios', () => {
|
||||
const textInput = 'testing';
|
||||
|
||||
describe('basic', () => {
|
||||
describe('adding items', () => {
|
||||
it('allows me to input items', () => {
|
||||
cy.get('[data-test-hook=basic]')
|
||||
.find('.choices__input--cloned')
|
||||
.type(textInput)
|
||||
.type('{enter}');
|
||||
|
||||
cy.get('[data-test-hook=basic]')
|
||||
.find('.choices__list--multiple .choices__item')
|
||||
.last()
|
||||
.should(($el) => {
|
||||
expect($el).to.contain(textInput);
|
||||
});
|
||||
});
|
||||
|
||||
it('updates the value of the original input', () => {
|
||||
cy.get('[data-test-hook=basic]')
|
||||
.find('.choices__input--cloned')
|
||||
.type(textInput)
|
||||
.type('{enter}');
|
||||
|
||||
cy.get('[data-test-hook=basic]')
|
||||
.find('.choices__input[hidden]')
|
||||
.should('have.value', textInput);
|
||||
});
|
||||
|
||||
describe('inputting data', () => {
|
||||
it('shows a dropdown prompt', () => {
|
||||
cy.get('[data-test-hook=basic]')
|
||||
.find('.choices__input--cloned')
|
||||
.type(textInput);
|
||||
|
||||
cy.get('[data-test-hook=basic]')
|
||||
.find('.choices__list--dropdown')
|
||||
.should('be.visible')
|
||||
.should(($dropdown) => {
|
||||
const dropdownText = $dropdown.text().trim();
|
||||
expect(dropdownText).to.equal(
|
||||
`Press Enter to add "${textInput}"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('editing items', () => {
|
||||
beforeEach(() => {
|
||||
for (let index = 0; index < 3; index++) {
|
||||
cy.get('[data-test-hook=edit-items]')
|
||||
.find('.choices__input--cloned')
|
||||
.type(textInput)
|
||||
.type('{enter}');
|
||||
}
|
||||
});
|
||||
|
||||
describe('on back space', () => {
|
||||
it('allows me to change my entry', () => {
|
||||
cy.get('[data-test-hook=edit-items]')
|
||||
.find('.choices__input--cloned')
|
||||
.type('{backspace}')
|
||||
.type('-edited')
|
||||
.type('{enter}');
|
||||
|
||||
cy.get('[data-test-hook=edit-items]')
|
||||
.find('.choices__list--multiple .choices__item')
|
||||
.last()
|
||||
.should(($choice) => {
|
||||
expect($choice.data('value')).to.equal(`${textInput}-edited`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('on cmd+a', () => {
|
||||
beforeEach(() => {
|
||||
cy.get('[data-test-hook=edit-items]')
|
||||
.find('.choices__input--cloned')
|
||||
.type('{cmd}a');
|
||||
});
|
||||
|
||||
it('highlights all items', () => {
|
||||
cy.get('[data-test-hook=edit-items]')
|
||||
.find('.choices__list--multiple .choices__item')
|
||||
.each(($choice) => {
|
||||
expect($choice.hasClass('is-highlighted')).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('on backspace', () => {
|
||||
it('clears all inputted values', () => {
|
||||
// two backspaces are needed as Cypress has an issue where
|
||||
// it will also insert an 'a' character into the text input
|
||||
cy.get('[data-test-hook=edit-items]')
|
||||
.find('.choices__input--cloned')
|
||||
.type('{backspace}{backspace}');
|
||||
|
||||
cy.get('[data-test-hook=edit-items]')
|
||||
.find('.choices__list--multiple .choices__item')
|
||||
.should('have.length', 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove button', () => {
|
||||
beforeEach(() => {
|
||||
cy.get('[data-test-hook=remove-button]')
|
||||
.find('.choices__input--cloned')
|
||||
.type(`${textInput}`)
|
||||
.type('{enter}');
|
||||
});
|
||||
|
||||
describe('on click', () => {
|
||||
it('removes respective choice', () => {
|
||||
cy.get('[data-test-hook=remove-button]')
|
||||
.find('.choices__list--multiple')
|
||||
.children()
|
||||
.should(($items) => {
|
||||
expect($items.length).to.equal(1);
|
||||
});
|
||||
|
||||
cy.get('[data-test-hook=remove-button]')
|
||||
.find('.choices__list--multiple .choices__item')
|
||||
.last()
|
||||
.find('.choices__button')
|
||||
.focus()
|
||||
.click();
|
||||
|
||||
cy.get('[data-test-hook=remove-button]')
|
||||
.find('.choices__list--multiple .choices__item')
|
||||
.should(($items) => {
|
||||
expect($items.length).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('updates the value of the original input', () => {
|
||||
cy.get('[data-test-hook=remove-button]')
|
||||
.find('.choices__list--multiple .choices__item')
|
||||
.last()
|
||||
.find('.choices__button')
|
||||
.focus()
|
||||
.click();
|
||||
|
||||
cy.get('[data-test-hook=remove-button]')
|
||||
.find('.choices__input[hidden]')
|
||||
.then(($input) => {
|
||||
expect($input.val()).to.not.contain(textInput);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('unique values only', () => {
|
||||
describe('unique values', () => {
|
||||
beforeEach(() => {
|
||||
cy.get('[data-test-hook=unique-values]')
|
||||
.find('.choices__input--cloned')
|
||||
.type(`${textInput}`)
|
||||
.type('{enter}')
|
||||
.type(`${textInput}`)
|
||||
.type('{enter}');
|
||||
});
|
||||
|
||||
it('only allows me to input unique values', () => {
|
||||
cy.get('[data-test-hook=unique-values]')
|
||||
.find('.choices__list--multiple')
|
||||
.first()
|
||||
.children()
|
||||
.should(($items) => {
|
||||
expect($items.length).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('inputting a non-unique value', () => {
|
||||
it('displays dropdown prompt', () => {
|
||||
cy.get('[data-test-hook=unique-values]')
|
||||
.find('.choices__list--dropdown')
|
||||
.should('be.visible')
|
||||
.should(($dropdown) => {
|
||||
const dropdownText = $dropdown.text().trim();
|
||||
expect(dropdownText).to.equal(
|
||||
'Only unique values can be added',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('input limit', () => {
|
||||
const inputLimit = 5;
|
||||
beforeEach(() => {
|
||||
for (let index = 0; index < inputLimit + 1; index++) {
|
||||
cy.get('[data-test-hook=input-limit]')
|
||||
.find('.choices__input--cloned')
|
||||
.type(`${textInput} + ${index}`)
|
||||
.type('{enter}');
|
||||
}
|
||||
});
|
||||
|
||||
it('does not let me input more than 5 choices', () => {
|
||||
cy.get('[data-test-hook=input-limit]')
|
||||
.find('.choices__list--multiple')
|
||||
.first()
|
||||
.children()
|
||||
.should(($items) => {
|
||||
expect($items.length).to.equal(inputLimit);
|
||||
});
|
||||
});
|
||||
|
||||
describe('reaching input limit', () => {
|
||||
it('displays dropdown prompt', () => {
|
||||
cy.get('[data-test-hook=input-limit]')
|
||||
.find('.choices__list--dropdown')
|
||||
.should('be.visible')
|
||||
.should(($dropdown) => {
|
||||
const dropdownText = $dropdown.text().trim();
|
||||
expect(dropdownText).to.equal(
|
||||
`Only ${inputLimit} values can be added`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('add item filter', () => {
|
||||
describe('inputting a value that satisfies the filter', () => {
|
||||
const input = 'joe@bloggs.com';
|
||||
|
||||
it('allows me to add choice', () => {
|
||||
cy.get('[data-test-hook=add-item-filter]')
|
||||
.find('.choices__input--cloned')
|
||||
.type(input)
|
||||
.type('{enter}');
|
||||
|
||||
cy.get('[data-test-hook=add-item-filter]')
|
||||
.find('.choices__list--multiple .choices__item')
|
||||
.last()
|
||||
.should(($choice) => {
|
||||
expect($choice.text().trim()).to.equal(input);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('inputting a value that does not satisfy the regex', () => {
|
||||
it('displays dropdown prompt', () => {
|
||||
cy.get('[data-test-hook=add-item-filter]')
|
||||
.find('.choices__input--cloned')
|
||||
.type(`this is not an email address`)
|
||||
.type('{enter}');
|
||||
|
||||
cy.get('[data-test-hook=add-item-filter]')
|
||||
.find('.choices__list--dropdown')
|
||||
.should('be.visible')
|
||||
.should(($dropdown) => {
|
||||
const dropdownText = $dropdown.text().trim();
|
||||
expect(dropdownText).to.equal(
|
||||
'Only values matching specific conditions can be added',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('prepend/append', () => {
|
||||
beforeEach(() => {
|
||||
cy.get('[data-test-hook=prepend-append]')
|
||||
.find('.choices__input--cloned')
|
||||
.type(textInput)
|
||||
.type('{enter}');
|
||||
});
|
||||
|
||||
it('prepends and appends value to inputted value', () => {
|
||||
cy.get('[data-test-hook=prepend-append]')
|
||||
.find('.choices__list--multiple .choices__item')
|
||||
.last()
|
||||
.should(($choice) => {
|
||||
expect($choice.data('value')).to.equal(`before-${textInput}-after`);
|
||||
});
|
||||
});
|
||||
|
||||
it('displays just the inputted value to the user', () => {
|
||||
cy.get('[data-test-hook=prepend-append]')
|
||||
.find('.choices__list--multiple .choices__item')
|
||||
.last()
|
||||
.should(($choice) => {
|
||||
expect($choice.text()).to.not.contain(`before-${textInput}-after`);
|
||||
expect($choice.text()).to.contain(textInput);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('adding items disabled', () => {
|
||||
it('does not allow me to input data', () => {
|
||||
cy.get('[data-test-hook=adding-items-disabled]')
|
||||
.find('.choices__input--cloned')
|
||||
.should('be.disabled');
|
||||
});
|
||||
});
|
||||
|
||||
describe('disabled via attribute', () => {
|
||||
it('does not allow me to input data', () => {
|
||||
cy.get('[data-test-hook=disabled-via-attr]')
|
||||
.find('.choices__input--cloned')
|
||||
.should('be.disabled');
|
||||
});
|
||||
});
|
||||
|
||||
describe('pre-populated choices', () => {
|
||||
it('pre-populates choices', () => {
|
||||
cy.get('[data-test-hook=prepopulated]')
|
||||
.find('.choices__list--multiple .choices__item')
|
||||
.should(($choices) => {
|
||||
expect($choices.length).to.equal(2);
|
||||
});
|
||||
|
||||
cy.get('[data-test-hook=prepopulated]')
|
||||
.find('.choices__list--multiple .choices__item')
|
||||
.first()
|
||||
.should(($choice) => {
|
||||
expect($choice.text().trim()).to.equal('Josh Johnson');
|
||||
});
|
||||
|
||||
cy.get('[data-test-hook=prepopulated]')
|
||||
.find('.choices__list--multiple .choices__item')
|
||||
.last()
|
||||
.should(($choice) => {
|
||||
expect($choice.text().trim()).to.equal('Joe Bloggs');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('placeholder', () => {
|
||||
/*
|
||||
{
|
||||
placeholder: true,
|
||||
placeholderValue: 'I am a placeholder',
|
||||
}
|
||||
*/
|
||||
describe('when no value has been inputted', () => {
|
||||
it('displays a placeholder', () => {
|
||||
cy.get('[data-test-hook=placeholder]')
|
||||
.find('.choices__input--cloned')
|
||||
.should('have.attr', 'placeholder', 'I am a placeholder');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('allow html', () => {
|
||||
describe('is undefined', () => {
|
||||
it('logs a deprecation warning', () => {
|
||||
cy.get('@consoleWarn').should(
|
||||
'be.calledOnceWithExactly',
|
||||
'Deprecation warning: allowHTML will default to false in a future release. To render HTML in Choices, you will need to set it to true. Setting allowHTML will suppress this message.',
|
||||
);
|
||||
});
|
||||
|
||||
it('does not show html as text', () => {
|
||||
cy.get('[data-test-hook=allowhtml-undefined]')
|
||||
.find('.choices__list--multiple .choices__item')
|
||||
.first()
|
||||
.should(($choice) => {
|
||||
expect($choice.text().trim()).to.equal('Mason Rogers');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('set to true', () => {
|
||||
it('does not show html as text', () => {
|
||||
cy.get('[data-test-hook=allowhtml-true]')
|
||||
.find('.choices__list--multiple .choices__item')
|
||||
.first()
|
||||
.should(($choice) => {
|
||||
expect($choice.text().trim()).to.equal('Mason Rogers');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('set to false', () => {
|
||||
it('shows html as text', () => {
|
||||
cy.get('[data-test-hook=allowhtml-false]')
|
||||
.find('.choices__list--multiple .choices__item')
|
||||
.first()
|
||||
.should(($choice) => {
|
||||
expect($choice.text().trim()).to.equal('<b>Mason Rogers</b>');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('within form', () => {
|
||||
describe('inputting item', () => {
|
||||
describe('on enter key', () => {
|
||||
it('does not submit form', () => {
|
||||
cy.get('[data-test-hook=within-form] form').then(($form) => {
|
||||
$form.submit(() => {
|
||||
// this will fail the test if the form submits
|
||||
throw new Error('Form submitted');
|
||||
});
|
||||
});
|
||||
|
||||
cy.get('[data-test-hook=within-form]')
|
||||
.find('.choices__input--cloned')
|
||||
.type(textInput)
|
||||
.type('{enter}');
|
||||
|
||||
cy.get('[data-test-hook=within-form]')
|
||||
.find('.choices__list--multiple .choices__item')
|
||||
.last()
|
||||
.should(($el) => {
|
||||
expect($el).to.contain(textInput);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
5
cypress/fixtures/example.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
17
cypress/plugins/index.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
};
|
25
cypress/support/commands.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add("login", (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
20
cypress/support/e2e.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands';
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
535
index.html
|
@ -1,535 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<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="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="assets/styles/css/base.min.css?version=2.8.12">
|
||||
<!-- End ignore these -->
|
||||
|
||||
<!-- Optional includes -->
|
||||
<script src="https://cdn.polyfill.io/v2/polyfill.js?features=es5,fetch,Element.prototype.classList,requestAnimationFrame,Node.insertBefore,Node.firstChild,Object.assign"></script>
|
||||
<!-- End optional includes -->
|
||||
|
||||
<!-- Choices includes -->
|
||||
<link rel="stylesheet" href="assets/styles/css/choices.min.css?version=2.8.12">
|
||||
<script src="assets/scripts/dist/choices.min.js?version=2.8.8"></script>
|
||||
<!-- End Choices includes -->
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<style>
|
||||
.hidden-ie { display: none; }
|
||||
.visible-ie { display: block; }
|
||||
</style>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="section">
|
||||
<a href="https://github.com/jshjohnson/Choices" class="logo">
|
||||
<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
|
||||
the jQuery dependency.</p>
|
||||
<p>For all config options, visit the <a href="https://github.com/jshjohnson/Choices">GitHub repo</a>.</p>
|
||||
|
||||
<hr>
|
||||
<p class="h4 text-center">
|
||||
<strong>Interested in writing your own ES6 JavaScript plugins? Check out <a href="https://ES6.io/friend/JOHNSON">ES6.io</a> for great tutorials! 💪🏼</strong>
|
||||
</p>
|
||||
<hr>
|
||||
|
||||
<h2>Text inputs</h2>
|
||||
<label for="choices-text-remove-button">Limited to 5 values with remove button</label>
|
||||
<input class="form-control" id="choices-text-remove-button" type="text" value="preset-1,preset-2" placeholder="Enter something">
|
||||
|
||||
<label for="choices-text-unique-values">Unique values only, no pasting</label>
|
||||
<input class="form-control" id="choices-text-unique-values" type="text" value="preset-1, preset-2" placeholder="This is a placeholder"
|
||||
class="custom class">
|
||||
|
||||
<label for="choices-text-email-filter">Email addresses only</label>
|
||||
<input class="form-control" id="choices-text-email-filter" type="text" placeholder="This is a placeholder">
|
||||
|
||||
<label for="choices-text-disabled">Disabled</label>
|
||||
<input class="form-control" id="choices-text-disabled" type="text" value="josh@joshuajohnson.co.uk, joe@bloggs.co.uk" placeholder="This is a placeholder">
|
||||
|
||||
<label for="choices-text-prepend-append-value">Prepends and appends a value to each items return value</label>
|
||||
<input class="form-control" id="choices-text-prepend-append-value" type="text" value="preset-1, preset-2" placeholder="This is a placeholder">
|
||||
|
||||
<label for="choices-text-preset-values">Preset values passed through options</label>
|
||||
<input class="form-control" id="choices-text-preset-values" type="text" value="Michael Smith" placeholder="This is a placeholder">
|
||||
|
||||
<label for="choices-text-i18n">I18N labels</label>
|
||||
<input class="form-control" data-trigger id="choices-text-i18n" type="text">
|
||||
|
||||
<label for="choices-text-rtl">Right-to-left</label>
|
||||
<input class="form-control" data-trigger id="choices-text-rtl" type="text" value="Value 1, Value 2" dir="rtl">
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Multiple select input</h2>
|
||||
<label for="choices-multiple-default">Default</label>
|
||||
<select class="form-control" data-trigger name="choices-multiple-default" id="choices-multiple-default" placeholder="This is a placeholder"
|
||||
multiple>
|
||||
<option value="Dropdown item 1" selected>Dropdown item 1</option>
|
||||
<option value="Dropdown item 2">Dropdown item 2</option>
|
||||
<option value="Dropdown item 3">Dropdown item 3</option>
|
||||
<option value="Dropdown item 4" disabled>Dropdown item 4</option>
|
||||
</select>
|
||||
|
||||
<label for="choices-multiple-remove-button">With remove button</label>
|
||||
<select class="form-control" name="choices-multiple-remove-button" id="choices-multiple-remove-button" placeholder="This is a placeholder"
|
||||
multiple>
|
||||
<option value="Dropdown item 1" selected>Dropdown item 1</option>
|
||||
<option value="Dropdown item 2">Dropdown item 2</option>
|
||||
<option value="Dropdown item 3">Dropdown item 3</option>
|
||||
<option value="Dropdown item 4">Dropdown item 4</option>
|
||||
</select>
|
||||
|
||||
<label for="choices-multiple-groups">Option groups</label>
|
||||
<select class="form-control" name="choices-multiple-groups" id="choices-multiple-groups" placeholder="This is a placeholder"
|
||||
multiple>
|
||||
<optgroup label="UK">
|
||||
<option value="London">London</option>
|
||||
<option value="Manchester">Manchester</option>
|
||||
<option value="Liverpool">Liverpool</option>
|
||||
</optgroup>
|
||||
<optgroup label="FR">
|
||||
<option value="Paris">Paris</option>
|
||||
<option value="Lyon">Lyon</option>
|
||||
<option value="Marseille">Marseille</option>
|
||||
</optgroup>
|
||||
<optgroup label="DE" disabled>
|
||||
<option value="Hamburg">Hamburg</option>
|
||||
<option value="Munich">Munich</option>
|
||||
<option value="Berlin">Berlin</option>
|
||||
</optgroup>
|
||||
<optgroup label="US">
|
||||
<option value="New York">New York</option>
|
||||
<option value="Washington" disabled>Washington</option>
|
||||
<option value="Michigan">Michigan</option>
|
||||
</optgroup>
|
||||
<optgroup label="SP">
|
||||
<option value="Madrid">Madrid</option>
|
||||
<option value="Barcelona">Barcelona</option>
|
||||
<option value="Malaga">Malaga</option>
|
||||
</optgroup>
|
||||
<optgroup label="CA">
|
||||
<option value="Montreal">Montreal</option>
|
||||
<option value="Toronto">Toronto</option>
|
||||
<option value="Vancouver">Vancouver</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
|
||||
<p><small>If the following example do not load, the Discogs rate limit has probably been reached. Try again later!</small></p>
|
||||
|
||||
<label for="choices-multiple-remote-fetch">Options from remote source (Fetch API) & limited to 5</label>
|
||||
<select class="form-control" name="choices-multiple-remote-fetch" id="choices-multiple-remote-fetch" multiple></select>
|
||||
|
||||
<label for="choices-multiple-rtl">Right-to-left</label>
|
||||
<select class="form-control" data-trigger name="choices-multiple-rtl" id="choices-multiple-rtl" placeholder="This is a placeholder"
|
||||
multiple dir="rtl">
|
||||
<option value="Dropdown item 1" selected>Dropdown item 1</option>
|
||||
<option value="Dropdown item 2">Dropdown item 2</option>
|
||||
<option value="Dropdown item 3">Dropdown item 3</option>
|
||||
<option value="Dropdown item 4" disabled>Dropdown item 4</option>
|
||||
</select>
|
||||
|
||||
<label for="label-event">Use label in event (add/remove)</label>
|
||||
<p id="message"></p>
|
||||
<select id="choices-multiple-labels" multiple></select>
|
||||
<hr>
|
||||
|
||||
<h2>Single select input</h2>
|
||||
<label for="choices-single-default">Default</label>
|
||||
<select class="form-control" data-trigger name="choices-single-default" id="choices-single-default" placeholder="This is a search placeholder">
|
||||
<option selected disabled>This is a placeholder</option>
|
||||
<option value="Dropdown item 1">Dropdown item 1</option>
|
||||
<option value="Dropdown item 2">Dropdown item 2</option>
|
||||
<option value="Dropdown item 3">Dropdown item 3</option>
|
||||
</select>
|
||||
|
||||
<p><small>If the following two examples do not load, the Discogs rate limit has probably been reached. Try again later!</small></p>
|
||||
|
||||
<label for="choices-single-remote-fetch">Options from remote source (Fetch API)</label>
|
||||
<select class="form-control" name="choices-single-remote-fetch" id="choices-single-remote-fetch" placeholder="Pick an Arctic Monkeys record"></select>
|
||||
|
||||
<label for="choices-single-remove-xhr">Options from remote source (XHR) & remove button</label>
|
||||
<select class="form-control" name="choices-single-remove-xhr" id="choices-single-remove-xhr" placeholder="Pick a Smiths record"></select>
|
||||
|
||||
<label for="choices-single-groups">Option groups</label>
|
||||
<select class="form-control" data-trigger name="choices-single-groups" id="choices-single-groups" placeholder="This is a placeholder">
|
||||
<optgroup label="UK">
|
||||
<option value="London">London</option>
|
||||
<option value="Manchester">Manchester</option>
|
||||
<option value="Liverpool">Liverpool</option>
|
||||
</optgroup>
|
||||
<optgroup label="FR">
|
||||
<option value="Paris">Paris</option>
|
||||
<option value="Lyon">Lyon</option>
|
||||
<option value="Marseille">Marseille</option>
|
||||
</optgroup>
|
||||
<optgroup label="DE" disabled>
|
||||
<option value="Hamburg">Hamburg</option>
|
||||
<option value="Munich">Munich</option>
|
||||
<option value="Berlin">Berlin</option>
|
||||
</optgroup>
|
||||
<optgroup label="US">
|
||||
<option value="New York">New York</option>
|
||||
<option value="Washington" disabled>Washington</option>
|
||||
<option value="Michigan">Michigan</option>
|
||||
</optgroup>
|
||||
<optgroup label="SP">
|
||||
<option value="Madrid">Madrid</option>
|
||||
<option value="Barcelona">Barcelona</option>
|
||||
<option value="Malaga">Malaga</option>
|
||||
</optgroup>
|
||||
<optgroup label="CA">
|
||||
<option value="Montreal">Montreal</option>
|
||||
<option value="Toronto">Toronto</option>
|
||||
<option value="Vancouver">Vancouver</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
|
||||
<label for="choices-single-rtl">Right-to-left</label>
|
||||
<select class="form-control" data-trigger name="choices-single-rtl" id="choices-single-rtl" placeholder="This is a placeholder"
|
||||
dir="rtl">
|
||||
<option value="Dropdown item 1">Dropdown item 1</option>
|
||||
<option value="Dropdown item 2">Dropdown item 2</option>
|
||||
<option value="Dropdown item 3">Dropdown item 3</option>
|
||||
</select>
|
||||
|
||||
<label for="choices-single-no-search">Options added via config with no search</label>
|
||||
<select class="form-control" name="choices-single-no-search" id="choices-single-no-search">
|
||||
<option value="0">Zero</option>
|
||||
</select>
|
||||
|
||||
<label for="choices-single-preset-options">Option and option groups added via config</label>
|
||||
<select class="form-control" name="choices-single-preset-options" id="choices-single-preset-options" placeholder="This is a placeholder"></select>
|
||||
|
||||
<label for="choices-single-selected-option">Option selected via config with custom properties</label>
|
||||
<p><small>Try searching for 'fantastic'</small></p>
|
||||
<select class="form-control" name="choices-single-selected-option" id="choices-single-selected-option" placeholder="This is a placeholder"></select>
|
||||
|
||||
<label for="choices-single-no-sorting">Options without sorting</label>
|
||||
<select class="form-control" name="choices-single-no-sorting" id="choices-single-no-sorting" placeholder="This is a placeholder">
|
||||
<option value="Madrid">Madrid</option>
|
||||
<option value="Toronto">Toronto</option>
|
||||
<option value="Vancouver">Vancouver</option>
|
||||
<option value="London">London</option>
|
||||
<option value="Manchester">Manchester</option>
|
||||
<option value="Liverpool">Liverpool</option>
|
||||
<option value="Paris">Paris</option>
|
||||
<option value="Malaga">Malaga</option>
|
||||
<option value="Washington" disabled>Washington</option>
|
||||
<option value="Lyon">Lyon</option>
|
||||
<option value="Marseille">Marseille</option>
|
||||
<option value="Hamburg">Hamburg</option>
|
||||
<option value="Munich">Munich</option>
|
||||
<option value="Barcelona">Barcelona</option>
|
||||
<option value="Berlin">Berlin</option>
|
||||
<option value="Montreal">Montreal</option>
|
||||
<option value="New York">New York</option>
|
||||
<option value="Michigan">Michigan</option>
|
||||
</select>
|
||||
|
||||
<label for="choices-single-custom-templates">Custom templates</label>
|
||||
<select class="form-control" name="choices-single-custom-templates" id="choices-single-custom-templates" placeholder="This is a placeholder">
|
||||
<option value="React">React</option>
|
||||
<option value="Angular">Angular</option>
|
||||
<option value="Ember">Ember</option>
|
||||
<option value="Vue">Vue</option>
|
||||
</select>
|
||||
|
||||
<p>Below is an example of how you could have two select inputs depend on eachother. 'Boroughs' will only be enabled if
|
||||
the value of 'States' is 'New York'</p>
|
||||
<label for="states">States</label>
|
||||
<select class="form-control" name="states" id="states" placeholder="Choose a state">
|
||||
<option value="Michigan">Michigan</option>
|
||||
<option value="Texas">Texas</option>
|
||||
<option value="Chicago">Chicago</option>
|
||||
<option value="New York">New York</option>
|
||||
<option value="Washington">Washington</option>
|
||||
</select>
|
||||
|
||||
<label for="boroughs">Boroughs</label>
|
||||
<select class="form-control" name="boroughs" id="boroughs" placeholder="Choose a borough">
|
||||
<option value="The Bronx">The Bronx</option>
|
||||
<option value="Brooklyn">Brooklyn</option>
|
||||
<option value="Manhattan">Manhattan</option>
|
||||
<option value="Queens">Queens</option>
|
||||
<option value="Staten Island">Staten Island</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var textRemove = new Choices(document.getElementById('choices-text-remove-button'), {
|
||||
delimiter: ',',
|
||||
editItems: true,
|
||||
maxItemCount: 5,
|
||||
removeItemButton: true,
|
||||
});
|
||||
|
||||
var textUniqueVals = new Choices('#choices-text-unique-values', {
|
||||
paste: false,
|
||||
duplicateItems: false,
|
||||
editItems: true,
|
||||
});
|
||||
|
||||
var texti18n = new Choices('#choices-text-i18n', {
|
||||
paste: false,
|
||||
duplicateItems: false,
|
||||
editItems: true,
|
||||
maxItemCount: 5,
|
||||
addItemText: function (value) {
|
||||
return 'Appuyez sur Entrée pour ajouter <b>"' + String(value) + '"</b>';
|
||||
},
|
||||
maxItemText: function (maxItemCount) {
|
||||
return String(maxItemCount) + 'valeurs peuvent être ajoutées';
|
||||
},
|
||||
uniqueItemText: 'Cette valeur est déjà présente',
|
||||
});
|
||||
|
||||
var textEmailFilter = new Choices('#choices-text-email-filter', {
|
||||
editItems: true,
|
||||
regexFilter: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
|
||||
}).setValue(['joe@bloggs.com']);
|
||||
|
||||
var textDisabled = new Choices('#choices-text-disabled', {
|
||||
addItems: false,
|
||||
removeItems: false,
|
||||
}).disable();
|
||||
|
||||
var textPrependAppendVal = new Choices('#choices-text-prepend-append-value', {
|
||||
prependValue: 'item-',
|
||||
appendValue: '-' + Date.now(),
|
||||
}).removeActiveItems();
|
||||
|
||||
var textPresetVal = new Choices('#choices-text-preset-values', {
|
||||
items: ['Josh Johnson', {
|
||||
value: 'joe@bloggs.co.uk',
|
||||
label: 'Joe Bloggs',
|
||||
customProperties: {
|
||||
description: 'Joe Blogg is such a generic name'
|
||||
}
|
||||
}],
|
||||
});
|
||||
|
||||
var multipleDefault = new Choices(document.getElementById('choices-multiple-groups'));
|
||||
|
||||
var multipleFetch = new Choices('#choices-multiple-remote-fetch', {
|
||||
placeholder: true,
|
||||
placeholderValue: 'Pick an Strokes record',
|
||||
maxItemCount: 5,
|
||||
}).ajax(function (callback) {
|
||||
fetch('https://api.discogs.com/artists/55980/releases?token=QBRmstCkwXEvCjTclCpumbtNwvVkEzGAdELXyRyW')
|
||||
.then(function (response) {
|
||||
response.json().then(function (data) {
|
||||
callback(data.releases, 'title', 'title');
|
||||
});
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.error(error);
|
||||
});
|
||||
});
|
||||
|
||||
var multipleCancelButton = new Choices('#choices-multiple-remove-button', {
|
||||
removeItemButton: true,
|
||||
});
|
||||
|
||||
/* Use label on event */
|
||||
var choicesSelect = new Choices('#choices-multiple-labels', {
|
||||
removeItemButton: true,
|
||||
choices: [
|
||||
{ value: 'One', label: 'Label One' },
|
||||
{ value: 'Two', label: 'Label Two', disabled: true },
|
||||
{ value: 'Three', label: 'Label Three' },
|
||||
],
|
||||
}).setChoices([
|
||||
{ value: 'Four', label: 'Label Four', disabled: true },
|
||||
{ value: 'Five', label: 'Label Five' },
|
||||
{ value: 'Six', label: 'Label Six', selected: true },
|
||||
], 'value', 'label', false);
|
||||
|
||||
choicesSelect.passedElement.addEventListener('addItem', function (event) {
|
||||
document.getElementById('message').innerHTML = 'You just added "' + event.detail.label + '"';
|
||||
});
|
||||
|
||||
choicesSelect.passedElement.addEventListener('removeItem', function (event) {
|
||||
document.getElementById('message').innerHTML = 'You just removed "' + event.detail.label + '"';
|
||||
});
|
||||
|
||||
var singleFetch = new Choices('#choices-single-remote-fetch', {
|
||||
placeholder: true,
|
||||
placeholderValue: 'Pick an Arctic Monkeys record'
|
||||
}).ajax(function (callback) {
|
||||
fetch('https://api.discogs.com/artists/391170/releases?token=QBRmstCkwXEvCjTclCpumbtNwvVkEzGAdELXyRyW')
|
||||
.then(function (response) {
|
||||
response.json().then(function (data) {
|
||||
callback(data.releases, 'title', 'title');
|
||||
singleFetch.setValueByChoice('Fake Tales Of San Francisco');
|
||||
});
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.error(error);
|
||||
});
|
||||
});
|
||||
|
||||
var singleXhrRemove = new Choices('#choices-single-remove-xhr', {
|
||||
removeItemButton: true,
|
||||
}).ajax(function (callback) {
|
||||
var request = new XMLHttpRequest();
|
||||
request.open('get', 'https://api.discogs.com/artists/83080/releases?token=QBRmstCkwXEvCjTclCpumbtNwvVkEzGAdELXyRyW', true);
|
||||
request.onreadystatechange = function () {
|
||||
var status;
|
||||
var data;
|
||||
if (request.readyState === 4) {
|
||||
status = request.status;
|
||||
if (status === 200) {
|
||||
data = JSON.parse(request.responseText);
|
||||
callback(data.releases, 'title', 'title');
|
||||
singleXhrRemove.setValueByChoice('How Soon Is Now?');
|
||||
} else {
|
||||
console.error(status);
|
||||
}
|
||||
}
|
||||
}
|
||||
request.send();
|
||||
});
|
||||
|
||||
var genericExamples = new Choices('[data-trigger]', {
|
||||
placeholderValue: 'This is a placeholder set in the config'
|
||||
});
|
||||
|
||||
var singleNoSearch = new Choices('#choices-single-no-search', {
|
||||
searchEnabled: false,
|
||||
removeItemButton: true,
|
||||
choices: [
|
||||
{ value: 'One', label: 'Label One' },
|
||||
{ value: 'Two', label: 'Label Two', disabled: true },
|
||||
{ value: 'Three', label: 'Label Three' },
|
||||
],
|
||||
}).setChoices([
|
||||
{ value: 'Four', label: 'Label Four', disabled: true },
|
||||
{ value: 'Five', label: 'Label Five' },
|
||||
{ value: 'Six', label: 'Label Six', selected: true },
|
||||
], 'value', 'label', false);
|
||||
|
||||
var singlePresetOpts = new Choices('#choices-single-preset-options', {
|
||||
placeholder: true,
|
||||
}).setChoices([{
|
||||
label: 'Group one',
|
||||
id: 1,
|
||||
disabled: false,
|
||||
choices: [
|
||||
{ value: 'Child One', label: 'Child One', selected: true },
|
||||
{ value: 'Child Two', label: 'Child Two', disabled: true },
|
||||
{ value: 'Child Three', label: 'Child Three' },
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Group two',
|
||||
id: 2,
|
||||
disabled: false,
|
||||
choices: [
|
||||
{ value: 'Child Four', label: 'Child Four', disabled: true },
|
||||
{ value: 'Child Five', label: 'Child Five' },
|
||||
{ value: 'Child Six', label: 'Child Six' },
|
||||
]
|
||||
}], 'value', 'label');
|
||||
|
||||
var singleSelectedOpt = new Choices('#choices-single-selected-option', {
|
||||
searchFields: ['label', 'value', 'customProperties.description'],
|
||||
choices: [
|
||||
{ value: 'One', label: 'Label One', selected: true },
|
||||
{ value: 'Two', label: 'Label Two', disabled: true },
|
||||
{
|
||||
value: 'Three', label: 'Label Three', customProperties: {
|
||||
description: 'This option is fantastic'
|
||||
}
|
||||
},
|
||||
],
|
||||
}).setValueByChoice('Two');
|
||||
|
||||
var singleNoSorting = new Choices('#choices-single-no-sorting', {
|
||||
shouldSort: false,
|
||||
});
|
||||
|
||||
var states = new Choices(document.getElementById('states'));
|
||||
|
||||
states.passedElement.addEventListener('change', function (e) {
|
||||
if (e.detail.value === 'New York') {
|
||||
boroughs.enable();
|
||||
} else {
|
||||
boroughs.disable();
|
||||
}
|
||||
});
|
||||
|
||||
var customTemplates = new Choices(document.getElementById('choices-single-custom-templates'), {
|
||||
callbackOnCreateTemplates: function (strToEl) {
|
||||
var classNames = this.config.classNames;
|
||||
var itemSelectText = this.config.itemSelectText;
|
||||
return {
|
||||
item: function (data) {
|
||||
return strToEl('\
|
||||
<div\
|
||||
class="'+ String(classNames.item) + ' ' + String(data.highlighted ? classNames.highlightedState : classNames.itemSelectable) + '"\
|
||||
data-item\
|
||||
data-id="'+ String(data.id) + '"\
|
||||
data-value="'+ String(data.value) + '"\
|
||||
'+ String(data.active ? 'aria-selected="true"' : '') + '\
|
||||
'+ String(data.disabled ? 'aria-disabled="true"' : '') + '\
|
||||
>\
|
||||
<span style="margin-right:10px;">🎉</span> ' + String(data.label) + '\
|
||||
</div>\
|
||||
');
|
||||
},
|
||||
choice: function (data) {
|
||||
return strToEl('\
|
||||
<div\
|
||||
class="'+ String(classNames.item) + ' ' + String(classNames.itemChoice) + ' ' + String(data.disabled ? classNames.itemDisabled : classNames.itemSelectable) + '"\
|
||||
data-select-text="'+ String(itemSelectText) + '"\
|
||||
data-choice \
|
||||
'+ String(data.disabled ? 'data-choice-disabled aria-disabled="true"' : 'data-choice-selectable') + '\
|
||||
data-id="'+ String(data.id) + '"\
|
||||
data-value="'+ String(data.value) + '"\
|
||||
'+ String(data.groupId > 0 ? 'role="treeitem"' : 'role="option"') + '\
|
||||
>\
|
||||
<span style="margin-right:10px;">👉🏽</span> ' + String(data.label) + '\
|
||||
</div>\
|
||||
');
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
var boroughs = new Choices(document.getElementById('boroughs')).disable();
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Google Analytics - Ignore me -->
|
||||
<script>
|
||||
window.ga = window.ga || function () { (ga.q = ga.q || []).push(arguments) }; ga.l = +new Date;
|
||||
ga('create', 'UA-31575166-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
<script async src='https://www.google-analytics.com/analytics.js'></script>
|
||||
<!-- End Google Analytics -->
|
||||
</body>
|
||||
|
||||
</html>
|
17
jsconfig.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"checkJs": true,
|
||||
"target": "es2020",
|
||||
"lib": ["esnext", "dom"],
|
||||
"types": ["cypress"],
|
||||
"strict": true,
|
||||
"moduleResolution": "node",
|
||||
/* Additional Checks */
|
||||
"noImplicitAny": false,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"strictNullChecks": false
|
||||
}
|
||||
}
|
14
lint-staged.config.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
module.exports = {
|
||||
'*.js': ['eslint --fix --quiet -f visualstudio', 'git add'],
|
||||
'*.{ts,scss,yaml,yml,md,html,json,babelrc,eslintrc}': [
|
||||
'prettier --write',
|
||||
'git add',
|
||||
],
|
||||
'src/icons/*.svg': [
|
||||
'prettier --write --parser=html --html-whitespace-sensitivity=ignore',
|
||||
'git add',
|
||||
],
|
||||
'.codecov.yml': () =>
|
||||
'curl -f --silent --data-binary @.codecov.yml https://codecov.io/validate',
|
||||
'src/scripts/**/*.js': () => 'npm run test:unit',
|
||||
};
|
23844
package-lock.json
generated
Normal file
169
package.json
|
@ -1,21 +1,33 @@
|
|||
{
|
||||
"name": "choices.js",
|
||||
"version": "2.8.12",
|
||||
"version": "10.2.0",
|
||||
"description": "A vanilla JS customisable text input/select box plugin",
|
||||
"main": "./assets/scripts/dist/choices.min.js",
|
||||
"main": "./public/assets/scripts/choices.js",
|
||||
"types": "./public/types/src/index.d.ts",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"test": "karma start --single-run --no-auto-watch tests/karma.config.js",
|
||||
"test:watch": "karma start --auto-watch --no-single-run tests/karma.config.js",
|
||||
"start": "run-p js:watch css:watch",
|
||||
"build": "run-p js:build css:build",
|
||||
"lint": "run-p lint:js lint:scss",
|
||||
"lint:js": "eslint src/scripts/**/*.ts",
|
||||
"lint:scss": "stylelint src/**/*.scss",
|
||||
"bundlesize": "bundlesize",
|
||||
"cypress:run": "cypress run --browser chrome",
|
||||
"cypress:open": "cypress open",
|
||||
"cypress:ci": "cypress run --browser chrome --record --group $GITHUB_REF --ci-build-id $GITHUB_SHA",
|
||||
"test": "run-s test:unit test:e2e",
|
||||
"test:unit": "cross-env TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test mocha",
|
||||
"test:unit:watch": "npm run test:unit -- --watch --inspect=5556",
|
||||
"test:unit:coverage": "NODE_ENV=test nyc --reporter=lcov --reporter=text --reporter=text-summary mocha",
|
||||
"test:e2e": "run-p --race start cypress:run",
|
||||
"js:watch": "cross-env NODE_ENV=development node server.js",
|
||||
"js:build": "webpack --config webpack.config.prod.js",
|
||||
"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 assets/styles/scss/base.scss assets/styles/css/base.css && node-sass --output-style expanded --include-path scss assets/styles/scss/choices.scss assets/styles/css/choices.css",
|
||||
"css:prefix": "postcss --use autoprefixer -b 'last 2 versions' assets/styles/css/*.css -d assets/styles/css/",
|
||||
"css:min": "csso assets/styles/css/base.css assets/styles/css/base.min.css && csso assets/styles/css/choices.css assets/styles/css/choices.min.css",
|
||||
"js:build": "concurrently --prefix-colors yellow,green \"webpack --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",
|
||||
"dev": "dev"
|
||||
"css:build": "run-s css:sass css:prefix css:min",
|
||||
"css:sass": "sass -I scss src/styles/base.scss public/assets/styles/base.css && sass -I scss src/styles/choices.scss public/assets/styles/choices.css",
|
||||
"css:prefix": "postcss public/assets/styles/*.css --use autoprefixer --no-map --env prod --dir public/assets/styles",
|
||||
"css:min": "csso public/assets/styles/base.css --output public/assets/styles/base.min.css && csso public/assets/styles/choices.css --output public/assets/styles/choices.min.css",
|
||||
"deploy": "git subtree push --prefix public origin gh-pages",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -23,59 +35,110 @@
|
|||
},
|
||||
"author": "Josh Johnson",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"public/assets/scripts",
|
||||
"public/assets/styles",
|
||||
"public/types",
|
||||
"src",
|
||||
"!src/**/*.test.js",
|
||||
"types"
|
||||
],
|
||||
"bugs": {
|
||||
"url": "https://github.com/jshjohnson/Choices/issues"
|
||||
},
|
||||
"homepage": "https://github.com/jshjohnson/Choices#readme",
|
||||
"keywords": [
|
||||
"customisable",
|
||||
"input",
|
||||
"select",
|
||||
"vanilla",
|
||||
"plugin",
|
||||
"js"
|
||||
],
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^6.3.3",
|
||||
"babel-core": "^6.7.2",
|
||||
"babel-eslint": "^6.1.2",
|
||||
"babel-loader": "^6.2.4",
|
||||
"babel-preset-es2015": "^6.6.0",
|
||||
"concurrently": "^3.1.0",
|
||||
"core-js": "^2.4.1",
|
||||
"csso": "^1.8.2",
|
||||
"es6-promise": "^3.2.1",
|
||||
"eslint": "^3.3.0",
|
||||
"eslint-config-airbnb": "^10.0.1",
|
||||
"eslint-loader": "^1.5.0",
|
||||
"eslint-plugin-import": "^1.13.0",
|
||||
"eslint-plugin-jsx-a11y": "^2.2.3",
|
||||
"eslint-plugin-react": "^6.4.1",
|
||||
"jasmine-core": "2.4.1",
|
||||
"karma": "^1.1.0",
|
||||
"karma-coverage": "^1.0.0",
|
||||
"karma-es6-shim": "^1.0.0",
|
||||
"karma-htmlfile-reporter": "^0.3.4",
|
||||
"karma-jasmine": "^1.0.2",
|
||||
"karma-phantomjs-launcher": "^1.0.1",
|
||||
"karma-spec-reporter": "0.0.26",
|
||||
"karma-webpack": "^1.7.0",
|
||||
"node-sass": "^3.4.2",
|
||||
"nodemon": "^1.9.1",
|
||||
"opn-cli": "^3.1.0",
|
||||
"postcss-cli": "^2.5.1",
|
||||
"webpack": "^1.12.14",
|
||||
"webpack-dashboard": "^0.1.8",
|
||||
"webpack-dev-server": "^1.14.1",
|
||||
"whatwg-fetch": "^1.0.0",
|
||||
"wrapper-webpack-plugin": "^0.1.7"
|
||||
"@babel/core": "^7.20.5",
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"@babel/register": "^7.18.9",
|
||||
"@types/chai": "^4.3.4",
|
||||
"@types/mocha": "^10.0.1",
|
||||
"@types/sinon": "^10.0.13",
|
||||
"@types/sinon-chai": "^3.2.9",
|
||||
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||
"@typescript-eslint/parser": "^5.45.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"babel-loader": "^9.1.0",
|
||||
"bundlesize": "^0.18.1",
|
||||
"chai": "^4.3.7",
|
||||
"cross-env": "^7.0.3",
|
||||
"csso-cli": "^4.0.1",
|
||||
"cypress": "11.2.0",
|
||||
"eslint": "^8.28.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-airbnb-typescript": "^17.0.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-compat": "4.0.2",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-sort-class-members": "^1.15.2",
|
||||
"eslint-webpack-plugin": "^3.2.0",
|
||||
"express": "^4.18.2",
|
||||
"husky": "^8.0.2",
|
||||
"jsdom": "^20.0.3",
|
||||
"lint-staged": "^13.0.4",
|
||||
"mocha": "^10.1.0",
|
||||
"nodemon": "^2.0.20",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"nyc": "^15.1.0",
|
||||
"postcss": "^8.4.19",
|
||||
"postcss-cli": "^10.0.0",
|
||||
"prettier": "^2.8.0",
|
||||
"sass": "^1.56.1",
|
||||
"sinon": "^15.0.0",
|
||||
"sinon-chai": "^3.7.0",
|
||||
"stylelint": "^14.15.0",
|
||||
"stylelint-config-standard": "^29.0.0",
|
||||
"stylelint-config-standard-scss": "^6.1.0",
|
||||
"ts-loader": "^9.4.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.3",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-cli": "^5.0.0",
|
||||
"webpack-dev-middleware": "^6.0.1",
|
||||
"webpack-hot-middleware": "^2.25.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "^2.2.5",
|
||||
"fuse.js": "^2.2.2",
|
||||
"redux": "^3.3.1"
|
||||
"deepmerge": "^4.2.2",
|
||||
"fuse.js": "^6.6.2",
|
||||
"redux": "^4.2.0"
|
||||
},
|
||||
"npmName": "choices.js",
|
||||
"npmFileMap": [
|
||||
{
|
||||
"basePath": "assets",
|
||||
"files": [
|
||||
"scripts/dist/*",
|
||||
"styles/css/*",
|
||||
"icons/*"
|
||||
"public/assets/scripts/*",
|
||||
"public/assets/styles/*",
|
||||
"public/types/*",
|
||||
"src/icons/*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"nyc": {
|
||||
"include": [
|
||||
"src/scripts/**/**/*.js"
|
||||
],
|
||||
"exclude": [
|
||||
"src/scripts/**/**/*.test.js"
|
||||
]
|
||||
},
|
||||
"bundlesize": [
|
||||
{
|
||||
"path": "public/assets/scripts/choices.min.js",
|
||||
"maxSize": "25 kB"
|
||||
},
|
||||
{
|
||||
"path": "public/assets/styles/choices.min.css",
|
||||
"maxSize": "2.5 kB"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 574 B After Width: | Height: | Size: 574 B |
Before Width: | Height: | Size: 887 B After Width: | Height: | Size: 887 B |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 6 KiB After Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 3 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
27
public/assets/scripts/.eslintrc.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
// get polyfill settings from top level config
|
||||
// @ts-ignore
|
||||
const { settings } = require('../../../.eslintrc.json');
|
||||
|
||||
// Adding non-polyfilable Symbol-related functions as they are most probably
|
||||
// behind the flag
|
||||
|
||||
settings.polyfills.push(
|
||||
'Symbol.toStringTag',
|
||||
'Symbol.for',
|
||||
'Object.getOwnPropertySymbols',
|
||||
'Object.getOwnPropertyDescriptors',
|
||||
'Promise', // Promise is gate checked
|
||||
);
|
||||
|
||||
module.exports = /** @type {import('eslint').Linter.Config} */ ({
|
||||
root: true,
|
||||
extends: ['plugin:compat/recommended'],
|
||||
parserOptions: {
|
||||
// ensure that it's compatible with ES5 browsers, so, no `const`, etc
|
||||
ecmaVersion: 5,
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
},
|
||||
settings,
|
||||
});
|
6925
public/assets/scripts/choices.js
Normal file
2
public/assets/scripts/choices.min.js
vendored
Normal file
1
public/assets/scripts/choices.min.js.LICENSE.txt
Normal file
|
@ -0,0 +1 @@
|
|||
/*! choices.js v10.2.0 | © 2022 Josh Johnson | https://github.com/jshjohnson/Choices#readme */
|
|
@ -1,16 +1,19 @@
|
|||
/*=============================================
|
||||
/* =============================================
|
||||
= Generic styling =
|
||||
=============================================*/
|
||||
============================================= */
|
||||
* {
|
||||
-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%;
|
||||
|
@ -21,7 +24,7 @@ body {
|
|||
font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
color: #FFFFFF;
|
||||
color: #fff;
|
||||
background-color: #333;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
@ -36,6 +39,7 @@ label {
|
|||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
hr {
|
||||
|
@ -46,15 +50,22 @@ 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 {
|
||||
color: #FFFFFF;
|
||||
a,
|
||||
a:visited,
|
||||
a:focus {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
@ -68,42 +79,50 @@ a, a:visited, a:focus {
|
|||
border-radius: 2.5px;
|
||||
font-size: 14px;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
appearance: none;
|
||||
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;
|
||||
}
|
||||
|
||||
label + p {
|
||||
margin-top: -4px;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 40em;
|
||||
padding: 48px;
|
||||
}
|
||||
|
||||
@media (max-width: 620px) {
|
||||
.container {
|
||||
padding: 0;
|
||||
|
@ -111,12 +130,13 @@ h6, .h6 {
|
|||
}
|
||||
|
||||
.section {
|
||||
background-color: #FFFFFF;
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.section a, .section a:visited, .section a:focus {
|
||||
.section a,
|
||||
.section a:visited,
|
||||
.section a:focus {
|
||||
color: #00bcd4;
|
||||
}
|
||||
|
||||
|
@ -125,7 +145,7 @@ h6, .h6 {
|
|||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.logo__img {
|
||||
.logo-img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: inline-block;
|
||||
|
@ -138,6 +158,10 @@ h6, .h6 {
|
|||
display: none;
|
||||
}
|
||||
|
||||
.push-bottom {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.zero-bottom {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
@ -150,4 +174,8 @@ h6, .h6 {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
/*===== End of Section comment block ======*/
|
||||
[data-test-hook] {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
/* ===== End of Section comment block ====== */
|
1
public/assets/styles/base.css.map
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"sourceRoot":"","sources":["../../../src/styles/base.scss"],"names":[],"mappings":"AAAA;AAAA;AAAA;AAYA;EACE;EACA;;;AAGF;AAAA;AAAA;EAGE;;;AAGF;AAAA;EAEE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;AAAA;AAAA;AAAA;AAAA;AAAA;EAME;EACA;EACA;EACA;;;AAGF;AAAA;AAAA;EAGE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,eAtFiB;;;AAyFnB;AAAA;EAEE,WA1FoB;;;AA6FtB;AAAA;EAEE,WA9FoB;;;AAiGtB;AAAA;EAEE,WAlGoB;;;AAqGtB;AAAA;EAEE,WAtGoB;;;AAyGtB;AAAA;EAEE,WA1GoB;;;AA6GtB;AAAA;EAEE,WA9GoB;;;AAiHtB;EACE;;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EANF;IAOI;;;;AAIJ;EACE;EACA,SAxIiB;EAyIjB;;AAEA;AAAA;AAAA;EAGE;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE,eArKiB;;;AAwKnB;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE,eArLiB;;;AAwLnB","file":"base.css"}
|
1
public/assets/styles/base.min.css
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
*{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}*,::after,::before{box-sizing:border-box}body,html{position:relative;margin:0;width:100%;height:100%}body{font-family:"Helvetica Neue",Helvetica,Arial,"Lucida Grande",sans-serif;font-size:16px;line-height:1.4;color:#fff;background-color:#333;overflow-x:hidden}hr,label{display:block}label,p{margin-bottom:8px}label{font-size:14px;font-weight:500;cursor:pointer}p{margin-top:0}hr{margin:30px 0;border:0;border-bottom:1px solid #eaeaea;height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:12px;font-weight:400;line-height:1.2}a,a:focus,a:visited{color:#fff;text-decoration:none;font-weight:600}.form-control{display:block;width:100%;background-color:#f9f9f9;padding:12px;border:1px solid #ddd;border-radius:2.5px;font-size:14px;-webkit-appearance:none;appearance:none;margin-bottom:24px}.h1,h1{font-size:32px}.h2,h2{font-size:24px}.h3,h3{font-size:20px}.h4,h4{font-size:18px}.h5,h5{font-size:16px}.h6,h6{font-size:14px}label+p{margin-top:-4px}.container{display:block;margin:auto;max-width:40em;padding:48px}@media (max-width:620px){.container{padding:0}}.section{background-color:#fff;padding:24px;color:#333}.section a,.section a:focus,.section a:visited{color:#00bcd4}.logo{display:block;margin-bottom:12px}.logo-img{width:100%;height:auto;display:inline-block;max-width:100%;vertical-align:top;padding:6px 0}.visible-ie{display:none}.push-bottom{margin-bottom:24px}.zero-bottom{margin-bottom:0}.zero-top{margin-top:0}.text-center{text-align:center}[data-test-hook]{margin-bottom:24px}
|
|
@ -1,55 +1,53 @@
|
|||
/*===============================
|
||||
/* ===============================
|
||||
= Choices =
|
||||
===============================*/
|
||||
=============================== */
|
||||
.choices {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin-bottom: 24px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.choices:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.choices:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.choices.is-disabled .choices__inner, .choices.is-disabled .choices__input {
|
||||
background-color: #EAEAEA;
|
||||
.choices.is-open {
|
||||
overflow: visible;
|
||||
}
|
||||
.choices.is-disabled .choices__inner,
|
||||
.choices.is-disabled .choices__input {
|
||||
background-color: #eaeaea;
|
||||
cursor: not-allowed;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.choices.is-disabled .choices__item {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.choices [hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.choices[data-type*="select-one"] {
|
||||
.choices[data-type*=select-one] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.choices[data-type*="select-one"] .choices__inner {
|
||||
.choices[data-type*=select-one] .choices__inner {
|
||||
padding-bottom: 7.5px;
|
||||
}
|
||||
|
||||
.choices[data-type*="select-one"] .choices__input {
|
||||
.choices[data-type*=select-one] .choices__input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #DDDDDD;
|
||||
background-color: #FFFFFF;
|
||||
border-bottom: 1px solid #ddd;
|
||||
background-color: #fff;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.choices[data-type*="select-one"] .choices__button {
|
||||
background-image: url("../../icons/cross-inverse.svg");
|
||||
.choices[data-type*=select-one] .choices__button {
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==");
|
||||
padding: 0;
|
||||
background-size: 8px;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
|
@ -58,23 +56,23 @@
|
|||
height: 20px;
|
||||
width: 20px;
|
||||
border-radius: 10em;
|
||||
opacity: .5;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
.choices[data-type*="select-one"] .choices__button:hover, .choices[data-type*="select-one"] .choices__button:focus {
|
||||
.choices[data-type*=select-one] .choices__button:hover, .choices[data-type*=select-one] .choices__button:focus {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.choices[data-type*="select-one"] .choices__button:focus {
|
||||
box-shadow: 0px 0px 0px 2px #00BCD4;
|
||||
.choices[data-type*=select-one] .choices__button:focus {
|
||||
box-shadow: 0 0 0 2px #00bcd4;
|
||||
}
|
||||
|
||||
.choices[data-type*="select-one"]:after {
|
||||
.choices[data-type*=select-one] .choices__item[data-value=""] .choices__button {
|
||||
display: none;
|
||||
}
|
||||
.choices[data-type*=select-one]::after {
|
||||
content: "";
|
||||
height: 0;
|
||||
width: 0;
|
||||
border-style: solid;
|
||||
border-color: #333333 transparent transparent transparent;
|
||||
border-color: #333 transparent transparent transparent;
|
||||
border-width: 5px;
|
||||
position: absolute;
|
||||
right: 11.5px;
|
||||
|
@ -82,29 +80,27 @@
|
|||
margin-top: -2.5px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.choices[data-type*="select-one"].is-open:after {
|
||||
border-color: transparent transparent #333333 transparent;
|
||||
.choices[data-type*=select-one].is-open::after {
|
||||
border-color: transparent transparent #333 transparent;
|
||||
margin-top: -7.5px;
|
||||
}
|
||||
|
||||
.choices[data-type*="select-one"][dir="rtl"]:after {
|
||||
.choices[data-type*=select-one][dir=rtl]::after {
|
||||
left: 11.5px;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
.choices[data-type*="select-one"][dir="rtl"] .choices__button {
|
||||
.choices[data-type*=select-one][dir=rtl] .choices__button {
|
||||
right: auto;
|
||||
left: 0;
|
||||
margin-left: 25px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.choices[data-type*="select-multiple"] .choices__inner, .choices[data-type*="text"] .choices__inner {
|
||||
.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;
|
||||
|
@ -113,14 +109,16 @@
|
|||
margin-left: 8px;
|
||||
padding-left: 16px;
|
||||
border-left: 1px solid #008fa1;
|
||||
background-image: url("../../icons/cross.svg");
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjRkZGIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==");
|
||||
background-size: 8px;
|
||||
width: 8px;
|
||||
line-height: 1;
|
||||
opacity: .75;
|
||||
opacity: 0.75;
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -130,21 +128,18 @@
|
|||
width: 100%;
|
||||
background-color: #f9f9f9;
|
||||
padding: 7.5px 7.5px 3.75px;
|
||||
border: 1px solid #DDDDDD;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 2.5px;
|
||||
font-size: 14px;
|
||||
min-height: 44px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.is-focused .choices__inner, .is-open .choices__inner {
|
||||
border-color: #b7b7b7;
|
||||
}
|
||||
|
||||
.is-open .choices__inner {
|
||||
border-radius: 2.5px 2.5px 0 0;
|
||||
}
|
||||
|
||||
.is-flipped.is-open .choices__inner {
|
||||
border-radius: 0 0 2.5px 2.5px;
|
||||
}
|
||||
|
@ -154,18 +149,15 @@
|
|||
padding-left: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.choices__list--single {
|
||||
display: inline-block;
|
||||
padding: 4px 16px 4px 4px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
[dir="rtl"] .choices__list--single {
|
||||
[dir=rtl] .choices__list--single {
|
||||
padding-right: 4px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.choices__list--single .choices__item {
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -173,7 +165,6 @@
|
|||
.choices__list--multiple {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.choices__list--multiple .choices__item {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
@ -183,111 +174,99 @@
|
|||
font-weight: 500;
|
||||
margin-right: 3.75px;
|
||||
margin-bottom: 3.75px;
|
||||
background-color: #00BCD4;
|
||||
background-color: #00bcd4;
|
||||
border: 1px solid #00a5bb;
|
||||
color: #FFFFFF;
|
||||
color: #fff;
|
||||
word-break: break-all;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.choices__list--multiple .choices__item[data-deletable] {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
[dir="rtl"] .choices__list--multiple .choices__item {
|
||||
[dir=rtl] .choices__list--multiple .choices__item {
|
||||
margin-right: 0;
|
||||
margin-left: 3.75px;
|
||||
}
|
||||
|
||||
.choices__list--multiple .choices__item.is-highlighted {
|
||||
background-color: #00a5bb;
|
||||
border: 1px solid #008fa1;
|
||||
}
|
||||
|
||||
.is-disabled .choices__list--multiple .choices__item {
|
||||
background-color: #aaaaaa;
|
||||
border: 1px solid #919191;
|
||||
}
|
||||
|
||||
.choices__list--dropdown {
|
||||
display: none;
|
||||
.choices__list--dropdown, .choices__list[aria-expanded] {
|
||||
visibility: hidden;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
background-color: #FFFFFF;
|
||||
border: 1px solid #DDDDDD;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ddd;
|
||||
top: 100%;
|
||||
margin-top: -1px;
|
||||
border-bottom-left-radius: 2.5px;
|
||||
border-bottom-right-radius: 2.5px;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
will-change: visibility;
|
||||
}
|
||||
|
||||
.choices__list--dropdown.is-active {
|
||||
display: block;
|
||||
.is-active.choices__list--dropdown, .is-active.choices__list[aria-expanded] {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.is-open .choices__list--dropdown {
|
||||
.is-open .choices__list--dropdown, .is-open .choices__list[aria-expanded] {
|
||||
border-color: #b7b7b7;
|
||||
}
|
||||
|
||||
.is-flipped .choices__list--dropdown {
|
||||
.is-flipped .choices__list--dropdown, .is-flipped .choices__list[aria-expanded] {
|
||||
top: auto;
|
||||
bottom: 100%;
|
||||
margin-top: 0;
|
||||
margin-bottom: -1px;
|
||||
border-radius: .25rem .25rem 0 0;
|
||||
border-radius: 0.25rem 0.25rem 0 0;
|
||||
}
|
||||
|
||||
.choices__list--dropdown .choices__list {
|
||||
.choices__list--dropdown .choices__list, .choices__list[aria-expanded] .choices__list {
|
||||
position: relative;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
will-change: scroll-position;
|
||||
}
|
||||
|
||||
.choices__list--dropdown .choices__item {
|
||||
.choices__list--dropdown .choices__item, .choices__list[aria-expanded] .choices__item {
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
[dir="rtl"] .choices__list--dropdown .choices__item {
|
||||
[dir=rtl] .choices__list--dropdown .choices__item, [dir=rtl] .choices__list[aria-expanded] .choices__item {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.choices__list--dropdown .choices__item--selectable {
|
||||
.choices__list--dropdown .choices__item--selectable, .choices__list[aria-expanded] .choices__item--selectable {
|
||||
padding-right: 100px;
|
||||
}
|
||||
.choices__list--dropdown .choices__item--selectable:after {
|
||||
.choices__list--dropdown .choices__item--selectable::after, .choices__list[aria-expanded] .choices__item--selectable::after {
|
||||
content: attr(data-select-text);
|
||||
font-size: 12px;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 50%;
|
||||
-webkit-transform: translateY(-50%);
|
||||
transform: translateY(-50%);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
[dir="rtl"] .choices__list--dropdown .choices__item--selectable {
|
||||
[dir=rtl] .choices__list--dropdown .choices__item--selectable, [dir=rtl] .choices__list[aria-expanded] .choices__item--selectable {
|
||||
text-align: right;
|
||||
padding-left: 100px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
[dir="rtl"] .choices__list--dropdown .choices__item--selectable:after {
|
||||
[dir=rtl] .choices__list--dropdown .choices__item--selectable::after, [dir=rtl] .choices__list[aria-expanded] .choices__item--selectable::after {
|
||||
right: auto;
|
||||
left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.choices__list--dropdown .choices__item--selectable.is-highlighted {
|
||||
.choices__list--dropdown .choices__item--selectable.is-highlighted, .choices__list[aria-expanded] .choices__item--selectable.is-highlighted {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
.choices__list--dropdown .choices__item--selectable.is-highlighted:after {
|
||||
opacity: .5;
|
||||
.choices__list--dropdown .choices__item--selectable.is-highlighted::after, .choices__list[aria-expanded] .choices__item--selectable.is-highlighted::after {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.choices__item {
|
||||
|
@ -301,10 +280,8 @@
|
|||
.choices__item--disabled {
|
||||
cursor: not-allowed;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
opacity: .5;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.choices__heading {
|
||||
|
@ -318,15 +295,13 @@
|
|||
.choices__button {
|
||||
text-indent: -9999px;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
appearance: none;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.choices__button:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
@ -342,18 +317,24 @@
|
|||
max-width: 100%;
|
||||
padding: 4px 0 4px 2px;
|
||||
}
|
||||
|
||||
.choices__input:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
[dir="rtl"] .choices__input {
|
||||
.choices__input::-webkit-search-decoration, .choices__input::-webkit-search-cancel-button, .choices__input::-webkit-search-results-button, .choices__input::-webkit-search-results-decoration {
|
||||
display: none;
|
||||
}
|
||||
.choices__input::-ms-clear, .choices__input::-ms-reveal {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
[dir=rtl] .choices__input {
|
||||
padding-right: 2px;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.choices__placeholder {
|
||||
opacity: .5;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/*===== End of Choices ======*/
|
||||
/* ===== End of Choices ====== */
|
1
public/assets/styles/choices.css.map
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"sourceRoot":"","sources":["../../../src/styles/choices.scss"],"names":[],"mappings":"AAAA;AAAA;AAAA;AA2BA;EACE;EACA;EACA,eApBkB;EAqBlB,WAxBqB;;AA0BrB;EACE;;AAGF;EACE;;AAGF;EACE;;AAIA;AAAA;EAEE,kBAlCsB;EAmCtB;EACA;;AAEF;EACE;;AAIJ;EACE;;;AAIJ;EACE;;AACA;EACE;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEF;EACE,kBApDyB;EAqDzB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAEE;;AAGF;EACE;;AAGJ;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAIA;EACE;EACA;;AAEF;EACE;EACA;EACA;EACA;;;AAOJ;AAAA;EACE;;AAEF;AAAA;EACE;EACA;EACA;EACA;EACA;EACA,aA5HoB;EA6HpB;EACA;EACA,kBA9HiB;EA+HjB,iBAjIuB;EAkIvB,OAlIuB;EAmIvB;EACA;EACA;;AAEA;AAAA;AAAA;EAEE;;;AAKN;EACE;EACA;EACA;EACA,kBA1JiB;EA2JjB;EACA;EACA,eA/JsB;EAgKtB,WAnKqB;EAoKrB;EACA;;AAEA;EAEE;;AAGF;EACE;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;;AAOF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAEF;EACE;;;AAIJ;EACE;;AACA;EACE;EACA;EACA,eA9MyB;EA+MzB;EACA,WAnNmB;EAoNnB;EACA;EACA;EACA,kBA9MoB;EA+MpB;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;;AAKN;EACE;EACA,SApOgB;EAqOhB;EACA;EACA,kBAjP0B;EAkP1B;EACA;EACA;EACA,2BAzPsB;EA0PtB,4BA1PsB;EA2PtB;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA,WA3RmB;;AA6RnB;EACE;;AAIF;EADF;IAEI;;EAEA;IACE;IACA,WAtSe;IAuSf;IACA;IACA;IACA;IACA;;EAGF;IACE;IACA;IACA;;EAEA;IACE;IACA;;;AAKN;EACE;;AAEA;EACE;;;AAUR;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA,WAxVqB;EAyVrB;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;EACA;EACA,kBA3WiB;EA4WjB,WAjXqB;EAkXrB;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EAIE;;AAGF;EAEE;EACA;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;;;AAGF","file":"choices.css"}
|
1
public/assets/styles/choices.min.css
vendored
Normal file
957
public/index.html
Normal file
|
@ -0,0 +1,957 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<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="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="assets/styles/base.min.css" />
|
||||
<!-- End ignore these -->
|
||||
|
||||
<!-- Optional includes -->
|
||||
<script src="https://cdn.polyfill.io/v3/polyfill.min.js?features=Array.from%2Ces5%2Ces6%2CSymbol%2CSymbol.iterator%2CDOMTokenList%2CObject.assign%2CCustomEvent%2CElement.prototype.classList%2CElement.prototype.closest%2CElement.prototype.dataset%2CArray.prototype.find%2CArray.prototype.includes%2Cfetch"></script>
|
||||
<!-- End optional includes -->
|
||||
|
||||
<!-- Choices includes -->
|
||||
<link rel="stylesheet" href="assets/styles/choices.min.css" />
|
||||
<script src="assets/scripts/choices.js"></script>
|
||||
<!-- End Choices includes -->
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<style>
|
||||
.hidden-ie {
|
||||
display: none;
|
||||
}
|
||||
.visible-ie {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="section">
|
||||
<a href="https://github.com/jshjohnson/Choices" class="logo">
|
||||
<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 the jQuery
|
||||
dependency.
|
||||
</p>
|
||||
<p>
|
||||
For all config options, visit the
|
||||
<a href="https://github.com/jshjohnson/Choices">GitHub repo</a>.
|
||||
</p>
|
||||
|
||||
<hr />
|
||||
<p class="h4 text-center">
|
||||
<strong
|
||||
>Interested in writing your own ES6 JavaScript plugins? Check out
|
||||
<a href="https://ES6.io/friend/JOHNSON">ES6.io</a> for great
|
||||
tutorials! 💪🏼</strong
|
||||
>
|
||||
</p>
|
||||
<p class="h4 text-center">
|
||||
<strong>Sponsored by:</strong>
|
||||
<br />
|
||||
<a href="https://wanderermaps.com/" target="_blank" rel="noopener noreferrer">
|
||||
<img src="https://cdn.shopify.com/s/files/1/0614/3357/7715/files/Logo_BlackWithBackground_150x.png?v=1644802773" alt="Wanderer Maps logo">
|
||||
</a>
|
||||
</p>
|
||||
<hr />
|
||||
|
||||
<h2>Text inputs</h2>
|
||||
<label for="choices-text-remove-button"
|
||||
>Limited to 5 values with remove button</label
|
||||
>
|
||||
<input
|
||||
class="form-control"
|
||||
id="choices-text-remove-button"
|
||||
type="text"
|
||||
value="preset-1,preset-2"
|
||||
placeholder="Enter something"
|
||||
/>
|
||||
|
||||
<label for="choices-text-unique-values"
|
||||
>Unique values only, no pasting</label
|
||||
>
|
||||
<input
|
||||
class="form-control"
|
||||
id="choices-text-unique-values"
|
||||
type="text"
|
||||
value="preset-1, preset-2"
|
||||
placeholder="This is a placeholder"
|
||||
class="custom class"
|
||||
/>
|
||||
|
||||
<label for="choices-text-email-filter">Email addresses only</label>
|
||||
<input
|
||||
class="form-control"
|
||||
id="choices-text-email-filter"
|
||||
type="text"
|
||||
placeholder="This is a placeholder"
|
||||
/>
|
||||
|
||||
<label for="choices-text-disabled">Disabled</label>
|
||||
<input
|
||||
class="form-control"
|
||||
id="choices-text-disabled"
|
||||
type="text"
|
||||
value="josh@joshuajohnson.co.uk, joe@bloggs.co.uk"
|
||||
placeholder="This is a placeholder"
|
||||
/>
|
||||
|
||||
<label for="choices-text-prepend-append-value"
|
||||
>Prepends and appends a value to each items return value</label
|
||||
>
|
||||
<input
|
||||
class="form-control"
|
||||
id="choices-text-prepend-append-value"
|
||||
type="text"
|
||||
value="preset-1, preset-2"
|
||||
placeholder="This is a placeholder"
|
||||
/>
|
||||
|
||||
<label for="choices-text-preset-values"
|
||||
>Preset values passed through options</label
|
||||
>
|
||||
<input
|
||||
class="form-control"
|
||||
id="choices-text-preset-values"
|
||||
type="text"
|
||||
value="Michael Smith"
|
||||
placeholder="This is a placeholder"
|
||||
/>
|
||||
|
||||
<label for="choices-text-i18n">I18N labels</label>
|
||||
<input class="form-control" id="choices-text-i18n" type="text" />
|
||||
|
||||
<label for="choices-text-rtl">Right-to-left</label>
|
||||
<input
|
||||
data-trigger
|
||||
class="form-control"
|
||||
id="choices-text-rtl"
|
||||
type="text"
|
||||
value="Value 1, Value 2"
|
||||
dir="rtl"
|
||||
/>
|
||||
|
||||
<hr />
|
||||
|
||||
<h2>Multiple select input</h2>
|
||||
<label for="choices-multiple-default">Default</label>
|
||||
<select
|
||||
class="form-control"
|
||||
data-trigger
|
||||
name="choices-multiple-default"
|
||||
id="choices-multiple-default"
|
||||
placeholder="This is a placeholder"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1" selected>Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
<option value="Choice 4" disabled>Choice 4</option>
|
||||
</select>
|
||||
|
||||
<label for="choices-multiple-remove-button">With remove button</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-multiple-remove-button"
|
||||
id="choices-multiple-remove-button"
|
||||
placeholder="This is a placeholder"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1" selected>Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
<option value="Choice 4">Choice 4</option>
|
||||
</select>
|
||||
|
||||
<label for="choices-multiple-groups">Option groups</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-multiple-groups"
|
||||
id="choices-multiple-groups"
|
||||
placeholder="This is a placeholder"
|
||||
multiple
|
||||
>
|
||||
<option value="">Choose a city</option>
|
||||
<optgroup label="UK">
|
||||
<option value="London">London</option>
|
||||
<option value="Manchester">Manchester</option>
|
||||
<option value="Liverpool">Liverpool</option>
|
||||
</optgroup>
|
||||
<optgroup label="FR">
|
||||
<option value="Paris">Paris</option>
|
||||
<option value="Lyon">Lyon</option>
|
||||
<option value="Marseille">Marseille</option>
|
||||
</optgroup>
|
||||
<optgroup label="DE" disabled>
|
||||
<option value="Hamburg">Hamburg</option>
|
||||
<option value="Munich">Munich</option>
|
||||
<option value="Berlin">Berlin</option>
|
||||
</optgroup>
|
||||
<optgroup label="US">
|
||||
<option value="New York">New York</option>
|
||||
<option value="Washington" disabled>Washington</option>
|
||||
<option value="Michigan">Michigan</option>
|
||||
</optgroup>
|
||||
<optgroup label="SP">
|
||||
<option value="Madrid">Madrid</option>
|
||||
<option value="Barcelona">Barcelona</option>
|
||||
<option value="Malaga">Malaga</option>
|
||||
</optgroup>
|
||||
<optgroup label="CA">
|
||||
<option value="Montreal">Montreal</option>
|
||||
<option value="Toronto">Toronto</option>
|
||||
<option value="Vancouver">Vancouver</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
|
||||
<p>
|
||||
<small
|
||||
>If the following example do not load, the Discogs rate limit has
|
||||
probably been reached. Try again later!</small
|
||||
>
|
||||
</p>
|
||||
|
||||
<label for="choices-multiple-remote-fetch"
|
||||
>Options from remote source (Fetch API) & limited to 5</label
|
||||
>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-multiple-remote-fetch"
|
||||
id="choices-multiple-remote-fetch"
|
||||
multiple
|
||||
></select>
|
||||
|
||||
<label for="choices-multiple-rtl">Right-to-left</label>
|
||||
<select
|
||||
class="form-control"
|
||||
data-trigger
|
||||
name="choices-multiple-rtl"
|
||||
id="choices-multiple-rtl"
|
||||
placeholder="This is a placeholder"
|
||||
multiple
|
||||
dir="rtl"
|
||||
>
|
||||
<option value="Choice 1" selected>Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
<option value="Choice 4" disabled>Choice 4</option>
|
||||
</select>
|
||||
|
||||
<label for="choices-multiple-labels"
|
||||
>Use label in event (add/remove)</label
|
||||
>
|
||||
<p id="message"></p>
|
||||
<select id="choices-multiple-labels" multiple></select>
|
||||
<hr />
|
||||
|
||||
<h2>Single select input</h2>
|
||||
<label id="choices-single-default-label" for="choices-single-default"
|
||||
>Default</label
|
||||
>
|
||||
<select
|
||||
class="form-control"
|
||||
data-trigger
|
||||
name="choices-single-default"
|
||||
id="choices-single-default"
|
||||
placeholder="This is a search placeholder"
|
||||
>
|
||||
<option value="">This is a placeholder</option>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
</select>
|
||||
|
||||
<p>
|
||||
<small
|
||||
>If the following two examples do not load, the Discogs rate limit
|
||||
has probably been reached. Try again later!</small
|
||||
>
|
||||
</p>
|
||||
|
||||
<label for="choices-single-remote-fetch"
|
||||
>Options from remote source (Fetch API)</label
|
||||
>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-single-remote-fetch"
|
||||
id="choices-single-remote-fetch"
|
||||
>
|
||||
<option value="">Pick an Arctic Monkeys' record</option>
|
||||
</select>
|
||||
|
||||
<label for="choices-single-remove-xhr"
|
||||
>Options from remote source (Fetch API) & remove button</label
|
||||
>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-single-remove-xhr"
|
||||
id="choices-single-remove-xhr"
|
||||
>
|
||||
<option value="">Pick a Smiths' record</option>
|
||||
</select>
|
||||
|
||||
<label for="choices-single-groups">Option groups</label>
|
||||
<select
|
||||
class="form-control"
|
||||
data-trigger
|
||||
name="choices-single-groups"
|
||||
id="choices-single-groups"
|
||||
>
|
||||
<option value="">Choose a city</option>
|
||||
<optgroup label="UK">
|
||||
<option value="London">London</option>
|
||||
<option value="Manchester">Manchester</option>
|
||||
<option value="Liverpool">Liverpool</option>
|
||||
</optgroup>
|
||||
<optgroup label="FR">
|
||||
<option value="Paris">Paris</option>
|
||||
<option value="Lyon">Lyon</option>
|
||||
<option value="Marseille">Marseille</option>
|
||||
</optgroup>
|
||||
<optgroup label="DE" disabled>
|
||||
<option value="Hamburg">Hamburg</option>
|
||||
<option value="Munich">Munich</option>
|
||||
<option value="Berlin">Berlin</option>
|
||||
</optgroup>
|
||||
<optgroup label="US">
|
||||
<option value="New York">New York</option>
|
||||
<option value="Washington" disabled>Washington</option>
|
||||
<option value="Michigan">Michigan</option>
|
||||
</optgroup>
|
||||
<optgroup label="SP">
|
||||
<option value="Madrid">Madrid</option>
|
||||
<option value="Barcelona">Barcelona</option>
|
||||
<option value="Malaga">Malaga</option>
|
||||
</optgroup>
|
||||
<optgroup label="CA">
|
||||
<option value="Montreal">Montreal</option>
|
||||
<option value="Toronto">Toronto</option>
|
||||
<option value="Vancouver">Vancouver</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
|
||||
<label for="choices-single-rtl">Right-to-left</label>
|
||||
<select
|
||||
class="form-control"
|
||||
data-trigger
|
||||
name="choices-single-rtl"
|
||||
id="choices-single-rtl"
|
||||
dir="rtl"
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
</select>
|
||||
|
||||
<label for="choices-single-no-search"
|
||||
>Options added via config with no search</label
|
||||
>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-single-no-search"
|
||||
id="choices-single-no-search"
|
||||
>
|
||||
<option value="0">Zero</option>
|
||||
</select>
|
||||
|
||||
<label for="choices-single-preset-options"
|
||||
>Option and option groups added via config</label
|
||||
>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-single-preset-options"
|
||||
id="choices-single-preset-options"
|
||||
></select>
|
||||
|
||||
<label for="choices-single-selected-option"
|
||||
>Option selected via config with custom properties</label
|
||||
>
|
||||
<p>
|
||||
<small>Try searching for 'fantastic', "Label 3" should display</small>
|
||||
</p>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-single-selected-option"
|
||||
id="choices-single-selected-option"
|
||||
></select>
|
||||
|
||||
<label for="choices-with-custom-props-via-html"
|
||||
>Option searchable by custom properties via
|
||||
<code>data-custom-properties</code> attribute</label
|
||||
>
|
||||
<p>
|
||||
<small>Try searching for 'fantastic', "Label 3" should display</small>
|
||||
</p>
|
||||
<select class="form-control" id="choices-with-custom-props-via-html">
|
||||
<option value="Dropdown item 1">Label One</option>
|
||||
<option value="Dropdown item 2" selected disabled>Label Two</option>
|
||||
<option
|
||||
value="Dropdown item 3"
|
||||
data-custom-properties="This option is fantastic"
|
||||
>Label Three</option
|
||||
>
|
||||
<option
|
||||
value="Dropdown item 4"
|
||||
data-custom-properties="{ 'description': 'foo' }"
|
||||
>Label Four</option
|
||||
>
|
||||
</select>
|
||||
|
||||
<label
|
||||
id="choices-single-no-sorting-label"
|
||||
for="choices-single-no-sorting"
|
||||
>Options without sorting</label
|
||||
>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-single-no-sorting"
|
||||
id="choices-single-no-sorting"
|
||||
>
|
||||
<option value="Madrid">Madrid</option>
|
||||
<option value="Toronto">Toronto</option>
|
||||
<option value="Vancouver">Vancouver</option>
|
||||
<option value="London">London</option>
|
||||
<option value="Manchester">Manchester</option>
|
||||
<option value="Liverpool">Liverpool</option>
|
||||
<option value="Paris">Paris</option>
|
||||
<option value="Malaga">Malaga</option>
|
||||
<option value="Washington" disabled>Washington</option>
|
||||
<option value="Lyon">Lyon</option>
|
||||
<option value="Marseille">Marseille</option>
|
||||
<option value="Hamburg">Hamburg</option>
|
||||
<option value="Munich">Munich</option>
|
||||
<option value="Barcelona">Barcelona</option>
|
||||
<option value="Berlin">Berlin</option>
|
||||
<option value="Montreal">Montreal</option>
|
||||
<option value="New York">New York</option>
|
||||
<option value="Michigan">Michigan</option>
|
||||
</select>
|
||||
|
||||
<label for="choices-single-custom-templates">Custom templates</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-single-custom-templates"
|
||||
id="choices-single-custom-templates"
|
||||
>
|
||||
<option value="React">React</option>
|
||||
<option value="Angular">Angular</option>
|
||||
<option value="Ember">Ember</option>
|
||||
<option value="Vue">Vue</option>
|
||||
</select>
|
||||
|
||||
<p>
|
||||
Below is an example of how you could have two select inputs depend on
|
||||
eachother. 'Tube stations' will only be enabled if the value of
|
||||
'Cities' is 'London'
|
||||
</p>
|
||||
<label for="cities">Cities</label>
|
||||
<select class="form-control" name="cities" id="cities">
|
||||
<option value="">Choose a city</option>
|
||||
<option value="Leeds">Leeds</option>
|
||||
<option value="Manchester">Manchester</option>
|
||||
<option value="London">London</option>
|
||||
<option value="Sheffield">Sheffield</option>
|
||||
<option value="Newcastle">Newcastle</option>
|
||||
</select>
|
||||
|
||||
<label for="tube-stations">Tube stations</label>
|
||||
<select class="form-control" name="tube-stations" id="tube-stations">
|
||||
<option value="">Choose a tube station</option>
|
||||
<option value="Moorgate">Moorgate</option>
|
||||
<option value="St Pauls">St Pauls</option>
|
||||
<option value="Old Street">Old Street</option>
|
||||
<option value="Liverpool Street">Liverpool Street</option>
|
||||
<option value="Kings Cross St. Pancras"
|
||||
>Kings Cross St. Pancras</option
|
||||
>
|
||||
</select>
|
||||
|
||||
<hr />
|
||||
<h2>Form interaction</h2>
|
||||
<p>Change the values and press reset to restore to initial state.</p>
|
||||
<form>
|
||||
<label for="reset-simple">Change me!</label>
|
||||
<select class="form-control" name="reset-simple" id="reset-simple">
|
||||
<option value="Option 1">Option 1</option>
|
||||
<option value="Option 2" selected>Option 2</option>
|
||||
<option value="Option 3">Option 3</option>
|
||||
<option value="Option 4">Option 4</option>
|
||||
<option value="Option 5">Option 5</option>
|
||||
</select>
|
||||
|
||||
<label for="reset-multiple">And me!</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="reset-multiple"
|
||||
id="reset-multiple"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1" selected>Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
<option value="Choice 4" disabled>Choice 4</option>
|
||||
</select>
|
||||
|
||||
<button type="reset">Reset</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var genericExamples = document.querySelectorAll('[data-trigger]');
|
||||
for (i = 0; i < genericExamples.length; ++i) {
|
||||
var element = genericExamples[i];
|
||||
new Choices(element, {
|
||||
allowHTML: true,
|
||||
placeholderValue: 'This is a placeholder set in the config',
|
||||
searchPlaceholderValue: 'This is a search placeholder',
|
||||
});
|
||||
}
|
||||
|
||||
var textRemove = new Choices(
|
||||
document.getElementById('choices-text-remove-button'),
|
||||
{
|
||||
allowHTML: true,
|
||||
delimiter: ',',
|
||||
editItems: true,
|
||||
maxItemCount: 5,
|
||||
removeItemButton: true,
|
||||
}
|
||||
);
|
||||
|
||||
var textUniqueVals = new Choices('#choices-text-unique-values', {
|
||||
allowHTML: true,
|
||||
paste: false,
|
||||
duplicateItemsAllowed: false,
|
||||
editItems: true,
|
||||
});
|
||||
|
||||
var texti18n = new Choices('#choices-text-i18n', {
|
||||
allowHTML: true,
|
||||
paste: false,
|
||||
duplicateItemsAllowed: false,
|
||||
editItems: true,
|
||||
maxItemCount: 5,
|
||||
addItemText: function(value) {
|
||||
return (
|
||||
'Appuyez sur Entrée pour ajouter <b>"' + String(value) + '"</b>'
|
||||
);
|
||||
},
|
||||
maxItemText: function(maxItemCount) {
|
||||
return String(maxItemCount) + 'valeurs peuvent être ajoutées';
|
||||
},
|
||||
uniqueItemText: 'Cette valeur est déjà présente',
|
||||
});
|
||||
|
||||
var textEmailFilter = new Choices('#choices-text-email-filter', {
|
||||
allowHTML: true,
|
||||
editItems: true,
|
||||
addItemFilter: function(value) {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const regex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
const expression = new RegExp(regex.source, 'i');
|
||||
return expression.test(value);
|
||||
},
|
||||
}).setValue(['joe@bloggs.com']);
|
||||
|
||||
var textDisabled = new Choices('#choices-text-disabled', {
|
||||
allowHTML: true,
|
||||
addItems: false,
|
||||
removeItems: false,
|
||||
}).disable();
|
||||
|
||||
var textPrependAppendVal = new Choices(
|
||||
'#choices-text-prepend-append-value',
|
||||
{
|
||||
allowHTML: true,
|
||||
prependValue: 'item-',
|
||||
appendValue: '-' + Date.now(),
|
||||
}
|
||||
).removeActiveItems();
|
||||
|
||||
var textPresetVal = new Choices('#choices-text-preset-values', {
|
||||
allowHTML: true,
|
||||
items: [
|
||||
'Josh Johnson',
|
||||
{
|
||||
value: 'joe@bloggs.co.uk',
|
||||
label: 'Joe Bloggs',
|
||||
customProperties: {
|
||||
description: 'Joe Blogg is such a generic name',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
var multipleDefault = new Choices(
|
||||
document.getElementById('choices-multiple-groups'),
|
||||
{ allowHTML: true }
|
||||
);
|
||||
|
||||
var multipleFetch = new Choices('#choices-multiple-remote-fetch', {
|
||||
allowHTML: false,
|
||||
placeholder: true,
|
||||
placeholderValue: 'Pick an Strokes record',
|
||||
maxItemCount: 5,
|
||||
}).setChoices(function() {
|
||||
return fetch(
|
||||
'https://api.discogs.com/artists/55980/releases?token=QBRmstCkwXEvCjTclCpumbtNwvVkEzGAdELXyRyW'
|
||||
)
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
return data.releases.map(function(release) {
|
||||
return { value: release.title, label: release.title };
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var multipleCancelButton = new Choices(
|
||||
'#choices-multiple-remove-button',
|
||||
{
|
||||
allowHTML: true,
|
||||
removeItemButton: true,
|
||||
}
|
||||
);
|
||||
|
||||
/* Use label on event */
|
||||
var choicesSelect = new Choices('#choices-multiple-labels', {
|
||||
allowHTML: true,
|
||||
removeItemButton: true,
|
||||
choices: [
|
||||
{ value: 'One', label: 'Label One' },
|
||||
{ value: 'Two', label: 'Label Two', disabled: true },
|
||||
{ value: 'Three', label: 'Label Three' },
|
||||
],
|
||||
}).setChoices(
|
||||
[
|
||||
{ value: 'Four', label: 'Label Four', disabled: true },
|
||||
{ value: 'Five', label: 'Label Five' },
|
||||
{ value: 'Six', label: 'Label Six', selected: true },
|
||||
],
|
||||
'value',
|
||||
'label',
|
||||
false
|
||||
);
|
||||
|
||||
choicesSelect.passedElement.element.addEventListener(
|
||||
'addItem',
|
||||
function(event) {
|
||||
document.getElementById('message').innerHTML =
|
||||
'You just added "' + event.detail.label + '"';
|
||||
}
|
||||
);
|
||||
|
||||
choicesSelect.passedElement.element.addEventListener(
|
||||
'removeItem',
|
||||
function(event) {
|
||||
document.getElementById('message').innerHTML =
|
||||
'You just removed "' + event.detail.label + '"';
|
||||
}
|
||||
);
|
||||
|
||||
var singleFetch = new Choices('#choices-single-remote-fetch', {
|
||||
allowHTML: false,
|
||||
searchPlaceholderValue: 'Search for an Arctic Monkeys record',
|
||||
})
|
||||
.setChoices(function() {
|
||||
return fetch(
|
||||
'https://api.discogs.com/artists/391170/releases?token=QBRmstCkwXEvCjTclCpumbtNwvVkEzGAdELXyRyW'
|
||||
)
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
return data.releases.map(function(release) {
|
||||
return { label: release.title, value: release.title };
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(function(instance) {
|
||||
instance.setChoiceByValue('Fake Tales Of San Francisco');
|
||||
});
|
||||
|
||||
var singleXhrRemove = new Choices('#choices-single-remove-xhr', {
|
||||
allowHTML: true,
|
||||
removeItemButton: true,
|
||||
searchPlaceholderValue: "Search for a Smiths' record",
|
||||
}).setChoices(function(callback) {
|
||||
return fetch(
|
||||
'https://api.discogs.com/artists/83080/releases?token=QBRmstCkwXEvCjTclCpumbtNwvVkEzGAdELXyRyW'
|
||||
)
|
||||
.then(function(res) {
|
||||
return res.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
return data.releases.map(function(release) {
|
||||
return { label: release.title, value: release.title };
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var singleNoSearch = new Choices('#choices-single-no-search', {
|
||||
allowHTML: true,
|
||||
searchEnabled: false,
|
||||
removeItemButton: true,
|
||||
choices: [
|
||||
{ value: 'One', label: 'Label One' },
|
||||
{ value: 'Two', label: 'Label Two', disabled: true },
|
||||
{ value: 'Three', label: 'Label Three' },
|
||||
],
|
||||
}).setChoices(
|
||||
[
|
||||
{ value: 'Four', label: 'Label Four', disabled: true },
|
||||
{ value: 'Five', label: 'Label Five' },
|
||||
{ value: 'Six', label: 'Label Six', selected: true },
|
||||
],
|
||||
'value',
|
||||
'label',
|
||||
false
|
||||
);
|
||||
|
||||
var singlePresetOpts = new Choices('#choices-single-preset-options', {
|
||||
allowHTML: true,
|
||||
placeholder: true,
|
||||
}).setChoices(
|
||||
[
|
||||
{
|
||||
label: 'Group one',
|
||||
id: 1,
|
||||
disabled: false,
|
||||
choices: [
|
||||
{ value: 'Child One', label: 'Child One', selected: true },
|
||||
{ value: 'Child Two', label: 'Child Two', disabled: true },
|
||||
{ value: 'Child Three', label: 'Child Three' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Group two',
|
||||
id: 2,
|
||||
disabled: false,
|
||||
choices: [
|
||||
{ value: 'Child Four', label: 'Child Four', disabled: true },
|
||||
{ value: 'Child Five', label: 'Child Five' },
|
||||
{ value: 'Child Six', label: 'Child Six' },
|
||||
],
|
||||
},
|
||||
],
|
||||
'value',
|
||||
'label'
|
||||
);
|
||||
|
||||
var singleSelectedOpt = new Choices('#choices-single-selected-option', {
|
||||
allowHTML: true,
|
||||
searchFields: ['label', 'value', 'customProperties.description'],
|
||||
choices: [
|
||||
{ value: 'One', label: 'Label One', selected: true },
|
||||
{ value: 'Two', label: 'Label Two', disabled: true },
|
||||
{
|
||||
value: 'Three',
|
||||
label: 'Label Three',
|
||||
customProperties: {
|
||||
description: 'This option is fantastic',
|
||||
},
|
||||
},
|
||||
],
|
||||
}).setChoiceByValue('Two');
|
||||
|
||||
var customChoicesPropertiesViaDataAttributes = new Choices(
|
||||
'#choices-with-custom-props-via-html',
|
||||
{
|
||||
allowHTML: true,
|
||||
searchFields: ['label', 'value', 'customProperties'],
|
||||
}
|
||||
);
|
||||
|
||||
var singleNoSorting = new Choices('#choices-single-no-sorting', {
|
||||
allowHTML: true,
|
||||
shouldSort: false,
|
||||
labelId: 'choices-single-no-sorting-label',
|
||||
});
|
||||
|
||||
var cities = new Choices(document.getElementById('cities'), { allowHTML: true });
|
||||
var tubeStations = new Choices(
|
||||
document.getElementById('tube-stations'),
|
||||
{ allowHTML: true }
|
||||
).disable();
|
||||
|
||||
cities.passedElement.element.addEventListener('change', function(e) {
|
||||
if (e.detail.value === 'London') {
|
||||
tubeStations.enable();
|
||||
} else {
|
||||
tubeStations.disable();
|
||||
}
|
||||
});
|
||||
|
||||
var customTemplates = new Choices(
|
||||
document.getElementById('choices-single-custom-templates'),
|
||||
{
|
||||
allowHTML: true,
|
||||
callbackOnCreateTemplates: function(strToEl) {
|
||||
var classNames = this.config.classNames;
|
||||
var itemSelectText = this.config.itemSelectText;
|
||||
return {
|
||||
item: function({ classNames }, data) {
|
||||
return strToEl(
|
||||
'\
|
||||
<div\
|
||||
class="' +
|
||||
String(classNames.item) +
|
||||
' ' +
|
||||
String(
|
||||
data.highlighted
|
||||
? classNames.highlightedState
|
||||
: classNames.itemSelectable
|
||||
) +
|
||||
'"\
|
||||
data-item\
|
||||
data-id="' +
|
||||
String(data.id) +
|
||||
'"\
|
||||
data-value="' +
|
||||
String(data.value) +
|
||||
'"\
|
||||
' +
|
||||
String(data.active ? 'aria-selected="true"' : '') +
|
||||
'\
|
||||
' +
|
||||
String(data.disabled ? 'aria-disabled="true"' : '') +
|
||||
'\
|
||||
>\
|
||||
<span style="margin-right:10px;">🎉</span> ' +
|
||||
String(data.label) +
|
||||
'\
|
||||
</div>\
|
||||
'
|
||||
);
|
||||
},
|
||||
choice: function({ classNames }, data) {
|
||||
return strToEl(
|
||||
'\
|
||||
<div\
|
||||
class="' +
|
||||
String(classNames.item) +
|
||||
' ' +
|
||||
String(classNames.itemChoice) +
|
||||
' ' +
|
||||
String(
|
||||
data.disabled
|
||||
? classNames.itemDisabled
|
||||
: classNames.itemSelectable
|
||||
) +
|
||||
'"\
|
||||
data-select-text="' +
|
||||
String(itemSelectText) +
|
||||
'"\
|
||||
data-choice \
|
||||
' +
|
||||
String(
|
||||
data.disabled
|
||||
? 'data-choice-disabled aria-disabled="true"'
|
||||
: 'data-choice-selectable'
|
||||
) +
|
||||
'\
|
||||
data-id="' +
|
||||
String(data.id) +
|
||||
'"\
|
||||
data-value="' +
|
||||
String(data.value) +
|
||||
'"\
|
||||
' +
|
||||
String(
|
||||
data.groupId > 0 ? 'role="treeitem"' : 'role="option"'
|
||||
) +
|
||||
'\
|
||||
>\
|
||||
<span style="margin-right:10px;">👉🏽</span> ' +
|
||||
String(data.label) +
|
||||
'\
|
||||
</div>\
|
||||
'
|
||||
);
|
||||
},
|
||||
};
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
var resetSimple = new Choices(document.getElementById('reset-simple'), {
|
||||
allowHTML: true,
|
||||
});
|
||||
|
||||
var resetMultiple = new Choices('#reset-multiple', {
|
||||
allowHTML: true,
|
||||
removeItemButton: true,
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Google Analytics - Ignore me -->
|
||||
<script>
|
||||
window.ga =
|
||||
window.ga ||
|
||||
function() {
|
||||
(ga.q = ga.q || []).push(arguments);
|
||||
};
|
||||
ga.l = +new Date();
|
||||
ga('create', 'UA-31575166-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
<script async src="https://www.google-analytics.com/analytics.js"></script>
|
||||
<!-- End Google Analytics -->
|
||||
</body>
|
||||
</html>
|
1
public/robots.txt
Normal file
|
@ -0,0 +1 @@
|
|||
Disallow: /test/*
|
628
public/test/select-multiple/index.html
Normal file
|
@ -0,0 +1,628 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<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="../../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="../../assets/styles/base.min.css" />
|
||||
<!-- End ignore these -->
|
||||
|
||||
<!-- Choices includes -->
|
||||
<link rel="stylesheet" href="../../assets/styles/choices.min.css" />
|
||||
<script src="../../assets/scripts/choices.min.js"></script>
|
||||
<!-- End Choices includes -->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="section">
|
||||
<h2>Select multiple inputs</h2>
|
||||
<div data-test-hook="basic">
|
||||
<label for="choices-basic">Basic</label>
|
||||
<button class="disable push-bottom">Disable</button>
|
||||
<button class="enable push-bottom">Enable</button>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-basic"
|
||||
id="choices-basic"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Find me">Choice 3</option>
|
||||
<option value="Choice 4">Choice 4</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="remove-button">
|
||||
<label for="choices-remove-button">Remove button</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-remove-button"
|
||||
id="choices-remove-button"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
<option value="Choice 4">Choice 4</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="disabled-choice">
|
||||
<label for="choices-disabled-choice">Disabled choice</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-disabled-choice"
|
||||
id="choices-disabled-choice"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
<option value="Choice 4" disabled>Choice 4</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="add-items-disabled">
|
||||
<label for="choices-add-items-disabled">Add items disabled</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-add-items-disabled"
|
||||
id="choices-add-items-disabled"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1" selected>Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="disabled-via-attr">
|
||||
<label for="choices-disabled-via-attr">Disabled via attribute</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-disabled-via-attr"
|
||||
id="choices-disabled-via-attr"
|
||||
multiple
|
||||
disabled
|
||||
>
|
||||
<option value="Choice 1" selected>Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="selection-limit">
|
||||
<label for="choices-selection-limit">Input limit</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-selection-limit"
|
||||
id="choices-selection-limit"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
<option value="Choice 4">Choice 4</option>
|
||||
<option value="Choice 5">Choice 5</option>
|
||||
<option value="Choice 6">Choice 6</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="prepend-append">
|
||||
<label for="choices-prepend-append">Prepend/append</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-prepend-append"
|
||||
id="choices-prepend-append"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="render-choice-limit">
|
||||
<label for="choices-render-choice-limit">Render choice limit</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-render-choice-limit"
|
||||
id="choices-render-choice-limit"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="search-floor">
|
||||
<label for="choices-search-floor">Search floor</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-search-floor"
|
||||
id="choices-search-floor"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="placeholder-via-option-value">
|
||||
<label for="choices-placeholder-via-option-value"
|
||||
>Placeholder via empty option value</label
|
||||
>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-placeholder-via-option-value"
|
||||
id="choices-placeholder-via-option-value"
|
||||
multiple
|
||||
>
|
||||
<option value="">I am a placeholder</option>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="placeholder-via-option-attr">
|
||||
<label for="choices-placeholder-via-option-attr"
|
||||
>Placeholder via option attribute</label
|
||||
>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-placeholder-via-option-attr"
|
||||
id="choices-placeholder-via-option-attr"
|
||||
multiple
|
||||
>
|
||||
<option placeholder>I am a placeholder</option>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="remote-data">
|
||||
<label for="choices-remote-data">Remote data</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-remote-data"
|
||||
id="choices-remote-data"
|
||||
multiple
|
||||
>
|
||||
<option value="">I am a placeholder</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="scrolling-dropdown">
|
||||
<label for="choices-scrolling-dropdown">Scrolling dropdown</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-scrolling-dropdown"
|
||||
id="choices-scrolling-dropdown"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
<option value="Choice 4">Choice 4</option>
|
||||
<option value="Choice 5">Choice 5</option>
|
||||
<option value="Choice 6">Choice 6</option>
|
||||
<option value="Choice 7">Choice 7</option>
|
||||
<option value="Choice 8">Choice 8</option>
|
||||
<option value="Choice 9">Choice 9</option>
|
||||
<option value="Choice 10">Choice 10</option>
|
||||
<option value="Choice 11">Choice 11</option>
|
||||
<option value="Choice 12">Choice 12</option>
|
||||
<option value="Choice 13">Choice 13</option>
|
||||
<option value="Choice 14">Choice 14</option>
|
||||
<option value="Choice 15">Choice 15</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="groups">
|
||||
<label for="choices-groups">Choice groups</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-groups"
|
||||
id="choices-groups"
|
||||
multiple
|
||||
>
|
||||
<optgroup label="UK">
|
||||
<option value="London">London</option>
|
||||
<option value="Manchester">Manchester</option>
|
||||
<option value="Liverpool">Liverpool</option>
|
||||
</optgroup>
|
||||
<optgroup label="FR">
|
||||
<option value="Paris">Paris</option>
|
||||
<option value="Lyon">Lyon</option>
|
||||
<option value="Marseille">Marseille</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="custom-properties">
|
||||
<label for="choices-custom-properties">Custom properties</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-custom-properties"
|
||||
id="choices-custom-properties"
|
||||
multiple
|
||||
></select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="custom-properties-html">
|
||||
<label for="choices-custom-properties-html">Custom properties</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-custom-properties-html"
|
||||
id="choices-custom-properties-html"
|
||||
>
|
||||
<option value="Dropdown item 1">Label One</option>
|
||||
<option value="Dropdown item 2">Label Two</option>
|
||||
<option
|
||||
value="Dropdown item 3"
|
||||
data-custom-properties="This option is fantastic"
|
||||
>Label Three</option
|
||||
>
|
||||
<option
|
||||
value="Dropdown item 4"
|
||||
data-custom-properties="{ 'description': 'foo' }"
|
||||
>Label Four</option
|
||||
>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="non-string-values">
|
||||
<label for="choices-non-string-values">Non-string values</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-non-string-values"
|
||||
id="choices-non-string-values"
|
||||
></select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="within-form">
|
||||
<form>
|
||||
<label for="choices-within-form">Within form</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-within-form"
|
||||
id="choices-within-form"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="set-choice-by-value">
|
||||
<label for="choices-set-choice-by-value"
|
||||
>Dynamically set choice by value</label
|
||||
>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-set-choice-by-value"
|
||||
id="choices-set-choice-by-value"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
<option value="Choice 3">Choice 3</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="search-by-label">
|
||||
<label for="choices-search-by-label">Search by label</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-search-by-label"
|
||||
id="choices-search-by-label"
|
||||
multiple
|
||||
>
|
||||
<option value="value1">label1</option>
|
||||
<option value="value2">label2</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="allowhtml-undefined">
|
||||
<label for="choices-allowhtml-undefined">HTML allowed by default</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-allowhtml-undefined"
|
||||
id="choices-allowhtml-undefined"
|
||||
multiple
|
||||
></select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="allowhtml-true">
|
||||
<label for="choices-allowhtml-true">HTML allowed</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-allowhtml-true"
|
||||
id="choices-allowhtml-true"
|
||||
multiple
|
||||
></select>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="allowhtml-false">
|
||||
<label for="choices-allowhtml-false">HTML disabled</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-allowhtml-false"
|
||||
id="choices-allowhtml-false"
|
||||
multiple
|
||||
></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const choicesBasic = new Choices('#choices-basic', {
|
||||
allowHTML: true,
|
||||
});
|
||||
|
||||
document
|
||||
.querySelector('button.disable')
|
||||
.addEventListener('click', () => {
|
||||
choicesBasic.disable();
|
||||
});
|
||||
|
||||
document
|
||||
.querySelector('button.enable')
|
||||
.addEventListener('click', () => {
|
||||
choicesBasic.enable();
|
||||
});
|
||||
|
||||
new Choices('#choices-remove-button', {
|
||||
allowHTML: true,
|
||||
removeItemButton: true,
|
||||
});
|
||||
|
||||
new Choices('#choices-disabled-choice', {
|
||||
allowHTML: true,
|
||||
});
|
||||
|
||||
new Choices('#choices-add-items-disabled', {
|
||||
allowHTML: true,
|
||||
addItems: false,
|
||||
});
|
||||
|
||||
new Choices('#choices-disabled-via-attr', {
|
||||
allowHTML: true,
|
||||
});
|
||||
|
||||
new Choices('#choices-selection-limit', {
|
||||
allowHTML: true,
|
||||
maxItemCount: 5,
|
||||
});
|
||||
|
||||
new Choices('#choices-prepend-append', {
|
||||
allowHTML: true,
|
||||
prependValue: 'before-',
|
||||
appendValue: '-after',
|
||||
});
|
||||
|
||||
new Choices('#choices-render-choice-limit', {
|
||||
allowHTML: true,
|
||||
renderChoiceLimit: 1,
|
||||
});
|
||||
|
||||
new Choices('#choices-search-floor', {
|
||||
allowHTML: true,
|
||||
searchFloor: 5,
|
||||
});
|
||||
|
||||
new Choices('#choices-placeholder-via-option-value', {
|
||||
allowHTML: true,
|
||||
});
|
||||
|
||||
new Choices('#choices-placeholder-via-option-attr', {
|
||||
allowHTML: true,
|
||||
});
|
||||
|
||||
new Choices('#choices-remote-data', {
|
||||
allowHTML: true,
|
||||
shouldSort: false,
|
||||
}).setChoices(async () => {
|
||||
const data = await fetch('/data');
|
||||
return data.json();
|
||||
});
|
||||
|
||||
new Choices('#choices-scrolling-dropdown', {
|
||||
allowHTML: true,
|
||||
shouldSort: false,
|
||||
});
|
||||
|
||||
new Choices('#choices-groups', {
|
||||
allowHTML: true,
|
||||
});
|
||||
|
||||
new Choices('#choices-custom-properties', {
|
||||
allowHTML: true,
|
||||
searchFields: ['label', 'value', 'customProperties.country'],
|
||||
choices: [
|
||||
{
|
||||
id: 1,
|
||||
value: 'London',
|
||||
label: 'London',
|
||||
customProperties: {
|
||||
country: 'United Kingdom',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
value: 'Berlin',
|
||||
label: 'Berlin',
|
||||
customProperties: {
|
||||
country: 'Germany',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
value: 'Lisbon',
|
||||
label: 'Lisbon',
|
||||
customProperties: {
|
||||
country: 'Portugal',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
new Choices('#choices-custom-properties-html', {
|
||||
allowHTML: true,
|
||||
searchFields: ['label', 'value', 'customProperties'],
|
||||
});
|
||||
|
||||
new Choices('#choices-non-string-values', {
|
||||
allowHTML: true,
|
||||
choices: [
|
||||
{
|
||||
id: 1,
|
||||
label: 'Number',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
label: 'Boolean',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
label: 'Object',
|
||||
value: {
|
||||
test: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
label: 'Array',
|
||||
value: ['test'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
new Choices('#choices-within-form', {
|
||||
allowHTML: true,
|
||||
});
|
||||
|
||||
new Choices('#choices-set-choice-by-value', {
|
||||
allowHTML: true,
|
||||
}).setChoiceByValue('Choice 2');
|
||||
|
||||
new Choices('#choices-search-by-label', {
|
||||
allowHTML: true,
|
||||
searchFields: ['label']
|
||||
});
|
||||
|
||||
new Choices('#choices-allowhtml-undefined', {
|
||||
choices: [
|
||||
{
|
||||
id: 1,
|
||||
label: '<b>Choice 1</b>',
|
||||
value: 'Choice 1',
|
||||
selected: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
label: '<b>Choice 2</b>',
|
||||
value: 'Choice 2',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
label: 'Choice 3',
|
||||
value: 'Choice 3',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
new Choices('#choices-allowhtml-true', {
|
||||
allowHTML: true,
|
||||
choices: [
|
||||
{
|
||||
id: 1,
|
||||
label: '<b>Choice 1</b>',
|
||||
value: 'Choice 1',
|
||||
selected: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
label: '<b>Choice 2</b>',
|
||||
value: 'Choice 2',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
label: 'Choice 3',
|
||||
value: 'Choice 3',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
new Choices('#choices-allowhtml-false', {
|
||||
allowHTML: false,
|
||||
choices: [
|
||||
{
|
||||
id: 1,
|
||||
label: '<b>Choice 1</b>',
|
||||
value: 'Choice 1',
|
||||
selected: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
label: '<b>Choice 2</b>',
|
||||
value: 'Choice 2',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
label: 'Choice 3',
|
||||
value: 'Choice 3',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|