@tldraw/editor 4.5.2 → 4.6.0-canary.4ec045c286e1

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 (226) hide show
  1. package/dist-cjs/index.d.ts +37 -6
  2. package/dist-cjs/index.js +6 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/TldrawEditor.js +7 -5
  5. package/dist-cjs/lib/TldrawEditor.js.map +3 -3
  6. package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js +3 -2
  7. package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js.map +2 -2
  8. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +1 -1
  9. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  10. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +8 -5
  11. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +2 -2
  12. package/dist-cjs/lib/config/TLSessionStateSnapshot.js +8 -5
  13. package/dist-cjs/lib/config/TLSessionStateSnapshot.js.map +2 -2
  14. package/dist-cjs/lib/config/TLUserPreferences.js +3 -2
  15. package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
  16. package/dist-cjs/lib/config/createTLStore.js +1 -0
  17. package/dist-cjs/lib/config/createTLStore.js.map +2 -2
  18. package/dist-cjs/lib/editor/Editor.js +52 -16
  19. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  20. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js +62 -6
  21. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js.map +2 -2
  22. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js +4 -3
  23. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +2 -2
  24. package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js +5 -0
  25. package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js.map +2 -2
  26. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +2 -2
  27. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
  28. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +3 -2
  29. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  30. package/dist-cjs/lib/editor/types/misc-types.js.map +1 -1
  31. package/dist-cjs/lib/exports/FontEmbedder.js +9 -8
  32. package/dist-cjs/lib/exports/FontEmbedder.js.map +2 -2
  33. package/dist-cjs/lib/exports/StyleEmbedder.js +27 -15
  34. package/dist-cjs/lib/exports/StyleEmbedder.js.map +3 -3
  35. package/dist-cjs/lib/exports/domUtils.js +15 -0
  36. package/dist-cjs/lib/exports/domUtils.js.map +2 -2
  37. package/dist-cjs/lib/exports/embedMedia.js +15 -12
  38. package/dist-cjs/lib/exports/embedMedia.js.map +2 -2
  39. package/dist-cjs/lib/exports/exportToSvg.js +8 -7
  40. package/dist-cjs/lib/exports/exportToSvg.js.map +2 -2
  41. package/dist-cjs/lib/exports/getSvgAsImage.js +181 -29
  42. package/dist-cjs/lib/exports/getSvgAsImage.js.map +3 -3
  43. package/dist-cjs/lib/exports/getSvgJsx.js +21 -9
  44. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  45. package/dist-cjs/lib/globals/environment.js +4 -3
  46. package/dist-cjs/lib/globals/environment.js.map +2 -2
  47. package/dist-cjs/lib/hooks/useCanvasEvents.js +2 -2
  48. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  49. package/dist-cjs/lib/hooks/useDocumentEvents.js +13 -11
  50. package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
  51. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js +3 -2
  52. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js.map +2 -2
  53. package/dist-cjs/lib/hooks/useScreenBounds.js +10 -6
  54. package/dist-cjs/lib/hooks/useScreenBounds.js.map +2 -2
  55. package/dist-cjs/lib/hooks/useViewportHeight.js +13 -11
  56. package/dist-cjs/lib/hooks/useViewportHeight.js.map +3 -3
  57. package/dist-cjs/lib/license/Watermark.js +10 -0
  58. package/dist-cjs/lib/license/Watermark.js.map +2 -2
  59. package/dist-cjs/lib/primitives/Vec.js +35 -22
  60. package/dist-cjs/lib/primitives/Vec.js.map +2 -2
  61. package/dist-cjs/lib/primitives/geometry/Arc2d.js +6 -13
  62. package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
  63. package/dist-cjs/lib/primitives/geometry/Circle2d.js +31 -2
  64. package/dist-cjs/lib/primitives/geometry/Circle2d.js.map +2 -2
  65. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +9 -0
  66. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
  67. package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js +9 -0
  68. package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js.map +2 -2
  69. package/dist-cjs/lib/primitives/geometry/Edge2d.js +32 -18
  70. package/dist-cjs/lib/primitives/geometry/Edge2d.js.map +2 -2
  71. package/dist-cjs/lib/primitives/geometry/Ellipse2d.js +12 -0
  72. package/dist-cjs/lib/primitives/geometry/Ellipse2d.js.map +2 -2
  73. package/dist-cjs/lib/primitives/geometry/Polyline2d.js +51 -12
  74. package/dist-cjs/lib/primitives/geometry/Polyline2d.js.map +2 -2
  75. package/dist-cjs/lib/primitives/geometry/Stadium2d.js +12 -0
  76. package/dist-cjs/lib/primitives/geometry/Stadium2d.js.map +2 -2
  77. package/dist-cjs/lib/primitives/geometry/geometry.bench.js +133 -0
  78. package/dist-cjs/lib/primitives/geometry/geometry.bench.js.map +7 -0
  79. package/dist-cjs/lib/primitives/intersect.js +16 -15
  80. package/dist-cjs/lib/primitives/intersect.js.map +2 -2
  81. package/dist-cjs/lib/primitives/utils.js +0 -1
  82. package/dist-cjs/lib/primitives/utils.js.map +2 -2
  83. package/dist-cjs/lib/utils/browserCanvasMaxSize.js +3 -2
  84. package/dist-cjs/lib/utils/browserCanvasMaxSize.js.map +2 -2
  85. package/dist-cjs/lib/utils/dom.js +15 -2
  86. package/dist-cjs/lib/utils/dom.js.map +2 -2
  87. package/dist-cjs/version.js +3 -3
  88. package/dist-cjs/version.js.map +1 -1
  89. package/dist-esm/index.d.mts +37 -6
  90. package/dist-esm/index.mjs +8 -1
  91. package/dist-esm/index.mjs.map +2 -2
  92. package/dist-esm/lib/TldrawEditor.mjs +7 -5
  93. package/dist-esm/lib/TldrawEditor.mjs.map +3 -3
  94. package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs +2 -1
  95. package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs.map +2 -2
  96. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +1 -1
  97. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  98. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +8 -5
  99. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +2 -2
  100. package/dist-esm/lib/config/TLSessionStateSnapshot.mjs +8 -5
  101. package/dist-esm/lib/config/TLSessionStateSnapshot.mjs.map +2 -2
  102. package/dist-esm/lib/config/TLUserPreferences.mjs +3 -2
  103. package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
  104. package/dist-esm/lib/config/createTLStore.mjs +1 -0
  105. package/dist-esm/lib/config/createTLStore.mjs.map +2 -2
  106. package/dist-esm/lib/editor/Editor.mjs +53 -17
  107. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  108. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs +64 -6
  109. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs.map +2 -2
  110. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs +4 -3
  111. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +2 -2
  112. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs +5 -0
  113. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs.map +2 -2
  114. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +2 -2
  115. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
  116. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +3 -2
  117. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  118. package/dist-esm/lib/exports/FontEmbedder.mjs +9 -8
  119. package/dist-esm/lib/exports/FontEmbedder.mjs.map +2 -2
  120. package/dist-esm/lib/exports/StyleEmbedder.mjs +29 -16
  121. package/dist-esm/lib/exports/StyleEmbedder.mjs.map +3 -3
  122. package/dist-esm/lib/exports/domUtils.mjs +15 -0
  123. package/dist-esm/lib/exports/domUtils.mjs.map +2 -2
  124. package/dist-esm/lib/exports/embedMedia.mjs +16 -13
  125. package/dist-esm/lib/exports/embedMedia.mjs.map +2 -2
  126. package/dist-esm/lib/exports/exportToSvg.mjs +8 -7
  127. package/dist-esm/lib/exports/exportToSvg.mjs.map +2 -2
  128. package/dist-esm/lib/exports/getSvgAsImage.mjs +181 -29
  129. package/dist-esm/lib/exports/getSvgAsImage.mjs.map +3 -3
  130. package/dist-esm/lib/exports/getSvgJsx.mjs +21 -9
  131. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  132. package/dist-esm/lib/globals/environment.mjs +4 -3
  133. package/dist-esm/lib/globals/environment.mjs.map +2 -2
  134. package/dist-esm/lib/hooks/useCanvasEvents.mjs +2 -2
  135. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  136. package/dist-esm/lib/hooks/useDocumentEvents.mjs +13 -11
  137. package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
  138. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs +3 -2
  139. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs.map +2 -2
  140. package/dist-esm/lib/hooks/useScreenBounds.mjs +10 -6
  141. package/dist-esm/lib/hooks/useScreenBounds.mjs.map +2 -2
  142. package/dist-esm/lib/hooks/useViewportHeight.mjs +13 -11
  143. package/dist-esm/lib/hooks/useViewportHeight.mjs.map +3 -3
  144. package/dist-esm/lib/license/Watermark.mjs +10 -0
  145. package/dist-esm/lib/license/Watermark.mjs.map +2 -2
  146. package/dist-esm/lib/primitives/Vec.mjs +35 -22
  147. package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
  148. package/dist-esm/lib/primitives/geometry/Arc2d.mjs +6 -13
  149. package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
  150. package/dist-esm/lib/primitives/geometry/Circle2d.mjs +31 -2
  151. package/dist-esm/lib/primitives/geometry/Circle2d.mjs.map +2 -2
  152. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +9 -0
  153. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
  154. package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs +9 -0
  155. package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs.map +2 -2
  156. package/dist-esm/lib/primitives/geometry/Edge2d.mjs +32 -18
  157. package/dist-esm/lib/primitives/geometry/Edge2d.mjs.map +2 -2
  158. package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs +13 -1
  159. package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs.map +2 -2
  160. package/dist-esm/lib/primitives/geometry/Polyline2d.mjs +51 -12
  161. package/dist-esm/lib/primitives/geometry/Polyline2d.mjs.map +2 -2
  162. package/dist-esm/lib/primitives/geometry/Stadium2d.mjs +13 -1
  163. package/dist-esm/lib/primitives/geometry/Stadium2d.mjs.map +2 -2
  164. package/dist-esm/lib/primitives/geometry/geometry.bench.mjs +132 -0
  165. package/dist-esm/lib/primitives/geometry/geometry.bench.mjs.map +7 -0
  166. package/dist-esm/lib/primitives/intersect.mjs +17 -16
  167. package/dist-esm/lib/primitives/intersect.mjs.map +2 -2
  168. package/dist-esm/lib/primitives/utils.mjs +0 -1
  169. package/dist-esm/lib/primitives/utils.mjs.map +2 -2
  170. package/dist-esm/lib/utils/browserCanvasMaxSize.mjs +3 -2
  171. package/dist-esm/lib/utils/browserCanvasMaxSize.mjs.map +2 -2
  172. package/dist-esm/lib/utils/dom.mjs +15 -2
  173. package/dist-esm/lib/utils/dom.mjs.map +2 -2
  174. package/dist-esm/version.mjs +3 -3
  175. package/dist-esm/version.mjs.map +1 -1
  176. package/package.json +7 -7
  177. package/src/index.ts +3 -0
  178. package/src/lib/TldrawEditor.tsx +7 -5
  179. package/src/lib/components/default-components/CanvasShapeIndicators.tsx +2 -1
  180. package/src/lib/components/default-components/DefaultCanvas.tsx +1 -1
  181. package/src/lib/components/default-components/DefaultErrorFallback.tsx +8 -5
  182. package/src/lib/config/TLSessionStateSnapshot.ts +8 -5
  183. package/src/lib/config/TLUserPreferences.ts +3 -2
  184. package/src/lib/config/createTLStore.ts +3 -0
  185. package/src/lib/editor/Editor.ts +53 -15
  186. package/src/lib/editor/managers/FocusManager/FocusManager.test.ts +7 -6
  187. package/src/lib/editor/managers/FocusManager/FocusManager.ts +10 -7
  188. package/src/lib/editor/managers/FontManager/FontManager.test.ts +1 -0
  189. package/src/lib/editor/managers/FontManager/FontManager.ts +4 -3
  190. package/src/lib/editor/managers/HistoryManager/HistoryManager.test.ts +16 -0
  191. package/src/lib/editor/managers/HistoryManager/HistoryManager.ts +7 -2
  192. package/src/lib/editor/managers/TextManager/TextManager.test.ts +4 -5
  193. package/src/lib/editor/managers/TextManager/TextManager.ts +2 -2
  194. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +3 -2
  195. package/src/lib/editor/types/misc-types.ts +8 -2
  196. package/src/lib/exports/FontEmbedder.ts +10 -9
  197. package/src/lib/exports/StyleEmbedder.ts +33 -15
  198. package/src/lib/exports/domUtils.ts +20 -0
  199. package/src/lib/exports/embedMedia.ts +23 -17
  200. package/src/lib/exports/exportToSvg.tsx +8 -7
  201. package/src/lib/exports/getSvgAsImage.ts +292 -32
  202. package/src/lib/exports/getSvgJsx.test.ts +103 -101
  203. package/src/lib/exports/getSvgJsx.tsx +33 -10
  204. package/src/lib/globals/environment.ts +4 -3
  205. package/src/lib/hooks/useCanvasEvents.ts +2 -3
  206. package/src/lib/hooks/useDocumentEvents.ts +16 -11
  207. package/src/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.ts +3 -3
  208. package/src/lib/hooks/useScreenBounds.ts +10 -6
  209. package/src/lib/hooks/useViewportHeight.ts +13 -11
  210. package/src/lib/license/Watermark.tsx +10 -0
  211. package/src/lib/primitives/Vec.ts +51 -24
  212. package/src/lib/primitives/geometry/Arc2d.ts +10 -15
  213. package/src/lib/primitives/geometry/Circle2d.ts +40 -2
  214. package/src/lib/primitives/geometry/CubicBezier2d.ts +10 -0
  215. package/src/lib/primitives/geometry/CubicSpline2d.ts +10 -0
  216. package/src/lib/primitives/geometry/Edge2d.ts +41 -18
  217. package/src/lib/primitives/geometry/Ellipse2d.ts +14 -1
  218. package/src/lib/primitives/geometry/Polyline2d.ts +60 -12
  219. package/src/lib/primitives/geometry/Stadium2d.ts +14 -1
  220. package/src/lib/primitives/geometry/geometry.bench.ts +179 -0
  221. package/src/lib/primitives/intersect.ts +27 -27
  222. package/src/lib/primitives/utils.ts +4 -4
  223. package/src/lib/test/TestEditor.ts +1 -0
  224. package/src/lib/utils/browserCanvasMaxSize.ts +4 -2
  225. package/src/lib/utils/dom.ts +34 -2
  226. package/src/version.ts +3 -3
@@ -1,4 +1,5 @@
1
1
  import { useLayoutEffect, useState } from 'react'
2
+ import { useMaybeEditor } from './useEditor'
2
3
 
3
4
  /*!
4
5
  * BSD License: https://github.com/outline/rich-markdown-editor/blob/main/LICENSE
@@ -12,26 +13,27 @@ import { useLayoutEffect, useState } from 'react'
12
13
  */
13
14
  /** @public */
14
15
  export function useViewportHeight(): number {
15
- const visualViewport = window.visualViewport
16
+ const editor = useMaybeEditor()
17
+ const win = editor?.getContainerWindow() ?? window
18
+ const vv = win.visualViewport
16
19
  const [height, setHeight] = useState<number>(() =>
17
- visualViewport ? visualViewport.height + visualViewport.offsetTop : window.innerHeight
20
+ vv ? vv.height + vv.offsetTop : win.innerHeight
18
21
  )
19
22
 
20
23
  useLayoutEffect(() => {
24
+ const win = editor?.getContainerWindow() ?? window
21
25
  const handleResize = () => {
22
- const visualViewport = window.visualViewport
23
- setHeight(() =>
24
- visualViewport ? visualViewport.height + visualViewport.offsetTop : window.innerHeight
25
- )
26
+ const vv = win.visualViewport
27
+ setHeight(() => (vv ? vv.height + vv.offsetTop : win.innerHeight))
26
28
  }
27
29
 
28
- window.visualViewport?.addEventListener('resize', handleResize)
29
- window.visualViewport?.addEventListener('scroll', handleResize)
30
+ win.visualViewport?.addEventListener('resize', handleResize)
31
+ win.visualViewport?.addEventListener('scroll', handleResize)
30
32
 
31
33
  return () => {
32
- window.visualViewport?.removeEventListener('resize', handleResize)
33
- window.visualViewport?.removeEventListener('scroll', handleResize)
34
+ win.visualViewport?.removeEventListener('resize', handleResize)
35
+ win.visualViewport?.removeEventListener('scroll', handleResize)
34
36
  }
35
- }, [])
37
+ }, [editor])
36
38
  return height
37
39
  }
@@ -191,6 +191,16 @@ To remove the watermark, please purchase a license at tldraw.dev.
191
191
  height: 32px;
192
192
  }
193
193
 
194
+ .tl-container[dir='rtl'] .${className} {
195
+ right: auto;
196
+ left: max(var(--tl-space-2), env(safe-area-inset-left));
197
+ }
198
+
199
+ .tl-container[dir='rtl'] .${className}[data-mobile='true'] {
200
+ border-radius: 0px 4px 4px 0px;
201
+ left: max(-2px, calc(env(safe-area-inset-left) - 2px));
202
+ }
203
+
194
204
  .${className}[data-unlicensed='true'] > button {
195
205
  font-size: 100px;
196
206
  position: absolute;
@@ -430,32 +430,58 @@ export class Vec {
430
430
  * @param P - A point not on the line to test.
431
431
  */
432
432
  static NearestPointOnLineThroughPoint(A: VecLike, u: VecLike, P: VecLike): Vec {
433
- return Vec.Mul(u, Vec.Sub(P, A).pry(u)).add(A)
433
+ // Inlined: t = Vec.Sub(P, A).pry(u), return Vec.Mul(u, t).add(A)
434
+ const t = (P.x - A.x) * u.x + (P.y - A.y) * u.y
435
+ return new Vec(A.x + u.x * t, A.y + u.y * t)
434
436
  }
435
437
 
436
438
  static NearestPointOnLineSegment(A: VecLike, B: VecLike, P: VecLike, clamp = true): Vec {
437
- if (Vec.Equals(A, P)) return Vec.From(P)
438
- if (Vec.Equals(B, P)) return Vec.From(P)
439
+ // Parametric projection of P onto segment AB.
440
+ // Inlined: d = Vec.Sub(B, A); t = Vec.Sub(P, A).pry(d) / d.len(); return Vec.Lrp(A, B, t)
441
+ const dx = B.x - A.x
442
+ const dy = B.y - A.y
443
+ const d2 = dx * dx + dy * dy
439
444
 
440
- const u = Vec.Tan(B, A)
441
- const C = Vec.Add(A, Vec.Mul(u, Vec.Sub(P, A).pry(u)))
445
+ if (d2 === 0) return Vec.From(A)
446
+
447
+ let t = ((P.x - A.x) * dx + (P.y - A.y) * dy) / d2
442
448
 
443
449
  if (clamp) {
444
- if (C.x < Math.min(A.x, B.x)) return Vec.Cast(A.x < B.x ? A : B)
445
- if (C.x > Math.max(A.x, B.x)) return Vec.Cast(A.x > B.x ? A : B)
446
- if (C.y < Math.min(A.y, B.y)) return Vec.Cast(A.y < B.y ? A : B)
447
- if (C.y > Math.max(A.y, B.y)) return Vec.Cast(A.y > B.y ? A : B)
450
+ if (t < 0) t = 0
451
+ else if (t > 1) t = 1
448
452
  }
449
453
 
450
- return C
454
+ return new Vec(A.x + t * dx, A.y + t * dy)
451
455
  }
452
456
 
453
457
  static DistanceToLineThroughPoint(A: VecLike, u: VecLike, P: VecLike): number {
454
- return Vec.Dist(P, Vec.NearestPointOnLineThroughPoint(A, u, P))
458
+ // Inlined: Vec.Dist(P, Vec.NearestPointOnLineThroughPoint(A, u, P))
459
+ // Uses |cross(P-A, u)| which equals the perpendicular distance when u is a unit vector.
460
+ const dx = P.x - A.x
461
+ const dy = P.y - A.y
462
+ return Math.abs(dx * u.y - dy * u.x)
455
463
  }
456
464
 
457
465
  static DistanceToLineSegment(A: VecLike, B: VecLike, P: VecLike, clamp = true): number {
458
- return Vec.Dist(P, Vec.NearestPointOnLineSegment(A, B, P, clamp))
466
+ // Inlined: Vec.Dist(P, Vec.NearestPointOnLineSegment(A, B, P, clamp))
467
+ // Computes the nearest point via parametric t-projection then returns the scalar distance,
468
+ // avoiding the intermediate Vec allocation that NearestPointOnLineSegment would create.
469
+ const dx = B.x - A.x
470
+ const dy = B.y - A.y
471
+ const d2 = dx * dx + dy * dy
472
+
473
+ if (d2 === 0) return Vec.Dist(A, P)
474
+
475
+ let t = ((P.x - A.x) * dx + (P.y - A.y) * dy) / d2
476
+
477
+ if (clamp) {
478
+ if (t < 0) t = 0
479
+ else if (t > 1) t = 1
480
+ }
481
+
482
+ const nx = A.x + t * dx - P.x
483
+ const ny = A.y + t * dy - P.y
484
+ return Math.sqrt(nx * nx + ny * ny)
459
485
  }
460
486
 
461
487
  static Snap(A: VecLike, step = 1) {
@@ -476,6 +502,10 @@ export class Vec {
476
502
  return isNaN(A.x) || isNaN(A.y)
477
503
  }
478
504
 
505
+ static IsFinite(A: VecLike): boolean {
506
+ return Number.isFinite(A.x) && Number.isFinite(A.y)
507
+ }
508
+
479
509
  /**
480
510
  * Get the angle from position A to position B.
481
511
  */
@@ -488,14 +518,11 @@ export class Vec {
488
518
  * two vectors, between -π and π. The sign indicates direction of angle.
489
519
  */
490
520
  static AngleBetween(A: VecLike, B: VecLike): number {
521
+ // p = dot(A, B); n = |A| * |B| (uses x*x instead of Math.pow(x, 2))
491
522
  const p = A.x * B.x + A.y * B.y
492
- const n = Math.sqrt(
493
- (Math.pow(A.x, 2) + Math.pow(A.y, 2)) * (Math.pow(B.x, 2) + Math.pow(B.y, 2))
494
- )
523
+ const n = Math.sqrt((A.x * A.x + A.y * A.y) * (B.x * B.x + B.y * B.y))
495
524
  const sign = A.x * B.y - A.y * B.x < 0 ? -1 : 1
496
- const angle = sign * Math.acos(clamp(p / n, -1, 1))
497
-
498
- return angle
525
+ return sign * Math.acos(clamp(p / n, -1, 1))
499
526
  }
500
527
 
501
528
  /**
@@ -506,7 +533,8 @@ export class Vec {
506
533
  * @returns The interpolated point.
507
534
  */
508
535
  static Lrp(A: VecLike, B: VecLike, t: number): Vec {
509
- return Vec.Sub(B, A).mul(t).add(A)
536
+ // Inlined: Vec.Sub(B, A).mul(t).add(A) — note: only interpolates x/y, not z.
537
+ return new Vec(A.x + (B.x - A.x) * t, A.y + (B.y - A.y) * t)
510
538
  }
511
539
 
512
540
  static Med(A: VecLike, B: VecLike): Vec {
@@ -604,14 +632,15 @@ export class Vec {
604
632
  * @param A - The first point.
605
633
  * @param B - The second point.
606
634
  * @param steps - The number of points to return.
635
+ * @param ease - The easing to use.
607
636
  */
608
- static PointsBetween(A: VecModel, B: VecModel, steps = 6): Vec[] {
637
+ static PointsBetween(A: VecModel, B: VecModel, steps = 6, ease = EASINGS.easeInQuad): Vec[] {
609
638
  const results: Vec[] = []
610
639
 
611
640
  for (let i = 0; i < steps; i++) {
612
- const t = EASINGS.easeInQuad(i / (steps - 1))
641
+ const t = ease(i / (steps - 1))
613
642
  const point = Vec.Lrp(A, B, t)
614
- point.z = Math.min(1, 0.5 + Math.abs(0.5 - ease(t)) * 0.65)
643
+ point.z = Math.min(1, 0.5 + Math.abs(0.5 - EASINGS.easeInOutQuad(t)) * 0.65)
615
644
  results.push(point)
616
645
  }
617
646
 
@@ -622,5 +651,3 @@ export class Vec {
622
651
  return new Vec(Math.round(A.x / gridSize) * gridSize, Math.round(A.y / gridSize) * gridSize)
623
652
  }
624
653
  }
625
-
626
- const ease = (t: number) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t)
@@ -57,21 +57,16 @@ export class Arc2d extends Geometry2d {
57
57
  if (t <= 0) return A
58
58
  if (t >= 1) return B
59
59
 
60
- // Get the point (P) on the arc, then pick the nearest of A, B, and P
61
- const P = Vec.Sub(point, _center).uni().mul(radius).add(_center)
62
-
63
- let nearest: Vec | undefined
64
- let dist = Infinity
65
- let d: number
66
- for (const p of [A, B, P]) {
67
- d = Vec.Dist2(point, p)
68
- if (d < dist) {
69
- nearest = p
70
- dist = d
71
- }
72
- }
73
- if (!nearest) throw Error('nearest point not found')
74
- return nearest
60
+ // Inlined: Vec.Sub(point, _center).uni().mul(radius).add(_center)
61
+ // When t is in (0,1), the nearest point is the radial projection of point onto the arc.
62
+ // Previously this also checked min-distance against A and B, but that's unnecessary when
63
+ // t is already in range — the radial projection is always closer.
64
+ const dx = point.x - _center.x
65
+ const dy = point.y - _center.y
66
+ const len = Math.sqrt(dx * dx + dy * dy)
67
+ if (len === 0) return A
68
+ const scale = radius / len
69
+ return new Vec(_center.x + dx * scale, _center.y + dy * scale)
75
70
  }
76
71
 
77
72
  hitTestLineSegment(A: VecLike, B: VecLike): boolean {
@@ -44,9 +44,47 @@ export class Circle2d extends Geometry2d {
44
44
  }
45
45
 
46
46
  nearestPoint(point: VecLike): Vec {
47
+ // Inlined: Vec.Sub(point, _center).uni().mul(radius).add(_center)
48
+ // Computes direction from center to point, normalizes, scales by radius, offsets by center.
47
49
  const { _center, _radius: radius } = this
48
- if (_center.equals(point)) return Vec.AddXY(_center, radius, 0)
49
- return Vec.Sub(point, _center).uni().mul(radius).add(_center)
50
+ const dx = point.x - _center.x
51
+ const dy = point.y - _center.y
52
+ const len = Math.sqrt(dx * dx + dy * dy)
53
+ if (len === 0) return new Vec(_center.x + radius, _center.y)
54
+ const scale = radius / len
55
+ return new Vec(_center.x + dx * scale, _center.y + dy * scale)
56
+ }
57
+
58
+ override distanceToPoint(point: VecLike, hitInside = false): number {
59
+ // Inlined: Math.abs(Vec.Dist(point, _center) - radius)
60
+ // Computes distance from point to center, then subtracts radius for edge distance.
61
+ // Returns negative when inside a filled circle to indicate containment.
62
+ const { _center, _radius: radius } = this
63
+ const dx = point.x - _center.x
64
+ const dy = point.y - _center.y
65
+ const dist = Math.sqrt(dx * dx + dy * dy)
66
+ const distToEdge = dist - radius
67
+ if (distToEdge < 0 && (this.isFilled || hitInside)) {
68
+ return distToEdge
69
+ }
70
+ return Math.abs(distToEdge)
71
+ }
72
+
73
+ override hitTestPoint(point: VecLike, margin = 0, hitInside = false): boolean {
74
+ // Equivalent to: dist = Vec.Dist(point, _center); return dist within [radius - margin, radius + margin]
75
+ // Uses squared distances throughout to avoid any sqrt calls.
76
+ const { _center, _radius: radius } = this
77
+ const dx = point.x - _center.x
78
+ const dy = point.y - _center.y
79
+ const dist2 = dx * dx + dy * dy
80
+ if ((this.isFilled || hitInside) && dist2 <= radius * radius) {
81
+ return true
82
+ }
83
+ const outerR = radius + margin
84
+ if (dist2 > outerR * outerR) return false
85
+ const innerR = radius - margin
86
+ if (innerR <= 0) return true
87
+ return dist2 >= innerR * innerR
50
88
  }
51
89
 
52
90
  hitTestLineSegment(A: VecLike, B: VecLike, distance = 0): boolean {
@@ -69,6 +69,16 @@ export class CubicBezier2d extends Polyline2d {
69
69
  return nearest
70
70
  }
71
71
 
72
+ override distanceToPoint(point: VecLike, _hitInside = false): number {
73
+ const { segments } = this
74
+ let minDist = Infinity
75
+ for (let i = 0; i < segments.length; i++) {
76
+ const d = segments[i].distanceToPoint(point)
77
+ if (d < minDist) minDist = d
78
+ }
79
+ return minDist
80
+ }
81
+
72
82
  getSvgPathData(first = true) {
73
83
  const { _a: a, _b: b, _c: c, _d: d } = this
74
84
  return `${first ? `M ${a.toFixed()} ` : ``} C${b.toFixed()} ${c.toFixed()} ${d.toFixed()}`
@@ -75,6 +75,16 @@ export class CubicSpline2d extends Geometry2d {
75
75
  return nearest
76
76
  }
77
77
 
78
+ override distanceToPoint(point: VecLike, _hitInside = false): number {
79
+ const { segments } = this
80
+ let minDist = Infinity
81
+ for (let i = 0; i < segments.length; i++) {
82
+ const d = segments[i].distanceToPoint(point)
83
+ if (d < minDist) minDist = d
84
+ }
85
+ return minDist
86
+ }
87
+
78
88
  hitTestLineSegment(A: VecLike, B: VecLike): boolean {
79
89
  return this.segments.some((segment) => segment.hitTestLineSegment(A, B))
80
90
  }
@@ -5,9 +5,9 @@ import { Geometry2d } from './Geometry2d'
5
5
  export class Edge2d extends Geometry2d {
6
6
  private _start: Vec
7
7
  private _end: Vec
8
- private _d: Vec
9
- private _u: Vec
10
- private _ul: number
8
+ private _dx: number
9
+ private _dy: number
10
+ private _len2: number
11
11
 
12
12
  constructor(config: { start: Vec; end: Vec }) {
13
13
  super({ ...config, isClosed: false, isFilled: false })
@@ -16,13 +16,14 @@ export class Edge2d extends Geometry2d {
16
16
  this._start = start
17
17
  this._end = end
18
18
 
19
- this._d = start.clone().sub(end) // the delta from start to end
20
- this._u = this._d.clone().uni() // the unit vector of the edge
21
- this._ul = this._u.len() // the length of the unit vector
19
+ // Precomputed segment delta and squared length (replaces Vec.Sub(end, start) and Vec.Len2)
20
+ this._dx = end.x - start.x
21
+ this._dy = end.y - start.y
22
+ this._len2 = this._dx * this._dx + this._dy * this._dy
22
23
  }
23
24
 
24
25
  override getLength() {
25
- return this._d.len()
26
+ return Math.sqrt(this._len2)
26
27
  }
27
28
 
28
29
  override getVertices(): Vec[] {
@@ -30,17 +31,39 @@ export class Edge2d extends Geometry2d {
30
31
  }
31
32
 
32
33
  override nearestPoint(point: VecLike): Vec {
33
- const { _start: start, _end: end, _d: d, _u: u, _ul: l } = this
34
- if (d.len() === 0) return start // start and end are the same
35
- if (l === 0) return start // no length in the unit vector
36
- const k = Vec.Sub(point, start).dpr(u) / l
37
- const cx = start.x + u.x * k
38
- if (cx < Math.min(start.x, end.x)) return start.x < end.x ? start : end
39
- if (cx > Math.max(start.x, end.x)) return start.x > end.x ? start : end
40
- const cy = start.y + u.y * k
41
- if (cy < Math.min(start.y, end.y)) return start.y < end.y ? start : end
42
- if (cy > Math.max(start.y, end.y)) return start.y > end.y ? start : end
43
- return new Vec(cx, cy)
34
+ // Inlined: Vec.NearestPointOnLineSegment(start, end, point)
35
+ // Uses precomputed dx/dy/len2 and parametric t-clamping instead of Vec allocations.
36
+ const { _start: start, _end: end, _dx: dx, _dy: dy, _len2: len2 } = this
37
+ if (len2 === 0) return start
38
+
39
+ const t = ((point.x - start.x) * dx + (point.y - start.y) * dy) / len2
40
+ if (t <= 0) return start
41
+ if (t >= 1) return end
42
+ return new Vec(start.x + dx * t, start.y + dy * t)
43
+ }
44
+
45
+ override distanceToPoint(point: VecLike, _hitInside = false): number {
46
+ // Inlined: Vec.Dist(point, this.nearestPoint(point))
47
+ // Finds nearest point via parametric t-projection then returns scalar distance directly,
48
+ // avoiding the Vec allocation that nearestPoint would create.
49
+ const { _start: start, _end: end, _dx: dx, _dy: dy, _len2: len2 } = this
50
+ if (len2 === 0) return Vec.Dist(point, start)
51
+
52
+ const t = ((point.x - start.x) * dx + (point.y - start.y) * dy) / len2
53
+ let nx: number, ny: number
54
+ if (t <= 0) {
55
+ nx = start.x
56
+ ny = start.y
57
+ } else if (t >= 1) {
58
+ nx = end.x
59
+ ny = end.y
60
+ } else {
61
+ nx = start.x + dx * t
62
+ ny = start.y + dy * t
63
+ }
64
+ const ex = point.x - nx
65
+ const ey = point.y - ny
66
+ return Math.sqrt(ex * ex + ey * ey)
44
67
  }
45
68
 
46
69
  getSvgPathData(first = true) {
@@ -1,6 +1,6 @@
1
1
  import { Box } from '../Box'
2
2
  import { Vec, VecLike } from '../Vec'
3
- import { PI, PI2, clamp, perimeterOfEllipse } from '../utils'
3
+ import { PI, PI2, clamp, perimeterOfEllipse, pointInPolygon } from '../utils'
4
4
  import { Edge2d } from './Edge2d'
5
5
  import { Geometry2d, Geometry2dOptions } from './Geometry2d'
6
6
  import { getVerticesCountForArcLength } from './geometry-constants'
@@ -89,6 +89,19 @@ export class Ellipse2d extends Geometry2d {
89
89
  return nearest
90
90
  }
91
91
 
92
+ override distanceToPoint(point: VecLike, hitInside = false): number {
93
+ const { edges } = this
94
+ let minDist = Infinity
95
+ for (let i = 0; i < edges.length; i++) {
96
+ const d = edges[i].distanceToPoint(point)
97
+ if (d < minDist) minDist = d
98
+ }
99
+ if (this.isClosed && (this.isFilled || hitInside) && pointInPolygon(point, this.vertices)) {
100
+ return -minDist
101
+ }
102
+ return minDist
103
+ }
104
+
92
105
  hitTestLineSegment(A: VecLike, B: VecLike): boolean {
93
106
  return this.edges.some((edge) => edge.hitTestLineSegment(A, B))
94
107
  }
@@ -1,4 +1,5 @@
1
1
  import { Vec, VecLike } from '../Vec'
2
+ import { pointInPolygon } from '../utils'
2
3
  import { Edge2d } from './Edge2d'
3
4
  import { Geometry2d, Geometry2dOptions } from './Geometry2d'
4
5
 
@@ -45,21 +46,68 @@ export class Polyline2d extends Geometry2d {
45
46
  }
46
47
 
47
48
  nearestPoint(A: VecLike): Vec {
49
+ // Inlined: for each segment, Edge2d.nearestPoint(A) + Vec.Dist2(result, A), pick closest.
50
+ // Inlines the per-segment nearest-point math to avoid N Edge2d.nearestPoint Vec allocations;
51
+ // only allocates a single Vec at the end for the best result.
52
+ const { vertices } = this
53
+ let bestX = vertices[0].x
54
+ let bestY = vertices[0].y
55
+ let bestDist2 = (A.x - bestX) * (A.x - bestX) + (A.y - bestY) * (A.y - bestY)
56
+
57
+ const limit = this.isClosed ? vertices.length : vertices.length - 1
58
+ for (let i = 0; i < limit; i++) {
59
+ const start = vertices[i]
60
+ const end = vertices[(i + 1) % vertices.length]
61
+ const dx = end.x - start.x
62
+ const dy = end.y - start.y
63
+ const len2 = dx * dx + dy * dy
64
+
65
+ let nx: number, ny: number
66
+ if (len2 === 0) {
67
+ nx = start.x
68
+ ny = start.y
69
+ } else {
70
+ const t = ((A.x - start.x) * dx + (A.y - start.y) * dy) / len2
71
+ if (t <= 0) {
72
+ nx = start.x
73
+ ny = start.y
74
+ } else if (t >= 1) {
75
+ nx = end.x
76
+ ny = end.y
77
+ } else {
78
+ nx = start.x + dx * t
79
+ ny = start.y + dy * t
80
+ }
81
+ }
82
+
83
+ const ex = A.x - nx
84
+ const ey = A.y - ny
85
+ const d2 = ex * ex + ey * ey
86
+ if (d2 < bestDist2) {
87
+ bestX = nx
88
+ bestY = ny
89
+ bestDist2 = d2
90
+ }
91
+ }
92
+
93
+ return new Vec(bestX, bestY)
94
+ }
95
+
96
+ override hitTestPoint(point: VecLike, margin = 0, hitInside = false): boolean {
97
+ return this.distanceToPoint(point, hitInside) <= margin
98
+ }
99
+
100
+ override distanceToPoint(point: VecLike, hitInside = false): number {
48
101
  const { segments } = this
49
- let nearest = this._points[0]
50
- let dist = Infinity
51
- let p: Vec // current point on segment
52
- let d: number // distance from A to p
102
+ let minDist = Infinity
53
103
  for (let i = 0; i < segments.length; i++) {
54
- p = segments[i].nearestPoint(A)
55
- d = Vec.Dist2(p, A)
56
- if (d < dist) {
57
- nearest = p
58
- dist = d
59
- }
104
+ const d = segments[i].distanceToPoint(point)
105
+ if (d < minDist) minDist = d
106
+ }
107
+ if (this.isClosed && (this.isFilled || hitInside) && pointInPolygon(point, this.vertices)) {
108
+ return -minDist
60
109
  }
61
- if (!nearest) throw Error('nearest point not found')
62
- return nearest
110
+ return minDist
63
111
  }
64
112
 
65
113
  hitTestLineSegment(A: VecLike, B: VecLike, distance = 0): boolean {
@@ -1,6 +1,6 @@
1
1
  import { Box } from '../Box'
2
2
  import { Vec, VecLike } from '../Vec'
3
- import { PI } from '../utils'
3
+ import { PI, pointInPolygon } from '../utils'
4
4
  import { Arc2d } from './Arc2d'
5
5
  import { Edge2d } from './Edge2d'
6
6
  import { Geometry2d, Geometry2dOptions } from './Geometry2d'
@@ -83,6 +83,19 @@ export class Stadium2d extends Geometry2d {
83
83
  return nearest
84
84
  }
85
85
 
86
+ override distanceToPoint(point: VecLike, hitInside = false): number {
87
+ const { _a: a, _b: b, _c: c, _d: d } = this
88
+ let minDist = Infinity
89
+ for (const part of [a, b, c, d]) {
90
+ const dist = part.distanceToPoint(point)
91
+ if (dist < minDist) minDist = dist
92
+ }
93
+ if (this.isClosed && (this.isFilled || hitInside) && pointInPolygon(point, this.vertices)) {
94
+ return -minDist
95
+ }
96
+ return minDist
97
+ }
98
+
86
99
  hitTestLineSegment(A: VecLike, B: VecLike): boolean {
87
100
  const { _a: a, _b: b, _c: c, _d: d } = this
88
101
  return [a, b, c, d].some((edge) => edge.hitTestLineSegment(A, B))