mirror of
https://github.com/excalidraw/excalidraw.git
synced 2024-11-10 11:35:52 +01:00
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(
|
private getElementAtPosition(
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
|
opts?: {
|
||||||
|
/** if true, returns the first selected element (with highest z-index)
|
||||||
|
of all hit elements */
|
||||||
|
preferSelected?: boolean;
|
||||||
|
},
|
||||||
): NonDeleted<ExcalidrawElement> | null {
|
): NonDeleted<ExcalidrawElement> | null {
|
||||||
const allHitElements = this.getElementsAtPosition(x, y);
|
const allHitElements = this.getElementsAtPosition(x, y);
|
||||||
if (allHitElements.length > 1) {
|
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 =
|
const elementWithHighestZIndex =
|
||||||
allHitElements[allHitElements.length - 1];
|
allHitElements[allHitElements.length - 1];
|
||||||
// If we're hitting element with highest z-index only on its bounding box
|
// 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();
|
event.preventDefault();
|
||||||
|
|
||||||
const { x, y } = viewportCoordsToSceneCoords(event, this.state);
|
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";
|
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`] = `
|
exports[`contextMenu element shows context menu for element: [end of test] element 0 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"angle": 0,
|
"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`] = `
|
exports[`contextMenu element shows context menu for element: [end of test] history 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"recording": false,
|
"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 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 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", () => {
|
it("shows 'Group selection' in context menu for multiple selected elements", () => {
|
||||||
UI.clickTool("rectangle");
|
UI.clickTool("rectangle");
|
||||||
mouse.down(10, 10);
|
mouse.down(10, 10);
|
||||||
|
@ -20,6 +20,15 @@ const readFile = util.promisify(fs.readFile);
|
|||||||
const { h } = window;
|
const { h } = window;
|
||||||
|
|
||||||
export class API {
|
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[] => {
|
static getSelectedElements = (): ExcalidrawElement[] => {
|
||||||
return h.elements.filter(
|
return h.elements.filter(
|
||||||
(element) => h.state.selectedElementIds[element.id],
|
(element) => h.state.selectedElementIds[element.id],
|
||||||
|
Loading…
Reference in New Issue
Block a user