Skip to content

Commit 44be50f

Browse files
committed
fix: fixed several issues with suggestions
- It now also emits "submit" properly on any "intentful" selection (pressing enter or directly clicking) instead of just when multiple values are enabled.
1 parent e28ae19 commit 44be50f

File tree

1 file changed

+88
-73
lines changed

1 file changed

+88
-73
lines changed

src/components/LibSuggestions/LibSuggestions.vue

Lines changed: 88 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
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()
5757
const 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
6564
const 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
86103
if (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
118126
const suggestionsFilter = computed(() =>
119127
props.suggestionsFilter ?? defaultSuggestionsFilter)
120128
121129
122130
const 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
136144
const exactlyMatchingSuggestion = computed(() =>
137145
props.suggestions?.find(suggestion =>
138-
inputValue.value === suggestionLabel.value(suggestion)))
146+
$inputValue.value === suggestionLabel.value(suggestion)))
139147
140148
const 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
160183
const 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
}
166188
const 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+
})
174202
watch(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
208235
const 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
228256
const 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
259287
const toggleSuggestions = (): void => {
260-
isOpen.value ? closeSuggestions() : openSuggestions()
288+
$isOpen.value ? closeSuggestions() : openSuggestions()
261289
}
262290
263291
@@ -279,32 +307,19 @@ const nextSuggestion = (): void => {
279307
}
280308
281309
const 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
300315
const 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

Comments
 (0)