"use client"; import * as React from "react"; import { Slot } from "@radix-ui/react-slot"; import { useComposedRefs } from "@djangocfg/ui-core/lib"; import { composeEventHandlers } from "@djangocfg/ui-core/lib"; import { cn } from "@djangocfg/ui-core/lib"; import { useListboxContext, useListboxState } from "../context/ListboxProvider"; import { ListboxGroupContext } from "./ListboxGroup"; import type { ListboxItemProps, ListboxItemIndicatorProps } from "../types"; const ITEM_NAME = "ListboxItem"; const ITEM_INDICATOR_NAME = "ListboxItemIndicator"; const ListboxItemContext = React.createContext<{ isSelected: boolean } | null>(null); ListboxItemContext.displayName = ITEM_NAME; function useListboxItemContext(name: string) { const context = React.useContext(ListboxItemContext); if (!context) { throw new Error(`\`${name}\` must be used within \`${ITEM_NAME}\``); } return context; } const useIsomorphicLayoutEffect = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect; const ListboxItem = React.forwardRef( (props, forwardedRef) => { const { asChild, value, disabled = false, onSelect, className, ...itemProps } = props; const context = useListboxContext(ITEM_NAME); const groupContext = React.useContext(ListboxGroupContext); const itemRef = React.useRef(null); const composedRef = useComposedRefs(itemRef, forwardedRef); const isSelected = useListboxState((state) => state.selectedValues.has(value)); const isHighlighted = useListboxState((state) => state.highlightedValue === value); const isDisabled = disabled || context.disabled; useIsomorphicLayoutEffect(() => { if (value === "") { throw new Error(`${ITEM_NAME} value cannot be an empty string`); } return context.onItemRegister( { ref: itemRef, value, disabled: isDisabled, onSelect }, groupContext?.id, ); }, [value, isDisabled, context.onItemRegister, groupContext?.id, onSelect]); const onBlur = React.useCallback(() => { context.onItemBlur(); }, [context.onItemBlur]); const onClick = React.useCallback( (event: React.MouseEvent) => { if (!isDisabled) { const isMultipleSelectionKey = context.multiple && (context.multiple === true || event.ctrlKey || event.metaKey); context.onItemSelect(value, isMultipleSelectionKey); } }, [context.onItemSelect, value, isDisabled, context.multiple], ); const onFocus = React.useCallback(() => { if (!isDisabled) { context.onItemFocus(value); context.onItemHighlight(value); } }, [context.onItemFocus, context.onItemHighlight, isDisabled, value]); const onKeyDown = React.useCallback( (event: React.KeyboardEvent) => { if (event.key === "Tab") { if (!event.shiftKey) { context.onItemBlur(); } return; } }, [context.onItemBlur], ); const onPointerLeave = React.useCallback(() => { context.onItemHighlight(null); }, [context.onItemHighlight]); const onPointerMove = React.useCallback(() => { if (!isDisabled) { context.onItemHighlight(value); } }, [context.onItemHighlight, isDisabled, value]); const ItemPrimitive = asChild ? Slot : "div"; const itemContextValue = React.useMemo(() => ({ isSelected }), [isSelected]); return ( ); }, ); ListboxItem.displayName = ITEM_NAME; const ListboxItemIndicator = React.forwardRef( (props, forwardedRef) => { const { forceMount = false, asChild, className, ...indicatorProps } = props; const itemContext = useListboxItemContext(ITEM_INDICATOR_NAME); if (!forceMount && !itemContext.isSelected) return null; const IndicatorPrimitive = asChild ? Slot : "span"; return (