@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
JavaScript
'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];
}