|
8 | 8 | * https://www.tldrlegal.com/license/apple-mit-license-aml |
9 | 9 | * |
10 | 10 | * @author Postcode.nl |
11 | | - * @version 1.4.0 |
| 11 | + * @version 1.4.1 |
12 | 12 | */ |
13 | 13 |
|
14 | 14 | (function (global, factory) { |
|
31 | 31 | const document = window.document, |
32 | 32 | $ = function (selector) { return document.querySelectorAll(selector); }, |
33 | 33 | elementData = new WeakMap(), |
34 | | - VERSION = '1.4.0', |
| 34 | + VERSION = '1.4.1', |
35 | 35 | EVENT_NAMESPACE = 'autocomplete-', |
36 | 36 | PRECISION_ADDRESS = 'Address', |
37 | 37 | KEY_ESC = 'Escape', |
|
42 | 42 | KEY_UP_LEGACY = 'Up', |
43 | 43 | KEY_DOWN = 'ArrowDown', |
44 | 44 | KEY_DOWN_LEGACY = 'Down', |
| 45 | + BUILDING_LIST_MODES = { PAGED: 'paged', SHORT: 'short' }, |
45 | 46 |
|
46 | 47 | /** |
47 | 48 | * Default options. |
|
185 | 186 | writable: true, |
186 | 187 | }, |
187 | 188 |
|
| 189 | + /** |
| 190 | + * Set mode for listing buildings in `Street` context. Supported modes: |
| 191 | + * |
| 192 | + * - 'paged': get a larger list of building results (actual number determined by API). Includes link to view more buildings. |
| 193 | + * - 'short': building results are limited to the first few items, similar to matches in other contexts. |
| 194 | + */ |
| 195 | + buildingListMode: { |
| 196 | + set (mode) { |
| 197 | + if (Object.values(BUILDING_LIST_MODES).includes(mode)) |
| 198 | + { |
| 199 | + this.mode = mode; |
| 200 | + } |
| 201 | + else |
| 202 | + { |
| 203 | + console.error('Invalid mode for option buildingListMode:', mode); |
| 204 | + } |
| 205 | + }, |
| 206 | + get () { |
| 207 | + return this.mode || BUILDING_LIST_MODES.SHORT; |
| 208 | + }, |
| 209 | + }, |
188 | 210 | }); |
189 | 211 |
|
190 | 212 | /** |
|
235 | 257 | return; |
236 | 258 | } |
237 | 259 |
|
238 | | - removeItemFocus(); |
239 | | - |
240 | 260 | setValue = setValue || false; |
241 | 261 | isRelative = isRelative || false; |
242 | 262 |
|
|
265 | 285 | && (index > 0 && toIndex < fromIndex || index < 0 && toIndex > fromIndex) // Wrapping |
266 | 286 | ) |
267 | 287 | { |
268 | | - item = null; |
| 288 | + setActiveItem(null); |
269 | 289 | inputElement.value = inputValue; |
270 | 290 | elementData.get(inputElement).context = inputContext; |
271 | 291 | return; |
272 | 292 | } |
273 | 293 |
|
274 | | - item = ul.children[toIndex]; |
275 | | - item.classList.add(classNames.itemFocus); |
276 | | - |
277 | | - // Scroll the menu item into view if needed. |
278 | | - if (ul.scrollTop > item.offsetTop) |
279 | | - { |
280 | | - ul.scrollTop = item.offsetTop; |
281 | | - } |
282 | | - else if ((item.offsetHeight + item.offsetTop) > ul.clientHeight) |
283 | | - { |
284 | | - ul.scrollTop = (item.offsetHeight + item.offsetTop) - ul.clientHeight; |
285 | | - } |
| 294 | + setActiveItem(ul.children[toIndex]); |
286 | 295 |
|
287 | 296 | const data = elementData.get(item); |
288 | 297 |
|
|
297 | 306 | inputElement.value = data.value; |
298 | 307 | elementData.get(inputElement).context = data.context; |
299 | 308 | selectedIndex = toIndex; |
| 309 | + |
| 310 | + // Scroll the menu item into view if needed. |
| 311 | + if (ul.scrollTop > item.offsetTop) |
| 312 | + { |
| 313 | + ul.scrollTop = item.offsetTop; |
| 314 | + } |
| 315 | + else if (item.offsetTop >= (ul.clientHeight + ul.scrollTop)) |
| 316 | + { |
| 317 | + ul.scrollTop = (item.offsetHeight + item.offsetTop) - ul.clientHeight; |
| 318 | + } |
300 | 319 | } |
301 | 320 | }, |
302 | 321 |
|
|
308 | 327 | return Array.prototype.indexOf.call(ul.children, element); |
309 | 328 | }, |
310 | 329 |
|
| 330 | + /** |
| 331 | + * Set and focus active item. Removes focus from previous item. |
| 332 | + */ |
| 333 | + setActiveItem = function (newItem) |
| 334 | + { |
| 335 | + removeItemFocus(); |
| 336 | + |
| 337 | + item = newItem; |
| 338 | + if (item) |
| 339 | + { |
| 340 | + item.classList.add(classNames.itemFocus); |
| 341 | + } |
| 342 | + }, |
| 343 | + |
311 | 344 | /** |
312 | 345 | * Remove the item focus CSS class from the active item, if any. |
313 | 346 | */ |
|
391 | 424 | return; |
392 | 425 | } |
393 | 426 |
|
394 | | - removeItemFocus(); |
395 | | - |
396 | 427 | let target = e.target; |
397 | 428 |
|
398 | 429 | while (target.parentElement !== ul) |
399 | 430 | { |
400 | 431 | target = target.parentElement; |
401 | 432 | } |
402 | 433 |
|
403 | | - item = target; |
404 | | - item.classList.add(classNames.itemFocus); |
405 | | - }); |
406 | | - |
407 | | - ul.addEventListener('mouseout', function () { |
408 | | - self.blur(); |
409 | | - |
410 | | - if (selectedIndex !== null) |
411 | | - { |
412 | | - moveItemFocus(selectedIndex); |
413 | | - } |
| 434 | + setActiveItem(target); |
| 435 | + moveItemFocus(indexOf(target)); |
414 | 436 | }); |
415 | 437 |
|
416 | 438 | wrapper.addEventListener('mousedown', function () { |
|
448 | 470 | this.setItems = function (matches, renderItem) |
449 | 471 | { |
450 | 472 | ul.innerHTML = ''; |
451 | | - ul.scrollTop = 0; |
452 | 473 |
|
453 | 474 | for (let i = 0, li, match; (match = matches[i++]);) |
454 | 475 | { |
455 | 476 | li = renderItem(ul, match); |
456 | 477 | elementData.set(li, match); |
457 | 478 | } |
458 | 479 |
|
459 | | - item = null; |
| 480 | + setActiveItem(null); |
460 | 481 | selectedIndex = null; |
| 482 | + ul.scrollTop = 0; |
461 | 483 | } |
462 | 484 |
|
463 | 485 | /** |
|
533 | 555 | */ |
534 | 556 | this.blur = function () |
535 | 557 | { |
536 | | - removeItemFocus(); |
537 | | - item = null; |
| 558 | + setActiveItem(null); |
538 | 559 | } |
539 | 560 |
|
540 | 561 | /** |
|
572 | 593 | */ |
573 | 594 | this.clear = function () |
574 | 595 | { |
575 | | - removeItemFocus(); |
576 | | - item = null; |
| 596 | + setActiveItem(null); |
577 | 597 | selectedIndex = null; |
578 | 598 | ul.innerHTML = ''; |
579 | 599 | } |
|
878 | 898 | */ |
879 | 899 | this.getSuggestions = function (context, term, response) |
880 | 900 | { |
881 | | - let url = this.options.autocompleteUrl + '/' + encodeURIComponent(context) + '/' + encodeURIComponent(term); |
882 | | - |
883 | | - if (typeof options.language !== 'undefined') |
| 901 | + const url = [ |
| 902 | + this.options.autocompleteUrl, |
| 903 | + encodeURIComponent(context), |
| 904 | + encodeURIComponent(term), |
| 905 | + options.language || '', |
| 906 | + ]; |
| 907 | + |
| 908 | + if (options.buildingListMode !== BUILDING_LIST_MODES.SHORT) |
884 | 909 | { |
885 | | - url += '/' + options.language; |
| 910 | + // Only specify when not the API default, for better compatibility with client proxies |
| 911 | + url.push(options.buildingListMode); |
886 | 912 | } |
887 | 913 |
|
888 | | - return this.xhrGet(url, response); |
| 914 | + return this.xhrGet( |
| 915 | + url.join('/').replace(/\/+$/, ''), |
| 916 | + response |
| 917 | + ); |
889 | 918 | } |
890 | 919 |
|
891 | 920 | /** |
|
961 | 990 | } |
962 | 991 |
|
963 | 992 | /** |
964 | | - * Highlight matched portions in the item label. |
965 | | - * |
966 | | - * @param {string} str - Item label to highlight. |
967 | | - * @param {Array.Array.<number>} indices - Array of character offset pairs. |
968 | | - * @return {string} Highlighted string (using "mark" elements). |
969 | | - */ |
| 993 | + * Highlight matched portions in the item label. |
| 994 | + * |
| 995 | + * @param {string} str - Item label to highlight. |
| 996 | + * @param {Array.Array.<number>} indices - Array of character offset pairs. |
| 997 | + * @return {string} Highlighted string (using "mark" elements). |
| 998 | + */ |
970 | 999 | this.highlight = function (str, indices) |
971 | 1000 | { |
972 | 1001 | if (indices.length === 0) |
|
1173 | 1202 | } |
1174 | 1203 |
|
1175 | 1204 | e.preventDefault(); |
1176 | | - break; |
| 1205 | + break; |
1177 | 1206 |
|
1178 | 1207 | case KEY_DOWN: |
1179 | 1208 | case KEY_DOWN_LEGACY: |
|
1187 | 1216 | } |
1188 | 1217 |
|
1189 | 1218 | e.preventDefault(); |
1190 | | - break; |
| 1219 | + break; |
1191 | 1220 |
|
1192 | 1221 | case KEY_ESC: |
1193 | 1222 | case KEY_ESC_LEGACY: |
1194 | 1223 | menu.close(true); |
1195 | | - break; |
| 1224 | + break; |
1196 | 1225 |
|
1197 | 1226 | case KEY_TAB: |
1198 | 1227 | if (menu.hasFocus) |
1199 | 1228 | { |
1200 | 1229 | menu.select(); |
1201 | 1230 | e.preventDefault(); |
1202 | 1231 | } |
1203 | | - break; |
| 1232 | + break; |
1204 | 1233 |
|
1205 | 1234 | case KEY_ENTER: |
1206 | 1235 | if (menu.hasFocus) |
|
1212 | 1241 | { |
1213 | 1242 | menu.close(); |
1214 | 1243 | } |
1215 | | - break; |
| 1244 | + break; |
1216 | 1245 |
|
1217 | 1246 | default: |
1218 | 1247 | searchDebounced(element); |
|
0 commit comments