fix: on contextMenu, use selected element regardless of z-index (#3668)
This commit is contained in:
parent
60cea7a0c2
commit
c819b653bf
@ -1858,9 +1858,21 @@ class App extends React.Component<AppProps, AppState> {
|
||||
private getElementAtPosition(
|
||||
x: number,
|
||||
y: number,
|
||||
opts?: {
|
||||
/** if true, returns the first selected element (with highest z-index)
|
||||
of all hit elements */
|
||||
preferSelected?: boolean;
|
||||
},
|
||||
): NonDeleted<ExcalidrawElement> | null {
|
||||
const allHitElements = this.getElementsAtPosition(x, y);
|
||||
if (allHitElements.length > 1) {
|
||||
if (opts?.preferSelected) {
|
||||
for (let index = allHitElements.length - 1; index > -1; index--) {
|
||||
if (this.state.selectedElementIds[allHitElements[index].id]) {
|
||||
return allHitElements[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
const elementWithHighestZIndex =
|
||||
allHitElements[allHitElements.length - 1];
|
||||
// If we're hitting element with highest z-index only on its bounding box
|
||||
@ -3935,7 +3947,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
event.preventDefault();
|
||||
|
||||
const { x, y } = viewportCoordsToSceneCoords(event, this.state);
|
||||
const element = this.getElementAtPosition(x, y);
|
||||
const element = this.getElementAtPosition(x, y, { preferSelected: true });
|
||||
|
||||
const type = element ? "element" : "canvas";
|
||||
|
||||
|
@ -4235,6 +4235,84 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`contextMenu element shows context menu for element: [end of test] appState 2`] = `
|
||||
Object {
|
||||
"collaborators": Map {},
|
||||
"currentChartType": "bar",
|
||||
"currentItemBackgroundColor": "transparent",
|
||||
"currentItemEndArrowhead": "arrow",
|
||||
"currentItemFillStyle": "hachure",
|
||||
"currentItemFontFamily": 1,
|
||||
"currentItemFontSize": 20,
|
||||
"currentItemLinearStrokeSharpness": "round",
|
||||
"currentItemOpacity": 100,
|
||||
"currentItemRoughness": 1,
|
||||
"currentItemStartArrowhead": null,
|
||||
"currentItemStrokeColor": "#000000",
|
||||
"currentItemStrokeSharpness": "sharp",
|
||||
"currentItemStrokeStyle": "solid",
|
||||
"currentItemStrokeWidth": 1,
|
||||
"currentItemTextAlign": "left",
|
||||
"cursorButton": "up",
|
||||
"draggingElement": null,
|
||||
"editingElement": null,
|
||||
"editingGroupId": null,
|
||||
"editingLinearElement": null,
|
||||
"elementLocked": false,
|
||||
"elementType": "selection",
|
||||
"errorMessage": null,
|
||||
"exportBackground": true,
|
||||
"exportEmbedScene": false,
|
||||
"exportWithDarkMode": false,
|
||||
"fileHandle": null,
|
||||
"gridSize": null,
|
||||
"height": 100,
|
||||
"isBindingEnabled": true,
|
||||
"isLibraryOpen": false,
|
||||
"isLoading": false,
|
||||
"isResizing": false,
|
||||
"isRotating": false,
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"offsetLeft": 20,
|
||||
"offsetTop": 10,
|
||||
"openMenu": null,
|
||||
"pasteDialog": Object {
|
||||
"data": null,
|
||||
"shown": false,
|
||||
},
|
||||
"previousSelectedElementIds": Object {},
|
||||
"resizingElement": null,
|
||||
"scrollX": 0,
|
||||
"scrollY": 0,
|
||||
"scrolledOutside": false,
|
||||
"selectedElementIds": Object {
|
||||
"id1": true,
|
||||
},
|
||||
"selectedGroupIds": Object {},
|
||||
"selectionElement": null,
|
||||
"shouldCacheIgnoreZoom": false,
|
||||
"showHelpDialog": false,
|
||||
"showStats": false,
|
||||
"startBoundElement": null,
|
||||
"suggestedBindings": Array [],
|
||||
"theme": "light",
|
||||
"toastMessage": null,
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
"viewModeEnabled": false,
|
||||
"width": 200,
|
||||
"zenModeEnabled": false,
|
||||
"zoom": Object {
|
||||
"translation": Object {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
},
|
||||
"value": 1,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`contextMenu element shows context menu for element: [end of test] element 0 1`] = `
|
||||
Object {
|
||||
"angle": 0,
|
||||
@ -4261,6 +4339,58 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`contextMenu element shows context menu for element: [end of test] element 0 2`] = `
|
||||
Object {
|
||||
"angle": 0,
|
||||
"backgroundColor": "red",
|
||||
"boundElementIds": null,
|
||||
"fillStyle": "hachure",
|
||||
"groupIds": Array [],
|
||||
"height": 200,
|
||||
"id": "id0",
|
||||
"isDeleted": false,
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"seed": 337897,
|
||||
"strokeColor": "#000000",
|
||||
"strokeSharpness": "sharp",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "rectangle",
|
||||
"version": 1,
|
||||
"versionNonce": 0,
|
||||
"width": 200,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`contextMenu element shows context menu for element: [end of test] element 1 1`] = `
|
||||
Object {
|
||||
"angle": 0,
|
||||
"backgroundColor": "red",
|
||||
"boundElementIds": null,
|
||||
"fillStyle": "hachure",
|
||||
"groupIds": Array [],
|
||||
"height": 200,
|
||||
"id": "id1",
|
||||
"isDeleted": false,
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#000000",
|
||||
"strokeSharpness": "sharp",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"type": "rectangle",
|
||||
"version": 1,
|
||||
"versionNonce": 0,
|
||||
"width": 200,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`contextMenu element shows context menu for element: [end of test] history 1`] = `
|
||||
Object {
|
||||
"recording": false,
|
||||
@ -4318,6 +4448,30 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`contextMenu element shows context menu for element: [end of test] history 2`] = `
|
||||
Object {
|
||||
"recording": false,
|
||||
"redoStack": Array [],
|
||||
"stateHistory": Array [
|
||||
Object {
|
||||
"appState": Object {
|
||||
"editingGroupId": null,
|
||||
"editingLinearElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"selectedElementIds": Object {},
|
||||
"selectedGroupIds": Object {},
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
},
|
||||
"elements": Array [],
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`contextMenu element shows context menu for element: [end of test] number of elements 1`] = `1`;
|
||||
|
||||
exports[`contextMenu element shows context menu for element: [end of test] number of elements 2`] = `2`;
|
||||
|
||||
exports[`contextMenu element shows context menu for element: [end of test] number of renders 1`] = `9`;
|
||||
|
||||
exports[`contextMenu element shows context menu for element: [end of test] number of renders 2`] = `6`;
|
||||
|
@ -147,6 +147,46 @@ describe("contextMenu element", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("shows context menu for element", () => {
|
||||
const rect1 = API.createElement({
|
||||
type: "rectangle",
|
||||
x: 0,
|
||||
y: 0,
|
||||
height: 200,
|
||||
width: 200,
|
||||
backgroundColor: "red",
|
||||
});
|
||||
const rect2 = API.createElement({
|
||||
type: "rectangle",
|
||||
x: 0,
|
||||
y: 0,
|
||||
height: 200,
|
||||
width: 200,
|
||||
backgroundColor: "red",
|
||||
});
|
||||
h.elements = [rect1, rect2];
|
||||
API.setSelectedElements([rect1]);
|
||||
|
||||
// lower z-index
|
||||
fireEvent.contextMenu(GlobalTestState.canvas, {
|
||||
button: 2,
|
||||
clientX: 100,
|
||||
clientY: 100,
|
||||
});
|
||||
expect(queryContextMenu()).not.toBeNull();
|
||||
expect(API.getSelectedElement().id).toBe(rect1.id);
|
||||
|
||||
// higher z-index
|
||||
API.setSelectedElements([rect2]);
|
||||
fireEvent.contextMenu(GlobalTestState.canvas, {
|
||||
button: 2,
|
||||
clientX: 100,
|
||||
clientY: 100,
|
||||
});
|
||||
expect(queryContextMenu()).not.toBeNull();
|
||||
expect(API.getSelectedElement().id).toBe(rect2.id);
|
||||
});
|
||||
|
||||
it("shows 'Group selection' in context menu for multiple selected elements", () => {
|
||||
UI.clickTool("rectangle");
|
||||
mouse.down(10, 10);
|
||||
|
@ -20,6 +20,15 @@ const readFile = util.promisify(fs.readFile);
|
||||
const { h } = window;
|
||||
|
||||
export class API {
|
||||
static setSelectedElements = (elements: ExcalidrawElement[]) => {
|
||||
h.setState({
|
||||
selectedElementIds: elements.reduce((acc, element) => {
|
||||
acc[element.id] = true;
|
||||
return acc;
|
||||
}, {} as Record<ExcalidrawElement["id"], true>),
|
||||
});
|
||||
};
|
||||
|
||||
static getSelectedElements = (): ExcalidrawElement[] => {
|
||||
return h.elements.filter(
|
||||
(element) => h.state.selectedElementIds[element.id],
|
||||
|
Loading…
x
Reference in New Issue
Block a user