import { onCleanup, untrack } from "solid-js"; import { useBaseUiId } from "./base-ui-id"; /** * Generates a base-ui id for a label component and registers it with the * parent root via `setLabelId`. Clears the registration on cleanup so the * parent's `aria-labelledby` empties when the label unmounts. Mirrors Base UI * React's `useRegisteredLabelId` hook (`packages/react/src/utils/useRegisteredLabelId.ts`). * * Solid 2 notes: * * - The optional `idOverride` is expected to be read from `props.id` by the * caller. Callers should wrap the read in `untrack` themselves, or pass an * accessor if they want the id to follow prop changes. In practice, id * overrides are stable per-mount (React's hook keys the effect on the * resolved id, but the generated id is stable anyway), so we read the * override once during setup. * - `setLabelId` is called synchronously during setup so the id is available * on the very first render pass (the parent's `aria-labelledby` binding * picks it up via its own reactive read). The parent's label-id signal is * expected to opt into `ownedWrite` so this setup-time write is allowed in * owned scope. * - Cleanup calls `setLabelId(undefined)` so a later-mounted label doesn't * see a stale id, and so the DOM attribute clears cleanly on unmount. */ export function useRegisteredLabelId( idOverride: string | undefined, setLabelId: (id: string | undefined) => void, ): string { // `useBaseUiId` is stable for the component's lifetime; generate once and // hold on to it. `untrack` is belt-and-braces: the override is typically a // plain string (prop read at call site), but if the caller hands us a // reactive value we don't want to track it here. const id = untrack(() => useBaseUiId(idOverride)); setLabelId(id); onCleanup(() => { setLabelId(undefined); }); return id; }