/** * `useCompositeItem` — thin wrapper around `useCompositeListItem` that also * reads the composite root context so items can contribute `tabIndex`, * `onFocus` and `onMouseMove` that keep `highlightedIndex` in sync. * * Mirrors `packages/react/src/internals/composite/item/useCompositeItem.ts` * in Base UI React. */ import type { Accessor } from "solid-js"; import type { PropsLike } from "../merge-props-n"; import { useCompositeRootContext } from "./composite-context"; import { type UseCompositeListItemParameters, useCompositeListItem, } from "./composite-list"; export interface UseCompositeItemParameters extends Pick< UseCompositeListItemParameters, "metadata" | "label" > {} export interface UseCompositeItemReturn { /** Props to pass to the item element through `renderElement`. */ props: PropsLike; /** Ref callback for the item element (composable via Solid 2 array ref form). */ ref: (el: HTMLElement | null) => void; /** Accessor for the item's live DOM-ordered index. */ index: Accessor; } export function useCompositeItem( params: UseCompositeItemParameters = {}, ): UseCompositeItemReturn { const root = useCompositeRootContext(); const { ref: listItemRef, index } = useCompositeListItem(params); let itemEl: HTMLElement | null = null; const ref = (el: HTMLElement | null): void => { itemEl = el; listItemRef(el); }; const isHighlighted = (): boolean => root.highlightedIndex() === index(); const props: PropsLike = Object.defineProperties( Object.create(null) as PropsLike, { tabIndex: { enumerable: true, configurable: true, get: () => (isHighlighted() ? 0 : -1), }, onFocus: { enumerable: true, configurable: true, value: (): void => { root.onHighlightedIndexChange(index()); }, }, onMouseMove: { enumerable: true, configurable: true, value: (): void => { if (!root.highlightItemOnHover()) return; const el = itemEl; if (!el) return; const disabled = el.hasAttribute("disabled") || el.getAttribute("aria-disabled") === "true"; if (!isHighlighted() && !disabled) el.focus(); }, }, }, ); return { props, ref, index }; }