diff --git a/packages/react-aria-components/src/Checkbox.tsx b/packages/react-aria-components/src/Checkbox.tsx index 74cced23747..5b3796f53c2 100644 --- a/packages/react-aria-components/src/Checkbox.tsx +++ b/packages/react-aria-components/src/Checkbox.tsx @@ -123,7 +123,9 @@ export const CheckboxGroup = /*#__PURE__*/ (forwardRef as forwardRefType)(functi ...props, validationBehavior }); - let [labelRef, label] = useSlot(); + let [labelRef, label] = useSlot( + !props['aria-label'] && !props['aria-labelledby'] + ); let {groupProps, labelProps, descriptionProps, errorMessageProps, ...validation} = useCheckboxGroup({ ...props, label, diff --git a/packages/react-aria-components/src/ColorField.tsx b/packages/react-aria-components/src/ColorField.tsx index ea1d27c3c6e..9d304e35715 100644 --- a/packages/react-aria-components/src/ColorField.tsx +++ b/packages/react-aria-components/src/ColorField.tsx @@ -86,7 +86,9 @@ function ColorChannelField(props: ColorChannelFieldProps) { }); let inputRef = useRef(null); - let [labelRef, label] = useSlot(); + let [labelRef, label] = useSlot( + !props['aria-label'] && !props['aria-labelledby'] + ); let { labelProps, inputProps, @@ -129,7 +131,9 @@ function HexColorField(props: HexColorFieldProps) { }); let inputRef = useRef(null); - let [labelRef, label] = useSlot(); + let [labelRef, label] = useSlot( + !props['aria-label'] && !props['aria-labelledby'] + ); let { labelProps, inputProps, diff --git a/packages/react-aria-components/src/ColorSlider.tsx b/packages/react-aria-components/src/ColorSlider.tsx index 0e9f8926b62..9cd774debf8 100644 --- a/packages/react-aria-components/src/ColorSlider.tsx +++ b/packages/react-aria-components/src/ColorSlider.tsx @@ -41,7 +41,9 @@ export const ColorSlider = forwardRef(function ColorSlider(props: ColorSliderPro let trackRef = React.useRef(null); let inputRef = React.useRef(null); - let [labelRef, label] = useSlot(); + let [labelRef, label] = useSlot( + !props['aria-label'] && !props['aria-labelledby'] + ); let { trackProps, thumbProps, diff --git a/packages/react-aria-components/src/ComboBox.tsx b/packages/react-aria-components/src/ComboBox.tsx index 39fe32d1dbd..2f846289e41 100644 --- a/packages/react-aria-components/src/ComboBox.tsx +++ b/packages/react-aria-components/src/ComboBox.tsx @@ -126,7 +126,9 @@ function ComboBoxInner({props, collection, comboBoxRef: ref}: let inputRef = useRef(null); let listBoxRef = useRef(null); let popoverRef = useRef(null); - let [labelRef, label] = useSlot(); + let [labelRef, label] = useSlot( + !props['aria-label'] && !props['aria-labelledby'] + ); let { buttonProps, inputProps, diff --git a/packages/react-aria-components/src/DateField.tsx b/packages/react-aria-components/src/DateField.tsx index 154aff7f9be..df81cb40008 100644 --- a/packages/react-aria-components/src/DateField.tsx +++ b/packages/react-aria-components/src/DateField.tsx @@ -64,7 +64,9 @@ export const DateField = /*#__PURE__*/ (forwardRef as forwardRefType)(function D }); let fieldRef = useRef(null); - let [labelRef, label] = useSlot(); + let [labelRef, label] = useSlot( + !props['aria-label'] && !props['aria-labelledby'] + ); let inputRef = useRef(null); let {labelProps, fieldProps, inputProps, descriptionProps, errorMessageProps, ...validation} = useDateField({ ...removeDataAttributes(props), @@ -127,7 +129,9 @@ export const TimeField = /*#__PURE__*/ (forwardRef as forwardRefType)(function T }); let fieldRef = useRef(null); - let [labelRef, label] = useSlot(); + let [labelRef, label] = useSlot( + !props['aria-label'] && !props['aria-labelledby'] + ); let inputRef = useRef(null); let {labelProps, fieldProps, inputProps, descriptionProps, errorMessageProps, ...validation} = useTimeField({ ...removeDataAttributes(props), diff --git a/packages/react-aria-components/src/DatePicker.tsx b/packages/react-aria-components/src/DatePicker.tsx index 544da15d4a8..ac7b8b6df70 100644 --- a/packages/react-aria-components/src/DatePicker.tsx +++ b/packages/react-aria-components/src/DatePicker.tsx @@ -85,7 +85,9 @@ export const DatePicker = /*#__PURE__*/ (forwardRef as forwardRefType)(function }); let groupRef = useRef(null); - let [labelRef, label] = useSlot(); + let [labelRef, label] = useSlot( + !props['aria-label'] && !props['aria-labelledby'] + ); let { groupProps, labelProps, @@ -186,7 +188,9 @@ export const DateRangePicker = /*#__PURE__*/ (forwardRef as forwardRefType)(func }); let groupRef = useRef(null); - let [labelRef, label] = useSlot(); + let [labelRef, label] = useSlot( + !props['aria-label'] && !props['aria-labelledby'] + ); let { groupProps, labelProps, diff --git a/packages/react-aria-components/src/Meter.tsx b/packages/react-aria-components/src/Meter.tsx index 08297219955..d74a1c226b2 100644 --- a/packages/react-aria-components/src/Meter.tsx +++ b/packages/react-aria-components/src/Meter.tsx @@ -45,7 +45,9 @@ export const Meter = /*#__PURE__*/ (forwardRef as forwardRefType)(function Meter } = props; value = clamp(value, minValue, maxValue); - let [labelRef, label] = useSlot(); + let [labelRef, label] = useSlot( + !props['aria-label'] && !props['aria-labelledby'] + ); let { meterProps, labelProps diff --git a/packages/react-aria-components/src/NumberField.tsx b/packages/react-aria-components/src/NumberField.tsx index bf106fcff25..c85aa252758 100644 --- a/packages/react-aria-components/src/NumberField.tsx +++ b/packages/react-aria-components/src/NumberField.tsx @@ -61,7 +61,9 @@ export const NumberField = /*#__PURE__*/ (forwardRef as forwardRefType)(function }); let inputRef = useRef(null); - let [labelRef, label] = useSlot(); + let [labelRef, label] = useSlot( + !props['aria-label'] && !props['aria-labelledby'] + ); let { labelProps, groupProps, diff --git a/packages/react-aria-components/src/ProgressBar.tsx b/packages/react-aria-components/src/ProgressBar.tsx index 664b20d5090..f18c67f7f22 100644 --- a/packages/react-aria-components/src/ProgressBar.tsx +++ b/packages/react-aria-components/src/ProgressBar.tsx @@ -51,7 +51,9 @@ export const ProgressBar = forwardRef(function ProgressBar(props: ProgressBarPro } = props; value = clamp(value, minValue, maxValue); - let [labelRef, label] = useSlot(); + let [labelRef, label] = useSlot( + !props['aria-label'] && !props['aria-labelledby'] + ); let { progressBarProps, labelProps diff --git a/packages/react-aria-components/src/RadioGroup.tsx b/packages/react-aria-components/src/RadioGroup.tsx index 3759a130dfd..c9e81c140c3 100644 --- a/packages/react-aria-components/src/RadioGroup.tsx +++ b/packages/react-aria-components/src/RadioGroup.tsx @@ -125,7 +125,9 @@ export const RadioGroup = /*#__PURE__*/ (forwardRef as forwardRefType)(function validationBehavior }); - let [labelRef, label] = useSlot(); + let [labelRef, label] = useSlot( + !props['aria-label'] && !props['aria-labelledby'] + ); let {radioGroupProps, labelProps, descriptionProps, errorMessageProps, ...validation} = useRadioGroup({ ...props, label, diff --git a/packages/react-aria-components/src/SearchField.tsx b/packages/react-aria-components/src/SearchField.tsx index 2953c6fd7d8..a01b62588e2 100644 --- a/packages/react-aria-components/src/SearchField.tsx +++ b/packages/react-aria-components/src/SearchField.tsx @@ -59,7 +59,9 @@ export const SearchField = /*#__PURE__*/ (forwardRef as forwardRefType)(function let validationBehavior = props.validationBehavior ?? formValidationBehavior ?? 'native'; let inputRef = useRef(null); let [inputContextProps, mergedInputRef] = useContextProps({}, inputRef, InputContext); - let [labelRef, label] = useSlot(); + let [labelRef, label] = useSlot( + !props['aria-label'] && !props['aria-labelledby'] + ); let state = useSearchFieldState({ ...props, validationBehavior diff --git a/packages/react-aria-components/src/Select.tsx b/packages/react-aria-components/src/Select.tsx index 29fc9066ddc..befbe1af75a 100644 --- a/packages/react-aria-components/src/Select.tsx +++ b/packages/react-aria-components/src/Select.tsx @@ -114,7 +114,9 @@ function SelectInner({props, selectRef: ref, collection}: Sele // Get props for child elements from useSelect let buttonRef = useRef(null); - let [labelRef, label] = useSlot(); + let [labelRef, label] = useSlot( + !props['aria-label'] && !props['aria-labelledby'] + ); let { labelProps, triggerProps, diff --git a/packages/react-aria-components/src/Slider.tsx b/packages/react-aria-components/src/Slider.tsx index 3e21d479bd1..ad7f1ebf77e 100644 --- a/packages/react-aria-components/src/Slider.tsx +++ b/packages/react-aria-components/src/Slider.tsx @@ -55,7 +55,9 @@ export const Slider = /*#__PURE__*/ (forwardRef as forwardRefType)(function Slid let trackRef = useRef(null); let numberFormatter = useNumberFormatter(props.formatOptions); let state = useSliderState({...props, numberFormatter}); - let [labelRef, label] = useSlot(); + let [labelRef, label] = useSlot( + !props['aria-label'] && !props['aria-labelledby'] + ); let { groupProps, trackProps, @@ -221,7 +223,9 @@ export const SliderThumb = /*#__PURE__*/ (forwardRef as forwardRefType)(function let {index = 0} = props; let defaultInputRef = useRef(null); let inputRef = userInputRef || defaultInputRef; - let [labelRef, label] = useSlot(); + let [labelRef, label] = useSlot( + !props['aria-label'] && !props['aria-labelledby'] + ); let {thumbProps, inputProps, labelProps, isDragging, isFocused, isDisabled} = useSliderThumb({ ...props, index, diff --git a/packages/react-aria-components/src/TagGroup.tsx b/packages/react-aria-components/src/TagGroup.tsx index 9de830a98a1..8f75ae13b94 100644 --- a/packages/react-aria-components/src/TagGroup.tsx +++ b/packages/react-aria-components/src/TagGroup.tsx @@ -75,7 +75,9 @@ interface TagGroupInnerProps { function TagGroupInner({props, forwardedRef: ref, collection}: TagGroupInnerProps) { let tagListRef = useRef(null); - let [labelRef, label] = useSlot(); + let [labelRef, label] = useSlot( + !props['aria-label'] && !props['aria-labelledby'] + ); let state = useListState({ ...props, children: undefined, diff --git a/packages/react-aria-components/src/TextField.tsx b/packages/react-aria-components/src/TextField.tsx index a965bee55b9..9f98bc8c0da 100644 --- a/packages/react-aria-components/src/TextField.tsx +++ b/packages/react-aria-components/src/TextField.tsx @@ -61,7 +61,9 @@ export const TextField = /*#__PURE__*/ (forwardRef as forwardRefType)(function T let validationBehavior = props.validationBehavior ?? formValidationBehavior ?? 'native'; let inputRef = useRef(null); let [inputContextProps, mergedInputRef] = useContextProps({}, inputRef, InputContext); - let [labelRef, label] = useSlot(); + let [labelRef, label] = useSlot( + !props['aria-label'] && !props['aria-labelledby'] + ); let [inputElementType, setInputElementType] = useState('input'); let {labelProps, inputProps, descriptionProps, errorMessageProps, ...validation} = useTextField({ ...removeDataAttributes(props), diff --git a/packages/react-aria-components/src/utils.tsx b/packages/react-aria-components/src/utils.tsx index 4072ab489a0..4f57ecfc00e 100644 --- a/packages/react-aria-components/src/utils.tsx +++ b/packages/react-aria-components/src/utils.tsx @@ -209,9 +209,10 @@ export function useContextProps(props return [mergedProps, mergedRef]; } -export function useSlot(): [RefCallback, boolean] { - // Assume we do have the slot in the initial render. - let [hasSlot, setHasSlot] = useState(true); +export function useSlot(initialState: boolean | (() => boolean) = true): [RefCallback, boolean] { + // Initial state is typically based on the parent having an aria-label or aria-labelledby. + // If it does, this value should be false so that we don't update the state and cause a rerender when we go through the layoutEffect + let [hasSlot, setHasSlot] = useState(initialState); let hasRun = useRef(false); // A callback ref which will run when the slotted element mounts.