mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
fix(tui): keep list selection unchanged on mouse hover
This commit is contained in:
parent
17de82d7be
commit
9841914c71
4 changed files with 85 additions and 19 deletions
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed mouse hover changing selection and recentering autocomplete and settings lists, causing clicks to target a different item.
|
||||
|
||||
## [0.85.0] - 2026-09-04
|
||||
|
||||
### Breaking Changes
|
||||
|
|
|
|||
|
|
@ -115,23 +115,19 @@ export class SelectList implements Component {
|
|||
if (this.selectedIndex !== previousIndex) this.notifySelectionChange();
|
||||
return { handled: true, render: this.selectedIndex !== previousIndex };
|
||||
}
|
||||
if (event.type !== "move" && event.button !== "left") return undefined;
|
||||
// Hover must not change selection: the visible range is centered on it.
|
||||
if (event.button !== "left" || (event.type !== "press" && event.type !== "click")) return undefined;
|
||||
const { startIndex, endIndex } = this.getVisibleRange();
|
||||
const itemIndex = startIndex + event.y;
|
||||
if (itemIndex < startIndex || itemIndex >= endIndex) return undefined;
|
||||
|
||||
if (event.type === "move" || event.type === "press") {
|
||||
if (event.type === "press") this.mousePressedIndex = itemIndex;
|
||||
const changed = this.selectedIndex !== itemIndex;
|
||||
if (changed) {
|
||||
if (event.type === "press") {
|
||||
this.mousePressedIndex = itemIndex;
|
||||
if (this.selectedIndex !== itemIndex) {
|
||||
this.selectedIndex = itemIndex;
|
||||
this.notifySelectionChange();
|
||||
}
|
||||
return {
|
||||
handled: true,
|
||||
focus: event.type === "press",
|
||||
...(event.type === "move" ? { render: changed } : {}),
|
||||
};
|
||||
return { handled: true, focus: true };
|
||||
}
|
||||
if (event.type === "click") {
|
||||
const clickedIndex = this.mousePressedIndex ?? itemIndex;
|
||||
|
|
|
|||
|
|
@ -198,21 +198,17 @@ export class SettingsList implements Component {
|
|||
this.selectedIndex = Math.max(0, Math.min(displayItems.length - 1, this.selectedIndex + delta));
|
||||
return { handled: true, render: this.selectedIndex !== previousIndex };
|
||||
}
|
||||
if (event.type !== "move" && event.button !== "left") return undefined;
|
||||
// Hover must not change selection: the visible range is centered on it.
|
||||
if (event.button !== "left" || (event.type !== "press" && event.type !== "click")) return undefined;
|
||||
|
||||
const rowOffset = this.searchEnabled ? 2 : 0;
|
||||
const { startIndex, endIndex } = this.getVisibleRange(displayItems);
|
||||
const itemIndex = startIndex + event.y - rowOffset;
|
||||
if (itemIndex < startIndex || itemIndex >= endIndex) return undefined;
|
||||
if (event.type === "move" || event.type === "press") {
|
||||
if (event.type === "press") this.mousePressedIndex = itemIndex;
|
||||
const changed = this.selectedIndex !== itemIndex;
|
||||
if (event.type === "press") {
|
||||
this.mousePressedIndex = itemIndex;
|
||||
this.selectedIndex = itemIndex;
|
||||
return {
|
||||
handled: true,
|
||||
focus: event.type === "press",
|
||||
...(event.type === "move" ? { render: changed } : {}),
|
||||
};
|
||||
return { handled: true, focus: true };
|
||||
}
|
||||
if (event.type === "click") {
|
||||
this.selectedIndex = this.mousePressedIndex ?? itemIndex;
|
||||
|
|
|
|||
|
|
@ -113,6 +113,76 @@ describe("mouse-aware components", () => {
|
|||
assert.deepStrictEqual(changes, [{ id: "third", value: "high" }]);
|
||||
});
|
||||
|
||||
for (const row of [0, 4]) {
|
||||
it(`ignores hover and clicks visible select-list row ${row} after scrolling`, () => {
|
||||
const list = new SelectList(
|
||||
Array.from({ length: 12 }, (_, i) => ({ value: `item-${i}`, label: `Item ${i}` })),
|
||||
5,
|
||||
selectTheme,
|
||||
);
|
||||
const changes: string[] = [];
|
||||
let selected: string | undefined;
|
||||
list.onSelectionChange = (item) => changes.push(item.value);
|
||||
list.onSelect = (item) => {
|
||||
selected = item.value;
|
||||
};
|
||||
list.setSelectedIndex(5);
|
||||
list.handleMouse({ ...mouse("wheel", 1, row), wheelDelta: 1 });
|
||||
assert.strictEqual(list.getSelectedItem()?.value, "item-6");
|
||||
assert.deepStrictEqual(changes, ["item-6"]);
|
||||
const before = list.render(80);
|
||||
assert.match(before[row], new RegExp(`Item ${4 + row}$`));
|
||||
|
||||
for (const y of [0, 1, 2, 3, 4, row]) {
|
||||
assert.strictEqual(list.handleMouse({ ...mouse("move", 1, y), button: "none" }), undefined);
|
||||
assert.deepStrictEqual(list.render(80), before);
|
||||
}
|
||||
assert.strictEqual(list.getSelectedItem()?.value, "item-6");
|
||||
assert.deepStrictEqual(changes, ["item-6"]);
|
||||
assert.strictEqual(selected, undefined);
|
||||
|
||||
list.handleMouse(mouse("press", 1, row));
|
||||
list.render(80);
|
||||
list.handleMouse(mouse("click", 1, row));
|
||||
assert.strictEqual(selected, `item-${4 + row}`);
|
||||
assert.deepStrictEqual(changes, ["item-6", `item-${4 + row}`]);
|
||||
});
|
||||
|
||||
it(`ignores hover and clicks visible settings row ${row} after scrolling`, () => {
|
||||
const changes: Array<{ id: string; value: string }> = [];
|
||||
const list = new SettingsList(
|
||||
Array.from({ length: 12 }, (_, i) => ({
|
||||
id: `item-${i}`,
|
||||
label: `Item ${i}`,
|
||||
description: `Description ${i}`,
|
||||
currentValue: "off",
|
||||
values: ["off", "on"],
|
||||
})),
|
||||
5,
|
||||
settingsTheme,
|
||||
(id, value) => changes.push({ id, value }),
|
||||
() => {},
|
||||
{ enableSearch: true },
|
||||
);
|
||||
list.selectItem("item-5");
|
||||
list.handleMouse({ ...mouse("wheel", 1, row + 2), wheelDelta: 1 });
|
||||
const before = list.render(80);
|
||||
assert.match(before[4], /^> Item 6/);
|
||||
assert.match(before[row + 2], new RegExp(`Item ${4 + row} `));
|
||||
|
||||
for (const y of [0, 1, 2, 3, 4, row]) {
|
||||
assert.strictEqual(list.handleMouse({ ...mouse("move", 1, y + 2), button: "none" }), undefined);
|
||||
assert.deepStrictEqual(list.render(80), before);
|
||||
}
|
||||
assert.deepStrictEqual(changes, []);
|
||||
|
||||
list.handleMouse(mouse("press", 1, row + 2));
|
||||
list.render(80);
|
||||
list.handleMouse(mouse("click", 1, row + 2));
|
||||
assert.deepStrictEqual(changes, [{ id: `item-${4 + row}`, value: "on" }]);
|
||||
});
|
||||
}
|
||||
|
||||
it("keeps a delegating overlay focused when its nested input is clicked", async () => {
|
||||
const terminal = new VirtualTerminal(20, 4);
|
||||
const tui = new TuiAltScreen(terminal);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue