fix: selection bugs — arrow tool click select, box-select on empty drag, preserve selection on tool switch

This commit is contained in:
A0 Orchestrator
2026-07-01 23:46:57 +02:00
parent 7b19a99b24
commit 2eead4c1aa
3 changed files with 51 additions and 5 deletions
+9
View File
@@ -294,4 +294,13 @@ export class SelectionEngine {
isBoxSelecting(): boolean { isBoxSelecting(): boolean {
return this.boxStart !== null; return this.boxStart !== null;
} }
/**
* Returns true when the box selection drag actually moved away from the start point.
* Used to distinguish a click (no drag) from a drag-based box selection.
*/
hasBoxMoved(): boolean {
if (!this.boxStart || !this.boxEnd) return false;
return this.boxStart.x !== this.boxEnd.x || this.boxStart.y !== this.boxEnd.y;
}
} }
+27 -5
View File
@@ -131,7 +131,8 @@ export class InteractionEngine {
this.state.phase = 'idle'; this.state.phase = 'idle';
this.state.points = []; this.state.points = [];
this.state.previewElement = null; this.state.previewElement = null;
if (tool !== 'select' && tool !== 'pan') { const drawingTools: ToolType[] = ['line', 'rect', 'circle', 'arc', 'polyline', 'polygon', 'revcloud', 'text', 'dimension', 'leader', 'chair', 'seating-row', 'seating-block', 'table', 'stage', 'seating-template', 'hatch'];
if (drawingTools.includes(tool)) {
this.selectionEngine.clearSelection(); this.selectionEngine.clearSelection();
this.emitSelectionChange(); this.emitSelectionChange();
} }
@@ -368,8 +369,13 @@ export class InteractionEngine {
} }
if (this.state.activeTool === 'select' && this.selectionEngine.isBoxSelecting()) { if (this.state.activeTool === 'select' && this.selectionEngine.isBoxSelecting()) {
this.selectionEngine.finishBoxSelect(this.allElements); if (this.selectionEngine.hasBoxMoved()) {
this.emitSelectionChange(); this.selectionEngine.finishBoxSelect(this.allElements);
this.emitSelectionChange();
} else {
// No drag movement — cancel box select (it was just a click, already handled by clickSelect)
this.selectionEngine.cancelBoxSelect();
}
this.requestRender(); this.requestRender();
return; return;
} }
@@ -510,8 +516,24 @@ export class InteractionEngine {
this.selectionEngine.setOptions({ additive, subtractive }); this.selectionEngine.setOptions({ additive, subtractive });
if (!additive && !subtractive) { if (!additive && !subtractive) {
// Start potential box select // Hit test first: if an object is clicked, select it directly
this.selectionEngine.startBoxSelect(world.x, world.y); const hit = this.renderEngine.hitTest(world.x, world.y, 5);
if (hit) {
// Group-aware click selection: if clicked element is in a group, select all group members
if (this.groupManager) {
const groupElements = this.groupManager.getGroupElements(hit.id);
if (groupElements.length > 1) {
this.selectionEngine.selectByIds(groupElements, false);
this.emitSelectionChange();
return;
}
}
this.selectionEngine.clickSelect(world.x, world.y, this.allElements);
this.emitSelectionChange();
} else {
// No object hit: start potential box select for drag
this.selectionEngine.startBoxSelect(world.x, world.y);
}
} else { } else {
// Group-aware click selection: if clicked element is in a group, select all group members // Group-aware click selection: if clicked element is in a group, select all group members
const hit = this.renderEngine.hitTest(world.x, world.y, 5); const hit = this.renderEngine.hitTest(world.x, world.y, 5);
+15
View File
@@ -272,6 +272,21 @@ describe('SelectionEngine', () => {
s.selectionEngine.cancelBoxSelect(); s.selectionEngine.cancelBoxSelect();
expect(s.selectionEngine.isBoxSelecting()).toBe(false); expect(s.selectionEngine.isBoxSelecting()).toBe(false);
}); });
it('hasBoxMoved should return false when box not started', () => {
expect(s.selectionEngine.hasBoxMoved()).toBe(false);
});
it('hasBoxMoved should return false when start equals end (no drag)', () => {
s.selectionEngine.startBoxSelect(50, 50);
expect(s.selectionEngine.hasBoxMoved()).toBe(false);
});
it('hasBoxMoved should return true when box end differs from start', () => {
s.selectionEngine.startBoxSelect(50, 50);
s.selectionEngine.updateBoxSelect(100, 100, []);
expect(s.selectionEngine.hasBoxMoved()).toBe(true);
});
}); });
describe('clearSelection', () => { describe('clearSelection', () => {