Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 3 additions & 23 deletions packages/@react-spectrum/s2/src/ComboBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import ChevronIcon from '../ui-icons/Chevron';
import {Collection} from 'react-aria/Collection';
import {ContextValue, Provider} from 'react-aria-components/slots';
import {control, controlBorderRadius, controlFont, controlSize, field, fieldInput, getAllowedOverrides, StyleProps} from './style-utils' with {type: 'macro'};
import {createContext, CSSProperties, ForwardedRef, forwardRef, ReactNode, Ref, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
import {createContext, CSSProperties, ForwardedRef, forwardRef, ReactNode, Ref, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
import {createFocusableRef} from './useDOMRef';
import {createLeafComponent} from 'react-aria/CollectionBuilder';
import {edgeToText} from '../style/spectrum-theme' with {type: 'macro'};
Expand All @@ -55,17 +55,16 @@ import {forwardRefType} from './types';
import {HeaderContext, HeadingContext, Text, TextContext} from './Content';
import {IconContext} from './Icon';
import {InputContext, InputProps} from 'react-aria-components/Input';
// @ts-ignore
import intlMessages from '../intl/*.json';
import {ListLayout} from 'react-stately/useVirtualizerState';
import {mergeRefs} from 'react-aria/mergeRefs';
// @ts-ignore
import {Node} from '@react-types/shared';
import {Popover} from './Popover';
import {pressScale} from './pressScale';
import {ProgressCircle} from './ProgressCircle';
import {TextFieldRef} from './TextField';
import {useLocalizedStringFormatter} from 'react-aria/useLocalizedStringFormatter';
import {useResizeObserver} from 'react-aria/private/utils/useResizeObserver';
import {useScale} from './utils';
import {useSlotId} from 'react-aria/private/utils/useId';
import {useSpectrumContextProps} from './useSpectrumContextProps';
Expand Down Expand Up @@ -500,23 +499,6 @@ const ComboboxInner = forwardRef(function ComboboxInner(props: ComboBoxProps<any
menuOffset = 8;
}

let triggerRef = useRef<HTMLDivElement>(null);
// Make menu width match input + button
let [triggerWidth, setTriggerWidth] = useState<string | null>(null);
let onResize = useCallback(() => {
if (triggerRef.current) {
let inputRect = triggerRef.current.getBoundingClientRect();
let minX = inputRect.left;
let maxX = inputRect.right;
setTriggerWidth((maxX - minX) + 'px');
}
}, [triggerRef, setTriggerWidth]);

useResizeObserver({
ref: triggerRef,
onResize: onResize
});

let state = useContext(ComboBoxStateContext);
let timeout = useRef<ReturnType<typeof setTimeout> | null>(null);
let [showLoading, setShowLoading] = useState(false);
Expand Down Expand Up @@ -614,7 +596,6 @@ const ComboboxInner = forwardRef(function ComboboxInner(props: ComboBoxProps<any
{label}
</FieldLabel>
<FieldGroup
ref={triggerRef}
role="presentation"
isDisabled={isDisabled}
isInvalid={isInvalid}
Expand Down Expand Up @@ -667,12 +648,11 @@ const ComboboxInner = forwardRef(function ComboboxInner(props: ComboBoxProps<any
</HelpText>
<Popover
hideArrow
triggerRef={triggerRef}
offset={menuOffset}
placement={`${direction} ${align}` as Placement}
shouldFlip={shouldFlip}
UNSAFE_style={{
'--trigger-width': (menuWidth ? menuWidth + 'px' : triggerWidth)
'--trigger-width': (menuWidth ? menuWidth + 'px' : undefined)
} as CSSProperties}
padding="none"
styles={style({
Expand Down
8 changes: 5 additions & 3 deletions packages/dev/s2-docs/pages/react-aria/ComboBox.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -445,11 +445,13 @@ import {ComboBox, ComboBoxItem} from 'vanilla-starter/ComboBox';

<Anatomy />

```tsx links={{ComboBox: '#combobox', ComboBoxValue: '#comboboxvalue', Button: 'Button', Popover: 'Popover', ListBox: 'ListBox'}}
```tsx links={{ComboBox: '#combobox', ComboBoxValue: '#comboboxvalue', Group: 'Group', Button: 'Button', Popover: 'Popover', ListBox: 'ListBox'}}
<ComboBox>
<Label />
<Input />
<Button />
<Group>
<Input />
<Button />
</Group>
<ComboBoxValue />
<Text slot="description" />
<FieldError />
Expand Down
15 changes: 12 additions & 3 deletions packages/react-aria-components/src/ComboBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ function ComboBoxInner<T extends object>({props, collection, comboBoxRef: ref}:

let buttonRef = useRef<HTMLButtonElement>(null);
let inputRef = useRef<HTMLInputElement>(null);
let groupRef = useRef<HTMLDivElement>(null);
let listBoxRef = useRef<HTMLDivElement>(null);
let popoverRef = useRef<HTMLDivElement>(null);
let [labelRef, label] = useSlot(
Expand All @@ -185,9 +186,10 @@ function ComboBoxInner<T extends object>({props, collection, comboBoxRef: ref}:
}, state);

// Make menu width match input + button
// Left for backward compatibility in case a <Group> is not rendered.
let [menuWidth, setMenuWidth] = useState<string | null>(null);
let onResize = useCallback(() => {
if (inputRef.current) {
if (inputRef.current && !groupRef.current) {
let buttonRect = buttonRef.current?.getBoundingClientRect();
let inputRect = inputRef.current.getBoundingClientRect();
let minX = buttonRect ? Math.min(buttonRect.left, inputRect.left) : inputRect.left;
Expand All @@ -201,6 +203,13 @@ function ComboBoxInner<T extends object>({props, collection, comboBoxRef: ref}:
onResize: onResize
});

// Position popover relative to group if available, otherwise input.
let triggerRef = useMemo(() => ({
get current() {
return groupRef.current || inputRef.current;
}
}), [groupRef, inputRef]);

// Only expose a subset of state to renderProps function to avoid infinite render loop
let renderPropsState = useMemo(() => ({
isOpen: state.isOpen,
Expand Down Expand Up @@ -241,7 +250,7 @@ function ComboBoxInner<T extends object>({props, collection, comboBoxRef: ref}:
[OverlayTriggerStateContext, state],
[PopoverContext, {
ref: popoverRef,
triggerRef: inputRef,
triggerRef,
scrollRef: listBoxRef,
placement: 'bottom start',
isNonModal: true,
Expand All @@ -257,7 +266,7 @@ function ComboBoxInner<T extends object>({props, collection, comboBoxRef: ref}:
errorMessage: errorMessageProps
}
}],
[GroupContext, {isInvalid: validation.isInvalid, isDisabled: props.isDisabled || false}],
[GroupContext, {ref: groupRef, isInvalid: validation.isInvalid, isDisabled: props.isDisabled || false}],
[FieldErrorContext, validation],
[ComboBoxValueContext, valueProps]
]}>
Expand Down
31 changes: 1 addition & 30 deletions packages/react-aria-components/src/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,9 @@ import {HiddenDateInput} from './HiddenDateInput';
import {LabelContext} from './Label';
import {mergeProps} from 'react-aria/mergeProps';
import {PopoverContext} from './Popover';
import React, {createContext, ForwardedRef, forwardRef, useCallback, useRef, useState} from 'react';
import React, {createContext, ForwardedRef, forwardRef, useRef} from 'react';
import {TextContext} from './Text';
import {useFocusRing} from 'react-aria/useFocusRing';
import {useResizeObserver} from 'react-aria/private/utils/useResizeObserver';

export interface DatePickerRenderProps {
/**
Expand Down Expand Up @@ -148,19 +147,6 @@ export const DatePicker = /*#__PURE__*/ (forwardRef as forwardRefType)(function
validationBehavior
}, state, groupRef);

// Allows calendar width to match input group
let [groupWidth, setGroupWidth] = useState<string | null>(null);
let onResize = useCallback(() => {
if (groupRef.current) {
setGroupWidth(groupRef.current.offsetWidth + 'px');
}
}, []);

useResizeObserver({
ref: groupRef,
onResize: onResize
});

let {focusProps, isFocused, isFocusVisible} = useFocusRing({within: true});
let renderProps = useRenderProps({
...props,
Expand Down Expand Up @@ -194,7 +180,6 @@ export const DatePicker = /*#__PURE__*/ (forwardRef as forwardRefType)(function
trigger: 'DatePicker',
triggerRef: groupRef,
placement: 'bottom start',
style: {'--trigger-width': groupWidth} as React.CSSProperties,
clearContexts: CLEAR_CONTEXTS
}],
[DialogContext, dialogProps],
Expand Down Expand Up @@ -260,19 +245,6 @@ export const DateRangePicker = /*#__PURE__*/ (forwardRef as forwardRefType)(func
validationBehavior
}, state, groupRef);

// Allows calendar width to match input group
let [groupWidth, setGroupWidth] = useState<string | null>(null);
let onResize = useCallback(() => {
if (groupRef.current) {
setGroupWidth(groupRef.current.offsetWidth + 'px');
}
}, []);

useResizeObserver({
ref: groupRef,
onResize: onResize
});

let {focusProps, isFocused, isFocusVisible} = useFocusRing({within: true});
let renderProps = useRenderProps({
...props,
Expand Down Expand Up @@ -305,7 +277,6 @@ export const DateRangePicker = /*#__PURE__*/ (forwardRef as forwardRefType)(func
trigger: 'DateRangePicker',
triggerRef: groupRef,
placement: 'bottom start',
style: {'--trigger-width': groupWidth} as React.CSSProperties,
clearContexts: CLEAR_CONTEXTS
}],
[DialogContext, dialogProps],
Expand Down
19 changes: 2 additions & 17 deletions packages/react-aria-components/src/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,11 @@ import {mergeProps} from 'react-aria/mergeProps';
import {OverlayTriggerProps, OverlayTriggerState} from 'react-stately/useOverlayTriggerState';
import {PopoverContext} from './Popover';
import {PressResponder} from 'react-aria/private/interactions/PressResponder';
import React, {createContext, ForwardedRef, forwardRef, JSX, ReactNode, useCallback, useContext, useRef, useState} from 'react';
import React, {createContext, ForwardedRef, forwardRef, JSX, ReactNode, useContext, useRef} from 'react';
import {RootMenuTriggerStateContext} from './Menu';
import {useId} from 'react-aria/useId';
import {useMenuTriggerState} from 'react-stately/useMenuTriggerState';
import {useOverlayTrigger} from 'react-aria/useOverlayTrigger';
import {useResizeObserver} from 'react-aria/private/utils/useResizeObserver';

export interface DialogTriggerProps extends OverlayTriggerProps {
children: ReactNode
Expand Down Expand Up @@ -59,19 +58,6 @@ export function DialogTrigger(props: DialogTriggerProps): JSX.Element {
let buttonRef = useRef<HTMLButtonElement>(null);
let {triggerProps, overlayProps} = useOverlayTrigger({type: 'dialog'}, state, buttonRef);

// Allows popover width to match trigger element
let [buttonWidth, setButtonWidth] = useState<string | null>(null);
let onResize = useCallback(() => {
if (buttonRef.current) {
setButtonWidth(buttonRef.current.offsetWidth + 'px');
}
}, [buttonRef]);

useResizeObserver({
ref: buttonRef,
onResize: onResize
});

// Label dialog by the trigger as a fallback if there is no title slot.
// This is done in RAC instead of hooks because otherwise we cannot distinguish
// between context and props. Normally aria-labelledby overrides the title
Expand All @@ -88,8 +74,7 @@ export function DialogTrigger(props: DialogTriggerProps): JSX.Element {
[PopoverContext, {
trigger: 'DialogTrigger',
triggerRef: buttonRef,
'aria-labelledby': overlayProps['aria-labelledby'],
style: {'--trigger-width': buttonWidth} as React.CSSProperties
'aria-labelledby': overlayProps['aria-labelledby']
}]
]}>
<PressResponder {...triggerProps} ref={buttonRef} isPressed={state.isOpen}>
Expand Down
21 changes: 1 addition & 20 deletions packages/react-aria-components/src/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {
useMenuTrigger,
useSubmenuTrigger
} from 'react-aria/useMenu';

import {BaseCollection, CollectionNode, ItemNode, SectionNode} from 'react-aria/private/collections/BaseCollection';
import {
MenuTriggerProps as BaseMenuTriggerProps,
Expand All @@ -45,9 +44,7 @@ import {
import {Collection} from 'react-aria/Collection';
import {CollectionBuilder, createBranchComponent, createLeafComponent} from 'react-aria/CollectionBuilder';
import {CollectionProps, CollectionRendererContext, ItemRenderProps, SectionContext, SectionProps, usePersistedKeys} from './Collection';

import {FieldInputContext, SelectableCollectionContext, SelectableCollectionContextValue} from './Autocomplete';

import {filterDOMProps} from 'react-aria/filterDOMProps';
import {FocusEvents, FocusStrategy, forwardRefType, GlobalDOMAttributes, HoverEvents, Key, LinkDOMProps, MultipleSelection, PressEvents} from '@react-types/shared';
import {FocusScope} from 'react-aria/FocusScope';
Expand All @@ -67,11 +64,9 @@ import React, {
ReactElement,
ReactNode,
RefObject,
useCallback,
useContext,
useMemo,
useRef,
useState
useRef
} from 'react';
import {SelectionIndicatorContext} from './SelectionIndicator';
import {SelectionManager} from 'react-stately/private/selection/SelectionManager';
Expand All @@ -82,7 +77,6 @@ import {TreeState, useTreeState} from 'react-stately/useTreeState';
import {useHover} from 'react-aria/useHover';
import {useMultipleSelectionState} from 'react-stately/useMultipleSelectionState';
import {useObjectRef} from 'react-aria/useObjectRef';
import {useResizeObserver} from 'react-aria/private/utils/useResizeObserver';

export const MenuContext = createContext<ContextValue<MenuProps<any>, HTMLDivElement>>(null);
export const MenuStateContext = createContext<TreeState<any> | null>(null);
Expand All @@ -100,18 +94,6 @@ export function MenuTrigger(props: MenuTriggerProps): JSX.Element {
...props,
type: 'menu'
}, state, ref);
// Allows menu width to match button
let [buttonWidth, setButtonWidth] = useState<string | null>(null);
let onResize = useCallback(() => {
if (ref.current) {
setButtonWidth(ref.current.offsetWidth + 'px');
}
}, [ref]);

useResizeObserver({
ref: ref,
onResize: onResize
});
let scrollRef = useRef(null);

return (
Expand All @@ -125,7 +107,6 @@ export function MenuTrigger(props: MenuTriggerProps): JSX.Element {
triggerRef: ref,
scrollRef,
placement: 'bottom start',
style: {'--trigger-width': buttonWidth} as React.CSSProperties,
'aria-labelledby': menuProps['aria-labelledby']
}]
]}>
Expand Down
19 changes: 17 additions & 2 deletions packages/react-aria-components/src/Popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ import {OverlayArrowContext} from './OverlayArrow';
import {OverlayTriggerProps, OverlayTriggerState, useOverlayTriggerState} from 'react-stately/useOverlayTriggerState';
import {OverlayTriggerStateContext} from './Dialog';
import {PlacementAxis, PositionProps} from 'react-aria/useOverlayPosition';
import React, {Context, createContext, ForwardedRef, forwardRef, useContext, useEffect, useMemo, useRef, useState} from 'react';
import React, {Context, createContext, ForwardedRef, forwardRef, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
import {useEnterAnimation, useExitAnimation} from 'react-aria/private/utils/animation';
import {useIsHidden} from 'react-aria/private/collections/Hidden';
import {useLayoutEffect} from 'react-aria/private/utils/useLayoutEffect';
import {useLocale} from 'react-aria/I18nProvider';
import {useResizeObserver} from 'react-aria/private/utils/useResizeObserver';

export interface PopoverProps extends Omit<PositionProps, 'isOpen'>, Omit<AriaPopoverProps, 'popoverRef' | 'triggerRef' | 'groupRef' | 'offset' | 'arrowSize'>, OverlayTriggerProps, RenderProps<PopoverRenderProps>, SlotProps, AriaLabelingProps, GlobalDOMAttributes<HTMLDivElement> {
/**
Expand Down Expand Up @@ -222,10 +223,24 @@ function PopoverInner({state, isExiting, UNSTABLE_portalContainer, clearContexts
return children;
}, [renderProps.children, clearContexts]);

let [triggerWidth, setTriggerWidth] = useState<string | null>(null);
let onResize = useCallback(() => {
if (props.triggerRef.current) {
setTriggerWidth(props.triggerRef.current.getBoundingClientRect().width + 'px');
}
}, [props.triggerRef]);

useLayoutEffect(onResize, [onResize]);
useResizeObserver({
ref: renderProps.style?.['--trigger-width'] ? undefined : props.triggerRef,
onResize: onResize
});

let style = {
...popoverProps.style,
'--trigger-anchor-point': triggerAnchorPoint ? `${triggerAnchorPoint.x}px ${triggerAnchorPoint.y}px` : undefined,
...renderProps.style
...renderProps.style,
'--trigger-width': renderProps.style?.['--trigger-width'] || triggerWidth
};

let overlay = (
Expand Down
Loading
Loading