feat(editor): group shortcuts by scope with conflict tiers

Closes #9075
This commit is contained in:
Abeautifulsnow authored and GitHub committed 2026-09-16 21:36:55 +08:00
1 parent b2d56625e0
commit ea1de14517
17 files changed
+970 -80

No files matched your search

@@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, ref, watch } from "vue";
import { cn } from "@/lib/common/utils";
import { floatingArrowOffset, floatingViewportShift } from "@/lib/common/floatingViewportPosition";
const props = withDefaults(
@@ -13,6 +14,7 @@ const props = withDefaults(
openOnFocus?: boolean;
nowrap?: boolean;
surface?: "foreground" | "popover";
contentClass?: string;
}>(),
{
disabled: false,
@@ -279,7 +281,7 @@ watch(
v-if="show"
ref="tooltipRef"
class="fixed z-50 rounded-md text-xs"
:class="[tooltipSurfaceClass, slots.content ? '' : ['inline-flex w-fit max-w-xs items-center gap-1.5 px-3 py-1.5', nowrap ? 'whitespace-nowrap' : ''], tooltipTransformClass]"
:class="cn([tooltipSurfaceClass, slots.content ? '' : ['inline-flex w-fit max-w-xs items-center gap-1.5 px-3 py-1.5', nowrap ? 'whitespace-nowrap' : ''], tooltipTransformClass], contentClass)"
:style="{ left: `${x}px`, top: `${y}px` }"
role="tooltip"
@mouseenter="clearCloseTimer"
@@ -50,3 +50,56 @@ describe("LightTooltip scrolling", () => {
app.unmount();
});
});
describe("LightTooltip contentClass", () => {
async function mountAndHover(props: Record<string, unknown>) {
const root = defineComponent({
setup() {
return () =>
h(
LightTooltip,
{ delay: 0, ...props },
{
default: () => h("span", { id: "content-class-trigger" }, "pill"),
},
);
},
});
const container = document.createElement("div");
document.body.append(container);
const app = createApp(root);
app.mount(container);
const trigger = container.querySelector<HTMLElement>("#content-class-trigger");
expect(trigger).toBeTruthy();
vi.spyOn(trigger!, "matches").mockImplementation((selector) => selector === ":hover");
trigger?.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true }));
await vi.runAllTimersAsync();
await nextTick();
return { app, tooltip: document.querySelector<HTMLElement>("[role=tooltip]") };
}
it("merges contentClass into the teleported tooltip and drops the default max-w-xs", async () => {
vi.useFakeTimers();
const { app, tooltip } = await mountAndHover({ text: "Conflict hint", contentClass: "max-w-[320px]" });
expect(tooltip).toBeTruthy();
expect(tooltip!.classList.contains("max-w-[320px]")).toBe(true);
// With both classes present Tailwind v4 emits max-w-[320px] before max-w-xs,
// so the default must be removed for the caller width to take effect.
expect(tooltip!.classList.contains("max-w-xs")).toBe(false);
app.unmount();
});
it("keeps the default max-w-xs when no contentClass is passed", async () => {
vi.useFakeTimers();
const { app, tooltip } = await mountAndHover({ text: "Plain hint" });
expect(tooltip).toBeTruthy();
expect(tooltip!.classList.contains("max-w-xs")).toBe(true);
app.unmount();
});
});