feat: better unlock (#9546)

* change lock label

* feat: add unlock logic for single units on pointer up

* feat: add unlock popup

* fix: linting errors

* style: padding tweaks

* style: remove redundant line

* feat: lock multiple units together

* style: tweak color & position

* feat: add highlight for locked elements

* feat: select groups correctly after unlocking

* test: update snapshots

* fix: lasso from selecting locked elements

* fix: should prevent selecting unlocked elements and setting locked id at the same time

* fix: reset hit locked id immediately when appropriate

* feat: capture locked units in delta

* test: update locking test

* feat: show lock highlight when locking (including undo/redo)

* feat: make locked highlighting consistent

* feat: show correct cursor type when moving over locked elements

* fix history

* remove `lockedUnits.singleUnits`

* tweak button

* do not render UnlockPopup if not locked element selected

* tweak actions

* refactor: simplify type

* refactor: rename type

* refactor: simplify hit element setting & checking

* fix: prefer locked over link

* rename to `activeLockedId`

* refactor: getElementAtPosition takes an optional hitelments array

* fix: avoid setting active locked id after resizing

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
Ryan Di
2025-05-21 21:57:12 +10:00
committed by GitHub
co-authored by dwelle
parent 41a7613dff
commit 712f267519
19 changed files with 843 additions and 109 deletions
@@ -72,7 +72,7 @@ describe("element locking", () => {
expect(lockedRectangle).toEqual(expect.objectContaining({ x: 0, y: 0 }));
});
it("you can drag element that's below a locked element", () => {
it("dragging element that's below a locked element", () => {
const rectangle = API.createElement({
type: "rectangle",
width: 100,
@@ -89,6 +89,14 @@ describe("element locking", () => {
API.setElements([rectangle, lockedRectangle]);
mouse.downAt(50, 50);
mouse.moveTo(100, 100);
mouse.upAt(100, 100);
expect(lockedRectangle).toEqual(expect.objectContaining({ x: 0, y: 0 }));
expect(rectangle).toEqual(expect.objectContaining({ x: 0, y: 0 }));
// once selected, the locked element above should be ignored
API.setSelectedElements([rectangle]);
mouse.downAt(50, 50);
mouse.moveTo(100, 100);
mouse.upAt(100, 100);
@@ -107,7 +115,7 @@ describe("element locking", () => {
expect(API.getSelectedElements().length).toBe(1);
});
it("clicking on a locked element should select the unlocked element beneath it", () => {
it("clicking on a locked element should not select the unlocked element beneath it", () => {
const rectangle = API.createElement({
type: "rectangle",
width: 100,
@@ -125,8 +133,8 @@ describe("element locking", () => {
API.setElements([rectangle, lockedRectangle]);
expect(API.getSelectedElements().length).toBe(0);
mouse.clickAt(50, 50);
expect(API.getSelectedElements().length).toBe(1);
expect(API.getSelectedElement().id).toBe(rectangle.id);
expect(API.getSelectedElements().length).toBe(0);
expect(h.state.activeLockedId).toBe(lockedRectangle.id);
});
it("right-clicking on a locked element should select it & open its contextMenu", () => {