import type { JSX, ValidComponent } from "@solidjs/web";
import { dynamic } from "@solidjs/web";
import { createMemo, omit } from "solid-js";
import {
createChangeEventDetails,
REASONS,
} from "../../internals/change-event-details";
import { composeRefs, userRef } from "../../internals/compose-refs";
import { useCompositeListItem } from "../../internals/composite/composite-list";
import { stopEvent } from "../../internals/floating-ui/utils/event";
import { mergeRootProps } from "../../internals/merge-root-props";
import { resolveClassStyle } from "../../internals/resolve-class-style";
import type { BaseUIComponentProps } from "../../internals/types";
import { useComboboxChipsContext } from "../chips/chips-context";
import {
type ComboboxRootChangeEventDetails,
useComboboxRootContext,
} from "../root/combobox-root-context";
import { ComboboxChipContext } from "./chip-context";
export interface ComboboxChipState {
disabled: boolean;
}
export interface ComboboxChipProps
extends BaseUIComponentProps<"div", ComboboxChipState> {
as?: ValidComponent;
}
/**
* An individual chip that represents a value in a multiselectable input.
* Renders a `
` element.
*/
export function ComboboxChip(props: ComboboxChipProps): JSX.Element {
const root = useComboboxRootContext();
const chipsCtxRaw = useComboboxChipsContext();
if (chipsCtxRaw == null) {
throw new Error(
"Base UI: must be rendered inside .",
);
}
const chipsCtx = chipsCtxRaw;
const disabled = (): boolean => root.disabled();
const readOnly = (): boolean => root.readOnly();
const listItem = useCompositeListItem();
const state = createMemo(() => ({
disabled: disabled(),
}));
function handleKeyDown(event: KeyboardEvent): number | undefined {
const index = listItem.index();
const chipsCount = chipsCtx.chipsRef.current.length;
const selected = root.selectedValue() as unknown;
const selectedArr = Array.isArray(selected) ? (selected as unknown[]) : [];
let nextIndex: number | undefined = index;
if (event.key === "ArrowLeft") {
event.preventDefault();
nextIndex = index > 0 ? index - 1 : undefined;
} else if (event.key === "ArrowRight") {
event.preventDefault();
nextIndex = index < chipsCount - 1 ? index + 1 : undefined;
} else if (event.key === "Backspace" || event.key === "Delete") {
const computedNext =
index >= selectedArr.length - 1 ? selectedArr.length - 2 : index;
nextIndex = computedNext >= 0 ? computedNext : undefined;
stopEvent(event);
root.setIndices({
activeIndex: null,
selectedIndex: null,
type: "keyboard",
});
const details = createChangeEventDetails(
REASONS.none,
event,
) as ComboboxRootChangeEventDetails;
root.setSelectedValue(
selectedArr.filter((_, i) => i !== index) as never,
details,
);
} else if (event.key === "Enter" || event.key === " ") {
stopEvent(event);
nextIndex = undefined;
} else if (event.key === "ArrowDown" || event.key === "ArrowUp") {
stopEvent(event);
const details = createChangeEventDetails(
REASONS.listNavigation,
event,
) as ComboboxRootChangeEventDetails;
root.setOpen(true, details);
nextIndex = undefined;
} else if (
event.key.length === 1 &&
!event.ctrlKey &&
!event.metaKey &&
!event.altKey
) {
nextIndex = undefined;
}
return nextIndex;
}
function onKeyDown(event: KeyboardEvent): void {
if (disabled() || readOnly()) return;
const next = handleKeyDown(event);
chipsCtx.setHighlightedChipIndex(next);
if (next === undefined) {
root.inputRef.current?.focus();
} else {
chipsCtx.chipsRef.current[next]?.focus();
}
}
const internal = {
tabIndex: -1,
get "aria-disabled"() {
return disabled() ? ("true" as const) : undefined;
},
get "aria-readonly"() {
return readOnly() ? ("true" as const) : undefined;
},
onKeyDown,
// Default state-attr projection: data-disabled present when true.
get "data-disabled"() {
return state().disabled ? "" : undefined;
},
get children() {
return props.children;
},
};
const userClassStyle = resolveClassStyle(props, () => state());
const userProps = omit(props, "as", "class", "style", "attrs", "children");
let merged: Record = mergeRootProps(
internal,
userClassStyle,
);
merged = mergeRootProps(merged, userProps);
const Tag = dynamic(() => props.as ?? "div");
return (
);
}