1
0
mirror of synced 2026-10-23 07:08:55 +03:00

Fix ERD tables becoming immovable after a Shift-drag selection. #9890 (#10581)

This commit is contained in:
Dave Page
2026-10-15 10:38:55 +01:00
parent 7c5b0b7da7
commit 70c6777b5e
2 changed files with 106 additions and 1 deletions

View File

@@ -26,6 +26,16 @@ import UniqueConstraintSchema from '../../../../../browser/server_groups/servers
import PrimaryKeySchema from '../../../../../browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/js/primary_key.ui';
import { boundingBoxFromPolygons } from '@projectstorm/geometry';
/* Modifier keys as recorded by the canvas event bus, mapped to the matching
* flag on mouse events. */
const MODIFIER_KEYS = {
shift: 'shiftKey',
control: 'ctrlKey',
alt: 'altKey',
meta: 'metaKey',
};
const MOUSE_EVENT_TYPES = ['mousedown', 'mousemove', 'mouseup', 'wheel'];
export default class ERDCore {
constructor() {
this._cache = {};
@@ -38,7 +48,40 @@ export default class ERDCore {
}
createEngine(options) {
return createEngine(options);
let engine = createEngine(options);
this.releaseStaleModifierKeysOnMouseEvents(engine.getActionEventBus());
return engine;
}
/*
* The canvas event bus only learns that a key was released from its keyup
* event. If that never reaches the page (for example, Shift is released
* whilst focus is elsewhere), it believes Shift is still held and stays in
* the selecting state, where every drag draws a selection box, and tables
* can no longer be moved nor the canvas panned. Mouse events carry the real
* modifier state, so release any key the event says is no longer held
* before the event is handled.
*/
releaseStaleModifierKeysOnMouseEvents(eventBus) {
const fireAction = eventBus.fireAction.bind(eventBus);
eventBus.fireAction = (actionEvent)=>{
const event = actionEvent?.event;
if(MOUSE_EVENT_TYPES.includes(event?.type)) {
for(const key of eventBus.getKeys()) {
if(MODIFIER_KEYS[key] && event[MODIFIER_KEYS[key]] === false) {
fireAction({
event: {
type: 'keyup',
key: key,
preventDefault: () => {/*This is intentional (SonarQube)*/},
stopPropagation: () => {/*This is intentional (SonarQube)*/},
},
});
}
}
}
fireAction(actionEvent);
};
}
initializeEngine() {

View File

@@ -0,0 +1,62 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2026, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import ERDCore from 'pgadmin.tools.erd/erd_tool/ERDCore';
describe('ERDCore modifier keys', ()=>{
let engine, canvas;
const fire = (event)=>{
engine.getActionEventBus().fireAction({
event: {
preventDefault: ()=>{/* intentional */},
stopPropagation: ()=>{/* intentional */},
target: canvas,
...event,
},
});
};
const currentState = ()=>engine.getStateMachine().getCurrentState().getOptions().name;
beforeEach(()=>{
engine = new ERDCore().getEngine();
canvas = document.createElement('div');
document.body.appendChild(canvas);
engine.setCanvas(canvas);
});
afterEach(()=>{
canvas.remove();
});
it('draws a selection box when dragging with Shift held', ()=>{
fire({type: 'keydown', key: 'Shift'});
fire({type: 'mousedown', clientX: 10, clientY: 10, buttons: 1, shiftKey: true});
expect(currentState()).toBe('selection-box');
});
it('pans the canvas once Shift is released, even if the keyup was missed', ()=>{
fire({type: 'keydown', key: 'Shift'});
fire({type: 'mousedown', clientX: 10, clientY: 10, buttons: 1, shiftKey: true});
fire({type: 'mousemove', clientX: 50, clientY: 50, buttons: 1, shiftKey: true});
fire({type: 'mouseup', clientX: 50, clientY: 50, buttons: 0, shiftKey: true});
expect(currentState()).toBe('selecting');
/* No keyup for Shift arrives, but the next mouse event says it is up */
fire({type: 'mousedown', clientX: 10, clientY: 10, buttons: 1, shiftKey: false});
expect(engine.getActionEventBus().getKeys()).not.toContain('shift');
expect(currentState()).toBe('drag-canvas');
});
it('leaves keys alone for events without modifier flags', ()=>{
fire({type: 'keydown', key: 'Shift'});
fire({type: 'mousedown', clientX: 10, clientY: 10, buttons: 1});
expect(engine.getActionEventBus().getKeys()).toContain('shift');
expect(currentState()).toBe('selection-box');
});
});