Skip to content

feat(core): drag to move and corner handles to resize in the inspector - #382

Open
bruno-begh wants to merge 2 commits into
1weiho:mainfrom
bruno-begh:feat/inspector-drag-resize
Open

feat(core): drag to move and corner handles to resize in the inspector#382
bruno-begh wants to merge 2 commits into
1weiho:mainfrom
bruno-begh:feat/inspector-drag-resize

Conversation

@bruno-begh

@bruno-begh bruno-begh commented Jul 29, 2026

Copy link
Copy Markdown

Closes #380.

Problem

Inspect mode edits everything about the selected element except the two most visual properties: where it sits and how big it is. Position and size are the only adjustments that have to leave the canvas, either back to the agent or into the .tsx to guess a number, reload, and look again.

Change

Dragging the body of the selected element moves it. Four corner handles resize it, with the opposite edge anchored.

Both gestures end as ordinary set-style operations, the same ones the panel already emits, so Save, undo, redo and the write-back into the .tsx need no new machinery:

  • move: translate
  • resize: width and height, plus translate when a top or left corner is dragged

Three things worth a reviewer's attention:

  • Movement uses the standalone translate property rather than transform, so it composes with whatever transform the slide already declares instead of overwriting it.
  • The canvas scale is resolved from the element's own canvas via closest('[data-osd-canvas]'). The thumbnail rail and the overview grid render the same slide at much smaller scales, and reading the wrong one multiplies every drag.
  • The DOM is restored to its pre-gesture state before bufferOps runs, because that call snapshots the current inline value for undo. Without the restore, undo would return to the dragged position rather than the original one.

A gesture ends with a click the overlay would otherwise read as "select whatever is under the pointer", so exactly one click is swallowed, and the listener disarms itself after 300ms if no click follows.

Testing

  • pnpm format:check, pnpm lint, pnpm typecheck: clean
  • pnpm test: 305 passing
  • e2e/tests/inspector.spec.ts: 7 passing
  • By hand in apps/demo: drag plus each of the four handles, then Save, undo and redo, checking the values written into the .tsx and that an existing transform on the element survives.
  • packages/core/tools/verify-drag-resize.mjs drives the gestures against the verify-drag-resize fixture and asserts the source was rewritten with the expected geometry. Run it with the demo dev server up.

No new dependencies.

The fixture slide and the verification script are there to make the behaviour checkable in a clone. Happy to drop either if you would rather keep the demo free of them.

Summary by CodeRabbit

  • New Features
    • Added interactive drag-and-drop repositioning for selected elements in the inspector.
    • Added corner handles to resize selected elements, with updates reflected immediately and saved as style edits.
    • Enabled the interactive selected-frame overlay in Inspect mode.
  • Tests
    • Added end-to-end verification covering drag, resize, and saving the resulting style changes.

The inspector edits properties of the selected element but cannot move it, so
nudging a headline a few pixels means going back to the agent.

Dragging the body of a selected element now repositions it and the four corner
handles resize it, with the opposite edge anchored. Both end up as ordinary
set-style operations (translate, width, height), the same ones the panel
already emits, so Save, undo and redo need no new machinery and the result is
written back into the .tsx.

Movement uses the standalone translate property rather than transform, so it
composes with whatever transform the slide already declares. The canvas scale
is resolved from the element's own canvas because the same slide also renders
in the thumbnail rail and the overview at smaller scales.

Verified with packages/core/tools/verify-drag-resize.mjs against the fixture
added in apps/demo.
@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

@bruno-begh is attempting to deploy a commit to the open-slide Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e393acc2-0e0a-4170-8fe2-a709e0c09d76

📥 Commits

Reviewing files that changed from the base of the PR and between 9e8e575 and d6391db.

📒 Files selected for processing (2)
  • .changeset/inspector-drag-resize.md
  • packages/core/src/app/components/inspector/drag-resize-layer.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/core/src/app/components/inspector/drag-resize-layer.tsx

Walkthrough

Adds direct dragging and corner resizing for selected elements in Inspect mode. Gestures apply normalized translate, width, and height style edits, while a Playwright script verifies persistence to the slide source.

Changes

Inspector interaction

Layer / File(s) Summary
Drag and resize interaction engine
packages/core/src/app/components/inspector/drag-resize-layer.tsx
Adds scaled drag and corner-resize handling, inline-style restoration, normalized translations, edit buffering, and click suppression.
Selected-frame integration
packages/core/src/app/components/inspector/inspect-overlay.tsx
Enables the interactive layer for selected frames while keeping hover frames non-interactive.
Fixture, end-to-end verification, and release entry
apps/demo/slides/verify-drag-resize/index.tsx, packages/core/tools/verify-drag-resize.mjs, .changeset/inspector-drag-resize.md
Adds a verification slide, Playwright checks for persisted drag and resize values, fixture restoration, and a minor release changeset.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant InspectorOverlay
  participant DragResizeLayer
  participant SelectedElement
  participant SourceFile
  InspectorOverlay->>DragResizeLayer: mount controls for selected element
  DragResizeLayer->>SelectedElement: apply scaled pointer movement
  DragResizeLayer->>SourceFile: persist translate, width, and height style edits
Loading

Possibly related PRs

Poem

A bunny nudges pixels with paws so light,
Corners grow wider, left or right.
Styles hop back into source with care,
Four little handles floating there.
“Save!” cries the rabbit—“What a sight!”

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main inspector drag-and-resize feature in this PR.
Linked Issues check ✅ Passed The changes implement direct drag movement, corner-handle resizing, set-style write-back, and canvas-scale handling requested by issue #380.
Out of Scope Changes check ✅ Passed The added changeset, demo fixture, and verification script all support the inspector drag-resize feature and do not appear unrelated.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (2)
packages/core/src/app/components/inspector/drag-resize-layer.tsx (2)

230-246: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

aria-label on role="presentation" is discarded.

role="presentation" strips semantics, so the label is never exposed. Either drop it or make the handle a real control (role="button", tabIndex={0}, keyboard nudge) if resizing should be reachable without a pointer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/app/components/inspector/drag-resize-layer.tsx` around
lines 230 - 246, Update the resize handle element in the drag-resize layer so
its accessibility semantics are consistent: remove the ineffective aria-label
and presentation role, or convert it into an interactive button with keyboard
focus and nudge behavior if keyboard resizing is required. Preserve the existing
pointer-resize behavior and corner-specific labeling.

56-107: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Handle pointercancel for the body drag.

Only pointerdown/pointermove/pointerup are bound. If the gesture is cancelled (touch cancel, browser gesture takeover), drag stays non-null and document.body.style.cursor = 'grabbing' / userSelect = 'none' remain applied until an unrelated pointerup arrives. Also consider setPointerCapture on the anchor so moves outside the window keep tracking, matching what ResizeHandle already does.

♻️ Suggested wiring
     window.addEventListener('pointerdown', onPointerDown, true);
     window.addEventListener('pointermove', onPointerMove, true);
     window.addEventListener('pointerup', onPointerUp, true);
+    window.addEventListener('pointercancel', onPointerCancel, true);
     return () => {
       window.removeEventListener('pointerdown', onPointerDown, true);
       window.removeEventListener('pointermove', onPointerMove, true);
       window.removeEventListener('pointerup', onPointerUp, true);
+      window.removeEventListener('pointercancel', onPointerCancel, true);

with

const onPointerCancel = () => {
  const wasMoving = drag?.moved;
  const snapshot = drag?.snapshot;
  drag = null;
  if (!wasMoving) return;
  document.body.style.cursor = '';
  document.body.style.userSelect = '';
  const { anchor: el } = stateRef.current;
  if (el?.isConnected && snapshot) restoreInline(el, snapshot);
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/app/components/inspector/drag-resize-layer.tsx` around
lines 56 - 107, Update the body-drag handlers around onPointerDown and
onPointerMove to add pointer-capture support on the anchor for the active
pointer, and implement an onPointerCancel cleanup path that clears drag state,
restores body cursor/userSelect, and restores the saved inline styles when
movement began. Wire the cancel handler wherever the existing pointerup listener
is registered, matching the cleanup behavior used by ResizeHandle.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.changeset/inspector-drag-resize.md:
- Line 5: Update the changeset description to use present-tense, user-facing
wording instead of the imperative “Drag”; describe that users can reposition the
selected element by dragging it and resize it with the corner handles, with
changes saved back to source as style edits.

In `@packages/core/src/app/components/inspector/drag-resize-layer.tsx`:
- Around line 263-268: Update readTranslate and the set-style normalization
logic at
packages/core/src/app/components/inspector/drag-resize-layer.tsx:263-268 and
:281-288 to handle computed translate units safely: resolve non-pixel components
or abort the gesture, and preserve the optional third z component instead of
dropping it. Apply consistent unit and z-component handling in both sites.
- Around line 206-224: Handle pointer cancellation in both gesture flows in
packages/core/src/app/components/inspector/drag-resize-layer.tsx: at lines
206-224, add a pointercancel handler that removes the pointermove and pointerup
listeners and restores the inline snapshot; at lines 56-107, add a pointercancel
handler that clears drag, resets document.body cursor and userSelect, and
restores the snapshot. Wire each handler into its corresponding gesture cleanup
without changing normal pointerup behavior.
- Around line 174-203: Update the resize initialization around the `startW` and
`startH` calculations to derive dimensions from the element’s computed box in
the same box model used when writing `el.style.width` and `el.style.height`.
Account for padding and borders, and adjust for the element’s scale/transform as
needed so the first pointer move preserves the current rendered size without
jumping; keep the existing corner anchoring and resize calculations unchanged.
- Around line 294-304: Update suppressNextClick so the swallow handler calls
stopImmediatePropagation() instead of stopPropagation(), ensuring the
later-registered window click listener is not invoked while preserving
preventDefault and listener cleanup.

In `@packages/core/tools/verify-drag-resize.mjs`:
- Around line 117-145: Update the resize checks in the corner-handle scenario to
capture the heading’s initial bounding box and validate persisted width and
height deltas against the drag distance scaled by scale, rather than comparing
absolute height. Add a northwest-handle resize case that verifies both
dimensions change and the persisted translate values shift to preserve top/left
anchoring, using the existing resize and save/readback helpers.
- Around line 34-35: Wrap the Playwright test flow using browser and page in a
try/finally structure so cleanup always executes after failures. In the finally
block, nest the existing browser.close() and writeFileSync(SLIDE_FILE, ORIGINAL)
cleanup so restoring the fixture still occurs if browser.close() throws;
preserve the existing test behavior and cleanup symbols.

---

Nitpick comments:
In `@packages/core/src/app/components/inspector/drag-resize-layer.tsx`:
- Around line 230-246: Update the resize handle element in the drag-resize layer
so its accessibility semantics are consistent: remove the ineffective aria-label
and presentation role, or convert it into an interactive button with keyboard
focus and nudge behavior if keyboard resizing is required. Preserve the existing
pointer-resize behavior and corner-specific labeling.
- Around line 56-107: Update the body-drag handlers around onPointerDown and
onPointerMove to add pointer-capture support on the anchor for the active
pointer, and implement an onPointerCancel cleanup path that clears drag state,
restores body cursor/userSelect, and restores the saved inline styles when
movement began. Wire the cancel handler wherever the existing pointerup listener
is registered, matching the cleanup behavior used by ResizeHandle.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: acf60ddd-6ca6-436e-8965-20c30c790d69

📥 Commits

Reviewing files that changed from the base of the PR and between 35dc46c and 9e8e575.

📒 Files selected for processing (5)
  • .changeset/inspector-drag-resize.md
  • apps/demo/slides/verify-drag-resize/index.tsx
  • packages/core/src/app/components/inspector/drag-resize-layer.tsx
  • packages/core/src/app/components/inspector/inspect-overlay.tsx
  • packages/core/tools/verify-drag-resize.mjs

Comment thread .changeset/inspector-drag-resize.md Outdated
'@open-slide/core': minor
---

Drag the selected element to reposition it and use the corner handles to resize, saved back to source as style edits.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use present-tense changeset wording.

“Drag” is imperative. Use a user-facing present-tense description instead.

Proposed fix
-Drag the selected element to reposition it and use the corner handles to resize, saved back to source as style edits.
+Lets you reposition selected elements by dragging and resize them with corner handles.

As per coding guidelines, changeset descriptions must be “one line, present-tense, describing what changed from a user's perspective.”

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Drag the selected element to reposition it and use the corner handles to resize, saved back to source as style edits.
Lets you reposition selected elements by dragging and resize them with corner handles.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.changeset/inspector-drag-resize.md at line 5, Update the changeset
description to use present-tense, user-facing wording instead of the imperative
“Drag”; describe that users can reposition the selected element by dragging it
and resize it with the corner handles, with changes saved back to source as
style edits.

Source: Coding guidelines

Comment thread packages/core/src/app/components/inspector/drag-resize-layer.tsx
Comment thread packages/core/src/app/components/inspector/drag-resize-layer.tsx Outdated
Comment on lines +263 to +268
function readTranslate(el: HTMLElement): { x: number; y: number } {
const raw = getComputedStyle(el).translate;
if (!raw || raw === 'none') return { x: 0, y: 0 };
const [x, y] = raw.split(' ');
return { x: Number.parseFloat(x ?? '0') || 0, y: Number.parseFloat(y ?? '0') || 0 };
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Translate parsing assumes exactly two px components. Both helpers split the value and parseFloat the first two tokens, so percentages are reinterpreted as px and a z component is dropped.

  • packages/core/src/app/components/inspector/drag-resize-layer.tsx#L263-L268: resolve non-px units (or bail out of the gesture) when reading the computed base translate, and preserve a third component.
  • packages/core/src/app/components/inspector/drag-resize-layer.tsx#L281-L288: mirror the same unit/z handling when normalising the value written to the set-style op.
📍 Affects 1 file
  • packages/core/src/app/components/inspector/drag-resize-layer.tsx#L263-L268 (this comment)
  • packages/core/src/app/components/inspector/drag-resize-layer.tsx#L281-L288
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/app/components/inspector/drag-resize-layer.tsx` around
lines 263 - 268, Update readTranslate and the set-style normalization logic at
packages/core/src/app/components/inspector/drag-resize-layer.tsx:263-268 and
:281-288 to handle computed translate units safely: resolve non-pixel components
or abort the gesture, and preserve the optional third z component instead of
dropping it. Apply consistent unit and z-component handling in both sites.

Comment on lines +294 to +304
function suppressNextClick(): void {
const swallow = (e: MouseEvent) => {
e.preventDefault();
e.stopPropagation();
window.removeEventListener('click', swallow, true);
};
window.addEventListener('click', swallow, true);
// If no click follows (pointer left the window, gesture cancelled), don't
// leave the listener armed for the user's next real click.
setTimeout(() => window.removeEventListener('click', swallow, true), 300);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

stopPropagation() does not block the overlay's own click listener.

InspectOverlay registers onClick on window in the capture phase (inspect-overlay.tsx Line 73), and this swallow listener is added on the same target later, so it runs after it. stopPropagation only stops propagation to other nodes, not to co-registered listeners on window — the post-gesture click still reaches onClick and re-selects whatever is under the pointer. Use stopImmediatePropagation().

🐛 Proposed fix
   const swallow = (e: MouseEvent) => {
     e.preventDefault();
-    e.stopPropagation();
+    e.stopImmediatePropagation();
+    e.stopPropagation();
     window.removeEventListener('click', swallow, true);
   };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/app/components/inspector/drag-resize-layer.tsx` around
lines 294 - 304, Update suppressNextClick so the swallow handler calls
stopImmediatePropagation() instead of stopPropagation(), ensuring the
later-registered window click listener is not invoked while preserving
preventDefault and listener cleanup.

Comment on lines +34 to +35
const browser = await chromium.launch({ channel: 'chrome', headless: true });
const page = await browser.newPage({ viewport: { width: 1600, height: 1000 } });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Always restore the fixture in a finally block.

Any failed Playwright action skips lines 149-150, leaving Chrome open and potentially leaving the saved .tsx fixture modified. Wrap the test flow in try/finally; nest cleanup so writeFileSync(SLIDE_FILE, ORIGINAL) still runs if browser.close() fails.

Also applies to: 149-150

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/tools/verify-drag-resize.mjs` around lines 34 - 35, Wrap the
Playwright test flow using browser and page in a try/finally structure so
cleanup always executes after failures. In the finally block, nest the existing
browser.close() and writeFileSync(SLIDE_FILE, ORIGINAL) cleanup so restoring the
fixture still occurs if browser.close() throws; preserve the existing test
behavior and cleanup symbols.

Comment on lines +117 to +145
console.log('\n2. corner handle resizes and persists to source');
{
const { scale } = await selectHeading();
const handle = page.locator('[data-drag-resize-handle="se"]');
const hb = await handle.boundingBox();
check(!!hb, 'bottom right handle is visible');
if (hb) {
const from = { x: hb.x + hb.width / 2, y: hb.y + hb.height / 2 };
await page.mouse.move(from.x, from.y);
await page.mouse.down();
for (let i = 1; i <= 10; i++) {
await page.mouse.move(from.x - 10 * i, from.y + 5 * i);
await page.waitForTimeout(16);
}
await page.mouse.up();
await page.waitForTimeout(300);

const saved = await save();
check(saved, 'Save button appeared after the resize');

const after = readFileSync(SLIDE_FILE, 'utf8');
check(/width:\s*'\d+px'/.test(after), 'width written into the .tsx');
check(/height:\s*'\d+px'/.test(after), 'height written into the .tsx');
const h = after.match(/height:\s*'(\d+)px'/);
if (h) {
const expected = 50 / scale;
const grew = Number.parseInt(h[1], 10);
check(grew > expected * 0.5, `height grew with the drag (${grew}px)`);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Make the resize check validate actual size deltas and anchored resizing.

grew > expected * 0.5 compares an absolute final height, so a no-op can pass when the heading’s original height exceeds the threshold; width is never checked for a delta. Record the initial bounding box, assert persisted width/height changed by the expected scaled amounts, and add a northwest-handle case that asserts translate changes alongside size. This covers the required top/left anchoring contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/tools/verify-drag-resize.mjs` around lines 117 - 145, Update
the resize checks in the corner-handle scenario to capture the heading’s initial
bounding box and validate persisted width and height deltas against the drag
distance scaled by scale, rather than comparing absolute height. Add a
northwest-handle resize case that verifies both dimensions change and the
persisted translate values shift to preserve top/left anchoring, using the
existing resize and save/readback helpers.

A cancelled pointer, a touch the browser turns into a scroll or a device that
goes away mid-gesture, never delivers pointerup. The resize handle left its
window listeners attached, so the element kept resizing on every later pointer
move, and both gestures left it wherever the gesture happened to die.

Also seed the resize from the computed width and height instead of the rendered
box. Under content-box the two differ by padding and border, and it is the
computed value that means the same thing as the width being written back.
@bruno-begh

Copy link
Copy Markdown
Author

Addressed all three points, pushed in d6391db.

pointercancel (major). Correct, and the resize handle was the worse of the two: its pointermove and pointerup listeners were only detached inside onUp, so a cancelled gesture leaked them permanently and the element kept resizing on every later pointer move. Both gestures now detach and restore the pre-gesture inline styles on pointercancel.

Resize seed. Also correct. I measured it rather than assume: with box-sizing: content-box and width:100px; padding:10px; border:5px, getBoundingClientRect().width is 130 while style.width is 100px. getComputedStyle().width returns 100px under both box models, which is exactly what gets written back, so the seed now comes from there. That also drops the element-local transform concern, since the computed value is a layout value.

Changeset wording. Reworded, though not to the suggested phrasing. Every entry in packages/core/CHANGELOG.md opens with a plain verb (Add, Fix, Restore, Stabilize), so it now reads "Add drag-to-move and corner resize handles for the selected element in the inspector, saved back to source as style edits."

I left the docstring coverage warning alone. CONTRIBUTING asks contributors to "default to writing no comments" and to document only the non-obvious why, which is what the file does.

Re-verified after the change: format:check, lint, typecheck, the unit suite and e2e/tests/inspector.spec.ts all pass, and tools/verify-drag-resize.mjs still confirms both gestures persist the expected geometry to the .tsx.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Drag and resize the selected element directly on the canvas

1 participant