@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
@@ -63,9 +63,9 @@ beforeEach(() => {
63
63
  })
64
64
 
65
65
  describe('getExportDefaultBounds', () => {
66
- it('returns null when no rendering shapes provided', () => {
66
+ it('returns null box when no rendering shapes provided', () => {
67
67
  const result = getExportDefaultBounds(editor, [], 32, null)
68
- expect(result).toBeNull()
68
+ expect(result.box).toBeNull()
69
69
  })
70
70
 
71
71
  it('returns bounds for single shape with padding', () => {
@@ -83,12 +83,13 @@ describe('getExportDefaultBounds', () => {
83
83
 
84
84
  const result = getExportDefaultBounds(editor, [testShape], 32, null)
85
85
 
86
- expect(result).toBeInstanceOf(Box)
86
+ expect(result.box).toBeInstanceOf(Box)
87
+ expect(result.paddingApplied).toBe(true)
87
88
  // Bounds should include 32px padding on all sides
88
- expect(result?.x).toBe(10 - 32) // -22
89
- expect(result?.y).toBe(20 - 32) // -12
90
- expect(result?.w).toBe(100 + 64) // 164 (32px on each side)
91
- expect(result?.h).toBe(80 + 64) // 144 (32px on each side)
89
+ expect(result.box?.x).toBe(10 - 32) // -22
90
+ expect(result.box?.y).toBe(20 - 32) // -12
91
+ expect(result.box?.w).toBe(100 + 64) // 164 (32px on each side)
92
+ expect(result.box?.h).toBe(80 + 64) // 144 (32px on each side)
92
93
  })
93
94
 
94
95
  it('returns union bounds for multiple shapes with padding', () => {
@@ -116,12 +117,12 @@ describe('getExportDefaultBounds', () => {
116
117
 
117
118
  const result = getExportDefaultBounds(editor, testShapes, 32, null)
118
119
 
119
- expect(result).toBeInstanceOf(Box)
120
+ expect(result.box).toBeInstanceOf(Box)
120
121
  // Raw bounds would be (0,0) to (90,90), with 32px padding on all sides
121
- expect(result?.x).toBe(0 - 32) // -32
122
- expect(result?.y).toBe(0 - 32) // -32
123
- expect(result?.w).toBe(90 + 64) // 154
124
- expect(result?.h).toBe(90 + 64) // 154
122
+ expect(result.box?.x).toBe(0 - 32) // -32
123
+ expect(result.box?.y).toBe(0 - 32) // -32
124
+ expect(result.box?.w).toBe(90 + 64) // 154
125
+ expect(result.box?.h).toBe(90 + 64) // 154
125
126
  })
126
127
 
127
128
  it('handles shapes with transforms correctly', () => {
@@ -142,10 +143,10 @@ describe('getExportDefaultBounds', () => {
142
143
 
143
144
  const result = getExportDefaultBounds(editor, [testShape], 32, null)
144
145
 
145
- expect(result).toBeInstanceOf(Box)
146
+ expect(result.box).toBeInstanceOf(Box)
146
147
  // The rotated shape should have expanded bounds, plus padding
147
- expect(result!.w).toBeGreaterThan(50 + 64)
148
- expect(result!.h).toBeGreaterThan(40 + 64)
148
+ expect(result.box!.w).toBeGreaterThan(50 + 64)
149
+ expect(result.box!.h).toBeGreaterThan(40 + 64)
149
150
  })
150
151
 
151
152
  it('handles multiple overlapping shapes correctly', () => {
@@ -183,12 +184,12 @@ describe('getExportDefaultBounds', () => {
183
184
 
184
185
  const result = getExportDefaultBounds(editor, testShapes, 32, null)
185
186
 
186
- expect(result).toBeInstanceOf(Box)
187
+ expect(result.box).toBeInstanceOf(Box)
187
188
  // Raw bounds would be (0,0) to (60,60), with 32px padding on all sides
188
- expect(result?.x).toBe(0 - 32) // -32
189
- expect(result?.y).toBe(0 - 32) // -32
190
- expect(result?.w).toBe(60 + 64) // 124 (32px on each side)
191
- expect(result?.h).toBe(60 + 64) // 124 (32px on each side)
189
+ expect(result.box?.x).toBe(0 - 32) // -32
190
+ expect(result.box?.y).toBe(0 - 32) // -32
191
+ expect(result.box?.w).toBe(60 + 64) // 124 (32px on each side)
192
+ expect(result.box?.h).toBe(60 + 64) // 124 (32px on each side)
192
193
  })
193
194
 
194
195
  it('handles complex geometry with multiple shapes', () => {
@@ -226,17 +227,17 @@ describe('getExportDefaultBounds', () => {
226
227
 
227
228
  const result = getExportDefaultBounds(editor, testShapes, 32, null)
228
229
 
229
- expect(result).toBeInstanceOf(Box)
230
+ expect(result.box).toBeInstanceOf(Box)
230
231
 
231
232
  // The bounds should encompass:
232
233
  // - shape1: (0, 0) to (50, 50)
233
234
  // - shape2: (100, 100) to (160, 140)
234
235
  // - shape3: (200, 50) to (240, 130)
235
236
  // Raw total bounds: (0, 0) to (240, 140), with 32px padding on all sides
236
- expect(result!.x).toBe(0 - 32) // -32 (leftmost edge with padding)
237
- expect(result!.y).toBe(0 - 32) // -32 (topmost edge with padding)
238
- expect(result!.w).toBe(240 + 64) // 304 (width + 32px on each side)
239
- expect(result!.h).toBe(140 + 64) // 204 (height + 32px on each side)
237
+ expect(result.box!.x).toBe(0 - 32) // -32 (leftmost edge with padding)
238
+ expect(result.box!.y).toBe(0 - 32) // -32 (topmost edge with padding)
239
+ expect(result.box!.w).toBe(240 + 64) // 304 (width + 32px on each side)
240
+ expect(result.box!.h).toBe(140 + 64) // 204 (height + 32px on each side)
240
241
  })
241
242
 
242
243
  it('handles empty rendering shapes array after filtering', () => {
@@ -253,7 +254,7 @@ describe('getExportDefaultBounds', () => {
253
254
  // Pass empty array to simulate filtered out shapes
254
255
  const result = getExportDefaultBounds(editor, [], 32, null)
255
256
 
256
- expect(result).toBeNull()
257
+ expect(result.box).toBeNull()
257
258
  })
258
259
 
259
260
  it('does not apply padding when exporting single frame shape', () => {
@@ -272,12 +273,13 @@ describe('getExportDefaultBounds', () => {
272
273
  // Pass the shape ID as singleFrameShapeId to simulate single frame export
273
274
  const result = getExportDefaultBounds(editor, [testShape], 32, shapeId)
274
275
 
275
- expect(result).toBeInstanceOf(Box)
276
+ expect(result.box).toBeInstanceOf(Box)
277
+ expect(result.paddingApplied).toBe(false)
276
278
  // No padding should be applied
277
- expect(result?.x).toBe(10)
278
- expect(result?.y).toBe(20)
279
- expect(result?.w).toBe(100)
280
- expect(result?.h).toBe(80)
279
+ expect(result.box?.x).toBe(10)
280
+ expect(result.box?.y).toBe(20)
281
+ expect(result.box?.w).toBe(100)
282
+ expect(result.box?.h).toBe(80)
281
283
  })
282
284
 
283
285
  describe('isExportBoundsContainer behavior', () => {
@@ -306,12 +308,12 @@ describe('getExportDefaultBounds', () => {
306
308
 
307
309
  const result = getExportDefaultBounds(editor, testShapes, 32, null)
308
310
 
309
- expect(result).toBeInstanceOf(Box)
311
+ expect(result.box).toBeInstanceOf(Box)
310
312
  // Raw bounds: (0,0) to (50,50), with padding
311
- expect(result?.x).toBe(-32)
312
- expect(result?.y).toBe(-32)
313
- expect(result?.w).toBe(50 + 64) // 114
314
- expect(result?.h).toBe(50 + 64) // 114
313
+ expect(result.box?.x).toBe(-32)
314
+ expect(result.box?.y).toBe(-32)
315
+ expect(result.box?.w).toBe(50 + 64) // 114
316
+ expect(result.box?.h).toBe(50 + 64) // 114
315
317
  })
316
318
 
317
319
  it('skips padding when container shape contains all other shapes', () => {
@@ -352,12 +354,12 @@ describe('getExportDefaultBounds', () => {
352
354
 
353
355
  const result = getExportDefaultBounds(editor, testShapes, 32, null)
354
356
 
355
- expect(result).toBeInstanceOf(Box)
357
+ expect(result.box).toBeInstanceOf(Box)
356
358
  // Should use container bounds without padding: (0,0) to (100,100)
357
- expect(result?.x).toBe(0)
358
- expect(result?.y).toBe(0)
359
- expect(result?.w).toBe(100)
360
- expect(result?.h).toBe(100)
359
+ expect(result.box?.x).toBe(0)
360
+ expect(result.box?.y).toBe(0)
361
+ expect(result.box?.w).toBe(100)
362
+ expect(result.box?.h).toBe(100)
361
363
  })
362
364
 
363
365
  it('applies padding when container does not contain all shapes', () => {
@@ -399,12 +401,12 @@ describe('getExportDefaultBounds', () => {
399
401
 
400
402
  const result = getExportDefaultBounds(editor, testShapes, 32, null)
401
403
 
402
- expect(result).toBeInstanceOf(Box)
404
+ expect(result.box).toBeInstanceOf(Box)
403
405
  // Total bounds: (0,0) to (100,100), with padding applied
404
- expect(result?.x).toBe(-32)
405
- expect(result?.y).toBe(-32)
406
- expect(result?.w).toBe(100 + 64) // 164
407
- expect(result?.h).toBe(100 + 64) // 164
406
+ expect(result.box?.x).toBe(-32)
407
+ expect(result.box?.y).toBe(-32)
408
+ expect(result.box?.w).toBe(100 + 64) // 164
409
+ expect(result.box?.h).toBe(100 + 64) // 164
408
410
  })
409
411
 
410
412
  it('works with multiple containers where one contains all', () => {
@@ -446,12 +448,12 @@ describe('getExportDefaultBounds', () => {
446
448
 
447
449
  const result = getExportDefaultBounds(editor, testShapes, 32, null)
448
450
 
449
- expect(result).toBeInstanceOf(Box)
451
+ expect(result.box).toBeInstanceOf(Box)
450
452
  // Should use the large container's bounds without padding
451
- expect(result?.x).toBe(0)
452
- expect(result?.y).toBe(0)
453
- expect(result?.w).toBe(100)
454
- expect(result?.h).toBe(100)
453
+ expect(result.box?.x).toBe(0)
454
+ expect(result.box?.y).toBe(0)
455
+ expect(result.box?.w).toBe(100)
456
+ expect(result.box?.h).toBe(100)
455
457
  })
456
458
 
457
459
  it('container behavior is overridden by single frame shape', () => {
@@ -482,12 +484,12 @@ describe('getExportDefaultBounds', () => {
482
484
  // Single frame shape logic takes precedence over container logic
483
485
  const result = getExportDefaultBounds(editor, testShapes, 32, containerId)
484
486
 
485
- expect(result).toBeInstanceOf(Box)
487
+ expect(result.box).toBeInstanceOf(Box)
486
488
  // Should use total bounds without padding (single frame overrides container)
487
- expect(result?.x).toBe(0)
488
- expect(result?.y).toBe(0)
489
- expect(result?.w).toBe(100)
490
- expect(result?.h).toBe(100)
489
+ expect(result.box?.x).toBe(0)
490
+ expect(result.box?.y).toBe(0)
491
+ expect(result.box?.w).toBe(100)
492
+ expect(result.box?.h).toBe(100)
491
493
  })
492
494
 
493
495
  it('handles containers with inner shapes correctly', () => {
@@ -517,13 +519,13 @@ describe('getExportDefaultBounds', () => {
517
519
 
518
520
  const result = getExportDefaultBounds(editor, testShapes, 32, null)
519
521
 
520
- expect(result).toBeInstanceOf(Box)
522
+ expect(result.box).toBeInstanceOf(Box)
521
523
  // Container (0,0,200,120) should contain inner shape bounds,
522
524
  // so no padding should be applied
523
- expect(result?.x).toBe(0)
524
- expect(result?.y).toBe(0)
525
- expect(result?.w).toBe(200)
526
- expect(result?.h).toBe(120)
525
+ expect(result.box?.x).toBe(0)
526
+ expect(result.box?.y).toBe(0)
527
+ expect(result.box?.w).toBe(200)
528
+ expect(result.box?.h).toBe(120)
527
529
  })
528
530
 
529
531
  it('handles order sensitivity - container processed first', () => {
@@ -553,12 +555,12 @@ describe('getExportDefaultBounds', () => {
553
555
 
554
556
  const result = getExportDefaultBounds(editor, testShapes, 32, null)
555
557
 
556
- expect(result).toBeInstanceOf(Box)
558
+ expect(result.box).toBeInstanceOf(Box)
557
559
  // Container should contain regular shape, no padding applied
558
- expect(result?.x).toBe(0)
559
- expect(result?.y).toBe(0)
560
- expect(result?.w).toBe(100)
561
- expect(result?.h).toBe(100)
560
+ expect(result.box?.x).toBe(0)
561
+ expect(result.box?.y).toBe(0)
562
+ expect(result.box?.w).toBe(100)
563
+ expect(result.box?.h).toBe(100)
562
564
  })
563
565
 
564
566
  it('handles order sensitivity - regular shape processed first', () => {
@@ -588,12 +590,12 @@ describe('getExportDefaultBounds', () => {
588
590
 
589
591
  const result = getExportDefaultBounds(editor, testShapes, 32, null)
590
592
 
591
- expect(result).toBeInstanceOf(Box)
593
+ expect(result.box).toBeInstanceOf(Box)
592
594
  // Container should still contain regular shape, no padding applied
593
- expect(result?.x).toBe(0)
594
- expect(result?.y).toBe(0)
595
- expect(result?.w).toBe(100)
596
- expect(result?.h).toBe(100)
595
+ expect(result.box?.x).toBe(0)
596
+ expect(result.box?.y).toBe(0)
597
+ expect(result.box?.w).toBe(100)
598
+ expect(result.box?.h).toBe(100)
597
599
  })
598
600
 
599
601
  it('multiple containers - only one that contains all others skips padding', () => {
@@ -635,12 +637,12 @@ describe('getExportDefaultBounds', () => {
635
637
 
636
638
  const result = getExportDefaultBounds(editor, testShapes, 32, null)
637
639
 
638
- expect(result).toBeInstanceOf(Box)
640
+ expect(result.box).toBeInstanceOf(Box)
639
641
  // Large container contains everything (including small container), no padding
640
- expect(result?.x).toBe(0)
641
- expect(result?.y).toBe(0)
642
- expect(result?.w).toBe(100)
643
- expect(result?.h).toBe(100)
642
+ expect(result.box?.x).toBe(0)
643
+ expect(result.box?.y).toBe(0)
644
+ expect(result.box?.w).toBe(100)
645
+ expect(result.box?.h).toBe(100)
644
646
  })
645
647
 
646
648
  it('multiple containers - none contains all others, padding applied', () => {
@@ -692,13 +694,13 @@ describe('getExportDefaultBounds', () => {
692
694
 
693
695
  const result = getExportDefaultBounds(editor, testShapes, 32, null)
694
696
 
695
- expect(result).toBeInstanceOf(Box)
697
+ expect(result.box).toBeInstanceOf(Box)
696
698
  // No single container contains all others, padding should be applied
697
699
  // Total bounds: (0,0) to (100,100), with padding
698
- expect(result?.x).toBe(-32)
699
- expect(result?.y).toBe(-32)
700
- expect(result?.w).toBe(100 + 64) // 164
701
- expect(result?.h).toBe(100 + 64) // 164
700
+ expect(result.box?.x).toBe(-32)
701
+ expect(result.box?.y).toBe(-32)
702
+ expect(result.box?.w).toBe(100 + 64) // 164
703
+ expect(result.box?.h).toBe(100 + 64) // 164
702
704
  })
703
705
 
704
706
  it('container covers most but not all shapes - padding applied', () => {
@@ -740,13 +742,13 @@ describe('getExportDefaultBounds', () => {
740
742
 
741
743
  const result = getExportDefaultBounds(editor, testShapes, 32, null)
742
744
 
743
- expect(result).toBeInstanceOf(Box)
745
+ expect(result.box).toBeInstanceOf(Box)
744
746
  // Container doesn't contain all shapes, padding applied
745
747
  // Total bounds: (0,0) to (90,90), with padding
746
- expect(result?.x).toBe(-32)
747
- expect(result?.y).toBe(-32)
748
- expect(result?.w).toBe(90 + 64) // 154
749
- expect(result?.h).toBe(90 + 64) // 154
748
+ expect(result.box?.x).toBe(-32)
749
+ expect(result.box?.y).toBe(-32)
750
+ expect(result.box?.w).toBe(90 + 64) // 154
751
+ expect(result.box?.h).toBe(90 + 64) // 154
750
752
  })
751
753
 
752
754
  it('nested containers - inner container processed first', () => {
@@ -788,12 +790,12 @@ describe('getExportDefaultBounds', () => {
788
790
 
789
791
  const result = getExportDefaultBounds(editor, testShapes, 32, null)
790
792
 
791
- expect(result).toBeInstanceOf(Box)
793
+ expect(result.box).toBeInstanceOf(Box)
792
794
  // Outer container contains everything, should use outer bounds without padding
793
- expect(result?.x).toBe(0)
794
- expect(result?.y).toBe(0)
795
- expect(result?.w).toBe(100)
796
- expect(result?.h).toBe(100)
795
+ expect(result.box?.x).toBe(0)
796
+ expect(result.box?.y).toBe(0)
797
+ expect(result.box?.w).toBe(100)
798
+ expect(result.box?.h).toBe(100)
797
799
  })
798
800
 
799
801
  it('container-only shapes should not skip padding', () => {
@@ -822,13 +824,13 @@ describe('getExportDefaultBounds', () => {
822
824
 
823
825
  const result = getExportDefaultBounds(editor, testShapes, 32, null)
824
826
 
825
- expect(result).toBeInstanceOf(Box)
827
+ expect(result.box).toBeInstanceOf(Box)
826
828
  // Neither container fully contains the other, padding should be applied
827
829
  // Total bounds: (0,0) to (80,80), with padding
828
- expect(result?.x).toBe(-32)
829
- expect(result?.y).toBe(-32)
830
- expect(result?.w).toBe(80 + 64) // 144
831
- expect(result?.h).toBe(80 + 64) // 144
830
+ expect(result.box?.x).toBe(-32)
831
+ expect(result.box?.y).toBe(-32)
832
+ expect(result.box?.w).toBe(80 + 64) // 144
833
+ expect(result.box?.h).toBe(80 + 64) // 144
832
834
  })
833
835
 
834
836
  it('single container with only itself skips padding', () => {
@@ -848,12 +850,12 @@ describe('getExportDefaultBounds', () => {
848
850
 
849
851
  const result = getExportDefaultBounds(editor, testShapes, 32, null)
850
852
 
851
- expect(result).toBeInstanceOf(Box)
853
+ expect(result.box).toBeInstanceOf(Box)
852
854
  // Single container should skip padding (it trivially contains "all other shapes")
853
- expect(result?.x).toBe(10)
854
- expect(result?.y).toBe(20)
855
- expect(result?.w).toBe(100)
856
- expect(result?.h).toBe(80)
855
+ expect(result.box?.x).toBe(10)
856
+ expect(result.box?.y).toBe(20)
857
+ expect(result.box?.w).toBe(100)
858
+ expect(result.box?.h).toBe(80)
857
859
  })
858
860
  })
859
861
  })
@@ -37,16 +37,23 @@ import { Mat } from '../primitives/Mat'
37
37
  import { ExportDelay } from './ExportDelay'
38
38
 
39
39
  export function getSvgJsx(editor: Editor, ids: TLShapeId[], opts: TLImageExportOptions = {}) {
40
- if (!window.document) throw Error('No document')
40
+ const editorDocument = editor.getContainerDocument()
41
+ if (!editorDocument) throw Error('No document')
41
42
 
42
43
  const {
43
44
  scale = 1,
44
45
  // should we include the background in the export? or is it transparent?
45
46
  background = editor.getInstanceState().exportBackground,
46
- padding = editor.options.defaultSvgPadding,
47
47
  preserveAspectRatio,
48
48
  } = opts
49
49
 
50
+ // Resolve the padding mode:
51
+ // - 'auto' (or undefined): render with default padding, then trim to actual visual content
52
+ // - number: fixed padding, no trimming
53
+ const isAutoTrim = typeof opts.padding !== 'number'
54
+ const renderPadding =
55
+ typeof opts.padding === 'number' ? opts.padding : editor.options.defaultSvgPadding
56
+
50
57
  const isDarkMode = opts.darkMode ?? editor.user.getIsDarkMode()
51
58
 
52
59
  // ---Figure out which shapes we need to include
@@ -60,21 +67,36 @@ export function getSvgJsx(editor: Editor, ids: TLShapeId[], opts: TLImageExportO
60
67
  ids.length === 1 && editor.isShapeOfType(editor.getShape(ids[0])!, 'frame') ? ids[0] : null
61
68
 
62
69
  let bbox: null | Box = null
70
+ let paddingWasApplied = false
63
71
  if (opts.bounds) {
64
- bbox = opts.bounds.clone().expandBy(padding)
72
+ // Explicit bounds: use exact bounds when auto, expand by padding when fixed
73
+ bbox = isAutoTrim ? opts.bounds.clone() : opts.bounds.clone().expandBy(renderPadding)
65
74
  } else {
66
- bbox = getExportDefaultBounds(editor, renderingShapes, padding, singleFrameShapeId)
75
+ const result = getExportDefaultBounds(
76
+ editor,
77
+ renderingShapes,
78
+ renderPadding,
79
+ singleFrameShapeId
80
+ )
81
+ bbox = result.box
82
+ paddingWasApplied = result.paddingApplied
67
83
  }
68
84
 
69
85
  // no unmasked shapes to export
70
86
  if (!bbox) return
71
87
 
88
+ // When auto-trim is active and padding was applied by getExportDefaultBounds,
89
+ // the padding region is trimmable: exports will scan pixels from each edge inward
90
+ // and trim to the actual visual content bounds. This ensures visual overflow
91
+ // (strokes, arrowheads) is captured without unnecessary whitespace.
92
+ const trimPadding = isAutoTrim && paddingWasApplied ? renderPadding : 0
93
+
72
94
  // We want the svg image to be BIGGER THAN USUAL to account for image quality
73
95
  const w = bbox.width * scale
74
96
  const h = bbox.height * scale
75
97
 
76
98
  try {
77
- document.body.focus?.() // weird but necessary
99
+ editorDocument.body.focus?.() // weird but necessary
78
100
  } catch {
79
101
  // not implemented
80
102
  }
@@ -102,7 +124,7 @@ export function getSvgJsx(editor: Editor, ids: TLShapeId[], opts: TLImageExportO
102
124
  </SvgExport>
103
125
  )
104
126
 
105
- return { jsx: svg, width: w, height: h, exportDelay }
127
+ return { jsx: svg, width: w, height: h, exportDelay, trimPadding }
106
128
  }
107
129
 
108
130
  /**
@@ -126,7 +148,7 @@ export function getExportDefaultBounds(
126
148
  renderingShapes: TLRenderingShape[],
127
149
  padding: number,
128
150
  singleFrameShapeId: TLShapeId | null
129
- ) {
151
+ ): { box: Box; paddingApplied: boolean } | { box: null; paddingApplied: false } {
130
152
  let isBoundedByContainer = false
131
153
  let bbox: null | Box = null
132
154
 
@@ -162,16 +184,17 @@ export function getExportDefaultBounds(
162
184
  }
163
185
 
164
186
  // No unmasked shapes to export
165
- if (!bbox) return null
187
+ if (!bbox) return { box: null, paddingApplied: false }
166
188
 
167
189
  // Only apply padding if:
168
190
  // - Not exporting a single frame (frames have their own padding rules)
169
191
  // - Not bounded by a container (containers define their own bounds precisely)
170
- if (!singleFrameShapeId && !isBoundedByContainer) {
192
+ const paddingApplied = !singleFrameShapeId && !isBoundedByContainer
193
+ if (paddingApplied) {
171
194
  bbox.expandBy(padding)
172
195
  }
173
196
 
174
- return bbox
197
+ return { box: bbox, paddingApplied }
175
198
  }
176
199
 
177
200
  function SvgExport({
@@ -1,4 +1,5 @@
1
1
  import { atom } from '@tldraw/state'
2
+ import { getGlobalWindow } from '../utils/dom'
2
3
 
3
4
  /**
4
5
  * An object that contains information about the current device and environment.
@@ -27,7 +28,7 @@ if (typeof window !== 'undefined') {
27
28
  tlenv.isChromeForIos = /crios.*safari/i.test(navigator.userAgent)
28
29
  tlenv.isFirefox = /firefox/i.test(navigator.userAgent)
29
30
  tlenv.isAndroid = /android/i.test(navigator.userAgent)
30
- tlenv.isDarwin = window.navigator.userAgent.toLowerCase().indexOf('mac') > -1
31
+ tlenv.isDarwin = getGlobalWindow().navigator.userAgent.toLowerCase().indexOf('mac') > -1
31
32
  }
32
33
  tlenv.hasCanvasSupport = 'Promise' in window && 'HTMLCanvasElement' in window
33
34
  isForcedFinePointer = tlenv.isFirefox && !tlenv.isAndroid && !tlenv.isIos
@@ -48,7 +49,7 @@ const tlenvReactive = atom('tlenvReactive', {
48
49
  })
49
50
 
50
51
  if (typeof window !== 'undefined' && !isForcedFinePointer) {
51
- const mql = window.matchMedia && window.matchMedia('(any-pointer: coarse)')
52
+ const mql = getGlobalWindow().matchMedia && getGlobalWindow().matchMedia('(any-pointer: coarse)')
52
53
 
53
54
  const isCurrentCoarsePointer = () => tlenvReactive.__unsafe__getWithoutCapture().isCoarsePointer
54
55
 
@@ -66,7 +67,7 @@ if (typeof window !== 'undefined' && !isForcedFinePointer) {
66
67
 
67
68
  // 2. Also update the coarse pointer state when a pointer down event occurs. We need `capture: true`
68
69
  // here because the tldraw component itself stops propagation on pointer events it receives.
69
- window.addEventListener(
70
+ getGlobalWindow().addEventListener(
70
71
  'pointerdown',
71
72
  (e: PointerEvent) => {
72
73
  // when the user interacts with a mouse, we assume they have a fine pointer.
@@ -13,7 +13,7 @@ import { useEditor } from './useEditor'
13
13
 
14
14
  export function useCanvasEvents() {
15
15
  const editor = useEditor()
16
- const ownerDocument = editor.getContainer().ownerDocument
16
+ const ownerDocument = editor.getContainerDocument()
17
17
  const currentTool = useValue('current tool', () => editor.getCurrentTool(), [editor])
18
18
 
19
19
  const events = useMemo(
@@ -80,8 +80,7 @@ export function useCanvasEvents() {
80
80
  function onTouchEnd(e: React.TouchEvent) {
81
81
  if (editor.wasEventAlreadyHandled(e)) return
82
82
  editor.markEventAsHandled(e)
83
- // check that e.target is an HTMLElement
84
- if (!(e.target instanceof HTMLElement)) return
83
+ if (!(e.target instanceof editor.getContainerWindow().HTMLElement)) return
85
84
 
86
85
  const editingShapeId = editor.getEditingShapeId()
87
86
  if (
@@ -46,7 +46,8 @@ export function useDocumentEvents() {
46
46
  }, [container])
47
47
 
48
48
  useEffect(() => {
49
- if (typeof window === 'undefined' || !('matchMedia' in window)) return
49
+ const win = editor.getContainerWindow()
50
+ if (!('matchMedia' in win)) return
50
51
 
51
52
  // https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio#monitoring_screen_resolution_or_zoom_level_changes
52
53
  let remove: (() => void) | null = null
@@ -54,8 +55,8 @@ export function useDocumentEvents() {
54
55
  if (remove != null) {
55
56
  remove()
56
57
  }
57
- const mqString = `(resolution: ${window.devicePixelRatio}dppx)`
58
- const media = matchMedia(mqString)
58
+ const mqString = `(resolution: ${win.devicePixelRatio}dppx)`
59
+ const media = win.matchMedia(mqString)
59
60
  // Safari only started supporting `addEventListener('change',...) in version 14
60
61
  // https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList/change_event
61
62
  const safariCb = (ev: any) => {
@@ -79,7 +80,7 @@ export function useDocumentEvents() {
79
80
  media.removeListener(safariCb)
80
81
  }
81
82
  }
82
- editor.updateInstanceState({ devicePixelRatio: window.devicePixelRatio })
83
+ editor.updateInstanceState({ devicePixelRatio: win.devicePixelRatio })
83
84
  }
84
85
  updatePixelRatio()
85
86
  return () => {
@@ -275,9 +276,10 @@ export function useDocumentEvents() {
275
276
 
276
277
  container.addEventListener('wheel', handleWheel, { passive: false })
277
278
 
278
- document.addEventListener('gesturestart', preventDefault)
279
- document.addEventListener('gesturechange', preventDefault)
280
- document.addEventListener('gestureend', preventDefault)
279
+ const ownerDoc = container.ownerDocument
280
+ ownerDoc.addEventListener('gesturestart', preventDefault)
281
+ ownerDoc.addEventListener('gesturechange', preventDefault)
282
+ ownerDoc.addEventListener('gestureend', preventDefault)
281
283
 
282
284
  container.addEventListener('keydown', handleKeyDown)
283
285
  container.addEventListener('keyup', handleKeyUp)
@@ -287,9 +289,9 @@ export function useDocumentEvents() {
287
289
 
288
290
  container.removeEventListener('wheel', handleWheel)
289
291
 
290
- document.removeEventListener('gesturestart', preventDefault)
291
- document.removeEventListener('gesturechange', preventDefault)
292
- document.removeEventListener('gestureend', preventDefault)
292
+ ownerDoc.removeEventListener('gesturestart', preventDefault)
293
+ ownerDoc.removeEventListener('gesturechange', preventDefault)
294
+ ownerDoc.removeEventListener('gestureend', preventDefault)
293
295
 
294
296
  container.removeEventListener('keydown', handleKeyDown)
295
297
  container.removeEventListener('keyup', handleKeyUp)
@@ -298,5 +300,8 @@ export function useDocumentEvents() {
298
300
  }
299
301
 
300
302
  function areShortcutsDisabled(editor: Editor) {
301
- return editor.menus.hasOpenMenus() || activeElementShouldCaptureKeys()
303
+ return (
304
+ editor.menus.hasOpenMenus() ||
305
+ activeElementShouldCaptureKeys(true, editor.getContainerDocument())
306
+ )
302
307
  }
@@ -15,14 +15,14 @@ export function useFixSafariDoubleTapZoomPencilEvents(ref: React.RefObject<HTMLE
15
15
 
16
16
  if (!elm) return
17
17
 
18
+ const win = editor.getContainerWindow()
18
19
  const handleEvent = (e: PointerEvent | TouchEvent) => {
19
- if (e instanceof PointerEvent && e.pointerType === 'pen') {
20
+ if (e instanceof win.PointerEvent && e.pointerType === 'pen') {
20
21
  editor.markEventAsHandled(e)
21
22
  const { target } = e
22
23
 
23
- // Allow events to propagate if the app is editing a shape, or if the event is occurring in a text area or input
24
24
  if (
25
- elementShouldCaptureKeys(target instanceof Element ? target : null, false) ||
25
+ elementShouldCaptureKeys(target instanceof win.Element ? target : null, false) ||
26
26
  editor.isIn('select.editing_shape')
27
27
  ) {
28
28
  return
@@ -1,5 +1,6 @@
1
1
  import { throttle } from '@tldraw/utils'
2
2
  import { useLayoutEffect } from 'react'
3
+ import { getOwnerWindow } from '../exports/domUtils'
3
4
  import { useEditor } from './useEditor'
4
5
 
5
6
  export function useScreenBounds(ref: React.RefObject<HTMLElement | null>) {
@@ -21,7 +22,8 @@ export function useScreenBounds(ref: React.RefObject<HTMLElement | null>) {
21
22
  // Rather than running getClientRects on every frame, we'll
22
23
  // run it once a second or when the window resizes.
23
24
  const interval = editor.timers.setInterval(updateBounds, 1000)
24
- window.addEventListener('resize', updateBounds)
25
+ const win = editor.getContainerWindow()
26
+ win.addEventListener('resize', updateBounds)
25
27
 
26
28
  const resizeObserver = new ResizeObserver((entries) => {
27
29
  if (!entries[0].contentRect) return
@@ -42,7 +44,7 @@ export function useScreenBounds(ref: React.RefObject<HTMLElement | null>) {
42
44
 
43
45
  return () => {
44
46
  clearInterval(interval)
45
- window.removeEventListener('resize', updateBounds)
47
+ win.removeEventListener('resize', updateBounds)
46
48
  resizeObserver.disconnect()
47
49
  scrollingParent?.removeEventListener('scroll', updateBounds)
48
50
  updateBounds.cancel()
@@ -56,12 +58,14 @@ export function useScreenBounds(ref: React.RefObject<HTMLElement | null>) {
56
58
  * https://github.com/excalidraw/excalidraw/blob/48c3465b19f10ec755b3eb84e21a01a468e96e43/packages/excalidraw/utils.ts#L600
57
59
  */
58
60
  const getNearestScrollableContainer = (element: HTMLElement): HTMLElement | Document => {
61
+ const doc = element.ownerDocument
62
+ const win = getOwnerWindow(element)
59
63
  let parent = element.parentElement
60
64
  while (parent) {
61
- if (parent === document.body) {
62
- return document
65
+ if (parent === doc.body) {
66
+ return doc
63
67
  }
64
- const { overflowY } = window.getComputedStyle(parent)
68
+ const { overflowY } = win.getComputedStyle(parent)
65
69
  const hasScrollableContent = parent.scrollHeight > parent.clientHeight
66
70
  if (
67
71
  hasScrollableContent &&
@@ -71,5 +75,5 @@ const getNearestScrollableContainer = (element: HTMLElement): HTMLElement | Docu
71
75
  }
72
76
  parent = parent.parentElement
73
77
  }
74
- return document
78
+ return doc
75
79
  }