,
},
{
- path: '/custom',
+ path: '/custom-config',
element:
,
},
{
@@ -92,8 +95,8 @@ export const allExamples: Example[] = [
element:
,
},
{
- path: '/e2e',
- element:
,
+ path: '/end-to-end',
+ element:
,
},
]
diff --git a/apps/vscode/editor/package.json b/apps/vscode/editor/package.json
index 109c03f6d..6935727e6 100644
--- a/apps/vscode/editor/package.json
+++ b/apps/vscode/editor/package.json
@@ -37,7 +37,6 @@
"@tldraw/editor": "workspace:*",
"@tldraw/file-format": "workspace:*",
"@tldraw/tldraw": "workspace:*",
- "@tldraw/tlsync-client": "workspace:*",
"@tldraw/ui": "workspace:*",
"@tldraw/utils": "workspace:*",
"@types/fs-extra": "^11.0.1",
diff --git a/apps/vscode/editor/src/ChangeResponder.tsx b/apps/vscode/editor/src/ChangeResponder.tsx
index 53c09a279..34f0bfb8b 100644
--- a/apps/vscode/editor/src/ChangeResponder.tsx
+++ b/apps/vscode/editor/src/ChangeResponder.tsx
@@ -1,4 +1,4 @@
-import { SyncedStore, TLInstanceId, useApp } from '@tldraw/editor'
+import { useApp } from '@tldraw/editor'
import { parseAndLoadDocument, serializeTldrawJson } from '@tldraw/file-format'
import { useDefaultHelpers } from '@tldraw/ui'
import { debounce } from '@tldraw/utils'
@@ -9,13 +9,7 @@ import { vscode } from './utils/vscode'
// @ts-ignore
import type { VscodeMessage } from '../../messages'
-export const ChangeResponder = ({
- syncedStore,
- instanceId,
-}: {
- syncedStore: SyncedStore
- instanceId: TLInstanceId
-}) => {
+export const ChangeResponder = () => {
const app = useApp()
const { addToast, clearToasts, msg } = useDefaultHelpers()
@@ -44,19 +38,17 @@ export const ChangeResponder = ({
clearToasts()
window.removeEventListener('message', handleMessage)
}
- }, [app, instanceId, msg, addToast, clearToasts])
+ }, [app, msg, addToast, clearToasts])
React.useEffect(() => {
// When the history changes, send the new file contents to VSCode
const handleChange = debounce(async () => {
- if (syncedStore.store) {
- vscode.postMessage({
- type: 'vscode:editor-updated',
- data: {
- fileContents: await serializeTldrawJson(syncedStore.store),
- },
- })
- }
+ vscode.postMessage({
+ type: 'vscode:editor-updated',
+ data: {
+ fileContents: await serializeTldrawJson(app.store),
+ },
+ })
}, 250)
vscode.postMessage({
@@ -69,7 +61,7 @@ export const ChangeResponder = ({
handleChange()
app.off('change-history', handleChange)
}
- }, [app, syncedStore, instanceId])
+ }, [app])
return null
}
diff --git a/apps/vscode/editor/src/FileOpen.tsx b/apps/vscode/editor/src/FileOpen.tsx
index 17ad6d0e0..850c958d8 100644
--- a/apps/vscode/editor/src/FileOpen.tsx
+++ b/apps/vscode/editor/src/FileOpen.tsx
@@ -1,4 +1,4 @@
-import { TLInstanceId, useApp } from '@tldraw/editor'
+import { useApp } from '@tldraw/editor'
import { parseAndLoadDocument } from '@tldraw/file-format'
import { useDefaultHelpers } from '@tldraw/ui'
import React from 'react'
@@ -6,10 +6,8 @@ import { vscode } from './utils/vscode'
export function FileOpen({
fileContents,
- instanceId,
forceDarkMode,
}: {
- instanceId: TLInstanceId
fileContents: string
forceDarkMode: boolean
}) {
@@ -42,7 +40,7 @@ export function FileOpen({
return () => {
clearToasts()
}
- }, [fileContents, app, instanceId, addToast, msg, clearToasts, forceDarkMode, isFileLoaded])
+ }, [fileContents, app, addToast, msg, clearToasts, forceDarkMode, isFileLoaded])
return null
}
diff --git a/apps/vscode/editor/src/app.tsx b/apps/vscode/editor/src/app.tsx
index 8c67abcdb..69f11b628 100644
--- a/apps/vscode/editor/src/app.tsx
+++ b/apps/vscode/editor/src/app.tsx
@@ -2,19 +2,18 @@ import {
App,
Canvas,
ErrorBoundary,
- setRuntimeOverrides,
+ TAB_ID,
TldrawEditor,
- TldrawEditorConfig,
+ setRuntimeOverrides,
} from '@tldraw/editor'
import { linksUiOverrides } from './utils/links'
// eslint-disable-next-line import/no-internal-modules
import '@tldraw/editor/editor.css'
-import { TAB_ID, useLocalSyncClient } from '@tldraw/tlsync-client'
import { ContextMenu, MenuSchema, TldrawUi } from '@tldraw/ui'
// eslint-disable-next-line import/no-internal-modules
-import { getAssetUrlsByImport } from '@tldraw/assets/imports'
-// eslint-disable-next-line import/no-internal-modules
import '@tldraw/ui/ui.css'
+// eslint-disable-next-line import/no-internal-modules
+import { getAssetUrlsByImport } from '@tldraw/assets/imports'
import { useEffect, useMemo, useState } from 'react'
import { VscodeMessage } from '../../messages'
import '../public/index.css'
@@ -24,10 +23,6 @@ import { FullPageMessage } from './FullPageMessage'
import { onCreateBookmarkFromUrl } from './utils/bookmarks'
import { vscode } from './utils/vscode'
-const config = new TldrawEditorConfig()
-
-// @ts-ignore
-
setRuntimeOverrides({
openWindow: (url, target) => {
vscode.postMessage({
@@ -97,7 +92,6 @@ export const TldrawWrapper = () => {
fileContents: message.data.fileContents,
uri: message.data.uri,
isDarkMode: message.data.isDarkMode,
- config,
})
// We only want to listen for this message once
window.removeEventListener('message', handleMessage)
@@ -127,32 +121,23 @@ export type TLDrawInnerProps = {
fileContents: string
uri: string
isDarkMode: boolean
- config: TldrawEditorConfig
}
-function TldrawInner({ uri, config, assetSrc, isDarkMode, fileContents }: TLDrawInnerProps) {
- const instanceId = TAB_ID
- const syncedStore = useLocalSyncClient({
- universalPersistenceKey: uri,
- instanceId,
- config,
- })
-
+function TldrawInner({ uri, assetSrc, isDarkMode, fileContents }: TLDrawInnerProps) {
const assetUrls = useMemo(() => getAssetUrlsByImport({ baseUrl: assetSrc }), [assetSrc])
return (
{/* */}
-
-
+
+
diff --git a/apps/vscode/editor/tsconfig.json b/apps/vscode/editor/tsconfig.json
index 815996187..cb90a5bb6 100644
--- a/apps/vscode/editor/tsconfig.json
+++ b/apps/vscode/editor/tsconfig.json
@@ -29,7 +29,6 @@
{ "path": "../../../packages/file-format" },
{ "path": "../../../packages/ui" },
{ "path": "../../../packages/editor" },
- { "path": "../../../packages/tlsync-client" },
{ "path": "../../../packages/utils" }
]
}
diff --git a/apps/vscode/extension/package.json b/apps/vscode/extension/package.json
index e2aa366c4..6a22cf1cf 100644
--- a/apps/vscode/extension/package.json
+++ b/apps/vscode/extension/package.json
@@ -124,7 +124,6 @@
"scripts": {
"dev": "tsx scripts/dev.ts",
"build": "cd ../editor && yarn build && cd ../extension && tsx scripts/build.ts",
- "web": "vscode-test-web --browserType=chromium --extensionDevelopmentPath=.",
"package": "yarn build && tsx scripts/package.ts",
"publish": "vsce publish",
"lint": "yarn run -T tsx ../../../scripts/lint.ts",
diff --git a/apps/vscode/extension/src/file.ts b/apps/vscode/extension/src/file.ts
index eac3650e8..716b8d0f3 100644
--- a/apps/vscode/extension/src/file.ts
+++ b/apps/vscode/extension/src/file.ts
@@ -1,16 +1,16 @@
-import { TldrawEditorConfig } from '@tldraw/editor'
+import { createTLSchema } from '@tldraw/editor'
import { TldrawFile } from '@tldraw/file-format'
import * as vscode from 'vscode'
export const defaultFileContents: TldrawFile = {
tldrawFileFormatVersion: 1,
- schema: new TldrawEditorConfig().storeSchema.serialize(),
+ schema: createTLSchema().serialize(),
records: [],
}
export const fileContentWithErrors: TldrawFile = {
tldrawFileFormatVersion: 1,
- schema: new TldrawEditorConfig().storeSchema.serialize(),
+ schema: createTLSchema().serialize(),
records: [{ typeName: 'shape', id: null } as any],
}
diff --git a/packages/editor/api-report.md b/packages/editor/api-report.md
index 51fc7df58..a923c6817 100644
--- a/packages/editor/api-report.md
+++ b/packages/editor/api-report.md
@@ -29,9 +29,8 @@ import { Matrix2d } from '@tldraw/primitives';
import { Matrix2dModel } from '@tldraw/primitives';
import { Migrations } from '@tldraw/tlstore';
import { Polyline2d } from '@tldraw/primitives';
-import * as React_2 from 'react';
-import { default as React_3 } from 'react';
-import { RecordType } from '@tldraw/tlstore';
+import { default as React_2 } from 'react';
+import * as React_3 from 'react';
import { RotateCorner } from '@tldraw/primitives';
import { SelectionCorner } from '@tldraw/primitives';
import { SelectionEdge } from '@tldraw/primitives';
@@ -39,7 +38,6 @@ import { SelectionHandle } from '@tldraw/primitives';
import { SerializedSchema } from '@tldraw/tlstore';
import { Signal } from 'signia';
import { sortByIndex } from '@tldraw/indices';
-import { StoreSchema } from '@tldraw/tlstore';
import { StoreSnapshot } from '@tldraw/tlstore';
import { StrokePoint } from '@tldraw/primitives';
import { TLAlignType } from '@tldraw/tlschema';
@@ -57,7 +55,6 @@ import { TLColorType } from '@tldraw/tlschema';
import { TLCursor } from '@tldraw/tlschema';
import { TLDocument } from '@tldraw/tlschema';
import { TLDrawShape } from '@tldraw/tlschema';
-import { TLDrawShapeSegment } from '@tldraw/tlschema';
import { TLEmbedShape } from '@tldraw/tlschema';
import { TLFontType } from '@tldraw/tlschema';
import { TLFrameShape } from '@tldraw/tlschema';
@@ -88,7 +85,6 @@ import { TLShapeProps } from '@tldraw/tlschema';
import { TLSizeStyle } from '@tldraw/tlschema';
import { TLSizeType } from '@tldraw/tlschema';
import { TLStore } from '@tldraw/tlschema';
-import { TLStoreProps } from '@tldraw/tlschema';
import { TLStyleCollections } from '@tldraw/tlschema';
import { TLStyleType } from '@tldraw/tlschema';
import { TLTextShape } from '@tldraw/tlschema';
@@ -125,7 +121,7 @@ export type AnimationOptions = Partial<{
// @public (undocumented)
export class App extends EventEmitter {
- constructor({ config, store, getContainer }: AppOptions);
+ constructor({ store, user, tools, shapes, getContainer, }: AppOptions);
addOpenMenu: (id: string) => this;
alignShapes(operation: 'bottom' | 'center-horizontal' | 'center-vertical' | 'left' | 'right' | 'top', ids?: TLShapeId[]): this;
get allShapesCommonBounds(): Box2d | null;
@@ -167,7 +163,6 @@ export class App extends EventEmitter {
// @internal
protected _clickManager: ClickManager;
complete(): this;
- readonly config: TldrawEditorConfig;
// @internal (undocumented)
crash(error: unknown): void;
// @internal
@@ -551,9 +546,11 @@ export function applyRotationToSnapshotShapes({ delta, app, snapshot, stage, }:
// @public (undocumented)
export interface AppOptions {
- config: TldrawEditorConfig;
getContainer: () => HTMLElement;
+ shapes?: Record;
store: TLStore;
+ tools?: StateNodeConstructor[];
+ user?: TLUser;
}
// @public (undocumented)
@@ -569,8 +566,8 @@ export const BOUND_ARROW_OFFSET = 10;
export function buildFromV1Document(app: App, document: LegacyTldrawDocument): void;
// @public (undocumented)
-export const Canvas: React_2.MemoExoticComponent<({ onDropOverride, }: {
- onDropOverride?: ((defaultOnDrop: (e: React_2.DragEvent) => Promise) => (e: React_2.DragEvent) => Promise) | undefined;
+export const Canvas: React_3.MemoExoticComponent<({ onDropOverride, }: {
+ onDropOverride?: ((defaultOnDrop: (e: React_3.DragEvent) => Promise) => (e: React_3.DragEvent) => Promise) | undefined;
}) => JSX.Element>;
// @public (undocumented)
@@ -613,6 +610,9 @@ export function createEmbedShapeAtPoint(app: App, url: string, point: Vec2dModel
// @public (undocumented)
export function createShapesFromFiles(app: App, files: File[], position: VecLike, _ignoreParent?: boolean): Promise;
+// @public
+export function createTLStore(opts?: StoreOptions): TLStore;
+
// @public (undocumented)
export function dataTransferItemAsString(item: DataTransferItem): Promise;
@@ -658,6 +658,12 @@ export function defaultEmptyAs(str: string, dflt: string): string;
// @internal (undocumented)
export const DefaultErrorFallback: TLErrorFallback;
+// @public (undocumented)
+export const defaultShapes: Record;
+
+// @public (undocumented)
+export const defaultTools: StateNodeConstructor[];
+
// @internal (undocumented)
export const DOUBLE_CLICK_DURATION = 450;
@@ -685,7 +691,7 @@ export type EmbedResult = {
} | undefined;
// @public (undocumented)
-export class ErrorBoundary extends React_2.Component>, ErrorBoundaryState> {
+export class ErrorBoundary extends React_3.Component>, ErrorBoundaryState> {
// (undocumented)
componentDidCatch(error: unknown): void;
// (undocumented)
@@ -693,7 +699,7 @@ export class ErrorBoundary extends React_2.Component React_2.ReactNode;
+ fallback: (error: unknown) => React_3.ReactNode;
// (undocumented)
onError?: ((error: unknown) => void) | null;
}
@@ -713,16 +719,6 @@ export function ErrorScreen({ children }: {
children: any;
}): JSX.Element;
-// @public (undocumented)
-export interface ErrorSyncedStore {
- // (undocumented)
- readonly error: Error;
- // (undocumented)
- readonly status: 'error';
- // (undocumented)
- readonly store?: undefined;
-}
-
// @public (undocumented)
export const EVENT_NAME_MAP: Record, keyof TLEventHandlers>;
@@ -842,6 +838,9 @@ export function getSvgPathFromStrokePoints(points: StrokePoint[], closed?: boole
// @public (undocumented)
export function getTextBoundingBox(text: SVGTextElement): DOMRect;
+// @public (undocumented)
+export function getUserPreferences(): TLUserPreferences;
+
// @public (undocumented)
export const getValidHttpURLList: (url: string) => string[] | undefined;
@@ -864,6 +863,11 @@ export const GRID_STEPS: {
// @internal (undocumented)
export const HAND_TOOL_FRICTION = 0.09;
+// @public
+export function hardReset({ shouldReload }?: {
+ shouldReload?: boolean | undefined;
+}): Promise;
+
// @public (undocumented)
export function hardResetApp(): void;
@@ -874,7 +878,7 @@ export const HASH_PATERN_ZOOM_NAMES: Record;
export function HTMLContainer({ children, className, ...rest }: HTMLContainerProps): JSX.Element;
// @public (undocumented)
-export type HTMLContainerProps = React_2.HTMLAttributes;
+export type HTMLContainerProps = React_3.HTMLAttributes;
// @public (undocumented)
export const ICON_SIZES: Record;
@@ -882,16 +886,6 @@ export const ICON_SIZES: Record;
// @public (undocumented)
export const INDENT = " ";
-// @public (undocumented)
-export interface InitializingSyncedStore {
- // (undocumented)
- readonly error?: undefined;
- // (undocumented)
- readonly status: 'loading';
- // (undocumented)
- readonly store?: undefined;
-}
-
// @public
export function isAnimated(buffer: ArrayBuffer): boolean;
@@ -1392,27 +1386,17 @@ export function openWindow(url: string, target?: string): void;
// @internal (undocumented)
export function OptionalErrorBoundary({ children, fallback, ...props }: Omit & {
- fallback: ((error: unknown) => React_2.ReactNode) | null;
+ fallback: ((error: unknown) => React_3.ReactNode) | null;
}): JSX.Element;
// @public
-export function preventDefault(event: Event | React_3.BaseSyntheticEvent): void;
-
-// @public (undocumented)
-export interface ReadySyncedStore {
- // (undocumented)
- readonly error?: undefined;
- // (undocumented)
- readonly status: 'synced';
- // (undocumented)
- readonly store: TLStore;
-}
+export function preventDefault(event: Event | React_2.BaseSyntheticEvent): void;
// @public (undocumented)
export function refreshPage(): void;
// @public (undocumented)
-export function releasePointerCapture(element: Element, event: PointerEvent | React_3.PointerEvent): void;
+export function releasePointerCapture(element: Element, event: PointerEvent | React_2.PointerEvent): void;
// @internal (undocumented)
export const REMOVE_SYMBOL: unique symbol;
@@ -1455,7 +1439,7 @@ export const runtime: {
export function setDefaultEditorAssetUrls(assetUrls: EditorAssetUrls): void;
// @public (undocumented)
-export function setPointerCapture(element: Element, event: PointerEvent | React_3.PointerEvent): void;
+export function setPointerCapture(element: Element, event: PointerEvent | React_2.PointerEvent): void;
// @public (undocumented)
export function setPropsForNextShape(previousProps: TLInstancePropsForNextShape, newProps: Partial): TLInstancePropsForNextShape;
@@ -1463,6 +1447,9 @@ export function setPropsForNextShape(previousProps: TLInstancePropsForNextShape,
// @public (undocumented)
export function setRuntimeOverrides(input: Partial): void;
+// @public (undocumented)
+export function setUserPreferences(user: TLUserPreferences): void;
+
// @public (undocumented)
export function snapToGrid(n: number, gridSize: number): number;
@@ -1559,6 +1546,30 @@ export interface StateNodeConstructor {
styles?: TLStyleType[];
}
+// @public (undocumented)
+export type StoreWithStatus = {
+ readonly status: 'error';
+ readonly store?: undefined;
+ readonly error: Error;
+} | {
+ readonly status: 'loading';
+ readonly store?: undefined;
+ readonly error?: undefined;
+} | {
+ readonly status: 'not-synced';
+ readonly store: TLStore;
+ readonly error?: undefined;
+} | {
+ readonly status: 'synced-local';
+ readonly store: TLStore;
+ readonly error?: undefined;
+} | {
+ readonly status: 'synced-remote';
+ readonly connectionStatus: 'offline' | 'online';
+ readonly store: TLStore;
+ readonly error?: undefined;
+};
+
// @public (undocumented)
export const STYLES: TLStyleCollections;
@@ -1569,10 +1580,10 @@ export const SVG_PADDING = 32;
export function SVGContainer({ children, className, ...rest }: SVGContainerProps): JSX.Element;
// @public (undocumented)
-export type SVGContainerProps = React_2.HTMLAttributes;
+export type SVGContainerProps = React_3.HTMLAttributes;
// @public (undocumented)
-export type SyncedStore = ErrorSyncedStore | InitializingSyncedStore | ReadySyncedStore;
+export const TAB_ID: TLInstanceId;
// @public (undocumented)
export const TEXT_PROPS: {
@@ -1696,7 +1707,7 @@ export type TLBoxLike = TLBaseShape (typeof Idle_4 | typeof Pointing_3)[];
+ static children: () => (typeof Idle_4 | typeof Pointing_2)[];
// (undocumented)
static id: string;
// (undocumented)
@@ -1793,51 +1804,31 @@ export type TLCompleteEventInfo = {
export type TLCopyType = 'jpeg' | 'json' | 'png' | 'svg';
// @public (undocumented)
-export function TldrawEditor(props: TldrawEditorProps): JSX.Element;
+export const TldrawEditor: React_2.NamedExoticComponent;
// @public (undocumented)
-export class TldrawEditorConfig {
- constructor(opts?: TldrawEditorConfigOptions);
- // (undocumented)
- createStore(config: {
- initialData?: StoreSnapshot;
- instanceId: TLInstanceId;
- }): TLStore;
- // (undocumented)
- readonly derivePresenceState: (store: TLStore) => Signal;
- // (undocumented)
- readonly setUserPreferences: (userPreferences: TLUserPreferences) => void;
- // (undocumented)
- readonly shapeUtils: Record>;
- // (undocumented)
- readonly storeSchema: StoreSchema;
- // (undocumented)
- readonly TLShape: RecordType;
- // (undocumented)
- readonly tools: readonly StateNodeConstructor[];
- // (undocumented)
- readonly userPreferences: Signal;
-}
-
-// @public (undocumented)
-export interface TldrawEditorProps {
+export type TldrawEditorProps = {
+ children?: any;
+ shapes?: Record;
+ tools?: StateNodeConstructor[];
assetUrls?: EditorAssetUrls;
autoFocus?: boolean;
- // (undocumented)
- children?: any;
components?: Partial;
- config: TldrawEditorConfig;
- instanceId?: TLInstanceId;
- isDarkMode?: boolean;
+ onMount?: (app: App) => void;
onCreateAssetFromFile?: (file: File) => Promise;
onCreateBookmarkFromUrl?: (url: string) => Promise<{
image: string;
title: string;
description: string;
}>;
- onMount?: (app: App) => void;
- store?: SyncedStore | TLStore;
-}
+} & ({
+ store: StoreWithStatus | TLStore;
+} | {
+ store?: undefined;
+ initialData?: StoreSnapshot;
+ instanceId?: TLInstanceId;
+ persistenceKey?: string;
+});
// @public (undocumented)
export class TLDrawUtil extends TLShapeUtil {
@@ -2209,6 +2200,8 @@ export class TLGroupUtil extends TLShapeUtil {
render(shape: TLGroupShape): JSX.Element | null;
// (undocumented)
static type: string;
+ // (undocumented)
+ type: "group";
}
// @public (undocumented)
@@ -2570,6 +2563,8 @@ export abstract class TLShapeUtil {
export interface TLShapeUtilConstructor = TLShapeUtil> {
// (undocumented)
new (app: App, type: T['type']): ShapeUtil;
+ // (undocumented)
+ type: T['type'];
}
// @public (undocumented)
@@ -2663,6 +2658,22 @@ export class TLTextUtil extends TLShapeUtil {
// @public (undocumented)
export type TLTickEvent = (elapsed: number) => void;
+// @public
+export interface TLUserPreferences {
+ // (undocumented)
+ animationSpeed: number;
+ // (undocumented)
+ color: string;
+ // (undocumented)
+ id: string;
+ // (undocumented)
+ isDarkMode: boolean;
+ // (undocumented)
+ locale: string;
+ // (undocumented)
+ name: string;
+}
+
// @public (undocumented)
export class TLVideoUtil extends TLBoxUtil {
// (undocumented)
@@ -2715,6 +2726,11 @@ export const useApp: () => App;
// @public (undocumented)
export function useContainer(): HTMLDivElement;
+// @internal (undocumented)
+export function useLocalStore(opts?: {
+ persistenceKey?: string | undefined;
+} & StoreOptions): StoreWithStatus;
+
// @internal (undocumented)
export function usePeerIds(): string[];
@@ -2733,6 +2749,9 @@ export const USER_COLORS: readonly ["#FF802B", "#EC5E41", "#F2555A", "#F04F88",
// @public (undocumented)
export function useReactor(name: string, reactFn: () => void, deps?: any[] | undefined): void;
+// @public (undocumented)
+export function useTLStore(opts: StoreOptions): TLStore;
+
// @internal (undocumented)
export const WAY_TOO_BIG_ARROW_BEND_FACTOR = 10;
diff --git a/packages/editor/package.json b/packages/editor/package.json
index 324945361..0c3a8e0d7 100644
--- a/packages/editor/package.json
+++ b/packages/editor/package.json
@@ -56,6 +56,7 @@
"crc": "^4.3.2",
"escape-string-regexp": "^5.0.0",
"eventemitter3": "^4.0.7",
+ "idb": "^7.1.1",
"is-plain-object": "^5.0.0",
"lodash.throttle": "^4.1.1",
"lodash.uniq": "^4.5.0",
@@ -79,7 +80,7 @@
"@types/wicg-file-system-access": "^2020.9.5",
"benchmark": "^2.1.4",
"fake-indexeddb": "^4.0.0",
- "jest-canvas-mock": "^2.4.0",
+ "jest-canvas-mock": "^2.5.1",
"jest-environment-jsdom": "^29.4.3",
"lazyrepo": "0.0.0-alpha.26",
"react-test-renderer": "^18.2.0",
@@ -103,6 +104,7 @@
},
"setupFiles": [
"raf/polyfill",
+ "jest-canvas-mock",
"/setupTests.js"
],
"setupFilesAfterEnv": [
diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts
index 6abc65eef..b91acd2ea 100644
--- a/packages/editor/src/index.ts
+++ b/packages/editor/src/index.ts
@@ -127,13 +127,14 @@ export {
export { HTMLContainer, type HTMLContainerProps } from './lib/components/HTMLContainer'
export { SVGContainer, type SVGContainerProps } from './lib/components/SVGContainer'
export {
- type ErrorSyncedStore,
- type InitializingSyncedStore,
- type ReadySyncedStore,
- type SyncedStore,
-} from './lib/config/SyncedStore'
-export { USER_COLORS } from './lib/config/TLUserPreferences'
-export { TldrawEditorConfig } from './lib/config/TldrawEditorConfig'
+ USER_COLORS,
+ getUserPreferences,
+ setUserPreferences,
+ type TLUserPreferences,
+} from './lib/config/TLUserPreferences'
+export { createTLStore } from './lib/config/createTLStore'
+export { defaultShapes } from './lib/config/defaultShapes'
+export { defaultTools } from './lib/config/defaultTools'
export {
ANIMATION_MEDIUM_MS,
ANIMATION_SHORT_MS,
@@ -176,10 +177,12 @@ export { normalizeWheel } from './lib/hooks/shared'
export { useApp } from './lib/hooks/useApp'
export { useContainer } from './lib/hooks/useContainer'
export type { TLEditorComponents } from './lib/hooks/useEditorComponents'
+export { useLocalStore } from './lib/hooks/useLocalStore'
export { usePeerIds } from './lib/hooks/usePeerIds'
export { usePresence } from './lib/hooks/usePresence'
export { useQuickReactor } from './lib/hooks/useQuickReactor'
export { useReactor } from './lib/hooks/useReactor'
+export { useTLStore } from './lib/hooks/useTLStore'
export { WeakMapCache } from './lib/utils/WeakMapCache'
export {
ACCEPTED_ASSET_TYPE,
@@ -256,4 +259,7 @@ export {
defaultEmptyAs,
} from './lib/utils/string'
export { getPointerInfo, getSvgPathFromStroke, getSvgPathFromStrokePoints } from './lib/utils/svg'
+export { type StoreWithStatus } from './lib/utils/sync/StoreWithStatus'
+export { hardReset } from './lib/utils/sync/hardReset'
+export { TAB_ID } from './lib/utils/sync/persistence-constants'
export { openWindow } from './lib/utils/window-open'
diff --git a/packages/editor/src/lib/TldrawEditor.tsx b/packages/editor/src/lib/TldrawEditor.tsx
index 2f5c57b61..2f83c7b65 100644
--- a/packages/editor/src/lib/TldrawEditor.tsx
+++ b/packages/editor/src/lib/TldrawEditor.tsx
@@ -1,15 +1,13 @@
-import { InstanceRecordType, TLAsset, TLInstanceId, TLStore } from '@tldraw/tlschema'
-import { Store } from '@tldraw/tlstore'
+import { TLAsset, TLInstanceId, TLRecord, TLStore } from '@tldraw/tlschema'
+import { Store, StoreSnapshot } from '@tldraw/tlstore'
import { annotateError } from '@tldraw/utils'
-import React, { useCallback, useMemo, useSyncExternalStore } from 'react'
+import React, { memo, useCallback, useLayoutEffect, useState, useSyncExternalStore } from 'react'
import { App } from './app/App'
+import { StateNodeConstructor } from './app/statechart/StateNode'
import { EditorAssetUrls, defaultEditorAssetUrls } from './assetUrls'
-import { OptionalErrorBoundary } from './components/ErrorBoundary'
-
-import { SyncedStore } from './config/SyncedStore'
-import { TldrawEditorConfig } from './config/TldrawEditorConfig'
-
import { DefaultErrorFallback } from './components/DefaultErrorFallback'
+import { OptionalErrorBoundary } from './components/ErrorBoundary'
+import { ShapeInfo } from './config/createTLStore'
import { AppContext } from './hooks/useApp'
import { ContainerProvider, useContainer } from './hooks/useContainer'
import { useCursor } from './hooks/useCursor'
@@ -21,21 +19,38 @@ import {
} from './hooks/useEditorComponents'
import { useEvent } from './hooks/useEvent'
import { useForceUpdate } from './hooks/useForceUpdate'
+import { useLocalStore } from './hooks/useLocalStore'
import { usePreloadAssets } from './hooks/usePreloadAssets'
import { useSafariFocusOutFix } from './hooks/useSafariFocusOutFix'
import { useZoomCss } from './hooks/useZoomCss'
+import { StoreWithStatus } from './utils/sync/StoreWithStatus'
+import { TAB_ID } from './utils/sync/persistence-constants'
/** @public */
-export interface TldrawEditorProps {
+export type 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
/**
- * Called when the app has mounted.
+ * An array of shape utils to use in the editor.
+ */
+ shapes?: Record
+ /**
+ * An array of tools to use in the editor.
+ */
+ tools?: StateNodeConstructor[]
+ /**
+ * Urls for where to find fonts and other assets.
+ */
+ assetUrls?: EditorAssetUrls
+ /**
+ * Whether to automatically focus the editor when it mounts.
+ */
+ autoFocus?: boolean
+ /**
+ * Overrides for the tldraw user interface components.
+ */
+ components?: Partial
+ /**
+ * Called when the editor has mounted.
*
* @example
*
@@ -49,7 +64,7 @@ export interface TldrawEditorProps {
*/
onMount?: (app: App) => void
/**
- * Called when the app generates a new asset from a file, such as when an image is dropped into
+ * Called when the editor generates a new asset from a file, such as when an image is dropped into
* the canvas.
*
* @example
@@ -81,22 +96,31 @@ export interface TldrawEditorProps {
onCreateBookmarkFromUrl?: (
url: string
) => Promise<{ image: string; title: string; description: string }>
-
- /**
- * The Store instance to use for keeping the app's data. This may be prepopulated, e.g. by loading
- * from a server or database.
- */
- store?: TLStore | SyncedStore
- /**
- * The id of the app instance (e.g. a browser tab if the app will have only one tldraw app per
- * tab). If not given, one will be generated.
- */
- instanceId?: TLInstanceId
- /** Asset URLs */
- assetUrls?: EditorAssetUrls
- /** Whether to automatically focus the editor when it mounts. */
- autoFocus?: boolean
-}
+} & (
+ | {
+ /**
+ * The Store instance to use for keeping the editor's data. This may be prepopulated, e.g. by loading
+ * from a server or database.
+ */
+ store: TLStore | StoreWithStatus
+ }
+ | {
+ store?: undefined
+ /**
+ * The editor's initial data.
+ */
+ initialData?: StoreSnapshot
+ /**
+ * The id of the editor instance (e.g. a browser tab if the editor will have only one tldraw app per
+ * tab). If not given, one will be generated.
+ */
+ instanceId?: TLInstanceId
+ /**
+ * The id under which to sync and persist the editor's data.
+ */
+ persistenceKey?: string
+ }
+)
declare global {
interface Window {
@@ -105,12 +129,15 @@ declare global {
}
/** @public */
-export function TldrawEditor(props: TldrawEditorProps) {
+export const TldrawEditor = memo(function TldrawEditor(props: TldrawEditorProps) {
const [container, setContainer] = React.useState(null)
- const { components, ...rest } = props
const ErrorFallback =
- components?.ErrorFallback === undefined ? DefaultErrorFallback : components?.ErrorFallback
+ props.components?.ErrorFallback === undefined
+ ? DefaultErrorFallback
+ : props.components?.ErrorFallback
+
+ const { store, ...rest } = props
return (
@@ -120,51 +147,68 @@ export function TldrawEditor(props: TldrawEditorProps) {
>
{container && (
-
-
+
+ {store ? (
+ store instanceof Store ? (
+ // Store is ready to go, whether externally synced or not
+
+ ) : (
+ // Store is a synced store, so handle syncing stages internally
+
+ )
+ ) : (
+ // We have no store (it's undefined) so create one and possibly sync it
+
+ )}
)}
)
+})
+
+function TldrawEditorWithOwnStore(props: TldrawEditorProps & { store: undefined }) {
+ const { initialData, instanceId = TAB_ID, shapes, persistenceKey } = props
+
+ const syncedStore = useLocalStore({
+ customShapes: shapes,
+ instanceId,
+ initialData,
+ persistenceKey,
+ })
+
+ return
}
-function TldrawEditorBeforeLoading({ config, instanceId, store, ...props }: TldrawEditorProps) {
+const TldrawEditorWithLoadingStore = memo(function TldrawEditorBeforeLoading({
+ store,
+ assetUrls,
+ ...rest
+}: TldrawEditorProps & { store: StoreWithStatus }) {
const { done: preloadingComplete, error: preloadingError } = usePreloadAssets(
- props.assetUrls ?? defaultEditorAssetUrls
+ assetUrls ?? defaultEditorAssetUrls
)
- const _store = useMemo(() => {
- return (
- store ??
- config.createStore({
- instanceId: instanceId ?? InstanceRecordType.createId(),
- })
- )
- }, [store, config, instanceId])
-
- let loadedStore: TLStore | SyncedStore
- if (!(_store instanceof Store)) {
- if (_store.error) {
+ switch (store.status) {
+ case '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) {
+ case 'loading': {
return Connecting...
}
-
- loadedStore = _store.store
- } else {
- loadedStore = _store
- }
-
- if (instanceId && loadedStore.props.instanceId !== instanceId) {
- console.error(
- `The store's instanceId (${loadedStore.props.instanceId}) does not match the instanceId prop (${instanceId}). This may cause unexpected behavior.`
- )
+ case 'not-synced': {
+ break
+ }
+ case 'synced-local': {
+ break
+ }
+ case 'synced-remote': {
+ break
+ }
}
if (preloadingError) {
@@ -175,57 +219,56 @@ function TldrawEditorBeforeLoading({ config, instanceId, store, ...props }: Tldr
return Loading assets...
}
- return
-}
+ return
+})
-function TldrawEditorAfterLoading({
+function TldrawEditorWithReadyStore({
onMount,
- config,
children,
onCreateAssetFromFile,
onCreateBookmarkFromUrl,
store,
+ tools,
+ shapes,
autoFocus,
-}: Omit & {
- config: TldrawEditorConfig
+}: TldrawEditorProps & {
store: TLStore
}) {
- const container = useContainer()
-
- const [app, setApp] = React.useState(null)
const { ErrorFallback } = useEditorComponents()
+ const container = useContainer()
+ const [app, setApp] = useState(null)
- React.useLayoutEffect(() => {
+ useLayoutEffect(() => {
const app = new App({
store,
- config,
+ shapes,
+ tools,
getContainer: () => container,
})
- setApp(app)
-
- if (autoFocus) {
- app.focus()
- }
;(window as any).app = app
+ setApp(app)
return () => {
app.dispose()
- setApp((prevApp) => (prevApp === app ? null : prevApp))
}
- }, [container, config, store, autoFocus])
+ }, [container, shapes, tools, store])
React.useEffect(() => {
- if (app) {
- // Overwrite the default onCreateAssetFromFile handler.
- if (onCreateAssetFromFile) {
- app.onCreateAssetFromFile = onCreateAssetFromFile
- }
+ if (!app) return
- if (onCreateBookmarkFromUrl) {
- app.onCreateBookmarkFromUrl = onCreateBookmarkFromUrl
- }
+ // Overwrite the default onCreateAssetFromFile handler.
+ if (onCreateAssetFromFile) {
+ app.onCreateAssetFromFile = onCreateAssetFromFile
+ }
+
+ if (onCreateBookmarkFromUrl) {
+ app.onCreateBookmarkFromUrl = onCreateBookmarkFromUrl
}
}, [app, onCreateAssetFromFile, onCreateBookmarkFromUrl])
+ React.useLayoutEffect(() => {
+ if (app && autoFocus) app.focus()
+ }, [app, autoFocus])
+
const onMountEvent = useEvent((app: App) => {
onMount?.(app)
app.emit('mount')
@@ -233,10 +276,7 @@ function TldrawEditorAfterLoading({
})
React.useEffect(() => {
- if (app) {
- // Run onMount
- onMountEvent(app)
- }
+ if (app) onMountEvent(app)
}, [app, onMountEvent])
const crashingError = useSyncExternalStore(
diff --git a/packages/editor/src/lib/app/App.ts b/packages/editor/src/lib/app/App.ts
index aa887f144..a4dab6415 100644
--- a/packages/editor/src/lib/app/App.ts
+++ b/packages/editor/src/lib/app/App.ts
@@ -64,7 +64,7 @@ import {
isShape,
isShapeId,
} from '@tldraw/tlschema'
-import { ComputedCache, HistoryEntry, UnknownRecord } from '@tldraw/tlstore'
+import { ComputedCache, HistoryEntry, RecordType, UnknownRecord } from '@tldraw/tlstore'
import {
annotateError,
compact,
@@ -77,7 +77,10 @@ import {
import { EventEmitter } from 'eventemitter3'
import { nanoid } from 'nanoid'
import { EMPTY_ARRAY, atom, computed, transact } from 'signia'
-import { TldrawEditorConfig } from '../config/TldrawEditorConfig'
+import { ShapeInfo } from '../config/createTLStore'
+import { TLUser, createTLUser } from '../config/createTLUser'
+import { coreShapes, defaultShapes } from '../config/defaultShapes'
+import { defaultTools } from '../config/defaultTools'
import {
ANIMATION_MEDIUM_MS,
BLACKLISTED_PROPS,
@@ -132,7 +135,7 @@ import { TLResizeMode, TLShapeUtil } from './shapeutils/TLShapeUtil'
import { TLTextUtil } from './shapeutils/TLTextUtil/TLTextUtil'
import { TLExportColors } from './shapeutils/shared/TLExportColors'
import { RootState } from './statechart/RootState'
-import { StateNode } from './statechart/StateNode'
+import { StateNode, StateNodeConstructor } from './statechart/StateNode'
import { TLClipboardModel } from './types/clipboard-types'
import { TLEventMap } from './types/emit-types'
import { TLEventInfo, TLPinchEventInfo, TLPointerEventInfo } from './types/event-types'
@@ -161,8 +164,18 @@ export interface AppOptions {
* from a server or database.
*/
store: TLStore
- /** A configuration defining major customizations to the app, such as custom shapes and new tools */
- config: TldrawEditorConfig
+ /**
+ * An array of shapes to use in the app. These will be used to create and manage shapes in the app.
+ */
+ shapes?: Record
+ /**
+ * An array of tools to use in the app. These will be used to handle events and manage user interactions in the app.
+ */
+ tools?: StateNodeConstructor[]
+ /**
+ * A user defined externally to replace the default user.
+ */
+ user?: TLUser
/**
* Should return a containing html element which has all the styles applied to the app. If not
* given, the body element will be used.
@@ -177,28 +190,54 @@ export function isShapeWithHandles(shape: TLShape) {
/** @public */
export class App extends EventEmitter {
- constructor({ config, store, getContainer }: AppOptions) {
+ constructor({
+ store,
+ user,
+ tools = defaultTools,
+ shapes = defaultShapes,
+ getContainer,
+ }: AppOptions) {
super()
- this.config = config
-
- if (store.schema !== this.config.storeSchema) {
- throw new Error('Store schema does not match schema given to App')
- }
-
this.store = store
- this.user = new UserPreferencesManager(this)
+ this.user = new UserPreferencesManager(user ?? createTLUser())
this.getContainer = getContainer ?? (() => document.body)
this.textMeasure = new TextManager(this)
- // Set the shape utils
- this.shapeUtils = Object.fromEntries(
- Object.entries(this.config.shapeUtils).map(([type, Util]) => [type, new Util(this, type)])
+ this.root = new RootState(this)
+
+ // Shapes.
+ // Accept shapes from constructor parameters which may not conflict with the root note's core tools.
+ const shapeUtils = Object.fromEntries(
+ Object.values(coreShapes).map(({ util: Util }) => [Util.type, new Util(this, Util.type)])
)
+ for (const [type, { util: Util }] of Object.entries(shapes)) {
+ if (shapeUtils[type]) {
+ throw Error(`May not overwrite core shape of type "${type}".`)
+ }
+ if (type !== Util.type) {
+ throw Error(`Shape util's type "${Util.type}" does not match provided type "${type}".`)
+ }
+ shapeUtils[type] = new Util(this, Util.type)
+ }
+ this.shapeUtils = shapeUtils
+
+ // Tools.
+ // Accept tools from constructor parameters which may not conflict with the root note's default or
+ // "baked in" tools, select and zoom.
+ const uniqueTools = Object.fromEntries(tools.map((Ctor) => [Ctor.id, Ctor]))
+ for (const [id, Ctor] of Object.entries(uniqueTools)) {
+ if (this.root.children?.[id]) {
+ throw Error(`Can't override tool with id "${id}"`)
+ }
+
+ this.root.children![id] = new Ctor(this)
+ }
+
if (typeof window !== 'undefined' && 'navigator' in window) {
this.isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
this.isIos = !!navigator.userAgent.match(/iPad/i) || !!navigator.userAgent.match(/iPhone/i)
@@ -212,13 +251,6 @@ export class App extends EventEmitter {
// Set styles
this.colors = new Map(App.styles.color.map((c) => [c.id, `var(--palette-${c.id})`]))
- this.root = new RootState(this)
- if (this.root.children) {
- this.config.tools.forEach((Ctor) => {
- this.root.children![Ctor.id] = new Ctor(this)
- })
- }
-
this.store.onBeforeDelete = (record) => {
if (record.typeName === 'shape') {
this._shapeWillBeDeleted(record)
@@ -310,13 +342,6 @@ export class App extends EventEmitter {
*/
readonly store: TLStore
- /**
- * The editor's config
- *
- * @public
- */
- readonly config: TldrawEditorConfig
-
/**
* The root state of the statechart.
*
@@ -4699,7 +4724,12 @@ export class App extends EventEmitter {
// When we create the shape, take in the partial (the props coming into the
// function) and merge it with the default props.
- let shapeRecordToCreate = this.config.TLShape.create({
+ let shapeRecordToCreate = (
+ this.store.schema.types.shape as RecordType<
+ TLShape,
+ 'type' | 'props' | 'index' | 'parentId'
+ >
+ ).create({
...partial,
index,
parentId: partial.parentId ?? focusLayerId,
diff --git a/packages/editor/src/lib/app/managers/UserPreferencesManager.ts b/packages/editor/src/lib/app/managers/UserPreferencesManager.ts
index cef9ad9c2..abeb746d9 100644
--- a/packages/editor/src/lib/app/managers/UserPreferencesManager.ts
+++ b/packages/editor/src/lib/app/managers/UserPreferencesManager.ts
@@ -1,37 +1,37 @@
import { TLUserPreferences } from '../../config/TLUserPreferences'
-import { App } from '../App'
+import { TLUser } from '../../config/createTLUser'
export class UserPreferencesManager {
- constructor(private readonly editor: App) {}
+ constructor(private readonly user: TLUser) {}
updateUserPreferences = (userPreferences: Partial) => {
- this.editor.config.setUserPreferences({
- ...this.editor.config.userPreferences.value,
+ this.user.setUserPreferences({
+ ...this.user.userPreferences.value,
...userPreferences,
})
}
get isDarkMode() {
- return this.editor.config.userPreferences.value.isDarkMode
+ return this.user.userPreferences.value.isDarkMode
}
get animationSpeed() {
- return this.editor.config.userPreferences.value.animationSpeed
+ return this.user.userPreferences.value.animationSpeed
}
get id() {
- return this.editor.config.userPreferences.value.id
+ return this.user.userPreferences.value.id
}
get name() {
- return this.editor.config.userPreferences.value.name
+ return this.user.userPreferences.value.name
}
get locale() {
- return this.editor.config.userPreferences.value.locale
+ return this.user.userPreferences.value.locale
}
get color() {
- return this.editor.config.userPreferences.value.color
+ return this.user.userPreferences.value.color
}
}
diff --git a/packages/editor/src/lib/app/shapeutils/TLGroupUtil/TLGroupUtil.tsx b/packages/editor/src/lib/app/shapeutils/TLGroupUtil/TLGroupUtil.tsx
index d535c9da0..e0d36c1e7 100644
--- a/packages/editor/src/lib/app/shapeutils/TLGroupUtil/TLGroupUtil.tsx
+++ b/packages/editor/src/lib/app/shapeutils/TLGroupUtil/TLGroupUtil.tsx
@@ -8,6 +8,8 @@ import { DashedOutlineBox } from '../shared/DashedOutlineBox'
export class TLGroupUtil extends TLShapeUtil {
static override type = 'group'
+ type = 'group' as const
+
hideSelectionBoundsBg = () => false
hideSelectionBoundsFg = () => true
diff --git a/packages/editor/src/lib/app/shapeutils/TLShapeUtil.ts b/packages/editor/src/lib/app/shapeutils/TLShapeUtil.ts
index c225e95a8..0ca6adccf 100644
--- a/packages/editor/src/lib/app/shapeutils/TLShapeUtil.ts
+++ b/packages/editor/src/lib/app/shapeutils/TLShapeUtil.ts
@@ -24,6 +24,7 @@ export interface TLShapeUtilConstructor<
ShapeUtil extends TLShapeUtil = TLShapeUtil
> {
new (app: App, type: T['type']): ShapeUtil
+ type: T['type']
}
/** @public */
diff --git a/packages/editor/src/lib/app/statechart/RootState.ts b/packages/editor/src/lib/app/statechart/RootState.ts
index 01bfee06d..e7e4e6570 100644
--- a/packages/editor/src/lib/app/statechart/RootState.ts
+++ b/packages/editor/src/lib/app/statechart/RootState.ts
@@ -1,37 +1,12 @@
import { TLEventHandlers } from '../types/event-types'
import { StateNode } from './StateNode'
-import { TLArrowTool } from './TLArrowTool/TLArrowTool'
-import { TLDrawTool } from './TLDrawTool/TLDrawTool'
-import { TLEraserTool } from './TLEraserTool/TLEraserTool'
-import { TLFrameTool } from './TLFrameTool/TLFrameTool'
-import { TLGeoTool } from './TLGeoTool/TLGeoTool'
-import { TLHandTool } from './TLHandTool/TLHandTool'
-import { TLHighlightTool } from './TLHighlightTool/TLHighlightTool'
-import { TLLaserTool } from './TLLaserTool/TLLaserTool'
-import { TLLineTool } from './TLLineTool/TLLineTool'
-import { TLNoteTool } from './TLNoteTool/TLNoteTool'
import { TLSelectTool } from './TLSelectTool/TLSelectTool'
-import { TLTextTool } from './TLTextTool/TLTextTool'
import { TLZoomTool } from './TLZoomTool/TLZoomTool'
export class RootState extends StateNode {
static override id = 'root'
static initial = 'select'
- static children = () => [
- TLSelectTool,
- TLHandTool,
- TLEraserTool,
- TLDrawTool,
- TLHighlightTool,
- TLTextTool,
- TLLineTool,
- TLArrowTool,
- TLGeoTool,
- TLNoteTool,
- TLFrameTool,
- TLZoomTool,
- TLLaserTool,
- ]
+ static children = () => [TLSelectTool, TLZoomTool]
onKeyDown: TLEventHandlers['onKeyDown'] = (info) => {
switch (info.code) {
diff --git a/packages/editor/src/lib/app/statechart/TLLaserTool/TLLaserTool.ts b/packages/editor/src/lib/app/statechart/TLLaserTool/TLLaserTool.ts
index c64608067..79f86f83e 100644
--- a/packages/editor/src/lib/app/statechart/TLLaserTool/TLLaserTool.ts
+++ b/packages/editor/src/lib/app/statechart/TLLaserTool/TLLaserTool.ts
@@ -5,6 +5,7 @@ import { Lasering } from './children/Lasering'
export class TLLaserTool extends StateNode {
static override id = 'laser'
+
static initial = 'idle'
static children = () => [Idle, Lasering]
diff --git a/packages/editor/src/lib/config/SyncedStore.tsx b/packages/editor/src/lib/config/SyncedStore.tsx
deleted file mode 100644
index 19397dc15..000000000
--- a/packages/editor/src/lib/config/SyncedStore.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { TLStore } from '@tldraw/tlschema'
-
-/** @public */
-export interface ReadySyncedStore {
- readonly status: 'synced'
- readonly store: TLStore
- readonly error?: undefined
-}
-
-/** @public */
-export interface ErrorSyncedStore {
- readonly status: 'error'
- readonly store?: undefined
- readonly error: Error
-}
-
-/** @public */
-export interface InitializingSyncedStore {
- readonly status: 'loading'
- readonly store?: undefined
- readonly error?: undefined
-}
-
-/** @public */
-export type SyncedStore = ReadySyncedStore | ErrorSyncedStore | InitializingSyncedStore
diff --git a/packages/editor/src/lib/config/TLUserPreferences.ts b/packages/editor/src/lib/config/TLUserPreferences.ts
index e502c10ef..333a0a4fe 100644
--- a/packages/editor/src/lib/config/TLUserPreferences.ts
+++ b/packages/editor/src/lib/config/TLUserPreferences.ts
@@ -146,6 +146,7 @@ function storeUserPreferences() {
}
}
+/** @public */
export function setUserPreferences(user: TLUserPreferences) {
userTypeValidator.validate(user)
globalUserPreferences.set(user)
diff --git a/packages/editor/src/lib/config/TldrawEditorConfig.tsx b/packages/editor/src/lib/config/TldrawEditorConfig.tsx
deleted file mode 100644
index 1c258fcb8..000000000
--- a/packages/editor/src/lib/config/TldrawEditorConfig.tsx
+++ /dev/null
@@ -1,131 +0,0 @@
-import {
- CLIENT_FIXUP_SCRIPT,
- InstanceRecordType,
- TLDOCUMENT_ID,
- TLDefaultShape,
- TLInstanceId,
- TLInstancePresence,
- TLRecord,
- TLShape,
- TLStore,
- TLStoreProps,
- createTLSchema,
-} from '@tldraw/tlschema'
-import { Migrations, RecordType, Store, StoreSchema, StoreSnapshot } from '@tldraw/tlstore'
-import { Signal, computed } from 'signia'
-import { TLArrowUtil } from '../app/shapeutils/TLArrowUtil/TLArrowUtil'
-import { TLBookmarkUtil } from '../app/shapeutils/TLBookmarkUtil/TLBookmarkUtil'
-import { TLDrawUtil } from '../app/shapeutils/TLDrawUtil/TLDrawUtil'
-import { TLEmbedUtil } from '../app/shapeutils/TLEmbedUtil/TLEmbedUtil'
-import { TLFrameUtil } from '../app/shapeutils/TLFrameUtil/TLFrameUtil'
-import { TLGeoUtil } from '../app/shapeutils/TLGeoUtil/TLGeoUtil'
-import { TLGroupUtil } from '../app/shapeutils/TLGroupUtil/TLGroupUtil'
-import { TLHighlightUtil } from '../app/shapeutils/TLHighlightUtil/TLHighlightUtil'
-import { TLImageUtil } from '../app/shapeutils/TLImageUtil/TLImageUtil'
-import { TLLineUtil } from '../app/shapeutils/TLLineUtil/TLLineUtil'
-import { TLNoteUtil } from '../app/shapeutils/TLNoteUtil/TLNoteUtil'
-import { TLShapeUtilConstructor } from '../app/shapeutils/TLShapeUtil'
-import { TLTextUtil } from '../app/shapeutils/TLTextUtil/TLTextUtil'
-import { TLVideoUtil } from '../app/shapeutils/TLVideoUtil/TLVideoUtil'
-import { StateNodeConstructor } from '../app/statechart/StateNode'
-import { TLUserPreferences, getUserPreferences, setUserPreferences } from './TLUserPreferences'
-
-// Secret shape types that don't have a shape util yet
-type ShapeTypesNotImplemented = 'icon'
-
-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,
- highlight: TLHighlightUtil,
-}
-
-/** @public */
-export type TldrawEditorConfigOptions = {
- tools?: readonly StateNodeConstructor[]
- shapes?: Record<
- string,
- {
- util: TLShapeUtilConstructor
- validator?: { validate: (record: T) => T }
- migrations?: Migrations
- }
- >
- /** @internal */
- derivePresenceState?: (store: TLStore) => Signal
- userPreferences?: Signal
- setUserPreferences?: (userPreferences: TLUserPreferences) => void
-}
-
-/** @public */
-export class TldrawEditorConfig {
- // Custom tools
- readonly tools: readonly StateNodeConstructor[]
-
- // Custom shape utils
- readonly shapeUtils: Record>
-
- // 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
- readonly derivePresenceState: (store: TLStore) => Signal
- readonly userPreferences: Signal
- readonly setUserPreferences: (userPreferences: TLUserPreferences) => void
-
- constructor(opts = {} as TldrawEditorConfigOptions) {
- const { shapes = {}, tools = [], derivePresenceState } = opts
-
- this.tools = tools
- this.derivePresenceState = derivePresenceState ?? (() => computed('presence', () => null))
- this.userPreferences =
- opts.userPreferences ?? computed('userPreferences', () => getUserPreferences())
- this.setUserPreferences = opts.setUserPreferences ?? setUserPreferences
-
- this.shapeUtils = {
- ...DEFAULT_SHAPE_UTILS,
- ...Object.fromEntries(Object.entries(shapes).map(([k, v]) => [k, v.util])),
- }
-
- this.storeSchema = createTLSchema({
- customShapes: shapes,
- })
-
- this.TLShape = this.storeSchema.types.shape as RecordType<
- TLShape,
- 'type' | 'props' | 'index' | 'parentId'
- >
- }
-
- createStore(config: {
- /** The store's initial data. */
- initialData?: StoreSnapshot
- instanceId: TLInstanceId
- }): TLStore {
- let initialData = config.initialData
- if (initialData) {
- initialData = CLIENT_FIXUP_SCRIPT(initialData)
- }
-
- return new Store({
- schema: this.storeSchema,
- initialData,
- props: {
- instanceId: config?.instanceId ?? InstanceRecordType.createId(),
- documentId: TLDOCUMENT_ID,
- },
- })
- }
-}
diff --git a/packages/editor/src/lib/config/createTLStore.ts b/packages/editor/src/lib/config/createTLStore.ts
new file mode 100644
index 000000000..c41d6c373
--- /dev/null
+++ b/packages/editor/src/lib/config/createTLStore.ts
@@ -0,0 +1,43 @@
+import {
+ InstanceRecordType,
+ TLDOCUMENT_ID,
+ TLInstanceId,
+ TLRecord,
+ TLStore,
+ createTLSchema,
+} from '@tldraw/tlschema'
+import { Migrations, Store, StoreSnapshot } from '@tldraw/tlstore'
+import { TLShapeUtilConstructor } from '../app/shapeutils/TLShapeUtil'
+
+/** @public */
+export type ShapeInfo = {
+ util: TLShapeUtilConstructor
+ migrations?: Migrations
+ validator?: { validate: (record: any) => any }
+}
+
+/** @public */
+export type StoreOptions = {
+ customShapes?: Record
+ instanceId?: TLInstanceId
+ initialData?: StoreSnapshot
+}
+
+/**
+ * A helper for creating a TLStore. Custom shapes cannot override default shapes.
+ *
+ * @param opts - Options for creating the store.
+ *
+ * @public */
+export function createTLStore(opts = {} as StoreOptions): TLStore {
+ const { customShapes = {}, instanceId = InstanceRecordType.createId(), initialData } = opts
+
+ return new Store({
+ schema: createTLSchema({ customShapes }),
+ initialData,
+ props: {
+ instanceId,
+ documentId: TLDOCUMENT_ID,
+ },
+ })
+}
diff --git a/packages/editor/src/lib/config/createTLUser.ts b/packages/editor/src/lib/config/createTLUser.ts
new file mode 100644
index 000000000..3e78fdd53
--- /dev/null
+++ b/packages/editor/src/lib/config/createTLUser.ts
@@ -0,0 +1,27 @@
+import { TLInstancePresence, TLStore } from '@tldraw/tlschema'
+import { Signal, computed } from 'signia'
+import { TLUserPreferences, getUserPreferences, setUserPreferences } from './TLUserPreferences'
+
+/** @public */
+export interface TLUser {
+ readonly derivePresenceState: (store: TLStore) => Signal
+ readonly userPreferences: Signal
+ readonly setUserPreferences: (userPreferences: TLUserPreferences) => void
+}
+
+/** @public */
+export function createTLUser(
+ opts = {} as {
+ /** @internal */
+ derivePresenceState?: (store: TLStore) => Signal
+ userPreferences?: Signal
+ setUserPreferences?: (userPreferences: TLUserPreferences) => void
+ }
+): TLUser {
+ return {
+ derivePresenceState: opts.derivePresenceState ?? (() => computed('presence', () => null)),
+ userPreferences:
+ opts.userPreferences ?? computed('userPreferences', () => getUserPreferences()),
+ setUserPreferences: opts.setUserPreferences ?? setUserPreferences,
+ }
+}
diff --git a/packages/editor/src/lib/config/defaultShapes.ts b/packages/editor/src/lib/config/defaultShapes.ts
new file mode 100644
index 000000000..bb3efa8a8
--- /dev/null
+++ b/packages/editor/src/lib/config/defaultShapes.ts
@@ -0,0 +1,121 @@
+import {
+ arrowShapeTypeMigrations,
+ arrowShapeTypeValidator,
+ bookmarkShapeTypeMigrations,
+ bookmarkShapeTypeValidator,
+ drawShapeTypeMigrations,
+ drawShapeTypeValidator,
+ embedShapeTypeMigrations,
+ embedShapeTypeValidator,
+ frameShapeTypeMigrations,
+ frameShapeTypeValidator,
+ geoShapeTypeMigrations,
+ geoShapeTypeValidator,
+ groupShapeTypeMigrations,
+ groupShapeTypeValidator,
+ highlightShapeTypeMigrations,
+ highlightShapeTypeValidator,
+ imageShapeTypeMigrations,
+ imageShapeTypeValidator,
+ lineShapeTypeMigrations,
+ lineShapeTypeValidator,
+ noteShapeTypeMigrations,
+ noteShapeTypeValidator,
+ textShapeTypeMigrations,
+ textShapeTypeValidator,
+ videoShapeTypeMigrations,
+ videoShapeTypeValidator,
+} from '@tldraw/tlschema'
+import { TLArrowUtil } from '../app/shapeutils/TLArrowUtil/TLArrowUtil'
+import { TLBookmarkUtil } from '../app/shapeutils/TLBookmarkUtil/TLBookmarkUtil'
+import { TLDrawUtil } from '../app/shapeutils/TLDrawUtil/TLDrawUtil'
+import { TLEmbedUtil } from '../app/shapeutils/TLEmbedUtil/TLEmbedUtil'
+import { TLFrameUtil } from '../app/shapeutils/TLFrameUtil/TLFrameUtil'
+import { TLGeoUtil } from '../app/shapeutils/TLGeoUtil/TLGeoUtil'
+import { TLGroupUtil } from '../app/shapeutils/TLGroupUtil/TLGroupUtil'
+import { TLHighlightUtil } from '../app/shapeutils/TLHighlightUtil/TLHighlightUtil'
+import { TLImageUtil } from '../app/shapeutils/TLImageUtil/TLImageUtil'
+import { TLLineUtil } from '../app/shapeutils/TLLineUtil/TLLineUtil'
+import { TLNoteUtil } from '../app/shapeutils/TLNoteUtil/TLNoteUtil'
+import { TLTextUtil } from '../app/shapeutils/TLTextUtil/TLTextUtil'
+import { TLVideoUtil } from '../app/shapeutils/TLVideoUtil/TLVideoUtil'
+import { ShapeInfo } from './createTLStore'
+
+/** @public */
+export const coreShapes: Record = {
+ // created by grouping interactions, probably the corest core shape that we have
+ group: {
+ util: TLGroupUtil,
+ validator: groupShapeTypeValidator,
+ migrations: groupShapeTypeMigrations,
+ },
+ // created by embed menu / url drop
+ embed: {
+ util: TLEmbedUtil,
+ validator: embedShapeTypeValidator,
+ migrations: embedShapeTypeMigrations,
+ },
+ // created by copy and paste / url drop
+ bookmark: {
+ util: TLBookmarkUtil,
+ validator: bookmarkShapeTypeValidator,
+ migrations: bookmarkShapeTypeMigrations,
+ },
+ // created by copy and paste / file drop
+ image: {
+ util: TLImageUtil,
+ validator: imageShapeTypeValidator,
+ migrations: imageShapeTypeMigrations,
+ },
+ // created by copy and paste / file drop
+ video: {
+ util: TLVideoUtil,
+ validator: videoShapeTypeValidator,
+ migrations: videoShapeTypeMigrations,
+ },
+ // created by copy and paste
+ text: {
+ util: TLTextUtil,
+ validator: textShapeTypeValidator,
+ migrations: textShapeTypeMigrations,
+ },
+}
+
+/** @public */
+export const defaultShapes: Record = {
+ draw: {
+ util: TLDrawUtil,
+ validator: drawShapeTypeValidator,
+ migrations: drawShapeTypeMigrations,
+ },
+ geo: {
+ util: TLGeoUtil,
+ validator: geoShapeTypeValidator,
+ migrations: geoShapeTypeMigrations,
+ },
+ line: {
+ util: TLLineUtil,
+ validator: lineShapeTypeValidator,
+ migrations: lineShapeTypeMigrations,
+ },
+ note: {
+ util: TLNoteUtil,
+ validator: noteShapeTypeValidator,
+ migrations: noteShapeTypeMigrations,
+ },
+ frame: {
+ util: TLFrameUtil,
+ validator: frameShapeTypeValidator,
+ migrations: frameShapeTypeMigrations,
+ },
+ arrow: {
+ util: TLArrowUtil,
+ validator: arrowShapeTypeValidator,
+ migrations: arrowShapeTypeMigrations,
+ },
+ highlight: {
+ util: TLHighlightUtil,
+ validator: highlightShapeTypeValidator,
+ migrations: highlightShapeTypeMigrations,
+ },
+}
diff --git a/packages/editor/src/lib/config/defaultTools.ts b/packages/editor/src/lib/config/defaultTools.ts
new file mode 100644
index 000000000..db2b6cb0c
--- /dev/null
+++ b/packages/editor/src/lib/config/defaultTools.ts
@@ -0,0 +1,27 @@
+import { StateNodeConstructor } from '../app/statechart/StateNode'
+import { TLArrowTool } from '../app/statechart/TLArrowTool/TLArrowTool'
+import { TLDrawTool } from '../app/statechart/TLDrawTool/TLDrawTool'
+import { TLEraserTool } from '../app/statechart/TLEraserTool/TLEraserTool'
+import { TLFrameTool } from '../app/statechart/TLFrameTool/TLFrameTool'
+import { TLGeoTool } from '../app/statechart/TLGeoTool/TLGeoTool'
+import { TLHandTool } from '../app/statechart/TLHandTool/TLHandTool'
+import { TLHighlightTool } from '../app/statechart/TLHighlightTool/TLHighlightTool'
+import { TLLaserTool } from '../app/statechart/TLLaserTool/TLLaserTool'
+import { TLLineTool } from '../app/statechart/TLLineTool/TLLineTool'
+import { TLNoteTool } from '../app/statechart/TLNoteTool/TLNoteTool'
+import { TLTextTool } from '../app/statechart/TLTextTool/TLTextTool'
+
+/** @public */
+export const defaultTools: StateNodeConstructor[] = [
+ TLHandTool,
+ TLEraserTool,
+ TLLaserTool,
+ TLDrawTool,
+ TLTextTool,
+ TLLineTool,
+ TLArrowTool,
+ TLGeoTool,
+ TLNoteTool,
+ TLFrameTool,
+ TLHighlightTool,
+]
diff --git a/packages/editor/src/lib/hooks/useCoarsePointer.ts b/packages/editor/src/lib/hooks/useCoarsePointer.ts
index 44a24c666..a17a4c1ff 100644
--- a/packages/editor/src/lib/hooks/useCoarsePointer.ts
+++ b/packages/editor/src/lib/hooks/useCoarsePointer.ts
@@ -4,12 +4,14 @@ import { useApp } from './useApp'
export function useCoarsePointer() {
const app = useApp()
useEffect(() => {
- const mql = window.matchMedia('(pointer: coarse)')
- const handler = () => {
- app.isCoarsePointer = mql.matches
+ if (window.matchMedia) {
+ const mql = window.matchMedia('(pointer: coarse)')
+ const handler = () => {
+ app.isCoarsePointer = mql.matches
+ }
+ handler()
+ mql.addEventListener('change', handler)
+ return () => mql.removeEventListener('change', handler)
}
- handler()
- mql.addEventListener('change', handler)
- return () => mql.removeEventListener('change', handler)
}, [app])
}
diff --git a/packages/editor/src/lib/hooks/useLocalStore.ts b/packages/editor/src/lib/hooks/useLocalStore.ts
new file mode 100644
index 000000000..a82c27f87
--- /dev/null
+++ b/packages/editor/src/lib/hooks/useLocalStore.ts
@@ -0,0 +1,59 @@
+import { useEffect, useState } from 'react'
+import { StoreOptions } from '../config/createTLStore'
+import { uniqueId } from '../utils/data'
+import { StoreWithStatus } from '../utils/sync/StoreWithStatus'
+import { TLLocalSyncClient } from '../utils/sync/TLLocalSyncClient'
+import { useTLStore } from './useTLStore'
+
+/** @internal */
+export function useLocalStore(
+ opts = {} as { persistenceKey?: string } & StoreOptions
+): StoreWithStatus {
+ const { persistenceKey, ...rest } = opts
+
+ const [state, setState] = useState<{ id: string; storeWithStatus: StoreWithStatus } | null>(null)
+ const store = useTLStore(rest)
+
+ useEffect(() => {
+ const id = uniqueId()
+
+ if (!persistenceKey) {
+ setState({
+ id,
+ storeWithStatus: { status: 'not-synced', store },
+ })
+ return
+ }
+
+ setState({
+ id,
+ storeWithStatus: { status: 'loading' },
+ })
+
+ const setStoreWithStatus = (storeWithStatus: StoreWithStatus) => {
+ setState((prev) => {
+ if (prev?.id === id) {
+ return { id, storeWithStatus }
+ }
+ return prev
+ })
+ }
+
+ const client = new TLLocalSyncClient(store, {
+ universalPersistenceKey: persistenceKey,
+ onLoad() {
+ setStoreWithStatus({ store, status: 'synced-local' })
+ },
+ onLoadError(err: any) {
+ setStoreWithStatus({ status: 'error', error: err })
+ },
+ })
+
+ return () => {
+ setState((prevState) => (prevState?.id === id ? null : prevState))
+ client.close()
+ }
+ }, [persistenceKey, store])
+
+ return state?.storeWithStatus ?? { status: 'loading' }
+}
diff --git a/packages/editor/src/lib/hooks/usePattern.tsx b/packages/editor/src/lib/hooks/usePattern.tsx
index 967802413..3063bc097 100644
--- a/packages/editor/src/lib/hooks/usePattern.tsx
+++ b/packages/editor/src/lib/hooks/usePattern.tsx
@@ -14,7 +14,7 @@ const generateImage = (dpr: number, currentZoom: number, darkMode: boolean) => {
canvasEl.height = size
const ctx = canvasEl.getContext('2d')
- if (!ctx) throw new Error('No canvas')
+ if (!ctx) return
ctx.fillStyle = darkMode ? '#212529' : '#f8f9fa'
ctx.fillRect(0, 0, size, size)
@@ -53,7 +53,9 @@ const canvasBlob = (size: [number, number], fn: (ctx: CanvasRenderingContext2D)
const canvas = document.createElement('canvas')
canvas.width = size[0]
canvas.height = size[1]
- fn(canvas.getContext('2d')!)
+ const ctx = canvas.getContext('2d')
+ if (!ctx) return ''
+ fn(ctx)
return canvas.toDataURL()
}
type PatternDef = { zoom: number; url: string; darkMode: boolean }
diff --git a/packages/editor/src/lib/hooks/usePrevious.ts b/packages/editor/src/lib/hooks/usePrevious.ts
new file mode 100644
index 000000000..3f4f68947
--- /dev/null
+++ b/packages/editor/src/lib/hooks/usePrevious.ts
@@ -0,0 +1,10 @@
+import { useEffect, useRef } from 'react'
+
+/** @internal */
+export function usePrevious(value: T) {
+ const ref = useRef(value)
+ useEffect(() => {
+ ref.current = value
+ })
+ return ref.current
+}
diff --git a/packages/editor/src/lib/hooks/useTLStore.ts b/packages/editor/src/lib/hooks/useTLStore.ts
new file mode 100644
index 000000000..4cb515960
--- /dev/null
+++ b/packages/editor/src/lib/hooks/useTLStore.ts
@@ -0,0 +1,19 @@
+import { useState } from 'react'
+import { StoreOptions, createTLStore } from '../config/createTLStore'
+import { usePrevious } from './usePrevious'
+
+/** @public */
+export function useTLStore(opts: StoreOptions) {
+ const [store, setStore] = useState(() => createTLStore(opts))
+ const previousOpts = usePrevious(opts)
+ if (
+ previousOpts.customShapes !== opts.customShapes ||
+ previousOpts.initialData !== opts.initialData ||
+ previousOpts.instanceId !== opts.instanceId
+ ) {
+ const newStore = createTLStore(opts)
+ setStore(newStore)
+ return newStore
+ }
+ return store
+}
diff --git a/packages/editor/src/lib/test/TestApp.ts b/packages/editor/src/lib/test/TestApp.ts
index e8c4f4d59..e32eca857 100644
--- a/packages/editor/src/lib/test/TestApp.ts
+++ b/packages/editor/src/lib/test/TestApp.ts
@@ -26,7 +26,9 @@ import {
TLWheelEventInfo,
} from '../app/types/event-types'
import { RequiredKeys } from '../app/types/misc-types'
-import { TldrawEditorConfig } from '../config/TldrawEditorConfig'
+import { createTLStore } from '../config/createTLStore'
+import { defaultShapes } from '../config/defaultShapes'
+import { defaultTools } from '../config/defaultTools'
import { shapesFromJsx } from './jsx'
jest.useFakeTimers()
@@ -56,12 +58,14 @@ export const TEST_INSTANCE_ID = InstanceRecordType.createCustomId('testInstance1
export class TestApp extends App {
constructor(options = {} as Partial>) {
const elm = document.createElement('div')
+ const { shapes = {}, tools = [] } = options
elm.tabIndex = 0
- const config = options.config ?? new TldrawEditorConfig()
super({
- config,
- store: config.createStore({
+ shapes: { ...defaultShapes, ...shapes },
+ tools: [...defaultTools, ...tools],
+ store: createTLStore({
instanceId: TEST_INSTANCE_ID,
+ customShapes: shapes,
}),
getContainer: () => elm,
...options,
diff --git a/packages/editor/src/lib/test/TldrawEditor.test.tsx b/packages/editor/src/lib/test/TldrawEditor.test.tsx
index 7437dc1a5..71afbd855 100644
--- a/packages/editor/src/lib/test/TldrawEditor.test.tsx
+++ b/packages/editor/src/lib/test/TldrawEditor.test.tsx
@@ -1,7 +1,12 @@
-import { render, screen } from '@testing-library/react'
-import { InstanceRecordType } from '@tldraw/tlschema'
+import { act, render, screen } from '@testing-library/react'
+import { InstanceRecordType, TLBaseShape, TLOpacityType } from '@tldraw/tlschema'
import { TldrawEditor } from '../TldrawEditor'
-import { TldrawEditorConfig } from '../config/TldrawEditorConfig'
+import { App } from '../app/App'
+import { TLBoxUtil } from '../app/shapeutils/TLBoxUtil'
+import { TLBoxTool } from '../app/statechart/TLBoxTool/TLBoxTool'
+import { Canvas } from '../components/Canvas'
+import { HTMLContainer } from '../components/HTMLContainer'
+import { createTLStore } from '../config/createTLStore'
let originalFetch: typeof window.fetch
beforeEach(() => {
@@ -9,7 +14,6 @@ beforeEach(() => {
if (args[0] === '/icons/icon/icon-names.json') {
return Promise.resolve({ json: () => Promise.resolve([]) } as Response)
}
-
return originalFetch(...args)
})
})
@@ -19,43 +23,75 @@ afterEach(() => {
window.fetch = originalFetch
})
-describe('', () => {
- it('Accepts fresh versions of store and calls `onMount` for each one', async () => {
- const config = new TldrawEditorConfig()
+describe('', () => {
+ it('Renders without crashing', async () => {
+ await act(async () => (
+
+
+
+ ))
+ })
- const initialStore = config.createStore({
+ it('Creates its own store', async () => {
+ let store: any
+ render(
+ await act(async () => (
+ (store = app.store)} autoFocus>
+
+
+ ))
+ )
+ await screen.findByTestId('canvas-1')
+ expect(store).toBeTruthy()
+ })
+
+ it('Renders with an external store', async () => {
+ const store = createTLStore()
+ render(
+ await act(async () => (
+ {
+ expect(app.store).toBe(store)
+ }}
+ autoFocus
+ >
+
+
+ ))
+ )
+ await screen.findByTestId('canvas-1')
+ })
+
+ it('Accepts fresh versions of store and calls `onMount` for each one', async () => {
+ const initialStore = createTLStore({
instanceId: InstanceRecordType.createCustomId('test'),
})
-
const onMount = jest.fn()
-
const rendered = render(
-
+
)
await screen.findByTestId('canvas-1')
- expect(onMount).toHaveBeenCalledTimes(1)
const initialApp = onMount.mock.lastCall[0]
jest.spyOn(initialApp, 'dispose')
expect(initialApp.store).toBe(initialStore)
-
// re-render with the same store:
rendered.rerender(
-
+
)
await screen.findByTestId('canvas-2')
// not called again:
expect(onMount).toHaveBeenCalledTimes(1)
-
// re-render with a new store:
- const newStore = config.createStore({
+ const newStore = createTLStore({
instanceId: InstanceRecordType.createCustomId('test'),
})
rendered.rerender(
-
+
)
@@ -64,4 +100,188 @@ describe('', () => {
expect(onMount).toHaveBeenCalledTimes(2)
expect(onMount.mock.lastCall[0].store).toBe(newStore)
})
+
+ it('Renders the canvas and shapes', async () => {
+ let app = {} as App
+ render(
+ await act(async () => (
+ {
+ app = editorApp
+ }}
+ >
+
+
+
+ ))
+ )
+ await screen.findByTestId('canvas-1')
+
+ expect(app).toBeTruthy()
+ await act(async () => {
+ app.updateInstanceState({ screenBounds: { x: 0, y: 0, w: 1080, h: 720 } }, true, true)
+ })
+
+ const id = app.createShapeId()
+
+ await act(async () => {
+ app.createShapes([
+ {
+ id,
+ type: 'geo',
+ props: { w: 100, h: 100 },
+ },
+ ])
+ })
+
+ // Does the shape exist?
+ expect(app.getShapeById(id)).toMatchObject({
+ id,
+ type: 'geo',
+ x: 0,
+ y: 0,
+ props: { geo: 'rectangle', w: 100, h: 100, opacity: '1' },
+ })
+
+ // Is the shape's component rendering?
+ expect(document.querySelectorAll('.tl-shape')).toHaveLength(1)
+
+ expect(document.querySelectorAll('.tl-shape-indicator')).toHaveLength(0)
+
+ // Select the shape
+ await act(async () => app.select(id))
+
+ // Is the shape's component rendering?
+ expect(document.querySelectorAll('.tl-shape-indicator')).toHaveLength(1)
+
+ // Select the eraser tool...
+ await act(async () => app.setSelectedTool('eraser'))
+
+ // Is the editor's current tool correct?
+ expect(app.currentToolId).toBe('eraser')
+ })
+})
+
+describe('Custom shapes', () => {
+ type CardShape = TLBaseShape<
+ 'card',
+ {
+ w: number
+ h: number
+ opacity: TLOpacityType
+ }
+ >
+
+ class CardUtil extends TLBoxUtil {
+ static override type = 'card' as const
+
+ override isAspectRatioLocked = (_shape: CardShape) => false
+ override canResize = (_shape: CardShape) => true
+ override canBind = (_shape: CardShape) => true
+
+ override defaultProps(): CardShape['props'] {
+ return {
+ opacity: '1',
+ w: 300,
+ h: 300,
+ }
+ }
+
+ render(shape: CardShape) {
+ const bounds = this.bounds(shape)
+
+ return (
+
+ {bounds.w.toFixed()}x{bounds.h.toFixed()}
+
+ )
+ }
+
+ indicator(shape: CardShape) {
+ return
+ }
+ }
+
+ class CardTool extends TLBoxTool {
+ static override id = 'card'
+ static override initial = 'idle'
+ override shapeType = 'card'
+ }
+
+ const tools = [CardTool]
+ const shapes = { card: { util: CardUtil } }
+
+ it('Uses custom shapes', async () => {
+ let app = {} as App
+ render(
+ await act(async () => (
+ {
+ app = editorApp
+ }}
+ >
+
+
+
+ ))
+ )
+ await screen.findByTestId('canvas-1')
+
+ expect(app).toBeTruthy()
+ await act(async () => {
+ app.updateInstanceState({ screenBounds: { x: 0, y: 0, w: 1080, h: 720 } }, true, true)
+ })
+
+ expect(app.shapeUtils.card).toBeTruthy()
+
+ const id = app.createShapeId()
+
+ await act(async () => {
+ app.createShapes([
+ {
+ id,
+ type: 'card',
+ props: { w: 100, h: 100 },
+ },
+ ])
+ })
+
+ // Does the shape exist?
+ expect(app.getShapeById(id)).toMatchObject({
+ id,
+ type: 'card',
+ x: 0,
+ y: 0,
+ props: { w: 100, h: 100, opacity: '1' },
+ })
+
+ // Is the shape's component rendering?
+ expect(await screen.findByTestId('card-shape')).toBeTruthy()
+
+ // Select the shape
+ await act(async () => app.select(id))
+
+ // Is the shape's component rendering?
+ expect(await screen.findByTestId('card-indicator')).toBeTruthy()
+
+ // Select the tool...
+ await act(async () => app.setSelectedTool('card'))
+
+ // Is the editor's current tool correct?
+ expect(app.currentToolId).toBe('card')
+ })
})
diff --git a/packages/editor/src/lib/test/tools/translating.test.ts b/packages/editor/src/lib/test/tools/translating.test.ts
index 4fc2b1d8b..97510dbe4 100644
--- a/packages/editor/src/lib/test/tools/translating.test.ts
+++ b/packages/editor/src/lib/test/tools/translating.test.ts
@@ -2,9 +2,9 @@ import { Box2d, Vec2d, VecLike } from '@tldraw/primitives'
import { TLShapeId, TLShapePartial, Vec2dModel, createCustomShapeId } from '@tldraw/tlschema'
import { GapsSnapLine, PointsSnapLine, SnapLine } from '../../app/managers/SnapManager'
import { TLShapeUtil } from '../../app/shapeutils/TLShapeUtil'
-import { TldrawEditorConfig } from '../../config/TldrawEditorConfig'
import { TestApp } from '../TestApp'
+import { defaultShapes } from '../../config/defaultShapes'
import { getSnapLines } from '../testutils/getSnapLines'
type __TopLeftSnapOnlyShape = any
@@ -40,14 +40,6 @@ class __TopLeftSnapOnlyShapeUtil extends TLShapeUtil<__TopLeftSnapOnlyShape> {
}
}
-const configWithCustomShape = new TldrawEditorConfig({
- shapes: {
- __test_top_left_snap_only: {
- util: __TopLeftSnapOnlyShapeUtil,
- },
- },
-})
-
let app: TestApp
afterEach(() => {
@@ -759,8 +751,12 @@ describe('custom snapping points', () => {
beforeEach(() => {
app?.dispose()
app = new TestApp({
- config: configWithCustomShape,
-
+ shapes: {
+ ...defaultShapes,
+ __test_top_left_snap_only: {
+ util: __TopLeftSnapOnlyShapeUtil,
+ },
+ },
// x───────┐
// │ T │
// │ │
diff --git a/packages/editor/src/lib/utils/sync/StoreWithStatus.ts b/packages/editor/src/lib/utils/sync/StoreWithStatus.ts
new file mode 100644
index 000000000..676cd7f2f
--- /dev/null
+++ b/packages/editor/src/lib/utils/sync/StoreWithStatus.ts
@@ -0,0 +1,30 @@
+import { TLStore } from '@tldraw/tlschema'
+
+/** @public */
+export type StoreWithStatus =
+ | {
+ readonly status: 'not-synced'
+ readonly store: TLStore
+ readonly error?: undefined
+ }
+ | {
+ readonly status: 'error'
+ readonly store?: undefined
+ readonly error: Error
+ }
+ | {
+ readonly status: 'loading'
+ readonly store?: undefined
+ readonly error?: undefined
+ }
+ | {
+ readonly status: 'synced-local'
+ readonly store: TLStore
+ readonly error?: undefined
+ }
+ | {
+ readonly status: 'synced-remote'
+ readonly connectionStatus: 'online' | 'offline'
+ readonly store: TLStore
+ readonly error?: undefined
+ }
diff --git a/packages/tlsync-client/src/lib/TLLocalSyncClient.test.ts b/packages/editor/src/lib/utils/sync/TLLocalSyncClient.test.ts
similarity index 96%
rename from packages/tlsync-client/src/lib/TLLocalSyncClient.test.ts
rename to packages/editor/src/lib/utils/sync/TLLocalSyncClient.test.ts
index 1cccbaaad..933409706 100644
--- a/packages/tlsync-client/src/lib/TLLocalSyncClient.test.ts
+++ b/packages/editor/src/lib/utils/sync/TLLocalSyncClient.test.ts
@@ -1,12 +1,8 @@
-import {
- InstanceRecordType,
- PageRecordType,
- TldrawEditorConfig,
- TLInstanceId,
-} from '@tldraw/editor'
+import { InstanceRecordType, PageRecordType, TLInstanceId } from '@tldraw/tlschema'
import { promiseWithResolve } from '@tldraw/utils'
-import * as idb from './indexedDb'
+import { createTLStore } from '../../config/createTLStore'
import { TLLocalSyncClient } from './TLLocalSyncClient'
+import * as idb from './indexedDb'
jest.mock('./indexedDb', () => ({
...jest.requireActual('./indexedDb'),
@@ -31,7 +27,7 @@ function testClient(
instanceId: TLInstanceId = InstanceRecordType.createCustomId('test'),
channel = new BroadcastChannelMock('test')
) {
- const store = new TldrawEditorConfig().createStore({
+ const store = createTLStore({
instanceId,
})
const onLoad = jest.fn(() => {
diff --git a/packages/tlsync-client/src/lib/TLLocalSyncClient.ts b/packages/editor/src/lib/utils/sync/TLLocalSyncClient.ts
similarity index 99%
rename from packages/tlsync-client/src/lib/TLLocalSyncClient.ts
rename to packages/editor/src/lib/utils/sync/TLLocalSyncClient.ts
index 2218d2139..6bd44b6fc 100644
--- a/packages/tlsync-client/src/lib/TLLocalSyncClient.ts
+++ b/packages/editor/src/lib/utils/sync/TLLocalSyncClient.ts
@@ -1,4 +1,4 @@
-import { TLInstanceId, TLRecord, TLStore } from '@tldraw/editor'
+import { TLInstanceId, TLRecord, TLStore } from '@tldraw/tlschema'
import { RecordsDiff, SerializedSchema, compareSchemas, squashRecordDiffs } from '@tldraw/tlstore'
import { assert, hasOwnProperty } from '@tldraw/utils'
import { transact } from 'signia'
diff --git a/packages/tlsync-client/src/lib/alerts.ts b/packages/editor/src/lib/utils/sync/alerts.ts
similarity index 100%
rename from packages/tlsync-client/src/lib/alerts.ts
rename to packages/editor/src/lib/utils/sync/alerts.ts
diff --git a/packages/tlsync-client/src/lib/hardReset.ts b/packages/editor/src/lib/utils/sync/hardReset.ts
similarity index 100%
rename from packages/tlsync-client/src/lib/hardReset.ts
rename to packages/editor/src/lib/utils/sync/hardReset.ts
diff --git a/packages/tlsync-client/src/lib/indexedDb.ts b/packages/editor/src/lib/utils/sync/indexedDb.ts
similarity index 98%
rename from packages/tlsync-client/src/lib/indexedDb.ts
rename to packages/editor/src/lib/utils/sync/indexedDb.ts
index 6f2617a8c..4f1ead6a9 100644
--- a/packages/tlsync-client/src/lib/indexedDb.ts
+++ b/packages/editor/src/lib/utils/sync/indexedDb.ts
@@ -1,4 +1,4 @@
-import { TLRecord, TLStoreSchema } from '@tldraw/editor'
+import { TLRecord, TLStoreSchema } from '@tldraw/tlschema'
import { RecordsDiff, SerializedSchema, StoreSnapshot } from '@tldraw/tlstore'
import { IDBPDatabase, openDB } from 'idb'
import { STORE_PREFIX, addDbName, getAllIndexDbNames } from './persistence-constants'
diff --git a/packages/tlsync-client/src/lib/persistence-constants.ts b/packages/editor/src/lib/utils/sync/persistence-constants.ts
similarity index 95%
rename from packages/tlsync-client/src/lib/persistence-constants.ts
rename to packages/editor/src/lib/utils/sync/persistence-constants.ts
index ed6ddecbf..623cc719d 100644
--- a/packages/tlsync-client/src/lib/persistence-constants.ts
+++ b/packages/editor/src/lib/utils/sync/persistence-constants.ts
@@ -1,4 +1,5 @@
-import { InstanceRecordType, TLInstanceId, uniqueId } from '@tldraw/editor'
+import { InstanceRecordType, TLInstanceId } from '@tldraw/tlschema'
+import { uniqueId } from '../data'
const tabIdKey = 'TLDRAW_TAB_ID_v2' as const
diff --git a/packages/file-format/api-report.md b/packages/file-format/api-report.md
index ee755f685..3d0b607fe 100644
--- a/packages/file-format/api-report.md
+++ b/packages/file-format/api-report.md
@@ -8,7 +8,6 @@ import { App } from '@tldraw/editor';
import { MigrationFailureReason } from '@tldraw/tlstore';
import { Result } from '@tldraw/utils';
import { SerializedSchema } from '@tldraw/tlstore';
-import { TldrawEditorConfig } from '@tldraw/editor';
import { TLInstanceId } from '@tldraw/editor';
import { TLStore } from '@tldraw/editor';
import { TLTranslationKey } from '@tldraw/ui';
@@ -22,8 +21,8 @@ export function isV1File(data: any): boolean;
export function parseAndLoadDocument(app: App, document: string, msg: (id: TLTranslationKey) => string, addToast: ToastsContextType['addToast'], onV1FileLoad?: () => void, forceDarkMode?: boolean): Promise;
// @public (undocumented)
-export function parseTldrawJsonFile({ config, json, instanceId, }: {
- config: TldrawEditorConfig;
+export function parseTldrawJsonFile({ json, instanceId, store, }: {
+ store: TLStore;
json: string;
instanceId: TLInstanceId;
}): Result;
diff --git a/packages/file-format/src/lib/file.ts b/packages/file-format/src/lib/file.ts
index 225cfcf8f..f4f368abb 100644
--- a/packages/file-format/src/lib/file.ts
+++ b/packages/file-format/src/lib/file.ts
@@ -1,9 +1,9 @@
import {
App,
buildFromV1Document,
+ createTLStore,
fileToBase64,
TLAsset,
- TldrawEditorConfig,
TLInstanceId,
TLRecord,
TLStore,
@@ -81,11 +81,11 @@ export type TldrawFileParseError =
/** @public */
export function parseTldrawJsonFile({
- config,
json,
instanceId,
+ store,
}: {
- config: TldrawEditorConfig
+ store: TLStore
json: string
instanceId: TLInstanceId
}): Result {
@@ -123,7 +123,7 @@ export function parseTldrawJsonFile({
let migrationResult: MigrationResult>
try {
const storeSnapshot = Object.fromEntries(data.records.map((r) => [r.id, r as TLRecord]))
- migrationResult = config.storeSchema.migrateStoreSnapshot(storeSnapshot, data.schema)
+ migrationResult = store.schema.migrateStoreSnapshot(storeSnapshot, data.schema)
} catch (e) {
// junk data in the migration
return Result.err({ type: 'invalidRecords', cause: e })
@@ -137,7 +137,12 @@ export function parseTldrawJsonFile({
// we should be able to validate them. if any of the records at this stage
// are invalid, we don't open the file
try {
- return Result.ok(config.createStore({ initialData: migrationResult.value, instanceId }))
+ return Result.ok(
+ createTLStore({
+ initialData: migrationResult.value,
+ instanceId,
+ })
+ )
} catch (e) {
// junk data in the records (they're not validated yet!) could cause the
// migrations to crash. We treat any throw from a migration as an
@@ -205,7 +210,7 @@ export async function parseAndLoadDocument(
forceDarkMode?: boolean
) {
const parseFileResult = parseTldrawJsonFile({
- config: new TldrawEditorConfig(),
+ store: createTLStore(),
json: document,
instanceId: app.instanceId,
})
diff --git a/packages/file-format/src/test/file.test.ts b/packages/file-format/src/test/file.test.ts
index df065d9b7..87af3d03b 100644
--- a/packages/file-format/src/test/file.test.ts
+++ b/packages/file-format/src/test/file.test.ts
@@ -1,11 +1,11 @@
-import { createCustomShapeId, InstanceRecordType, TldrawEditorConfig } from '@tldraw/editor'
+import { createCustomShapeId, createTLStore, InstanceRecordType, TLStore } from '@tldraw/editor'
import { MigrationFailureReason, UnknownRecord } from '@tldraw/tlstore'
import { assert } from '@tldraw/utils'
import { parseTldrawJsonFile as _parseTldrawJsonFile, TldrawFile } from '../lib/file'
-const parseTldrawJsonFile = (config: TldrawEditorConfig, json: string) =>
+const parseTldrawJsonFile = (store: TLStore, json: string) =>
_parseTldrawJsonFile({
- config,
+ store,
json,
instanceId: InstanceRecordType.createCustomId('instance'),
})
@@ -16,26 +16,26 @@ function serialize(file: TldrawFile): string {
describe('parseTldrawJsonFile', () => {
it('returns an error if the file is not json', () => {
- const result = parseTldrawJsonFile(new TldrawEditorConfig(), 'not json')
+ const store = createTLStore()
+ const result = parseTldrawJsonFile(store, 'not json')
assert(!result.ok)
expect(result.error.type).toBe('notATldrawFile')
})
it("returns an error if the file doesn't look like a tldraw file", () => {
- const result = parseTldrawJsonFile(
- new TldrawEditorConfig(),
- JSON.stringify({ not: 'a tldraw file' })
- )
+ const store = createTLStore()
+ const result = parseTldrawJsonFile(store, JSON.stringify({ not: 'a tldraw file' }))
assert(!result.ok)
expect(result.error.type).toBe('notATldrawFile')
})
it('returns an error if the file version is too old', () => {
+ const store = createTLStore()
const result = parseTldrawJsonFile(
- new TldrawEditorConfig(),
+ store,
serialize({
tldrawFileFormatVersion: 0,
- schema: new TldrawEditorConfig().storeSchema.serialize(),
+ schema: store.schema.serialize(),
records: [],
})
)
@@ -44,11 +44,12 @@ describe('parseTldrawJsonFile', () => {
})
it('returns an error if the file version is too new', () => {
+ const store = createTLStore()
const result = parseTldrawJsonFile(
- new TldrawEditorConfig(),
+ store,
serialize({
tldrawFileFormatVersion: 100,
- schema: new TldrawEditorConfig().storeSchema.serialize(),
+ schema: store.schema.serialize(),
records: [],
})
)
@@ -57,10 +58,11 @@ describe('parseTldrawJsonFile', () => {
})
it('returns an error if migrations fail', () => {
- const serializedSchema = new TldrawEditorConfig().storeSchema.serialize()
+ const store = createTLStore()
+ const serializedSchema = store.schema.serialize()
serializedSchema.storeVersion = 100
const result = parseTldrawJsonFile(
- new TldrawEditorConfig(),
+ store,
serialize({
tldrawFileFormatVersion: 1,
schema: serializedSchema,
@@ -71,10 +73,11 @@ describe('parseTldrawJsonFile', () => {
assert(result.error.type === 'migrationFailed')
expect(result.error.reason).toBe(MigrationFailureReason.TargetVersionTooOld)
- const serializedSchema2 = new TldrawEditorConfig().storeSchema.serialize()
+ const store2 = createTLStore()
+ const serializedSchema2 = store2.schema.serialize()
serializedSchema2.recordVersions.shape.version = 100
const result2 = parseTldrawJsonFile(
- new TldrawEditorConfig(),
+ store2,
serialize({
tldrawFileFormatVersion: 1,
schema: serializedSchema2,
@@ -88,11 +91,12 @@ describe('parseTldrawJsonFile', () => {
})
it('returns an error if a record is invalid', () => {
+ const store = createTLStore()
const result = parseTldrawJsonFile(
- new TldrawEditorConfig(),
+ store,
serialize({
tldrawFileFormatVersion: 1,
- schema: new TldrawEditorConfig().storeSchema.serialize(),
+ schema: store.schema.serialize(),
records: [
{
typeName: 'shape',
@@ -103,19 +107,21 @@ describe('parseTldrawJsonFile', () => {
],
})
)
+
assert(!result.ok)
assert(result.error.type === 'invalidRecords')
expect(result.error.cause).toMatchInlineSnapshot(
- `[ValidationError: At shape(id = shape:shape, type = geo).rotation: Expected number, got undefined]`
+ `[ValidationError: At shape(id = shape:shape, type = geo).x: Expected number, got undefined]`
)
})
it('returns a store if the file is valid', () => {
+ const store = createTLStore()
const result = parseTldrawJsonFile(
- new TldrawEditorConfig(),
+ store,
serialize({
tldrawFileFormatVersion: 1,
- schema: new TldrawEditorConfig().storeSchema.serialize(),
+ schema: store.schema.serialize(),
records: [],
})
)
diff --git a/packages/tldraw/api-report.md b/packages/tldraw/api-report.md
index 90a6c20aa..73bdd9e74 100644
--- a/packages/tldraw/api-report.md
+++ b/packages/tldraw/api-report.md
@@ -5,18 +5,13 @@
```ts
import { TldrawEditorProps } from '@tldraw/editor';
-import { TldrawUiContextProviderProps } from '@tldraw/ui';
+import { TldrawUiProps } from '@tldraw/ui';
// @public (undocumented)
-export function Tldraw(props: Omit & TldrawUiContextProviderProps & {
- persistenceKey?: string;
- hideUi?: boolean;
- config?: TldrawEditorProps['config'];
-}): JSX.Element;
+export function Tldraw(props: TldrawEditorProps & TldrawUiProps): JSX.Element;
export * from "@tldraw/editor";
-export * from "@tldraw/tlsync-client";
export * from "@tldraw/ui";
// (No @packageDocumentation comment for this package)
diff --git a/packages/tldraw/package.json b/packages/tldraw/package.json
index cf928e3a9..cdd24f7a7 100644
--- a/packages/tldraw/package.json
+++ b/packages/tldraw/package.json
@@ -47,7 +47,6 @@
"dependencies": {
"@tldraw/editor": "workspace:*",
"@tldraw/polyfills": "workspace:*",
- "@tldraw/tlsync-client": "workspace:*",
"@tldraw/ui": "workspace:*"
},
"peerDependencies": {
diff --git a/packages/tldraw/src/index.ts b/packages/tldraw/src/index.ts
index 0f366bafb..999e55610 100644
--- a/packages/tldraw/src/index.ts
+++ b/packages/tldraw/src/index.ts
@@ -4,7 +4,5 @@ import '@tldraw/polyfills'
// eslint-disable-next-line local/no-export-star
export * from '@tldraw/editor'
// eslint-disable-next-line local/no-export-star
-export * from '@tldraw/tlsync-client'
-// eslint-disable-next-line local/no-export-star
export * from '@tldraw/ui'
export { Tldraw } from './lib/Tldraw'
diff --git a/packages/tldraw/src/lib/Tldraw.tsx b/packages/tldraw/src/lib/Tldraw.tsx
index ab4c327ff..0ca054a18 100644
--- a/packages/tldraw/src/lib/Tldraw.tsx
+++ b/packages/tldraw/src/lib/Tldraw.tsx
@@ -1,38 +1,12 @@
-import { Canvas, TldrawEditor, TldrawEditorConfig, TldrawEditorProps } from '@tldraw/editor'
-import { DEFAULT_DOCUMENT_NAME, TAB_ID, useLocalSyncClient } from '@tldraw/tlsync-client'
-import { ContextMenu, TldrawUi, TldrawUiContextProviderProps } from '@tldraw/ui'
-import { useMemo } from 'react'
+import { Canvas, TldrawEditor, TldrawEditorProps } from '@tldraw/editor'
+import { ContextMenu, TldrawUi, TldrawUiProps } from '@tldraw/ui'
/** @public */
-export function Tldraw(
- 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 {
- config,
- children,
- persistenceKey = DEFAULT_DOCUMENT_NAME,
- instanceId = TAB_ID,
- ...rest
- } = props
-
- const _config = useMemo(() => config ?? new TldrawEditorConfig(), [config])
-
- const syncedStore = useLocalSyncClient({
- instanceId,
- config: _config,
- universalPersistenceKey: persistenceKey,
- })
+export function Tldraw(props: TldrawEditorProps & TldrawUiProps) {
+ const { children, ...rest } = props
return (
-
+
diff --git a/packages/tldraw/tsconfig.json b/packages/tldraw/tsconfig.json
index 85a041e12..66bea2adf 100644
--- a/packages/tldraw/tsconfig.json
+++ b/packages/tldraw/tsconfig.json
@@ -8,5 +8,5 @@
"noImplicitReturns": false,
"rootDir": "src"
},
- "references": [{ "path": "../editor" }, { "path": "../tlsync-client" }, { "path": "../ui" }]
+ "references": [{ "path": "../editor" }, { "path": "../ui" }]
}
diff --git a/packages/tlschema/api-report.md b/packages/tlschema/api-report.md
index f9f682a01..522a943c7 100644
--- a/packages/tlschema/api-report.md
+++ b/packages/tlschema/api-report.md
@@ -118,8 +118,8 @@ export function createShapeValidator(
}>;
// @public
-export function createTLSchema(opts?: {
- customShapes?: { [K in T["type"]]: CustomShapeInfo; } | undefined;
+export function createTLSchema(opts?: {
+ customShapes: Record;
}): StoreSchema;
// @public (undocumented)
@@ -376,7 +376,7 @@ export const groupShapeTypeValidator: T.Validator;
export const handleTypeValidator: T.Validator;
// @public (undocumented)
-export const highlightShapeMigrations: Migrations;
+export const highlightShapeTypeMigrations: Migrations;
// @public (undocumented)
export const highlightShapeTypeValidator: T.Validator;
@@ -477,6 +477,14 @@ export const pointerTypeValidator: T.Validator;
// @internal (undocumented)
export const rootShapeTypeMigrations: Migrations;
+// @public (undocumented)
+export type SchemaShapeInfo = {
+ migrations?: Migrations;
+ validator?: {
+ validate: (record: any) => any;
+ };
+};
+
// @public (undocumented)
export const scribbleTypeValidator: T.Validator;
diff --git a/packages/tlschema/src/createTLSchema.ts b/packages/tlschema/src/createTLSchema.ts
index 36d660f74..c06a7b2e8 100644
--- a/packages/tlschema/src/createTLSchema.ts
+++ b/packages/tlschema/src/createTLSchema.ts
@@ -10,7 +10,7 @@ import { InstancePageStateRecordType } from './records/TLInstancePageState'
import { InstancePresenceRecordType } from './records/TLInstancePresence'
import { PageRecordType } from './records/TLPage'
import { PointerRecordType } from './records/TLPointer'
-import { TLShape, TLUnknownShape, rootShapeTypeMigrations } from './records/TLShape'
+import { TLShape, rootShapeTypeMigrations } from './records/TLShape'
import { UserDocumentRecordType } from './records/TLUserDocument'
import { storeMigrations } from './schema'
import { arrowShapeTypeMigrations, arrowShapeTypeValidator } from './shapes/TLArrowShape'
@@ -20,92 +20,122 @@ import { embedShapeTypeMigrations, embedShapeTypeValidator } from './shapes/TLEm
import { frameShapeTypeMigrations, frameShapeTypeValidator } from './shapes/TLFrameShape'
import { geoShapeTypeMigrations, geoShapeTypeValidator } from './shapes/TLGeoShape'
import { groupShapeTypeMigrations, groupShapeTypeValidator } from './shapes/TLGroupShape'
-import { highlightShapeMigrations, highlightShapeTypeValidator } from './shapes/TLHighlightShape'
+import {
+ highlightShapeTypeMigrations,
+ highlightShapeTypeValidator,
+} from './shapes/TLHighlightShape'
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
+/** @public */
+export type SchemaShapeInfo = {
+ migrations?: Migrations
+ validator?: { validate: (record: any) => any }
}
-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 },
- highlight: { migrations: highlightShapeMigrations, validator: highlightShapeTypeValidator },
- }
+const coreShapes: Record = {
+ group: {
+ migrations: groupShapeTypeMigrations,
+ validator: groupShapeTypeValidator,
+ },
+ bookmark: {
+ migrations: bookmarkShapeTypeMigrations,
+ validator: bookmarkShapeTypeValidator,
+ },
+ embed: {
+ migrations: embedShapeTypeMigrations,
+ validator: embedShapeTypeValidator,
+ },
+ image: {
+ migrations: imageShapeTypeMigrations,
+ validator: imageShapeTypeValidator,
+ },
+ text: {
+ migrations: textShapeTypeMigrations,
+ validator: textShapeTypeValidator,
+ },
+ video: {
+ migrations: videoShapeTypeMigrations,
+ validator: videoShapeTypeValidator,
+ },
+}
-type CustomShapeInfo = {
- validator?: { validate: (record: T) => T }
- migrations?: Migrations
+const defaultShapes: Record = {
+ arrow: {
+ migrations: arrowShapeTypeMigrations,
+ validator: arrowShapeTypeValidator,
+ },
+ draw: {
+ migrations: drawShapeTypeMigrations,
+ validator: drawShapeTypeValidator,
+ },
+ frame: {
+ migrations: frameShapeTypeMigrations,
+ validator: frameShapeTypeValidator,
+ },
+ geo: {
+ migrations: geoShapeTypeMigrations,
+ validator: geoShapeTypeValidator,
+ },
+ line: {
+ migrations: lineShapeTypeMigrations,
+ validator: lineShapeTypeValidator,
+ },
+ note: {
+ migrations: noteShapeTypeMigrations,
+ validator: noteShapeTypeValidator,
+ },
+ highlight: {
+ migrations: highlightShapeTypeMigrations,
+ validator: highlightShapeTypeValidator,
+ },
}
/**
- * Create a store schema for a tldraw store that includes all the default shapes together with any custom shapes.
- * @public */
-export function createTLSchema(
+ * Create a TLSchema with custom shapes. Custom shapes cannot override default shapes.
+ *
+ * @param opts - Options
+ *
+ * @public */
+export function createTLSchema(
opts = {} as {
- customShapes?: { [K in T['type']]: CustomShapeInfo }
+ customShapes: Record
}
) {
- const { customShapes = {} } = opts
+ const { customShapes } = 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 default 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({})])
- ),
+ for (const key in customShapes) {
+ if (key in coreShapes) {
+ throw Error(`Can't override default shape ${key}!`)
+ }
}
- 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 allShapeEntries = Object.entries({ ...coreShapes, ...defaultShapes, ...customShapes })
- const shapeRecord = createRecordType('shape', {
+ const ShapeRecordType = createRecordType('shape', {
migrations: defineMigrations({
currentVersion: rootShapeTypeMigrations.currentVersion,
firstVersion: rootShapeTypeMigrations.firstVersion,
migrators: rootShapeTypeMigrations.migrators,
subTypeKey: 'type',
- subTypeMigrations: shapeSubTypeMigrationsWithCustomSubTypeMigrations,
+ subTypeMigrations: {
+ ...Object.fromEntries(
+ allShapeEntries.map(([k, v]) => [k, v.migrations ?? defineMigrations({})])
+ ),
+ },
}),
- validator: validatorWithCustomShapeValidators,
scope: 'document',
+ validator: T.model(
+ 'shape',
+ T.union('type', {
+ ...Object.fromEntries(
+ allShapeEntries.map(([k, v]) => [k, (v.validator as T.Validator) ?? T.any])
+ ),
+ })
+ ),
}).withDefaultProperties(() => ({ x: 0, y: 0, rotation: 0, isLocked: false }))
return StoreSchema.create(
@@ -116,7 +146,7 @@ export function createTLSchema(
instance: InstanceRecordType,
instance_page_state: InstancePageStateRecordType,
page: PageRecordType,
- shape: shapeRecord,
+ shape: ShapeRecordType,
user_document: UserDocumentRecordType,
instance_presence: InstancePresenceRecordType,
pointer: PointerRecordType,
diff --git a/packages/tlschema/src/index.ts b/packages/tlschema/src/index.ts
index d9dedcf63..d9e013622 100644
--- a/packages/tlschema/src/index.ts
+++ b/packages/tlschema/src/index.ts
@@ -24,7 +24,7 @@ export {
} from './assets/TLVideoAsset'
export { createAssetValidator, type TLBaseAsset } from './assets/asset-validation'
export { createPresenceStateDerivation } from './createPresenceStateDerivation'
-export { createTLSchema } from './createTLSchema'
+export { createTLSchema, type SchemaShapeInfo } from './createTLSchema'
export { CLIENT_FIXUP_SCRIPT, fixupRecord } from './fixup'
export { type Box2dModel, type Vec2dModel } from './geometry-types'
export {
@@ -157,7 +157,7 @@ export {
type TLGroupShapeProps,
} from './shapes/TLGroupShape'
export {
- highlightShapeMigrations,
+ highlightShapeTypeMigrations,
highlightShapeTypeValidator,
type TLHighlightShape,
type TLHighlightShapeProps,
diff --git a/packages/tlschema/src/shapes/TLHighlightShape.ts b/packages/tlschema/src/shapes/TLHighlightShape.ts
index 737114656..62e56418d 100644
--- a/packages/tlschema/src/shapes/TLHighlightShape.ts
+++ b/packages/tlschema/src/shapes/TLHighlightShape.ts
@@ -18,7 +18,6 @@ export type TLHighlightShapeProps = {
/** @public */
export type TLHighlightShape = TLBaseShape<'highlight', TLHighlightShapeProps>
-// --- VALIDATION ---
/** @public */
export const highlightShapeTypeValidator: T.Validator = createShapeValidator(
'highlight',
@@ -32,6 +31,5 @@ export const highlightShapeTypeValidator: T.Validator = create
})
)
-// --- MIGRATIONS ---
/** @public */
-export const highlightShapeMigrations = defineMigrations({})
+export const highlightShapeTypeMigrations = defineMigrations({})
diff --git a/packages/tlsync-client/CHANGELOG.md b/packages/tlsync-client/CHANGELOG.md
deleted file mode 100644
index 6b92f8b33..000000000
--- a/packages/tlsync-client/CHANGELOG.md
+++ /dev/null
@@ -1,200 +0,0 @@
-# v2.0.0-alpha.12 (Mon Apr 03 2023)
-
-#### 🐛 Bug Fix
-
-- Make sure all types and build stuff get run in CI [#1548](https://github.com/tldraw/tldraw-lite/pull/1548) ([@SomeHats](https://github.com/SomeHats))
-- add pre-commit api report generation [#1517](https://github.com/tldraw/tldraw-lite/pull/1517) ([@SomeHats](https://github.com/SomeHats))
-- [chore] restore api extractor [#1500](https://github.com/tldraw/tldraw-lite/pull/1500) ([@steveruizok](https://github.com/steveruizok))
-- Remove initial data parameter as it is not being used. [#1480](https://github.com/tldraw/tldraw-lite/pull/1480) ([@MitjaBezensek](https://github.com/MitjaBezensek))
-- David/publish good [#1488](https://github.com/tldraw/tldraw-lite/pull/1488) ([@ds300](https://github.com/ds300))
-- [chore] alpha 10 [#1486](https://github.com/tldraw/tldraw-lite/pull/1486) ([@ds300](https://github.com/ds300))
-- [chore] package build improvements [#1484](https://github.com/tldraw/tldraw-lite/pull/1484) ([@ds300](https://github.com/ds300))
-- [chore] bump for alpha 8 [#1485](https://github.com/tldraw/tldraw-lite/pull/1485) ([@steveruizok](https://github.com/steveruizok))
-- stop using broken-af turbo for publishing [#1476](https://github.com/tldraw/tldraw-lite/pull/1476) ([@ds300](https://github.com/ds300))
-- [chore] add canary release script [#1423](https://github.com/tldraw/tldraw-lite/pull/1423) ([@ds300](https://github.com/ds300) [@steveruizok](https://github.com/steveruizok))
-- [chore] upgrade yarn [#1430](https://github.com/tldraw/tldraw-lite/pull/1430) ([@ds300](https://github.com/ds300))
-- [update] docs [#1448](https://github.com/tldraw/tldraw-lite/pull/1448) ([@steveruizok](https://github.com/steveruizok))
-- [fix] dev version number for tldraw/tldraw [#1434](https://github.com/tldraw/tldraw-lite/pull/1434) ([@steveruizok](https://github.com/steveruizok))
-- repo cleanup [#1426](https://github.com/tldraw/tldraw-lite/pull/1426) ([@steveruizok](https://github.com/steveruizok))
-- Vscode extension [#1253](https://github.com/tldraw/tldraw-lite/pull/1253) ([@steveruizok](https://github.com/steveruizok) [@MitjaBezensek](https://github.com/MitjaBezensek) [@orangemug](https://github.com/orangemug))
-- Run all the tests. Fix linting for tests. [#1389](https://github.com/tldraw/tldraw-lite/pull/1389) ([@MitjaBezensek](https://github.com/MitjaBezensek))
-- add beta-redirect app [#1415](https://github.com/tldraw/tldraw-lite/pull/1415) ([@SomeHats](https://github.com/SomeHats))
-
-#### Authors: 5
-
-- alex ([@SomeHats](https://github.com/SomeHats))
-- David Sheldrick ([@ds300](https://github.com/ds300))
-- Mitja Bezenšek ([@MitjaBezensek](https://github.com/MitjaBezensek))
-- Orange Mug ([@orangemug](https://github.com/orangemug))
-- Steve Ruiz ([@steveruizok](https://github.com/steveruizok))
-
----
-
-# @tldraw/tlsync-client
-
-## 2.0.0-alpha.11
-
-### Patch Changes
-
-- fix some package build scripting
-- Updated dependencies
- - @tldraw/editor@2.0.0-alpha.11
- - @tldraw/tlstore@2.0.0-alpha.11
-
-## 2.0.0-alpha.10
-
-### Patch Changes
-
-- 4b4399b6e: redeploy with yarn to prevent package version issues
-- Updated dependencies [4b4399b6e]
- - @tldraw/tlstore@2.0.0-alpha.10
- - @tldraw/editor@2.0.0-alpha.10
-
-## 2.0.0-alpha.9
-
-### Patch Changes
-
-- Release day!
-- Updated dependencies
- - @tldraw/editor@2.0.0-alpha.9
- - @tldraw/tlstore@2.0.0-alpha.9
-
-## 2.0.0-alpha.8
-
-### Patch Changes
-
-- 23dd81cfe: Make signia a peer dependency
-- Updated dependencies [23dd81cfe]
- - @tldraw/editor@2.0.0-alpha.8
- - @tldraw/tlstore@2.0.0-alpha.8
- - @tldraw/tlsync@2.0.0-alpha.8
-
-## 2.0.0-alpha.7
-
-### Patch Changes
-
-- Bug fixes.
-- Updated dependencies
- - @tldraw/editor@2.0.0-alpha.7
- - @tldraw/tlstore@2.0.0-alpha.7
- - @tldraw/tlsync@2.0.0-alpha.7
-
-## 2.0.0-alpha.6
-
-### Patch Changes
-
-- Add licenses.
-- Updated dependencies
- - @tldraw/editor@2.0.0-alpha.6
- - @tldraw/tlstore@2.0.0-alpha.6
- - @tldraw/tlsync@2.0.0-alpha.6
-
-## 2.0.0-alpha.5
-
-### Patch Changes
-
-- Add CSS files to tldraw/tldraw.
-- Updated dependencies
- - @tldraw/editor@2.0.0-alpha.5
- - @tldraw/tlstore@2.0.0-alpha.5
- - @tldraw/tlsync@2.0.0-alpha.5
-
-## 2.0.0-alpha.4
-
-### Patch Changes
-
-- Add children to tldraw/tldraw
-- Updated dependencies
- - @tldraw/editor@2.0.0-alpha.4
- - @tldraw/tlstore@2.0.0-alpha.4
- - @tldraw/tlsync@2.0.0-alpha.4
-
-## 2.0.0-alpha.3
-
-### Patch Changes
-
-- Change permissions.
-- Updated dependencies
- - @tldraw/editor@2.0.0-alpha.3
- - @tldraw/tlstore@2.0.0-alpha.3
- - @tldraw/tlsync@2.0.0-alpha.3
-
-## 2.0.0-alpha.2
-
-### Patch Changes
-
-- Add tldraw, editor
-- Updated dependencies
- - @tldraw/editor@2.0.0-alpha.2
- - @tldraw/tlstore@2.0.0-alpha.2
- - @tldraw/tlsync@2.0.0-alpha.2
-
-## 0.1.0-alpha.11
-
-### Patch Changes
-
-- Fix stale reactors.
-- Updated dependencies
- - @tldraw/tldraw-beta@0.1.0-alpha.11
- - @tldraw/tlstore@0.1.0-alpha.11
- - @tldraw/tlsync@0.1.0-alpha.11
-
-## 0.1.0-alpha.10
-
-### Patch Changes
-
-- Fix type export bug.
-- Updated dependencies
- - @tldraw/tldraw-beta@0.1.0-alpha.10
- - @tldraw/tlstore@0.1.0-alpha.10
- - @tldraw/tlsync@0.1.0-alpha.10
-
-## 0.1.0-alpha.9
-
-### Patch Changes
-
-- Fix import bugs.
-- Updated dependencies
- - @tldraw/tldraw-beta@0.1.0-alpha.9
- - @tldraw/tlstore@0.1.0-alpha.9
- - @tldraw/tlsync@0.1.0-alpha.9
-
-## 0.1.0-alpha.8
-
-### Patch Changes
-
-- Changes validation requirements, exports validation helpers.
-- Updated dependencies
- - @tldraw/tldraw-beta@0.1.0-alpha.8
- - @tldraw/tlstore@0.1.0-alpha.8
- - @tldraw/tlsync@0.1.0-alpha.8
-
-## 0.1.0-alpha.7
-
-### Patch Changes
-
-- - Pre-pre-release update
-- Updated dependencies
- - @tldraw/tldraw-beta@0.1.0-alpha.7
- - @tldraw/tlstore@0.1.0-alpha.7
- - @tldraw/tlsync@0.1.0-alpha.7
-
-## 0.0.2-alpha.1
-
-### Patch Changes
-
-- Fix error with HMR
-- Updated dependencies
- - @tldraw/tldraw-beta@0.0.2-alpha.1
- - @tldraw/tlstore@0.0.2-alpha.1
- - @tldraw/tlsync@0.0.2-alpha.1
-
-## 0.0.2-alpha.0
-
-### Patch Changes
-
-- Initial release
-- Updated dependencies
- - @tldraw/tldraw-beta@0.0.2-alpha.0
- - @tldraw/tlstore@0.0.2-alpha.0
- - @tldraw/tlsync@0.0.2-alpha.0
diff --git a/packages/tlsync-client/LICENSE b/packages/tlsync-client/LICENSE
deleted file mode 100644
index 4f227c380..000000000
--- a/packages/tlsync-client/LICENSE
+++ /dev/null
@@ -1,190 +0,0 @@
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
-TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
-2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
-3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
-4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
-5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
-6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
-7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
-8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
-9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
-END OF TERMS AND CONDITIONS
-
-Copyright 2023 tldraw GB Ltd.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
diff --git a/packages/tlsync-client/README.md b/packages/tlsync-client/README.md
deleted file mode 100644
index bb4f6cb2e..000000000
--- a/packages/tlsync-client/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# @tldraw/tlsync-client
-
-## License
-
-The source code in this repository (as well as our 2.0+ distributions and releases) are currently licensed under Apache-2.0. These licenses are subject to change in our upcoming 2.0 release. If you are planning to use tldraw in a commercial product, please reach out at [hello@tldraw.com](mailto://hello@tldraw.com).
diff --git a/packages/tlsync-client/api-extractor.json b/packages/tlsync-client/api-extractor.json
deleted file mode 100644
index f1ed80e93..000000000
--- a/packages/tlsync-client/api-extractor.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
- "extends": "../../config/api-extractor.json"
-}
diff --git a/packages/tlsync-client/api-report.md b/packages/tlsync-client/api-report.md
deleted file mode 100644
index 36da2194c..000000000
--- a/packages/tlsync-client/api-report.md
+++ /dev/null
@@ -1,34 +0,0 @@
-## API Report File for "@tldraw/tlsync-client"
-
-> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
-
-```ts
-
-import { SyncedStore } from '@tldraw/editor';
-import { TldrawEditorConfig } from '@tldraw/editor';
-import { TLInstanceId } from '@tldraw/editor';
-
-// @public (undocumented)
-export const DEFAULT_DOCUMENT_NAME: any;
-
-// @public
-export function hardReset({ shouldReload }?: {
- shouldReload?: boolean | undefined;
-}): Promise;
-
-// @public (undocumented)
-export const STORE_PREFIX = "TLDRAW_DOCUMENT_v2";
-
-// @public (undocumented)
-export const TAB_ID: TLInstanceId;
-
-// @public
-export function useLocalSyncClient({ universalPersistenceKey, instanceId, config, }: {
- universalPersistenceKey: string;
- instanceId: TLInstanceId;
- config: TldrawEditorConfig;
-}): SyncedStore;
-
-// (No @packageDocumentation comment for this package)
-
-```
diff --git a/packages/tlsync-client/package.json b/packages/tlsync-client/package.json
deleted file mode 100644
index 579a6ec55..000000000
--- a/packages/tlsync-client/package.json
+++ /dev/null
@@ -1,75 +0,0 @@
-{
- "name": "@tldraw/tlsync-client",
- "description": "A tiny little drawing app (multiplayer sync).",
- "version": "2.0.0-alpha.12",
- "packageManager": "yarn@3.5.0",
- "author": {
- "name": "tldraw GB Ltd.",
- "email": "hello@tldraw.com"
- },
- "homepage": "https://tldraw.dev",
- "license": "Apache-2.0",
- "repository": {
- "type": "git",
- "url": "https://github.com/tldraw/tldraw"
- },
- "bugs": {
- "url": "https://github.com/tldraw/tldraw/issues"
- },
- "keywords": [
- "tldraw",
- "drawing",
- "app",
- "development",
- "whiteboard",
- "canvas",
- "infinite"
- ],
- "/* NOTE */": "These `main` and `types` fields are rewritten by the build script. They are not the actual values we publish",
- "main": "./src/index.ts",
- "types": "./.tsbuild/index.d.ts",
- "/* GOTCHA */": "files will include ./dist and index.d.ts by default, add any others you want to include in here",
- "files": [],
- "scripts": {
- "test": "lazy inherit",
- "test-coverage": "lazy inherit",
- "build": "yarn run -T tsx ../../scripts/build-package.ts",
- "build-api": "yarn run -T tsx ../../scripts/build-api.ts",
- "prepack": "yarn run -T tsx ../../scripts/prepack.ts",
- "postpack": "../../scripts/postpack.sh",
- "pack-tarball": "yarn pack",
- "lint": "yarn run -T tsx ../../scripts/lint.ts"
- },
- "devDependencies": {
- "@types/react": "*",
- "@types/react-dom": "*",
- "lazyrepo": "0.0.0-alpha.26",
- "ws": "^8.10.0"
- },
- "optionalDependencies": {
- "react": "*"
- },
- "jest": {
- "preset": "config/jest/node",
- "testEnvironment": "jsdom",
- "setupFiles": [
- "./setupJest.js"
- ],
- "moduleNameMapper": {
- "^~(.*)": "/src/$1"
- },
- "transformIgnorePatterns": [
- "node_modules/(?!(nanoid|escape-string-regexp)/)"
- ]
- },
- "peerDependencies": {
- "signia": "*",
- "signia-react": "*"
- },
- "dependencies": {
- "@tldraw/editor": "workspace:*",
- "@tldraw/tlstore": "workspace:*",
- "@tldraw/utils": "workspace:*",
- "idb": "^7.1.0"
- }
-}
diff --git a/packages/tlsync-client/setupJest.js b/packages/tlsync-client/setupJest.js
deleted file mode 100644
index 83a93db34..000000000
--- a/packages/tlsync-client/setupJest.js
+++ /dev/null
@@ -1,10 +0,0 @@
-window.crypto = {
- // required by nanoid
- // if we need more of the crypto apis, just add a proper mock here
- getRandomValues: function (array) {
- for (var i = 0; i < array.length; i++) {
- array[i] = Math.floor(Math.random() * 256)
- }
- return array
- },
-}
diff --git a/packages/tlsync-client/src/index.ts b/packages/tlsync-client/src/index.ts
deleted file mode 100644
index c414107c9..000000000
--- a/packages/tlsync-client/src/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export { hardReset } from './lib/hardReset'
-export { useLocalSyncClient } from './lib/hooks/useLocalSyncClient'
-export { DEFAULT_DOCUMENT_NAME, STORE_PREFIX, TAB_ID } from './lib/persistence-constants'
diff --git a/packages/tlsync-client/src/lib/hooks/useLocalSyncClient.ts b/packages/tlsync-client/src/lib/hooks/useLocalSyncClient.ts
deleted file mode 100644
index c5f094fe0..000000000
--- a/packages/tlsync-client/src/lib/hooks/useLocalSyncClient.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import { SyncedStore, TldrawEditorConfig, TLInstanceId, uniqueId } from '@tldraw/editor'
-import { useEffect, useState } from 'react'
-import '../hardReset'
-import { TLLocalSyncClient } from '../TLLocalSyncClient'
-
-/**
- * Use a client that persists to indexedDB and syncs to other stores with the same instance id, e.g. other tabs running the same instance of tldraw.
- *
- * @public
- */
-export function useLocalSyncClient({
- universalPersistenceKey,
- instanceId,
- config,
-}: {
- universalPersistenceKey: string
- instanceId: TLInstanceId
- config: TldrawEditorConfig
-}): SyncedStore {
- const [state, setState] = useState<{ id: string; syncedStore: SyncedStore } | null>(null)
-
- useEffect(() => {
- const id = uniqueId()
- setState({
- id,
- syncedStore: { status: 'loading' },
- })
- const setSyncedStore = (syncedStore: SyncedStore) => {
- setState((prev) => {
- if (prev?.id === id) {
- return { id, syncedStore }
- }
- return prev
- })
- }
-
- const store = config.createStore({ instanceId })
-
- const client = new TLLocalSyncClient(store, {
- universalPersistenceKey,
- onLoad() {
- setSyncedStore({ status: 'synced', store })
- },
- onLoadError(err) {
- setSyncedStore({ status: 'error', error: err })
- },
- })
-
- return () => {
- setState((prevState) => (prevState?.id === id ? null : prevState))
- client.close()
- }
- }, [instanceId, universalPersistenceKey, config])
-
- return state?.syncedStore ?? { status: 'loading' }
-}
diff --git a/packages/tlsync-client/tsconfig.json b/packages/tlsync-client/tsconfig.json
deleted file mode 100644
index 3604fadf0..000000000
--- a/packages/tlsync-client/tsconfig.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "extends": "../../config/tsconfig.base.json",
- "include": ["src", "start.ts"],
- "exclude": ["node_modules", "dist", "docs", ".tsbuild*"],
- "compilerOptions": {
- "outDir": "./.tsbuild",
- "rootDir": "src"
- },
- "references": [{ "path": "../tlstore" }, { "path": "../editor" }]
-}
diff --git a/packages/ui/api-report.md b/packages/ui/api-report.md
index 098d2bada..a436a9bd7 100644
--- a/packages/ui/api-report.md
+++ b/packages/ui/api-report.md
@@ -621,10 +621,10 @@ export interface TLDialog {
// @public (undocumented)
export const TldrawUi: React_2.NamedExoticComponent<{
- shareZone?: ReactNode;
- renderDebugMenuItems?: (() => React_2.ReactNode) | undefined;
children?: ReactNode;
hideUi?: boolean | undefined;
+ shareZone?: ReactNode;
+ renderDebugMenuItems?: (() => React_2.ReactNode) | undefined;
} & TldrawUiContextProviderProps>;
// @public (undocumented)
@@ -667,6 +667,14 @@ export interface TldrawUiOverrides {
translations?: TranslationProviderProps['overrides'];
}
+// @public (undocumented)
+export type TldrawUiProps = {
+ children?: ReactNode;
+ hideUi?: boolean;
+ shareZone?: ReactNode;
+ renderDebugMenuItems?: () => React_2.ReactNode;
+} & TldrawUiContextProviderProps;
+
// @public (undocumented)
export type TLListedTranslation = {
readonly locale: string;
diff --git a/packages/ui/package.json b/packages/ui/package.json
index efb3110d2..7bb92608f 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -55,7 +55,6 @@
"@tldraw/editor": "workspace:*",
"@tldraw/primitives": "workspace:*",
"@tldraw/tlschema": "workspace:*",
- "@tldraw/tlsync-client": "workspace:*",
"@tldraw/utils": "workspace:*",
"browser-fs-access": "^0.31.0",
"classnames": "^2.3.2",
diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
index e8319f8fc..71b4c6ea4 100644
--- a/packages/ui/src/index.ts
+++ b/packages/ui/src/index.ts
@@ -1,7 +1,7 @@
import * as Dialog from './lib/components/primitives/Dialog'
import * as DropdownMenu from './lib/components/primitives/DropdownMenu'
-export { TldrawUi, TldrawUiContent } from './lib/TldrawUi'
+export { TldrawUi, TldrawUiContent, type TldrawUiProps } from './lib/TldrawUi'
export {
TldrawUiContextProvider,
type TldrawUiContextProviderProps,
diff --git a/packages/ui/src/lib/TldrawUi.tsx b/packages/ui/src/lib/TldrawUi.tsx
index c0f453ce8..ae4b0500d 100644
--- a/packages/ui/src/lib/TldrawUi.tsx
+++ b/packages/ui/src/lib/TldrawUi.tsx
@@ -24,6 +24,17 @@ import { useNativeClipboardEvents } from './hooks/useClipboardEvents'
import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts'
import { useTranslation } from './hooks/useTranslation/useTranslation'
+/** @public */
+export type TldrawUiProps = {
+ children?: ReactNode
+ /** Whether to hide the interface and only display the canvas. */
+ hideUi?: boolean
+ /** A component to use for the share zone (will be deprecated) */
+ shareZone?: ReactNode
+ /** Additional items to add to the debug menu (will be deprecated)*/
+ renderDebugMenuItems?: () => React.ReactNode
+} & TldrawUiContextProviderProps
+
/**
* @public
*/
@@ -33,13 +44,7 @@ export const TldrawUi = React.memo(function TldrawUi({
children,
hideUi,
...rest
-}: {
- shareZone?: ReactNode
- renderDebugMenuItems?: () => React.ReactNode
- children?: ReactNode
- /** Whether to hide the interface and only display the canvas. */
- hideUi?: boolean
-} & TldrawUiContextProviderProps) {
+}: TldrawUiProps) {
return (
Loading assets...
- // }
-
// The hideUi prop should prevent the UI from mounting.
// If we ever need want the UI to mount and preserve state, then
// we should change this behavior and hide the UI via CSS instead.
diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json
index f5a963eef..587dc0a83 100644
--- a/packages/ui/tsconfig.json
+++ b/packages/ui/tsconfig.json
@@ -8,10 +8,5 @@
"noImplicitReturns": false,
"rootDir": "src"
},
- "references": [
- { "path": "../editor" },
- { "path": "../primitives" },
- { "path": "../tlsync-client" },
- { "path": "../utils" }
- ]
+ "references": [{ "path": "../editor" }, { "path": "../primitives" }, { "path": "../utils" }]
}
diff --git a/public-yarn.lock b/public-yarn.lock
index c93046e70..9a4919692 100644
--- a/public-yarn.lock
+++ b/public-yarn.lock
@@ -4297,7 +4297,6 @@ __metadata:
"@tldraw/tldraw": "workspace:*"
"@tldraw/tlschema": "workspace:*"
"@tldraw/tlstore": "workspace:*"
- "@tldraw/tlsync-client": "workspace:*"
"@tldraw/tlvalidate": "workspace:*"
"@tldraw/ui": "workspace:*"
"@tldraw/utils": "workspace:*"
@@ -4351,8 +4350,9 @@ __metadata:
escape-string-regexp: ^5.0.0
eventemitter3: ^4.0.7
fake-indexeddb: ^4.0.0
+ idb: ^7.1.1
is-plain-object: ^5.0.0
- jest-canvas-mock: ^2.4.0
+ jest-canvas-mock: ^2.5.1
jest-environment-jsdom: ^29.4.3
lazyrepo: 0.0.0-alpha.26
lodash.throttle: ^4.1.1
@@ -4477,7 +4477,6 @@ __metadata:
"@testing-library/react": ^14.0.0
"@tldraw/editor": "workspace:*"
"@tldraw/polyfills": "workspace:*"
- "@tldraw/tlsync-client": "workspace:*"
"@tldraw/ui": "workspace:*"
chokidar-cli: ^3.0.0
jest-canvas-mock: ^2.4.0
@@ -4521,28 +4520,6 @@ __metadata:
languageName: unknown
linkType: soft
-"@tldraw/tlsync-client@workspace:*, @tldraw/tlsync-client@workspace:packages/tlsync-client":
- version: 0.0.0-use.local
- resolution: "@tldraw/tlsync-client@workspace:packages/tlsync-client"
- dependencies:
- "@tldraw/editor": "workspace:*"
- "@tldraw/tlstore": "workspace:*"
- "@tldraw/utils": "workspace:*"
- "@types/react": "*"
- "@types/react-dom": "*"
- idb: ^7.1.0
- lazyrepo: 0.0.0-alpha.26
- react: "*"
- ws: ^8.10.0
- peerDependencies:
- signia: "*"
- signia-react: "*"
- dependenciesMeta:
- react:
- optional: true
- languageName: unknown
- linkType: soft
-
"@tldraw/tlvalidate@workspace:*, @tldraw/tlvalidate@workspace:packages/tlvalidate":
version: 0.0.0-use.local
resolution: "@tldraw/tlvalidate@workspace:packages/tlvalidate"
@@ -4570,7 +4547,6 @@ __metadata:
"@tldraw/editor": "workspace:*"
"@tldraw/primitives": "workspace:*"
"@tldraw/tlschema": "workspace:*"
- "@tldraw/tlsync-client": "workspace:*"
"@tldraw/utils": "workspace:*"
"@types/lz-string": ^1.3.34
browser-fs-access: ^0.31.0
@@ -4606,7 +4582,6 @@ __metadata:
"@tldraw/editor": "workspace:*"
"@tldraw/file-format": "workspace:*"
"@tldraw/tldraw": "workspace:*"
- "@tldraw/tlsync-client": "workspace:*"
"@tldraw/ui": "workspace:*"
"@tldraw/utils": "workspace:*"
"@types/fs-extra": ^11.0.1
@@ -5149,15 +5124,6 @@ __metadata:
languageName: node
linkType: hard
-"@types/react-dom@npm:*, @types/react-dom@npm:^18.0.0, @types/react-dom@npm:^18.0.6":
- version: 18.0.11
- resolution: "@types/react-dom@npm:18.0.11"
- dependencies:
- "@types/react": "*"
- checksum: 579691e4d5ec09688087568037c35edf8cfb1ab3e07f6c60029280733ee7b5c06d66df6fcc90786702c93ac8cb13bc7ff16c79ddfc75d082938fbaa36e1cdbf4
- languageName: node
- linkType: hard
-
"@types/react-dom@npm:<18.0.0":
version: 17.0.19
resolution: "@types/react-dom@npm:17.0.19"
@@ -5167,6 +5133,15 @@ __metadata:
languageName: node
linkType: hard
+"@types/react-dom@npm:^18.0.0, @types/react-dom@npm:^18.0.6":
+ version: 18.0.11
+ resolution: "@types/react-dom@npm:18.0.11"
+ dependencies:
+ "@types/react": "*"
+ checksum: 579691e4d5ec09688087568037c35edf8cfb1ab3e07f6c60029280733ee7b5c06d66df6fcc90786702c93ac8cb13bc7ff16c79ddfc75d082938fbaa36e1cdbf4
+ languageName: node
+ linkType: hard
+
"@types/react-router-dom@npm:^5.1.8":
version: 5.3.3
resolution: "@types/react-router-dom@npm:5.3.3"
@@ -10588,7 +10563,7 @@ __metadata:
languageName: node
linkType: hard
-"idb@npm:^7.1.0, idb@npm:^7.1.1":
+"idb@npm:^7.1.1":
version: 7.1.1
resolution: "idb@npm:7.1.1"
checksum: 1973c28d53c784b177bdef9f527ec89ec239ec7cf5fcbd987dae75a16c03f5b7dfcc8c6d3285716fd0309dd57739805390bd9f98ce23b1b7d8849a3b52de8d56
@@ -11316,6 +11291,16 @@ __metadata:
languageName: node
linkType: hard
+"jest-canvas-mock@npm:^2.5.1":
+ version: 2.5.1
+ resolution: "jest-canvas-mock@npm:2.5.1"
+ dependencies:
+ cssfontparser: ^1.2.1
+ moo-color: ^1.0.2
+ checksum: b8ff56c1b7b7feb6d33b7914dbfac21f19a5a33db0bc092f0426e500e80e67df1286bf817eb780e378b648c9130d7b8ca20cd46e45520657996273a948a7c198
+ languageName: node
+ linkType: hard
+
"jest-changed-files@npm:^28.1.3":
version: 28.1.3
resolution: "jest-changed-files@npm:28.1.3"
@@ -15392,7 +15377,7 @@ __metadata:
languageName: node
linkType: hard
-"react@npm:*, react@npm:18.2.0, react@npm:^18.2.0":
+"react@npm:18.2.0, react@npm:^18.2.0":
version: 18.2.0
resolution: "react@npm:18.2.0"
dependencies:
@@ -18357,9 +18342,9 @@ __metadata:
linkType: hard
"which-module@npm:^2.0.0":
- version: 2.0.1
- resolution: "which-module@npm:2.0.1"
- checksum: 1967b7ce17a2485544a4fdd9063599f0f773959cca24176dbe8f405e55472d748b7c549cd7920ff6abb8f1ab7db0b0f1b36de1a21c57a8ff741f4f1e792c52be
+ version: 2.0.0
+ resolution: "which-module@npm:2.0.0"
+ checksum: 809f7fd3dfcb2cdbe0180b60d68100c88785084f8f9492b0998c051d7a8efe56784492609d3f09ac161635b78ea29219eb1418a98c15ce87d085bce905705c9c
languageName: node
linkType: hard
@@ -18476,7 +18461,7 @@ __metadata:
languageName: node
linkType: hard
-"ws@npm:^8.10.0, ws@npm:^8.11.0, ws@npm:^8.2.3":
+"ws@npm:^8.11.0, ws@npm:^8.2.3":
version: 8.13.0
resolution: "ws@npm:8.13.0"
peerDependencies: