diff --git a/packages/@react-aria/utils/src/useResizeObserver.ts b/packages/@react-aria/utils/src/useResizeObserver.ts index 075dc88f76e..bb328f26358 100644 --- a/packages/@react-aria/utils/src/useResizeObserver.ts +++ b/packages/@react-aria/utils/src/useResizeObserver.ts @@ -5,7 +5,7 @@ function hasResizeObserver() { } type useResizeObserverOptionsType = { - ref: RefObject, + ref: RefObject | undefined, onResize: () => void } diff --git a/packages/@react-spectrum/actionbar/src/ActionBar.tsx b/packages/@react-spectrum/actionbar/src/ActionBar.tsx index 317a6c0e958..2de9577cf54 100644 --- a/packages/@react-spectrum/actionbar/src/ActionBar.tsx +++ b/packages/@react-spectrum/actionbar/src/ActionBar.tsx @@ -37,16 +37,16 @@ function ActionBar(props: SpectrumActionBarProps, ref: DOMR in={isOpen} mountOnEnter unmountOnExit> - + ); } -interface ActionBarInnerProps extends SpectrumActionBarProps { +interface ActionBarInnerProps extends SpectrumActionBarProps { isOpen?: boolean } -const ActionBarInner = React.forwardRef((props: ActionBarInnerProps, ref: DOMRef) => { +function ActionBarInner(props: ActionBarInnerProps, ref: DOMRef) { props = useProviderProps(props); let { @@ -101,7 +101,7 @@ const ActionBarInner = React.forwardRef((props: ActionBarInnerProps, ref: DOMRef onClearSelection()} isQuiet - staticColor={isEmphasized ? 'white' : null}> + staticColor={isEmphasized ? 'white' : undefined}> @@ -125,7 +125,9 @@ const ActionBarInner = React.forwardRef((props: ActionBarInnerProps, ref: DOMRef ); -}); +} + +const ActionBarInnerWithRef = React.forwardRef(ActionBarInner) as (props: SpectrumActionBarProps & {ref?: DOMRef}) => ReturnType; /** * TODO: Add description of component here. diff --git a/packages/@react-spectrum/actiongroup/src/ActionGroup.tsx b/packages/@react-spectrum/actiongroup/src/ActionGroup.tsx index 089ddfb38eb..f5b74339e71 100644 --- a/packages/@react-spectrum/actiongroup/src/ActionGroup.tsx +++ b/packages/@react-spectrum/actiongroup/src/ActionGroup.tsx @@ -84,36 +84,42 @@ function ActionGroup(props: SpectrumActionGroupProps, ref: } let computeVisibleItems = (visibleItems: number) => { - let listItems = Array.from(domRef.current.children) as HTMLLIElement[]; - let containerSize = orientation === 'horizontal' ? wrapperRef.current.offsetWidth : wrapperRef.current.offsetHeight; - let isShowingMenu = visibleItems < state.collection.size; - let calculatedSize = 0; - let newVisibleItems = 0; - - if (isShowingMenu) { - calculatedSize += orientation === 'horizontal' - ? outerWidth(listItems.pop(), false, true) - : outerHeight(listItems.pop(), false, true); - } + if (domRef.current && wrapperRef.current) { + let listItems = Array.from(domRef.current.children) as HTMLLIElement[]; + let containerSize = orientation === 'horizontal' ? wrapperRef.current.offsetWidth : wrapperRef.current.offsetHeight; + let isShowingMenu = visibleItems < state.collection.size; + let calculatedSize = 0; + let newVisibleItems = 0; + + if (isShowingMenu) { + let item = listItems.pop(); + if (item) { + calculatedSize += orientation === 'horizontal' + ? outerWidth(item, false, true) + : outerHeight(item, false, true); + } + } - for (let [i, item] of listItems.entries()) { - calculatedSize += orientation === 'horizontal' - ? outerWidth(item, i === 0, i === listItems.length - 1) - : outerHeight(item, i === 0, i === listItems.length - 1); - if (calculatedSize <= containerSize) { - newVisibleItems++; - } else { - break; + for (let [i, item] of listItems.entries()) { + calculatedSize += orientation === 'horizontal' + ? outerWidth(item, i === 0, i === listItems.length - 1) + : outerHeight(item, i === 0, i === listItems.length - 1); + if (calculatedSize <= containerSize) { + newVisibleItems++; + } else { + break; + } } - } - // If selection is enabled, and not all of the items fit, collapse all of them into a dropdown - // immediately rather than having some visible and some not. - if (selectionMode !== 'none' && newVisibleItems < state.collection.size) { - return 0; - } + // If selection is enabled, and not all of the items fit, collapse all of them into a dropdown + // immediately rather than having some visible and some not. + if (selectionMode !== 'none' && newVisibleItems < state.collection.size) { + return 0; + } - return newVisibleItems; + return newVisibleItems; + } + return visibleItems; }; setVisibleItems(function *() { @@ -166,14 +172,14 @@ function ActionGroup(props: SpectrumActionGroupProps, ref: // in all scenarios because it may not shrink when available space is reduced. let parentRef = useMemo(() => ({ get current() { - return wrapperRef.current.parentElement; + return wrapperRef.current?.parentElement; } }), [wrapperRef]); - useResizeObserver({ref: overflowMode !== 'wrap' ? parentRef : null, onResize: updateOverflow}); + useResizeObserver({ref: overflowMode !== 'wrap' ? parentRef : undefined, onResize: updateOverflow}); useLayoutEffect(updateOverflow, [updateOverflow, state.collection]); let children = [...state.collection]; - let menuItem = null; + let menuItem: ReactElement | null = null; let menuProps = {}; // If there are no visible items, don't apply any props to the action group container @@ -257,16 +263,16 @@ export {_ActionGroup as ActionGroup}; interface ActionGroupItemProps extends DOMProps, StyleProps { item: Node, state: ListState, - isDisabled: boolean, - isEmphasized: boolean, + isDisabled?: boolean, + isEmphasized?: boolean, staticColor?: 'white' | 'black', hideButtonText?: boolean, orientation?: 'horizontal' | 'vertical', - onAction: (key: Key) => void + onAction?: (key: Key) => void } function ActionGroupItem({item, state, isDisabled, isEmphasized, staticColor, onAction, hideButtonText, orientation}: ActionGroupItemProps) { - let ref = useRef(); + let ref = useRef(null); let {buttonProps} = useActionGroupItem({key: item.key}, state); isDisabled = isDisabled || state.disabledKeys.has(item.key); let isSelected = state.selectionManager.isSelected(item.key); @@ -282,7 +288,7 @@ function ActionGroupItem({item, state, isDisabled, isEmphasized, staticColor, // If button text is hidden, we need to show it as a tooltip instead, so // go find the text element in the DOM after rendering. let textId = useId(); - let [textContent, setTextContent] = useState(''); + let [textContent, setTextContent] = useState(''); useLayoutEffect(() => { if (hideButtonText) { setTextContent(document.getElementById(textId)?.textContent); @@ -359,7 +365,7 @@ interface ActionGroupMenuProps extends AriaLabelingProps { summaryIcon?: ReactNode, isOnlyItem?: boolean, orientation?: 'horizontal' | 'vertical', - onAction: (key: Key) => void + onAction?: (key: Key) => void } function ActionGroupMenu({state, isDisabled, isEmphasized, staticColor, items, onAction, summaryIcon, hideButtonText, isOnlyItem, orientation, ...otherProps}: ActionGroupMenuProps) { @@ -376,7 +382,7 @@ function ActionGroupMenu({state, isDisabled, isEmphasized, staticColor, items let {hoverProps, isHovered} = useHover({isDisabled}); // If no aria-label or aria-labelledby is given, provide a default one. - let ariaLabel = otherProps['aria-label'] || (otherProps['aria-labelledby'] ? null : '…'); + let ariaLabel = otherProps['aria-label'] || (otherProps['aria-labelledby'] ? undefined : '…'); let ariaLabelledby = otherProps['aria-labelledby']; let textId = useId(); let id = useId(); @@ -392,14 +398,14 @@ function ActionGroupMenu({state, isDisabled, isEmphasized, staticColor, items let isSelected = state.selectionManager.selectionMode !== 'none' && !state.selectionManager.isEmpty; // If single selection and empty selection is not allowed, swap the contents of the button to the selected item (like a Picker). - if (!summaryIcon && state.selectionManager.selectionMode === 'single' && state.selectionManager.disallowEmptySelection) { + if (!summaryIcon && state.selectionManager.selectionMode === 'single' && state.selectionManager.disallowEmptySelection && state.selectionManager.firstSelectedKey != null) { let selectedItem = state.collection.getItem(state.selectionManager.firstSelectedKey); if (selectedItem) { summaryIcon = selectedItem.rendered; if (typeof summaryIcon === 'string') { summaryIcon = {summaryIcon}; } - iconOnly = hideButtonText; + iconOnly = !!hideButtonText; ariaLabelledby = `${ariaLabelledby ?? id} ${textId}`; } } @@ -453,7 +459,7 @@ function ActionGroupMenu({state, isDisabled, isEmphasized, staticColor, items } isDisabled={isDisabled} staticColor={staticColor}> - {summaryIcon || } + {summaryIcon ? : null} diff --git a/tsconfig.json b/tsconfig.json index 8af79e7adff..878e043aa42 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -33,6 +33,7 @@ { "name": "typescript-strict-plugin", "paths": [ + "./packages/@react-spectrum/action", "./packages/@react-spectrum/button", "./packages/@react-spectrum/checkbox", "./packages/@react-spectrum/divider", @@ -44,6 +45,7 @@ "./packages/@react-spectrum/text", "./packages/@react-spectrum/view", "./packages/@react-spectrum/well", + "./packages/@react-types/action", "./packages/@react-types/button", "./packages/@react-types/checkbox", "./packages/@react-types/divider",