@tldraw/editor 3.16.0-internal.a478398270c6 → 3.16.0-internal.f8b97f0c414f

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 (325) hide show
  1. package/dist-cjs/index.d.ts +350 -142
  2. package/dist-cjs/index.js +13 -6
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/TldrawEditor.js +10 -8
  5. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  6. package/dist-cjs/lib/components/MenuClickCapture.js +0 -5
  7. package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
  8. package/dist-cjs/lib/components/SVGContainer.js +1 -1
  9. package/dist-cjs/lib/components/SVGContainer.js.map +2 -2
  10. package/dist-cjs/lib/components/Shape.js +11 -36
  11. package/dist-cjs/lib/components/Shape.js.map +2 -2
  12. package/dist-cjs/lib/components/default-components/DefaultBrush.js +1 -1
  13. package/dist-cjs/lib/components/default-components/DefaultBrush.js.map +2 -2
  14. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +15 -24
  15. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  16. package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js +2 -2
  17. package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js.map +2 -2
  18. package/dist-cjs/lib/components/default-components/DefaultCursor.js +1 -1
  19. package/dist-cjs/lib/components/default-components/DefaultCursor.js.map +2 -2
  20. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +1 -1
  21. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +2 -2
  22. package/dist-cjs/lib/components/default-components/DefaultGrid.js +1 -1
  23. package/dist-cjs/lib/components/default-components/DefaultGrid.js.map +2 -2
  24. package/dist-cjs/lib/components/default-components/DefaultHandles.js +1 -1
  25. package/dist-cjs/lib/components/default-components/DefaultHandles.js.map +2 -2
  26. package/dist-cjs/lib/components/default-components/DefaultScribble.js +1 -1
  27. package/dist-cjs/lib/components/default-components/DefaultScribble.js.map +2 -2
  28. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js +9 -1
  29. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js.map +2 -2
  30. package/dist-cjs/lib/components/default-components/DefaultShapeWrapper.js +53 -0
  31. package/dist-cjs/lib/components/default-components/DefaultShapeWrapper.js.map +7 -0
  32. package/dist-cjs/lib/components/default-components/DefaultSnapIndictor.js +1 -1
  33. package/dist-cjs/lib/components/default-components/DefaultSnapIndictor.js.map +2 -2
  34. package/dist-cjs/lib/components/default-components/DefaultSpinner.js +27 -15
  35. package/dist-cjs/lib/components/default-components/DefaultSpinner.js.map +3 -3
  36. package/dist-cjs/lib/config/TLUserPreferences.js +15 -3
  37. package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
  38. package/dist-cjs/lib/editor/Editor.js +174 -180
  39. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  40. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +4 -0
  41. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +2 -2
  42. package/dist-cjs/lib/editor/derivations/parentsToChildren.js.map +2 -2
  43. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +14 -4
  44. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  45. package/dist-cjs/lib/editor/shapes/BaseBoxShapeUtil.js.map +1 -1
  46. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +23 -0
  47. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  48. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool.js.map +2 -2
  49. package/dist-cjs/lib/editor/tools/StateNode.js +20 -1
  50. package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
  51. package/dist-cjs/lib/editor/types/misc-types.js.map +1 -1
  52. package/dist-cjs/lib/exports/getSvgJsx.js +35 -16
  53. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  54. package/dist-cjs/lib/hooks/useCanvasEvents.js +44 -35
  55. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  56. package/dist-cjs/lib/hooks/useDocumentEvents.js +5 -5
  57. package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
  58. package/dist-cjs/lib/hooks/useEditor.js +1 -4
  59. package/dist-cjs/lib/hooks/useEditor.js.map +2 -2
  60. package/dist-cjs/lib/hooks/useEditorComponents.js +2 -0
  61. package/dist-cjs/lib/hooks/useEditorComponents.js.map +2 -2
  62. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js +1 -2
  63. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js.map +2 -2
  64. package/dist-cjs/lib/hooks/useGestureEvents.js +1 -1
  65. package/dist-cjs/lib/hooks/useGestureEvents.js.map +2 -2
  66. package/dist-cjs/lib/hooks/useHandleEvents.js +3 -3
  67. package/dist-cjs/lib/hooks/useHandleEvents.js.map +2 -2
  68. package/dist-cjs/lib/hooks/usePassThroughMouseOverEvents.js +4 -1
  69. package/dist-cjs/lib/hooks/usePassThroughMouseOverEvents.js.map +2 -2
  70. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js +4 -1
  71. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +2 -2
  72. package/dist-cjs/lib/hooks/useSelectionEvents.js +4 -4
  73. package/dist-cjs/lib/hooks/useSelectionEvents.js.map +2 -2
  74. package/dist-cjs/lib/{utils/nearestMultiple.js → hooks/useStateAttribute.js} +15 -14
  75. package/dist-cjs/lib/hooks/useStateAttribute.js.map +7 -0
  76. package/dist-cjs/lib/license/LicenseManager.js +140 -53
  77. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  78. package/dist-cjs/lib/license/LicenseProvider.js +39 -1
  79. package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
  80. package/dist-cjs/lib/license/Watermark.js +75 -13
  81. package/dist-cjs/lib/license/Watermark.js.map +3 -3
  82. package/dist-cjs/lib/license/useLicenseManagerState.js.map +2 -2
  83. package/dist-cjs/lib/options.js +7 -0
  84. package/dist-cjs/lib/options.js.map +2 -2
  85. package/dist-cjs/lib/primitives/Box.js +3 -0
  86. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  87. package/dist-cjs/lib/primitives/Vec.js +0 -4
  88. package/dist-cjs/lib/primitives/Vec.js.map +2 -2
  89. package/dist-cjs/lib/primitives/geometry/Arc2d.js +1 -1
  90. package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
  91. package/dist-cjs/lib/primitives/geometry/Circle2d.js +1 -1
  92. package/dist-cjs/lib/primitives/geometry/Circle2d.js.map +2 -2
  93. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +3 -1
  94. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
  95. package/dist-cjs/lib/primitives/geometry/Ellipse2d.js +1 -1
  96. package/dist-cjs/lib/primitives/geometry/Ellipse2d.js.map +2 -2
  97. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +50 -20
  98. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  99. package/dist-cjs/lib/primitives/geometry/Group2d.js +8 -1
  100. package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
  101. package/dist-cjs/lib/primitives/geometry/geometry-constants.js +2 -2
  102. package/dist-cjs/lib/primitives/geometry/geometry-constants.js.map +2 -2
  103. package/dist-cjs/lib/primitives/intersect.js +4 -4
  104. package/dist-cjs/lib/primitives/intersect.js.map +2 -2
  105. package/dist-cjs/lib/primitives/utils.js +4 -0
  106. package/dist-cjs/lib/primitives/utils.js.map +2 -2
  107. package/dist-cjs/lib/utils/EditorAtom.js +45 -0
  108. package/dist-cjs/lib/utils/EditorAtom.js.map +7 -0
  109. package/dist-cjs/lib/utils/dom.js +12 -1
  110. package/dist-cjs/lib/utils/dom.js.map +2 -2
  111. package/dist-cjs/lib/utils/getPointerInfo.js +2 -2
  112. package/dist-cjs/lib/utils/getPointerInfo.js.map +2 -2
  113. package/dist-cjs/lib/utils/reparenting.js +2 -35
  114. package/dist-cjs/lib/utils/reparenting.js.map +3 -3
  115. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +0 -1
  116. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +2 -2
  117. package/dist-cjs/version.js +3 -3
  118. package/dist-cjs/version.js.map +1 -1
  119. package/dist-esm/index.d.mts +350 -142
  120. package/dist-esm/index.mjs +24 -8
  121. package/dist-esm/index.mjs.map +2 -2
  122. package/dist-esm/lib/TldrawEditor.mjs +11 -9
  123. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  124. package/dist-esm/lib/components/MenuClickCapture.mjs +0 -5
  125. package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
  126. package/dist-esm/lib/components/SVGContainer.mjs +1 -1
  127. package/dist-esm/lib/components/SVGContainer.mjs.map +2 -2
  128. package/dist-esm/lib/components/Shape.mjs +11 -36
  129. package/dist-esm/lib/components/Shape.mjs.map +2 -2
  130. package/dist-esm/lib/components/default-components/DefaultBrush.mjs +1 -1
  131. package/dist-esm/lib/components/default-components/DefaultBrush.mjs.map +2 -2
  132. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +16 -25
  133. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  134. package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs +2 -2
  135. package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs.map +2 -2
  136. package/dist-esm/lib/components/default-components/DefaultCursor.mjs +1 -1
  137. package/dist-esm/lib/components/default-components/DefaultCursor.mjs.map +2 -2
  138. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +1 -1
  139. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +2 -2
  140. package/dist-esm/lib/components/default-components/DefaultGrid.mjs +1 -1
  141. package/dist-esm/lib/components/default-components/DefaultGrid.mjs.map +2 -2
  142. package/dist-esm/lib/components/default-components/DefaultHandles.mjs +1 -1
  143. package/dist-esm/lib/components/default-components/DefaultHandles.mjs.map +2 -2
  144. package/dist-esm/lib/components/default-components/DefaultScribble.mjs +1 -1
  145. package/dist-esm/lib/components/default-components/DefaultScribble.mjs.map +2 -2
  146. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs +9 -1
  147. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +2 -2
  148. package/dist-esm/lib/components/default-components/DefaultShapeWrapper.mjs +23 -0
  149. package/dist-esm/lib/components/default-components/DefaultShapeWrapper.mjs.map +7 -0
  150. package/dist-esm/lib/components/default-components/DefaultSnapIndictor.mjs +1 -1
  151. package/dist-esm/lib/components/default-components/DefaultSnapIndictor.mjs.map +2 -2
  152. package/dist-esm/lib/components/default-components/DefaultSpinner.mjs +17 -15
  153. package/dist-esm/lib/components/default-components/DefaultSpinner.mjs.map +2 -2
  154. package/dist-esm/lib/config/TLUserPreferences.mjs +15 -3
  155. package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
  156. package/dist-esm/lib/editor/Editor.mjs +174 -180
  157. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  158. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +4 -0
  159. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +2 -2
  160. package/dist-esm/lib/editor/derivations/parentsToChildren.mjs.map +2 -2
  161. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +14 -4
  162. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  163. package/dist-esm/lib/editor/shapes/BaseBoxShapeUtil.mjs.map +1 -1
  164. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +23 -0
  165. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  166. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool.mjs.map +2 -2
  167. package/dist-esm/lib/editor/tools/StateNode.mjs +20 -1
  168. package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
  169. package/dist-esm/lib/exports/getSvgJsx.mjs +36 -16
  170. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  171. package/dist-esm/lib/hooks/useCanvasEvents.mjs +47 -37
  172. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  173. package/dist-esm/lib/hooks/useDocumentEvents.mjs +11 -6
  174. package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
  175. package/dist-esm/lib/hooks/useEditor.mjs +1 -4
  176. package/dist-esm/lib/hooks/useEditor.mjs.map +2 -2
  177. package/dist-esm/lib/hooks/useEditorComponents.mjs +4 -0
  178. package/dist-esm/lib/hooks/useEditorComponents.mjs.map +2 -2
  179. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs +2 -3
  180. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs.map +2 -2
  181. package/dist-esm/lib/hooks/useGestureEvents.mjs +2 -2
  182. package/dist-esm/lib/hooks/useGestureEvents.mjs.map +2 -2
  183. package/dist-esm/lib/hooks/useHandleEvents.mjs +9 -4
  184. package/dist-esm/lib/hooks/useHandleEvents.mjs.map +2 -2
  185. package/dist-esm/lib/hooks/usePassThroughMouseOverEvents.mjs +4 -1
  186. package/dist-esm/lib/hooks/usePassThroughMouseOverEvents.mjs.map +2 -2
  187. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs +4 -1
  188. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +2 -2
  189. package/dist-esm/lib/hooks/useSelectionEvents.mjs +6 -5
  190. package/dist-esm/lib/hooks/useSelectionEvents.mjs.map +2 -2
  191. package/dist-esm/lib/hooks/useStateAttribute.mjs +15 -0
  192. package/dist-esm/lib/hooks/useStateAttribute.mjs.map +7 -0
  193. package/dist-esm/lib/license/LicenseManager.mjs +141 -54
  194. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  195. package/dist-esm/lib/license/LicenseProvider.mjs +39 -2
  196. package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
  197. package/dist-esm/lib/license/Watermark.mjs +76 -14
  198. package/dist-esm/lib/license/Watermark.mjs.map +3 -3
  199. package/dist-esm/lib/license/useLicenseManagerState.mjs.map +2 -2
  200. package/dist-esm/lib/options.mjs +7 -0
  201. package/dist-esm/lib/options.mjs.map +2 -2
  202. package/dist-esm/lib/primitives/Box.mjs +4 -1
  203. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  204. package/dist-esm/lib/primitives/Vec.mjs +0 -4
  205. package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
  206. package/dist-esm/lib/primitives/geometry/Arc2d.mjs +2 -2
  207. package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
  208. package/dist-esm/lib/primitives/geometry/Circle2d.mjs +2 -2
  209. package/dist-esm/lib/primitives/geometry/Circle2d.mjs.map +2 -2
  210. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +3 -1
  211. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
  212. package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs +2 -2
  213. package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs.map +2 -2
  214. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +53 -21
  215. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  216. package/dist-esm/lib/primitives/geometry/Group2d.mjs +8 -1
  217. package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
  218. package/dist-esm/lib/primitives/geometry/geometry-constants.mjs +2 -2
  219. package/dist-esm/lib/primitives/geometry/geometry-constants.mjs.map +2 -2
  220. package/dist-esm/lib/primitives/intersect.mjs +5 -5
  221. package/dist-esm/lib/primitives/intersect.mjs.map +2 -2
  222. package/dist-esm/lib/primitives/utils.mjs +4 -0
  223. package/dist-esm/lib/primitives/utils.mjs.map +2 -2
  224. package/dist-esm/lib/utils/EditorAtom.mjs +25 -0
  225. package/dist-esm/lib/utils/EditorAtom.mjs.map +7 -0
  226. package/dist-esm/lib/utils/dom.mjs +12 -1
  227. package/dist-esm/lib/utils/dom.mjs.map +2 -2
  228. package/dist-esm/lib/utils/getPointerInfo.mjs +2 -2
  229. package/dist-esm/lib/utils/getPointerInfo.mjs.map +2 -2
  230. package/dist-esm/lib/utils/reparenting.mjs +3 -40
  231. package/dist-esm/lib/utils/reparenting.mjs.map +2 -2
  232. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +0 -1
  233. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
  234. package/dist-esm/version.mjs +3 -3
  235. package/dist-esm/version.mjs.map +1 -1
  236. package/editor.css +327 -315
  237. package/package.json +16 -38
  238. package/src/index.ts +19 -10
  239. package/src/lib/TldrawEditor.tsx +16 -21
  240. package/src/lib/components/MenuClickCapture.tsx +0 -8
  241. package/src/lib/components/SVGContainer.tsx +1 -1
  242. package/src/lib/components/Shape.tsx +12 -33
  243. package/src/lib/components/default-components/DefaultBrush.tsx +1 -1
  244. package/src/lib/components/default-components/DefaultCanvas.tsx +13 -24
  245. package/src/lib/components/default-components/DefaultCollaboratorHint.tsx +2 -2
  246. package/src/lib/components/default-components/DefaultCursor.tsx +1 -1
  247. package/src/lib/components/default-components/DefaultErrorFallback.tsx +1 -1
  248. package/src/lib/components/default-components/DefaultGrid.tsx +1 -1
  249. package/src/lib/components/default-components/DefaultHandles.tsx +5 -1
  250. package/src/lib/components/default-components/DefaultScribble.tsx +1 -1
  251. package/src/lib/components/default-components/DefaultShapeIndicator.tsx +6 -2
  252. package/src/lib/components/default-components/DefaultShapeWrapper.tsx +35 -0
  253. package/src/lib/components/default-components/DefaultSnapIndictor.tsx +1 -1
  254. package/src/lib/components/default-components/DefaultSpinner.tsx +12 -12
  255. package/src/lib/config/TLUserPreferences.ts +15 -1
  256. package/src/lib/editor/Editor.test.ts +512 -8
  257. package/src/lib/editor/Editor.ts +252 -267
  258. package/src/lib/editor/derivations/notVisibleShapes.ts +6 -0
  259. package/src/lib/editor/derivations/parentsToChildren.ts +1 -1
  260. package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +15 -14
  261. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +16 -15
  262. package/src/lib/editor/managers/FocusManager/FocusManager.test.ts +49 -48
  263. package/src/lib/editor/managers/FontManager/FontManager.test.ts +38 -27
  264. package/src/lib/editor/managers/HistoryManager/HistoryManager.test.ts +7 -6
  265. package/src/lib/editor/managers/ScribbleManager/ScribbleManager.test.ts +12 -11
  266. package/src/lib/editor/managers/SnapManager/SnapManager.test.ts +57 -50
  267. package/src/lib/editor/managers/TextManager/TextManager.test.ts +51 -26
  268. package/src/lib/editor/managers/TickManager/TickManager.test.ts +14 -13
  269. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +55 -26
  270. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +14 -1
  271. package/src/lib/editor/shapes/BaseBoxShapeUtil.tsx +2 -2
  272. package/src/lib/editor/shapes/ShapeUtil.ts +108 -8
  273. package/src/lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool.ts +2 -1
  274. package/src/lib/editor/tools/StateNode.test.ts +285 -0
  275. package/src/lib/editor/tools/StateNode.ts +27 -1
  276. package/src/lib/editor/types/misc-types.ts +73 -7
  277. package/src/lib/exports/getSvgJsx.test.ts +874 -0
  278. package/src/lib/exports/getSvgJsx.tsx +78 -21
  279. package/src/lib/hooks/useCanvasEvents.ts +60 -47
  280. package/src/lib/hooks/useDocumentEvents.ts +11 -6
  281. package/src/lib/hooks/useEditor.tsx +6 -5
  282. package/src/lib/hooks/useEditorComponents.tsx +8 -2
  283. package/src/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.ts +2 -2
  284. package/src/lib/hooks/useGestureEvents.ts +2 -2
  285. package/src/lib/hooks/useHandleEvents.ts +9 -4
  286. package/src/lib/hooks/usePassThroughMouseOverEvents.ts +4 -1
  287. package/src/lib/hooks/usePassThroughWheelEvents.ts +6 -1
  288. package/src/lib/hooks/useSelectionEvents.ts +6 -5
  289. package/src/lib/hooks/useStateAttribute.ts +15 -0
  290. package/src/lib/license/LicenseManager.test.ts +724 -383
  291. package/src/lib/license/LicenseManager.ts +201 -58
  292. package/src/lib/license/LicenseProvider.tsx +74 -2
  293. package/src/lib/license/Watermark.test.tsx +2 -1
  294. package/src/lib/license/Watermark.tsx +81 -14
  295. package/src/lib/license/useLicenseManagerState.ts +2 -2
  296. package/src/lib/options.ts +8 -0
  297. package/src/lib/primitives/Box.test.ts +126 -0
  298. package/src/lib/primitives/Box.ts +10 -1
  299. package/src/lib/primitives/Vec.ts +0 -5
  300. package/src/lib/primitives/geometry/Arc2d.ts +2 -2
  301. package/src/lib/primitives/geometry/Circle2d.ts +2 -2
  302. package/src/lib/primitives/geometry/CubicBezier2d.ts +4 -1
  303. package/src/lib/primitives/geometry/Ellipse2d.ts +2 -2
  304. package/src/lib/primitives/geometry/Geometry2d.test.ts +420 -0
  305. package/src/lib/primitives/geometry/Geometry2d.ts +78 -21
  306. package/src/lib/primitives/geometry/Group2d.ts +10 -1
  307. package/src/lib/primitives/geometry/geometry-constants.ts +2 -1
  308. package/src/lib/primitives/intersect.test.ts +946 -0
  309. package/src/lib/primitives/intersect.ts +12 -5
  310. package/src/lib/primitives/utils.ts +11 -0
  311. package/src/lib/test/InFrontOfTheCanvas.test.tsx +187 -0
  312. package/src/lib/utils/EditorAtom.ts +37 -0
  313. package/src/lib/utils/dom.test.ts +94 -0
  314. package/src/lib/utils/dom.ts +38 -1
  315. package/src/lib/utils/getPointerInfo.ts +2 -1
  316. package/src/lib/utils/reparenting.ts +3 -69
  317. package/src/lib/utils/sync/LocalIndexedDb.test.ts +2 -1
  318. package/src/lib/utils/sync/TLLocalSyncClient.test.ts +15 -15
  319. package/src/lib/utils/sync/TLLocalSyncClient.ts +0 -1
  320. package/src/version.ts +3 -3
  321. package/dist-cjs/lib/utils/nearestMultiple.js.map +0 -7
  322. package/dist-esm/lib/utils/nearestMultiple.mjs +0 -14
  323. package/dist-esm/lib/utils/nearestMultiple.mjs.map +0 -7
  324. package/src/lib/test/currentToolIdMask.test.ts +0 -49
  325. package/src/lib/utils/nearestMultiple.ts +0 -13
@@ -1,19 +1,19 @@
1
+ import classNames from 'classnames'
2
+
1
3
  /** @public @react */
2
- export function DefaultSpinner() {
4
+ export function DefaultSpinner(props: React.SVGProps<SVGSVGElement>) {
3
5
  return (
4
- <svg width={16} height={16} viewBox="0 0 16 16" aria-hidden="false">
6
+ <svg
7
+ width={16}
8
+ height={16}
9
+ viewBox="0 0 16 16"
10
+ aria-hidden="false"
11
+ {...props}
12
+ className={classNames('tl-spinner', props.className)}
13
+ >
5
14
  <g strokeWidth={2} fill="none" fillRule="evenodd">
6
15
  <circle strokeOpacity={0.25} cx={8} cy={8} r={7} stroke="currentColor" />
7
- <path strokeLinecap="round" d="M15 8c0-4.5-4.5-7-7-7" stroke="currentColor">
8
- <animateTransform
9
- attributeName="transform"
10
- type="rotate"
11
- from="0 8 8"
12
- to="360 8 8"
13
- dur="1s"
14
- repeatCount="indefinite"
15
- />
16
- </path>
16
+ <path strokeLinecap="round" d="M15 8c0-4.5-4.5-7-7-7" stroke="currentColor" />
17
17
  </g>
18
18
  </svg>
19
19
  )
@@ -17,12 +17,14 @@ export interface TLUserPreferences {
17
17
  // N.B. These are duplicated in TLdrawAppUser.
18
18
  locale?: string | null
19
19
  animationSpeed?: number | null
20
+ areKeyboardShortcutsEnabled?: boolean | null
20
21
  edgeScrollSpeed?: number | null
21
22
  colorScheme?: 'light' | 'dark' | 'system'
22
23
  isSnapMode?: boolean | null
23
24
  isWrapMode?: boolean | null
24
25
  isDynamicSizeMode?: boolean | null
25
26
  isPasteAtCursorMode?: boolean | null
27
+ showUiLabels?: boolean | null
26
28
  }
27
29
 
28
30
  interface UserDataSnapshot {
@@ -44,12 +46,14 @@ export const userTypeValidator: T.Validator<TLUserPreferences> = T.object<TLUser
44
46
  // N.B. These are duplicated in TLdrawAppUser.
45
47
  locale: T.string.nullable().optional(),
46
48
  animationSpeed: T.number.nullable().optional(),
49
+ areKeyboardShortcutsEnabled: T.boolean.nullable().optional(),
47
50
  edgeScrollSpeed: T.number.nullable().optional(),
48
51
  colorScheme: T.literalEnum('light', 'dark', 'system').optional(),
49
52
  isSnapMode: T.boolean.nullable().optional(),
50
53
  isWrapMode: T.boolean.nullable().optional(),
51
54
  isDynamicSizeMode: T.boolean.nullable().optional(),
52
55
  isPasteAtCursorMode: T.boolean.nullable().optional(),
56
+ showUiLabels: T.boolean.nullable().optional(),
53
57
  })
54
58
 
55
59
  const Versions = {
@@ -61,6 +65,8 @@ const Versions = {
61
65
  AddDynamicSizeMode: 6,
62
66
  AllowSystemColorScheme: 7,
63
67
  AddPasteAtCursor: 8,
68
+ AddKeyboardShortcuts: 9,
69
+ AddShowUiLabels: 10,
64
70
  } as const
65
71
 
66
72
  const CURRENT_VERSION = Math.max(...Object.values(Versions))
@@ -96,6 +102,12 @@ function migrateSnapshot(data: { version: number; user: any }) {
96
102
  if (data.version < Versions.AddPasteAtCursor) {
97
103
  data.user.isPasteAtCursorMode = false
98
104
  }
105
+ if (data.version < Versions.AddKeyboardShortcuts) {
106
+ data.user.areKeyboardShortcutsEnabled = true
107
+ }
108
+ if (data.version < Versions.AddShowUiLabels) {
109
+ data.user.showUiLabels = false
110
+ }
99
111
 
100
112
  // finally
101
113
  data.version = CURRENT_VERSION
@@ -123,7 +135,7 @@ function getRandomColor() {
123
135
 
124
136
  /** @internal */
125
137
  export function userPrefersReducedMotion() {
126
- if (typeof window !== 'undefined' && 'matchMedia' in window) {
138
+ if (typeof window !== 'undefined' && window.matchMedia) {
127
139
  return window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches ?? false
128
140
  }
129
141
 
@@ -139,10 +151,12 @@ export const defaultUserPreferences = Object.freeze({
139
151
  // N.B. These are duplicated in TLdrawAppUser.
140
152
  edgeScrollSpeed: 1,
141
153
  animationSpeed: userPrefersReducedMotion() ? 0 : 1,
154
+ areKeyboardShortcutsEnabled: true,
142
155
  isSnapMode: false,
143
156
  isWrapMode: false,
144
157
  isDynamicSizeMode: false,
145
158
  isPasteAtCursorMode: false,
159
+ showUiLabels: false,
146
160
  colorScheme: 'light',
147
161
  }) satisfies Readonly<Omit<TLUserPreferences, 'id'>>
148
162
 
@@ -1,3 +1,4 @@
1
+ import { vi } from 'vitest'
1
2
  import {
2
3
  Box,
3
4
  Geometry2d,
@@ -11,6 +12,12 @@ import {
11
12
  } from '../..'
12
13
  import { Editor } from './Editor'
13
14
 
15
+ declare module '@tldraw/tlschema' {
16
+ export interface GlobalShapePropsMap {
17
+ 'my-custom-shape': ICustomShape
18
+ }
19
+ }
20
+
14
21
  type ICustomShape = TLBaseShape<
15
22
  'my-custom-shape',
16
23
  {
@@ -59,8 +66,8 @@ beforeEach(() => {
59
66
  getContainer: () => document.body,
60
67
  })
61
68
  editor.setCameraOptions({ isLocked: true })
62
- editor.setCamera = jest.fn()
63
- editor.user.getAnimationSpeed = jest.fn()
69
+ editor.setCamera = vi.fn()
70
+ editor.user.getAnimationSpeed = vi.fn()
64
71
  })
65
72
 
66
73
  describe('centerOnPoint', () => {
@@ -94,13 +101,13 @@ describe('updateShape', () => {
94
101
 
95
102
  describe('zoomToFit', () => {
96
103
  it('no-op when isLocked is set', () => {
97
- editor.getCurrentPageShapeIds = jest.fn(() => new Set([createShapeId('box1')]))
104
+ editor.getCurrentPageShapeIds = vi.fn(() => new Set([createShapeId('box1')]))
98
105
  editor.zoomToFit()
99
106
  expect(editor.setCamera).not.toHaveBeenCalled()
100
107
  })
101
108
 
102
109
  it('sets camera when isLocked is set and force flag is set', () => {
103
- editor.getCurrentPageShapeIds = jest.fn(() => new Set([createShapeId('box1')]))
110
+ editor.getCurrentPageShapeIds = vi.fn(() => new Set([createShapeId('box1')]))
104
111
  editor.zoomToFit({ force: true })
105
112
  expect(editor.setCamera).toHaveBeenCalled()
106
113
  })
@@ -144,13 +151,13 @@ describe('zoomOut', () => {
144
151
 
145
152
  describe('zoomToSelection', () => {
146
153
  it('no-op when isLocked is set', () => {
147
- editor.getSelectionPageBounds = jest.fn(() => Box.From({ x: 0, y: 0, w: 100, h: 100 }))
154
+ editor.getSelectionPageBounds = vi.fn(() => Box.From({ x: 0, y: 0, w: 100, h: 100 }))
148
155
  editor.zoomToSelection()
149
156
  expect(editor.setCamera).not.toHaveBeenCalled()
150
157
  })
151
158
 
152
159
  it('sets camera when isLocked is set and force flag is set', () => {
153
- editor.getSelectionPageBounds = jest.fn(() => Box.From({ x: 0, y: 0, w: 100, h: 100 }))
160
+ editor.getSelectionPageBounds = vi.fn(() => Box.From({ x: 0, y: 0, w: 100, h: 100 }))
154
161
  editor.zoomToSelection({ force: true })
155
162
  expect(editor.setCamera).toHaveBeenCalled()
156
163
  })
@@ -286,7 +293,7 @@ describe('getShapesAtPoint', () => {
286
293
 
287
294
  it('filters out hidden shapes', () => {
288
295
  // Create a spy to mock isShapeHidden
289
- const isShapeHiddenSpy = jest.spyOn(editor, 'isShapeHidden')
296
+ const isShapeHiddenSpy = vi.spyOn(editor, 'isShapeHidden')
290
297
  isShapeHiddenSpy.mockImplementation((shape) => {
291
298
  return typeof shape === 'string' ? shape === ids.shape3 : shape.id === ids.shape3
292
299
  })
@@ -352,7 +359,7 @@ describe('getShapesAtPoint', () => {
352
359
 
353
360
  it('returns empty array when all shapes are hidden', () => {
354
361
  // Mock all shapes as hidden
355
- const isShapeHiddenSpy = jest.spyOn(editor, 'isShapeHidden')
362
+ const isShapeHiddenSpy = vi.spyOn(editor, 'isShapeHidden')
356
363
  isShapeHiddenSpy.mockReturnValue(true)
357
364
 
358
365
  const shapes = editor.getShapesAtPoint({ x: 50, y: 50 })
@@ -425,3 +432,500 @@ describe('getShapesAtPoint', () => {
425
432
  expect(hollowShapesWithHitInside[0].id).toBe(ids.hollowShape)
426
433
  })
427
434
  })
435
+
436
+ describe('selectAll', () => {
437
+ const selectAllIds = {
438
+ pageShape1: createShapeId('pageShape1'),
439
+ pageShape2: createShapeId('pageShape2'),
440
+ pageShape3: createShapeId('pageShape3'),
441
+ container1: createShapeId('container1'),
442
+ containerChild1: createShapeId('containerChild1'),
443
+ containerChild2: createShapeId('containerChild2'),
444
+ containerChild3: createShapeId('containerChild3'),
445
+ containerGrandchild1: createShapeId('containerGrandchild1'),
446
+ container2: createShapeId('container2'),
447
+ container2Child1: createShapeId('container2Child1'),
448
+ container2Child2: createShapeId('container2Child2'),
449
+ container2Grandchild1: createShapeId('container2Grandchild1'),
450
+ lockedShape: createShapeId('lockedShape'),
451
+ }
452
+
453
+ beforeEach(() => {
454
+ // Clear any existing shapes
455
+ editor.selectAll().deleteShapes(editor.getSelectedShapeIds())
456
+
457
+ // Create shapes directly on the page (no parentId means they're children of the page)
458
+ editor.createShapes([
459
+ {
460
+ id: selectAllIds.pageShape1,
461
+ type: 'my-custom-shape',
462
+ x: 100,
463
+ y: 100,
464
+ props: { w: 100, h: 100 },
465
+ },
466
+ {
467
+ id: selectAllIds.pageShape2,
468
+ type: 'my-custom-shape',
469
+ x: 300,
470
+ y: 100,
471
+ props: { w: 100, h: 100 },
472
+ },
473
+ {
474
+ id: selectAllIds.pageShape3,
475
+ type: 'my-custom-shape',
476
+ x: 500,
477
+ y: 100,
478
+ props: { w: 100, h: 100 },
479
+ },
480
+ {
481
+ id: selectAllIds.lockedShape,
482
+ type: 'my-custom-shape',
483
+ x: 700,
484
+ y: 100,
485
+ props: { w: 100, h: 100 },
486
+ isLocked: true,
487
+ },
488
+ ])
489
+
490
+ // Create a container shape (simulating a frame or group)
491
+ editor.createShape({
492
+ id: selectAllIds.container1,
493
+ type: 'my-custom-shape',
494
+ x: 100,
495
+ y: 300,
496
+ props: { w: 400, h: 200 },
497
+ })
498
+
499
+ // Create children inside the container (parentId set to container1)
500
+ editor.createShapes([
501
+ {
502
+ id: selectAllIds.containerChild1,
503
+ type: 'my-custom-shape',
504
+ parentId: selectAllIds.container1,
505
+ x: 120,
506
+ y: 320,
507
+ props: { w: 50, h: 50 },
508
+ },
509
+ {
510
+ id: selectAllIds.containerChild2,
511
+ type: 'my-custom-shape',
512
+ parentId: selectAllIds.container1,
513
+ x: 200,
514
+ y: 320,
515
+ props: { w: 50, h: 50 },
516
+ },
517
+ {
518
+ id: selectAllIds.containerChild3,
519
+ type: 'my-custom-shape',
520
+ parentId: selectAllIds.container1,
521
+ x: 280,
522
+ y: 320,
523
+ props: { w: 50, h: 50 },
524
+ },
525
+ ])
526
+
527
+ // Create a grandchild inside one of the container children
528
+ editor.createShape({
529
+ id: selectAllIds.containerGrandchild1,
530
+ type: 'my-custom-shape',
531
+ parentId: selectAllIds.containerChild3,
532
+ x: 290,
533
+ y: 330,
534
+ props: { w: 30, h: 30 },
535
+ })
536
+
537
+ // Create a second container (simulating a group)
538
+ editor.createShape({
539
+ id: selectAllIds.container2,
540
+ type: 'my-custom-shape',
541
+ x: 600,
542
+ y: 300,
543
+ props: { w: 200, h: 200 },
544
+ })
545
+
546
+ // Create children inside the second container
547
+ editor.createShapes([
548
+ {
549
+ id: selectAllIds.container2Child1,
550
+ type: 'my-custom-shape',
551
+ parentId: selectAllIds.container2,
552
+ x: 620,
553
+ y: 320,
554
+ props: { w: 50, h: 50 },
555
+ },
556
+ {
557
+ id: selectAllIds.container2Child2,
558
+ type: 'my-custom-shape',
559
+ parentId: selectAllIds.container2,
560
+ x: 680,
561
+ y: 320,
562
+ props: { w: 50, h: 50 },
563
+ },
564
+ ])
565
+
566
+ // Create a grandchild in the second container
567
+ editor.createShape({
568
+ id: selectAllIds.container2Grandchild1,
569
+ type: 'my-custom-shape',
570
+ parentId: selectAllIds.container2Child1,
571
+ x: 630,
572
+ y: 330,
573
+ props: { w: 30, h: 30 },
574
+ })
575
+
576
+ // Clear selection
577
+ editor.selectNone()
578
+ })
579
+
580
+ it('when no shapes are selected, selects all page-level shapes (excluding locked ones)', () => {
581
+ // Initially no shapes selected
582
+ expect(editor.getSelectedShapeIds()).toEqual([])
583
+
584
+ // Call selectAll
585
+ editor.selectAll()
586
+
587
+ // Should select all page-level shapes (excluding locked ones)
588
+ const selectedIds = editor.getSelectedShapeIds()
589
+ expect(Array.from(selectedIds).sort()).toEqual(
590
+ [
591
+ selectAllIds.pageShape1,
592
+ selectAllIds.pageShape2,
593
+ selectAllIds.pageShape3,
594
+ selectAllIds.container1,
595
+ selectAllIds.container2,
596
+ ].sort()
597
+ )
598
+
599
+ // Should NOT include locked shape or children/grandchildren
600
+ expect(selectedIds).not.toContain(selectAllIds.lockedShape)
601
+ expect(selectedIds).not.toContain(selectAllIds.containerChild1)
602
+ expect(selectedIds).not.toContain(selectAllIds.containerChild2)
603
+ expect(selectedIds).not.toContain(selectAllIds.containerChild3)
604
+ expect(selectedIds).not.toContain(selectAllIds.containerGrandchild1)
605
+ expect(selectedIds).not.toContain(selectAllIds.container2Child1)
606
+ expect(selectedIds).not.toContain(selectAllIds.container2Child2)
607
+ expect(selectedIds).not.toContain(selectAllIds.container2Grandchild1)
608
+ })
609
+
610
+ it('when shapes are selected only on the page, all children of the page should be selected (but not their descendants)', () => {
611
+ // Select some page-level shapes
612
+ editor.select(selectAllIds.pageShape1, selectAllIds.pageShape2)
613
+
614
+ // Call selectAll
615
+ editor.selectAll()
616
+
617
+ // Should select all page-level shapes (excluding locked ones), but not descendants
618
+ const selectedIds = editor.getSelectedShapeIds()
619
+ expect(Array.from(selectedIds).sort()).toEqual(
620
+ [
621
+ selectAllIds.pageShape1,
622
+ selectAllIds.pageShape2,
623
+ selectAllIds.pageShape3,
624
+ selectAllIds.container1,
625
+ selectAllIds.container2,
626
+ ].sort()
627
+ )
628
+
629
+ // Should NOT include children or grandchildren or locked shapes
630
+ expect(selectedIds).not.toContain(selectAllIds.containerChild1)
631
+ expect(selectedIds).not.toContain(selectAllIds.containerChild2)
632
+ expect(selectedIds).not.toContain(selectAllIds.containerChild3)
633
+ expect(selectedIds).not.toContain(selectAllIds.containerGrandchild1)
634
+ expect(selectedIds).not.toContain(selectAllIds.container2Child1)
635
+ expect(selectedIds).not.toContain(selectAllIds.container2Child2)
636
+ expect(selectedIds).not.toContain(selectAllIds.container2Grandchild1)
637
+ expect(selectedIds).not.toContain(selectAllIds.lockedShape)
638
+ })
639
+
640
+ it('when shapes are selected within a container, only children of the container should be selected (not their descendants)', () => {
641
+ // Select some container children
642
+ editor.select(selectAllIds.containerChild1, selectAllIds.containerChild2)
643
+
644
+ // Call selectAll
645
+ editor.selectAll()
646
+
647
+ // Should select all container children (but not their descendants)
648
+ const selectedIds = editor.getSelectedShapeIds()
649
+ expect(Array.from(selectedIds).sort()).toEqual(
650
+ [
651
+ selectAllIds.containerChild1,
652
+ selectAllIds.containerChild2,
653
+ selectAllIds.containerChild3,
654
+ ].sort()
655
+ )
656
+
657
+ // Should NOT include page-level shapes or grandchildren
658
+ expect(selectedIds).not.toContain(selectAllIds.pageShape1)
659
+ expect(selectedIds).not.toContain(selectAllIds.pageShape2)
660
+ expect(selectedIds).not.toContain(selectAllIds.pageShape3)
661
+ expect(selectedIds).not.toContain(selectAllIds.container1)
662
+ expect(selectedIds).not.toContain(selectAllIds.container2)
663
+ expect(selectedIds).not.toContain(selectAllIds.containerGrandchild1)
664
+ expect(selectedIds).not.toContain(selectAllIds.container2Child1)
665
+ expect(selectedIds).not.toContain(selectAllIds.container2Child2)
666
+ expect(selectedIds).not.toContain(selectAllIds.container2Grandchild1)
667
+ })
668
+
669
+ it('when shapes are selected within a second container, only children of that container should be selected', () => {
670
+ // Select some second container children
671
+ editor.select(selectAllIds.container2Child1)
672
+
673
+ // Call selectAll
674
+ editor.selectAll()
675
+
676
+ // Should select all second container children (but not their descendants)
677
+ const selectedIds = editor.getSelectedShapeIds()
678
+ expect(Array.from(selectedIds).sort()).toEqual(
679
+ [selectAllIds.container2Child1, selectAllIds.container2Child2].sort()
680
+ )
681
+
682
+ // Should NOT include page-level shapes or other container's children or grandchildren
683
+ expect(selectedIds).not.toContain(selectAllIds.pageShape1)
684
+ expect(selectedIds).not.toContain(selectAllIds.pageShape2)
685
+ expect(selectedIds).not.toContain(selectAllIds.pageShape3)
686
+ expect(selectedIds).not.toContain(selectAllIds.container1)
687
+ expect(selectedIds).not.toContain(selectAllIds.container2)
688
+ expect(selectedIds).not.toContain(selectAllIds.containerChild1)
689
+ expect(selectedIds).not.toContain(selectAllIds.containerChild2)
690
+ expect(selectedIds).not.toContain(selectAllIds.containerChild3)
691
+ expect(selectedIds).not.toContain(selectAllIds.containerGrandchild1)
692
+ expect(selectedIds).not.toContain(selectAllIds.container2Grandchild1)
693
+ })
694
+
695
+ it('when shapes are selected that belong to different parents, no change/history entry should be made', () => {
696
+ // Select shapes from different parents (page and container)
697
+ editor.select(selectAllIds.pageShape1, selectAllIds.containerChild1)
698
+
699
+ const initialSelectedIds = editor.getSelectedShapeIds()
700
+
701
+ // Spy on setSelectedShapes to verify it's not called
702
+ const setSelectedShapesSpy = vi.spyOn(editor, 'setSelectedShapes')
703
+
704
+ // Call selectAll
705
+ editor.selectAll()
706
+
707
+ // Selection should remain unchanged
708
+ expect(editor.getSelectedShapeIds()).toEqual(initialSelectedIds)
709
+
710
+ // setSelectedShapes should not have been called (the method returns early)
711
+ expect(setSelectedShapesSpy).not.toHaveBeenCalled()
712
+
713
+ setSelectedShapesSpy.mockRestore()
714
+ })
715
+
716
+ it('when shapes are selected that belong to different containers, no change/history entry should be made', () => {
717
+ // Select shapes from different containers
718
+ editor.select(selectAllIds.containerChild1, selectAllIds.container2Child1)
719
+
720
+ const initialSelectedIds = editor.getSelectedShapeIds()
721
+
722
+ // Spy on setSelectedShapes to verify it's not called
723
+ const setSelectedShapesSpy = vi.spyOn(editor, 'setSelectedShapes')
724
+
725
+ // Call selectAll
726
+ editor.selectAll()
727
+
728
+ // Selection should remain unchanged
729
+ expect(editor.getSelectedShapeIds()).toEqual(initialSelectedIds)
730
+
731
+ // setSelectedShapes should not have been called
732
+ expect(setSelectedShapesSpy).not.toHaveBeenCalled()
733
+
734
+ setSelectedShapesSpy.mockRestore()
735
+ })
736
+
737
+ it('should not select locked shapes', () => {
738
+ // Select a page-level shape
739
+ editor.select(selectAllIds.pageShape1)
740
+
741
+ // Call selectAll
742
+ editor.selectAll()
743
+
744
+ // Should select all page-level shapes except locked ones
745
+ const selectedIds = editor.getSelectedShapeIds()
746
+ expect(selectedIds).not.toContain(selectAllIds.lockedShape)
747
+ expect(selectedIds).toContain(selectAllIds.pageShape1)
748
+ expect(selectedIds).toContain(selectAllIds.pageShape2)
749
+ expect(selectedIds).toContain(selectAllIds.pageShape3)
750
+ expect(selectedIds).toContain(selectAllIds.container1)
751
+ expect(selectedIds).toContain(selectAllIds.container2)
752
+ })
753
+
754
+ it('should handle empty container by selecting all siblings at the same level', () => {
755
+ // Create an empty container
756
+ const emptyContainerId = createShapeId('emptyContainer')
757
+ editor.createShape({
758
+ id: emptyContainerId,
759
+ type: 'my-custom-shape',
760
+ x: 800,
761
+ y: 400,
762
+ props: { w: 100, h: 100 },
763
+ })
764
+
765
+ // Clear selection first
766
+ editor.selectNone()
767
+
768
+ // Select the empty container
769
+ editor.select(emptyContainerId)
770
+
771
+ // Call selectAll - since the empty container has no children, it should select all siblings (page-level shapes)
772
+ editor.selectAll()
773
+
774
+ // Should select all page-level shapes (including the empty container itself)
775
+ const selectedIds = editor.getSelectedShapeIds()
776
+ expect(Array.from(selectedIds).sort()).toEqual(
777
+ [
778
+ selectAllIds.pageShape1,
779
+ selectAllIds.pageShape2,
780
+ selectAllIds.pageShape3,
781
+ selectAllIds.container1,
782
+ selectAllIds.container2,
783
+ emptyContainerId,
784
+ ].sort()
785
+ )
786
+
787
+ // Should NOT include locked shapes or children/grandchildren
788
+ expect(selectedIds).not.toContain(selectAllIds.lockedShape)
789
+ expect(selectedIds).not.toContain(selectAllIds.containerChild1)
790
+ expect(selectedIds).not.toContain(selectAllIds.containerChild2)
791
+ expect(selectedIds).not.toContain(selectAllIds.containerChild3)
792
+ expect(selectedIds).not.toContain(selectAllIds.containerGrandchild1)
793
+ expect(selectedIds).not.toContain(selectAllIds.container2Child1)
794
+ expect(selectedIds).not.toContain(selectAllIds.container2Child2)
795
+ expect(selectedIds).not.toContain(selectAllIds.container2Grandchild1)
796
+ })
797
+
798
+ it('should work correctly when selecting all shapes of same parent type', () => {
799
+ // Select all container children
800
+ editor.select(
801
+ selectAllIds.containerChild1,
802
+ selectAllIds.containerChild2,
803
+ selectAllIds.containerChild3
804
+ )
805
+
806
+ // Call selectAll - should maintain the same selection since all children are already selected
807
+ editor.selectAll()
808
+
809
+ // Should still have all container children selected
810
+ const selectedIds = editor.getSelectedShapeIds()
811
+ expect(Array.from(selectedIds).sort()).toEqual(
812
+ [
813
+ selectAllIds.containerChild1,
814
+ selectAllIds.containerChild2,
815
+ selectAllIds.containerChild3,
816
+ ].sort()
817
+ )
818
+ })
819
+
820
+ it('should handle mixed selection levels gracefully by doing nothing', () => {
821
+ // Select a mix: page shape (parent=page), container (parent=page), and container child (parent=container1)
822
+ // These all have different parent IDs so selectAll should do nothing
823
+ editor.select(selectAllIds.pageShape1, selectAllIds.containerChild1)
824
+
825
+ const initialSelectedIds = Array.from(editor.getSelectedShapeIds())
826
+
827
+ // Spy on setSelectedShapes to verify it's not called
828
+ const setSelectedShapesSpy = vi.spyOn(editor, 'setSelectedShapes')
829
+
830
+ // Call selectAll
831
+ editor.selectAll()
832
+
833
+ // Selection should remain unchanged since shapes have different parents
834
+ expect(Array.from(editor.getSelectedShapeIds())).toEqual(initialSelectedIds)
835
+
836
+ // setSelectedShapes should not have been called
837
+ expect(setSelectedShapesSpy).not.toHaveBeenCalled()
838
+
839
+ setSelectedShapesSpy.mockRestore()
840
+ })
841
+ })
842
+
843
+ describe('putExternalContent', () => {
844
+ let mockHandler: any
845
+
846
+ beforeEach(() => {
847
+ mockHandler = vi.fn()
848
+ editor.registerExternalContentHandler('text', mockHandler)
849
+ })
850
+
851
+ it('calls external content handler when not readonly', async () => {
852
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(false)
853
+
854
+ const info = { type: 'text' as const, text: 'test-data' }
855
+ await editor.putExternalContent(info)
856
+
857
+ expect(mockHandler).toHaveBeenCalledWith(info)
858
+ })
859
+
860
+ it('does not call external content handler when readonly', async () => {
861
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(true)
862
+
863
+ const info = { type: 'text' as const, text: 'test-data' }
864
+ await editor.putExternalContent(info)
865
+
866
+ expect(mockHandler).not.toHaveBeenCalled()
867
+ })
868
+
869
+ it('calls external content handler when readonly but force is true', async () => {
870
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(true)
871
+
872
+ const info = { type: 'text' as const, text: 'test-data' }
873
+ await editor.putExternalContent(info, { force: true })
874
+
875
+ expect(mockHandler).toHaveBeenCalledWith(info)
876
+ })
877
+
878
+ it('calls external content handler when force is false and not readonly', async () => {
879
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(false)
880
+
881
+ const info = { type: 'text' as const, text: 'test-data' }
882
+ await editor.putExternalContent(info, { force: false })
883
+
884
+ expect(mockHandler).toHaveBeenCalledWith(info)
885
+ })
886
+ })
887
+
888
+ describe('replaceExternalContent', () => {
889
+ let mockHandler: any
890
+
891
+ beforeEach(() => {
892
+ mockHandler = vi.fn()
893
+ editor.registerExternalContentHandler('text', mockHandler)
894
+ })
895
+
896
+ it('calls external content handler when not readonly', async () => {
897
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(false)
898
+
899
+ const info = { type: 'text' as const, text: 'test-data' }
900
+ await editor.replaceExternalContent(info)
901
+
902
+ expect(mockHandler).toHaveBeenCalledWith(info)
903
+ })
904
+
905
+ it('does not call external content handler when readonly', async () => {
906
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(true)
907
+
908
+ const info = { type: 'text' as const, text: 'test-data' }
909
+ await editor.replaceExternalContent(info)
910
+
911
+ expect(mockHandler).not.toHaveBeenCalled()
912
+ })
913
+
914
+ it('calls external content handler when readonly but force is true', async () => {
915
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(true)
916
+
917
+ const info = { type: 'text' as const, text: 'test-data' }
918
+ await editor.replaceExternalContent(info, { force: true })
919
+
920
+ expect(mockHandler).toHaveBeenCalledWith(info)
921
+ })
922
+
923
+ it('calls external content handler when force is false and not readonly', async () => {
924
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(false)
925
+
926
+ const info = { type: 'text' as const, text: 'test-data' }
927
+ await editor.replaceExternalContent(info, { force: false })
928
+
929
+ expect(mockHandler).toHaveBeenCalledWith(info)
930
+ })
931
+ })