@tldraw/editor 4.6.0-next.0eb36d65eec3 → 4.6.0-next.1a05e8ec7833

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 (300) hide show
  1. package/dist-cjs/index.d.ts +816 -112
  2. package/dist-cjs/index.js +22 -6
  3. package/dist-cjs/index.js.map +3 -3
  4. package/dist-cjs/lib/TldrawEditor.js +59 -14
  5. package/dist-cjs/lib/TldrawEditor.js.map +3 -3
  6. package/dist-cjs/lib/components/LiveCollaborators.js +1 -0
  7. package/dist-cjs/lib/components/LiveCollaborators.js.map +2 -2
  8. package/dist-cjs/lib/components/MenuClickCapture.js +99 -38
  9. package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
  10. package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js +10 -3
  11. package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js.map +3 -3
  12. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +5 -2
  13. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  14. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js +1 -1
  15. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js.map +2 -2
  16. package/dist-cjs/lib/components/default-components/DefaultShapeIndicators.js +1 -0
  17. package/dist-cjs/lib/components/default-components/DefaultShapeIndicators.js.map +2 -2
  18. package/dist-cjs/lib/config/{createTLUser.js → createTLCurrentUser.js} +9 -9
  19. package/dist-cjs/lib/config/createTLCurrentUser.js.map +7 -0
  20. package/dist-cjs/lib/config/createTLStore.js +30 -0
  21. package/dist-cjs/lib/config/createTLStore.js.map +2 -2
  22. package/dist-cjs/lib/config/defaultAssets.js +36 -0
  23. package/dist-cjs/lib/config/defaultAssets.js.map +7 -0
  24. package/dist-cjs/lib/editor/Editor.js +389 -13
  25. package/dist-cjs/lib/editor/Editor.js.map +3 -3
  26. package/dist-cjs/lib/editor/assets/AssetUtil.js +67 -0
  27. package/dist-cjs/lib/editor/assets/AssetUtil.js.map +7 -0
  28. package/dist-cjs/lib/editor/bindings/BindingUtil.js +1 -0
  29. package/dist-cjs/lib/editor/bindings/BindingUtil.js.map +1 -1
  30. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js +1 -0
  31. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +1 -1
  32. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js +1 -0
  33. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js.map +1 -1
  34. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js +1 -0
  35. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js.map +1 -1
  36. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js +2 -0
  37. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +2 -2
  38. package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js +2 -0
  39. package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js.map +1 -1
  40. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js +12 -0
  41. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js.map +2 -2
  42. package/dist-cjs/lib/editor/managers/PerformanceManager/PerformanceApiAdapter.js +80 -0
  43. package/dist-cjs/lib/editor/managers/PerformanceManager/PerformanceApiAdapter.js.map +7 -0
  44. package/dist-cjs/lib/editor/managers/PerformanceManager/PerformanceManager.js +466 -0
  45. package/dist-cjs/lib/editor/managers/PerformanceManager/PerformanceManager.js.map +7 -0
  46. package/dist-cjs/lib/editor/managers/PerformanceManager/perf-types.js +17 -0
  47. package/dist-cjs/lib/editor/managers/PerformanceManager/perf-types.js.map +7 -0
  48. package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js +1 -0
  49. package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js.map +1 -1
  50. package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js +1 -0
  51. package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js.map +1 -1
  52. package/dist-cjs/lib/editor/managers/SnapManager/HandleSnaps.js +1 -0
  53. package/dist-cjs/lib/editor/managers/SnapManager/HandleSnaps.js.map +1 -1
  54. package/dist-cjs/lib/editor/managers/SnapManager/SnapManager.js +2 -1
  55. package/dist-cjs/lib/editor/managers/SnapManager/SnapManager.js.map +2 -2
  56. package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js +1 -0
  57. package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js.map +1 -1
  58. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +1 -0
  59. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +1 -1
  60. package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js +107 -0
  61. package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js.map +7 -0
  62. package/dist-cjs/lib/editor/managers/ThemeManager/defaultThemes.js +586 -0
  63. package/dist-cjs/lib/editor/managers/ThemeManager/defaultThemes.js.map +7 -0
  64. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js +1 -0
  65. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +1 -1
  66. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +8 -4
  67. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  68. package/dist-cjs/lib/editor/shapes/BaseFrameLikeShapeUtil.js +76 -0
  69. package/dist-cjs/lib/editor/shapes/BaseFrameLikeShapeUtil.js.map +7 -0
  70. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +32 -2
  71. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  72. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +1 -1
  73. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +2 -2
  74. package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js +6 -0
  75. package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js.map +2 -2
  76. package/dist-cjs/lib/editor/tools/StateNode.js +15 -17
  77. package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
  78. package/dist-cjs/lib/editor/types/SvgExportContext.js.map +2 -2
  79. package/dist-cjs/lib/editor/types/clipboard-types.js.map +1 -1
  80. package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
  81. package/dist-cjs/lib/exports/ExportDelay.js +1 -0
  82. package/dist-cjs/lib/exports/ExportDelay.js.map +1 -1
  83. package/dist-cjs/lib/exports/StyleEmbedder.js +1 -0
  84. package/dist-cjs/lib/exports/StyleEmbedder.js.map +1 -1
  85. package/dist-cjs/lib/exports/getSvgJsx.js +14 -8
  86. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  87. package/dist-cjs/lib/globals/environment.js +18 -1
  88. package/dist-cjs/lib/globals/environment.js.map +2 -2
  89. package/dist-cjs/lib/hooks/useCanvasEvents.js +25 -4
  90. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  91. package/dist-cjs/lib/hooks/{useIsDarkMode.js → useColorMode.js} +14 -10
  92. package/dist-cjs/lib/hooks/useColorMode.js.map +7 -0
  93. package/dist-cjs/lib/hooks/useCursor.js +3 -7
  94. package/dist-cjs/lib/hooks/useCursor.js.map +2 -2
  95. package/dist-cjs/lib/hooks/useDarkMode.js +4 -4
  96. package/dist-cjs/lib/hooks/useDarkMode.js.map +2 -2
  97. package/dist-cjs/lib/hooks/useGestureEvents.js +171 -127
  98. package/dist-cjs/lib/hooks/useGestureEvents.js.map +3 -3
  99. package/dist-cjs/lib/options.js +2 -0
  100. package/dist-cjs/lib/options.js.map +2 -2
  101. package/dist-cjs/lib/primitives/Vec.js +3 -0
  102. package/dist-cjs/lib/primitives/Vec.js.map +1 -1
  103. package/dist-cjs/lib/primitives/geometry/Circle2d.js +1 -0
  104. package/dist-cjs/lib/primitives/geometry/Circle2d.js.map +1 -1
  105. package/dist-cjs/lib/primitives/geometry/Ellipse2d.js +1 -0
  106. package/dist-cjs/lib/primitives/geometry/Ellipse2d.js.map +1 -1
  107. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +2 -0
  108. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +1 -1
  109. package/dist-cjs/lib/primitives/geometry/Stadium2d.js +1 -0
  110. package/dist-cjs/lib/primitives/geometry/Stadium2d.js.map +1 -1
  111. package/dist-cjs/lib/utils/EditorAtom.js +2 -0
  112. package/dist-cjs/lib/utils/EditorAtom.js.map +1 -1
  113. package/dist-cjs/lib/utils/reparenting.js +2 -1
  114. package/dist-cjs/lib/utils/reparenting.js.map +2 -2
  115. package/dist-cjs/lib/utils/richText.js.map +2 -2
  116. package/dist-cjs/lib/utils/runtime.js +2 -1
  117. package/dist-cjs/lib/utils/runtime.js.map +2 -2
  118. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +2 -0
  119. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +1 -1
  120. package/dist-cjs/lib/utils/sync/hardReset.js +0 -8
  121. package/dist-cjs/lib/utils/sync/hardReset.js.map +2 -2
  122. package/dist-cjs/version.js +3 -3
  123. package/dist-cjs/version.js.map +1 -1
  124. package/dist-esm/index.d.mts +816 -112
  125. package/dist-esm/index.mjs +27 -9
  126. package/dist-esm/index.mjs.map +2 -2
  127. package/dist-esm/lib/TldrawEditor.mjs +62 -14
  128. package/dist-esm/lib/TldrawEditor.mjs.map +3 -3
  129. package/dist-esm/lib/components/LiveCollaborators.mjs +1 -0
  130. package/dist-esm/lib/components/LiveCollaborators.mjs.map +2 -2
  131. package/dist-esm/lib/components/MenuClickCapture.mjs +100 -39
  132. package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
  133. package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs +10 -3
  134. package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs.map +3 -3
  135. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +5 -2
  136. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  137. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs +1 -1
  138. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +2 -2
  139. package/dist-esm/lib/components/default-components/DefaultShapeIndicators.mjs +1 -0
  140. package/dist-esm/lib/components/default-components/DefaultShapeIndicators.mjs.map +2 -2
  141. package/dist-esm/lib/config/{createTLUser.mjs → createTLCurrentUser.mjs} +6 -6
  142. package/dist-esm/lib/config/createTLCurrentUser.mjs.map +7 -0
  143. package/dist-esm/lib/config/createTLStore.mjs +36 -1
  144. package/dist-esm/lib/config/createTLStore.mjs.map +2 -2
  145. package/dist-esm/lib/config/defaultAssets.mjs +16 -0
  146. package/dist-esm/lib/config/defaultAssets.mjs.map +7 -0
  147. package/dist-esm/lib/editor/Editor.mjs +391 -13
  148. package/dist-esm/lib/editor/Editor.mjs.map +3 -3
  149. package/dist-esm/lib/editor/assets/AssetUtil.mjs +47 -0
  150. package/dist-esm/lib/editor/assets/AssetUtil.mjs.map +7 -0
  151. package/dist-esm/lib/editor/bindings/BindingUtil.mjs +1 -0
  152. package/dist-esm/lib/editor/bindings/BindingUtil.mjs.map +1 -1
  153. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs +1 -0
  154. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +1 -1
  155. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs +1 -0
  156. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs.map +1 -1
  157. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs +1 -0
  158. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs.map +1 -1
  159. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs +2 -0
  160. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +2 -2
  161. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs +2 -0
  162. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs.map +1 -1
  163. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs +12 -0
  164. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs.map +2 -2
  165. package/dist-esm/lib/editor/managers/PerformanceManager/PerformanceApiAdapter.mjs +60 -0
  166. package/dist-esm/lib/editor/managers/PerformanceManager/PerformanceApiAdapter.mjs.map +7 -0
  167. package/dist-esm/lib/editor/managers/PerformanceManager/PerformanceManager.mjs +438 -0
  168. package/dist-esm/lib/editor/managers/PerformanceManager/PerformanceManager.mjs.map +7 -0
  169. package/dist-esm/lib/editor/managers/PerformanceManager/perf-types.mjs +1 -0
  170. package/dist-esm/lib/editor/managers/PerformanceManager/perf-types.mjs.map +7 -0
  171. package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs +1 -0
  172. package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs.map +1 -1
  173. package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs +1 -0
  174. package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs.map +1 -1
  175. package/dist-esm/lib/editor/managers/SnapManager/HandleSnaps.mjs +1 -0
  176. package/dist-esm/lib/editor/managers/SnapManager/HandleSnaps.mjs.map +1 -1
  177. package/dist-esm/lib/editor/managers/SnapManager/SnapManager.mjs +2 -1
  178. package/dist-esm/lib/editor/managers/SnapManager/SnapManager.mjs.map +2 -2
  179. package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs +1 -0
  180. package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs.map +1 -1
  181. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +1 -0
  182. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +1 -1
  183. package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs +89 -0
  184. package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs.map +7 -0
  185. package/dist-esm/lib/editor/managers/ThemeManager/defaultThemes.mjs +568 -0
  186. package/dist-esm/lib/editor/managers/ThemeManager/defaultThemes.mjs.map +7 -0
  187. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs +1 -0
  188. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +1 -1
  189. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +8 -4
  190. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  191. package/dist-esm/lib/editor/shapes/BaseFrameLikeShapeUtil.mjs +56 -0
  192. package/dist-esm/lib/editor/shapes/BaseFrameLikeShapeUtil.mjs.map +7 -0
  193. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +32 -2
  194. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  195. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +1 -1
  196. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +2 -2
  197. package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs +6 -0
  198. package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs.map +2 -2
  199. package/dist-esm/lib/editor/tools/StateNode.mjs +15 -17
  200. package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
  201. package/dist-esm/lib/editor/types/SvgExportContext.mjs.map +2 -2
  202. package/dist-esm/lib/exports/ExportDelay.mjs +1 -0
  203. package/dist-esm/lib/exports/ExportDelay.mjs.map +1 -1
  204. package/dist-esm/lib/exports/StyleEmbedder.mjs +1 -0
  205. package/dist-esm/lib/exports/StyleEmbedder.mjs.map +1 -1
  206. package/dist-esm/lib/exports/getSvgJsx.mjs +14 -11
  207. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  208. package/dist-esm/lib/globals/environment.mjs +18 -1
  209. package/dist-esm/lib/globals/environment.mjs.map +2 -2
  210. package/dist-esm/lib/hooks/useCanvasEvents.mjs +25 -4
  211. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  212. package/dist-esm/lib/hooks/useColorMode.mjs +19 -0
  213. package/dist-esm/lib/hooks/useColorMode.mjs.map +7 -0
  214. package/dist-esm/lib/hooks/useCursor.mjs +3 -7
  215. package/dist-esm/lib/hooks/useCursor.mjs.map +2 -2
  216. package/dist-esm/lib/hooks/useDarkMode.mjs +4 -4
  217. package/dist-esm/lib/hooks/useDarkMode.mjs.map +2 -2
  218. package/dist-esm/lib/hooks/useGestureEvents.mjs +171 -127
  219. package/dist-esm/lib/hooks/useGestureEvents.mjs.map +3 -3
  220. package/dist-esm/lib/options.mjs +2 -0
  221. package/dist-esm/lib/options.mjs.map +2 -2
  222. package/dist-esm/lib/primitives/Vec.mjs +3 -0
  223. package/dist-esm/lib/primitives/Vec.mjs.map +1 -1
  224. package/dist-esm/lib/primitives/geometry/Circle2d.mjs +1 -0
  225. package/dist-esm/lib/primitives/geometry/Circle2d.mjs.map +1 -1
  226. package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs +1 -0
  227. package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs.map +1 -1
  228. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +2 -0
  229. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +1 -1
  230. package/dist-esm/lib/primitives/geometry/Stadium2d.mjs +1 -0
  231. package/dist-esm/lib/primitives/geometry/Stadium2d.mjs.map +1 -1
  232. package/dist-esm/lib/utils/EditorAtom.mjs +2 -0
  233. package/dist-esm/lib/utils/EditorAtom.mjs.map +1 -1
  234. package/dist-esm/lib/utils/reparenting.mjs +2 -1
  235. package/dist-esm/lib/utils/reparenting.mjs.map +2 -2
  236. package/dist-esm/lib/utils/richText.mjs.map +2 -2
  237. package/dist-esm/lib/utils/runtime.mjs +2 -1
  238. package/dist-esm/lib/utils/runtime.mjs.map +2 -2
  239. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +2 -0
  240. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +1 -1
  241. package/dist-esm/lib/utils/sync/hardReset.mjs +0 -8
  242. package/dist-esm/lib/utils/sync/hardReset.mjs.map +2 -2
  243. package/dist-esm/version.mjs +3 -3
  244. package/dist-esm/version.mjs.map +1 -1
  245. package/editor.css +13 -37
  246. package/package.json +8 -9
  247. package/src/index.ts +30 -7
  248. package/src/lib/TldrawEditor.tsx +98 -19
  249. package/src/lib/components/LiveCollaborators.tsx +8 -2
  250. package/src/lib/components/MenuClickCapture.tsx +129 -49
  251. package/src/lib/components/default-components/CanvasShapeIndicators.tsx +14 -3
  252. package/src/lib/components/default-components/DefaultCanvas.tsx +6 -2
  253. package/src/lib/components/default-components/DefaultShapeIndicator.tsx +2 -2
  254. package/src/lib/components/default-components/DefaultShapeIndicators.tsx +2 -0
  255. package/src/lib/config/{createTLUser.ts → createTLCurrentUser.ts} +6 -6
  256. package/src/lib/config/createTLStore.ts +57 -2
  257. package/src/lib/config/defaultAssets.ts +19 -0
  258. package/src/lib/editor/Editor.ts +526 -42
  259. package/src/lib/editor/assets/AssetUtil.ts +85 -0
  260. package/src/lib/editor/managers/FontManager/FontManager.test.ts +9 -2
  261. package/src/lib/editor/managers/FontManager/FontManager.ts +1 -67
  262. package/src/lib/editor/managers/InputsManager/InputsManager.ts +12 -0
  263. package/src/lib/editor/managers/PerformanceManager/PerformanceApiAdapter.ts +82 -0
  264. package/src/lib/editor/managers/PerformanceManager/PerformanceManager.test.ts +522 -0
  265. package/src/lib/editor/managers/PerformanceManager/PerformanceManager.ts +583 -0
  266. package/src/lib/editor/managers/PerformanceManager/perf-types.ts +196 -0
  267. package/src/lib/editor/managers/SnapManager/SnapManager.test.ts +13 -2
  268. package/src/lib/editor/managers/SnapManager/SnapManager.ts +1 -1
  269. package/src/lib/editor/managers/ThemeManager/ThemeManager.ts +116 -0
  270. package/src/lib/editor/managers/ThemeManager/defaultThemes.ts +605 -0
  271. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +25 -31
  272. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +7 -5
  273. package/src/lib/editor/shapes/BaseFrameLikeShapeUtil.tsx +121 -0
  274. package/src/lib/editor/shapes/ShapeUtil.ts +50 -3
  275. package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +1 -1
  276. package/src/lib/editor/shapes/shared/getPerfectDashProps.ts +7 -0
  277. package/src/lib/editor/tools/StateNode.ts +16 -18
  278. package/src/lib/editor/types/SvgExportContext.tsx +5 -0
  279. package/src/lib/editor/types/clipboard-types.ts +2 -1
  280. package/src/lib/editor/types/external-content.ts +1 -0
  281. package/src/lib/exports/getSvgJsx.tsx +23 -16
  282. package/src/lib/globals/environment.ts +18 -0
  283. package/src/lib/hooks/useCanvasEvents.ts +40 -3
  284. package/src/lib/hooks/{useIsDarkMode.ts → useColorMode.ts} +9 -5
  285. package/src/lib/hooks/useCursor.ts +3 -7
  286. package/src/lib/hooks/useDarkMode.ts +4 -4
  287. package/src/lib/hooks/useGestureEvents.ts +240 -168
  288. package/src/lib/options.ts +14 -0
  289. package/src/lib/primitives/Box.test.ts +30 -0
  290. package/src/lib/primitives/geometry/Geometry2d.test.ts +21 -0
  291. package/src/lib/utils/reparenting.ts +6 -2
  292. package/src/lib/utils/richText.ts +1 -1
  293. package/src/lib/utils/runtime.ts +3 -1
  294. package/src/lib/utils/sync/hardReset.ts +0 -8
  295. package/src/version.ts +3 -3
  296. package/dist-cjs/lib/config/createTLUser.js.map +0 -7
  297. package/dist-cjs/lib/hooks/useIsDarkMode.js.map +0 -7
  298. package/dist-esm/lib/config/createTLUser.mjs.map +0 -7
  299. package/dist-esm/lib/hooks/useIsDarkMode.mjs +0 -15
  300. package/dist-esm/lib/hooks/useIsDarkMode.mjs.map +0 -7
@@ -10,10 +10,10 @@ import {
10
10
  import {
11
11
  ComputedCache,
12
12
  RecordType,
13
+ StoreSideEffects,
14
+ StoreSnapshot,
13
15
  UnknownRecord,
14
16
  reverseRecordsDiff,
15
- StoreSnapshot,
16
- StoreSideEffects,
17
17
  } from '@tldraw/store'
18
18
  import {
19
19
  CameraRecordType,
@@ -50,9 +50,16 @@ import {
50
50
  TLShapePartial,
51
51
  TLStore,
52
52
  TLStoreSnapshot,
53
+ TLTheme,
54
+ TLThemeId,
55
+ TLThemes,
56
+ TLUser,
57
+ TLUserId,
53
58
  TLVideoAsset,
59
+ UserRecordType,
54
60
  createBindingId,
55
61
  createShapeId,
62
+ createUserId,
56
63
  getShapePropKeysByStyle,
57
64
  isPageId,
58
65
  isShapeId,
@@ -90,7 +97,8 @@ import {
90
97
  uniqueId,
91
98
  } from '@tldraw/utils'
92
99
  import EventEmitter from 'eventemitter3'
93
- import { TLUser, createTLUser } from '../config/createTLUser'
100
+ import { TLCurrentUser, createTLCurrentUser } from '../config/createTLCurrentUser'
101
+ import { TLAnyAssetUtilConstructor, checkAssets } from '../config/defaultAssets'
94
102
  import { TLAnyBindingUtilConstructor, checkBindings } from '../config/defaultBindings'
95
103
  import { TLAnyShapeUtilConstructor, checkShapesAndAddCore } from '../config/defaultShapes'
96
104
  import {
@@ -137,6 +145,7 @@ import { getDroppedShapesToNewParents, kickoutOccludedShapes } from '../utils/re
137
145
  import { TLTextOptions, TiptapEditor } from '../utils/richText'
138
146
  import { applyRotationToSnapshotShapes, getRotationSnapshot } from '../utils/rotation'
139
147
  import { ReadonlySharedStyleMap, SharedStyle, SharedStyleMap } from '../utils/SharedStylesMap'
148
+ import { AssetUtil } from './assets/AssetUtil'
140
149
  import { BindingOnDeleteOptions, BindingUtil } from './bindings/BindingUtil'
141
150
  import { bindingsIndex } from './derivations/bindingsIndex'
142
151
  import { notVisibleShapes } from './derivations/notVisibleShapes'
@@ -148,10 +157,12 @@ import { FocusManager } from './managers/FocusManager/FocusManager'
148
157
  import { FontManager } from './managers/FontManager/FontManager'
149
158
  import { HistoryManager } from './managers/HistoryManager/HistoryManager'
150
159
  import { InputsManager } from './managers/InputsManager/InputsManager'
160
+ import { PerformanceManager } from './managers/PerformanceManager/PerformanceManager'
151
161
  import { ScribbleManager } from './managers/ScribbleManager/ScribbleManager'
152
162
  import { SnapManager } from './managers/SnapManager/SnapManager'
153
163
  import { SpatialIndexManager } from './managers/SpatialIndexManager/SpatialIndexManager'
154
164
  import { TextManager } from './managers/TextManager/TextManager'
165
+ import { ThemeManager, resolveThemes } from './managers/ThemeManager/ThemeManager'
155
166
  import { TickManager } from './managers/TickManager/TickManager'
156
167
  import { UserPreferencesManager } from './managers/UserPreferencesManager/UserPreferencesManager'
157
168
  import {
@@ -210,18 +221,17 @@ export interface TLEditorOptions {
210
221
  */
211
222
  bindingUtils: readonly TLAnyBindingUtilConstructor[]
212
223
  /**
213
- * An array of tools to use in the editor. These will be used to handle events and manage user interactions in the editor.
224
+ * An array of asset utils to use in the editor. These will be used to handle asset-type-specific behavior.
214
225
  */
215
- tools: readonly TLStateNodeConstructor[]
226
+ assetUtils?: readonly TLAnyAssetUtilConstructor[]
216
227
  /**
217
- * Should return a containing html element which has all the styles applied to the editor. If not
218
- * given, the body element will be used.
228
+ * An array of tools to use in the editor. These will be used to handle events and manage user interactions in the editor.
219
229
  */
220
- getContainer(): HTMLElement
230
+ tools: readonly TLStateNodeConstructor[]
221
231
  /**
222
232
  * A user defined externally to replace the default user.
223
233
  */
224
- user?: TLUser
234
+ user?: TLCurrentUser
225
235
  /**
226
236
  * The editor's initial active tool (or other state node id).
227
237
  */
@@ -230,25 +240,13 @@ export interface TLEditorOptions {
230
240
  * Whether to automatically focus the editor when it mounts.
231
241
  */
232
242
  autoFocus?: boolean
233
- /**
234
- * Whether to infer dark mode from the user's system preferences. Defaults to false.
235
- */
236
- inferDarkMode?: boolean
237
- /**
238
- * Options for the editor's camera.
239
- *
240
- * @deprecated Use `options.cameraOptions` instead. This will be removed in a future release.
241
- */
242
- cameraOptions?: Partial<TLCameraOptions>
243
- options?: Partial<TldrawOptions>
244
- /**
245
- * Text options for the editor.
246
- *
247
- * @deprecated Use `options.text` instead. This prop will be removed in a future release.
248
- */
249
- textOptions?: TLTextOptions
250
243
  licenseKey?: string
251
244
  fontAssetUrls?: { [key: string]: string | undefined }
245
+ /**
246
+ * Should return a containing html element which has all the styles applied to the editor. If not
247
+ * given, the body element will be used.
248
+ */
249
+ getContainer(): HTMLElement
252
250
  /**
253
251
  * Provides a way to hide shapes.
254
252
  *
@@ -268,6 +266,41 @@ export interface TLEditorOptions {
268
266
  shape: TLShape,
269
267
  editor: Editor
270
268
  ): 'visible' | 'hidden' | 'inherit' | null | undefined
269
+ /**
270
+ * Named theme definitions for the editor. Each theme contains shared
271
+ * properties (font size, line height, stroke width) and color palettes
272
+ * for both light and dark modes.
273
+ */
274
+ themes?: Partial<TLThemes>
275
+ /**
276
+ * The id of the initially active theme. Defaults to `'default'`.
277
+ */
278
+ initialTheme?: TLThemeId
279
+ /**
280
+ * The editor's color scheme preference, controls the default color mode. Defaults to `'light'`.
281
+ *
282
+ * - `'light'` - Always use light mode.
283
+ * - `'dark'` - Always use dark mode.
284
+ * - `'system'` - Follow the OS color scheme preference.
285
+ */
286
+ colorScheme?: 'light' | 'dark' | 'system'
287
+ /**
288
+ * Additional configuration options for the tldraw editor.
289
+ */
290
+ options?: Partial<TldrawOptions>
291
+ // --- Deprecated ----
292
+ /**
293
+ * Options for the editor's camera.
294
+ *
295
+ * @deprecated Use `options.cameraOptions` instead. This will be removed in a future release.
296
+ */
297
+ cameraOptions?: Partial<TLCameraOptions>
298
+ /**
299
+ * Text options for the editor.
300
+ *
301
+ * @deprecated Use `options.text` instead. This prop will be removed in a future release.
302
+ */
303
+ textOptions?: TLTextOptions
271
304
  }
272
305
 
273
306
  /**
@@ -296,6 +329,7 @@ export class Editor extends EventEmitter<TLEventMap> {
296
329
  user,
297
330
  shapeUtils,
298
331
  bindingUtils,
332
+ assetUtils: assetUtilConstructors,
299
333
  tools,
300
334
  getContainer,
301
335
  // needs to be here for backwards compatibility with TldrawEditor
@@ -303,13 +337,15 @@ export class Editor extends EventEmitter<TLEventMap> {
303
337
  cameraOptions,
304
338
  initialState,
305
339
  autoFocus,
306
- inferDarkMode,
307
340
  options: _options,
308
341
  // needs to be here for backwards compatibility with TldrawEditor
309
342
  // eslint-disable-next-line @typescript-eslint/no-deprecated
310
343
  textOptions: _textOptions,
311
344
  getShapeVisibility,
345
+ colorScheme,
312
346
  fontAssetUrls,
347
+ themes,
348
+ initialTheme,
313
349
  }: TLEditorOptions) {
314
350
  super()
315
351
 
@@ -344,21 +380,36 @@ export class Editor extends EventEmitter<TLEventMap> {
344
380
  ...options?.camera,
345
381
  })
346
382
 
383
+ this.getContainer = getContainer
384
+
347
385
  this._textOptions = atom('text options', options?.text ?? null)
348
386
 
349
- this.user = new UserPreferencesManager(user ?? createTLUser(), inferDarkMode ?? false)
387
+ this.user = new UserPreferencesManager(user ?? createTLCurrentUser(), colorScheme ?? 'light')
350
388
  this.disposables.add(() => this.user.dispose())
351
389
 
352
- this.getContainer = getContainer
353
-
354
390
  this.textMeasure = new TextManager(this)
355
391
  this.disposables.add(() => this.textMeasure.dispose())
356
392
 
357
- this.fonts = new FontManager(this, fontAssetUrls)
393
+ this._themeManager = new ThemeManager(this, {
394
+ themes: resolveThemes(themes),
395
+ initial: initialTheme ?? 'default',
396
+ })
397
+ this.disposables.add(() => this._themeManager.dispose())
358
398
 
359
399
  this._tickManager = new TickManager(this)
400
+ this.disposables.add(() => this._tickManager.dispose())
401
+ this.disposables.add(() => {
402
+ // Reset camera state to 'idle' so the store isn't left stuck at 'moving'
403
+ // when tick events stop (e.g. React strict mode disposes while camera is moving)
404
+ this.off('tick', this._decayCameraStateTimeout)
405
+ this._setCameraState('idle')
406
+ })
407
+
408
+ this.fonts = new FontManager(this, fontAssetUrls)
360
409
 
361
410
  this.inputs = new InputsManager(this)
411
+ this.performance = new PerformanceManager(this)
412
+ this.disposables.add(() => this.performance.dispose())
362
413
 
363
414
  class NewRoot extends RootState {
364
415
  static override initial = initialState ?? ''
@@ -396,6 +447,17 @@ export class Editor extends EventEmitter<TLEventMap> {
396
447
  this.shapeUtils = _shapeUtils
397
448
  this.styleProps = _styleProps
398
449
 
450
+ const _shapeUtilsByAssetType = {} as Record<string, ShapeUtil<any>>
451
+ for (const Util of allShapeUtils) {
452
+ const assetTypes = Util.handledAssetTypes
453
+ if (assetTypes) {
454
+ for (const assetType of assetTypes) {
455
+ _shapeUtilsByAssetType[assetType] = _shapeUtils[Util.type]
456
+ }
457
+ }
458
+ }
459
+ this._shapeUtilsByAssetType = _shapeUtilsByAssetType
460
+
399
461
  const allBindingUtils = checkBindings(bindingUtils)
400
462
  const _bindingUtils = {} as Record<string, BindingUtil<any>>
401
463
  for (const Util of allBindingUtils) {
@@ -404,6 +466,17 @@ export class Editor extends EventEmitter<TLEventMap> {
404
466
  }
405
467
  this.bindingUtils = _bindingUtils
406
468
 
469
+ // Asset utils
470
+ if (assetUtilConstructors) {
471
+ const allAssetUtils = checkAssets(assetUtilConstructors)
472
+ const _assetUtils = {} as Record<string, AssetUtil<any>>
473
+ for (const Util of allAssetUtils) {
474
+ const util = new Util(this)
475
+ _assetUtils[Util.type] = util
476
+ }
477
+ this.assetUtils = _assetUtils
478
+ }
479
+
407
480
  // Tools.
408
481
  // Accept tools from constructor parameters which may not conflict with the root note's default or
409
482
  // "baked in" tools, select and zoom.
@@ -820,6 +893,15 @@ export class Editor extends EventEmitter<TLEventMap> {
820
893
  })
821
894
  )
822
895
  }
896
+
897
+ this.disposables.add(
898
+ react('sync current user record', () => {
899
+ const user = this.store.props.users.currentUser.get()
900
+ if (user) {
901
+ this._ensureUserRecord(user)
902
+ }
903
+ })
904
+ )
823
905
  }
824
906
 
825
907
  private readonly _getShapeVisibility?: TLEditorOptions['getShapeVisibility']
@@ -929,6 +1011,18 @@ export class Editor extends EventEmitter<TLEventMap> {
929
1011
  */
930
1012
  readonly snaps: SnapManager
931
1013
 
1014
+ /**
1015
+ * A manager for performance measurement hooks.
1016
+ *
1017
+ * @public
1018
+ */
1019
+ readonly performance: PerformanceManager
1020
+
1021
+ /**
1022
+ * A manager for the spatial index, tracking where shapes exist on the canvas.
1023
+ *
1024
+ * @internal
1025
+ */
932
1026
  private readonly _spatialIndex: SpatialIndexManager
933
1027
 
934
1028
  /**
@@ -946,6 +1040,13 @@ export class Editor extends EventEmitter<TLEventMap> {
946
1040
  */
947
1041
  readonly user: UserPreferencesManager
948
1042
 
1043
+ /**
1044
+ * A manager for the editor's themes.
1045
+ *
1046
+ * @internal
1047
+ */
1048
+ private readonly _themeManager: ThemeManager
1049
+
949
1050
  /**
950
1051
  * A helper for measuring text.
951
1052
  *
@@ -1026,13 +1127,134 @@ export class Editor extends EventEmitter<TLEventMap> {
1026
1127
  * @public
1027
1128
  */
1028
1129
  dispose() {
1130
+ // Stop any in-progress camera animations and following before
1131
+ // running disposables, so their cleanup listeners fire first
1132
+ this.stopCameraAnimation()
1133
+ if (this.getInstanceState().followingUserId) {
1134
+ this.stopFollowingUser()
1135
+ }
1136
+
1029
1137
  this.disposables.forEach((dispose) => dispose())
1030
1138
  this.disposables.clear()
1139
+
1140
+ // Clear any open menus for this editor's context
1141
+ this.menus.clearOpenMenus()
1142
+
1031
1143
  this.store.dispose()
1032
1144
  this.isDisposed = true
1033
1145
  this.emit('dispose')
1034
1146
  }
1035
1147
 
1148
+ /* ------------------ Themes (shadowing the theme manager) ------------------ */
1149
+
1150
+ /**
1151
+ * Get the current color mode (`'light'` or `'dark'`), based on the user's dark mode preference.
1152
+ *
1153
+ * @public
1154
+ */
1155
+ getColorMode(): 'light' | 'dark' {
1156
+ return this._themeManager.getColorMode()
1157
+ }
1158
+
1159
+ /**
1160
+ * Set the color mode. Note that this is a convenience method that passes the mode to
1161
+ * `user.updateUserPreferences`, which is the source of truth for the user's color mode preference.
1162
+ *
1163
+ * @public
1164
+ */
1165
+ setColorMode(mode: 'light' | 'dark') {
1166
+ this.user.updateUserPreferences({ colorScheme: mode })
1167
+ return this
1168
+ }
1169
+
1170
+ /**
1171
+ * Get the id of the current theme.
1172
+ *
1173
+ * @public
1174
+ */
1175
+ getCurrentThemeId(): TLThemeId {
1176
+ return this._themeManager.getCurrentThemeId()
1177
+ }
1178
+
1179
+ /**
1180
+ * Get the current theme definition.
1181
+ *
1182
+ * @public
1183
+ */
1184
+ getCurrentTheme(): TLTheme {
1185
+ return this._themeManager.getCurrentTheme()
1186
+ }
1187
+
1188
+ /**
1189
+ * Set the current theme by id.
1190
+ *
1191
+ * @public
1192
+ */
1193
+ setCurrentTheme(id: TLThemeId) {
1194
+ this._themeManager.setCurrentTheme(id)
1195
+ return this
1196
+ }
1197
+
1198
+ /**
1199
+ * Get all registered theme definitions.
1200
+ *
1201
+ * @public
1202
+ */
1203
+ getThemes(): TLThemes {
1204
+ return this._themeManager.getThemes()
1205
+ }
1206
+
1207
+ /**
1208
+ * Get a single theme definition by id.
1209
+ *
1210
+ * @public
1211
+ */
1212
+ getTheme(id: TLThemeId): TLTheme | undefined {
1213
+ return this._themeManager.getTheme(id)
1214
+ }
1215
+
1216
+ /**
1217
+ * Replace all theme definitions, or update them via a callback that receives a deep copy.
1218
+ * The `'default'` theme must always be present in the result.
1219
+ *
1220
+ * @example
1221
+ * ```ts
1222
+ * // Replace all themes
1223
+ * editor.updateThemes({ default: myDefaultTheme, ocean: myOceanTheme })
1224
+ *
1225
+ * // Update via callback
1226
+ * editor.updateThemes((themes) => {
1227
+ * delete themes.ocean
1228
+ * return themes
1229
+ * })
1230
+ * ```
1231
+ *
1232
+ * @public
1233
+ */
1234
+ updateThemes(themes: TLThemes | ((themes: TLThemes) => TLThemes)) {
1235
+ this._themeManager.updateThemes(themes)
1236
+ return this
1237
+ }
1238
+
1239
+ /**
1240
+ * Register or update a single theme definition. The theme is keyed by its `id` property.
1241
+ *
1242
+ * @example
1243
+ * ```ts
1244
+ * // Override a property on the default theme
1245
+ * editor.updateTheme({ ...editor.getTheme('default')!, fontSize: 24 })
1246
+ *
1247
+ * // Register a new theme
1248
+ * editor.updateTheme({ id: 'ocean', ...myOceanTheme })
1249
+ * ```
1250
+ *
1251
+ * @public
1252
+ */
1253
+ updateTheme(theme: TLTheme) {
1254
+ this._themeManager.updateTheme(theme)
1255
+ return this
1256
+ }
1257
+
1036
1258
  /* ------------------- Shape Utils ------------------ */
1037
1259
 
1038
1260
  /**
@@ -1042,6 +1264,9 @@ export class Editor extends EventEmitter<TLEventMap> {
1042
1264
  */
1043
1265
  shapeUtils: { readonly [K in string]?: ShapeUtil<TLShape> }
1044
1266
 
1267
+ /** @internal */
1268
+ private _shapeUtilsByAssetType: { readonly [K in string]?: ShapeUtil<TLShape> } = {}
1269
+
1045
1270
  styleProps: { [key: string]: Map<StyleProp<any>, string> }
1046
1271
 
1047
1272
  /**
@@ -1084,6 +1309,18 @@ export class Editor extends EventEmitter<TLEventMap> {
1084
1309
  return hasOwnProperty(this.shapeUtils, type)
1085
1310
  }
1086
1311
 
1312
+ /**
1313
+ * Get the shape util that handles the given asset type.
1314
+ * Returns the shape util whose {@link ShapeUtil.handledAssetTypes} includes
1315
+ * the given asset type, or undefined if none matches.
1316
+ *
1317
+ * @param assetType - The asset type string.
1318
+ * @public
1319
+ */
1320
+ getShapeUtilForAssetType(assetType: string): ShapeUtil | undefined {
1321
+ return getOwnProperty(this._shapeUtilsByAssetType, assetType)
1322
+ }
1323
+
1087
1324
  /* ------------------- Binding Utils ------------------ */
1088
1325
  /**
1089
1326
  * A map of shape utility classes (TLShapeUtils) by shape type.
@@ -1119,6 +1356,56 @@ export class Editor extends EventEmitter<TLEventMap> {
1119
1356
  return bindingUtil
1120
1357
  }
1121
1358
 
1359
+ /* ------------------- Asset Utils ------------------ */
1360
+
1361
+ /**
1362
+ * A map of asset utility classes by asset type.
1363
+ *
1364
+ * @public
1365
+ */
1366
+ assetUtils: { readonly [K in string]?: AssetUtil<TLAsset> } = {}
1367
+
1368
+ /**
1369
+ * Get an asset util from an asset or asset type.
1370
+ *
1371
+ * @param arg - An asset, asset type string, or object with type.
1372
+ *
1373
+ * @public
1374
+ */
1375
+ getAssetUtil<S extends TLAsset>(asset: S | { type: S['type'] }): AssetUtil<S>
1376
+ getAssetUtil(type: string): AssetUtil
1377
+ getAssetUtil(arg: string | { type: string }) {
1378
+ const type = typeof arg === 'string' ? arg : arg.type
1379
+ const assetUtil = getOwnProperty(this.assetUtils, type)
1380
+ assert(assetUtil, `No asset util found for type "${type}"`)
1381
+ return assetUtil
1382
+ }
1383
+
1384
+ /**
1385
+ * Returns true if the editor has an asset util for the given asset type.
1386
+ *
1387
+ * @public
1388
+ */
1389
+ hasAssetUtil(arg: string | { type: string }): boolean {
1390
+ const type = typeof arg === 'string' ? arg : arg.type
1391
+ return hasOwnProperty(this.assetUtils, type)
1392
+ }
1393
+
1394
+ /**
1395
+ * Get the asset util that accepts the given MIME type.
1396
+ * Returns null if no registered asset util accepts the MIME type.
1397
+ *
1398
+ * @public
1399
+ */
1400
+ getAssetUtilForMimeType(mimeType: string): AssetUtil | null {
1401
+ for (const util of Object.values(this.assetUtils)) {
1402
+ if (util && util.acceptsMimeType(mimeType)) {
1403
+ return util
1404
+ }
1405
+ }
1406
+ return null
1407
+ }
1408
+
1122
1409
  /* --------------------- History -------------------- */
1123
1410
 
1124
1411
  /**
@@ -1142,6 +1429,7 @@ export class Editor extends EventEmitter<TLEventMap> {
1142
1429
  this._flushEventsForTick(0)
1143
1430
  this.complete()
1144
1431
  this.history.undo()
1432
+ this.performance._notifyUndoRedo('undo', this.history.getNumUndos(), this.history.getNumRedos())
1145
1433
  return this
1146
1434
  }
1147
1435
 
@@ -1172,6 +1460,7 @@ export class Editor extends EventEmitter<TLEventMap> {
1172
1460
  this._flushEventsForTick(0)
1173
1461
  this.complete()
1174
1462
  this.history.redo()
1463
+ this.performance._notifyUndoRedo('redo', this.history.getNumUndos(), this.history.getNumRedos())
1175
1464
  return this
1176
1465
  }
1177
1466
 
@@ -3972,6 +4261,94 @@ export class Editor extends EventEmitter<TLEventMap> {
3972
4261
  return this.getCollaborators().filter((c) => c.currentPageId === currentPageId)
3973
4262
  }
3974
4263
 
4264
+ // Attribution
4265
+
4266
+ /**
4267
+ * Get the current user's ID for attribution purposes.
4268
+ * Also ensures a `user:` record exists in the store for the current user.
4269
+ * Returns `null` when the user store has no current user.
4270
+ *
4271
+ * @public
4272
+ */
4273
+ getAttributionUserId(): string | null {
4274
+ const user = this.store.props.users.currentUser.get()
4275
+ if (!user) return null
4276
+ this._ensureUserRecord(user)
4277
+ return UserRecordType.parseId(user.id)
4278
+ }
4279
+
4280
+ /**
4281
+ * Ensure a user record exists in the store for the given user,
4282
+ * updating it if the data has changed.
4283
+ *
4284
+ * @internal
4285
+ */
4286
+ _ensureUserRecord(user: TLUser): void {
4287
+ const existing = this.store.get(user.id)
4288
+ if (
4289
+ existing &&
4290
+ existing.name === user.name &&
4291
+ existing.color === user.color &&
4292
+ existing.imageUrl === user.imageUrl &&
4293
+ existing.meta === user.meta
4294
+ ) {
4295
+ return
4296
+ }
4297
+ this.run(
4298
+ () => {
4299
+ this.store.put([user])
4300
+ },
4301
+ { history: 'ignore' }
4302
+ )
4303
+ }
4304
+
4305
+ /**
4306
+ * Resolve a display name for a user ID. Asks the
4307
+ * {@link @tldraw/tlschema#TLUserStore} first (the app's source of truth),
4308
+ * falling back to the `user:` record in the store.
4309
+ *
4310
+ * @public
4311
+ */
4312
+ getAttributionDisplayName(userId: string | null): string | null {
4313
+ if (!userId) return null
4314
+ return (
4315
+ this.store.props.users.resolve(userId).get()?.name ??
4316
+ this.store.get(createUserId(userId))?.name ??
4317
+ null
4318
+ )
4319
+ }
4320
+
4321
+ /**
4322
+ * Resolve a user record by ID. Asks the
4323
+ * {@link @tldraw/tlschema#TLUserStore} first (the app's source of truth),
4324
+ * falling back to the `user:` record in the store.
4325
+ *
4326
+ * @public
4327
+ */
4328
+ getAttributionUser(userId: string | null): TLUser | null {
4329
+ if (!userId) return null
4330
+ return (
4331
+ this.store.props.users.resolve(userId).get() ?? this.store.get(createUserId(userId)) ?? null
4332
+ )
4333
+ }
4334
+
4335
+ /**
4336
+ * Collect user IDs referenced by a set of shapes via shape-specific props
4337
+ * (e.g. `textFirstEditedBy` on notes).
4338
+ *
4339
+ * @internal
4340
+ */
4341
+ _getReferencedUserIds(shapes: TLShape[]): Set<string> {
4342
+ const userIds = new Set<string>()
4343
+ for (const shape of shapes) {
4344
+ const util = this.getShapeUtil(shape)
4345
+ for (const id of util.getReferencedUserIds(shape)) {
4346
+ userIds.add(id)
4347
+ }
4348
+ }
4349
+ return userIds
4350
+ }
4351
+
3975
4352
  // Following
3976
4353
 
3977
4354
  // When we are 'locked on' to a user, our camera is derived from their camera.
@@ -5326,8 +5703,8 @@ export class Editor extends EventEmitter<TLEventMap> {
5326
5703
  ? this.getCurrentPageRenderingShapesSorted()
5327
5704
  : this.getCurrentPageShapesSorted()
5328
5705
  ).filter((shape) => {
5329
- // Frames have labels positioned above the shape (outside bounds), so always include them
5330
- if (!candidateIds.has(shape.id) && !this.isShapeOfType(shape, 'frame')) return false
5706
+ // Frame-like shapes have labels positioned above the shape (outside bounds), so always include them
5707
+ if (!candidateIds.has(shape.id) && !this.isShapeFrameLike(shape)) return false
5331
5708
 
5332
5709
  if (
5333
5710
  (shape.isLocked && !hitLocked) ||
@@ -5349,12 +5726,14 @@ export class Editor extends EventEmitter<TLEventMap> {
5349
5726
  const pointInShapeSpace = this.getPointInShapeSpace(shape, point)
5350
5727
 
5351
5728
  // Check labels first
5729
+ const shapeUtil = this.getShapeUtil(shape)
5730
+ const isShapeFrameLike = this.isShapeFrameLike(shape)
5352
5731
  if (
5353
- this.isShapeOfType(shape, 'frame') ||
5732
+ isShapeFrameLike ||
5354
5733
  ((this.isShapeOfType(shape, 'note') ||
5355
5734
  this.isShapeOfType(shape, 'arrow') ||
5356
5735
  (this.isShapeOfType(shape, 'geo') && shape.props.fill === 'none')) &&
5357
- this.getShapeUtil(shape).getText(shape)?.trim())
5736
+ shapeUtil.getText(shape)?.trim())
5358
5737
  ) {
5359
5738
  for (const childGeometry of (geometry as Group2d).children) {
5360
5739
  if (childGeometry.isLabel && childGeometry.isPointInBounds(pointInShapeSpace)) {
@@ -5363,8 +5742,8 @@ export class Editor extends EventEmitter<TLEventMap> {
5363
5742
  }
5364
5743
  }
5365
5744
 
5366
- if (this.isShapeOfType(shape, 'frame')) {
5367
- // On the rare case that we've hit a frame (not its label), test again hitInside to be forced true;
5745
+ if (isShapeFrameLike) {
5746
+ // On the rare case that we've hit a frame-like shape (not its label), test again hitInside to be forced true;
5368
5747
  // this prevents clicks from passing through the body of a frame to shapes behind it.
5369
5748
 
5370
5749
  // If the hit is within the frame's outer margin, then select the frame
@@ -5523,11 +5902,11 @@ export class Editor extends EventEmitter<TLEventMap> {
5523
5902
  const candidateIds = this._spatialIndex.getShapeIdsAtPoint(point, margin)
5524
5903
 
5525
5904
  // Get all page shapes in z-index order and filter to candidates that pass isPointInShape
5526
- // Frames are always checked because their labels can be outside their bounds
5905
+ // Frame-like shapes are always checked because their labels can be outside their bounds
5527
5906
  return this.getCurrentPageShapesSorted()
5528
5907
  .filter((shape) => {
5529
5908
  if (this.isShapeHidden(shape)) return false
5530
- if (!candidateIds.has(shape.id) && !this.isShapeOfType(shape, 'frame')) return false
5909
+ if (!candidateIds.has(shape.id) && !this.isShapeFrameLike(shape)) return false
5531
5910
  return this.isPointInShape(shape, point, opts)
5532
5911
  })
5533
5912
  .reverse()
@@ -5702,6 +6081,25 @@ export class Editor extends EventEmitter<TLEventMap> {
5702
6081
  return shape.type === type
5703
6082
  }
5704
6083
 
6084
+ /**
6085
+ * Get whether a shape behaves like a frame — a container that has child
6086
+ * shapes, requires full-brush selection, blocks erasure from inside, etc.
6087
+ *
6088
+ * @example
6089
+ * ```ts
6090
+ * const isFrameLike = editor.isShapeFrameLike(someShape)
6091
+ * ```
6092
+ *
6093
+ * @param shape - The shape (or shape id) to test.
6094
+ *
6095
+ * @public
6096
+ */
6097
+ isShapeFrameLike(shape: TLShape | TLShapeId): boolean {
6098
+ const _shape = typeof shape === 'string' ? this.getShape(shape) : shape
6099
+ if (!_shape) return false
6100
+ return this.getShapeUtil(_shape).isFrameLike(_shape)
6101
+ }
6102
+
5705
6103
  /**
5706
6104
  * Get a shape by its id.
5707
6105
  *
@@ -9157,12 +9555,23 @@ export class Editor extends EventEmitter<TLEventMap> {
9157
9555
  assets.push(asset)
9158
9556
  }
9159
9557
 
9558
+ const users: TLUser[] = []
9559
+ const seenUserIds = new Set<TLUserId>()
9560
+ for (const userId of this._getReferencedUserIds(shapes)) {
9561
+ const recordId = createUserId(userId)
9562
+ if (seenUserIds.has(recordId)) continue
9563
+ seenUserIds.add(recordId)
9564
+ const user = this.store.get(recordId)
9565
+ if (user) users.push(user)
9566
+ }
9567
+
9160
9568
  return {
9161
9569
  schema: this.store.schema.serialize(),
9162
9570
  shapes,
9163
9571
  rootShapeIds,
9164
9572
  bindings,
9165
9573
  assets,
9574
+ users,
9166
9575
  }
9167
9576
  })
9168
9577
  }
@@ -9238,6 +9647,7 @@ export class Editor extends EventEmitter<TLEventMap> {
9238
9647
  const assets: TLAsset[] = []
9239
9648
  const shapes: TLShape[] = []
9240
9649
  const bindings: TLBinding[] = []
9650
+ const users: TLUser[] = []
9241
9651
 
9242
9652
  // Let's treat the content as a store, and then migrate that store.
9243
9653
  const store: StoreSnapshot<TLRecord> = {
@@ -9247,6 +9657,7 @@ export class Editor extends EventEmitter<TLEventMap> {
9247
9657
  ...Object.fromEntries(
9248
9658
  content.bindings?.map((bindings) => [bindings.id, bindings] as const) ?? []
9249
9659
  ),
9660
+ ...Object.fromEntries(content.users?.map((user) => [user.id, user] as const) ?? []),
9250
9661
  },
9251
9662
  schema: content.schema,
9252
9663
  }
@@ -9268,6 +9679,23 @@ export class Editor extends EventEmitter<TLEventMap> {
9268
9679
  bindings.push(record)
9269
9680
  break
9270
9681
  }
9682
+ case 'user': {
9683
+ users.push(record)
9684
+ break
9685
+ }
9686
+ }
9687
+ }
9688
+
9689
+ if (users.length > 0) {
9690
+ const existingUserIds = new Set(
9691
+ this.store
9692
+ .allRecords()
9693
+ .filter((r): r is TLUser => r.typeName === 'user')
9694
+ .map((r) => r.id)
9695
+ )
9696
+ const usersToCreate = users.filter((u) => !existingUserIds.has(u.id))
9697
+ if (usersToCreate.length > 0) {
9698
+ this.store.put(usersToCreate)
9271
9699
  }
9272
9700
  }
9273
9701
 
@@ -10458,6 +10886,7 @@ export class Editor extends EventEmitter<TLEventMap> {
10458
10886
  { immediate: true }
10459
10887
  )
10460
10888
 
10889
+ this.performance._notifyCameraOperation('zooming')
10461
10890
  this.emit('event', info)
10462
10891
  return // Stop here!
10463
10892
  }
@@ -10550,6 +10979,7 @@ export class Editor extends EventEmitter<TLEventMap> {
10550
10979
  immediate: true,
10551
10980
  })
10552
10981
  this.maybeTrackPerformance('Zooming')
10982
+ this.performance._notifyCameraOperation('zooming')
10553
10983
  this.root.handleEvent(info)
10554
10984
  this.emit('event', info)
10555
10985
  return
@@ -10560,6 +10990,7 @@ export class Editor extends EventEmitter<TLEventMap> {
10560
10990
  immediate: true,
10561
10991
  })
10562
10992
  this.maybeTrackPerformance('Panning')
10993
+ this.performance._notifyCameraOperation('panning')
10563
10994
  this.root.handleEvent(info)
10564
10995
  this.emit('event', info)
10565
10996
  return
@@ -10626,6 +11057,10 @@ export class Editor extends EventEmitter<TLEventMap> {
10626
11057
  }
10627
11058
  this.inputs.setIsPanning(true)
10628
11059
  clearTimeout(this._longPressTimeout)
11060
+ } else if (info.button === RIGHT_MOUSE_BUTTON && this.options.rightClickPanning) {
11061
+ this.inputs.setIsRightPointing(true)
11062
+ clearTimeout(this._longPressTimeout)
11063
+ return this
10629
11064
  }
10630
11065
 
10631
11066
  // We might be panning because we did a middle mouse click, or because we're holding spacebar and started a regular click
@@ -10644,9 +11079,31 @@ export class Editor extends EventEmitter<TLEventMap> {
10644
11079
 
10645
11080
  const { x: cx, y: cy, z: cz } = unsafe__withoutCapture(() => this.getCamera())
10646
11081
 
11082
+ // Right-click pointing: waiting to see if this becomes a drag
11083
+ if (this.inputs.getIsRightPointing() && !this.inputs.getIsPanning()) {
11084
+ const currentScreenPoint = this.inputs.getCurrentScreenPoint()
11085
+ const originScreenPoint = this.inputs.getOriginScreenPoint()
11086
+ if (
11087
+ Vec.Dist2(originScreenPoint, currentScreenPoint) > this.options.dragDistanceSquared
11088
+ ) {
11089
+ // Passed the drag threshold—transition to panning
11090
+ this._prevCursor = this.getInstanceState().cursor.type
11091
+ this.inputs.setIsPanning(true)
11092
+ this.setCursor({ type: 'grabbing', rotation: 0 })
11093
+ this.stopCameraAnimation()
11094
+ // Apply the initial delta from the pointer down origin
11095
+ const offset = Vec.Sub(currentScreenPoint, originScreenPoint)
11096
+ this.setCamera(new Vec(cx + offset.x / cz, cy + offset.y / cz, cz), {
11097
+ immediate: true,
11098
+ })
11099
+ this.maybeTrackPerformance('Panning')
11100
+ }
11101
+ return
11102
+ }
11103
+
10647
11104
  // If we've started panning, then clear any long press timeout
10648
11105
  if (this.inputs.getIsPanning() && this.inputs.getIsPointing()) {
10649
- // Handle spacebar / middle mouse button panning
11106
+ // Handle spacebar / middle mouse button / right-click panning
10650
11107
  const currentScreenPoint = this.inputs.getCurrentScreenPoint()
10651
11108
  const previousScreenPoint = this.inputs.getPreviousScreenPoint()
10652
11109
  const offset = Vec.Sub(currentScreenPoint, previousScreenPoint)
@@ -10654,6 +11111,7 @@ export class Editor extends EventEmitter<TLEventMap> {
10654
11111
  immediate: true,
10655
11112
  })
10656
11113
  this.maybeTrackPerformance('Panning')
11114
+ this.performance._notifyCameraOperation('panning')
10657
11115
  return
10658
11116
  }
10659
11117
 
@@ -10678,13 +11136,24 @@ export class Editor extends EventEmitter<TLEventMap> {
10678
11136
  inputs.setIsDragging(false)
10679
11137
  inputs.setIsPointing(false)
10680
11138
  clearTimeout(this._longPressTimeout)
10681
-
10682
11139
  // Remove the button from the buttons set
10683
11140
  inputs.buttons.delete(info.button)
10684
11141
 
10685
11142
  // If we're in pen mode and we're not using a pen, stop here
10686
11143
  if (instanceState.isPenMode && !isPen) return
10687
11144
 
11145
+ // Right-click pointing ended without dragging—this is a static
11146
+ // right-click, so let it through to the state chart as right_click.
11147
+ // Check isPanning first: if we transitioned to panning, isRightPointing
11148
+ // is still true but we want the panning cleanup path instead.
11149
+ if (this.inputs.getIsRightPointing() && !this.inputs.getIsPanning()) {
11150
+ this.inputs.setIsRightPointing(false)
11151
+ this._selectedShapeIdsAtPointerDown = []
11152
+ break // fall through to state chart dispatch as right_click
11153
+ }
11154
+
11155
+ this.inputs.setIsRightPointing(false)
11156
+
10688
11157
  // Firefox bug fix...
10689
11158
  // If it's the same pointer that we stored earlier...
10690
11159
  // ... then it's probably still a left-mouse-click!
@@ -10707,11 +11176,26 @@ export class Editor extends EventEmitter<TLEventMap> {
10707
11176
  break
10708
11177
  }
10709
11178
  case MIDDLE_MOUSE_BUTTON: {
10710
- if (this.inputs.keys.has(' ')) {
11179
+ if (this.inputs.keys.has('Space')) {
10711
11180
  this.setCursor({ type: 'grab', rotation: 0 })
10712
11181
  } else {
10713
11182
  this.setCursor({ type: this._prevCursor, rotation: 0 })
10714
11183
  }
11184
+ break
11185
+ }
11186
+ case RIGHT_MOUSE_BUTTON: {
11187
+ if (this.inputs.keys.has('Space')) {
11188
+ this.setCursor({ type: 'grab', rotation: 0 })
11189
+ } else {
11190
+ this.setCursor({ type: this._prevCursor, rotation: 0 })
11191
+ }
11192
+ // Don't pass right-click panning events to the state chart
11193
+ // as it causes unintended shape selection on release
11194
+ if (slideSpeed > 0) {
11195
+ this.slideCamera({ speed: slideSpeed, direction: slideDirection })
11196
+ }
11197
+ this._selectedShapeIdsAtPointerDown = []
11198
+ return this
10715
11199
  }
10716
11200
  }
10717
11201