apps/docs/content/releases/next.mdx
This release adds a center operation to Editor.alignShapes, a tleditors registry of mounted editors, iterateGraphemes to @tldraw/utils, and two new reactive methods on AtomMap. It also deprecates useViewportHeight, speeds up the signals and store layers, and lands a broad reliability pass across the SDK, with over a hundred fixes to undo history, selection and tool interactions, cropping, arrows, the signals and store layers, sync, and local persistence.
🔜 useViewportHeight() is deprecated. Read window.visualViewport directly instead. The hook returns only the visible viewport's bottom edge, and keeping a panel clear of the software keyboard generally needs the whole visible rectangle. (#9826)
Add 'center' as an Editor.alignShapes operation. It applies the existing 'center-horizontal' and 'center-vertical' operations together, so shapes center on both axes in one command. (#9074)
Add tleditors, a reactive global registry of currently mounted editors. Use it to reach live editors from outside the React tree, for example from sidebar chrome or a keyboard shortcut handler. (#9904)
import { tleditors, useValue } from 'tldraw'
const mounted = useValue('mounted editors', () => tleditors.getMounted(), [])
Add getOrInsert(key, defaultValue) and getOrInsertComputed(key, callback) to AtomMap, matching the Map methods added in newer JavaScript runtimes. Both are reactive, and the callback only runs when the key is absent. (#9752)
Add isPage(record) to @tldraw/tlschema, a type guard that narrows an unknown record to TLPage, matching the guards already exported for the other record types. (#9774)
Add iterateGraphemes(str) to @tldraw/utils. It iterates a string's grapheme clusters, using Intl.Segmenter when it's available and falling back to code-point iteration when it isn't. (#9896)
EmojiPickerProps.onSelect now receives the originating event as an optional second argument. Custom reaction palettes can forward it to opt into shift multi-select. (#9999)
Group2d and Editor hit-testing. (#8600)@tldraw/state. Capturing a parent used to scan the capturing signal's parent set twice, once to check for the parent and again to add it. (#9690)@tldraw/state. (#10155)@tldraw/store history bookkeeping, so squashing record diffs and filtering query history no longer build intermediate copies on every tick. (#9689, #9869)exportBackground default (true) into the TLInstance record type in @tldraw/tlschema, so every code path that creates the instance record gets the same value. (#10534)Intl.Segmenter, such as Firefox before 125. (#9896)tick or frame listener threw, which silently disabled camera animations, edge scrolling, and other frame-driven behavior for the rest of the session. (#9946)DefaultToolbar crashing when mounted outside the Tldraw component. (#10007)onEditEnd are now recorded in history. (#10369)bailToMark removing records that were not part of the undone change. (#10207)operationComplete handlers after a remote merge being attributed to the remote source. (#10229)onEnter transitions to a child state ending up with two active children. (#10354)size values rendering as NaN. (#10214)getCustomDisplayValues. (#10230)setCameraOptions({ zoomSteps: undefined }) breaking every later camera move. (#10387)slideCamera zoom momentum driving the zoom to zero on a long frame. (#10388)getSelectionRotatedScreenBounds going stale when the editor container moves. (#10390)duplicateShapes throwing on unknown ids and offsetting nested duplicates twice. (#10355)packShapes packing shapes of different heights into a single column. (#10381)stackShapes reordering shapes when a gap is given. (#10383)putContentOntoCurrentPage mutating the content passed to it. (#10367)setErasingShapes mutating its input array, so it accepts store-owned (frozen) arrays. (#10371)createTLStore onMount option never running when the editor unmounts. (#10375)Vec.Slope, Vec.cross, and areAnglesCompatible for negative angles, rotateSelectionHandle for negative rotations, and dotted dash arrays for very short paths. (#10352)useSyncDemo creating assets for uploads the server rejected. (#10109)loadSnapshot failing on better-sqlite3, and NodeSqliteWrapper getting stuck in a transaction when COMMIT fails. (#10111, #10113)start(). (#10148)deferAsyncEffects call throws. (#10150)@computed on a subclass override that calls super, and @computed() with no arguments. (#10151)computeDiff never seeing that change, and an explicit null diff passed to atom.set() or returned from withDiff() clearing history. (#10153)localStorageAtom throwing outside the browser. (#10154)ImmutableMap and AtomMap for keys named after Object.prototype members, and make NaN and -0 key equality match native Map. (#10202)AtomMap throwing when an entry is deleted mid-iteration. (#10209)StoreSchema.create reporting a redundant migration dependency as circular. (#10213)store.query.ids() query creator being called with a computed's (previousValue, epoch) arguments. (#10218)CollectionDiffs from store.query.ids() and index() containing an empty added or removed set after an add/remove round trip. (#10221)