diff --git a/apps/examples/src/4-custom-ui/CustomUiExample.tsx b/apps/examples/src/4-custom-ui/CustomUiExample.tsx
index 5812871e9..66014805b 100644
--- a/apps/examples/src/4-custom-ui/CustomUiExample.tsx
+++ b/apps/examples/src/4-custom-ui/CustomUiExample.tsx
@@ -1,13 +1,15 @@
-import { Canvas, TldrawEditor, useApp } from '@tldraw/tldraw'
+import { Canvas, TldrawEditor, TldrawEditorConfig, useApp } from '@tldraw/tldraw'
import '@tldraw/tldraw/editor.css'
import { useEffect } from 'react'
import { track } from 'signia-react'
import './custom-ui.css'
+const config = new TldrawEditorConfig()
+
export default function Example() {
return (
-
+
diff --git a/apps/examples/src/5-exploded/ExplodedExample.tsx b/apps/examples/src/5-exploded/ExplodedExample.tsx
index a17c24bd3..538055310 100644
--- a/apps/examples/src/5-exploded/ExplodedExample.tsx
+++ b/apps/examples/src/5-exploded/ExplodedExample.tsx
@@ -3,6 +3,7 @@ import {
ContextMenu,
getUserData,
TldrawEditor,
+ TldrawEditorConfig,
TldrawUi,
TLInstance,
useLocalSyncClient,
@@ -12,10 +13,13 @@ import '@tldraw/tldraw/ui.css'
const instanceId = TLInstance.createCustomId('example')
+const config = new TldrawEditorConfig()
+
export default function Example() {
const userData = getUserData()
const syncedStore = useLocalSyncClient({
+ config,
instanceId,
userId: userData.id,
universalPersistenceKey: 'exploded-example',
@@ -24,7 +28,13 @@ export default function Example() {
return (
-
+
diff --git a/apps/vscode/editor/src/app.tsx b/apps/vscode/editor/src/app.tsx
index feccc27fa..92f7ef8e2 100644
--- a/apps/vscode/editor/src/app.tsx
+++ b/apps/vscode/editor/src/app.tsx
@@ -4,6 +4,7 @@ import {
ErrorBoundary,
setRuntimeOverrides,
TldrawEditor,
+ TldrawEditorConfig,
TLUserId,
} from '@tldraw/editor'
import { linksUiOverrides } from './utils/links'
@@ -24,6 +25,8 @@ import { FullPageMessage } from './FullPageMessage'
import { onCreateBookmarkFromUrl } from './utils/bookmarks'
import { vscode } from './utils/vscode'
+const config = new TldrawEditorConfig()
+
// @ts-ignore
setRuntimeOverrides({
@@ -96,6 +99,7 @@ export const TldrawWrapper = () => {
uri: message.data.uri,
userId: message.data.userId as TLUserId,
isDarkMode: message.data.isDarkMode,
+ config,
})
// We only want to listen for this message once
window.removeEventListener('message', handleMessage)
@@ -126,20 +130,30 @@ export type TLDrawInnerProps = {
uri: string
userId: TLUserId
isDarkMode: boolean
+ config: TldrawEditorConfig
}
-function TldrawInner({ uri, assetSrc, userId, isDarkMode, fileContents }: TLDrawInnerProps) {
+function TldrawInner({
+ uri,
+ config,
+ assetSrc,
+ userId,
+ isDarkMode,
+ fileContents,
+}: TLDrawInnerProps) {
const instanceId = TAB_ID
const syncedStore = useLocalSyncClient({
universalPersistenceKey: uri,
instanceId,
userId,
+ config,
})
const assetUrls = useMemo(() => getAssetUrlsByImport({ baseUrl: assetSrc }), [assetSrc])
return (
{
- return browser.execute(() => {
- return window.tldrawReady
- })
- })
+ await Promise.any([
+ new Promise((r) => {
+ browser.waitUntil(() => browser.execute(() => window.tldrawReady)).then(() => r(true))
+ }),
+ new Promise((r) => {
+ // eslint-disable-next-line no-console
+ console.log('waitFor failed, using timeout')
+ setTimeout(() => r(true), 2000)
+ }),
+ ])
// Make sure the window is focused... maybe
await ui.canvas.click(100, 100)
diff --git a/packages/editor/api-report.md b/packages/editor/api-report.md
index b79ef3806..dcad156a7 100644
--- a/packages/editor/api-report.md
+++ b/packages/editor/api-report.md
@@ -44,7 +44,6 @@ import { sortByIndex } from '@tldraw/indices';
import { StoreSchema } from '@tldraw/tlstore';
import { StoreSnapshot } from '@tldraw/tlstore';
import { StrokePoint } from '@tldraw/primitives';
-import { T } from '@tldraw/tlvalidate';
import { TLAlignType } from '@tldraw/tlschema';
import { TLArrowheadType } from '@tldraw/tlschema';
import { TLArrowShape } from '@tldraw/tlschema';
@@ -87,7 +86,6 @@ import { TLShapeId } from '@tldraw/tlschema';
import { TLShapePartial } from '@tldraw/tlschema';
import { TLShapeProp } from '@tldraw/tlschema';
import { TLShapeProps } from '@tldraw/tlschema';
-import { TLShapeType } from '@tldraw/tlschema';
import { TLSizeStyle } from '@tldraw/tlschema';
import { TLSizeType } from '@tldraw/tlschema';
import { TLStore } from '@tldraw/tlschema';
@@ -274,7 +272,7 @@ export class App extends EventEmitter {
getPageTransform(shape: TLShape): Matrix2d | undefined;
getPageTransformById(id: TLShapeId): Matrix2d | undefined;
// (undocumented)
- getParentIdForNewShapeAtPoint(point: VecLike, shapeType: TLShapeType): TLPageId | TLShapeId;
+ getParentIdForNewShapeAtPoint(point: VecLike, shapeType: TLShape['type']): TLPageId | TLShapeId;
getParentPageId(shape?: TLShape): TLPageId | undefined;
getParentShape(shape?: TLShape): TLShape | undefined;
getParentsMappedToChildren(ids: TLShapeId[]): Map>;
@@ -556,7 +554,7 @@ export function applyRotationToSnapshotShapes({ delta, app, snapshot, stage, }:
// @public (undocumented)
export interface AppOptions {
- config?: TldrawEditorConfig;
+ config: TldrawEditorConfig;
getContainer: () => HTMLElement;
store: TLStore;
}
@@ -1793,7 +1791,7 @@ export function TldrawEditor(props: TldrawEditorProps): JSX.Element;
// @public (undocumented)
export class TldrawEditorConfig {
- constructor(opts: TldrawEditorConfigOptions);
+ constructor(opts?: TldrawEditorConfigOptions);
// (undocumented)
createStore(config: {
initialData?: StoreSnapshot;
@@ -1801,13 +1799,7 @@ export class TldrawEditorConfig {
instanceId: TLInstanceId;
}): TLStore;
// (undocumented)
- static readonly default: TldrawEditorConfig;
- // (undocumented)
- readonly shapeMigrations: MigrationsForShapes;
- // (undocumented)
- readonly shapeUtils: UtilsForShapes;
- // (undocumented)
- readonly shapeValidators: Record>;
+ readonly shapeUtils: Record>;
// (undocumented)
readonly storeSchema: StoreSchema;
// (undocumented)
@@ -1823,7 +1815,7 @@ export interface TldrawEditorProps {
// (undocumented)
children?: any;
components?: Partial;
- config?: TldrawEditorConfig;
+ config: TldrawEditorConfig;
instanceId?: TLInstanceId;
isDarkMode?: boolean;
onCreateAssetFromFile?: (file: File) => Promise;
@@ -2044,7 +2036,7 @@ export class TLFrameUtil extends TLBoxUtil {
// (undocumented)
canEdit: () => boolean;
// (undocumented)
- canReceiveNewChildrenOfType: (_type: TLShapeType) => boolean;
+ canReceiveNewChildrenOfType: (_type: TLShape['type']) => boolean;
// (undocumented)
defaultProps(): TLFrameShape['props'];
// (undocumented)
@@ -2456,7 +2448,7 @@ export abstract class TLShapeUtil {
canCrop: TLShapeUtilFlag;
canDropShapes(shape: T, shapes: TLShape[]): boolean;
canEdit: TLShapeUtilFlag;
- canReceiveNewChildrenOfType(type: TLShapeType): boolean;
+ canReceiveNewChildrenOfType(type: TLShape['type']): boolean;
canResize: TLShapeUtilFlag;
canScroll: TLShapeUtilFlag;
canUnmount: TLShapeUtilFlag;
diff --git a/packages/editor/src/lib/TldrawEditor.tsx b/packages/editor/src/lib/TldrawEditor.tsx
index 948845a7c..7a55fba6d 100644
--- a/packages/editor/src/lib/TldrawEditor.tsx
+++ b/packages/editor/src/lib/TldrawEditor.tsx
@@ -1,7 +1,7 @@
import { TLAsset, TLInstance, TLInstanceId, TLStore, TLUser, TLUserId } from '@tldraw/tlschema'
import { Store } from '@tldraw/tlstore'
import { annotateError } from '@tldraw/utils'
-import React, { useCallback, useSyncExternalStore } from 'react'
+import React, { useCallback, useEffect, useState, useSyncExternalStore } from 'react'
import { App } from './app/App'
import { EditorAssetUrls, defaultEditorAssetUrls } from './assetUrls'
import { OptionalErrorBoundary } from './components/ErrorBoundary'
@@ -28,12 +28,12 @@ import { useZoomCss } from './hooks/useZoomCss'
/** @public */
export interface TldrawEditorProps {
children?: any
+ /** A configuration defining major customizations to the app, such as custom shapes and new tools */
+ config: TldrawEditorConfig
/** Overrides for the tldraw components */
components?: Partial
/** Whether to display the dark mode. */
isDarkMode?: boolean
- /** A configuration defining major customizations to the app, such as custom shapes and new tools */
- config?: TldrawEditorConfig
/**
* Called when the app has mounted.
*
@@ -133,7 +133,7 @@ export function TldrawEditor(props: TldrawEditorProps) {
}
function TldrawEditorBeforeLoading({
- config = TldrawEditorConfig.default,
+ config,
userId,
instanceId,
store,
@@ -143,26 +143,43 @@ function TldrawEditorBeforeLoading({
props.assetUrls ?? defaultEditorAssetUrls
)
- store ??= config.createStore({
- userId: userId ?? TLUser.createId(),
- instanceId: instanceId ?? TLInstance.createId(),
+ const [_store, _setStore] = useState(() => {
+ return (
+ store ??
+ config.createStore({
+ userId: userId ?? TLUser.createId(),
+ instanceId: instanceId ?? TLInstance.createId(),
+ })
+ )
})
- let loadedStore
- if (!(store instanceof Store)) {
- if (store.error) {
+ useEffect(() => {
+ _setStore(() => {
+ return (
+ store ??
+ config.createStore({
+ userId: userId ?? TLUser.createId(),
+ instanceId: instanceId ?? TLInstance.createId(),
+ })
+ )
+ })
+ }, [store, config, userId, instanceId])
+
+ let loadedStore: TLStore | SyncedStore
+ if (!(_store instanceof Store)) {
+ if (_store.error) {
// for error handling, we fall back to the default error boundary.
// if users want to handle this error differently, they can render
// their own error screen before the TldrawEditor component
- throw store.error
+ throw _store.error
}
- if (!store.store) {
+ if (!_store.store) {
return Connecting...
}
- loadedStore = store.store
+ loadedStore = _store.store
} else {
- loadedStore = store
+ loadedStore = _store
}
if (instanceId && loadedStore.props.instanceId !== instanceId) {
@@ -209,8 +226,8 @@ function TldrawEditorAfterLoading({
React.useLayoutEffect(() => {
const app = new App({
store,
- getContainer: () => container,
config,
+ getContainer: () => container,
})
setApp(app)
diff --git a/packages/editor/src/lib/app/App.ts b/packages/editor/src/lib/app/App.ts
index 2394808ce..048a7c042 100644
--- a/packages/editor/src/lib/app/App.ts
+++ b/packages/editor/src/lib/app/App.ts
@@ -49,7 +49,6 @@ import {
TLShapeId,
TLShapePartial,
TLShapeProp,
- TLShapeType,
TLSizeStyle,
TLStore,
TLUnknownShape,
@@ -160,7 +159,7 @@ export interface AppOptions {
*/
store: TLStore
/** A configuration defining major customizations to the app, such as custom shapes and new tools */
- config?: TldrawEditorConfig
+ config: TldrawEditorConfig
/**
* Should return a containing html element which has all the styles applied to the app. If not
* given, the body element will be used.
@@ -175,14 +174,15 @@ export function isShapeWithHandles(shape: TLShape) {
/** @public */
export class App extends EventEmitter {
- constructor({ config = TldrawEditorConfig.default, store, getContainer }: AppOptions) {
+ constructor({ config, store, getContainer }: AppOptions) {
super()
- if (store.schema !== config.storeSchema) {
+ this.config = config
+
+ if (store.schema !== this.config.storeSchema) {
throw new Error('Store schema does not match schema given to App')
}
- this.config = config
this.store = store
this.getContainer = getContainer ?? (() => document.body)
@@ -191,7 +191,7 @@ export class App extends EventEmitter {
// Set the shape utils
this.shapeUtils = Object.fromEntries(
- Object.entries(config.shapeUtils).map(([type, Util]) => [type, new Util(this, type)])
+ Object.entries(this.config.shapeUtils).map(([type, Util]) => [type, new Util(this, type)])
)
if (typeof window !== 'undefined' && 'navigator' in window) {
@@ -209,7 +209,7 @@ export class App extends EventEmitter {
this.root = new RootState(this)
if (this.root.children) {
- config.tools.forEach((Ctor) => {
+ this.config.tools.forEach((Ctor) => {
this.root.children![Ctor.id] = new Ctor(this)
})
}
@@ -2864,7 +2864,7 @@ export class App extends EventEmitter {
return this
}
- getParentIdForNewShapeAtPoint(point: VecLike, shapeType: TLShapeType) {
+ getParentIdForNewShapeAtPoint(point: VecLike, shapeType: TLShape['type']) {
const shapes = this.sortedShapesArray
for (let i = shapes.length - 1; i >= 0; i--) {
diff --git a/packages/editor/src/lib/app/shapeutils/TLFrameUtil/TLFrameUtil.tsx b/packages/editor/src/lib/app/shapeutils/TLFrameUtil/TLFrameUtil.tsx
index 6f1e2b899..1b131ae27 100644
--- a/packages/editor/src/lib/app/shapeutils/TLFrameUtil/TLFrameUtil.tsx
+++ b/packages/editor/src/lib/app/shapeutils/TLFrameUtil/TLFrameUtil.tsx
@@ -1,5 +1,5 @@
import { canolicalizeRotation, SelectionEdge, toDomPrecision } from '@tldraw/primitives'
-import { TLFrameShape, TLShape, TLShapeId, TLShapeType } from '@tldraw/tlschema'
+import { TLFrameShape, TLShape, TLShapeId } from '@tldraw/tlschema'
import { last } from '@tldraw/utils'
import { SVGContainer } from '../../../components/SVGContainer'
import { defaultEmptyAs } from '../../../utils/string'
@@ -148,7 +148,7 @@ export class TLFrameUtil extends TLBoxUtil {
)
}
- override canReceiveNewChildrenOfType = (_type: TLShapeType) => {
+ override canReceiveNewChildrenOfType = (_type: TLShape['type']) => {
return true
}
diff --git a/packages/editor/src/lib/app/shapeutils/TLIconUtil/TLIconUtil.tsx b/packages/editor/src/lib/app/shapeutils/TLIconUtil/TLIconUtil.tsx
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/editor/src/lib/app/shapeutils/TLShapeUtil.ts b/packages/editor/src/lib/app/shapeutils/TLShapeUtil.ts
index 4df6e1b94..3ad5f7b11 100644
--- a/packages/editor/src/lib/app/shapeutils/TLShapeUtil.ts
+++ b/packages/editor/src/lib/app/shapeutils/TLShapeUtil.ts
@@ -5,7 +5,6 @@ import {
TLHandle,
TLShape,
TLShapePartial,
- TLShapeType,
TLUnknownShape,
Vec2dModel,
} from '@tldraw/tlschema'
@@ -308,7 +307,7 @@ export abstract class TLShapeUtil {
* @param type - The shape type.
* @public
*/
- canReceiveNewChildrenOfType(type: TLShapeType) {
+ canReceiveNewChildrenOfType(type: TLShape['type']) {
return false
}
diff --git a/packages/editor/src/lib/app/statechart/TLArrowTool/TLArrowTool.ts b/packages/editor/src/lib/app/statechart/TLArrowTool/TLArrowTool.ts
index 6333b0014..5b6527978 100644
--- a/packages/editor/src/lib/app/statechart/TLArrowTool/TLArrowTool.ts
+++ b/packages/editor/src/lib/app/statechart/TLArrowTool/TLArrowTool.ts
@@ -1,4 +1,4 @@
-import { TLShapeType, TLStyleType } from '@tldraw/tlschema'
+import { TLStyleType } from '@tldraw/tlschema'
import { StateNode } from '../StateNode'
import { Idle } from './children/Idle'
import { Pointing } from './children/Pointing'
@@ -8,7 +8,7 @@ export class TLArrowTool extends StateNode {
static initial = 'idle'
static children = () => [Idle, Pointing]
- shapeType: TLShapeType = 'arrow'
+ shapeType = 'arrow'
styles = [
'color',
diff --git a/packages/editor/src/lib/app/statechart/TLArrowTool/children/Pointing.ts b/packages/editor/src/lib/app/statechart/TLArrowTool/children/Pointing.ts
index fffb2e1f6..6ff6ab18a 100644
--- a/packages/editor/src/lib/app/statechart/TLArrowTool/children/Pointing.ts
+++ b/packages/editor/src/lib/app/statechart/TLArrowTool/children/Pointing.ts
@@ -1,4 +1,4 @@
-import { createShapeId, TLArrowShape, TLShapeType } from '@tldraw/tlschema'
+import { createShapeId, TLArrowShape } from '@tldraw/tlschema'
import { TLArrowUtil } from '../../../shapeutils/TLArrowUtil/TLArrowUtil'
import { TLEventHandlers } from '../../../types/event-types'
import { StateNode } from '../../StateNode'
@@ -7,8 +7,6 @@ import { TLArrowTool } from '../TLArrowTool'
export class Pointing extends StateNode {
static override id = 'pointing'
- shapeType = '' as TLShapeType
-
shape?: TLArrowShape
preciseTimeout = -1
@@ -33,7 +31,7 @@ export class Pointing extends StateNode {
this.didTimeout = false
- this.shapeType = (this.parent as TLArrowTool).shapeType
+ const shapeType = (this.parent as TLArrowTool).shapeType
this.app.mark('creating')
@@ -42,7 +40,7 @@ export class Pointing extends StateNode {
this.app.createShapes([
{
id,
- type: this.shapeType,
+ type: shapeType,
x: currentPagePoint.x,
y: currentPagePoint.y,
},
diff --git a/packages/editor/src/lib/app/statechart/TLLineTool/TLLineTool.ts b/packages/editor/src/lib/app/statechart/TLLineTool/TLLineTool.ts
index ecfb52a1c..6a3365054 100644
--- a/packages/editor/src/lib/app/statechart/TLLineTool/TLLineTool.ts
+++ b/packages/editor/src/lib/app/statechart/TLLineTool/TLLineTool.ts
@@ -1,4 +1,4 @@
-import { TLShapeType, TLStyleType } from '@tldraw/tlschema'
+import { TLStyleType } from '@tldraw/tlschema'
import { StateNode } from '../StateNode'
import { Idle } from './children/Idle'
@@ -9,7 +9,7 @@ export class TLLineTool extends StateNode {
static initial = 'idle'
static children = () => [Idle, Pointing]
- shapeType: TLShapeType = 'line'
+ shapeType = 'line'
styles = ['color', 'opacity', 'dash', 'size', 'spline'] as TLStyleType[]
}
diff --git a/packages/editor/src/lib/app/statechart/TLLineTool/children/Pointing.ts b/packages/editor/src/lib/app/statechart/TLLineTool/children/Pointing.ts
index 997036889..049a11e41 100644
--- a/packages/editor/src/lib/app/statechart/TLLineTool/children/Pointing.ts
+++ b/packages/editor/src/lib/app/statechart/TLLineTool/children/Pointing.ts
@@ -1,6 +1,6 @@
import { getIndexAbove, sortByIndex } from '@tldraw/indices'
import { Matrix2d, Vec2d } from '@tldraw/primitives'
-import { TLHandle, TLLineShape, TLShapeId, TLShapeType, createShapeId } from '@tldraw/tlschema'
+import { TLHandle, TLLineShape, TLShapeId, createShapeId } from '@tldraw/tlschema'
import { last, structuredClone } from '@tldraw/utils'
import { TLEventHandlers, TLInterruptEvent } from '../../../types/event-types'
import { StateNode } from '../../StateNode'
@@ -9,8 +9,6 @@ import { TLLineTool } from '../TLLineTool'
export class Pointing extends StateNode {
static override id = 'pointing'
- shapeType = '' as TLShapeType
-
shape = {} as TLLineShape
markPointId = ''
@@ -19,7 +17,6 @@ export class Pointing extends StateNode {
const { inputs } = this.app
const { currentPagePoint } = inputs
- this.shapeType = (this.parent as TLLineTool).shapeType
this.markPointId = this.app.mark('creating')
let shapeExists = false
@@ -85,7 +82,7 @@ export class Pointing extends StateNode {
this.app.createShapes([
{
id,
- type: this.shapeType,
+ type: (this.parent as TLLineTool).shapeType,
x: currentPagePoint.x,
y: currentPagePoint.y,
},
diff --git a/packages/editor/src/lib/config/TldrawEditorConfig.tsx b/packages/editor/src/lib/config/TldrawEditorConfig.tsx
index b70ecfb50..056643124 100644
--- a/packages/editor/src/lib/config/TldrawEditorConfig.tsx
+++ b/packages/editor/src/lib/config/TldrawEditorConfig.tsx
@@ -1,63 +1,19 @@
import {
CLIENT_FIXUP_SCRIPT,
- TLAsset,
- TLCamera,
TLDOCUMENT_ID,
- TLDocument,
+ TLDefaultShape,
TLInstance,
TLInstanceId,
- TLInstancePageState,
TLInstancePresence,
- TLPage,
TLRecord,
TLShape,
TLStore,
TLStoreProps,
- TLUnknownShape,
TLUser,
- TLUserDocument,
TLUserId,
- TLUserPresence,
- arrowShapeTypeMigrations,
- arrowShapeTypeValidator,
- bookmarkShapeTypeMigrations,
- bookmarkShapeTypeValidator,
- createIntegrityChecker,
- defaultDerivePresenceState,
- drawShapeTypeMigrations,
- drawShapeTypeValidator,
- embedShapeTypeMigrations,
- embedShapeTypeValidator,
- frameShapeTypeMigrations,
- frameShapeTypeValidator,
- geoShapeTypeMigrations,
- geoShapeTypeValidator,
- groupShapeTypeMigrations,
- groupShapeTypeValidator,
- imageShapeTypeMigrations,
- imageShapeTypeValidator,
- lineShapeTypeMigrations,
- lineShapeTypeValidator,
- noteShapeTypeMigrations,
- noteShapeTypeValidator,
- onValidationFailure,
- rootShapeTypeMigrations,
- storeMigrations,
- textShapeTypeMigrations,
- textShapeTypeValidator,
- videoShapeTypeMigrations,
- videoShapeTypeValidator,
+ createTLSchema,
} from '@tldraw/tlschema'
-import {
- Migrations,
- RecordType,
- Store,
- StoreSchema,
- StoreSnapshot,
- createRecordType,
- defineMigrations,
-} from '@tldraw/tlstore'
-import { T } from '@tldraw/tlvalidate'
+import { Migrations, RecordType, Store, StoreSchema, StoreSnapshot } from '@tldraw/tlstore'
import { Signal } from 'signia'
import { TLArrowUtil } from '../app/shapeutils/TLArrowUtil/TLArrowUtil'
import { TLBookmarkUtil } from '../app/shapeutils/TLBookmarkUtil/TLBookmarkUtil'
@@ -74,135 +30,69 @@ import { TLTextUtil } from '../app/shapeutils/TLTextUtil/TLTextUtil'
import { TLVideoUtil } from '../app/shapeutils/TLVideoUtil/TLVideoUtil'
import { StateNodeConstructor } from '../app/statechart/StateNode'
-/** @public */
-export type ValidatorsForShapes = Record<
- T['type'],
- { validate: (record: T) => T }
->
+// Secret shape types that don't have a shape util yet
+type ShapeTypesNotImplemented = 'icon'
-/** @public */
-export type MigrationsForShapes = Record
-
-type CustomShapeInfo = {
- util: TLShapeUtilConstructor
- validator?: { validate: (record: T) => T }
- migrations?: Migrations
+const DEFAULT_SHAPE_UTILS: {
+ [K in Exclude]: TLShapeUtilConstructor
+} = {
+ arrow: TLArrowUtil,
+ bookmark: TLBookmarkUtil,
+ draw: TLDrawUtil,
+ embed: TLEmbedUtil,
+ frame: TLFrameUtil,
+ geo: TLGeoUtil,
+ group: TLGroupUtil,
+ image: TLImageUtil,
+ line: TLLineUtil,
+ note: TLNoteUtil,
+ text: TLTextUtil,
+ video: TLVideoUtil,
}
-type UtilsForShapes = Record>
-
-type TldrawEditorConfigOptions = {
+/** @public */
+export type TldrawEditorConfigOptions = {
tools?: readonly StateNodeConstructor[]
- shapes?: { [K in T['type']]: CustomShapeInfo }
+ shapes?: Record<
+ string,
+ {
+ util: TLShapeUtilConstructor
+ validator?: { validate: (record: T) => T }
+ migrations?: Migrations
+ }
+ >
/** @internal */
derivePresenceState?: (store: TLStore) => Signal
}
/** @public */
export class TldrawEditorConfig {
- static readonly default = new TldrawEditorConfig({})
-
- readonly storeSchema: StoreSchema
- readonly TLShape: RecordType
+ // Custom tools
readonly tools: readonly StateNodeConstructor[]
// Custom shape utils
- readonly shapeUtils: UtilsForShapes
- // Validators for shape subtypes
- readonly shapeValidators: Record>
- // Migrations for shape subtypes
- readonly shapeMigrations: MigrationsForShapes
+ readonly shapeUtils: Record>
- constructor(opts: TldrawEditorConfigOptions) {
- const { shapes = [], tools = [], derivePresenceState } = opts
+ // The record used for TLShape incorporating any custom shapes
+ readonly TLShape: RecordType
+
+ // The schema used for the store incorporating any custom shapes
+ readonly storeSchema: StoreSchema
+
+ constructor(opts = {} as TldrawEditorConfigOptions) {
+ const { shapes = {}, tools = [], derivePresenceState } = opts
this.tools = tools
this.shapeUtils = {
- arrow: TLArrowUtil,
- bookmark: TLBookmarkUtil,
- draw: TLDrawUtil,
- embed: TLEmbedUtil,
- frame: TLFrameUtil,
- geo: TLGeoUtil,
- group: TLGroupUtil,
- image: TLImageUtil,
- line: TLLineUtil,
- note: TLNoteUtil,
- text: TLTextUtil,
- video: TLVideoUtil,
+ ...DEFAULT_SHAPE_UTILS,
+ ...Object.fromEntries(Object.entries(shapes).map(([k, v]) => [k, v.util])),
}
- this.shapeMigrations = {
- arrow: arrowShapeTypeMigrations,
- bookmark: bookmarkShapeTypeMigrations,
- draw: drawShapeTypeMigrations,
- embed: embedShapeTypeMigrations,
- frame: frameShapeTypeMigrations,
- geo: geoShapeTypeMigrations,
- group: groupShapeTypeMigrations,
- image: imageShapeTypeMigrations,
- line: lineShapeTypeMigrations,
- note: noteShapeTypeMigrations,
- text: textShapeTypeMigrations,
- video: videoShapeTypeMigrations,
- }
-
- this.shapeValidators = {
- arrow: arrowShapeTypeValidator,
- bookmark: bookmarkShapeTypeValidator,
- draw: drawShapeTypeValidator,
- embed: embedShapeTypeValidator,
- frame: frameShapeTypeValidator,
- geo: geoShapeTypeValidator,
- group: groupShapeTypeValidator,
- image: imageShapeTypeValidator,
- line: lineShapeTypeValidator,
- note: noteShapeTypeValidator,
- text: textShapeTypeValidator,
- video: videoShapeTypeValidator,
- }
-
- // Add custom shapes
- for (const [type, shape] of Object.entries(shapes)) {
- this.shapeUtils[type] = shape.util
- this.shapeMigrations[type] = shape.migrations ?? defineMigrations({})
- this.shapeValidators[type] = (shape.validator ?? T.any) as T.Validator
- }
-
- const shapeRecord = createRecordType('shape', {
- migrations: defineMigrations({
- currentVersion: rootShapeTypeMigrations.currentVersion,
- firstVersion: rootShapeTypeMigrations.firstVersion,
- migrators: rootShapeTypeMigrations.migrators,
- subTypeKey: 'type',
- subTypeMigrations: this.shapeMigrations,
- }),
- validator: T.model('shape', T.union('type', { ...this.shapeValidators })),
- scope: 'document',
- }).withDefaultProperties(() => ({ x: 0, y: 0, rotation: 0, isLocked: false }))
-
- this.storeSchema = StoreSchema.create(
- {
- asset: TLAsset,
- camera: TLCamera,
- document: TLDocument,
- instance: TLInstance,
- instance_page_state: TLInstancePageState,
- page: TLPage,
- shape: shapeRecord,
- user: TLUser,
- user_document: TLUserDocument,
- user_presence: TLUserPresence,
- instance_presence: TLInstancePresence,
- },
- {
- snapshotMigrations: storeMigrations,
- onValidationFailure,
- createIntegrityChecker: createIntegrityChecker,
- derivePresenceState: derivePresenceState ?? defaultDerivePresenceState,
- }
- )
+ this.storeSchema = createTLSchema({
+ customShapes: shapes,
+ derivePresenceState: derivePresenceState,
+ })
this.TLShape = this.storeSchema.types.shape as RecordType<
TLShape,
diff --git a/packages/editor/src/lib/test/TestApp.test.ts b/packages/editor/src/lib/test/TestApp.test.ts
new file mode 100644
index 000000000..c60a0a4c1
--- /dev/null
+++ b/packages/editor/src/lib/test/TestApp.test.ts
@@ -0,0 +1,7 @@
+import { TestApp } from './TestApp'
+
+it('loads the test app', () => {
+ expect(() => {
+ new TestApp()
+ }).not.toThrow()
+})
diff --git a/packages/editor/src/lib/test/TestApp.ts b/packages/editor/src/lib/test/TestApp.ts
index 6f9d17404..1e12fca62 100644
--- a/packages/editor/src/lib/test/TestApp.ts
+++ b/packages/editor/src/lib/test/TestApp.ts
@@ -55,11 +55,13 @@ export const TEST_INSTANCE_ID = TLInstance.createCustomId('testInstance1')
export const TEST_USER_ID = TLUser.createCustomId('testUser1')
export class TestApp extends App {
- constructor(options = {} as Partial) {
+ constructor(options = {} as Partial>) {
const elm = document.createElement('div')
elm.tabIndex = 0
+ const config = options.config ?? new TldrawEditorConfig()
super({
- store: (options.config ?? TldrawEditorConfig.default).createStore({
+ config,
+ store: config.createStore({
userId: TEST_USER_ID,
instanceId: TEST_INSTANCE_ID,
}),
diff --git a/packages/editor/src/lib/test/TldrawEditor.test.tsx b/packages/editor/src/lib/test/TldrawEditor.test.tsx
index 35d30aba6..4af352bb4 100644
--- a/packages/editor/src/lib/test/TldrawEditor.test.tsx
+++ b/packages/editor/src/lib/test/TldrawEditor.test.tsx
@@ -21,14 +21,17 @@ afterEach(() => {
describe('', () => {
it('Accepts fresh versions of store and calls `onMount` for each one', async () => {
- const initialStore = TldrawEditorConfig.default.createStore({
+ const config = new TldrawEditorConfig()
+
+ const initialStore = config.createStore({
instanceId: TLInstance.createCustomId('test'),
userId: TLUser.createCustomId('test'),
})
+
const onMount = jest.fn()
const rendered = render(
-
+
)
@@ -40,7 +43,7 @@ describe('', () => {
// re-render with the same store:
rendered.rerender(
-
+
)
@@ -49,12 +52,12 @@ describe('', () => {
expect(onMount).toHaveBeenCalledTimes(1)
// re-render with a new store:
- const newStore = TldrawEditorConfig.default.createStore({
+ const newStore = config.createStore({
instanceId: TLInstance.createCustomId('test'),
userId: TLUser.createCustomId('test'),
})
rendered.rerender(
-
+
)
diff --git a/packages/editor/src/lib/utils/buildFromV1Document.ts b/packages/editor/src/lib/utils/buildFromV1Document.ts
index 702bb79a6..bc2c44e51 100644
--- a/packages/editor/src/lib/utils/buildFromV1Document.ts
+++ b/packages/editor/src/lib/utils/buildFromV1Document.ts
@@ -1076,19 +1076,6 @@ export interface LegacyTldrawDocument {
/* ------------------ Translations ------------------ */
-// const v1ShapeTypesToV2ShapeTypes: Record = {
-// [TDShapeType.Rectangle]: 'geo',
-// [TDShapeType.Ellipse]: 'geo',
-// [TDShapeType.Text]: 'text',
-// [TDShapeType.Image]: 'image',
-// [TDShapeType.Video]: 'video',
-// [TDShapeType.Group]: 'group',
-// [TDShapeType.Arrow]: 'arrow',
-// [TDShapeType.Sticky]: 'note',
-// [TDShapeType.Draw]: 'draw',
-// [TDShapeType.Triangle]: 'geo',
-// }
-
const v1ColorsToV2Colors: Record = {
[ColorStyle.White]: 'black',
[ColorStyle.Black]: 'black',
diff --git a/packages/file-format/src/lib/file.ts b/packages/file-format/src/lib/file.ts
index ad774be26..cfc96e4d4 100644
--- a/packages/file-format/src/lib/file.ts
+++ b/packages/file-format/src/lib/file.ts
@@ -208,7 +208,7 @@ export async function parseAndLoadDocument(
forceDarkMode?: boolean
) {
const parseFileResult = parseTldrawJsonFile({
- config: TldrawEditorConfig.default,
+ config: new TldrawEditorConfig(),
json: document,
instanceId: app.instanceId,
userId: app.userId,
diff --git a/packages/file-format/src/test/file.test.ts b/packages/file-format/src/test/file.test.ts
index 85c0d5c89..ef6fc06b3 100644
--- a/packages/file-format/src/test/file.test.ts
+++ b/packages/file-format/src/test/file.test.ts
@@ -17,14 +17,14 @@ function serialize(file: TldrawFile): string {
describe('parseTldrawJsonFile', () => {
it('returns an error if the file is not json', () => {
- const result = parseTldrawJsonFile(TldrawEditorConfig.default, 'not json')
+ const result = parseTldrawJsonFile(new TldrawEditorConfig(), 'not json')
assert(!result.ok)
expect(result.error.type).toBe('notATldrawFile')
})
it('returns an error if the file doesnt look like a tldraw file', () => {
const result = parseTldrawJsonFile(
- TldrawEditorConfig.default,
+ new TldrawEditorConfig(),
JSON.stringify({ not: 'a tldraw file' })
)
assert(!result.ok)
@@ -33,10 +33,10 @@ describe('parseTldrawJsonFile', () => {
it('returns an error if the file version is too old', () => {
const result = parseTldrawJsonFile(
- TldrawEditorConfig.default,
+ new TldrawEditorConfig(),
serialize({
tldrawFileFormatVersion: 0,
- schema: TldrawEditorConfig.default.storeSchema.serialize(),
+ schema: new TldrawEditorConfig().storeSchema.serialize(),
records: [],
})
)
@@ -46,10 +46,10 @@ describe('parseTldrawJsonFile', () => {
it('returns an error if the file version is too new', () => {
const result = parseTldrawJsonFile(
- TldrawEditorConfig.default,
+ new TldrawEditorConfig(),
serialize({
tldrawFileFormatVersion: 100,
- schema: TldrawEditorConfig.default.storeSchema.serialize(),
+ schema: new TldrawEditorConfig().storeSchema.serialize(),
records: [],
})
)
@@ -58,10 +58,10 @@ describe('parseTldrawJsonFile', () => {
})
it('returns an error if migrations fail', () => {
- const serializedSchema = TldrawEditorConfig.default.storeSchema.serialize()
+ const serializedSchema = new TldrawEditorConfig().storeSchema.serialize()
serializedSchema.storeVersion = 100
const result = parseTldrawJsonFile(
- TldrawEditorConfig.default,
+ new TldrawEditorConfig(),
serialize({
tldrawFileFormatVersion: 1,
schema: serializedSchema,
@@ -72,10 +72,10 @@ describe('parseTldrawJsonFile', () => {
assert(result.error.type === 'migrationFailed')
expect(result.error.reason).toBe(MigrationFailureReason.TargetVersionTooOld)
- const serializedSchema2 = TldrawEditorConfig.default.storeSchema.serialize()
+ const serializedSchema2 = new TldrawEditorConfig().storeSchema.serialize()
serializedSchema2.recordVersions.shape.version = 100
const result2 = parseTldrawJsonFile(
- TldrawEditorConfig.default,
+ new TldrawEditorConfig(),
serialize({
tldrawFileFormatVersion: 1,
schema: serializedSchema2,
@@ -90,10 +90,10 @@ describe('parseTldrawJsonFile', () => {
it('returns an error if a record is invalid', () => {
const result = parseTldrawJsonFile(
- TldrawEditorConfig.default,
+ new TldrawEditorConfig(),
serialize({
tldrawFileFormatVersion: 1,
- schema: TldrawEditorConfig.default.storeSchema.serialize(),
+ schema: new TldrawEditorConfig().storeSchema.serialize(),
records: [
{
typeName: 'shape',
@@ -113,10 +113,10 @@ describe('parseTldrawJsonFile', () => {
it('returns a store if the file is valid', () => {
const result = parseTldrawJsonFile(
- TldrawEditorConfig.default,
+ new TldrawEditorConfig(),
serialize({
tldrawFileFormatVersion: 1,
- schema: TldrawEditorConfig.default.storeSchema.serialize(),
+ schema: new TldrawEditorConfig().storeSchema.serialize(),
records: [],
})
)
diff --git a/packages/tldraw/api-report.md b/packages/tldraw/api-report.md
index 3625195a4..90a6c20aa 100644
--- a/packages/tldraw/api-report.md
+++ b/packages/tldraw/api-report.md
@@ -8,9 +8,10 @@ import { TldrawEditorProps } from '@tldraw/editor';
import { TldrawUiContextProviderProps } from '@tldraw/ui';
// @public (undocumented)
-export function Tldraw(props: Omit & TldrawUiContextProviderProps & {
+export function Tldraw(props: Omit & TldrawUiContextProviderProps & {
persistenceKey?: string;
hideUi?: boolean;
+ config?: TldrawEditorProps['config'];
}): JSX.Element;
diff --git a/packages/tldraw/src/lib/Tldraw.tsx b/packages/tldraw/src/lib/Tldraw.tsx
index 8697f3585..06f59b7db 100644
--- a/packages/tldraw/src/lib/Tldraw.tsx
+++ b/packages/tldraw/src/lib/Tldraw.tsx
@@ -1,4 +1,4 @@
-import { Canvas, TldrawEditor, TldrawEditorProps } from '@tldraw/editor'
+import { Canvas, TldrawEditor, TldrawEditorConfig, TldrawEditorProps } from '@tldraw/editor'
import {
DEFAULT_DOCUMENT_NAME,
TAB_ID,
@@ -6,18 +6,33 @@ import {
useLocalSyncClient,
} from '@tldraw/tlsync-client'
import { ContextMenu, TldrawUi, TldrawUiContextProviderProps } from '@tldraw/ui'
+import { useEffect, useState } from 'react'
/** @public */
export function Tldraw(
- props: Omit &
+ props: Omit &
TldrawUiContextProviderProps & {
/** The key under which to persist this editor's data to local storage. */
persistenceKey?: string
/** Whether to hide the user interface and only display the canvas. */
hideUi?: boolean
+ /** A custom configuration for this Tldraw editor */
+ config?: TldrawEditorProps['config']
}
) {
- const { children, persistenceKey = DEFAULT_DOCUMENT_NAME, instanceId = TAB_ID, ...rest } = props
+ const {
+ config,
+ children,
+ persistenceKey = DEFAULT_DOCUMENT_NAME,
+ instanceId = TAB_ID,
+ ...rest
+ } = props
+
+ const [_config, _setConfig] = useState(() => config ?? new TldrawEditorConfig())
+
+ useEffect(() => {
+ _setConfig(config ?? new TldrawEditorConfig())
+ }, [config])
const userData = getUserData()
@@ -25,13 +40,19 @@ export function Tldraw(
const syncedStore = useLocalSyncClient({
instanceId,
- userId: userId,
+ userId,
+ config: _config,
universalPersistenceKey: persistenceKey,
- config: props.config,
})
return (
-
+
diff --git a/packages/tlschema/api-report.md b/packages/tlschema/api-report.md
index 5ee8b2e5c..4395a9c02 100644
--- a/packages/tlschema/api-report.md
+++ b/packages/tlschema/api-report.md
@@ -103,6 +103,12 @@ export function createShapeValidator(
props: Props;
}>;
+// @public
+export function createTLSchema(opts?: {
+ customShapes?: { [K in T["type"]]: CustomShapeInfo; } | undefined;
+ derivePresenceState?: ((store: TLStore) => Signal) | undefined;
+}): StoreSchema;
+
// @public (undocumented)
export const cursorTypeValidator: T.Validator;
@@ -722,6 +728,9 @@ export interface TLDashStyle extends TLBaseStyle {
// @public (undocumented)
export type TLDashType = SetValue;
+// @public
+export type TLDefaultShape = TLArrowShape | TLBookmarkShape | TLDrawShape | TLEmbedShape | TLFrameShape | TLGeoShape | TLGroupShape | TLIconShape | TLImageShape | TLLineShape | TLNoteShape | TLTextShape | TLVideoShape;
+
// @public
export interface TLDocument extends BaseRecord<'document'> {
// (undocumented)
@@ -1137,7 +1146,7 @@ export type TLScribble = {
};
// @public
-export type TLShape = TLArrowShape | TLBookmarkShape | TLDrawShape | TLEmbedShape | TLFrameShape | TLGeoShape | TLGroupShape | TLIconShape | TLImageShape | TLLineShape | TLNoteShape | TLTextShape | TLUnknownShape | TLVideoShape;
+export type TLShape = TLDefaultShape | TLUnknownShape;
// @public (undocumented)
export type TLShapeId = ID>;
@@ -1155,9 +1164,6 @@ export type TLShapeProp = keyof TLShapeProps;
// @public (undocumented)
export type TLShapeProps = SmooshedUnionObject;
-// @public (undocumented)
-export type TLShapeType = TLShape['type'];
-
// @public (undocumented)
export interface TLSizeStyle extends TLBaseStyle {
// (undocumented)
@@ -1252,7 +1258,7 @@ export type TLTextShapeProps = {
// @public (undocumented)
export type TLUiColorType = SetValue;
-// @public (undocumented)
+// @public
export type TLUnknownShape = TLBaseShape;
// @public
diff --git a/packages/tlschema/src/createTLSchema.ts b/packages/tlschema/src/createTLSchema.ts
new file mode 100644
index 000000000..564f6bbb7
--- /dev/null
+++ b/packages/tlschema/src/createTLSchema.ts
@@ -0,0 +1,134 @@
+import { Migrations, StoreSchema, createRecordType, defineMigrations } from '@tldraw/tlstore'
+import { T } from '@tldraw/tlvalidate'
+import { Signal } from 'signia'
+import { TLRecord } from './TLRecord'
+import { TLStore, TLStoreProps, createIntegrityChecker, onValidationFailure } from './TLStore'
+import { defaultDerivePresenceState } from './defaultDerivePresenceState'
+import { TLAsset } from './records/TLAsset'
+import { TLCamera } from './records/TLCamera'
+import { TLDocument } from './records/TLDocument'
+import { TLInstance } from './records/TLInstance'
+import { TLInstancePageState } from './records/TLInstancePageState'
+import { TLInstancePresence } from './records/TLInstancePresence'
+import { TLPage } from './records/TLPage'
+import { TLShape, TLUnknownShape, rootShapeTypeMigrations } from './records/TLShape'
+import { TLUser } from './records/TLUser'
+import { TLUserDocument } from './records/TLUserDocument'
+import { TLUserPresence } from './records/TLUserPresence'
+import { storeMigrations } from './schema'
+import { arrowShapeTypeMigrations, arrowShapeTypeValidator } from './shapes/TLArrowShape'
+import { bookmarkShapeTypeMigrations, bookmarkShapeTypeValidator } from './shapes/TLBookmarkShape'
+import { drawShapeTypeMigrations, drawShapeTypeValidator } from './shapes/TLDrawShape'
+import { embedShapeTypeMigrations, embedShapeTypeValidator } from './shapes/TLEmbedShape'
+import { frameShapeTypeMigrations, frameShapeTypeValidator } from './shapes/TLFrameShape'
+import { geoShapeTypeMigrations, geoShapeTypeValidator } from './shapes/TLGeoShape'
+import { groupShapeTypeMigrations, groupShapeTypeValidator } from './shapes/TLGroupShape'
+import { imageShapeTypeMigrations, imageShapeTypeValidator } from './shapes/TLImageShape'
+import { lineShapeTypeMigrations, lineShapeTypeValidator } from './shapes/TLLineShape'
+import { noteShapeTypeMigrations, noteShapeTypeValidator } from './shapes/TLNoteShape'
+import { textShapeTypeMigrations, textShapeTypeValidator } from './shapes/TLTextShape'
+import { videoShapeTypeMigrations, videoShapeTypeValidator } from './shapes/TLVideoShape'
+
+type DefaultShapeInfo = {
+ validator: T.Validator
+ migrations: Migrations
+}
+
+const DEFAULT_SHAPES: { [K in TLShape['type']]: DefaultShapeInfo> } =
+ {
+ arrow: { migrations: arrowShapeTypeMigrations, validator: arrowShapeTypeValidator },
+ bookmark: { migrations: bookmarkShapeTypeMigrations, validator: bookmarkShapeTypeValidator },
+ draw: { migrations: drawShapeTypeMigrations, validator: drawShapeTypeValidator },
+ embed: { migrations: embedShapeTypeMigrations, validator: embedShapeTypeValidator },
+ frame: { migrations: frameShapeTypeMigrations, validator: frameShapeTypeValidator },
+ geo: { migrations: geoShapeTypeMigrations, validator: geoShapeTypeValidator },
+ group: { migrations: groupShapeTypeMigrations, validator: groupShapeTypeValidator },
+ image: { migrations: imageShapeTypeMigrations, validator: imageShapeTypeValidator },
+ line: { migrations: lineShapeTypeMigrations, validator: lineShapeTypeValidator },
+ note: { migrations: noteShapeTypeMigrations, validator: noteShapeTypeValidator },
+ text: { migrations: textShapeTypeMigrations, validator: textShapeTypeValidator },
+ video: { migrations: videoShapeTypeMigrations, validator: videoShapeTypeValidator },
+ }
+
+type CustomShapeInfo = {
+ validator?: { validate: (record: T) => T }
+ migrations?: Migrations
+}
+
+/**
+ * Create a store schema for a tldraw store that includes all the default shapes together with any custom shapes.
+ * @public */
+export function createTLSchema(
+ opts = {} as {
+ customShapes?: { [K in T['type']]: CustomShapeInfo }
+ derivePresenceState?: (store: TLStore) => Signal
+ }
+) {
+ const { customShapes = {}, derivePresenceState } = opts
+
+ const defaultShapeSubTypeEntries = Object.entries(DEFAULT_SHAPES) as [
+ TLShape['type'],
+ DefaultShapeInfo
+ ][]
+
+ const customShapeSubTypeEntries = Object.entries(customShapes) as [
+ T['type'],
+ CustomShapeInfo
+ ][]
+
+ // Create a shape record that incorporates the defeault shapes and any custom shapes
+ // into its subtype migrations and validators, so that we can migrate any new custom
+ // subtypes. Note that migrations AND validators for custom shapes are optional. If
+ // not provided, we use an empty migrations set and/or an "any" validator.
+
+ const shapeSubTypeMigrationsWithCustomSubTypeMigrations = {
+ ...Object.fromEntries(defaultShapeSubTypeEntries.map(([k, v]) => [k, v.migrations])),
+ ...Object.fromEntries(
+ customShapeSubTypeEntries.map(([k, v]) => [k, v.migrations ?? defineMigrations({})])
+ ),
+ }
+
+ const validatorWithCustomShapeValidators = T.model(
+ 'shape',
+ T.union('type', {
+ ...Object.fromEntries(defaultShapeSubTypeEntries.map(([k, v]) => [k, v.validator])),
+ ...Object.fromEntries(
+ customShapeSubTypeEntries.map(([k, v]) => [k, (v.validator as T.Validator) ?? T.any])
+ ),
+ })
+ )
+
+ const shapeRecord = createRecordType('shape', {
+ migrations: defineMigrations({
+ currentVersion: rootShapeTypeMigrations.currentVersion,
+ firstVersion: rootShapeTypeMigrations.firstVersion,
+ migrators: rootShapeTypeMigrations.migrators,
+ subTypeKey: 'type',
+ subTypeMigrations: shapeSubTypeMigrationsWithCustomSubTypeMigrations,
+ }),
+ validator: validatorWithCustomShapeValidators,
+ scope: 'document',
+ }).withDefaultProperties(() => ({ x: 0, y: 0, rotation: 0, isLocked: false }))
+
+ return StoreSchema.create(
+ {
+ asset: TLAsset,
+ camera: TLCamera,
+ document: TLDocument,
+ instance: TLInstance,
+ instance_page_state: TLInstancePageState,
+ page: TLPage,
+ shape: shapeRecord,
+ user: TLUser,
+ user_document: TLUserDocument,
+ user_presence: TLUserPresence,
+ instance_presence: TLInstancePresence,
+ },
+ {
+ snapshotMigrations: storeMigrations,
+ onValidationFailure,
+ createIntegrityChecker: createIntegrityChecker,
+ derivePresenceState: derivePresenceState ?? defaultDerivePresenceState,
+ }
+ )
+}
diff --git a/packages/tlschema/src/index.ts b/packages/tlschema/src/index.ts
index b21a865d0..ccd19d9ba 100644
--- a/packages/tlschema/src/index.ts
+++ b/packages/tlschema/src/index.ts
@@ -24,6 +24,7 @@ export {
type TLVideoAsset,
} from './assets/TLVideoAsset'
export { createAssetValidator, type TLBaseAsset } from './assets/asset-validation'
+export { createTLSchema } from './createTLSchema'
export { defaultDerivePresenceState } from './defaultDerivePresenceState'
export { CLIENT_FIXUP_SCRIPT, fixupRecord } from './fixup'
export { type Box2dModel, type Vec2dModel } from './geometry-types'
@@ -58,6 +59,7 @@ export {
isShape,
isShapeId,
rootShapeTypeMigrations,
+ type TLDefaultShape,
type TLNullableShapeProps,
type TLParentId,
type TLShape,
@@ -65,7 +67,6 @@ export {
type TLShapePartial,
type TLShapeProp,
type TLShapeProps,
- type TLShapeType,
type TLUnknownShape,
} from './records/TLShape'
export { TLUser, userTypeValidator, type TLUserId } from './records/TLUser'
diff --git a/packages/tlschema/src/records/TLShape.ts b/packages/tlschema/src/records/TLShape.ts
index 8f8847ee9..c933555fb 100644
--- a/packages/tlschema/src/records/TLShape.ts
+++ b/packages/tlschema/src/records/TLShape.ts
@@ -17,15 +17,11 @@ import { TLVideoShape } from '../shapes/TLVideoShape'
import { SmooshedUnionObject } from '../util-types'
import { TLPageId } from './TLPage'
-/** @public */
-export type TLUnknownShape = TLBaseShape
-
/**
- * TLShape
+ * The default set of shapes that are available in the editor.
*
- * @public
- */
-export type TLShape =
+ * @public */
+export type TLDefaultShape =
| TLArrowShape
| TLBookmarkShape
| TLDrawShape
@@ -38,11 +34,21 @@ export type TLShape =
| TLNoteShape
| TLTextShape
| TLVideoShape
- | TLUnknownShape
| TLIconShape
-/** @public */
-export type TLShapeType = TLShape['type']
+/**
+ * A type for a shape that is available in the editor but whose type is
+ * unknown—either one of the editor's default shapes or else a custom shape.
+ *
+ * @public */
+export type TLUnknownShape = TLBaseShape
+
+/**
+ * The set of all shapes that are available in the editor, including unknown shapes.
+ *
+ * @public
+ */
+export type TLShape = TLDefaultShape | TLUnknownShape
/** @public */
export type TLShapePartial = T extends T
diff --git a/packages/tlsync-client/api-report.md b/packages/tlsync-client/api-report.md
index 59c22fa2c..d3c953fe2 100644
--- a/packages/tlsync-client/api-report.md
+++ b/packages/tlsync-client/api-report.md
@@ -101,7 +101,7 @@ export function useLocalSyncClient({ universalPersistenceKey, instanceId, userId
universalPersistenceKey: string;
instanceId: TLInstanceId;
userId: TLUserId;
- config?: TldrawEditorConfig;
+ config: TldrawEditorConfig;
}): SyncedStore;
// (No @packageDocumentation comment for this package)
diff --git a/packages/tlsync-client/src/lib/TLLocalSyncClient.test.ts b/packages/tlsync-client/src/lib/TLLocalSyncClient.test.ts
index b03c2a205..726bedfb1 100644
--- a/packages/tlsync-client/src/lib/TLLocalSyncClient.test.ts
+++ b/packages/tlsync-client/src/lib/TLLocalSyncClient.test.ts
@@ -34,7 +34,7 @@ function testClient(
userId: TLUserId = TLUser.createCustomId('test'),
channel = new BroadcastChannelMock('test')
) {
- const store = TldrawEditorConfig.default.createStore({
+ const store = new TldrawEditorConfig().createStore({
userId,
instanceId,
})
diff --git a/packages/tlsync-client/src/lib/hooks/useLocalSyncClient.ts b/packages/tlsync-client/src/lib/hooks/useLocalSyncClient.ts
index 7775fff77..aeab97120 100644
--- a/packages/tlsync-client/src/lib/hooks/useLocalSyncClient.ts
+++ b/packages/tlsync-client/src/lib/hooks/useLocalSyncClient.ts
@@ -14,12 +14,12 @@ export function useLocalSyncClient({
universalPersistenceKey,
instanceId,
userId,
- config = TldrawEditorConfig.default,
+ config,
}: {
universalPersistenceKey: string
instanceId: TLInstanceId
userId: TLUserId
- config?: TldrawEditorConfig
+ config: TldrawEditorConfig
}): SyncedStore {
const [state, setState] = useState<{ id: string; syncedStore: SyncedStore } | null>(null)