11<template >
22<div
3- v-if =" isOpen"
3+ v-if =" $ isOpen"
44 :id =" `suggestions-${id}`"
55 class ="
66 suggestions
77 bg-bg
88 dark:bg-fg
99 "
10- :data-open =" isOpen"
10+ :data-open =" $ isOpen"
1111 role =" listbox"
1212 ref =" el"
1313 v-bind =" $attrs"
@@ -57,31 +57,48 @@ const $attrs = useAttrs()
5757const emits = defineEmits <{
5858 (e : " update:activeSuggestion" , val : number ): void
5959 (e : " submit" , val : string ): void
60- (e : " update:isValid" , val : boolean ): void
6160 (e : " update:isOpen" , val : boolean ): void
6261}>()
6362
6463
6564const props = defineProps ({
66- isValid: { type: Boolean as PropType <boolean >, required: false , default: true },
6765 ... linkableByIdProps (),
6866 ... suggestionsProps ,
6967 ... baseInteractiveProps ,
7068 ... multiValueProps ,
71- values: { type: Array as PropType <string []>, required: false , default : () => []},
7269 canOpen: { type: Boolean as PropType <boolean >, required: false , default: false },
70+ values: { type: Array as PropType <string []>, required: false , default : () => []},
7371})
72+
7473/**
7574 * The final valid value. This is *not* the value you want to share with the input. If `restrictToSuggestions` is true this will not update on any invalid values that `inputValue` might be set to.
7675 */
77- const modelValue = defineModel <string >(" modelValue" , { required: true })
76+ const $ modelValue = defineModel <string >(" modelValue" , { required: true })
7877/**
7978 * If the element is bound to an input, this is the value that the input should be sharing.
8079 *
81- * It allows the component to read even invalid output, and also to reset that invalid output when either modelValue is set to a new value, or when the component is close via cancel.
80+ * It allows the component to read even invalid output, and also to reset that invalid output when either modelValue is set to a new value, or when the component is closed via cancel.
8281 */
83- const inputValue = defineModel <string >(" inputValue" , { default: " " })
84- const isOpen = ref (false )
82+ const $inputValue = defineModel <string >(" inputValue" , { local: true , default: " " })
83+ const $isValid = defineModel <boolean >(" isValid" , { required: false , default: true })
84+ const _isOpen = ref (false )
85+ const $isOpen = computed <boolean >({
86+ get : () => _isOpen .value ,
87+ set : (val : boolean ) => {
88+ _isOpen .value = val
89+ emits (" update:isOpen" , val )
90+ },
91+ })
92+
93+ const activeSuggestionRef = ref <number >(- 1 )
94+ const activeSuggestion = computed ({
95+ get : () => activeSuggestionRef .value ,
96+ set : (val : number ) => {
97+ activeSuggestionRef .value = val
98+ emits (" update:activeSuggestion" , activeSuggestionRef .value )
99+ },
100+ })
101+
85102
86103if (typeof props .suggestions ?.[0 ] === " object" && ! props .suggestionLabel && ! props .suggestionsFilter ) {
87104 throw new Error (" `suggestionLabel` or `suggestionsFilter` must be passed if suggestions are objects." )
@@ -105,23 +122,14 @@ const defaultSuggestionsFilter = (input: string, items: T[]): T[] => input === "
105122 ? [... items ]
106123 : items .filter (item => suggestionLabel .value (item ).toLowerCase ().includes (input .toLowerCase ()))
107124
108- const activeSuggestionRef = ref <number >(- 1 )
109- const activeSuggestion = computed ({
110- get : () => activeSuggestionRef .value ,
111- set : (val : number ) => {
112- activeSuggestionRef .value = val
113- emits (" update:activeSuggestion" , activeSuggestionRef .value )
114- },
115- })
116-
117125
118126const suggestionsFilter = computed (() =>
119127 props .suggestionsFilter ?? defaultSuggestionsFilter )
120128
121129
122130const suggestionsList = computed (() => {
123131 if (props .suggestions ) {
124- const res = suggestionsFilter .value (inputValue .value , props .suggestions )
132+ const res = suggestionsFilter .value ($ inputValue .value , props .suggestions )
125133 return res
126134 }
127135 return undefined
@@ -135,7 +143,7 @@ const moreThanOneSuggestionAvailable = computed<boolean>(() =>
135143
136144const exactlyMatchingSuggestion = computed (() =>
137145 props .suggestions ?.find (suggestion =>
138- inputValue .value === suggestionLabel .value (suggestion )))
146+ $ inputValue .value === suggestionLabel .value (suggestion )))
139147
140148const isValidSuggestion = computed (() =>
141149 ! props .restrictToSuggestions || suggestionAvailable .value )
@@ -156,81 +164,101 @@ const fullSuggestionsList = computed(() => {
156164 return undefined
157165})
158166
167+ const openable = computed (() =>
168+ props .canOpen && (
169+ (isBlank ($inputValue .value ) && props .allowOpenEmpty ) ||
170+ moreThanOneSuggestionAvailable .value ||
171+ (
172+ ! exactlyMatchingSuggestion .value &&
173+ suggestionAvailable .value
174+ ) ||
175+ (
176+ ! isValidSuggestion .value &&
177+ suggestionAvailable .value
178+ )
179+ ),
180+ )
181+
159182
160183const closeSuggestions = (): void => {
161- isOpen .value = false
184+ $ isOpen .value = false
162185 mousedown .value = false
163- emits (" update:isOpen" , isOpen .value )
164186 activeSuggestion .value = - 1
165187}
166188const openSuggestions = (): void => {
167- if (! props .canOpen ) return
168- isOpen .value = true
169- emits (" update:isOpen" , isOpen .value )
189+ if (! openable .value ) return
190+ $isOpen .value = true
170191 // see delay close
171192 if (activeSuggestion .value === - 1 ) activeSuggestion .value = 0
172193 // activeSuggestion.value = 0
173194}
195+ watch (() => props .canOpen , val => {
196+ if (! val ) {
197+ closeSuggestions ()
198+ } else {
199+ openSuggestions ()
200+ }
201+ })
174202watch (isValidSuggestion , () => {
175- emits ( " update: isValid" , isValidSuggestion .value )
203+ $ isValid. value = isValidSuggestion .value
176204 if (! isValidSuggestion .value ) {
177205 openSuggestions ()
178206 }
179207})
180- watch (() => modelValue .value , () => {
181- inputValue .value = getStringValue (modelValue .value )
208+ watch (() => $ modelValue .value , () => {
209+ $ inputValue .value = getStringValue ($ modelValue .value )
182210})
183- watch (() => inputValue .value , () => {
211+ watch (() => $ inputValue .value , () => {
184212 if (props .restrictToSuggestions ) {
185213 if (exactlyMatchingSuggestion .value !== undefined ) {
186- modelValue .value = inputValue .value
214+ $ modelValue .value = $ inputValue .value
187215 }
188216 } else {
189- modelValue .value = inputValue .value
217+ $modelValue .value = $inputValue .value
218+ }
219+ })
220+ watchPostEffect ((): void => {
221+ if (! openable .value ) {
222+ closeSuggestions ()
223+ return
224+ }
225+ if (suggestionAvailable .value ) {
226+ if (! exactlyMatchingSuggestion .value || moreThanOneSuggestionAvailable .value ) {
227+ openSuggestions ()
228+ }
229+ } else if (isValidSuggestion .value ) {
230+ closeSuggestions ()
190231 }
191232})
192-
193-
194- const openable = computed (() =>
195- (isBlank (inputValue .value ) && props .allowOpenEmpty ) ||
196- moreThanOneSuggestionAvailable .value ||
197- (
198- ! exactlyMatchingSuggestion .value &&
199- suggestionAvailable .value
200- ) ||
201- (
202- ! isValidSuggestion .value &&
203- suggestionAvailable .value
204- ),
205- )
206233
207234
208235const setSuggestion = (num : number ): void => {
209236 if (fullSuggestionsList .value === undefined ) return
210237 const val = suggestionLabel .value (fullSuggestionsList .value [num ])
211238
212- modelValue .value = val
213- inputValue .value = val
239+ $ modelValue .value = val
240+ $ inputValue .value = val
214241 closeSuggestions ()
215242}
216- const setSelected = (): void => {
243+
244+ // #region Exposed Functions
245+ const setSelected = (submit : boolean = props .values !== undefined ): void => {
217246 if (activeSuggestion .value > - 1 ) {
218247 // set to active suggestion
219248 setSuggestion (activeSuggestion .value )
220249 }
221- if (props . values ) {
222- emits (" submit" , getStringValue (modelValue .value ))
250+ if (submit ) {
251+ emits (" submit" , getStringValue ($ modelValue .value ))
223252 }
224253}
225254
226255
227- // #region Exposed Functions
228256const inputBlurHandler = (_e : MouseEvent ): void => {
229- if (! isOpen .value ) return
257+ if (! $ isOpen .value ) return
230258 // clicked on self, ignore
231259 if (mousedown .value ) {
232260 mousedown .value = false
233- setSelected ()
261+ setSelected (true )
234262 return
235263 }
236264
@@ -245,19 +273,19 @@ const inputBlurHandler = (_e: MouseEvent): void => {
245273 if (activeSuggestion .value > - 1 ) {
246274 setSuggestion (activeSuggestion .value )
247275 } else {
248- inputValue .value = getStringValue (modelValue .value )
276+ $ inputValue .value = getStringValue ($ modelValue .value )
249277 }
250278 }
251279 } else {
252- modelValue .value = inputValue .value
280+ $ modelValue .value = $ inputValue .value
253281 }
254- if (isOpen .value ) {
282+ if ($ isOpen .value ) {
255283 closeSuggestions ()
256284 }
257285}
258286
259287const toggleSuggestions = (): void => {
260- isOpen .value ? closeSuggestions () : openSuggestions ()
288+ $ isOpen .value ? closeSuggestions () : openSuggestions ()
261289}
262290
263291
@@ -279,32 +307,19 @@ const nextSuggestion = (): void => {
279307}
280308
281309const cancel = (): void => {
282- inputValue .value = getStringValue (modelValue .value )
310+ $ inputValue .value = getStringValue ($ modelValue .value )
283311 closeSuggestions ()
284312}
285313
286- watchPostEffect ((): void => {
287- if (! openable .value ) {
288- closeSuggestions ()
289- return
290- }
291- if (suggestionAvailable .value ) {
292- if (! exactlyMatchingSuggestion .value || moreThanOneSuggestionAvailable .value ) {
293- openSuggestions ()
294- }
295- } else if (isValidSuggestion .value ) {
296- closeSuggestions ()
297- }
298- })
299314
300315const inputKeydownHandler = (e : KeyboardEvent ): void => {
301316 if (e .key === " Enter" ) {
302- setSelected ()
317+ setSelected (true )
303318 e .preventDefault ()
304319 } else if (e .key === " Escape" ) {
305320 cancel ()
306321 e .preventDefault ()
307- } else if (! isOpen .value && [" ArrowDown" , " ArrowUp" ].includes (e .key ) && suggestionAvailable .value ) {
322+ } else if (! $ isOpen .value && [" ArrowDown" , " ArrowUp" ].includes (e .key ) && suggestionAvailable .value ) {
308323 openSuggestions ()
309324 e .preventDefault ()
310325 } else if (e .key === " ArrowUp" ) {
0 commit comments