UNPKG

@base-ui/react

Version:

Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.

56 lines (52 loc) 1.99 kB
'use client'; import * as React from 'react'; import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect'; import { useBaseUiId } from "../useBaseUiId.js"; /** * @internal */ export function useAriaLabelledBy(explicitAriaLabelledBy, labelId, labelSourceRef, enableFallback = true, labelSourceId) { const [fallbackAriaLabelledBy, setFallbackAriaLabelledBy] = React.useState(); const generatedLabelId = useBaseUiId(labelSourceId ? `${labelSourceId}-label` : undefined); const ariaLabelledBy = explicitAriaLabelledBy ?? labelId ?? fallbackAriaLabelledBy; // Fallback for <span> controls labelled by wrapping/sibling native <label>. // Run after every commit so DOM association changes (e.g. label mount/unmount) // are reflected even when props/state deps are unchanged. // eslint-disable-next-line react-hooks/exhaustive-deps useIsoLayoutEffect(() => { const nextAriaLabelledBy = explicitAriaLabelledBy || labelId || !enableFallback ? undefined : getAriaLabelledBy(labelSourceRef.current, generatedLabelId); if (fallbackAriaLabelledBy !== nextAriaLabelledBy) { setFallbackAriaLabelledBy(nextAriaLabelledBy); } }); return ariaLabelledBy; } function getAriaLabelledBy(labelSource, generatedLabelId) { const label = findAssociatedLabel(labelSource); if (!label) { return undefined; } if (!label.id && generatedLabelId) { label.id = generatedLabelId; } return label.id || undefined; } function findAssociatedLabel(labelSource) { if (!labelSource) { return undefined; } // Fast path before the expensive `.labels` read. const parent = labelSource.parentElement; if (parent && parent.tagName === 'LABEL') { return parent; } const controlId = labelSource.id; if (controlId) { const nextSibling = labelSource.nextElementSibling; if (nextSibling && nextSibling.htmlFor === controlId) { return nextSibling; } } const labels = labelSource.labels; return labels && labels[0]; }