@tldraw/editor 4.4.0-next.84d68f44c848 → 4.4.0-next.bde73a32273d

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 (126) hide show
  1. package/dist-cjs/index.d.ts +277 -30
  2. package/dist-cjs/index.js +1 -6
  3. package/dist-cjs/index.js.map +3 -3
  4. package/dist-cjs/lib/TldrawEditor.js +20 -8
  5. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  6. package/dist-cjs/lib/components/LiveCollaborators.js +14 -24
  7. package/dist-cjs/lib/components/LiveCollaborators.js.map +2 -2
  8. package/dist-cjs/lib/components/Shape.js +12 -17
  9. package/dist-cjs/lib/components/Shape.js.map +2 -2
  10. package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js +226 -0
  11. package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js.map +7 -0
  12. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +45 -15
  13. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  14. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js +3 -1
  15. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js.map +2 -2
  16. package/dist-cjs/lib/components/default-components/DefaultShapeIndicators.js +13 -1
  17. package/dist-cjs/lib/components/default-components/DefaultShapeIndicators.js.map +2 -2
  18. package/dist-cjs/lib/config/TLUserPreferences.js +9 -3
  19. package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
  20. package/dist-cjs/lib/config/createTLStore.js.map +1 -1
  21. package/dist-cjs/lib/editor/Editor.js +39 -18
  22. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  23. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +32 -13
  24. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +2 -2
  25. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js +2 -3
  26. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js.map +2 -2
  27. package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js +13 -38
  28. package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js.map +3 -3
  29. package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js +378 -89
  30. package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js.map +2 -2
  31. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +8 -3
  32. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  33. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +29 -0
  34. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  35. package/dist-cjs/lib/editor/tools/RootState.js +0 -13
  36. package/dist-cjs/lib/editor/tools/RootState.js.map +2 -2
  37. package/dist-cjs/lib/hooks/usePeerIds.js +35 -0
  38. package/dist-cjs/lib/hooks/usePeerIds.js.map +2 -2
  39. package/dist-cjs/lib/hooks/useShapeCulling.js +75 -0
  40. package/dist-cjs/lib/hooks/useShapeCulling.js.map +7 -0
  41. package/dist-cjs/lib/license/LicenseManager.js +6 -6
  42. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  43. package/dist-cjs/lib/options.js +7 -1
  44. package/dist-cjs/lib/options.js.map +2 -2
  45. package/dist-cjs/lib/utils/collaboratorState.js +42 -0
  46. package/dist-cjs/lib/utils/collaboratorState.js.map +7 -0
  47. package/dist-cjs/version.js +3 -3
  48. package/dist-cjs/version.js.map +1 -1
  49. package/dist-esm/index.d.mts +277 -30
  50. package/dist-esm/index.mjs +1 -6
  51. package/dist-esm/index.mjs.map +2 -2
  52. package/dist-esm/lib/TldrawEditor.mjs +20 -8
  53. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  54. package/dist-esm/lib/components/LiveCollaborators.mjs +17 -24
  55. package/dist-esm/lib/components/LiveCollaborators.mjs.map +2 -2
  56. package/dist-esm/lib/components/Shape.mjs +12 -17
  57. package/dist-esm/lib/components/Shape.mjs.map +2 -2
  58. package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs +206 -0
  59. package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs.map +7 -0
  60. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +45 -15
  61. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  62. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs +3 -1
  63. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +2 -2
  64. package/dist-esm/lib/components/default-components/DefaultShapeIndicators.mjs +13 -1
  65. package/dist-esm/lib/components/default-components/DefaultShapeIndicators.mjs.map +2 -2
  66. package/dist-esm/lib/config/TLUserPreferences.mjs +9 -3
  67. package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
  68. package/dist-esm/lib/config/createTLStore.mjs.map +1 -1
  69. package/dist-esm/lib/editor/Editor.mjs +39 -18
  70. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  71. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +32 -13
  72. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +2 -2
  73. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs +2 -3
  74. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs.map +2 -2
  75. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs +14 -39
  76. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs.map +2 -2
  77. package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs +378 -89
  78. package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs.map +2 -2
  79. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +8 -3
  80. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  81. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +29 -0
  82. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  83. package/dist-esm/lib/editor/tools/RootState.mjs +0 -13
  84. package/dist-esm/lib/editor/tools/RootState.mjs.map +2 -2
  85. package/dist-esm/lib/hooks/usePeerIds.mjs +39 -1
  86. package/dist-esm/lib/hooks/usePeerIds.mjs.map +2 -2
  87. package/dist-esm/lib/hooks/useShapeCulling.mjs +55 -0
  88. package/dist-esm/lib/hooks/useShapeCulling.mjs.map +7 -0
  89. package/dist-esm/lib/license/LicenseManager.mjs +6 -6
  90. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  91. package/dist-esm/lib/options.mjs +7 -1
  92. package/dist-esm/lib/options.mjs.map +2 -2
  93. package/dist-esm/lib/utils/collaboratorState.mjs +22 -0
  94. package/dist-esm/lib/utils/collaboratorState.mjs.map +7 -0
  95. package/dist-esm/version.mjs +3 -3
  96. package/dist-esm/version.mjs.map +1 -1
  97. package/editor.css +28 -11
  98. package/package.json +8 -11
  99. package/src/index.ts +2 -5
  100. package/src/lib/TldrawEditor.tsx +35 -13
  101. package/src/lib/components/LiveCollaborators.tsx +26 -37
  102. package/src/lib/components/Shape.tsx +15 -16
  103. package/src/lib/components/default-components/CanvasShapeIndicators.tsx +288 -0
  104. package/src/lib/components/default-components/DefaultCanvas.tsx +39 -3
  105. package/src/lib/components/default-components/DefaultShapeIndicator.tsx +6 -1
  106. package/src/lib/components/default-components/DefaultShapeIndicators.tsx +16 -1
  107. package/src/lib/config/TLUserPreferences.test.ts +1 -0
  108. package/src/lib/config/TLUserPreferences.ts +8 -0
  109. package/src/lib/config/createTLStore.ts +1 -1
  110. package/src/lib/editor/Editor.ts +72 -20
  111. package/src/lib/editor/derivations/notVisibleShapes.ts +39 -17
  112. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +0 -35
  113. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.ts +4 -8
  114. package/src/lib/editor/managers/HistoryManager/HistoryManager.ts +19 -47
  115. package/src/lib/editor/managers/ScribbleManager/ScribbleManager.ts +491 -106
  116. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +24 -0
  117. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +8 -0
  118. package/src/lib/editor/shapes/ShapeUtil.ts +63 -5
  119. package/src/lib/editor/tools/RootState.ts +0 -16
  120. package/src/lib/hooks/usePeerIds.ts +53 -1
  121. package/src/lib/hooks/useShapeCulling.tsx +98 -0
  122. package/src/lib/license/LicenseManager.ts +6 -6
  123. package/src/lib/options.ts +48 -2
  124. package/src/lib/utils/collaboratorState.ts +54 -0
  125. package/src/version.ts +3 -3
  126. package/src/lib/editor/managers/ScribbleManager/ScribbleManager.test.ts +0 -621
@@ -1,4 +1,5 @@
1
1
  import { Fragment } from "react";
2
+ import { DEFAULT_CAMERA_OPTIONS } from "./constants.mjs";
2
3
  const defaultTldrawOptions = {
3
4
  maxShapesPerPage: 4e3,
4
5
  maxFilesAtOnce: 100,
@@ -43,6 +44,7 @@ const defaultTldrawOptions = {
43
44
  flattenImageBoundsExpand: 64,
44
45
  flattenImageBoundsPadding: 16,
45
46
  laserDelayMs: 1200,
47
+ laserFadeoutMs: 500,
46
48
  maxExportDelayMs: 5e3,
47
49
  tooltipDelayMs: 700,
48
50
  temporaryAssetPreviewLifetimeMs: 18e4,
@@ -56,7 +58,11 @@ const defaultTldrawOptions = {
56
58
  debouncedZoomThreshold: 500,
57
59
  spacebarPanning: true,
58
60
  zoomToFitPadding: 128,
59
- snapThreshold: 8
61
+ snapThreshold: 8,
62
+ camera: DEFAULT_CAMERA_OPTIONS,
63
+ text: {},
64
+ deepLinks: void 0,
65
+ quickZoomPreservesScreenBounds: true
60
66
  };
61
67
  export {
62
68
  defaultTldrawOptions
@@ -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 uiDragDistanceSquared: number\n\treadonly uiCoarseDragDistanceSquared: number\n\treadonly defaultSvgPadding: number\n\treadonly cameraSlideFriction: 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\treadonly tooltipDelayMs: 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 * By default, the toolbar items are accessible via number shortcuts according to their order. To disable this, set this option to false.\n\t */\n\treadonly enableToolbarKeyboardShortcuts: boolean\n\t/**\n\t * The maximum number of fonts that will be loaded while blocking the main rendering of the\n\t * canvas. If there are more than this number of fonts needed, we'll just show the canvas right\n\t * away and let the fonts load in in the background.\n\t */\n\treadonly maxFontsToLoadBeforeRender: number\n\t/**\n\t * If you have a CSP policy that blocks inline styles, you can use this prop to provide a\n\t * nonce to use in the editor's styles.\n\t */\n\treadonly nonce: string | undefined\n\t/**\n\t * Branding name of the app, currently only used for adding aria-label for the application.\n\t */\n\treadonly branding?: string\n\t/**\n\t * Whether to use debounced zoom level for certain rendering optimizations. When true,\n\t * `editor.getDebouncedZoomLevel()` returns a cached zoom value while the camera is moving,\n\t * reducing re-renders. When false, it always returns the current zoom level.\n\t */\n\treadonly debouncedZoom: boolean\n\t/**\n\t * The number of shapes that must be on the page for the debounced zoom level to be used.\n\t * Defaults to 300 shapes.\n\t */\n\treadonly debouncedZoomThreshold: number\n\t/**\n\t * Whether to allow spacebar panning. When true, the spacebar will pan the camera when held down.\n\t * When false, the spacebar will not pan the camera.\n\t */\n\treadonly spacebarPanning: boolean\n\t/**\n\t * The default padding (in pixels) used when zooming to fit content in the viewport.\n\t * This affects methods like `zoomToFit()`, `zoomToSelection()`, and `zoomToBounds()`.\n\t * The actual padding used is the minimum of this value and 28% of the viewport width.\n\t * Defaults to 128 pixels.\n\t */\n\treadonly zoomToFitPadding: number\n\t/**\n\t * The distance (in screen pixels) at which shapes snap to guides and other shapes.\n\t */\n\treadonly snapThreshold: number\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\tuiDragDistanceSquared: 16, // 4 squared\n\t// it's really easy to accidentally drag from the toolbar on mobile, so we use a much larger\n\t// threshold than usual here to try and prevent accidental drags.\n\tuiCoarseDragDistanceSquared: 625, // 25 squared\n\tdefaultSvgPadding: 32,\n\tcameraSlideFriction: 0.09,\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\ttooltipDelayMs: 700,\n\ttemporaryAssetPreviewLifetimeMs: 180000,\n\tactionShortcutsLocation: 'swap',\n\tcreateTextOnCanvasDoubleClick: true,\n\texportProvider: Fragment,\n\tenableToolbarKeyboardShortcuts: true,\n\tmaxFontsToLoadBeforeRender: Infinity,\n\tnonce: undefined,\n\tdebouncedZoom: true,\n\tdebouncedZoomThreshold: 500,\n\tspacebarPanning: true,\n\tzoomToFitPadding: 128,\n\tsnapThreshold: 8,\n} as const satisfies TldrawOptions\n"],
5
- "mappings": "AAAA,SAAwB,gBAAgB;AAuHjC,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,uBAAuB;AAAA;AAAA;AAAA;AAAA,EAGvB,6BAA6B;AAAA;AAAA,EAC7B,mBAAmB;AAAA,EACnB,qBAAqB;AAAA,EACrB,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,gBAAgB;AAAA,EAChB,iCAAiC;AAAA,EACjC,yBAAyB;AAAA,EACzB,+BAA+B;AAAA,EAC/B,gBAAgB;AAAA,EAChB,gCAAgC;AAAA,EAChC,4BAA4B;AAAA,EAC5B,OAAO;AAAA,EACP,eAAe;AAAA,EACf,wBAAwB;AAAA,EACxB,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB,eAAe;AAChB;",
4
+ "sourcesContent": ["import { ComponentType, Fragment } from 'react'\nimport { DEFAULT_CAMERA_OPTIONS } from './constants'\nimport { TLCameraOptions } from './editor/types/misc-types'\nimport { TLDeepLinkOptions } from './utils/deepLinks'\nimport { TLTextOptions } from './utils/richText'\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 uiDragDistanceSquared: number\n\treadonly uiCoarseDragDistanceSquared: number\n\treadonly defaultSvgPadding: number\n\treadonly cameraSlideFriction: 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\t/**\n\t * How long (in milliseconds) to fade all laser scribbles after the session ends.\n\t * The total points across all scribbles will be removed proportionally over this duration.\n\t * Defaults to 500ms (0.5 seconds).\n\t */\n\treadonly laserFadeoutMs: number\n\treadonly maxExportDelayMs: number\n\treadonly tooltipDelayMs: 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 * By default, the toolbar items are accessible via number shortcuts according to their order. To disable this, set this option to false.\n\t */\n\treadonly enableToolbarKeyboardShortcuts: boolean\n\t/**\n\t * The maximum number of fonts that will be loaded while blocking the main rendering of the\n\t * canvas. If there are more than this number of fonts needed, we'll just show the canvas right\n\t * away and let the fonts load in in the background.\n\t */\n\treadonly maxFontsToLoadBeforeRender: number\n\t/**\n\t * If you have a CSP policy that blocks inline styles, you can use this prop to provide a\n\t * nonce to use in the editor's styles.\n\t */\n\treadonly nonce: string | undefined\n\t/**\n\t * Branding name of the app, currently only used for adding aria-label for the application.\n\t */\n\treadonly branding?: string\n\t/**\n\t * Whether to use debounced zoom level for certain rendering optimizations. When true,\n\t * `editor.getEfficientZoomLevel()` returns a cached zoom value while the camera is moving,\n\t * reducing re-renders. When false, it always returns the current zoom level.\n\t */\n\treadonly debouncedZoom: boolean\n\t/**\n\t * The number of shapes that must be on the page for the debounced zoom level to be used.\n\t * Defaults to 500 shapes.\n\t */\n\treadonly debouncedZoomThreshold: number\n\t/**\n\t * Whether to allow spacebar panning. When true, the spacebar will pan the camera when held down.\n\t * When false, the spacebar will not pan the camera.\n\t */\n\treadonly spacebarPanning: boolean\n\t/**\n\t * The default padding (in pixels) used when zooming to fit content in the viewport.\n\t * This affects methods like `zoomToFit()`, `zoomToSelection()`, and `zoomToBounds()`.\n\t * The actual padding used is the minimum of this value and 28% of the viewport width.\n\t * Defaults to 128 pixels.\n\t */\n\treadonly zoomToFitPadding: number\n\t/**\n\t * The distance (in screen pixels) at which shapes snap to guides and other shapes.\n\t */\n\treadonly snapThreshold: number\n\t/**\n\t * Options for the editor's camera. These are the initial camera options.\n\t * Use {@link Editor.setCameraOptions} to update camera options at runtime.\n\t */\n\treadonly camera: Partial<TLCameraOptions>\n\t/**\n\t * Options for the editor's text rendering. These include TipTap configuration and\n\t * font handling. These are the initial text options and cannot be changed at runtime.\n\t */\n\treadonly text: TLTextOptions\n\t/**\n\t * Options for syncing the editor's camera state with the URL. Set to `true` to enable\n\t * with default options, or pass an options object to customize behavior.\n\t *\n\t * @example\n\t * ```tsx\n\t * // Enable with defaults\n\t * <Tldraw options={{ deepLinks: true }} />\n\t *\n\t * // Enable with custom options\n\t * <Tldraw options={{ deepLinks: { param: 'd', debounceMs: 500 } }} />\n\t * ```\n\t */\n\treadonly deepLinks: true | TLDeepLinkOptions | undefined\n\t/**\n\t * Whether the quick-zoom brush preserves its screen-pixel size when the user\n\t * zooms the overview. When true, zooming in shrinks the target viewport (higher\n\t * return zoom); zooming out expands it. When false, the brush keeps the original\n\t * viewport's page dimensions regardless of overview zoom changes.\n\t */\n\treadonly quickZoomPreservesScreenBounds: boolean\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\tuiDragDistanceSquared: 16, // 4 squared\n\t// it's really easy to accidentally drag from the toolbar on mobile, so we use a much larger\n\t// threshold than usual here to try and prevent accidental drags.\n\tuiCoarseDragDistanceSquared: 625, // 25 squared\n\tdefaultSvgPadding: 32,\n\tcameraSlideFriction: 0.09,\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\tlaserFadeoutMs: 500,\n\tmaxExportDelayMs: 5000,\n\ttooltipDelayMs: 700,\n\ttemporaryAssetPreviewLifetimeMs: 180000,\n\tactionShortcutsLocation: 'swap',\n\tcreateTextOnCanvasDoubleClick: true,\n\texportProvider: Fragment,\n\tenableToolbarKeyboardShortcuts: true,\n\tmaxFontsToLoadBeforeRender: Infinity,\n\tnonce: undefined,\n\tdebouncedZoom: true,\n\tdebouncedZoomThreshold: 500,\n\tspacebarPanning: true,\n\tzoomToFitPadding: 128,\n\tsnapThreshold: 8,\n\tcamera: DEFAULT_CAMERA_OPTIONS,\n\ttext: {},\n\tdeepLinks: undefined,\n\tquickZoomPreservesScreenBounds: true,\n} as const satisfies TldrawOptions\n"],
5
+ "mappings": "AAAA,SAAwB,gBAAgB;AACxC,SAAS,8BAA8B;AA+JhC,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,uBAAuB;AAAA;AAAA;AAAA;AAAA,EAGvB,6BAA6B;AAAA;AAAA,EAC7B,mBAAmB;AAAA,EACnB,qBAAqB;AAAA,EACrB,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,gBAAgB;AAAA,EAChB,kBAAkB;AAAA,EAClB,gBAAgB;AAAA,EAChB,iCAAiC;AAAA,EACjC,yBAAyB;AAAA,EACzB,+BAA+B;AAAA,EAC/B,gBAAgB;AAAA,EAChB,gCAAgC;AAAA,EAChC,4BAA4B;AAAA,EAC5B,OAAO;AAAA,EACP,eAAe;AAAA,EACf,wBAAwB;AAAA,EACxB,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,QAAQ;AAAA,EACR,MAAM,CAAC;AAAA,EACP,WAAW;AAAA,EACX,gCAAgC;AACjC;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,22 @@
1
+ function getCollaboratorStateFromElapsedTime(editor, elapsed) {
2
+ return elapsed > editor.options.collaboratorInactiveTimeoutMs ? "inactive" : elapsed > editor.options.collaboratorIdleTimeoutMs ? "idle" : "active";
3
+ }
4
+ function shouldShowCollaborator(editor, presence, state) {
5
+ const { followingUserId, highlightedUserIds } = editor.getInstanceState();
6
+ switch (state) {
7
+ case "inactive":
8
+ return followingUserId === presence.userId || highlightedUserIds.includes(presence.userId);
9
+ case "idle":
10
+ if (presence.followingUserId === editor.user.getId()) {
11
+ return !!(presence.chatMessage || highlightedUserIds.includes(presence.userId));
12
+ }
13
+ return true;
14
+ case "active":
15
+ return true;
16
+ }
17
+ }
18
+ export {
19
+ getCollaboratorStateFromElapsedTime,
20
+ shouldShowCollaborator
21
+ };
22
+ //# sourceMappingURL=collaboratorState.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/utils/collaboratorState.ts"],
4
+ "sourcesContent": ["import { TLInstancePresence } from '@tldraw/tlschema'\nimport { Editor } from '../editor/Editor'\n\n/** The activity state of a collaborator */\nexport type CollaboratorState = 'active' | 'idle' | 'inactive'\n\n/**\n * Get the activity state of a collaborator based on elapsed time since their last activity.\n *\n * @param editor - The editor instance\n * @param elapsed - Time in milliseconds since the collaborator's last activity\n * @returns The collaborator's activity state\n */\nexport function getCollaboratorStateFromElapsedTime(\n\teditor: Editor,\n\telapsed: number\n): CollaboratorState {\n\treturn elapsed > editor.options.collaboratorInactiveTimeoutMs\n\t\t? 'inactive'\n\t\t: elapsed > editor.options.collaboratorIdleTimeoutMs\n\t\t\t? 'idle'\n\t\t\t: 'active'\n}\n\n/**\n * Determine whether a collaborator should be shown based on their activity state\n * and the current instance state (following, highlighted users, etc.).\n *\n * @param editor - The editor instance\n * @param presence - The collaborator's presence data\n * @param state - The collaborator's activity state\n * @returns Whether the collaborator should be shown\n */\nexport function shouldShowCollaborator(\n\teditor: Editor,\n\tpresence: TLInstancePresence,\n\tstate: CollaboratorState\n): boolean {\n\tconst { followingUserId, highlightedUserIds } = editor.getInstanceState()\n\n\tswitch (state) {\n\t\tcase 'inactive':\n\t\t\t// If they're inactive, only show if we're following them or they're highlighted\n\t\t\treturn followingUserId === presence.userId || highlightedUserIds.includes(presence.userId)\n\t\tcase 'idle':\n\t\t\t// If they're idle and following us, hide them unless they have a chat message or are highlighted\n\t\t\tif (presence.followingUserId === editor.user.getId()) {\n\t\t\t\treturn !!(presence.chatMessage || highlightedUserIds.includes(presence.userId))\n\t\t\t}\n\t\t\treturn true\n\t\tcase 'active':\n\t\t\treturn true\n\t}\n}\n"],
5
+ "mappings": "AAaO,SAAS,oCACf,QACA,SACoB;AACpB,SAAO,UAAU,OAAO,QAAQ,gCAC7B,aACA,UAAU,OAAO,QAAQ,4BACxB,SACA;AACL;AAWO,SAAS,uBACf,QACA,UACA,OACU;AACV,QAAM,EAAE,iBAAiB,mBAAmB,IAAI,OAAO,iBAAiB;AAExE,UAAQ,OAAO;AAAA,IACd,KAAK;AAEJ,aAAO,oBAAoB,SAAS,UAAU,mBAAmB,SAAS,SAAS,MAAM;AAAA,IAC1F,KAAK;AAEJ,UAAI,SAAS,oBAAoB,OAAO,KAAK,MAAM,GAAG;AACrD,eAAO,CAAC,EAAE,SAAS,eAAe,mBAAmB,SAAS,SAAS,MAAM;AAAA,MAC9E;AACA,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,EACT;AACD;",
6
+ "names": []
7
+ }
@@ -1,8 +1,8 @@
1
- const version = "4.4.0-next.84d68f44c848";
1
+ const version = "4.4.0-next.bde73a32273d";
2
2
  const publishDates = {
3
3
  major: "2025-09-18T14:39:22.803Z",
4
- minor: "2026-01-22T09:49:37.857Z",
5
- patch: "2026-01-22T09:49:37.857Z"
4
+ minor: "2026-02-11T22:21:46.227Z",
5
+ patch: "2026-02-11T22:21:46.227Z"
6
6
  };
7
7
  export {
8
8
  publishDates,
@@ -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 = '4.4.0-next.84d68f44c848'\nexport const publishDates = {\n\tmajor: '2025-09-18T14:39:22.803Z',\n\tminor: '2026-01-22T09:49:37.857Z',\n\tpatch: '2026-01-22T09:49:37.857Z',\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 = '4.4.0-next.bde73a32273d'\nexport const publishDates = {\n\tmajor: '2025-09-18T14:39:22.803Z',\n\tminor: '2026-02-11T22:21:46.227Z',\n\tpatch: '2026-02-11T22:21:46.227Z',\n}\n"],
5
5
  "mappings": "AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
6
6
  "names": []
7
7
  }
package/editor.css CHANGED
@@ -22,14 +22,25 @@
22
22
  --tl-radius-3: 9px;
23
23
  --tl-radius-4: 11px;
24
24
 
25
- /* Canvas z-index */
25
+ /*
26
+ * Canvas z-index
27
+ *
28
+ * .tl-canvas has `contain: strict` which creates its own stacking context.
29
+ * background, grid, shapes, overlays, and blocker are all children of .tl-canvas,
30
+ * so their z-indices are relative to the canvas, not the container.
31
+ *
32
+ * watermark and canvas-in-front are siblings of .tl-canvas (outside it),
33
+ * so they compete with the UI layer (.tlui-layout at z-index 300).
34
+ * canvas-in-front must be below the UI layer (300) to sit between
35
+ * the canvas and the UI.
36
+ */
26
37
  --tl-layer-canvas-hidden: -999999;
27
38
  --tl-layer-canvas-background: 100;
28
39
  --tl-layer-canvas-grid: 150;
29
40
  --tl-layer-watermark: 200;
41
+ --tl-layer-canvas-in-front: 250;
30
42
  --tl-layer-canvas-shapes: 300;
31
43
  --tl-layer-canvas-overlays: 500;
32
- --tl-layer-canvas-in-front: 600;
33
44
  --tl-layer-canvas-blocker: 10000;
34
45
 
35
46
  /* Canvas overlays z-index */
@@ -60,6 +71,7 @@
60
71
 
61
72
  /* Misc */
62
73
  --tl-zoom: 1;
74
+ --tl-tab-size: 2;
63
75
 
64
76
  /* Cursor SVGs */
65
77
  --tl-cursor-none: none;
@@ -146,6 +158,8 @@
146
158
  --tl-color-selection-fill: hsl(210, 100%, 56%, 24%);
147
159
  --tl-color-selection-stroke: hsl(214, 84%, 56%);
148
160
  --tl-color-background: hsl(210, 20%, 98%);
161
+ /* if you ever update --tl-color-background, update the hardcoded values in theme-init.js and globals.css
162
+ they're there to make sure the background color matches the user's preference before the actual app loads*/
149
163
  --tl-color-brush-fill: hsl(0, 0%, 56%, 10.2%);
150
164
  --tl-color-brush-stroke: hsl(0, 0%, 56%, 25.1%);
151
165
  --tl-color-grid: hsl(0, 0%, 43%);
@@ -202,6 +216,8 @@
202
216
  --tl-color-selection-fill: hsl(209, 100%, 57%, 20%);
203
217
  --tl-color-selection-stroke: hsl(214, 84%, 56%);
204
218
  --tl-color-background: hsl(240, 5%, 6.5%);
219
+ /* if you ever update --tl-color-background, update the hardcoded values in theme-init.js and globals.css
220
+ they're there to make sure the background color matches the user's preference before the actual app loads*/
205
221
  --tl-color-brush-fill: hsl(0, 0%, 71%, 5.1%);
206
222
  --tl-color-brush-stroke: hsl(0, 0%, 71%, 25.1%);
207
223
  --tl-color-grid: hsl(0, 0%, 40%);
@@ -895,6 +911,10 @@ input,
895
911
  /* white-space: break-spaces; */
896
912
  }
897
913
 
914
+ .tl-rich-text {
915
+ tab-size: var(--tl-tab-size, 2);
916
+ }
917
+
898
918
  .tl-rich-text p {
899
919
  margin: 0;
900
920
  /* Depending on the extensions, <p> tags can be empty, without a <br />. */
@@ -1162,6 +1182,12 @@ input,
1162
1182
  contain: size layout;
1163
1183
  }
1164
1184
 
1185
+ .tl-canvas-indicators {
1186
+ position: absolute;
1187
+ inset: 0;
1188
+ pointer-events: none;
1189
+ }
1190
+
1165
1191
  /* ---------------------- Shape --------------------- */
1166
1192
 
1167
1193
  .tl-shape {
@@ -1536,15 +1562,6 @@ input,
1536
1562
  cursor: var(--tl-cursor-text);
1537
1563
  }
1538
1564
 
1539
- /* If mobile use 16px as font size */
1540
- /* On iOS, font size under 16px in an input will make the page zoom into the input 🤦‍♂️ */
1541
- /* https://css-tricks.com/16px-or-larger-text-prevents-ios-form-zoom/ */
1542
- @media (max-width: 600px) {
1543
- .tl-frame-heading {
1544
- font-size: 16px;
1545
- }
1546
- }
1547
-
1548
1565
  /* ------------------- Embed Shape ------------------ */
1549
1566
 
1550
1567
  .tl-embed {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tldraw/editor",
3
3
  "description": "tldraw infinite canvas SDK (editor).",
4
- "version": "4.4.0-next.84d68f44c848",
4
+ "version": "4.4.0-next.bde73a32273d",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -43,23 +43,20 @@
43
43
  "prepack": "yarn run -T tsx ../../internal/scripts/prepack.ts",
44
44
  "postpack": "../../internal/scripts/postpack.sh",
45
45
  "pack-tarball": "yarn pack",
46
- "lint": "yarn run -T tsx ../../internal/scripts/lint.ts",
47
- "context": "yarn run -T tsx ../../internal/scripts/context.ts"
46
+ "lint": "yarn run -T tsx ../../internal/scripts/lint.ts"
48
47
  },
49
48
  "dependencies": {
50
49
  "@tiptap/core": "^3.12.1",
51
50
  "@tiptap/pm": "^3.12.1",
52
51
  "@tiptap/react": "^3.12.1",
53
- "@tldraw/state": "4.4.0-next.84d68f44c848",
54
- "@tldraw/state-react": "4.4.0-next.84d68f44c848",
55
- "@tldraw/store": "4.4.0-next.84d68f44c848",
56
- "@tldraw/tlschema": "4.4.0-next.84d68f44c848",
57
- "@tldraw/utils": "4.4.0-next.84d68f44c848",
58
- "@tldraw/validate": "4.4.0-next.84d68f44c848",
59
- "@types/core-js": "^2.5.8",
52
+ "@tldraw/state": "4.4.0-next.bde73a32273d",
53
+ "@tldraw/state-react": "4.4.0-next.bde73a32273d",
54
+ "@tldraw/store": "4.4.0-next.bde73a32273d",
55
+ "@tldraw/tlschema": "4.4.0-next.bde73a32273d",
56
+ "@tldraw/utils": "4.4.0-next.bde73a32273d",
57
+ "@tldraw/validate": "4.4.0-next.bde73a32273d",
60
58
  "@use-gesture/react": "^10.3.1",
61
59
  "classnames": "^2.5.1",
62
- "core-js": "^3.40.0",
63
60
  "eventemitter3": "^4.0.7",
64
61
  "idb": "^7.1.1",
65
62
  "is-plain-object": "^5.0.0",
package/src/index.ts CHANGED
@@ -1,9 +1,4 @@
1
1
  import { registerTldrawLibraryVersion } from '@tldraw/utils'
2
- import 'core-js/stable/array/at.js'
3
- import 'core-js/stable/array/flat-map.js'
4
- import 'core-js/stable/array/flat.js'
5
- import 'core-js/stable/string/at.js'
6
- import 'core-js/stable/string/replace-all.js'
7
2
 
8
3
  // eslint-disable-next-line local/no-export-star
9
4
  export * from '@tldraw/state'
@@ -150,6 +145,7 @@ export { InputsManager } from './lib/editor/managers/InputsManager/InputsManager
150
145
  export {
151
146
  ScribbleManager,
152
147
  type ScribbleItem,
148
+ type ScribbleSessionOptions,
153
149
  } from './lib/editor/managers/ScribbleManager/ScribbleManager'
154
150
  export {
155
151
  BoundsSnaps,
@@ -184,6 +180,7 @@ export {
184
180
  type TLEditStartInfo,
185
181
  type TLGeometryOpts,
186
182
  type TLHandleDragInfo,
183
+ type TLIndicatorPath,
187
184
  type TLResizeInfo,
188
185
  type TLResizeMode,
189
186
  type TLShapeUtilCanBeLaidOutOpts,
@@ -164,18 +164,22 @@ export interface TldrawEditorBaseProps {
164
164
 
165
165
  /**
166
166
  * Camera options for the editor.
167
+ *
168
+ * @deprecated Use `options.cameraOptions` instead. This will be removed in a future release.
167
169
  */
168
170
  cameraOptions?: Partial<TLCameraOptions>
169
171
 
170
172
  /**
171
- * Text options for the editor.
173
+ * Options for the editor.
172
174
  */
173
- textOptions?: TLTextOptions
175
+ options?: Partial<TldrawOptions>
174
176
 
175
177
  /**
176
- * Options for the editor.
178
+ * Text options for the editor.
179
+ *
180
+ * @deprecated Use `options.text` instead. This prop will be removed in a future release.
177
181
  */
178
- options?: Partial<TldrawOptions>
182
+ textOptions?: TLTextOptions
179
183
 
180
184
  /**
181
185
  * The license key.
@@ -184,6 +188,8 @@ export interface TldrawEditorBaseProps {
184
188
 
185
189
  /**
186
190
  * Options for syncing the editor's camera state with the URL.
191
+ *
192
+ * @deprecated Use `options.deepLinks` instead. This prop will be removed in a future release.
187
193
  */
188
194
  deepLinks?: true | TLDeepLinkOptions
189
195
 
@@ -191,7 +197,7 @@ export interface TldrawEditorBaseProps {
191
197
  * Provides a way to hide shapes.
192
198
  *
193
199
  * Hidden shapes will not render in the editor, and they will not be eligible for hit test via
194
- * {@link Editor#getShapeAtPoint} and {@link Editor#getShapesAtPoint}. But otherwise they will
200
+ * {@link @tldraw/editor#Editor.getShapeAtPoint} and {@link @tldraw/editor#Editor.getShapesAtPoint}. But otherwise they will
195
201
  * remain in the store and participate in all other operations.
196
202
  *
197
203
  * @example
@@ -248,6 +254,10 @@ export const TldrawEditor = memo(function TldrawEditor({
248
254
  className,
249
255
  user: _user,
250
256
  options: _options,
257
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
258
+ textOptions: _textOptions,
259
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
260
+ deepLinks: _deepLinks,
251
261
  ...rest
252
262
  }: TldrawEditorProps) {
253
263
  const [container, setContainer] = useState<HTMLElement | null>(null)
@@ -256,6 +266,19 @@ export const TldrawEditor = memo(function TldrawEditor({
256
266
  const ErrorFallback =
257
267
  components?.ErrorFallback === undefined ? DefaultErrorFallback : components?.ErrorFallback
258
268
 
269
+ // Merge deprecated props with options
270
+ // options values take precedence over the deprecated props
271
+ const mergedOptions = useMemo(() => {
272
+ let result = _options
273
+ if (_textOptions) {
274
+ result = { ...result, text: result?.text ?? _textOptions }
275
+ }
276
+ if (_deepLinks !== undefined) {
277
+ result = { ...result, deepLinks: result?.deepLinks ?? _deepLinks }
278
+ }
279
+ return result
280
+ }, [_options, _textOptions, _deepLinks])
281
+
259
282
  // apply defaults. if you're using the bare @tldraw/editor package, we
260
283
  // default these to the "tldraw zero" configuration. We have different
261
284
  // defaults applied in tldraw.
@@ -265,7 +288,7 @@ export const TldrawEditor = memo(function TldrawEditor({
265
288
  bindingUtils: rest.bindingUtils ?? EMPTY_BINDING_UTILS_ARRAY,
266
289
  tools: rest.tools ?? EMPTY_TOOLS_ARRAY,
267
290
  components,
268
- options: useShallowObjectIdentity(_options),
291
+ options: useShallowObjectIdentity(mergedOptions),
269
292
  }
270
293
 
271
294
  return (
@@ -397,11 +420,10 @@ function TldrawEditorWithReadyStore({
397
420
  initialState,
398
421
  autoFocus = true,
399
422
  inferDarkMode,
423
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
400
424
  cameraOptions,
401
- textOptions,
402
425
  options,
403
426
  licenseKey,
404
- deepLinks: _deepLinks,
405
427
  getShapeVisibility,
406
428
  assetUrls,
407
429
  }: Required<
@@ -418,6 +440,7 @@ function TldrawEditorWithReadyStore({
418
440
 
419
441
  const canvasRef = useRef<HTMLDivElement | null>(null)
420
442
 
443
+ const _deepLinks = options?.deepLinks
421
444
  const deepLinks = useShallowObjectIdentity(_deepLinks === true ? {} : _deepLinks)
422
445
 
423
446
  // props in this ref can be changed without causing the editor to be recreated.
@@ -458,7 +481,6 @@ function TldrawEditorWithReadyStore({
458
481
  autoFocus,
459
482
  inferDarkMode,
460
483
  cameraOptions,
461
- textOptions,
462
484
  options,
463
485
  licenseKey,
464
486
  getShapeVisibility,
@@ -497,7 +519,6 @@ function TldrawEditorWithReadyStore({
497
519
  setEditor,
498
520
  licenseKey,
499
521
  getShapeVisibility,
500
- textOptions,
501
522
  assetUrls,
502
523
  ]
503
524
  )
@@ -510,11 +531,12 @@ function TldrawEditorWithReadyStore({
510
531
  }, [editor, deepLinks])
511
532
 
512
533
  // keep the editor up to date with the latest camera options
534
+ // options.cameraOptions takes precedence over the deprecated cameraOptions prop
513
535
  useLayoutEffect(() => {
514
- if (editor && cameraOptions) {
515
- editor.setCameraOptions(cameraOptions)
536
+ if (editor && (cameraOptions || options?.camera)) {
537
+ editor.setCameraOptions({ ...cameraOptions, ...options?.camera })
516
538
  }
517
- }, [editor, cameraOptions])
539
+ }, [editor, cameraOptions, options?.camera])
518
540
 
519
541
  const crashingError = useSyncExternalStore(
520
542
  useCallback(
@@ -6,6 +6,11 @@ import { useEditor } from '../hooks/useEditor'
6
6
  import { useEditorComponents } from '../hooks/useEditorComponents'
7
7
  import { usePeerIds } from '../hooks/usePeerIds'
8
8
  import { usePresence } from '../hooks/usePresence'
9
+ import {
10
+ CollaboratorState,
11
+ getCollaboratorStateFromElapsedTime,
12
+ shouldShowCollaborator,
13
+ } from '../utils/collaboratorState'
9
14
 
10
15
  export const LiveCollaborators = track(function Collaborators() {
11
16
  const peerIds = usePeerIds()
@@ -26,30 +31,8 @@ const CollaboratorGuard = track(function CollaboratorGuard({
26
31
  return null
27
32
  }
28
33
 
29
- switch (collaboratorState) {
30
- case 'inactive': {
31
- const { followingUserId, highlightedUserIds } = editor.getInstanceState()
32
- // If they're inactive and unless we're following them or they're highlighted, hide them
33
- if (!(followingUserId === presence.userId || highlightedUserIds.includes(presence.userId))) {
34
- return null
35
- }
36
- break
37
- }
38
- case 'idle': {
39
- const { highlightedUserIds } = editor.getInstanceState()
40
- // If they're idle and following us and unless they have a chat message or are highlighted, hide them
41
- if (
42
- presence.followingUserId === editor.user.getId() &&
43
- !(presence.chatMessage || highlightedUserIds.includes(presence.userId))
44
- ) {
45
- return null
46
- }
47
- break
48
- }
49
- case 'active': {
50
- // If they're active, show them
51
- break
52
- }
34
+ if (!shouldShowCollaborator(editor, presence, collaboratorState)) {
35
+ return null
53
36
  }
54
37
 
55
38
  return <Collaborator latestPresence={presence} />
@@ -137,7 +120,16 @@ const Collaborator = track(function Collaborator({
137
120
  ) : null}
138
121
  {CollaboratorShapeIndicator &&
139
122
  selectedShapeIds
140
- .filter((id) => !editor.isShapeHidden(id))
123
+ .filter((id) => {
124
+ // Skip hidden shapes
125
+ if (editor.isShapeHidden(id)) return false
126
+ // Only render SVG indicators for shapes that use legacy indicators
127
+ // Canvas-based indicators are handled by CanvasShapeIndicators
128
+ const shape = editor.getShape(id)
129
+ if (!shape) return false
130
+ const util = editor.getShapeUtil(shape)
131
+ return util.useLegacyIndicator()
132
+ })
141
133
  .map((shapeId) => (
142
134
  <CollaboratorShapeIndicator
143
135
  className="tl-collaborator__shape-indicator"
@@ -152,24 +144,21 @@ const Collaborator = track(function Collaborator({
152
144
  )
153
145
  })
154
146
 
155
- function getStateFromElapsedTime(editor: Editor, elapsed: number) {
156
- return elapsed > editor.options.collaboratorInactiveTimeoutMs
157
- ? 'inactive'
158
- : elapsed > editor.options.collaboratorIdleTimeoutMs
159
- ? 'idle'
160
- : 'active'
161
- }
162
-
163
- function useCollaboratorState(editor: Editor, latestPresence: TLInstancePresence | null) {
147
+ function useCollaboratorState(
148
+ editor: Editor,
149
+ latestPresence: TLInstancePresence | null
150
+ ): CollaboratorState {
164
151
  const rLastActivityTimestamp = useRef(latestPresence?.lastActivityTimestamp ?? -1)
165
152
 
166
- const [state, setState] = useState<'active' | 'idle' | 'inactive'>(() =>
167
- getStateFromElapsedTime(editor, Date.now() - rLastActivityTimestamp.current)
153
+ const [state, setState] = useState<CollaboratorState>(() =>
154
+ getCollaboratorStateFromElapsedTime(editor, Date.now() - rLastActivityTimestamp.current)
168
155
  )
169
156
 
170
157
  useEffect(() => {
171
158
  const interval = editor.timers.setInterval(() => {
172
- setState(getStateFromElapsedTime(editor, Date.now() - rLastActivityTimestamp.current))
159
+ setState(
160
+ getCollaboratorStateFromElapsedTime(editor, Date.now() - rLastActivityTimestamp.current)
161
+ )
173
162
  }, editor.options.collaboratorCheckIntervalMs)
174
163
 
175
164
  return () => clearInterval(interval)
@@ -5,6 +5,7 @@ import { memo, useCallback, useEffect, useLayoutEffect, useRef } from 'react'
5
5
  import { ShapeUtil } from '../editor/shapes/ShapeUtil'
6
6
  import { useEditor } from '../hooks/useEditor'
7
7
  import { useEditorComponents } from '../hooks/useEditorComponents'
8
+ import { useShapeCulling } from '../hooks/useShapeCulling'
8
9
  import { Mat } from '../primitives/Mat'
9
10
  import { areShapesContentEqual } from '../utils/areShapesContentEqual'
10
11
  import { setStyleProperty } from '../utils/dom'
@@ -57,7 +58,6 @@ export const Shape = memo(function Shape({
57
58
  height: 0,
58
59
  x: 0,
59
60
  y: 0,
60
- isCulled: false,
61
61
  })
62
62
 
63
63
  useQuickReactor(
@@ -118,22 +118,21 @@ export const Shape = memo(function Shape({
118
118
  setStyleProperty(bgContainer, 'z-index', backgroundIndex)
119
119
  }, [opacity, index, backgroundIndex])
120
120
 
121
- useQuickReactor(
122
- 'set display',
123
- () => {
124
- const shape = editor.getShape(id)
125
- if (!shape) return // probably the shape was just deleted
121
+ // Register container refs with the centralized culling context.
122
+ // This runs on mount and handles initial display state.
123
+ const { register, unregister } = useShapeCulling()
124
+ useLayoutEffect(() => {
125
+ const container = containerRef.current
126
+ if (!container) return
126
127
 
127
- const culledShapes = editor.getCulledShapes()
128
- const isCulled = culledShapes.has(id)
129
- if (isCulled !== memoizedStuffRef.current.isCulled) {
130
- setStyleProperty(containerRef.current, 'display', isCulled ? 'none' : 'block')
131
- setStyleProperty(bgContainerRef.current, 'display', isCulled ? 'none' : 'block')
132
- memoizedStuffRef.current.isCulled = isCulled
133
- }
134
- },
135
- [editor]
136
- )
128
+ // Check initial culling state and register with the context
129
+ const isCulled = editor.getCulledShapes().has(id)
130
+ register(id, container, bgContainerRef.current, isCulled)
131
+
132
+ return () => {
133
+ unregister(id)
134
+ }
135
+ }, [editor, id, register, unregister])
137
136
  const annotateError = useCallback(
138
137
  (error: any) => editor.annotateError(error, { origin: 'shape', willCrashApp: false }),
139
138
  [editor]