mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-07 00:07:29 +00:00
fix(search): make the search popups draggable (#625)
Find in Files, Replace in Files and Search Everywhere have no window title bar, so they could not be moved off the editor underneath. Dialogs can now be dragged from any element marked `data-dialog-drag`: the headers of those search popups, the title of any titled popup, and the existing window title bars and alert bodies, which now use the same mark. Controls inside a handle never start a drag. Also waits for a review thread inlay in a test where it can mount a tick later. Co-authored-by: a-rookie-of-C-language <186315048+a-rookie-of-C-language@users.noreply.github.com>
This commit is contained in:
co-authored by
a-rookie-of-C-language
parent
95e79116d1
commit
0bf597c2fd
@@ -216,7 +216,8 @@ describe("review threads in a pull request diff", () => {
|
||||
expect(right.contains(t1)).toBe(true);
|
||||
expect(t1.closest(".cm-lineInlay")!.previousElementSibling?.textContent).toBe("line three");
|
||||
expect(right.querySelectorAll(".cm-reviewControl-toggle")).toHaveLength(2);
|
||||
expect(inlay("T2")).not.toBeNull();
|
||||
// Each thread's inlay mounts in its own effect, so T2 may land a tick after T1.
|
||||
await waitFor(() => expect(inlay("T2")).not.toBeNull());
|
||||
|
||||
act(() => setReviewDiffSettings({ threadsView: "unresolved" }));
|
||||
await waitFor(() => expect(inlay("T2")).toBeNull());
|
||||
|
||||
@@ -394,7 +394,7 @@ function SearchEverywhereDialog() {
|
||||
aria-label={tx("Search Everywhere")}
|
||||
className={styles.dialog}
|
||||
>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.header} data-dialog-drag="">
|
||||
<div role="tablist" aria-label={tx("Search scope")} className={styles.tabs} onKeyDown={onTabKey}>
|
||||
{TABS.map((t) => (
|
||||
<button
|
||||
|
||||
@@ -483,7 +483,7 @@ function FindInFilesDialog() {
|
||||
className={styles.dialog}
|
||||
>
|
||||
<div className={styles.root} onKeyDown={onKeyDown}>
|
||||
<header className={styles.header}>
|
||||
<header className={styles.header} data-dialog-drag="">
|
||||
<span className={styles.title}>{s.replace ? tx("Replace in Files") : tx("Find in Files")}</span>
|
||||
<span className={styles.status} aria-live="polite">
|
||||
{state.status === "searching" || state.status === "validating" ? <Spinner size={12} /> : null}
|
||||
|
||||
@@ -103,6 +103,47 @@ describe("Dialog chrome", () => {
|
||||
expect(document.querySelector("[data-dialog-titlebar]")).toBeNull();
|
||||
});
|
||||
|
||||
it("drags a popup by a [data-dialog-drag] handle, but not by its controls or body", () => {
|
||||
render(
|
||||
<Dialog open onClose={() => {}} position="top" aria-label="Search Everywhere">
|
||||
<div data-dialog-drag="">
|
||||
<span>header</span>
|
||||
<button>tab</button>
|
||||
</div>
|
||||
<div>rows</div>
|
||||
</Dialog>,
|
||||
);
|
||||
const dialog = screen.getByRole("dialog");
|
||||
const handle = dialog.querySelector("[data-dialog-drag]")!;
|
||||
const down = (el: Element, x: number) => fireEvent.pointerDown(el, { button: 0, clientX: x, clientY: 10 });
|
||||
down(handle.querySelector("button")!, 10);
|
||||
down(dialog.querySelector("div:last-child")!, 10);
|
||||
expect(dialog.style.translate).toBe("");
|
||||
down(handle, 10);
|
||||
fireEvent.pointerMove(window, { clientX: 200, clientY: 40 });
|
||||
fireEvent.pointerUp(window);
|
||||
expect(dialog.style.translate).toBe("190px 30px");
|
||||
});
|
||||
|
||||
it("still drags a window dialog by its title bar", () => {
|
||||
render(
|
||||
<Dialog open onClose={() => {}} title="Usages Detected" buttons={safeDelete()} platform="other">
|
||||
<p>Usages found</p>
|
||||
</Dialog>,
|
||||
);
|
||||
const dialog = screen.getByRole("dialog");
|
||||
const bar = document.querySelector("[data-dialog-titlebar]") as HTMLElement;
|
||||
fireEvent.pointerDown(bar, { button: 0, clientX: 10, clientY: 10 });
|
||||
fireEvent.pointerMove(window, { clientX: 60, clientY: 40 });
|
||||
fireEvent.pointerUp(window);
|
||||
expect(dialog.style.translate).toBe("80px 30px");
|
||||
// The close button never starts a drag.
|
||||
fireEvent.pointerDown(within(bar).getByRole("button", { name: "Close" }), { button: 0, clientX: 10, clientY: 10 });
|
||||
fireEvent.pointerMove(window, { clientX: 200, clientY: 40 });
|
||||
fireEvent.pointerUp(window);
|
||||
expect(dialog.style.translate).toBe("80px 30px");
|
||||
});
|
||||
|
||||
it("renders the help button and footer start on the left", () => {
|
||||
const onHelp = vi.fn();
|
||||
render(
|
||||
|
||||
+8
-7
@@ -117,7 +117,7 @@ export interface DialogProps {
|
||||
/**
|
||||
* `top` places a lightweight modal popup (choosers, Search Everywhere) near
|
||||
* the top. Popups (also any dialog without a title) get no window title bar
|
||||
* and are not draggable.
|
||||
* and drag only by a `[data-dialog-drag]` element, such as their title.
|
||||
*/
|
||||
position?: "center" | "top";
|
||||
className?: string;
|
||||
@@ -287,8 +287,8 @@ function DialogInner({
|
||||
|
||||
const drag = (e: PointerEvent<HTMLDivElement>) => {
|
||||
if (
|
||||
popup ||
|
||||
e.button !== 0 ||
|
||||
!(e.target as Element).closest("[data-dialog-drag]") ||
|
||||
(e.target as Element).closest(`button, input, label, a, select, textarea, .${selectableText}`)
|
||||
)
|
||||
return;
|
||||
@@ -362,6 +362,7 @@ function DialogInner({
|
||||
data-alert={icon ? "" : undefined}
|
||||
data-inactive={active ? undefined : ""}
|
||||
className={clsx(styles.dialog, popup ? styles.popup : styles.window, className)}
|
||||
onPointerDown={drag}
|
||||
style={{
|
||||
width: size ? size.w : width,
|
||||
height: size?.h,
|
||||
@@ -370,12 +371,12 @@ function DialogInner({
|
||||
>
|
||||
{icon ? null : popup ? (
|
||||
title != null && (
|
||||
<div className={styles.popupHeader}>
|
||||
<div className={styles.popupHeader} data-dialog-drag="">
|
||||
<span className={styles.title}>{title}</span>
|
||||
</div>
|
||||
)
|
||||
) : platform === "mac" ? (
|
||||
<div className={clsx(styles.titleBar, styles.macTitleBar)} onPointerDown={drag} data-dialog-titlebar="">
|
||||
<div className={clsx(styles.titleBar, styles.macTitleBar)} data-dialog-drag="" data-dialog-titlebar="">
|
||||
<span className={styles.lights}>
|
||||
<button
|
||||
type="button"
|
||||
@@ -392,13 +393,13 @@ function DialogInner({
|
||||
<span className={styles.title}>{title}</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.titleBar} onPointerDown={drag} data-dialog-titlebar="">
|
||||
<div className={styles.titleBar} data-dialog-drag="" data-dialog-titlebar="">
|
||||
<span className={styles.title}>{title}</span>
|
||||
<IconButton icon="close" title="Close" size="small" tabIndex={-1} onClick={onClose} />
|
||||
</div>
|
||||
)}
|
||||
{icon ? (
|
||||
<div className={styles.message} data-dialog-body="" onPointerDown={drag}>
|
||||
<div className={styles.message} data-dialog-body="" data-dialog-drag="">
|
||||
{icon === "app" ? (
|
||||
<img className={styles.messageAppIcon} src={appIcon} alt="" draggable={false} />
|
||||
) : (
|
||||
@@ -419,7 +420,7 @@ function DialogInner({
|
||||
)}
|
||||
{icon
|
||||
? !!buttons?.length && (
|
||||
<div className={clsx(styles.footer, styles.messageFooter)} data-dialog-footer="" onPointerDown={drag}>
|
||||
<div className={clsx(styles.footer, styles.messageFooter)} data-dialog-footer="" data-dialog-drag="">
|
||||
<DialogButtons buttons={buttons} onCancel={onClose} platform={platform} />
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user