fix(dropdown): _a2.find is not a function (#3762)

* feat(dropdown): add "should respect closeOnSelect setting of DropdownItem (dynamic)"

* chore(changeset): add changeset

* fix(dropdown): find is not function error when click dropdown item (#3763)

* fix: find is not function error when click dropdown item

* fix: find is not function error when click dropdown item

* fix: type error

* fix: optimization

* refactor(dropdown): must have return value

* chore(changeset): revise changeset

---------

Co-authored-by: winches <329487092@qq.com>
This commit is contained in:
աӄա 2024-09-15 19:17:00 +08:00 committed by GitHub
parent 2293884229
commit 8fecb5afa9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 96 additions and 14 deletions

View File

@ -0,0 +1,6 @@
---
"@nextui-org/dropdown": patch
"@nextui-org/use-aria-menu": patch
---
fixed `_a2.find` is not a function (#3761)

View File

@ -796,7 +796,7 @@ describe("Keyboard interactions", () => {
logSpy.mockRestore();
});
it("should respect closeOnSelect setting of DropdownItem", async () => {
it("should respect closeOnSelect setting of DropdownItem (static)", async () => {
const onOpenChange = jest.fn();
const wrapper = render(
<Dropdown onOpenChange={onOpenChange}>
@ -831,4 +831,51 @@ describe("Keyboard interactions", () => {
expect(onOpenChange).toBeCalledTimes(2);
});
});
it("should respect closeOnSelect setting of DropdownItem (dynamic)", async () => {
const onOpenChange = jest.fn();
const items = [
{
key: "new",
label: "New file",
},
{
key: "copy",
label: "Copy link",
},
];
const wrapper = render(
<Dropdown onOpenChange={onOpenChange}>
<DropdownTrigger>
<Button data-testid="trigger-test">Trigger</Button>
</DropdownTrigger>
<DropdownMenu aria-label="Actions" items={items}>
{(item) => (
<DropdownItem key={item.key} closeOnSelect={item.key !== "new"}>
{item.label}
</DropdownItem>
)}
</DropdownMenu>
</Dropdown>,
);
let triggerButton = wrapper.getByTestId("trigger-test");
act(() => {
triggerButton.click();
});
expect(onOpenChange).toBeCalledTimes(1);
let menuItems = wrapper.getAllByRole("menuitem");
await act(async () => {
await userEvent.click(menuItems[0]);
expect(onOpenChange).toBeCalledTimes(1);
});
await act(async () => {
await userEvent.click(menuItems[1]);
expect(onOpenChange).toBeCalledTimes(2);
});
});
});

View File

@ -13,6 +13,7 @@ import {ariaShouldCloseOnInteractOutside} from "@nextui-org/aria-utils";
import {useMemo, useRef} from "react";
import {mergeProps} from "@react-aria/utils";
import {MenuProps} from "@nextui-org/menu";
import {CollectionElement} from "@react-types/shared";
interface Props extends HTMLNextUIProps<"div"> {
/**
@ -42,6 +43,40 @@ interface Props extends HTMLNextUIProps<"div"> {
export type UseDropdownProps = Props & Omit<PopoverProps, "children" | "color" | "variant">;
const getMenuItem = <T extends object>(props: Partial<MenuProps<T>> | undefined, key: string) => {
if (props) {
const mergedChildren = Array.isArray(props.children)
? props.children
: [...(props?.items || [])];
if (mergedChildren && mergedChildren.length) {
const item = ((mergedChildren as CollectionElement<T>[]).find((item) => {
if (item.key === key) {
return item;
}
}) || {}) as {props: MenuProps};
return item;
}
}
return null;
};
const getCloseOnSelect = <T extends object>(
props: Partial<MenuProps<T>> | undefined,
key: string,
item?: any,
) => {
const mergedItem = item || getMenuItem(props, key);
if (mergedItem && mergedItem.props && "closeOnSelect" in mergedItem.props) {
return mergedItem.props.closeOnSelect;
}
return props?.closeOnSelect;
};
export function useDropdown(props: UseDropdownProps) {
const globalContext = useProviderContext();
@ -152,16 +187,10 @@ export function useDropdown(props: UseDropdownProps) {
menuProps,
closeOnSelect,
...mergeProps(props, {
onAction: (key: any) => {
// @ts-ignore
const item = props?.children?.find((item) => item.key === key);
onAction: (key: any, item?: any) => {
const closeOnSelect = getCloseOnSelect(props, key, item);
if (item?.props?.closeOnSelect === false) {
onMenuAction(false);
return;
}
onMenuAction(props?.closeOnSelect);
onMenuAction(closeOnSelect);
},
onClose: state.close,
}),

View File

@ -94,7 +94,7 @@ export interface AriaMenuItemProps
* Handler that is called when the user activates the item.
* @deprecated - pass to the menu instead.
*/
onAction?: (key: Key) => void;
onAction?: (key: Key, item: any) => void;
/**
* The native button click event handler
@ -167,11 +167,11 @@ export function useMenuItem<T>(
if (props.onAction) {
// @ts-ignore
props.onAction(key);
props.onAction(key, item);
// @ts-ignore
} else if (data.onAction) {
// @ts-ignore
data.onAction(key);
data.onAction(key, item);
}
if (e.target instanceof HTMLAnchorElement) {

View File

@ -24,7 +24,7 @@ export interface AriaMenuOptions<T> extends Omit<AriaMenuProps<T>, "children">,
interface MenuData {
onClose?: () => void;
onAction?: (key: Key) => void;
onAction?: (key: Key, item: any) => void;
}
export const menuData = new WeakMap<TreeState<unknown>, MenuData>();