@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
@@ -0,0 +1,583 @@
1
+ import type { TLRecord, TLShapeId } from '@tldraw/tlschema'
2
+ import { bind } from '@tldraw/utils'
3
+ import EventEmitter from 'eventemitter3'
4
+ import type { Editor } from '../../Editor'
5
+ import type {
6
+ TLCameraEndPerfEvent,
7
+ TLCameraStartPerfEvent,
8
+ TLFramePerfEvent,
9
+ TLInteractionEndPerfEvent,
10
+ TLInteractionStartPerfEvent,
11
+ TLPerfEventMap,
12
+ TLPerfLongAnimationFrame,
13
+ TLShapeOperationPerfEvent,
14
+ TLUndoRedoPerfEvent,
15
+ } from './perf-types'
16
+
17
+ function percentile(sorted: number[], p: number): number {
18
+ const idx = Math.ceil(p * sorted.length) - 1
19
+ return sorted[Math.max(0, idx)]
20
+ }
21
+
22
+ function computeFrameTimeStats(frameTimes: number[]) {
23
+ if (frameTimes.length === 0) return { avg: 0, median: 0, p95: 0, p99: 0, min: 0, max: 0 }
24
+ const sorted = [...frameTimes].sort((a, b) => a - b)
25
+ const sum = sorted.reduce((a, b) => a + b, 0)
26
+ return {
27
+ avg: sum / sorted.length,
28
+ median: percentile(sorted, 0.5),
29
+ p95: percentile(sorted, 0.95),
30
+ p99: percentile(sorted, 0.99),
31
+ min: sorted[0],
32
+ max: sorted[sorted.length - 1],
33
+ }
34
+ }
35
+
36
+ function toLoafEntry(entry: PerformanceEntry): TLPerfLongAnimationFrame | null {
37
+ // LoAF entries have these properties but TypeScript doesn't know about them yet
38
+ const e = entry as PerformanceEntry & {
39
+ blockingDuration?: number
40
+ scripts?: ReadonlyArray<{
41
+ sourceURL?: string
42
+ invoker?: string
43
+ duration?: number
44
+ }>
45
+ }
46
+ if (typeof e.duration !== 'number') return null
47
+ return {
48
+ startTime: e.startTime,
49
+ duration: e.duration,
50
+ blockingDuration: e.blockingDuration ?? 0,
51
+ scripts: (e.scripts ?? []).map((s) => ({
52
+ sourceURL: s.sourceURL ?? '',
53
+ invoker: s.invoker ?? '',
54
+ duration: s.duration ?? 0,
55
+ })),
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Manages performance event subscriptions for the editor. Available as `editor.performance`.
61
+ *
62
+ * Listeners are lazy — internal editor hooks (frame, shape events) are only attached while
63
+ * at least one subscriber exists, so there is zero overhead when unused.
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * const unsub = editor.performance.on('interaction-end', (event) => {
68
+ * console.log(`${event.name}: ${event.fps.toFixed(1)} fps, p95=${event.p95FrameTime.toFixed(1)}ms`)
69
+ * })
70
+ * ```
71
+ *
72
+ * @public
73
+ */
74
+ export class PerformanceManager {
75
+ /** @internal */
76
+ readonly emitter = new EventEmitter<TLPerfEventMap>()
77
+
78
+ private editor: Editor
79
+
80
+ // Active interaction tracking
81
+ private activeInteraction: {
82
+ name: string
83
+ path: string
84
+ startTime: number
85
+ frameTimes: number[]
86
+ selectedShapeTypes: Record<string, number>
87
+ loafEntries: TLPerfLongAnimationFrame[]
88
+ } | null = null
89
+
90
+ // Active camera tracking
91
+ private activeCamera: {
92
+ type: 'panning' | 'zooming'
93
+ startTime: number
94
+ frameTimes: number[]
95
+ timeout: number | null
96
+ loafEntries: TLPerfLongAnimationFrame[]
97
+ } | null = null
98
+
99
+ // Lazy listener cleanup functions
100
+ private frameCleanup: (() => void) | null = null
101
+ private shapeCreatedCleanup: (() => void) | null = null
102
+ private shapeEditedCleanup: (() => void) | null = null
103
+ private shapeDeletedCleanup: (() => void) | null = null
104
+
105
+ // LoAF observer
106
+ private loafObserver: PerformanceObserver | null = null
107
+
108
+ constructor(editor: Editor) {
109
+ this.editor = editor
110
+ }
111
+
112
+ /**
113
+ * Subscribe to a performance event. Returns an unsubscribe function.
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * const unsub = editor.performance.on('interaction-end', (event) => {
118
+ * sendToAnalytics({ name: event.name, fps: event.fps, p95: event.p95FrameTime })
119
+ * })
120
+ * // later: unsub()
121
+ * ```
122
+ *
123
+ * @public
124
+ */
125
+ on<K extends keyof TLPerfEventMap>(
126
+ event: K,
127
+ fn: (...args: TLPerfEventMap[K]) => void
128
+ ): () => void {
129
+ this.emitter.on(event, fn as any)
130
+ this._maybeAttachLazyListeners(event)
131
+ return () => {
132
+ this.emitter.off(event, fn as any)
133
+ this._maybeDetachLazyListeners(event)
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Subscribe to a performance event once. The listener is removed after the first invocation.
139
+ * Returns an unsubscribe function for early removal.
140
+ *
141
+ * @public
142
+ */
143
+ once<K extends keyof TLPerfEventMap>(
144
+ event: K,
145
+ fn: (...args: TLPerfEventMap[K]) => void
146
+ ): () => void {
147
+ const wrapped = (...args: TLPerfEventMap[K]) => {
148
+ ;(fn as any)(...args)
149
+ this._maybeDetachLazyListeners(event)
150
+ }
151
+ this.emitter.once(event, wrapped as any)
152
+ this._maybeAttachLazyListeners(event)
153
+ return () => {
154
+ this.emitter.off(event, wrapped as any)
155
+ this._maybeDetachLazyListeners(event)
156
+ }
157
+ }
158
+
159
+ /** @internal */
160
+ dispose() {
161
+ if (this.activeCamera?.timeout) clearTimeout(this.activeCamera.timeout)
162
+ this.activeInteraction = null
163
+ this.activeCamera = null
164
+ this.frameCleanup?.()
165
+ this.frameCleanup = null
166
+ this.shapeCreatedCleanup?.()
167
+ this.shapeCreatedCleanup = null
168
+ this.shapeEditedCleanup?.()
169
+ this.shapeEditedCleanup = null
170
+ this.shapeDeletedCleanup?.()
171
+ this.shapeDeletedCleanup = null
172
+ this._stopLoafObserver()
173
+ this.emitter.removeAllListeners()
174
+ }
175
+
176
+ // --- Internal notification methods ---
177
+
178
+ /** @internal */
179
+ _notifyInteractionStart(name: string, path: string) {
180
+ if (
181
+ this.emitter.listenerCount('interaction-start') === 0 &&
182
+ this.emitter.listenerCount('interaction-end') === 0
183
+ ) {
184
+ return
185
+ }
186
+
187
+ if (this.activeInteraction) {
188
+ console.warn(
189
+ `[tldraw] New interaction '${name}' started while '${this.activeInteraction.name}' was still active`
190
+ )
191
+ }
192
+
193
+ // Capture selected shape types at start
194
+ const selectedShapeTypes: Record<string, number> = {}
195
+ for (const shape of this.editor.getSelectedShapes()) {
196
+ selectedShapeTypes[shape.type] = (selectedShapeTypes[shape.type] || 0) + 1
197
+ }
198
+
199
+ this.activeInteraction = {
200
+ name,
201
+ path,
202
+ startTime: performance.now(),
203
+ frameTimes: [],
204
+ selectedShapeTypes,
205
+ loafEntries: [],
206
+ }
207
+
208
+ const event: TLInteractionStartPerfEvent = {
209
+ name,
210
+ path,
211
+ timestamp: performance.now(),
212
+ }
213
+ this.emitter.emit('interaction-start', event)
214
+ }
215
+
216
+ /** @internal */
217
+ _notifyInteractionEnd() {
218
+ const interaction = this.activeInteraction
219
+ if (!interaction) return
220
+ this.activeInteraction = null
221
+
222
+ if (this.emitter.listenerCount('interaction-end') === 0) return
223
+
224
+ const duration = performance.now() - interaction.startTime
225
+ const stats = computeFrameTimeStats(interaction.frameTimes)
226
+
227
+ const event: TLInteractionEndPerfEvent = {
228
+ name: interaction.name,
229
+ path: interaction.path,
230
+ duration,
231
+ fps:
232
+ interaction.frameTimes.length > 0 ? (interaction.frameTimes.length / duration) * 1000 : 0,
233
+ frameCount: interaction.frameTimes.length,
234
+ avgFrameTime: stats.avg,
235
+ medianFrameTime: stats.median,
236
+ p95FrameTime: stats.p95,
237
+ p99FrameTime: stats.p99,
238
+ minFrameTime: stats.min,
239
+ maxFrameTime: stats.max,
240
+ frameTimes: interaction.frameTimes,
241
+ shapeCount: this.editor.getCurrentPageShapeIds().size,
242
+ selectedShapeTypes: interaction.selectedShapeTypes,
243
+ longAnimationFrames: interaction.loafEntries.length > 0 ? interaction.loafEntries : undefined,
244
+ zoomLevel: this.editor.getCamera().z,
245
+ timestamp: performance.now(),
246
+ }
247
+ this.emitter.emit('interaction-end', event)
248
+ }
249
+
250
+ /** @internal */
251
+ _notifyCameraOperation(type: 'panning' | 'zooming') {
252
+ if (
253
+ this.emitter.listenerCount('camera-start') === 0 &&
254
+ this.emitter.listenerCount('camera-end') === 0
255
+ ) {
256
+ return
257
+ }
258
+
259
+ if (this.activeCamera) {
260
+ // Extend existing camera session
261
+ if (this.activeCamera.timeout) {
262
+ clearTimeout(this.activeCamera.timeout)
263
+ }
264
+ // If type changed, end old and start new
265
+ if (this.activeCamera.type !== type) {
266
+ this._endCameraSession()
267
+ this._startCameraSession(type)
268
+ } else {
269
+ // Reset timeout
270
+ this.activeCamera.timeout = this.editor.timers.setTimeout(
271
+ () => this._endCameraSession(),
272
+ 50
273
+ )
274
+ }
275
+ } else {
276
+ this._startCameraSession(type)
277
+ }
278
+ }
279
+
280
+ /** @internal */
281
+ _notifyUndoRedo(type: 'undo' | 'redo', undoDepth: number, redoDepth: number) {
282
+ if (this.emitter.listenerCount(type) === 0) return
283
+
284
+ const event: TLUndoRedoPerfEvent = {
285
+ type,
286
+ undoDepth,
287
+ redoDepth,
288
+ }
289
+ this.emitter.emit(type, event)
290
+ }
291
+
292
+ // --- Private helpers ---
293
+
294
+ private _startCameraSession(type: 'panning' | 'zooming') {
295
+ this.activeCamera = {
296
+ type,
297
+ startTime: performance.now(),
298
+ frameTimes: [],
299
+ timeout: this.editor.timers.setTimeout(() => this._endCameraSession(), 50),
300
+ loafEntries: [],
301
+ }
302
+
303
+ if (this.emitter.listenerCount('camera-start') > 0) {
304
+ const event: TLCameraStartPerfEvent = {
305
+ type,
306
+ timestamp: performance.now(),
307
+ }
308
+ this.emitter.emit('camera-start', event)
309
+ }
310
+ }
311
+
312
+ private _endCameraSession() {
313
+ const camera = this.activeCamera
314
+ if (!camera) return
315
+ this.activeCamera = null
316
+ if (camera.timeout) clearTimeout(camera.timeout)
317
+
318
+ if (this.emitter.listenerCount('camera-end') === 0) return
319
+
320
+ const duration = performance.now() - camera.startTime
321
+ const stats = computeFrameTimeStats(camera.frameTimes)
322
+ const viewportBounds = this.editor.getViewportScreenBounds()
323
+ const totalShapes = this.editor.getCurrentPageShapeIds().size
324
+ const culledShapeCount = this.editor.getCulledShapes().size
325
+
326
+ const event: TLCameraEndPerfEvent = {
327
+ type: camera.type,
328
+ duration,
329
+ fps: camera.frameTimes.length > 0 ? (camera.frameTimes.length / duration) * 1000 : 0,
330
+ frameCount: camera.frameTimes.length,
331
+ avgFrameTime: stats.avg,
332
+ medianFrameTime: stats.median,
333
+ p95FrameTime: stats.p95,
334
+ p99FrameTime: stats.p99,
335
+ minFrameTime: stats.min,
336
+ maxFrameTime: stats.max,
337
+ frameTimes: camera.frameTimes,
338
+ shapeCount: totalShapes,
339
+ viewportWidth: viewportBounds.w,
340
+ viewportHeight: viewportBounds.h,
341
+ longAnimationFrames: camera.loafEntries.length > 0 ? camera.loafEntries : undefined,
342
+ visibleShapeCount: totalShapes - culledShapeCount,
343
+ culledShapeCount,
344
+ zoomLevel: this.editor.getCamera().z,
345
+ timestamp: performance.now(),
346
+ }
347
+ this.emitter.emit('camera-end', event)
348
+ }
349
+
350
+ @bind
351
+ private _onFrame(elapsed: number) {
352
+ // Record frame time for active interaction/camera
353
+ if (this.activeInteraction) {
354
+ this.activeInteraction.frameTimes.push(elapsed)
355
+ }
356
+ if (this.activeCamera) {
357
+ this.activeCamera.frameTimes.push(elapsed)
358
+ }
359
+
360
+ // Emit standalone frame event if listeners exist
361
+ if (this.emitter.listenerCount('frame') > 0) {
362
+ const totalShapes = this.editor.getCurrentPageShapeIds().size
363
+ const culledShapes = this.editor.getCulledShapes()
364
+ const culledCount = culledShapes.size
365
+ const event: TLFramePerfEvent = {
366
+ elapsed,
367
+ shapeCount: totalShapes,
368
+ culledShapeCount: culledCount,
369
+ visibleShapeCount: totalShapes - culledCount,
370
+ }
371
+ this.emitter.emit('frame', event)
372
+ }
373
+ }
374
+
375
+ @bind
376
+ private _onShapesCreated(records: TLRecord[]) {
377
+ if (this.emitter.listenerCount('shapes-created') === 0) return
378
+ const shapeTypes: Record<string, number> = {}
379
+ for (const record of records) {
380
+ if (record.typeName === 'shape') {
381
+ shapeTypes[record.type] = (shapeTypes[record.type] || 0) + 1
382
+ }
383
+ }
384
+ const count = Object.values(shapeTypes).reduce((a, b) => a + b, 0)
385
+ if (count === 0) return
386
+ const event: TLShapeOperationPerfEvent = {
387
+ operation: 'create',
388
+ count,
389
+ shapeTypes,
390
+ timestamp: performance.now(),
391
+ }
392
+ this.emitter.emit('shapes-created', event)
393
+ }
394
+
395
+ @bind
396
+ private _onShapesEdited(records: TLRecord[]) {
397
+ if (this.emitter.listenerCount('shapes-updated') === 0) return
398
+ const shapeTypes: Record<string, number> = {}
399
+ for (const record of records) {
400
+ if (record.typeName === 'shape') {
401
+ shapeTypes[record.type] = (shapeTypes[record.type] || 0) + 1
402
+ }
403
+ }
404
+ const count = Object.values(shapeTypes).reduce((a, b) => a + b, 0)
405
+ if (count === 0) return
406
+ const event: TLShapeOperationPerfEvent = {
407
+ operation: 'update',
408
+ count,
409
+ shapeTypes,
410
+ timestamp: performance.now(),
411
+ }
412
+ this.emitter.emit('shapes-updated', event)
413
+ }
414
+
415
+ @bind
416
+ private _onShapesDeleted(ids: TLShapeId[]) {
417
+ if (this.emitter.listenerCount('shapes-deleted') === 0) return
418
+ const shapeTypes: Record<string, number> = {}
419
+ for (const id of ids) {
420
+ // Works because 'deleted-shapes' fires before store.remove() in Editor.deleteShapes
421
+ const shape = this.editor.getShape(id)
422
+ if (shape) {
423
+ shapeTypes[shape.type] = (shapeTypes[shape.type] || 0) + 1
424
+ }
425
+ }
426
+ const event: TLShapeOperationPerfEvent = {
427
+ operation: 'delete',
428
+ count: ids.length,
429
+ shapeTypes,
430
+ timestamp: performance.now(),
431
+ }
432
+ this.emitter.emit('shapes-deleted', event)
433
+ }
434
+
435
+ // --- LoAF observer ---
436
+
437
+ private _startLoafObserver() {
438
+ if (typeof PerformanceObserver === 'undefined') return
439
+
440
+ try {
441
+ const supported = PerformanceObserver.supportedEntryTypes
442
+ if (!supported?.includes('long-animation-frame')) return
443
+ } catch {
444
+ return
445
+ }
446
+
447
+ this.loafObserver = new PerformanceObserver((list) => {
448
+ const isInteractionActive = this.activeInteraction !== null
449
+ const isCameraActive = this.activeCamera !== null
450
+
451
+ if (!isInteractionActive && !isCameraActive) return
452
+
453
+ for (const entry of list.getEntries()) {
454
+ const loaf = toLoafEntry(entry)
455
+ if (!loaf) continue
456
+
457
+ if (isInteractionActive) {
458
+ this.activeInteraction!.loafEntries.push(loaf)
459
+ }
460
+ if (isCameraActive) {
461
+ this.activeCamera!.loafEntries.push(loaf)
462
+ }
463
+ }
464
+ })
465
+
466
+ this.loafObserver.observe({ type: 'long-animation-frame', buffered: false })
467
+ }
468
+
469
+ private _stopLoafObserver() {
470
+ if (this.loafObserver) {
471
+ this.loafObserver.disconnect()
472
+ this.loafObserver = null
473
+ }
474
+ }
475
+
476
+ // --- Lazy listener management ---
477
+
478
+ private _needsFrameListener(): boolean {
479
+ return (
480
+ this.emitter.listenerCount('frame') > 0 ||
481
+ this.emitter.listenerCount('interaction-start') > 0 ||
482
+ this.emitter.listenerCount('interaction-end') > 0 ||
483
+ this.emitter.listenerCount('camera-start') > 0 ||
484
+ this.emitter.listenerCount('camera-end') > 0
485
+ )
486
+ }
487
+
488
+ private _needsLoafObserver(): boolean {
489
+ return (
490
+ this.emitter.listenerCount('interaction-end') > 0 ||
491
+ this.emitter.listenerCount('camera-end') > 0
492
+ )
493
+ }
494
+
495
+ private _maybeAttachLazyListeners(event: keyof TLPerfEventMap) {
496
+ // Frame listener needed for frame event + interaction/camera frame time tracking
497
+ if (
498
+ !this.frameCleanup &&
499
+ (event === 'frame' ||
500
+ event === 'interaction-start' ||
501
+ event === 'interaction-end' ||
502
+ event === 'camera-start' ||
503
+ event === 'camera-end')
504
+ ) {
505
+ if (this._needsFrameListener()) {
506
+ this.editor.on('frame', this._onFrame)
507
+ this.frameCleanup = () => this.editor.off('frame', this._onFrame)
508
+ }
509
+ }
510
+
511
+ // LoAF observer needed when interaction-end or camera-end listeners exist
512
+ if (!this.loafObserver && (event === 'interaction-end' || event === 'camera-end')) {
513
+ if (this._needsLoafObserver()) {
514
+ this._startLoafObserver()
515
+ }
516
+ }
517
+
518
+ if (!this.shapeCreatedCleanup && event === 'shapes-created') {
519
+ this.editor.on('created-shapes', this._onShapesCreated)
520
+ this.shapeCreatedCleanup = () => this.editor.off('created-shapes', this._onShapesCreated)
521
+ }
522
+
523
+ if (!this.shapeEditedCleanup && event === 'shapes-updated') {
524
+ this.editor.on('edited-shapes', this._onShapesEdited)
525
+ this.shapeEditedCleanup = () => this.editor.off('edited-shapes', this._onShapesEdited)
526
+ }
527
+
528
+ if (!this.shapeDeletedCleanup && event === 'shapes-deleted') {
529
+ this.editor.on('deleted-shapes', this._onShapesDeleted)
530
+ this.shapeDeletedCleanup = () => this.editor.off('deleted-shapes', this._onShapesDeleted)
531
+ }
532
+ }
533
+
534
+ private _maybeDetachLazyListeners(event: keyof TLPerfEventMap) {
535
+ if (
536
+ this.frameCleanup &&
537
+ (event === 'frame' ||
538
+ event === 'interaction-start' ||
539
+ event === 'interaction-end' ||
540
+ event === 'camera-start' ||
541
+ event === 'camera-end')
542
+ ) {
543
+ if (!this._needsFrameListener()) {
544
+ this.frameCleanup()
545
+ this.frameCleanup = null
546
+ }
547
+ }
548
+
549
+ // Stop LoAF observer when no longer needed
550
+ if (this.loafObserver && (event === 'interaction-end' || event === 'camera-end')) {
551
+ if (!this._needsLoafObserver()) {
552
+ this._stopLoafObserver()
553
+ }
554
+ }
555
+
556
+ if (
557
+ this.shapeCreatedCleanup &&
558
+ event === 'shapes-created' &&
559
+ this.emitter.listenerCount('shapes-created') === 0
560
+ ) {
561
+ this.shapeCreatedCleanup()
562
+ this.shapeCreatedCleanup = null
563
+ }
564
+
565
+ if (
566
+ this.shapeEditedCleanup &&
567
+ event === 'shapes-updated' &&
568
+ this.emitter.listenerCount('shapes-updated') === 0
569
+ ) {
570
+ this.shapeEditedCleanup()
571
+ this.shapeEditedCleanup = null
572
+ }
573
+
574
+ if (
575
+ this.shapeDeletedCleanup &&
576
+ event === 'shapes-deleted' &&
577
+ this.emitter.listenerCount('shapes-deleted') === 0
578
+ ) {
579
+ this.shapeDeletedCleanup()
580
+ this.shapeDeletedCleanup = null
581
+ }
582
+ }
583
+ }