From 4729230943d8757dfb3715844f5f0baa0d01d784 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Thu, 30 May 2024 23:46:00 +0800 Subject: [PATCH 01/34] fix(aria-utils): handle click on listbox --- .../ariaShouldCloseOnInteractOutside.ts | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts b/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts index 0be005b516..155179215e 100644 --- a/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts +++ b/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts @@ -16,26 +16,30 @@ export const ariaShouldCloseOnInteractOutside = ( state: any, shouldFocus?: MutableRefObject, ) => { - let trigger = ref?.current; - - // check if the click is on the underlay - const clickOnUnderlay = element?.children?.[0]?.getAttribute("role") === "dialog" ?? false; + const trigger = ref?.current; + const clickOnDialog = element?.children?.[0]?.getAttribute("role") === "dialog"; + const clickOnOnlistbox = ["listbox", "option"].includes( + element?.parentElement?.getAttribute("role") ?? "", + ); // if interacting outside the component if (!trigger || !trigger.contains(element)) { // blur the component (e.g. autocomplete) if (shouldFocus) shouldFocus.current = false; - // if the click is not on the underlay, + // if the click is not on the dialog nor on the listbox, // trigger the state close to prevent from opening multiple popovers at the same time // e.g. open dropdown1 -> click dropdown2 (dropdown1 should be closed and dropdown2 should be open) - if (!clickOnUnderlay) state.close(); + if (!clickOnDialog && !clickOnOnlistbox) { + // TODO: close the inner state only (e.g. popover -> autocomplete (close this one)) + state.close(); + } } else { // otherwise the component (e.g. autocomplete) should keep focused if (shouldFocus) shouldFocus.current = true; } - // if the click is on the underlay, - // clicking the overlay should close the popover instead of closing the modal + // if the click is on the dialog or option, + // it should close the popover instead of closing the modal / listbox // otherwise, allow interaction with other elements - return clickOnUnderlay; + return clickOnDialog || clickOnOnlistbox; }; From 56e343bdd82ad1f1984bb43af3a0239beb242e4b Mon Sep 17 00:00:00 2001 From: WK Wong Date: Thu, 6 Jun 2024 00:14:05 +0800 Subject: [PATCH 02/34] fix(popover): move useDialog to popover-content --- .../popover/src/popover-content.tsx | 14 ++++--- .../components/popover/src/use-popover.ts | 39 +++++++++---------- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/packages/components/popover/src/popover-content.tsx b/packages/components/popover/src/popover-content.tsx index 0c22513c8e..47142ffcd9 100644 --- a/packages/components/popover/src/popover-content.tsx +++ b/packages/components/popover/src/popover-content.tsx @@ -1,7 +1,7 @@ import type {AriaDialogProps} from "@react-aria/dialog"; import type {HTMLMotionProps} from "framer-motion"; -import {DOMAttributes, ReactNode, useMemo, useCallback, ReactElement} from "react"; +import {DOMAttributes, ReactNode, useMemo, useCallback, ReactElement, useRef} from "react"; import {forwardRef} from "@nextui-org/system"; import {DismissButton} from "@react-aria/overlays"; import {TRANSITION_VARIANTS} from "@nextui-org/framer-utils"; @@ -9,6 +9,7 @@ import {m, domAnimation, LazyMotion} from "framer-motion"; import {HTMLNextUIProps} from "@nextui-org/system"; import {RemoveScroll} from "react-remove-scroll"; import {getTransformOrigins} from "@nextui-org/aria-utils"; +import {useDialog} from "@react-aria/dialog"; import {usePopoverContext} from "./popover-context"; @@ -27,7 +28,6 @@ const PopoverContent = forwardRef<"div", PopoverContentProps>((props, _) => { placement, backdrop, motionProps, - titleProps, disableAnimation, shouldBlockScroll, getPopoverProps, @@ -38,10 +38,14 @@ const PopoverContent = forwardRef<"div", PopoverContentProps>((props, _) => { onClose, } = usePopoverContext(); - const dialogProps = getDialogProps(otherProps); + const dialogRef = useRef(null); + let {dialogProps: ariaDialogProps, titleProps} = useDialog({}, dialogRef); - // Not needed in the popover context, the popover role comes from getPopoverProps - delete dialogProps.role; + const dialogProps = getDialogProps({ + ref: dialogRef, + ...ariaDialogProps, + ...otherProps, + }); const Component = as || OverlayComponent || "div"; diff --git a/packages/components/popover/src/use-popover.ts b/packages/components/popover/src/use-popover.ts index b1ea300088..34445d272a 100644 --- a/packages/components/popover/src/use-popover.ts +++ b/packages/components/popover/src/use-popover.ts @@ -19,7 +19,7 @@ import {popover} from "@nextui-org/theme"; import {mergeProps, mergeRefs} from "@react-aria/utils"; import {clsx, dataAttr, objectToDeps} from "@nextui-org/shared-utils"; import {useMemo, useCallback, useRef} from "react"; -import {AriaDialogProps, useDialog} from "@react-aria/dialog"; +import {AriaDialogProps} from "@react-aria/dialog"; import {useReactAriaPopover, ReactAriaPopoverProps} from "./use-aria-popover"; @@ -131,7 +131,6 @@ export function usePopover(originalProps: UsePopoverProps) { const domTriggerRef = useRef(null); const wasTriggerPressedRef = useRef(false); - const dialogRef = useRef(null); const triggerRef = triggerRefProp || domTriggerRef; const disableAnimation = @@ -179,8 +178,6 @@ export function usePopover(originalProps: UsePopoverProps) { const {isFocusVisible, isFocused, focusProps} = useFocusRing(); - const {dialogProps, titleProps} = useDialog({}, dialogRef); - const slots = useMemo( () => popover({ @@ -197,21 +194,24 @@ export function usePopover(originalProps: UsePopoverProps) { style: mergeProps(popoverProps.style, otherProps.style, props.style), }); - const getDialogProps: PropGetter = (props = {}) => ({ - ref: dialogRef, - "data-slot": "base", - "data-open": dataAttr(state.isOpen), - "data-focus": dataAttr(isFocused), - "data-arrow": dataAttr(showArrow), - "data-focus-visible": dataAttr(isFocusVisible), - "data-placement": getArrowPlacement(ariaPlacement, placementProp), - ...mergeProps(focusProps, dialogProps, dialogPropsProp, props), - className: slots.base({class: clsx(baseStyles)}), - style: { - // this prevent the dialog to have a default outline - outline: "none", - }, - }); + const getDialogProps: PropGetter = (props = {}) => { + return { + // `ref` and `dialogProps` from `useDialog` are passed from props + // see `popover-content.tsx` for more + "data-slot": "base", + "data-open": dataAttr(state.isOpen), + "data-focus": dataAttr(isFocused), + "data-arrow": dataAttr(showArrow), + "data-focus-visible": dataAttr(isFocusVisible), + "data-placement": getArrowPlacement(ariaPlacement, placementProp), + ...mergeProps(focusProps, dialogPropsProp, props), + className: slots.base({class: clsx(baseStyles)}), + style: { + // this prevent the dialog to have a default outline + outline: "none", + }, + }; + }; const getContentProps = useCallback( (props = {}) => ({ @@ -316,7 +316,6 @@ export function usePopover(originalProps: UsePopoverProps) { triggerRef, placement, isNonModal, - titleProps, popoverRef: domRef, portalContainer, isOpen: state.isOpen, From bd494d231185d527267ddc59865b62e159406c96 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Thu, 6 Jun 2024 14:01:10 +0800 Subject: [PATCH 03/34] fix(popover): move useDialog to free-solo-popover --- packages/components/popover/src/free-solo-popover.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/components/popover/src/free-solo-popover.tsx b/packages/components/popover/src/free-solo-popover.tsx index edab8f19ec..036a0ce348 100644 --- a/packages/components/popover/src/free-solo-popover.tsx +++ b/packages/components/popover/src/free-solo-popover.tsx @@ -14,6 +14,7 @@ import {domAnimation, HTMLMotionProps, LazyMotion, m} from "framer-motion"; import {mergeProps} from "@react-aria/utils"; import {getTransformOrigins} from "@nextui-org/aria-utils"; import {TRANSITION_VARIANTS} from "@nextui-org/framer-utils"; +import {useDialog} from "@react-aria/dialog"; import {usePopover, UsePopoverProps, UsePopoverReturn} from "./use-popover"; @@ -92,7 +93,6 @@ const FreeSoloPopover = forwardRef<"div", FreeSoloPopoverProps>( state, placement, backdrop, - titleProps, portalContainer, disableAnimation, motionProps, @@ -106,6 +106,13 @@ const FreeSoloPopover = forwardRef<"div", FreeSoloPopoverProps>( ref, }); + const dialogRef = React.useRef(null); + const {dialogProps: ariaDialogProps, titleProps} = useDialog({}, dialogRef); + const dialogProps = getDialogProps({ + ref: dialogRef, + ...ariaDialogProps, + }); + const backdropContent = React.useMemo(() => { if (backdrop === "transparent") { return null; @@ -138,7 +145,7 @@ const FreeSoloPopover = forwardRef<"div", FreeSoloPopoverProps>( placement={placement} tabIndex={-1} transformOrigin={transformOrigin} - {...getDialogProps()} + {...dialogProps} > {!isNonModal && }
From 936f0bc1b8e846179849fe95d5538fa511a31185 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Thu, 6 Jun 2024 14:01:22 +0800 Subject: [PATCH 04/34] refactor(popover): use const instead --- packages/components/popover/src/popover-content.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/components/popover/src/popover-content.tsx b/packages/components/popover/src/popover-content.tsx index 47142ffcd9..05566fcc30 100644 --- a/packages/components/popover/src/popover-content.tsx +++ b/packages/components/popover/src/popover-content.tsx @@ -39,8 +39,7 @@ const PopoverContent = forwardRef<"div", PopoverContentProps>((props, _) => { } = usePopoverContext(); const dialogRef = useRef(null); - let {dialogProps: ariaDialogProps, titleProps} = useDialog({}, dialogRef); - + const {dialogProps: ariaDialogProps, titleProps} = useDialog({}, dialogRef); const dialogProps = getDialogProps({ ref: dialogRef, ...ariaDialogProps, From 264a6a60df5947e36086f11f05663812a6eef503 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Thu, 6 Jun 2024 14:02:07 +0800 Subject: [PATCH 05/34] feat(changset): add changeset --- .changeset/clever-gifts-joke.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/clever-gifts-joke.md diff --git a/.changeset/clever-gifts-joke.md b/.changeset/clever-gifts-joke.md new file mode 100644 index 0000000000..20effb69ba --- /dev/null +++ b/.changeset/clever-gifts-joke.md @@ -0,0 +1,5 @@ +--- +"@nextui-org/popover": patch +--- + +Fix popover focus issue (#3171, #2992) From 57b7aa88ed09dbb85194a6a1a64825e3a6dce13b Mon Sep 17 00:00:00 2001 From: WK Wong Date: Thu, 6 Jun 2024 14:20:26 +0800 Subject: [PATCH 06/34] feat(popover): popover focus test --- .../popover/__tests__/popover.test.tsx | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/packages/components/popover/__tests__/popover.test.tsx b/packages/components/popover/__tests__/popover.test.tsx index 2ab4e75da9..9b6c449bdc 100644 --- a/packages/components/popover/__tests__/popover.test.tsx +++ b/packages/components/popover/__tests__/popover.test.tsx @@ -213,4 +213,63 @@ describe("Popover", () => { // assert that the second popover is open expect(popover2).toHaveAttribute("aria-expanded", "true"); }); + + it("should focus on dialog when opened", async () => { + const wrapper = render( + + + + + +

This is the content of the popover.

+
+
, + ); + + const trigger = wrapper.getByTestId("trigger-test"); + + // open popover + await act(async () => { + await userEvent.click(trigger); + }); + + const {getByRole} = wrapper; + + let dialog = getByRole("dialog"); + + // assert that the focus is on the dialog + expect(dialog).toHaveFocus(); + }); + + it("should restore focus on trigger when closed", async () => { + const wrapper = render( + + + + + +

This is the content of the popover.

+
+
, + ); + + const trigger = wrapper.getByTestId("trigger-test"); + + // open popover + await act(async () => { + await userEvent.click(trigger); + }); + + // close popover + await act(async () => { + await userEvent.click(trigger); + }); + + // assert that the focus is restored back to trigger + expect(trigger).toHaveFocus(); + }); }); From 1667bfaa28d80bd17a269db6c96bfd1b0be4a0ab Mon Sep 17 00:00:00 2001 From: WK Wong Date: Thu, 6 Jun 2024 16:08:30 +0800 Subject: [PATCH 07/34] refactor(popover): getDialogProps --- .../components/popover/src/use-popover.ts | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/components/popover/src/use-popover.ts b/packages/components/popover/src/use-popover.ts index 34445d272a..66321c6968 100644 --- a/packages/components/popover/src/use-popover.ts +++ b/packages/components/popover/src/use-popover.ts @@ -194,24 +194,22 @@ export function usePopover(originalProps: UsePopoverProps) { style: mergeProps(popoverProps.style, otherProps.style, props.style), }); - const getDialogProps: PropGetter = (props = {}) => { - return { - // `ref` and `dialogProps` from `useDialog` are passed from props - // see `popover-content.tsx` for more - "data-slot": "base", - "data-open": dataAttr(state.isOpen), - "data-focus": dataAttr(isFocused), - "data-arrow": dataAttr(showArrow), - "data-focus-visible": dataAttr(isFocusVisible), - "data-placement": getArrowPlacement(ariaPlacement, placementProp), - ...mergeProps(focusProps, dialogPropsProp, props), - className: slots.base({class: clsx(baseStyles)}), - style: { - // this prevent the dialog to have a default outline - outline: "none", - }, - }; - }; + const getDialogProps: PropGetter = (props = {}) => ({ + // `ref` and `dialogProps` from `useDialog` are passed from props + // if we use `useDialog` here, dialogRef won't be focused on mount + "data-slot": "base", + "data-open": dataAttr(state.isOpen), + "data-focus": dataAttr(isFocused), + "data-arrow": dataAttr(showArrow), + "data-focus-visible": dataAttr(isFocusVisible), + "data-placement": getArrowPlacement(ariaPlacement, placementProp), + ...mergeProps(focusProps, dialogPropsProp, props), + className: slots.base({class: clsx(baseStyles)}), + style: { + // this prevent the dialog to have a default outline + outline: "none", + }, + }); const getContentProps = useCallback( (props = {}) => ({ From 082da69a206c5363f1bfde707b872451705831ae Mon Sep 17 00:00:00 2001 From: WK Wong Date: Mon, 10 Jun 2024 00:29:51 +0800 Subject: [PATCH 08/34] chore(utilities): remove ariaShouldCloseOnInteractOutside --- packages/components/autocomplete/package.json | 1 - packages/components/date-picker/package.json | 1 - .../date-picker/src/use-date-picker.ts | 3 +- .../date-picker/src/use-date-range-picker.ts | 3 +- packages/components/dropdown/package.json | 1 - .../components/dropdown/src/use-dropdown.ts | 3 +- packages/components/select/package.json | 1 - packages/components/select/src/use-select.ts | 3 +- .../ariaShouldCloseOnInteractOutside.ts | 45 ------------------- .../aria-utils/src/overlays/index.ts | 1 - 10 files changed, 4 insertions(+), 58 deletions(-) delete mode 100644 packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts diff --git a/packages/components/autocomplete/package.json b/packages/components/autocomplete/package.json index ca9404a604..dc839a3ecd 100644 --- a/packages/components/autocomplete/package.json +++ b/packages/components/autocomplete/package.json @@ -41,7 +41,6 @@ "@nextui-org/system": ">=2.0.0" }, "dependencies": { - "@nextui-org/aria-utils": "workspace:*", "@nextui-org/listbox": "workspace:*", "@nextui-org/popover": "workspace:*", "@nextui-org/react-utils": "workspace:*", diff --git a/packages/components/date-picker/package.json b/packages/components/date-picker/package.json index 8cf1bfc22e..a669e5a169 100644 --- a/packages/components/date-picker/package.json +++ b/packages/components/date-picker/package.json @@ -47,7 +47,6 @@ "@nextui-org/button": "workspace:*", "@nextui-org/date-input": "workspace:*", "@nextui-org/shared-icons": "workspace:*", - "@nextui-org/aria-utils": "workspace:*", "@react-stately/overlays": "3.6.5", "@react-stately/utils": "3.9.1", "@internationalized/date": "^3.5.2", diff --git a/packages/components/date-picker/src/use-date-picker.ts b/packages/components/date-picker/src/use-date-picker.ts index 8d4957bdf2..e330b46315 100644 --- a/packages/components/date-picker/src/use-date-picker.ts +++ b/packages/components/date-picker/src/use-date-picker.ts @@ -15,7 +15,6 @@ import {useDatePickerState} from "@react-stately/datepicker"; import {AriaDatePickerProps, useDatePicker as useAriaDatePicker} from "@react-aria/datepicker"; import {clsx, dataAttr, objectToDeps} from "@nextui-org/shared-utils"; import {mergeProps} from "@react-aria/utils"; -import {ariaShouldCloseOnInteractOutside} from "@nextui-org/aria-utils"; import {useDatePickerBase} from "./use-date-picker-base"; @@ -182,7 +181,7 @@ export function useDatePicker({ }, shouldCloseOnInteractOutside: popoverProps?.shouldCloseOnInteractOutside ? popoverProps.shouldCloseOnInteractOutside - : (element: Element) => ariaShouldCloseOnInteractOutside(element, domRef, state), + : () => true, }; }; diff --git a/packages/components/date-picker/src/use-date-range-picker.ts b/packages/components/date-picker/src/use-date-range-picker.ts index 42df170d40..a74992ce7b 100644 --- a/packages/components/date-picker/src/use-date-range-picker.ts +++ b/packages/components/date-picker/src/use-date-range-picker.ts @@ -21,7 +21,6 @@ import {useDateRangePicker as useAriaDateRangePicker} from "@react-aria/datepick import {clsx, dataAttr, objectToDeps} from "@nextui-org/shared-utils"; import {mergeProps} from "@react-aria/utils"; import {dateRangePicker, dateInput} from "@nextui-org/theme"; -import {ariaShouldCloseOnInteractOutside} from "@nextui-org/aria-utils"; import {useDatePickerBase} from "./use-date-picker-base"; interface Props @@ -218,7 +217,7 @@ export function useDateRangePicker({ }, shouldCloseOnInteractOutside: popoverProps?.shouldCloseOnInteractOutside ? popoverProps.shouldCloseOnInteractOutside - : (element: Element) => ariaShouldCloseOnInteractOutside(element, domRef, state), + : () => true, } as PopoverProps; }; diff --git a/packages/components/dropdown/package.json b/packages/components/dropdown/package.json index 1dbfbe1d1f..58c08d8c08 100644 --- a/packages/components/dropdown/package.json +++ b/packages/components/dropdown/package.json @@ -45,7 +45,6 @@ "@nextui-org/popover": "workspace:*", "@nextui-org/shared-utils": "workspace:*", "@nextui-org/react-utils": "workspace:*", - "@nextui-org/aria-utils": "workspace:*", "@react-aria/menu": "3.13.1", "@react-aria/utils": "3.24.1", "@react-stately/menu": "3.6.1", diff --git a/packages/components/dropdown/src/use-dropdown.ts b/packages/components/dropdown/src/use-dropdown.ts index 0124669b95..cbde20aa32 100644 --- a/packages/components/dropdown/src/use-dropdown.ts +++ b/packages/components/dropdown/src/use-dropdown.ts @@ -8,7 +8,6 @@ import {useMenuTrigger} from "@react-aria/menu"; import {dropdown} from "@nextui-org/theme"; import {clsx} from "@nextui-org/shared-utils"; import {ReactRef, mergeRefs} from "@nextui-org/react-utils"; -import {ariaShouldCloseOnInteractOutside} from "@nextui-org/aria-utils"; import {useMemo, useRef} from "react"; import {mergeProps} from "@react-aria/utils"; import {MenuProps} from "@nextui-org/menu"; @@ -124,7 +123,7 @@ export function useDropdown(props: UseDropdownProps) { }, shouldCloseOnInteractOutside: popoverProps?.shouldCloseOnInteractOutside ? popoverProps.shouldCloseOnInteractOutside - : (element: Element) => ariaShouldCloseOnInteractOutside(element, triggerRef, state), + : () => true, }; }; diff --git a/packages/components/select/package.json b/packages/components/select/package.json index 8ee374227d..2514e8ce6f 100644 --- a/packages/components/select/package.json +++ b/packages/components/select/package.json @@ -41,7 +41,6 @@ "@nextui-org/system": ">=2.0.0" }, "dependencies": { - "@nextui-org/aria-utils": "workspace:*", "@nextui-org/listbox": "workspace:*", "@nextui-org/popover": "workspace:*", "@nextui-org/spinner": "workspace:*", diff --git a/packages/components/select/src/use-select.ts b/packages/components/select/src/use-select.ts index 35600017ba..06882e1de5 100644 --- a/packages/components/select/src/use-select.ts +++ b/packages/components/select/src/use-select.ts @@ -27,7 +27,6 @@ import { } from "@nextui-org/use-aria-multiselect"; import {SpinnerProps} from "@nextui-org/spinner"; import {useSafeLayoutEffect} from "@nextui-org/use-safe-layout-effect"; -import {ariaShouldCloseOnInteractOutside} from "@nextui-org/aria-utils"; import {CollectionChildren} from "@react-types/shared"; export type SelectedItemProps = { @@ -523,7 +522,7 @@ export function useSelect(originalProps: UseSelectProps) { : slotsProps.popoverProps?.offset, shouldCloseOnInteractOutside: popoverProps?.shouldCloseOnInteractOutside ? popoverProps.shouldCloseOnInteractOutside - : (element: Element) => ariaShouldCloseOnInteractOutside(element, triggerRef, state), + : () => true, } as PopoverProps; }, [ diff --git a/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts b/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts deleted file mode 100644 index 155179215e..0000000000 --- a/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts +++ /dev/null @@ -1,45 +0,0 @@ -import {MutableRefObject, RefObject} from "react"; - -/** - * Used to handle the outside interaction for popover-based components - * e.g. dropdown, datepicker, date-range-picker, popover, select, autocomplete etc - * @param element - the element outside of the popover ref, originally from `shouldCloseOnInteractOutside` - * @param ref - The popover ref object that will interact outside with - * @param state - The popover state from the target component - * @param shouldFocus - a mutable ref boolean object to control the focus state - * (used in input-based component such as autocomplete) - * @returns - a boolean value which is same as shouldCloseOnInteractOutside - */ -export const ariaShouldCloseOnInteractOutside = ( - element: Element, - ref: RefObject, - state: any, - shouldFocus?: MutableRefObject, -) => { - const trigger = ref?.current; - const clickOnDialog = element?.children?.[0]?.getAttribute("role") === "dialog"; - const clickOnOnlistbox = ["listbox", "option"].includes( - element?.parentElement?.getAttribute("role") ?? "", - ); - - // if interacting outside the component - if (!trigger || !trigger.contains(element)) { - // blur the component (e.g. autocomplete) - if (shouldFocus) shouldFocus.current = false; - // if the click is not on the dialog nor on the listbox, - // trigger the state close to prevent from opening multiple popovers at the same time - // e.g. open dropdown1 -> click dropdown2 (dropdown1 should be closed and dropdown2 should be open) - if (!clickOnDialog && !clickOnOnlistbox) { - // TODO: close the inner state only (e.g. popover -> autocomplete (close this one)) - state.close(); - } - } else { - // otherwise the component (e.g. autocomplete) should keep focused - if (shouldFocus) shouldFocus.current = true; - } - - // if the click is on the dialog or option, - // it should close the popover instead of closing the modal / listbox - // otherwise, allow interaction with other elements - return clickOnDialog || clickOnOnlistbox; -}; diff --git a/packages/utilities/aria-utils/src/overlays/index.ts b/packages/utilities/aria-utils/src/overlays/index.ts index 6999e8b90c..ccf839f2d9 100644 --- a/packages/utilities/aria-utils/src/overlays/index.ts +++ b/packages/utilities/aria-utils/src/overlays/index.ts @@ -9,4 +9,3 @@ export { } from "./utils"; export {ariaHideOutside} from "./ariaHideOutside"; -export {ariaShouldCloseOnInteractOutside} from "./ariaShouldCloseOnInteractOutside"; From db05b15a14ba574fde108edb11e602084555a02b Mon Sep 17 00:00:00 2001 From: WK Wong Date: Mon, 10 Jun 2024 00:30:08 +0800 Subject: [PATCH 09/34] chore(deps): pnpm-lock.yaml --- pnpm-lock.yaml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 76d4099581..dbce6471e6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -686,9 +686,6 @@ importers: packages/components/autocomplete: dependencies: - '@nextui-org/aria-utils': - specifier: workspace:* - version: link:../../utilities/aria-utils '@nextui-org/button': specifier: workspace:* version: link:../button @@ -1327,9 +1324,6 @@ importers: '@internationalized/date': specifier: ^3.5.2 version: 3.5.2 - '@nextui-org/aria-utils': - specifier: workspace:* - version: link:../../utilities/aria-utils '@nextui-org/button': specifier: workspace:* version: link:../button @@ -1428,9 +1422,6 @@ importers: packages/components/dropdown: dependencies: - '@nextui-org/aria-utils': - specifier: workspace:* - version: link:../../utilities/aria-utils '@nextui-org/menu': specifier: workspace:* version: link:../menu @@ -2229,9 +2220,6 @@ importers: packages/components/select: dependencies: - '@nextui-org/aria-utils': - specifier: workspace:* - version: link:../../utilities/aria-utils '@nextui-org/listbox': specifier: workspace:* version: link:../listbox From 9d44401d66984bd3073f1218c232d45cc7aade78 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Mon, 10 Jun 2024 00:31:02 +0800 Subject: [PATCH 10/34] fix(popover): remove disableFocusManagement --- packages/components/popover/src/popover.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/components/popover/src/popover.tsx b/packages/components/popover/src/popover.tsx index c44888f487..3465d884b3 100644 --- a/packages/components/popover/src/popover.tsx +++ b/packages/components/popover/src/popover.tsx @@ -20,11 +20,7 @@ const Popover = forwardRef<"div", PopoverProps>((props, ref) => { const [trigger, content] = Children.toArray(children); - const overlay = ( - - {content} - - ); + const overlay = {content}; return ( From 60e8711fbfa857b525c998f2e75edd97dbdb7cd2 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Mon, 10 Jun 2024 00:31:10 +0800 Subject: [PATCH 11/34] fix(modal): remove disableFocusManagement --- packages/components/modal/src/modal.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/components/modal/src/modal.tsx b/packages/components/modal/src/modal.tsx index 1aa2ceb64b..323aa53089 100644 --- a/packages/components/modal/src/modal.tsx +++ b/packages/components/modal/src/modal.tsx @@ -17,11 +17,7 @@ const Modal = forwardRef<"div", ModalProps>((props, ref) => { const {children, ...otherProps} = props; const context = useModal({...otherProps, ref}); - const overlay = ( - - {children} - - ); + const overlay = {children}; return ( From b2c2b823757b89aa45eb220e1b218083aadef7ea Mon Sep 17 00:00:00 2001 From: WK Wong Date: Mon, 10 Jun 2024 00:32:02 +0800 Subject: [PATCH 12/34] fix(autocomplete): remove custom focus logic and remove ariaShouldCloseOnInteractOutside --- .../autocomplete/src/use-autocomplete.ts | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/packages/components/autocomplete/src/use-autocomplete.ts b/packages/components/autocomplete/src/use-autocomplete.ts index 68019369cf..4f7c809395 100644 --- a/packages/components/autocomplete/src/use-autocomplete.ts +++ b/packages/components/autocomplete/src/use-autocomplete.ts @@ -18,7 +18,6 @@ import {chain, mergeProps} from "@react-aria/utils"; import {ButtonProps} from "@nextui-org/button"; import {AsyncLoadable, PressEvent} from "@react-types/shared"; import {useComboBox} from "@react-aria/combobox"; -import {ariaShouldCloseOnInteractOutside} from "@nextui-org/aria-utils"; interface Props extends Omit, keyof ComboBoxProps> { /** @@ -138,7 +137,8 @@ export function useAutocomplete(originalProps: UseAutocomplete as, label, isLoading, - menuTrigger = "focus", + // TODO: change back to focus later + menuTrigger = "manual", filterOptions = { sensitivity: "base", }, @@ -202,9 +202,6 @@ export function useAutocomplete(originalProps: UseAutocomplete const inputRef = useDOMRef(ref); const scrollShadowRef = useDOMRef(scrollRefProp); - // control the input focus behaviours internally - const shouldFocus = useRef(false); - const { buttonProps, inputProps, @@ -331,15 +328,6 @@ export function useAutocomplete(originalProps: UseAutocomplete } }, [isOpen]); - // react aria has different focus strategies internally - // hence, handle focus behaviours on our side for better flexibilty - useEffect(() => { - const action = shouldFocus.current || isOpen ? "focus" : "blur"; - - inputRef?.current?.[action](); - if (action === "blur") shouldFocus.current = false; - }, [shouldFocus.current, isOpen]); - // to prevent the error message: // stopPropagation is now the default behavior for events in React Spectrum. // You can use continuePropagation() to revert this behavior. @@ -466,8 +454,7 @@ export function useAutocomplete(originalProps: UseAutocomplete }, shouldCloseOnInteractOutside: popoverProps?.shouldCloseOnInteractOutside ? popoverProps.shouldCloseOnInteractOutside - : (element: Element) => - ariaShouldCloseOnInteractOutside(element, inputWrapperRef, state, shouldFocus), + : () => true, } as unknown as PopoverProps; }; From 73027712f01aa0e666c1763baf974065dc0231da Mon Sep 17 00:00:00 2001 From: WK Wong Date: Mon, 10 Jun 2024 00:34:47 +0800 Subject: [PATCH 13/34] fix(popover): rewrite shouldCloseOnInteractOutside logic --- .../popover/src/use-aria-popover.ts | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/components/popover/src/use-aria-popover.ts b/packages/components/popover/src/use-aria-popover.ts index d1b3ea1a01..0aad90c3b8 100644 --- a/packages/components/popover/src/use-aria-popover.ts +++ b/packages/components/popover/src/use-aria-popover.ts @@ -10,7 +10,6 @@ import {OverlayPlacement, ariaHideOutside, toReactAriaPlacement} from "@nextui-o import {OverlayTriggerState} from "@react-stately/overlays"; import {mergeProps} from "@react-aria/utils"; import {useSafeLayoutEffect} from "@nextui-org/use-safe-layout-effect"; -import {ariaShouldCloseOnInteractOutside} from "@nextui-org/aria-utils"; export interface Props { /** @@ -76,7 +75,36 @@ export function useReactAriaPopover( isKeyboardDismissDisabled, shouldCloseOnInteractOutside: shouldCloseOnInteractOutside ? shouldCloseOnInteractOutside - : (element: Element) => ariaShouldCloseOnInteractOutside(element, triggerRef, state), + : (element: Element) => { + const trigger = triggerRef?.current!; + + if (!trigger || !trigger.contains(element)) { + const startElements = document.querySelectorAll("[data-focus-scope-start]"); + const endElements = document.querySelectorAll("[data-focus-scope-end]"); + let focusScopeElements = []; + + startElements.forEach((startElement, index) => { + if (endElements[index]) { + let currentElement = startElement.nextElementSibling; + let elementsBetween = []; + + while (currentElement && currentElement !== endElements[index]) { + elementsBetween.push(currentElement); + currentElement = currentElement.nextElementSibling; + } + focusScopeElements.push(elementsBetween); + } + }); + + if (focusScopeElements.length === 1) { + state.close(); + + return false; + } + } + + return !trigger || !trigger.contains(element); + }, }, popoverRef, ); From d9546c80788b6869b77fa30b38c4e5e1992577b6 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Mon, 10 Jun 2024 00:55:41 +0800 Subject: [PATCH 14/34] chore(utilities): remove ariaShouldCloseOnInteractOutside --- packages/utilities/aria-utils/src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/utilities/aria-utils/src/index.ts b/packages/utilities/aria-utils/src/index.ts index 6b8d27ad3f..8e70c1b183 100644 --- a/packages/utilities/aria-utils/src/index.ts +++ b/packages/utilities/aria-utils/src/index.ts @@ -7,7 +7,6 @@ export {isNonContiguousSelectionModifier, isCtrlKeyPressed} from "./utils"; export { ariaHideOutside, - ariaShouldCloseOnInteractOutside, getTransformOrigins, toReactAriaPlacement, toOverlayPlacement, From 6ccb509139e84e548fa6b2f8d52e2e3b51cb9365 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Mon, 10 Jun 2024 22:51:55 +0800 Subject: [PATCH 15/34] chore(deps): bump react-aria dependencies --- packages/components/autocomplete/package.json | 12 +- packages/components/popover/package.json | 14 +- packages/utilities/aria-utils/package.json | 6 +- pnpm-lock.yaml | 558 ++++++++++++++---- 4 files changed, 466 insertions(+), 124 deletions(-) diff --git a/packages/components/autocomplete/package.json b/packages/components/autocomplete/package.json index dc839a3ecd..3b2815e7b3 100644 --- a/packages/components/autocomplete/package.json +++ b/packages/components/autocomplete/package.json @@ -52,14 +52,14 @@ "@nextui-org/use-aria-button": "workspace:*", "@nextui-org/shared-icons": "workspace:*", "@nextui-org/use-safe-layout-effect": "workspace:*", - "@react-aria/combobox": "3.8.4", - "@react-aria/focus": "3.16.2", + "@react-aria/combobox": "3.9.1", + "@react-aria/focus": "3.17.1", "@react-aria/i18n": "3.10.2", - "@react-aria/interactions": "3.21.1", + "@react-aria/interactions": "3.21.3", "@react-aria/utils": "3.24.1", - "@react-aria/visually-hidden": "3.8.10", - "@react-stately/combobox": "3.8.2", - "@react-types/combobox": "3.10.1", + "@react-aria/visually-hidden": "3.8.12", + "@react-stately/combobox": "3.8.4", + "@react-types/combobox": "3.11.1", "@react-types/shared": "3.23.1" }, "devDependencies": { diff --git a/packages/components/popover/package.json b/packages/components/popover/package.json index c91b194489..43ea18abb7 100644 --- a/packages/components/popover/package.json +++ b/packages/components/popover/package.json @@ -48,14 +48,14 @@ "@nextui-org/shared-utils": "workspace:*", "@nextui-org/react-utils": "workspace:*", "@nextui-org/use-safe-layout-effect": "workspace:*", - "@react-aria/dialog": "3.5.12", - "@react-aria/focus": "3.16.2", - "@react-aria/interactions": "3.21.1", - "@react-aria/overlays": "3.21.1", + "@react-aria/dialog": "3.5.14", + "@react-aria/focus": "3.17.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/overlays": "3.22.1", "@react-aria/utils": "3.24.1", - "@react-stately/overlays": "3.6.5", - "@react-types/button": "3.9.2", - "@react-types/overlays": "3.8.5", + "@react-stately/overlays": "3.6.7", + "@react-types/button": "3.9.4", + "@react-types/overlays": "3.8.7", "react-remove-scroll": "^2.5.6" }, "devDependencies": { diff --git a/packages/utilities/aria-utils/package.json b/packages/utilities/aria-utils/package.json index 8df7018f95..7e1157d2a8 100644 --- a/packages/utilities/aria-utils/package.json +++ b/packages/utilities/aria-utils/package.json @@ -42,9 +42,9 @@ "@nextui-org/shared-utils": "workspace:*", "@nextui-org/react-rsc-utils": "workspace:*", "@react-aria/utils": "3.24.1", - "@react-stately/collections": "3.10.5", - "@react-stately/overlays": "3.6.5", - "@react-types/overlays": "3.8.5", + "@react-stately/collections": "3.10.7", + "@react-stately/overlays": "3.6.7", + "@react-types/overlays": "3.8.7", "@react-types/shared": "3.23.1" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dbce6471e6..392f1a9fc2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -720,29 +720,29 @@ importers: specifier: workspace:* version: link:../../hooks/use-safe-layout-effect '@react-aria/combobox': - specifier: 3.8.4 - version: 3.8.4(react-dom@18.2.0)(react@18.2.0) + specifier: 3.9.1 + version: 3.9.1(react-dom@18.2.0)(react@18.2.0) '@react-aria/focus': - specifier: 3.16.2 - version: 3.16.2(react@18.2.0) + specifier: 3.17.1 + version: 3.17.1(react@18.2.0) '@react-aria/i18n': specifier: 3.10.2 version: 3.10.2(react@18.2.0) '@react-aria/interactions': - specifier: 3.21.1 - version: 3.21.1(react@18.2.0) + specifier: 3.21.3 + version: 3.21.3(react@18.2.0) '@react-aria/utils': specifier: 3.24.1 version: 3.24.1(react@18.2.0) '@react-aria/visually-hidden': - specifier: 3.8.10 - version: 3.8.10(react@18.2.0) + specifier: 3.8.12 + version: 3.8.12(react@18.2.0) '@react-stately/combobox': - specifier: 3.8.2 - version: 3.8.2(react@18.2.0) + specifier: 3.8.4 + version: 3.8.4(react@18.2.0) '@react-types/combobox': - specifier: 3.10.1 - version: 3.10.1(react@18.2.0) + specifier: 3.11.1 + version: 3.11.1(react@18.2.0) '@react-types/shared': specifier: 3.23.1 version: 3.23.1(react@18.2.0) @@ -2009,29 +2009,29 @@ importers: specifier: workspace:* version: link:../../hooks/use-safe-layout-effect '@react-aria/dialog': - specifier: 3.5.12 - version: 3.5.12(react-dom@18.2.0)(react@18.2.0) + specifier: 3.5.14 + version: 3.5.14(react-dom@18.2.0)(react@18.2.0) '@react-aria/focus': - specifier: 3.16.2 - version: 3.16.2(react@18.2.0) + specifier: 3.17.1 + version: 3.17.1(react@18.2.0) '@react-aria/interactions': - specifier: 3.21.1 - version: 3.21.1(react@18.2.0) + specifier: 3.21.3 + version: 3.21.3(react@18.2.0) '@react-aria/overlays': - specifier: 3.21.1 - version: 3.21.1(react-dom@18.2.0)(react@18.2.0) + specifier: 3.22.1 + version: 3.22.1(react-dom@18.2.0)(react@18.2.0) '@react-aria/utils': specifier: 3.24.1 version: 3.24.1(react@18.2.0) '@react-stately/overlays': - specifier: 3.6.5 - version: 3.6.5(react@18.2.0) + specifier: 3.6.7 + version: 3.6.7(react@18.2.0) '@react-types/button': - specifier: 3.9.2 - version: 3.9.2(react@18.2.0) + specifier: 3.9.4 + version: 3.9.4(react@18.2.0) '@react-types/overlays': - specifier: 3.8.5 - version: 3.8.5(react@18.2.0) + specifier: 3.8.7 + version: 3.8.7(react@18.2.0) react-remove-scroll: specifier: ^2.5.6 version: 2.5.9(@types/react@18.2.8)(react@18.2.0) @@ -3643,14 +3643,14 @@ importers: specifier: 3.24.1 version: 3.24.1(react@18.2.0) '@react-stately/collections': - specifier: 3.10.5 - version: 3.10.5(react@18.2.0) + specifier: 3.10.7 + version: 3.10.7(react@18.2.0) '@react-stately/overlays': - specifier: 3.6.5 - version: 3.6.5(react@18.2.0) + specifier: 3.6.7 + version: 3.6.7(react@18.2.0) '@react-types/overlays': - specifier: 3.8.5 - version: 3.8.5(react@18.2.0) + specifier: 3.8.7 + version: 3.8.7(react@18.2.0) '@react-types/shared': specifier: 3.23.1 version: 3.23.1(react@18.2.0) @@ -6715,22 +6715,47 @@ packages: dependencies: '@swc/helpers': 0.5.9 + /@internationalized/date@3.5.4: + resolution: {integrity: sha512-qoVJVro+O0rBaw+8HPjUB1iH8Ihf8oziEnqMnvhJUSuVIrHOuZ6eNLHNvzXJKUvAtaDiqMnRlg8Z2mgh09BlUw==} + dependencies: + '@swc/helpers': 0.5.9 + dev: false + /@internationalized/message@3.1.2: resolution: {integrity: sha512-MHAWsZWz8jf6jFPZqpTudcCM361YMtPIRu9CXkYmKjJ/0R3pQRScV5C0zS+Qi50O5UAm8ecKhkXx6mWDDcF6/g==} dependencies: '@swc/helpers': 0.5.9 intl-messageformat: 10.5.11 + /@internationalized/message@3.1.4: + resolution: {integrity: sha512-Dygi9hH1s7V9nha07pggCkvmRfDd3q2lWnMGvrJyrOwYMe1yj4D2T9BoH9I6MGR7xz0biQrtLPsqUkqXzIrBOw==} + dependencies: + '@swc/helpers': 0.5.9 + intl-messageformat: 10.5.11 + dev: false + /@internationalized/number@3.5.1: resolution: {integrity: sha512-N0fPU/nz15SwR9IbfJ5xaS9Ss/O5h1sVXMZf43vc9mxEG48ovglvvzBjF53aHlq20uoR6c+88CrIXipU/LSzwg==} dependencies: '@swc/helpers': 0.5.9 + /@internationalized/number@3.5.3: + resolution: {integrity: sha512-rd1wA3ebzlp0Mehj5YTuTI50AQEx80gWFyHcQu+u91/5NgdwBecO8BH6ipPfE+lmQ9d63vpB3H9SHoIUiupllw==} + dependencies: + '@swc/helpers': 0.5.9 + dev: false + /@internationalized/string@3.2.1: resolution: {integrity: sha512-vWQOvRIauvFMzOO+h7QrdsJmtN1AXAFVcaLWP9AseRN2o7iHceZ6bIXhBD4teZl8i91A3gxKnWBlGgjCwU6MFQ==} dependencies: '@swc/helpers': 0.5.9 + /@internationalized/string@3.2.3: + resolution: {integrity: sha512-9kpfLoA8HegiWTeCbR2livhdVeKobCnVv8tlJ6M2jF+4tcMqDo94ezwlnrUANBWPgd8U7OXIHCk2Ov2qhk4KXw==} + dependencies: + '@swc/helpers': 0.5.9 + dev: false + /@isaacs/cliui@8.0.2: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -9623,8 +9648,8 @@ packages: peerDependencies: react: ^18.2.0 dependencies: - '@react-aria/focus': 3.16.2(react@18.2.0) - '@react-aria/interactions': 3.21.1(react@18.2.0) + '@react-aria/focus': 3.17.1(react@18.2.0) + '@react-aria/interactions': 3.21.3(react@18.2.0) '@react-aria/utils': 3.24.1(react@18.2.0) '@react-stately/toggle': 3.7.2(react@18.2.0) '@react-types/button': 3.9.2(react@18.2.0) @@ -9641,7 +9666,7 @@ packages: dependencies: '@internationalized/date': 3.5.2 '@react-aria/i18n': 3.10.2(react@18.2.0) - '@react-aria/interactions': 3.21.1(react@18.2.0) + '@react-aria/interactions': 3.21.3(react@18.2.0) '@react-aria/live-announcer': 3.3.2 '@react-aria/utils': 3.24.1(react@18.2.0) '@react-stately/calendar': 3.4.4(react@18.2.0) @@ -9659,7 +9684,7 @@ packages: react: ^18.2.0 dependencies: '@react-aria/form': 3.0.3(react@18.2.0) - '@react-aria/interactions': 3.21.1(react@18.2.0) + '@react-aria/interactions': 3.21.3(react@18.2.0) '@react-aria/label': 3.7.6(react@18.2.0) '@react-aria/toggle': 3.10.2(react@18.2.0) '@react-aria/utils': 3.24.1(react@18.2.0) @@ -9672,25 +9697,25 @@ packages: react: 18.2.0 dev: false - /@react-aria/combobox@3.8.4(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-HyTWIo2B/0xq0Of+sDEZCfJyf4BvCvDYIWG4UhjqL1kHIHIGQyyr+SldbVUjXVYnk8pP1eGB3ttiREujjjALPQ==} + /@react-aria/combobox@3.9.1(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-SpK92dCmT8qn8aEcUAihRQrBb5LZUhwIbDExFII8PvUvEFy/PoQHXIo3j1V29WkutDBDpMvBv/6XRCHGXPqrhQ==} peerDependencies: react: ^18.2.0 react-dom: ^18.2.0 dependencies: - '@react-aria/i18n': 3.10.2(react@18.2.0) - '@react-aria/listbox': 3.11.5(react-dom@18.2.0)(react@18.2.0) - '@react-aria/live-announcer': 3.3.2 - '@react-aria/menu': 3.13.1(react-dom@18.2.0)(react@18.2.0) - '@react-aria/overlays': 3.21.1(react-dom@18.2.0)(react@18.2.0) - '@react-aria/selection': 3.17.5(react-dom@18.2.0)(react@18.2.0) - '@react-aria/textfield': 3.14.3(react@18.2.0) + '@react-aria/i18n': 3.11.1(react@18.2.0) + '@react-aria/listbox': 3.12.1(react-dom@18.2.0)(react@18.2.0) + '@react-aria/live-announcer': 3.3.4 + '@react-aria/menu': 3.14.1(react-dom@18.2.0)(react@18.2.0) + '@react-aria/overlays': 3.22.1(react-dom@18.2.0)(react@18.2.0) + '@react-aria/selection': 3.18.1(react-dom@18.2.0)(react@18.2.0) + '@react-aria/textfield': 3.14.5(react@18.2.0) '@react-aria/utils': 3.24.1(react@18.2.0) - '@react-stately/collections': 3.10.5(react@18.2.0) - '@react-stately/combobox': 3.8.2(react@18.2.0) - '@react-stately/form': 3.0.1(react@18.2.0) - '@react-types/button': 3.9.2(react@18.2.0) - '@react-types/combobox': 3.10.1(react@18.2.0) + '@react-stately/collections': 3.10.7(react@18.2.0) + '@react-stately/combobox': 3.8.4(react@18.2.0) + '@react-stately/form': 3.0.3(react@18.2.0) + '@react-types/button': 3.9.4(react@18.2.0) + '@react-types/combobox': 3.11.1(react@18.2.0) '@react-types/shared': 3.23.1(react@18.2.0) '@swc/helpers': 0.5.9 react: 18.2.0 @@ -9706,10 +9731,10 @@ packages: '@internationalized/date': 3.5.2 '@internationalized/number': 3.5.1 '@internationalized/string': 3.2.1 - '@react-aria/focus': 3.16.2(react@18.2.0) + '@react-aria/focus': 3.17.1(react@18.2.0) '@react-aria/form': 3.0.3(react@18.2.0) '@react-aria/i18n': 3.10.2(react@18.2.0) - '@react-aria/interactions': 3.21.1(react@18.2.0) + '@react-aria/interactions': 3.21.3(react@18.2.0) '@react-aria/label': 3.7.6(react@18.2.0) '@react-aria/spinbutton': 3.6.3(react-dom@18.2.0)(react@18.2.0) '@react-aria/utils': 3.24.1(react@18.2.0) @@ -9731,7 +9756,7 @@ packages: react: ^18.2.0 react-dom: ^18.2.0 dependencies: - '@react-aria/focus': 3.16.2(react@18.2.0) + '@react-aria/focus': 3.17.1(react@18.2.0) '@react-aria/overlays': 3.21.1(react-dom@18.2.0)(react@18.2.0) '@react-aria/utils': 3.24.1(react@18.2.0) '@react-types/dialog': 3.5.8(react@18.2.0) @@ -9741,6 +9766,22 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@react-aria/dialog@3.5.14(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-oqDCjQ8hxe3GStf48XWBf2CliEnxlR9GgSYPHJPUc69WBj68D9rVcCW3kogJnLAnwIyf3FnzbX4wSjvUa88sAQ==} + peerDependencies: + react: ^18.2.0 + react-dom: ^18.2.0 + dependencies: + '@react-aria/focus': 3.17.1(react@18.2.0) + '@react-aria/overlays': 3.22.1(react-dom@18.2.0)(react@18.2.0) + '@react-aria/utils': 3.24.1(react@18.2.0) + '@react-types/dialog': 3.5.10(react@18.2.0) + '@react-types/shared': 3.23.1(react@18.2.0) + '@swc/helpers': 0.5.9 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@react-aria/focus@3.16.2(react@18.2.0): resolution: {integrity: sha512-Rqo9ummmgotESfypzFjI3uh58yMpL+E+lJBbQuXkBM0u0cU2YYzu0uOrFrq3zcHk997udZvq1pGK/R+2xk9B7g==} peerDependencies: @@ -9772,7 +9813,7 @@ packages: peerDependencies: react: ^18.2.0 dependencies: - '@react-aria/interactions': 3.21.1(react@18.2.0) + '@react-aria/interactions': 3.21.3(react@18.2.0) '@react-aria/utils': 3.24.1(react@18.2.0) '@react-stately/form': 3.0.1(react@18.2.0) '@react-types/shared': 3.23.1(react@18.2.0) @@ -9780,19 +9821,32 @@ packages: react: 18.2.0 dev: false + /@react-aria/form@3.0.5(react@18.2.0): + resolution: {integrity: sha512-n290jRwrrRXO3fS82MyWR+OKN7yznVesy5Q10IclSTVYHHI3VI53xtAPr/WzNjJR1um8aLhOcDNFKwnNIUUCsQ==} + peerDependencies: + react: ^18.2.0 + dependencies: + '@react-aria/interactions': 3.21.3(react@18.2.0) + '@react-aria/utils': 3.24.1(react@18.2.0) + '@react-stately/form': 3.0.3(react@18.2.0) + '@react-types/shared': 3.23.1(react@18.2.0) + '@swc/helpers': 0.5.9 + react: 18.2.0 + dev: false + /@react-aria/grid@3.8.8(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-7Bzbya4tO0oIgqexwRb8D6ZdC0GASYq9f/pnkrqocgvG9e1SCld4zOioKbYQDvAK/NnbCgXmmdqFAcLM/iazaA==} peerDependencies: react: ^18.2.0 react-dom: ^18.2.0 dependencies: - '@react-aria/focus': 3.16.2(react@18.2.0) + '@react-aria/focus': 3.17.1(react@18.2.0) '@react-aria/i18n': 3.10.2(react@18.2.0) - '@react-aria/interactions': 3.21.1(react@18.2.0) + '@react-aria/interactions': 3.21.3(react@18.2.0) '@react-aria/live-announcer': 3.3.2 '@react-aria/selection': 3.17.5(react-dom@18.2.0)(react@18.2.0) '@react-aria/utils': 3.24.1(react@18.2.0) - '@react-stately/collections': 3.10.5(react@18.2.0) + '@react-stately/collections': 3.10.7(react@18.2.0) '@react-stately/grid': 3.8.5(react@18.2.0) '@react-stately/selection': 3.14.3(react@18.2.0) '@react-stately/virtualizer': 3.6.8(react@18.2.0) @@ -9819,6 +9873,22 @@ packages: '@swc/helpers': 0.5.9 react: 18.2.0 + /@react-aria/i18n@3.11.1(react@18.2.0): + resolution: {integrity: sha512-vuiBHw1kZruNMYeKkTGGnmPyMnM5T+gT8bz97H1FqIq1hQ6OPzmtBZ6W6l6OIMjeHI5oJo4utTwfZl495GALFQ==} + peerDependencies: + react: ^18.2.0 + dependencies: + '@internationalized/date': 3.5.4 + '@internationalized/message': 3.1.4 + '@internationalized/number': 3.5.3 + '@internationalized/string': 3.2.3 + '@react-aria/ssr': 3.9.4(react@18.2.0) + '@react-aria/utils': 3.24.1(react@18.2.0) + '@react-types/shared': 3.23.1(react@18.2.0) + '@swc/helpers': 0.5.9 + react: 18.2.0 + dev: false + /@react-aria/interactions@3.21.1(react@18.2.0): resolution: {integrity: sha512-AlHf5SOzsShkHfV8GLLk3v9lEmYqYHURKcXWue0JdYbmquMRkUsf/+Tjl1+zHVAQ8lKqRnPYbTmc4AcZbqxltw==} peerDependencies: @@ -9854,13 +9924,24 @@ packages: react: 18.2.0 dev: false + /@react-aria/label@3.7.8(react@18.2.0): + resolution: {integrity: sha512-MzgTm5+suPA3KX7Ug6ZBK2NX9cin/RFLsv1BdafJ6CZpmUSpWnGE/yQfYUB7csN7j31OsZrD3/P56eShYWAQfg==} + peerDependencies: + react: ^18.2.0 + dependencies: + '@react-aria/utils': 3.24.1(react@18.2.0) + '@react-types/shared': 3.23.1(react@18.2.0) + '@swc/helpers': 0.5.9 + react: 18.2.0 + dev: false + /@react-aria/link@3.6.5(react@18.2.0): resolution: {integrity: sha512-kg8CxKqkciQFzODvLAfxEs8gbqNXFZCW/ISOE2LHYKbh9pA144LVo71qO3SPeYVVzIjmZeW4vEMdZwqkNozecw==} peerDependencies: react: ^18.2.0 dependencies: - '@react-aria/focus': 3.16.2(react@18.2.0) - '@react-aria/interactions': 3.21.1(react@18.2.0) + '@react-aria/focus': 3.17.1(react@18.2.0) + '@react-aria/interactions': 3.21.3(react@18.2.0) '@react-aria/utils': 3.24.1(react@18.2.0) '@react-types/link': 3.5.3(react@18.2.0) '@react-types/shared': 3.23.1(react@18.2.0) @@ -9888,7 +9969,7 @@ packages: react: ^18.2.0 react-dom: ^18.2.0 dependencies: - '@react-aria/interactions': 3.21.1(react@18.2.0) + '@react-aria/interactions': 3.21.3(react@18.2.0) '@react-aria/label': 3.7.6(react@18.2.0) '@react-aria/selection': 3.17.5(react-dom@18.2.0)(react@18.2.0) '@react-aria/utils': 3.24.1(react@18.2.0) @@ -9901,21 +9982,46 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@react-aria/listbox@3.12.1(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-7JiUp0NGykbv/HgSpmTY1wqhuf/RmjFxs1HZcNaTv8A+DlzgJYc7yQqFjP3ZA/z5RvJFuuIxggIYmgIFjaRYdA==} + peerDependencies: + react: ^18.2.0 + react-dom: ^18.2.0 + dependencies: + '@react-aria/interactions': 3.21.3(react@18.2.0) + '@react-aria/label': 3.7.8(react@18.2.0) + '@react-aria/selection': 3.18.1(react-dom@18.2.0)(react@18.2.0) + '@react-aria/utils': 3.24.1(react@18.2.0) + '@react-stately/collections': 3.10.7(react@18.2.0) + '@react-stately/list': 3.10.5(react@18.2.0) + '@react-types/listbox': 3.4.9(react@18.2.0) + '@react-types/shared': 3.23.1(react@18.2.0) + '@swc/helpers': 0.5.9 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@react-aria/live-announcer@3.3.2: resolution: {integrity: sha512-aOyPcsfyY9tLCBhuUaYCruwcd1IrYLc47Ou+J7wMzjeN9v4lsaEfiN12WFl8pDqOwfy6/7It2wmlm5hOuZY8wQ==} dependencies: '@swc/helpers': 0.5.9 dev: false + /@react-aria/live-announcer@3.3.4: + resolution: {integrity: sha512-w8lxs35QrRrn6pBNzVfyGOeqWdxeVKf9U6bXIVwhq7rrTqRULL8jqy8RJIMfIs1s8G5FpwWYjyBOjl2g5Cu1iA==} + dependencies: + '@swc/helpers': 0.5.9 + dev: false + /@react-aria/menu@3.13.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-jF80YIcvD16Fgwm5pj7ViUE3Dj7z5iewQixLaFVdvpgfyE58SD/ZVU9/JkK5g/03DYM0sjpUKZGkdFxxw8eKnw==} peerDependencies: react: ^18.2.0 react-dom: ^18.2.0 dependencies: - '@react-aria/focus': 3.16.2(react@18.2.0) + '@react-aria/focus': 3.17.1(react@18.2.0) '@react-aria/i18n': 3.10.2(react@18.2.0) - '@react-aria/interactions': 3.21.1(react@18.2.0) + '@react-aria/interactions': 3.21.3(react@18.2.0) '@react-aria/overlays': 3.21.1(react-dom@18.2.0)(react@18.2.0) '@react-aria/selection': 3.17.5(react-dom@18.2.0)(react@18.2.0) '@react-aria/utils': 3.24.1(react@18.2.0) @@ -9930,21 +10036,65 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@react-aria/menu@3.14.1(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-BYliRb38uAzq05UOFcD5XkjA5foQoXRbcH3ZufBsc4kvh79BcP1PMW6KsXKGJ7dC/PJWUwCui6QL1kUg8PqMHA==} + peerDependencies: + react: ^18.2.0 + react-dom: ^18.2.0 + dependencies: + '@react-aria/focus': 3.17.1(react@18.2.0) + '@react-aria/i18n': 3.11.1(react@18.2.0) + '@react-aria/interactions': 3.21.3(react@18.2.0) + '@react-aria/overlays': 3.22.1(react-dom@18.2.0)(react@18.2.0) + '@react-aria/selection': 3.18.1(react-dom@18.2.0)(react@18.2.0) + '@react-aria/utils': 3.24.1(react@18.2.0) + '@react-stately/collections': 3.10.7(react@18.2.0) + '@react-stately/menu': 3.7.1(react@18.2.0) + '@react-stately/tree': 3.8.1(react@18.2.0) + '@react-types/button': 3.9.4(react@18.2.0) + '@react-types/menu': 3.9.9(react@18.2.0) + '@react-types/shared': 3.23.1(react@18.2.0) + '@swc/helpers': 0.5.9 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@react-aria/overlays@3.21.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-djEBDF+TbIIOHWWNpdm19+z8xtY8U+T+wKVQg/UZ6oWnclSqSWeGl70vu73Cg4HVBJ4hKf1SRx4Z/RN6VvH4Yw==} peerDependencies: react: ^18.2.0 react-dom: ^18.2.0 dependencies: - '@react-aria/focus': 3.16.2(react@18.2.0) + '@react-aria/focus': 3.17.1(react@18.2.0) '@react-aria/i18n': 3.10.2(react@18.2.0) - '@react-aria/interactions': 3.21.1(react@18.2.0) + '@react-aria/interactions': 3.21.3(react@18.2.0) '@react-aria/ssr': 3.9.2(react@18.2.0) '@react-aria/utils': 3.24.1(react@18.2.0) - '@react-aria/visually-hidden': 3.8.10(react@18.2.0) - '@react-stately/overlays': 3.6.5(react@18.2.0) - '@react-types/button': 3.9.2(react@18.2.0) - '@react-types/overlays': 3.8.5(react@18.2.0) + '@react-aria/visually-hidden': 3.8.12(react@18.2.0) + '@react-stately/overlays': 3.6.7(react@18.2.0) + '@react-types/button': 3.9.4(react@18.2.0) + '@react-types/overlays': 3.8.7(react@18.2.0) + '@react-types/shared': 3.23.1(react@18.2.0) + '@swc/helpers': 0.5.9 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@react-aria/overlays@3.22.1(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-GHiFMWO4EQ6+j6b5QCnNoOYiyx1Gk8ZiwLzzglCI4q1NY5AG2EAmfU4Z1+Gtrf2S5Y0zHbumC7rs9GnPoGLUYg==} + peerDependencies: + react: ^18.2.0 + react-dom: ^18.2.0 + dependencies: + '@react-aria/focus': 3.17.1(react@18.2.0) + '@react-aria/i18n': 3.11.1(react@18.2.0) + '@react-aria/interactions': 3.21.3(react@18.2.0) + '@react-aria/ssr': 3.9.4(react@18.2.0) + '@react-aria/utils': 3.24.1(react@18.2.0) + '@react-aria/visually-hidden': 3.8.12(react@18.2.0) + '@react-stately/overlays': 3.6.7(react@18.2.0) + '@react-types/button': 3.9.4(react@18.2.0) + '@react-types/overlays': 3.8.7(react@18.2.0) '@react-types/shared': 3.23.1(react@18.2.0) '@swc/helpers': 0.5.9 react: 18.2.0 @@ -9970,10 +10120,10 @@ packages: peerDependencies: react: ^18.2.0 dependencies: - '@react-aria/focus': 3.16.2(react@18.2.0) + '@react-aria/focus': 3.17.1(react@18.2.0) '@react-aria/form': 3.0.3(react@18.2.0) '@react-aria/i18n': 3.10.2(react@18.2.0) - '@react-aria/interactions': 3.21.1(react@18.2.0) + '@react-aria/interactions': 3.21.3(react@18.2.0) '@react-aria/label': 3.7.6(react@18.2.0) '@react-aria/utils': 3.24.1(react@18.2.0) '@react-stately/radio': 3.10.2(react@18.2.0) @@ -10000,14 +10150,31 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@react-aria/selection@3.18.1(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-GSqN2jX6lh7v+ldqhVjAXDcrWS3N4IsKXxO6L6Ygsye86Q9q9Mq9twWDWWu5IjHD6LoVZLUBCMO+ENGbOkyqeQ==} + peerDependencies: + react: ^18.2.0 + react-dom: ^18.2.0 + dependencies: + '@react-aria/focus': 3.17.1(react@18.2.0) + '@react-aria/i18n': 3.11.1(react@18.2.0) + '@react-aria/interactions': 3.21.3(react@18.2.0) + '@react-aria/utils': 3.24.1(react@18.2.0) + '@react-stately/selection': 3.15.1(react@18.2.0) + '@react-types/shared': 3.23.1(react@18.2.0) + '@swc/helpers': 0.5.9 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@react-aria/slider@3.7.6(react@18.2.0): resolution: {integrity: sha512-ZeZhyHzhk9gxGuThPKgX2K3RKsxPxsFig1iYoJvqP8485NtHYQIPht2YcpEKA9siLxGF0DR9VCfouVhSoW0AEA==} peerDependencies: react: ^18.2.0 dependencies: - '@react-aria/focus': 3.16.2(react@18.2.0) + '@react-aria/focus': 3.17.1(react@18.2.0) '@react-aria/i18n': 3.10.2(react@18.2.0) - '@react-aria/interactions': 3.21.1(react@18.2.0) + '@react-aria/interactions': 3.21.3(react@18.2.0) '@react-aria/label': 3.7.6(react@18.2.0) '@react-aria/utils': 3.24.1(react@18.2.0) '@react-stately/slider': 3.5.2(react@18.2.0) @@ -10026,7 +10193,7 @@ packages: '@react-aria/i18n': 3.10.2(react@18.2.0) '@react-aria/live-announcer': 3.3.2 '@react-aria/utils': 3.24.1(react@18.2.0) - '@react-types/button': 3.9.2(react@18.2.0) + '@react-types/button': 3.9.4(react@18.2.0) '@react-types/shared': 3.23.1(react@18.2.0) '@swc/helpers': 0.5.9 react: 18.2.0 @@ -10069,13 +10236,13 @@ packages: react: ^18.2.0 react-dom: ^18.2.0 dependencies: - '@react-aria/focus': 3.16.2(react@18.2.0) + '@react-aria/focus': 3.17.1(react@18.2.0) '@react-aria/grid': 3.8.8(react-dom@18.2.0)(react@18.2.0) '@react-aria/i18n': 3.10.2(react@18.2.0) - '@react-aria/interactions': 3.21.1(react@18.2.0) + '@react-aria/interactions': 3.21.3(react@18.2.0) '@react-aria/live-announcer': 3.3.2 '@react-aria/utils': 3.24.1(react@18.2.0) - '@react-aria/visually-hidden': 3.8.10(react@18.2.0) + '@react-aria/visually-hidden': 3.8.12(react@18.2.0) '@react-stately/collections': 3.10.5(react@18.2.0) '@react-stately/flags': 3.0.1 '@react-stately/table': 3.11.6(react@18.2.0) @@ -10095,7 +10262,7 @@ packages: react: ^18.2.0 react-dom: ^18.2.0 dependencies: - '@react-aria/focus': 3.16.2(react@18.2.0) + '@react-aria/focus': 3.17.1(react@18.2.0) '@react-aria/i18n': 3.10.2(react@18.2.0) '@react-aria/selection': 3.17.5(react-dom@18.2.0)(react@18.2.0) '@react-aria/utils': 3.24.1(react@18.2.0) @@ -10112,7 +10279,7 @@ packages: peerDependencies: react: ^18.2.0 dependencies: - '@react-aria/focus': 3.16.2(react@18.2.0) + '@react-aria/focus': 3.17.1(react@18.2.0) '@react-aria/form': 3.0.3(react@18.2.0) '@react-aria/label': 3.7.6(react@18.2.0) '@react-aria/utils': 3.24.1(react@18.2.0) @@ -10124,13 +10291,30 @@ packages: react: 18.2.0 dev: false + /@react-aria/textfield@3.14.5(react@18.2.0): + resolution: {integrity: sha512-hj7H+66BjB1iTKKaFXwSZBZg88YT+wZboEXZ0DNdQB2ytzoz/g045wBItUuNi4ZjXI3P+0AOZznVMYadWBAmiA==} + peerDependencies: + react: ^18.2.0 + dependencies: + '@react-aria/focus': 3.17.1(react@18.2.0) + '@react-aria/form': 3.0.5(react@18.2.0) + '@react-aria/label': 3.7.8(react@18.2.0) + '@react-aria/utils': 3.24.1(react@18.2.0) + '@react-stately/form': 3.0.3(react@18.2.0) + '@react-stately/utils': 3.10.1(react@18.2.0) + '@react-types/shared': 3.23.1(react@18.2.0) + '@react-types/textfield': 3.9.3(react@18.2.0) + '@swc/helpers': 0.5.9 + react: 18.2.0 + dev: false + /@react-aria/toggle@3.10.2(react@18.2.0): resolution: {integrity: sha512-DgitscHWgI6IFgnvp2HcMpLGX/cAn+XX9kF5RJQbRQ9NqUgruU5cEEGSOLMrEJ6zXDa2xmOiQ+kINcyNhA+JLg==} peerDependencies: react: ^18.2.0 dependencies: - '@react-aria/focus': 3.16.2(react@18.2.0) - '@react-aria/interactions': 3.21.1(react@18.2.0) + '@react-aria/focus': 3.17.1(react@18.2.0) + '@react-aria/interactions': 3.21.3(react@18.2.0) '@react-aria/utils': 3.24.1(react@18.2.0) '@react-stately/toggle': 3.7.2(react@18.2.0) '@react-types/checkbox': 3.7.1(react@18.2.0) @@ -10143,8 +10327,8 @@ packages: peerDependencies: react: ^18.2.0 dependencies: - '@react-aria/focus': 3.16.2(react@18.2.0) - '@react-aria/interactions': 3.21.1(react@18.2.0) + '@react-aria/focus': 3.17.1(react@18.2.0) + '@react-aria/interactions': 3.21.3(react@18.2.0) '@react-aria/utils': 3.24.1(react@18.2.0) '@react-stately/tooltip': 3.4.7(react@18.2.0) '@react-types/shared': 3.23.1(react@18.2.0) @@ -10206,6 +10390,18 @@ packages: react: 18.2.0 dev: false + /@react-aria/visually-hidden@3.8.12(react@18.2.0): + resolution: {integrity: sha512-Bawm+2Cmw3Xrlr7ARzl2RLtKh0lNUdJ0eNqzWcyx4c0VHUAWtThmH5l+HRqFUGzzutFZVo89SAy40BAbd0gjVw==} + peerDependencies: + react: ^18.2.0 + dependencies: + '@react-aria/interactions': 3.21.3(react@18.2.0) + '@react-aria/utils': 3.24.1(react@18.2.0) + '@react-types/shared': 3.23.1(react@18.2.0) + '@swc/helpers': 0.5.9 + react: 18.2.0 + dev: false + /@react-bootstrap/babel-preset@2.2.0: resolution: {integrity: sha512-lJNNow6APKf1pH5/R60i7c/QGYbx/VCKdqzWWIMDyyXvNDGFwcM2T0DNOBqe9Rjz8rEROS/mKGLN3C1Im6+adQ==} dependencies: @@ -10280,18 +10476,28 @@ packages: react: 18.2.0 dev: false - /@react-stately/combobox@3.8.2(react@18.2.0): - resolution: {integrity: sha512-f+IHuFW848VoMbvTfSakn2WIh2urDxO355LrKxnisXPCkpQHpq3lvT2mJtKJwkPxjAy7xPjpV8ejgga2R6p53Q==} + /@react-stately/collections@3.10.7(react@18.2.0): + resolution: {integrity: sha512-KRo5O2MWVL8n3aiqb+XR3vP6akmHLhLWYZEmPKjIv0ghQaEebBTrN3wiEjtd6dzllv0QqcWvDLM1LntNfJ2TsA==} peerDependencies: react: ^18.2.0 dependencies: - '@react-stately/collections': 3.10.5(react@18.2.0) - '@react-stately/form': 3.0.1(react@18.2.0) - '@react-stately/list': 3.10.3(react@18.2.0) - '@react-stately/overlays': 3.6.5(react@18.2.0) - '@react-stately/select': 3.6.2(react@18.2.0) - '@react-stately/utils': 3.9.1(react@18.2.0) - '@react-types/combobox': 3.10.1(react@18.2.0) + '@react-types/shared': 3.23.1(react@18.2.0) + '@swc/helpers': 0.5.9 + react: 18.2.0 + dev: false + + /@react-stately/combobox@3.8.4(react@18.2.0): + resolution: {integrity: sha512-iLVGvKRRz0TeJXZhZyK783hveHpYA6xovOSdzSD+WGYpiPXo1QrcrNoH3AE0Z2sHtorU+8nc0j58vh5PB+m2AA==} + peerDependencies: + react: ^18.2.0 + dependencies: + '@react-stately/collections': 3.10.7(react@18.2.0) + '@react-stately/form': 3.0.3(react@18.2.0) + '@react-stately/list': 3.10.5(react@18.2.0) + '@react-stately/overlays': 3.6.7(react@18.2.0) + '@react-stately/select': 3.6.4(react@18.2.0) + '@react-stately/utils': 3.10.1(react@18.2.0) + '@react-types/combobox': 3.11.1(react@18.2.0) '@react-types/shared': 3.23.1(react@18.2.0) '@swc/helpers': 0.5.9 react: 18.2.0 @@ -10338,12 +10544,22 @@ packages: react: 18.2.0 dev: false + /@react-stately/form@3.0.3(react@18.2.0): + resolution: {integrity: sha512-92YYBvlHEWUGUpXgIaQ48J50jU9XrxfjYIN8BTvvhBHdD63oWgm8DzQnyT/NIAMzdLnhkg7vP+fjG8LjHeyIAg==} + peerDependencies: + react: ^18.2.0 + dependencies: + '@react-types/shared': 3.23.1(react@18.2.0) + '@swc/helpers': 0.5.9 + react: 18.2.0 + dev: false + /@react-stately/grid@3.8.5(react@18.2.0): resolution: {integrity: sha512-KCzi0x0p1ZKK+OptonvJqMbn6Vlgo6GfOIlgcDd0dNYDP8TJ+3QFJAFre5mCr7Fubx7LcAOio4Rij0l/R8fkXQ==} peerDependencies: react: ^18.2.0 dependencies: - '@react-stately/collections': 3.10.5(react@18.2.0) + '@react-stately/collections': 3.10.7(react@18.2.0) '@react-stately/selection': 3.14.3(react@18.2.0) '@react-types/grid': 3.2.4(react@18.2.0) '@react-types/shared': 3.23.1(react@18.2.0) @@ -10371,7 +10587,7 @@ packages: peerDependencies: react: ^18.2.0 dependencies: - '@react-stately/collections': 3.10.5(react@18.2.0) + '@react-stately/collections': 3.10.7(react@18.2.0) '@react-stately/selection': 3.14.3(react@18.2.0) '@react-stately/utils': 3.9.1(react@18.2.0) '@react-types/shared': 3.23.1(react@18.2.0) @@ -10379,18 +10595,43 @@ packages: react: 18.2.0 dev: false + /@react-stately/list@3.10.5(react@18.2.0): + resolution: {integrity: sha512-fV9plO+6QDHiewsYIhboxcDhF17GO95xepC5ki0bKXo44gr14g/LSo/BMmsaMnV+1BuGdBunB05bO4QOIaigXA==} + peerDependencies: + react: ^18.2.0 + dependencies: + '@react-stately/collections': 3.10.7(react@18.2.0) + '@react-stately/selection': 3.15.1(react@18.2.0) + '@react-stately/utils': 3.10.1(react@18.2.0) + '@react-types/shared': 3.23.1(react@18.2.0) + '@swc/helpers': 0.5.9 + react: 18.2.0 + dev: false + /@react-stately/menu@3.6.1(react@18.2.0): resolution: {integrity: sha512-3v0vkTm/kInuuG8jG7jbxXDBnMQcoDZKWvYsBQq7+POt0LmijbLdbdZPBoz9TkZ3eo/OoP194LLHOaFTQyHhlw==} peerDependencies: react: ^18.2.0 dependencies: - '@react-stately/overlays': 3.6.5(react@18.2.0) + '@react-stately/overlays': 3.6.7(react@18.2.0) '@react-types/menu': 3.9.7(react@18.2.0) '@react-types/shared': 3.23.1(react@18.2.0) '@swc/helpers': 0.5.9 react: 18.2.0 dev: false + /@react-stately/menu@3.7.1(react@18.2.0): + resolution: {integrity: sha512-mX1w9HHzt+xal1WIT2xGrTQsoLvDwuB2R1Er1MBABs//MsJzccycatcgV/J/28m6tO5M9iuFQQvLV+i1dCtodg==} + peerDependencies: + react: ^18.2.0 + dependencies: + '@react-stately/overlays': 3.6.7(react@18.2.0) + '@react-types/menu': 3.9.9(react@18.2.0) + '@react-types/shared': 3.23.1(react@18.2.0) + '@swc/helpers': 0.5.9 + react: 18.2.0 + dev: false + /@react-stately/overlays@3.6.5(react@18.2.0): resolution: {integrity: sha512-U4rCFj6TPJPXLUvYXAcvh+yP/CO2W+7f0IuqP7ZZGE+Osk9qFkT+zRK5/6ayhBDFpmueNfjIEAzT9gYPQwNHFw==} peerDependencies: @@ -10402,6 +10643,17 @@ packages: react: 18.2.0 dev: false + /@react-stately/overlays@3.6.7(react@18.2.0): + resolution: {integrity: sha512-6zp8v/iNUm6YQap0loaFx6PlvN8C0DgWHNlrlzMtMmNuvjhjR0wYXVaTfNoUZBWj25tlDM81ukXOjpRXg9rLrw==} + peerDependencies: + react: ^18.2.0 + dependencies: + '@react-stately/utils': 3.10.1(react@18.2.0) + '@react-types/overlays': 3.8.7(react@18.2.0) + '@swc/helpers': 0.5.9 + react: 18.2.0 + dev: false + /@react-stately/radio@3.10.2(react@18.2.0): resolution: {integrity: sha512-JW5ZWiNMKcZvMTsuPeWJQLHXD5rlqy7Qk6fwUx/ZgeibvMBW/NnW19mm2+IMinzmbtERXvR6nsiA837qI+4dew==} peerDependencies: @@ -10415,15 +10667,15 @@ packages: react: 18.2.0 dev: false - /@react-stately/select@3.6.2(react@18.2.0): - resolution: {integrity: sha512-duOxdHKol93h6Ew6fap6Amz+zngoERKZLSKVm/8I8uaBgkoBhEeTFv7mlpHTgINxymMw3mMrvy6GL/gfKFwkqg==} + /@react-stately/select@3.6.4(react@18.2.0): + resolution: {integrity: sha512-whZgF1N53D0/dS8tOFdrswB0alsk5Q5620HC3z+5f2Hpi8gwgAZ8TYa+2IcmMYRiT+bxVuvEc/NirU9yPmqGbA==} peerDependencies: react: ^18.2.0 dependencies: - '@react-stately/form': 3.0.1(react@18.2.0) - '@react-stately/list': 3.10.3(react@18.2.0) - '@react-stately/overlays': 3.6.5(react@18.2.0) - '@react-types/select': 3.9.2(react@18.2.0) + '@react-stately/form': 3.0.3(react@18.2.0) + '@react-stately/list': 3.10.5(react@18.2.0) + '@react-stately/overlays': 3.6.7(react@18.2.0) + '@react-types/select': 3.9.4(react@18.2.0) '@react-types/shared': 3.23.1(react@18.2.0) '@swc/helpers': 0.5.9 react: 18.2.0 @@ -10434,13 +10686,25 @@ packages: peerDependencies: react: ^18.2.0 dependencies: - '@react-stately/collections': 3.10.5(react@18.2.0) + '@react-stately/collections': 3.10.7(react@18.2.0) '@react-stately/utils': 3.9.1(react@18.2.0) '@react-types/shared': 3.23.1(react@18.2.0) '@swc/helpers': 0.5.9 react: 18.2.0 dev: false + /@react-stately/selection@3.15.1(react@18.2.0): + resolution: {integrity: sha512-6TQnN9L0UY9w19B7xzb1P6mbUVBtW840Cw1SjgNXCB3NPaCf59SwqClYzoj8O2ZFzMe8F/nUJtfU1NS65/OLlw==} + peerDependencies: + react: ^18.2.0 + dependencies: + '@react-stately/collections': 3.10.7(react@18.2.0) + '@react-stately/utils': 3.10.1(react@18.2.0) + '@react-types/shared': 3.23.1(react@18.2.0) + '@swc/helpers': 0.5.9 + react: 18.2.0 + dev: false + /@react-stately/slider@3.5.2(react@18.2.0): resolution: {integrity: sha512-ntH3NLRG+AwVC7q4Dx9DcmMkMh9vmHjHNXAgaoqNjhvwfSIae7sQ69CkVe6XeJjIBy6LlH81Kgapz+ABe5a1ZA==} peerDependencies: @@ -10458,7 +10722,7 @@ packages: peerDependencies: react: ^18.2.0 dependencies: - '@react-stately/collections': 3.10.5(react@18.2.0) + '@react-stately/collections': 3.10.7(react@18.2.0) '@react-stately/flags': 3.0.1 '@react-stately/grid': 3.8.5(react@18.2.0) '@react-stately/selection': 3.14.3(react@18.2.0) @@ -10498,7 +10762,7 @@ packages: peerDependencies: react: ^18.2.0 dependencies: - '@react-stately/overlays': 3.6.5(react@18.2.0) + '@react-stately/overlays': 3.6.7(react@18.2.0) '@react-types/tooltip': 3.4.7(react@18.2.0) '@swc/helpers': 0.5.9 react: 18.2.0 @@ -10517,6 +10781,19 @@ packages: react: 18.2.0 dev: false + /@react-stately/tree@3.8.1(react@18.2.0): + resolution: {integrity: sha512-LOdkkruJWch3W89h4B/bXhfr0t0t1aRfEp+IMrrwdRAl23NaPqwl5ILHs4Xu5XDHqqhg8co73pHrJwUyiTWEjw==} + peerDependencies: + react: ^18.2.0 + dependencies: + '@react-stately/collections': 3.10.7(react@18.2.0) + '@react-stately/selection': 3.15.1(react@18.2.0) + '@react-stately/utils': 3.10.1(react@18.2.0) + '@react-types/shared': 3.23.1(react@18.2.0) + '@swc/helpers': 0.5.9 + react: 18.2.0 + dev: false + /@react-stately/utils@3.10.1(react@18.2.0): resolution: {integrity: sha512-VS/EHRyicef25zDZcM/ClpzYMC5i2YGN6uegOeQawmgfGjb02yaCX0F0zR69Pod9m2Hr3wunTbtpgVXvYbZItg==} peerDependencies: @@ -10573,6 +10850,15 @@ packages: react: 18.2.0 dev: false + /@react-types/button@3.9.4(react@18.2.0): + resolution: {integrity: sha512-raeQBJUxBp0axNF74TXB8/H50GY8Q3eV6cEKMbZFP1+Dzr09Ngv0tJBeW0ewAxAguNH5DRoMUAUGIXtSXskVdA==} + peerDependencies: + react: ^18.2.0 + dependencies: + '@react-types/shared': 3.23.1(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/calendar@3.4.4(react@18.2.0): resolution: {integrity: sha512-hV1Thmb/AES5OmfPvvmyjSkmsEULjiDfA7Yyy70L/YKuSNKb7Su+Bf2VnZuDW3ec+GxO4JJNlpJ0AkbphWBvcg==} peerDependencies: @@ -10592,8 +10878,8 @@ packages: react: 18.2.0 dev: false - /@react-types/combobox@3.10.1(react@18.2.0): - resolution: {integrity: sha512-XMno1rgVRNta49vf5nV7VJpVSVAV20tt79t618gG1qRKH5Kt2Cy8lz2fQ5vHG6UTv/6jUOvU8g5Pc93sLaTmoA==} + /@react-types/combobox@3.11.1(react@18.2.0): + resolution: {integrity: sha512-UNc3OHt5cUt5gCTHqhQIqhaWwKCpaNciD8R7eQazmHiA9fq8ROlV+7l3gdNgdhJbTf5Bu/V5ISnN7Y1xwL3zqQ==} peerDependencies: react: ^18.2.0 dependencies: @@ -10613,12 +10899,22 @@ packages: react: 18.2.0 dev: false + /@react-types/dialog@3.5.10(react@18.2.0): + resolution: {integrity: sha512-S9ga+edOLNLZw7/zVOnZdT5T40etpzUYBXEKdFPbxyPYnERvRxJAsC1/ASuBU9fQAXMRgLZzADWV+wJoGS/X9g==} + peerDependencies: + react: ^18.2.0 + dependencies: + '@react-types/overlays': 3.8.7(react@18.2.0) + '@react-types/shared': 3.23.1(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/dialog@3.5.8(react@18.2.0): resolution: {integrity: sha512-RX8JsMvty8ADHRqVEkppoynXLtN4IzUh8d5z88UEBbcvWKlHfd6bOBQjQcBH3AUue5wjfpPIt6brw2VzgBY/3Q==} peerDependencies: react: ^18.2.0 dependencies: - '@react-types/overlays': 3.8.5(react@18.2.0) + '@react-types/overlays': 3.8.7(react@18.2.0) '@react-types/shared': 3.23.1(react@18.2.0) react: 18.2.0 dev: false @@ -10658,12 +10954,31 @@ packages: react: 18.2.0 dev: false + /@react-types/listbox@3.4.9(react@18.2.0): + resolution: {integrity: sha512-S5G+WmNKUIOPZxZ4svWwWQupP3C6LmVfnf8QQmPDvwYXGzVc0WovkqUWyhhjJirFDswTXRCO9p0yaTHHIlkdwQ==} + peerDependencies: + react: ^18.2.0 + dependencies: + '@react-types/shared': 3.23.1(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/menu@3.9.7(react@18.2.0): resolution: {integrity: sha512-K6KhloJVoGsqwkdeez72fkNI9dfrmLI/sNrB4XuOKo2crDQ/eyZYWyJmzz8giz/tHME9w774k487rVoefoFh5w==} peerDependencies: react: ^18.2.0 dependencies: - '@react-types/overlays': 3.8.5(react@18.2.0) + '@react-types/overlays': 3.8.7(react@18.2.0) + '@react-types/shared': 3.23.1(react@18.2.0) + react: 18.2.0 + dev: false + + /@react-types/menu@3.9.9(react@18.2.0): + resolution: {integrity: sha512-FamUaPVs1Fxr4KOMI0YcR2rYZHoN7ypGtgiEiJ11v/tEPjPPGgeKDxii0McCrdOkjheatLN1yd2jmMwYj6hTDg==} + peerDependencies: + react: ^18.2.0 + dependencies: + '@react-types/overlays': 3.8.7(react@18.2.0) '@react-types/shared': 3.23.1(react@18.2.0) react: 18.2.0 dev: false @@ -10677,6 +10992,15 @@ packages: react: 18.2.0 dev: false + /@react-types/overlays@3.8.7(react@18.2.0): + resolution: {integrity: sha512-zCOYvI4at2DkhVpviIClJ7bRrLXYhSg3Z3v9xymuPH3mkiuuP/dm8mUCtkyY4UhVeUTHmrQh1bzaOP00A+SSQA==} + peerDependencies: + react: ^18.2.0 + dependencies: + '@react-types/shared': 3.23.1(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/progress@3.5.2(react@18.2.0): resolution: {integrity: sha512-aQql22kusEudsHwDEzq6y/Mh29AM+ftRDKdS5E5g4MkCY5J4FMbOYco1T5So83NIvvG9+eKcxPoJUMjQQACAyA==} peerDependencies: @@ -10704,6 +11028,15 @@ packages: react: 18.2.0 dev: false + /@react-types/select@3.9.4(react@18.2.0): + resolution: {integrity: sha512-xI7dnOW2st91fPPcv6hdtrTdcfetYiqZuuVPZ5TRobY7Q10/Zqqe/KqtOw1zFKUj9xqNJe4Ov3xP5GSdcO60Eg==} + peerDependencies: + react: ^18.2.0 + dependencies: + '@react-types/shared': 3.23.1(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/shared@3.23.1(react@18.2.0): resolution: {integrity: sha512-5d+3HbFDxGZjhbMBeFHRQhexMFt4pUce3okyRtUVKbbedQFUrtXSBg9VszgF2RTeQDKDkMCIQDtz5ccP/Lk1gw==} peerDependencies: @@ -10757,12 +11090,21 @@ packages: react: 18.2.0 dev: false + /@react-types/textfield@3.9.3(react@18.2.0): + resolution: {integrity: sha512-DoAY6cYOL0pJhgNGI1Rosni7g72GAt4OVr2ltEx2S9ARmFZ0DBvdhA9lL2nywcnKMf27PEJcKMXzXc10qaHsJw==} + peerDependencies: + react: ^18.2.0 + dependencies: + '@react-types/shared': 3.23.1(react@18.2.0) + react: 18.2.0 + dev: false + /@react-types/tooltip@3.4.7(react@18.2.0): resolution: {integrity: sha512-rV4HZRQxLRNhe24yATOxnFQtGRUmsR7mqxMupXCmd1vrw8h+rdKlQv1zW2q8nALAKNmnRXZJHxYQ1SFzb98fgg==} peerDependencies: react: ^18.2.0 dependencies: - '@react-types/overlays': 3.8.5(react@18.2.0) + '@react-types/overlays': 3.8.7(react@18.2.0) '@react-types/shared': 3.23.1(react@18.2.0) react: 18.2.0 dev: false From 4718d4c0a5eb6201063fb8cab70943acda3338e4 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Mon, 10 Jun 2024 22:52:09 +0800 Subject: [PATCH 16/34] chore(autocomplete): change back to focus --- packages/components/autocomplete/src/use-autocomplete.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/components/autocomplete/src/use-autocomplete.ts b/packages/components/autocomplete/src/use-autocomplete.ts index 4f7c809395..3d8aeef07c 100644 --- a/packages/components/autocomplete/src/use-autocomplete.ts +++ b/packages/components/autocomplete/src/use-autocomplete.ts @@ -137,8 +137,7 @@ export function useAutocomplete(originalProps: UseAutocomplete as, label, isLoading, - // TODO: change back to focus later - menuTrigger = "manual", + menuTrigger = "focus", filterOptions = { sensitivity: "base", }, From 7057442ccba0355597811dcf1f43356cfaf9523d Mon Sep 17 00:00:00 2001 From: WK Wong Date: Mon, 10 Jun 2024 22:55:04 +0800 Subject: [PATCH 17/34] feat(changeset): update changeset --- .changeset/clever-gifts-joke.md | 5 +++++ .changeset/cold-peas-dream.md | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 .changeset/cold-peas-dream.md diff --git a/.changeset/clever-gifts-joke.md b/.changeset/clever-gifts-joke.md index 20effb69ba..8c1ef25d03 100644 --- a/.changeset/clever-gifts-joke.md +++ b/.changeset/clever-gifts-joke.md @@ -1,5 +1,10 @@ --- "@nextui-org/popover": patch +"@nextui-org/autocomplete": patch +"@nextui-org/date-picker": patch +"@nextui-org/dropdown": patch +"@nextui-org/select": patch +"@nextui-org/aria-utils": patch --- Fix popover focus issue (#3171, #2992) diff --git a/.changeset/cold-peas-dream.md b/.changeset/cold-peas-dream.md new file mode 100644 index 0000000000..2f85f9495b --- /dev/null +++ b/.changeset/cold-peas-dream.md @@ -0,0 +1,6 @@ +--- +"@nextui-org/modal": patch +"@nextui-org/popover": patch +--- + +remove `disableFocusManagement` from Overlay From ea315c575621253b39e42e663a64885d25eea5d3 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 11 Jun 2024 14:05:34 +0800 Subject: [PATCH 18/34] chore(docs): update type in onSelectionChange --- apps/docs/app/examples/autocomplete/fully-controlled/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/app/examples/autocomplete/fully-controlled/page.tsx b/apps/docs/app/examples/autocomplete/fully-controlled/page.tsx index e2e21baa6f..ee671adf45 100644 --- a/apps/docs/app/examples/autocomplete/fully-controlled/page.tsx +++ b/apps/docs/app/examples/autocomplete/fully-controlled/page.tsx @@ -53,7 +53,7 @@ export default function Page() { // Specify how each of the Autocomplete values should change when an // option is selected from the list box - const onSelectionChange = (key: React.Key) => { + const onSelectionChange = (key: React.Key | null) => { setFieldState((prevState) => { let selectedItem = prevState.items.find((option) => option.value === key); From 1e08cda149e056c2f22d0fd06300270133790f51 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 11 Jun 2024 14:13:24 +0800 Subject: [PATCH 19/34] fix(popover): revise popover test case --- .../popover/__tests__/popover.test.tsx | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/components/popover/__tests__/popover.test.tsx b/packages/components/popover/__tests__/popover.test.tsx index 9b6c449bdc..bc7236a243 100644 --- a/packages/components/popover/__tests__/popover.test.tsx +++ b/packages/components/popover/__tests__/popover.test.tsx @@ -247,9 +247,7 @@ describe("Popover", () => { const wrapper = render( - +

This is the content of the popover.

@@ -257,19 +255,15 @@ describe("Popover", () => {
, ); - const trigger = wrapper.getByTestId("trigger-test"); + const trigger = wrapper.getByTestId("popover-trigger"); - // open popover await act(async () => { + // open popover await userEvent.click(trigger); - }); - - // close popover - await act(async () => { + // close popover await userEvent.click(trigger); + // assert that the focus is restored back to trigger + expect(trigger).toHaveFocus(); }); - - // assert that the focus is restored back to trigger - expect(trigger).toHaveFocus(); }); }); From 6bd216f6e221b4a9a573c5d41a84e91d6e59674b Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 11 Jun 2024 16:16:49 +0800 Subject: [PATCH 20/34] chore(deps): add @nextui-org/aria-utils --- packages/components/autocomplete/package.json | 1 + packages/components/date-picker/package.json | 1 + packages/components/dropdown/package.json | 1 + pnpm-lock.yaml | 9 +++++++++ 4 files changed, 12 insertions(+) diff --git a/packages/components/autocomplete/package.json b/packages/components/autocomplete/package.json index 3b2815e7b3..29788656d7 100644 --- a/packages/components/autocomplete/package.json +++ b/packages/components/autocomplete/package.json @@ -41,6 +41,7 @@ "@nextui-org/system": ">=2.0.0" }, "dependencies": { + "@nextui-org/aria-utils": "workspace:*", "@nextui-org/listbox": "workspace:*", "@nextui-org/popover": "workspace:*", "@nextui-org/react-utils": "workspace:*", diff --git a/packages/components/date-picker/package.json b/packages/components/date-picker/package.json index a669e5a169..8cf1bfc22e 100644 --- a/packages/components/date-picker/package.json +++ b/packages/components/date-picker/package.json @@ -47,6 +47,7 @@ "@nextui-org/button": "workspace:*", "@nextui-org/date-input": "workspace:*", "@nextui-org/shared-icons": "workspace:*", + "@nextui-org/aria-utils": "workspace:*", "@react-stately/overlays": "3.6.5", "@react-stately/utils": "3.9.1", "@internationalized/date": "^3.5.2", diff --git a/packages/components/dropdown/package.json b/packages/components/dropdown/package.json index 58c08d8c08..1dbfbe1d1f 100644 --- a/packages/components/dropdown/package.json +++ b/packages/components/dropdown/package.json @@ -45,6 +45,7 @@ "@nextui-org/popover": "workspace:*", "@nextui-org/shared-utils": "workspace:*", "@nextui-org/react-utils": "workspace:*", + "@nextui-org/aria-utils": "workspace:*", "@react-aria/menu": "3.13.1", "@react-aria/utils": "3.24.1", "@react-stately/menu": "3.6.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 392f1a9fc2..2d58c1d113 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -686,6 +686,9 @@ importers: packages/components/autocomplete: dependencies: + '@nextui-org/aria-utils': + specifier: workspace:* + version: link:../../utilities/aria-utils '@nextui-org/button': specifier: workspace:* version: link:../button @@ -1324,6 +1327,9 @@ importers: '@internationalized/date': specifier: ^3.5.2 version: 3.5.2 + '@nextui-org/aria-utils': + specifier: workspace:* + version: link:../../utilities/aria-utils '@nextui-org/button': specifier: workspace:* version: link:../button @@ -1422,6 +1428,9 @@ importers: packages/components/dropdown: dependencies: + '@nextui-org/aria-utils': + specifier: workspace:* + version: link:../../utilities/aria-utils '@nextui-org/menu': specifier: workspace:* version: link:../menu From 12168cc85d1af504d687e21d327ff7b8822db981 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 11 Jun 2024 16:17:07 +0800 Subject: [PATCH 21/34] fix(autocomplete): add ariaShouldCloseOnInteractOutside --- packages/components/autocomplete/src/use-autocomplete.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/components/autocomplete/src/use-autocomplete.ts b/packages/components/autocomplete/src/use-autocomplete.ts index 3d8aeef07c..514c5bd069 100644 --- a/packages/components/autocomplete/src/use-autocomplete.ts +++ b/packages/components/autocomplete/src/use-autocomplete.ts @@ -18,6 +18,7 @@ import {chain, mergeProps} from "@react-aria/utils"; import {ButtonProps} from "@nextui-org/button"; import {AsyncLoadable, PressEvent} from "@react-types/shared"; import {useComboBox} from "@react-aria/combobox"; +import {ariaShouldCloseOnInteractOutside} from "@nextui-org/aria-utils"; interface Props extends Omit, keyof ComboBoxProps> { /** @@ -453,7 +454,7 @@ export function useAutocomplete(originalProps: UseAutocomplete }, shouldCloseOnInteractOutside: popoverProps?.shouldCloseOnInteractOutside ? popoverProps.shouldCloseOnInteractOutside - : () => true, + : (element: Element) => ariaShouldCloseOnInteractOutside(element, inputWrapperRef, state), } as unknown as PopoverProps; }; From 8142716a220c230a855ba0c4b2335ca52c85e07c Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 11 Jun 2024 16:17:17 +0800 Subject: [PATCH 22/34] fix(date-picker): add ariaShouldCloseOnInteractOutside --- packages/components/date-picker/src/use-date-picker.ts | 3 ++- packages/components/date-picker/src/use-date-range-picker.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/components/date-picker/src/use-date-picker.ts b/packages/components/date-picker/src/use-date-picker.ts index e330b46315..8d4957bdf2 100644 --- a/packages/components/date-picker/src/use-date-picker.ts +++ b/packages/components/date-picker/src/use-date-picker.ts @@ -15,6 +15,7 @@ import {useDatePickerState} from "@react-stately/datepicker"; import {AriaDatePickerProps, useDatePicker as useAriaDatePicker} from "@react-aria/datepicker"; import {clsx, dataAttr, objectToDeps} from "@nextui-org/shared-utils"; import {mergeProps} from "@react-aria/utils"; +import {ariaShouldCloseOnInteractOutside} from "@nextui-org/aria-utils"; import {useDatePickerBase} from "./use-date-picker-base"; @@ -181,7 +182,7 @@ export function useDatePicker({ }, shouldCloseOnInteractOutside: popoverProps?.shouldCloseOnInteractOutside ? popoverProps.shouldCloseOnInteractOutside - : () => true, + : (element: Element) => ariaShouldCloseOnInteractOutside(element, domRef, state), }; }; diff --git a/packages/components/date-picker/src/use-date-range-picker.ts b/packages/components/date-picker/src/use-date-range-picker.ts index a74992ce7b..42df170d40 100644 --- a/packages/components/date-picker/src/use-date-range-picker.ts +++ b/packages/components/date-picker/src/use-date-range-picker.ts @@ -21,6 +21,7 @@ import {useDateRangePicker as useAriaDateRangePicker} from "@react-aria/datepick import {clsx, dataAttr, objectToDeps} from "@nextui-org/shared-utils"; import {mergeProps} from "@react-aria/utils"; import {dateRangePicker, dateInput} from "@nextui-org/theme"; +import {ariaShouldCloseOnInteractOutside} from "@nextui-org/aria-utils"; import {useDatePickerBase} from "./use-date-picker-base"; interface Props @@ -217,7 +218,7 @@ export function useDateRangePicker({ }, shouldCloseOnInteractOutside: popoverProps?.shouldCloseOnInteractOutside ? popoverProps.shouldCloseOnInteractOutside - : () => true, + : (element: Element) => ariaShouldCloseOnInteractOutside(element, domRef, state), } as PopoverProps; }; From dd14f90659bf6730b4a6f10d833ee9f67f3ea7c9 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 11 Jun 2024 16:18:22 +0800 Subject: [PATCH 23/34] fix(select): add ariaShouldCloseOnInteractOutside --- packages/components/select/src/use-select.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/components/select/src/use-select.ts b/packages/components/select/src/use-select.ts index 8d73387f77..fb3b730b6e 100644 --- a/packages/components/select/src/use-select.ts +++ b/packages/components/select/src/use-select.ts @@ -27,6 +27,7 @@ import { } from "@nextui-org/use-aria-multiselect"; import {SpinnerProps} from "@nextui-org/spinner"; import {useSafeLayoutEffect} from "@nextui-org/use-safe-layout-effect"; +import {ariaShouldCloseOnInteractOutside} from "@nextui-org/aria-utils"; import {CollectionChildren} from "@react-types/shared"; export type SelectedItemProps = { @@ -523,7 +524,7 @@ export function useSelect(originalProps: UseSelectProps) { : slotsProps.popoverProps?.offset, shouldCloseOnInteractOutside: popoverProps?.shouldCloseOnInteractOutside ? popoverProps.shouldCloseOnInteractOutside - : () => true, + : (element: Element) => ariaShouldCloseOnInteractOutside(element, triggerRef, state), } as PopoverProps; }, [ From 438d83d1243fb4cd85a832d56e289f3093d66c0d Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 11 Jun 2024 16:18:35 +0800 Subject: [PATCH 24/34] chore(deps): add @nextui-org/aria-utils --- packages/components/select/package.json | 1 + pnpm-lock.yaml | 3 +++ 2 files changed, 4 insertions(+) diff --git a/packages/components/select/package.json b/packages/components/select/package.json index 2514e8ce6f..8ee374227d 100644 --- a/packages/components/select/package.json +++ b/packages/components/select/package.json @@ -41,6 +41,7 @@ "@nextui-org/system": ">=2.0.0" }, "dependencies": { + "@nextui-org/aria-utils": "workspace:*", "@nextui-org/listbox": "workspace:*", "@nextui-org/popover": "workspace:*", "@nextui-org/spinner": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2d58c1d113..ee69fa3855 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2229,6 +2229,9 @@ importers: packages/components/select: dependencies: + '@nextui-org/aria-utils': + specifier: workspace:* + version: link:../../utilities/aria-utils '@nextui-org/listbox': specifier: workspace:* version: link:../listbox From 8fd22daef9eee05fd0813be0bb89a47658f7b082 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 11 Jun 2024 16:27:50 +0800 Subject: [PATCH 25/34] fix(dropdown): add ariaShouldCloseOnInteractOutside --- packages/components/dropdown/src/use-dropdown.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/components/dropdown/src/use-dropdown.ts b/packages/components/dropdown/src/use-dropdown.ts index cbde20aa32..0124669b95 100644 --- a/packages/components/dropdown/src/use-dropdown.ts +++ b/packages/components/dropdown/src/use-dropdown.ts @@ -8,6 +8,7 @@ import {useMenuTrigger} from "@react-aria/menu"; import {dropdown} from "@nextui-org/theme"; import {clsx} from "@nextui-org/shared-utils"; import {ReactRef, mergeRefs} from "@nextui-org/react-utils"; +import {ariaShouldCloseOnInteractOutside} from "@nextui-org/aria-utils"; import {useMemo, useRef} from "react"; import {mergeProps} from "@react-aria/utils"; import {MenuProps} from "@nextui-org/menu"; @@ -123,7 +124,7 @@ export function useDropdown(props: UseDropdownProps) { }, shouldCloseOnInteractOutside: popoverProps?.shouldCloseOnInteractOutside ? popoverProps.shouldCloseOnInteractOutside - : () => true, + : (element: Element) => ariaShouldCloseOnInteractOutside(element, triggerRef, state), }; }; From 985895b5c3780226ab9d7a6b3b9583b149c2153f Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 11 Jun 2024 16:28:21 +0800 Subject: [PATCH 26/34] feat(utilities): rewrite ariaShouldCloseOnInteractOutside --- packages/utilities/aria-utils/src/index.ts | 1 + .../ariaShouldCloseOnInteractOutside.ts | 34 +++++++++++++++++++ .../aria-utils/src/overlays/index.ts | 1 + 3 files changed, 36 insertions(+) create mode 100644 packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts diff --git a/packages/utilities/aria-utils/src/index.ts b/packages/utilities/aria-utils/src/index.ts index 8e70c1b183..6b8d27ad3f 100644 --- a/packages/utilities/aria-utils/src/index.ts +++ b/packages/utilities/aria-utils/src/index.ts @@ -7,6 +7,7 @@ export {isNonContiguousSelectionModifier, isCtrlKeyPressed} from "./utils"; export { ariaHideOutside, + ariaShouldCloseOnInteractOutside, getTransformOrigins, toReactAriaPlacement, toOverlayPlacement, diff --git a/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts b/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts new file mode 100644 index 0000000000..b1e5fafc9f --- /dev/null +++ b/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts @@ -0,0 +1,34 @@ +import {RefObject} from "react"; + +/** + * Used to handle the outside interaction for popover-based components + * e.g. dropdown, datepicker, date-range-picker, popover, select, autocomplete etc + * @param element - the element outside of the popover ref, originally from `shouldCloseOnInteractOutside` + * @param triggerRef - The trigger ref object + * @param state - The state from the popover component + * @returns - a boolean value which is same as shouldCloseOnInteractOutside + */ +export const ariaShouldCloseOnInteractOutside = ( + element: Element, + triggerRef: RefObject, + state: any, +) => { + const trigger = triggerRef?.current!; + + if (!trigger || !trigger.contains(element)) { + const startElements = document.querySelectorAll("body > span[data-focus-scope-start]"); + let focusScopeElements: Element[] = []; + + startElements.forEach((startElement) => { + focusScopeElements.push(startElement.nextElementSibling!); + }); + + if (focusScopeElements.length === 1) { + state.close(); + + return false; + } + } + + return !trigger || !trigger.contains(element); +}; diff --git a/packages/utilities/aria-utils/src/overlays/index.ts b/packages/utilities/aria-utils/src/overlays/index.ts index ccf839f2d9..6999e8b90c 100644 --- a/packages/utilities/aria-utils/src/overlays/index.ts +++ b/packages/utilities/aria-utils/src/overlays/index.ts @@ -9,3 +9,4 @@ export { } from "./utils"; export {ariaHideOutside} from "./ariaHideOutside"; +export {ariaShouldCloseOnInteractOutside} from "./ariaShouldCloseOnInteractOutside"; From e2d802275679520e67b51b2a0d38733f2e4e0d93 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 11 Jun 2024 17:52:24 +0800 Subject: [PATCH 27/34] fix(popover): use ariaShouldCloseOnInteractOutside --- .../popover/src/use-aria-popover.ts | 38 ++++--------------- 1 file changed, 7 insertions(+), 31 deletions(-) diff --git a/packages/components/popover/src/use-aria-popover.ts b/packages/components/popover/src/use-aria-popover.ts index 0aad90c3b8..f9f323f5b1 100644 --- a/packages/components/popover/src/use-aria-popover.ts +++ b/packages/components/popover/src/use-aria-popover.ts @@ -6,7 +6,12 @@ import { useOverlayPosition, AriaOverlayProps, } from "@react-aria/overlays"; -import {OverlayPlacement, ariaHideOutside, toReactAriaPlacement} from "@nextui-org/aria-utils"; +import { + OverlayPlacement, + ariaHideOutside, + toReactAriaPlacement, + ariaShouldCloseOnInteractOutside, +} from "@nextui-org/aria-utils"; import {OverlayTriggerState} from "@react-stately/overlays"; import {mergeProps} from "@react-aria/utils"; import {useSafeLayoutEffect} from "@nextui-org/use-safe-layout-effect"; @@ -75,36 +80,7 @@ export function useReactAriaPopover( isKeyboardDismissDisabled, shouldCloseOnInteractOutside: shouldCloseOnInteractOutside ? shouldCloseOnInteractOutside - : (element: Element) => { - const trigger = triggerRef?.current!; - - if (!trigger || !trigger.contains(element)) { - const startElements = document.querySelectorAll("[data-focus-scope-start]"); - const endElements = document.querySelectorAll("[data-focus-scope-end]"); - let focusScopeElements = []; - - startElements.forEach((startElement, index) => { - if (endElements[index]) { - let currentElement = startElement.nextElementSibling; - let elementsBetween = []; - - while (currentElement && currentElement !== endElements[index]) { - elementsBetween.push(currentElement); - currentElement = currentElement.nextElementSibling; - } - focusScopeElements.push(elementsBetween); - } - }); - - if (focusScopeElements.length === 1) { - state.close(); - - return false; - } - } - - return !trigger || !trigger.contains(element); - }, + : (element: Element) => ariaShouldCloseOnInteractOutside(element, triggerRef, state), }, popoverRef, ); From d06075f3033a13b144d0ccc3efd71a6855ce4cfb Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 11 Jun 2024 17:59:55 +0800 Subject: [PATCH 28/34] fix(autocomplete): add back shouldFocus --- .../autocomplete/src/use-autocomplete.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/components/autocomplete/src/use-autocomplete.ts b/packages/components/autocomplete/src/use-autocomplete.ts index 514c5bd069..68019369cf 100644 --- a/packages/components/autocomplete/src/use-autocomplete.ts +++ b/packages/components/autocomplete/src/use-autocomplete.ts @@ -202,6 +202,9 @@ export function useAutocomplete(originalProps: UseAutocomplete const inputRef = useDOMRef(ref); const scrollShadowRef = useDOMRef(scrollRefProp); + // control the input focus behaviours internally + const shouldFocus = useRef(false); + const { buttonProps, inputProps, @@ -328,6 +331,15 @@ export function useAutocomplete(originalProps: UseAutocomplete } }, [isOpen]); + // react aria has different focus strategies internally + // hence, handle focus behaviours on our side for better flexibilty + useEffect(() => { + const action = shouldFocus.current || isOpen ? "focus" : "blur"; + + inputRef?.current?.[action](); + if (action === "blur") shouldFocus.current = false; + }, [shouldFocus.current, isOpen]); + // to prevent the error message: // stopPropagation is now the default behavior for events in React Spectrum. // You can use continuePropagation() to revert this behavior. @@ -454,7 +466,8 @@ export function useAutocomplete(originalProps: UseAutocomplete }, shouldCloseOnInteractOutside: popoverProps?.shouldCloseOnInteractOutside ? popoverProps.shouldCloseOnInteractOutside - : (element: Element) => ariaShouldCloseOnInteractOutside(element, inputWrapperRef, state), + : (element: Element) => + ariaShouldCloseOnInteractOutside(element, inputWrapperRef, state, shouldFocus), } as unknown as PopoverProps; }; From 68779022e8b5d37a9e751d8c847b08cb8cf2cb37 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 11 Jun 2024 18:00:09 +0800 Subject: [PATCH 29/34] fix(utilities): include shouldFocus logic --- .../src/overlays/ariaShouldCloseOnInteractOutside.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts b/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts index b1e5fafc9f..bf0b6ef78c 100644 --- a/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts +++ b/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts @@ -1,4 +1,4 @@ -import {RefObject} from "react"; +import {MutableRefObject, RefObject} from "react"; /** * Used to handle the outside interaction for popover-based components @@ -6,16 +6,22 @@ import {RefObject} from "react"; * @param element - the element outside of the popover ref, originally from `shouldCloseOnInteractOutside` * @param triggerRef - The trigger ref object * @param state - The state from the popover component + * @param shouldFocus - a mutable ref boolean object to control the focus state + * (used in input-based component such as autocomplete) * @returns - a boolean value which is same as shouldCloseOnInteractOutside */ export const ariaShouldCloseOnInteractOutside = ( element: Element, triggerRef: RefObject, state: any, + shouldFocus?: MutableRefObject, ) => { const trigger = triggerRef?.current!; if (!trigger || !trigger.contains(element)) { + // blur the component (e.g. autocomplete) + if (shouldFocus) shouldFocus.current = false; + const startElements = document.querySelectorAll("body > span[data-focus-scope-start]"); let focusScopeElements: Element[] = []; @@ -28,6 +34,9 @@ export const ariaShouldCloseOnInteractOutside = ( return false; } + } else { + // otherwise the component (e.g. autocomplete) should keep focused + if (shouldFocus) shouldFocus.current = true; } return !trigger || !trigger.contains(element); From 255eddbcbf21391e088d544aa09c6fe379d50e50 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 11 Jun 2024 18:01:12 +0800 Subject: [PATCH 30/34] chore(utilities): remove ! --- .../aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts b/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts index bf0b6ef78c..01e30cb2fc 100644 --- a/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts +++ b/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts @@ -16,7 +16,7 @@ export const ariaShouldCloseOnInteractOutside = ( state: any, shouldFocus?: MutableRefObject, ) => { - const trigger = triggerRef?.current!; + const trigger = triggerRef?.current; if (!trigger || !trigger.contains(element)) { // blur the component (e.g. autocomplete) From d3ab74cc4f8442c539252170388935faf46443ca Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 11 Jun 2024 18:06:31 +0800 Subject: [PATCH 31/34] refactor(aria-utils): add more comments --- .../src/overlays/ariaShouldCloseOnInteractOutside.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts b/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts index 01e30cb2fc..371c677b18 100644 --- a/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts +++ b/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts @@ -19,9 +19,13 @@ export const ariaShouldCloseOnInteractOutside = ( const trigger = triggerRef?.current; if (!trigger || !trigger.contains(element)) { + // if clicking outside the trigger, // blur the component (e.g. autocomplete) if (shouldFocus) shouldFocus.current = false; + // if there is focus scope block, there will be a pair of span[data-focus-scope-start] and span[data-focus-scope-end] + // the element with focus trap resides inbetween these two blocks + // we push all the elements in focus scope to `focusScopeElements` const startElements = document.querySelectorAll("body > span[data-focus-scope-start]"); let focusScopeElements: Element[] = []; @@ -29,6 +33,10 @@ export const ariaShouldCloseOnInteractOutside = ( focusScopeElements.push(startElement.nextElementSibling!); }); + // if there is just one focusScopeElement, we close the state + // e.g. open a popover A -> click popover B + // then popover A should be closed and popover B should be open + // TODO: handle cases like modal > popover A -> click modal > popover B if (focusScopeElements.length === 1) { state.close(); From d8f249d23774a1870aa41c6b2d1ba38b3939cb6e Mon Sep 17 00:00:00 2001 From: WK Wong Date: Tue, 11 Jun 2024 18:10:25 +0800 Subject: [PATCH 32/34] chore(changeset): update packages --- .changeset/clever-gifts-joke.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/.changeset/clever-gifts-joke.md b/.changeset/clever-gifts-joke.md index 8c1ef25d03..57a7802cf6 100644 --- a/.changeset/clever-gifts-joke.md +++ b/.changeset/clever-gifts-joke.md @@ -1,9 +1,6 @@ --- "@nextui-org/popover": patch "@nextui-org/autocomplete": patch -"@nextui-org/date-picker": patch -"@nextui-org/dropdown": patch -"@nextui-org/select": patch "@nextui-org/aria-utils": patch --- From a66ac2dd2191466c0a12a04153a9d36f8167ab4b Mon Sep 17 00:00:00 2001 From: WK Wong Date: Wed, 12 Jun 2024 13:30:31 +0800 Subject: [PATCH 33/34] refactor(aria-utils): add more comments --- .../src/overlays/ariaShouldCloseOnInteractOutside.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts b/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts index 371c677b18..3a1c002a9b 100644 --- a/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts +++ b/packages/utilities/aria-utils/src/overlays/ariaShouldCloseOnInteractOutside.ts @@ -37,6 +37,10 @@ export const ariaShouldCloseOnInteractOutside = ( // e.g. open a popover A -> click popover B // then popover A should be closed and popover B should be open // TODO: handle cases like modal > popover A -> click modal > popover B + // we should close the popover when it is the last opened + // however, currently ariaShouldCloseOnInteractOutside is called recursively + // and we need a way to check if there is something closed before that (i.e. nested elements) + // if so, popover shouldn't be closed in this case if (focusScopeElements.length === 1) { state.close(); From ff5445a381cec6e996864a39ddb927c4076a6f46 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Wed, 12 Jun 2024 15:58:20 +0800 Subject: [PATCH 34/34] feat(popover): add test --- .../popover/__tests__/popover.test.tsx | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/packages/components/popover/__tests__/popover.test.tsx b/packages/components/popover/__tests__/popover.test.tsx index bc7236a243..53d463a699 100644 --- a/packages/components/popover/__tests__/popover.test.tsx +++ b/packages/components/popover/__tests__/popover.test.tsx @@ -4,6 +4,7 @@ import userEvent from "@testing-library/user-event"; import {Button} from "@nextui-org/button"; import {Popover, PopoverContent, PopoverTrigger} from "../src"; +import {Select, SelectItem} from "../../select/src"; // e.g. console.error Warning: Function components cannot be given refs. // Attempts to access this ref will fail. Did you mean to use React.forwardRef()? @@ -266,4 +267,51 @@ describe("Popover", () => { expect(trigger).toHaveFocus(); }); }); + + it("should not close popover if nested select is closed", async () => { + const wrapper = render( + + + + + + + + , + ); + + const popover = wrapper.getByTestId("popover"); + + await act(async () => { + // open popover + await userEvent.click(popover); + }); + + // assert that the popover is open + expect(popover).toHaveAttribute("aria-expanded", "true"); + + const select = wrapper.getByTestId("select"); + + await act(async () => { + // open select + await userEvent.click(select); + }); + + // assert that the select is open + expect(select).toHaveAttribute("aria-expanded", "true"); + + await act(async () => { + await userEvent.click(document.body); + }); + + // assert that the select is closed + expect(select).toHaveAttribute("aria-expanded", "false"); + + // assert that the popover is still open + expect(popover).toHaveAttribute("aria-expanded", "true"); + }); });