48fa9018f4
Before this PR the interface for doing cleanup when shapes/bindings were deleted was quite footgunny and inexpressive. We were abusing the shape beforeDelete callbacks to implement copy+paste, which doesn't work in situations where cascading deletes are required. This caused bugs in both our pin and sticker examples, where copy+paste was broken. I noticed the same bug in my experiment with text labels, and I think the fact that it took us a while to notice these bugs indicates other users are gonna fall prey to the same bugs unless we help them out. One suggestion to fix this was to add `onAfterDelete(From|To)Shape` callbacks. The cascading deletes could happen in those, while keeping the 'commit changes' kinds of updates in the `before` callbacks and theoretically that would fix the issues with copy+paste. However, expecting people to figure this out on their own is asking a heckuva lot IMO, and it's a heavy bit of nuance to try to convey in the docs. It's hard enough to convey it here. Plus I could imagine for some users it might easily even leave the store in an inconsistent state to allow a bound shape to exist for any length of time after the shape it was bound to was already deleted. It also just makes an already large and muddy API surface area even larger and muddier and if that can be avoided let's avoid it. This PR clears things up by making it so that there's only one callback for when a binding is removed. The callback is given a `reason` for why it is being called The `reason` is one of the following: - The 'from' is being deleted - The 'to' shape is being deleted - The binding is being deleted on it's own. Technically a binding might end up being deleted when both the `from` and `to` shapes are being deleted, but it's very hard to know for certain when that is happening, so I decided to just ignore it for now. I think it would only matter for perf reasons, to avoid doing useless work. So this PR replaces the `onBeforeDelete`, `onAfterDelete`, `onBeforeFromShapeDelete` and `onBeforeToShapeDelete` (and the prospective `onAfterFromShapeDelete` and `onAfterToShapeDelete`) with just two callbacks: - `onBeforeUnbind({binding, reason})` - called before any shapes or the binding have been deleted. - `onAfterUnbind({binding, reason})` - called after the binding and any shapes have been deleted. This still allows all the same behaviour as before, without having to spread the logic between multiple callbacks. It's also just clearer IMO since you only get one callback invocation per unbinding rather than potentially two. It also fixes our copy+paste footgun since we can now implement that by just deleting the bindings rather than invoking the `onBeforeDelete(From|To)Shape` callbacks. I'm not worried about losing the explicit before/after delete callbacks for the binding record or shape records because sdk users still have the ability to detect all those situations with full nuance in obvious ways. The one thing that would even require extra bookkeeping is getting access to a shape record after the shape was deleted, but that's probably not a thing anybody would want to do 🤷🏼 ### Change Type <!-- ❗ Please select a 'Scope' label ❗️ --> - [x] `sdk` — Changes the tldraw SDK - [ ] `dotcom` — Changes the tldraw.com web app - [ ] `docs` — Changes to the documentation, examples, or templates. - [ ] `vs code` — Changes to the vscode plugin - [ ] `internal` — Does not affect user-facing stuff <!-- ❗ Please select a 'Type' label ❗️ --> - [ ] `bugfix` — Bug fix - [ ] `feature` — New feature - [x] `improvement` — Improving existing features - [ ] `chore` — Updating dependencies, other boring stuff - [ ] `galaxy brain` — Architectural changes - [ ] `tests` — Changes to any test code - [ ] `tools` — Changes to infrastructure, CI, internal scripts, debugging tools, etc. - [ ] `dunno` — I don't know ### Test Plan 1. Add a step-by-step description of how to test your PR here. 2. - [ ] Unit Tests - [ ] End to end tests ### Release Notes - Add a brief release note for your PR here. |
||
---|---|---|
.github | ||
.husky | ||
.yarn/patches | ||
apps | ||
assets | ||
config | ||
packages | ||
scripts | ||
templates | ||
.dockerignore | ||
.eslintignore | ||
.eslintplugin.js | ||
.eslintrc.js | ||
.gitignore | ||
.ignore | ||
.prettierignore | ||
.prettierrc | ||
.yarnrc.yml | ||
CHANGELOG.md | ||
CLA.md | ||
CODE_OF_CONDUCT.md | ||
CONTRIBUTING.md | ||
lazy.config.ts | ||
lerna.json | ||
LICENSE.md | ||
package.json | ||
README.md | ||
RELEASES.md | ||
TRADEMARKS.md | ||
yarn.config.cjs | ||
yarn.lock |
tldraw
Welcome to the public monorepo for tldraw. tldraw is a library for creating infinite canvas experiences in React. It's the software behind the digital whiteboard tldraw.com.
- Read the docs and learn more at tldraw.dev.
- Learn about our license.
🤵 Interested in purchasing a commercial license for the tldraw SDK? Fill out this form.
Installation
npm i tldraw
Usage
import { Tldraw } from 'tldraw'
import 'tldraw/tldraw.css'
export default function App() {
return (
<div style={{ position: 'fixed', inset: 0 }}>
<Tldraw />
</div>
)
}
Learn more at tldraw.dev.
Local development
The local development server will run our examples app. The basic example will show any changes you've made to the codebase.
To run the local development server, first clone this repo.
Install dependencies:
yarn
Start the local development server:
yarn dev
Open the example project at localhost:5420
.
License
The tldraw source code and its distributions are provided under the tldraw license. This license does not permit commercial use. To purchase a commercial license or learn more, please fill out this form.
Trademarks
Copyright (c) 2023-present tldraw Inc. The tldraw name and logo are trademarks of tldraw. Please see our trademark guidelines for info on acceptable usage.
Contact
Find us on Twitter/X at @tldraw.
Community
Have questions, comments or feedback? Join our discord or start a discussion. For the latest news and release notes, check out our Substack.
Contribution
Please see our contributing guide. Found a bug? Please submit an issue.