From d0011b02aae276c045815a83ec26f09208f8fe6e Mon Sep 17 00:00:00 2001 From: Maxime Le Breton Date: Thu, 11 Sep 2025 00:32:47 +0200 Subject: [PATCH 1/9] feat(InputMenu): allow free input not restricted to items Add `allowFreeInput` prop to InputMenu component. This prop enables users to type any value in the input field, even if it is not present in the items list. When enabled, the typed value is directly reflected in `v-model`, making the InputMenu behave more like a standard input field rather than a strict select. - Added `allowFreeInput?: boolean` prop (default: false) - Updated ComboboxInput to emit `update:modelValue` when free input is allowed - Maintains backward compatibility for users who do not enable this prop Closes #4859 --- src/runtime/components/InputMenu.vue | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/runtime/components/InputMenu.vue b/src/runtime/components/InputMenu.vue index 857c26d4ea..64a853ad1b 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 @@ -330,6 +335,14 @@ function onUpdate(value: any) { } } +function onSearchTermUpdate(value: string) { + searchTerm.value = value + if (props.allowFreeInput) { + emits('update:modelValue', value) + onUpdate(value) + } +} + function onBlur(event: FocusEvent) { emits('blur', event) emitFormBlur() @@ -478,7 +491,7 @@ defineExpose({ :required="required" @blur="onBlur" @focus="onFocus" - @update:model-value="searchTerm = $event" + @update:model-value="onSearchTermUpdate" /> From aa0ee66f7266f5f0fce459164199f2d76f0d037c Mon Sep 17 00:00:00 2001 From: Maxime Le Breton Date: Thu, 11 Sep 2025 01:18:10 +0200 Subject: [PATCH 2/9] Trying to fix: Argument of type '"update:modelValue"' is not assignable to parameter of type '"remove-tag"'. --- src/runtime/components/InputMenu.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/components/InputMenu.vue b/src/runtime/components/InputMenu.vue index 64a853ad1b..197f51a4ae 100644 --- a/src/runtime/components/InputMenu.vue +++ b/src/runtime/components/InputMenu.vue @@ -338,7 +338,7 @@ function onUpdate(value: any) { function onSearchTermUpdate(value: string) { searchTerm.value = value if (props.allowFreeInput) { - emits('update:modelValue', value) + emits('update:modelValue', value as GetModelValue) onUpdate(value) } } From b69f2ca286a653edff143a7677ba0dd57eb2fcf1 Mon Sep 17 00:00:00 2001 From: Maxime Le Breton Date: Fri, 12 Sep 2025 18:48:46 +0200 Subject: [PATCH 3/9] Update select.md doc --- docs/content/docs/2.components/select.md | 25 ++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/content/docs/2.components/select.md b/docs/content/docs/2.components/select.md index d8859bd3c2..91ced735a4 100644 --- a/docs/content/docs/2.components/select.md +++ b/docs/content/docs/2.components/select.md @@ -569,6 +569,31 @@ props: --- :: +### Allow 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 + - class +external: + - items +props: + placeholder: 'Type or select a fruit' + allowFreeInput: true + items: + - Apple + - Banana + - Blueberry + - Grapes + - Pineapple + class: 'w-48' +--- +:: + ## Examples ### With items type From 30ae319f3ce6cc2a746050190856440a9b214d3c Mon Sep 17 00:00:00 2001 From: Maxime Le Breton Date: Fri, 12 Sep 2025 18:55:21 +0200 Subject: [PATCH 4/9] Revert select.md --- docs/content/docs/2.components/select.md | 25 ------------------------ 1 file changed, 25 deletions(-) diff --git a/docs/content/docs/2.components/select.md b/docs/content/docs/2.components/select.md index 91ced735a4..d8859bd3c2 100644 --- a/docs/content/docs/2.components/select.md +++ b/docs/content/docs/2.components/select.md @@ -569,31 +569,6 @@ props: --- :: -### Allow 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 - - class -external: - - items -props: - placeholder: 'Type or select a fruit' - allowFreeInput: true - items: - - Apple - - Banana - - Blueberry - - Grapes - - Pineapple - class: 'w-48' ---- -:: - ## Examples ### With items type From 720d6671418d37440e1947825eedc2e82ec69e8a Mon Sep 17 00:00:00 2001 From: Maxime Le Breton Date: Fri, 12 Sep 2025 18:57:35 +0200 Subject: [PATCH 5/9] Update input-menu.md doc --- docs/content/docs/2.components/input-menu.md | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) 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 From cb5762a819fde014c6ebf93c98366ea56d0b7735 Mon Sep 17 00:00:00 2001 From: Maxime Le Breton Date: Sat, 13 Sep 2025 10:35:36 +0200 Subject: [PATCH 6/9] prevent resetSearchTerm if allowFreeInput is true --- src/runtime/components/InputMenu.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/components/InputMenu.vue b/src/runtime/components/InputMenu.vue index 197f51a4ae..44db231d26 100644 --- a/src/runtime/components/InputMenu.vue +++ b/src/runtime/components/InputMenu.vue @@ -330,7 +330,7 @@ function onUpdate(value: any) { emitFormChange() emitFormInput() - if (props.resetSearchTermOnSelect) { + if (props.resetSearchTermOnSelect && !props.allowFreeInput) { searchTerm.value = '' } } From bd9f48e4549744de50f2d85263db655f37edb701 Mon Sep 17 00:00:00 2001 From: Maxime Le Breton Date: Sat, 13 Sep 2025 11:01:51 +0200 Subject: [PATCH 7/9] on allowFreeInput, getDisplayValue return the value & empty box is disabled --- src/runtime/components/InputMenu.vue | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/runtime/components/InputMenu.vue b/src/runtime/components/InputMenu.vue index 44db231d26..7f5da20f69 100644 --- a/src/runtime/components/InputMenu.vue +++ b/src/runtime/components/InputMenu.vue @@ -243,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 value + } else { + return getDisplayValue>(items.value, value, { + labelKey: props.labelKey, + valueKey: props.valueKey + }) ?? '' + } } const groups = computed(() => @@ -512,7 +516,7 @@ defineExpose({ - + {{ searchTerm ? t('inputMenu.noMatch', { searchTerm }) : t('inputMenu.noData') }} From 281ed4fbd74184b23a3cbcf6cb27e62f156fa68f Mon Sep 17 00:00:00 2001 From: Maxime Le Breton Date: Sat, 13 Sep 2025 11:12:28 +0200 Subject: [PATCH 8/9] Fix type issue --- src/runtime/components/InputMenu.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/components/InputMenu.vue b/src/runtime/components/InputMenu.vue index 7f5da20f69..361c549cac 100644 --- a/src/runtime/components/InputMenu.vue +++ b/src/runtime/components/InputMenu.vue @@ -244,7 +244,7 @@ const items = computed(() => groups.value.flatMap(group => group) as T[]) function displayValue(value: GetItemValue): string { if (props.allowFreeInput) { - return value + return String(value ?? '') } else { return getDisplayValue>(items.value, value, { labelKey: props.labelKey, From 67541d312daad4ae8c7d1bd9a0705f46afa5dd92 Mon Sep 17 00:00:00 2001 From: Maxime Le Breton Date: Sat, 13 Sep 2025 13:22:30 +0200 Subject: [PATCH 9/9] disable props.resetSearchTermOnBlur if allowFreeInput --- src/runtime/components/InputMenu.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/components/InputMenu.vue b/src/runtime/components/InputMenu.vue index 361c549cac..0c9d0a4b31 100644 --- a/src/runtime/components/InputMenu.vue +++ b/src/runtime/components/InputMenu.vue @@ -368,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(() => {