@tldraw/editor 4.5.3 → 4.6.0-canary.0bcbb3ed5bcb

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,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/utils/browserCanvasMaxSize.ts"],
4
- "sourcesContent": ["/** @internal */\nexport interface CanvasMaxSize {\n\tmaxWidth: number\n\tmaxHeight: number\n\tmaxArea: number\n}\n\n// Cache this, only want to do this once per browser session\nlet maxCanvasSizes: CanvasMaxSize | null = null\n\nfunction getBrowserCanvasMaxSize(): CanvasMaxSize {\n\tif (!maxCanvasSizes) {\n\t\tmaxCanvasSizes = {\n\t\t\tmaxWidth: getCanvasSize('width'), // test very wide but 1 pixel tall canvases\n\t\t\tmaxHeight: getCanvasSize('height'), // test very tall but 1 pixel wide canvases\n\t\t\tmaxArea: getCanvasSize('area'), // test square canvases\n\t\t}\n\t}\n\treturn maxCanvasSizes\n}\n\n/*!\n * Extracted from https://github.com/jhildenbiddle/canvas-size\n * MIT License: https://github.com/jhildenbiddle/canvas-size/blob/master/LICENSE\n * Copyright (c) John Hildenbiddle\n */\n\nconst MAX_SAFE_CANVAS_DIMENSION = 8192\nconst MAX_SAFE_CANVAS_AREA = 4096 * 4096\n\nconst TEST_SIZES = {\n\tarea: [\n\t\t// Chrome 70 (Mac, Win)\n\t\t// Chrome 68 (Android 4.4)\n\t\t// Edge 17 (Win)\n\t\t// Safari 7-12 (Mac)\n\t\t16384,\n\t\t// Chrome 68 (Android 7.1-9)\n\t\t14188,\n\t\t// Chrome 68 (Android 5)\n\t\t11402,\n\t\t// Firefox 63 (Mac, Win)\n\t\t11180,\n\t\t// Chrome 68 (Android 6)\n\t\t10836,\n\t\t// IE 9-11 (Win)\n\t\t8192,\n\t\t// IE Mobile (Windows Phone 8.x)\n\t\t// Safari (iOS 9 - 12)\n\t\t4096,\n\t],\n\theight: [\n\t\t// Safari 7-12 (Mac)\n\t\t// Safari (iOS 9-12)\n\t\t8388607,\n\t\t// Chrome 83 (Mac, Win)\n\t\t65535,\n\t\t// Chrome 70 (Mac, Win)\n\t\t// Chrome 68 (Android 4.4-9)\n\t\t// Firefox 63 (Mac, Win)\n\t\t32767,\n\t\t// Edge 17 (Win)\n\t\t// IE11 (Win)\n\t\t16384,\n\t\t// IE 9-10 (Win)\n\t\t8192,\n\t\t// IE Mobile (Windows Phone 8.x)\n\t\t4096,\n\t],\n\twidth: [\n\t\t// Safari 7-12 (Mac)\n\t\t// Safari (iOS 9-12)\n\t\t4194303,\n\t\t// Chrome 83 (Mac, Win)\n\t\t65535,\n\t\t// Chrome 70 (Mac, Win)\n\t\t// Chrome 68 (Android 4.4-9)\n\t\t// Firefox 63 (Mac, Win)\n\t\t32767,\n\t\t// Edge 17 (Win)\n\t\t// IE11 (Win)\n\t\t16384,\n\t\t// IE 9-10 (Win)\n\t\t8192,\n\t\t// IE Mobile (Windows Phone 8.x)\n\t\t4096,\n\t],\n} as const\n\n/**\n * Tests ability to read pixel data from canvas elements of various dimensions\n * by decreasing canvas height and/or width until a test succeeds.\n * @param dimension - The dimension to test.\n * @returns The maximum size of the canvas for the given dimension.\n */\nfunction getCanvasSize(dimension: 'width' | 'height' | 'area'): number {\n\tconst cropCvs = document.createElement('canvas')\n\tcropCvs.width = 1\n\tcropCvs.height = 1\n\tconst cropCtx = cropCvs.getContext('2d')!\n\n\tfor (const size of TEST_SIZES[dimension]) {\n\t\tconst w = dimension === 'height' ? 1 : size\n\t\tconst h = dimension === 'width' ? 1 : size\n\n\t\tconst testCvs = document.createElement('canvas')\n\t\ttestCvs.width = w\n\t\ttestCvs.height = h\n\t\tconst testCtx = testCvs.getContext('2d')!\n\n\t\ttestCtx.fillRect(w - 1, h - 1, 1, 1)\n\t\tcropCtx.drawImage(testCvs, w - 1, h - 1, 1, 1, 0, 0, 1, 1)\n\n\t\tconst isTestPassed = cropCtx.getImageData(0, 0, 1, 1).data[3] !== 0\n\t\t// release memory\n\t\ttestCvs.width = 0\n\t\ttestCvs.height = 0\n\n\t\tif (isTestPassed) {\n\t\t\t// release memory\n\t\t\tcropCvs.width = 0\n\t\t\tcropCvs.height = 0\n\n\t\t\tif (dimension === 'area') {\n\t\t\t\treturn size * size\n\t\t\t} else {\n\t\t\t\treturn size\n\t\t\t}\n\t\t}\n\t}\n\n\t// didn't find a good size, release memory and error\n\tcropCvs.width = 0\n\tcropCvs.height = 0\n\n\tthrow Error('Failed to determine maximum canvas dimension')\n}\n\n/** @internal */\nexport function clampToBrowserMaxCanvasSize(width: number, height: number): [number, number] {\n\tif (\n\t\twidth <= MAX_SAFE_CANVAS_DIMENSION &&\n\t\theight <= MAX_SAFE_CANVAS_DIMENSION &&\n\t\twidth * height <= MAX_SAFE_CANVAS_AREA\n\t) {\n\t\treturn [width, height]\n\t}\n\n\tconst { maxWidth, maxHeight, maxArea } = getBrowserCanvasMaxSize()\n\tconst aspectRatio = width / height\n\n\tif (width > maxWidth) {\n\t\twidth = maxWidth\n\t\theight = width / aspectRatio\n\t}\n\n\tif (height > maxHeight) {\n\t\theight = maxHeight\n\t\twidth = height * aspectRatio\n\t}\n\n\tif (width * height > maxArea) {\n\t\tconst ratio = Math.sqrt(maxArea / (width * height))\n\t\twidth *= ratio\n\t\theight *= ratio\n\t}\n\n\treturn [width, height]\n}\n"],
5
- "mappings": "AAQA,IAAI,iBAAuC;AAE3C,SAAS,0BAAyC;AACjD,MAAI,CAAC,gBAAgB;AACpB,qBAAiB;AAAA,MAChB,UAAU,cAAc,OAAO;AAAA;AAAA,MAC/B,WAAW,cAAc,QAAQ;AAAA;AAAA,MACjC,SAAS,cAAc,MAAM;AAAA;AAAA,IAC9B;AAAA,EACD;AACA,SAAO;AACR;AAEA;AAAA;AAAA;AAAA;AAAA;AAMA,MAAM,4BAA4B;AAClC,MAAM,uBAAuB,OAAO;AAEpC,MAAM,aAAa;AAAA,EAClB,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA,IAKL;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA;AAAA;AAAA,IAGA;AAAA,EACD;AAAA,EACA,QAAQ;AAAA;AAAA;AAAA,IAGP;AAAA;AAAA,IAEA;AAAA;AAAA;AAAA;AAAA,IAIA;AAAA;AAAA;AAAA,IAGA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA,EACD;AAAA,EACA,OAAO;AAAA;AAAA;AAAA,IAGN;AAAA;AAAA,IAEA;AAAA;AAAA;AAAA;AAAA,IAIA;AAAA;AAAA;AAAA,IAGA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA,EACD;AACD;AAQA,SAAS,cAAc,WAAgD;AACtE,QAAM,UAAU,SAAS,cAAc,QAAQ;AAC/C,UAAQ,QAAQ;AAChB,UAAQ,SAAS;AACjB,QAAM,UAAU,QAAQ,WAAW,IAAI;AAEvC,aAAW,QAAQ,WAAW,SAAS,GAAG;AACzC,UAAM,IAAI,cAAc,WAAW,IAAI;AACvC,UAAM,IAAI,cAAc,UAAU,IAAI;AAEtC,UAAM,UAAU,SAAS,cAAc,QAAQ;AAC/C,YAAQ,QAAQ;AAChB,YAAQ,SAAS;AACjB,UAAM,UAAU,QAAQ,WAAW,IAAI;AAEvC,YAAQ,SAAS,IAAI,GAAG,IAAI,GAAG,GAAG,CAAC;AACnC,YAAQ,UAAU,SAAS,IAAI,GAAG,IAAI,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAEzD,UAAM,eAAe,QAAQ,aAAa,GAAG,GAAG,GAAG,CAAC,EAAE,KAAK,CAAC,MAAM;AAElE,YAAQ,QAAQ;AAChB,YAAQ,SAAS;AAEjB,QAAI,cAAc;AAEjB,cAAQ,QAAQ;AAChB,cAAQ,SAAS;AAEjB,UAAI,cAAc,QAAQ;AACzB,eAAO,OAAO;AAAA,MACf,OAAO;AACN,eAAO;AAAA,MACR;AAAA,IACD;AAAA,EACD;AAGA,UAAQ,QAAQ;AAChB,UAAQ,SAAS;AAEjB,QAAM,MAAM,8CAA8C;AAC3D;AAGO,SAAS,4BAA4B,OAAe,QAAkC;AAC5F,MACC,SAAS,6BACT,UAAU,6BACV,QAAQ,UAAU,sBACjB;AACD,WAAO,CAAC,OAAO,MAAM;AAAA,EACtB;AAEA,QAAM,EAAE,UAAU,WAAW,QAAQ,IAAI,wBAAwB;AACjE,QAAM,cAAc,QAAQ;AAE5B,MAAI,QAAQ,UAAU;AACrB,YAAQ;AACR,aAAS,QAAQ;AAAA,EAClB;AAEA,MAAI,SAAS,WAAW;AACvB,aAAS;AACT,YAAQ,SAAS;AAAA,EAClB;AAEA,MAAI,QAAQ,SAAS,SAAS;AAC7B,UAAM,QAAQ,KAAK,KAAK,WAAW,QAAQ,OAAO;AAClD,aAAS;AACT,cAAU;AAAA,EACX;AAEA,SAAO,CAAC,OAAO,MAAM;AACtB;",
4
+ "sourcesContent": ["import { getGlobalDocument } from './dom'\n\n/** @internal */\nexport interface CanvasMaxSize {\n\tmaxWidth: number\n\tmaxHeight: number\n\tmaxArea: number\n}\n\n// Cache this, only want to do this once per browser session\nlet maxCanvasSizes: CanvasMaxSize | null = null\n\nfunction getBrowserCanvasMaxSize(): CanvasMaxSize {\n\tif (!maxCanvasSizes) {\n\t\tmaxCanvasSizes = {\n\t\t\tmaxWidth: getCanvasSize('width'), // test very wide but 1 pixel tall canvases\n\t\t\tmaxHeight: getCanvasSize('height'), // test very tall but 1 pixel wide canvases\n\t\t\tmaxArea: getCanvasSize('area'), // test square canvases\n\t\t}\n\t}\n\treturn maxCanvasSizes\n}\n\n/*!\n * Extracted from https://github.com/jhildenbiddle/canvas-size\n * MIT License: https://github.com/jhildenbiddle/canvas-size/blob/master/LICENSE\n * Copyright (c) John Hildenbiddle\n */\n\nconst MAX_SAFE_CANVAS_DIMENSION = 8192\nconst MAX_SAFE_CANVAS_AREA = 4096 * 4096\n\nconst TEST_SIZES = {\n\tarea: [\n\t\t// Chrome 70 (Mac, Win)\n\t\t// Chrome 68 (Android 4.4)\n\t\t// Edge 17 (Win)\n\t\t// Safari 7-12 (Mac)\n\t\t16384,\n\t\t// Chrome 68 (Android 7.1-9)\n\t\t14188,\n\t\t// Chrome 68 (Android 5)\n\t\t11402,\n\t\t// Firefox 63 (Mac, Win)\n\t\t11180,\n\t\t// Chrome 68 (Android 6)\n\t\t10836,\n\t\t// IE 9-11 (Win)\n\t\t8192,\n\t\t// IE Mobile (Windows Phone 8.x)\n\t\t// Safari (iOS 9 - 12)\n\t\t4096,\n\t],\n\theight: [\n\t\t// Safari 7-12 (Mac)\n\t\t// Safari (iOS 9-12)\n\t\t8388607,\n\t\t// Chrome 83 (Mac, Win)\n\t\t65535,\n\t\t// Chrome 70 (Mac, Win)\n\t\t// Chrome 68 (Android 4.4-9)\n\t\t// Firefox 63 (Mac, Win)\n\t\t32767,\n\t\t// Edge 17 (Win)\n\t\t// IE11 (Win)\n\t\t16384,\n\t\t// IE 9-10 (Win)\n\t\t8192,\n\t\t// IE Mobile (Windows Phone 8.x)\n\t\t4096,\n\t],\n\twidth: [\n\t\t// Safari 7-12 (Mac)\n\t\t// Safari (iOS 9-12)\n\t\t4194303,\n\t\t// Chrome 83 (Mac, Win)\n\t\t65535,\n\t\t// Chrome 70 (Mac, Win)\n\t\t// Chrome 68 (Android 4.4-9)\n\t\t// Firefox 63 (Mac, Win)\n\t\t32767,\n\t\t// Edge 17 (Win)\n\t\t// IE11 (Win)\n\t\t16384,\n\t\t// IE 9-10 (Win)\n\t\t8192,\n\t\t// IE Mobile (Windows Phone 8.x)\n\t\t4096,\n\t],\n} as const\n\n/**\n * Tests ability to read pixel data from canvas elements of various dimensions\n * by decreasing canvas height and/or width until a test succeeds.\n * @param dimension - The dimension to test.\n * @returns The maximum size of the canvas for the given dimension.\n */\nfunction getCanvasSize(dimension: 'width' | 'height' | 'area'): number {\n\tconst cropCvs = getGlobalDocument().createElement('canvas')\n\tcropCvs.width = 1\n\tcropCvs.height = 1\n\tconst cropCtx = cropCvs.getContext('2d')!\n\n\tfor (const size of TEST_SIZES[dimension]) {\n\t\tconst w = dimension === 'height' ? 1 : size\n\t\tconst h = dimension === 'width' ? 1 : size\n\n\t\tconst testCvs = getGlobalDocument().createElement('canvas')\n\t\ttestCvs.width = w\n\t\ttestCvs.height = h\n\t\tconst testCtx = testCvs.getContext('2d')!\n\n\t\ttestCtx.fillRect(w - 1, h - 1, 1, 1)\n\t\tcropCtx.drawImage(testCvs, w - 1, h - 1, 1, 1, 0, 0, 1, 1)\n\n\t\tconst isTestPassed = cropCtx.getImageData(0, 0, 1, 1).data[3] !== 0\n\t\t// release memory\n\t\ttestCvs.width = 0\n\t\ttestCvs.height = 0\n\n\t\tif (isTestPassed) {\n\t\t\t// release memory\n\t\t\tcropCvs.width = 0\n\t\t\tcropCvs.height = 0\n\n\t\t\tif (dimension === 'area') {\n\t\t\t\treturn size * size\n\t\t\t} else {\n\t\t\t\treturn size\n\t\t\t}\n\t\t}\n\t}\n\n\t// didn't find a good size, release memory and error\n\tcropCvs.width = 0\n\tcropCvs.height = 0\n\n\tthrow Error('Failed to determine maximum canvas dimension')\n}\n\n/** @internal */\nexport function clampToBrowserMaxCanvasSize(width: number, height: number): [number, number] {\n\tif (\n\t\twidth <= MAX_SAFE_CANVAS_DIMENSION &&\n\t\theight <= MAX_SAFE_CANVAS_DIMENSION &&\n\t\twidth * height <= MAX_SAFE_CANVAS_AREA\n\t) {\n\t\treturn [width, height]\n\t}\n\n\tconst { maxWidth, maxHeight, maxArea } = getBrowserCanvasMaxSize()\n\tconst aspectRatio = width / height\n\n\tif (width > maxWidth) {\n\t\twidth = maxWidth\n\t\theight = width / aspectRatio\n\t}\n\n\tif (height > maxHeight) {\n\t\theight = maxHeight\n\t\twidth = height * aspectRatio\n\t}\n\n\tif (width * height > maxArea) {\n\t\tconst ratio = Math.sqrt(maxArea / (width * height))\n\t\twidth *= ratio\n\t\theight *= ratio\n\t}\n\n\treturn [width, height]\n}\n"],
5
+ "mappings": "AAAA,SAAS,yBAAyB;AAUlC,IAAI,iBAAuC;AAE3C,SAAS,0BAAyC;AACjD,MAAI,CAAC,gBAAgB;AACpB,qBAAiB;AAAA,MAChB,UAAU,cAAc,OAAO;AAAA;AAAA,MAC/B,WAAW,cAAc,QAAQ;AAAA;AAAA,MACjC,SAAS,cAAc,MAAM;AAAA;AAAA,IAC9B;AAAA,EACD;AACA,SAAO;AACR;AAEA;AAAA;AAAA;AAAA;AAAA;AAMA,MAAM,4BAA4B;AAClC,MAAM,uBAAuB,OAAO;AAEpC,MAAM,aAAa;AAAA,EAClB,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA,IAKL;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA;AAAA;AAAA,IAGA;AAAA,EACD;AAAA,EACA,QAAQ;AAAA;AAAA;AAAA,IAGP;AAAA;AAAA,IAEA;AAAA;AAAA;AAAA;AAAA,IAIA;AAAA;AAAA;AAAA,IAGA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA,EACD;AAAA,EACA,OAAO;AAAA;AAAA;AAAA,IAGN;AAAA;AAAA,IAEA;AAAA;AAAA;AAAA;AAAA,IAIA;AAAA;AAAA;AAAA,IAGA;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA;AAAA,EACD;AACD;AAQA,SAAS,cAAc,WAAgD;AACtE,QAAM,UAAU,kBAAkB,EAAE,cAAc,QAAQ;AAC1D,UAAQ,QAAQ;AAChB,UAAQ,SAAS;AACjB,QAAM,UAAU,QAAQ,WAAW,IAAI;AAEvC,aAAW,QAAQ,WAAW,SAAS,GAAG;AACzC,UAAM,IAAI,cAAc,WAAW,IAAI;AACvC,UAAM,IAAI,cAAc,UAAU,IAAI;AAEtC,UAAM,UAAU,kBAAkB,EAAE,cAAc,QAAQ;AAC1D,YAAQ,QAAQ;AAChB,YAAQ,SAAS;AACjB,UAAM,UAAU,QAAQ,WAAW,IAAI;AAEvC,YAAQ,SAAS,IAAI,GAAG,IAAI,GAAG,GAAG,CAAC;AACnC,YAAQ,UAAU,SAAS,IAAI,GAAG,IAAI,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAEzD,UAAM,eAAe,QAAQ,aAAa,GAAG,GAAG,GAAG,CAAC,EAAE,KAAK,CAAC,MAAM;AAElE,YAAQ,QAAQ;AAChB,YAAQ,SAAS;AAEjB,QAAI,cAAc;AAEjB,cAAQ,QAAQ;AAChB,cAAQ,SAAS;AAEjB,UAAI,cAAc,QAAQ;AACzB,eAAO,OAAO;AAAA,MACf,OAAO;AACN,eAAO;AAAA,MACR;AAAA,IACD;AAAA,EACD;AAGA,UAAQ,QAAQ;AAChB,UAAQ,SAAS;AAEjB,QAAM,MAAM,8CAA8C;AAC3D;AAGO,SAAS,4BAA4B,OAAe,QAAkC;AAC5F,MACC,SAAS,6BACT,UAAU,6BACV,QAAQ,UAAU,sBACjB;AACD,WAAO,CAAC,OAAO,MAAM;AAAA,EACtB;AAEA,QAAM,EAAE,UAAU,WAAW,QAAQ,IAAI,wBAAwB;AACjE,QAAM,cAAc,QAAQ;AAE5B,MAAI,QAAQ,UAAU;AACrB,YAAQ;AACR,aAAS,QAAQ;AAAA,EAClB;AAEA,MAAI,SAAS,WAAW;AACvB,aAAS;AACT,YAAQ,SAAS;AAAA,EAClB;AAEA,MAAI,QAAQ,SAAS,SAAS;AAC7B,UAAM,QAAQ,KAAK,KAAK,WAAW,QAAQ,OAAO;AAClD,aAAS;AACT,cAAU;AAAA,EACX;AAEA,SAAO,CAAC,OAAO,MAAM;AACtB;",
6
6
  "names": []
7
7
  }
@@ -45,12 +45,25 @@ function elementShouldCaptureKeys(el, includeButtonsAndMenus = true) {
45
45
  const tagName = el.tagName.toLowerCase();
46
46
  return el.isContentEditable || tagName === "input" || tagName === "textarea" || includeButtonsAndMenus && tagName === "select" || includeButtonsAndMenus && tagName === "button" || el.classList.contains("tlui-slider__thumb");
47
47
  }
48
- function activeElementShouldCaptureKeys(includeButtonsAndMenus = true) {
49
- return elementShouldCaptureKeys(document.activeElement, includeButtonsAndMenus);
48
+ function getGlobalDocument() {
49
+ if (typeof document !== "undefined") return document;
50
+ return globalThis.document;
51
+ }
52
+ function getGlobalWindow() {
53
+ if (typeof window !== "undefined") return window;
54
+ return globalThis;
55
+ }
56
+ function activeElementShouldCaptureKeys(includeButtonsAndMenus = true, doc) {
57
+ return elementShouldCaptureKeys(
58
+ (doc ?? getGlobalDocument()).activeElement,
59
+ includeButtonsAndMenus
60
+ );
50
61
  }
51
62
  export {
52
63
  activeElementShouldCaptureKeys,
53
64
  elementShouldCaptureKeys,
65
+ getGlobalDocument,
66
+ getGlobalWindow,
54
67
  loopToHtmlElement,
55
68
  preventDefault,
56
69
  releasePointerCapture,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/utils/dom.ts"],
4
- "sourcesContent": ["/*\nThis is used to facilitate double clicking and pointer capture on elements.\n\nThe events in this file are possibly set on individual SVG elements, \nsuch as handles or corner handles, rather than on HTML elements or \nSVGSVGElements. Raw SVG elements do not support pointerCapture in\nmost cases, meaning that in order for pointer capture to work, we \nneed to crawl up the DOM tree to find the nearest HTML element. Then,\nin order for that element to also call the `onPointerUp` event from\nthis file, we need to manually set that event on that element and\nlater remove it when the pointerup occurs. This is a potential leak\nif the user clicks on a handle but the pointerup does not fire for\nwhatever reason.\n*/\n\nimport { debugFlags, pointerCaptureTrackingObject } from './debug-flags'\n\n/** @public */\nexport function loopToHtmlElement(elm: Element): HTMLElement {\n\tif (elm.nodeType === Node.ELEMENT_NODE) return elm as HTMLElement\n\tif (elm.parentElement) return loopToHtmlElement(elm.parentElement)\n\telse throw Error('Could not find a parent element of an HTML type!')\n}\n\n/**\n * This function calls `event.preventDefault()` for you. Why is that useful?\n *\n * Because if you enable `window.preventDefaultLogging = true` it'll log out a message when it\n * happens. Because we use console.warn rather than (log) you'll get a stack trace in the inspector\n * telling you exactly where it happened. This is important because `e.preventDefault()` is the\n * source of many bugs, but unfortunately it can't be avoided because it also stops a lot of default\n * behaviour which doesn't make sense in our UI\n *\n * @param event - To prevent default on\n * @public\n */\nexport function preventDefault(event: React.BaseSyntheticEvent | Event) {\n\tevent.preventDefault()\n\tif (debugFlags.logPreventDefaults.get()) {\n\t\tconsole.warn('preventDefault called on event:', event)\n\t}\n}\n\n/** @public */\nexport function setPointerCapture(\n\telement: Element,\n\tevent: React.PointerEvent<Element> | PointerEvent\n) {\n\telement.setPointerCapture(event.pointerId)\n\tif (debugFlags.logPointerCaptures.get()) {\n\t\tconst trackingObj = pointerCaptureTrackingObject.get()\n\t\ttrackingObj.set(element, (trackingObj.get(element) ?? 0) + 1)\n\t\tconsole.warn('setPointerCapture called on element:', element, event)\n\t}\n}\n\n/** @public */\nexport function releasePointerCapture(\n\telement: Element,\n\tevent: React.PointerEvent<Element> | PointerEvent\n) {\n\tif (!element.hasPointerCapture(event.pointerId)) {\n\t\treturn\n\t}\n\n\telement.releasePointerCapture(event.pointerId)\n\tif (debugFlags.logPointerCaptures.get()) {\n\t\tconst trackingObj = pointerCaptureTrackingObject.get()\n\t\tif (trackingObj.get(element) === 1) {\n\t\t\ttrackingObj.delete(element)\n\t\t} else if (trackingObj.has(element)) {\n\t\t\ttrackingObj.set(element, trackingObj.get(element)! - 1)\n\t\t} else {\n\t\t\tconsole.warn('Release without capture')\n\t\t}\n\t\tconsole.warn('releasePointerCapture called on element:', element, event)\n\t}\n}\n\n/**\n * Calls `event.stopPropagation()`.\n *\n * @deprecated Use {@link Editor.markEventAsHandled} instead, or manually call `event.stopPropagation()` if\n * that's what you really want.\n *\n * @public\n */\nexport const stopEventPropagation = (e: any) => e.stopPropagation()\n\n/** @internal */\nexport const setStyleProperty = (\n\telm: HTMLElement | null,\n\tproperty: string,\n\tvalue: string | number\n) => {\n\tif (!elm) return\n\telm.style.setProperty(property, String(value))\n}\n\n/** @internal */\nexport function elementShouldCaptureKeys(el: Element | null, includeButtonsAndMenus = true) {\n\tif (!el) return false\n\n\tconst tagName = el.tagName.toLowerCase()\n\treturn (\n\t\t(el as HTMLElement).isContentEditable ||\n\t\ttagName === 'input' ||\n\t\ttagName === 'textarea' ||\n\t\t(includeButtonsAndMenus && tagName === 'select') ||\n\t\t(includeButtonsAndMenus && tagName === 'button') ||\n\t\tel.classList.contains('tlui-slider__thumb')\n\t)\n}\n\n/** @internal */\nexport function activeElementShouldCaptureKeys(includeButtonsAndMenus = true) {\n\treturn elementShouldCaptureKeys(document.activeElement, includeButtonsAndMenus)\n}\n"],
5
- "mappings": "AAeA,SAAS,YAAY,oCAAoC;AAGlD,SAAS,kBAAkB,KAA2B;AAC5D,MAAI,IAAI,aAAa,KAAK,aAAc,QAAO;AAC/C,MAAI,IAAI,cAAe,QAAO,kBAAkB,IAAI,aAAa;AAAA,MAC5D,OAAM,MAAM,kDAAkD;AACpE;AAcO,SAAS,eAAe,OAAyC;AACvE,QAAM,eAAe;AACrB,MAAI,WAAW,mBAAmB,IAAI,GAAG;AACxC,YAAQ,KAAK,mCAAmC,KAAK;AAAA,EACtD;AACD;AAGO,SAAS,kBACf,SACA,OACC;AACD,UAAQ,kBAAkB,MAAM,SAAS;AACzC,MAAI,WAAW,mBAAmB,IAAI,GAAG;AACxC,UAAM,cAAc,6BAA6B,IAAI;AACrD,gBAAY,IAAI,UAAU,YAAY,IAAI,OAAO,KAAK,KAAK,CAAC;AAC5D,YAAQ,KAAK,wCAAwC,SAAS,KAAK;AAAA,EACpE;AACD;AAGO,SAAS,sBACf,SACA,OACC;AACD,MAAI,CAAC,QAAQ,kBAAkB,MAAM,SAAS,GAAG;AAChD;AAAA,EACD;AAEA,UAAQ,sBAAsB,MAAM,SAAS;AAC7C,MAAI,WAAW,mBAAmB,IAAI,GAAG;AACxC,UAAM,cAAc,6BAA6B,IAAI;AACrD,QAAI,YAAY,IAAI,OAAO,MAAM,GAAG;AACnC,kBAAY,OAAO,OAAO;AAAA,IAC3B,WAAW,YAAY,IAAI,OAAO,GAAG;AACpC,kBAAY,IAAI,SAAS,YAAY,IAAI,OAAO,IAAK,CAAC;AAAA,IACvD,OAAO;AACN,cAAQ,KAAK,yBAAyB;AAAA,IACvC;AACA,YAAQ,KAAK,4CAA4C,SAAS,KAAK;AAAA,EACxE;AACD;AAUO,MAAM,uBAAuB,CAAC,MAAW,EAAE,gBAAgB;AAG3D,MAAM,mBAAmB,CAC/B,KACA,UACA,UACI;AACJ,MAAI,CAAC,IAAK;AACV,MAAI,MAAM,YAAY,UAAU,OAAO,KAAK,CAAC;AAC9C;AAGO,SAAS,yBAAyB,IAAoB,yBAAyB,MAAM;AAC3F,MAAI,CAAC,GAAI,QAAO;AAEhB,QAAM,UAAU,GAAG,QAAQ,YAAY;AACvC,SACE,GAAmB,qBACpB,YAAY,WACZ,YAAY,cACX,0BAA0B,YAAY,YACtC,0BAA0B,YAAY,YACvC,GAAG,UAAU,SAAS,oBAAoB;AAE5C;AAGO,SAAS,+BAA+B,yBAAyB,MAAM;AAC7E,SAAO,yBAAyB,SAAS,eAAe,sBAAsB;AAC/E;",
4
+ "sourcesContent": ["/*\nThis is used to facilitate double clicking and pointer capture on elements.\n\nThe events in this file are possibly set on individual SVG elements, \nsuch as handles or corner handles, rather than on HTML elements or \nSVGSVGElements. Raw SVG elements do not support pointerCapture in\nmost cases, meaning that in order for pointer capture to work, we \nneed to crawl up the DOM tree to find the nearest HTML element. Then,\nin order for that element to also call the `onPointerUp` event from\nthis file, we need to manually set that event on that element and\nlater remove it when the pointerup occurs. This is a potential leak\nif the user clicks on a handle but the pointerup does not fire for\nwhatever reason.\n*/\n\nimport { debugFlags, pointerCaptureTrackingObject } from './debug-flags'\n\n/** @public */\nexport function loopToHtmlElement(elm: Element): HTMLElement {\n\tif (elm.nodeType === Node.ELEMENT_NODE) return elm as HTMLElement\n\tif (elm.parentElement) return loopToHtmlElement(elm.parentElement)\n\telse throw Error('Could not find a parent element of an HTML type!')\n}\n\n/**\n * This function calls `event.preventDefault()` for you. Why is that useful?\n *\n * Because if you enable `window.preventDefaultLogging = true` it'll log out a message when it\n * happens. Because we use console.warn rather than (log) you'll get a stack trace in the inspector\n * telling you exactly where it happened. This is important because `e.preventDefault()` is the\n * source of many bugs, but unfortunately it can't be avoided because it also stops a lot of default\n * behaviour which doesn't make sense in our UI\n *\n * @param event - To prevent default on\n * @public\n */\nexport function preventDefault(event: React.BaseSyntheticEvent | Event) {\n\tevent.preventDefault()\n\tif (debugFlags.logPreventDefaults.get()) {\n\t\tconsole.warn('preventDefault called on event:', event)\n\t}\n}\n\n/** @public */\nexport function setPointerCapture(\n\telement: Element,\n\tevent: React.PointerEvent<Element> | PointerEvent\n) {\n\telement.setPointerCapture(event.pointerId)\n\tif (debugFlags.logPointerCaptures.get()) {\n\t\tconst trackingObj = pointerCaptureTrackingObject.get()\n\t\ttrackingObj.set(element, (trackingObj.get(element) ?? 0) + 1)\n\t\tconsole.warn('setPointerCapture called on element:', element, event)\n\t}\n}\n\n/** @public */\nexport function releasePointerCapture(\n\telement: Element,\n\tevent: React.PointerEvent<Element> | PointerEvent\n) {\n\tif (!element.hasPointerCapture(event.pointerId)) {\n\t\treturn\n\t}\n\n\telement.releasePointerCapture(event.pointerId)\n\tif (debugFlags.logPointerCaptures.get()) {\n\t\tconst trackingObj = pointerCaptureTrackingObject.get()\n\t\tif (trackingObj.get(element) === 1) {\n\t\t\ttrackingObj.delete(element)\n\t\t} else if (trackingObj.has(element)) {\n\t\t\ttrackingObj.set(element, trackingObj.get(element)! - 1)\n\t\t} else {\n\t\t\tconsole.warn('Release without capture')\n\t\t}\n\t\tconsole.warn('releasePointerCapture called on element:', element, event)\n\t}\n}\n\n/**\n * Calls `event.stopPropagation()`.\n *\n * @deprecated Use {@link Editor.markEventAsHandled} instead, or manually call `event.stopPropagation()` if\n * that's what you really want.\n *\n * @public\n */\nexport const stopEventPropagation = (e: any) => e.stopPropagation()\n\n/** @internal */\nexport const setStyleProperty = (\n\telm: HTMLElement | null,\n\tproperty: string,\n\tvalue: string | number\n) => {\n\tif (!elm) return\n\telm.style.setProperty(property, String(value))\n}\n\n/** @internal */\nexport function elementShouldCaptureKeys(el: Element | null, includeButtonsAndMenus = true) {\n\tif (!el) return false\n\n\tconst tagName = el.tagName.toLowerCase()\n\treturn (\n\t\t(el as HTMLElement).isContentEditable ||\n\t\ttagName === 'input' ||\n\t\ttagName === 'textarea' ||\n\t\t(includeButtonsAndMenus && tagName === 'select') ||\n\t\t(includeButtonsAndMenus && tagName === 'button') ||\n\t\tel.classList.contains('tlui-slider__thumb')\n\t)\n}\n\n/**\n * Returns the global `document`. Use this instead of bare `document` to satisfy lint rules.\n *\n * When you have a DOM node or editor instance, prefer the scoped versions instead:\n * - `getOwnerDocument(node)` \u2013 the document that owns a specific DOM node\n * - `editor.getContainerDocument()` \u2013 the document where the editor is mounted\n *\n * @internal\n */\nexport function getGlobalDocument(): Document {\n\t// eslint-disable-next-line no-restricted-globals\n\tif (typeof document !== 'undefined') return document\n\treturn globalThis.document\n}\n\n/**\n * Returns the global `window`. Use this instead of bare `window` to satisfy lint rules.\n *\n * When you have a DOM node or editor instance, prefer the scoped versions instead:\n * - `getOwnerWindow(node)` \u2013 the window that owns a specific DOM node\n * - `editor.getContainerWindow()` \u2013 the window where the editor is mounted\n *\n * @internal\n */\nexport function getGlobalWindow(): Window & typeof globalThis {\n\tif (typeof window !== 'undefined') return window as Window & typeof globalThis\n\treturn globalThis as Window & typeof globalThis\n}\n\n/** @internal */\nexport function activeElementShouldCaptureKeys(includeButtonsAndMenus = true, doc?: Document) {\n\treturn elementShouldCaptureKeys(\n\t\t(doc ?? getGlobalDocument()).activeElement,\n\t\tincludeButtonsAndMenus\n\t)\n}\n"],
5
+ "mappings": "AAeA,SAAS,YAAY,oCAAoC;AAGlD,SAAS,kBAAkB,KAA2B;AAC5D,MAAI,IAAI,aAAa,KAAK,aAAc,QAAO;AAC/C,MAAI,IAAI,cAAe,QAAO,kBAAkB,IAAI,aAAa;AAAA,MAC5D,OAAM,MAAM,kDAAkD;AACpE;AAcO,SAAS,eAAe,OAAyC;AACvE,QAAM,eAAe;AACrB,MAAI,WAAW,mBAAmB,IAAI,GAAG;AACxC,YAAQ,KAAK,mCAAmC,KAAK;AAAA,EACtD;AACD;AAGO,SAAS,kBACf,SACA,OACC;AACD,UAAQ,kBAAkB,MAAM,SAAS;AACzC,MAAI,WAAW,mBAAmB,IAAI,GAAG;AACxC,UAAM,cAAc,6BAA6B,IAAI;AACrD,gBAAY,IAAI,UAAU,YAAY,IAAI,OAAO,KAAK,KAAK,CAAC;AAC5D,YAAQ,KAAK,wCAAwC,SAAS,KAAK;AAAA,EACpE;AACD;AAGO,SAAS,sBACf,SACA,OACC;AACD,MAAI,CAAC,QAAQ,kBAAkB,MAAM,SAAS,GAAG;AAChD;AAAA,EACD;AAEA,UAAQ,sBAAsB,MAAM,SAAS;AAC7C,MAAI,WAAW,mBAAmB,IAAI,GAAG;AACxC,UAAM,cAAc,6BAA6B,IAAI;AACrD,QAAI,YAAY,IAAI,OAAO,MAAM,GAAG;AACnC,kBAAY,OAAO,OAAO;AAAA,IAC3B,WAAW,YAAY,IAAI,OAAO,GAAG;AACpC,kBAAY,IAAI,SAAS,YAAY,IAAI,OAAO,IAAK,CAAC;AAAA,IACvD,OAAO;AACN,cAAQ,KAAK,yBAAyB;AAAA,IACvC;AACA,YAAQ,KAAK,4CAA4C,SAAS,KAAK;AAAA,EACxE;AACD;AAUO,MAAM,uBAAuB,CAAC,MAAW,EAAE,gBAAgB;AAG3D,MAAM,mBAAmB,CAC/B,KACA,UACA,UACI;AACJ,MAAI,CAAC,IAAK;AACV,MAAI,MAAM,YAAY,UAAU,OAAO,KAAK,CAAC;AAC9C;AAGO,SAAS,yBAAyB,IAAoB,yBAAyB,MAAM;AAC3F,MAAI,CAAC,GAAI,QAAO;AAEhB,QAAM,UAAU,GAAG,QAAQ,YAAY;AACvC,SACE,GAAmB,qBACpB,YAAY,WACZ,YAAY,cACX,0BAA0B,YAAY,YACtC,0BAA0B,YAAY,YACvC,GAAG,UAAU,SAAS,oBAAoB;AAE5C;AAWO,SAAS,oBAA8B;AAE7C,MAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,SAAO,WAAW;AACnB;AAWO,SAAS,kBAA8C;AAC7D,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,SAAO;AACR;AAGO,SAAS,+BAA+B,yBAAyB,MAAM,KAAgB;AAC7F,SAAO;AAAA,KACL,OAAO,kBAAkB,GAAG;AAAA,IAC7B;AAAA,EACD;AACD;",
6
6
  "names": []
7
7
  }
@@ -1,8 +1,8 @@
1
- const version = "4.5.3";
1
+ const version = "4.6.0-canary.0bcbb3ed5bcb";
2
2
  const publishDates = {
3
3
  major: "2025-09-18T14:39:22.803Z",
4
- minor: "2026-03-18T11:05:13.340Z",
5
- patch: "2026-03-18T15:57:06.790Z"
4
+ minor: "2026-03-18T17:26:38.671Z",
5
+ patch: "2026-03-18T17:26:38.671Z"
6
6
  };
7
7
  export {
8
8
  publishDates,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/version.ts"],
4
- "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '4.5.3'\nexport const publishDates = {\n\tmajor: '2025-09-18T14:39:22.803Z',\n\tminor: '2026-03-18T11:05:13.340Z',\n\tpatch: '2026-03-18T15:57:06.790Z',\n}\n"],
4
+ "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '4.6.0-canary.0bcbb3ed5bcb'\nexport const publishDates = {\n\tmajor: '2025-09-18T14:39:22.803Z',\n\tminor: '2026-03-18T17:26:38.671Z',\n\tpatch: '2026-03-18T17:26:38.671Z',\n}\n"],
5
5
  "mappings": "AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tldraw/editor",
3
3
  "description": "tldraw infinite canvas SDK (editor).",
4
- "version": "4.5.3",
4
+ "version": "4.6.0-canary.0bcbb3ed5bcb",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -49,12 +49,12 @@
49
49
  "@tiptap/core": "^3.12.1",
50
50
  "@tiptap/pm": "^3.12.1",
51
51
  "@tiptap/react": "^3.12.1",
52
- "@tldraw/state": "4.5.3",
53
- "@tldraw/state-react": "4.5.3",
54
- "@tldraw/store": "4.5.3",
55
- "@tldraw/tlschema": "4.5.3",
56
- "@tldraw/utils": "4.5.3",
57
- "@tldraw/validate": "4.5.3",
52
+ "@tldraw/state": "4.6.0-canary.0bcbb3ed5bcb",
53
+ "@tldraw/state-react": "4.6.0-canary.0bcbb3ed5bcb",
54
+ "@tldraw/store": "4.6.0-canary.0bcbb3ed5bcb",
55
+ "@tldraw/tlschema": "4.6.0-canary.0bcbb3ed5bcb",
56
+ "@tldraw/utils": "4.6.0-canary.0bcbb3ed5bcb",
57
+ "@tldraw/validate": "4.6.0-canary.0bcbb3ed5bcb",
58
58
  "@use-gesture/react": "^10.3.1",
59
59
  "classnames": "^2.5.1",
60
60
  "eventemitter3": "^4.0.7",
package/src/index.ts CHANGED
@@ -282,6 +282,7 @@ export {
282
282
  type SvgExportContext,
283
283
  type SvgExportDef,
284
284
  } from './lib/editor/types/SvgExportContext'
285
+ export { getOwnerDocument, getOwnerWindow } from './lib/exports/domUtils'
285
286
  export { getSvgAsImage } from './lib/exports/getSvgAsImage'
286
287
  export { tlenv, tlenvReactive } from './lib/globals/environment'
287
288
  export { tlmenus } from './lib/globals/menus'
@@ -448,6 +449,8 @@ export {
448
449
  export {
449
450
  activeElementShouldCaptureKeys,
450
451
  elementShouldCaptureKeys,
452
+ getGlobalDocument,
453
+ getGlobalWindow,
451
454
  loopToHtmlElement,
452
455
  preventDefault,
453
456
  releasePointerCapture,
@@ -42,6 +42,7 @@ import { LicenseProvider } from './license/LicenseProvider'
42
42
  import { Watermark } from './license/Watermark'
43
43
  import { TldrawOptions } from './options'
44
44
  import { TLDeepLinkOptions } from './utils/deepLinks'
45
+ import { getGlobalDocument } from './utils/dom'
45
46
  import { TLTextOptions } from './utils/richText'
46
47
  import { TLStoreWithStatus } from './utils/sync/StoreWithStatus'
47
48
 
@@ -405,7 +406,7 @@ const TldrawEditorWithLoadingStore = memo(function TldrawEditorBeforeLoading({
405
406
  return <TldrawEditorWithReadyStore {...rest} store={store.store} user={user} />
406
407
  })
407
408
 
408
- const noAutoFocus = () => document.location.search.includes('tldraw_preserve_focus') // || !document.hasFocus() // breaks in nextjs
409
+ const noAutoFocus = () => getGlobalDocument().location.search.includes('tldraw_preserve_focus')
409
410
 
410
411
  function TldrawEditorWithReadyStore({
411
412
  onMount,
@@ -572,12 +573,13 @@ function TldrawEditorWithReadyStore({
572
573
  }
573
574
 
574
575
  if (autoFocus && noAutoFocus()) {
575
- editor.getContainer().addEventListener('pointerdown', handleFocusOnPointerDown)
576
- document.body.addEventListener('pointerdown', handleBlurOnPointerDown)
576
+ const container = editor.getContainer()
577
+ container.addEventListener('pointerdown', handleFocusOnPointerDown)
578
+ container.ownerDocument.body.addEventListener('pointerdown', handleBlurOnPointerDown)
577
579
 
578
580
  return () => {
579
- editor.getContainer()?.removeEventListener('pointerdown', handleFocusOnPointerDown)
580
- document.body.removeEventListener('pointerdown', handleBlurOnPointerDown)
581
+ container.removeEventListener('pointerdown', handleFocusOnPointerDown)
582
+ container.ownerDocument.body.removeEventListener('pointerdown', handleBlurOnPointerDown)
581
583
  }
582
584
  }
583
585
  },
@@ -5,6 +5,7 @@ import { dedupe } from '@tldraw/utils'
5
5
  import { memo, useEffect, useRef } from 'react'
6
6
  import { Editor } from '../../editor/Editor'
7
7
  import { TLIndicatorPath } from '../../editor/shapes/ShapeUtil'
8
+ import { getComputedStyle } from '../../exports/domUtils'
8
9
  import { useEditor } from '../../hooks/useEditor'
9
10
  import { useIsDarkMode } from '../../hooks/useIsDarkMode'
10
11
  import { useActivePeerIds$ } from '../../hooks/usePeerIds'
@@ -223,7 +224,7 @@ export const CanvasShapeIndicators = memo(function CanvasShapeIndicators() {
223
224
  $renderData.get()
224
225
 
225
226
  const { w, h } = editor.getViewportScreenBounds()
226
- const dpr = window.devicePixelRatio || 1
227
+ const dpr = editor.getInstanceState().devicePixelRatio
227
228
  const { x: cx, y: cy, z: zoom } = editor.getCamera()
228
229
 
229
230
  const canvasWidth = Math.ceil(w * dpr)
@@ -427,7 +427,7 @@ function ReflowIfNeeded() {
427
427
  if (culledShapesRef.current === culledShapes) return
428
428
 
429
429
  culledShapesRef.current = culledShapes
430
- const canvas = document.getElementsByClassName('tl-canvas')
430
+ const canvas = editor.getContainerDocument().getElementsByClassName('tl-canvas')
431
431
  if (canvas.length === 0) return
432
432
  // This causes a reflow
433
433
  // https://gist.github.com/paulirish/5d52fb081b3570c81e3a
@@ -3,8 +3,10 @@ import { noop } from '@tldraw/utils'
3
3
  import classNames from 'classnames'
4
4
  import { ComponentType, useEffect, useLayoutEffect, useRef, useState } from 'react'
5
5
  import { Editor } from '../../editor/Editor'
6
+ import { getOwnerDocument } from '../../exports/domUtils'
6
7
  import { useEditorComponents } from '../../hooks/EditorComponentsContext'
7
8
  import { EditorProvider } from '../../hooks/useEditor'
9
+ import { getGlobalWindow } from '../../utils/dom'
8
10
  import { hardResetEditor, refreshPage } from '../../utils/runtime'
9
11
  import { ErrorBoundary } from '../ErrorBoundary'
10
12
 
@@ -74,8 +76,8 @@ export const DefaultErrorFallback: TLErrorFallbackComponent = ({ error, editor }
74
76
 
75
77
  // if we can't find a theme class from the app or from a parent, we have
76
78
  // to fall back on using a media query:
77
- if (typeof window !== 'undefined' && window.matchMedia) {
78
- setIsDarkMode(window.matchMedia('(prefers-color-scheme: dark)').matches)
79
+ if (typeof window !== 'undefined' && getGlobalWindow().matchMedia) {
80
+ setIsDarkMode(getGlobalWindow().matchMedia('(prefers-color-scheme: dark)').matches)
79
81
  }
80
82
  }, [isDarkModeFromApp])
81
83
 
@@ -89,12 +91,13 @@ export const DefaultErrorFallback: TLErrorFallbackComponent = ({ error, editor }
89
91
  }, [didCopy, editor])
90
92
 
91
93
  const copyError = () => {
92
- const textarea = document.createElement('textarea')
94
+ const doc = getOwnerDocument(containerRef.current)
95
+ const textarea = doc.createElement('textarea')
93
96
  textarea.value = errorStack ?? errorMessage
94
- document.body.appendChild(textarea)
97
+ doc.body.appendChild(textarea)
95
98
  textarea.select()
96
99
  // eslint-disable-next-line @typescript-eslint/no-deprecated
97
- document.execCommand('copy')
100
+ doc.execCommand('copy')
98
101
  textarea.remove()
99
102
  setDidCopy(true)
100
103
  }
@@ -21,6 +21,7 @@ import {
21
21
  } from '@tldraw/utils'
22
22
  import { T } from '@tldraw/validate'
23
23
  import { tlenv } from '../globals/environment'
24
+ import { getGlobalDocument, getGlobalWindow } from '../utils/dom'
24
25
 
25
26
  const tabIdKey = 'TLDRAW_TAB_ID_v2' as const
26
27
 
@@ -38,10 +39,10 @@ function iOS() {
38
39
  return (
39
40
  ['iPad Simulator', 'iPhone Simulator', 'iPod Simulator', 'iPad', 'iPhone', 'iPod'].includes(
40
41
  // eslint-disable-next-line @typescript-eslint/no-deprecated
41
- window.navigator.platform
42
+ getGlobalWindow().navigator.platform
42
43
  ) ||
43
44
  // iPad on iOS 13 detection
44
- (tlenv.isDarwin && 'ontouchend' in document)
45
+ (tlenv.isDarwin && 'ontouchend' in getGlobalDocument())
45
46
  )
46
47
  }
47
48
 
@@ -69,9 +70,11 @@ if (window) {
69
70
  }
70
71
  }
71
72
 
72
- window?.addEventListener('beforeunload', () => {
73
- setInSessionStorage(tabIdKey, TAB_ID)
74
- })
73
+ if (typeof window !== 'undefined') {
74
+ getGlobalWindow().addEventListener('beforeunload', () => {
75
+ setInSessionStorage(tabIdKey, TAB_ID)
76
+ })
77
+ }
75
78
 
76
79
  const Versions = {
77
80
  Initial: 0,
@@ -2,6 +2,7 @@ import { atom } from '@tldraw/state'
2
2
  import { getDefaultTranslationLocale } from '@tldraw/tlschema'
3
3
  import { getFromLocalStorage, setInLocalStorage, structuredClone, uniqueId } from '@tldraw/utils'
4
4
  import { T } from '@tldraw/validate'
5
+ import { getGlobalWindow } from '../utils/dom'
5
6
 
6
7
  const USER_DATA_KEY = 'TLDRAW_USER_DATA_v3'
7
8
 
@@ -154,8 +155,8 @@ function getRandomColor() {
154
155
 
155
156
  /** @internal */
156
157
  export function userPrefersReducedMotion() {
157
- if (typeof window !== 'undefined' && window.matchMedia) {
158
- return window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches ?? false
158
+ if (typeof window !== 'undefined' && getGlobalWindow().matchMedia) {
159
+ return getGlobalWindow().matchMedia?.('(prefers-reduced-motion: reduce)')?.matches ?? false
159
160
  }
160
161
 
161
162
  return false
@@ -1,6 +1,7 @@
1
1
  import { Signal } from '@tldraw/state'
2
2
  import { HistoryEntry, MigrationSequence, SerializedStore, Store, StoreSchema } from '@tldraw/store'
3
3
  import {
4
+ CustomRecordInfo,
4
5
  SchemaPropsInfo,
5
6
  TLAssetStore,
6
7
  TLRecord,
@@ -42,6 +43,7 @@ export type TLStoreSchemaOptions =
42
43
  shapeUtils?: readonly TLAnyShapeUtilConstructor[]
43
44
  migrations?: readonly MigrationSequence[]
44
45
  bindingUtils?: readonly TLAnyBindingUtilConstructor[]
46
+ records?: Record<string, CustomRecordInfo>
45
47
  }
46
48
 
47
49
  /** @public */
@@ -88,6 +90,7 @@ export function createTLSchemaFromUtils(
88
90
  'bindingUtils' in opts && opts.bindingUtils
89
91
  ? utilsToMap(checkBindings(opts.bindingUtils))
90
92
  : undefined,
93
+ records: 'records' in opts ? opts.records : undefined,
91
94
  migrations: 'migrations' in opts ? opts.migrations : undefined,
92
95
  })
93
96
  }
@@ -108,8 +108,9 @@ import {
108
108
  RIGHT_MOUSE_BUTTON,
109
109
  STYLUS_ERASER_BUTTON,
110
110
  } from '../constants'
111
+ import { getOwnerWindow } from '../exports/domUtils'
111
112
  import { exportToSvg } from '../exports/exportToSvg'
112
- import { getSvgAsImage } from '../exports/getSvgAsImage'
113
+ import { getSvgAsImageWithOptions, trimSvgToContent } from '../exports/getSvgAsImage'
113
114
  import { tlmenus } from '../globals/menus'
114
115
  import { tltime } from '../globals/time'
115
116
  import { TldrawOptions, defaultTldrawOptions } from '../options'
@@ -999,6 +1000,26 @@ export class Editor extends EventEmitter<TLEventMap> {
999
1000
  */
1000
1001
  getContainer: () => HTMLElement
1001
1002
 
1003
+ /**
1004
+ * The document that the editor's container element belongs to.
1005
+ * Use this instead of the global `document` to support cross-window embedding.
1006
+ *
1007
+ * @internal
1008
+ */
1009
+ getContainerDocument(): Document {
1010
+ return this.getContainer().ownerDocument
1011
+ }
1012
+
1013
+ /**
1014
+ * The window that the editor's container element belongs to.
1015
+ * Use this instead of the global `window` to support cross-window embedding.
1016
+ *
1017
+ * @internal
1018
+ */
1019
+ getContainerWindow(): Window & typeof globalThis {
1020
+ return getOwnerWindow(this.getContainer())
1021
+ }
1022
+
1002
1023
  /**
1003
1024
  * Dispose the editor.
1004
1025
  *
@@ -3774,13 +3795,14 @@ export class Editor extends EventEmitter<TLEventMap> {
3774
3795
  screenBounds.height = Math.max(screenBounds.height, 1)
3775
3796
  }
3776
3797
 
3798
+ const doc = this.getContainerDocument()
3777
3799
  const insets = [
3778
3800
  // top
3779
3801
  screenBounds.minY !== 0,
3780
3802
  // right
3781
- !approximately(document.body.scrollWidth, screenBounds.maxX, 1),
3803
+ !approximately(doc.body.scrollWidth, screenBounds.maxX, 1),
3782
3804
  // bottom
3783
- !approximately(document.body.scrollHeight, screenBounds.maxY, 1),
3805
+ !approximately(doc.body.scrollHeight, screenBounds.maxY, 1),
3784
3806
  // left
3785
3807
  screenBounds.minX !== 0,
3786
3808
  ]
@@ -9593,6 +9615,7 @@ export class Editor extends EventEmitter<TLEventMap> {
9593
9615
  svg: serializer.serializeToString(result.svg),
9594
9616
  width: result.width,
9595
9617
  height: result.height,
9618
+ trimPadding: result.trimPadding,
9596
9619
  }
9597
9620
  }
9598
9621
 
@@ -9616,30 +9639,45 @@ export class Editor extends EventEmitter<TLEventMap> {
9616
9639
  if (!result) throw new Error('Could not create SVG')
9617
9640
 
9618
9641
  switch (withDefaults.format) {
9619
- case 'svg':
9642
+ case 'svg': {
9643
+ let svg = result.svg
9644
+ let w = result.width
9645
+ let h = result.height
9646
+ if (result.trimPadding > 0) {
9647
+ const trimmed = await trimSvgToContent(svg, {
9648
+ width: w,
9649
+ height: h,
9650
+ trimPadding: result.trimPadding,
9651
+ scale: withDefaults.scale,
9652
+ })
9653
+ if (trimmed) {
9654
+ svg = trimmed.svg
9655
+ w = trimmed.width
9656
+ h = trimmed.height
9657
+ }
9658
+ }
9620
9659
  return {
9621
- blob: new Blob([result.svg], { type: 'image/svg+xml' }),
9622
- width: result.width,
9623
- height: result.height,
9660
+ blob: new Blob([svg], { type: 'image/svg+xml' }),
9661
+ width: w,
9662
+ height: h,
9624
9663
  }
9664
+ }
9625
9665
  case 'jpeg':
9626
9666
  case 'png':
9627
9667
  case 'webp': {
9628
- const blob = await getSvgAsImage(result.svg, {
9668
+ const imageResult = await getSvgAsImageWithOptions(result.svg, {
9629
9669
  type: withDefaults.format,
9630
9670
  quality: withDefaults.quality,
9631
9671
  pixelRatio: withDefaults.pixelRatio,
9632
9672
  width: result.width,
9633
9673
  height: result.height,
9674
+ trimPadding: result.trimPadding,
9675
+ scale: withDefaults.scale,
9634
9676
  })
9635
- if (!blob) {
9677
+ if (!imageResult) {
9636
9678
  throw new Error('Could not construct image.')
9637
9679
  }
9638
- return {
9639
- blob,
9640
- width: result.width,
9641
- height: result.height,
9642
- }
9680
+ return imageResult
9643
9681
  }
9644
9682
  default: {
9645
9683
  exhaustiveSwitchError(withDefaults.format)
@@ -10078,7 +10116,7 @@ export class Editor extends EventEmitter<TLEventMap> {
10078
10116
  to: opts?.getTarget?.(this),
10079
10117
  })
10080
10118
 
10081
- window.history.replaceState({}, document.title, url.toString())
10119
+ window.history.replaceState({}, this.getContainerDocument().title, url.toString())
10082
10120
  })
10083
10121
 
10084
10122
  const scheduleEffect = debounce((execute: () => void) => execute(), opts?.debounceMs ?? 500)
@@ -36,6 +36,12 @@ describe('FocusManager', () => {
36
36
  // Create mock dispose function
37
37
  mockDispose = vi.fn()
38
38
 
39
+ // Mock document.body event listeners
40
+ originalAddEventListener = document.body.addEventListener
41
+ originalRemoveEventListener = document.body.removeEventListener
42
+ document.body.addEventListener = vi.fn()
43
+ document.body.removeEventListener = vi.fn()
44
+
39
45
  // Mock editor
40
46
  editor = {
41
47
  sideEffects: {
@@ -44,16 +50,11 @@ describe('FocusManager', () => {
44
50
  getInstanceState: vi.fn(() => ({ isFocused: false })),
45
51
  updateInstanceState: vi.fn(),
46
52
  getContainer: vi.fn(() => mockContainer),
53
+ getContainerDocument: vi.fn(() => document),
47
54
  isIn: vi.fn(() => false),
48
55
  getSelectedShapeIds: vi.fn(() => []),
49
56
  complete: vi.fn(),
50
57
  } as any
51
-
52
- // Mock document.body event listeners
53
- originalAddEventListener = document.body.addEventListener
54
- originalRemoveEventListener = document.body.removeEventListener
55
- document.body.addEventListener = vi.fn()
56
- document.body.removeEventListener = vi.fn()
57
58
  })
58
59
 
59
60
  afterEach(() => {
@@ -1,3 +1,4 @@
1
+ import { bind } from '@tldraw/utils'
1
2
  import type { Editor } from '../../Editor'
2
3
 
3
4
  /**
@@ -31,8 +32,9 @@ export class FocusManager {
31
32
  }
32
33
  this.updateContainerClass()
33
34
 
34
- document.body.addEventListener('keydown', this.handleKeyDown.bind(this))
35
- document.body.addEventListener('mousedown', this.handleMouseDown.bind(this))
35
+ const body = editor.getContainerDocument().body
36
+ body.addEventListener('keydown', this.handleKeyDown)
37
+ body.addEventListener('mousedown', this.handleMouseDown)
36
38
  }
37
39
 
38
40
  /**
@@ -56,9 +58,9 @@ export class FocusManager {
56
58
  container.classList.add('tl-container__no-focus-ring')
57
59
  }
58
60
 
59
- private handleKeyDown(keyEvent: KeyboardEvent) {
61
+ @bind private handleKeyDown(keyEvent: KeyboardEvent) {
60
62
  const container = this.editor.getContainer()
61
- const activeEl = document.activeElement
63
+ const activeEl = container.ownerDocument.activeElement
62
64
  // Edit mode should remove the focus ring, however if the active element's
63
65
  // parent is the contextual toolbar, then allow it.
64
66
  if (this.editor.isIn('select.editing_shape') && !activeEl?.closest('.tlui-contextual-toolbar'))
@@ -69,7 +71,7 @@ export class FocusManager {
69
71
  }
70
72
  }
71
73
 
72
- private handleMouseDown() {
74
+ @bind private handleMouseDown() {
73
75
  const container = this.editor.getContainer()
74
76
  container.classList.add('tl-container__no-focus-ring')
75
77
  }
@@ -84,8 +86,9 @@ export class FocusManager {
84
86
  }
85
87
 
86
88
  dispose() {
87
- document.body.removeEventListener('keydown', this.handleKeyDown.bind(this))
88
- document.body.removeEventListener('mousedown', this.handleMouseDown.bind(this))
89
+ const body = this.editor.getContainerDocument().body
90
+ body.removeEventListener('keydown', this.handleKeyDown)
91
+ body.removeEventListener('mousedown', this.handleMouseDown)
89
92
  this.disposeSideEffectListener?.()
90
93
  }
91
94
  }
@@ -85,6 +85,7 @@ describe('FontManager', () => {
85
85
  getShapeUtil: vi.fn(() => mockShapeUtil),
86
86
  getCurrentPageShapeIds: vi.fn(() => new Set()),
87
87
  getShape: vi.fn(),
88
+ getContainerDocument: vi.fn(() => document),
88
89
  isDisposed: false,
89
90
  } as any
90
91
 
@@ -160,7 +160,7 @@ export class FontManager {
160
160
  loadingPromise: instance
161
161
  .load()
162
162
  .then(() => {
163
- document.fonts.add(instance)
163
+ this.editor.getContainerDocument().fonts.add(instance)
164
164
  this.fontStates.update(font, (s) => ({ ...s, state: 'ready' }))
165
165
  })
166
166
  .catch((err) => {
@@ -193,7 +193,8 @@ export class FontManager {
193
193
  }
194
194
 
195
195
  private findOrCreateFontFace(font: TLFontFace) {
196
- for (const existing of document.fonts) {
196
+ const fonts = this.editor.getContainerDocument().fonts
197
+ for (const existing of fonts) {
197
198
  if (
198
199
  existing.family === font.family &&
199
200
  objectMapEntries(defaultFontFaceDescriptors).every(
@@ -210,7 +211,7 @@ export class FontManager {
210
211
  display: 'swap',
211
212
  })
212
213
 
213
- document.fonts.add(instance)
214
+ fonts.add(instance)
214
215
 
215
216
  return instance
216
217
  }
@@ -736,6 +736,22 @@ describe('HistoryManager error scenarios and edge cases', () => {
736
736
  expect(store.get(ids.a)!.value).toBe(originalValue)
737
737
  })
738
738
 
739
+ it('should preserve pending diff when mark is not found', () => {
740
+ manager._mark('real-mark')
741
+ store.update(ids.a, (s) => ({ ...s, value: 1 }))
742
+
743
+ // bail to a mark that doesn't exist
744
+ manager.bailToMark('non-existent-mark')
745
+
746
+ // the pending diff should still be intact
747
+ expect(store.get(ids.a)!.value).toBe(1)
748
+ expect(manager.getNumUndos()).toBeGreaterThan(0)
749
+
750
+ // a subsequent bail to the real mark should still work
751
+ manager.bailToMark('real-mark')
752
+ expect(store.get(ids.a)!.value).toBe(0)
753
+ })
754
+
739
755
  it('should find mark correctly when it exists', () => {
740
756
  manager._mark('existing-mark')
741
757
  store.update(ids.a, (s) => ({ ...s, value: 1 }))