govuk_publishing_components 12.13.0 → 12.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/javascripts/govuk_publishing_components/components/accessible-autocomplete.js +37 -0
- data/app/assets/javascripts/govuk_publishing_components/components/checkboxes.js +6 -5
- data/app/assets/javascripts/govuk_publishing_components/components/copy-to-clipboard.js +15 -15
- data/app/assets/javascripts/govuk_publishing_components/components/feedback.js +2 -4
- data/app/assets/javascripts/govuk_publishing_components/components/initial-focus.js +8 -8
- data/app/assets/javascripts/govuk_publishing_components/components/step-by-step-nav.js +3 -3
- data/app/assets/stylesheets/govuk_publishing_components/_all_components.scss +1 -0
- data/app/assets/stylesheets/govuk_publishing_components/components/_accessible-autocomplete.scss +16 -0
- data/app/views/govuk_publishing_components/components/_accessible_autocomplete.html.erb +24 -0
- data/app/views/govuk_publishing_components/components/docs/accessible_autocomplete.yml +42 -0
- data/config/initializers/assets.rb +1 -0
- data/lib/govuk_publishing_components/version.rb +1 -1
- data/node_modules/accessible-autocomplete/CHANGELOG.md +269 -0
- data/node_modules/accessible-autocomplete/CONTRIBUTING.md +150 -0
- data/node_modules/accessible-autocomplete/LICENSE.txt +20 -0
- data/node_modules/accessible-autocomplete/Procfile +1 -0
- data/node_modules/accessible-autocomplete/README.md +416 -0
- data/node_modules/accessible-autocomplete/accessibility-criteria.md +42 -0
- data/node_modules/accessible-autocomplete/app.json +15 -0
- data/node_modules/accessible-autocomplete/dist/accessible-autocomplete.min.css +1 -0
- data/node_modules/accessible-autocomplete/dist/accessible-autocomplete.min.js +2 -0
- data/node_modules/accessible-autocomplete/dist/accessible-autocomplete.min.js.map +1 -0
- data/node_modules/accessible-autocomplete/dist/lib/accessible-autocomplete.preact.min.js +2 -0
- data/node_modules/accessible-autocomplete/dist/lib/accessible-autocomplete.preact.min.js.map +1 -0
- data/node_modules/accessible-autocomplete/dist/lib/accessible-autocomplete.react.min.js +2 -0
- data/node_modules/accessible-autocomplete/dist/lib/accessible-autocomplete.react.min.js.map +1 -0
- data/node_modules/accessible-autocomplete/examples/form.html +671 -0
- data/node_modules/accessible-autocomplete/examples/index.html +616 -0
- data/node_modules/accessible-autocomplete/examples/preact/index.html +346 -0
- data/node_modules/accessible-autocomplete/examples/react/index.html +347 -0
- data/node_modules/accessible-autocomplete/package.json +192 -0
- data/node_modules/accessible-autocomplete/preact.js +1 -0
- data/node_modules/accessible-autocomplete/react.js +1 -0
- data/node_modules/accessible-autocomplete/scripts/check-staged.js +14 -0
- data/node_modules/accessible-autocomplete/src/autocomplete.css +141 -0
- data/node_modules/accessible-autocomplete/src/autocomplete.js +524 -0
- data/node_modules/accessible-autocomplete/src/dropdown-arrow-down.js +11 -0
- data/node_modules/accessible-autocomplete/src/status.js +80 -0
- data/node_modules/accessible-autocomplete/src/wrapper.js +60 -0
- data/node_modules/accessible-autocomplete/test/functional/dropdown-arrow-down.js +44 -0
- data/node_modules/accessible-autocomplete/test/functional/index.js +485 -0
- data/node_modules/accessible-autocomplete/test/functional/wrapper.js +267 -0
- data/node_modules/accessible-autocomplete/test/integration/index.js +188 -0
- data/node_modules/accessible-autocomplete/test/karma.config.js +42 -0
- data/node_modules/accessible-autocomplete/test/wdio.config.js +80 -0
- data/node_modules/accessible-autocomplete/webpack.config.babel.js +193 -0
- data/node_modules/preact/LICENSE +21 -0
- data/node_modules/preact/README.md +580 -0
- data/node_modules/preact/debug.js +112 -0
- data/node_modules/preact/debug.js.map +1 -0
- data/node_modules/preact/debug/index.js +121 -0
- data/node_modules/preact/devtools.js +403 -0
- data/node_modules/preact/devtools.js.map +1 -0
- data/node_modules/preact/devtools/devtools.js +395 -0
- data/node_modules/preact/devtools/index.js +4 -0
- data/node_modules/preact/dist/preact.d.ts +891 -0
- data/node_modules/preact/dist/preact.dev.js +718 -0
- data/node_modules/preact/dist/preact.dev.js.map +1 -0
- data/node_modules/preact/dist/preact.js +408 -0
- data/node_modules/preact/dist/preact.js.flow +13 -0
- data/node_modules/preact/dist/preact.js.map +1 -0
- data/node_modules/preact/dist/preact.min.js +2 -0
- data/node_modules/preact/dist/preact.min.js.map +1 -0
- data/node_modules/preact/dist/preact.mjs +715 -0
- data/node_modules/preact/dist/preact.mjs.map +1 -0
- data/node_modules/preact/package.json +218 -0
- data/node_modules/preact/src/clone-element.js +18 -0
- data/node_modules/preact/src/component.js +90 -0
- data/node_modules/preact/src/constants.js +17 -0
- data/node_modules/preact/src/dom/index.js +138 -0
- data/node_modules/preact/src/h.js +86 -0
- data/node_modules/preact/src/options.js +22 -0
- data/node_modules/preact/src/preact.d.ts +891 -0
- data/node_modules/preact/src/preact.js +26 -0
- data/node_modules/preact/src/preact.js.flow +13 -0
- data/node_modules/preact/src/render-queue.js +28 -0
- data/node_modules/preact/src/render.js +22 -0
- data/node_modules/preact/src/util.js +19 -0
- data/node_modules/preact/src/vdom/component-recycler.js +48 -0
- data/node_modules/preact/src/vdom/component.js +296 -0
- data/node_modules/preact/src/vdom/diff.js +336 -0
- data/node_modules/preact/src/vdom/index.js +54 -0
- data/node_modules/preact/src/vnode.js +9 -0
- data/node_modules/preact/typings.json +5 -0
- metadata +78 -2
@@ -0,0 +1,192 @@
|
|
1
|
+
{
|
2
|
+
"_args": [
|
3
|
+
[
|
4
|
+
{
|
5
|
+
"raw": "accessible-autocomplete@^1.6.2",
|
6
|
+
"scope": null,
|
7
|
+
"escapedName": "accessible-autocomplete",
|
8
|
+
"name": "accessible-autocomplete",
|
9
|
+
"rawSpec": "^1.6.2",
|
10
|
+
"spec": ">=1.6.2 <2.0.0",
|
11
|
+
"type": "range"
|
12
|
+
},
|
13
|
+
"/var/lib/jenkins/workspace/ublishing_components_master-N4FWJIUY4CIFHKGZOAAEVVXODRY3YBORQOPIBBXWX72VUPSGJRRQ"
|
14
|
+
]
|
15
|
+
],
|
16
|
+
"_from": "accessible-autocomplete@>=1.6.2 <2.0.0",
|
17
|
+
"_hasShrinkwrap": false,
|
18
|
+
"_id": "accessible-autocomplete@1.6.2",
|
19
|
+
"_inCache": true,
|
20
|
+
"_location": "/accessible-autocomplete",
|
21
|
+
"_nodeVersion": "8.9.4",
|
22
|
+
"_npmOperationalInternal": {
|
23
|
+
"host": "s3://npm-registry-packages",
|
24
|
+
"tmp": "tmp/accessible-autocomplete_1.6.2_1542120962811_0.125232612907197"
|
25
|
+
},
|
26
|
+
"_npmUser": {
|
27
|
+
"name": "alphagov",
|
28
|
+
"email": "govuk-dev@digital.cabinet-office.gov.uk"
|
29
|
+
},
|
30
|
+
"_npmVersion": "5.6.0",
|
31
|
+
"_phantomChildren": {},
|
32
|
+
"_requested": {
|
33
|
+
"raw": "accessible-autocomplete@^1.6.2",
|
34
|
+
"scope": null,
|
35
|
+
"escapedName": "accessible-autocomplete",
|
36
|
+
"name": "accessible-autocomplete",
|
37
|
+
"rawSpec": "^1.6.2",
|
38
|
+
"spec": ">=1.6.2 <2.0.0",
|
39
|
+
"type": "range"
|
40
|
+
},
|
41
|
+
"_requiredBy": [
|
42
|
+
"/"
|
43
|
+
],
|
44
|
+
"_resolved": "https://registry.npmjs.org/accessible-autocomplete/-/accessible-autocomplete-1.6.2.tgz",
|
45
|
+
"_shasum": "cb69d37748ba5b0351c84422468538890e25759c",
|
46
|
+
"_shrinkwrap": null,
|
47
|
+
"_spec": "accessible-autocomplete@^1.6.2",
|
48
|
+
"_where": "/var/lib/jenkins/workspace/ublishing_components_master-N4FWJIUY4CIFHKGZOAAEVVXODRY3YBORQOPIBBXWX72VUPSGJRRQ",
|
49
|
+
"author": {
|
50
|
+
"name": "Government Digital Service",
|
51
|
+
"url": "https://www.gov.uk/government/organisations/government-digital-service"
|
52
|
+
},
|
53
|
+
"browserslist": [
|
54
|
+
">0.1%",
|
55
|
+
"last 2 Chrome versions",
|
56
|
+
"last 2 Firefox versions",
|
57
|
+
"last 2 Edge versions",
|
58
|
+
"last 2 Samsung versions",
|
59
|
+
"Safari >= 9",
|
60
|
+
"ie 8-11",
|
61
|
+
"iOS >= 9"
|
62
|
+
],
|
63
|
+
"bugs": {
|
64
|
+
"url": "https://github.com/alphagov/accessible-autocomplete/issues"
|
65
|
+
},
|
66
|
+
"dependencies": {
|
67
|
+
"preact": "^8.3.1"
|
68
|
+
},
|
69
|
+
"description": "An autocomplete component, built to be accessible.",
|
70
|
+
"devDependencies": {
|
71
|
+
"@babel/core": "^7.1.5",
|
72
|
+
"@babel/plugin-proposal-class-properties": "^7.1.0",
|
73
|
+
"@babel/plugin-proposal-decorators": "^7.1.2",
|
74
|
+
"@babel/plugin-transform-member-expression-literals": "^7.0.0",
|
75
|
+
"@babel/plugin-transform-modules-commonjs": "^7.1.0",
|
76
|
+
"@babel/plugin-transform-property-literals": "^7.0.0",
|
77
|
+
"@babel/plugin-transform-react-jsx": "^7.0.0",
|
78
|
+
"@babel/preset-env": "^7.1.5",
|
79
|
+
"@babel/register": "^7.0.0",
|
80
|
+
"babel-eslint": "^10.0.1",
|
81
|
+
"babel-loader": "^8.0.4",
|
82
|
+
"babel-plugin-istanbul": "^5.1.0",
|
83
|
+
"chai": "^4.2.0",
|
84
|
+
"chalk": "^2.4.1",
|
85
|
+
"copy-webpack-plugin": "^4.6.0",
|
86
|
+
"coveralls": "^3.0.2",
|
87
|
+
"cross-env": "^5.2.0",
|
88
|
+
"csso-cli": "^1.1.0",
|
89
|
+
"dotenv": "^6.1.0",
|
90
|
+
"husky": "^1.1.3",
|
91
|
+
"karma": "^3.1.1",
|
92
|
+
"karma-chai": "^0.1.0",
|
93
|
+
"karma-chai-sinon": "^0.1.5",
|
94
|
+
"karma-chrome-launcher": "^2.2.0",
|
95
|
+
"karma-coverage": "^1.1.2",
|
96
|
+
"karma-mocha": "^1.0.1",
|
97
|
+
"karma-mocha-reporter": "^2.2.5",
|
98
|
+
"karma-sourcemap-loader": "^0.3.7",
|
99
|
+
"karma-webpack": "^4.0.0-rc.2",
|
100
|
+
"mocha": "^5.2.0",
|
101
|
+
"npm-run-all": "^4.1.3",
|
102
|
+
"puppeteer": "^1.10.0",
|
103
|
+
"replace-bundle-webpack-plugin": "^1.0.0",
|
104
|
+
"sinon": "^6.3.5",
|
105
|
+
"sinon-chai": "^3.2.0",
|
106
|
+
"source-map-loader": "^0.2.4",
|
107
|
+
"standard": "^12.0.1",
|
108
|
+
"uglifyjs-webpack-plugin": "^2.0.1",
|
109
|
+
"wdio-mocha-framework": "^0.6.4",
|
110
|
+
"wdio-sauce-service": "^0.4.13",
|
111
|
+
"wdio-selenium-standalone-service": "^0.0.10",
|
112
|
+
"wdio-spec-reporter": "^0.1.5",
|
113
|
+
"wdio-static-server-service": "^1.0.1",
|
114
|
+
"wdio-webpack-dev-server-service": "^2.0.2",
|
115
|
+
"webdriverio": "^4.14.0",
|
116
|
+
"webpack": "^4.25.1",
|
117
|
+
"webpack-cli": "^3.1.2",
|
118
|
+
"webpack-dev-middleware": "^3.4.0",
|
119
|
+
"webpack-dev-server": "^3.1.10",
|
120
|
+
"webpack-sources": "^1.3.0"
|
121
|
+
},
|
122
|
+
"directories": {},
|
123
|
+
"dist": {
|
124
|
+
"integrity": "sha512-7S+6Vi82LQFSSd5feKedu46tiY2/DShpdXiRp0NY3cLwc+DKe1ayWd66mb3JVi8LTQubRM7jco+u92e6w0bbvg==",
|
125
|
+
"shasum": "cb69d37748ba5b0351c84422468538890e25759c",
|
126
|
+
"tarball": "https://registry.npmjs.org/accessible-autocomplete/-/accessible-autocomplete-1.6.2.tgz",
|
127
|
+
"fileCount": 37,
|
128
|
+
"unpackedSize": 540441,
|
129
|
+
"npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJb6uYDCRA9TVsSAnZWagAAo6sP/igz39UbvmkraVAOGsF2\nqyHQ8DzJv6pIU9qXyZ7mEVk9gTpBtZQVKLtK2X9Pi9ZitKOO4P9+R04CuYur\n6EYrD1YAwrIcuK4Rr7nsnmfsRQxijdmuXTSRQI/nXCeRsqPMuaCphpVdnBkV\nsuOV40I+zuWDQn+2tK9JS/SyW0UF9kAf54uk/VntpricZK9BPESt/40uUYKn\nqSxG3grkfwvE7DoJlI2HuZ0Q1/CR9xHBYtmUu7oYbyO1KWSEzjdZvNf7+zb9\nuEHWTzueLO1O572XxEp8rjLJB1U5W/cu7GLQBDD1/5ShGK3ghJnea2zpCEGn\nEZAK/llomb6X4jdUqsh8glyhhq4qwm/7JGPRrraTODzSIAXgpCZ/a88FSJ+x\n58ZEzB+bCA/h+YRpG5yehWiObLCz0lHMcSW38vZwX0xgrtVBgxzFS88r+QR8\ntKLfoTTJWa7cjlbWfFCIip6H3oSYKKg3uW8wKCqFt91sMp2//9ctFUfmavJm\nkQzvSpDcCz5C7Na11XDlsdz3x5RoYUGnhBXlNEtpZ/SN5cA7XZFyEa6hSu9J\nZzCpaxK7HdXx01i7yCx83oZxPkwXcOzwEVB1B/3kssKZgtuO6pAW+17s8HTA\nwpVJX6g2Wrrfgr/6uBqznlS4E8LhMwceJLSy2gbTD/8Akn9Lv8ntwyNyGomA\nQ6iu\r\n=i6eo\r\n-----END PGP SIGNATURE-----\r\n"
|
130
|
+
},
|
131
|
+
"gitHead": "70af963ba97f840025a6ee0569b2e9a687e904f9",
|
132
|
+
"homepage": "https://github.com/alphagov/accessible-autocomplete#readme",
|
133
|
+
"husky": {
|
134
|
+
"hooks": {
|
135
|
+
"pre-push": "npm run build && node scripts/check-staged.js"
|
136
|
+
}
|
137
|
+
},
|
138
|
+
"keywords": [
|
139
|
+
"a11y",
|
140
|
+
"accessibility",
|
141
|
+
"autocomplete",
|
142
|
+
"component",
|
143
|
+
"plugin",
|
144
|
+
"typeahead",
|
145
|
+
"widget"
|
146
|
+
],
|
147
|
+
"license": "MIT",
|
148
|
+
"main": "dist/accessible-autocomplete.min.js",
|
149
|
+
"maintainers": [
|
150
|
+
{
|
151
|
+
"name": "alphagov",
|
152
|
+
"email": "govuk-dev@digital.cabinet-office.gov.uk"
|
153
|
+
},
|
154
|
+
{
|
155
|
+
"name": "carolinegreen",
|
156
|
+
"email": "caroline.green@digital.cabinet-office.gov.uk"
|
157
|
+
},
|
158
|
+
{
|
159
|
+
"name": "danmitchell-",
|
160
|
+
"email": "dan@digitalreflow.co.uk"
|
161
|
+
},
|
162
|
+
{
|
163
|
+
"name": "tvararu",
|
164
|
+
"email": "theo@vararu.org"
|
165
|
+
}
|
166
|
+
],
|
167
|
+
"name": "accessible-autocomplete",
|
168
|
+
"optionalDependencies": {},
|
169
|
+
"readme": "ERROR: No README data found!",
|
170
|
+
"repository": {
|
171
|
+
"type": "git",
|
172
|
+
"url": "git+https://github.com/alphagov/accessible-autocomplete.git"
|
173
|
+
},
|
174
|
+
"scripts": {
|
175
|
+
"build": "run-s 'build:js' 'build:css'",
|
176
|
+
"build:css": "csso src/autocomplete.css -o dist/accessible-autocomplete.min.css",
|
177
|
+
"build:js": "cross-env NODE_ENV=production webpack --progress --display-modules",
|
178
|
+
"dev": "cross-env NODE_ENV=development webpack-dev-server --inline --hot --progress",
|
179
|
+
"karma": "npm run karma:dev -- --single-run",
|
180
|
+
"karma:dev": "cross-env NODE_ENV=test karma start test/karma.config.js",
|
181
|
+
"preversion": "npm test",
|
182
|
+
"standard": "standard",
|
183
|
+
"test": "run-p standard karma wdio",
|
184
|
+
"version": "npm run build && git add -A dist",
|
185
|
+
"wdio": "cross-env NODE_ENV=test wdio test/wdio.config.js && git checkout dist/"
|
186
|
+
},
|
187
|
+
"standard": {
|
188
|
+
"parser": "babel-eslint"
|
189
|
+
},
|
190
|
+
"style": "dist/accessible-autocomplete.min.css",
|
191
|
+
"version": "1.6.2"
|
192
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
module.exports = require('./dist/lib/accessible-autocomplete.preact.min')
|
@@ -0,0 +1 @@
|
|
1
|
+
module.exports = require('./dist/lib/accessible-autocomplete.react.min')
|
@@ -0,0 +1,14 @@
|
|
1
|
+
const cp = require('child_process')
|
2
|
+
const chalk = require('chalk')
|
3
|
+
|
4
|
+
cp.exec('git diff --name-only dist/', (err, stdout) => {
|
5
|
+
if (err) {
|
6
|
+
console.log(chalk.red('ERROR:'), err)
|
7
|
+
return process.exit(1)
|
8
|
+
}
|
9
|
+
if (stdout.toString().length) {
|
10
|
+
console.log(chalk.red('ERROR:'), 'There are unstaged changes in `dist/` after running `npm run build`. Please commit them.')
|
11
|
+
return process.exit(1)
|
12
|
+
}
|
13
|
+
process.exit()
|
14
|
+
})
|
@@ -0,0 +1,141 @@
|
|
1
|
+
.autocomplete__wrapper {
|
2
|
+
position: relative;
|
3
|
+
}
|
4
|
+
|
5
|
+
.autocomplete__hint,
|
6
|
+
.autocomplete__input {
|
7
|
+
-webkit-appearance: none;
|
8
|
+
border: 2px solid;
|
9
|
+
border-radius: 0; /* Safari 10 on iOS adds implicit border rounding. */
|
10
|
+
box-sizing: border-box;
|
11
|
+
-moz-box-sizing: border-box;
|
12
|
+
-webkit-box-sizing: border-box;
|
13
|
+
margin-bottom: 0; /* BUG: Safari 10 on macOS seems to add an implicit margin. */
|
14
|
+
width: 100%;
|
15
|
+
}
|
16
|
+
|
17
|
+
.autocomplete__input {
|
18
|
+
background-color: transparent;
|
19
|
+
position: relative;
|
20
|
+
}
|
21
|
+
|
22
|
+
.autocomplete__hint {
|
23
|
+
color: #BFC1C3;
|
24
|
+
position: absolute;
|
25
|
+
}
|
26
|
+
|
27
|
+
.autocomplete__input--default{
|
28
|
+
padding: 4px;
|
29
|
+
}
|
30
|
+
|
31
|
+
.autocomplete__input--focused {
|
32
|
+
outline-offset: 0;
|
33
|
+
outline: 3px solid #ffbf47;
|
34
|
+
}
|
35
|
+
|
36
|
+
.autocomplete__input--show-all-values {
|
37
|
+
padding: 4px 34px 4px 4px;
|
38
|
+
cursor: pointer;
|
39
|
+
}
|
40
|
+
|
41
|
+
.autocomplete__dropdown-arrow-down{
|
42
|
+
z-index: -1;
|
43
|
+
display: inline-block;
|
44
|
+
position: absolute;
|
45
|
+
right: 8px;
|
46
|
+
width: 24px;
|
47
|
+
height: 24px;
|
48
|
+
top: 10px;
|
49
|
+
}
|
50
|
+
|
51
|
+
.autocomplete__menu {
|
52
|
+
background-color: #fff;
|
53
|
+
border: 2px solid #0B0C0C;
|
54
|
+
border-top: 0;
|
55
|
+
color: #34384B;
|
56
|
+
margin: 0;
|
57
|
+
max-height: 342px;
|
58
|
+
overflow-x: hidden;
|
59
|
+
padding: 0;
|
60
|
+
width: 100%;
|
61
|
+
width: calc(100% - 4px);
|
62
|
+
}
|
63
|
+
|
64
|
+
.autocomplete__menu--visible {
|
65
|
+
display: block;
|
66
|
+
}
|
67
|
+
|
68
|
+
.autocomplete__menu--hidden {
|
69
|
+
display: none;
|
70
|
+
}
|
71
|
+
|
72
|
+
.autocomplete__menu--overlay {
|
73
|
+
box-shadow: rgba(0, 0, 0, 0.256863) 0px 2px 6px;
|
74
|
+
left: 0;
|
75
|
+
position: absolute;
|
76
|
+
top: 100%;
|
77
|
+
z-index: 100;
|
78
|
+
}
|
79
|
+
|
80
|
+
.autocomplete__menu--inline {
|
81
|
+
position: relative;
|
82
|
+
}
|
83
|
+
|
84
|
+
.autocomplete__option {
|
85
|
+
border-bottom: solid #BFC1C3;
|
86
|
+
border-width: 1px 0;
|
87
|
+
cursor: pointer;
|
88
|
+
display: block;
|
89
|
+
position: relative;
|
90
|
+
}
|
91
|
+
|
92
|
+
.autocomplete__option > * {
|
93
|
+
pointer-events: none;
|
94
|
+
}
|
95
|
+
|
96
|
+
.autocomplete__option:first-of-type {
|
97
|
+
border-top-width: 0;
|
98
|
+
}
|
99
|
+
|
100
|
+
.autocomplete__option:last-of-type {
|
101
|
+
border-bottom-width: 0;
|
102
|
+
}
|
103
|
+
|
104
|
+
.autocomplete__option--odd {
|
105
|
+
background-color: #FAFAFA;
|
106
|
+
}
|
107
|
+
|
108
|
+
.autocomplete__option--focused,
|
109
|
+
.autocomplete__option:hover {
|
110
|
+
background-color: #005EA5;
|
111
|
+
border-color: #005EA5;
|
112
|
+
color: white;
|
113
|
+
outline: none;
|
114
|
+
}
|
115
|
+
|
116
|
+
.autocomplete__option--no-results {
|
117
|
+
background-color: #FAFAFA;
|
118
|
+
color: #646b6f;
|
119
|
+
cursor: not-allowed;
|
120
|
+
}
|
121
|
+
|
122
|
+
.autocomplete__hint,
|
123
|
+
.autocomplete__input,
|
124
|
+
.autocomplete__option {
|
125
|
+
font-size: 16px;
|
126
|
+
line-height: 1.25;
|
127
|
+
}
|
128
|
+
|
129
|
+
.autocomplete__hint,
|
130
|
+
.autocomplete__option {
|
131
|
+
padding: 4px;
|
132
|
+
}
|
133
|
+
|
134
|
+
@media (min-width: 641px) {
|
135
|
+
.autocomplete__hint,
|
136
|
+
.autocomplete__input,
|
137
|
+
.autocomplete__option {
|
138
|
+
font-size: 19px;
|
139
|
+
line-height: 1.31579;
|
140
|
+
}
|
141
|
+
}
|
@@ -0,0 +1,524 @@
|
|
1
|
+
import { createElement, Component } from 'preact' /** @jsx createElement */
|
2
|
+
import Status from './status'
|
3
|
+
import DropdownArrowDown from './dropdown-arrow-down'
|
4
|
+
|
5
|
+
const IS_PREACT = process.env.COMPONENT_LIBRARY === 'PREACT'
|
6
|
+
const IS_REACT = process.env.COMPONENT_LIBRARY === 'REACT'
|
7
|
+
|
8
|
+
const keyCodes = {
|
9
|
+
13: 'enter',
|
10
|
+
27: 'escape',
|
11
|
+
32: 'space',
|
12
|
+
38: 'up',
|
13
|
+
40: 'down'
|
14
|
+
}
|
15
|
+
|
16
|
+
// Based on https://github.com/ausi/Feature-detection-technique-for-pointer-events
|
17
|
+
const hasPointerEvents = (() => {
|
18
|
+
const element = document.createElement('x')
|
19
|
+
element.style.cssText = 'pointer-events:auto'
|
20
|
+
return element.style.pointerEvents === 'auto'
|
21
|
+
})()
|
22
|
+
|
23
|
+
function isIosDevice () {
|
24
|
+
return !!(navigator.userAgent.match(/(iPod|iPhone|iPad)/g) && navigator.userAgent.match(/AppleWebKit/g))
|
25
|
+
}
|
26
|
+
|
27
|
+
function isPrintableKeyCode (keyCode) {
|
28
|
+
return (
|
29
|
+
(keyCode > 47 && keyCode < 58) || // number keys
|
30
|
+
keyCode === 32 || keyCode === 8 || // spacebar or backspace
|
31
|
+
(keyCode > 64 && keyCode < 91) || // letter keys
|
32
|
+
(keyCode > 95 && keyCode < 112) || // numpad keys
|
33
|
+
(keyCode > 185 && keyCode < 193) || // ;=,-./` (in order)
|
34
|
+
(keyCode > 218 && keyCode < 223) // [\]' (in order)
|
35
|
+
)
|
36
|
+
}
|
37
|
+
|
38
|
+
// Preact does not implement onChange on inputs, but React does.
|
39
|
+
function onChangeCrossLibrary (handler) {
|
40
|
+
if (IS_PREACT) { return { onInput: handler } }
|
41
|
+
if (IS_REACT) { return { onChange: handler } }
|
42
|
+
}
|
43
|
+
|
44
|
+
export default class Autocomplete extends Component {
|
45
|
+
static defaultProps = {
|
46
|
+
autoselect: false,
|
47
|
+
cssNamespace: 'autocomplete',
|
48
|
+
defaultValue: '',
|
49
|
+
displayMenu: 'inline',
|
50
|
+
minLength: 0,
|
51
|
+
name: 'input-autocomplete',
|
52
|
+
placeholder: '',
|
53
|
+
onConfirm: () => {},
|
54
|
+
confirmOnBlur: true,
|
55
|
+
showNoOptionsFound: true,
|
56
|
+
showAllValues: false,
|
57
|
+
required: false,
|
58
|
+
tNoResults: () => 'No results found',
|
59
|
+
dropdownArrow: DropdownArrowDown
|
60
|
+
}
|
61
|
+
|
62
|
+
elementReferences = {}
|
63
|
+
|
64
|
+
constructor (props) {
|
65
|
+
super(props)
|
66
|
+
|
67
|
+
this.state = {
|
68
|
+
focused: null,
|
69
|
+
hovered: null,
|
70
|
+
clicked: null,
|
71
|
+
menuOpen: false,
|
72
|
+
options: props.defaultValue ? [props.defaultValue] : [],
|
73
|
+
query: props.defaultValue,
|
74
|
+
selected: null
|
75
|
+
}
|
76
|
+
|
77
|
+
this.handleComponentBlur = this.handleComponentBlur.bind(this)
|
78
|
+
this.handleKeyDown = this.handleKeyDown.bind(this)
|
79
|
+
this.handleUpArrow = this.handleUpArrow.bind(this)
|
80
|
+
this.handleDownArrow = this.handleDownArrow.bind(this)
|
81
|
+
this.handleEnter = this.handleEnter.bind(this)
|
82
|
+
this.handlePrintableKey = this.handlePrintableKey.bind(this)
|
83
|
+
|
84
|
+
this.handleListMouseLeave = this.handleListMouseLeave.bind(this)
|
85
|
+
|
86
|
+
this.handleOptionBlur = this.handleOptionBlur.bind(this)
|
87
|
+
this.handleOptionClick = this.handleOptionClick.bind(this)
|
88
|
+
this.handleOptionFocus = this.handleOptionFocus.bind(this)
|
89
|
+
this.handleOptionMouseEnter = this.handleOptionMouseEnter.bind(this)
|
90
|
+
|
91
|
+
this.handleInputBlur = this.handleInputBlur.bind(this)
|
92
|
+
this.handleInputChange = this.handleInputChange.bind(this)
|
93
|
+
this.handleInputFocus = this.handleInputFocus.bind(this)
|
94
|
+
|
95
|
+
this.pollInputElement = this.pollInputElement.bind(this)
|
96
|
+
this.getDirectInputChanges = this.getDirectInputChanges.bind(this)
|
97
|
+
}
|
98
|
+
|
99
|
+
componentDidMount () {
|
100
|
+
this.pollInputElement()
|
101
|
+
}
|
102
|
+
|
103
|
+
componentWillUnmount () {
|
104
|
+
clearTimeout(this.$pollInput)
|
105
|
+
clearTimeout(this.$blurInput)
|
106
|
+
}
|
107
|
+
|
108
|
+
// Applications like Dragon NaturallySpeaking will modify the
|
109
|
+
// `input` field by directly changing its `.value`. These events
|
110
|
+
// don't trigger our JavaScript event listeners, so we need to poll
|
111
|
+
// to handle when and if they occur.
|
112
|
+
pollInputElement () {
|
113
|
+
this.getDirectInputChanges()
|
114
|
+
this.$pollInput = setTimeout(() => {
|
115
|
+
this.pollInputElement()
|
116
|
+
}, 100)
|
117
|
+
}
|
118
|
+
|
119
|
+
getDirectInputChanges () {
|
120
|
+
const inputReference = this.elementReferences[-1]
|
121
|
+
const queryHasChanged = inputReference && inputReference.value !== this.state.query
|
122
|
+
|
123
|
+
if (queryHasChanged) {
|
124
|
+
this.handleInputChange({ target: { value: inputReference.value } })
|
125
|
+
}
|
126
|
+
}
|
127
|
+
|
128
|
+
componentDidUpdate (prevProps, prevState) {
|
129
|
+
const { focused, clicked } = this.state
|
130
|
+
const componentLostFocus = focused === null
|
131
|
+
const focusedChanged = prevState.focused !== focused
|
132
|
+
const focusDifferentElement = (focusedChanged && !componentLostFocus) || clicked !== null
|
133
|
+
if (focusDifferentElement) {
|
134
|
+
this.elementReferences[focused].focus()
|
135
|
+
}
|
136
|
+
const focusedInput = focused === -1
|
137
|
+
const componentGainedFocus = focusedChanged && prevState.focused === null
|
138
|
+
const selectAllText = focusedInput && componentGainedFocus
|
139
|
+
if (selectAllText) {
|
140
|
+
const inputElement = this.elementReferences[focused]
|
141
|
+
inputElement.setSelectionRange(0, inputElement.value.length)
|
142
|
+
}
|
143
|
+
}
|
144
|
+
|
145
|
+
hasAutoselect () {
|
146
|
+
return isIosDevice() ? false : this.props.autoselect
|
147
|
+
}
|
148
|
+
|
149
|
+
// This template is used when converting from a state.options object into a state.query.
|
150
|
+
templateInputValue (value) {
|
151
|
+
const inputValueTemplate = this.props.templates && this.props.templates.inputValue
|
152
|
+
return inputValueTemplate ? inputValueTemplate(value) : value
|
153
|
+
}
|
154
|
+
|
155
|
+
// This template is used when displaying results / suggestions.
|
156
|
+
templateSuggestion (value) {
|
157
|
+
const suggestionTemplate = this.props.templates && this.props.templates.suggestion
|
158
|
+
return suggestionTemplate ? suggestionTemplate(value) : value
|
159
|
+
}
|
160
|
+
|
161
|
+
handleComponentBlur (newState) {
|
162
|
+
const { options, query, selected } = this.state
|
163
|
+
let newQuery
|
164
|
+
if (this.props.confirmOnBlur) {
|
165
|
+
newQuery = newState.query || query
|
166
|
+
this.props.onConfirm(options[selected])
|
167
|
+
} else {
|
168
|
+
newQuery = query
|
169
|
+
}
|
170
|
+
this.setState({
|
171
|
+
focused: null,
|
172
|
+
clicked: null,
|
173
|
+
menuOpen: newState.menuOpen || false,
|
174
|
+
query: newQuery,
|
175
|
+
selected: null
|
176
|
+
})
|
177
|
+
}
|
178
|
+
|
179
|
+
handleListMouseLeave (event) {
|
180
|
+
this.setState({
|
181
|
+
hovered: null
|
182
|
+
})
|
183
|
+
}
|
184
|
+
|
185
|
+
handleOptionBlur (event, index) {
|
186
|
+
const { focused, clicked, menuOpen, options, selected } = this.state
|
187
|
+
const focusingOutsideComponent = event.relatedTarget === null && clicked === null
|
188
|
+
const focusingInput = event.relatedTarget === this.elementReferences[-1]
|
189
|
+
const focusingAnotherOption = focused !== index && focused !== -1
|
190
|
+
const blurComponent = (!focusingAnotherOption && focusingOutsideComponent) || !(focusingAnotherOption || focusingInput)
|
191
|
+
if (blurComponent) {
|
192
|
+
const keepMenuOpen = menuOpen && isIosDevice()
|
193
|
+
this.handleComponentBlur({
|
194
|
+
menuOpen: keepMenuOpen,
|
195
|
+
query: this.templateInputValue(options[selected])
|
196
|
+
})
|
197
|
+
}
|
198
|
+
}
|
199
|
+
|
200
|
+
handleInputBlur (event) {
|
201
|
+
const { focused, menuOpen, options, query, selected } = this.state
|
202
|
+
const focusingAnOption = focused !== -1
|
203
|
+
clearTimeout(this.$blurInput)
|
204
|
+
if (!focusingAnOption) {
|
205
|
+
const keepMenuOpen = menuOpen && isIosDevice()
|
206
|
+
const newQuery = isIosDevice() ? query : this.templateInputValue(options[selected])
|
207
|
+
this.$blurInput = setTimeout(() => this.handleComponentBlur({
|
208
|
+
menuOpen: keepMenuOpen,
|
209
|
+
query: newQuery
|
210
|
+
}), 200)
|
211
|
+
}
|
212
|
+
}
|
213
|
+
|
214
|
+
handleInputChange (event) {
|
215
|
+
const { minLength, source, showAllValues } = this.props
|
216
|
+
const autoselect = this.hasAutoselect()
|
217
|
+
const query = event.target.value
|
218
|
+
const queryEmpty = query.length === 0
|
219
|
+
const queryChanged = this.state.query.length !== query.length
|
220
|
+
const queryLongEnough = query.length >= minLength
|
221
|
+
|
222
|
+
this.setState({ query })
|
223
|
+
|
224
|
+
const searchForOptions = showAllValues || (!queryEmpty && queryChanged && queryLongEnough)
|
225
|
+
if (searchForOptions) {
|
226
|
+
source(query, (options) => {
|
227
|
+
const optionsAvailable = options.length > 0
|
228
|
+
this.setState({
|
229
|
+
menuOpen: optionsAvailable,
|
230
|
+
options,
|
231
|
+
selected: (autoselect && optionsAvailable) ? 0 : -1
|
232
|
+
})
|
233
|
+
})
|
234
|
+
} else if (queryEmpty || !queryLongEnough) {
|
235
|
+
this.setState({
|
236
|
+
menuOpen: false,
|
237
|
+
options: []
|
238
|
+
})
|
239
|
+
}
|
240
|
+
}
|
241
|
+
|
242
|
+
handleInputClick (event) {
|
243
|
+
this.handleInputChange(event)
|
244
|
+
}
|
245
|
+
|
246
|
+
handleInputFocus (event) {
|
247
|
+
this.setState({
|
248
|
+
focused: -1
|
249
|
+
})
|
250
|
+
}
|
251
|
+
|
252
|
+
handleOptionFocus (index) {
|
253
|
+
this.setState({
|
254
|
+
focused: index,
|
255
|
+
hovered: null,
|
256
|
+
selected: index
|
257
|
+
})
|
258
|
+
}
|
259
|
+
|
260
|
+
handleOptionMouseEnter (event, index) {
|
261
|
+
// iOS Safari prevents click event if mouseenter adds hover background colour
|
262
|
+
// See: https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html#//apple_ref/doc/uid/TP40006511-SW4
|
263
|
+
if (!isIosDevice()) {
|
264
|
+
this.setState({
|
265
|
+
hovered: index
|
266
|
+
})
|
267
|
+
}
|
268
|
+
}
|
269
|
+
|
270
|
+
handleOptionClick (event, index) {
|
271
|
+
const selectedOption = this.state.options[index]
|
272
|
+
const newQuery = this.templateInputValue(selectedOption)
|
273
|
+
clearTimeout(this.$blurInput)
|
274
|
+
this.props.onConfirm(selectedOption)
|
275
|
+
this.setState({
|
276
|
+
focused: -1,
|
277
|
+
clicked: index,
|
278
|
+
hovered: null,
|
279
|
+
menuOpen: false,
|
280
|
+
query: newQuery,
|
281
|
+
selected: -1
|
282
|
+
})
|
283
|
+
this.forceUpdate()
|
284
|
+
}
|
285
|
+
|
286
|
+
handleUpArrow (event) {
|
287
|
+
event.preventDefault()
|
288
|
+
const { menuOpen, selected } = this.state
|
289
|
+
const isNotAtTop = selected !== -1
|
290
|
+
const allowMoveUp = isNotAtTop && menuOpen
|
291
|
+
if (allowMoveUp) {
|
292
|
+
this.handleOptionFocus(selected - 1)
|
293
|
+
}
|
294
|
+
}
|
295
|
+
|
296
|
+
handleDownArrow (event) {
|
297
|
+
event.preventDefault()
|
298
|
+
// if not open, open
|
299
|
+
if (this.props.showAllValues && this.state.menuOpen === false) {
|
300
|
+
event.preventDefault()
|
301
|
+
this.props.source('', (options) => {
|
302
|
+
this.setState({
|
303
|
+
menuOpen: true,
|
304
|
+
options,
|
305
|
+
selected: 0,
|
306
|
+
focused: 0,
|
307
|
+
hovered: null
|
308
|
+
})
|
309
|
+
})
|
310
|
+
} else if (this.state.menuOpen === true) {
|
311
|
+
const { menuOpen, options, selected } = this.state
|
312
|
+
const isNotAtBottom = selected !== options.length - 1
|
313
|
+
const allowMoveDown = isNotAtBottom && menuOpen
|
314
|
+
if (allowMoveDown) {
|
315
|
+
this.handleOptionFocus(selected + 1)
|
316
|
+
}
|
317
|
+
}
|
318
|
+
}
|
319
|
+
|
320
|
+
handleSpace (event) {
|
321
|
+
// if not open, open
|
322
|
+
if (this.props.showAllValues && this.state.menuOpen === false && this.state.query === '') {
|
323
|
+
event.preventDefault()
|
324
|
+
this.props.source('', (options) => {
|
325
|
+
this.setState({
|
326
|
+
menuOpen: true,
|
327
|
+
options
|
328
|
+
})
|
329
|
+
})
|
330
|
+
}
|
331
|
+
const focusIsOnOption = this.state.focused !== -1
|
332
|
+
if (focusIsOnOption) {
|
333
|
+
event.preventDefault()
|
334
|
+
this.handleOptionClick(event, this.state.focused)
|
335
|
+
}
|
336
|
+
}
|
337
|
+
|
338
|
+
handleEnter (event) {
|
339
|
+
if (this.state.menuOpen) {
|
340
|
+
event.preventDefault()
|
341
|
+
const hasSelectedOption = this.state.selected >= 0
|
342
|
+
if (hasSelectedOption) {
|
343
|
+
this.handleOptionClick(event, this.state.selected)
|
344
|
+
}
|
345
|
+
}
|
346
|
+
}
|
347
|
+
|
348
|
+
handlePrintableKey (event) {
|
349
|
+
const inputElement = this.elementReferences[-1]
|
350
|
+
const eventIsOnInput = event.target === inputElement
|
351
|
+
if (!eventIsOnInput) {
|
352
|
+
// FIXME: This would be better if it was in componentDidUpdate,
|
353
|
+
// but using setState to trigger that seems to not work correctly
|
354
|
+
// in preact@8.1.0.
|
355
|
+
inputElement.focus()
|
356
|
+
}
|
357
|
+
}
|
358
|
+
|
359
|
+
handleKeyDown (event) {
|
360
|
+
switch (keyCodes[event.keyCode]) {
|
361
|
+
case 'up':
|
362
|
+
this.handleUpArrow(event)
|
363
|
+
break
|
364
|
+
case 'down':
|
365
|
+
this.handleDownArrow(event)
|
366
|
+
break
|
367
|
+
case 'space':
|
368
|
+
this.handleSpace(event)
|
369
|
+
break
|
370
|
+
case 'enter':
|
371
|
+
this.handleEnter(event)
|
372
|
+
break
|
373
|
+
case 'escape':
|
374
|
+
this.handleComponentBlur({
|
375
|
+
query: this.state.query
|
376
|
+
})
|
377
|
+
break
|
378
|
+
default:
|
379
|
+
if (isPrintableKeyCode(event.keyCode)) {
|
380
|
+
this.handlePrintableKey(event)
|
381
|
+
}
|
382
|
+
break
|
383
|
+
}
|
384
|
+
}
|
385
|
+
|
386
|
+
render () {
|
387
|
+
const {
|
388
|
+
cssNamespace,
|
389
|
+
displayMenu,
|
390
|
+
id,
|
391
|
+
minLength,
|
392
|
+
name,
|
393
|
+
placeholder,
|
394
|
+
required,
|
395
|
+
showAllValues,
|
396
|
+
tNoResults,
|
397
|
+
tStatusQueryTooShort,
|
398
|
+
tStatusNoResults,
|
399
|
+
tStatusSelectedOption,
|
400
|
+
tStatusResults,
|
401
|
+
dropdownArrow: dropdownArrowFactory
|
402
|
+
} = this.props
|
403
|
+
const { focused, hovered, menuOpen, options, query, selected } = this.state
|
404
|
+
const autoselect = this.hasAutoselect()
|
405
|
+
|
406
|
+
const inputFocused = focused === -1
|
407
|
+
const noOptionsAvailable = options.length === 0
|
408
|
+
const queryNotEmpty = query.length !== 0
|
409
|
+
const queryLongEnough = query.length >= minLength
|
410
|
+
const showNoOptionsFound = this.props.showNoOptionsFound &&
|
411
|
+
inputFocused && noOptionsAvailable && queryNotEmpty && queryLongEnough
|
412
|
+
|
413
|
+
const wrapperClassName = `${cssNamespace}__wrapper`
|
414
|
+
|
415
|
+
const inputClassName = `${cssNamespace}__input`
|
416
|
+
const componentIsFocused = focused !== null
|
417
|
+
const inputModifierFocused = componentIsFocused ? ` ${inputClassName}--focused` : ''
|
418
|
+
const inputModifierType = this.props.showAllValues ? ` ${inputClassName}--show-all-values` : ` ${inputClassName}--default`
|
419
|
+
const dropdownArrowClassName = `${cssNamespace}__dropdown-arrow-down`
|
420
|
+
const optionFocused = focused !== -1 && focused !== null
|
421
|
+
|
422
|
+
const menuClassName = `${cssNamespace}__menu`
|
423
|
+
const menuModifierDisplayMenu = `${menuClassName}--${displayMenu}`
|
424
|
+
const menuIsVisible = menuOpen || showNoOptionsFound
|
425
|
+
const menuModifierVisibility = `${menuClassName}--${(menuIsVisible) ? 'visible' : 'hidden'}`
|
426
|
+
|
427
|
+
const optionClassName = `${cssNamespace}__option`
|
428
|
+
|
429
|
+
const hintClassName = `${cssNamespace}__hint`
|
430
|
+
const selectedOptionText = this.templateInputValue(options[selected])
|
431
|
+
const optionBeginsWithQuery = selectedOptionText &&
|
432
|
+
selectedOptionText.toLowerCase().indexOf(query.toLowerCase()) === 0
|
433
|
+
const hintValue = (optionBeginsWithQuery && autoselect)
|
434
|
+
? query + selectedOptionText.substr(query.length)
|
435
|
+
: ''
|
436
|
+
const showHint = hasPointerEvents && hintValue
|
437
|
+
|
438
|
+
let dropdownArrow
|
439
|
+
|
440
|
+
// we only need a dropdown arrow if showAllValues is set to a truthy value
|
441
|
+
if (showAllValues) {
|
442
|
+
dropdownArrow = dropdownArrowFactory({ className: dropdownArrowClassName })
|
443
|
+
|
444
|
+
// if the factory returns a string we'll render this as HTML (usage w/o (P)React)
|
445
|
+
if (typeof dropdownArrow === 'string') {
|
446
|
+
dropdownArrow = <div className={`${cssNamespace}__dropdown-arrow-down-wrapper`} dangerouslySetInnerHTML={{ __html: dropdownArrow }} />
|
447
|
+
}
|
448
|
+
}
|
449
|
+
|
450
|
+
return (
|
451
|
+
<div className={wrapperClassName} onKeyDown={this.handleKeyDown} role='combobox' aria-expanded={menuOpen ? 'true' : 'false'}>
|
452
|
+
<Status
|
453
|
+
length={options.length}
|
454
|
+
queryLength={query.length}
|
455
|
+
minQueryLength={minLength}
|
456
|
+
selectedOption={this.templateInputValue(options[selected])}
|
457
|
+
selectedOptionIndex={selected}
|
458
|
+
tQueryTooShort={tStatusQueryTooShort}
|
459
|
+
tNoResults={tStatusNoResults}
|
460
|
+
tSelectedOption={tStatusSelectedOption}
|
461
|
+
tResults={tStatusResults}
|
462
|
+
/>
|
463
|
+
|
464
|
+
{showHint && (
|
465
|
+
<span><input className={hintClassName} readonly tabIndex='-1' value={hintValue} /></span>
|
466
|
+
)}
|
467
|
+
|
468
|
+
<input
|
469
|
+
aria-activedescendant={optionFocused ? `${id}__option--${focused}` : false}
|
470
|
+
aria-owns={`${id}__listbox`}
|
471
|
+
autoComplete='off'
|
472
|
+
className={`${inputClassName}${inputModifierFocused}${inputModifierType}`}
|
473
|
+
id={id}
|
474
|
+
onClick={(event) => this.handleInputClick(event)}
|
475
|
+
onBlur={this.handleInputBlur}
|
476
|
+
{...onChangeCrossLibrary(this.handleInputChange)}
|
477
|
+
onFocus={this.handleInputFocus}
|
478
|
+
name={name}
|
479
|
+
placeholder={placeholder}
|
480
|
+
ref={(inputElement) => { this.elementReferences[-1] = inputElement }}
|
481
|
+
type='text'
|
482
|
+
role='textbox'
|
483
|
+
required={required}
|
484
|
+
value={query}
|
485
|
+
/>
|
486
|
+
|
487
|
+
{dropdownArrow}
|
488
|
+
|
489
|
+
<ul
|
490
|
+
className={`${menuClassName} ${menuModifierDisplayMenu} ${menuModifierVisibility}`}
|
491
|
+
onMouseLeave={(event) => this.handleListMouseLeave(event)}
|
492
|
+
id={`${id}__listbox`}
|
493
|
+
role='listbox'
|
494
|
+
>
|
495
|
+
{options.map((option, index) => {
|
496
|
+
const showFocused = focused === -1 ? selected === index : focused === index
|
497
|
+
const optionModifierFocused = showFocused && hovered === null ? ` ${optionClassName}--focused` : ''
|
498
|
+
const optionModifierOdd = (index % 2) ? ` ${optionClassName}--odd` : ''
|
499
|
+
|
500
|
+
return (
|
501
|
+
<li
|
502
|
+
aria-selected={focused === index}
|
503
|
+
className={`${optionClassName}${optionModifierFocused}${optionModifierOdd}`}
|
504
|
+
dangerouslySetInnerHTML={{ __html: this.templateSuggestion(option) }}
|
505
|
+
id={`${id}__option--${index}`}
|
506
|
+
key={index}
|
507
|
+
onBlur={(event) => this.handleOptionBlur(event, index)}
|
508
|
+
onClick={(event) => this.handleOptionClick(event, index)}
|
509
|
+
onMouseEnter={(event) => this.handleOptionMouseEnter(event, index)}
|
510
|
+
ref={(optionEl) => { this.elementReferences[index] = optionEl }}
|
511
|
+
role='option'
|
512
|
+
tabIndex='-1'
|
513
|
+
/>
|
514
|
+
)
|
515
|
+
})}
|
516
|
+
|
517
|
+
{showNoOptionsFound && (
|
518
|
+
<li className={`${optionClassName} ${optionClassName}--no-results`}>{tNoResults()}</li>
|
519
|
+
)}
|
520
|
+
</ul>
|
521
|
+
</div>
|
522
|
+
)
|
523
|
+
}
|
524
|
+
}
|