From 06f66856cda2e6bef4fe8c87ec4ed78eb1ddf4a0 Mon Sep 17 00:00:00 2001 From: Vehbi Karaagac <42275242+Xapollon430@users.noreply.github.com> Date: Thu, 12 Nov 2026 11:55:39 -0500 Subject: [PATCH] fix(dropdown): block popup actions while disabled --- .coderocket/source-manifest.json | 7 +++-- packages/react/src/components/dropdown.tsx | 4 +-- packages/react/src/dropdown.test.ts | 34 ++++++++++++++++++++++ packages/vue/src/components/dropdown.vue | 8 ++--- packages/vue/src/display.test.ts | 20 +++++++++++++ 5 files changed, 64 insertions(+), 9 deletions(-) create mode 100644 packages/react/src/dropdown.test.ts diff --git a/.coderocket/source-manifest.json b/.coderocket/source-manifest.json index ed73452..135d5a1 100644 --- a/.coderocket/source-manifest.json +++ b/.coderocket/source-manifest.json @@ -20,7 +20,7 @@ "packages/react/src/components/date-picker.tsx": "cbdfe9b6a3e5881b825f518b3278036fa9facf71fdce7859d44692bfc03c8989", "packages/react/src/components/dialog.tsx": "5d7d9e338a64d78d4844ccdb1bf2b52b9894783206871e86c787f337c956c3e2", "packages/react/src/components/drawer.tsx": "07a0fa5f4b19178b3f92cf64ee9007f53418fe44fac29f10059a225b7418463d", - "packages/react/src/components/dropdown.tsx": "58142308d0d2f3d93e647c898ecc82a8376107010fc2396d4bcabf7b0970c372", + "packages/react/src/components/dropdown.tsx": "ca8756aec65dedccfceeaa53060eadce25b09cbea32591d8b140a0fac84f9d77", "packages/react/src/components/empty-state.tsx": "18fd528f3564a5b4db8c3ebb1be629145173413c4df5bf91c880db6dbc66ab28", "packages/react/src/components/field.tsx": "0e45324275e6b014802bd457a589d752141e4a62f93cc74c6a8066c039df591e", "packages/react/src/components/file-upload.tsx": "79f0f91c44ae634e6d4cb53de168b69fd2f6fcf8dd8328555e3abde10d03bf19", @@ -55,6 +55,7 @@ "packages/react/src/controls.test.ts": "368ff01b0a724a5b76bf76b93d23cda78bfbd09e1729219ced902dc6cb7820f2", "packages/react/src/date-picker.css": "2d4cd1d1237c2ebda98df2b6470261a37104cbef45cafc0ab90d2624d9dd96b5", "packages/react/src/display-contracts.test.ts": "08a48b59deb4d715133365601cdd276266eff2b23a572de75dc629d6214e8337", + "packages/react/src/dropdown.test.ts": "a366cc44741953f00fe87ea6bf5c2f84199af7d1e93ee19f26344886a099a4f9", "packages/react/src/index.ts": "5cb0f2316e4017d28719989862d73bcd115c35949d772e86c60ca0bd46048c3f", "packages/react/src/navigation-data.test.ts": "a5034b62e0cbb05e717db42262c61a508035bb3ba7c24b1401d1048ebe5ffc7c", "packages/react/src/showcase.tsx": "fd745b9ffe5ae94b91c54052ff28cfe1c847372c0b5a492acaa59605cfc8f172", @@ -118,7 +119,7 @@ "packages/vue/src/components/display-types.ts": "36e23efbcab2f68cd50aab2b906f4622257f89ba84a4e34ba50cf7ebc2b567e1", "packages/vue/src/components/display-utils.ts": "2f167920c09f94f91f46be7729c6c75f773e66a54bcc38a5089c6bfa0aa40ba9", "packages/vue/src/components/drawer.vue": "3735eb73f31c8283cd009d9ae3480ee95f970280c61ea8d679ab0bb2cf216c4f", - "packages/vue/src/components/dropdown.vue": "8768bc3b099359464fb73c85c63e2212d7262701ad60fec8823b157d58bf0c4e", + "packages/vue/src/components/dropdown.vue": "7f591571d70425f228dd45d3eedb9fd8ab559118ed000db458d000a700453d4b", "packages/vue/src/components/empty-state.vue": "8cba09cb012e30684ae236f9e29f1a2dd72020abe523c80183c86ceef96116d0", "packages/vue/src/components/field-validation.ts": "885858f4c1be2c27cd884e3ad2bd2a8f05f26562d65be05d87da43d2a80a4dcd", "packages/vue/src/components/field.vue": "32ac595e796966a5dc80b535617c58f0b38340863cbb4c01d32980283310d359", @@ -159,7 +160,7 @@ "packages/vue/src/composition.vue": "6bf9776f8bd664a6cb6c425a3a7800aff2d96e9bbae9027feac82a510faaf5f7", "packages/vue/src/controls.test.ts": "cce4bcc6c6395bafef942a286618d2bcda72e5891586a44d5f71660110de1091", "packages/vue/src/display.css": "4cb71af46e0494a60013494d24c9469eb0b17c34e1f51ace7ddd2cb82d482e3b", - "packages/vue/src/display.test.ts": "e99c1de6ae542e5b917cb99b9bf5eb9ed5f8b907b98c64f364bb212950a67766", + "packages/vue/src/display.test.ts": "9c40404e96ce88083d6bdd9ab20d6fcde0549f86dae109b8a9837d30b41bb9d2", "packages/vue/src/index.ts": "08854e81a0c8bf9615940ae6ce4e77f2f0045d5a9ffee98dca77066c990a6dd5", "packages/vue/src/overlay-defaults.test.ts": "d76231b4be804caf0084487f6018db278ef1c130644b17b5b47696cb23886bf5", "packages/vue/src/styles.css": "fe552be1645a496b050d58794bd34b3af3c3e74ba40da3187b4a636a279b747f", diff --git a/packages/react/src/components/dropdown.tsx b/packages/react/src/components/dropdown.tsx index 29b8293..878771d 100644 --- a/packages/react/src/components/dropdown.tsx +++ b/packages/react/src/components/dropdown.tsx @@ -30,7 +30,7 @@ export function Dropdown({ }) { const container = usePortalContainer(); return ( - + {item.label} ) : undefined } diff --git a/packages/react/src/dropdown.test.ts b/packages/react/src/dropdown.test.ts new file mode 100644 index 0000000..5ea2f13 --- /dev/null +++ b/packages/react/src/dropdown.test.ts @@ -0,0 +1,34 @@ +// @vitest-environment jsdom +import { act, createElement } from "react"; +import { createRoot } from "react-dom/client"; +import { expect, it, vi } from "vitest"; +import { Dropdown } from "./components/dropdown"; + +it("blocks actions when an open dropdown becomes disabled", async () => { + vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); + const action = vi.fn(); + const host = document.createElement("div"); + document.body.append(host); + const root = createRoot(host); + const props = { + open: true, + items: [{ label: "Orange", href: "#orange", onSelect: action }], + }; + + try { + await act(async () => root.render(createElement(Dropdown, props))); + await act(async () => + root.render(createElement(Dropdown, { ...props, disabled: true })), + ); + const item = document.querySelector('[role="menuitem"]')!; + await act(async () => item.click()); + + expect(action).not.toHaveBeenCalled(); + expect(item.getAttribute("aria-disabled")).toBe("true"); + expect(item.hasAttribute("href")).toBe(false); + } finally { + await act(async () => root.unmount()); + host.remove(); + vi.unstubAllGlobals(); + } +}); diff --git a/packages/vue/src/components/dropdown.vue b/packages/vue/src/components/dropdown.vue index 98bd590..d24205e 100644 --- a/packages/vue/src/components/dropdown.vue +++ b/packages/vue/src/components/dropdown.vue @@ -49,7 +49,7 @@ const expanded = useDisplayModel( ); const container = usePortalContainer(); function select(item: MenuAction) { - if (item.disabled) return; + if (props.disabled || item.disabled) return; item.onSelect?.(); emit("select", item); } @@ -90,9 +90,9 @@ function select(item: MenuAction) { { await nextTick(); expect(select).toHaveBeenCalledOnce(); }); + it("blocks actions when an open dropdown becomes disabled", async () => { + const action = vi.fn(); + const wrapper = render(Dropdown, { + props: { + open: true, + items: [{ label: "Orange", href: "#orange", onSelect: action }], + }, + }); + await flushPromises(); + + await wrapper.setProps({ disabled: true }); + const item = document.querySelector('[role="menuitem"]')!; + item.click(); + await nextTick(); + + expect(action).not.toHaveBeenCalled(); + expect(wrapper.emitted("select")).toBeUndefined(); + expect(item.getAttribute("aria-disabled")).toBe("true"); + expect(item.hasAttribute("href")).toBe(false); + }); it("opens context actions from the keyboard and honors controlled open state", async () => { const wrapper = render(ContextMenu, { props: { items: [{ label: "Copy" }], open: false },