@tldraw/editor 3.8.0-canary.c45a05a15c4a → 3.8.0-canary.c5d6281c3fdd

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist-cjs/index.d.ts +167 -12
  2. package/dist-cjs/index.js +7 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +2 -5
  5. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  6. package/dist-cjs/lib/config/createTLStore.js +4 -2
  7. package/dist-cjs/lib/config/createTLStore.js.map +2 -2
  8. package/dist-cjs/lib/editor/Editor.js +95 -21
  9. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  10. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  11. package/dist-cjs/lib/editor/shapes/shared/resizeScaled.js +66 -0
  12. package/dist-cjs/lib/editor/shapes/shared/resizeScaled.js.map +7 -0
  13. package/dist-cjs/lib/editor/types/SvgExportContext.js.map +2 -2
  14. package/dist-cjs/lib/editor/types/misc-types.js.map +1 -1
  15. package/dist-cjs/lib/exports/exportToSvg.js.map +2 -2
  16. package/dist-cjs/lib/exports/getSvgAsImage.js +83 -0
  17. package/dist-cjs/lib/exports/getSvgAsImage.js.map +7 -0
  18. package/dist-cjs/lib/exports/getSvgJsx.js +16 -3
  19. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  20. package/dist-cjs/lib/hooks/useLocalStore.js +1 -1
  21. package/dist-cjs/lib/hooks/useLocalStore.js.map +2 -2
  22. package/dist-cjs/lib/options.js +2 -1
  23. package/dist-cjs/lib/options.js.map +2 -2
  24. package/dist-cjs/lib/utils/browserCanvasMaxSize.js +75 -0
  25. package/dist-cjs/lib/utils/browserCanvasMaxSize.js.map +7 -0
  26. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +3 -1
  27. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +2 -2
  28. package/dist-cjs/version.js +3 -3
  29. package/dist-cjs/version.js.map +1 -1
  30. package/dist-esm/index.d.mts +167 -12
  31. package/dist-esm/index.mjs +7 -1
  32. package/dist-esm/index.mjs.map +2 -2
  33. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +2 -5
  34. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  35. package/dist-esm/lib/config/createTLStore.mjs +4 -2
  36. package/dist-esm/lib/config/createTLStore.mjs.map +2 -2
  37. package/dist-esm/lib/editor/Editor.mjs +95 -21
  38. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  39. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  40. package/dist-esm/lib/editor/shapes/shared/resizeScaled.mjs +46 -0
  41. package/dist-esm/lib/editor/shapes/shared/resizeScaled.mjs.map +7 -0
  42. package/dist-esm/lib/editor/types/SvgExportContext.mjs.map +2 -2
  43. package/dist-esm/lib/exports/exportToSvg.mjs.map +2 -2
  44. package/dist-esm/lib/exports/getSvgAsImage.mjs +63 -0
  45. package/dist-esm/lib/exports/getSvgAsImage.mjs.map +7 -0
  46. package/dist-esm/lib/exports/getSvgJsx.mjs +16 -3
  47. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  48. package/dist-esm/lib/hooks/useLocalStore.mjs +1 -1
  49. package/dist-esm/lib/hooks/useLocalStore.mjs.map +2 -2
  50. package/dist-esm/lib/options.mjs +2 -1
  51. package/dist-esm/lib/options.mjs.map +2 -2
  52. package/dist-esm/lib/utils/browserCanvasMaxSize.mjs +45 -0
  53. package/dist-esm/lib/utils/browserCanvasMaxSize.mjs.map +7 -0
  54. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +3 -1
  55. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
  56. package/dist-esm/version.mjs +3 -3
  57. package/dist-esm/version.mjs.map +1 -1
  58. package/editor.css +2 -1
  59. package/package.json +9 -7
  60. package/src/index.ts +6 -0
  61. package/src/lib/components/default-components/DefaultCanvas.tsx +2 -5
  62. package/src/lib/config/createTLStore.ts +4 -2
  63. package/src/lib/editor/Editor.ts +129 -39
  64. package/src/lib/editor/shapes/ShapeUtil.ts +30 -1
  65. package/src/lib/editor/shapes/shared/resizeScaled.ts +61 -0
  66. package/src/lib/editor/types/SvgExportContext.tsx +21 -0
  67. package/src/lib/editor/types/misc-types.ts +55 -2
  68. package/src/lib/exports/exportToSvg.tsx +2 -2
  69. package/src/lib/exports/getSvgAsImage.ts +92 -0
  70. package/src/lib/exports/getSvgJsx.tsx +17 -2
  71. package/src/lib/hooks/useLocalStore.ts +1 -1
  72. package/src/lib/options.ts +6 -0
  73. package/src/lib/utils/browserCanvasMaxSize.ts +65 -0
  74. package/src/lib/utils/sync/TLLocalSyncClient.ts +3 -1
  75. package/src/version.ts +3 -3
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/options.ts"],
4
- "sourcesContent": ["import { ComponentType, Fragment } from 'react'\n\n/**\n * Options for configuring tldraw. For defaults, see {@link defaultTldrawOptions}.\n *\n * @example\n * ```tsx\n * const options: Partial<TldrawOptions> = {\n * maxPages: 3,\n * maxShapesPerPage: 1000,\n * }\n *\n * function MyTldrawComponent() {\n * return <Tldraw options={options} />\n * }\n * ```\n *\n * @public\n */\nexport interface TldrawOptions {\n\treadonly maxShapesPerPage: number\n\treadonly maxFilesAtOnce: number\n\treadonly maxPages: number\n\treadonly animationMediumMs: number\n\treadonly followChaseViewportSnap: number\n\treadonly doubleClickDurationMs: number\n\treadonly multiClickDurationMs: number\n\treadonly coarseDragDistanceSquared: number\n\treadonly dragDistanceSquared: number\n\treadonly defaultSvgPadding: number\n\treadonly cameraSlideFriction: number\n\treadonly maxPointsPerDrawShape: number\n\treadonly gridSteps: readonly {\n\t\treadonly min: number\n\t\treadonly mid: number\n\t\treadonly step: number\n\t}[]\n\treadonly collaboratorInactiveTimeoutMs: number\n\treadonly collaboratorIdleTimeoutMs: number\n\treadonly collaboratorCheckIntervalMs: number\n\treadonly cameraMovingTimeoutMs: number\n\treadonly hitTestMargin: number\n\treadonly edgeScrollDelay: number\n\treadonly edgeScrollEaseDuration: number\n\treadonly edgeScrollSpeed: number\n\treadonly edgeScrollDistance: number\n\treadonly coarsePointerWidth: number\n\treadonly coarseHandleRadius: number\n\treadonly handleRadius: number\n\treadonly longPressDurationMs: number\n\treadonly textShadowLod: number\n\treadonly adjacentShapeMargin: number\n\treadonly flattenImageBoundsExpand: number\n\treadonly flattenImageBoundsPadding: number\n\treadonly laserDelayMs: number\n\treadonly maxExportDelayMs: number\n\t/**\n\t * How long should previews created by {@link Editor.createTemporaryAssetPreview} last before\n\t * they expire? Defaults to 3 minutes.\n\t */\n\treadonly temporaryAssetPreviewLifetimeMs: number\n\treadonly actionShortcutsLocation: 'menu' | 'toolbar' | 'swap'\n\treadonly createTextOnCanvasDoubleClick: boolean\n\t/**\n\t * The react provider to use when exporting an image. This is useful if your shapes depend on\n\t * external context providers. By default, this is `React.Fragment`.\n\t */\n\treadonly exportProvider: ComponentType<{ children: React.ReactNode }>\n}\n\n/** @public */\nexport const defaultTldrawOptions = {\n\tmaxShapesPerPage: 4000,\n\tmaxFilesAtOnce: 100,\n\tmaxPages: 40,\n\tanimationMediumMs: 320,\n\tfollowChaseViewportSnap: 2,\n\tdoubleClickDurationMs: 450,\n\tmultiClickDurationMs: 200,\n\tcoarseDragDistanceSquared: 36, // 6 squared\n\tdragDistanceSquared: 16, // 4 squared\n\tdefaultSvgPadding: 32,\n\tcameraSlideFriction: 0.09,\n\tmaxPointsPerDrawShape: 500,\n\tgridSteps: [\n\t\t{ min: -1, mid: 0.15, step: 64 },\n\t\t{ min: 0.05, mid: 0.375, step: 16 },\n\t\t{ min: 0.15, mid: 1, step: 4 },\n\t\t{ min: 0.7, mid: 2.5, step: 1 },\n\t],\n\tcollaboratorInactiveTimeoutMs: 60000,\n\tcollaboratorIdleTimeoutMs: 3000,\n\tcollaboratorCheckIntervalMs: 1200,\n\tcameraMovingTimeoutMs: 64,\n\thitTestMargin: 8,\n\tedgeScrollDelay: 200,\n\tedgeScrollEaseDuration: 200,\n\tedgeScrollSpeed: 25,\n\tedgeScrollDistance: 8,\n\tcoarsePointerWidth: 12,\n\tcoarseHandleRadius: 20,\n\thandleRadius: 12,\n\tlongPressDurationMs: 500,\n\ttextShadowLod: 0.35,\n\tadjacentShapeMargin: 10,\n\tflattenImageBoundsExpand: 64,\n\tflattenImageBoundsPadding: 16,\n\tlaserDelayMs: 1200,\n\tmaxExportDelayMs: 5000,\n\ttemporaryAssetPreviewLifetimeMs: 180000,\n\tactionShortcutsLocation: 'swap',\n\tcreateTextOnCanvasDoubleClick: true,\n\texportProvider: Fragment,\n} as const satisfies TldrawOptions\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAwC;AAuEjC,MAAM,uBAAuB;AAAA,EACnC,kBAAkB;AAAA,EAClB,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,mBAAmB;AAAA,EACnB,yBAAyB;AAAA,EACzB,uBAAuB;AAAA,EACvB,sBAAsB;AAAA,EACtB,2BAA2B;AAAA;AAAA,EAC3B,qBAAqB;AAAA;AAAA,EACrB,mBAAmB;AAAA,EACnB,qBAAqB;AAAA,EACrB,uBAAuB;AAAA,EACvB,WAAW;AAAA,IACV,EAAE,KAAK,IAAI,KAAK,MAAM,MAAM,GAAG;AAAA,IAC/B,EAAE,KAAK,MAAM,KAAK,OAAO,MAAM,GAAG;AAAA,IAClC,EAAE,KAAK,MAAM,KAAK,GAAG,MAAM,EAAE;AAAA,IAC7B,EAAE,KAAK,KAAK,KAAK,KAAK,MAAM,EAAE;AAAA,EAC/B;AAAA,EACA,+BAA+B;AAAA,EAC/B,2BAA2B;AAAA,EAC3B,6BAA6B;AAAA,EAC7B,uBAAuB;AAAA,EACvB,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,wBAAwB;AAAA,EACxB,iBAAiB;AAAA,EACjB,oBAAoB;AAAA,EACpB,oBAAoB;AAAA,EACpB,oBAAoB;AAAA,EACpB,cAAc;AAAA,EACd,qBAAqB;AAAA,EACrB,eAAe;AAAA,EACf,qBAAqB;AAAA,EACrB,0BAA0B;AAAA,EAC1B,2BAA2B;AAAA,EAC3B,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,iCAAiC;AAAA,EACjC,yBAAyB;AAAA,EACzB,+BAA+B;AAAA,EAC/B,gBAAgB;AACjB;",
4
+ "sourcesContent": ["import { ComponentType, Fragment } from 'react'\n\n/**\n * Options for configuring tldraw. For defaults, see {@link defaultTldrawOptions}.\n *\n * @example\n * ```tsx\n * const options: Partial<TldrawOptions> = {\n * maxPages: 3,\n * maxShapesPerPage: 1000,\n * }\n *\n * function MyTldrawComponent() {\n * return <Tldraw options={options} />\n * }\n * ```\n *\n * @public\n */\nexport interface TldrawOptions {\n\treadonly maxShapesPerPage: number\n\treadonly maxFilesAtOnce: number\n\treadonly maxPages: number\n\treadonly animationMediumMs: number\n\treadonly followChaseViewportSnap: number\n\treadonly doubleClickDurationMs: number\n\treadonly multiClickDurationMs: number\n\treadonly coarseDragDistanceSquared: number\n\treadonly dragDistanceSquared: number\n\treadonly defaultSvgPadding: number\n\treadonly cameraSlideFriction: number\n\treadonly maxPointsPerDrawShape: number\n\treadonly gridSteps: readonly {\n\t\treadonly min: number\n\t\treadonly mid: number\n\t\treadonly step: number\n\t}[]\n\treadonly collaboratorInactiveTimeoutMs: number\n\treadonly collaboratorIdleTimeoutMs: number\n\treadonly collaboratorCheckIntervalMs: number\n\treadonly cameraMovingTimeoutMs: number\n\treadonly hitTestMargin: number\n\treadonly edgeScrollDelay: number\n\treadonly edgeScrollEaseDuration: number\n\treadonly edgeScrollSpeed: number\n\treadonly edgeScrollDistance: number\n\treadonly coarsePointerWidth: number\n\treadonly coarseHandleRadius: number\n\treadonly handleRadius: number\n\treadonly longPressDurationMs: number\n\treadonly textShadowLod: number\n\treadonly adjacentShapeMargin: number\n\treadonly flattenImageBoundsExpand: number\n\treadonly flattenImageBoundsPadding: number\n\treadonly laserDelayMs: number\n\treadonly maxExportDelayMs: number\n\t/**\n\t * How long should previews created by {@link Editor.createTemporaryAssetPreview} last before\n\t * they expire? Defaults to 3 minutes.\n\t */\n\treadonly temporaryAssetPreviewLifetimeMs: number\n\treadonly actionShortcutsLocation: 'menu' | 'toolbar' | 'swap'\n\treadonly createTextOnCanvasDoubleClick: boolean\n\t/**\n\t * The react provider to use when exporting an image. This is useful if your shapes depend on\n\t * external context providers. By default, this is `React.Fragment`.\n\t */\n\treadonly exportProvider: ComponentType<{ children: React.ReactNode }>\n\t/**\n\t * How should the note shape resize? By default it does not resize (except automatically based on its text content),\n\t * but you can set it to be user-resizable using scale.\n\t */\n\treadonly noteShapeResizeMode: 'none' | 'scale'\n}\n\n/** @public */\nexport const defaultTldrawOptions = {\n\tmaxShapesPerPage: 4000,\n\tmaxFilesAtOnce: 100,\n\tmaxPages: 40,\n\tanimationMediumMs: 320,\n\tfollowChaseViewportSnap: 2,\n\tdoubleClickDurationMs: 450,\n\tmultiClickDurationMs: 200,\n\tcoarseDragDistanceSquared: 36, // 6 squared\n\tdragDistanceSquared: 16, // 4 squared\n\tdefaultSvgPadding: 32,\n\tcameraSlideFriction: 0.09,\n\tmaxPointsPerDrawShape: 500,\n\tgridSteps: [\n\t\t{ min: -1, mid: 0.15, step: 64 },\n\t\t{ min: 0.05, mid: 0.375, step: 16 },\n\t\t{ min: 0.15, mid: 1, step: 4 },\n\t\t{ min: 0.7, mid: 2.5, step: 1 },\n\t],\n\tcollaboratorInactiveTimeoutMs: 60000,\n\tcollaboratorIdleTimeoutMs: 3000,\n\tcollaboratorCheckIntervalMs: 1200,\n\tcameraMovingTimeoutMs: 64,\n\thitTestMargin: 8,\n\tedgeScrollDelay: 200,\n\tedgeScrollEaseDuration: 200,\n\tedgeScrollSpeed: 25,\n\tedgeScrollDistance: 8,\n\tcoarsePointerWidth: 12,\n\tcoarseHandleRadius: 20,\n\thandleRadius: 12,\n\tlongPressDurationMs: 500,\n\ttextShadowLod: 0.35,\n\tadjacentShapeMargin: 10,\n\tflattenImageBoundsExpand: 64,\n\tflattenImageBoundsPadding: 16,\n\tlaserDelayMs: 1200,\n\tmaxExportDelayMs: 5000,\n\ttemporaryAssetPreviewLifetimeMs: 180000,\n\tactionShortcutsLocation: 'swap',\n\tcreateTextOnCanvasDoubleClick: true,\n\texportProvider: Fragment,\n\tnoteShapeResizeMode: 'none',\n} as const satisfies TldrawOptions\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAwC;AA4EjC,MAAM,uBAAuB;AAAA,EACnC,kBAAkB;AAAA,EAClB,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,mBAAmB;AAAA,EACnB,yBAAyB;AAAA,EACzB,uBAAuB;AAAA,EACvB,sBAAsB;AAAA,EACtB,2BAA2B;AAAA;AAAA,EAC3B,qBAAqB;AAAA;AAAA,EACrB,mBAAmB;AAAA,EACnB,qBAAqB;AAAA,EACrB,uBAAuB;AAAA,EACvB,WAAW;AAAA,IACV,EAAE,KAAK,IAAI,KAAK,MAAM,MAAM,GAAG;AAAA,IAC/B,EAAE,KAAK,MAAM,KAAK,OAAO,MAAM,GAAG;AAAA,IAClC,EAAE,KAAK,MAAM,KAAK,GAAG,MAAM,EAAE;AAAA,IAC7B,EAAE,KAAK,KAAK,KAAK,KAAK,MAAM,EAAE;AAAA,EAC/B;AAAA,EACA,+BAA+B;AAAA,EAC/B,2BAA2B;AAAA,EAC3B,6BAA6B;AAAA,EAC7B,uBAAuB;AAAA,EACvB,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,wBAAwB;AAAA,EACxB,iBAAiB;AAAA,EACjB,oBAAoB;AAAA,EACpB,oBAAoB;AAAA,EACpB,oBAAoB;AAAA,EACpB,cAAc;AAAA,EACd,qBAAqB;AAAA,EACrB,eAAe;AAAA,EACf,qBAAqB;AAAA,EACrB,0BAA0B;AAAA,EAC1B,2BAA2B;AAAA,EAC3B,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,iCAAiC;AAAA,EACjC,yBAAyB;AAAA,EACzB,+BAA+B;AAAA,EAC/B,gBAAgB;AAAA,EAChB,qBAAqB;AACtB;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var browserCanvasMaxSize_exports = {};
30
+ __export(browserCanvasMaxSize_exports, {
31
+ clampToBrowserMaxCanvasSize: () => clampToBrowserMaxCanvasSize
32
+ });
33
+ module.exports = __toCommonJS(browserCanvasMaxSize_exports);
34
+ var import_canvas_size = __toESM(require("canvas-size"));
35
+ let maxSizePromise = null;
36
+ function getBrowserCanvasMaxSize() {
37
+ if (!maxSizePromise) {
38
+ maxSizePromise = calculateBrowserCanvasMaxSize();
39
+ }
40
+ return maxSizePromise;
41
+ }
42
+ async function calculateBrowserCanvasMaxSize() {
43
+ const maxWidth = await import_canvas_size.default.maxWidth({ usePromise: true });
44
+ const maxHeight = await import_canvas_size.default.maxHeight({ usePromise: true });
45
+ const maxArea = await import_canvas_size.default.maxArea({ usePromise: true });
46
+ return {
47
+ maxWidth: maxWidth.width,
48
+ maxHeight: maxHeight.height,
49
+ maxArea: maxArea.width * maxArea.height
50
+ };
51
+ }
52
+ const MAX_SAFE_CANVAS_DIMENSION = 8192;
53
+ const MAX_SAFE_CANVAS_AREA = 4096 * 4096;
54
+ async function clampToBrowserMaxCanvasSize(width, height) {
55
+ if (width <= MAX_SAFE_CANVAS_DIMENSION && height <= MAX_SAFE_CANVAS_DIMENSION && width * height <= MAX_SAFE_CANVAS_AREA) {
56
+ return [width, height];
57
+ }
58
+ const { maxWidth, maxHeight, maxArea } = await getBrowserCanvasMaxSize();
59
+ const aspectRatio = width / height;
60
+ if (width > maxWidth) {
61
+ width = maxWidth;
62
+ height = width / aspectRatio;
63
+ }
64
+ if (height > maxHeight) {
65
+ height = maxHeight;
66
+ width = height * aspectRatio;
67
+ }
68
+ if (width * height > maxArea) {
69
+ const ratio = Math.sqrt(maxArea / (width * height));
70
+ width *= ratio;
71
+ height *= ratio;
72
+ }
73
+ return [width, height];
74
+ }
75
+ //# sourceMappingURL=browserCanvasMaxSize.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/utils/browserCanvasMaxSize.ts"],
4
+ "sourcesContent": ["import canvasSize from 'canvas-size'\n\n/** @internal */\nexport interface CanvasMaxSize {\n\tmaxWidth: number\n\tmaxHeight: number\n\tmaxArea: number\n}\n\nlet maxSizePromise: Promise<CanvasMaxSize> | null = null\n\nfunction getBrowserCanvasMaxSize() {\n\tif (!maxSizePromise) {\n\t\tmaxSizePromise = calculateBrowserCanvasMaxSize()\n\t}\n\n\treturn maxSizePromise\n}\n\nasync function calculateBrowserCanvasMaxSize(): Promise<CanvasMaxSize> {\n\tconst maxWidth = await canvasSize.maxWidth({ usePromise: true })\n\tconst maxHeight = await canvasSize.maxHeight({ usePromise: true })\n\tconst maxArea = await canvasSize.maxArea({ usePromise: true })\n\treturn {\n\t\tmaxWidth: maxWidth.width,\n\t\tmaxHeight: maxHeight.height,\n\t\tmaxArea: maxArea.width * maxArea.height,\n\t}\n}\n\n// https://github.com/jhildenbiddle/canvas-size?tab=readme-ov-file#test-results\nconst MAX_SAFE_CANVAS_DIMENSION = 8192\nconst MAX_SAFE_CANVAS_AREA = 4096 * 4096\n\n/** @internal */\nexport async function clampToBrowserMaxCanvasSize(width: number, height: number) {\n\tif (\n\t\twidth <= MAX_SAFE_CANVAS_DIMENSION &&\n\t\theight <= MAX_SAFE_CANVAS_DIMENSION &&\n\t\twidth * height <= MAX_SAFE_CANVAS_AREA\n\t) {\n\t\treturn [width, height]\n\t}\n\n\tconst { maxWidth, maxHeight, maxArea } = await getBrowserCanvasMaxSize()\n\tconst aspectRatio = width / height\n\n\tif (width > maxWidth) {\n\t\twidth = maxWidth\n\t\theight = width / aspectRatio\n\t}\n\n\tif (height > maxHeight) {\n\t\theight = maxHeight\n\t\twidth = height * aspectRatio\n\t}\n\n\tif (width * height > maxArea) {\n\t\tconst ratio = Math.sqrt(maxArea / (width * height))\n\t\twidth *= ratio\n\t\theight *= ratio\n\t}\n\n\treturn [width, height]\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAAuB;AASvB,IAAI,iBAAgD;AAEpD,SAAS,0BAA0B;AAClC,MAAI,CAAC,gBAAgB;AACpB,qBAAiB,8BAA8B;AAAA,EAChD;AAEA,SAAO;AACR;AAEA,eAAe,gCAAwD;AACtE,QAAM,WAAW,MAAM,mBAAAA,QAAW,SAAS,EAAE,YAAY,KAAK,CAAC;AAC/D,QAAM,YAAY,MAAM,mBAAAA,QAAW,UAAU,EAAE,YAAY,KAAK,CAAC;AACjE,QAAM,UAAU,MAAM,mBAAAA,QAAW,QAAQ,EAAE,YAAY,KAAK,CAAC;AAC7D,SAAO;AAAA,IACN,UAAU,SAAS;AAAA,IACnB,WAAW,UAAU;AAAA,IACrB,SAAS,QAAQ,QAAQ,QAAQ;AAAA,EAClC;AACD;AAGA,MAAM,4BAA4B;AAClC,MAAM,uBAAuB,OAAO;AAGpC,eAAsB,4BAA4B,OAAe,QAAgB;AAChF,MACC,SAAS,6BACT,UAAU,6BACV,QAAQ,UAAU,sBACjB;AACD,WAAO,CAAC,OAAO,MAAM;AAAA,EACtB;AAEA,QAAM,EAAE,UAAU,WAAW,QAAQ,IAAI,MAAM,wBAAwB;AACvE,QAAM,cAAc,QAAQ;AAE5B,MAAI,QAAQ,UAAU;AACrB,YAAQ;AACR,aAAS,QAAQ;AAAA,EAClB;AAEA,MAAI,SAAS,WAAW;AACvB,aAAS;AACT,YAAQ,SAAS;AAAA,EAClB;AAEA,MAAI,QAAQ,SAAS,SAAS;AAC7B,UAAM,QAAQ,KAAK,KAAK,WAAW,QAAQ,OAAO;AAClD,aAAS;AACT,cAAU;AAAA,EACX;AAEA,SAAO,CAAC,OAAO,MAAM;AACtB;",
6
+ "names": ["canvasSize"]
7
+ }
@@ -150,7 +150,9 @@ class TLLocalSyncClient {
150
150
  });
151
151
  }
152
152
  if (sessionStateSnapshot) {
153
- (0, import_TLSessionStateSnapshot.loadSessionStateSnapshotIntoStore)(this.store, sessionStateSnapshot);
153
+ (0, import_TLSessionStateSnapshot.loadSessionStateSnapshotIntoStore)(this.store, sessionStateSnapshot, {
154
+ forceOverwrite: true
155
+ });
154
156
  }
155
157
  }
156
158
  this.channel.onmessage = ({ data: data2 }) => {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/utils/sync/TLLocalSyncClient.ts"],
4
- "sourcesContent": ["import { Signal, transact } from '@tldraw/state'\nimport { RecordsDiff, SerializedSchema, UnknownRecord, squashRecordDiffs } from '@tldraw/store'\nimport { TLStore } from '@tldraw/tlschema'\nimport { assert } from '@tldraw/utils'\nimport {\n\tTAB_ID,\n\tTLSessionStateSnapshot,\n\tcreateSessionStateSnapshotSignal,\n\textractSessionStateFromLegacySnapshot,\n\tloadSessionStateSnapshotIntoStore,\n} from '../../config/TLSessionStateSnapshot'\nimport { LocalIndexedDb } from './LocalIndexedDb'\nimport { showCantReadFromIndexDbAlert, showCantWriteToIndexDbAlert } from './alerts'\n\n/** How should we debounce persists? */\nconst PERSIST_THROTTLE_MS = 350\n/** If we're in an error state, how long should we wait before retrying a write? */\nconst PERSIST_RETRY_THROTTLE_MS = 10_000\n\nconst UPDATE_INSTANCE_STATE = Symbol('UPDATE_INSTANCE_STATE')\n\n/**\n * IMPORTANT!!!\n *\n * This is just a quick-n-dirty temporary solution that will be replaced with the remote sync client\n * once it has the db integrated\n */\n\ninterface SyncMessage {\n\ttype: 'diff'\n\tstoreId: string\n\tchanges: RecordsDiff<UnknownRecord>\n\tschema: SerializedSchema\n}\n\n// Sent by new clients when they connect\n// If another client is on the channel with a newer schema version\n// It will\ninterface AnnounceMessage {\n\ttype: 'announce'\n\tschema: SerializedSchema\n}\n\ntype Message = SyncMessage | AnnounceMessage\n\ntype UnpackPromise<T> = T extends Promise<infer U> ? U : T\n\nconst msg = (msg: Message) => msg\n\n/** @internal */\nexport class BroadcastChannelMock {\n\tonmessage?: (e: MessageEvent) => void\n\tconstructor(_name: string) {\n\t\t// noop\n\t}\n\tpostMessage(_msg: Message) {\n\t\t// noop\n\t}\n\tclose() {\n\t\t// noop\n\t}\n}\n\nconst BC = typeof BroadcastChannel === 'undefined' ? BroadcastChannelMock : BroadcastChannel\n\n/** @internal */\nexport class TLLocalSyncClient {\n\tprivate disposables = new Set<() => void>()\n\tprivate diffQueue: Array<RecordsDiff<UnknownRecord> | typeof UPDATE_INSTANCE_STATE> = []\n\tprivate didDispose = false\n\tprivate shouldDoFullDBWrite = true\n\tprivate isReloading = false\n\treadonly persistenceKey: string\n\treadonly sessionId: string\n\treadonly serializedSchema: SerializedSchema\n\tprivate isDebugging = false\n\tprivate readonly documentTypes: ReadonlySet<string>\n\tprivate readonly $sessionStateSnapshot: Signal<TLSessionStateSnapshot | null>\n\t/** @internal */\n\treadonly db: LocalIndexedDb\n\n\tinitTime = Date.now()\n\tprivate debug(...args: any[]) {\n\t\tif (this.isDebugging) {\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.debug(...args)\n\t\t}\n\t}\n\tconstructor(\n\t\tpublic readonly store: TLStore,\n\t\t{\n\t\t\tpersistenceKey,\n\t\t\tsessionId = TAB_ID,\n\t\t\tonLoad,\n\t\t\tonLoadError,\n\t\t}: {\n\t\t\tpersistenceKey: string\n\t\t\tsessionId?: string\n\t\t\tonLoad(self: TLLocalSyncClient): void\n\t\t\tonLoadError(error: Error): void\n\t\t},\n\t\tpublic readonly channel = new BC(`tldraw-tab-sync-${persistenceKey}`)\n\t) {\n\t\tif (typeof window !== 'undefined') {\n\t\t\t;(window as any).tlsync = this\n\t\t}\n\t\tthis.persistenceKey = persistenceKey\n\t\tthis.sessionId = sessionId\n\t\tthis.db = new LocalIndexedDb(persistenceKey)\n\t\tthis.disposables.add(() => this.db.close())\n\n\t\tthis.serializedSchema = this.store.schema.serialize()\n\t\tthis.$sessionStateSnapshot = createSessionStateSnapshotSignal(this.store)\n\n\t\tthis.disposables.add(\n\t\t\t// Set up a subscription to changes from the store: When\n\t\t\t// the store changes (and if the change was made by the user)\n\t\t\t// then immediately send the diff to other tabs via postMessage\n\t\t\t// and schedule a persist.\n\t\t\tstore.listen(\n\t\t\t\t({ changes }) => {\n\t\t\t\t\tthis.diffQueue.push(changes)\n\t\t\t\t\tthis.channel.postMessage(\n\t\t\t\t\t\tmsg({\n\t\t\t\t\t\t\ttype: 'diff',\n\t\t\t\t\t\t\tstoreId: this.store.id,\n\t\t\t\t\t\t\tchanges,\n\t\t\t\t\t\t\tschema: this.serializedSchema,\n\t\t\t\t\t\t})\n\t\t\t\t\t)\n\t\t\t\t\tthis.schedulePersist()\n\t\t\t\t},\n\t\t\t\t{ source: 'user', scope: 'document' }\n\t\t\t)\n\t\t)\n\t\tthis.disposables.add(\n\t\t\tstore.listen(\n\t\t\t\t() => {\n\t\t\t\t\tthis.diffQueue.push(UPDATE_INSTANCE_STATE)\n\t\t\t\t\tthis.schedulePersist()\n\t\t\t\t},\n\t\t\t\t{ scope: 'session' }\n\t\t\t)\n\t\t)\n\n\t\tthis.connect(onLoad, onLoadError)\n\n\t\tthis.documentTypes = new Set(\n\t\t\tObject.values(this.store.schema.types)\n\t\t\t\t.filter((t) => t.scope === 'document')\n\t\t\t\t.map((t) => t.typeName)\n\t\t)\n\t}\n\n\tprivate async connect(onLoad: (client: this) => void, onLoadError: (error: Error) => void) {\n\t\tthis.debug('connecting')\n\t\tlet data: UnpackPromise<ReturnType<LocalIndexedDb['load']>> | undefined\n\n\t\ttry {\n\t\t\tdata = await this.db.load({ sessionId: this.sessionId })\n\t\t} catch (error: any) {\n\t\t\tonLoadError(error)\n\t\t\tshowCantReadFromIndexDbAlert()\n\t\t\treturn\n\t\t}\n\n\t\tthis.debug('loaded data from store', data, 'didDispose', this.didDispose)\n\t\tif (this.didDispose) return\n\n\t\ttry {\n\t\t\tif (data) {\n\t\t\t\tconst documentSnapshot = Object.fromEntries(data.records.map((r) => [r.id, r]))\n\t\t\t\tconst sessionStateSnapshot =\n\t\t\t\t\tdata.sessionStateSnapshot ?? extractSessionStateFromLegacySnapshot(documentSnapshot)\n\t\t\t\tconst migrationResult = this.store.schema.migrateStoreSnapshot({\n\t\t\t\t\tstore: documentSnapshot,\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\t\t\t\tschema: data.schema ?? this.store.schema.serializeEarliestVersion(),\n\t\t\t\t})\n\n\t\t\t\tif (migrationResult.type === 'error') {\n\t\t\t\t\tconsole.error('failed to migrate store', migrationResult)\n\t\t\t\t\tonLoadError(new Error(`Failed to migrate store: ${migrationResult.reason}`))\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst records = Object.values(migrationResult.value).filter((r) =>\n\t\t\t\t\tthis.documentTypes.has(r.typeName)\n\t\t\t\t)\n\t\t\t\tif (records.length > 0) {\n\t\t\t\t\t// 3. Merge the changes into the REAL STORE\n\t\t\t\t\tthis.store.mergeRemoteChanges(() => {\n\t\t\t\t\t\t// Calling put will validate the records!\n\t\t\t\t\t\tthis.store.put(records, 'initialize')\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\tif (sessionStateSnapshot) {\n\t\t\t\t\tloadSessionStateSnapshotIntoStore(this.store, sessionStateSnapshot)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.channel.onmessage = ({ data }) => {\n\t\t\t\tthis.debug('got message', data)\n\t\t\t\tconst msg = data as Message\n\t\t\t\t// if their schema is earlier than ours, we need to tell them so they can refresh\n\t\t\t\t// if their schema is later than ours, we need to refresh\n\t\t\t\tconst res = this.store.schema.getMigrationsSince(msg.schema)\n\n\t\t\t\tif (!res.ok) {\n\t\t\t\t\t// we are older, refresh\n\t\t\t\t\t// but add a safety check to make sure we don't get in an infinite loop\n\t\t\t\t\tconst timeSinceInit = Date.now() - this.initTime\n\t\t\t\t\tif (timeSinceInit < 5000) {\n\t\t\t\t\t\t// This tab was just reloaded, but is out of date compared to other tabs.\n\t\t\t\t\t\t// Not expecting this to ever happen. It should only happen if we roll back a release that incremented\n\t\t\t\t\t\t// the schema version (which we should never do)\n\t\t\t\t\t\t// Or maybe during development if you have multiple local tabs open running the app on prod mode and you\n\t\t\t\t\t\t// check out an older commit. Dev server should be fine.\n\t\t\t\t\t\tonLoadError(new Error('Schema mismatch, please close other tabs and reload the page'))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tthis.debug('reloading')\n\t\t\t\t\tthis.isReloading = true\n\t\t\t\t\twindow?.location?.reload?.()\n\t\t\t\t\treturn\n\t\t\t\t} else if (res.value.length > 0) {\n\t\t\t\t\t// they are older, tell them to refresh and not write any more data\n\t\t\t\t\tthis.debug('telling them to reload')\n\t\t\t\t\tthis.channel.postMessage({ type: 'announce', schema: this.serializedSchema })\n\t\t\t\t\t// schedule a full db write in case they wrote data anyway\n\t\t\t\t\tthis.shouldDoFullDBWrite = true\n\t\t\t\t\tthis.persistIfNeeded()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// otherwise, all good, same version :)\n\t\t\t\tif (msg.type === 'diff') {\n\t\t\t\t\tthis.debug('applying diff')\n\t\t\t\t\ttransact(() => {\n\t\t\t\t\t\tthis.store.mergeRemoteChanges(() => {\n\t\t\t\t\t\t\tthis.store.applyDiff(msg.changes as any)\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.channel.postMessage({ type: 'announce', schema: this.serializedSchema })\n\t\t\tthis.disposables.add(() => {\n\t\t\t\tthis.channel.close()\n\t\t\t})\n\t\t\tonLoad(this)\n\t\t} catch (e: any) {\n\t\t\tthis.debug('error loading data from store', e)\n\t\t\tif (this.didDispose) return\n\t\t\tonLoadError(e)\n\t\t\treturn\n\t\t}\n\t}\n\n\tclose() {\n\t\tthis.debug('closing')\n\t\tthis.didDispose = true\n\t\tthis.disposables.forEach((d) => d())\n\t}\n\n\tprivate isPersisting = false\n\tprivate didLastWriteError = false\n\t// eslint-disable-next-line no-restricted-globals\n\tprivate scheduledPersistTimeout: ReturnType<typeof setTimeout> | null = null\n\n\t/**\n\t * Schedule a persist. Persists don't happen immediately: they are throttled to avoid writing too\n\t * often, and will retry if failed.\n\t *\n\t * @internal\n\t */\n\tprivate schedulePersist() {\n\t\tthis.debug('schedulePersist', this.scheduledPersistTimeout)\n\t\tif (this.scheduledPersistTimeout) return\n\t\t// eslint-disable-next-line no-restricted-globals\n\t\tthis.scheduledPersistTimeout = setTimeout(\n\t\t\t() => {\n\t\t\t\tthis.scheduledPersistTimeout = null\n\t\t\t\tthis.persistIfNeeded()\n\t\t\t},\n\t\t\tthis.didLastWriteError ? PERSIST_RETRY_THROTTLE_MS : PERSIST_THROTTLE_MS\n\t\t)\n\t}\n\n\t/**\n\t * Persist to IndexedDB only under certain circumstances:\n\t *\n\t * - If we're not already persisting\n\t * - If we're not reloading the page\n\t * - And we have something to persist (a full db write scheduled or changes in the diff queue)\n\t *\n\t * @internal\n\t */\n\tprivate persistIfNeeded() {\n\t\tthis.debug('persistIfNeeded', {\n\t\t\tisPersisting: this.isPersisting,\n\t\t\tisReloading: this.isReloading,\n\t\t\tshouldDoFullDBWrite: this.shouldDoFullDBWrite,\n\t\t\tdiffQueueLength: this.diffQueue.length,\n\t\t\tstoreIsPossiblyCorrupt: this.store.isPossiblyCorrupted(),\n\t\t})\n\n\t\t// if we've scheduled a persist for the future, that's no longer needed\n\t\tif (this.scheduledPersistTimeout) {\n\t\t\tclearTimeout(this.scheduledPersistTimeout)\n\t\t\tthis.scheduledPersistTimeout = null\n\t\t}\n\n\t\t// if a persist is already in progress, we don't need to do anything -\n\t\t// if there are still outstanding changes once it's finished, it'll\n\t\t// schedule another persist\n\t\tif (this.isPersisting) return\n\n\t\t// if we're reloading the page, it's because there's a newer client\n\t\t// present so lets not overwrite their changes\n\t\tif (this.isReloading) return\n\n\t\t// if the store is possibly corrupted, we don't want to persist\n\t\tif (this.store.isPossiblyCorrupted()) return\n\n\t\t// if we're scheduled for a full write or if we have changes outstanding, let's persist them!\n\t\tif (this.shouldDoFullDBWrite || this.diffQueue.length > 0) {\n\t\t\tthis.doPersist()\n\t\t}\n\t}\n\n\t/**\n\t * Actually persist to IndexedDB. If the write fails, then we'll retry with a full db write after\n\t * a short delay.\n\t */\n\tprivate async doPersist() {\n\t\tassert(!this.isPersisting, 'persist already in progress')\n\t\tif (this.didDispose) return\n\t\tthis.isPersisting = true\n\n\t\tthis.debug('doPersist start')\n\n\t\t// instantly empty the diff queue, but keep our own copy of it. this way\n\t\t// diffs that come in during the persist will still get tracked\n\t\tconst diffQueue = this.diffQueue\n\t\tthis.diffQueue = []\n\n\t\ttry {\n\t\t\tif (this.shouldDoFullDBWrite) {\n\t\t\t\tthis.shouldDoFullDBWrite = false\n\t\t\t\tawait this.db.storeSnapshot({\n\t\t\t\t\tschema: this.store.schema,\n\t\t\t\t\tsnapshot: this.store.serialize(),\n\t\t\t\t\tsessionId: this.sessionId,\n\t\t\t\t\tsessionStateSnapshot: this.$sessionStateSnapshot.get(),\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tconst diffs = squashRecordDiffs(\n\t\t\t\t\tdiffQueue.filter((d): d is RecordsDiff<UnknownRecord> => d !== UPDATE_INSTANCE_STATE)\n\t\t\t\t)\n\t\t\t\tawait this.db.storeChanges({\n\t\t\t\t\tchanges: diffs,\n\t\t\t\t\tschema: this.store.schema,\n\t\t\t\t\tsessionId: this.sessionId,\n\t\t\t\t\tsessionStateSnapshot: this.$sessionStateSnapshot.get(),\n\t\t\t\t})\n\t\t\t}\n\t\t\tthis.didLastWriteError = false\n\t\t} catch (e) {\n\t\t\t// set this.shouldDoFullDBWrite because we clear the diffQueue no matter what,\n\t\t\t// so if this is just a temporary error, we will still persist all changes\n\t\t\tthis.shouldDoFullDBWrite = true\n\t\t\tthis.didLastWriteError = true\n\t\t\tconsole.error('failed to store changes in indexed db', e)\n\n\t\t\tshowCantWriteToIndexDbAlert()\n\t\t\tif (typeof window !== 'undefined') {\n\t\t\t\t// adios\n\t\t\t\twindow.location.reload()\n\t\t\t}\n\t\t}\n\n\t\tthis.isPersisting = false\n\t\tthis.debug('doPersist end')\n\n\t\t// changes might have come in between when we started the persist and\n\t\t// now. we request another persist so any new changes can get written\n\t\tthis.schedulePersist()\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Signal, transact } from '@tldraw/state'\nimport { RecordsDiff, SerializedSchema, UnknownRecord, squashRecordDiffs } from '@tldraw/store'\nimport { TLStore } from '@tldraw/tlschema'\nimport { assert } from '@tldraw/utils'\nimport {\n\tTAB_ID,\n\tTLSessionStateSnapshot,\n\tcreateSessionStateSnapshotSignal,\n\textractSessionStateFromLegacySnapshot,\n\tloadSessionStateSnapshotIntoStore,\n} from '../../config/TLSessionStateSnapshot'\nimport { LocalIndexedDb } from './LocalIndexedDb'\nimport { showCantReadFromIndexDbAlert, showCantWriteToIndexDbAlert } from './alerts'\n\n/** How should we debounce persists? */\nconst PERSIST_THROTTLE_MS = 350\n/** If we're in an error state, how long should we wait before retrying a write? */\nconst PERSIST_RETRY_THROTTLE_MS = 10_000\n\nconst UPDATE_INSTANCE_STATE = Symbol('UPDATE_INSTANCE_STATE')\n\n/**\n * IMPORTANT!!!\n *\n * This is just a quick-n-dirty temporary solution that will be replaced with the remote sync client\n * once it has the db integrated\n */\n\ninterface SyncMessage {\n\ttype: 'diff'\n\tstoreId: string\n\tchanges: RecordsDiff<UnknownRecord>\n\tschema: SerializedSchema\n}\n\n// Sent by new clients when they connect\n// If another client is on the channel with a newer schema version\n// It will\ninterface AnnounceMessage {\n\ttype: 'announce'\n\tschema: SerializedSchema\n}\n\ntype Message = SyncMessage | AnnounceMessage\n\ntype UnpackPromise<T> = T extends Promise<infer U> ? U : T\n\nconst msg = (msg: Message) => msg\n\n/** @internal */\nexport class BroadcastChannelMock {\n\tonmessage?: (e: MessageEvent) => void\n\tconstructor(_name: string) {\n\t\t// noop\n\t}\n\tpostMessage(_msg: Message) {\n\t\t// noop\n\t}\n\tclose() {\n\t\t// noop\n\t}\n}\n\nconst BC = typeof BroadcastChannel === 'undefined' ? BroadcastChannelMock : BroadcastChannel\n\n/** @internal */\nexport class TLLocalSyncClient {\n\tprivate disposables = new Set<() => void>()\n\tprivate diffQueue: Array<RecordsDiff<UnknownRecord> | typeof UPDATE_INSTANCE_STATE> = []\n\tprivate didDispose = false\n\tprivate shouldDoFullDBWrite = true\n\tprivate isReloading = false\n\treadonly persistenceKey: string\n\treadonly sessionId: string\n\treadonly serializedSchema: SerializedSchema\n\tprivate isDebugging = false\n\tprivate readonly documentTypes: ReadonlySet<string>\n\tprivate readonly $sessionStateSnapshot: Signal<TLSessionStateSnapshot | null>\n\t/** @internal */\n\treadonly db: LocalIndexedDb\n\n\tinitTime = Date.now()\n\tprivate debug(...args: any[]) {\n\t\tif (this.isDebugging) {\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.debug(...args)\n\t\t}\n\t}\n\tconstructor(\n\t\tpublic readonly store: TLStore,\n\t\t{\n\t\t\tpersistenceKey,\n\t\t\tsessionId = TAB_ID,\n\t\t\tonLoad,\n\t\t\tonLoadError,\n\t\t}: {\n\t\t\tpersistenceKey: string\n\t\t\tsessionId?: string\n\t\t\tonLoad(self: TLLocalSyncClient): void\n\t\t\tonLoadError(error: Error): void\n\t\t},\n\t\tpublic readonly channel = new BC(`tldraw-tab-sync-${persistenceKey}`)\n\t) {\n\t\tif (typeof window !== 'undefined') {\n\t\t\t;(window as any).tlsync = this\n\t\t}\n\t\tthis.persistenceKey = persistenceKey\n\t\tthis.sessionId = sessionId\n\t\tthis.db = new LocalIndexedDb(persistenceKey)\n\t\tthis.disposables.add(() => this.db.close())\n\n\t\tthis.serializedSchema = this.store.schema.serialize()\n\t\tthis.$sessionStateSnapshot = createSessionStateSnapshotSignal(this.store)\n\n\t\tthis.disposables.add(\n\t\t\t// Set up a subscription to changes from the store: When\n\t\t\t// the store changes (and if the change was made by the user)\n\t\t\t// then immediately send the diff to other tabs via postMessage\n\t\t\t// and schedule a persist.\n\t\t\tstore.listen(\n\t\t\t\t({ changes }) => {\n\t\t\t\t\tthis.diffQueue.push(changes)\n\t\t\t\t\tthis.channel.postMessage(\n\t\t\t\t\t\tmsg({\n\t\t\t\t\t\t\ttype: 'diff',\n\t\t\t\t\t\t\tstoreId: this.store.id,\n\t\t\t\t\t\t\tchanges,\n\t\t\t\t\t\t\tschema: this.serializedSchema,\n\t\t\t\t\t\t})\n\t\t\t\t\t)\n\t\t\t\t\tthis.schedulePersist()\n\t\t\t\t},\n\t\t\t\t{ source: 'user', scope: 'document' }\n\t\t\t)\n\t\t)\n\t\tthis.disposables.add(\n\t\t\tstore.listen(\n\t\t\t\t() => {\n\t\t\t\t\tthis.diffQueue.push(UPDATE_INSTANCE_STATE)\n\t\t\t\t\tthis.schedulePersist()\n\t\t\t\t},\n\t\t\t\t{ scope: 'session' }\n\t\t\t)\n\t\t)\n\n\t\tthis.connect(onLoad, onLoadError)\n\n\t\tthis.documentTypes = new Set(\n\t\t\tObject.values(this.store.schema.types)\n\t\t\t\t.filter((t) => t.scope === 'document')\n\t\t\t\t.map((t) => t.typeName)\n\t\t)\n\t}\n\n\tprivate async connect(onLoad: (client: this) => void, onLoadError: (error: Error) => void) {\n\t\tthis.debug('connecting')\n\t\tlet data: UnpackPromise<ReturnType<LocalIndexedDb['load']>> | undefined\n\n\t\ttry {\n\t\t\tdata = await this.db.load({ sessionId: this.sessionId })\n\t\t} catch (error: any) {\n\t\t\tonLoadError(error)\n\t\t\tshowCantReadFromIndexDbAlert()\n\t\t\treturn\n\t\t}\n\n\t\tthis.debug('loaded data from store', data, 'didDispose', this.didDispose)\n\t\tif (this.didDispose) return\n\n\t\ttry {\n\t\t\tif (data) {\n\t\t\t\tconst documentSnapshot = Object.fromEntries(data.records.map((r) => [r.id, r]))\n\t\t\t\tconst sessionStateSnapshot =\n\t\t\t\t\tdata.sessionStateSnapshot ?? extractSessionStateFromLegacySnapshot(documentSnapshot)\n\t\t\t\tconst migrationResult = this.store.schema.migrateStoreSnapshot({\n\t\t\t\t\tstore: documentSnapshot,\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\t\t\t\tschema: data.schema ?? this.store.schema.serializeEarliestVersion(),\n\t\t\t\t})\n\n\t\t\t\tif (migrationResult.type === 'error') {\n\t\t\t\t\tconsole.error('failed to migrate store', migrationResult)\n\t\t\t\t\tonLoadError(new Error(`Failed to migrate store: ${migrationResult.reason}`))\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst records = Object.values(migrationResult.value).filter((r) =>\n\t\t\t\t\tthis.documentTypes.has(r.typeName)\n\t\t\t\t)\n\t\t\t\tif (records.length > 0) {\n\t\t\t\t\t// 3. Merge the changes into the REAL STORE\n\t\t\t\t\tthis.store.mergeRemoteChanges(() => {\n\t\t\t\t\t\t// Calling put will validate the records!\n\t\t\t\t\t\tthis.store.put(records, 'initialize')\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\tif (sessionStateSnapshot) {\n\t\t\t\t\tloadSessionStateSnapshotIntoStore(this.store, sessionStateSnapshot, {\n\t\t\t\t\t\tforceOverwrite: true,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.channel.onmessage = ({ data }) => {\n\t\t\t\tthis.debug('got message', data)\n\t\t\t\tconst msg = data as Message\n\t\t\t\t// if their schema is earlier than ours, we need to tell them so they can refresh\n\t\t\t\t// if their schema is later than ours, we need to refresh\n\t\t\t\tconst res = this.store.schema.getMigrationsSince(msg.schema)\n\n\t\t\t\tif (!res.ok) {\n\t\t\t\t\t// we are older, refresh\n\t\t\t\t\t// but add a safety check to make sure we don't get in an infinite loop\n\t\t\t\t\tconst timeSinceInit = Date.now() - this.initTime\n\t\t\t\t\tif (timeSinceInit < 5000) {\n\t\t\t\t\t\t// This tab was just reloaded, but is out of date compared to other tabs.\n\t\t\t\t\t\t// Not expecting this to ever happen. It should only happen if we roll back a release that incremented\n\t\t\t\t\t\t// the schema version (which we should never do)\n\t\t\t\t\t\t// Or maybe during development if you have multiple local tabs open running the app on prod mode and you\n\t\t\t\t\t\t// check out an older commit. Dev server should be fine.\n\t\t\t\t\t\tonLoadError(new Error('Schema mismatch, please close other tabs and reload the page'))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tthis.debug('reloading')\n\t\t\t\t\tthis.isReloading = true\n\t\t\t\t\twindow?.location?.reload?.()\n\t\t\t\t\treturn\n\t\t\t\t} else if (res.value.length > 0) {\n\t\t\t\t\t// they are older, tell them to refresh and not write any more data\n\t\t\t\t\tthis.debug('telling them to reload')\n\t\t\t\t\tthis.channel.postMessage({ type: 'announce', schema: this.serializedSchema })\n\t\t\t\t\t// schedule a full db write in case they wrote data anyway\n\t\t\t\t\tthis.shouldDoFullDBWrite = true\n\t\t\t\t\tthis.persistIfNeeded()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// otherwise, all good, same version :)\n\t\t\t\tif (msg.type === 'diff') {\n\t\t\t\t\tthis.debug('applying diff')\n\t\t\t\t\ttransact(() => {\n\t\t\t\t\t\tthis.store.mergeRemoteChanges(() => {\n\t\t\t\t\t\t\tthis.store.applyDiff(msg.changes as any)\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.channel.postMessage({ type: 'announce', schema: this.serializedSchema })\n\t\t\tthis.disposables.add(() => {\n\t\t\t\tthis.channel.close()\n\t\t\t})\n\t\t\tonLoad(this)\n\t\t} catch (e: any) {\n\t\t\tthis.debug('error loading data from store', e)\n\t\t\tif (this.didDispose) return\n\t\t\tonLoadError(e)\n\t\t\treturn\n\t\t}\n\t}\n\n\tclose() {\n\t\tthis.debug('closing')\n\t\tthis.didDispose = true\n\t\tthis.disposables.forEach((d) => d())\n\t}\n\n\tprivate isPersisting = false\n\tprivate didLastWriteError = false\n\t// eslint-disable-next-line no-restricted-globals\n\tprivate scheduledPersistTimeout: ReturnType<typeof setTimeout> | null = null\n\n\t/**\n\t * Schedule a persist. Persists don't happen immediately: they are throttled to avoid writing too\n\t * often, and will retry if failed.\n\t *\n\t * @internal\n\t */\n\tprivate schedulePersist() {\n\t\tthis.debug('schedulePersist', this.scheduledPersistTimeout)\n\t\tif (this.scheduledPersistTimeout) return\n\t\t// eslint-disable-next-line no-restricted-globals\n\t\tthis.scheduledPersistTimeout = setTimeout(\n\t\t\t() => {\n\t\t\t\tthis.scheduledPersistTimeout = null\n\t\t\t\tthis.persistIfNeeded()\n\t\t\t},\n\t\t\tthis.didLastWriteError ? PERSIST_RETRY_THROTTLE_MS : PERSIST_THROTTLE_MS\n\t\t)\n\t}\n\n\t/**\n\t * Persist to IndexedDB only under certain circumstances:\n\t *\n\t * - If we're not already persisting\n\t * - If we're not reloading the page\n\t * - And we have something to persist (a full db write scheduled or changes in the diff queue)\n\t *\n\t * @internal\n\t */\n\tprivate persistIfNeeded() {\n\t\tthis.debug('persistIfNeeded', {\n\t\t\tisPersisting: this.isPersisting,\n\t\t\tisReloading: this.isReloading,\n\t\t\tshouldDoFullDBWrite: this.shouldDoFullDBWrite,\n\t\t\tdiffQueueLength: this.diffQueue.length,\n\t\t\tstoreIsPossiblyCorrupt: this.store.isPossiblyCorrupted(),\n\t\t})\n\n\t\t// if we've scheduled a persist for the future, that's no longer needed\n\t\tif (this.scheduledPersistTimeout) {\n\t\t\tclearTimeout(this.scheduledPersistTimeout)\n\t\t\tthis.scheduledPersistTimeout = null\n\t\t}\n\n\t\t// if a persist is already in progress, we don't need to do anything -\n\t\t// if there are still outstanding changes once it's finished, it'll\n\t\t// schedule another persist\n\t\tif (this.isPersisting) return\n\n\t\t// if we're reloading the page, it's because there's a newer client\n\t\t// present so lets not overwrite their changes\n\t\tif (this.isReloading) return\n\n\t\t// if the store is possibly corrupted, we don't want to persist\n\t\tif (this.store.isPossiblyCorrupted()) return\n\n\t\t// if we're scheduled for a full write or if we have changes outstanding, let's persist them!\n\t\tif (this.shouldDoFullDBWrite || this.diffQueue.length > 0) {\n\t\t\tthis.doPersist()\n\t\t}\n\t}\n\n\t/**\n\t * Actually persist to IndexedDB. If the write fails, then we'll retry with a full db write after\n\t * a short delay.\n\t */\n\tprivate async doPersist() {\n\t\tassert(!this.isPersisting, 'persist already in progress')\n\t\tif (this.didDispose) return\n\t\tthis.isPersisting = true\n\n\t\tthis.debug('doPersist start')\n\n\t\t// instantly empty the diff queue, but keep our own copy of it. this way\n\t\t// diffs that come in during the persist will still get tracked\n\t\tconst diffQueue = this.diffQueue\n\t\tthis.diffQueue = []\n\n\t\ttry {\n\t\t\tif (this.shouldDoFullDBWrite) {\n\t\t\t\tthis.shouldDoFullDBWrite = false\n\t\t\t\tawait this.db.storeSnapshot({\n\t\t\t\t\tschema: this.store.schema,\n\t\t\t\t\tsnapshot: this.store.serialize(),\n\t\t\t\t\tsessionId: this.sessionId,\n\t\t\t\t\tsessionStateSnapshot: this.$sessionStateSnapshot.get(),\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tconst diffs = squashRecordDiffs(\n\t\t\t\t\tdiffQueue.filter((d): d is RecordsDiff<UnknownRecord> => d !== UPDATE_INSTANCE_STATE)\n\t\t\t\t)\n\t\t\t\tawait this.db.storeChanges({\n\t\t\t\t\tchanges: diffs,\n\t\t\t\t\tschema: this.store.schema,\n\t\t\t\t\tsessionId: this.sessionId,\n\t\t\t\t\tsessionStateSnapshot: this.$sessionStateSnapshot.get(),\n\t\t\t\t})\n\t\t\t}\n\t\t\tthis.didLastWriteError = false\n\t\t} catch (e) {\n\t\t\t// set this.shouldDoFullDBWrite because we clear the diffQueue no matter what,\n\t\t\t// so if this is just a temporary error, we will still persist all changes\n\t\t\tthis.shouldDoFullDBWrite = true\n\t\t\tthis.didLastWriteError = true\n\t\t\tconsole.error('failed to store changes in indexed db', e)\n\n\t\t\tshowCantWriteToIndexDbAlert()\n\t\t\tif (typeof window !== 'undefined') {\n\t\t\t\t// adios\n\t\t\t\twindow.location.reload()\n\t\t\t}\n\t\t}\n\n\t\tthis.isPersisting = false\n\t\tthis.debug('doPersist end')\n\n\t\t// changes might have come in between when we started the persist and\n\t\t// now. we request another persist so any new changes can get written\n\t\tthis.schedulePersist()\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["msg", "data"]
7
7
  }
@@ -22,10 +22,10 @@ __export(version_exports, {
22
22
  version: () => version
23
23
  });
24
24
  module.exports = __toCommonJS(version_exports);
25
- const version = "3.8.0-canary.c45a05a15c4a";
25
+ const version = "3.8.0-canary.c5d6281c3fdd";
26
26
  const publishDates = {
27
27
  major: "2024-09-13T14:36:29.063Z",
28
- minor: "2025-01-20T09:00:00.135Z",
29
- patch: "2025-01-20T09:00:00.135Z"
28
+ minor: "2025-01-28T16:50:53.939Z",
29
+ patch: "2025-01-28T16:50:53.939Z"
30
30
  };
31
31
  //# sourceMappingURL=version.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/version.ts"],
4
- "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '3.8.0-canary.c45a05a15c4a'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-01-20T09:00:00.135Z',\n\tpatch: '2025-01-20T09:00:00.135Z',\n}\n"],
4
+ "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '3.8.0-canary.c5d6281c3fdd'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-01-28T16:50:53.939Z',\n\tpatch: '2025-01-28T16:50:53.939Z',\n}\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
6
6
  "names": []
7
7
  }
@@ -68,6 +68,7 @@ import { TLPropsMigrations } from '@tldraw/tlschema';
68
68
  import { TLRecord } from '@tldraw/tlschema';
69
69
  import { TLScribble } from '@tldraw/tlschema';
70
70
  import { TLShape } from '@tldraw/tlschema';
71
+ import { TLShapeCrop } from '@tldraw/tlschema';
71
72
  import { TLShapeId } from '@tldraw/tlschema';
72
73
  import { TLShapePartial } from '@tldraw/tlschema';
73
74
  import { TLStore } from '@tldraw/tlschema';
@@ -536,6 +537,8 @@ export declare type BoxLike = Box | BoxModel;
536
537
  */
537
538
  export declare function canonicalizeRotation(a: number): number;
538
539
 
540
+ /* Excluded from this release type: CanvasMaxSize */
541
+
539
542
  /**
540
543
  * Get the center of a circle from three points.
541
544
  *
@@ -615,6 +618,8 @@ export declare function clamp(n: number, min: number, max: number): number;
615
618
  */
616
619
  export declare function clampRadians(r: number): number;
617
620
 
621
+ /* Excluded from this release type: clampToBrowserMaxCanvasSize */
622
+
618
623
  /** @public */
619
624
  export declare class ClickManager {
620
625
  editor: Editor;
@@ -880,6 +885,7 @@ export declare const defaultTldrawOptions: {
880
885
  readonly maxPointsPerDrawShape: 500;
881
886
  readonly maxShapesPerPage: 4000;
882
887
  readonly multiClickDurationMs: 200;
888
+ readonly noteShapeResizeMode: "none";
883
889
  readonly temporaryAssetPreviewLifetimeMs: 180000;
884
890
  readonly textShadowLod: 0.35;
885
891
  };
@@ -1113,6 +1119,14 @@ export declare class Editor extends EventEmitter<TLEventMap> {
1113
1119
  getShapeUtil<S extends TLUnknownShape>(shape: S | TLShapePartial<S>): ShapeUtil<S>;
1114
1120
  getShapeUtil<S extends TLUnknownShape>(type: S['type']): ShapeUtil<S>;
1115
1121
  getShapeUtil<T extends ShapeUtil>(type: T extends ShapeUtil<infer R> ? R['type'] : string): T;
1122
+ /**
1123
+ * Returns true if the editor has a shape util for the given shape / shape type.
1124
+ *
1125
+ * @param shape - A shape, shape partial, or shape type.
1126
+ */
1127
+ hasShapeUtil<S extends TLUnknownShape>(shape: S | TLShapePartial<S>): boolean;
1128
+ hasShapeUtil<S extends TLUnknownShape>(type: S['type']): boolean;
1129
+ hasShapeUtil<T extends ShapeUtil>(type: T extends ShapeUtil<infer R> ? R['type'] : string): boolean;
1116
1130
  /**
1117
1131
  * A map of shape utility classes (TLShapeUtils) by shape type.
1118
1132
  *
@@ -2348,6 +2362,7 @@ export declare class Editor extends EventEmitter<TLEventMap> {
2348
2362
  */
2349
2363
  getAsset<T extends TLAsset>(asset: T | T['id']): T | undefined;
2350
2364
  resolveAssetUrl(assetId: null | TLAssetId, context: {
2365
+ dpr?: number;
2351
2366
  screenScale?: number;
2352
2367
  shouldResolveToOriginal?: boolean;
2353
2368
  }): Promise<null | string>;
@@ -2355,7 +2370,10 @@ export declare class Editor extends EventEmitter<TLEventMap> {
2355
2370
  * Upload an asset to the store's asset service, returning a URL that can be used to resolve the
2356
2371
  * asset.
2357
2372
  */
2358
- uploadAsset(asset: TLAsset, file: File, abortSignal?: AbortSignal): Promise<string>;
2373
+ uploadAsset(asset: TLAsset, file: File, abortSignal?: AbortSignal): Promise<{
2374
+ meta?: JsonObject;
2375
+ src: string;
2376
+ }>;
2359
2377
  private _getShapeGeometryCache;
2360
2378
  /**
2361
2379
  * Get the geometry of a shape.
@@ -3538,7 +3556,7 @@ export declare class Editor extends EventEmitter<TLEventMap> {
3538
3556
  *
3539
3557
  * @public
3540
3558
  */
3541
- getSvgElement(shapes: TLShape[] | TLShapeId[], opts?: TLImageExportOptions): Promise<{
3559
+ getSvgElement(shapes: TLShape[] | TLShapeId[], opts?: TLSvgExportOptions): Promise<{
3542
3560
  height: number;
3543
3561
  svg: SVGSVGElement;
3544
3562
  width: number;
@@ -3553,13 +3571,27 @@ export declare class Editor extends EventEmitter<TLEventMap> {
3553
3571
  *
3554
3572
  * @public
3555
3573
  */
3556
- getSvgString(shapes: TLShape[] | TLShapeId[], opts?: TLImageExportOptions): Promise<{
3574
+ getSvgString(shapes: TLShape[] | TLShapeId[], opts?: TLSvgExportOptions): Promise<{
3557
3575
  height: number;
3558
3576
  svg: string;
3559
3577
  width: number;
3560
3578
  } | undefined>;
3561
3579
  /** @deprecated Use {@link Editor.getSvgString} or {@link Editor.getSvgElement} instead. */
3562
- getSvg(shapes: TLShape[] | TLShapeId[], opts?: TLImageExportOptions): Promise<SVGSVGElement | undefined>;
3580
+ getSvg(shapes: TLShape[] | TLShapeId[], opts?: TLSvgExportOptions): Promise<SVGSVGElement | undefined>;
3581
+ /**
3582
+ * Get an exported image of the given shapes.
3583
+ *
3584
+ * @param shapes - The shapes (or shape ids) to export.
3585
+ * @param opts - Options for the export.
3586
+ *
3587
+ * @returns A blob of the image.
3588
+ * @public
3589
+ */
3590
+ toImage(shapes: TLShape[] | TLShapeId[], opts?: TLImageExportOptions): Promise<{
3591
+ blob: Blob;
3592
+ height: number;
3593
+ width: number;
3594
+ }>;
3563
3595
  /**
3564
3596
  * The app's current input state.
3565
3597
  *
@@ -4073,6 +4105,15 @@ export declare function getPolygonVertices(width: number, height: number, sides:
4073
4105
  /** @public */
4074
4106
  export declare function getSnapshot(store: TLStore): TLEditorSnapshot;
4075
4107
 
4108
+ /** @public */
4109
+ export declare function getSvgAsImage(svgString: string, options: {
4110
+ height: number;
4111
+ pixelRatio?: number;
4112
+ quality?: number;
4113
+ type: 'jpeg' | 'png' | 'webp';
4114
+ width: number;
4115
+ }): Promise<Blob | null>;
4116
+
4076
4117
  /**
4077
4118
  * Turn an array of points into a path of quadradic curves.
4078
4119
  *
@@ -4652,6 +4693,23 @@ export declare interface ResizeBoxOptions {
4652
4693
  maxHeight?: number;
4653
4694
  }
4654
4695
 
4696
+ /**
4697
+ * Resize a shape that has a scale prop.
4698
+ *
4699
+ * @param shape - The shape to resize
4700
+ * @param info - The resize info
4701
+ *
4702
+ * @public */
4703
+ export declare function resizeScaled(shape: TLBaseShape<any, {
4704
+ scale: number;
4705
+ }>, { initialBounds, scaleX, scaleY, newPoint, handle }: TLResizeInfo<any>): {
4706
+ props: {
4707
+ scale: number;
4708
+ };
4709
+ x: number;
4710
+ y: number;
4711
+ };
4712
+
4655
4713
  /** @public */
4656
4714
  export declare const ROTATE_CORNER_TO_SELECTION_CORNER: {
4657
4715
  readonly bottom_left_rotate: "bottom_left";
@@ -5023,6 +5081,15 @@ export declare abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknown
5023
5081
  * @public
5024
5082
  */
5025
5083
  onBeforeUpdate?(prev: Shape, next: Shape): Shape | void;
5084
+ /**
5085
+ * A callback called when a shape changes from a crop.
5086
+ *
5087
+ * @param shape - The shape at the start of the crop.
5088
+ * @param info - Info about the crop.
5089
+ * @returns A change to apply to the shape, or void.
5090
+ * @public
5091
+ */
5092
+ onCrop?(shape: Shape, info: TLCropInfo<Shape>): Omit<TLShapePartial<Shape>, 'id' | 'type'> | undefined | void;
5026
5093
  /**
5027
5094
  * A callback called when some other shapes are dragged over this one.
5028
5095
  *
@@ -5404,10 +5471,27 @@ export declare interface SvgExportContext {
5404
5471
  * method depending on your use-case.
5405
5472
  */
5406
5473
  waitUntil(promise: Promise<void>): void;
5474
+ /**
5475
+ * Resolve an asset URL in the context of this export. Supply the asset ID and the width in
5476
+ * shape-pixels it'll be displayed at, and this will resolve the asset according to the export
5477
+ * options.
5478
+ */
5479
+ resolveAssetUrl(assetId: TLAssetId, width: number): Promise<null | string>;
5407
5480
  /**
5408
5481
  * Whether the export should be in dark mode.
5409
5482
  */
5410
5483
  readonly isDarkMode: boolean;
5484
+ /**
5485
+ * The scale of the export - how much CSS pixels will be scaled up/down by.
5486
+ */
5487
+ readonly scale: number;
5488
+ /**
5489
+ * Use this value to optionally downscale images in the export. If we're exporting directly to
5490
+ * an SVG, this will usually be null, and you shouldn't downscale images. If the export is to a
5491
+ * raster format like PNG, this will be the number of raster pixels in the resulting bitmap per
5492
+ * CSS pixel in the resulting SVG.
5493
+ */
5494
+ readonly pixelRatio: null | number;
5411
5495
  }
5412
5496
 
5413
5497
  /** @public */
@@ -5663,6 +5747,24 @@ export declare interface TLContent {
5663
5747
  schema: SerializedSchema;
5664
5748
  }
5665
5749
 
5750
+ /**
5751
+ * Info about a crop.
5752
+ * @param handle - The handle being dragged.
5753
+ * @param change - The distance the handle is moved.
5754
+ * @param initialShape - The shape at the start of the resize.
5755
+ * @public
5756
+ */
5757
+ export declare interface TLCropInfo<T extends TLShape> {
5758
+ handle: SelectionHandle;
5759
+ change: Vec;
5760
+ crop: TLShapeCrop;
5761
+ uncroppedSize: {
5762
+ h: number;
5763
+ w: number;
5764
+ };
5765
+ initialShape: T;
5766
+ }
5767
+
5666
5768
  /** @public */
5667
5769
  export declare interface TLCursorProps {
5668
5770
  className?: string;
@@ -5915,6 +6017,11 @@ export declare interface TldrawOptions {
5915
6017
  readonly exportProvider: ComponentType<{
5916
6018
  children: React.ReactNode;
5917
6019
  }>;
6020
+ /**
6021
+ * How should the note shape resize? By default it does not resize (except automatically based on its text content),
6022
+ * but you can set it to be user-resizable using scale.
6023
+ */
6024
+ readonly noteShapeResizeMode: 'none' | 'scale';
5918
6025
  }
5919
6026
 
5920
6027
  /** @public */
@@ -6103,6 +6210,9 @@ export declare type TLEventName = 'cancel' | 'complete' | 'interrupt' | 'tick' |
6103
6210
  /** @public */
6104
6211
  export declare type TLExitEventHandler = (info: any, to: string) => void;
6105
6212
 
6213
+ /** @public */
6214
+ export declare type TLExportType = 'jpeg' | 'png' | 'svg' | 'webp';
6215
+
6106
6216
  /** @public */
6107
6217
  export declare type TLExternalAssetContent = {
6108
6218
  assetId?: TLAssetId;
@@ -6209,15 +6319,16 @@ export declare interface TLHistoryMark {
6209
6319
  }
6210
6320
 
6211
6321
  /** @public */
6212
- export declare interface TLImageExportOptions {
6213
- bounds?: Box;
6214
- scale?: number;
6322
+ export declare interface TLImageExportOptions extends TLSvgExportOptions {
6323
+ /**
6324
+ * If the export is being converted to a lossy bitmap format (e.g. jpeg), this is the quality of
6325
+ * the export. This is a number between 0 and 1.
6326
+ */
6215
6327
  quality?: number;
6216
- pixelRatio?: number;
6217
- background?: boolean;
6218
- padding?: number;
6219
- darkMode?: boolean;
6220
- preserveAspectRatio?: React.SVGAttributes<SVGSVGElement>['preserveAspectRatio'];
6328
+ /**
6329
+ * The format to export as. Defaults to 'png'.
6330
+ */
6331
+ format?: TLExportType;
6221
6332
  }
6222
6333
 
6223
6334
  /** @public */
@@ -6721,6 +6832,50 @@ export declare type TLStoreWithStatus = {
6721
6832
  readonly store: TLStore;
6722
6833
  };
6723
6834
 
6835
+ /** @public */
6836
+ export declare interface TLSvgExportOptions {
6837
+ /**
6838
+ * The bounding box, in page coordinates, of the area being exported.
6839
+ */
6840
+ bounds?: Box;
6841
+ /**
6842
+ * The logical scale of the export. This scales the resulting size of the SVG being generated.
6843
+ */
6844
+ scale?: number;
6845
+ /**
6846
+ * When exporting an SVG, the expected pixel ratio of the export will be passed in to
6847
+ * {@link @tldraw/tlschema#TLAssetStore.resolve} as the `dpr` property, so that assets can be
6848
+ * downscaled to the appropriate resolution.
6849
+ *
6850
+ * When exporting to a bitmap image format, the size of the resulting image will be multiplied
6851
+ * by this number.
6852
+ *
6853
+ * For SVG exports, this defaults to undefined - which means we'll request original-quality
6854
+ * assets. For bitmap exports, this defaults to 2.
6855
+ */
6856
+ pixelRatio?: number;
6857
+ /**
6858
+ * Should the background color be included in the export? If false, the generated image will be
6859
+ * transparent (if exporting to a format that supports transparency).
6860
+ */
6861
+ background?: boolean;
6862
+ /**
6863
+ * How much padding to include around the bounds of exports? Defaults to 32px.
6864
+ */
6865
+ padding?: number;
6866
+ /**
6867
+ * Should the export be rendered in dark mode (true) or light mode (false)? Defaults to the
6868
+ * current instance's dark mode setting.
6869
+ */
6870
+ darkMode?: boolean;
6871
+ /**
6872
+ * The
6873
+ * {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAspectRatio | `preserveAspectRatio` }
6874
+ * attribute of the SVG element.
6875
+ */
6876
+ preserveAspectRatio?: React.SVGAttributes<SVGSVGElement>['preserveAspectRatio'];
6877
+ }
6878
+
6724
6879
  /**
6725
6880
  * @public
6726
6881
  * @deprecated use {@link TLImageExportOptions} instead
@@ -23,6 +23,7 @@ import {
23
23
  useStateTracking,
24
24
  useValue
25
25
  } from "@tldraw/state-react";
26
+ import { resizeScaled } from "./lib/editor/shapes/shared/resizeScaled.mjs";
26
27
  import { LocalIndexedDb, Table } from "./lib/utils/sync/LocalIndexedDb.mjs";
27
28
  export * from "@tldraw/store";
28
29
  export * from "@tldraw/tlschema";
@@ -142,6 +143,7 @@ import {
142
143
  import {
143
144
  EVENT_NAME_MAP
144
145
  } from "./lib/editor/types/event-types.mjs";
146
+ import { getSvgAsImage } from "./lib/exports/getSvgAsImage.mjs";
145
147
  import { tlenv } from "./lib/globals/environment.mjs";
146
148
  import { tlmenus } from "./lib/globals/menus.mjs";
147
149
  import { tltime } from "./lib/globals/time.mjs";
@@ -250,6 +252,7 @@ import {
250
252
  SharedStyleMap
251
253
  } from "./lib/utils/SharedStylesMap.mjs";
252
254
  import { dataUrlToFile, getDefaultCdnBaseUrl } from "./lib/utils/assets.mjs";
255
+ import { clampToBrowserMaxCanvasSize } from "./lib/utils/browserCanvasMaxSize.mjs";
253
256
  import {
254
257
  debugFlags,
255
258
  featureFlags
@@ -285,7 +288,7 @@ function debugEnableLicensing() {
285
288
  }
286
289
  registerTldrawLibraryVersion(
287
290
  "@tldraw/editor",
288
- "3.8.0-canary.c45a05a15c4a",
291
+ "3.8.0-canary.c5d6281c3fdd",
289
292
  "esm"
290
293
  );
291
294
  export {
@@ -377,6 +380,7 @@ export {
377
380
  centerOfCircleFromThreePoints,
378
381
  clamp,
379
382
  clampRadians,
383
+ clampToBrowserMaxCanvasSize,
380
384
  clockwiseAngleDist,
381
385
  computed,
382
386
  coreShapes,
@@ -407,6 +411,7 @@ export {
407
411
  getPolygonVertices,
408
412
  getRotationSnapshot,
409
413
  getSnapshot,
414
+ getSvgAsImage,
410
415
  getSvgPathFromPoints,
411
416
  getUserPreferences,
412
417
  hardReset,
@@ -443,6 +448,7 @@ export {
443
448
  refreshPage,
444
449
  releasePointerCapture,
445
450
  resizeBox,
451
+ resizeScaled,
446
452
  rotateSelectionHandle,
447
453
  runtime,
448
454
  sanitizeId,