1
+ 'use strict' ;
2
+
1
3
import {
2
4
AutocompleteEventFunction as EventFunction ,
3
5
CloseEventData ,
@@ -29,6 +31,7 @@ export class Autocomplete<T = { label: string; value: string }> {
29
31
lastTerm : string ;
30
32
valueStore ?: string ;
31
33
focusValue ?: string ;
34
+ focusPoint ?: [ number , number ] ;
32
35
}
33
36
> = { } ;
34
37
@@ -168,8 +171,14 @@ export class Autocomplete<T = { label: string; value: string }> {
168
171
this . _removeFocus ( data . ul ) ;
169
172
170
173
// Focus on the new one
171
- const liEl = < HTMLLIElement > ev . target ,
172
- newVal = liEl . dataset . value || liEl . innerText ;
174
+ const liEl = ( < HTMLElement > ev . target ) . closest ( 'li' ) ;
175
+
176
+ if ( ! liEl ) {
177
+ return ;
178
+ }
179
+
180
+ const newVal = liEl . dataset . value || liEl . innerText ;
181
+
173
182
liEl . classList . add ( 'focused' ) ;
174
183
175
184
// Update the input value and store
@@ -200,10 +209,10 @@ export class Autocomplete<T = { label: string; value: string }> {
200
209
if ( typeof ( data . item as { link ?: string } ) . link === 'string' ) {
201
210
window . location . href = ( data . item as { link : string } ) . link ;
202
211
} else {
203
- const liEl = < HTMLLIElement > ev . target ;
212
+ const liEl = ( < HTMLElement > ev . target ) . closest ( 'li' ) ;
204
213
205
214
// Set input value
206
- data . input . value = liEl . dataset . value ?? liEl . innerText ;
215
+ data . input . value = liEl ? .dataset . value ?? liEl ? .innerText ?? '' ;
207
216
this . _stateData [ data . ul . id ] . valueStore = data . input . value ;
208
217
209
218
this . _clearFocusStore ( data . ul . id ) ;
@@ -229,20 +238,36 @@ export class Autocomplete<T = { label: string; value: string }> {
229
238
230
239
this . options . onOpen ?.( ev , data ) ;
231
240
232
- const tL = position ( {
241
+ const { top , left } = position ( {
233
242
target : data . ul ,
234
243
anchor : < HTMLElement > ev . target ,
235
244
my : this . options . position . my ,
236
245
at : this . options . position . at ,
237
246
collision : this . options . position . collision ,
238
247
} ) ;
239
248
240
- data . ul . style . top = tL . top ;
241
- data . ul . style . left = tL . left ;
249
+ data . ul . style . top = top ;
250
+ data . ul . style . left = left ;
242
251
data . ul . hidden = false ;
243
252
244
253
if ( this . options . autoFocus ) {
245
254
data . ul . children [ 0 ] ?. dispatchEvent ( new Event ( 'focus' ) ) ;
255
+ } else {
256
+ this . _stateData [ data . ul . id ] . focusPoint = [ - 1 , - 1 ] ;
257
+
258
+ // If they aren't already hovering over it, remove the focusPoint
259
+ // so we can trigger mouseover events immediately
260
+ setTimeout ( ( ) => {
261
+ const focusPoint = this . _stateData [ data . ul . id ] . focusPoint ;
262
+
263
+ if (
264
+ focusPoint &&
265
+ focusPoint [ 0 ] === - 1 &&
266
+ focusPoint [ 1 ] === - 1
267
+ ) {
268
+ this . _stateData [ data . ul . id ] . focusPoint = undefined ;
269
+ }
270
+ } , 333 ) ;
246
271
}
247
272
248
273
this . _traceLog ( 'Opened menu' , `Menu id: ${ data . ul . id } ` ) ;
@@ -279,6 +304,7 @@ export class Autocomplete<T = { label: string; value: string }> {
279
304
target . value = vS ;
280
305
281
306
this . _stateData [ data . ul . id ] . valueStore = undefined ;
307
+ this . _stateData [ data . ul . id ] . focusValue = undefined ;
282
308
283
309
this . _infoLog ( 'Reverted input' , `Input ac-id: ${ data . ul . id } ` ) ;
284
310
}
@@ -306,8 +332,8 @@ export class Autocomplete<T = { label: string; value: string }> {
306
332
)
307
333
) . json ( ) as Promise < ListItemType < T > [ ] > )
308
334
: typeof this . options . source === 'function'
309
- ? this . options . source ( { term : data . term } )
310
- : this . options . source ,
335
+ ? this . options . source ( { term : data . term } )
336
+ : this . options . source ,
311
337
} ) ;
312
338
} catch {
313
339
return ;
@@ -491,9 +517,14 @@ export class Autocomplete<T = { label: string; value: string }> {
491
517
} ;
492
518
493
519
private _itemClickEvent = ( ev : MouseEvent ) => {
494
- const li = < HTMLLIElement > ev . target ,
495
- ul = < HTMLUListElement > li . parentElement ,
496
- id = ul . id ,
520
+ const li = ( < HTMLElement > ev . target ) . closest ( 'li' ) ,
521
+ ul = li ?. closest ( 'ul' ) ;
522
+
523
+ if ( ! ul || ! li ) {
524
+ return ;
525
+ }
526
+
527
+ const id = ul . id ,
497
528
item =
498
529
this . _stateData [ id ] . data [ Array . from ( ul . children ) . indexOf ( li ) ] ,
499
530
input = < HTMLInputElement > (
@@ -505,19 +536,53 @@ export class Autocomplete<T = { label: string; value: string }> {
505
536
this . itemSelect ( ev , { ul, item, input } ) ;
506
537
} ;
507
538
508
- private _itemFocusEvent = ( ev : FocusEvent ) => {
509
- const li = < HTMLLIElement > ev . target ,
510
- ul = < HTMLUListElement > li . parentElement ,
511
- id = ul . id ,
539
+ private _itemFocusEvent = ( ev : FocusEvent | MouseEvent ) => {
540
+ const li = ( < HTMLElement > ev . target ) . closest ( 'li' ) ,
541
+ ul = li ?. closest ( 'ul' ) ;
542
+
543
+ if ( ! ul || ! li ) {
544
+ return ;
545
+ }
546
+
547
+ const id = ul . id ,
512
548
item =
513
549
this . _stateData [ id ] . data [ Array . from ( ul . children ) . indexOf ( li ) ] ,
514
550
input = < HTMLInputElement > (
515
551
document . querySelector ( `input[data-ac-id='${ id } ']` )
516
- ) ;
552
+ ) ,
553
+ that = this ;
554
+
555
+ if ( ev instanceof MouseEvent && this . _stateData [ id ] . focusPoint ) {
556
+ const [ x , y ] = this . _stateData [ id ] . focusPoint ;
557
+
558
+ if ( x === - 1 && y === - 1 ) {
559
+ this . _stateData [ id ] . focusPoint = [ ev . clientX , ev . clientY ] ;
560
+ li . addEventListener ( 'mousemove' , handlePopHover ) ;
561
+
562
+ return ;
563
+ }
564
+
565
+ this . _stateData [ id ] . focusPoint = undefined ;
566
+ }
517
567
518
568
this . _traceLog ( 'Menu item focused' , `Item summary: ${ li . innerText } ` ) ;
519
569
520
570
this . itemFocus ( ev , { ul, item, input } ) ;
571
+
572
+ function handlePopHover ( this : HTMLLIElement , subEv : MouseEvent ) {
573
+ const focusPoint = that . _stateData [ id ] . focusPoint ;
574
+
575
+ if (
576
+ focusPoint === undefined ||
577
+ Math . abs ( focusPoint [ 0 ] - subEv . clientX ) > 5 ||
578
+ Math . abs ( focusPoint [ 1 ] - subEv . clientY ) > 5
579
+ ) {
580
+ that . _stateData [ id ] . focusPoint = undefined ;
581
+ li ! . removeEventListener ( 'mousemove' , handlePopHover ) ;
582
+
583
+ li ! . dispatchEvent ( new MouseEvent ( 'mouseover' , subEv ) ) ;
584
+ }
585
+ }
521
586
} ;
522
587
523
588
private _removeFocus = ( ul : HTMLUListElement ) => {
@@ -555,25 +620,25 @@ export class Autocomplete<T = { label: string; value: string }> {
555
620
private _debrottle < F extends { ( someEv : Event ) : void } > ( func : F ) {
556
621
const that = this ;
557
622
let calledAgain : boolean ;
558
- let dTimer : NodeJS . Timer | number | undefined ;
623
+ let dTimer : ReturnType < typeof setTimeout > | undefined ;
559
624
560
625
return function ( this : ThisParameterType < F > , ...args : Parameters < F > ) {
561
626
if ( dTimer ) {
562
627
calledAgain = true ;
563
628
} else {
564
- const context = this ;
629
+ const subThat = this ;
565
630
566
631
dTimer = setTimeout ( ( ) => {
567
632
if ( calledAgain ) {
568
633
calledAgain = false ;
569
634
570
- func . apply ( context , args ) ;
635
+ func . apply ( subThat , args ) ;
571
636
}
572
637
573
638
dTimer = undefined ;
574
639
} , that . options . delay ) ;
575
640
576
- func . apply ( context , args ) ;
641
+ func . apply ( subThat , args ) ;
577
642
}
578
643
} ;
579
644
}
0 commit comments