diff --git a/docs/content/docs/2.components/input-menu.md b/docs/content/docs/2.components/input-menu.md index 99d4d9da8f..9a338ae390 100644 --- a/docs/content/docs/2.components/input-menu.md +++ b/docs/content/docs/2.components/input-menu.md @@ -576,6 +576,28 @@ props: --- :: +### Free input + +Use the `allowFreeInput` prop to let users type any value, even if it is not present in the items list. + +::component-code +--- +prettier: true +ignore: + - items +external: + - items +props: + placeholder: 'Type or select a status' + allowFreeInput: true + items: + - Backlog + - Todo + - In Progress + - Done +--- +:: + ## Examples ### With items type diff --git a/src/runtime/components/InputMenu.vue b/src/runtime/components/InputMenu.vue index 857c26d4ea..0c9d0a4b31 100644 --- a/src/runtime/components/InputMenu.vue +++ b/src/runtime/components/InputMenu.vue @@ -109,6 +109,11 @@ export interface InputMenuProps = ArrayOr multiple?: M & boolean /** Highlight the ring color like a focus state. */ highlight?: boolean + /** + * Allow free input that is not present in the items list. + * When true, the value typed by the user is directly reflected in `v-model`. + */ + allowFreeInput?: boolean /** * Determines if custom user input that does not exist in options can be added. * @defaultValue false @@ -238,10 +243,14 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.inputMenu || const items = computed(() => groups.value.flatMap(group => group) as T[]) function displayValue(value: GetItemValue): string { - return getDisplayValue>(items.value, value, { - labelKey: props.labelKey, - valueKey: props.valueKey - }) ?? '' + if (props.allowFreeInput) { + return String(value ?? '') + } else { + return getDisplayValue>(items.value, value, { + labelKey: props.labelKey, + valueKey: props.valueKey + }) ?? '' + } } const groups = computed(() => @@ -325,11 +334,19 @@ function onUpdate(value: any) { emitFormChange() emitFormInput() - if (props.resetSearchTermOnSelect) { + if (props.resetSearchTermOnSelect && !props.allowFreeInput) { searchTerm.value = '' } } +function onSearchTermUpdate(value: string) { + searchTerm.value = value + if (props.allowFreeInput) { + emits('update:modelValue', value as GetModelValue) + onUpdate(value) + } +} + function onBlur(event: FocusEvent) { emits('blur', event) emitFormBlur() @@ -351,7 +368,7 @@ function onUpdateOpen(value: boolean) { // Since we use `displayValue` prop inside ComboboxInput we should reset searchTerm manually // https://reka-ui.com/docs/components/combobox#api-reference - if (props.resetSearchTermOnBlur) { + if (props.resetSearchTermOnBlur && !props.allowFreeInput) { const STATE_ANIMATION_DELAY_MS = 100 timeoutId = setTimeout(() => { @@ -478,7 +495,7 @@ defineExpose({ :required="required" @blur="onBlur" @focus="onFocus" - @update:model-value="searchTerm = $event" + @update:model-value="onSearchTermUpdate" /> @@ -499,7 +516,7 @@ defineExpose({ - + {{ searchTerm ? t('inputMenu.noMatch', { searchTerm }) : t('inputMenu.noData') }}