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
40 changes: 31 additions & 9 deletions packages/@react-spectrum/combobox/src/ComboBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@

import {AriaButtonProps} from '@react-types/button';
import ChevronDownMedium from '@spectrum-icons/ui/ChevronDownMedium';
import {classNames, unwrapDOMRef, useFocusableRef, useIsMobileDevice} from '@react-spectrum/utils';
import {
classNames,
useFocusableRef,
useIsMobileDevice,
useResizeObserver,
useUnwrapDOMRef
} from '@react-spectrum/utils';
import {DismissButton, useOverlayPosition} from '@react-aria/overlays';
import {DOMRefValue, FocusableRef, FocusableRefValue} from '@react-types/shared';
import {Field} from '@react-spectrum/label';
Expand All @@ -23,7 +29,14 @@ import {MobileComboBox} from './MobileComboBox';
import {Placement} from '@react-types/overlays';
import {Popover} from '@react-spectrum/overlays';
import {PressResponder, useHover} from '@react-aria/interactions';
import React, {InputHTMLAttributes, ReactElement, RefObject, useRef, useState} from 'react';
import React, {
InputHTMLAttributes,
ReactElement,
RefObject,
useCallback,
useRef,
useState
} from 'react';
import {SpectrumComboBoxProps} from '@react-types/combobox';
import styles from '@adobe/spectrum-css-temp/components/inputgroup/vars.css';
import {TextFieldBase} from '@react-spectrum/textfield';
Expand Down Expand Up @@ -52,7 +65,9 @@ const ComboBoxBase = React.forwardRef(function ComboBoxBase<T extends object>(pr
} = props;

let popoverRef = useRef<DOMRefValue<HTMLDivElement>>();
let unwrappedPopoverRef = useUnwrapDOMRef(popoverRef);
let buttonRef = useRef<FocusableRefValue<HTMLElement>>();
let unwrappedButtonRef = useUnwrapDOMRef(buttonRef);
let listBoxRef = useRef();
let inputRef = useRef<HTMLInputElement | HTMLTextAreaElement>();
let domRef = useFocusableRef(ref, inputRef);
Expand All @@ -65,8 +80,8 @@ const ComboBoxBase = React.forwardRef(function ComboBoxBase<T extends object>(pr
{
...props,
keyboardDelegate: layout,
buttonRef: unwrapDOMRef(buttonRef),
popoverRef: unwrapDOMRef(popoverRef),
buttonRef: unwrappedButtonRef,
popoverRef: unwrappedPopoverRef,
listBoxRef,
inputRef: inputRef,
menuTrigger
Expand All @@ -75,8 +90,8 @@ const ComboBoxBase = React.forwardRef(function ComboBoxBase<T extends object>(pr
);

let {overlayProps, placement} = useOverlayPosition({
targetRef: unwrapDOMRef(buttonRef),
overlayRef: unwrapDOMRef(popoverRef),
targetRef: unwrappedButtonRef,
overlayRef: unwrappedPopoverRef,
scrollRef: listBoxRef,
placement: `${direction} end` as Placement,
shouldFlip: shouldFlip,
Expand All @@ -88,11 +103,18 @@ const ComboBoxBase = React.forwardRef(function ComboBoxBase<T extends object>(pr
let [menuWidth, setMenuWidth] = useState(null);
let {scale} = useProvider();

useLayoutEffect(() => {
let buttonWidth = buttonRef.current.UNSAFE_getDOMNode().offsetWidth;
let onResize = useCallback(() => {
let buttonWidth = unwrappedButtonRef.current.offsetWidth;
let inputWidth = inputRef.current.offsetWidth;
setMenuWidth(buttonWidth + inputWidth);
}, [scale, buttonRef, inputRef]);
}, [unwrappedButtonRef, inputRef, setMenuWidth]);

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

useLayoutEffect(onResize, [scale, onResize]);

let style = {
...overlayProps.style,
Expand Down
28 changes: 26 additions & 2 deletions packages/@react-spectrum/combobox/stories/ComboBox.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@
*/

import {action} from '@storybook/addon-actions';
import {ActionButton, Button} from '@react-spectrum/button';
import Add from '@spectrum-icons/workflow/Add';
import Alert from '@spectrum-icons/workflow/Alert';
import Bell from '@spectrum-icons/workflow/Bell';
import {Button} from '@react-spectrum/button';
import {ButtonGroup} from '@react-spectrum/buttongroup';
import {ComboBox, Item, Section} from '../';
import Copy from '@spectrum-icons/workflow/Copy';
import Draw from '@spectrum-icons/workflow/Draw';
import {Flex} from '@react-spectrum/layout';
import React from 'react';
import React, {useState} from 'react';
import {storiesOf} from '@storybook/react';
import {Text} from '@react-spectrum/text';
import {TextField} from '@react-spectrum/textfield';
Expand Down Expand Up @@ -343,6 +343,10 @@ storiesOf('ComboBox', module)
</Flex>
)
)
.add(
'resize',
() => <ResizeCombobox />
)
.add(
'in small div',
() => (
Expand Down Expand Up @@ -659,6 +663,26 @@ function AllControlledOpenComboBox(props) {
);
}

function ResizeCombobox() {
let [size, setSize] = useState(true);

return (
<Flex direction="column" gap="size-200" alignItems="start">
<div style={{width: size ? '200px' : '300px'}}>
<ComboBox label="Combobox" {...actions} width="100%">
<Item key="one">Item One</Item>
<Item key="two" textValue="Item Two">
<Copy size="S" />
<Text>Item Two</Text>
</Item>
<Item key="three">Item Three</Item>
</ComboBox>
</div>
<ActionButton onPress={() => setSize(prev => !prev)}>Toggle size</ActionButton>
</Flex>
);
}

function render(props = {}) {
return (
<ComboBox label="Combobox" onOpenChange={action('onOpenChange')} onInputChange={action('onInputChange')} onSelectionChange={action('onSelectionChange')} onBlur={action('onBlur')} onFocus={action('onFocus')} {...props}>
Expand Down
42 changes: 23 additions & 19 deletions packages/@react-spectrum/picker/src/Picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@

import AlertMedium from '@spectrum-icons/ui/AlertMedium';
import ChevronDownMedium from '@spectrum-icons/ui/ChevronDownMedium';
import {classNames, dimensionValue, SlotProvider, unwrapDOMRef, useDOMRef, useIsMobileDevice, useStyleProps} from '@react-spectrum/utils';
import {
classNames,
dimensionValue,
SlotProvider,
useDOMRef,
useIsMobileDevice,
useStyleProps,
useUnwrapDOMRef
} from '@react-spectrum/utils';
import {DismissButton, useOverlayPosition} from '@react-aria/overlays';
import {DOMRef, DOMRefValue, FocusableRefValue, LabelPosition} from '@react-types/shared';
import {FieldButton} from '@react-spectrum/button';
Expand All @@ -28,7 +36,7 @@ import {Placement} from '@react-types/overlays';
import {Popover, Tray} from '@react-spectrum/overlays';
import {PressResponder, useHover} from '@react-aria/interactions';
import {ProgressCircle} from '@react-spectrum/progress';
import React, {ReactElement, useCallback, useMemo, useRef, useState} from 'react';
import React, {ReactElement, useCallback, useRef, useState} from 'react';
import {SpectrumPickerProps} from '@react-types/select';
import styles from '@adobe/spectrum-css-temp/components/dropdown/vars.css';
import {Text} from '@react-spectrum/text';
Expand Down Expand Up @@ -63,7 +71,9 @@ function Picker<T extends object>(props: SpectrumPickerProps<T>, ref: DOMRef<HTM
let domRef = useDOMRef(ref);

let popoverRef = useRef<DOMRefValue<HTMLDivElement>>();
let unwrappedPopoverRef = useUnwrapDOMRef(popoverRef);
let triggerRef = useRef<FocusableRefValue<HTMLElement>>();
let unwrappedTriggerRef = useUnwrapDOMRef(triggerRef);
let listboxRef = useRef();

// We create the listbox layout in Picker and pass it to ListBoxBase below
Expand All @@ -73,12 +83,12 @@ function Picker<T extends object>(props: SpectrumPickerProps<T>, ref: DOMRef<HTM
let {labelProps, triggerProps, valueProps, menuProps} = useSelect({
...props,
keyboardDelegate: layout
}, state, unwrapDOMRef(triggerRef));
}, state, unwrappedTriggerRef);

let isMobile = useIsMobileDevice();
let {overlayProps, placement, updatePosition} = useOverlayPosition({
targetRef: unwrapDOMRef(triggerRef),
overlayRef: unwrapDOMRef(popoverRef),
targetRef: unwrappedTriggerRef,
overlayRef: unwrappedPopoverRef,
scrollRef: listboxRef,
placement: `${direction} ${align}` as Placement,
shouldFlip: shouldFlip,
Expand Down Expand Up @@ -127,27 +137,21 @@ function Picker<T extends object>(props: SpectrumPickerProps<T>, ref: DOMRef<HTM
// Measure the width of the button to inform the width of the menu (below).
let [buttonWidth, setButtonWidth] = useState(null);
let {scale} = useProvider();
useLayoutEffect(() => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does resize observer always get called on the initial render? I think at least when not supported it wouldn't based on my reading of the code.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O good point, forgot about the unsupported case. Otherwise yes, it seems to be called on the initial render https://drafts.csswg.org/resize-observer/#ref-for-element%E2%91%A3

ResizeObserver’s notifications can be used to respond to changes in Element's size. Some interesting facts about these observations:

Observation will fire when watched Element is inserted/removed from DOM.

Observation will fire when watched Element display gets set to none.

Observations do not fire for non-replaced inline Elements.

Observations will not be triggered by CSS transforms.

Observation will fire when observation starts if Element is being rendered, and Element’s size is not 0,0.

Copy link
Copy Markdown
Member Author

@snowystinger snowystinger Dec 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be better to implement that in useResizeObserver's fallback case. Trigger a "resize" event on mount? Ah, but then scale changes...


let onResize = useCallback(() => {
if (!isMobile) {
let width = triggerRef.current.UNSAFE_getDOMNode().offsetWidth;
let width = unwrappedTriggerRef.current.offsetWidth;
setButtonWidth(width);
}
}, [scale, isMobile, triggerRef, state.selectedKey]);

// Make sure we only unwrap if the trigger changes otherwise we'll retrigger
// the observer too often
let resizeRef = useMemo(() => unwrapDOMRef(triggerRef), [triggerRef]);

let onResize = useCallback(() => {
let width = resizeRef.current.offsetWidth;
setButtonWidth(width);
}, [resizeRef, setButtonWidth]);
}, [unwrappedTriggerRef, setButtonWidth, isMobile]);

useResizeObserver({
ref: resizeRef,
ref: unwrappedTriggerRef,
onResize: onResize
});

useLayoutEffect(onResize, [scale, state.selectedKey, onResize]);

let overlay;
if (isMobile) {
overlay = (
Expand Down Expand Up @@ -202,7 +206,7 @@ function Picker<T extends object>(props: SpectrumPickerProps<T>, ref: DOMRef<HTM
<HiddenSelect
isDisabled={isDisabled}
state={state}
triggerRef={unwrapDOMRef(triggerRef)}
triggerRef={unwrappedTriggerRef}
label={label}
name={name} />
<PressResponder {...mergeProps(hoverProps, triggerProps)}>
Expand Down