From f08daa22e5c5491c3c9478cd27d1007a56550184 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 3 Oct 2024 20:55:01 -0500 Subject: [PATCH] add `allowsCustomValue` --- packages/core/src/combobox/combobox-base.tsx | 26 ++++++++++++------- .../core/src/combobox/combobox-context.tsx | 10 ++++++- packages/core/src/combobox/combobox-input.tsx | 5 ++++ 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/packages/core/src/combobox/combobox-base.tsx b/packages/core/src/combobox/combobox-base.tsx index 8841ee24..db6d0b5c 100644 --- a/packages/core/src/combobox/combobox-base.tsx +++ b/packages/core/src/combobox/combobox-base.tsx @@ -20,6 +20,7 @@ import { type Accessor, type Component, type JSX, + type Setter, type ValidComponent, createEffect, createMemo, @@ -158,6 +159,9 @@ export interface ComboboxBaseOptions /** Property name that refers to the children options of an option group. */ optionGroupChildren?: keyof Exclude; + /** Whether the combobox allows a non-item matching input value to be set. */ + allowsCustomValue?: boolean; + /** An optional keyboard delegate to override the default. */ keyboardDelegate?: KeyboardDelegate; @@ -301,6 +305,7 @@ export function ComboboxBase< const [local, popperProps, formControlProps, others] = splitProps( mergedProps, [ + "allowsCustomValue", "translations", "itemComponent", "sectionComponent", @@ -368,6 +373,7 @@ export function ComboboxBase< const [showAllOptions, setShowAllOptions] = createSignal(false); + const [options, setOptions] = createSignal(local.options!); const [lastDisplayedOptions, setLastDisplayedOptions] = createSignal( local.options!, ); @@ -454,10 +460,10 @@ export function ComboboxBase< // The combobox doesn't contains option groups. if (optionGroupChildren == null) { - return local.options as Option[]; + return options() as Option[]; } - return local.options!.flatMap( + return options().flatMap( (item) => ((item as any)[optionGroupChildren] as Option[]) ?? (item as Option), ); @@ -488,11 +494,11 @@ export function ComboboxBase< // The combobox doesn't contains option groups. if (optionGroupChildren == null) { - return (local.options as Option[]).filter(filterFn); + return (options() as Option[]).filter(filterFn); } const filteredGroups: OptGroup[] = []; - for (const optGroup of local.options as OptGroup[]) { + for (const optGroup of options() as OptGroup[]) { // Filter options of the group const filteredChildrenOptions = ( (optGroup as any)[optionGroupChildren] as Option[] @@ -513,7 +519,7 @@ export function ComboboxBase< const displayedOptions = createMemo(() => { if (disclosureState.isOpen()) { if (showAllOptions()) { - return local.options!; + return options(); } return filteredOptions(); } @@ -605,7 +611,7 @@ export function ComboboxBase< const showAllOptions = setShowAllOptions(triggerMode === "manual"); const hasOptions = showAllOptions - ? local.options!.length > 0 + ? options().length > 0 : filteredOptions().length > 0; // Don't open if there is no option. @@ -738,15 +744,13 @@ export function ComboboxBase< const prevShowAllOptions = prevInput[1]; setLastDisplayedOptions( - prevShowAllOptions ? local.options! : prevFilteredOptions, + prevShowAllOptions ? options() : prevFilteredOptions, ); } else { const filteredOptions = input[0]; const showAllOptions = input[1]; - setLastDisplayedOptions( - showAllOptions ? local.options! : filteredOptions, - ); + setLastDisplayedOptions(showAllOptions ? options() : filteredOptions); } }), ); @@ -857,6 +861,8 @@ export function ComboboxBase< const context: ComboboxContextValue = { dataset, + allowsCustomValue: local.allowsCustomValue ?? false, + setOptions: setOptions as Setter, isOpen: disclosureState.isOpen, isDisabled: () => formControlContext.isDisabled() ?? false, isMultiple: () => access(local.selectionMode) === "multiple", diff --git a/packages/core/src/combobox/combobox-context.tsx b/packages/core/src/combobox/combobox-context.tsx index 852c5640..be7c55bf 100644 --- a/packages/core/src/combobox/combobox-context.tsx +++ b/packages/core/src/combobox/combobox-context.tsx @@ -1,4 +1,10 @@ -import { type Accessor, type JSX, createContext, useContext } from "solid-js"; +import { + type Accessor, + type JSX, + type Setter, + createContext, + useContext, +} from "solid-js"; import type { ListState } from "../list"; import type { CollectionNode } from "../primitives"; @@ -28,6 +34,8 @@ export interface ComboboxContextValue { activeDescendant: Accessor; inputValue: Accessor; triggerMode: Accessor; + setOptions: Setter; + allowsCustomValue: boolean; controlRef: Accessor; inputRef: Accessor; triggerRef: Accessor; diff --git a/packages/core/src/combobox/combobox-input.tsx b/packages/core/src/combobox/combobox-input.tsx index fd21950b..63d8379e 100644 --- a/packages/core/src/combobox/combobox-input.tsx +++ b/packages/core/src/combobox/combobox-input.tsx @@ -177,6 +177,11 @@ export function ComboboxInput( selectionManager().select(focusedKey); } } + if (context.allowsCustomValue) { + const val = e.currentTarget.value; + context.setOptions((x) => [val, ...x]); + selectionManager().select(val); + } break; case "Tab":