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 {
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()) {
|
||||||
|
if (this.selectionEngine.hasBoxMoved()) {
|
||||||
this.selectionEngine.finishBoxSelect(this.allElements);
|
this.selectionEngine.finishBoxSelect(this.allElements);
|
||||||
this.emitSelectionChange();
|
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
|
||||||
|
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);
|
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);
|
||||||
|
|||||||
@@ -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', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user