From 99c573e88e7ea1c29979cfde776e555763311e26 Mon Sep 17 00:00:00 2001 From: Ryan Di Date: Mon, 3 Mar 2025 11:18:14 +1100 Subject: [PATCH] lasso tests --- packages/excalidraw/lasso/index.ts | 113 +- packages/excalidraw/lasso/lasso.test.tsx | 1806 ++++++++++++++++++++++ packages/excalidraw/lasso/types.ts | 2 +- packages/excalidraw/lasso/worker.ts | 5 +- 4 files changed, 1870 insertions(+), 56 deletions(-) create mode 100644 packages/excalidraw/lasso/lasso.test.tsx diff --git a/packages/excalidraw/lasso/index.ts b/packages/excalidraw/lasso/index.ts index 9528212bf..f2fd6ad46 100644 --- a/packages/excalidraw/lasso/index.ts +++ b/packages/excalidraw/lasso/index.ts @@ -52,68 +52,73 @@ export class LassoTrail extends AnimatedTrail { this.intersectedElements.clear(); this.enclosedElements.clear(); - this.worker = new Worker(new URL("./worker.ts", import.meta.url), { - type: "module", - }); + try { + this.worker = new Worker(new URL("./worker.ts", import.meta.url), { + type: "module", + }); - this.worker.onmessage = (event: MessageEvent) => { - const { selectedElementIds } = event.data; + this.worker.onmessage = (event: MessageEvent) => { + const { selectedElementIds } = event.data; + this.selectElementsFromIds(selectedElementIds); + }; - this.app.setState((prevState) => { - const nextSelectedElementIds = selectedElementIds.reduce((acc, id) => { - acc[id] = true; - return acc; - }, {} as Record); + this.worker.onerror = (error) => { + console.error("Worker error:", error); + }; + } catch (error) { + console.error("Failed to start worker", error); + } + } - for (const [id] of Object.entries(nextSelectedElementIds)) { - const element = this.app.scene.getNonDeletedElement(id); - if (element && isFrameLikeElement(element)) { - const elementsInFrame = getFrameChildren( - this.app.scene.getNonDeletedElementsMap(), - element.id, - ); - for (const child of elementsInFrame) { - delete nextSelectedElementIds[child.id]; - } + selectElementsFromIds = (ids: string[]) => { + this.app.setState((prevState) => { + const nextSelectedElementIds = ids.reduce((acc, id) => { + acc[id] = true; + return acc; + }, {} as Record); + + for (const [id] of Object.entries(nextSelectedElementIds)) { + const element = this.app.scene.getNonDeletedElement(id); + if (element && isFrameLikeElement(element)) { + const elementsInFrame = getFrameChildren( + this.app.scene.getNonDeletedElementsMap(), + element.id, + ); + for (const child of elementsInFrame) { + delete nextSelectedElementIds[child.id]; } } + } - const nextSelection = selectGroupsForSelectedElements( - { - editingGroupId: prevState.editingGroupId, - selectedElementIds: nextSelectedElementIds, - }, - this.app.scene.getNonDeletedElements(), - prevState, - this.app, - ); + const nextSelection = selectGroupsForSelectedElements( + { + editingGroupId: prevState.editingGroupId, + selectedElementIds: nextSelectedElementIds, + }, + this.app.scene.getNonDeletedElements(), + prevState, + this.app, + ); - const selectedIds = [...Object.keys(nextSelection.selectedElementIds)]; - const selectedGroupIds = [ - ...Object.keys(nextSelection.selectedGroupIds), - ]; + const selectedIds = [...Object.keys(nextSelection.selectedElementIds)]; + const selectedGroupIds = [...Object.keys(nextSelection.selectedGroupIds)]; - return { - selectedElementIds: nextSelection.selectedElementIds, - selectedGroupIds: nextSelection.selectedGroupIds, - selectedLinearElement: - selectedIds.length === 1 && - !selectedGroupIds.length && - isLinearElement(this.app.scene.getNonDeletedElement(selectedIds[0])) - ? new LinearElementEditor( - this.app.scene.getNonDeletedElement( - selectedIds[0], - ) as NonDeleted, - ) - : null, - }; - }); - }; - - this.worker.onerror = (error) => { - console.error("Worker error:", error); - }; - } + return { + selectedElementIds: nextSelection.selectedElementIds, + selectedGroupIds: nextSelection.selectedGroupIds, + selectedLinearElement: + selectedIds.length === 1 && + !selectedGroupIds.length && + isLinearElement(this.app.scene.getNonDeletedElement(selectedIds[0])) + ? new LinearElementEditor( + this.app.scene.getNonDeletedElement( + selectedIds[0], + ) as NonDeleted, + ) + : null, + }; + }); + }; addPointToPath = (x: number, y: number) => { super.addPointToPath(x, y); diff --git a/packages/excalidraw/lasso/lasso.test.tsx b/packages/excalidraw/lasso/lasso.test.tsx new file mode 100644 index 000000000..98ff8a02f --- /dev/null +++ b/packages/excalidraw/lasso/lasso.test.tsx @@ -0,0 +1,1806 @@ +/** + * Test case: + * + * create a few random elements on canvas + * creates a lasso path for each of these cases + * - do not intersect / enclose at all + * - intersects some, does not enclose/intersect the rest + * - intersects and encloses some + * - single linear element should be selected if lasso intersects/encloses it + * + * + * special cases: + * - selects only frame if frame and children both selected by lasso + * - selects group if any group from group is selected + */ + +import { act, render } from "../tests/test-utils"; +import { Excalidraw } from "../index"; +import type { ExcalidrawElement } from "../element/types"; +import { + type GlobalPoint, + type LocalPoint, + pointFrom, + type Radians, +} from "../../math"; +import { getSelectedElements } from "../scene"; +import { updateSelection } from "./worker"; +import { getElementLineSegments } from "../element/bounds"; +import type { ElementsSegmentsMap } from "./types"; + +const { h } = window; + +beforeEach(async () => { + localStorage.clear(); + await render(); + h.state.width = 1000; + h.state.height = 1000; +}); + +const updatePath = (startPoint: GlobalPoint, points: LocalPoint[]) => { + act(() => { + h.app.lassoTrail.startPath(startPoint[0], startPoint[1]); + + points.forEach((point) => { + h.app.lassoTrail.addPointToPath( + startPoint[0] + point[0], + startPoint[1] + point[1], + ); + }); + + const elementsSegments: ElementsSegmentsMap = new Map(); + for (const element of h.elements) { + const segments = getElementLineSegments( + element, + h.app.scene.getElementsMapIncludingDeleted(), + ); + elementsSegments.set(element.id, segments); + } + + const result = updateSelection({ + lassoPath: + h.app.lassoTrail + .getCurrentTrail() + ?.originalPoints?.map((p) => pointFrom(p[0], p[1])) ?? + [], + elements: h.elements, + elementsSegments, + intersectedElements: new Set(), + enclosedElements: new Set(), + }); + + act(() => + h.app.lassoTrail.selectElementsFromIds(result.selectedElementIds), + ); + + h.app.lassoTrail.endPath(); + }); +}; + +describe("Basic lasso selection tests", () => { + beforeEach(() => { + const elements: ExcalidrawElement[] = [ + { + id: "FLZN67ISZbMV-RH8SzS9W", + type: "rectangle", + x: 0, + y: 0, + width: 107.11328125, + height: 90.16015625, + angle: 5.40271241072378, + strokeColor: "#1e1e1e", + backgroundColor: "transparent", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 1, + opacity: 100, + groupIds: [], + frameId: null, + index: "a8", + roundness: { + type: 3, + }, + seed: 1558764732, + version: 43, + versionNonce: 575357188, + isDeleted: false, + boundElements: [], + updated: 1740723127946, + link: null, + locked: false, + }, + { + id: "T3TSAFUwp--pT2b_q7Y5U", + type: "diamond", + x: 349.822265625, + y: -201.244140625, + width: 123.3828125, + height: 74.66796875, + angle: 0.6498998717212414, + strokeColor: "#1e1e1e", + backgroundColor: "transparent", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 1, + opacity: 100, + groupIds: [], + frameId: null, + index: "a9", + roundness: { + type: 2, + }, + seed: 1720937276, + version: 69, + versionNonce: 1991578556, + isDeleted: false, + boundElements: [], + updated: 1740723132096, + link: null, + locked: false, + }, + { + id: "a9RZwSeqlZHyhses2iYZ0", + type: "ellipse", + x: 188.259765625, + y: -48.193359375, + width: 146.8984375, + height: 91.01171875, + angle: 0.6070652964532064, + strokeColor: "#1e1e1e", + backgroundColor: "transparent", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 1, + opacity: 100, + groupIds: [], + frameId: null, + index: "aA", + roundness: { + type: 2, + }, + seed: 476696636, + version: 38, + versionNonce: 1903760444, + isDeleted: false, + boundElements: [], + updated: 1740723125079, + link: null, + locked: false, + }, + { + id: "vCw17KEn9h4sY2KMdnq0G", + type: "arrow", + x: -257.388671875, + y: 78.583984375, + width: 168.4765625, + height: 153.38671875, + angle: 0, + strokeColor: "#1e1e1e", + backgroundColor: "transparent", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 1, + opacity: 100, + groupIds: [], + frameId: null, + index: "aB", + roundness: { + type: 2, + }, + seed: 1302309508, + version: 19, + versionNonce: 1230691388, + isDeleted: false, + boundElements: [], + updated: 1740723110578, + link: null, + locked: false, + points: [ + [0, 0], + [168.4765625, -153.38671875], + ], + lastCommittedPoint: null, + startBinding: null, + endBinding: null, + startArrowhead: null, + endArrowhead: "arrow", + elbowed: false, + }, + { + id: "dMsLoKhGsWQXpiKGWZ6Cn", + type: "line", + x: -113.748046875, + y: -165.224609375, + width: 206.12890625, + height: 35.4140625, + angle: 0, + strokeColor: "#1e1e1e", + backgroundColor: "transparent", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 1, + opacity: 100, + groupIds: [], + frameId: null, + index: "aC", + roundness: { + type: 2, + }, + seed: 514585788, + version: 18, + versionNonce: 1338507580, + isDeleted: false, + boundElements: [], + updated: 1740723112995, + link: null, + locked: false, + points: [ + [0, 0], + [206.12890625, 35.4140625], + ], + lastCommittedPoint: null, + startBinding: null, + endBinding: null, + startArrowhead: null, + endArrowhead: null, + }, + { + id: "1GUDjUg8ibE_4qMFtdQiK", + type: "freedraw", + x: 384.404296875, + y: 91.580078125, + width: 537.55078125, + height: 288.48046875, + angle: 5.5342222396022285, + strokeColor: "#1e1e1e", + backgroundColor: "transparent", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 1, + opacity: 100, + groupIds: [], + frameId: null, + index: "aD", + roundness: null, + seed: 103578044, + version: 167, + versionNonce: 1117299588, + isDeleted: false, + boundElements: [], + updated: 1740723137180, + link: null, + locked: false, + points: [ + [0, 0], + [-0.10546875, 0], + [-3.23046875, -0.859375], + [-18.09765625, -4.6953125], + [-54.40625, -13.765625], + [-103.48046875, -23.05859375], + [-155.6640625, -27.5390625], + [-205.5703125, -27.96484375], + [-239, -24.4765625], + [-257.27734375, -17.0390625], + [-270.1015625, -5.43359375], + [-279.94140625, 12.12109375], + [-286.828125, 36.6875], + [-291.03515625, 65.63671875], + [-292.5546875, 94.96875], + [-291.8203125, 122.1875], + [-286.140625, 144.703125], + [-274.60546875, 160.01953125], + [-257.1171875, 170.375], + [-237.7890625, 176.1953125], + [-218.85546875, 178.69921875], + [-199.33984375, 181.56640625], + [-182.4609375, 188.4765625], + [-168.97265625, 200.14453125], + [-160.83984375, 211.1875], + [-156.40234375, 220.0703125], + [-153.60546875, 226.12890625], + [-151.3203125, 229.30078125], + [-146.28125, 231.7421875], + [-136.140625, 233.30859375], + [-122.1953125, 233.80078125], + [-108.66015625, 234.23828125], + [-97.0234375, 235.0546875], + [-89.6171875, 235.7421875], + [-85.84375, 237.52734375], + [-82.546875, 240.41796875], + [-79.64453125, 243.2734375], + [-75.71875, 245.99609375], + [-69.734375, 248.4453125], + [-59.6640625, 250.87890625], + [-45.1171875, 252.4453125], + [-23.9453125, 251.7265625], + [7.41796875, 244.0546875], + [48.58203125, 223.734375], + [93.5078125, 192.859375], + [135.8359375, 153.9453125], + [168.875, 114.015625], + [186.5625, 86.640625], + [194.9765625, 71.19140625], + [199.0234375, 62.671875], + [199.875, 59.6171875], + [200.1796875, 58.72265625], + [200.4140625, 58.62109375], + [200.87109375, 58.57421875], + [203.1796875, 58.2734375], + [208.72265625, 55.671875], + [216.421875, 50.89453125], + [224.546875, 45.265625], + [234.40625, 36.30859375], + [241.71484375, 28.14453125], + [243.6875, 24.1171875], + [244.6171875, 21.34375], + [244.99609375, 18.5625], + [243.78515625, 12.41015625], + [237.6328125, -4.8125], + [222.91796875, -36.03515625], + [222.91796875, -36.03515625], + ], + pressures: [], + simulatePressure: true, + lastCommittedPoint: null, + }, + ].map( + (e) => + ({ + ...e, + angle: e.angle as Radians, + index: null, + } as ExcalidrawElement), + ); + + act(() => { + h.elements = elements; + h.app.setActiveTool({ type: "lasso" }); + }); + }); + + it("None should be selected", () => { + const startPoint = pointFrom(-533, 611); + + const points = [ + [0, 0], + [0.1015625, -0.09765625], + [10.16796875, -8.15625], + [25.71484375, -18.5078125], + [46.078125, -28.63671875], + [90.578125, -41.9140625], + [113.04296875, -45.0859375], + [133.95703125, -46.2890625], + [152.92578125, -46.2890625], + [170.921875, -44.98828125], + [190.1640625, -39.61328125], + [213.73046875, -29], + [238.859375, -16.59375], + [261.87890625, -5.80078125], + [281.63671875, 2.4453125], + [300.125, 9.01953125], + [320.09375, 14.046875], + [339.140625, 16.95703125], + [358.3203125, 18.41796875], + [377.5234375, 17.890625], + [396.45703125, 14.53515625], + [416.4921875, 8.015625], + [438.796875, -1.54296875], + [461.6328125, -11.5703125], + [483.36328125, -21.48828125], + [503.37109375, -30.87109375], + [517.0546875, -36.49609375], + [525.62109375, -39.6640625], + [531.45703125, -41.46875], + [534.1328125, -41.9375], + [535.32421875, -42.09375], + [544.4140625, -42.09375], + [567.2265625, -42.09375], + [608.1875, -38.5625], + [665.203125, -27.66796875], + [725.8984375, -11.30078125], + [785.05078125, 8.17578125], + [832.12109375, 25.55078125], + [861.62109375, 36.32421875], + [881.91796875, 42.203125], + [896.75, 45.125], + [907.04296875, 46.46484375], + [917.44921875, 46.42578125], + [930.671875, 42.59765625], + [945.953125, 34.66796875], + [964.08984375, 22.43359375], + [989.8125, 2.328125], + [1014.6640625, -17.79296875], + [1032.7734375, -32.70703125], + [1045.984375, -43.9921875], + [1052.48828125, -50.1875], + [1054.97265625, -53.3046875], + [1055.65234375, -54.38671875], + [1060.48046875, -54.83984375], + [1073.03125, -55.2734375], + [1095.6484375, -54], + [1125.41796875, -49.05859375], + [1155.33984375, -41.21484375], + [1182.33203125, -33.6875], + [1204.1171875, -27.75390625], + [1220.95703125, -23.58203125], + [1235.390625, -21.06640625], + [1248.078125, -19.3515625], + [1257.78125, -18.6484375], + [1265.6640625, -19.22265625], + [1271.5703125, -20.42578125], + [1276.046875, -21.984375], + [1280.328125, -25.23828125], + [1284.19140625, -29.953125], + [1288.22265625, -35.8125], + [1292.87109375, -43.21484375], + [1296.6796875, -50.44921875], + [1299.3828125, -56.40234375], + [1301.48828125, -61.08203125], + [1302.89453125, -64.75], + [1303.890625, -67.37890625], + [1304.41796875, -68.953125], + [1304.65234375, -69.8046875], + [1304.80078125, -70.2578125], + [1304.80078125, -70.2578125], + ] as LocalPoint[]; + + updatePath(startPoint, points); + + const selectedElements = getSelectedElements(h.elements, h.app.state); + + expect(selectedElements.length).toBe(0); + }); + + it("Intersects some, does not enclose/intersect the rest", () => { + const startPoint = pointFrom(-311, 50); + const points = [ + [0, 0], + [0.1015625, 0], + [3.40234375, -2.25390625], + [12.25390625, -7.84375], + [22.71484375, -13.89453125], + [39.09765625, -22.3359375], + [58.5546875, -31.9609375], + [79.91796875, -41.21875], + [90.53125, -44.76953125], + [99.921875, -47.16796875], + [107.46484375, -48.640625], + [113.92578125, -49.65625], + [119.57421875, -50.1953125], + [124.640625, -50.1953125], + [129.49609375, -50.1953125], + [134.53125, -50.1953125], + [140.59375, -50.1953125], + [147.27734375, -49.87109375], + [154.32421875, -48.453125], + [160.93359375, -46.0390625], + [166.58203125, -42.8828125], + [172.0078125, -38.8671875], + [176.75390625, -34.1015625], + [180.41796875, -29.609375], + [183.09375, -25.0390625], + [185.11328125, -19.70703125], + [186.8828125, -13.04296875], + [188.515625, -6.39453125], + [189.8515625, -1.04296875], + [190.9609375, 4.34375], + [191.9296875, 9.3125], + [193.06640625, 13.73046875], + [194.21875, 17.51953125], + [195.32421875, 20.83984375], + [196.5625, 23.4296875], + [198.2109375, 25.5234375], + [200.04296875, 27.38671875], + [202.1640625, 28.80078125], + [204.43359375, 30.33984375], + [207.10546875, 31.7109375], + [210.69921875, 33.1640625], + [214.6015625, 34.48828125], + [218.5390625, 35.18359375], + [222.703125, 35.71875], + [227.16015625, 35.98828125], + [232.01171875, 35.98828125], + [237.265625, 35.98828125], + [242.59765625, 35.015625], + [247.421875, 33.4140625], + [251.61328125, 31.90625], + [255.84375, 30.1328125], + [260.25390625, 28.62109375], + [264.44140625, 27.41796875], + [268.5546875, 26.34765625], + [272.6171875, 25.42578125], + [276.72265625, 24.37890625], + [281.234375, 23.140625], + [286.69921875, 22.046875], + [293.5859375, 20.82421875], + [300.6328125, 19.4140625], + [309.83984375, 18.1640625], + [320.28125, 16.7578125], + [329.46875, 15.91015625], + [337.453125, 15.53515625], + [344.515625, 14.8203125], + [350.45703125, 14.4453125], + [354.64453125, 14.5546875], + [358.10546875, 14.921875], + [360.83203125, 15.5234375], + [362.796875, 16.3671875], + [364.1328125, 17.43359375], + [365.13671875, 18.6015625], + [365.8984375, 19.8203125], + [366.71484375, 21.30078125], + [368.34375, 23.59765625], + [370.37890625, 26.70703125], + [372.15625, 30.5], + [374.16015625, 34.390625], + [376.21875, 38.4921875], + [378.19140625, 43.921875], + [380.4140625, 50.31640625], + [382.671875, 56.2890625], + [384.48046875, 61.34765625], + [385.7890625, 65.14453125], + [386.5390625, 66.98828125], + [386.921875, 67.60546875], + [387.171875, 67.80859375], + [388.0390625, 68.32421875], + [392.23828125, 70.3671875], + [403.59765625, 76.4296875], + [419.5390625, 85.5], + [435.5078125, 93.82421875], + [451.3046875, 101.015625], + [465.05078125, 107.02734375], + [476.828125, 111.97265625], + [487.38671875, 115.578125], + [495.98046875, 118.03125], + [503.203125, 120.3515625], + [510.375, 122.3828125], + [517.8203125, 124.32421875], + [525.38671875, 126.9375], + [532.9765625, 130.12890625], + [539.046875, 133.22265625], + [543.85546875, 136.421875], + [549.28125, 140.84375], + [554.41015625, 146.04296875], + [558.34375, 151.4921875], + [561.859375, 157.09375], + [564.734375, 162.71875], + [566.95703125, 168.375], + [568.87109375, 174.33984375], + [570.41796875, 181.26953125], + [571.74609375, 189.37890625], + [572.55859375, 197.3515625], + [573.046875, 204.26171875], + [573.7421875, 210.9453125], + [574.38671875, 216.91796875], + [574.75, 222.8515625], + [575.0703125, 228.78515625], + [575.67578125, 234.0078125], + [576.26171875, 238.3515625], + [576.84765625, 242.64453125], + [577.328125, 247.53125], + [577.6484375, 252.56640625], + [577.80859375, 257.91015625], + [578.12890625, 263.2578125], + [578.44921875, 269.1875], + [578.16796875, 275.17578125], + [577.5234375, 281.078125], + [576.14453125, 287.59375], + [574.19921875, 296.390625], + [571.96484375, 306.03125], + [568.765625, 315.54296875], + [564.68359375, 325.640625], + [560.3671875, 335.03125], + [555.93359375, 343.68359375], + [551.56640625, 352.03515625], + [547.86328125, 359.2734375], + [543.82421875, 365.2421875], + [539.91015625, 370.0078125], + [537.37109375, 372.5546875], + [535.4765625, 374.23828125], + [533.37890625, 375.5859375], + [531.2578125, 376.75390625], + [528.46875, 378.96875], + [524.296875, 381.8359375], + [519.03515625, 385.31640625], + [513.50390625, 389.2890625], + [506.43359375, 394.55078125], + [497.18359375, 401.51953125], + [488.43359375, 408.40625], + [481.15234375, 414.0703125], + [475.64453125, 417.7578125], + [471.55078125, 420.32421875], + [468.73828125, 421.828125], + [467.1640625, 422.328125], + [465.9296875, 422.6953125], + [464.7109375, 422.91796875], + [463.2734375, 423.12890625], + [462.06640625, 423.33203125], + [460.88671875, 423.33203125], + [459.484375, 423.33203125], + [458.57421875, 423.33203125], + [457.9296875, 423.10546875], + [457.15234375, 422.796875], + [456.3984375, 422.5625], + [455.8828125, 422.41015625], + [455.55859375, 422.41015625], + [455.453125, 422.3203125], + [455.4453125, 422.06640625], + [455.4453125, 422.06640625], + ] as LocalPoint[]; + + updatePath(startPoint, points); + const selectedElements = getSelectedElements(h.elements, h.state); + expect(selectedElements.length).toBe(3); + expect(selectedElements.filter((e) => e.type === "arrow").length).toBe(1); + expect(selectedElements.filter((e) => e.type === "rectangle").length).toBe( + 1, + ); + expect(selectedElements.filter((e) => e.type === "freedraw").length).toBe( + 1, + ); + }); + + it("Intersects some and encloses some", () => { + const startPoint = pointFrom(112, -190); + const points = [ + [0, 0], + [-0.1015625, 0], + [-6.265625, 3.09375], + [-18.3671875, 9.015625], + [-28.3125, 13.94921875], + [-38.03125, 19.0625], + [-52.578125, 28.72265625], + [-54.51953125, 33.00390625], + [-55.39453125, 36.07421875], + [-56.046875, 39.890625], + [-57.06640625, 45.2734375], + [-57.76171875, 51.2265625], + [-57.76171875, 56.16796875], + [-57.76171875, 60.96875], + [-57.76171875, 65.796875], + [-57.76171875, 70.54296875], + [-57.33203125, 75.21484375], + [-56.17578125, 79.5078125], + [-54.55078125, 83.5625], + [-51.88671875, 88.09375], + [-48.72265625, 92.46875], + [-45.32421875, 96.2421875], + [-41.62890625, 100.5859375], + [-37.9375, 104.92578125], + [-33.94921875, 108.91796875], + [-29.703125, 113.51953125], + [-24.45703125, 118.49609375], + [-18.66796875, 123.5390625], + [-12.7109375, 128.96484375], + [-6.2578125, 133.984375], + [0.203125, 138.5078125], + [7.1640625, 143.71875], + [16.08984375, 149.9765625], + [25.01953125, 156.1640625], + [33.8203125, 162.25], + [42.05078125, 167.79296875], + [48.75390625, 172.46484375], + [55.3984375, 177.90625], + [61.296875, 184.12890625], + [66.02734375, 191.21484375], + [69.765625, 198.109375], + [73.03515625, 204.79296875], + [76.09375, 212.26171875], + [78.984375, 219.52734375], + [81.58203125, 226.34765625], + [84.1640625, 232.3046875], + [86.7265625, 237.16796875], + [89.68359375, 241.34765625], + [93.83984375, 245.12890625], + [100.12109375, 249.328125], + [107.109375, 253.65625], + [114.08203125, 257.89453125], + [122.578125, 262.31640625], + [130.83984375, 266.359375], + [138.33203125, 269.8671875], + [144.984375, 272.3515625], + [150.265625, 274.1953125], + [155.42578125, 275.9296875], + [159.1328125, 276.73828125], + [161.2421875, 276.73828125], + [165.11328125, 276.7578125], + [172.546875, 276.76171875], + [183.14453125, 276.76171875], + [194.015625, 276.76171875], + [204.1796875, 276.76171875], + [213.484375, 276.76171875], + [221.40625, 276.76171875], + [228.47265625, 276.76171875], + [234.40234375, 276.67578125], + [240.28515625, 275.9765625], + [246.12109375, 274.59375], + [250.75390625, 272.8515625], + [255.046875, 270.18359375], + [259.6328125, 266.60546875], + [264.04296875, 262.4375], + [268.69140625, 256.69921875], + [273.25390625, 249.9375], + [277.85546875, 243.0546875], + [282.19140625, 236.5859375], + [285.24609375, 231.484375], + [287.39453125, 227.1875], + [289.078125, 223.78125], + [290.328125, 221.28125], + [291.0390625, 219.2109375], + [291.40625, 217.83984375], + [291.546875, 216.75390625], + [291.546875, 215.84375], + [291.75390625, 214.7734375], + [291.9609375, 213.15234375], + [291.9609375, 211.125], + [291.9609375, 208.6953125], + [291.9609375, 205.25], + [291.9609375, 201.4453125], + [291.62890625, 197.68359375], + [291.0625, 194.29296875], + [290.6484375, 192.21875], + [290.25390625, 190.8203125], + [289.88671875, 189.94140625], + [289.75, 189.53125], + [289.75, 189.2109375], + [289.7265625, 188.29296875], + [290.09375, 186.3125], + [293.04296875, 182.46875], + [298.671875, 177.46484375], + [305.45703125, 172.13671875], + [312.4921875, 167.35546875], + [318.640625, 163.6875], + [323.1484375, 161.0703125], + [326.484375, 159.37109375], + [329.8046875, 157.39453125], + [332.98046875, 155.2265625], + [336.09765625, 152.6875], + [339.14453125, 149.640625], + [342.37890625, 146.5078125], + [345.96875, 143.03125], + [349.4609375, 139.24609375], + [353.23046875, 134.83203125], + [356.68359375, 129.72265625], + [359.48828125, 123.9140625], + [362.76953125, 116.09765625], + [367.91796875, 93.69140625], + [368.23828125, 88.5546875], + [368.34375, 86.2890625], + [369.94921875, 80.15234375], + [372.7578125, 72.04296875], + [375.703125, 62.5], + [378.33203125, 52.72265625], + [380.109375, 44.4453125], + [381.40625, 37.59375], + [382.26953125, 31.95703125], + [382.71875, 26.60546875], + [382.81640625, 21.76171875], + [382.81640625, 17.84375], + [382.55859375, 13.9609375], + [382.27734375, 9.65625], + [381.67578125, 5.3515625], + [380.40625, 1.0703125], + [378.71484375, -3.2109375], + [376.48046875, -7.52734375], + [373.93359375, -11.71875], + [370.44140625, -16.32421875], + [365.86328125, -21.49609375], + [359.94921875, -26.8359375], + [353.33984375, -32.046875], + [345.84765625, -37.30859375], + [336.55859375, -43.21484375], + [326.34765625, -48.5859375], + [315.515625, -53.15234375], + [305.375, -56.67578125], + [296, -59.47265625], + [286.078125, -61.984375], + [276.078125, -63.78125], + [266.578125, -65.09765625], + [258.90625, -66.11328125], + [249.8984375, -67.34765625], + [238.84765625, -68.6796875], + [229.19921875, -70.01171875], + [219.66015625, -71.50390625], + [209.109375, -72.99609375], + [197.14453125, -74.625], + [186.52734375, -76.421875], + [176.66796875, -77.8203125], + [167.26953125, -79.1328125], + [159.57421875, -80.6328125], + [152.75, -81.4609375], + [146.4609375, -81.89453125], + [139.97265625, -82.23828125], + [133.546875, -82.23828125], + [127.84765625, -82.23828125], + [123.01953125, -82.23828125], + [117.9375, -81.9140625], + [112.59765625, -81.046875], + [107.3046875, -79.90234375], + [100.41796875, -78.45703125], + [92.74609375, -76.87890625], + [85.40625, -75.359375], + [77.546875, -73.80859375], + [69.71875, -72.6640625], + [62.4921875, -71.9609375], + [56.02734375, -71.23046875], + [50.37109375, -70.26171875], + [46.20703125, -69.32421875], + [43.45703125, -68.48046875], + [41.48046875, -67.5703125], + [39.99609375, -66.90234375], + [38.51171875, -66.23828125], + [36.7734375, -65.3671875], + [35.4609375, -64.359375], + [34.18359375, -63.328125], + [33.0078125, -62.54296875], + [31.8125, -61.76953125], + [30.5234375, -60.8984375], + [29.4921875, -60.09765625], + [28.5078125, -59.3828125], + [27.24609375, -58.61328125], + [25.49609375, -57.73828125], + [23.7421875, -56.859375], + [21.99609375, -55.984375], + [20.51953125, -55.16796875], + [19.4921875, -54.44140625], + [18.81640625, -53.84375], + [18.35546875, -53.52734375], + [18.0859375, -53.46484375], + [17.85546875, -53.44921875], + [17.85546875, -53.44921875], + ] as LocalPoint[]; + + updatePath(startPoint, points); + + const selectedElements = getSelectedElements(h.elements, h.state); + expect(selectedElements.length).toBe(4); + expect(selectedElements.filter((e) => e.type === "line").length).toBe(1); + expect(selectedElements.filter((e) => e.type === "ellipse").length).toBe(1); + expect(selectedElements.filter((e) => e.type === "diamond").length).toBe(1); + expect(selectedElements.filter((e) => e.type === "freedraw").length).toBe( + 1, + ); + }); + + it("Single linear element", () => { + const startPoint = pointFrom(62, -200); + const points = [ + [0, 0], + [0, 0.1015625], + [-1.65625, 2.2734375], + [-8.43359375, 12.265625], + [-17.578125, 25.83203125], + [-25.484375, 37.38671875], + [-31.453125, 47.828125], + [-34.92578125, 55.21875], + [-37.1171875, 60.05859375], + [-38.4375, 63.49609375], + [-39.5, 66.6328125], + [-40.57421875, 69.84375], + [-41.390625, 73.53515625], + [-41.9296875, 77.078125], + [-42.40625, 79.71484375], + [-42.66796875, 81.83203125], + [-42.70703125, 83.32421875], + [-42.70703125, 84.265625], + [-42.70703125, 85.171875], + [-42.70703125, 86.078125], + [-42.70703125, 86.6484375], + [-42.70703125, 87], + [-42.70703125, 87.1796875], + [-42.70703125, 87.4296875], + [-42.70703125, 87.83203125], + [-42.70703125, 88.86328125], + [-42.70703125, 91.27734375], + [-42.70703125, 95.0703125], + [-42.44140625, 98.46875], + [-42.17578125, 100.265625], + [-42.17578125, 101.16015625], + [-42.16015625, 101.76171875], + [-42.0625, 102.12109375], + [-42.0625, 102.12109375], + ] as LocalPoint[]; + updatePath(startPoint, points); + + const selectedElements = getSelectedElements(h.elements, h.state); + expect(selectedElements.length).toBe(1); + expect(h.app.state.selectedLinearElement).toBeDefined(); + }); +}); + +describe("Special cases", () => { + it("Select only frame if its children are also selected", () => { + act(() => { + const elements = [ + { + id: "CaUA2mmuudojzY98_oVXo", + type: "rectangle", + x: -96.64353835077907, + y: -270.1600585741129, + width: 146.8359375, + height: 104.921875, + angle: 0, + strokeColor: "#1e1e1e", + backgroundColor: "transparent", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 1, + opacity: 100, + groupIds: [], + frameId: "85VShCn1P9k81JqSeOg-c", + index: "aE", + roundness: { + type: 3, + }, + seed: 227442978, + version: 15, + versionNonce: 204983970, + isDeleted: false, + boundElements: [], + updated: 1740959550684, + link: null, + locked: false, + }, + { + id: "RZzDDA1DBJHw5OzHVNDvc", + type: "diamond", + x: 126.64943039922093, + y: -212.4920898241129, + width: 102.55859375, + height: 93.80078125, + angle: 0, + strokeColor: "#1e1e1e", + backgroundColor: "transparent", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 1, + opacity: 100, + groupIds: [], + frameId: "85VShCn1P9k81JqSeOg-c", + index: "aH", + roundness: { + type: 2, + }, + seed: 955233890, + version: 14, + versionNonce: 2135303358, + isDeleted: false, + boundElements: [], + updated: 1740959550684, + link: null, + locked: false, + }, + { + id: "CSVDDbC9vxqgO2uDahcE9", + type: "ellipse", + x: -20.999007100779068, + y: -87.0272460741129, + width: 116.13671875, + height: 70.7734375, + angle: 0, + strokeColor: "#1e1e1e", + backgroundColor: "transparent", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 1, + opacity: 100, + groupIds: [], + frameId: "85VShCn1P9k81JqSeOg-c", + index: "aI", + roundness: { + type: 2, + }, + seed: 807647870, + version: 16, + versionNonce: 455740962, + isDeleted: false, + boundElements: [], + updated: 1740959550684, + link: null, + locked: false, + }, + { + id: "85VShCn1P9k81JqSeOg-c", + type: "frame", + x: -164.95603835077907, + y: -353.5155273241129, + width: 451.04296875, + height: 397.09765625, + angle: 0, + strokeColor: "#bbb", + backgroundColor: "transparent", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 0, + opacity: 100, + groupIds: [], + frameId: null, + index: "aJ", + roundness: null, + seed: 1134892578, + version: 57, + versionNonce: 1699466238, + isDeleted: false, + boundElements: [], + updated: 1740959550367, + link: null, + locked: false, + name: null, + }, + ].map((e) => ({ + ...e, + index: null, + angle: e.angle as Radians, + })) as ExcalidrawElement[]; + + h.elements = elements; + }); + + const startPoint = pointFrom(-352, -64); + const points = [ + [0, 0], + [0.1015625, 0], + [3.80078125, -1.05859375], + [14.38671875, -5.10546875], + [26.828125, -10.70703125], + [38.17578125, -16.10546875], + [49.6328125, -21.59375], + [79.890625, -34.078125], + [111.5859375, -46.4140625], + [125.61328125, -51.265625], + [139.20703125, -55.81640625], + [151.046875, -60.27734375], + [160.86328125, -64.140625], + [170.15625, -67.51171875], + [181.0234375, -71.5234375], + [192.6796875, -75.79296875], + [204.66015625, -80.19921875], + [218.22265625, -85.6875], + [233.359375, -91.9375], + [264.22265625, -103.91796875], + [280.390625, -109.80859375], + [295.48046875, -114.99609375], + [309.453125, -120.28125], + [323.5546875, -126.125], + [339.26953125, -132.6796875], + [354.67578125, -139.64453125], + [370.86328125, -146.53125], + [384.70703125, -152.4921875], + [394.7109375, -157.6796875], + [405.6171875, -163.07421875], + [416.390625, -167.96484375], + [425.41796875, -171.6484375], + [433.26171875, -174.78515625], + [440.76953125, -177.68359375], + [447.4140625, -179.71875], + [453.3828125, -181.11328125], + [458.421875, -182.13671875], + [462.82421875, -182.5546875], + [467.2109375, -182.640625], + [472.09765625, -182.640625], + [481.9609375, -182.640625], + [487.23828125, -182.5859375], + [492.03515625, -181.91796875], + [496.76953125, -180.640625], + [501.43359375, -179.2734375], + [505.203125, -177.73046875], + [508.33984375, -176.08984375], + [511.8671875, -174.16796875], + [515.9140625, -172.09375], + [519.703125, -170.125], + [523.6796875, -167.8828125], + [528.109375, -165.3984375], + [532.01953125, -163.3125], + [535.28125, -161.65625], + [537.62890625, -159.7734375], + [539.0859375, -157.53125], + [540.1640625, -155.7421875], + [540.98046875, -154.2578125], + [541.87890625, -152.33203125], + [542.69140625, -150.0078125], + [543.25390625, -147.671875], + [543.90625, -145.125], + [544.66796875, -142.01171875], + [545.34375, -138.1484375], + [546.03515625, -132.72265625], + [546.41015625, -126.80078125], + [546.44921875, -121.25390625], + [546.38671875, -116.3046875], + [545.21484375, -112], + [541.50390625, -107.2421875], + [536.515625, -102.83203125], + [531.44140625, -98.95703125], + [526.39453125, -95.23046875], + [521.15234375, -91.9921875], + [514.38671875, -87.984375], + [506.953125, -83.19140625], + [499.1171875, -77.52734375], + [491.37109375, -71.6484375], + [484.85546875, -66.3984375], + [477.8203125, -60.21875], + [469.921875, -53.26953125], + [460.84765625, -45.6171875], + [451.796875, -38.359375], + [444.33984375, -32.48046875], + [438.4296875, -27.68359375], + [435.2109375, -24.84375], + [433.07421875, -23.23828125], + [429.7421875, -21.125], + [424.8984375, -17.546875], + [418.7421875, -13.01171875], + [411.84375, -8.3359375], + [404.80078125, -3.65625], + [398.23828125, 0.6171875], + [392.32421875, 4.74609375], + [386.21875, 9.69921875], + [379.7421875, 14.734375], + [373.6015625, 19.95703125], + [367.34375, 26.72265625], + [360.73828125, 34.48046875], + [354.1484375, 42.51953125], + [347.21484375, 51.19140625], + [340.59765625, 59.7265625], + [334.46875, 67.703125], + [328.9921875, 74.82421875], + [323.78515625, 81.6796875], + [318.6640625, 88.34375], + [314.2109375, 93.8984375], + [309.10546875, 100.66015625], + [304.17578125, 107.2734375], + [299.97265625, 112.421875], + [295.890625, 117.99609375], + [291.8828125, 123.4453125], + [288.0078125, 128.25], + [284.91796875, 132.265625], + [282.453125, 135.66796875], + [279.80078125, 139.16015625], + [276.7734375, 143.53515625], + [274.3515625, 147.6484375], + [272.0859375, 151.0546875], + [269.5546875, 154.37890625], + [267.71484375, 156.73828125], + [266.62890625, 158.484375], + [265.5546875, 160.03125], + [264.73828125, 161.30078125], + [264.16015625, 162.51953125], + [263.46484375, 163.734375], + [262.9140625, 164.9453125], + [262.05078125, 166.3046875], + [261.234375, 167.390625], + [260.46484375, 168.53515625], + [259.5703125, 169.6640625], + [258.9296875, 170.1875], + [258.9296875, 170.1875], + ] as LocalPoint[]; + + updatePath(startPoint, points); + + const selectedElements = getSelectedElements(h.elements, h.state); + expect(selectedElements.length).toBe(1); + expect(selectedElements[0].type).toBe("frame"); + }); + + it("Selects group if any group from group is selected", () => { + act(() => { + const elements = [ + { + type: "line", + version: 594, + versionNonce: 1548428815, + isDeleted: false, + id: "FBFkTIUB1trLc6nEdp1Pu", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 1, + opacity: 100, + angle: 0, + x: 170.81219641259787, + y: 391.1659993876855, + strokeColor: "#1e1e1e", + backgroundColor: "#846358", + width: 66.16406551308279, + height: 78.24124358133415, + seed: 838106785, + groupIds: ["ts5VcKn3YXMmP8ipg8J5v", "-9NzH7Fa5JaHu4ArEFpa_"], + frameId: null, + roundness: { + type: 2, + }, + boundElements: [], + updated: 1740960278015, + link: null, + locked: false, + startBinding: null, + endBinding: null, + lastCommittedPoint: null, + startArrowhead: null, + endArrowhead: null, + points: [ + [0, 0], + [-12.922669045523984, 78.24124358133415], + [53.24139646755881, 78.24124358133415], + [41.35254094567674, 4.2871914291142], + [0, 0], + ], + index: "aJ", + }, + { + type: "line", + version: 947, + versionNonce: 1038960225, + isDeleted: false, + id: "RsALsOjcB5dAyH4JNlfqJ", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 1, + opacity: 100, + angle: 0, + x: 188.53119264021603, + y: 207.94959072391882, + strokeColor: "#1e1e1e", + backgroundColor: "#2f9e44", + width: 369.2312846526558, + height: 192.4489303545334, + seed: 319685249, + groupIds: ["ts5VcKn3YXMmP8ipg8J5v", "-9NzH7Fa5JaHu4ArEFpa_"], + frameId: null, + roundness: { + type: 2, + }, + boundElements: [], + updated: 1740960278015, + link: null, + locked: false, + startBinding: null, + endBinding: null, + lastCommittedPoint: null, + startArrowhead: null, + endArrowhead: null, + points: [ + [0, 0], + [-184.8271826294887, 192.4489303545334], + [184.4041020231671, 192.4489303545334], + [0, 0], + ], + index: "aK", + }, + { + type: "line", + version: 726, + versionNonce: 1463389231, + isDeleted: false, + id: "YNXwgpVIEUFgUZpJ564wo", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 1, + opacity: 100, + angle: 0, + x: 184.66726071162367, + y: 123.16737006571739, + strokeColor: "#1e1e1e", + backgroundColor: "#2f9e44", + width: 290.9653230160535, + height: 173.62827429793325, + seed: 1108085345, + groupIds: ["ts5VcKn3YXMmP8ipg8J5v", "-9NzH7Fa5JaHu4ArEFpa_"], + frameId: null, + roundness: { + type: 2, + }, + boundElements: [], + updated: 1740960278015, + link: null, + locked: false, + startBinding: null, + endBinding: null, + lastCommittedPoint: null, + startArrowhead: null, + endArrowhead: null, + points: [ + [0, 0], + [-142.34630272423374, 173.62827429793325], + [148.61902029181974, 173.62827429793325], + [0, 0], + ], + index: "aL", + }, + { + type: "line", + version: 478, + versionNonce: 2081935937, + isDeleted: false, + id: "NV7XOz9ZIB8CbuqQIjt5k", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 1, + opacity: 100, + angle: 0, + x: 189.05565121741444, + y: 54.65530340848173, + strokeColor: "#1e1e1e", + backgroundColor: "#2f9e44", + width: 194.196753378859, + height: 137.02921662223056, + seed: 398333505, + groupIds: ["ts5VcKn3YXMmP8ipg8J5v", "-9NzH7Fa5JaHu4ArEFpa_"], + frameId: null, + roundness: { + type: 2, + }, + boundElements: [], + updated: 1740960278015, + link: null, + locked: false, + startBinding: null, + endBinding: null, + lastCommittedPoint: null, + startArrowhead: null, + endArrowhead: null, + points: [ + [0, 0], + [-97.0316913876915, 135.70546644407042], + [97.1650619911675, 137.02921662223056], + [0, 0], + ], + index: "aM", + }, + { + type: "ellipse", + version: 282, + versionNonce: 1337339471, + isDeleted: false, + id: "b7FzLnG0L3-50bqij9mGX", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 1, + opacity: 100, + angle: 0, + x: 73.9036924826674, + y: 334.3607129519222, + strokeColor: "#000000", + backgroundColor: "#c2255c", + width: 25.723148574685204, + height: 25.723148574685204, + seed: 654550561, + groupIds: ["ts5VcKn3YXMmP8ipg8J5v", "-9NzH7Fa5JaHu4ArEFpa_"], + frameId: null, + roundness: { + type: 2, + }, + boundElements: [], + updated: 1740960278015, + link: null, + locked: false, + index: "aN", + }, + { + type: "ellipse", + version: 292, + versionNonce: 1355145761, + isDeleted: false, + id: "XzVfrVf3-sFJFPdOo51sb", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 1, + opacity: 100, + angle: 0, + x: 138.21156391938035, + y: 380.4480208148999, + strokeColor: "#000000", + backgroundColor: "#f08c00", + width: 25.723148574685204, + height: 25.723148574685204, + seed: 2060204545, + groupIds: ["ts5VcKn3YXMmP8ipg8J5v", "-9NzH7Fa5JaHu4ArEFpa_"], + frameId: null, + roundness: { + type: 2, + }, + boundElements: [], + updated: 1740960278015, + link: null, + locked: false, + index: "aO", + }, + { + type: "ellipse", + version: 288, + versionNonce: 1889111151, + isDeleted: false, + id: "D4m0Ex4rPc1-8T-uv5vGh", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 1, + opacity: 100, + angle: 0, + x: 208.9502224997646, + y: 331.14531938008656, + strokeColor: "#000000", + backgroundColor: "#6741d9", + width: 25.723148574685204, + height: 25.723148574685204, + seed: 337072609, + groupIds: ["ts5VcKn3YXMmP8ipg8J5v", "-9NzH7Fa5JaHu4ArEFpa_"], + frameId: null, + roundness: { + type: 2, + }, + boundElements: [], + updated: 1740960278015, + link: null, + locked: false, + index: "aP", + }, + { + type: "ellipse", + version: 296, + versionNonce: 686224897, + isDeleted: false, + id: "E0wxH4dAzQsv7Mj6OngC8", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 1, + opacity: 100, + angle: 0, + x: 285.04787036654153, + y: 367.5864465275573, + strokeColor: "#000000", + backgroundColor: "#e8590c", + width: 25.723148574685204, + height: 25.723148574685204, + seed: 670330305, + groupIds: ["ts5VcKn3YXMmP8ipg8J5v", "-9NzH7Fa5JaHu4ArEFpa_"], + frameId: null, + roundness: { + type: 2, + }, + boundElements: [], + updated: 1740960278015, + link: null, + locked: false, + index: "aQ", + }, + { + type: "ellipse", + version: 290, + versionNonce: 1974216335, + isDeleted: false, + id: "yKv_UI6iqa6zjVgYtXVcg", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 1, + opacity: 100, + angle: 0, + x: 113.56021320197334, + y: 228.25272508134577, + strokeColor: "#000000", + backgroundColor: "#228be6", + width: 25.723148574685204, + height: 25.723148574685204, + seed: 495127969, + groupIds: ["ts5VcKn3YXMmP8ipg8J5v", "-9NzH7Fa5JaHu4ArEFpa_"], + frameId: null, + roundness: { + type: 2, + }, + boundElements: [], + updated: 1740960278015, + link: null, + locked: false, + index: "aR", + }, + { + type: "ellipse", + version: 290, + versionNonce: 662343137, + isDeleted: false, + id: "udyW842HtUTlqjDEOxoPN", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 1, + opacity: 100, + angle: 0, + x: 166.0783082086228, + y: 271.12463937248776, + strokeColor: "#000000", + backgroundColor: "#ffd43b", + width: 25.723148574685204, + height: 25.723148574685204, + seed: 1274196353, + groupIds: ["ts5VcKn3YXMmP8ipg8J5v", "-9NzH7Fa5JaHu4ArEFpa_"], + frameId: null, + roundness: { + type: 2, + }, + boundElements: [], + updated: 1740960278015, + link: null, + locked: false, + index: "aS", + }, + { + type: "ellipse", + version: 300, + versionNonce: 229014703, + isDeleted: false, + id: "R3VRfgkowIgnr5dFXwWXa", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 1, + opacity: 100, + angle: 0, + x: 234.67337107445002, + y: 237.89890579685272, + strokeColor: "#000000", + backgroundColor: "#38d9a9", + width: 25.723148574685204, + height: 25.723148574685204, + seed: 2021841249, + groupIds: ["ts5VcKn3YXMmP8ipg8J5v", "-9NzH7Fa5JaHu4ArEFpa_"], + frameId: null, + roundness: { + type: 2, + }, + boundElements: [], + updated: 1740960278015, + link: null, + locked: false, + index: "aT", + }, + { + type: "ellipse", + version: 332, + versionNonce: 1670392257, + isDeleted: false, + id: "90W2w6zgGHdYda8UBiG2R", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 1, + opacity: 100, + angle: 0, + x: 136.0679682048231, + y: 155.37047078640435, + strokeColor: "#000000", + backgroundColor: "#fa5252", + width: 25.723148574685204, + height: 25.723148574685204, + seed: 344130881, + groupIds: ["ts5VcKn3YXMmP8ipg8J5v", "-9NzH7Fa5JaHu4ArEFpa_"], + frameId: null, + roundness: { + type: 2, + }, + boundElements: [], + updated: 1740960278015, + link: null, + locked: false, + index: "aU", + }, + { + type: "ellipse", + version: 337, + versionNonce: 2083589839, + isDeleted: false, + id: "nTDHvOk2mXLUFNn--7JvS", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 1, + opacity: 100, + angle: 0, + x: 176.7962867814079, + y: 102.85237577975542, + strokeColor: "#000000", + backgroundColor: "#9775fa", + width: 25.723148574685204, + height: 25.723148574685204, + seed: 995276065, + groupIds: ["ts5VcKn3YXMmP8ipg8J5v", "-9NzH7Fa5JaHu4ArEFpa_"], + frameId: null, + roundness: { + type: 2, + }, + boundElements: [], + updated: 1740960278015, + link: null, + locked: false, + index: "aV", + }, + { + type: "ellipse", + version: 313, + versionNonce: 1715947937, + isDeleted: false, + id: "iS2Q6cvQ5n_kxINfwu0HS", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 1, + opacity: 100, + angle: 0, + x: 212.16561607160025, + y: 153.22687507184727, + strokeColor: "#000000", + backgroundColor: "#fab005", + width: 25.723148574685204, + height: 25.723148574685204, + seed: 1885432065, + groupIds: ["ts5VcKn3YXMmP8ipg8J5v", "-9NzH7Fa5JaHu4ArEFpa_"], + frameId: null, + roundness: { + type: 2, + }, + boundElements: [], + updated: 1740960278015, + link: null, + locked: false, + index: "aW", + }, + { + type: "line", + version: 1590, + versionNonce: 2078563567, + isDeleted: false, + id: "X4-EPaJDEnPZKN1bWhFvs", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 1, + opacity: 100, + angle: 0, + x: 158.19616469467843, + y: 72.35608879274483, + strokeColor: "#000000", + backgroundColor: "#fab005", + width: 84.29101925982515, + height: 84.66090652809709, + seed: 489595105, + groupIds: ["uRBC-GT117eEzaf2ehdX_", "-9NzH7Fa5JaHu4ArEFpa_"], + frameId: null, + roundness: null, + boundElements: [], + updated: 1740960278015, + link: null, + locked: false, + startBinding: null, + endBinding: null, + lastCommittedPoint: null, + startArrowhead: null, + endArrowhead: null, + points: [ + [0, 0], + [20.062524675376327, -6.158183070239938], + [30.37468761946564, 12.304817735352257], + [40.379925616688446, -6.1461467434106165], + [60.1779773078079, -0.29196108982718233], + [54.38738874028317, -19.998927589317724], + [72.41919795710177, -30.209920701755497], + [54.27320673839876, -40.520131959038096], + [60.381292814514666, -60.13991664051316], + [40.445474389553596, -54.21058549574358], + [30.40022403822941, -72.35608879274483], + [20.373038782485533, -54.22107619387393], + [0.4211938029164521, -59.96466401524409], + [5.9466053348070105, -39.94020499773404], + [-11.871821302723378, -30.212694376435106], + [5.916318974536789, -20.128073448241587], + [0, 0], + ], + index: "aX", + }, + { + type: "line", + version: 1719, + versionNonce: 1758424449, + isDeleted: false, + id: "MHWh6yM-hxZbKvIX473TA", + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: 1, + opacity: 100, + angle: 0, + x: 166.45958905094363, + y: 64.16037225967494, + strokeColor: "#000000", + backgroundColor: "#ffd43b", + width: 61.28316986803382, + height: 61.55209370467244, + seed: 1330240705, + groupIds: ["uRBC-GT117eEzaf2ehdX_", "-9NzH7Fa5JaHu4ArEFpa_"], + frameId: null, + roundness: null, + boundElements: [], + updated: 1740960278015, + link: null, + locked: false, + startBinding: null, + endBinding: null, + lastCommittedPoint: null, + startArrowhead: null, + endArrowhead: null, + points: [ + [0, 0], + [14.586312023026041, -4.477262020152575], + [22.08369476864762, 8.946127856709843], + [29.357929973514253, -4.468511096647962], + [43.751958845119866, -0.2122681777946347], + [39.54195372315489, -14.540074226137776], + [52.651848904941595, -21.96390218462942], + [39.45893853270161, -29.459865970613833], + [43.89977789919855, -43.724287114968824], + [29.40558673003957, -39.41341021563198], + [22.102260835384786, -52.605965847962594], + [14.812069036519228, -39.4210374010807], + [0.30622587789485506, -43.5968709738604], + [4.323435973028119, -29.038234309343977], + [-8.631320963092225, -21.96591876454555], + [4.301416496013572, -14.633968779551441], + [0, 0], + ], + index: "aY", + }, + ].map((e) => ({ + ...e, + index: null, + angle: e.angle as Radians, + })) as ExcalidrawElement[]; + + h.elements = elements; + }); + + const startPoint = pointFrom(117, 463); + const points = [ + [0, 0], + [0.09765625, 0], + [3.24609375, 0], + [6.9765625, 0], + [10.76171875, 0], + [14.03125, 0], + [23.24609375, 0.32421875], + [28.65625, 0.6484375], + [32.0546875, 0.6484375], + [35.4296875, 0.6484375], + [38.86328125, 0.3828125], + [41.9765625, -0.109375], + [45.0390625, -0.4296875], + [47.74609375, -0.5234375], + [49.953125, -0.73046875], + [52.12890625, -0.9375], + [54.25, -1.14453125], + [55.9921875, -1.3828125], + [57.67578125, -1.58984375], + [58.8125, -1.76953125], + [59.453125, -1.76953125], + [60.09375, -1.76953125], + [60.09375, -1.76953125], + ] as LocalPoint[]; + + updatePath(startPoint, points); + + const selectedElements = getSelectedElements(h.elements, h.state); + expect(selectedElements.length).toBe(16); + expect(h.app.state.selectedGroupIds["-9NzH7Fa5JaHu4ArEFpa_"]).toBe(true); + }); +}); diff --git a/packages/excalidraw/lasso/types.ts b/packages/excalidraw/lasso/types.ts index c8c4bdccc..a3557debf 100644 --- a/packages/excalidraw/lasso/types.ts +++ b/packages/excalidraw/lasso/types.ts @@ -9,7 +9,7 @@ export type LassoWorkerInput = { elementsSegments: ElementsSegmentsMap; intersectedElements: Set; enclosedElements: Set; - simplifyDistance: number; + simplifyDistance?: number; }; export type LassoWorkerOutput = { diff --git a/packages/excalidraw/lasso/worker.ts b/packages/excalidraw/lasso/worker.ts index 24d42915f..773656c35 100644 --- a/packages/excalidraw/lasso/worker.ts +++ b/packages/excalidraw/lasso/worker.ts @@ -86,7 +86,10 @@ export const updateSelection = (input: LassoWorkerInput): LassoWorkerOutput => { simplifyDistance, } = input; // simplify the path to reduce the number of points - const path = simplify(lassoPath, simplifyDistance) as GlobalPoint[]; + let path: GlobalPoint[] = lassoPath; + if (simplifyDistance) { + path = simplify(lassoPath, simplifyDistance) as GlobalPoint[]; + } // close the path to form a polygon for enclosure check const closedPath = polygonFromPoints(path); // as the path might not enclose a shape anymore, clear before checking