@tldraw/editor 3.16.0-canary.d04b7fc312b4 → 3.16.0-canary.d354cc4340c1

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 (192) hide show
  1. package/dist-cjs/index.d.ts +113 -105
  2. package/dist-cjs/index.js +3 -5
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/TldrawEditor.js +6 -8
  5. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  6. package/dist-cjs/lib/components/Shape.js +7 -10
  7. package/dist-cjs/lib/components/Shape.js.map +2 -2
  8. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +14 -23
  9. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  10. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +1 -1
  11. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +2 -2
  12. package/dist-cjs/lib/config/TLUserPreferences.js +1 -1
  13. package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
  14. package/dist-cjs/lib/editor/Editor.js +79 -114
  15. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  16. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +4 -0
  17. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +2 -2
  18. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js +4 -2
  19. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js.map +2 -2
  20. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +1 -1
  21. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  22. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +23 -0
  23. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  24. package/dist-cjs/lib/editor/types/misc-types.js.map +1 -1
  25. package/dist-cjs/lib/exports/getSvgJsx.js +34 -14
  26. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  27. package/dist-cjs/lib/hooks/useCanvasEvents.js +26 -21
  28. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  29. package/dist-cjs/lib/hooks/useDocumentEvents.js +5 -5
  30. package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
  31. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js +1 -2
  32. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js.map +2 -2
  33. package/dist-cjs/lib/hooks/useGestureEvents.js +1 -1
  34. package/dist-cjs/lib/hooks/useGestureEvents.js.map +2 -2
  35. package/dist-cjs/lib/hooks/useHandleEvents.js +6 -6
  36. package/dist-cjs/lib/hooks/useHandleEvents.js.map +2 -2
  37. package/dist-cjs/lib/hooks/usePassThroughMouseOverEvents.js +4 -1
  38. package/dist-cjs/lib/hooks/usePassThroughMouseOverEvents.js.map +2 -2
  39. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js +4 -1
  40. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +2 -2
  41. package/dist-cjs/lib/hooks/useSelectionEvents.js +8 -8
  42. package/dist-cjs/lib/hooks/useSelectionEvents.js.map +2 -2
  43. package/dist-cjs/lib/license/LicenseManager.js +143 -53
  44. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  45. package/dist-cjs/lib/license/LicenseProvider.js +39 -1
  46. package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
  47. package/dist-cjs/lib/license/Watermark.js +144 -75
  48. package/dist-cjs/lib/license/Watermark.js.map +3 -3
  49. package/dist-cjs/lib/license/useLicenseManagerState.js.map +2 -2
  50. package/dist-cjs/lib/options.js +6 -0
  51. package/dist-cjs/lib/options.js.map +2 -2
  52. package/dist-cjs/lib/primitives/Box.js +3 -0
  53. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  54. package/dist-cjs/lib/primitives/Vec.js +0 -4
  55. package/dist-cjs/lib/primitives/Vec.js.map +2 -2
  56. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +50 -20
  57. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  58. package/dist-cjs/lib/primitives/geometry/Group2d.js +8 -1
  59. package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
  60. package/dist-cjs/lib/utils/dom.js.map +2 -2
  61. package/dist-cjs/lib/utils/getPointerInfo.js +2 -3
  62. package/dist-cjs/lib/utils/getPointerInfo.js.map +2 -2
  63. package/dist-cjs/lib/utils/reparenting.js +2 -35
  64. package/dist-cjs/lib/utils/reparenting.js.map +3 -3
  65. package/dist-cjs/version.js +3 -3
  66. package/dist-cjs/version.js.map +1 -1
  67. package/dist-esm/index.d.mts +113 -105
  68. package/dist-esm/index.mjs +3 -5
  69. package/dist-esm/index.mjs.map +2 -2
  70. package/dist-esm/lib/TldrawEditor.mjs +6 -8
  71. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  72. package/dist-esm/lib/components/Shape.mjs +7 -10
  73. package/dist-esm/lib/components/Shape.mjs.map +2 -2
  74. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +14 -23
  75. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  76. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +1 -1
  77. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +2 -2
  78. package/dist-esm/lib/config/TLUserPreferences.mjs +1 -1
  79. package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
  80. package/dist-esm/lib/editor/Editor.mjs +79 -114
  81. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  82. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +4 -0
  83. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +2 -2
  84. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs +4 -2
  85. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs.map +2 -2
  86. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +1 -1
  87. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  88. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +23 -0
  89. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  90. package/dist-esm/lib/exports/getSvgJsx.mjs +34 -14
  91. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  92. package/dist-esm/lib/hooks/useCanvasEvents.mjs +27 -27
  93. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  94. package/dist-esm/lib/hooks/useDocumentEvents.mjs +6 -6
  95. package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
  96. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs +1 -2
  97. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs.map +2 -2
  98. package/dist-esm/lib/hooks/useGestureEvents.mjs +2 -2
  99. package/dist-esm/lib/hooks/useGestureEvents.mjs.map +2 -2
  100. package/dist-esm/lib/hooks/useHandleEvents.mjs +6 -6
  101. package/dist-esm/lib/hooks/useHandleEvents.mjs.map +2 -2
  102. package/dist-esm/lib/hooks/usePassThroughMouseOverEvents.mjs +4 -1
  103. package/dist-esm/lib/hooks/usePassThroughMouseOverEvents.mjs.map +2 -2
  104. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs +4 -1
  105. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +2 -2
  106. package/dist-esm/lib/hooks/useSelectionEvents.mjs +9 -14
  107. package/dist-esm/lib/hooks/useSelectionEvents.mjs.map +2 -2
  108. package/dist-esm/lib/license/LicenseManager.mjs +144 -54
  109. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  110. package/dist-esm/lib/license/LicenseProvider.mjs +39 -2
  111. package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
  112. package/dist-esm/lib/license/Watermark.mjs +145 -76
  113. package/dist-esm/lib/license/Watermark.mjs.map +3 -3
  114. package/dist-esm/lib/license/useLicenseManagerState.mjs.map +2 -2
  115. package/dist-esm/lib/options.mjs +6 -0
  116. package/dist-esm/lib/options.mjs.map +2 -2
  117. package/dist-esm/lib/primitives/Box.mjs +4 -1
  118. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  119. package/dist-esm/lib/primitives/Vec.mjs +0 -4
  120. package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
  121. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +53 -21
  122. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  123. package/dist-esm/lib/primitives/geometry/Group2d.mjs +8 -1
  124. package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
  125. package/dist-esm/lib/utils/dom.mjs.map +2 -2
  126. package/dist-esm/lib/utils/getPointerInfo.mjs +2 -3
  127. package/dist-esm/lib/utils/getPointerInfo.mjs.map +2 -2
  128. package/dist-esm/lib/utils/reparenting.mjs +3 -40
  129. package/dist-esm/lib/utils/reparenting.mjs.map +2 -2
  130. package/dist-esm/version.mjs +3 -3
  131. package/dist-esm/version.mjs.map +1 -1
  132. package/editor.css +16 -3
  133. package/package.json +14 -37
  134. package/src/index.ts +2 -9
  135. package/src/lib/TldrawEditor.tsx +7 -16
  136. package/src/lib/components/Shape.tsx +6 -12
  137. package/src/lib/components/default-components/DefaultCanvas.tsx +11 -22
  138. package/src/lib/components/default-components/DefaultErrorFallback.tsx +1 -1
  139. package/src/lib/config/TLUserPreferences.ts +1 -1
  140. package/src/lib/editor/Editor.test.ts +102 -11
  141. package/src/lib/editor/Editor.ts +98 -151
  142. package/src/lib/editor/derivations/notVisibleShapes.ts +6 -0
  143. package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +15 -14
  144. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +16 -15
  145. package/src/lib/editor/managers/FocusManager/FocusManager.test.ts +49 -48
  146. package/src/lib/editor/managers/FocusManager/FocusManager.ts +6 -2
  147. package/src/lib/editor/managers/FontManager/FontManager.test.ts +24 -23
  148. package/src/lib/editor/managers/HistoryManager/HistoryManager.test.ts +7 -6
  149. package/src/lib/editor/managers/ScribbleManager/ScribbleManager.test.ts +12 -11
  150. package/src/lib/editor/managers/SnapManager/SnapManager.test.ts +57 -50
  151. package/src/lib/editor/managers/TextManager/TextManager.test.ts +51 -26
  152. package/src/lib/editor/managers/TickManager/TickManager.test.ts +14 -13
  153. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +21 -26
  154. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +1 -1
  155. package/src/lib/editor/shapes/ShapeUtil.ts +46 -0
  156. package/src/lib/editor/types/misc-types.ts +0 -6
  157. package/src/lib/exports/getSvgJsx.test.ts +868 -0
  158. package/src/lib/exports/getSvgJsx.tsx +76 -19
  159. package/src/lib/hooks/useCanvasEvents.ts +26 -26
  160. package/src/lib/hooks/useDocumentEvents.ts +6 -6
  161. package/src/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.ts +1 -1
  162. package/src/lib/hooks/useGestureEvents.ts +2 -2
  163. package/src/lib/hooks/useHandleEvents.ts +6 -6
  164. package/src/lib/hooks/usePassThroughMouseOverEvents.ts +4 -1
  165. package/src/lib/hooks/usePassThroughWheelEvents.ts +6 -1
  166. package/src/lib/hooks/useSelectionEvents.ts +9 -14
  167. package/src/lib/license/LicenseManager.test.ts +724 -383
  168. package/src/lib/license/LicenseManager.ts +204 -58
  169. package/src/lib/license/LicenseProvider.tsx +74 -2
  170. package/src/lib/license/Watermark.test.tsx +2 -1
  171. package/src/lib/license/Watermark.tsx +152 -77
  172. package/src/lib/license/useLicenseManagerState.ts +2 -2
  173. package/src/lib/options.ts +6 -0
  174. package/src/lib/primitives/Box.test.ts +126 -0
  175. package/src/lib/primitives/Box.ts +10 -1
  176. package/src/lib/primitives/Vec.ts +0 -5
  177. package/src/lib/primitives/geometry/Geometry2d.test.ts +420 -0
  178. package/src/lib/primitives/geometry/Geometry2d.ts +78 -21
  179. package/src/lib/primitives/geometry/Group2d.ts +10 -1
  180. package/src/lib/test/InFrontOfTheCanvas.test.tsx +187 -0
  181. package/src/lib/utils/dom.test.ts +103 -0
  182. package/src/lib/utils/dom.ts +8 -1
  183. package/src/lib/utils/getPointerInfo.ts +3 -2
  184. package/src/lib/utils/reparenting.ts +3 -69
  185. package/src/lib/utils/sync/LocalIndexedDb.test.ts +2 -1
  186. package/src/lib/utils/sync/TLLocalSyncClient.test.ts +15 -15
  187. package/src/version.ts +3 -3
  188. package/dist-cjs/lib/utils/nearestMultiple.js +0 -34
  189. package/dist-cjs/lib/utils/nearestMultiple.js.map +0 -7
  190. package/dist-esm/lib/utils/nearestMultiple.mjs +0 -14
  191. package/dist-esm/lib/utils/nearestMultiple.mjs.map +0 -7
  192. package/src/lib/utils/nearestMultiple.ts +0 -13
@@ -1,3 +1,4 @@
1
+ import { vi } from 'vitest'
1
2
  import {
2
3
  Box,
3
4
  Geometry2d,
@@ -59,8 +60,8 @@ beforeEach(() => {
59
60
  getContainer: () => document.body,
60
61
  })
61
62
  editor.setCameraOptions({ isLocked: true })
62
- editor.setCamera = jest.fn()
63
- editor.user.getAnimationSpeed = jest.fn()
63
+ editor.setCamera = vi.fn()
64
+ editor.user.getAnimationSpeed = vi.fn()
64
65
  })
65
66
 
66
67
  describe('centerOnPoint', () => {
@@ -94,13 +95,13 @@ describe('updateShape', () => {
94
95
 
95
96
  describe('zoomToFit', () => {
96
97
  it('no-op when isLocked is set', () => {
97
- editor.getCurrentPageShapeIds = jest.fn(() => new Set([createShapeId('box1')]))
98
+ editor.getCurrentPageShapeIds = vi.fn(() => new Set([createShapeId('box1')]))
98
99
  editor.zoomToFit()
99
100
  expect(editor.setCamera).not.toHaveBeenCalled()
100
101
  })
101
102
 
102
103
  it('sets camera when isLocked is set and force flag is set', () => {
103
- editor.getCurrentPageShapeIds = jest.fn(() => new Set([createShapeId('box1')]))
104
+ editor.getCurrentPageShapeIds = vi.fn(() => new Set([createShapeId('box1')]))
104
105
  editor.zoomToFit({ force: true })
105
106
  expect(editor.setCamera).toHaveBeenCalled()
106
107
  })
@@ -144,13 +145,13 @@ describe('zoomOut', () => {
144
145
 
145
146
  describe('zoomToSelection', () => {
146
147
  it('no-op when isLocked is set', () => {
147
- editor.getSelectionPageBounds = jest.fn(() => Box.From({ x: 0, y: 0, w: 100, h: 100 }))
148
+ editor.getSelectionPageBounds = vi.fn(() => Box.From({ x: 0, y: 0, w: 100, h: 100 }))
148
149
  editor.zoomToSelection()
149
150
  expect(editor.setCamera).not.toHaveBeenCalled()
150
151
  })
151
152
 
152
153
  it('sets camera when isLocked is set and force flag is set', () => {
153
- editor.getSelectionPageBounds = jest.fn(() => Box.From({ x: 0, y: 0, w: 100, h: 100 }))
154
+ editor.getSelectionPageBounds = vi.fn(() => Box.From({ x: 0, y: 0, w: 100, h: 100 }))
154
155
  editor.zoomToSelection({ force: true })
155
156
  expect(editor.setCamera).toHaveBeenCalled()
156
157
  })
@@ -286,7 +287,7 @@ describe('getShapesAtPoint', () => {
286
287
 
287
288
  it('filters out hidden shapes', () => {
288
289
  // Create a spy to mock isShapeHidden
289
- const isShapeHiddenSpy = jest.spyOn(editor, 'isShapeHidden')
290
+ const isShapeHiddenSpy = vi.spyOn(editor, 'isShapeHidden')
290
291
  isShapeHiddenSpy.mockImplementation((shape) => {
291
292
  return typeof shape === 'string' ? shape === ids.shape3 : shape.id === ids.shape3
292
293
  })
@@ -352,7 +353,7 @@ describe('getShapesAtPoint', () => {
352
353
 
353
354
  it('returns empty array when all shapes are hidden', () => {
354
355
  // Mock all shapes as hidden
355
- const isShapeHiddenSpy = jest.spyOn(editor, 'isShapeHidden')
356
+ const isShapeHiddenSpy = vi.spyOn(editor, 'isShapeHidden')
356
357
  isShapeHiddenSpy.mockReturnValue(true)
357
358
 
358
359
  const shapes = editor.getShapesAtPoint({ x: 50, y: 50 })
@@ -692,7 +693,7 @@ describe('selectAll', () => {
692
693
  const initialSelectedIds = editor.getSelectedShapeIds()
693
694
 
694
695
  // Spy on setSelectedShapes to verify it's not called
695
- const setSelectedShapesSpy = jest.spyOn(editor, 'setSelectedShapes')
696
+ const setSelectedShapesSpy = vi.spyOn(editor, 'setSelectedShapes')
696
697
 
697
698
  // Call selectAll
698
699
  editor.selectAll()
@@ -713,7 +714,7 @@ describe('selectAll', () => {
713
714
  const initialSelectedIds = editor.getSelectedShapeIds()
714
715
 
715
716
  // Spy on setSelectedShapes to verify it's not called
716
- const setSelectedShapesSpy = jest.spyOn(editor, 'setSelectedShapes')
717
+ const setSelectedShapesSpy = vi.spyOn(editor, 'setSelectedShapes')
717
718
 
718
719
  // Call selectAll
719
720
  editor.selectAll()
@@ -818,7 +819,7 @@ describe('selectAll', () => {
818
819
  const initialSelectedIds = Array.from(editor.getSelectedShapeIds())
819
820
 
820
821
  // Spy on setSelectedShapes to verify it's not called
821
- const setSelectedShapesSpy = jest.spyOn(editor, 'setSelectedShapes')
822
+ const setSelectedShapesSpy = vi.spyOn(editor, 'setSelectedShapes')
822
823
 
823
824
  // Call selectAll
824
825
  editor.selectAll()
@@ -832,3 +833,93 @@ describe('selectAll', () => {
832
833
  setSelectedShapesSpy.mockRestore()
833
834
  })
834
835
  })
836
+
837
+ describe('putExternalContent', () => {
838
+ let mockHandler: any
839
+
840
+ beforeEach(() => {
841
+ mockHandler = vi.fn()
842
+ editor.registerExternalContentHandler('text', mockHandler)
843
+ })
844
+
845
+ it('calls external content handler when not readonly', async () => {
846
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(false)
847
+
848
+ const info = { type: 'text' as const, text: 'test-data' }
849
+ await editor.putExternalContent(info)
850
+
851
+ expect(mockHandler).toHaveBeenCalledWith(info)
852
+ })
853
+
854
+ it('does not call external content handler when readonly', async () => {
855
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(true)
856
+
857
+ const info = { type: 'text' as const, text: 'test-data' }
858
+ await editor.putExternalContent(info)
859
+
860
+ expect(mockHandler).not.toHaveBeenCalled()
861
+ })
862
+
863
+ it('calls external content handler when readonly but force is true', async () => {
864
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(true)
865
+
866
+ const info = { type: 'text' as const, text: 'test-data' }
867
+ await editor.putExternalContent(info, { force: true })
868
+
869
+ expect(mockHandler).toHaveBeenCalledWith(info)
870
+ })
871
+
872
+ it('calls external content handler when force is false and not readonly', async () => {
873
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(false)
874
+
875
+ const info = { type: 'text' as const, text: 'test-data' }
876
+ await editor.putExternalContent(info, { force: false })
877
+
878
+ expect(mockHandler).toHaveBeenCalledWith(info)
879
+ })
880
+ })
881
+
882
+ describe('replaceExternalContent', () => {
883
+ let mockHandler: any
884
+
885
+ beforeEach(() => {
886
+ mockHandler = vi.fn()
887
+ editor.registerExternalContentHandler('text', mockHandler)
888
+ })
889
+
890
+ it('calls external content handler when not readonly', async () => {
891
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(false)
892
+
893
+ const info = { type: 'text' as const, text: 'test-data' }
894
+ await editor.replaceExternalContent(info)
895
+
896
+ expect(mockHandler).toHaveBeenCalledWith(info)
897
+ })
898
+
899
+ it('does not call external content handler when readonly', async () => {
900
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(true)
901
+
902
+ const info = { type: 'text' as const, text: 'test-data' }
903
+ await editor.replaceExternalContent(info)
904
+
905
+ expect(mockHandler).not.toHaveBeenCalled()
906
+ })
907
+
908
+ it('calls external content handler when readonly but force is true', async () => {
909
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(true)
910
+
911
+ const info = { type: 'text' as const, text: 'test-data' }
912
+ await editor.replaceExternalContent(info, { force: true })
913
+
914
+ expect(mockHandler).toHaveBeenCalledWith(info)
915
+ })
916
+
917
+ it('calls external content handler when force is false and not readonly', async () => {
918
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(false)
919
+
920
+ const info = { type: 'text' as const, text: 'test-data' }
921
+ await editor.replaceExternalContent(info, { force: false })
922
+
923
+ expect(mockHandler).toHaveBeenCalledWith(info)
924
+ })
925
+ })
@@ -116,7 +116,6 @@ import {
116
116
  } from '../constants'
117
117
  import { exportToSvg } from '../exports/exportToSvg'
118
118
  import { getSvgAsImage } from '../exports/getSvgAsImage'
119
- import { tlenv } from '../globals/environment'
120
119
  import { tlmenus } from '../globals/menus'
121
120
  import { tltime } from '../globals/time'
122
121
  import { TldrawOptions, defaultTldrawOptions } from '../options'
@@ -244,16 +243,6 @@ export interface TLEditorOptions {
244
243
  options?: Partial<TldrawOptions>
245
244
  licenseKey?: string
246
245
  fontAssetUrls?: { [key: string]: string | undefined }
247
- /**
248
- * A predicate that should return true if the given shape should be hidden.
249
- *
250
- * @deprecated Use {@link Editor#getShapeVisibility} instead.
251
- *
252
- * @param shape - The shape to check.
253
- * @param editor - The editor instance.
254
- */
255
- isShapeHidden?(shape: TLShape, editor: Editor): boolean
256
-
257
246
  /**
258
247
  * Provides a way to hide shapes.
259
248
  *
@@ -309,21 +298,12 @@ export class Editor extends EventEmitter<TLEventMap> {
309
298
  autoFocus,
310
299
  inferDarkMode,
311
300
  options,
312
- // eslint-disable-next-line @typescript-eslint/no-deprecated
313
- isShapeHidden,
314
301
  getShapeVisibility,
315
302
  fontAssetUrls,
316
303
  }: TLEditorOptions) {
317
304
  super()
318
- assert(
319
- !(isShapeHidden && getShapeVisibility),
320
- 'Cannot use both isShapeHidden and getShapeVisibility'
321
- )
322
305
 
323
- this._getShapeVisibility = isShapeHidden
324
- ? // eslint-disable-next-line @typescript-eslint/no-deprecated
325
- (shape: TLShape, editor: Editor) => (isShapeHidden(shape, editor) ? 'hidden' : 'inherit')
326
- : getShapeVisibility
306
+ this._getShapeVisibility = getShapeVisibility
327
307
 
328
308
  this.options = { ...defaultTldrawOptions, ...options }
329
309
 
@@ -363,6 +343,8 @@ export class Editor extends EventEmitter<TLEventMap> {
363
343
  this.root = new NewRoot(this)
364
344
  this.root.children = {}
365
345
 
346
+ this.markEventAsHandled = this.markEventAsHandled.bind(this)
347
+
366
348
  const allShapeUtils = checkShapesAndAddCore(shapeUtils)
367
349
 
368
350
  const _shapeUtils = {} as Record<string, ShapeUtil<any>>
@@ -907,14 +889,6 @@ export class Editor extends EventEmitter<TLEventMap> {
907
889
  */
908
890
  readonly fonts: FontManager
909
891
 
910
- /**
911
- * A manager for the editor's environment.
912
- *
913
- * @deprecated This is deprecated and will be removed in a future version. Use the `tlenv` global export instead.
914
- * @public
915
- */
916
- readonly environment = tlenv
917
-
918
892
  /**
919
893
  * A manager for the editor's scribbles.
920
894
  *
@@ -1119,35 +1093,6 @@ export class Editor extends EventEmitter<TLEventMap> {
1119
1093
  return this.history.getNumRedos() > 0
1120
1094
  }
1121
1095
 
1122
- /**
1123
- * Create a new "mark", or stopping point, in the undo redo history. Creating a mark will clear
1124
- * any redos.
1125
- *
1126
- * @example
1127
- * ```ts
1128
- * editor.mark()
1129
- * editor.mark('flip shapes')
1130
- * ```
1131
- *
1132
- * @param markId - The mark's id, usually the reason for adding the mark.
1133
- *
1134
- * @public
1135
- * @deprecated use {@link Editor.markHistoryStoppingPoint} instead
1136
- */
1137
- mark(markId?: string): this {
1138
- if (typeof markId === 'string') {
1139
- console.warn(
1140
- `[tldraw] \`editor.history.mark("${markId}")\` is deprecated. Please use \`const myMarkId = editor.markHistoryStoppingPoint()\` instead.`
1141
- )
1142
- } else {
1143
- console.warn(
1144
- '[tldraw] `editor.mark()` is deprecated. Use `editor.markHistoryStoppingPoint()` instead.'
1145
- )
1146
- }
1147
- this.history._mark(markId ?? uniqueId())
1148
- return this
1149
- }
1150
-
1151
1096
  /**
1152
1097
  * Create a new "mark", or stopping point, in the undo redo history. Creating a mark will clear
1153
1098
  * any redos. You typically want to do this just before a user interaction begins or is handled.
@@ -1272,13 +1217,6 @@ export class Editor extends EventEmitter<TLEventMap> {
1272
1217
  return this
1273
1218
  }
1274
1219
 
1275
- /**
1276
- * @deprecated Use `Editor.run` instead.
1277
- */
1278
- batch(fn: () => void, opts?: TLEditorRunOptions): this {
1279
- return this.run(fn, opts)
1280
- }
1281
-
1282
1220
  /* --------------------- Errors --------------------- */
1283
1221
 
1284
1222
  /** @internal */
@@ -1580,54 +1518,6 @@ export class Editor extends EventEmitter<TLEventMap> {
1580
1518
 
1581
1519
  menus = tlmenus.forContext(this.contextId)
1582
1520
 
1583
- /**
1584
- * @deprecated Use `editor.menus.getOpenMenus` instead.
1585
- *
1586
- * @public
1587
- */
1588
- @computed getOpenMenus(): string[] {
1589
- return this.menus.getOpenMenus()
1590
- }
1591
-
1592
- /**
1593
- * @deprecated Use `editor.menus.addOpenMenu` instead.
1594
- *
1595
- * @public
1596
- */
1597
- addOpenMenu(id: string): this {
1598
- this.menus.addOpenMenu(id)
1599
- return this
1600
- }
1601
-
1602
- /**
1603
- * @deprecated Use `editor.menus.deleteOpenMenu` instead.
1604
- *
1605
- * @public
1606
- */
1607
- deleteOpenMenu(id: string): this {
1608
- this.menus.deleteOpenMenu(id)
1609
- return this
1610
- }
1611
-
1612
- /**
1613
- * @deprecated Use `editor.menus.clearOpenMenus` instead.
1614
- *
1615
- * @public
1616
- */
1617
- clearOpenMenus(): this {
1618
- this.menus.clearOpenMenus()
1619
- return this
1620
- }
1621
-
1622
- /**
1623
- * @deprecated Use `editor.menus.hasAnyOpenMenus` instead.
1624
- *
1625
- * @public
1626
- */
1627
- @computed getIsMenuOpen(): boolean {
1628
- return this.menus.hasAnyOpenMenus()
1629
- }
1630
-
1631
1521
  /* --------------------- Cursor --------------------- */
1632
1522
 
1633
1523
  /**
@@ -4792,8 +4682,10 @@ export class Editor extends EventEmitter<TLEventMap> {
4792
4682
  return this.store.createComputedCache<Box, TLShape>('pageBoundsCache', (shape) => {
4793
4683
  const pageTransform = this.getShapePageTransform(shape)
4794
4684
  if (!pageTransform) return undefined
4795
- const geometry = this.getShapeGeometry(shape)
4796
- return Box.FromPoints(pageTransform.applyToPoints(geometry.vertices))
4685
+
4686
+ return Box.FromPoints(
4687
+ pageTransform.applyToPoints(this.getShapeGeometry(shape).boundsVertices)
4688
+ )
4797
4689
  })
4798
4690
  }
4799
4691
 
@@ -4860,27 +4752,25 @@ export class Editor extends EventEmitter<TLEventMap> {
4860
4752
  return this.store.createComputedCache('pageMaskCache', (shape) => {
4861
4753
  if (isPageId(shape.parentId)) return undefined
4862
4754
 
4863
- const frameAncestors = this.getShapeAncestors(shape.id).filter((shape) =>
4864
- this.isShapeOfType<TLFrameShape>(shape, 'frame')
4865
- )
4866
-
4867
- if (frameAncestors.length === 0) return undefined
4868
-
4869
- const pageMask = frameAncestors
4870
- .map<Vec[] | undefined>((s) => {
4871
- // Apply the frame transform to the frame outline to get the frame outline in the current page space
4872
- const geometry = this.getShapeGeometry(s.id)
4873
- const pageTransform = this.getShapePageTransform(s.id)
4874
- return pageTransform.applyToPoints(geometry.vertices)
4875
- })
4876
- .reduce((acc, b) => {
4877
- if (!(b && acc)) return undefined
4878
- const intersection = intersectPolygonPolygon(acc, b)
4879
- if (intersection) {
4880
- return intersection.map(Vec.Cast)
4881
- }
4882
- return []
4883
- })
4755
+ const clipPaths: Vec[][] = []
4756
+ // Get all ancestors that can potentially clip this shape
4757
+ for (const ancestor of this.getShapeAncestors(shape.id)) {
4758
+ const util = this.getShapeUtil(ancestor)
4759
+ const clipPath = util.getClipPath?.(ancestor)
4760
+ if (!clipPath) continue
4761
+ if (util.shouldClipChild?.(shape) === false) continue
4762
+ const pageTransform = this.getShapePageTransform(ancestor.id)
4763
+ clipPaths.push(pageTransform.applyToPoints(clipPath))
4764
+ }
4765
+ if (clipPaths.length === 0) return undefined
4766
+
4767
+ const pageMask = clipPaths.reduce((acc, b) => {
4768
+ const intersection = intersectPolygonPolygon(acc, b)
4769
+ if (intersection) {
4770
+ return intersection.map(Vec.Cast)
4771
+ }
4772
+ return []
4773
+ })
4884
4774
 
4885
4775
  return pageMask
4886
4776
  })
@@ -5841,11 +5731,6 @@ export class Editor extends EventEmitter<TLEventMap> {
5841
5731
  return shapeIds
5842
5732
  }
5843
5733
 
5844
- /** @deprecated Use {@link Editor.getDraggingOverShape} instead */
5845
- getDroppingOverShape(point: Vec, droppingShapes: TLShape[]): TLShape | undefined {
5846
- return this.getDraggingOverShape(point, droppingShapes)
5847
- }
5848
-
5849
5734
  /**
5850
5735
  * Get the shape that some shapes should be dropped on at a given point.
5851
5736
  *
@@ -6333,7 +6218,17 @@ export class Editor extends EventEmitter<TLEventMap> {
6333
6218
 
6334
6219
  this.createShapes(shapesToCreate)
6335
6220
  this.createBindings(bindingsToCreate)
6336
- this.setSelectedShapes(compact(ids.map((id) => shapeIds.get(id))))
6221
+
6222
+ this.setSelectedShapes(
6223
+ compact(
6224
+ ids.map((oldId) => {
6225
+ const newId = shapeIds.get(oldId)
6226
+ if (!newId) return null
6227
+ if (!this.getShape(newId)) return null
6228
+ return newId
6229
+ })
6230
+ )
6231
+ )
6337
6232
 
6338
6233
  if (offset !== undefined) {
6339
6234
  // If we've offset the duplicated shapes, check to see whether their new bounds is entirely
@@ -8940,8 +8835,13 @@ export class Editor extends EventEmitter<TLEventMap> {
8940
8835
  * Handle external content, such as files, urls, embeds, or plain text which has been put into the app, for example by pasting external text or dropping external images onto canvas.
8941
8836
  *
8942
8837
  * @param info - Info about the external content.
8838
+ * @param opts - Options for handling external content, including force flag to bypass readonly checks.
8943
8839
  */
8944
- async putExternalContent<E>(info: TLExternalContent<E>): Promise<void> {
8840
+ async putExternalContent<E>(
8841
+ info: TLExternalContent<E>,
8842
+ opts = {} as { force?: boolean }
8843
+ ): Promise<void> {
8844
+ if (!opts.force && this.getIsReadonly()) return
8945
8845
  return this.externalContentHandlers[info.type]?.(info as any)
8946
8846
  }
8947
8847
 
@@ -8949,8 +8849,13 @@ export class Editor extends EventEmitter<TLEventMap> {
8949
8849
  * Handle replacing external content.
8950
8850
  *
8951
8851
  * @param info - Info about the external content.
8852
+ * @param opts - Options for handling external content, including force flag to bypass readonly checks.
8952
8853
  */
8953
- async replaceExternalContent<E>(info: TLExternalContent<E>): Promise<void> {
8854
+ async replaceExternalContent<E>(
8855
+ info: TLExternalContent<E>,
8856
+ opts = {} as { force?: boolean }
8857
+ ): Promise<void> {
8858
+ if (!opts.force && this.getIsReadonly()) return
8954
8859
  return this.externalContentHandlers[info.type]?.(info as any)
8955
8860
  }
8956
8861
 
@@ -9451,13 +9356,6 @@ export class Editor extends EventEmitter<TLEventMap> {
9451
9356
  }
9452
9357
  }
9453
9358
 
9454
- /** @deprecated Use {@link Editor.getSvgString} or {@link Editor.getSvgElement} instead. */
9455
- async getSvg(shapes: TLShapeId[] | TLShape[], opts: TLSvgExportOptions = {}) {
9456
- const result = await this.getSvgElement(shapes, opts)
9457
- if (!result) return undefined
9458
- return result.svg
9459
- }
9460
-
9461
9359
  /**
9462
9360
  * Get an exported image of the given shapes.
9463
9361
  *
@@ -9509,6 +9407,24 @@ export class Editor extends EventEmitter<TLEventMap> {
9509
9407
  }
9510
9408
  }
9511
9409
 
9410
+ /**
9411
+ * Get an exported image of the given shapes as a data URL.
9412
+ *
9413
+ * @param shapes - The shapes (or shape ids) to export.
9414
+ * @param opts - Options for the export.
9415
+ *
9416
+ * @returns A data URL of the image.
9417
+ * @public
9418
+ */
9419
+ async toImageDataUrl(shapes: TLShapeId[] | TLShape[], opts: TLImageExportOptions = {}) {
9420
+ const { blob, width, height } = await this.toImage(shapes, opts)
9421
+ return {
9422
+ url: await FileHelpers.blobToDataUrl(blob),
9423
+ width,
9424
+ height,
9425
+ }
9426
+ }
9427
+
9512
9428
  /* --------------------- Events --------------------- */
9513
9429
 
9514
9430
  /**
@@ -10183,6 +10099,37 @@ export class Editor extends EventEmitter<TLEventMap> {
10183
10099
  /** @internal */
10184
10100
  private performanceTrackerTimeout = -1 as any
10185
10101
 
10102
+ /** @internal */
10103
+ private handledEvents = new WeakSet<Event>()
10104
+
10105
+ /**
10106
+ * In tldraw, events are sometimes handled by multiple components. For example, the shapes might
10107
+ * have events, but the canvas handles events too. The way that the canvas handles events can
10108
+ * interfere with the with the shapes event handlers - for example, it calls `.preventDefault()`
10109
+ * on `pointerDown`, which also prevents `click` events from firing on the shapes.
10110
+ *
10111
+ * You can use `.stopPropagation()` to prevent the event from propagating to the rest of the
10112
+ * DOM, but that can impact non-tldraw event handlers set up elsewhere. By using
10113
+ * `markEventAsHandled`, you'll stop other parts of tldraw from handling the event without
10114
+ * impacting other, non-tldraw event handlers. See also {@link Editor.wasEventAlreadyHandled}.
10115
+ *
10116
+ * @public
10117
+ */
10118
+ markEventAsHandled(e: Event | { nativeEvent: Event }) {
10119
+ const nativeEvent = 'nativeEvent' in e ? e.nativeEvent : e
10120
+ this.handledEvents.add(nativeEvent)
10121
+ }
10122
+
10123
+ /**
10124
+ * Checks if an event has already been handled. See {@link Editor.markEventAsHandled}.
10125
+ *
10126
+ * @public
10127
+ */
10128
+ wasEventAlreadyHandled(e: Event | { nativeEvent: Event }) {
10129
+ const nativeEvent = 'nativeEvent' in e ? e.nativeEvent : e
10130
+ return this.handledEvents.has(nativeEvent)
10131
+ }
10132
+
10186
10133
  /**
10187
10134
  * Dispatch an event to the editor.
10188
10135
  *
@@ -7,6 +7,12 @@ function fromScratch(editor: Editor): Set<TLShapeId> {
7
7
  const viewportPageBounds = editor.getViewportPageBounds()
8
8
  const notVisibleShapes = new Set<TLShapeId>()
9
9
  shapesIds.forEach((id) => {
10
+ const shape = editor.getShape(id)
11
+ if (!shape) return
12
+
13
+ const canCull = editor.getShapeUtil(shape.type).canCull(shape)
14
+ if (!canCull) return
15
+
10
16
  // If the shape is fully outside of the viewport page bounds, add it to the set.
11
17
  // We'll ignore masks here, since they're more expensive to compute and the overhead is not worth it.
12
18
  const pageBounds = editor.getShapePageBounds(id)
@@ -1,12 +1,13 @@
1
+ import { Mocked, vi } from 'vitest'
1
2
  import { Editor } from '../../Editor'
2
3
  import { TLClickEventInfo, TLPointerEventInfo } from '../../types/event-types'
3
4
  import { ClickManager } from './ClickManager'
4
5
 
5
6
  // Mock the Editor class
6
- jest.mock('../../Editor')
7
+ vi.mock('../../Editor')
7
8
 
8
9
  describe('ClickManager', () => {
9
- let editor: jest.Mocked<Editor>
10
+ let editor: Mocked<Editor>
10
11
  let clickManager: ClickManager
11
12
  let mockTimers: any
12
13
 
@@ -29,14 +30,14 @@ describe('ClickManager', () => {
29
30
  })
30
31
 
31
32
  beforeEach(() => {
32
- jest.useFakeTimers()
33
+ vi.useFakeTimers()
33
34
  mockTimers = {
34
- setTimeout: jest.fn((fn, delay) => setTimeout(fn, delay)),
35
+ setTimeout: vi.fn((fn, delay) => setTimeout(fn, delay)),
35
36
  }
36
37
 
37
38
  editor = {
38
39
  timers: mockTimers,
39
- dispatch: jest.fn(),
40
+ dispatch: vi.fn(),
40
41
  options: {
41
42
  doubleClickDurationMs: 300,
42
43
  multiClickDurationMs: 300,
@@ -46,7 +47,7 @@ describe('ClickManager', () => {
46
47
  inputs: {
47
48
  currentScreenPoint: { x: 0, y: 0 },
48
49
  },
49
- getInstanceState: jest.fn(() => ({
50
+ getInstanceState: vi.fn(() => ({
50
51
  isCoarsePointer: false,
51
52
  })),
52
53
  } as any
@@ -55,8 +56,8 @@ describe('ClickManager', () => {
55
56
  })
56
57
 
57
58
  afterEach(() => {
58
- jest.useRealTimers()
59
- jest.clearAllMocks()
59
+ vi.useRealTimers()
60
+ vi.clearAllMocks()
60
61
  })
61
62
 
62
63
  describe('constructor and initial state', () => {
@@ -100,7 +101,7 @@ describe('ClickManager', () => {
100
101
  clickManager.handlePointerEvent(pointerEvent)
101
102
  expect(clickManager.clickState).toBe('pendingDouble')
102
103
 
103
- jest.advanceTimersByTime(350)
104
+ vi.advanceTimersByTime(350)
104
105
 
105
106
  expect(clickManager.clickState).toBe('idle')
106
107
  })
@@ -141,7 +142,7 @@ describe('ClickManager', () => {
141
142
  clickManager.handlePointerEvent(firstDown)
142
143
  clickManager.handlePointerEvent(secondDown)
143
144
 
144
- jest.advanceTimersByTime(350)
145
+ vi.advanceTimersByTime(350)
145
146
 
146
147
  expect(editor.dispatch).toHaveBeenCalledWith(
147
148
  expect.objectContaining({
@@ -235,7 +236,7 @@ describe('ClickManager', () => {
235
236
  clickManager.handlePointerEvent(pointerDown) // second
236
237
  clickManager.handlePointerEvent(pointerDown) // third
237
238
 
238
- jest.advanceTimersByTime(350)
239
+ vi.advanceTimersByTime(350)
239
240
 
240
241
  expect(editor.dispatch).toHaveBeenCalledWith(
241
242
  expect.objectContaining({
@@ -255,7 +256,7 @@ describe('ClickManager', () => {
255
256
  clickManager.handlePointerEvent(pointerDown) // third
256
257
  clickManager.handlePointerEvent(pointerDown) // fourth
257
258
 
258
- jest.advanceTimersByTime(350)
259
+ vi.advanceTimersByTime(350)
259
260
 
260
261
  expect(editor.dispatch).toHaveBeenCalledWith(
261
262
  expect.objectContaining({
@@ -277,7 +278,7 @@ describe('ClickManager', () => {
277
278
  editor.options.doubleClickDurationMs
278
279
  )
279
280
 
280
- jest.clearAllMocks()
281
+ vi.clearAllMocks()
281
282
 
282
283
  // Second click - should use multiClickDurationMs
283
284
  clickManager.handlePointerEvent(pointerDown)
@@ -392,7 +393,7 @@ describe('ClickManager', () => {
392
393
  clickManager.cancelDoubleClickTimeout()
393
394
 
394
395
  // Advance time - should not dispatch settle event
395
- jest.advanceTimersByTime(350)
396
+ vi.advanceTimersByTime(350)
396
397
 
397
398
  expect(editor.dispatch).not.toHaveBeenCalled()
398
399
  expect(clickManager.clickState).toBe('idle')