feat(grid): improve data copy format controls

This commit is contained in:
t8y2 committed 2026-07-30 23:08:15 +08:00
1 parent 0234470466
commit 38e1c66f26
18 files changed
+235 -35

No files matched your search

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { ref, watch, onBeforeUnmount, onMounted, nextTick, type Component } from "vue";
import { ChevronRight } from "@lucide/vue";
import { Check, ChevronRight } from "@lucide/vue";
import { shortcutDisplayKeys } from "@/lib/editor/shortcutDisplay";
import { registerGlobalContextMenu, type ContextMenuRegistration } from "@/components/ui/customContextMenuRegistry";
@@ -11,6 +11,7 @@ export interface ContextMenuItem {
separator?: boolean;
icon?: Component;
iconClass?: string;
checked?: boolean;
// Raw shortcut syntax such as `Mod+C` or `Shift+Alt+U`; display formatting stays in this component.
shortcut?: string;
variant?: "default" | "destructive";
@@ -283,6 +284,7 @@ onBeforeUnmount(() => {
<component :is="item.icon" v-if="item.icon" :class="['size-4', item.iconClass]" />
</span>
<span class="flex-1 whitespace-nowrap">{{ item.label }}</span>
<Check v-if="item.checked" class="ml-4 size-4 shrink-0 text-primary" />
<span v-if="item.shortcut" class="ml-8 inline-flex shrink-0 items-center gap-1 text-muted-foreground">
<kbd v-for="key in shortcutKeys(item.shortcut)" :key="key" class="min-w-4 rounded border border-border/70 bg-muted/60 px-1 py-0.5 text-center font-mono text-[10px] leading-none text-muted-foreground shadow-xs">{{ key }}</kbd>
</span>
@@ -313,6 +315,7 @@ onBeforeUnmount(() => {
<component :is="child.icon" v-if="child.icon" :class="['size-4', child.iconClass]" />
</span>
<span class="flex-1 whitespace-nowrap">{{ child.label }}</span>
<Check v-if="child.checked" class="ml-4 size-4 shrink-0 text-primary" />
<span v-if="child.shortcut" class="ml-8 inline-flex shrink-0 items-center gap-1 text-muted-foreground">
<kbd v-for="key in shortcutKeys(child.shortcut)" :key="key" class="min-w-4 rounded border border-border/70 bg-muted/60 px-1 py-0.5 text-center font-mono text-[10px] leading-none text-muted-foreground shadow-xs">{{ key }}</kbd>
</span>
@@ -97,6 +97,36 @@ describe("CustomContextMenu lifecycle", () => {
app.unmount();
});
it("renders checked state after the menu label", async () => {
const root = defineComponent({
setup() {
return () =>
h(
CustomContextMenu,
{ items: [{ label: "Current", checked: true }, { label: "Other" }] },
{
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 current = Array.from(document.body.querySelectorAll("button")).find((button) => button.textContent?.includes("Current"));
const other = Array.from(document.body.querySelectorAll("button")).find((button) => button.textContent?.includes("Other"));
expect(current?.lastElementChild?.tagName.toLowerCase()).toBe("svg");
expect(other?.querySelector("svg")).toBeNull();
app.unmount();
});
it("resolves lazy menu items again for every open", async () => {
let copied = false;
const items = vi.fn(() =>