11<template >
2- <div class =" autocomplete-container" >
3- <input
4- v-model =" searchText"
5- type =" text"
6- class =" form-control"
7- @focus =" handleFocus"
8- @blur =" handleBlur"
9- @keydown =" handleKeydown"
10- @keyup =" handleKeyup()"
11- />
12- <div v-show =" menuOpen" class =" autocomplete-menu dropdown-menu show" >
13- <a
14- v-for =" (item, index) in filteredItems"
15- :key =" `${item[itemKey]}-${index}`"
16- href =" #"
17- class =" autocomplete-menu-item dropdown-item"
18- :class =" { active: index === activeIndex }"
19- @click.stop.prevent =" handleSelect(item)"
20- >
21- <div v-show =" !$slots.item" class =" autocomplete-menu-item-display" >
22- {{ searchKey ? item[searchKey] : item }}
23- </div >
24- </a >
25- <div
26- v-show =" !filteredItems.length"
27- class =" autocomplete-menu-item dropdown-item no-items"
28- >
29- No items
30- </div >
31- </div >
32- <button
33- v-if =" searchText.length"
34- type =" button"
35- class =" btn btn-primary btn-sm btn-smaller clear-button"
36- @click =" handleClear"
37- >
38- Clear
39- </button >
40- </div >
2+ <div class =" autocomplete-container" >
3+ <input
4+ v-model =" searchText"
5+ type =" text"
6+ class =" form-control"
7+ @focus =" handleFocus"
8+ @blur =" handleBlur"
9+ @keydown =" handleKeydown"
10+ @keyup =" handleKeyup()"
11+ />
12+ <div v-show =" menuOpen" class =" autocomplete-menu dropdown-menu show" >
13+ <a
14+ v-for =" (item, index) in filteredItems"
15+ :key =" `${item[itemKey]}-${index}`"
16+ href =" #"
17+ class =" autocomplete-menu-item dropdown-item"
18+ :class =" { active: index === activeIndex }"
19+ @click.stop.prevent =" handleSelect(item)"
20+ >
21+ <div v-show =" !$slots.item" class =" autocomplete-menu-item-display" >
22+ {{ searchKey ? item[searchKey] : item }}
23+ </div >
24+ </a >
25+ <div
26+ v-show =" !filteredItems.length"
27+ class =" autocomplete-menu-item dropdown-item no-items"
28+ >
29+ No items
30+ </div >
31+ </div >
32+ <button
33+ v-if =" searchText.length"
34+ type =" button"
35+ class =" btn btn-primary btn-sm btn-smaller clear-button"
36+ @click =" handleClear"
37+ >
38+ Clear
39+ </button >
40+ </div >
4141</template >
4242
4343<style lang="scss" scoped>
4444.autocomplete-container {
45- position : relative ;
46-
47- .autocomplete-menu {
48- width : 100% ;
49- max-height : 50vh ;
50- overflow : auto ;
51-
52- .autocomplete-menu-item {
53- cursor : pointer ;
54- }
55- }
56-
57- button .clear-button {
58- position : absolute ;
59- right : 10px ;
60- top : 10px ;
61- }
45+ position : relative ;
46+
47+ .autocomplete-menu {
48+ width : 100% ;
49+ max-height : 50vh ;
50+ overflow : auto ;
51+
52+ .autocomplete-menu-item {
53+ cursor : pointer ;
54+ }
55+ }
56+
57+ button .clear-button {
58+ position : absolute ;
59+ right : 10px ;
60+ top : 10px ;
61+ }
6262}
6363 </style >
6464
@@ -67,27 +67,27 @@ import { ref, PropType, computed, watch } from 'vue';
6767import { debounce } from ' ts-debounce' ;
6868
6969const props = defineProps ({
70- items: {
71- // eslint-disable-next-line @typescript-eslint/no-explicit-any
72- type: Array as PropType <any []>,
73- default : () => []
74- },
75- itemKey: {
76- type: String ,
77- required: true
78- },
79- searchKey: {
80- type: String ,
81- required: true
82- },
83- value: {
84- type: String ,
85- default: undefined
86- },
87- minSearchLength: {
88- type: Number ,
89- default: 2
90- }
70+ items: {
71+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
72+ type: Array as PropType <any []>,
73+ default : () => []
74+ },
75+ itemKey: {
76+ type: String ,
77+ required: true
78+ },
79+ searchKey: {
80+ type: String ,
81+ required: true
82+ },
83+ value: {
84+ type: String ,
85+ default: undefined
86+ },
87+ minSearchLength: {
88+ type: Number ,
89+ default: 2
90+ }
9191});
9292
9393const emit = defineEmits ([' select' , ' inputChange' ]);
@@ -99,89 +99,89 @@ const initialValueSet = ref(false);
9999const lastEmittedValue = ref (' ' );
100100
101101const filteredItems = computed (() =>
102- props .items .filter ((i ) =>
103- ((props .searchKey ? i [props .searchKey ] : i ) as string )
104- ?.toLocaleLowerCase ()
105- .includes (searchText .value .toLocaleLowerCase ())
106- )
102+ props .items .filter ((i ) =>
103+ ((props .searchKey ? i [props .searchKey ] : i ) as string )
104+ ?.toLocaleLowerCase ()
105+ .includes (searchText .value .toLocaleLowerCase ())
106+ )
107107);
108108
109109function handleFocus() {
110- menuOpen .value = true ;
110+ menuOpen .value = true ;
111111}
112112
113113function handleBlur(event : FocusEvent ) {
114- if (! event .relatedTarget ) {
115- menuOpen .value = false ;
116- }
114+ if (! event .relatedTarget ) {
115+ menuOpen .value = false ;
116+ }
117117}
118118
119119// eslint-disable-next-line @typescript-eslint/no-explicit-any
120120function handleSelect(item ? : any ) {
121- emit (' select' , item );
122- menuOpen .value = false ;
123- searchText .value = item ? item [props .searchKey ].toString () : ' ' ;
121+ emit (' select' , item );
122+ menuOpen .value = false ;
123+ searchText .value = item ? item [props .searchKey ].toString () : ' ' ;
124124}
125125
126126function handleClear() {
127- searchText .value = ' ' ;
128- handleSelect ();
129- handleKeyup ();
127+ searchText .value = ' ' ;
128+ handleSelect ();
129+ handleKeyup ();
130130}
131131
132132function setActiveIndex(i : number ) {
133- if (i < 0 ) {
134- activeIndex .value = filteredItems .value .length - 1 ;
135- } else if (i > filteredItems .value .length - 1 ) {
136- activeIndex .value = 0 ;
137- } else {
138- activeIndex .value = i ;
139- }
133+ if (i < 0 ) {
134+ activeIndex .value = filteredItems .value .length - 1 ;
135+ } else if (i > filteredItems .value .length - 1 ) {
136+ activeIndex .value = 0 ;
137+ } else {
138+ activeIndex .value = i ;
139+ }
140140}
141141
142142function handleKeydown(event : KeyboardEvent ) {
143- menuOpen .value = true ;
144- if (event .key === ' ArrowDown' ) {
145- event .preventDefault ();
146- setActiveIndex (activeIndex .value + 1 );
147- } else if (event .key === ' ArrowUp' ) {
148- event .preventDefault ();
149- setActiveIndex (activeIndex .value - 1 );
150- } else if (event .key === ' Enter' ) {
151- event .preventDefault ();
152- if (filteredItems .value .length === 1 ) {
153- activeIndex .value = 0 ;
154- }
155- const item = filteredItems .value [activeIndex .value ];
156- handleSelect (item );
157- }
143+ menuOpen .value = true ;
144+ if (event .key === ' ArrowDown' ) {
145+ event .preventDefault ();
146+ setActiveIndex (activeIndex .value + 1 );
147+ } else if (event .key === ' ArrowUp' ) {
148+ event .preventDefault ();
149+ setActiveIndex (activeIndex .value - 1 );
150+ } else if (event .key === ' Enter' ) {
151+ event .preventDefault ();
152+ if (filteredItems .value .length === 1 ) {
153+ activeIndex .value = 0 ;
154+ }
155+ const item = filteredItems .value [activeIndex .value ];
156+ handleSelect (item );
157+ }
158158}
159159
160160const handleKeyup = debounce (() => {
161- if (
162- searchText .value === lastEmittedValue .value ||
163- props .minSearchLength > searchText .value .length
164- ) {
165- return ;
166- }
167- emit (' inputChange' , searchText .value ?? ' ' );
168- lastEmittedValue .value = searchText .value ;
161+ if (
162+ searchText .value === lastEmittedValue .value ||
163+ props .minSearchLength > searchText .value .length
164+ ) {
165+ return ;
166+ }
167+ emit (' inputChange' , searchText .value ?? ' ' );
168+ lastEmittedValue .value = searchText .value ;
169169}, 250 );
170170
171171function valueChanged() {
172- if (props .value ) {
173- const match = props .items .find ((i ) => i [props .itemKey ] === props .value );
174- if (match && ! initialValueSet .value ) {
175- searchText .value = match [props .searchKey ].toString ();
176- initialValueSet .value = true ;
177- }
178- }
172+ if (props .value ) {
173+ const match = props .items .find ((i ) => i [props .itemKey ] === props .value );
174+ if (match && ! initialValueSet .value ) {
175+ searchText .value = match [props .searchKey ].toString ();
176+ initialValueSet .value = true ;
177+ }
178+ }
179179}
180180
181181function itemsChanged() {
182- if (! initialValueSet .value ) {
183- valueChanged ();
184- }
182+ if (! initialValueSet .value ) {
183+ valueChanged ();
184+ }
185185}
186186
187187watch (() => props .value , valueChanged );
0 commit comments