@tldraw/editor 5.1.0 → 5.2.0-canary.019da1aa690a

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 (171) hide show
  1. package/README.md +7 -1
  2. package/dist-cjs/index.d.ts +52 -50
  3. package/dist-cjs/index.js +4 -4
  4. package/dist-cjs/index.js.map +2 -2
  5. package/dist-cjs/lib/components/MenuClickCapture.js +8 -5
  6. package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
  7. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +4 -1
  8. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +3 -3
  9. package/dist-cjs/lib/components/default-components/DefaultLoadingScreen.js +2 -2
  10. package/dist-cjs/lib/components/default-components/DefaultLoadingScreen.js.map +2 -2
  11. package/dist-cjs/lib/components/default-components/DefaultShapeErrorFallback.js +1 -1
  12. package/dist-cjs/lib/components/default-components/DefaultShapeErrorFallback.js.map +3 -3
  13. package/dist-cjs/lib/components/default-components/DefaultSvgDefs.js +2 -2
  14. package/dist-cjs/lib/components/default-components/DefaultSvgDefs.js.map +2 -2
  15. package/dist-cjs/lib/editor/Editor.js +125 -56
  16. package/dist-cjs/lib/editor/Editor.js.map +3 -3
  17. package/dist-cjs/lib/editor/derivations/bindingsIndex.js +2 -2
  18. package/dist-cjs/lib/editor/derivations/bindingsIndex.js.map +2 -2
  19. package/dist-cjs/lib/editor/derivations/parentsToChildren.js +2 -2
  20. package/dist-cjs/lib/editor/derivations/parentsToChildren.js.map +2 -2
  21. package/dist-cjs/lib/editor/derivations/shapeIdsInCurrentPage.js +2 -2
  22. package/dist-cjs/lib/editor/derivations/shapeIdsInCurrentPage.js.map +2 -2
  23. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js +8 -58
  24. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +2 -2
  25. package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js +3 -3
  26. package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js.map +2 -2
  27. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js +1 -2
  28. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js.map +2 -2
  29. package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js +24 -2
  30. package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js.map +2 -2
  31. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js +14 -3
  32. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js.map +2 -2
  33. package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js +4 -2
  34. package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js.map +2 -2
  35. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +7 -3
  36. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
  37. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js +0 -1
  38. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +2 -2
  39. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +15 -2
  40. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  41. package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js +79 -0
  42. package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js.map +7 -0
  43. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +3 -0
  44. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +2 -2
  45. package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
  46. package/dist-cjs/lib/editor/types/event-types.js +0 -2
  47. package/dist-cjs/lib/editor/types/event-types.js.map +2 -2
  48. package/dist-cjs/lib/hooks/useCanvasEvents.js +14 -7
  49. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  50. package/dist-cjs/lib/hooks/usePresence.js.map +2 -2
  51. package/dist-cjs/lib/license/LicenseProvider.js +3 -1
  52. package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
  53. package/dist-cjs/lib/primitives/utils.js +2 -2
  54. package/dist-cjs/lib/primitives/utils.js.map +2 -2
  55. package/dist-cjs/lib/utils/dom.js +5 -3
  56. package/dist-cjs/lib/utils/dom.js.map +2 -2
  57. package/dist-cjs/lib/utils/getPointerInfo.js +2 -1
  58. package/dist-cjs/lib/utils/getPointerInfo.js.map +2 -2
  59. package/dist-cjs/lib/utils/pointer.js +32 -0
  60. package/dist-cjs/lib/utils/pointer.js.map +7 -0
  61. package/dist-cjs/version.js +3 -3
  62. package/dist-cjs/version.js.map +1 -1
  63. package/dist-esm/index.d.mts +52 -50
  64. package/dist-esm/index.mjs +5 -7
  65. package/dist-esm/index.mjs.map +2 -2
  66. package/dist-esm/lib/components/MenuClickCapture.mjs +8 -5
  67. package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
  68. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +4 -1
  69. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +3 -3
  70. package/dist-esm/lib/components/default-components/DefaultLoadingScreen.mjs +2 -2
  71. package/dist-esm/lib/components/default-components/DefaultLoadingScreen.mjs.map +2 -2
  72. package/dist-esm/lib/components/default-components/DefaultShapeErrorFallback.mjs +1 -1
  73. package/dist-esm/lib/components/default-components/DefaultShapeErrorFallback.mjs.map +3 -3
  74. package/dist-esm/lib/components/default-components/DefaultSvgDefs.mjs +2 -2
  75. package/dist-esm/lib/components/default-components/DefaultSvgDefs.mjs.map +2 -2
  76. package/dist-esm/lib/editor/Editor.mjs +125 -56
  77. package/dist-esm/lib/editor/Editor.mjs.map +3 -3
  78. package/dist-esm/lib/editor/derivations/bindingsIndex.mjs +2 -2
  79. package/dist-esm/lib/editor/derivations/bindingsIndex.mjs.map +2 -2
  80. package/dist-esm/lib/editor/derivations/parentsToChildren.mjs +2 -2
  81. package/dist-esm/lib/editor/derivations/parentsToChildren.mjs.map +2 -2
  82. package/dist-esm/lib/editor/derivations/shapeIdsInCurrentPage.mjs +2 -2
  83. package/dist-esm/lib/editor/derivations/shapeIdsInCurrentPage.mjs.map +2 -2
  84. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs +8 -58
  85. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +2 -2
  86. package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs +3 -3
  87. package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs.map +2 -2
  88. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs +1 -2
  89. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs.map +2 -2
  90. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs +24 -2
  91. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs.map +2 -2
  92. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs +14 -3
  93. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs.map +2 -2
  94. package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs +4 -2
  95. package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs.map +2 -2
  96. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +7 -3
  97. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
  98. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs +0 -1
  99. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +2 -2
  100. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +15 -2
  101. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  102. package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs +59 -0
  103. package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs.map +7 -0
  104. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +3 -0
  105. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +2 -2
  106. package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
  107. package/dist-esm/lib/editor/types/event-types.mjs +0 -2
  108. package/dist-esm/lib/editor/types/event-types.mjs.map +2 -2
  109. package/dist-esm/lib/hooks/useCanvasEvents.mjs +14 -7
  110. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  111. package/dist-esm/lib/hooks/usePresence.mjs.map +2 -2
  112. package/dist-esm/lib/license/LicenseProvider.mjs +3 -1
  113. package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
  114. package/dist-esm/lib/primitives/utils.mjs +2 -2
  115. package/dist-esm/lib/primitives/utils.mjs.map +2 -2
  116. package/dist-esm/lib/utils/dom.mjs +5 -3
  117. package/dist-esm/lib/utils/dom.mjs.map +2 -2
  118. package/dist-esm/lib/utils/getPointerInfo.mjs +2 -1
  119. package/dist-esm/lib/utils/getPointerInfo.mjs.map +2 -2
  120. package/dist-esm/lib/utils/pointer.mjs +12 -0
  121. package/dist-esm/lib/utils/pointer.mjs.map +7 -0
  122. package/dist-esm/version.mjs +3 -3
  123. package/dist-esm/version.mjs.map +1 -1
  124. package/editor.css +5 -3
  125. package/package.json +11 -8
  126. package/src/index.ts +2 -5
  127. package/src/lib/components/MenuClickCapture.tsx +8 -4
  128. package/src/lib/components/default-components/DefaultErrorFallback.tsx +4 -1
  129. package/src/lib/components/default-components/DefaultLoadingScreen.tsx +1 -1
  130. package/src/lib/components/default-components/DefaultShapeErrorFallback.tsx +4 -3
  131. package/src/lib/components/default-components/DefaultSvgDefs.tsx +1 -1
  132. package/src/lib/editor/Editor.ts +174 -73
  133. package/src/lib/editor/derivations/bindingsIndex.ts +1 -1
  134. package/src/lib/editor/derivations/parentsToChildren.ts +1 -1
  135. package/src/lib/editor/derivations/shapeIdsInCurrentPage.ts +1 -1
  136. package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +54 -74
  137. package/src/lib/editor/managers/ClickManager/ClickManager.ts +15 -65
  138. package/src/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.test.ts +43 -16
  139. package/src/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.ts +8 -5
  140. package/src/lib/editor/managers/FocusManager/FocusManager.test.ts +4 -4
  141. package/src/lib/editor/managers/FocusManager/FocusManager.ts +1 -2
  142. package/src/lib/editor/managers/FontManager/FontManager.test.ts +13 -9
  143. package/src/lib/editor/managers/HistoryManager/HistoryManager.test.ts +32 -0
  144. package/src/lib/editor/managers/HistoryManager/HistoryManager.ts +34 -4
  145. package/src/lib/editor/managers/InputsManager/InputsManager.test.ts +61 -0
  146. package/src/lib/editor/managers/InputsManager/InputsManager.ts +16 -4
  147. package/src/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.ts +9 -2
  148. package/src/lib/editor/managers/TextManager/TextManager.test.ts +16 -14
  149. package/src/lib/editor/managers/TextManager/TextManager.ts +17 -2
  150. package/src/lib/editor/managers/TickManager/TickManager.test.ts +0 -40
  151. package/src/lib/editor/managers/TickManager/TickManager.ts +0 -1
  152. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +12 -2
  153. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +27 -2
  154. package/src/lib/editor/overlays/strokeShapeIndicators.ts +86 -0
  155. package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +4 -0
  156. package/src/lib/editor/tools/StateNode.ts +0 -2
  157. package/src/lib/editor/types/event-types.ts +2 -6
  158. package/src/lib/hooks/useCanvasEvents.ts +19 -12
  159. package/src/lib/hooks/usePresence.ts +2 -2
  160. package/src/lib/license/LicenseProvider.tsx +3 -1
  161. package/src/lib/primitives/utils.ts +1 -1
  162. package/src/lib/utils/dom.ts +5 -3
  163. package/src/lib/utils/getPointerInfo.ts +2 -1
  164. package/src/lib/utils/pointer.test.ts +48 -0
  165. package/src/lib/utils/pointer.ts +18 -0
  166. package/src/version.ts +3 -3
  167. package/dist-cjs/lib/editor/overlays/ShapeIndicatorOverlayUtil.js +0 -161
  168. package/dist-cjs/lib/editor/overlays/ShapeIndicatorOverlayUtil.js.map +0 -7
  169. package/dist-esm/lib/editor/overlays/ShapeIndicatorOverlayUtil.mjs +0 -141
  170. package/dist-esm/lib/editor/overlays/ShapeIndicatorOverlayUtil.mjs.map +0 -7
  171. package/src/lib/editor/overlays/ShapeIndicatorOverlayUtil.ts +0 -216
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/components/MenuClickCapture.tsx"],
4
- "sourcesContent": ["import { useValue } from '@tldraw/state-react'\nimport { type PointerEvent, useCallback, useEffect, useRef, useState } from 'react'\nimport { flushSync } from 'react-dom'\nimport { useCanvasEvents } from '../hooks/useCanvasEvents'\nimport { useEditor } from '../hooks/useEditor'\nimport { Vec } from '../primitives/Vec'\nimport { releasePointerCapture, setPointerCapture } from '../utils/dom'\nimport { getPointerInfo } from '../utils/getPointerInfo'\n\n/**\n * When a menu is open, this component prevents the user from interacting with the canvas.\n *\n * @public @react\n */\nexport function MenuClickCapture() {\n\tconst editor = useEditor()\n\n\tconst isMenuOpen = useValue('is menu open', () => editor.menus.hasAnyOpenMenus(), [editor])\n\n\t// Keep this component mounted while the pointer is down so pointerup/move still\n\t// land here after a synchronous clearOpenMenus() flips isMenuOpen to false.\n\tconst [isPointing, setIsPointing] = useState(false)\n\tconst showElement = isMenuOpen || isPointing\n\n\tconst canvasEvents = useCanvasEvents()\n\n\tconst rPointerState = useRef({\n\t\tisDown: false,\n\t\tisDragging: false,\n\t\tbutton: 0,\n\t\tstart: new Vec(),\n\t})\n\n\t// Swallow the native contextmenu that follows a right-click pointerdown. Without\n\t// this, Radix's trigger catches the native event and opens a menu at the pointer-\n\t// DOWN position \u2014 then our own synthetic contextmenu (fired on pointerup) opens it\n\t// again at the release position, producing a visible flash.\n\tconst rCancelContextMenuSwallow = useRef<null | (() => void)>(null)\n\tuseEffect(\n\t\t() => () => {\n\t\t\trCancelContextMenuSwallow.current?.()\n\t\t\trCancelContextMenuSwallow.current = null\n\t\t},\n\t\t[]\n\t)\n\tconst swallowNextNativeContextMenu = useCallback(() => {\n\t\trCancelContextMenuSwallow.current?.()\n\t\tconst doc = editor.getContainerDocument()\n\t\tconst onContextMenu = (event: MouseEvent) => {\n\t\t\t// Skip our own synthetic contextmenu \u2014 only swallow the real browser one\n\t\t\tif (!event.isTrusted) return\n\t\t\trCancelContextMenuSwallow.current?.()\n\t\t\trCancelContextMenuSwallow.current = null\n\t\t\tevent.preventDefault()\n\t\t\tevent.stopImmediatePropagation()\n\t\t}\n\t\tconst cancel = () => doc.removeEventListener('contextmenu', onContextMenu, true)\n\t\trCancelContextMenuSwallow.current = cancel\n\t\tdoc.addEventListener('contextmenu', onContextMenu, true)\n\t\t// Drop the listener on the next tick if it never fires (e.g. pointer moved off-screen)\n\t\tdoc.defaultView?.setTimeout(() => {\n\t\t\tif (rCancelContextMenuSwallow.current === cancel) {\n\t\t\t\tcancel()\n\t\t\t\trCancelContextMenuSwallow.current = null\n\t\t\t}\n\t\t}, 0)\n\t}, [editor])\n\n\tconst handlePointerDown = useCallback(\n\t\t(e: PointerEvent) => {\n\t\t\tif (e.button !== 0 && e.button !== 2) return\n\n\t\t\tflushSync(() => setIsPointing(true))\n\t\t\tsetPointerCapture(e.currentTarget, e)\n\t\t\trPointerState.current = {\n\t\t\t\tisDown: true,\n\t\t\t\tisDragging: false,\n\t\t\t\tbutton: e.button,\n\t\t\t\tstart: new Vec(e.clientX, e.clientY),\n\t\t\t}\n\n\t\t\tif (e.button === 2) {\n\t\t\t\tif (!editor.options.rightClickPanning) {\n\t\t\t\t\t// Right-click panning off: close the open menu and swallow the native\n\t\t\t\t\t// contextmenu that would otherwise briefly open a new one (causing a flash).\n\t\t\t\t\tswallowNextNativeContextMenu()\n\t\t\t\t\teditor.menus.clearOpenMenus()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// Forward right-click pointerdown through the canvas's own handler so\n\t\t\t\t// pointer capture is also set on the canvas (load-bearing: without this\n\t\t\t\t// the context menu briefly flashes closed during consecutive right-clicks).\n\t\t\t\t// We don't clearOpenMenus() \u2014 Radix's DismissableLayer closes the menu\n\t\t\t\t// via outside-click detection, keeping its internal state in sync.\n\t\t\t\tconst canvas =\n\t\t\t\t\teditor.getContainer().querySelector<HTMLDivElement>('.tl-canvas') ?? e.currentTarget\n\t\t\t\tcanvasEvents.onPointerDown?.({ ...e, currentTarget: canvas })\n\t\t\t\tswallowNextNativeContextMenu()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\teditor.menus.clearOpenMenus()\n\t\t},\n\t\t[canvasEvents, editor, swallowNextNativeContextMenu]\n\t)\n\n\tconst handlePointerMove = useCallback(\n\t\t(e: PointerEvent) => {\n\t\t\tconst state = rPointerState.current\n\t\t\tif (!state.isDown) return\n\n\t\t\t// Left-click: wait for the drag threshold before forwarding anything, then\n\t\t\t// replay pointerdown at the original start so the editor records the\n\t\t\t// correct drag origin. Right-click forwards moves immediately (pointerdown\n\t\t\t// was already dispatched in handlePointerDown).\n\t\t\tif (state.button !== 2 && !state.isDragging) {\n\t\t\t\tif (\n\t\t\t\t\tVec.Dist2(state.start, new Vec(e.clientX, e.clientY)) <=\n\t\t\t\t\teditor.options.dragDistanceSquared\n\t\t\t\t) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tstate.isDragging = true\n\t\t\t\teditor.dispatch({\n\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t\tname: 'pointer_down',\n\t\t\t\t\t...getPointerInfo(editor, { ...e, clientX: state.start.x, clientY: state.start.y }),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\teditor.dispatch({\n\t\t\t\ttype: 'pointer',\n\t\t\t\ttarget: 'canvas',\n\t\t\t\tname: 'pointer_move',\n\t\t\t\t...getPointerInfo(editor, e),\n\t\t\t})\n\t\t},\n\t\t[editor]\n\t)\n\n\tconst handlePointerUp = useCallback(\n\t\t(e: PointerEvent) => {\n\t\t\tconst isStaticRightClick = e.button === 2 && !rPointerState.current.isDragging\n\n\t\t\teditor.dispatch({\n\t\t\t\ttype: 'pointer',\n\t\t\t\ttarget: 'canvas',\n\t\t\t\tname: 'pointer_up',\n\t\t\t\t...getPointerInfo(editor, e),\n\t\t\t})\n\n\t\t\tif (isStaticRightClick && editor.options.rightClickPanning) {\n\t\t\t\t// Dispatch contextmenu on the canvas's parent (Radix's trigger) so the\n\t\t\t\t// menu opens at the release position. Bypassing the canvas avoids its\n\t\t\t\t// own onContextMenu handler, which preventDefaults non-synthesized events.\n\t\t\t\tconst canvas = editor.getContainer().querySelector<HTMLDivElement>('.tl-canvas')\n\t\t\t\tconst trigger = canvas?.parentElement ?? e.currentTarget\n\t\t\t\teditor.timers.requestAnimationFrame(() => {\n\t\t\t\t\ttrigger.dispatchEvent(\n\t\t\t\t\t\tnew PointerEvent('contextmenu', {\n\t\t\t\t\t\t\tbubbles: true,\n\t\t\t\t\t\t\tclientX: e.clientX,\n\t\t\t\t\t\t\tclientY: e.clientY,\n\t\t\t\t\t\t\tbutton: 2,\n\t\t\t\t\t\t\tbuttons: 0,\n\t\t\t\t\t\t\tpointerId: e.pointerId,\n\t\t\t\t\t\t\tpointerType: e.pointerType,\n\t\t\t\t\t\t\tisPrimary: e.isPrimary,\n\t\t\t\t\t\t})\n\t\t\t\t\t)\n\t\t\t\t})\n\t\t\t}\n\n\t\t\treleasePointerCapture(e.currentTarget, e)\n\t\t\tsetIsPointing(false)\n\t\t\trPointerState.current = {\n\t\t\t\tisDown: false,\n\t\t\t\tisDragging: false,\n\t\t\t\tbutton: 0,\n\t\t\t\tstart: new Vec(e.clientX, e.clientY),\n\t\t\t}\n\t\t},\n\t\t[editor]\n\t)\n\n\treturn (\n\t\tshowElement && (\n\t\t\t<div\n\t\t\t\tclassName=\"tlui-menu-click-capture\"\n\t\t\t\tdata-testid=\"menu-click-capture.content\"\n\t\t\t\t{...canvasEvents}\n\t\t\t\tonPointerDown={handlePointerDown}\n\t\t\t\tonPointerMove={handlePointerMove}\n\t\t\t\tonPointerUp={handlePointerUp}\n\t\t\t\tonContextMenu={(e) => {\n\t\t\t\t\te.preventDefault()\n\t\t\t\t\te.stopPropagation()\n\t\t\t\t}}\n\t\t\t/>\n\t\t)\n\t)\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { useValue } from '@tldraw/state-react'\nimport { type PointerEvent, useCallback, useEffect, useRef, useState } from 'react'\nimport { flushSync } from 'react-dom'\nimport { useCanvasEvents } from '../hooks/useCanvasEvents'\nimport { useEditor } from '../hooks/useEditor'\nimport { Vec } from '../primitives/Vec'\nimport { releasePointerCapture, setPointerCapture } from '../utils/dom'\nimport { getPointerInfo } from '../utils/getPointerInfo'\nimport { getPointerEventButton } from '../utils/pointer'\n\n/**\n * When a menu is open, this component prevents the user from interacting with the canvas.\n *\n * @public @react\n */\nexport function MenuClickCapture() {\n\tconst editor = useEditor()\n\n\tconst isMenuOpen = useValue('is menu open', () => editor.menus.hasAnyOpenMenus(), [editor])\n\n\t// Keep this component mounted while the pointer is down so pointerup/move still\n\t// land here after a synchronous clearOpenMenus() flips isMenuOpen to false.\n\tconst [isPointing, setIsPointing] = useState(false)\n\tconst showElement = isMenuOpen || isPointing\n\n\tconst canvasEvents = useCanvasEvents()\n\n\tconst rPointerState = useRef({\n\t\tisDown: false,\n\t\tisDragging: false,\n\t\tbutton: 0,\n\t\tstart: new Vec(),\n\t})\n\n\t// Swallow the native contextmenu that follows a right-click pointerdown. Without\n\t// this, Radix's trigger catches the native event and opens a menu at the pointer-\n\t// DOWN position \u2014 then our own synthetic contextmenu (fired on pointerup) opens it\n\t// again at the release position, producing a visible flash.\n\tconst rCancelContextMenuSwallow = useRef<null | (() => void)>(null)\n\tuseEffect(\n\t\t() => () => {\n\t\t\trCancelContextMenuSwallow.current?.()\n\t\t\trCancelContextMenuSwallow.current = null\n\t\t},\n\t\t[]\n\t)\n\tconst swallowNextNativeContextMenu = useCallback(() => {\n\t\trCancelContextMenuSwallow.current?.()\n\t\tconst doc = editor.getContainerDocument()\n\t\tconst onContextMenu = (event: MouseEvent) => {\n\t\t\t// Skip our own synthetic contextmenu \u2014 only swallow the real browser one\n\t\t\tif (!event.isTrusted) return\n\t\t\trCancelContextMenuSwallow.current?.()\n\t\t\trCancelContextMenuSwallow.current = null\n\t\t\tevent.preventDefault()\n\t\t\tevent.stopImmediatePropagation()\n\t\t}\n\t\tconst cancel = () => doc.removeEventListener('contextmenu', onContextMenu, true)\n\t\trCancelContextMenuSwallow.current = cancel\n\t\tdoc.addEventListener('contextmenu', onContextMenu, true)\n\t\t// Drop the listener on the next tick if it never fires (e.g. pointer moved off-screen)\n\t\tdoc.defaultView?.setTimeout(() => {\n\t\t\tif (rCancelContextMenuSwallow.current === cancel) {\n\t\t\t\tcancel()\n\t\t\t\trCancelContextMenuSwallow.current = null\n\t\t\t}\n\t\t}, 0)\n\t}, [editor])\n\n\tconst handlePointerDown = useCallback(\n\t\t(e: PointerEvent) => {\n\t\t\tconst button = getPointerEventButton(e)\n\t\t\tif (button !== 0 && button !== 2) return\n\n\t\t\tflushSync(() => setIsPointing(true))\n\t\t\tsetPointerCapture(e.currentTarget, e)\n\t\t\trPointerState.current = {\n\t\t\t\tisDown: true,\n\t\t\t\tisDragging: false,\n\t\t\t\tbutton,\n\t\t\t\tstart: new Vec(e.clientX, e.clientY),\n\t\t\t}\n\n\t\t\tif (button === 2) {\n\t\t\t\tif (!editor.options.rightClickPanning) {\n\t\t\t\t\t// Right-click panning off: close the open menu and swallow the native\n\t\t\t\t\t// contextmenu that would otherwise briefly open a new one (causing a flash).\n\t\t\t\t\tswallowNextNativeContextMenu()\n\t\t\t\t\teditor.menus.clearOpenMenus()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// Forward right-click pointerdown through the canvas's own handler so\n\t\t\t\t// pointer capture is also set on the canvas (load-bearing: without this\n\t\t\t\t// the context menu briefly flashes closed during consecutive right-clicks).\n\t\t\t\t// We don't clearOpenMenus() \u2014 Radix's DismissableLayer closes the menu\n\t\t\t\t// via outside-click detection, keeping its internal state in sync.\n\t\t\t\tconst canvas =\n\t\t\t\t\teditor.getContainer().querySelector<HTMLDivElement>('.tl-canvas') ?? e.currentTarget\n\t\t\t\tcanvasEvents.onPointerDown?.({ ...e, currentTarget: canvas })\n\t\t\t\tswallowNextNativeContextMenu()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\teditor.menus.clearOpenMenus()\n\t\t},\n\t\t[canvasEvents, editor, swallowNextNativeContextMenu]\n\t)\n\n\tconst handlePointerMove = useCallback(\n\t\t(e: PointerEvent) => {\n\t\t\tconst state = rPointerState.current\n\t\t\tif (!state.isDown) return\n\n\t\t\t// Left-click: wait for the drag threshold before forwarding anything, then\n\t\t\t// replay pointerdown at the original start so the editor records the\n\t\t\t// correct drag origin. Right-click forwards moves immediately (pointerdown\n\t\t\t// was already dispatched in handlePointerDown).\n\t\t\tif (state.button !== 2 && !state.isDragging) {\n\t\t\t\tif (\n\t\t\t\t\tVec.Dist2(state.start, new Vec(e.clientX, e.clientY)) <=\n\t\t\t\t\teditor.options.dragDistanceSquared\n\t\t\t\t) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tstate.isDragging = true\n\t\t\t\teditor.dispatch({\n\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t\tname: 'pointer_down',\n\t\t\t\t\t...getPointerInfo(editor, { ...e, clientX: state.start.x, clientY: state.start.y }),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\teditor.dispatch({\n\t\t\t\ttype: 'pointer',\n\t\t\t\ttarget: 'canvas',\n\t\t\t\tname: 'pointer_move',\n\t\t\t\t...getPointerInfo(editor, e),\n\t\t\t})\n\t\t},\n\t\t[editor]\n\t)\n\n\tconst handlePointerUp = useCallback(\n\t\t(e: PointerEvent) => {\n\t\t\tconst isStaticRightClick =\n\t\t\t\trPointerState.current.button === 2 && !rPointerState.current.isDragging\n\n\t\t\teditor.dispatch({\n\t\t\t\ttype: 'pointer',\n\t\t\t\ttarget: 'canvas',\n\t\t\t\tname: 'pointer_up',\n\t\t\t\t...getPointerInfo(editor, e),\n\t\t\t\tbutton: rPointerState.current.button === 2 ? 2 : getPointerEventButton(e),\n\t\t\t})\n\n\t\t\tif (isStaticRightClick && editor.options.rightClickPanning) {\n\t\t\t\t// Dispatch contextmenu on the canvas's parent (Radix's trigger) so the\n\t\t\t\t// menu opens at the release position. Bypassing the canvas avoids its\n\t\t\t\t// own onContextMenu handler, which preventDefaults non-synthesized events.\n\t\t\t\tconst canvas = editor.getContainer().querySelector<HTMLDivElement>('.tl-canvas')\n\t\t\t\tconst trigger = canvas?.parentElement ?? e.currentTarget\n\t\t\t\teditor.timers.requestAnimationFrame(() => {\n\t\t\t\t\ttrigger.dispatchEvent(\n\t\t\t\t\t\tnew PointerEvent('contextmenu', {\n\t\t\t\t\t\t\tbubbles: true,\n\t\t\t\t\t\t\tclientX: e.clientX,\n\t\t\t\t\t\t\tclientY: e.clientY,\n\t\t\t\t\t\t\tbutton: 2,\n\t\t\t\t\t\t\tbuttons: 0,\n\t\t\t\t\t\t\tpointerId: e.pointerId,\n\t\t\t\t\t\t\tpointerType: e.pointerType,\n\t\t\t\t\t\t\tisPrimary: e.isPrimary,\n\t\t\t\t\t\t})\n\t\t\t\t\t)\n\t\t\t\t})\n\t\t\t}\n\n\t\t\treleasePointerCapture(e.currentTarget, e)\n\t\t\tsetIsPointing(false)\n\t\t\trPointerState.current = {\n\t\t\t\tisDown: false,\n\t\t\t\tisDragging: false,\n\t\t\t\tbutton: 0,\n\t\t\t\tstart: new Vec(e.clientX, e.clientY),\n\t\t\t}\n\t\t},\n\t\t[editor]\n\t)\n\n\treturn (\n\t\tshowElement && (\n\t\t\t<div\n\t\t\t\tclassName=\"tlui-menu-click-capture\"\n\t\t\t\tdata-testid=\"menu-click-capture.content\"\n\t\t\t\t{...canvasEvents}\n\t\t\t\tonPointerDown={handlePointerDown}\n\t\t\t\tonPointerMove={handlePointerMove}\n\t\t\t\tonPointerUp={handlePointerUp}\n\t\t\t\tonContextMenu={(e) => {\n\t\t\t\t\te.preventDefault()\n\t\t\t\t\te.stopPropagation()\n\t\t\t\t}}\n\t\t\t/>\n\t\t)\n\t)\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -10,7 +10,10 @@ import { getGlobalWindow } from "../../utils/dom.mjs";
10
10
  import { hardResetEditor, refreshPage } from "../../utils/runtime.mjs";
11
11
  import { ErrorBoundary } from "../ErrorBoundary.mjs";
12
12
  const BASE_ERROR_URL = "https://github.com/tldraw/tldraw/issues/new";
13
- const DefaultErrorFallback = ({ error, editor }) => {
13
+ const DefaultErrorFallback = function DefaultErrorFallback2({
14
+ error,
15
+ editor
16
+ }) {
14
17
  const containerRef = useRef(null);
15
18
  const [shouldShowError, setShouldShowError] = useState(process.env.NODE_ENV === "development");
16
19
  const [didCopy, setDidCopy] = useState(false);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/components/default-components/DefaultErrorFallback.tsx"],
4
- "sourcesContent": ["import { useValue } from '@tldraw/state-react'\nimport { noop } from '@tldraw/utils'\nimport classNames from 'classnames'\nimport { ComponentType, useEffect, useLayoutEffect, useRef, useState } from 'react'\nimport { Editor } from '../../editor/Editor'\nimport { getOwnerDocument } from '../../exports/domUtils'\nimport { useEditorComponents } from '../../hooks/EditorComponentsContext'\nimport { EditorProvider } from '../../hooks/useEditor'\nimport { getGlobalWindow } from '../../utils/dom'\nimport { hardResetEditor, refreshPage } from '../../utils/runtime'\nimport { ErrorBoundary } from '../ErrorBoundary'\n\nconst BASE_ERROR_URL = 'https://github.com/tldraw/tldraw/issues/new'\n\n/** @public */\nexport type TLErrorFallbackComponent = ComponentType<{ error: unknown; editor?: Editor }>\n\n/** @public @react */\nexport const DefaultErrorFallback: TLErrorFallbackComponent = ({ error, editor }) => {\n\tconst containerRef = useRef<HTMLDivElement>(null)\n\tconst [shouldShowError, setShouldShowError] = useState(process.env.NODE_ENV === 'development')\n\tconst [didCopy, setDidCopy] = useState(false)\n\tconst [shouldShowResetConfirmation, setShouldShowResetConfirmation] = useState(false)\n\n\tlet Canvas: React.ComponentType | null = null\n\ttry {\n\t\t// eslint-disable-next-line react-hooks/rules-of-hooks\n\t\tconst components = useEditorComponents()\n\t\tCanvas = components.Canvas ?? null\n\t} catch {\n\t\t// allow this to fail silently\n\t}\n\n\tconst errorMessage = error instanceof Error ? error.message : String(error)\n\tconst errorStack = error instanceof Error ? error.stack : null\n\n\tconst isDarkModeFromApp = useValue(\n\t\t'isDarkMode',\n\t\t() => {\n\t\t\ttry {\n\t\t\t\tif (editor) {\n\t\t\t\t\treturn editor.user.getIsDarkMode()\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// we're in a funky error state so this might not work for spooky\n\t\t\t\t// reasons. if not, we'll have another attempt later:\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\t\t[editor]\n\t)\n\tconst [isDarkMode, setIsDarkMode] = useState<null | boolean>(null)\n\tuseLayoutEffect(() => {\n\t\t// if we found a theme class from the app, we can just use that\n\t\tif (isDarkModeFromApp !== null) {\n\t\t\tsetIsDarkMode(isDarkModeFromApp)\n\t\t}\n\n\t\t// do any of our parents have a theme class? if yes then we can just\n\t\t// rely on that and don't need to set our own class\n\t\tlet parent = containerRef.current?.parentElement\n\t\tlet foundParentThemeClass = false\n\t\twhile (parent) {\n\t\t\tif (\n\t\t\t\tparent.classList.contains('tl-theme__dark') ||\n\t\t\t\tparent.classList.contains('tl-theme__light')\n\t\t\t) {\n\t\t\t\tfoundParentThemeClass = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tparent = parent.parentElement\n\t\t}\n\t\tif (foundParentThemeClass) {\n\t\t\tsetIsDarkMode(null)\n\t\t\treturn\n\t\t}\n\n\t\t// if we can't find a theme class from the app or from a parent, we have\n\t\t// to fall back on using a media query:\n\t\tif (typeof window !== 'undefined' && getGlobalWindow().matchMedia) {\n\t\t\tsetIsDarkMode(getGlobalWindow().matchMedia('(prefers-color-scheme: dark)').matches)\n\t\t}\n\t}, [isDarkModeFromApp])\n\n\tuseEffect(() => {\n\t\tif (didCopy) {\n\t\t\tconst timeout = editor?.timers.setTimeout(() => {\n\t\t\t\tsetDidCopy(false)\n\t\t\t}, 2000)\n\t\t\treturn () => clearTimeout(timeout)\n\t\t}\n\t}, [didCopy, editor])\n\n\tconst copyError = () => {\n\t\tconst doc = getOwnerDocument(containerRef.current)\n\t\tconst textarea = doc.createElement('textarea')\n\t\ttextarea.value = errorStack ?? errorMessage\n\t\tdoc.body.appendChild(textarea)\n\t\ttextarea.select()\n\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\tdoc.execCommand('copy')\n\t\ttextarea.remove()\n\t\tsetDidCopy(true)\n\t}\n\n\tconst refresh = () => {\n\t\trefreshPage()\n\t}\n\n\tconst resetLocalState = async () => {\n\t\thardResetEditor()\n\t}\n\n\tconst url = new URL(BASE_ERROR_URL)\n\turl.searchParams.set('title', errorMessage)\n\turl.searchParams.set('labels', `bug`)\n\turl.searchParams.set(\n\t\t'body',\n\t\t`Hey, I ran into an error while using tldraw:\n\n\\`\\`\\`js\n${errorStack ?? errorMessage}\n\\`\\`\\`\n\nMy browser: ${navigator.userAgent}`\n\t)\n\n\treturn (\n\t\t<div\n\t\t\tref={containerRef}\n\t\t\tclassName={classNames(\n\t\t\t\t'tl-container tl-error-boundary',\n\t\t\t\t// error-boundary is sometimes used outside of the theme\n\t\t\t\t// container, so we need to provide it with a theme for our\n\t\t\t\t// styles to work correctly\n\t\t\t\tisDarkMode === null ? '' : isDarkMode ? 'tl-theme__dark' : 'tl-theme__light'\n\t\t\t)}\n\t\t>\n\t\t\t<div className=\"tl-error-boundary__overlay\" />\n\t\t\t{editor && (\n\t\t\t\t// opportunistically attempt to render the canvas to reassure\n\t\t\t\t// the user that their document is still there. there's a good\n\t\t\t\t// chance this won't work (ie the error that we're currently\n\t\t\t\t// notifying the user about originates in the canvas) so it's\n\t\t\t\t// not a big deal if it doesn't work - in that case we just have\n\t\t\t\t// a plain grey background.\n\t\t\t\t<ErrorBoundary onError={noop} fallback={() => null}>\n\t\t\t\t\t<EditorProvider editor={editor}>\n\t\t\t\t\t\t<div className=\"tl-overlay tl-error-boundary__canvas\">{Canvas ? <Canvas /> : null}</div>\n\t\t\t\t\t</EditorProvider>\n\t\t\t\t</ErrorBoundary>\n\t\t\t)}\n\t\t\t<div\n\t\t\t\tclassName={classNames('tl-modal', 'tl-error-boundary__content', {\n\t\t\t\t\t'tl-error-boundary__content__expanded': shouldShowError && !shouldShowResetConfirmation,\n\t\t\t\t})}\n\t\t\t>\n\t\t\t\t{shouldShowResetConfirmation ? (\n\t\t\t\t\t<>\n\t\t\t\t\t\t<h2>Are you sure?</h2>\n\t\t\t\t\t\t<p>Resetting your data will delete your drawing and cannot be undone.</p>\n\t\t\t\t\t\t<div className=\"tl-error-boundary__content__actions\">\n\t\t\t\t\t\t\t<button className=\"tlui-button\" onClick={() => setShouldShowResetConfirmation(false)}>\n\t\t\t\t\t\t\t\tCancel\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t<button className=\"tlui-button tl-error-boundary__reset\" onClick={resetLocalState}>\n\t\t\t\t\t\t\t\tReset data\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</>\n\t\t\t\t) : (\n\t\t\t\t\t<>\n\t\t\t\t\t\t<h2>Something went wrong</h2>\n\t\t\t\t\t\t<p>Please refresh your browser.</p>\n\t\t\t\t\t\t<p>\n\t\t\t\t\t\t\tIf the issue continues after refreshing, you may need to reset the tldraw data stored\n\t\t\t\t\t\t\ton your device.\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<p>\n\t\t\t\t\t\t\t<strong>Note:</strong> Resetting will erase your current project and any unsaved work.\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t{process.env.NODE_ENV !== 'production' && (\n\t\t\t\t\t\t\t<p>\n\t\t\t\t\t\t\t\tIf you&apos;re developing with the SDK and need help, join us on{' '}\n\t\t\t\t\t\t\t\t<a href=\"https://discord.tldraw.com/?utm_source=sdk&utm_medium=organic&utm_campaign=error-screen\">\n\t\t\t\t\t\t\t\t\tDiscord\n\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t\t.\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{shouldShowError && (\n\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\tMessage:\n\t\t\t\t\t\t\t\t<h4>\n\t\t\t\t\t\t\t\t\t<code>{errorMessage}</code>\n\t\t\t\t\t\t\t\t</h4>\n\t\t\t\t\t\t\t\tStack trace:\n\t\t\t\t\t\t\t\t<div className=\"tl-error-boundary__content__error\">\n\t\t\t\t\t\t\t\t\t<pre>\n\t\t\t\t\t\t\t\t\t\t<code>{errorStack ?? errorMessage}</code>\n\t\t\t\t\t\t\t\t\t</pre>\n\t\t\t\t\t\t\t\t\t<button className=\"tlui-button\" onClick={copyError}>\n\t\t\t\t\t\t\t\t\t\t{didCopy ? 'Copied!' : 'Copy'}\n\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t<div className=\"tl-error-boundary__content__actions\">\n\t\t\t\t\t\t\t<button className=\"tlui-button\" onClick={() => setShouldShowError(!shouldShowError)}>\n\t\t\t\t\t\t\t\t{shouldShowError ? 'Hide details' : 'Show details'}\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t<div className=\"tl-error-boundary__content__actions__group\">\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\tclassName=\"tlui-button tl-error-boundary__reset\"\n\t\t\t\t\t\t\t\t\tonClick={() => setShouldShowResetConfirmation(true)}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\tReset data\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t<button className=\"tlui-button tl-error-boundary__refresh\" onClick={refresh}>\n\t\t\t\t\t\t\t\t\tRefresh Page\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n"],
5
- "mappings": "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",
6
- "names": []
4
+ "sourcesContent": ["import { useValue } from '@tldraw/state-react'\nimport { noop } from '@tldraw/utils'\nimport classNames from 'classnames'\nimport { ComponentType, useEffect, useLayoutEffect, useRef, useState } from 'react'\nimport { Editor } from '../../editor/Editor'\nimport { getOwnerDocument } from '../../exports/domUtils'\nimport { useEditorComponents } from '../../hooks/EditorComponentsContext'\nimport { EditorProvider } from '../../hooks/useEditor'\nimport { getGlobalWindow } from '../../utils/dom'\nimport { hardResetEditor, refreshPage } from '../../utils/runtime'\nimport { ErrorBoundary } from '../ErrorBoundary'\n\nconst BASE_ERROR_URL = 'https://github.com/tldraw/tldraw/issues/new'\n\n/** @public */\nexport type TLErrorFallbackComponent = ComponentType<{ error: unknown; editor?: Editor }>\n\n/** @public @react */\nexport const DefaultErrorFallback: TLErrorFallbackComponent = function DefaultErrorFallback({\n\terror,\n\teditor,\n}) {\n\tconst containerRef = useRef<HTMLDivElement>(null)\n\tconst [shouldShowError, setShouldShowError] = useState(process.env.NODE_ENV === 'development')\n\tconst [didCopy, setDidCopy] = useState(false)\n\tconst [shouldShowResetConfirmation, setShouldShowResetConfirmation] = useState(false)\n\n\tlet Canvas: React.ComponentType | null = null\n\ttry {\n\t\t// eslint-disable-next-line react-hooks/rules-of-hooks\n\t\tconst components = useEditorComponents()\n\t\tCanvas = components.Canvas ?? null\n\t} catch {\n\t\t// allow this to fail silently\n\t}\n\n\tconst errorMessage = error instanceof Error ? error.message : String(error)\n\tconst errorStack = error instanceof Error ? error.stack : null\n\n\tconst isDarkModeFromApp = useValue(\n\t\t'isDarkMode',\n\t\t() => {\n\t\t\ttry {\n\t\t\t\tif (editor) {\n\t\t\t\t\treturn editor.user.getIsDarkMode()\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// we're in a funky error state so this might not work for spooky\n\t\t\t\t// reasons. if not, we'll have another attempt later:\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\t\t[editor]\n\t)\n\tconst [isDarkMode, setIsDarkMode] = useState<null | boolean>(null)\n\tuseLayoutEffect(() => {\n\t\t// if we found a theme class from the app, we can just use that\n\t\tif (isDarkModeFromApp !== null) {\n\t\t\tsetIsDarkMode(isDarkModeFromApp)\n\t\t}\n\n\t\t// do any of our parents have a theme class? if yes then we can just\n\t\t// rely on that and don't need to set our own class\n\t\tlet parent = containerRef.current?.parentElement\n\t\tlet foundParentThemeClass = false\n\t\twhile (parent) {\n\t\t\tif (\n\t\t\t\tparent.classList.contains('tl-theme__dark') ||\n\t\t\t\tparent.classList.contains('tl-theme__light')\n\t\t\t) {\n\t\t\t\tfoundParentThemeClass = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tparent = parent.parentElement\n\t\t}\n\t\tif (foundParentThemeClass) {\n\t\t\tsetIsDarkMode(null)\n\t\t\treturn\n\t\t}\n\n\t\t// if we can't find a theme class from the app or from a parent, we have\n\t\t// to fall back on using a media query:\n\t\tif (typeof window !== 'undefined' && getGlobalWindow().matchMedia) {\n\t\t\tsetIsDarkMode(getGlobalWindow().matchMedia('(prefers-color-scheme: dark)').matches)\n\t\t}\n\t}, [isDarkModeFromApp])\n\n\tuseEffect(() => {\n\t\tif (didCopy) {\n\t\t\tconst timeout = editor?.timers.setTimeout(() => {\n\t\t\t\tsetDidCopy(false)\n\t\t\t}, 2000)\n\t\t\treturn () => clearTimeout(timeout)\n\t\t}\n\t}, [didCopy, editor])\n\n\tconst copyError = () => {\n\t\tconst doc = getOwnerDocument(containerRef.current)\n\t\tconst textarea = doc.createElement('textarea')\n\t\ttextarea.value = errorStack ?? errorMessage\n\t\tdoc.body.appendChild(textarea)\n\t\ttextarea.select()\n\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\tdoc.execCommand('copy')\n\t\ttextarea.remove()\n\t\tsetDidCopy(true)\n\t}\n\n\tconst refresh = () => {\n\t\trefreshPage()\n\t}\n\n\tconst resetLocalState = async () => {\n\t\thardResetEditor()\n\t}\n\n\tconst url = new URL(BASE_ERROR_URL)\n\turl.searchParams.set('title', errorMessage)\n\turl.searchParams.set('labels', `bug`)\n\turl.searchParams.set(\n\t\t'body',\n\t\t`Hey, I ran into an error while using tldraw:\n\n\\`\\`\\`js\n${errorStack ?? errorMessage}\n\\`\\`\\`\n\nMy browser: ${navigator.userAgent}`\n\t)\n\n\treturn (\n\t\t<div\n\t\t\tref={containerRef}\n\t\t\tclassName={classNames(\n\t\t\t\t'tl-container tl-error-boundary',\n\t\t\t\t// error-boundary is sometimes used outside of the theme\n\t\t\t\t// container, so we need to provide it with a theme for our\n\t\t\t\t// styles to work correctly\n\t\t\t\tisDarkMode === null ? '' : isDarkMode ? 'tl-theme__dark' : 'tl-theme__light'\n\t\t\t)}\n\t\t>\n\t\t\t<div className=\"tl-error-boundary__overlay\" />\n\t\t\t{editor && (\n\t\t\t\t// opportunistically attempt to render the canvas to reassure\n\t\t\t\t// the user that their document is still there. there's a good\n\t\t\t\t// chance this won't work (ie the error that we're currently\n\t\t\t\t// notifying the user about originates in the canvas) so it's\n\t\t\t\t// not a big deal if it doesn't work - in that case we just have\n\t\t\t\t// a plain grey background.\n\t\t\t\t<ErrorBoundary onError={noop} fallback={() => null}>\n\t\t\t\t\t<EditorProvider editor={editor}>\n\t\t\t\t\t\t<div className=\"tl-overlay tl-error-boundary__canvas\">{Canvas ? <Canvas /> : null}</div>\n\t\t\t\t\t</EditorProvider>\n\t\t\t\t</ErrorBoundary>\n\t\t\t)}\n\t\t\t<div\n\t\t\t\tclassName={classNames('tl-modal', 'tl-error-boundary__content', {\n\t\t\t\t\t'tl-error-boundary__content__expanded': shouldShowError && !shouldShowResetConfirmation,\n\t\t\t\t})}\n\t\t\t>\n\t\t\t\t{shouldShowResetConfirmation ? (\n\t\t\t\t\t<>\n\t\t\t\t\t\t<h2>Are you sure?</h2>\n\t\t\t\t\t\t<p>Resetting your data will delete your drawing and cannot be undone.</p>\n\t\t\t\t\t\t<div className=\"tl-error-boundary__content__actions\">\n\t\t\t\t\t\t\t<button className=\"tlui-button\" onClick={() => setShouldShowResetConfirmation(false)}>\n\t\t\t\t\t\t\t\tCancel\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t<button className=\"tlui-button tl-error-boundary__reset\" onClick={resetLocalState}>\n\t\t\t\t\t\t\t\tReset data\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</>\n\t\t\t\t) : (\n\t\t\t\t\t<>\n\t\t\t\t\t\t<h2>Something went wrong</h2>\n\t\t\t\t\t\t<p>Please refresh your browser.</p>\n\t\t\t\t\t\t<p>\n\t\t\t\t\t\t\tIf the issue continues after refreshing, you may need to reset the tldraw data stored\n\t\t\t\t\t\t\ton your device.\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<p>\n\t\t\t\t\t\t\t<strong>Note:</strong> Resetting will erase your current project and any unsaved work.\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t{process.env.NODE_ENV !== 'production' && (\n\t\t\t\t\t\t\t<p>\n\t\t\t\t\t\t\t\tIf you&apos;re developing with the SDK and need help, join us on{' '}\n\t\t\t\t\t\t\t\t<a href=\"https://discord.tldraw.com/?utm_source=sdk&utm_medium=organic&utm_campaign=error-screen\">\n\t\t\t\t\t\t\t\t\tDiscord\n\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t\t.\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{shouldShowError && (\n\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\tMessage:\n\t\t\t\t\t\t\t\t<h4>\n\t\t\t\t\t\t\t\t\t<code>{errorMessage}</code>\n\t\t\t\t\t\t\t\t</h4>\n\t\t\t\t\t\t\t\tStack trace:\n\t\t\t\t\t\t\t\t<div className=\"tl-error-boundary__content__error\">\n\t\t\t\t\t\t\t\t\t<pre>\n\t\t\t\t\t\t\t\t\t\t<code>{errorStack ?? errorMessage}</code>\n\t\t\t\t\t\t\t\t\t</pre>\n\t\t\t\t\t\t\t\t\t<button className=\"tlui-button\" onClick={copyError}>\n\t\t\t\t\t\t\t\t\t\t{didCopy ? 'Copied!' : 'Copy'}\n\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t<div className=\"tl-error-boundary__content__actions\">\n\t\t\t\t\t\t\t<button className=\"tlui-button\" onClick={() => setShouldShowError(!shouldShowError)}>\n\t\t\t\t\t\t\t\t{shouldShowError ? 'Hide details' : 'Show details'}\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t<div className=\"tl-error-boundary__content__actions__group\">\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\tclassName=\"tlui-button tl-error-boundary__reset\"\n\t\t\t\t\t\t\t\t\tonClick={() => setShouldShowResetConfirmation(true)}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\tReset data\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t<button className=\"tlui-button tl-error-boundary__refresh\" onClick={refresh}>\n\t\t\t\t\t\t\t\t\tRefresh Page\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["DefaultErrorFallback"]
7
7
  }
@@ -1,9 +1,9 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { useEditorComponents } from "../../hooks/EditorComponentsContext.mjs";
3
- const DefaultLoadingScreen = () => {
3
+ function DefaultLoadingScreen() {
4
4
  const { Spinner } = useEditorComponents();
5
5
  return /* @__PURE__ */ jsx("div", { className: "tl-loading", "aria-busy": "true", tabIndex: 0, children: Spinner ? /* @__PURE__ */ jsx(Spinner, {}) : null });
6
- };
6
+ }
7
7
  export {
8
8
  DefaultLoadingScreen
9
9
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/components/default-components/DefaultLoadingScreen.tsx"],
4
- "sourcesContent": ["import { useEditorComponents } from '../../hooks/EditorComponentsContext'\n\n/** @public @react */\nexport const DefaultLoadingScreen = () => {\n\tconst { Spinner } = useEditorComponents()\n\treturn (\n\t\t<div className=\"tl-loading\" aria-busy=\"true\" tabIndex={0}>\n\t\t\t{Spinner ? <Spinner /> : null}\n\t\t</div>\n\t)\n}\n"],
5
- "mappings": "AAOc;AAPd,SAAS,2BAA2B;AAG7B,MAAM,uBAAuB,MAAM;AACzC,QAAM,EAAE,QAAQ,IAAI,oBAAoB;AACxC,SACC,oBAAC,SAAI,WAAU,cAAa,aAAU,QAAO,UAAU,GACrD,oBAAU,oBAAC,WAAQ,IAAK,MAC1B;AAEF;",
4
+ "sourcesContent": ["import { useEditorComponents } from '../../hooks/EditorComponentsContext'\n\n/** @public @react */\nexport function DefaultLoadingScreen() {\n\tconst { Spinner } = useEditorComponents()\n\treturn (\n\t\t<div className=\"tl-loading\" aria-busy=\"true\" tabIndex={0}>\n\t\t\t{Spinner ? <Spinner /> : null}\n\t\t</div>\n\t)\n}\n"],
5
+ "mappings": "AAOc;AAPd,SAAS,2BAA2B;AAG7B,SAAS,uBAAuB;AACtC,QAAM,EAAE,QAAQ,IAAI,oBAAoB;AACxC,SACC,oBAAC,SAAI,WAAU,cAAa,aAAU,QAAO,UAAU,GACrD,oBAAU,oBAAC,WAAQ,IAAK,MAC1B;AAEF;",
6
6
  "names": []
7
7
  }
@@ -1,5 +1,5 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- const DefaultShapeErrorFallback = () => {
2
+ const DefaultShapeErrorFallback = function DefaultShapeErrorFallback2() {
3
3
  return /* @__PURE__ */ jsx("div", { className: "tl-shape-error-boundary" });
4
4
  };
5
5
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/components/default-components/DefaultShapeErrorFallback.tsx"],
4
- "sourcesContent": ["import { ComponentType } from 'react'\n\n/** @public */\nexport type TLShapeErrorFallbackComponent = ComponentType<{ error: any }>\n\n/** @internal */\nexport const DefaultShapeErrorFallback: TLShapeErrorFallbackComponent = () => {\n\treturn <div className=\"tl-shape-error-boundary\" />\n}\n"],
5
- "mappings": "AAOQ;AADD,MAAM,4BAA2D,MAAM;AAC7E,SAAO,oBAAC,SAAI,WAAU,2BAA0B;AACjD;",
6
- "names": []
4
+ "sourcesContent": ["import { ComponentType } from 'react'\n\n/** @public */\nexport type TLShapeErrorFallbackComponent = ComponentType<{ error: any }>\n\n/** @internal */\nexport const DefaultShapeErrorFallback: TLShapeErrorFallbackComponent =\n\tfunction DefaultShapeErrorFallback() {\n\t\treturn <div className=\"tl-shape-error-boundary\" />\n\t}\n"],
5
+ "mappings": "AAQS;AAFF,MAAM,4BACZ,SAASA,6BAA4B;AACpC,SAAO,oBAAC,SAAI,WAAU,2BAA0B;AACjD;",
6
+ "names": ["DefaultShapeErrorFallback"]
7
7
  }
@@ -1,6 +1,6 @@
1
- const DefaultSvgDefs = () => {
1
+ function DefaultSvgDefs() {
2
2
  return null;
3
- };
3
+ }
4
4
  export {
5
5
  DefaultSvgDefs
6
6
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/components/default-components/DefaultSvgDefs.tsx"],
4
- "sourcesContent": ["/** @public @react */\nexport const DefaultSvgDefs = () => {\n\treturn null\n}\n"],
5
- "mappings": "AACO,MAAM,iBAAiB,MAAM;AACnC,SAAO;AACR;",
4
+ "sourcesContent": ["/** @public @react */\nexport function DefaultSvgDefs() {\n\treturn null\n}\n"],
5
+ "mappings": "AACO,SAAS,iBAAiB;AAChC,SAAO;AACR;",
6
6
  "names": []
7
7
  }
@@ -188,12 +188,12 @@ class Editor extends EventEmitter {
188
188
  this._tickManager = new TickManager(this);
189
189
  this.disposables.add(() => this._tickManager.dispose());
190
190
  this.disposables.add(() => {
191
- this.off("tick", this._decayCameraStateTimeout);
192
191
  this._setCameraState("idle");
193
192
  });
194
193
  this.fonts = new FontManager(this, fontAssetUrls);
195
194
  this.disposables.add(() => this.fonts.dispose());
196
195
  this.inputs = new InputsManager(this);
196
+ this.disposables.add(() => this.inputs.dispose());
197
197
  this.performance = new PerformanceManager(this);
198
198
  this.disposables.add(() => this.performance.dispose());
199
199
  this.collaborators = new CollaboratorsManager(this);
@@ -822,6 +822,7 @@ class Editor extends EventEmitter {
822
822
  this.store.dispose();
823
823
  this.isDisposed = true;
824
824
  this.emit("dispose");
825
+ this.removeAllListeners();
825
826
  }
826
827
  /* ------------------ Themes (shadowing the theme manager) ------------------ */
827
828
  /**
@@ -1091,6 +1092,14 @@ class Editor extends EventEmitter {
1091
1092
  getMarkIdMatching(idSubstring) {
1092
1093
  return this.history.getMarkIdMatching(idSubstring);
1093
1094
  }
1095
+ /**
1096
+ * Whether the editor is currently replaying history (i.e. an undo or redo is being applied).
1097
+ *
1098
+ * @internal
1099
+ */
1100
+ isReplayingHistory() {
1101
+ return this.history.isReplaying();
1102
+ }
1094
1103
  /**
1095
1104
  * Coalesces all changes since the given mark into a single change, removing any intermediate marks.
1096
1105
  *
@@ -2185,7 +2194,7 @@ class Editor extends EventEmitter {
2185
2194
  return baseCamera;
2186
2195
  }
2187
2196
  _getFollowingPresence(targetUserId) {
2188
- const visited = [this.user.getId()];
2197
+ const visited = [this.user.getRecordId()];
2189
2198
  const collaborators = this.getCollaborators();
2190
2199
  let leaderPresence = null;
2191
2200
  while (targetUserId && !visited.includes(targetUserId)) {
@@ -2373,6 +2382,11 @@ class Editor extends EventEmitter {
2373
2382
  getConstrainedCamera(point, opts) {
2374
2383
  const currentCamera = this.getCamera();
2375
2384
  let { x, y, z = currentCamera.z } = point;
2385
+ const preserveFocalPoint = (current, requested, rz, z2) => {
2386
+ const cz = currentCamera.z;
2387
+ if (rz === cz) return current;
2388
+ return current + (requested - current) * (1 / z2 - 1 / cz) / (1 / rz - 1 / cz);
2389
+ };
2376
2390
  if (!opts?.force) {
2377
2391
  const cameraOptions = this.getCameraOptions();
2378
2392
  const zoomMin = cameraOptions.zoomSteps[0];
@@ -2392,14 +2406,10 @@ class Editor extends EventEmitter {
2392
2406
  z = this.getInitialZoom();
2393
2407
  }
2394
2408
  if (z < minZ || z > maxZ) {
2395
- const { x: cx, y: cy, z: cz } = currentCamera;
2396
- const cxA = -cx + vsb.w / cz / 2;
2397
- const cyA = -cy + vsb.h / cz / 2;
2409
+ const rz = z;
2398
2410
  z = clamp(z, minZ, maxZ);
2399
- const cxB = -cx + vsb.w / z / 2;
2400
- const cyB = -cy + vsb.h / z / 2;
2401
- x = cx + cxB - cxA;
2402
- y = cy + cyB - cyA;
2411
+ x = preserveFocalPoint(currentCamera.x, x, rz, z);
2412
+ y = preserveFocalPoint(currentCamera.y, y, rz, z);
2403
2413
  }
2404
2414
  const minX = px / z - bounds.x;
2405
2415
  const minY = py / z - bounds.y;
@@ -2468,10 +2478,10 @@ class Editor extends EventEmitter {
2468
2478
  }
2469
2479
  } else {
2470
2480
  if (z > zoomMax || z < zoomMin) {
2471
- const { x: cx, y: cy, z: cz } = currentCamera;
2481
+ const rz = z;
2472
2482
  z = clamp(z, zoomMin, zoomMax);
2473
- x = cx + (-cx + vsb.w / z / 2) - (-cx + vsb.w / cz / 2);
2474
- y = cy + (-cy + vsb.h / z / 2) - (-cy + vsb.h / cz / 2);
2483
+ x = preserveFocalPoint(currentCamera.x, x, rz, z);
2484
+ y = preserveFocalPoint(currentCamera.y, y, rz, z);
2475
2485
  }
2476
2486
  }
2477
2487
  }
@@ -2894,14 +2904,25 @@ class Editor extends EventEmitter {
2894
2904
  this.off("stop-camera-animation", cancel);
2895
2905
  };
2896
2906
  this.once("stop-camera-animation", cancel);
2907
+ const dirZ = direction.z ?? 0;
2897
2908
  const moveCamera = (elapsed) => {
2898
2909
  const { x: cx, y: cy, z: cz } = this.getCamera();
2899
- const movementVec = Vec.Mul(direction, currentSpeed * elapsed / cz);
2910
+ const dx = direction.x * (currentSpeed * elapsed) / cz;
2911
+ const dy = direction.y * (currentSpeed * elapsed) / cz;
2912
+ let newCx = cx + dx;
2913
+ let newCy = cy + dy;
2914
+ let newCz = cz;
2915
+ if (dirZ !== 0) {
2916
+ newCz = cz * (1 + dirZ * currentSpeed * elapsed);
2917
+ const center = this.getViewportScreenCenter();
2918
+ newCx += center.x / newCz - center.x / cz;
2919
+ newCy += center.y / newCz - center.y / cz;
2920
+ }
2900
2921
  currentSpeed *= 1 - friction;
2901
2922
  if (currentSpeed < speedThreshold) {
2902
2923
  cancel();
2903
2924
  } else {
2904
- this._setCamera(new Vec(cx + movementVec.x, cy + movementVec.y, cz));
2925
+ this._setCamera(new Vec(newCx, newCy, newCz));
2905
2926
  }
2906
2927
  };
2907
2928
  this.on("tick", moveCamera);
@@ -3221,7 +3242,7 @@ class Editor extends EventEmitter {
3221
3242
  */
3222
3243
  startFollowingUser(userId) {
3223
3244
  this.stopFollowingUser();
3224
- const thisUserId = this.user.getId();
3245
+ const thisUserId = this.user.getExternalId();
3225
3246
  if (!thisUserId) {
3226
3247
  console.warn("You should set the userId for the current instance before following a user");
3227
3248
  }
@@ -5925,24 +5946,40 @@ class Editor extends EventEmitter {
5925
5946
  * @public
5926
5947
  */
5927
5948
  resizeShape(shape, scale, opts = {}) {
5949
+ const partial = this.getResizeShapePartial(shape, scale, opts);
5950
+ if (partial) this.updateShapes([partial]);
5951
+ return this;
5952
+ }
5953
+ /**
5954
+ * Get the update for a resized shape without committing it to the store. Interactions that
5955
+ * resize many shapes at once use this to collect all of the updates and commit them in a
5956
+ * single batch. Returns null when there is nothing to update.
5957
+ *
5958
+ * Shapes that are rotated out of alignment with the scale axis cannot be resized with a
5959
+ * single update; those shapes are resized immediately (as `resizeShape` would do) and null
5960
+ * is returned.
5961
+ *
5962
+ * @internal
5963
+ */
5964
+ getResizeShapePartial(shape, scale, opts = {}) {
5928
5965
  const id = typeof shape === "string" ? shape : shape.id;
5929
- if (this.getIsReadonly()) return this;
5966
+ if (this.getIsReadonly()) return null;
5930
5967
  if (!Number.isFinite(scale.x)) scale = new Vec(1, scale.y);
5931
5968
  if (!Number.isFinite(scale.y)) scale = new Vec(scale.x, 1);
5932
5969
  const initialShape = opts.initialShape ?? this.getShape(id);
5933
- if (!initialShape) return this;
5970
+ if (!initialShape) return null;
5934
5971
  const scaleOrigin = opts.scaleOrigin ?? this.getShapePageBounds(id)?.center;
5935
- if (!scaleOrigin) return this;
5972
+ if (!scaleOrigin) return null;
5936
5973
  const pageTransform = opts.initialPageTransform ? Mat.Cast(opts.initialPageTransform) : this.getShapePageTransform(id);
5937
- if (!pageTransform) return this;
5974
+ if (!pageTransform) return null;
5938
5975
  const pageRotation = pageTransform.rotation();
5939
- if (pageRotation == null) return this;
5976
+ if (pageRotation == null) return null;
5940
5977
  const scaleAxisRotation = opts.scaleAxisRotation ?? pageRotation;
5941
5978
  const initialBounds = opts.initialBounds ?? this.getShapeGeometry(id).bounds;
5942
- if (!initialBounds) return this;
5979
+ if (!initialBounds) return null;
5943
5980
  const isAspectRatioLocked = opts.isAspectRatioLocked ?? this.getShapeUtil(initialShape).isAspectRatioLocked(initialShape);
5944
5981
  if (!areAnglesCompatible(pageRotation, scaleAxisRotation)) {
5945
- return this._resizeUnalignedShape(id, scale, {
5982
+ this._resizeUnalignedShape(id, scale, {
5946
5983
  ...opts,
5947
5984
  initialBounds,
5948
5985
  scaleOrigin,
@@ -5951,6 +5988,7 @@ class Editor extends EventEmitter {
5951
5988
  isAspectRatioLocked,
5952
5989
  initialShape
5953
5990
  });
5991
+ return null;
5954
5992
  }
5955
5993
  const util = this.getShapeUtil(initialShape);
5956
5994
  if (isAspectRatioLocked) {
@@ -5960,7 +5998,7 @@ class Editor extends EventEmitter {
5960
5998
  scale = new Vec(Math.sign(scale.x) * Math.abs(scale.y), scale.y);
5961
5999
  }
5962
6000
  }
5963
- let didResize = false;
6001
+ let workingShape = null;
5964
6002
  if (util.onResize && util.canResize(initialShape)) {
5965
6003
  const newPagePoint = this._scalePagePoint(
5966
6004
  Mat.applyToPoint(pageTransform, new Vec(0, 0)),
@@ -5978,7 +6016,7 @@ class Editor extends EventEmitter {
5978
6016
  myScale.y = areWidthAndHeightAlignedWithCorrectAxis ? scale.y : scale.x;
5979
6017
  const initialPagePoint = Mat.applyToPoint(pageTransform, new Vec());
5980
6018
  const { x, y } = this.getPointInParentSpace(initialShape.id, initialPagePoint);
5981
- let workingShape = initialShape;
6019
+ workingShape = initialShape;
5982
6020
  if (!opts.skipStartAndEndCallbacks) {
5983
6021
  workingShape = applyPartialToRecordWithProps(
5984
6022
  initialShape,
@@ -5998,9 +6036,6 @@ class Editor extends EventEmitter {
5998
6036
  initialShape
5999
6037
  }
6000
6038
  );
6001
- if (resizedShape) {
6002
- didResize = true;
6003
- }
6004
6039
  workingShape = applyPartialToRecordWithProps(workingShape, {
6005
6040
  id,
6006
6041
  type: initialShape.type,
@@ -6014,32 +6049,36 @@ class Editor extends EventEmitter {
6014
6049
  util.onResizeEnd?.(initialShape, workingShape) ?? void 0
6015
6050
  );
6016
6051
  }
6017
- this.updateShapes([workingShape]);
6052
+ if (resizedShape) {
6053
+ return workingShape;
6054
+ }
6018
6055
  }
6019
- if (!didResize) {
6020
- const initialPageCenter = Mat.applyToPoint(pageTransform, initialBounds.center);
6021
- const newPageCenter = this._scalePagePoint(
6022
- initialPageCenter,
6023
- scaleOrigin,
6024
- scale,
6025
- scaleAxisRotation
6026
- );
6027
- const initialPageCenterInParentSpace = this.getPointInParentSpace(
6028
- initialShape.id,
6029
- initialPageCenter
6030
- );
6031
- const newPageCenterInParentSpace = this.getPointInParentSpace(initialShape.id, newPageCenter);
6032
- const delta = Vec.Sub(newPageCenterInParentSpace, initialPageCenterInParentSpace);
6033
- this.updateShapes([
6034
- {
6035
- id,
6036
- type: initialShape.type,
6037
- x: initialShape.x + delta.x,
6038
- y: initialShape.y + delta.y
6039
- }
6040
- ]);
6056
+ const initialPageCenter = Mat.applyToPoint(pageTransform, initialBounds.center);
6057
+ const newPageCenter = this._scalePagePoint(
6058
+ initialPageCenter,
6059
+ scaleOrigin,
6060
+ scale,
6061
+ scaleAxisRotation
6062
+ );
6063
+ const initialPageCenterInParentSpace = this.getPointInParentSpace(
6064
+ initialShape.id,
6065
+ initialPageCenter
6066
+ );
6067
+ const newPageCenterInParentSpace = this.getPointInParentSpace(initialShape.id, newPageCenter);
6068
+ const delta = Vec.Sub(newPageCenterInParentSpace, initialPageCenterInParentSpace);
6069
+ if (workingShape) {
6070
+ return {
6071
+ ...workingShape,
6072
+ x: initialShape.x + delta.x,
6073
+ y: initialShape.y + delta.y
6074
+ };
6041
6075
  }
6042
- return this;
6076
+ return {
6077
+ id,
6078
+ type: initialShape.type,
6079
+ x: initialShape.x + delta.x,
6080
+ y: initialShape.y + delta.y
6081
+ };
6043
6082
  }
6044
6083
  /** @internal */
6045
6084
  _scalePagePoint(point, scaleOrigin, scale, scaleAxisRotation) {
@@ -7867,6 +7906,15 @@ class Editor extends EventEmitter {
7867
7906
  _didPinch = false;
7868
7907
  /** @internal */
7869
7908
  _selectedShapeIdsAtPointerDown = [];
7909
+ /**
7910
+ * Whether `_selectedShapeIdsAtPointerDown` holds a pre-gesture selection
7911
+ * captured by a `pointer_down` (the touch path) that a following pinch
7912
+ * should restore. False when no pointer_down preceded the pinch (the
7913
+ * Safari trackpad path uses gesture events), in which case `pinch_start`
7914
+ * captures the live selection instead.
7915
+ * @internal
7916
+ */
7917
+ _didCaptureSelectionAtPointerDown = false;
7870
7918
  /** @internal */
7871
7919
  _longPressTimeout = -1;
7872
7920
  /** @internal */
@@ -7999,10 +8047,15 @@ class Editor extends EventEmitter {
7999
8047
  case "pinch_start": {
8000
8048
  if (inputs.getIsPinching()) return;
8001
8049
  if (!inputs.getIsEditing()) {
8002
- this._selectedShapeIdsAtPointerDown = [...pageState.selectedShapeIds];
8050
+ if (!this._didCaptureSelectionAtPointerDown) {
8051
+ this._selectedShapeIdsAtPointerDown = [...pageState.selectedShapeIds];
8052
+ }
8003
8053
  this._didPinch = true;
8004
8054
  inputs.setIsPinching(true);
8005
8055
  this.interrupt();
8056
+ if (this._didCaptureSelectionAtPointerDown) {
8057
+ this.setSelectedShapes(this._selectedShapeIdsAtPointerDown);
8058
+ }
8006
8059
  }
8007
8060
  this.emit("event", info);
8008
8061
  return;
@@ -8042,6 +8095,7 @@ class Editor extends EventEmitter {
8042
8095
  const { _selectedShapeIdsAtPointerDown: shapesToReselect } = this;
8043
8096
  this.setSelectedShapes(this._selectedShapeIdsAtPointerDown);
8044
8097
  this._selectedShapeIdsAtPointerDown = [];
8098
+ this._didCaptureSelectionAtPointerDown = false;
8045
8099
  if (this._didPinch) {
8046
8100
  this._didPinch = false;
8047
8101
  if (shapesToReselect.length > 0) {
@@ -8135,12 +8189,18 @@ class Editor extends EventEmitter {
8135
8189
  });
8136
8190
  }, this.options.longPressDurationMs);
8137
8191
  }
8138
- this._selectedShapeIdsAtPointerDown = this.getSelectedShapeIds();
8192
+ if (!this._didCaptureSelectionAtPointerDown) {
8193
+ this._selectedShapeIdsAtPointerDown = this.getSelectedShapeIds();
8194
+ this._didCaptureSelectionAtPointerDown = true;
8195
+ }
8139
8196
  if (info.button === LEFT_MOUSE_BUTTON) this.capturedPointerId = info.pointerId;
8140
8197
  inputs.buttons.add(info.button);
8141
8198
  inputs.setIsPointing(true);
8142
8199
  inputs.setIsDragging(false);
8143
- if (!isPenMode && isPen) this.updateInstanceState({ isPenMode: true });
8200
+ if (!isPenMode && isPen) {
8201
+ this.updateInstanceState({ isPenMode: true });
8202
+ this.interrupt();
8203
+ }
8144
8204
  if (info.button === STYLUS_ERASER_BUTTON) {
8145
8205
  this._restoreToolId = this.getCurrentToolId();
8146
8206
  this.complete();
@@ -8208,6 +8268,7 @@ class Editor extends EventEmitter {
8208
8268
  if (this.inputs.getIsRightPointing() && !this.inputs.getIsPanning()) {
8209
8269
  this.inputs.setIsRightPointing(false);
8210
8270
  this._selectedShapeIdsAtPointerDown = [];
8271
+ this._didCaptureSelectionAtPointerDown = false;
8211
8272
  break;
8212
8273
  }
8213
8274
  this.inputs.setIsRightPointing(false);
@@ -8242,14 +8303,21 @@ class Editor extends EventEmitter {
8242
8303
  this.setCursor({ type: this._prevCursor, rotation: 0 });
8243
8304
  }
8244
8305
  if (slideSpeed > 0) {
8245
- this.slideCamera({ speed: slideSpeed, direction: slideDirection });
8306
+ this.slideCamera({
8307
+ speed: slideSpeed,
8308
+ direction: { x: slideDirection.x, y: slideDirection.y, z: 0 }
8309
+ });
8246
8310
  }
8247
8311
  this._selectedShapeIdsAtPointerDown = [];
8312
+ this._didCaptureSelectionAtPointerDown = false;
8248
8313
  return this;
8249
8314
  }
8250
8315
  }
8251
8316
  if (slideSpeed > 0) {
8252
- this.slideCamera({ speed: slideSpeed, direction: slideDirection });
8317
+ this.slideCamera({
8318
+ speed: slideSpeed,
8319
+ direction: { x: slideDirection.x, y: slideDirection.y, z: 0 }
8320
+ });
8253
8321
  }
8254
8322
  } else {
8255
8323
  if (info.button === STYLUS_ERASER_BUTTON) {
@@ -8258,6 +8326,7 @@ class Editor extends EventEmitter {
8258
8326
  }
8259
8327
  }
8260
8328
  this._selectedShapeIdsAtPointerDown = [];
8329
+ this._didCaptureSelectionAtPointerDown = false;
8261
8330
  break;
8262
8331
  }
8263
8332
  }