@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
@@ -0,0 +1,874 @@
1
+ import {
2
+ Geometry2d,
3
+ RecordProps,
4
+ Rectangle2d,
5
+ ShapeUtil,
6
+ T,
7
+ TLBaseShape,
8
+ createShapeId,
9
+ } from '../..'
10
+ import { createTLStore } from '../config/createTLStore'
11
+ import { Editor } from '../editor/Editor'
12
+ import { Box } from '../primitives/Box'
13
+ import { getExportDefaultBounds } from './getSvgJsx'
14
+
15
+ declare module '@tldraw/tlschema' {
16
+ export interface GlobalShapePropsMap {
17
+ 'test-shape': ITestShape
18
+ }
19
+ }
20
+
21
+ type ITestShape = TLBaseShape<
22
+ 'test-shape',
23
+ {
24
+ w: number
25
+ h: number
26
+ x: number
27
+ y: number
28
+ isContainer?: boolean
29
+ }
30
+ >
31
+
32
+ class TestShape extends ShapeUtil<ITestShape> {
33
+ static override type = 'test-shape' as const
34
+ static override props: RecordProps<ITestShape> = {
35
+ w: T.number,
36
+ h: T.number,
37
+ x: T.number,
38
+ y: T.number,
39
+ isContainer: T.boolean.optional(),
40
+ }
41
+ getDefaultProps(): ITestShape['props'] {
42
+ return {
43
+ w: 100,
44
+ h: 100,
45
+ x: 0,
46
+ y: 0,
47
+ isContainer: false,
48
+ }
49
+ }
50
+ getGeometry(shape: ITestShape): Geometry2d {
51
+ return new Rectangle2d({
52
+ width: shape.props.w,
53
+ height: shape.props.h,
54
+ x: shape.props.x,
55
+ y: shape.props.y,
56
+ isFilled: false,
57
+ })
58
+ }
59
+
60
+ override isExportBoundsContainer(shape: ITestShape): boolean {
61
+ return shape.props.isContainer ?? false
62
+ }
63
+
64
+ indicator() {}
65
+ component() {}
66
+ }
67
+
68
+ let editor: Editor
69
+
70
+ beforeEach(() => {
71
+ editor = new Editor({
72
+ shapeUtils: [TestShape],
73
+ bindingUtils: [],
74
+ tools: [],
75
+ store: createTLStore({ shapeUtils: [TestShape], bindingUtils: [] }),
76
+ getContainer: () => document.body,
77
+ })
78
+ })
79
+
80
+ describe('getExportDefaultBounds', () => {
81
+ it('returns null when no rendering shapes provided', () => {
82
+ const result = getExportDefaultBounds(editor, [], 32, null)
83
+ expect(result).toBeNull()
84
+ })
85
+
86
+ it('returns bounds for single shape with padding', () => {
87
+ const shapeId = createShapeId('test1')
88
+ editor.createShape({
89
+ id: shapeId,
90
+ type: 'test-shape',
91
+ x: 10,
92
+ y: 20,
93
+ props: { w: 100, h: 80, x: 0, y: 0 },
94
+ })
95
+
96
+ const renderingShapes = editor.getUnorderedRenderingShapes(false)
97
+ const testShape = renderingShapes.find((s) => s.id === shapeId)!
98
+
99
+ const result = getExportDefaultBounds(editor, [testShape], 32, null)
100
+
101
+ expect(result).toBeInstanceOf(Box)
102
+ // Bounds should include 32px padding on all sides
103
+ expect(result?.x).toBe(10 - 32) // -22
104
+ expect(result?.y).toBe(20 - 32) // -12
105
+ expect(result?.w).toBe(100 + 64) // 164 (32px on each side)
106
+ expect(result?.h).toBe(80 + 64) // 144 (32px on each side)
107
+ })
108
+
109
+ it('returns union bounds for multiple shapes with padding', () => {
110
+ const shape1Id = createShapeId('test1')
111
+ const shape2Id = createShapeId('test2')
112
+
113
+ editor.createShape({
114
+ id: shape1Id,
115
+ type: 'test-shape',
116
+ x: 0,
117
+ y: 0,
118
+ props: { w: 50, h: 50, x: 0, y: 0 },
119
+ })
120
+
121
+ editor.createShape({
122
+ id: shape2Id,
123
+ type: 'test-shape',
124
+ x: 30,
125
+ y: 30,
126
+ props: { w: 60, h: 60, x: 0, y: 0 },
127
+ })
128
+
129
+ const renderingShapes = editor.getUnorderedRenderingShapes(false)
130
+ const testShapes = renderingShapes.filter((s) => [shape1Id, shape2Id].includes(s.id))
131
+
132
+ const result = getExportDefaultBounds(editor, testShapes, 32, null)
133
+
134
+ expect(result).toBeInstanceOf(Box)
135
+ // Raw bounds would be (0,0) to (90,90), with 32px padding on all sides
136
+ expect(result?.x).toBe(0 - 32) // -32
137
+ expect(result?.y).toBe(0 - 32) // -32
138
+ expect(result?.w).toBe(90 + 64) // 154
139
+ expect(result?.h).toBe(90 + 64) // 154
140
+ })
141
+
142
+ it('handles shapes with transforms correctly', () => {
143
+ const shapeId = createShapeId('test1')
144
+ editor.createShape({
145
+ id: shapeId,
146
+ type: 'test-shape',
147
+ x: 25,
148
+ y: 35,
149
+ props: { w: 50, h: 40, x: 0, y: 0 },
150
+ })
151
+
152
+ // Rotate the shape
153
+ editor.rotateShapesBy([shapeId], Math.PI / 4)
154
+
155
+ const renderingShapes = editor.getUnorderedRenderingShapes(false)
156
+ const testShape = renderingShapes.find((s) => s.id === shapeId)!
157
+
158
+ const result = getExportDefaultBounds(editor, [testShape], 32, null)
159
+
160
+ expect(result).toBeInstanceOf(Box)
161
+ // The rotated shape should have expanded bounds, plus padding
162
+ expect(result!.w).toBeGreaterThan(50 + 64)
163
+ expect(result!.h).toBeGreaterThan(40 + 64)
164
+ })
165
+
166
+ it('handles multiple overlapping shapes correctly', () => {
167
+ const shape1Id = createShapeId('test1')
168
+ const shape2Id = createShapeId('test2')
169
+ const shape3Id = createShapeId('test3')
170
+
171
+ // Create overlapping shapes
172
+ editor.createShape({
173
+ id: shape1Id,
174
+ type: 'test-shape',
175
+ x: 0,
176
+ y: 0,
177
+ props: { w: 40, h: 40, x: 0, y: 0 },
178
+ })
179
+
180
+ editor.createShape({
181
+ id: shape2Id,
182
+ type: 'test-shape',
183
+ x: 20,
184
+ y: 20,
185
+ props: { w: 40, h: 40, x: 0, y: 0 },
186
+ })
187
+
188
+ editor.createShape({
189
+ id: shape3Id,
190
+ type: 'test-shape',
191
+ x: 10,
192
+ y: 10,
193
+ props: { w: 20, h: 20, x: 0, y: 0 },
194
+ })
195
+
196
+ const renderingShapes = editor.getUnorderedRenderingShapes(false)
197
+ const testShapes = renderingShapes.filter((s) => [shape1Id, shape2Id, shape3Id].includes(s.id))
198
+
199
+ const result = getExportDefaultBounds(editor, testShapes, 32, null)
200
+
201
+ expect(result).toBeInstanceOf(Box)
202
+ // Raw bounds would be (0,0) to (60,60), with 32px padding on all sides
203
+ expect(result?.x).toBe(0 - 32) // -32
204
+ expect(result?.y).toBe(0 - 32) // -32
205
+ expect(result?.w).toBe(60 + 64) // 124 (32px on each side)
206
+ expect(result?.h).toBe(60 + 64) // 124 (32px on each side)
207
+ })
208
+
209
+ it('handles complex geometry with multiple shapes', () => {
210
+ const shape1Id = createShapeId('shape1')
211
+ const shape2Id = createShapeId('shape2')
212
+ const shape3Id = createShapeId('shape3')
213
+
214
+ // Create shapes with different positions and sizes
215
+ editor.createShape({
216
+ id: shape1Id,
217
+ type: 'test-shape',
218
+ x: 0,
219
+ y: 0,
220
+ props: { w: 50, h: 50, x: 0, y: 0 },
221
+ })
222
+
223
+ editor.createShape({
224
+ id: shape2Id,
225
+ type: 'test-shape',
226
+ x: 100,
227
+ y: 100,
228
+ props: { w: 60, h: 40, x: 0, y: 0 },
229
+ })
230
+
231
+ editor.createShape({
232
+ id: shape3Id,
233
+ type: 'test-shape',
234
+ x: 200,
235
+ y: 50,
236
+ props: { w: 40, h: 80, x: 0, y: 0 },
237
+ })
238
+
239
+ const renderingShapes = editor.getUnorderedRenderingShapes(false)
240
+ const testShapes = renderingShapes.filter((s) => [shape1Id, shape2Id, shape3Id].includes(s.id))
241
+
242
+ const result = getExportDefaultBounds(editor, testShapes, 32, null)
243
+
244
+ expect(result).toBeInstanceOf(Box)
245
+
246
+ // The bounds should encompass:
247
+ // - shape1: (0, 0) to (50, 50)
248
+ // - shape2: (100, 100) to (160, 140)
249
+ // - shape3: (200, 50) to (240, 130)
250
+ // Raw total bounds: (0, 0) to (240, 140), with 32px padding on all sides
251
+ expect(result!.x).toBe(0 - 32) // -32 (leftmost edge with padding)
252
+ expect(result!.y).toBe(0 - 32) // -32 (topmost edge with padding)
253
+ expect(result!.w).toBe(240 + 64) // 304 (width + 32px on each side)
254
+ expect(result!.h).toBe(140 + 64) // 204 (height + 32px on each side)
255
+ })
256
+
257
+ it('handles empty rendering shapes array after filtering', () => {
258
+ // Create a shape but don't include it in rendering shapes
259
+ const shapeId = createShapeId('test1')
260
+ editor.createShape({
261
+ id: shapeId,
262
+ type: 'test-shape',
263
+ x: 10,
264
+ y: 20,
265
+ props: { w: 100, h: 80, x: 0, y: 0 },
266
+ })
267
+
268
+ // Pass empty array to simulate filtered out shapes
269
+ const result = getExportDefaultBounds(editor, [], 32, null)
270
+
271
+ expect(result).toBeNull()
272
+ })
273
+
274
+ it('does not apply padding when exporting single frame shape', () => {
275
+ const shapeId = createShapeId('test1')
276
+ editor.createShape({
277
+ id: shapeId,
278
+ type: 'test-shape',
279
+ x: 10,
280
+ y: 20,
281
+ props: { w: 100, h: 80, x: 0, y: 0 },
282
+ })
283
+
284
+ const renderingShapes = editor.getUnorderedRenderingShapes(false)
285
+ const testShape = renderingShapes.find((s) => s.id === shapeId)!
286
+
287
+ // Pass the shape ID as singleFrameShapeId to simulate single frame export
288
+ const result = getExportDefaultBounds(editor, [testShape], 32, shapeId)
289
+
290
+ expect(result).toBeInstanceOf(Box)
291
+ // No padding should be applied
292
+ expect(result?.x).toBe(10)
293
+ expect(result?.y).toBe(20)
294
+ expect(result?.w).toBe(100)
295
+ expect(result?.h).toBe(80)
296
+ })
297
+
298
+ describe('isExportBoundsContainer behavior', () => {
299
+ it('applies normal padding when no container shapes exist', () => {
300
+ const shape1Id = createShapeId('shape1')
301
+ const shape2Id = createShapeId('shape2')
302
+
303
+ editor.createShape({
304
+ id: shape1Id,
305
+ type: 'test-shape',
306
+ x: 0,
307
+ y: 0,
308
+ props: { w: 50, h: 50, x: 0, y: 0, isContainer: false },
309
+ })
310
+
311
+ editor.createShape({
312
+ id: shape2Id,
313
+ type: 'test-shape',
314
+ x: 10,
315
+ y: 10,
316
+ props: { w: 30, h: 30, x: 0, y: 0, isContainer: false },
317
+ })
318
+
319
+ const renderingShapes = editor.getUnorderedRenderingShapes(false)
320
+ const testShapes = renderingShapes.filter((s) => [shape1Id, shape2Id].includes(s.id))
321
+
322
+ const result = getExportDefaultBounds(editor, testShapes, 32, null)
323
+
324
+ expect(result).toBeInstanceOf(Box)
325
+ // Raw bounds: (0,0) to (50,50), with padding
326
+ expect(result?.x).toBe(-32)
327
+ expect(result?.y).toBe(-32)
328
+ expect(result?.w).toBe(50 + 64) // 114
329
+ expect(result?.h).toBe(50 + 64) // 114
330
+ })
331
+
332
+ it('skips padding when container shape contains all other shapes', () => {
333
+ const containerId = createShapeId('container')
334
+ const shape1Id = createShapeId('shape1')
335
+ const shape2Id = createShapeId('shape2')
336
+
337
+ // Container shape that encompasses everything
338
+ editor.createShape({
339
+ id: containerId,
340
+ type: 'test-shape',
341
+ x: 0,
342
+ y: 0,
343
+ props: { w: 100, h: 100, x: 0, y: 0, isContainer: true },
344
+ })
345
+
346
+ // Smaller shapes inside the container
347
+ editor.createShape({
348
+ id: shape1Id,
349
+ type: 'test-shape',
350
+ x: 10,
351
+ y: 10,
352
+ props: { w: 20, h: 20, x: 0, y: 0, isContainer: false },
353
+ })
354
+
355
+ editor.createShape({
356
+ id: shape2Id,
357
+ type: 'test-shape',
358
+ x: 60,
359
+ y: 60,
360
+ props: { w: 30, h: 30, x: 0, y: 0, isContainer: false },
361
+ })
362
+
363
+ const renderingShapes = editor.getUnorderedRenderingShapes(false)
364
+ const testShapes = renderingShapes.filter((s) =>
365
+ [containerId, shape1Id, shape2Id].includes(s.id)
366
+ )
367
+
368
+ const result = getExportDefaultBounds(editor, testShapes, 32, null)
369
+
370
+ expect(result).toBeInstanceOf(Box)
371
+ // Should use container bounds without padding: (0,0) to (100,100)
372
+ expect(result?.x).toBe(0)
373
+ expect(result?.y).toBe(0)
374
+ expect(result?.w).toBe(100)
375
+ expect(result?.h).toBe(100)
376
+ })
377
+
378
+ it('applies padding when container does not contain all shapes', () => {
379
+ const containerId = createShapeId('container')
380
+ const insideShapeId = createShapeId('inside')
381
+ const outsideShapeId = createShapeId('outside')
382
+
383
+ // Small container
384
+ editor.createShape({
385
+ id: containerId,
386
+ type: 'test-shape',
387
+ x: 0,
388
+ y: 0,
389
+ props: { w: 50, h: 50, x: 0, y: 0, isContainer: true },
390
+ })
391
+
392
+ // Shape inside container
393
+ editor.createShape({
394
+ id: insideShapeId,
395
+ type: 'test-shape',
396
+ x: 10,
397
+ y: 10,
398
+ props: { w: 20, h: 20, x: 0, y: 0, isContainer: false },
399
+ })
400
+
401
+ // Shape outside container bounds
402
+ editor.createShape({
403
+ id: outsideShapeId,
404
+ type: 'test-shape',
405
+ x: 70,
406
+ y: 70,
407
+ props: { w: 30, h: 30, x: 0, y: 0, isContainer: false },
408
+ })
409
+
410
+ const renderingShapes = editor.getUnorderedRenderingShapes(false)
411
+ const testShapes = renderingShapes.filter((s) =>
412
+ [containerId, insideShapeId, outsideShapeId].includes(s.id)
413
+ )
414
+
415
+ const result = getExportDefaultBounds(editor, testShapes, 32, null)
416
+
417
+ expect(result).toBeInstanceOf(Box)
418
+ // Total bounds: (0,0) to (100,100), with padding applied
419
+ expect(result?.x).toBe(-32)
420
+ expect(result?.y).toBe(-32)
421
+ expect(result?.w).toBe(100 + 64) // 164
422
+ expect(result?.h).toBe(100 + 64) // 164
423
+ })
424
+
425
+ it('works with multiple containers where one contains all', () => {
426
+ const container1Id = createShapeId('container1')
427
+ const container2Id = createShapeId('container2')
428
+ const shapeId = createShapeId('shape1')
429
+
430
+ // Small container
431
+ editor.createShape({
432
+ id: container1Id,
433
+ type: 'test-shape',
434
+ x: 10,
435
+ y: 10,
436
+ props: { w: 40, h: 40, x: 0, y: 0, isContainer: true },
437
+ })
438
+
439
+ // Large container that contains everything
440
+ editor.createShape({
441
+ id: container2Id,
442
+ type: 'test-shape',
443
+ x: 0,
444
+ y: 0,
445
+ props: { w: 100, h: 100, x: 0, y: 0, isContainer: true },
446
+ })
447
+
448
+ // Shape inside both containers
449
+ editor.createShape({
450
+ id: shapeId,
451
+ type: 'test-shape',
452
+ x: 20,
453
+ y: 20,
454
+ props: { w: 10, h: 10, x: 0, y: 0, isContainer: false },
455
+ })
456
+
457
+ const renderingShapes = editor.getUnorderedRenderingShapes(false)
458
+ const testShapes = renderingShapes.filter((s) =>
459
+ [container1Id, container2Id, shapeId].includes(s.id)
460
+ )
461
+
462
+ const result = getExportDefaultBounds(editor, testShapes, 32, null)
463
+
464
+ expect(result).toBeInstanceOf(Box)
465
+ // Should use the large container's bounds without padding
466
+ expect(result?.x).toBe(0)
467
+ expect(result?.y).toBe(0)
468
+ expect(result?.w).toBe(100)
469
+ expect(result?.h).toBe(100)
470
+ })
471
+
472
+ it('container behavior is overridden by single frame shape', () => {
473
+ const containerId = createShapeId('container')
474
+ const shapeId = createShapeId('shape1')
475
+
476
+ // Container that would normally prevent padding
477
+ editor.createShape({
478
+ id: containerId,
479
+ type: 'test-shape',
480
+ x: 0,
481
+ y: 0,
482
+ props: { w: 100, h: 100, x: 0, y: 0, isContainer: true },
483
+ })
484
+
485
+ // Shape inside container
486
+ editor.createShape({
487
+ id: shapeId,
488
+ type: 'test-shape',
489
+ x: 10,
490
+ y: 10,
491
+ props: { w: 20, h: 20, x: 0, y: 0, isContainer: false },
492
+ })
493
+
494
+ const renderingShapes = editor.getUnorderedRenderingShapes(false)
495
+ const testShapes = renderingShapes.filter((s) => [containerId, shapeId].includes(s.id))
496
+
497
+ // Single frame shape logic takes precedence over container logic
498
+ const result = getExportDefaultBounds(editor, testShapes, 32, containerId)
499
+
500
+ expect(result).toBeInstanceOf(Box)
501
+ // Should use total bounds without padding (single frame overrides container)
502
+ expect(result?.x).toBe(0)
503
+ expect(result?.y).toBe(0)
504
+ expect(result?.w).toBe(100)
505
+ expect(result?.h).toBe(100)
506
+ })
507
+
508
+ it('handles containers with inner shapes correctly', () => {
509
+ const containerId = createShapeId('container')
510
+ const innerShapeId = createShapeId('inner')
511
+
512
+ // Container shape large enough to contain inner shape
513
+ editor.createShape({
514
+ id: containerId,
515
+ type: 'test-shape',
516
+ x: 0,
517
+ y: 0,
518
+ props: { w: 200, h: 120, x: 0, y: 0, isContainer: true },
519
+ })
520
+
521
+ // Shape inside container bounds
522
+ editor.createShape({
523
+ id: innerShapeId,
524
+ type: 'test-shape',
525
+ x: 50,
526
+ y: 20,
527
+ props: { w: 100, h: 60, x: 0, y: 0, isContainer: false },
528
+ })
529
+
530
+ const renderingShapes = editor.getUnorderedRenderingShapes(false)
531
+ const testShapes = renderingShapes.filter((s) => [containerId, innerShapeId].includes(s.id))
532
+
533
+ const result = getExportDefaultBounds(editor, testShapes, 32, null)
534
+
535
+ expect(result).toBeInstanceOf(Box)
536
+ // Container (0,0,200,120) should contain inner shape bounds,
537
+ // so no padding should be applied
538
+ expect(result?.x).toBe(0)
539
+ expect(result?.y).toBe(0)
540
+ expect(result?.w).toBe(200)
541
+ expect(result?.h).toBe(120)
542
+ })
543
+
544
+ it('handles order sensitivity - container processed first', () => {
545
+ const containerId = createShapeId('container')
546
+ const shapeId = createShapeId('shape')
547
+
548
+ // Create container first (will be processed first due to creation order)
549
+ editor.createShape({
550
+ id: containerId,
551
+ type: 'test-shape',
552
+ x: 0,
553
+ y: 0,
554
+ props: { w: 100, h: 100, x: 0, y: 0, isContainer: true },
555
+ })
556
+
557
+ // Create regular shape second
558
+ editor.createShape({
559
+ id: shapeId,
560
+ type: 'test-shape',
561
+ x: 20,
562
+ y: 20,
563
+ props: { w: 30, h: 30, x: 0, y: 0, isContainer: false },
564
+ })
565
+
566
+ const renderingShapes = editor.getUnorderedRenderingShapes(false)
567
+ const testShapes = renderingShapes.filter((s) => [containerId, shapeId].includes(s.id))
568
+
569
+ const result = getExportDefaultBounds(editor, testShapes, 32, null)
570
+
571
+ expect(result).toBeInstanceOf(Box)
572
+ // Container should contain regular shape, no padding applied
573
+ expect(result?.x).toBe(0)
574
+ expect(result?.y).toBe(0)
575
+ expect(result?.w).toBe(100)
576
+ expect(result?.h).toBe(100)
577
+ })
578
+
579
+ it('handles order sensitivity - regular shape processed first', () => {
580
+ const shapeId = createShapeId('shape')
581
+ const containerId = createShapeId('container')
582
+
583
+ // Create regular shape first (will be processed first due to creation order)
584
+ editor.createShape({
585
+ id: shapeId,
586
+ type: 'test-shape',
587
+ x: 20,
588
+ y: 20,
589
+ props: { w: 30, h: 30, x: 0, y: 0, isContainer: false },
590
+ })
591
+
592
+ // Create container second
593
+ editor.createShape({
594
+ id: containerId,
595
+ type: 'test-shape',
596
+ x: 0,
597
+ y: 0,
598
+ props: { w: 100, h: 100, x: 0, y: 0, isContainer: true },
599
+ })
600
+
601
+ const renderingShapes = editor.getUnorderedRenderingShapes(false)
602
+ const testShapes = renderingShapes.filter((s) => [shapeId, containerId].includes(s.id))
603
+
604
+ const result = getExportDefaultBounds(editor, testShapes, 32, null)
605
+
606
+ expect(result).toBeInstanceOf(Box)
607
+ // Container should still contain regular shape, no padding applied
608
+ expect(result?.x).toBe(0)
609
+ expect(result?.y).toBe(0)
610
+ expect(result?.w).toBe(100)
611
+ expect(result?.h).toBe(100)
612
+ })
613
+
614
+ it('multiple containers - only one that contains all others skips padding', () => {
615
+ const smallContainerId = createShapeId('smallContainer')
616
+ const largeContainerId = createShapeId('largeContainer')
617
+ const shapeId = createShapeId('shape')
618
+
619
+ // Small container
620
+ editor.createShape({
621
+ id: smallContainerId,
622
+ type: 'test-shape',
623
+ x: 10,
624
+ y: 10,
625
+ props: { w: 30, h: 30, x: 0, y: 0, isContainer: true },
626
+ })
627
+
628
+ // Large container that contains the small container AND the regular shape
629
+ editor.createShape({
630
+ id: largeContainerId,
631
+ type: 'test-shape',
632
+ x: 0,
633
+ y: 0,
634
+ props: { w: 100, h: 100, x: 0, y: 0, isContainer: true },
635
+ })
636
+
637
+ // Regular shape inside both containers
638
+ editor.createShape({
639
+ id: shapeId,
640
+ type: 'test-shape',
641
+ x: 15,
642
+ y: 15,
643
+ props: { w: 10, h: 10, x: 0, y: 0, isContainer: false },
644
+ })
645
+
646
+ const renderingShapes = editor.getUnorderedRenderingShapes(false)
647
+ const testShapes = renderingShapes.filter((s) =>
648
+ [smallContainerId, largeContainerId, shapeId].includes(s.id)
649
+ )
650
+
651
+ const result = getExportDefaultBounds(editor, testShapes, 32, null)
652
+
653
+ expect(result).toBeInstanceOf(Box)
654
+ // Large container contains everything (including small container), no padding
655
+ expect(result?.x).toBe(0)
656
+ expect(result?.y).toBe(0)
657
+ expect(result?.w).toBe(100)
658
+ expect(result?.h).toBe(100)
659
+ })
660
+
661
+ it('multiple containers - none contains all others, padding applied', () => {
662
+ const container1Id = createShapeId('container1')
663
+ const container2Id = createShapeId('container2')
664
+ const shape1Id = createShapeId('shape1')
665
+ const shape2Id = createShapeId('shape2')
666
+
667
+ // Container 1 contains shape1 but not container2 or shape2
668
+ editor.createShape({
669
+ id: container1Id,
670
+ type: 'test-shape',
671
+ x: 0,
672
+ y: 0,
673
+ props: { w: 40, h: 40, x: 0, y: 0, isContainer: true },
674
+ })
675
+
676
+ // Container 2 contains shape2 but not container1 or shape1
677
+ editor.createShape({
678
+ id: container2Id,
679
+ type: 'test-shape',
680
+ x: 60,
681
+ y: 60,
682
+ props: { w: 40, h: 40, x: 0, y: 0, isContainer: true },
683
+ })
684
+
685
+ // Shape inside container1
686
+ editor.createShape({
687
+ id: shape1Id,
688
+ type: 'test-shape',
689
+ x: 10,
690
+ y: 10,
691
+ props: { w: 20, h: 20, x: 0, y: 0, isContainer: false },
692
+ })
693
+
694
+ // Shape inside container2
695
+ editor.createShape({
696
+ id: shape2Id,
697
+ type: 'test-shape',
698
+ x: 70,
699
+ y: 70,
700
+ props: { w: 20, h: 20, x: 0, y: 0, isContainer: false },
701
+ })
702
+
703
+ const renderingShapes = editor.getUnorderedRenderingShapes(false)
704
+ const testShapes = renderingShapes.filter((s) =>
705
+ [container1Id, container2Id, shape1Id, shape2Id].includes(s.id)
706
+ )
707
+
708
+ const result = getExportDefaultBounds(editor, testShapes, 32, null)
709
+
710
+ expect(result).toBeInstanceOf(Box)
711
+ // No single container contains all others, padding should be applied
712
+ // Total bounds: (0,0) to (100,100), with padding
713
+ expect(result?.x).toBe(-32)
714
+ expect(result?.y).toBe(-32)
715
+ expect(result?.w).toBe(100 + 64) // 164
716
+ expect(result?.h).toBe(100 + 64) // 164
717
+ })
718
+
719
+ it('container covers most but not all shapes - padding applied', () => {
720
+ const containerId = createShapeId('container')
721
+ const insideShapeId = createShapeId('inside')
722
+ const partiallyOutsideId = createShapeId('partiallyOutside')
723
+
724
+ // Container
725
+ editor.createShape({
726
+ id: containerId,
727
+ type: 'test-shape',
728
+ x: 0,
729
+ y: 0,
730
+ props: { w: 80, h: 80, x: 0, y: 0, isContainer: true },
731
+ })
732
+
733
+ // Shape fully inside container
734
+ editor.createShape({
735
+ id: insideShapeId,
736
+ type: 'test-shape',
737
+ x: 20,
738
+ y: 20,
739
+ props: { w: 20, h: 20, x: 0, y: 0, isContainer: false },
740
+ })
741
+
742
+ // Shape that partially extends outside container
743
+ editor.createShape({
744
+ id: partiallyOutsideId,
745
+ type: 'test-shape',
746
+ x: 70,
747
+ y: 70,
748
+ props: { w: 20, h: 20, x: 0, y: 0, isContainer: false },
749
+ })
750
+
751
+ const renderingShapes = editor.getUnorderedRenderingShapes(false)
752
+ const testShapes = renderingShapes.filter((s) =>
753
+ [containerId, insideShapeId, partiallyOutsideId].includes(s.id)
754
+ )
755
+
756
+ const result = getExportDefaultBounds(editor, testShapes, 32, null)
757
+
758
+ expect(result).toBeInstanceOf(Box)
759
+ // Container doesn't contain all shapes, padding applied
760
+ // Total bounds: (0,0) to (90,90), with padding
761
+ expect(result?.x).toBe(-32)
762
+ expect(result?.y).toBe(-32)
763
+ expect(result?.w).toBe(90 + 64) // 154
764
+ expect(result?.h).toBe(90 + 64) // 154
765
+ })
766
+
767
+ it('nested containers - inner container processed first', () => {
768
+ const outerContainerId = createShapeId('outerContainer')
769
+ const innerContainerId = createShapeId('innerContainer')
770
+ const shapeId = createShapeId('shape')
771
+
772
+ // Inner container (created first)
773
+ editor.createShape({
774
+ id: innerContainerId,
775
+ type: 'test-shape',
776
+ x: 20,
777
+ y: 20,
778
+ props: { w: 40, h: 40, x: 0, y: 0, isContainer: true },
779
+ })
780
+
781
+ // Outer container that contains inner container
782
+ editor.createShape({
783
+ id: outerContainerId,
784
+ type: 'test-shape',
785
+ x: 0,
786
+ y: 0,
787
+ props: { w: 100, h: 100, x: 0, y: 0, isContainer: true },
788
+ })
789
+
790
+ // Shape inside inner container
791
+ editor.createShape({
792
+ id: shapeId,
793
+ type: 'test-shape',
794
+ x: 30,
795
+ y: 30,
796
+ props: { w: 20, h: 20, x: 0, y: 0, isContainer: false },
797
+ })
798
+
799
+ const renderingShapes = editor.getUnorderedRenderingShapes(false)
800
+ const testShapes = renderingShapes.filter((s) =>
801
+ [innerContainerId, outerContainerId, shapeId].includes(s.id)
802
+ )
803
+
804
+ const result = getExportDefaultBounds(editor, testShapes, 32, null)
805
+
806
+ expect(result).toBeInstanceOf(Box)
807
+ // Outer container contains everything, should use outer bounds without padding
808
+ expect(result?.x).toBe(0)
809
+ expect(result?.y).toBe(0)
810
+ expect(result?.w).toBe(100)
811
+ expect(result?.h).toBe(100)
812
+ })
813
+
814
+ it('container-only shapes should not skip padding', () => {
815
+ const container1Id = createShapeId('container1')
816
+ const container2Id = createShapeId('container2')
817
+
818
+ // Two containers, neither containing the other completely
819
+ editor.createShape({
820
+ id: container1Id,
821
+ type: 'test-shape',
822
+ x: 0,
823
+ y: 0,
824
+ props: { w: 50, h: 50, x: 0, y: 0, isContainer: true },
825
+ })
826
+
827
+ editor.createShape({
828
+ id: container2Id,
829
+ type: 'test-shape',
830
+ x: 30,
831
+ y: 30,
832
+ props: { w: 50, h: 50, x: 0, y: 0, isContainer: true },
833
+ })
834
+
835
+ const renderingShapes = editor.getUnorderedRenderingShapes(false)
836
+ const testShapes = renderingShapes.filter((s) => [container1Id, container2Id].includes(s.id))
837
+
838
+ const result = getExportDefaultBounds(editor, testShapes, 32, null)
839
+
840
+ expect(result).toBeInstanceOf(Box)
841
+ // Neither container fully contains the other, padding should be applied
842
+ // Total bounds: (0,0) to (80,80), with padding
843
+ expect(result?.x).toBe(-32)
844
+ expect(result?.y).toBe(-32)
845
+ expect(result?.w).toBe(80 + 64) // 144
846
+ expect(result?.h).toBe(80 + 64) // 144
847
+ })
848
+
849
+ it('single container with only itself skips padding', () => {
850
+ const containerId = createShapeId('container')
851
+
852
+ // Single container shape
853
+ editor.createShape({
854
+ id: containerId,
855
+ type: 'test-shape',
856
+ x: 10,
857
+ y: 20,
858
+ props: { w: 100, h: 80, x: 0, y: 0, isContainer: true },
859
+ })
860
+
861
+ const renderingShapes = editor.getUnorderedRenderingShapes(false)
862
+ const testShapes = renderingShapes.filter((s) => s.id === containerId)
863
+
864
+ const result = getExportDefaultBounds(editor, testShapes, 32, null)
865
+
866
+ expect(result).toBeInstanceOf(Box)
867
+ // Single container should skip padding (it trivially contains "all other shapes")
868
+ expect(result?.x).toBe(10)
869
+ expect(result?.y).toBe(20)
870
+ expect(result?.w).toBe(100)
871
+ expect(result?.h).toBe(80)
872
+ })
873
+ })
874
+ })