@@ -3,7 +3,7 @@ import type { ComboboxRootProps, ComboboxRootEmits, ComboboxContentProps, Combob
33import type { AppConfig } from ' @nuxt/schema'
44import theme from ' #build/ui/input-menu'
55import type { UseComponentIconsProps } from ' ../composables/useComponentIcons'
6- import type { AvatarProps , ChipProps , IconProps , InputProps } from ' ../types'
6+ import type { AvatarProps , ButtonProps , ChipProps , IconProps , InputProps , LinkPropsKeys } from ' ../types'
77import type { ModelModifiers } from ' ../types/input'
88import type { InputHTMLAttributes } from ' ../types/html'
99import type { AcceptableValue , ArrayOrNested , GetItemKeys , GetItemValue , GetModelValue , GetModelValueEmits , NestedItem , EmitsToProps } from ' ../types/utils'
@@ -34,7 +34,7 @@ export type InputMenuItem = InputMenuValue | {
3434 [key : string ]: any
3535}
3636
37- export interface InputMenuProps <T extends ArrayOrNested <InputMenuItem > = ArrayOrNested <InputMenuItem >, VK extends GetItemKeys <T > | undefined = undefined , M extends boolean = false > extends Pick <ComboboxRootProps <T >, ' open' | ' defaultOpen' | ' disabled' | ' name' | ' resetSearchTermOnBlur' | ' resetSearchTermOnSelect' | ' highlightOnHover' | ' openOnClick' | ' openOnFocus' >, UseComponentIconsProps , /** @vue-ignore */ Omit <InputHTMLAttributes , ' disabled' | ' name' | ' type' | ' placeholder' | ' autofocus' | ' maxlength' | ' minlength' | ' pattern' | ' size' | ' min' | ' max' | ' step' > {
37+ export interface InputMenuProps <T extends ArrayOrNested <InputMenuItem > = ArrayOrNested <InputMenuItem >, VK extends GetItemKeys <T > | undefined = undefined , M extends boolean = false > extends Pick <ComboboxRootProps <T >, ' open' | ' defaultOpen' | ' disabled' | ' name' | ' resetSearchTermOnBlur' | ' resetSearchTermOnSelect' | ' resetModelValueOnClear ' | ' highlightOnHover' | ' openOnClick' | ' openOnFocus' >, UseComponentIconsProps , /** @vue-ignore */ Omit <InputHTMLAttributes , ' disabled' | ' name' | ' type' | ' placeholder' | ' autofocus' | ' maxlength' | ' minlength' | ' pattern' | ' size' | ' min' | ' max' | ' step' > {
3838 /**
3939 * The element or component this component should render as.
4040 * @defaultValue 'div'
@@ -78,6 +78,18 @@ export interface InputMenuProps<T extends ArrayOrNested<InputMenuItem> = ArrayOr
7878 * @IconifyIcon
7979 */
8080 deleteIcon? : IconProps [' name' ]
81+ /**
82+ * Display a clear button to reset the model value.
83+ * Can be an object to pass additional props to the Button.
84+ * @defaultValue false
85+ */
86+ clear? : boolean | Partial <Omit <ButtonProps , LinkPropsKeys >>
87+ /**
88+ * The icon displayed in the clear button.
89+ * @defaultValue appConfig.ui.icons.close
90+ * @IconifyIcon
91+ */
92+ clearIcon? : IconProps [' name' ]
8193 /**
8294 * The content of the menu.
8395 * @defaultValue { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }
@@ -159,6 +171,7 @@ export type InputMenuEmits<A extends ArrayOrNested<InputMenuItem>, VK extends Ge
159171 ' blur' : [event : FocusEvent ]
160172 ' focus' : [event : FocusEvent ]
161173 ' create' : [item : string ]
174+ ' clear' : []
162175 /** Event handler when highlighted element changes. */
163176 ' highlight' : [payload : {
164177 ref: HTMLElement
@@ -193,7 +206,7 @@ export interface InputMenuSlots<
193206
194207<script setup lang="ts" generic =" T extends ArrayOrNested <InputMenuItem >, VK extends GetItemKeys <T > | undefined = undefined , M extends boolean = false " >
195208import { computed , useTemplateRef , toRef , onMounted , toRaw , nextTick } from ' vue'
196- import { ComboboxRoot , ComboboxArrow , ComboboxAnchor , ComboboxInput , ComboboxTrigger , ComboboxPortal , ComboboxContent , ComboboxEmpty , ComboboxGroup , ComboboxVirtualizer , ComboboxLabel , ComboboxSeparator , ComboboxItem , ComboboxItemIndicator , TagsInputRoot , TagsInputItem , TagsInputItemText , TagsInputItemDelete , TagsInputInput , useForwardPropsEmits , useFilter } from ' reka-ui'
209+ import { ComboboxRoot , ComboboxArrow , ComboboxAnchor , ComboboxInput , ComboboxTrigger , ComboboxCancel , ComboboxPortal , ComboboxContent , ComboboxEmpty , ComboboxGroup , ComboboxVirtualizer , ComboboxLabel , ComboboxSeparator , ComboboxItem , ComboboxItemIndicator , TagsInputRoot , TagsInputItem , TagsInputItemText , TagsInputItemDelete , TagsInputInput , useForwardPropsEmits , useFilter } from ' reka-ui'
197210import { defu } from ' defu'
198211import { isEqual } from ' ohash/utils'
199212import { reactivePick , createReusableTemplate } from ' @vueuse/core'
@@ -208,6 +221,7 @@ import { getEstimateSize } from '../utils/virtualizer'
208221import { tv } from ' ../utils/tv'
209222import UIcon from ' ./Icon.vue'
210223import UAvatar from ' ./Avatar.vue'
224+ import UButton from ' ./Button.vue'
211225import UChip from ' ./Chip.vue'
212226
213227defineOptions ({ inheritAttrs: false })
@@ -220,6 +234,7 @@ const props = withDefaults(defineProps<InputMenuProps<T, VK, M>>(), {
220234 descriptionKey: ' description' ,
221235 resetSearchTermOnBlur: true ,
222236 resetSearchTermOnSelect: true ,
237+ resetModelValueOnClear: true ,
223238 virtualize: false
224239})
225240const emits = defineEmits <InputMenuEmits <T , VK , M >>()
@@ -231,10 +246,11 @@ const { t } = useLocale()
231246const appConfig = useAppConfig () as InputMenu [' AppConfig' ]
232247const { contains } = useFilter ({ sensitivity: ' base' })
233248
234- const rootProps = useForwardPropsEmits (reactivePick (props , ' as' , ' modelValue' , ' defaultValue' , ' open' , ' defaultOpen' , ' required' , ' multiple' , ' resetSearchTermOnBlur' , ' resetSearchTermOnSelect' , ' highlightOnHover' , ' openOnClick' , ' openOnFocus' ), emits )
249+ const rootProps = useForwardPropsEmits (reactivePick (props , ' as' , ' modelValue' , ' defaultValue' , ' open' , ' defaultOpen' , ' required' , ' multiple' , ' resetSearchTermOnBlur' , ' resetSearchTermOnSelect' , ' resetModelValueOnClear ' , ' highlightOnHover' , ' openOnClick' , ' openOnFocus' ), emits )
235250const portalProps = usePortal (toRef (() => props .portal ))
236251const contentProps = toRef (() => defu (props .content , { side: ' bottom' , sideOffset: 8 , collisionPadding: 8 , position: ' popper' }) as ComboboxContentProps )
237252const arrowProps = toRef (() => props .arrow as ComboboxArrowProps )
253+ const clearProps = computed (() => typeof props .clear === ' object' ? props .clear : {} as Partial <Omit <ButtonProps , LinkPropsKeys >>)
238254const virtualizerProps = toRef (() => {
239255 if (! props .virtualize ) return false
240256
@@ -458,6 +474,17 @@ function isInputItem(item: InputMenuItem): item is Exclude<InputMenuItem, InputM
458474 return typeof item === ' object' && item !== null
459475}
460476
477+ function isModelValueEmpty(modelValue : GetModelValue <T , VK , M >): boolean {
478+ if (props .multiple && Array .isArray (modelValue )) {
479+ return modelValue .length === 0
480+ }
481+ return modelValue === undefined || modelValue === null || modelValue === ' '
482+ }
483+
484+ function onClear() {
485+ emits (' clear' )
486+ }
487+
461488defineExpose ({
462489 inputRef: toRef (() => inputRef .value ?.$el as HTMLInputElement )
463490})
@@ -609,9 +636,23 @@ defineExpose({
609636 </slot >
610637 </span >
611638
612- <ComboboxTrigger v-if =" isTrailing || !!slots.trailing" data-slot =" trailing" :class =" ui.trailing({ class: props.ui?.trailing })" >
639+ <ComboboxTrigger v-if =" isTrailing || !!slots.trailing || !!clear " data-slot =" trailing" :class =" ui.trailing({ class: props.ui?.trailing })" >
613640 <slot name =" trailing" :model-value =" (modelValue as GetModelValue<T, VK, M>)" :open =" open" :ui =" ui" >
614- <UIcon v-if =" trailingIconName" :name =" trailingIconName" data-slot =" trailingIcon" :class =" ui.trailingIcon({ class: props.ui?.trailingIcon })" />
641+ <ComboboxCancel v-if =" !!clear && !isModelValueEmpty(modelValue as GetModelValue<T, VK, M>)" as-child >
642+ <UButton
643+ as =" span"
644+ :icon =" clearIcon || appConfig.ui.icons.close"
645+ variant =" link"
646+ color =" neutral"
647+ tabindex =" -1"
648+ v-bind =" clearProps"
649+ data-slot =" trailingClear"
650+ :class =" ui.trailingClear({ class: props.ui?.trailingClear })"
651+ @click.stop =" onClear"
652+ />
653+ </ComboboxCancel >
654+
655+ <UIcon v-else-if =" trailingIconName" :name =" trailingIconName" data-slot =" trailingIcon" :class =" ui.trailingIcon({ class: props.ui?.trailingIcon })" />
615656 </slot >
616657 </ComboboxTrigger >
617658 </ComboboxAnchor >
0 commit comments