Skip to content

fix(rotate): selection box rotates with shape during rotate drag#40

Merged
eugenioenko merged 7 commits into
mainfrom
fix/rotate-highlight-box
Apr 21, 2026
Merged

fix(rotate): selection box rotates with shape during rotate drag#40
eugenioenko merged 7 commits into
mainfrom
fix/rotate-highlight-box

Conversation

@eugenioenko
Copy link
Copy Markdown
Owner

Summary

  • doRotate was rebuilding handles via showResizeHandles(shapes) every frame, producing a growing/shrinking axis-aligned AABB while the shape rotated — the highlight box visibly lagged behind the shape.
  • Snapshot handle/outline origins at startRotate and rotate them around the selection center on each frame, so the selection rectangle tracks the shape tightly (Figma/Illustrator behavior).
  • Keep the showResizeHandles(shapes) call in endRotate so handles snap to AABB corners on release — startResize anchors to AABB corners, so leaving them rotated would break the next corner resize.

Test plan

  • Select a single shape, drag the rotate handle — selection box rotates tightly with the shape, handles follow the corners.
  • Release mid-rotation — box snaps to axis-aligned AABB of the rotated shape (consistent with re-selection).
  • Rotate, then drag a corner handle to resize — resize anchors correctly.
  • Multi-select (two+ shapes), rotate — whole group rotates around the combined-bbox center, outline and handles follow.
  • Hold shift while rotating — 15° snap still works.

🤖 Generated with Claude Code

eugenioenko and others added 7 commits April 20, 2026 21:18
Previously `doRotate` rebuilt the handles via `showResizeHandles(shapes)`
every frame, producing a growing/shrinking axis-aligned AABB instead of
a tight box tracking the shape's rotation. Snapshot handle origins at
`startRotate` and apply the delta rotation around the selection center
on each frame; snap back to the AABB on release so the next corner
resize anchors correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Map each handle to a data-option value in doPointerMove so the CSS
cursor reflects the affordance: nwse-resize / nesw-resize for corners,
grab for the rotate handle. Clear the option when leaving the pointer
tool so a stale hover state doesn't survive tool switches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extend the hover-cursor logic so the cursor shows grab when the mouse
is over a selected shape's bbox or inside the group outline (and not
over a resize/rotate handle, which keep priority). Makes the move
affordance discoverable without clicking.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Grab-on-hover felt off in practice. Keep the handle cursor feedback
but let the shape hover remain the default arrow, consistent with
Figma/Illustrator where the hover highlight is the affordance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a `highlights` Two.js Group as a child of the ZUI-wrapped canvas so
selection handles, outlines, marquee, text preview, and bezier node
handles live in their own scene-graph subtree. `addDoodle` re-appends
the highlights group after each new shape so it stays on top (Two.js
`.add` moves an existing child to the end).

Drop the `isHighlight` marker and the `if (shape.isHighlight) continue`
filters in hit-test and export paths — those iterate the doodles
Zustand list, which never contained highlights, so the filters were
defensive dead code.

Zoom-reactive rescaling (`updateOutlineScales`, `updateNodeHandleScales`)
is unchanged: the rebuild helpers now target `doodler.highlights`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the Transform section (rotation/scale/skew sliders) — the canvas
handles cover rotation and resize directly, so the sliders were
redundant.

The Layers panel had its title rendered twice in mismatched styles:
once by the outer <Section title="Layers"> wrapper and once by the
component's own header (which carries the count). Remove the wrapper
and apply Section's text-sm/text-text-secondary styling to the
internal header so the count badge is preserved and the styling lines
up with other sections.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Give Button and ToggleButton `flex items-center justify-center` so
their content aligns with the ButtonGroup container's 26px min-height
— the default `align-items: stretch` wasn't enough because <button>
line-height left a 2px gap at the bottom.

Rename ToggleGroup → ButtonGroup: the bordered container works for
both action buttons (undo/redo, zoom) and toggle buttons; toggle-ness
lives on the child, not the group. Update call sites and wrap the
panel footer's undo/redo and zoom groups in ButtonGroup instead of
inlining the bordered flex div.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@eugenioenko eugenioenko merged commit bd287dd into main Apr 21, 2026
1 check passed
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.

1 participant