@tldraw/editor 5.4.0-next.ef99ab47e5ce → 5.5.0-canary.078a77321dc3

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 (288) hide show
  1. package/dist-cjs/index.d.ts +119 -49
  2. package/dist-cjs/index.js +4 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/TldrawEditor.js +7 -2
  5. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  6. package/dist-cjs/lib/components/MenuClickCapture.js +6 -8
  7. package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
  8. package/dist-cjs/lib/components/default-components/CanvasOverlays.js +1 -3
  9. package/dist-cjs/lib/components/default-components/CanvasOverlays.js.map +2 -2
  10. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +1 -1
  11. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +1 -1
  12. package/dist-cjs/lib/config/TLUserPreferences.js +6 -2
  13. package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
  14. package/dist-cjs/lib/config/createTLStore.js +1 -1
  15. package/dist-cjs/lib/config/createTLStore.js.map +2 -2
  16. package/dist-cjs/lib/constants.js +2 -0
  17. package/dist-cjs/lib/constants.js.map +2 -2
  18. package/dist-cjs/lib/editor/Editor.js +190 -300
  19. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  20. package/dist-cjs/lib/editor/derivations/shapeIdsInCurrentPage.js +7 -3
  21. package/dist-cjs/lib/editor/derivations/shapeIdsInCurrentPage.js.map +2 -2
  22. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js +3 -3
  23. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +2 -2
  24. package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js +8 -8
  25. package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js.map +2 -2
  26. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js +10 -6
  27. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js.map +2 -2
  28. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js +2 -1
  29. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +2 -2
  30. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js +79 -6
  31. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js.map +2 -2
  32. package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js +7 -0
  33. package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js.map +2 -2
  34. package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js +0 -2
  35. package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js.map +2 -2
  36. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +1 -2
  37. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
  38. package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js +7 -3
  39. package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js.map +2 -2
  40. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js +6 -3
  41. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +2 -2
  42. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +12 -1
  43. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  44. package/dist-cjs/lib/editor/overlays/OverlayManager.js +3 -7
  45. package/dist-cjs/lib/editor/overlays/OverlayManager.js.map +2 -2
  46. package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js +0 -1
  47. package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js.map +2 -2
  48. package/dist-cjs/lib/editor/shapes/BaseFrameLikeShapeUtil.js +3 -1
  49. package/dist-cjs/lib/editor/shapes/BaseFrameLikeShapeUtil.js.map +2 -2
  50. package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js +1 -0
  51. package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js.map +2 -2
  52. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +1 -1
  53. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +2 -2
  54. package/dist-cjs/lib/editor/tools/StateNode.js +3 -2
  55. package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
  56. package/dist-cjs/lib/globals/editors.js +57 -0
  57. package/dist-cjs/lib/globals/editors.js.map +7 -0
  58. package/dist-cjs/lib/globals/menus.js +9 -4
  59. package/dist-cjs/lib/globals/menus.js.map +2 -2
  60. package/dist-cjs/lib/hooks/useCanvasEvents.js +2 -0
  61. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  62. package/dist-cjs/lib/hooks/useDocumentEvents.js +39 -9
  63. package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
  64. package/dist-cjs/lib/hooks/useLocalStore.js +10 -6
  65. package/dist-cjs/lib/hooks/useLocalStore.js.map +2 -2
  66. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js +1 -0
  67. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +2 -2
  68. package/dist-cjs/lib/hooks/usePeerIds.js +2 -1
  69. package/dist-cjs/lib/hooks/usePeerIds.js.map +2 -2
  70. package/dist-cjs/lib/license/LicenseManager.js +9 -2
  71. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  72. package/dist-cjs/lib/license/LicenseProvider.js +16 -3
  73. package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
  74. package/dist-cjs/lib/license/useLicenseManagerState.js +5 -4
  75. package/dist-cjs/lib/license/useLicenseManagerState.js.map +2 -2
  76. package/dist-cjs/lib/primitives/Box.js +2 -1
  77. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  78. package/dist-cjs/lib/primitives/Vec.js +4 -3
  79. package/dist-cjs/lib/primitives/Vec.js.map +2 -2
  80. package/dist-cjs/lib/primitives/geometry/Arc2d.js +1 -1
  81. package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
  82. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +1 -1
  83. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
  84. package/dist-cjs/lib/primitives/geometry/Edge2d.js +1 -1
  85. package/dist-cjs/lib/primitives/geometry/Edge2d.js.map +2 -2
  86. package/dist-cjs/lib/primitives/geometry/Group2d.js +71 -26
  87. package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
  88. package/dist-cjs/lib/primitives/geometry/Point2d.js +1 -1
  89. package/dist-cjs/lib/primitives/geometry/Point2d.js.map +2 -2
  90. package/dist-cjs/lib/primitives/utils.js +2 -1
  91. package/dist-cjs/lib/primitives/utils.js.map +2 -2
  92. package/dist-cjs/lib/utils/deepLinks.js +5 -1
  93. package/dist-cjs/lib/utils/deepLinks.js.map +2 -2
  94. package/dist-cjs/lib/utils/normalizeWheel.js +2 -0
  95. package/dist-cjs/lib/utils/normalizeWheel.js.map +2 -2
  96. package/dist-cjs/lib/utils/rotation.js +1 -9
  97. package/dist-cjs/lib/utils/rotation.js.map +2 -2
  98. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +17 -0
  99. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +2 -2
  100. package/dist-cjs/lib/utils/sync/alerts.js +1 -1
  101. package/dist-cjs/lib/utils/sync/alerts.js.map +1 -1
  102. package/dist-cjs/version.js +3 -3
  103. package/dist-cjs/version.js.map +1 -1
  104. package/dist-esm/index.d.mts +119 -49
  105. package/dist-esm/index.mjs +9 -2
  106. package/dist-esm/index.mjs.map +2 -2
  107. package/dist-esm/lib/TldrawEditor.mjs +9 -4
  108. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  109. package/dist-esm/lib/components/MenuClickCapture.mjs +6 -8
  110. package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
  111. package/dist-esm/lib/components/default-components/CanvasOverlays.mjs +1 -3
  112. package/dist-esm/lib/components/default-components/CanvasOverlays.mjs.map +2 -2
  113. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +1 -1
  114. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +1 -1
  115. package/dist-esm/lib/config/TLUserPreferences.mjs +6 -2
  116. package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
  117. package/dist-esm/lib/config/createTLStore.mjs +1 -1
  118. package/dist-esm/lib/config/createTLStore.mjs.map +2 -2
  119. package/dist-esm/lib/constants.mjs +2 -0
  120. package/dist-esm/lib/constants.mjs.map +2 -2
  121. package/dist-esm/lib/editor/Editor.mjs +199 -301
  122. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  123. package/dist-esm/lib/editor/derivations/shapeIdsInCurrentPage.mjs +7 -3
  124. package/dist-esm/lib/editor/derivations/shapeIdsInCurrentPage.mjs.map +2 -2
  125. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs +3 -3
  126. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +2 -2
  127. package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs +9 -9
  128. package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs.map +2 -2
  129. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs +10 -6
  130. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs.map +2 -2
  131. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs +2 -1
  132. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +2 -2
  133. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs +81 -8
  134. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs.map +2 -2
  135. package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs +7 -0
  136. package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs.map +2 -2
  137. package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs +0 -2
  138. package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs.map +2 -2
  139. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +2 -3
  140. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
  141. package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs +7 -3
  142. package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs.map +2 -2
  143. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs +6 -3
  144. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +2 -2
  145. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +12 -1
  146. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  147. package/dist-esm/lib/editor/overlays/OverlayManager.mjs +3 -7
  148. package/dist-esm/lib/editor/overlays/OverlayManager.mjs.map +2 -2
  149. package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs +0 -1
  150. package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs.map +2 -2
  151. package/dist-esm/lib/editor/shapes/BaseFrameLikeShapeUtil.mjs +3 -1
  152. package/dist-esm/lib/editor/shapes/BaseFrameLikeShapeUtil.mjs.map +2 -2
  153. package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs +1 -0
  154. package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs.map +2 -2
  155. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +1 -1
  156. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +2 -2
  157. package/dist-esm/lib/editor/tools/StateNode.mjs +3 -2
  158. package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
  159. package/dist-esm/lib/globals/editors.mjs +37 -0
  160. package/dist-esm/lib/globals/editors.mjs.map +7 -0
  161. package/dist-esm/lib/globals/menus.mjs +9 -4
  162. package/dist-esm/lib/globals/menus.mjs.map +2 -2
  163. package/dist-esm/lib/hooks/useCanvasEvents.mjs +2 -0
  164. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  165. package/dist-esm/lib/hooks/useDocumentEvents.mjs +40 -10
  166. package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
  167. package/dist-esm/lib/hooks/useLocalStore.mjs +11 -7
  168. package/dist-esm/lib/hooks/useLocalStore.mjs.map +2 -2
  169. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs +1 -0
  170. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +2 -2
  171. package/dist-esm/lib/hooks/usePeerIds.mjs +2 -1
  172. package/dist-esm/lib/hooks/usePeerIds.mjs.map +2 -2
  173. package/dist-esm/lib/license/LicenseManager.mjs +9 -2
  174. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  175. package/dist-esm/lib/license/LicenseProvider.mjs +16 -3
  176. package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
  177. package/dist-esm/lib/license/useLicenseManagerState.mjs +5 -4
  178. package/dist-esm/lib/license/useLicenseManagerState.mjs.map +2 -2
  179. package/dist-esm/lib/primitives/Box.mjs +2 -1
  180. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  181. package/dist-esm/lib/primitives/Vec.mjs +4 -3
  182. package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
  183. package/dist-esm/lib/primitives/geometry/Arc2d.mjs +1 -1
  184. package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
  185. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +1 -1
  186. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
  187. package/dist-esm/lib/primitives/geometry/Edge2d.mjs +1 -1
  188. package/dist-esm/lib/primitives/geometry/Edge2d.mjs.map +2 -2
  189. package/dist-esm/lib/primitives/geometry/Group2d.mjs +75 -27
  190. package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
  191. package/dist-esm/lib/primitives/geometry/Point2d.mjs +1 -1
  192. package/dist-esm/lib/primitives/geometry/Point2d.mjs.map +2 -2
  193. package/dist-esm/lib/primitives/utils.mjs +2 -1
  194. package/dist-esm/lib/primitives/utils.mjs.map +2 -2
  195. package/dist-esm/lib/utils/deepLinks.mjs +5 -1
  196. package/dist-esm/lib/utils/deepLinks.mjs.map +2 -2
  197. package/dist-esm/lib/utils/normalizeWheel.mjs +2 -0
  198. package/dist-esm/lib/utils/normalizeWheel.mjs.map +2 -2
  199. package/dist-esm/lib/utils/rotation.mjs +1 -9
  200. package/dist-esm/lib/utils/rotation.mjs.map +2 -2
  201. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +17 -0
  202. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
  203. package/dist-esm/lib/utils/sync/alerts.mjs +1 -1
  204. package/dist-esm/lib/utils/sync/alerts.mjs.map +1 -1
  205. package/dist-esm/version.mjs +3 -3
  206. package/dist-esm/version.mjs.map +1 -1
  207. package/package.json +13 -7
  208. package/src/index.ts +6 -1
  209. package/src/lib/TldrawEditor.tsx +16 -4
  210. package/src/lib/components/MenuClickCapture.tsx +19 -18
  211. package/src/lib/components/default-components/CanvasOverlays.tsx +1 -3
  212. package/src/lib/components/default-components/DefaultErrorFallback.tsx +1 -1
  213. package/src/lib/config/TLUserPreferences.test.ts +18 -1
  214. package/src/lib/config/TLUserPreferences.ts +8 -4
  215. package/src/lib/config/createTLStore.ts +1 -1
  216. package/src/lib/constants.ts +8 -0
  217. package/src/lib/editor/Editor.ts +285 -392
  218. package/src/lib/editor/derivations/shapeIdsInCurrentPage.ts +7 -3
  219. package/src/lib/editor/managers/ClickManager/ClickManager.ts +5 -3
  220. package/src/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.ts +9 -9
  221. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +35 -4
  222. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.ts +13 -7
  223. package/src/lib/editor/managers/FontManager/FontManager.ts +2 -1
  224. package/src/lib/editor/managers/InputsManager/InputsManager.test.ts +199 -3
  225. package/src/lib/editor/managers/InputsManager/InputsManager.ts +128 -9
  226. package/src/lib/editor/managers/ScribbleManager/ScribbleManager.ts +12 -1
  227. package/src/lib/editor/managers/SnapManager/BoundsSnaps.ts +0 -2
  228. package/src/lib/editor/managers/TextManager/TextManager.ts +2 -6
  229. package/src/lib/editor/managers/ThemeManager/ThemeManager.test.ts +53 -0
  230. package/src/lib/editor/managers/ThemeManager/ThemeManager.ts +9 -3
  231. package/src/lib/editor/managers/TickManager/TickManager.test.ts +13 -0
  232. package/src/lib/editor/managers/TickManager/TickManager.ts +9 -3
  233. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +64 -3
  234. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +18 -1
  235. package/src/lib/editor/overlays/OverlayManager.ts +3 -7
  236. package/src/lib/editor/overlays/strokeShapeIndicators.ts +0 -1
  237. package/src/lib/editor/shapes/BaseFrameLikeShapeUtil.tsx +5 -1
  238. package/src/lib/editor/shapes/shared/getPerfectDashProps.ts +3 -0
  239. package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +4 -1
  240. package/src/lib/editor/tools/StateNode.test.ts +64 -0
  241. package/src/lib/editor/tools/StateNode.ts +12 -2
  242. package/src/lib/globals/editors.test.ts +76 -0
  243. package/src/lib/globals/editors.ts +53 -0
  244. package/src/lib/globals/menus.test.ts +33 -0
  245. package/src/lib/globals/menus.ts +11 -4
  246. package/src/lib/hooks/useCanvasEvents.ts +2 -0
  247. package/src/lib/hooks/useDocumentEvents.ts +60 -16
  248. package/src/lib/hooks/useLocalStore.ts +16 -8
  249. package/src/lib/hooks/usePassThroughWheelEvents.ts +3 -0
  250. package/src/lib/hooks/usePeerIds.ts +2 -1
  251. package/src/lib/license/LicenseManager.test.ts +160 -0
  252. package/src/lib/license/LicenseManager.ts +13 -4
  253. package/src/lib/license/LicenseProvider.tsx +24 -3
  254. package/src/lib/license/useLicenseManagerState.ts +8 -6
  255. package/src/lib/primitives/Box.test.ts +9 -1
  256. package/src/lib/primitives/Box.ts +3 -1
  257. package/src/lib/primitives/Vec.test.ts +10 -0
  258. package/src/lib/primitives/Vec.ts +4 -4
  259. package/src/lib/primitives/geometry/Arc2d.test.ts +235 -3
  260. package/src/lib/primitives/geometry/Arc2d.ts +1 -1
  261. package/src/lib/primitives/geometry/CubicBezier2d.test.ts +186 -3
  262. package/src/lib/primitives/geometry/CubicBezier2d.ts +1 -1
  263. package/src/lib/primitives/geometry/CubicSpline2d.test.ts +160 -3
  264. package/src/lib/primitives/geometry/Edge2d.test.ts +21 -3
  265. package/src/lib/primitives/geometry/Edge2d.ts +1 -1
  266. package/src/lib/primitives/geometry/Ellipse2d.test.ts +243 -3
  267. package/src/lib/primitives/geometry/Geometry2d.test.ts +553 -1
  268. package/src/lib/primitives/geometry/Group2d.test.ts +419 -3
  269. package/src/lib/primitives/geometry/Group2d.ts +81 -34
  270. package/src/lib/primitives/geometry/Point2d.test.ts +14 -3
  271. package/src/lib/primitives/geometry/Point2d.ts +1 -1
  272. package/src/lib/primitives/geometry/Polyline2d.test.ts +207 -0
  273. package/src/lib/primitives/geometry/Stadium2d.test.ts +191 -3
  274. package/src/lib/primitives/utils.test.ts +10 -1
  275. package/src/lib/primitives/utils.ts +4 -1
  276. package/src/lib/test/useCanvasEvents.test.tsx +74 -0
  277. package/src/lib/test/useDocumentEvents.test.tsx +107 -0
  278. package/src/lib/test/usePassThroughWheelEvents.test.tsx +25 -2
  279. package/src/lib/utils/deepLinks.test.ts +15 -0
  280. package/src/lib/utils/deepLinks.ts +7 -1
  281. package/src/lib/utils/normalizeWheel.test.ts +39 -0
  282. package/src/lib/utils/normalizeWheel.ts +6 -0
  283. package/src/lib/utils/rotation.ts +4 -12
  284. package/src/lib/utils/sync/TLLocalSyncClient.test.ts +56 -0
  285. package/src/lib/utils/sync/TLLocalSyncClient.ts +24 -0
  286. package/src/lib/utils/sync/alerts.ts +1 -1
  287. package/src/version.ts +3 -3
  288. package/src/lib/primitives/geometry/Polyline.test.ts +0 -5
@@ -1,5 +1,421 @@
1
- describe('Group2d', () => {
2
- it('should be tested', () => {
3
- expect(true).toBe(true)
1
+ import { Mat } from '../Mat'
2
+ import { Vec, VecLike } from '../Vec'
3
+ import { Edge2d } from './Edge2d'
4
+ import { Geometry2dFilters, TransformedGeometry2d } from './Geometry2d'
5
+ import { Group2d } from './Group2d'
6
+ import { Rectangle2d } from './Rectangle2d'
7
+
8
+ // Two rectangles side by side with a gap between them
9
+ const rectA = () => new Rectangle2d({ width: 100, height: 50, isFilled: true })
10
+ const rectB = () => new Rectangle2d({ x: 200, width: 50, height: 50, isFilled: false })
11
+ // A small filled label to the right of both, and an internal geometry further right
12
+ const label = () =>
13
+ new Rectangle2d({ x: 300, width: 20, height: 20, isFilled: true, isLabel: true })
14
+ const internal = () =>
15
+ new Rectangle2d({ x: 400, width: 10, height: 10, isFilled: true, isInternal: true })
16
+
17
+ const group = () => new Group2d({ children: [rectA(), rectB()] })
18
+ const groupWithLabel = () => new Group2d({ children: [rectA(), label(), rectB()] })
19
+
20
+ // An L shaped path made of two edges, 200 units long in total
21
+ const path = () =>
22
+ new Group2d({
23
+ children: [
24
+ new Edge2d({ start: new Vec(0, 0), end: new Vec(100, 0) }),
25
+ new Edge2d({ start: new Vec(100, 0), end: new Vec(100, 100) }),
26
+ ],
27
+ })
28
+
29
+ function sortByX(points: VecLike[]) {
30
+ const round = (n: number) => Math.round(n * 1e6) / 1e6
31
+ return [...points]
32
+ .map((p) => ({ x: round(p.x), y: round(p.y) }))
33
+ .sort((a, b) => a.x - b.x || a.y - b.y)
34
+ }
35
+
36
+ describe('Group2d construction', () => {
37
+ it('is closed and unfilled and passes through the other options', () => {
38
+ const g = new Group2d({
39
+ children: [rectA()],
40
+ isLabel: true,
41
+ debugColor: 'red',
42
+ excludeFromShapeBounds: true,
43
+ })
44
+ expect(g).toMatchObject({
45
+ isClosed: true,
46
+ isFilled: false,
47
+ isLabel: true,
48
+ debugColor: 'red',
49
+ excludeFromShapeBounds: true,
50
+ })
51
+ })
52
+
53
+ it('keeps the children in order', () => {
54
+ const a = rectA()
55
+ const b = rectB()
56
+ const g = new Group2d({ children: [a, b] })
57
+ expect(g.children).toEqual([a, b])
58
+ expect(g.ignoredChildren).toEqual([])
59
+ })
60
+
61
+ it('flattens nested groups', () => {
62
+ const a = rectA()
63
+ const b = rectB()
64
+ const c = label()
65
+ const g = new Group2d({ children: [a, new Group2d({ children: [b, c] })] })
66
+ expect(g.children).toEqual([a, b, c])
67
+ })
68
+
69
+ it('separates ignored children', () => {
70
+ const a = rectA()
71
+ const ignored = new Rectangle2d({ width: 10, height: 10, isFilled: true, ignore: true })
72
+ const g = new Group2d({ children: [a, ignored] })
73
+ expect(g.children).toEqual([a])
74
+ expect(g.ignoredChildren).toEqual([ignored])
75
+ })
76
+
77
+ it('throws without any non-ignored children', () => {
78
+ expect(() => new Group2d({ children: [] })).toThrow('Group2d must have at least one child')
79
+ expect(
80
+ () =>
81
+ new Group2d({
82
+ children: [new Rectangle2d({ width: 10, height: 10, isFilled: true, ignore: true })],
83
+ })
84
+ ).toThrow('Group2d must have at least one child')
85
+ })
86
+ })
87
+
88
+ describe('Group2d.getVertices', () => {
89
+ it('concatenates the vertices of the children', () => {
90
+ expect(group().vertices).toMatchObject([
91
+ { x: 0, y: 0 },
92
+ { x: 100, y: 0 },
93
+ { x: 100, y: 50 },
94
+ { x: 0, y: 50 },
95
+ { x: 200, y: 0 },
96
+ { x: 250, y: 0 },
97
+ { x: 250, y: 50 },
98
+ { x: 200, y: 50 },
99
+ ])
100
+ })
101
+
102
+ it('excludes labels by default but keeps internal geometry', () => {
103
+ const g = new Group2d({ children: [rectA(), label(), internal()] })
104
+ expect(g.vertices.length).toBe(8)
105
+ expect(g.getVertices(Geometry2dFilters.EXCLUDE_LABELS).length).toBe(8)
106
+ expect(g.getVertices(Geometry2dFilters.INCLUDE_ALL).length).toBe(12)
107
+ expect(g.getVertices(Geometry2dFilters.EXCLUDE_INTERNAL).length).toBe(8)
108
+ expect(g.getVertices(Geometry2dFilters.EXCLUDE_NON_STANDARD).length).toBe(4)
109
+ })
110
+
111
+ it('returns nothing when the group itself is excluded by the filter', () => {
112
+ const g = new Group2d({ children: [rectA()], isLabel: true })
113
+ expect(g.getVertices(Geometry2dFilters.EXCLUDE_LABELS)).toEqual([])
114
+ expect(g.getVertices(Geometry2dFilters.INCLUDE_ALL).length).toBe(4)
115
+ })
116
+
117
+ it('ignores ignored children', () => {
118
+ const ignored = new Rectangle2d({ x: 500, width: 10, height: 10, isFilled: true, ignore: true })
119
+ const g = new Group2d({ children: [rectA(), ignored] })
120
+ expect(g.getVertices(Geometry2dFilters.INCLUDE_ALL).length).toBe(4)
121
+ })
122
+ })
123
+
124
+ describe('Group2d.bounds', () => {
125
+ it('is the union of the children bounds', () => {
126
+ expect(group().bounds).toMatchObject({ x: 0, y: 0, w: 250, h: 50 })
127
+ expect(group().center).toMatchObject({ x: 125, y: 25 })
128
+ })
129
+
130
+ it('includes labels', () => {
131
+ expect(groupWithLabel().bounds).toMatchObject({ x: 0, y: 0, w: 320, h: 50 })
132
+ })
133
+ })
134
+
135
+ describe('Group2d.getLength', () => {
136
+ it('sums the child lengths, excluding labels by default', () => {
137
+ expect(group().length).toBe(500)
138
+ expect(groupWithLabel().length).toBe(500)
139
+ expect(groupWithLabel().getLength(Geometry2dFilters.INCLUDE_ALL)).toBe(580)
140
+ expect(groupWithLabel().getLength(Geometry2dFilters.EXCLUDE_LABELS)).toBe(500)
141
+ })
142
+ })
143
+
144
+ describe('Group2d.getArea', () => {
145
+ it('uses the area of the first child', () => {
146
+ expect(group().area).toBe(5000)
147
+ expect(new Group2d({ children: [rectB(), rectA()] }).area).toBe(2500)
148
+ })
149
+ })
150
+
151
+ describe('Group2d.nearestPoint', () => {
152
+ it('returns the nearest point across the children, preferring earlier children on ties', () => {
153
+ expect(group().nearestPoint(new Vec(190, 25))).toMatchObject({ x: 200, y: 25 })
154
+ expect(group().nearestPoint(new Vec(50, -10))).toMatchObject({ x: 50, y: 0 })
155
+ expect(group().nearestPoint(new Vec(150, 25))).toMatchObject({ x: 100, y: 25 })
156
+ })
157
+
158
+ it('respects the filters', () => {
159
+ const g = groupWithLabel()
160
+ expect(g.nearestPoint(new Vec(310, 30), Geometry2dFilters.INCLUDE_ALL)).toMatchObject({
161
+ x: 310,
162
+ y: 20,
163
+ })
164
+ expect(g.nearestPoint(new Vec(310, 30), Geometry2dFilters.EXCLUDE_LABELS)).toMatchObject({
165
+ x: 250,
166
+ y: 30,
167
+ })
168
+ })
169
+
170
+ it('uses the label when no filters are given', () => {
171
+ expect(groupWithLabel().nearestPoint(new Vec(310, 30))).toMatchObject({ x: 310, y: 20 })
172
+ })
173
+ })
174
+
175
+ describe('Group2d.distanceToPoint', () => {
176
+ it('is the smallest child distance, negative inside a filled child', () => {
177
+ expect(group().distanceToPoint(new Vec(50, 25))).toBe(-25)
178
+ expect(group().distanceToPoint(new Vec(225, 25))).toBe(25)
179
+ expect(group().distanceToPoint(new Vec(225, 25), true)).toBe(-25)
180
+ expect(group().distanceToPoint(new Vec(150, 25))).toBe(50)
181
+ })
182
+
183
+ it('respects the filters', () => {
184
+ const g = groupWithLabel()
185
+ expect(g.distanceToPoint(new Vec(310, 10), false, Geometry2dFilters.INCLUDE_ALL)).toBe(-10)
186
+ expect(g.distanceToPoint(new Vec(310, 10), false, Geometry2dFilters.EXCLUDE_LABELS)).toBe(60)
187
+ })
188
+ })
189
+
190
+ describe('Group2d.hitTestPoint', () => {
191
+ it('hits when any child hits', () => {
192
+ expect(group().hitTestPoint(new Vec(50, 25), 0, false)).toBe(true)
193
+ expect(group().hitTestPoint(new Vec(225, 25), 0, false)).toBe(false)
194
+ expect(group().hitTestPoint(new Vec(225, 25), 0, true)).toBe(true)
195
+ expect(group().hitTestPoint(new Vec(150, 25), 0, true)).toBe(false)
196
+ expect(group().hitTestPoint(new Vec(150, 25), 50, false)).toBe(true)
197
+ })
198
+
199
+ it('excludes labels by default', () => {
200
+ const g = groupWithLabel()
201
+ expect(g.hitTestPoint(new Vec(310, 10), 0, false)).toBe(false)
202
+ expect(g.hitTestPoint(new Vec(310, 10), 0, false, Geometry2dFilters.INCLUDE_ALL)).toBe(true)
203
+ })
204
+
205
+ it('never hits ignored children', () => {
206
+ const ignored = new Rectangle2d({ x: 500, width: 10, height: 10, isFilled: true, ignore: true })
207
+ const g = new Group2d({ children: [rectA(), ignored] })
208
+ expect(g.hitTestPoint(new Vec(505, 5), 0, true)).toBe(false)
209
+ })
210
+ })
211
+
212
+ describe('Group2d.hitTestLineSegment', () => {
213
+ it('hits when any child hits', () => {
214
+ expect(group().hitTestLineSegment(new Vec(90, 25), new Vec(110, 25), 0)).toBe(true)
215
+ expect(group().hitTestLineSegment(new Vec(240, 25), new Vec(260, 25), 0)).toBe(true)
216
+ expect(group().hitTestLineSegment(new Vec(150, -10), new Vec(150, 60), 0)).toBe(false)
217
+ })
218
+
219
+ it('passes the distance to the children', () => {
220
+ expect(group().hitTestLineSegment(new Vec(150, 0), new Vec(150, 50), 50)).toBe(true)
221
+ expect(group().hitTestLineSegment(new Vec(150, 0), new Vec(150, 50), 49)).toBe(false)
222
+ })
223
+
224
+ it('excludes labels by default', () => {
225
+ const g = groupWithLabel()
226
+ expect(g.hitTestLineSegment(new Vec(310, -10), new Vec(310, 30), 0)).toBe(false)
227
+ expect(
228
+ g.hitTestLineSegment(new Vec(310, -10), new Vec(310, 30), 0, Geometry2dFilters.INCLUDE_ALL)
229
+ ).toBe(true)
230
+ })
231
+ })
232
+
233
+ describe('Group2d.intersectLineSegment', () => {
234
+ it('collects the intersections from every child', () => {
235
+ const hits = group().intersectLineSegment(new Vec(-10, 25), new Vec(300, 25))
236
+ expect(sortByX(hits)).toEqual([
237
+ { x: 0, y: 25 },
238
+ { x: 100, y: 25 },
239
+ { x: 200, y: 25 },
240
+ { x: 250, y: 25 },
241
+ ])
242
+ })
243
+
244
+ it('respects the filters', () => {
245
+ const g = groupWithLabel()
246
+ const A = new Vec(-10, 10)
247
+ const B = new Vec(400, 10)
248
+ expect(g.intersectLineSegment(A, B).length).toBe(6)
249
+ expect(g.intersectLineSegment(A, B, Geometry2dFilters.EXCLUDE_LABELS).length).toBe(4)
250
+ })
251
+
252
+ it('returns an empty array when nothing is hit', () => {
253
+ expect(group().intersectLineSegment(new Vec(150, -10), new Vec(150, 60))).toEqual([])
254
+ })
255
+ })
256
+
257
+ describe('Group2d.intersectCircle', () => {
258
+ it('collects the intersections from every child', () => {
259
+ const hits = group().intersectCircle(new Vec(0, 25), 10)
260
+ expect(sortByX(hits)).toEqual([
261
+ { x: 0, y: 15 },
262
+ { x: 0, y: 35 },
263
+ ])
264
+ })
265
+
266
+ it('respects the filters', () => {
267
+ const g = groupWithLabel()
268
+ expect(g.intersectCircle(new Vec(300, 10), 5).length).toBe(2)
269
+ expect(g.intersectCircle(new Vec(300, 10), 5, Geometry2dFilters.EXCLUDE_LABELS)).toEqual([])
270
+ })
271
+ })
272
+
273
+ describe('Group2d.intersectPolygon and intersectPolyline', () => {
274
+ const polygon = [new Vec(50, -10), new Vec(110, -10), new Vec(110, 60), new Vec(50, 60)]
275
+
276
+ it('collects polygon intersections from every child', () => {
277
+ expect(sortByX(group().intersectPolygon(polygon))).toEqual([
278
+ { x: 50, y: 0 },
279
+ { x: 50, y: 50 },
280
+ ])
281
+ })
282
+
283
+ it('collects polyline intersections from every child', () => {
284
+ const polyline = [new Vec(50, -10), new Vec(50, 60)]
285
+ expect(sortByX(group().intersectPolyline(polyline))).toEqual([
286
+ { x: 50, y: 0 },
287
+ { x: 50, y: 50 },
288
+ ])
289
+ })
290
+
291
+ it('respects the filters', () => {
292
+ const g = groupWithLabel()
293
+ const labelPolygon = [new Vec(310, -10), new Vec(330, -10), new Vec(330, 30), new Vec(310, 30)]
294
+ expect(g.intersectPolygon(labelPolygon).length).toBe(2)
295
+ expect(g.intersectPolygon(labelPolygon, Geometry2dFilters.EXCLUDE_LABELS)).toEqual([])
296
+ const labelPolyline = [new Vec(310, -10), new Vec(310, 30)]
297
+ expect(g.intersectPolyline(labelPolyline).length).toBe(2)
298
+ expect(g.intersectPolyline(labelPolyline, Geometry2dFilters.EXCLUDE_LABELS)).toEqual([])
299
+ })
300
+ })
301
+
302
+ describe('Group2d.interpolateAlongEdge', () => {
303
+ it('walks through the children in order', () => {
304
+ const p = path()
305
+ expect(p.interpolateAlongEdge(0)).toMatchObject({ x: 0, y: 0 })
306
+ expect(p.interpolateAlongEdge(0.25)).toMatchObject({ x: 50, y: 0 })
307
+ expect(p.interpolateAlongEdge(0.5)).toMatchObject({ x: 100, y: 0 })
308
+ expect(p.interpolateAlongEdge(0.75)).toMatchObject({ x: 100, y: 50 })
309
+ expect(p.interpolateAlongEdge(1)).toMatchObject({ x: 100, y: 100 })
310
+ })
311
+
312
+ it('clamps to the end of the last child', () => {
313
+ expect(path().interpolateAlongEdge(1.5)).toMatchObject({ x: 100, y: 100 })
314
+ })
315
+ })
316
+
317
+ describe('Group2d.uninterpolateAlongEdge', () => {
318
+ it('is the inverse of interpolateAlongEdge', () => {
319
+ const p = path()
320
+ expect(p.uninterpolateAlongEdge(new Vec(0, 0))).toBe(0)
321
+ expect(p.uninterpolateAlongEdge(new Vec(50, 0))).toBe(0.25)
322
+ expect(p.uninterpolateAlongEdge(new Vec(100, 0))).toBe(0.5)
323
+ expect(p.uninterpolateAlongEdge(new Vec(100, 50))).toBe(0.75)
324
+ expect(p.uninterpolateAlongEdge(new Vec(100, 100))).toBe(1)
325
+ })
326
+
327
+ it('snaps points off the path to the nearest child', () => {
328
+ expect(path().uninterpolateAlongEdge(new Vec(50, -20))).toBe(0.25)
329
+ expect(path().uninterpolateAlongEdge(new Vec(120, 50))).toBe(0.75)
330
+ })
331
+ })
332
+
333
+ describe('Group2d.transform', () => {
334
+ it('transforms every child', () => {
335
+ const t = group().transform(Mat.Translate(10, 20))
336
+ expect(t).toBeInstanceOf(Group2d)
337
+ expect((t as Group2d).children.every((c) => c instanceof TransformedGeometry2d)).toBe(true)
338
+ expect(t.bounds).toMatchObject({ x: 10, y: 20, w: 250, h: 50 })
339
+ expect(t.vertices[0]).toMatchObject({ x: 10, y: 20 })
340
+ expect(t.vertices.length).toBe(8)
341
+ })
342
+
343
+ it('carries all the flags forward', () => {
344
+ const g = new Group2d({
345
+ children: [rectA()],
346
+ isLabel: true,
347
+ isEmptyLabel: true,
348
+ isInternal: true,
349
+ excludeFromShapeBounds: true,
350
+ debugColor: 'red',
351
+ ignore: true,
352
+ })
353
+ expect(g.transform(Mat.Identity())).toMatchObject({
354
+ isLabel: true,
355
+ isEmptyLabel: true,
356
+ isInternal: true,
357
+ excludeFromShapeBounds: true,
358
+ debugColor: 'red',
359
+ ignore: true,
360
+ })
361
+ })
362
+
363
+ it('applies the transform options over the carried flags', () => {
364
+ const g = new Group2d({ children: [rectA()], isLabel: true, isInternal: true })
365
+ expect(
366
+ g.transform(Mat.Identity(), {
367
+ isLabel: false,
368
+ isInternal: false,
369
+ debugColor: 'red',
370
+ ignore: true,
371
+ })
372
+ ).toMatchObject({ isLabel: false, isInternal: false, debugColor: 'red', ignore: true })
373
+ })
374
+
375
+ it('scales hit testing with the transform', () => {
376
+ const t = group().transform(Mat.Scale(2, 2))
377
+ expect(t.hitTestPoint(new Vec(100, 50), 0, false)).toBe(true)
378
+ expect(t.hitTestPoint(new Vec(300, 50), 0, false)).toBe(false)
379
+ expect(t.distanceToPoint(new Vec(300, 50))).toBe(100)
380
+ })
381
+
382
+ // #10562: transform used to drop the ignored children.
383
+ it('keeps the transformed ignored children', () => {
384
+ const ignored = new Rectangle2d({ x: 500, width: 10, height: 10, isFilled: true, ignore: true })
385
+ const g = new Group2d({ children: [rectA(), ignored] })
386
+ const t = g.transform(Mat.Translate(10, 20)) as Group2d
387
+ expect(t.children.length).toBe(1)
388
+ expect(t.ignoredChildren.length).toBe(1)
389
+ expect(t.ignoredChildren[0].ignore).toBe(true)
390
+ expect(t.ignoredChildren[0].bounds).toMatchObject({ x: 510, y: 20, w: 10, h: 10 })
391
+ })
392
+ })
393
+
394
+ describe('Group2d.overlapsPolygon', () => {
395
+ it('overlaps when any child overlaps', () => {
396
+ const aroundB = [new Vec(190, -10), new Vec(260, -10), new Vec(260, 60), new Vec(190, 60)]
397
+ expect(group().overlapsPolygon(aroundB)).toBe(true)
398
+ const gap = [new Vec(120, 10), new Vec(180, 10), new Vec(180, 40), new Vec(120, 40)]
399
+ expect(group().overlapsPolygon(gap)).toBe(false)
400
+ })
401
+ })
402
+
403
+ describe('Group2d svg output', () => {
404
+ it('joins the children paths and skips labels', () => {
405
+ expect(group().getSvgPathData()).toBe('M0,0 h100 v50 h-100z M200,0 h50 v50 h-50z')
406
+ expect(groupWithLabel().getSvgPathData()).toBe('M0,0 h100 v50 h-100z M200,0 h50 v50 h-50z')
407
+ })
408
+
409
+ it('draws the children plus a small corner mark at each bounds corner', () => {
410
+ const g = new Group2d({
411
+ children: [new Rectangle2d({ width: 128, height: 64, isFilled: true })],
412
+ })
413
+ expect(g.toSimpleSvgPath()).toBe(
414
+ 'M0,0L128,0L128,64L0,64Z' +
415
+ 'M0,4 L0,0 L4,0 ' +
416
+ 'M124,0 L128,0 L128,4 ' +
417
+ 'M128,60 L128,64 L124,64 ' +
418
+ 'M4,64 L0,64 L0,60 '
419
+ )
4
420
  })
5
421
  })
@@ -1,9 +1,13 @@
1
- import { EMPTY_ARRAY } from '@tldraw/state'
2
1
  import { assert, invLerp, lerp } from '@tldraw/utils'
3
2
  import { Box } from '../Box'
4
- import { Mat } from '../Mat'
3
+ import { MatModel } from '../Mat'
5
4
  import { Vec, VecLike } from '../Vec'
6
- import { Geometry2d, Geometry2dFilters, Geometry2dOptions } from './Geometry2d'
5
+ import {
6
+ Geometry2d,
7
+ Geometry2dFilters,
8
+ Geometry2dOptions,
9
+ TransformedGeometry2dOptions,
10
+ } from './Geometry2d'
7
11
 
8
12
  /** @public */
9
13
  export class Group2d extends Geometry2d {
@@ -36,9 +40,15 @@ export class Group2d extends Geometry2d {
36
40
 
37
41
  override getVertices(filters: Geometry2dFilters): Vec[] {
38
42
  if (this.isExcludedByFilter(filters)) return []
39
- return this.children
40
- .filter((c) => !c.isExcludedByFilter(filters))
41
- .flatMap((c) => c.getVertices(filters))
43
+ const vertices: Vec[] = []
44
+ for (const child of this.children) {
45
+ if (child.isExcludedByFilter(filters)) continue
46
+ const childVertices = child.getVertices(filters)
47
+ for (let i = 0, n = childVertices.length; i < n; i++) {
48
+ vertices.push(childVertices[i])
49
+ }
50
+ }
51
+ return vertices
42
52
  }
43
53
 
44
54
  override nearestPoint(point: VecLike, filters?: Geometry2dFilters): Vec {
@@ -84,9 +94,11 @@ export class Group2d extends Geometry2d {
84
94
  hitInside: boolean,
85
95
  filters = Geometry2dFilters.EXCLUDE_LABELS
86
96
  ): boolean {
87
- return !!this.children
88
- .filter((c) => !c.isExcludedByFilter(filters))
89
- .find((c) => c.hitTestPoint(point, margin, hitInside))
97
+ for (const child of this.children) {
98
+ if (child.isExcludedByFilter(filters)) continue
99
+ if (child.hitTestPoint(point, margin, hitInside)) return true
100
+ }
101
+ return false
90
102
  }
91
103
 
92
104
  override hitTestLineSegment(
@@ -95,42 +107,71 @@ export class Group2d extends Geometry2d {
95
107
  zoom: number,
96
108
  filters = Geometry2dFilters.EXCLUDE_LABELS
97
109
  ): boolean {
98
- return !!this.children
99
- .filter((c) => !c.isExcludedByFilter(filters))
100
- .find((c) => c.hitTestLineSegment(A, B, zoom))
110
+ for (const child of this.children) {
111
+ if (child.isExcludedByFilter(filters)) continue
112
+ if (child.hitTestLineSegment(A, B, zoom)) return true
113
+ }
114
+ return false
101
115
  }
102
116
 
103
117
  override intersectLineSegment(A: VecLike, B: VecLike, filters?: Geometry2dFilters) {
104
- return this.children.flatMap((child) => {
105
- if (child.isExcludedByFilter(filters)) return EMPTY_ARRAY
106
- return child.intersectLineSegment(A, B, filters)
107
- })
118
+ const result: VecLike[] = []
119
+ for (const child of this.children) {
120
+ if (child.isExcludedByFilter(filters)) continue
121
+ const hits = child.intersectLineSegment(A, B, filters)
122
+ for (let i = 0, n = hits.length; i < n; i++) {
123
+ result.push(hits[i])
124
+ }
125
+ }
126
+ return result
108
127
  }
109
128
 
110
129
  override intersectCircle(center: VecLike, radius: number, filters?: Geometry2dFilters) {
111
- return this.children.flatMap((child) => {
112
- if (child.isExcludedByFilter(filters)) return EMPTY_ARRAY
113
- return child.intersectCircle(center, radius, filters)
114
- })
130
+ const result: VecLike[] = []
131
+ for (const child of this.children) {
132
+ if (child.isExcludedByFilter(filters)) continue
133
+ const hits = child.intersectCircle(center, radius, filters)
134
+ for (let i = 0, n = hits.length; i < n; i++) {
135
+ result.push(hits[i])
136
+ }
137
+ }
138
+ return result
115
139
  }
116
140
 
117
141
  override getBoundsVertices(): Vec[] {
118
142
  if (this.excludeFromShapeBounds) return []
119
- return this.children.flatMap((child) => child.getBoundsVertices())
143
+ const vertices: Vec[] = []
144
+ for (const child of this.children) {
145
+ const childVertices = child.getBoundsVertices()
146
+ for (let i = 0, n = childVertices.length; i < n; i++) {
147
+ vertices.push(childVertices[i])
148
+ }
149
+ }
150
+ return vertices
120
151
  }
121
152
 
122
153
  override intersectPolygon(polygon: VecLike[], filters?: Geometry2dFilters) {
123
- return this.children.flatMap((child) => {
124
- if (child.isExcludedByFilter(filters)) return EMPTY_ARRAY
125
- return child.intersectPolygon(polygon, filters)
126
- })
154
+ const result: VecLike[] = []
155
+ for (const child of this.children) {
156
+ if (child.isExcludedByFilter(filters)) continue
157
+ const hits = child.intersectPolygon(polygon, filters)
158
+ for (let i = 0, n = hits.length; i < n; i++) {
159
+ result.push(hits[i])
160
+ }
161
+ }
162
+ return result
127
163
  }
128
164
 
129
165
  override intersectPolyline(polyline: VecLike[], filters?: Geometry2dFilters) {
130
- return this.children.flatMap((child) => {
131
- if (child.isExcludedByFilter(filters)) return EMPTY_ARRAY
132
- return child.intersectPolyline(polyline, filters)
133
- })
166
+ const result: VecLike[] = []
167
+ for (const child of this.children) {
168
+ if (child.isExcludedByFilter(filters)) continue
169
+ const hits = child.intersectPolyline(polyline, filters)
170
+ for (let i = 0, n = hits.length; i < n; i++) {
171
+ result.push(hits[i])
172
+ }
173
+ }
174
+ return result
134
175
  }
135
176
 
136
177
  override interpolateAlongEdge(t: number, filters?: Geometry2dFilters): Vec {
@@ -190,12 +231,18 @@ export class Group2d extends Geometry2d {
190
231
  return childTLength / totalLength
191
232
  }
192
233
 
193
- override transform(transform: Mat): Geometry2d {
234
+ override transform(transform: MatModel, opts?: TransformedGeometry2dOptions): Geometry2d {
235
+ // Each child's own transform preserves its `ignore` flag, so the constructor
236
+ // re-partitions ignored children back into `ignoredChildren` instead of dropping
237
+ // them (#10562).
194
238
  return new Group2d({
195
- children: this.children.map((c) => c.transform(transform)),
196
- isLabel: this.isLabel,
197
- debugColor: this.debugColor,
198
- ignore: this.ignore,
239
+ children: [...this.children, ...this.ignoredChildren].map((c) => c.transform(transform)),
240
+ isLabel: opts?.isLabel ?? this.isLabel,
241
+ isEmptyLabel: this.isEmptyLabel,
242
+ isInternal: opts?.isInternal ?? this.isInternal,
243
+ debugColor: opts?.debugColor ?? this.debugColor,
244
+ ignore: opts?.ignore ?? this.ignore,
245
+ excludeFromShapeBounds: this.excludeFromShapeBounds,
199
246
  })
200
247
  }
201
248
 
@@ -1,5 +1,16 @@
1
- describe('Point2d', () => {
2
- it('should be tested', () => {
3
- expect(true).toBe(true)
1
+ import { Vec } from '../Vec'
2
+ import { Point2d } from './Point2d'
3
+
4
+ describe('Point2d.getSvgPathData', () => {
5
+ it('emits a move command rounded to two decimals', () => {
6
+ const point = new Point2d({ margin: 0, point: new Vec(0.123456, 7.891234) })
7
+ expect(point.getSvgPathData()).toBe('M0.12, 7.89')
8
+ })
9
+
10
+ it('does not mutate the stored point', () => {
11
+ const vec = new Vec(0.123456, 7.891234)
12
+ const point = new Point2d({ margin: 0, point: vec })
13
+ point.getSvgPathData()
14
+ expect(vec).toMatchObject({ x: 0.123456, y: 7.891234 })
4
15
  })
5
16
  })
@@ -28,6 +28,6 @@ export class Point2d extends Geometry2d {
28
28
 
29
29
  getSvgPathData() {
30
30
  const { _point: point } = this
31
- return `M${point.toFixed()}`
31
+ return `M${point}`
32
32
  }
33
33
  }