fix: selection bugs — arrow tool click select, box-select on empty drag, preserve selection on tool switch
This commit is contained in:
@@ -294,4 +294,13 @@ export class SelectionEngine {
|
||||
isBoxSelecting(): boolean {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +131,8 @@ export class InteractionEngine {
|
||||
this.state.phase = 'idle';
|
||||
this.state.points = [];
|
||||
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.emitSelectionChange();
|
||||
}
|
||||
@@ -368,8 +369,13 @@ export class InteractionEngine {
|
||||
}
|
||||
|
||||
if (this.state.activeTool === 'select' && this.selectionEngine.isBoxSelecting()) {
|
||||
if (this.selectionEngine.hasBoxMoved()) {
|
||||
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();
|
||||
return;
|
||||
}
|
||||
@@ -510,8 +516,24 @@ export class InteractionEngine {
|
||||
this.selectionEngine.setOptions({ additive, subtractive });
|
||||
|
||||
if (!additive && !subtractive) {
|
||||
// Start potential box select
|
||||
// Hit test first: if an object is clicked, select it directly
|
||||
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 {
|
||||
// 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);
|
||||
|
||||
@@ -272,6 +272,21 @@ describe('SelectionEngine', () => {
|
||||
s.selectionEngine.cancelBoxSelect();
|
||||
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', () => {
|
||||
|
||||
Reference in New Issue
Block a user