This PR adds a component for `ShapeIndicators` to the UI component
overrides. It moves the "select tool" state logic up to the new
`TldrawShapeIndicators` component.
### Change type
- [ ] `bugfix`
- [x] `improvement`
- [ ] `feature`
- [x] `api`
- [ ] `other`
### Release notes
- Added new `ShapeIndicators` component to `components` object.
- Added new `TldrawShapeIndicators` component.
This PR:
- simplifies a lot of z-index layers
### Change Type
- [x] `sdk` — Changes the tldraw SDK
- [x] `bugfix` — Bug fix
### Release Notes
- Cleans up z-indexes and removes some unused CSS.
Simplify the types used by the props of the `Tldraw` and `TldrawEditor`
components. This doesn't make the docs perfect, but it makes them quite
a bit better than they were.
![image](https://github.com/tldraw/tldraw/assets/1489520/66c72e0e-c22b-4414-b194-f0598e4a3736)
### Change Type
- [x] `sdk` — Changes the tldraw SDK
- [x] `docs` — Changes to the documentation, examples, or templates.
- [x] `improvement` — Improving existing features
Our reference docs don't currently include members inherited through the
`extends` keyword. These extended items are barely referenced at all -
you have to find them in the signature.
This diff adds a clearer note to the docs saying which type has been
extended, and if possible brings the extended items through onto the
current documentation page (with a note saying where they're from)
![image](https://github.com/tldraw/tldraw/assets/1489520/0349252d-e8bc-406b-bf47-636da424ebe0)
### Change Type
- [x] `docs` — Changes to the documentation, examples, or templates.
- [x] `improvement` — Improving existing features
Allow the users to fully use the same colour scheme as their system.
Allows the users to either: force dark colour scheme, force light colour
scheme, or use the system one.
It's reactive to the system changes.
https://github.com/tldraw/tldraw/assets/2523721/6d4cef03-9ef0-4098-b299-6bf5d7513e98
### 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.
---------
Co-authored-by: David Sheldrick <d.j.sheldrick@gmail.com>
This PR:
- moves the edge scrolling logic into a manager
- adds a new Editor option for `edgeScrollDelay`
- adds a new Editor option for `edgeScrollEaseDuration`
When in a state that would trigger an edge scroll, a delay is added
before the scrolling starts. When scrolling does start, it is eased in
by a certain duration.
### Change Type
- [x] `sdk` — Changes the tldraw SDK
- [x] `improvement` — Improving existing features
### Test Plan
1. Drag shapes, resize, or drag select to the edge of the screen
2. The screen should move
- [x] Unit Tests
### Release Notes
- Add a delay and easing to edge scrolling.
---------
Co-authored-by: Mitja Bezenšek <mitja.bezensek@gmail.com>
This PR adds a user preference for "dynamic size mode" where the scale
of shapes (text size, stroke width) is relative to the current zoom
level. This means that the stroke width in screen pixels (or text size
in screen pixels) is identical regardless of zoom level.
![Kapture 2024-05-27 at 05 23
21](https://github.com/tldraw/tldraw/assets/23072548/f247ecce-bfcd-4f85-b7a5-d7677b38e4d8)
- [x] Draw shape
- [x] Text shape
- [x] Highlighter shape
- [x] Geo shape
- [x] Arrow shape
- [x] Note shape
- [x] Line shape
Embed shape?
### Change Type
- [x] `sdk` — Changes the tldraw SDK
- [x] `feature` — New feature
### Test Plan
1. Use the tools.
2. Change zoom
- [ ] Unit Tests
### Release Notes
- Adds a dynamic size user preferences.
- Removes double click to reset scale on text shapes.
- Removes double click to reset autosize on text shapes.
---------
Co-authored-by: Taha <98838967+Taha-Hassan-Git@users.noreply.github.com>
Co-authored-by: huppy-bot[bot] <128400622+huppy-bot[bot]@users.noreply.github.com>
I originally didn't want to add these methods to the Editor class, to
avoid muddying the API with multiple ways to do one thing, but I've
found myself reaching for these on a number of occasions so I think
maybe it would be better to have them?
### 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.
This PR adds some functionality for turning shapes into images.
![Kapture 2024-06-13 at 12 51
00](https://github.com/tldraw/tldraw/assets/23072548/78525e29-61b5-418f-889d-2f061f26f34d)
It adds:
- the `flattenShapesToImages`
- the `useFlatten` hook
- a `flatten-shapes-to-images` action (shift + f)
- adds `flattenImageBoundsExpand` option
- adds `flattenImageBoundsPadding` option
## Flatten shapes to images
The `flattenShapesToImages` helper method will 1) create an image for
the given shape ids, 2) add it to the canvas in the same location / size
as the source shapes, and then 3) delete the original shapes. The new
image will be placed correctly in the z index and in the correct
rotation of the root-most ancestor of the given shape ids.
![image](https://github.com/tldraw/tldraw/assets/23072548/fe888980-05a5-4369-863f-90c142f9f8b9)
It has an argument, `flattenImageBoundsExpand`, which if provided will
chunk the given shapes into images based on their overlapping (expanded)
bounding boxes.
![image](https://github.com/tldraw/tldraw/assets/23072548/c4799309-244d-4a2b-ac59-9c2fd100319c)
By default, the flatten action uses the editor's
`options.flattenImageBoundsExpand`. The `flattenImageBoundsPadding`
option is used as a value for how much larger the image should be than
the source image bounds (to account for large strokes, for example).
### Change Type
- [x] `sdk` — Changes the tldraw SDK
- [x] `feature` — New feature
### Test Plan
1. Select shapes
2. Select context menu > edit > flatten
- [ ] Unit Tests
- [ ] End to end tests
### Release Notes
- Add Flatten, a new menu item to flatten shapes into images
this is take #2 of this PR https://github.com/tldraw/tldraw/pull/3745
As I look at LOD holistically and whether we have multiple sources when
working locally, I learned that our system used base64 encoding of
assets directly. Issue https://github.com/tldraw/tldraw/issues/3728
<img width="1350" alt="assetstore"
src="https://github.com/tldraw/tldraw/assets/469604/e7b41e29-6656-4d9b-b462-72d43b98f3f7">
The motivations and benefits are:
- store size: not having a huge base64 blobs injected in room data
- perf on loading snapshot: this helps with loading the room data more
quickly
- multiple sources: furthermore, if we do decide to have multiple
sources locally (for each asset), then we won't get a multiplicative
effect of even larger JSON blobs that have lots of base64 data in them
- encoding/decoding perf: this also saves the (slow) step of having to
base64 encode/decode our assets, we can just strictly with work with
blobs.
Todo:
- [x] decodes video and images
- [x] make sure it syncs to other tabs
- [x] make sure it syncs to other multiplayer room
- [x] fix tests
### 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. Test the shit out of uploading/downloading video/image assets,
locally+multiplayer.
- [ ] Need to fix current tests and write new ones
### Release Notes
- Assets: store as reference to blob in indexedDB instead of storing
directly as base64 in the snapshot.
couple fixes and improvements for the LOD work.
- add `format=auto` for Cloudflare to send back more modern image
formats
- fix the broken asset logic that regressed (should not have looked at
`url`)
- fix stray parenthesis, omg
- rm the `useValueDebounced` function in lieu of just debouncing the
resolver. the problem was that the initial load in a multiplayer room
has a zoom of 1 but then the real zoom comes in (via the url) and so we
would double load all images 😬. this switches the debouncing to the
resolving stage, not making it tied to the zoom specifically.
### 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 ❗️ -->
- [x] `bugfix` — Bug fix
- [ ] `feature` — New feature
- [ ] `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
Before:
![Screenshot 2024-06-12 at 12 57
26](https://github.com/tldraw/tldraw/assets/1489520/2a9f6098-ef2a-4f52-88f5-d6e4311c067d)
After:
![Screenshot 2024-06-12 at 12 59
16](https://github.com/tldraw/tldraw/assets/1489520/51733c2a-a2b4-4084-a89a-85bce5b47672)
React components in docs now list their props, and appear under a new
"Component" section instead of randomly under either `Function` or
`Variable`. In order to have our docs generate this, a few criteria need
to be met:
1. They need to be tagged with the `@react` tsdoc tag
2. Their props need to be a simple type alias, typically to an
interface.
Both of these rules are enforced with a new lint rule - any component
tagged as `@public` will have these rules enforced.
### Change Type
- [x] `docs` — Changes to the documentation, examples, or templates.
- [x] `improvement` — Improving existing features
this is take #2 of this PR https://github.com/tldraw/tldraw/pull/3764
This continues the idea kicked off in
https://github.com/tldraw/tldraw/pull/3684 to explore LOD and takes it
in a different direction.
Several things here to call out:
- our dotcom version would start to use Cloudflare's image transforms
- we don't rewrite non-image assets
- we debounce zooming so that we're not swapping out images while
zooming (it creates jank)
- we load different images based on steps of .25 (maybe we want to make
this more, like 0.33). Feels like 0.5 might be a bit too much but we can
play around with it.
- we take into account network connection speed. if you're on 3g, for
example, we have the size of the image.
- dpr is taken into account - in our case, Cloudflare handles it. But if
it wasn't Cloudflare, we could add it to our width equation.
- we use Cloudflare's `fit=scale-down` setting to never scale _up_ an
image.
- we don't swap the image in until we've finished loading it
programatically (to avoid a blank image while it loads)
TODO
- [x] We need to enable Cloudflare's pricing on image transforms btw
@steveruizok 😉 - this won't work quite yet until we do that.
### 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
- [x] `feature` — New feature
- [ ] `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. Test images on staging, small, medium, large, mega
2. Test videos on staging
- [x] Unit Tests
- [ ] End to end tests
### Release Notes
- Assets: make option to transform urls dynamically to provide different
sized images on demand.
followup to https://github.com/tldraw/tldraw/pull/3881 to enforce this
in the codebase
Describe what your pull request does. If appropriate, add GIFs or images
showing the before and after.
### Change Type
<!-- ❗ Please select a 'Scope' label ❗️ -->
- [x] `sdk` — Changes the tldraw SDK
- [x] `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
This PR aims to improve the UX around undo/redo and cropping. Before the
PR if you do some cropping, then stop cropping, then hit `undo`, you
will end up back in the cropping state and it will undo each of your
resize/translate cropping operations individually. This is weird 🙅🏼 It
should just undo the whole sequence of changes that happened during
cropping.
To achieve that, this PR introduces a new history method called
`squashToMark`, which strips out all the marks between the current head
of the undo stack and the mark id you pass in.
This PR also makes the default history record mode of
`updateCurrentPageState` to `ignore` like it already was for
`updateInstanceState`. The fact that it was recording changes to the
`croppingShapeId` was the reason that hitting undo would put you back
into the cropping state.
### 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.
Previously, we had the `ae-forgotten-export` rule from api-extractor
disabled. This rule makes sure that everything that's referred to in the
public API is actually exported. There are more details on the rule
[here](https://api-extractor.com/pages/messages/ae-forgotten-export/),
but not exporting public API entires is bad because they're hard to
document and can't be typed/called from consumer code. For us, the big
effect is that they don't appear in our docs at all.
This diff re-enables that rule. Now, if you introduce something new to
the public API but don't export it, your build will fail.
### Change Type
- [x] `docs` — Changes to the documentation, examples, or templates.
- [x] `improvement` — Improving existing features
Adds docs (reference material and a guide) for the bindings API. Also,
the unbind reason enum is now a union of strings.
### Change Type
- [x] `docs` — Changes to the documentation, examples, or templates.
- [x] `improvement` — Improving existing features
When a draw shape gets long, we split it into multiple shapes. This PR
gives the user the option to change how long a shape can be before it
needs to be split.
### 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
- [x] `feature` — New feature
- [ ] `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
### Release Notes
- SDK: Add option for controlling max length of draw shapes
So we were kinda bending over backwards to capture the use case where we
update the arrow's terminal x,y coords when unbinding, copy-pasting, and
duplicating.
- At first we abused the `onBeforeShapeDelete` callbacks, but that was
footgunny.
- Then we created a `onBeforeUnbind` callback, which was less footgunny
but still subtly footgunny.
This PR proposes reverting the `onBeforeUnbind` stuff, taking us back to
having `onBeforeShapeDelete` stuff. But at the same time it adds
`onBeforeShapeIsolate` callbacks which are triggered at the following
times:
- When you delete the other shape in a bound shape pair
- When you copy/paste or duplicate one shape in a bound shape pair but
not the other one
- When you opt-in while deleting bindings e.g. `deleteBindings([...],
{isolateShapes: true})`
This PR also fixes the bound arrow drag interaction. We can probably
extract that out to a separate PR if needed.
![Kapture 2024-06-04 at 12 42
40](https://github.com/tldraw/tldraw/assets/1242537/95b51e14-1119-4dad-91e4-8b19fdb5e862)
### 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 ❗️ -->
- [x] `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.
`createTLStore` had defaults of empty arrays for shapeUtils and
bindingUtils. this is problematic since people who already are calling
`createTLStore` manually with like `createTLStore({shapeUtils:
defaultShapeUtils})` will miss out on bindings utils when they upgrade
to the latest version, and this will probably only fail at runtime for
them.
To prevent issues we could have made `shapeUtils` and `bindingUtils`
required args but it feels better to me, long term, if we bring
`createTLStore` in line with `createTLSchema` and configure it to use
tldraw's default shapes/bindings if no custom overrides are specified.
i.e. we can do this
```diff
- const store = createTLStore({ shapeUtils: defaultShapeUtils, bindingUtils: defaultBindingUtils })
+ const store = createTLStore()
```
There's still technically potential for breaking changes by people
accidentally including the arrow binding util when they might not have
arrows in the app, but I don't think that's likely to actually cause any
bugs unless they add their own arrow binding type later on.
### 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.
This PR adds a `editor.blur()` method to complement the `editor.focus()`
method, and enhances both with an options param that allows to skip
dispatching a focus/blur event on the container.
### 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
- [x] `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.
We have a lot of events that fire in the editor and, technically, they
can fire after the Editor is long gone.
This adds a registry/manager to track those timeout/interval/raf IDs
(and some eslint rules to enforce it).
Some other cleanups:
- `requestAnimationFrame.polyfill.ts` looks like it's unused now (it
used to be used in a prev. revision)
- @ds300 I could use your feedback on the `EffectScheduler` tweak. in
`useReactor` we do: `() => new EffectScheduler(name, reactFn, {
scheduleEffect: (cb) => requestAnimationFrame(cb) }),`
and that looks like it doesn't currently get disposed of properly.
thoughts? happy to do that separately from this PR if you think that's a
trickier thing.
### 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. Test async operations and make sure they don't fire after disposal.
### Release Notes
- Editor: add registry of timeouts/intervals/rafs
---------
Co-authored-by: Steve Ruiz <steveruizok@gmail.com>
Lots of people are having a bad time with loading/restoring snapshots
and there's a few reasons for that:
- It's not clear how to preserve UI state independently of document
state.
- Loading a snapshot wipes the instance state, which means we almost
always need to
- update the viewport page bounds
- refocus the editor
- preserver some other sneaky properties of the `instance` record
### 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
- [ ] `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.
This PR adds an (optional) options argument to the `Editor.groupShapes`
and `Editor.ungroupShapes` methods.
Taha's original PR:
This PR seeks to make it easier to disentangle shape selection logic
from the grouping shapes logic. This is one of two approaches we could
take to this problem.
Other PR is here: https://github.com/tldraw/tldraw/pull/3691
I think this is a better method because it doesn't require consumers of
the library with their own custom actions to change the way their own
grouping logic works. As evidenced by all the tests failing on the other
PR.
### 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.
---------
Co-authored-by: Steve Ruiz <steveruizok@gmail.com>
Another go at #3628 & #3783. This moves (most) constants into
`editor.options`, configurable by the `options` prop on the tldraw
component.
### Change Type
- [x] `sdk` — Changes the tldraw SDK
- [x] `feature` — New feature
### Release Notes
You can now override many options which were previously hard-coded
constants. Pass an `options` prop into the tldraw component to change
the maximum number of pages, grid steps, or other previously hard-coded
values. See `TldrawOptions` for more
This PR adds a heart geo shape. ❤️
It also:
- adds `toSvgPathData` to geometry2d
- uses geometry2d in places where previously we recalculated things like
perimeter of ellipse
- flattens geo shape util components
- [x] Calculate the path length for the DashStyleHeart
### Change Type
- [x] `sdk` — Changes the tldraw SDK
- [x] `feature` — New feature
### Release Notes
- Adds a heart shape to the geo shape set.
This PR reworks the `canBind` callback to work with customizable
bindings. It now accepts an object with a the shape, the other shape
(optional - it may not exist yet), the direction, and the type of the
binding. Devs can use this to create shapes that only participate in
certain binding types, can have bindings from but not to them, etc.
If you're implementing a binding, you can see if binding two shapes is
allowed using `editor.canBindShapes(fromShape, toShape, 'my binding
type')`
### Change Type
- [x] `sdk` — Changes the tldraw SDK
- [x] `improvement` — Improving existing features
### Release Notes
#### Breaking changes
The `canBind` flag now accepts an options object instead of just the
shape in question. If you're relying on its arguments, you need to
change from `canBind(shape) {}` to `canBind({shape}) {}`.
Typescript's type aliases (`type X = thing`) can refer to basically
anything, which makes it hard to write an automatic document formatter
for them. Interfaces on the other hand are only object, so they play
much nicer with docs. Currently, object-flavoured type aliases don't
really get expanded at all on our docs site, which means we have a bunch
of docs content that's not shown on the site.
This diff introduces a lint rule that forces `interface X {foo: bar}`s
instead of `type X = {foo: bar}` where possible, as it results in a much
better documentation experience:
Before:
<img width="437" alt="Screenshot 2024-05-22 at 15 24 13"
src="https://github.com/tldraw/tldraw/assets/1489520/32606fd1-6832-4a1e-aa5f-f0534d160c92">
After:
<img width="431" alt="Screenshot 2024-05-22 at 15 33 01"
src="https://github.com/tldraw/tldraw/assets/1489520/4e0d59ee-c38e-4056-b9fd-6a7f15d28f0f">
### Change Type
- [x] `sdk` — Changes the tldraw SDK
- [x] `docs` — Changes to the documentation, examples, or templates.
- [x] `improvement` — Improving existing features
@SomeHats and me fixed arrow flipping, which was a little bit broken
after the bindings things
### 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 ❗️ -->
- [x] `bugfix` — Bug fix
- [ ] `feature` — New feature
- [ ] `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.
---------
Co-authored-by: Alex <alex@dytry.ch>
This PR increases the maximum number of shapes per page from 2000 to
4000.
### Change Type
- [x] `sdk` — Changes the tldraw SDK
- [x] `improvement` — Improving existing features
### Test Plan
1. Create max shapes
2. Does it work?
- [ ] Unit Tests
- [ ] End to end tests
### Release Notes
- Increase maximum number of shapes per page from 2000 to 4000.
Focus management is really scattered across the codebase. There's sort
of a battle between different code paths to make the focus the correct
desired state. It seemed to grow like a knot and once I started pulling
on one thread to see if it was still needed you could see underneath
that it was accounting for another thing underneath that perhaps wasn't
needed.
The impetus for this PR came but especially during the text label
rework, now that it's much more easy to jump around from textfield to
textfield. It became apparent that we were playing whack-a-mole trying
to preserve the right focus conditions (especially on iOS, ugh).
This tries to remove as many hacks as possible, and bring together in
place the focus logic (and in the darkness, bind them).
## Places affected
- [x] `useEditableText`: was able to remove a bunch of the focus logic
here. In addition, it doesn't look like we need to save the selection
range anymore.
- lingering footgun that needed to be fixed anyway: if there are two
labels in the same shape, because we were just checking `editingShapeId
=== id`, the two text labels would have just fought each other for
control
- [x] `useFocusEvents`: nixed and refactored — we listen to the store in
`FocusManager` and then take care of autoFocus there
- [x] `useSafariFocusOutFix`: nixed. not necessary anymore because we're
not trying to refocus when blurring in `useEditableText`. original PR
for reference: https://github.com/tldraw/brivate/pull/79
- [x] `defaultSideEffects`: moved logic to `FocusManager`
- [x] `PointingShape` focus for `startTranslating`, decided to leave
this alone actually.
- [x] `TldrawUIButton`: it doesn't look like this focus bug fix is
needed anymore, original PR for reference:
https://github.com/tldraw/tldraw/pull/2630
- [x] `useDocumentEvents`: left alone its manual focus after the Escape
key is hit
- [x] `FrameHeading`: double focus/select doesn't seem necessary anymore
- [x] `useCanvasEvents`: `onPointerDown` focus logic never happened b/c
in `Editor.ts` we `clearedMenus` on pointer down
- [x] `onTouchStart`: looks like `document.body.click()` is not
necessary anymore
## Future Changes
- [ ] a11y: work on having an accessebility focus ring
- [ ] Page visibility API:
(https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API)
events when tab is back in focus vs. background, different kind of focus
- [ ] Reexamine places we manually dispatch `pointer_down` events to see
if they're necessary.
- [ ] Minor: get rid of `useContainer` maybe? Is it really necessary to
have this hook? you can just do `useEditor` → `editor.getContainer()`,
feels superfluous.
## Methodology
Looked for places where we do:
- `body.click()`
- places we do `container.focus()`
- places we do `container.blur()`
- places we do `editor.updateInstanceState({ isFocused })`
- places we do `autofocus`
- searched for `document.activeElement`
### 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
- [x] run test-focus.spec.ts
- [x] check MultipleExample
- [x] check EditorFocusExample
- [x] check autoFocus
- [x] check style panel usage and focus events in general
- [x] check text editing focus, lots of different devices,
mobile/desktop
### Release Notes
- Focus: rework and untangle existing focus management logic in the SDK
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.
in many places, we use a pattern like `React.createContext({} as
Editor)` when defining contexts. This causes a problem: `{}` is not
`Editor`, but you can still `useEditor` wherever you like and your code
with run with this confusing non-editor value.
This diff updates all our `createContext` calls to default to `null`,
with an explicit check and error for missing values. Now, if you
`useEditor` outside of `<Tldraw />`, you'll get a message telling you
that it can only be used within `<Tldraw />`.
### Change Type
- [x] `sdk` — Changes the tldraw SDK
- [x] `improvement` — Improving existing features
### Release Notes
`useEditor` and other context-based hooks will now throw an error when
used out-of-context, instead of returning a fake value.
This adds a store-level "operation end" event which fires at the end of
atomic operations. It includes some other changes too:
- The `SideEffectManager` now lives in & is a property of the store as
`StoreSideEffects`. One benefit to this is that instead of overriding
methods on the store to register side effects (meaning the store can
only ever be used in one place) the store now calls directly into the
side effect manager, which is responsible for dealing with any other
callbacks
- The history manager's "batch complete" event is gone, in favour of
this new event. We were using the batch complete event for only one
thing, calling `onChildrenChange` - which meant it wasn't getting called
for undo/redo events, which aren't part of a batch. `onChildrenChange`
is now called after each atomic store operation affecting children.
I've also added a rough pin example which shows (kinda messily) how you
might use the operation complete handler to traverse a graph of bindings
and resolve constraints between them.
### Change Type
- [x] `sdk` — Changes the tldraw SDK
- [x] `feature` — New feature
### Release Notes
#### Breaking changes
`editor.registerBatchCompleteHandler` has been replaced with
`editor.registerOperationCompleteHandler`
This PR changes our imports so that they work in a few rare cases.
https://github.com/tldraw/tldraw/issues/1817
### Change Type
- [x] `sdk` — Changes the tldraw SDK
- [x] `bugfix` — Bug fix
### Release Notes
- Fix bug effecting imports in Astro.
With the new work on bindings, we no longer need to keep any arrows
stuff hard-coded in `editor`, so let's move it to `tldraw` with the rest
of the shapes.
Couple other changes as part of this:
- We had two different types of `WeakMap` backed cache, but we now only
have one
- There's a new free-standing version of `createComputedCache` that
doesn't need access to the editor/store in order to create the cache.
instead, it returns a `{get(editor, id)}` object and instantiates the
cache on a per-editor basis for each call.
- Fixed a bug in `createSelectedComputedCache` where the selector
derivation would get re-created on every call to `get`
### Change Type
- [x] `sdk` — Changes the tldraw SDK
- [x] `improvement` — Improving existing features
### Release Notes
#### Breaking changes
- `editor.getArrowInfo(shape)` has been replaced with
`getArrowInfo(editor, shape)`
- `editor.getArrowsBoundTo(shape)` has been removed. Instead, use
`editor.getBindingsToShape(shape, 'arrow')` and follow the `fromId` of
each binding to the corresponding arrow shape
- These types have moved from `@tldraw/editor` to `tldraw`:
- `TLArcInfo`
- `TLArrowInfo`
- `TLArrowPoint`
- `WeakMapCache` has been removed
First draft of the new bindings API. We'll follow this up with some API
refinements, tests, documentation, and examples.
Bindings are a new record type for establishing relationships between
two shapes so they can update at the same time.
### Change Type
- [x] `sdk` — Changes the tldraw SDK
- [x] `feature` — New feature
### Release Notes
#### Breaking changes
- The `start` and `end` properties on `TLArrowShape` no longer have
`type: point | binding`. Instead, they're always a point, which may be
out of date if a binding exists. To check for & retrieve arrow bindings,
use `getArrowBindings(editor, shape)` instead.
- `getArrowTerminalsInArrowSpace` must be passed a `TLArrowBindings` as
a third argument: `getArrowTerminalsInArrowSpace(editor, shape,
getArrowBindings(editor, shape))`
- The following types have been renamed:
- `ShapeProps` -> `RecordProps`
- `ShapePropsType` -> `RecordPropsType`
- `TLShapePropsMigrations` -> `TLPropsMigrations`
- `SchemaShapeInfo` -> `SchemaPropsInfo`
---------
Co-authored-by: David Sheldrick <d.j.sheldrick@gmail.com>
Adds a feature flag `Measure performance` that allows us to:
- Measure the performance of all the actions (it wraps them into
`measureCbDuration`).
- Measure the frame rate of certain interactions like resizing,
erasing,....
Example of how it looks like:
![CleanShot 2024-04-17 at 18 04
05](https://github.com/tldraw/tldraw/assets/2523721/0fb69745-f7b2-4b55-ac01-27ea26963d9a)
### Change Type
<!-- ❗ Please select a 'Scope' label ❗️ -->
- [ ] `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
- [x] `internal` — Does not affect user-facing stuff
<!-- ❗ Please select a 'Type' label ❗️ -->
- [ ] `bugfix` — Bug fix
- [ ] `feature` — New feature
- [ ] `improvement` — Improving existing features
- [ ] `chore` — Updating dependencies, other boring stuff
- [ ] `galaxy brain` — Architectural changes
- [ ] `tests` — Changes to any test code
- [x] `tools` — Changes to infrastructure, CI, internal scripts,
debugging tools, etc.
- [ ] `dunno` — I don't know
=
---------
Co-authored-by: Mime Čuvalo <mimecuvalo@gmail.com>
This PR adds a slideshow example (similar to @TodePond's slides but more
on rails) as a way to put some pressure on camera controls.
Along the way, it fixes some issues I found with animations and the new
camera controls.
- forced changes will continue to force through animations
- animations no longer set unnecessary additional listeners
- animations end correctly
- updating camera options does not immediately update the camera (to
allow for animations, etc.)
It also changes the location of the "in front of the canvas" element so
that it is not hidden by the hit test blocking element.
### Change Type
- [x] `sdk` — Changes the tldraw SDK
- [x] `improvement` — Improving existing features
This PR implements a camera options API.
- [x] Initial PR
- [x] Updated unit tests
- [x] Feedback / review
- [x] New unit tests
- [x] Update use-case examples
- [x] Ship?
## Public API
A user can provide camera options to the `Tldraw` component via the
`cameraOptions` prop. The prop is also available on the `TldrawEditor`
component and the constructor parameters of the `Editor` class.
```tsx
export default function CameraOptionsExample() {
return (
<div className="tldraw__editor">
<Tldraw cameraOptions={CAMERA_OPTIONS} />
</div>
)
}
```
At runtime, a user can:
- get the current camera options with `Editor.getCameraOptions`
- update the camera options with `Editor.setCameraOptions`
Setting the camera options automatically applies them to the current
camera.
```ts
editor.setCameraOptions({...editor.getCameraOptions(), isLocked: true })
```
A user can get the "camera fit zoom" via `editor.getCameraFitZoom()`.
# Interface
The camera options themselves can look a few different ways depending on
the `type` provided.
```tsx
export type TLCameraOptions = {
/** Whether the camera is locked. */
isLocked: boolean
/** The speed of a scroll wheel / trackpad pan. Default is 1. */
panSpeed: number
/** The speed of a scroll wheel / trackpad zoom. Default is 1. */
zoomSpeed: number
/** The steps that a user can zoom between with zoom in / zoom out. The first and last value will determine the min and max zoom. */
zoomSteps: number[]
/** Controls whether the wheel pans or zooms.
*
* - `zoom`: The wheel will zoom in and out.
* - `pan`: The wheel will pan the camera.
* - `none`: The wheel will do nothing.
*/
wheelBehavior: 'zoom' | 'pan' | 'none'
/** The camera constraints. */
constraints?: {
/** The bounds (in page space) of the constrained space */
bounds: BoxModel
/** The padding inside of the viewport (in screen space) */
padding: VecLike
/** The origin for placement. Used to position the bounds within the viewport when an axis is fixed or contained and zoom is below the axis fit. */
origin: VecLike
/** The camera's initial zoom, used also when the camera is reset.
*
* - `default`: Sets the initial zoom to 100%.
* - `fit-x`: The x axis will completely fill the viewport bounds.
* - `fit-y`: The y axis will completely fill the viewport bounds.
* - `fit-min`: The smaller axis will completely fill the viewport bounds.
* - `fit-max`: The larger axis will completely fill the viewport bounds.
* - `fit-x-100`: The x axis will completely fill the viewport bounds, or 100% zoom, whichever is smaller.
* - `fit-y-100`: The y axis will completely fill the viewport bounds, or 100% zoom, whichever is smaller.
* - `fit-min-100`: The smaller axis will completely fill the viewport bounds, or 100% zoom, whichever is smaller.
* - `fit-max-100`: The larger axis will completely fill the viewport bounds, or 100% zoom, whichever is smaller.
*/
initialZoom:
| 'fit-min'
| 'fit-max'
| 'fit-x'
| 'fit-y'
| 'fit-min-100'
| 'fit-max-100'
| 'fit-x-100'
| 'fit-y-100'
| 'default'
/** The camera's base for its zoom steps.
*
* - `default`: Sets the initial zoom to 100%.
* - `fit-x`: The x axis will completely fill the viewport bounds.
* - `fit-y`: The y axis will completely fill the viewport bounds.
* - `fit-min`: The smaller axis will completely fill the viewport bounds.
* - `fit-max`: The larger axis will completely fill the viewport bounds.
* - `fit-x-100`: The x axis will completely fill the viewport bounds, or 100% zoom, whichever is smaller.
* - `fit-y-100`: The y axis will completely fill the viewport bounds, or 100% zoom, whichever is smaller.
* - `fit-min-100`: The smaller axis will completely fill the viewport bounds, or 100% zoom, whichever is smaller.
* - `fit-max-100`: The larger axis will completely fill the viewport bounds, or 100% zoom, whichever is smaller.
*/
baseZoom:
| 'fit-min'
| 'fit-max'
| 'fit-x'
| 'fit-y'
| 'fit-min-100'
| 'fit-max-100'
| 'fit-x-100'
| 'fit-y-100'
| 'default'
/** The behavior for the constraints for both axes or each axis individually.
*
* - `free`: The bounds are ignored when moving the camera.
* - 'fixed': The bounds will be positioned within the viewport based on the origin
* - `contain`: The 'fixed' behavior will be used when the zoom is below the zoom level at which the bounds would fill the viewport; and when above this zoom, the bounds will use the 'inside' behavior.
* - `inside`: The bounds will stay completely within the viewport.
* - `outside`: The bounds will stay touching the viewport.
*/
behavior:
| 'free'
| 'fixed'
| 'inside'
| 'outside'
| 'contain'
| {
x: 'free' | 'fixed' | 'inside' | 'outside' | 'contain'
y: 'free' | 'fixed' | 'inside' | 'outside' | 'contain'
}
}
}
```
### Change Type
- [x] `sdk` — Changes the tldraw SDK
- [x] `feature` — New feature
### Test Plan
These features combine in different ways, so we'll want to write some
more tests to find surprises.
1. Add a step-by-step description of how to test your PR here.
2.
- [ ] Unit Tests
### Release Notes
- SDK: Adds camera options.
---------
Co-authored-by: Mitja Bezenšek <mitja.bezensek@gmail.com>
This PR fixes a bug where alt-dragging the left or right handles of a
text shape would not produce the correct outcome: the width would double
but the center would change.
![Kapture 2024-04-28 at 13 48
52](https://github.com/tldraw/tldraw/assets/23072548/ad339a57-4efd-4201-86bc-c03a379f7e0c)
This is because the text shape is aspect ratio locked only when dragging
handles other than the left or right, but we didn't have the ability to
differentiate between that. We've had to add that optionality in,
together with a hard-coded override of the normal behavior for text
shapes.
### Change Type
- [x] `sdk` — Changes the tldraw SDK
- [x] `bugfix` — Bug fix
### Test Plan
1. Resize text.
2. Resize text with the alt key held.
- [x] Unit Tests
### Release Notes
- Fixed a bug with resizing text shapes from the left and right while
holding alt.
This PR:
- updates `getHoveredId` to `getHoveredShapeId`
- adds an option to ignore locked shapes to `Editor.getShapeAtPoint`.
### Change Type
- [x] `sdk` — Changes the tldraw SDK
- [x] `improvement` — Improving existing features
### Test Plan
1. Put two shapes on top of eachother
2. Lock the top shape
3. Hover the shape
4. The bottom shape should be hovered
5. Right click
6. The top shape should be selected
- [x] Unit tests
### Release Notes
- Fixed a bug with locked shapes being hoverable.
Our undo-redo system before this diff is based on commands. A command
is:
- A function that produces some data required to perform and undo a
change
- A function that actually performs the change, based on the data
- Another function that undoes the change, based on the data
- Optionally, a function to _redo_ the change, although in practice we
never use this
Each command that gets run is added to the undo/redo stack unless it
says it shouldn't be.
This diff replaces this system of commands with a new one where all
changes to the store are automatically recorded in the undo/redo stack.
You can imagine the new history manager like a tape recorder - it
automatically records everything that happens to the store in a special
diff, unless you "pause" the recording and ask it not to. Undo and redo
rewind/fast-forward the tape to certain marks.
As the command concept is gone, the things that were commands are now
just functions that manipulate the store.
One other change here is that the store's after-phase callbacks (and the
after-phase side-effects as a result) are now batched up and called at
the end of certain key operations. For example, `applyDiff` would
previously call all the `afterCreate` callbacks before making any
removals from the diff. Now, it (and anything else that uses
`store.atomic(fn)` will defer firing any after callbacks until the end
of an operation. before callbacks are still called part-way through
operations.
## Design options
Automatic recording is a fairly large big semantic change, particularly
to the standalone `store.put`/`store.remove` etc. commands. We could
instead make not-recording the default, and make recording opt-in
instead. However, I think auto-record-by-default is the right choice for
a few reasons:
1. Switching to a recording-based vs command-based undo-redo model is
fundamentally a big semantic change. In the past, `store.put` etc. were
always ignored. Now, regardless of whether we choose record-by-default
or ignore-by-default, the behaviour of `store.put` is _context_
dependant.
2. Switching to ignore-by-default means that either our commands don't
record undo/redo history any more (unless wrapped in
`editor.history.record`, a far larger semantic change) or they have to
always-record/all accept a history options bag. If we choose
always-record, we can't use commands within `history.ignore` as they'll
start recording again. If we choose the history options bag, we have to
accept those options in 10s of methods - basically the entire `Editor`
api surface.
Overall, given that some breaking semantic change here is unavoidable, I
think that record-by-default hits the right balance of tradeoffs. I
think it's a better API going forward, whilst also not being too
disruptive as the APIs it affects are very "deep" ones that we don't
typically encourage people to use.
### Change Type
- [x] `sdk` — Changes the tldraw SDK
- [x] `improvement` — Improving existing features
- [x] `galaxy brain` — Architectural changes
### Release Note
#### Breaking changes
##### 1. History Options
Previously, some (not all!) commands accepted a history options object
with `squashing`, `ephemeral`, and `preserveRedoStack` flags. Squashing
enabled/disabled a memory optimisation (storing individual commands vs
squashing them together). Ephemeral stopped a command from affecting the
undo/redo stack at all. Preserve redo stack stopped commands from wiping
the redo stack. These flags were never available consistently - some
commands had them and others didn't.
In this version, most of these flags have been removed. `squashing` is
gone entirely (everything squashes & does so much faster than before).
There were a couple of commands that had a special default - for
example, `updateInstanceState` used to default to being `ephemeral`.
Those maintain the defaults, but the options look a little different now
- `{ephemeral: true}` is now `{history: 'ignore'}` and
`{preserveRedoStack: true}` is now `{history:
'record-preserveRedoStack'}`.
If you were previously using these options in places where they've now
been removed, you can use wrap them with `editor.history.ignore(fn)` or
`editor.history.batch(fn, {history: 'record-preserveRedoStack'})`. For
example,
```ts
editor.nudgeShapes(..., { ephemeral: true })
```
can now be written as
```ts
editor.history.ignore(() => {
editor.nudgeShapes(...)
})
```
##### 2. Automatic recording
Previously, only commands (e.g. `editor.updateShapes` and things that
use it) were added to the undo/redo stack. Everything else (e.g.
`editor.store.put`) wasn't. Now, _everything_ that touches the store is
recorded in the undo/redo stack (unless it's part of
`mergeRemoteChanges`). You can use `editor.history.ignore(fn)` as above
if you want to make other changes to the store that aren't recorded -
this is short for `editor.history.batch(fn, {history: 'ignore'})`
When upgrading to this version of tldraw, you shouldn't need to change
anything unless you're using `store.put`, `store.remove`, or
`store.applyDiff` outside of `store.mergeRemoteChanges`. If you are, you
can preserve the functionality of those not being recorded by wrapping
them either in `mergeRemoteChanges` (if they're multiplayer-related) or
`history.ignore` as appropriate.
##### 3. Side effects
Before this diff, any changes in side-effects weren't captured by the
undo-redo stack. This was actually the motivation for this change in the
first place! But it's a pretty big change, and if you're using side
effects we recommend you double-check how they interact with undo/redo
before/after this change. To get the old behaviour back, wrap your side
effects in `editor.history.ignore`.
##### 4. Mark options
Previously, `editor.mark(id)` accepted two additional boolean
parameters: `onUndo` and `onRedo`. If these were set to false, then when
undoing or redoing we'd skip over that mark and keep going until we
found one with those values set to true. We've removed those options -
if you're using them, let us know and we'll figure out an alternative!
This PR replaces our current minimap implementation with one that uses
WebGL
### 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.
---------
Co-authored-by: Steve Ruiz <steveruizok@gmail.com>
Adds RBush to handle spatial querying. We use it for:
- Culling. Helps a lot with panning as we don't have to compute the
culled shapes from scratch. Instead we just query rbush again. It makes
culling quite granular: spatial index updates when shapes change
(additions, removals, changes to bounds), visible shapes depends on
that, but also updates when the viewport page bound change, culled
shapes then depend on that but also change with selections changes. The
api stayed the same, which is great since the fuzz tests can stay as
they are.
- Brushing
- Erasing
- Scribble brushing
- Getting shapes at point (for example, when updating the hover id)
This improves performance of all of those operations. I might have
missed some places where this might also be useful.
### Erasing before (Test on my old ipad)
https://github.com/tldraw/tldraw/assets/2523721/edb9c004-a44a-4779-b2d0-98617b057314
### Erasing after
https://github.com/tldraw/tldraw/assets/2523721/8f8367fd-fa8e-4963-ba13-720c5f0c2da5
### Creating an arrow before
https://github.com/tldraw/tldraw/assets/2523721/4068f8b7-f7b8-4826-83f2-083b1f3783bc
### After (much better, but still bad)
https://github.com/tldraw/tldraw/assets/2523721/11af6be6-01d8-4740-bf15-896e2dd31dd6
### Change Type
<!-- ❗ Please select a 'Scope' label ❗️ -->
- [ ] `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
- [x] `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
---------
Co-authored-by: Steve Ruiz <steveruizok@gmail.com>
Describe what your pull request does. If appropriate, add GIFs or images
showing the before and after.
### Change Type
- [x] `sdk` — Changes the tldraw SDK
- [x] `galaxy brain` — Architectural changes
### 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
#### BREAKING CHANGES
- The `Migrations` type is now called `LegacyMigrations`.
- The serialized schema format (e.g. returned by
`StoreSchema.serialize()` and `Store.getSnapshot()`) has changed. You
don't need to do anything about it unless you were reading data directly
from the schema for some reason. In which case it'd be best to avoid
that in the future! We have no plans to change the schema format again
(this time was traumatic enough) but you never know.
- `compareRecordVersions` and the `RecordVersion` type have both
disappeared. There is no replacement. These were public by mistake
anyway, so hopefully nobody had been using it.
- `compareSchemas` is a bit less useful now. Our migrations system has
become a little fuzzy to allow for simpler UX when adding/removing
custom extensions and 3rd party dependencies, and as a result we can no
longer compare serialized schemas in any rigorous manner. You can rely
on this function to return `0` if the schemas are the same. Otherwise it
will return `-1` if the schema on the right _seems_ to be newer than the
schema on the left, but it cannot guarantee that in situations where
migration sequences have been removed over time (e.g. if you remove one
of the builtin tldraw shapes).
Generally speaking, the best way to check schema compatibility now is to
call `store.schema.getMigrationsSince(persistedSchema)`. This will throw
an error if there is no upgrade path from the `persistedSchema` to the
current version.
- `defineMigrations` has been deprecated and will be removed in a future
release. For upgrade instructions see
https://tldraw.dev/docs/persistence#Updating-legacy-shape-migrations-defineMigrations
- `migrate` has been removed. Nobody should have been using this but if
you were you'll need to find an alternative. For migrating tldraw data,
you should stick to using `schema.migrateStoreSnapshot` and, if you are
building a nuanced sync engine that supports some amount of backwards
compatibility, also feel free to use `schema.migratePersistedRecord`.
- the `Migration` type has changed. If you need the old one for some
reason it has been renamed to `LegacyMigration`. It will be removed in a
future release.
- the `Migrations` type has been renamed to `LegacyMigrations` and will
be removed in a future release.
- the `SerializedSchema` type has been augmented. If you need the old
version specifically you can use `SerializedSchemaV1`
---------
Co-authored-by: Steve Ruiz <steveruizok@gmail.com>