mouse: track buttons independently (#1416)

Keep left-button selection state until its own release so right-click
interactions do not interrupt dragging.
This commit is contained in:
Simon Klee
2026-08-24 09:03:34 +02:00
committed by GitHub
parent 6b3a9e46cb
commit 4d983f032b
4 changed files with 17 additions and 15 deletions
+4 -11
View File
@@ -380,19 +380,12 @@ describe("MouseParser SGR mode", () => {
expect(move.type).toBe("move")
})
test("multiple buttons pressed — any motion is drag", () => {
test("releasing one of multiple pressed buttons keeps motion dragging", () => {
parser.parseMouseEvent(encodeSGR(0, 5, 5, true)) // left down
parser.parseMouseEvent(encodeSGR(2, 5, 5, true)) // right down
const e = parser.parseMouseEvent(encodeSGR(32, 8, 5, false))!
expect(e.type).toBe("drag")
})
test("release clears ALL tracked buttons", () => {
parser.parseMouseEvent(encodeSGR(0, 5, 5, true)) // left down
parser.parseMouseEvent(encodeSGR(2, 5, 5, true)) // right down
parser.parseMouseEvent(encodeSGR(0, 5, 5, false)) // release (clears all)
const e = parser.parseMouseEvent(encodeSGR(32, 8, 5, false))!
expect(e.type).toBe("move") // no buttons tracked → move, not drag
parser.parseMouseEvent(encodeSGR(2, 5, 5, false)) // right up
const event = parser.parseMouseEvent(encodeSGR(32, 8, 5, false))!
expect(event.type).toBe("drag")
})
test("reset() clears button tracking state", () => {
+1 -1
View File
@@ -175,7 +175,7 @@ export class MouseParser {
if (type === "down" && button !== 3) {
this.mouseButtonsPressed.add(button)
} else if (type === "up") {
this.mouseButtonsPressed.clear()
this.mouseButtonsPressed.delete(button)
}
}
+7 -2
View File
@@ -3630,7 +3630,7 @@ export class CliRenderer extends EventEmitter implements RenderContext {
return true
}
if (mouseEvent.type === "up" && this.currentSelection?.isDragging) {
if (mouseEvent.type === "up" && mouseEvent.button === MouseButton.LEFT && this.currentSelection?.isDragging) {
if (maybeRenderable) {
const event = new MouseEvent(maybeRenderable, {
...mouseEvent,
@@ -3716,7 +3716,12 @@ export class CliRenderer extends EventEmitter implements RenderContext {
this.lastOverRenderable = undefined
}
if (!event?.defaultPrevented && mouseEvent.type === "down" && this.currentSelection) {
if (
!event?.defaultPrevented &&
mouseEvent.type === "down" &&
mouseEvent.button === MouseButton.LEFT &&
this.currentSelection
) {
this.clearSelection()
}
@@ -343,7 +343,7 @@ describe("renderer handleMouseData", () => {
}
})
test("selection drag marks events as dragging and ends on mouse up", async () => {
test("selection drag ignores right-button release and ends on left mouse up", async () => {
try {
const target = new TestRenderable(renderer, {
id: "selectable",
@@ -372,7 +372,11 @@ describe("renderer handleMouseData", () => {
const endY = target.y + 3
await mockMouse.pressDown(startX, startY)
await mockMouse.click(startX, startY, MouseButtons.RIGHT)
expect(renderer.getSelection()?.isDragging).toBe(true)
await mockMouse.moveTo(endX, endY)
expect(renderer.getSelection()?.focus).toEqual({ x: endX, y: endY })
await mockMouse.release(endX, endY)
expect(renderer.hasSelection).toBe(true)