Skip to content

Commit

Permalink
wip - amend me
Browse files Browse the repository at this point in the history
  • Loading branch information
jer3m01 committed Apr 7, 2024
1 parent ad95a8f commit a038f34
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 64 deletions.
58 changes: 35 additions & 23 deletions packages/core/src/combobox/combobox-base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
*/

import {
OverrideComponentProps,
ValidationState,
access,
createGenerateId,
Expand All @@ -21,6 +20,7 @@ import {
Accessor,
Component,
JSX,
ValidComponent,
createEffect,
createMemo,
createSignal,
Expand All @@ -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,
Expand Down Expand Up @@ -79,10 +80,9 @@ export interface ComboboxBaseSectionComponentProps<T> {

export interface ComboboxBaseOptions<Option, OptGroup = never>
extends Omit<
PopperRootOptions,
"anchorRef" | "contentRef" | "onCurrentPlacementChange"
>,
AsChildProp {
PopperRootOptions,
"anchorRef" | "contentRef" | "onCurrentPlacementChange"
> {
/** The localized strings of the component. */
translations?: ComboboxIntlTranslations;

Expand Down Expand Up @@ -243,21 +243,33 @@ export interface ComboboxBaseOptions<Option, OptGroup = never>

/** 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<Option, OptGroup = never>
extends OverrideComponentProps<"div", ComboboxBaseOptions<Option, OptGroup>>,
AsChildProp {}
export type ComboboxBaseProps<Option, OptGroup = never> = ComboboxBaseOptions<
Option,
OptGroup
> &
Partial<ComboboxBaseCommonProps>;

/**
* Base component for a combobox, provide context for its children.
*/
export function ComboboxBase<Option, OptGroup = never>(
props: ComboboxBaseProps<Option, OptGroup>,
) {
export function ComboboxBase<
Option,
OptGroup = never,
T extends ValidComponent = "div",
>(props: PolymorphicProps<T, ComboboxBaseProps<Option, OptGroup>>) {
const defaultId = `combobox-${createUniqueId()}`;

const filter = createFilter({ sensitivity: "base" });
Expand All @@ -278,7 +290,7 @@ export function ComboboxBase<Option, OptGroup = never>(
triggerMode: "input",
translations: COMBOBOX_INTL_TRANSLATIONS,
},
props,
props as ComboboxBaseProps<Option, OptGroup>,
);

const [local, popperProps, formControlProps, others] = splitProps(
Expand Down Expand Up @@ -352,7 +364,7 @@ export function ComboboxBase<Option, OptGroup = never>(
const [showAllOptions, setShowAllOptions] = createSignal(false);

const [lastDisplayedOptions, setLastDisplayedOptions] = createSignal(
local.options,
local.options!,
);

const disclosureState = createDisclosureState({
Expand Down Expand Up @@ -424,7 +436,7 @@ export function ComboboxBase<Option, OptGroup = never>(
return local.options as Option[];
}

return local.options.flatMap(
return local.options!.flatMap(
(item) =>
((item as any)[optionGroupChildren] as Option[]) ?? (item as Option),
);
Expand Down Expand Up @@ -480,7 +492,7 @@ export function ComboboxBase<Option, OptGroup = never>(
const displayedOptions = createMemo(() => {
if (disclosureState.isOpen()) {
if (showAllOptions()) {
return local.options;
return local.options!;
}
return filteredOptions();
}
Expand Down Expand Up @@ -565,7 +577,7 @@ export function ComboboxBase<Option, OptGroup = never>(
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.
Expand Down Expand Up @@ -698,14 +710,14 @@ export function ComboboxBase<Option, OptGroup = never>(
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,
);
}
}),
Expand Down Expand Up @@ -870,10 +882,10 @@ export function ComboboxBase<Option, OptGroup = never>(
contentRef={contentRef}
{...popperProps}
>
<Polymorphic
<Polymorphic<ComboboxBaseRenderProps>
as="div"
role="group"
id={access(formControlProps.id)}
id={access(formControlProps.id)!}
{...formControlContext.dataset()}
{...dataset()}
{...others}
Expand Down
45 changes: 29 additions & 16 deletions packages/core/src/combobox/combobox-content.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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`.
Expand All @@ -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<ComboboxContentCommonProps>;

/**
* The component that pops out when the combobox is open.
*/
export function ComboboxContent(props: ComboboxContentProps) {
export function ComboboxContent<T extends ValidComponent = "div">(
props: PolymorphicProps<T, ComboboxContentProps>,
) {
let ref: HTMLElement | undefined;

const context = useComboboxContext();

const [local, others] = splitProps(props, [
const [local, others] = splitProps(props as ComboboxContentProps, [
"ref",
"id",
"style",
"onCloseAutoFocus",
"onFocusOutside",
Expand Down Expand Up @@ -129,7 +138,11 @@ export function ComboboxContent(props: ComboboxContentProps) {
return (
<Show when={context.contentPresence.isPresent()}>
<PopperPositioner>
<DismissableLayer
<DismissableLayer<
Component<
Omit<ComboboxContentRenderProps, keyof DismissableLayerRenderProps>
>
>
ref={mergeRefs((el) => {
context.setContentRef(el);
context.contentPresence.setRef(el);
Expand Down
14 changes: 7 additions & 7 deletions packages/core/src/combobox/combobox-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ export interface ComboboxContextValue {
activeDescendant: Accessor<string | undefined>;
inputValue: Accessor<string | undefined>;
triggerMode: Accessor<ComboboxTriggerMode>;
controlRef: Accessor<HTMLDivElement | undefined>;
controlRef: Accessor<HTMLElement | undefined>;
inputRef: Accessor<HTMLInputElement | undefined>;
triggerRef: Accessor<HTMLButtonElement | undefined>;
contentRef: Accessor<HTMLDivElement | undefined>;
triggerRef: Accessor<HTMLElement | undefined>;
contentRef: Accessor<HTMLElement | undefined>;
listboxId: Accessor<string | undefined>;
triggerAriaLabel: Accessor<string | undefined>;
listboxAriaLabel: Accessor<string | undefined>;
Expand All @@ -40,11 +40,11 @@ export interface ComboboxContextValue {
resetInputValue: (selectedKeys: Set<string>) => 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,
Expand Down
56 changes: 38 additions & 18 deletions packages/core/src/combobox/combobox-control.tsx
Original file line number Diff line number Diff line change
@@ -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<T> {
export interface ComboboxControlState<Option> {
/** The selected options. */
selectedOptions: Accessor<T[]>;
selectedOptions: Accessor<Option[]>;

/** A function to remove an option from the selection. */
remove: (option: T) => void;
remove: (option: Option) => void;

/** A function to clear the selection. */
clear: () => void;
}

export interface ComboboxControlOptions<T> {
export interface ComboboxControlOptions<Option> {
/**
* The children of the combobox control.
* Can be a `JSX.Element` or a _render prop_ for having access to the internal state.
*/
children?: JSX.Element | ((state: ComboboxControlState<T>) => JSX.Element);
children?:
| JSX.Element
| ((state: ComboboxControlState<Option>) => JSX.Element);
}

export interface ComboboxControlProps<T>
extends OverrideComponentProps<"div", ComboboxControlOptions<T>> {}
export interface ComboboxControlCommonProps {
ref: HTMLElement | ((el: HTMLElement) => void);
}

export interface ComboboxControlRenderProps
extends ComboboxControlCommonProps,
FormControlDataSet,
ComboboxDataSet {
children: JSX.Element;
}

export type ComboboxControlProps<Option> = ComboboxControlOptions<Option> &
Partial<ComboboxControlCommonProps>;

/**
* Contains the combobox input and trigger.
*/
export function ComboboxControl<T>(props: ComboboxControlProps<T>) {
export function ComboboxControl<Option, T extends ValidComponent = "div">(
props: PolymorphicProps<T, ComboboxControlProps<Option>>,
) {
const formControlContext = useFormControlContext();
const context = useComboboxContext();

const [local, others] = splitProps(props, ["ref", "children"]);
const [local, others] = splitProps(props as ComboboxControlProps<Option>, [
"ref",
"children",
]);

const selectionManager = () => context.listState().selectionManager();

return (
<Polymorphic
<Polymorphic<ComboboxControlRenderProps>
as="div"
ref={mergeRefs(context.setControlRef, local.ref)}
{...context.dataset()}
Expand All @@ -58,12 +76,14 @@ export function ComboboxControl<T>(props: ComboboxControlProps<T>) {
);
}

interface ComboboxControlChildProps<T>
extends Pick<ComboboxControlOptions<T>, "children"> {
state: ComboboxControlState<T>;
interface ComboboxControlChildProps<Option>
extends Pick<ComboboxControlOptions<Option>, "children"> {
state: ComboboxControlState<Option>;
}

function ComboboxControlChild<T>(props: ComboboxControlChildProps<T>) {
function ComboboxControlChild<Option>(
props: ComboboxControlChildProps<Option>,
) {
const resolvedChildren = children(() => {
const body = props.children;
return isFunction(body) ? body(props.state) : body;
Expand Down

0 comments on commit a038f34

Please sign in to comment.