diff --git a/packages/core/src/combobox/combobox-base.tsx b/packages/core/src/combobox/combobox-base.tsx index b048596f..34fdbbaf 100644 --- a/packages/core/src/combobox/combobox-base.tsx +++ b/packages/core/src/combobox/combobox-base.tsx @@ -8,7 +8,6 @@ */ import { - OverrideComponentProps, ValidationState, access, createGenerateId, @@ -21,6 +20,7 @@ import { Accessor, Component, JSX, + ValidComponent, createEffect, createMemo, createSignal, @@ -32,12 +32,13 @@ import { import { FORM_CONTROL_PROP_NAMES, FormControlContext, + FormControlDataSet, createFormControl, } from "../form-control"; import { createFilter } from "../i18n"; import { ListKeyboardDelegate, createListState } from "../list"; import { announce } from "../live-announcer"; -import { AsChildProp, Polymorphic } from "../polymorphic"; +import { Polymorphic, PolymorphicProps } from "../polymorphic"; import { PopperRoot, PopperRootOptions } from "../popper"; import { CollectionNode, @@ -79,10 +80,9 @@ export interface ComboboxBaseSectionComponentProps { export interface ComboboxBaseOptions extends Omit< - PopperRootOptions, - "anchorRef" | "contentRef" | "onCurrentPlacementChange" - >, - AsChildProp { + PopperRootOptions, + "anchorRef" | "contentRef" | "onCurrentPlacementChange" + > { /** The localized strings of the component. */ translations?: ComboboxIntlTranslations; @@ -243,21 +243,33 @@ export interface ComboboxBaseOptions /** Whether the combobox is read only. */ readOnly?: boolean; +} + +export interface ComboboxBaseCommonProps { + id: string; +} - /** The children of the combobox. */ - children?: JSX.Element; +export interface ComboboxBaseRenderProps + extends ComboboxBaseCommonProps, + FormControlDataSet, + ComboboxDataSet { + role: "group"; } -export interface ComboboxBaseProps - extends OverrideComponentProps<"div", ComboboxBaseOptions>, - AsChildProp {} +export type ComboboxBaseProps = ComboboxBaseOptions< + Option, + OptGroup +> & + Partial; /** * Base component for a combobox, provide context for its children. */ -export function ComboboxBase( - props: ComboboxBaseProps, -) { +export function ComboboxBase< + Option, + OptGroup = never, + T extends ValidComponent = "div", +>(props: PolymorphicProps>) { const defaultId = `combobox-${createUniqueId()}`; const filter = createFilter({ sensitivity: "base" }); @@ -278,7 +290,7 @@ export function ComboboxBase( triggerMode: "input", translations: COMBOBOX_INTL_TRANSLATIONS, }, - props, + props as ComboboxBaseProps, ); const [local, popperProps, formControlProps, others] = splitProps( @@ -352,7 +364,7 @@ export function ComboboxBase( const [showAllOptions, setShowAllOptions] = createSignal(false); const [lastDisplayedOptions, setLastDisplayedOptions] = createSignal( - local.options, + local.options!, ); const disclosureState = createDisclosureState({ @@ -424,7 +436,7 @@ export function ComboboxBase( return local.options as Option[]; } - return local.options.flatMap( + return local.options!.flatMap( (item) => ((item as any)[optionGroupChildren] as Option[]) ?? (item as Option), ); @@ -480,7 +492,7 @@ export function ComboboxBase( const displayedOptions = createMemo(() => { if (disclosureState.isOpen()) { if (showAllOptions()) { - return local.options; + return local.options!; } return filteredOptions(); } @@ -565,7 +577,7 @@ export function ComboboxBase( const showAllOptions = setShowAllOptions(triggerMode === "manual"); const hasOptions = showAllOptions - ? local.options.length > 0 + ? local.options!.length > 0 : filteredOptions().length > 0; // Don't open if there is no option. @@ -698,14 +710,14 @@ export function ComboboxBase( const prevShowAllOptions = prevInput[1]; setLastDisplayedOptions( - prevShowAllOptions ? local.options : prevFilteredOptions, + prevShowAllOptions ? local.options! : prevFilteredOptions, ); } else { const filteredOptions = input[0]; const showAllOptions = input[1]; setLastDisplayedOptions( - showAllOptions ? local.options : filteredOptions, + showAllOptions ? local.options! : filteredOptions, ); } }), @@ -870,10 +882,10 @@ export function ComboboxBase( contentRef={contentRef} {...popperProps} > - as="div" role="group" - id={access(formControlProps.id)} + id={access(formControlProps.id)!} {...formControlContext.dataset()} {...dataset()} {...others} diff --git a/packages/core/src/combobox/combobox-content.tsx b/packages/core/src/combobox/combobox-content.tsx index aede2921..aae9083d 100644 --- a/packages/core/src/combobox/combobox-content.tsx +++ b/packages/core/src/combobox/combobox-content.tsx @@ -1,13 +1,13 @@ -import { - OverrideComponentProps, - focusWithoutScrolling, - mergeRefs, -} from "@kobalte/utils"; -import { JSX, Show, splitProps } from "solid-js"; +import { focusWithoutScrolling, mergeRefs } from "@kobalte/utils"; +import { Component, JSX, Show, ValidComponent, splitProps } from "solid-js"; import createPreventScroll from "solid-prevent-scroll"; -import { DismissableLayer } from "../dismissable-layer"; -import { AsChildProp } from "../polymorphic"; +import { + DismissableLayer, + DismissableLayerCommonProps, + DismissableLayerRenderProps, +} from "../dismissable-layer"; +import { PolymorphicProps } from "../polymorphic"; import { PopperPositioner } from "../popper"; import { FocusOutsideEvent, @@ -16,9 +16,9 @@ import { createFocusScope, createHideOutside, } from "../primitives"; -import { useComboboxContext } from "./combobox-context"; +import { ComboboxDataSet, useComboboxContext } from "./combobox-context"; -export interface ComboboxContentOptions extends AsChildProp { +export interface ComboboxContentOptions { /** * Event handler called when focus moves to the trigger after closing. * It can be prevented by calling `event.preventDefault`. @@ -42,25 +42,34 @@ export interface ComboboxContentOptions extends AsChildProp { * It can be prevented by calling `event.preventDefault`. */ onInteractOutside?: (event: InteractOutsideEvent) => void; +} +export interface ComboboxContentCommonProps + extends DismissableLayerCommonProps { /** The HTML styles attribute (object form only). */ style?: JSX.CSSProperties; } -export interface ComboboxContentProps - extends OverrideComponentProps<"div", ComboboxContentOptions> {} +export interface ComboboxContentRenderProps + extends ComboboxContentCommonProps, + DismissableLayerRenderProps, + ComboboxDataSet {} + +export type ComboboxContentProps = ComboboxContentOptions & + Partial; /** * The component that pops out when the combobox is open. */ -export function ComboboxContent(props: ComboboxContentProps) { +export function ComboboxContent( + props: PolymorphicProps, +) { let ref: HTMLElement | undefined; const context = useComboboxContext(); - const [local, others] = splitProps(props, [ + const [local, others] = splitProps(props as ComboboxContentProps, [ "ref", - "id", "style", "onCloseAutoFocus", "onFocusOutside", @@ -129,7 +138,11 @@ export function ComboboxContent(props: ComboboxContentProps) { return ( - + > + > ref={mergeRefs((el) => { context.setContentRef(el); context.contentPresence.setRef(el); diff --git a/packages/core/src/combobox/combobox-context.tsx b/packages/core/src/combobox/combobox-context.tsx index 42849d37..53c5f0cb 100644 --- a/packages/core/src/combobox/combobox-context.tsx +++ b/packages/core/src/combobox/combobox-context.tsx @@ -28,10 +28,10 @@ export interface ComboboxContextValue { activeDescendant: Accessor; inputValue: Accessor; triggerMode: Accessor; - controlRef: Accessor; + controlRef: Accessor; inputRef: Accessor; - triggerRef: Accessor; - contentRef: Accessor; + triggerRef: Accessor; + contentRef: Accessor; listboxId: Accessor; triggerAriaLabel: Accessor; listboxAriaLabel: Accessor; @@ -40,11 +40,11 @@ export interface ComboboxContextValue { resetInputValue: (selectedKeys: Set) => void; setIsInputFocused: (isFocused: boolean) => void; setInputValue: (value: string) => void; - setControlRef: (el: HTMLDivElement) => void; + setControlRef: (el: HTMLElement) => void; setInputRef: (el: HTMLInputElement) => void; - setTriggerRef: (el: HTMLButtonElement) => void; - setContentRef: (el: HTMLDivElement) => void; - setListboxRef: (el: HTMLUListElement) => void; + setTriggerRef: (el: HTMLElement) => void; + setContentRef: (el: HTMLElement) => void; + setListboxRef: (el: HTMLElement) => void; open: ( focusStrategy: FocusStrategy | boolean, triggerMode?: ComboboxTriggerMode, diff --git a/packages/core/src/combobox/combobox-control.tsx b/packages/core/src/combobox/combobox-control.tsx index b74e2ebc..8cf6a6cf 100644 --- a/packages/core/src/combobox/combobox-control.tsx +++ b/packages/core/src/combobox/combobox-control.tsx @@ -1,45 +1,63 @@ import { OverrideComponentProps, isFunction, mergeRefs } from "@kobalte/utils"; -import { Accessor, JSX, children, splitProps } from "solid-js"; +import { Accessor, JSX, ValidComponent, children, splitProps } from "solid-js"; -import { useFormControlContext } from "../form-control"; -import { Polymorphic } from "../polymorphic"; -import { useComboboxContext } from "./combobox-context"; +import { FormControlDataSet, useFormControlContext } from "../form-control"; +import { Polymorphic, PolymorphicProps } from "../polymorphic"; +import { ComboboxDataSet, useComboboxContext } from "./combobox-context"; -export interface ComboboxControlState { +export interface ComboboxControlState