mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 08:07:49 +00:00
feat(editor): close editor tabs with Shift+click (#581)
Add Shift+left-click as a way to close editor tabs. The tab strip gets a closeOnShiftClick option, enabled for the editor area only, so tool window tabs such as Run and Terminal still select on Shift+click. Keyboard activation with Shift held still selects, non-closable tabs still select, and Shift+click does not start a tab drag. Co-authored-by: iGink <5266594+iGink@users.noreply.github.com>
This commit is contained in:
@@ -279,6 +279,7 @@ function PaneView({ paneId }: { paneId: string }) {
|
||||
onSelect={(id) => ctrl.activate(paneId, id)}
|
||||
onClose={(id) => void ctrl.closeTab(paneId, id)}
|
||||
onCloseOthers={(id) => void ctrl.closeOthers(paneId, id)}
|
||||
closeOnShiftClick
|
||||
onDoubleClick={(id) => {
|
||||
// IDEA: a preview tab becomes a regular one; any other tab maximizes the editor.
|
||||
if (pane.tabs.find((t) => t.id === id)?.preview) ctrl.keepTab(paneId, id);
|
||||
|
||||
@@ -71,6 +71,42 @@ describe("Tabs interactions", () => {
|
||||
expect(onClose).toHaveBeenCalledWith("b");
|
||||
});
|
||||
|
||||
it("Shift+left-click closes an editor tab without selecting it", () => {
|
||||
const onClose = vi.fn();
|
||||
const onSelect = vi.fn();
|
||||
render(<Tabs items={items} activeId="a" onSelect={onSelect} onClose={onClose} closeOnShiftClick />);
|
||||
fireEvent.click(screen.getByRole("tab", { name: "B" }), { shiftKey: true, detail: 1 });
|
||||
expect(onClose).toHaveBeenCalledWith("b");
|
||||
expect(onSelect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Shift+click only closes closable tabs", () => {
|
||||
const onClose = vi.fn();
|
||||
const onSelect = vi.fn();
|
||||
render(<Tabs items={items} activeId="a" onSelect={onSelect} onClose={onClose} closeOnShiftClick />);
|
||||
fireEvent.click(screen.getByRole("tab", { name: "C" }), { shiftKey: true, detail: 1 });
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
expect(onSelect).toHaveBeenCalledWith("c");
|
||||
});
|
||||
|
||||
it("keeps Shift+click selection where the strip does not opt in", () => {
|
||||
const onClose = vi.fn();
|
||||
const onSelect = vi.fn();
|
||||
render(<Tabs items={items} activeId="a" onSelect={onSelect} onClose={onClose} />);
|
||||
fireEvent.click(screen.getByRole("tab", { name: "B" }), { shiftKey: true, detail: 1 });
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
expect(onSelect).toHaveBeenCalledWith("b");
|
||||
});
|
||||
|
||||
it("does not treat a keyboard click with Shift held as a close", () => {
|
||||
const onClose = vi.fn();
|
||||
const onSelect = vi.fn();
|
||||
render(<Tabs items={items} activeId="a" onSelect={onSelect} onClose={onClose} closeOnShiftClick />);
|
||||
fireEvent.click(screen.getByRole("tab", { name: "B" }), { shiftKey: true });
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
expect(onSelect).toHaveBeenCalledWith("b");
|
||||
});
|
||||
|
||||
it("lets a container close the selected tab when it is closable", () => {
|
||||
const handles: TabStripHandle[] = [];
|
||||
const register = (h: TabStripHandle) => (handles.push(h), () => void handles.splice(handles.indexOf(h), 1));
|
||||
|
||||
+14
-2
@@ -88,6 +88,8 @@ export interface TabsProps {
|
||||
onClose?: (id: string) => void;
|
||||
/** Alt+click on a close button (IDEA: close all other tabs). */
|
||||
onCloseOthers?: (id: string) => void;
|
||||
/** Shift+left-click a tab closes it (editor tabs; plain clicks still select). */
|
||||
closeOnShiftClick?: boolean;
|
||||
onDoubleClick?: (id: string) => void;
|
||||
onContextMenu?: (id: string, e: MouseEvent) => void;
|
||||
/** `editor`: 30px with separators (editor tabs); `compact`: 28px plain (popups, tool window sub-tabs). */
|
||||
@@ -114,6 +116,7 @@ export function Tabs({
|
||||
onSelect,
|
||||
onClose,
|
||||
onCloseOthers,
|
||||
closeOnShiftClick,
|
||||
onDoubleClick,
|
||||
onContextMenu,
|
||||
variant = "editor",
|
||||
@@ -287,7 +290,8 @@ export function Tabs({
|
||||
onPointerDown={
|
||||
dnd
|
||||
? (e) => {
|
||||
if ((e.target as Element).closest("[data-tab-close]")) return;
|
||||
if ((closeOnShiftClick && e.shiftKey) || (e.target as Element).closest("[data-tab-close]"))
|
||||
return;
|
||||
startPointerDrag(
|
||||
e,
|
||||
e.currentTarget,
|
||||
@@ -336,7 +340,15 @@ export function Tabs({
|
||||
tabIndex={selected || (!activeId && index === 0) ? 0 : -1}
|
||||
className={styles.tabButton}
|
||||
style={item.color ? { color: item.color } : undefined}
|
||||
onClick={() => onSelect(item.id)}
|
||||
onClick={(e) => {
|
||||
// Shift+click is mouse-only: a keyboard Enter with Shift held still selects.
|
||||
if (closeOnShiftClick && e.shiftKey && e.detail > 0 && closable) {
|
||||
e.preventDefault();
|
||||
onClose!(item.id);
|
||||
return;
|
||||
}
|
||||
onSelect(item.id);
|
||||
}}
|
||||
onDoubleClick={onDoubleClick ? () => onDoubleClick(item.id) : undefined}
|
||||
onKeyDown={(e) => onKeyDown(e, index, item)}
|
||||
{...tooltipAttrs(item.tooltip)}
|
||||
|
||||
Reference in New Issue
Block a user