diff --git a/packages/dev/src/app.tsx b/packages/dev/src/app.tsx
index 1e6ebd434..5896eb292 100644
--- a/packages/dev/src/app.tsx
+++ b/packages/dev/src/app.tsx
@@ -1,6 +1,7 @@
import * as React from 'react'
-import Editor from './components/editor'
+import Controlled from './controlled'
+import Basic from './basic'
export default function App(): JSX.Element {
- return
+ return
}
diff --git a/packages/dev/src/basic.tsx b/packages/dev/src/basic.tsx
new file mode 100644
index 000000000..3574cae02
--- /dev/null
+++ b/packages/dev/src/basic.tsx
@@ -0,0 +1,6 @@
+import * as React from 'react'
+import Editor from './components/editor'
+
+export default function BasicUsage(): JSX.Element {
+ return
+}
diff --git a/packages/dev/src/controlled.tsx b/packages/dev/src/controlled.tsx
new file mode 100644
index 000000000..ae69fc11f
--- /dev/null
+++ b/packages/dev/src/controlled.tsx
@@ -0,0 +1,93 @@
+import * as React from 'react'
+import {
+ TLDraw,
+ ColorStyle,
+ DashStyle,
+ SizeStyle,
+ TLDrawDocument,
+ TLDrawShapeType,
+} from '@tldraw/tldraw'
+
+export default function Controlled() {
+ const [doc, setDoc] = React.useState({
+ id: 'doc',
+ pages: {
+ page1: {
+ id: 'page1',
+ shapes: {
+ rect1: {
+ id: 'rect1',
+ type: TLDrawShapeType.Rectangle,
+ parentId: 'page1',
+ name: 'Rectangle',
+ childIndex: 1,
+ point: [100, 100],
+ size: [100, 100],
+ style: {
+ dash: DashStyle.Draw,
+ size: SizeStyle.Medium,
+ color: ColorStyle.Blue,
+ },
+ },
+ rect2: {
+ id: 'rect2',
+ parentId: 'page1',
+ name: 'Rectangle',
+ childIndex: 2,
+ type: TLDrawShapeType.Rectangle,
+ point: [150, 250],
+ size: [150, 150],
+ style: {
+ dash: DashStyle.Draw,
+ size: SizeStyle.Medium,
+ color: ColorStyle.Blue,
+ },
+ },
+ },
+ bindings: {},
+ },
+ },
+ pageStates: {
+ page1: {
+ id: 'page1',
+ selectedIds: ['rect1'],
+ camera: {
+ point: [0, 0],
+ zoom: 1,
+ },
+ },
+ },
+ })
+
+ React.useEffect(() => {
+ const timeout = setTimeout(
+ () =>
+ setDoc({
+ ...doc,
+ pages: {
+ ...doc.pages,
+ page1: {
+ ...doc.pages.page1,
+ shapes: {
+ ...doc.pages.page1.shapes,
+ rect2: {
+ ...doc.pages.page1.shapes.rect2,
+ style: {
+ ...doc.pages.page1.shapes.rect2.style,
+ color: ColorStyle.Orange,
+ },
+ },
+ },
+ },
+ },
+ }),
+ 1000
+ )
+
+ return () => {
+ clearTimeout(timeout)
+ }
+ }, [])
+
+ return
+}
diff --git a/packages/tldraw/src/components/tldraw/tldraw.tsx b/packages/tldraw/src/components/tldraw/tldraw.tsx
index c8ba14a29..9b28157da 100644
--- a/packages/tldraw/src/components/tldraw/tldraw.tsx
+++ b/packages/tldraw/src/components/tldraw/tldraw.tsx
@@ -61,30 +61,26 @@ export function TLDraw({ id, document, currentPageId, onMount, onChange }: TLDra
return { tlstate, useSelector: tlstate.useStore }
})
- React.useEffect(() => {
- if (!document) return
- tlstate.loadDocument(document)
- }, [document, tlstate])
-
- React.useEffect(() => {
- if (!currentPageId) return
- tlstate.changePage(currentPageId)
- }, [currentPageId, tlstate])
-
return (
-
+
)
}
-function InnerTldraw() {
- useCustomFonts()
-
+function InnerTldraw({
+ currentPageId,
+ document,
+}: {
+ currentPageId?: string
+ document?: TLDrawDocument
+}) {
const { tlstate, useSelector } = useTLDrawContext()
+ useCustomFonts()
+
useKeyboardShortcuts()
const page = useSelector(pageSelector)
@@ -128,6 +124,20 @@ function InnerTldraw() {
return {}
}, [isDarkMode])
+ React.useEffect(() => {
+ if (!document) return
+ if (document.id === tlstate.document.id) {
+ tlstate.updateDocument(document)
+ } else {
+ tlstate.loadDocument(document)
+ }
+ }, [document, tlstate])
+
+ React.useEffect(() => {
+ if (!currentPageId) return
+ tlstate.changePage(currentPageId)
+ }, [currentPageId, tlstate])
+
return (
diff --git a/packages/tldraw/src/state/tlstate.ts b/packages/tldraw/src/state/tlstate.ts
index 7cb8c1596..34107be69 100644
--- a/packages/tldraw/src/state/tlstate.ts
+++ b/packages/tldraw/src/state/tlstate.ts
@@ -67,7 +67,7 @@ const defaultDocument: TLDrawDocument = {
},
}
-const initialData: Data = {
+const defaultState: Data = {
settings: {
isPenMode: false,
isDarkMode: false,
@@ -120,11 +120,11 @@ export class TLDrawState extends StateManager {
selectedGroupId?: string
constructor(
- id = Utils.uniqueId(),
+ id?: string,
onChange?: (tlstate: TLDrawState, data: Data, reason: string) => void,
onMount?: (tlstate: TLDrawState) => void
) {
- super(initialData, id, 2, (prev, next, prevVersion) => {
+ super(defaultState, id, 2, (prev, next, prevVersion) => {
const state = { ...prev }
if (prevVersion === 1)
state.settings = {
@@ -477,6 +477,69 @@ export class TLDrawState extends StateManager {
return this
}
+ /**
+ * Update the current document.
+ * @param document
+ */
+ updateDocument = (document: TLDrawDocument, reason = 'updated_document'): this => {
+ console.log(reason)
+
+ const state = this.state
+
+ const currentPageId = document.pages[this.currentPageId]
+ ? this.currentPageId
+ : Object.keys(document.pages)[0]
+
+ this.replaceState(
+ {
+ ...this.state,
+ appState: {
+ ...this.appState,
+ currentPageId,
+ },
+ document: {
+ ...document,
+ pages: Object.fromEntries(
+ Object.entries(document.pages)
+ .sort((a, b) => (a[1].childIndex || 0) - (b[1].childIndex || 0))
+ .map(([pageId, page], i) => {
+ const nextPage = { ...page }
+ if (!nextPage.name) nextPage.name = `Page ${i + 1}`
+ return [pageId, nextPage]
+ })
+ ),
+ pageStates: Object.fromEntries(
+ Object.entries(document.pageStates).map(([pageId, pageState]) => {
+ const page = document.pages[pageId]
+ const nextPageState = { ...pageState }
+ const keysToCheck = ['bindingId', 'editingId', 'hoveredId', 'pointedId'] as const
+
+ for (const key of keysToCheck) {
+ if (!page.shapes[key]) {
+ nextPageState[key] = undefined
+ }
+ }
+
+ nextPageState.selectedIds = pageState.selectedIds.filter(
+ (id) => !!document.pages[pageId].shapes[id]
+ )
+
+ return [pageId, nextPageState]
+ })
+ ),
+ },
+ },
+ `${reason}:${document.id}`
+ )
+
+ console.log(
+ 'did page change?',
+ this.state.document.pages['page1'] !== state.document.pages['page1']
+ )
+
+ return this
+ }
+
/**
* Load a new document.
* @param document The document to load
@@ -487,47 +550,7 @@ export class TLDrawState extends StateManager {
this.clearSelectHistory()
this.session = undefined
this.selectedGroupId = undefined
-
- return this.replaceState(
- {
- ...this.state,
- appState: {
- ...this.appState,
- currentPageId: Object.keys(document.pages)[0],
- },
- document: {
- ...document,
- pages: Object.fromEntries(
- Object.entries(document.pages)
- .sort((a, b) => (a[1].childIndex || 0) - (b[1].childIndex || 0))
- .map(([id, page], i) => {
- return [
- id,
- {
- ...page,
- name: page.name ? page.name : `Page ${i++}`,
- },
- ]
- })
- ),
- pageStates: Object.fromEntries(
- Object.entries(document.pageStates).map(([id, pageState]) => {
- return [
- id,
- {
- ...pageState,
- bindingId: undefined,
- editingId: undefined,
- hoveredId: undefined,
- pointedId: undefined,
- },
- ]
- })
- ),
- },
- },
- `loaded_document:${document.id}`
- )
+ return this.updateDocument(document, 'loaded_document')
}
/**