Skip to content

feat(editor): add resizeToBounds method#8120

Merged
steveruizok merged 3 commits intomainfrom
sr/resize-to-bounds
Mar 3, 2026
Merged

feat(editor): add resizeToBounds method#8120
steveruizok merged 3 commits intomainfrom
sr/resize-to-bounds

Conversation

@steveruizok
Copy link
Copy Markdown
Collaborator

@steveruizok steveruizok commented Mar 1, 2026

In order to support resizing a set of shapes to fit a target bounding box (e.g. for layout operations), this PR adds Editor.resizeToBounds().

The method takes an array of shapes (or shape IDs) and a target BoxLike, then scales and repositions the shapes so their combined page bounds match the target. It handles arrow-bound shape clusters, parent transforms, and aspect ratio locking.

This PR also:

  • Fixes a bug in stretchShapes where the shared localOffset vector was being mutated instead of the per-shape clone when applying parent rotation
  • Adds a zero-dimension guard to resizeToBounds to avoid NaN/Infinity scales
  • Extracts the shared clustering boilerplate from 6 layout methods (stackShapes, packShapes, alignShapes, distributeShapes, stretchShapes, resizeToBounds) into a private getShapeClusters helper, removing ~230 lines of duplicated code
  • Adds a resizeToBounds interactive example
  • Updates the shape-transforms docs page with a new "Resizing to bounds" subsection and updates canBeLaidOut type comment
Area Added Removed Net
Core code +161 -270 -109
Tests +245 0 +245
Example +172 0 +172
Docs +14 -1 +13
API report +2 -1 +1
Total +594 -271 +323

Change type

  • feature

Test plan

  1. Call editor.resizeToBounds([shape1, shape2], { x: 0, y: 0, w: 500, h: 500 })
  2. Verify shapes are resized and repositioned to fill the target bounds
  3. Verify undo restores original positions
  4. Navigate to the resize-to-bounds example and verify buttons resize shapes correctly
  • Unit tests

API changes

  • Added Editor.resizeToBounds(shapes, bounds) method
  • Added 'resize_to_bounds' to TLShapeUtilCanBeLaidOutOpts.type

Release notes

  • Add Editor.resizeToBounds() method for resizing shapes to fit target bounds
  • Fix localOffset mutation bug in stretchShapes when shapes have parent transforms

Add a method to resize and reposition a set of shapes so their combined
page bounds match a target bounding box. Handles shape clusters connected
by arrow bindings, parent transforms, and aspect ratio locking.

Also adds 'resize_to_bounds' to the canBeLaidOut opts type union so
shapes can opt out of this layout operation.
@huppy-bot huppy-bot bot added the feature New feature label Mar 1, 2026
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
examples Ready Ready Preview Mar 1, 2026 7:02pm
5 Skipped Deployments
Project Deployment Actions Updated (UTC)
analytics Ignored Ignored Preview Mar 1, 2026 7:02pm
chat-template Ignored Ignored Preview Mar 1, 2026 7:02pm
tldraw-docs Ignored Ignored Preview Mar 1, 2026 7:02pm
tldraw-shader Ignored Ignored Preview Mar 1, 2026 7:02pm
workflow-template Ignored Ignored Preview Mar 1, 2026 7:02pm

Request Review

steveruizok added a commit that referenced this pull request Mar 1, 2026
The resizeToBounds feature is independent of the display values work
and has been moved to its own PR (#8120).
Fix localOffset mutation bug in stretchShapes where the shared
localOffset was rotated instead of the per-shape clone. Add
zero-dimension guard to resizeToBounds to prevent NaN/Infinity
scales. Remove this.run() wrapper so callers control undo batching.
Add tests for read-only mode, rotated shapes, framed shapes, and
arrow-bound clusters.
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

…rs helper

Six layout methods (stackShapes, packShapes, alignShapes, distributeShapes,
stretchShapes, resizeToBounds) each repeated ~35 lines of identical clustering
boilerplate. Extract this into a private getShapeClusters helper that handles
shape resolution, axis-aligned filtering, canBeLaidOut checks, and arrow-bound
cluster collection.
@steveruizok
Copy link
Copy Markdown
Collaborator Author

For additional context: I was watching an agent try to use our Editor methods to move a group of shapes from one place / size to another. (IIRC, something like "make the man bigger and put him next to the tree"). It was very difficult / inefficient because of how many intermediate calculations it needed to make in order to update each of the shapes. As a user, I would have used the selection to operate on the shapes as if they were a single rectangle: select the shapes to create the selection box, then move the selection box, resize the selection box, etc. This method allows the same sort of abstraction at the level of our API.

export interface TLShapeUtilCanBeLaidOutOpts {
/** The type of action causing the layout. */
type?: 'align' | 'distribute' | 'pack' | 'stack' | 'flip' | 'stretch'
type?: 'align' | 'distribute' | 'pack' | 'stack' | 'flip' | 'stretch' | 'resize_to_bounds'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

maybe just scale?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

hm, could be collidey with other scale stuff. I'd like to reserve scale for when/if scale becomes a first class property

@steveruizok steveruizok added this pull request to the merge queue Mar 3, 2026
Merged via the queue into main with commit bcdd9cb Mar 3, 2026
23 checks passed
@steveruizok steveruizok deleted the sr/resize-to-bounds branch March 3, 2026 03:08
github-merge-queue bot pushed a commit that referenced this pull request Mar 4, 2026
Update `next.mdx` release notes to cover all SDK-relevant PRs merged to
main since v4.4.0.

Highlights:
- Display values system (#8121) with breaking changes and migration
guide
- Click-through on transparent image pixels (#7942)
- `Editor.resizeToBounds()` (#8120)
- SVG sanitization (#7896)
- TypeScript enum-to-const refactoring (#8084)
- 14 bug fixes and 4 improvements

### Change type

- [x] `other`

### Test plan

1. Verify the release notes render correctly on the docs site

### Release notes

- Update next release notes with changes since v4.4.0.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk: documentation-only changes updating release notes content
and date, with no runtime/code behavior impact.
> 
> **Overview**
> Updates `apps/docs/content/releases/next.mdx` for the upcoming release
by refreshing the date and replacing the brief blurb with expanded
release notes.
> 
> Documents new SDK surface area (`Geometry2d.ignoreHit`,
`Editor.resizeToBounds`, `sanitizeSvg`), highlights click-through on
transparent image pixels, and adds a list of recent improvements and bug
fixes (paste parenting, link/alt-text persistence, SVG sanitization
behavior, circular-dependency cleanup, and several crash/export/sync
fixes).
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
5f7dc0a. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants