fix(grid): keep row copy actions consistent after refresh

This commit is contained in:
zipg authored and GitHub committed 2026-08-05 01:10:57 +08:00
1 parent 27d72f8776
commit be2590347f
6 files changed
+138 -7

No files matched your search

@@ -5534,6 +5534,8 @@ const {
copyText,
copyCell,
copyRow,
copyRowCount,
canCopyRow,
copyAll,
copyWithExtractor,
previewWithExtractor,
@@ -7113,6 +7115,7 @@ watch(
}
clearCellSelection();
clearRowSelection();
invalidateSyntheticContextSelection();
closeCellDetails();
closeDetailDialogs();
if (shouldPreserveTranspose) {
@@ -8298,7 +8301,11 @@ function copySubmenu(): ContextMenuItem {
if (contextColumn.value) {
items.push({ label: t("grid.copyCell"), action: copyCell });
}
items.push({ label: isMultiRow.value ? t("grid.copyRows", { count: multiRowCount.value }) : t("grid.copyRow"), action: copyRow });
items.push({
label: copyRowCount.value > 1 ? t("grid.copyRows", { count: copyRowCount.value }) : t("grid.copyRow"),
action: copyRow,
disabled: () => !canCopyRow.value,
});
items.push({ label: "", separator: true });
items.push(...buildExtractorContextItems());
items.push({ label: "", separator: true });
@@ -86,7 +86,8 @@ function onScroll(e: Event) {
}
function onKeydown(e: KeyboardEvent) {
if (e.key === "Escape") close();
if (["Alt", "Control", "Meta", "Shift"].includes(e.key)) return;
close();
}
function onResize() {
@@ -97,12 +98,12 @@ watch(show, (val) => {
contextMenuRegistration?.setOpen(val);
if (val) {
document.addEventListener("pointerdown", onPointerDownOutside, true);
document.addEventListener("keydown", onKeydown);
document.addEventListener("keydown", onKeydown, true);
document.addEventListener("scroll", onScroll, true);
window.addEventListener("resize", onResize);
} else {
document.removeEventListener("pointerdown", onPointerDownOutside, true);
document.removeEventListener("keydown", onKeydown);
document.removeEventListener("keydown", onKeydown, true);
document.removeEventListener("scroll", onScroll, true);
window.removeEventListener("resize", onResize);
}
@@ -263,7 +264,7 @@ onBeforeUnmount(() => {
contextMenuRegistration?.dispose();
contextMenuRegistration = null;
document.removeEventListener("pointerdown", onPointerDownOutside, true);
document.removeEventListener("keydown", onKeydown);
document.removeEventListener("keydown", onKeydown, true);
document.removeEventListener("scroll", onScroll, true);
window.removeEventListener("resize", onResize);
});
@@ -20,6 +20,38 @@ afterEach(() => {
});
describe("CustomContextMenu lifecycle", () => {
it("removes the capture keydown listener when unmounted while open", async () => {
const documentAdd = vi.spyOn(document, "addEventListener");
const documentRemove = vi.spyOn(document, "removeEventListener");
const root = defineComponent({
setup() {
return () =>
h(
CustomContextMenu,
{ items: [{ label: "Inspect" }] },
{
default: ({ onContextMenu }: { onContextMenu: (event: MouseEvent) => void }) => h("div", { id: "context-target", onContextmenu: onContextMenu }, "Target"),
},
);
},
});
const container = document.createElement("div");
mountedContainers.push(container);
document.body.append(container);
const app = createApp(root);
app.mount(container);
container.querySelector("#context-target")?.dispatchEvent(new MouseEvent("contextmenu", { bubbles: true }));
await nextTick();
const keydownListener = callsFor(documentAdd, "keydown").at(-1)?.[1];
expect(keydownListener).toBeTruthy();
app.unmount();
await nextTick();
expect(documentRemove.mock.calls).toContainEqual(["keydown", keydownListener, true]);
});
it("uses one shared listener set across repeated bulk mount cycles", async () => {
const documentAdd = vi.spyOn(document, "addEventListener");
const documentRemove = vi.spyOn(document, "removeEventListener");
@@ -97,6 +129,41 @@ describe("CustomContextMenu lifecycle", () => {
app.unmount();
});
it("closes the menu when an application shortcut is pressed", async () => {
const root = defineComponent({
setup() {
return () =>
h(
CustomContextMenu,
{ items: [{ label: "Refresh row" }] },
{
default: ({ onContextMenu }: { onContextMenu: (event: MouseEvent) => void }) => h("div", { id: "context-target", onContextmenu: onContextMenu, onKeydown: (event: KeyboardEvent) => event.stopPropagation() }, "Target"),
},
);
},
});
const container = document.createElement("div");
mountedContainers.push(container);
document.body.append(container);
const app = createApp(root);
app.mount(container);
const target = container.querySelector("#context-target");
target?.dispatchEvent(new MouseEvent("contextmenu", { bubbles: true }));
await nextTick();
expect(document.body.textContent).toContain("Refresh row");
target?.dispatchEvent(new KeyboardEvent("keydown", { key: "Meta", metaKey: true, bubbles: true }));
await nextTick();
expect(document.body.textContent).toContain("Refresh row");
target?.dispatchEvent(new KeyboardEvent("keydown", { key: "r", metaKey: true, bubbles: true }));
await nextTick();
expect(document.body.textContent).not.toContain("Refresh row");
app.unmount();
});
it("renders checked state after the menu label", async () => {
const root = defineComponent({
setup() {