1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 | function $root(f){
|
10 |
|
11 | |
12 |
|
13 |
|
14 | function action(o){
|
15 | var r =
|
16 | f(o)
|
17 |
|
18 | return typeof r !== 'undefined'
|
19 | ? r
|
20 | : o
|
21 | }
|
22 | return action
|
23 | }
|
24 |
|
25 |
|
26 |
|
27 |
|
28 | function $ul(f){
|
29 |
|
30 | |
31 |
|
32 |
|
33 | function action(div){
|
34 | var r =
|
35 | f(div.children[1])
|
36 |
|
37 | if( typeof r !== 'undefined' ){
|
38 |
|
39 | div.children[1] = r
|
40 | }
|
41 | return div
|
42 | }
|
43 | return action
|
44 | }
|
45 |
|
46 |
|
47 |
|
48 |
|
49 | function $li( f ){
|
50 |
|
51 | return $ul(function(ul){
|
52 | var r =
|
53 | f(ul.children)
|
54 |
|
55 |
|
56 | if( typeof r !== 'undefined' ){
|
57 |
|
58 | ul.children = r
|
59 | }
|
60 |
|
61 | return ul
|
62 | })
|
63 | }
|
64 |
|
65 |
|
66 |
|
67 |
|
68 | function $input(f){
|
69 |
|
70 | |
71 |
|
72 |
|
73 | function action(div){
|
74 | var r =
|
75 | f(div.children[0])
|
76 |
|
77 | if( typeof r !== 'undefined' ){
|
78 |
|
79 | div.children[0] = r
|
80 | }
|
81 |
|
82 | return div
|
83 | }
|
84 | return action
|
85 | }
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 | |
93 |
|
94 |
|
95 | var contains = function contains(input, text){
|
96 | return input.trim().length
|
97 | ? RegExp(regExpEscape(input.trim()), "i").test(text)
|
98 | : true
|
99 | }
|
100 |
|
101 | |
102 |
|
103 |
|
104 | var regExpEscape = function regExpEscape(s) {
|
105 | return s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&");
|
106 | }
|
107 |
|
108 | |
109 |
|
110 |
|
111 | var sortByLength = function sortByLength(a, b) {
|
112 |
|
113 | if (a.length !== b.length) {
|
114 | return a.length - b.length;
|
115 | }
|
116 |
|
117 | return a < b ? -1 : 1;
|
118 | };
|
119 |
|
120 |
|
121 |
|
122 |
|
123 |
|
124 |
|
125 | var assignPair = function assignPair(p, pair){
|
126 |
|
127 | p[ pair[0] ] = pair[1]
|
128 | return p
|
129 | }
|
130 |
|
131 | var keyboard = {
|
132 | submit:
|
133 | function submit(
|
134 | /** @type {number} */code
|
135 | ,/** @type {string} */ highlighted
|
136 | ) {
|
137 | return code == 13 && highlighted
|
138 | ? [highlighted]
|
139 | : []
|
140 | }
|
141 |
|
142 | ,dismiss:
|
143 |
|
144 | function dismiss(
|
145 | /** @type {number} */ code
|
146 | ){
|
147 | return code == 27
|
148 | ? [true]
|
149 | : []
|
150 | }
|
151 |
|
152 | ,navigate:
|
153 | function navigate(
|
154 | /** @type {boolean} */ showingDrawer
|
155 | ,/** @type {string} */ highlighted
|
156 | ,/** @type {string[]} */ renderedList
|
157 | ,/** @type {number} */ code
|
158 | ){
|
159 | var KEY_UP = 38
|
160 | var KEY_DOWN = 40
|
161 | var i = renderedList.indexOf(highlighted)
|
162 | var NO_MATCH = i == -1
|
163 | var LOWER_BOUND = 0
|
164 | var UPPER_BOUND = renderedList.length -1
|
165 | var MATCH = i >= -1
|
166 | var NEXT = i+1
|
167 | var PREV = i-1
|
168 |
|
169 | if( showingDrawer && (code == KEY_UP || code == KEY_DOWN) ){
|
170 | if( code == KEY_UP && (NO_MATCH || i == LOWER_BOUND) ){
|
171 | return [renderedList[UPPER_BOUND]]
|
172 | } else if(code == KEY_UP && MATCH) {
|
173 | return [renderedList[PREV]]
|
174 | } else if (
|
175 | code == KEY_DOWN && (
|
176 | NO_MATCH || i == UPPER_BOUND
|
177 | )
|
178 | ) {
|
179 | return [renderedList[LOWER_BOUND]]
|
180 | } else {
|
181 | return [renderedList[NEXT]]
|
182 | }
|
183 | } else {
|
184 | return []
|
185 | }
|
186 | }
|
187 | }
|
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 |
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 |
|
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 |
|
212 |
|
213 |
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 |
|
225 |
|
226 |
|
227 |
|
228 |
|
229 |
|
230 |
|
231 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
237 |
|
238 |
|
239 |
|
240 |
|
241 |
|
242 |
|
243 |
|
244 |
|
245 |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 | function BaseAutocomplete(framework){
|
251 |
|
252 | var h = framework.hyperscript
|
253 | var get = framework.get
|
254 | var set = framework.set
|
255 |
|
256 | /**
|
257 | *
|
258 | * @param {Model} model
|
259 | * @param { Partial<Overrides> } nullableOverrides
|
260 | */
|
261 | function Autocomplete(model, nullableOverrides){
|
262 | var overrides = nullableOverrides || {}
|
263 | var list = model.list
|
264 | var input = model.input
|
265 | var chosen = model.chosen
|
266 | var open = model.open
|
267 |
|
268 |
|
269 | var getList = function(){
|
270 | return get(list)
|
271 | }
|
272 |
|
273 | /** @type { () => string } */
|
274 | var getInput = function(){
|
275 | return get(input)
|
276 | }
|
277 |
|
278 | /** @type { () => string } */
|
279 | var getChosen = function(){
|
280 | return get(chosen)
|
281 | }
|
282 |
|
283 | /** @type { () => boolean } */
|
284 | var getOpen = function(){
|
285 | return get(open)
|
286 | }
|
287 |
|
288 | /** @type { () => string } */
|
289 | var getHighlighted = function(){
|
290 | return get(highlighted)
|
291 | }
|
292 |
|
293 | var highlighted = model.highlighted
|
294 |
|
295 | var value = getInput()
|
296 |
|
297 | var minChars = typeof overrides.minChars != 'undefined'
|
298 | ? overrides.minChars
|
299 | : 2
|
300 |
|
301 | var maxItems = typeof overrides.maxItems != 'undefined'
|
302 | ? overrides.maxItems
|
303 | : 10
|
304 |
|
305 | var sort = typeof overrides.sort != 'undefined'
|
306 | ? overrides.sort
|
307 | : sortByLength
|
308 |
|
309 | var filter = typeof overrides.filter != 'undefined'
|
310 | ? overrides.filter
|
311 | : contains
|
312 |
|
313 | var filteredList =
|
314 | typeof overrides.filteredList != 'undefined'
|
315 | ? overrides.filteredList
|
316 | : getList()
|
317 | .filter(function (s){
|
318 | return filter(value, s)
|
319 | })
|
320 | .sort( sort )
|
321 | .slice(0, maxItems)
|
322 |
|
323 | if( getHighlighted() != null
|
324 | && filteredList.indexOf( get( highlighted ) ) == -1
|
325 | ){
|
326 | set(highlighted, null)
|
327 | }
|
328 |
|
329 | if( getChosen() != null
|
330 | && getChosen() != value
|
331 | ){
|
332 | set(chosen, null)
|
333 | }
|
334 |
|
335 | /** @type {Overrides} */
|
336 | var config = {
|
337 |
|
338 | filteredList: filteredList
|
339 | ,minChars: minChars
|
340 | ,maxItems: maxItems
|
341 | ,sort: sort
|
342 | ,filter: filter
|
343 | ,eventNames:
|
344 | typeof overrides.eventNames != 'undefined'
|
345 | ? overrides.eventNames
|
346 | : { oninput: 'oninput'
|
347 | , onfocus: 'onfocus'
|
348 | , onblur: 'onblur'
|
349 | , onkeydown: 'onkeydown'
|
350 | , onmousedown: 'onmousedown'
|
351 | }
|
352 | ,showingDrawer:
|
353 | typeof overrides.showingDrawer != 'undefined'
|
354 | ? overrides.showingDrawer
|
355 | : getOpen()
|
356 | && value.length >= minChars
|
357 | && filteredList.length > 0
|
358 |
|
359 | ,choose:
|
360 | typeof overrides.choose != 'undefined'
|
361 | ? overrides.choose
|
362 | : function choose(x){
|
363 |
|
364 |
|
365 | if( getInput() != x ){
|
366 | set(input, x)
|
367 | }
|
368 |
|
369 | if( getChosen() != x ){
|
370 | set(chosen, x)
|
371 | }
|
372 |
|
373 | config.close()
|
374 | }
|
375 | ,clickItem:
|
376 | typeof overrides.clickItem != 'undefined'
|
377 | ? overrides.clickItem
|
378 | : function clickItem(x){
|
379 | return config.choose(x)
|
380 |
|
381 | }
|
382 | ,PATTERN_INPUT:
|
383 | typeof overrides.PATTERN_INPUT != 'undefined'
|
384 | ? overrides.PATTERN_INPUT
|
385 | : value
|
386 | ? new RegExp(value, 'gi')
|
387 | : null
|
388 | ,mark:
|
389 | typeof overrides.mark != 'undefined'
|
390 | ? overrides.mark
|
391 | : function(x){
|
392 | return h('mark', {}, [x])
|
393 | }
|
394 |
|
395 | ,highlight:
|
396 | typeof overrides.highlight != 'undefined'
|
397 | ? overrides.highlight
|
398 | : function highlight( x ){
|
399 |
|
400 |
|
401 | var matches =
|
402 | config.PATTERN_INPUT != null
|
403 | ? x.match( config.PATTERN_INPUT )
|
404 | : null
|
405 |
|
406 |
|
407 | var initial = {
|
408 | buffer: x
|
409 | ,output: []
|
410 | }
|
411 |
|
412 | var processed =
|
413 | matches != null
|
414 | ? matches
|
415 | .reduce(function(p, n){
|
416 | var i = p.buffer.indexOf(n)
|
417 |
|
418 | return {
|
419 | buffer: p.buffer.slice(i+n.length)
|
420 | ,output: p.output.concat(
|
421 | i === 0
|
422 | ? []
|
423 | : p.buffer.slice(0, i)
|
424 | ,[ config.mark(
|
425 | p.buffer.slice(i, i+n.length)
|
426 | )
|
427 | ]
|
428 | )
|
429 | }
|
430 | }, initial )
|
431 | : { output: [x], buffer: '' }
|
432 |
|
433 | return processed.output.concat(
|
434 | processed.buffer || []
|
435 | )
|
436 | }
|
437 | ,oninput:
|
438 | typeof overrides.oninput != 'undefined'
|
439 | ? overrides.oninput
|
440 | : function oninput(e){
|
441 |
|
442 | var v = e.currentTarget.value
|
443 |
|
444 | if( getInput() != v ){
|
445 | set(input, v)
|
446 | }
|
447 |
|
448 | if( !getOpen() ){
|
449 | set(open, true)
|
450 | }
|
451 | }
|
452 |
|
453 | ,onfocus:
|
454 | typeof overrides.onfocus != 'undefined'
|
455 | ? overrides.onfocus
|
456 | : function onfocus(){
|
457 | if( !getOpen() ){
|
458 | set(open, true)
|
459 | }
|
460 | }
|
461 | ,close:
|
462 | typeof overrides.close != 'undefined'
|
463 | ? overrides.close
|
464 | : function close(){
|
465 |
|
466 | if( getOpen() ){
|
467 | set(open, false)
|
468 | }
|
469 | }
|
470 | ,onblur:
|
471 | typeof overrides.onblur != 'undefined'
|
472 | ? overrides.onblur
|
473 | : function onblur(){
|
474 | config.close()
|
475 | }
|
476 | ,renderInput:
|
477 | typeof overrides.renderInput != 'undefined'
|
478 | ? overrides.renderInput
|
479 | : function renderInput(){
|
480 | return h('input'
|
481 | ,[ ['value', value]
|
482 | , [config.eventNames.oninput, config.oninput]
|
483 | , [config.eventNames.onfocus, config.onfocus]
|
484 | , [config.eventNames.onblur, config.onblur]
|
485 | ]
|
486 | .reduce(assignPair, {})
|
487 | , []
|
488 | )
|
489 | }
|
490 |
|
491 | ,itemClassNames:
|
492 | typeof overrides.itemClassNames != 'undefined'
|
493 | ? overrides.itemClassNames
|
494 | : function itemClassNames(x){
|
495 | return x == get(highlighted)
|
496 | ? 'highlight'
|
497 | : ''
|
498 | }
|
499 |
|
500 | ,renderItem:
|
501 | typeof overrides.renderItem != 'undefined'
|
502 | ? overrides.renderItem
|
503 | : function renderItem(x, config){
|
504 | return h(
|
505 | 'li'
|
506 | ,[ ['className', config.itemClassNames(x, config) ]
|
507 | , [ config.eventNames.onmousedown, function(
|
508 | /** @type {Event} */ e
|
509 | ){
|
510 | config.clickItem(x)
|
511 | e.stopPropagation()
|
512 | }]
|
513 | ]
|
514 | .reduce(assignPair, {})
|
515 |
|
516 | , config.highlight(x)
|
517 | )
|
518 | }
|
519 | ,renderItems:
|
520 | typeof overrides.renderItems != 'undefined'
|
521 | ? overrides.renderItems
|
522 | : function renderItems(config){
|
523 | return h(
|
524 | 'ul'
|
525 | , {}
|
526 | , config.filteredList.map(
|
527 | function filteredList$map(x){
|
528 | return config.renderItem(x, config)
|
529 | }
|
530 | )
|
531 | )
|
532 | }
|
533 | ,classNames:
|
534 | typeof overrides.classNames != 'undefined'
|
535 | ? overrides.classNames
|
536 | : function classNames(){
|
537 | return ['manuel-complete']
|
538 | .concat(
|
539 | config.showingDrawer ? ['open'] : []
|
540 | ,value.length > 0 ? ['not-empty'] : []
|
541 | ,getList().length > 0 ? ['loaded'] : []
|
542 | )
|
543 | .join(' ')
|
544 | }
|
545 | ,renderRoot:
|
546 | typeof overrides.renderRoot != 'undefined'
|
547 | ? overrides.renderRoot
|
548 | : function renderRoot(config){
|
549 | return h('div'
|
550 | ,[[ 'className', config.classNames() ]
|
551 | , [config.eventNames.onkeydown, config.onkeydown]
|
552 | ]
|
553 | .reduce(assignPair, {})
|
554 | ,[ config.renderInput(config)
|
555 | , config.renderItems(config)
|
556 | ]
|
557 | )
|
558 | }
|
559 | ,keyboardSubmit:
|
560 | typeof overrides.keyboardSubmit != 'undefined'
|
561 | ? overrides.keyboardSubmit
|
562 | : keyboard.submit
|
563 | ,keyboardDismiss:
|
564 | typeof overrides.keyboardDismiss != 'undefined'
|
565 | ? overrides.keyboardDismiss
|
566 | : keyboard.dismiss
|
567 | ,keyboardNavigate:
|
568 | typeof overrides.keyboardNavigate != 'undefined'
|
569 | ? overrides.keyboardNavigate
|
570 | : keyboard.navigate
|
571 | ,onkeydown:
|
572 | typeof overrides.onkeydown != 'undefined'
|
573 | ? overrides.onkeydown
|
574 | : function onkeydown(e){
|
575 | var new_chosen =
|
576 | config.keyboardSubmit(
|
577 | e.keyCode
|
578 | , get(highlighted)
|
579 | )
|
580 |
|
581 | var dismiss = config.keyboardDismiss(
|
582 | e.keyCode
|
583 | )
|
584 |
|
585 | var new_highlighted =
|
586 | e.shiftKey
|
587 | ? []
|
588 | : config.keyboardNavigate(
|
589 | config.showingDrawer
|
590 | , get(highlighted)
|
591 | , config.filteredList
|
592 | , e.keyCode
|
593 | )
|
594 |
|
595 | new_chosen.map(
|
596 | config.choose
|
597 | )
|
598 |
|
599 | new_highlighted.map(
|
600 | function new_highlighted$map(v){
|
601 | return set(highlighted, v)
|
602 | }
|
603 | )
|
604 |
|
605 | dismiss.map( config.close )
|
606 |
|
607 | new_chosen.length
|
608 | + dismiss.length
|
609 | + new_highlighted.length
|
610 | > 0
|
611 |
|
612 | && e.preventDefault()
|
613 | }
|
614 | }
|
615 |
|
616 | return config.renderRoot(config)
|
617 | }
|
618 |
|
619 | return Autocomplete
|
620 | }
|
621 |
|
622 | module.exports = BaseAutocomplete
|
623 |
|
624 | // eslint-disable-next-line fp/no-mutation
|
625 | BaseAutocomplete.queries = {
|
626 | listItems: $li
|
627 | , list: $ul
|
628 | , root: $root
|
629 | , input: $input
|
630 | } |
\ | No newline at end of file |