@tldraw/editor 3.16.0-internal.a478398270c6 → 3.16.0-next.15f085081fd5

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 (235) hide show
  1. package/dist-cjs/index.d.ts +243 -16
  2. package/dist-cjs/index.js +8 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/TldrawEditor.js +8 -2
  5. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  6. package/dist-cjs/lib/components/MenuClickCapture.js +0 -5
  7. package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
  8. package/dist-cjs/lib/components/SVGContainer.js +1 -1
  9. package/dist-cjs/lib/components/SVGContainer.js.map +2 -2
  10. package/dist-cjs/lib/components/Shape.js +11 -36
  11. package/dist-cjs/lib/components/Shape.js.map +2 -2
  12. package/dist-cjs/lib/components/default-components/DefaultBrush.js +1 -1
  13. package/dist-cjs/lib/components/default-components/DefaultBrush.js.map +2 -2
  14. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +5 -24
  15. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  16. package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js +2 -2
  17. package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js.map +2 -2
  18. package/dist-cjs/lib/components/default-components/DefaultCursor.js +1 -1
  19. package/dist-cjs/lib/components/default-components/DefaultCursor.js.map +2 -2
  20. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +1 -1
  21. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +2 -2
  22. package/dist-cjs/lib/components/default-components/DefaultGrid.js +1 -1
  23. package/dist-cjs/lib/components/default-components/DefaultGrid.js.map +2 -2
  24. package/dist-cjs/lib/components/default-components/DefaultHandles.js +1 -1
  25. package/dist-cjs/lib/components/default-components/DefaultHandles.js.map +2 -2
  26. package/dist-cjs/lib/components/default-components/DefaultScribble.js +1 -1
  27. package/dist-cjs/lib/components/default-components/DefaultScribble.js.map +2 -2
  28. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js +9 -1
  29. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js.map +2 -2
  30. package/dist-cjs/lib/components/default-components/DefaultShapeWrapper.js +53 -0
  31. package/dist-cjs/lib/components/default-components/DefaultShapeWrapper.js.map +7 -0
  32. package/dist-cjs/lib/components/default-components/DefaultSnapIndictor.js +1 -1
  33. package/dist-cjs/lib/components/default-components/DefaultSnapIndictor.js.map +2 -2
  34. package/dist-cjs/lib/components/default-components/DefaultSpinner.js +27 -15
  35. package/dist-cjs/lib/components/default-components/DefaultSpinner.js.map +3 -3
  36. package/dist-cjs/lib/config/TLUserPreferences.js +15 -3
  37. package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
  38. package/dist-cjs/lib/editor/Editor.js +151 -67
  39. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  40. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +14 -4
  41. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  42. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +13 -0
  43. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  44. package/dist-cjs/lib/editor/tools/StateNode.js +20 -1
  45. package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
  46. package/dist-cjs/lib/editor/types/misc-types.js.map +1 -1
  47. package/dist-cjs/lib/exports/getSvgJsx.js +35 -16
  48. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  49. package/dist-cjs/lib/hooks/useCanvasEvents.js +31 -25
  50. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  51. package/dist-cjs/lib/hooks/useEditor.js +1 -4
  52. package/dist-cjs/lib/hooks/useEditor.js.map +2 -2
  53. package/dist-cjs/lib/hooks/useEditorComponents.js +2 -0
  54. package/dist-cjs/lib/hooks/useEditorComponents.js.map +2 -2
  55. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js +4 -1
  56. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +2 -2
  57. package/dist-cjs/lib/{utils/nearestMultiple.js → hooks/useStateAttribute.js} +15 -14
  58. package/dist-cjs/lib/hooks/useStateAttribute.js.map +7 -0
  59. package/dist-cjs/lib/license/Watermark.js +8 -8
  60. package/dist-cjs/lib/license/Watermark.js.map +2 -2
  61. package/dist-cjs/lib/options.js +7 -0
  62. package/dist-cjs/lib/options.js.map +2 -2
  63. package/dist-cjs/lib/primitives/Box.js +3 -0
  64. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  65. package/dist-cjs/lib/primitives/geometry/Arc2d.js +1 -1
  66. package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
  67. package/dist-cjs/lib/primitives/geometry/Circle2d.js +1 -1
  68. package/dist-cjs/lib/primitives/geometry/Circle2d.js.map +2 -2
  69. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +3 -1
  70. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
  71. package/dist-cjs/lib/primitives/geometry/Ellipse2d.js +1 -1
  72. package/dist-cjs/lib/primitives/geometry/Ellipse2d.js.map +2 -2
  73. package/dist-cjs/lib/primitives/geometry/geometry-constants.js +2 -2
  74. package/dist-cjs/lib/primitives/geometry/geometry-constants.js.map +2 -2
  75. package/dist-cjs/lib/primitives/intersect.js +4 -4
  76. package/dist-cjs/lib/primitives/intersect.js.map +2 -2
  77. package/dist-cjs/lib/primitives/utils.js +4 -0
  78. package/dist-cjs/lib/primitives/utils.js.map +2 -2
  79. package/dist-cjs/lib/utils/EditorAtom.js +45 -0
  80. package/dist-cjs/lib/utils/EditorAtom.js.map +7 -0
  81. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +0 -1
  82. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +2 -2
  83. package/dist-cjs/version.js +3 -3
  84. package/dist-cjs/version.js.map +1 -1
  85. package/dist-esm/index.d.mts +243 -16
  86. package/dist-esm/index.mjs +16 -2
  87. package/dist-esm/index.mjs.map +2 -2
  88. package/dist-esm/lib/TldrawEditor.mjs +8 -2
  89. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  90. package/dist-esm/lib/components/MenuClickCapture.mjs +0 -5
  91. package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
  92. package/dist-esm/lib/components/SVGContainer.mjs +1 -1
  93. package/dist-esm/lib/components/SVGContainer.mjs.map +2 -2
  94. package/dist-esm/lib/components/Shape.mjs +11 -36
  95. package/dist-esm/lib/components/Shape.mjs.map +2 -2
  96. package/dist-esm/lib/components/default-components/DefaultBrush.mjs +1 -1
  97. package/dist-esm/lib/components/default-components/DefaultBrush.mjs.map +2 -2
  98. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +5 -24
  99. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  100. package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs +2 -2
  101. package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs.map +2 -2
  102. package/dist-esm/lib/components/default-components/DefaultCursor.mjs +1 -1
  103. package/dist-esm/lib/components/default-components/DefaultCursor.mjs.map +2 -2
  104. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +1 -1
  105. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +2 -2
  106. package/dist-esm/lib/components/default-components/DefaultGrid.mjs +1 -1
  107. package/dist-esm/lib/components/default-components/DefaultGrid.mjs.map +2 -2
  108. package/dist-esm/lib/components/default-components/DefaultHandles.mjs +1 -1
  109. package/dist-esm/lib/components/default-components/DefaultHandles.mjs.map +2 -2
  110. package/dist-esm/lib/components/default-components/DefaultScribble.mjs +1 -1
  111. package/dist-esm/lib/components/default-components/DefaultScribble.mjs.map +2 -2
  112. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs +9 -1
  113. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +2 -2
  114. package/dist-esm/lib/components/default-components/DefaultShapeWrapper.mjs +23 -0
  115. package/dist-esm/lib/components/default-components/DefaultShapeWrapper.mjs.map +7 -0
  116. package/dist-esm/lib/components/default-components/DefaultSnapIndictor.mjs +1 -1
  117. package/dist-esm/lib/components/default-components/DefaultSnapIndictor.mjs.map +2 -2
  118. package/dist-esm/lib/components/default-components/DefaultSpinner.mjs +17 -15
  119. package/dist-esm/lib/components/default-components/DefaultSpinner.mjs.map +2 -2
  120. package/dist-esm/lib/config/TLUserPreferences.mjs +15 -3
  121. package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
  122. package/dist-esm/lib/editor/Editor.mjs +151 -67
  123. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  124. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +14 -4
  125. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  126. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +13 -0
  127. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  128. package/dist-esm/lib/editor/tools/StateNode.mjs +20 -1
  129. package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
  130. package/dist-esm/lib/exports/getSvgJsx.mjs +36 -16
  131. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  132. package/dist-esm/lib/hooks/useCanvasEvents.mjs +32 -26
  133. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  134. package/dist-esm/lib/hooks/useEditor.mjs +1 -4
  135. package/dist-esm/lib/hooks/useEditor.mjs.map +2 -2
  136. package/dist-esm/lib/hooks/useEditorComponents.mjs +4 -0
  137. package/dist-esm/lib/hooks/useEditorComponents.mjs.map +2 -2
  138. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs +4 -1
  139. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +2 -2
  140. package/dist-esm/lib/hooks/useStateAttribute.mjs +15 -0
  141. package/dist-esm/lib/hooks/useStateAttribute.mjs.map +7 -0
  142. package/dist-esm/lib/license/Watermark.mjs +8 -8
  143. package/dist-esm/lib/license/Watermark.mjs.map +2 -2
  144. package/dist-esm/lib/options.mjs +7 -0
  145. package/dist-esm/lib/options.mjs.map +2 -2
  146. package/dist-esm/lib/primitives/Box.mjs +4 -1
  147. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  148. package/dist-esm/lib/primitives/geometry/Arc2d.mjs +2 -2
  149. package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
  150. package/dist-esm/lib/primitives/geometry/Circle2d.mjs +2 -2
  151. package/dist-esm/lib/primitives/geometry/Circle2d.mjs.map +2 -2
  152. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +3 -1
  153. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
  154. package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs +2 -2
  155. package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs.map +2 -2
  156. package/dist-esm/lib/primitives/geometry/geometry-constants.mjs +2 -2
  157. package/dist-esm/lib/primitives/geometry/geometry-constants.mjs.map +2 -2
  158. package/dist-esm/lib/primitives/intersect.mjs +5 -5
  159. package/dist-esm/lib/primitives/intersect.mjs.map +2 -2
  160. package/dist-esm/lib/primitives/utils.mjs +4 -0
  161. package/dist-esm/lib/primitives/utils.mjs.map +2 -2
  162. package/dist-esm/lib/utils/EditorAtom.mjs +25 -0
  163. package/dist-esm/lib/utils/EditorAtom.mjs.map +7 -0
  164. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +0 -1
  165. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
  166. package/dist-esm/version.mjs +3 -3
  167. package/dist-esm/version.mjs.map +1 -1
  168. package/editor.css +320 -313
  169. package/package.json +16 -38
  170. package/src/index.ts +15 -1
  171. package/src/lib/TldrawEditor.tsx +13 -6
  172. package/src/lib/components/MenuClickCapture.tsx +0 -8
  173. package/src/lib/components/SVGContainer.tsx +1 -1
  174. package/src/lib/components/Shape.tsx +12 -33
  175. package/src/lib/components/default-components/DefaultBrush.tsx +1 -1
  176. package/src/lib/components/default-components/DefaultCanvas.tsx +6 -23
  177. package/src/lib/components/default-components/DefaultCollaboratorHint.tsx +2 -2
  178. package/src/lib/components/default-components/DefaultCursor.tsx +1 -1
  179. package/src/lib/components/default-components/DefaultErrorFallback.tsx +1 -1
  180. package/src/lib/components/default-components/DefaultGrid.tsx +1 -1
  181. package/src/lib/components/default-components/DefaultHandles.tsx +5 -1
  182. package/src/lib/components/default-components/DefaultScribble.tsx +1 -1
  183. package/src/lib/components/default-components/DefaultShapeIndicator.tsx +6 -2
  184. package/src/lib/components/default-components/DefaultShapeWrapper.tsx +35 -0
  185. package/src/lib/components/default-components/DefaultSnapIndictor.tsx +1 -1
  186. package/src/lib/components/default-components/DefaultSpinner.tsx +12 -12
  187. package/src/lib/config/TLUserPreferences.ts +15 -1
  188. package/src/lib/editor/Editor.test.ts +416 -8
  189. package/src/lib/editor/Editor.ts +195 -92
  190. package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +15 -14
  191. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +16 -15
  192. package/src/lib/editor/managers/FocusManager/FocusManager.test.ts +49 -48
  193. package/src/lib/editor/managers/FontManager/FontManager.test.ts +24 -23
  194. package/src/lib/editor/managers/HistoryManager/HistoryManager.test.ts +7 -6
  195. package/src/lib/editor/managers/ScribbleManager/ScribbleManager.test.ts +12 -11
  196. package/src/lib/editor/managers/SnapManager/SnapManager.test.ts +57 -50
  197. package/src/lib/editor/managers/TextManager/TextManager.test.ts +51 -26
  198. package/src/lib/editor/managers/TickManager/TickManager.test.ts +14 -13
  199. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +55 -26
  200. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +14 -1
  201. package/src/lib/editor/shapes/ShapeUtil.ts +71 -0
  202. package/src/lib/editor/tools/StateNode.test.ts +285 -0
  203. package/src/lib/editor/tools/StateNode.ts +27 -1
  204. package/src/lib/editor/types/misc-types.ts +73 -1
  205. package/src/lib/exports/getSvgJsx.test.ts +868 -0
  206. package/src/lib/exports/getSvgJsx.tsx +78 -21
  207. package/src/lib/hooks/useCanvasEvents.ts +45 -38
  208. package/src/lib/hooks/useEditor.tsx +6 -5
  209. package/src/lib/hooks/useEditorComponents.tsx +8 -2
  210. package/src/lib/hooks/usePassThroughWheelEvents.ts +6 -1
  211. package/src/lib/hooks/useStateAttribute.ts +15 -0
  212. package/src/lib/license/LicenseManager.test.ts +3 -1
  213. package/src/lib/license/Watermark.test.tsx +2 -1
  214. package/src/lib/license/Watermark.tsx +8 -8
  215. package/src/lib/options.ts +8 -0
  216. package/src/lib/primitives/Box.test.ts +126 -0
  217. package/src/lib/primitives/Box.ts +10 -1
  218. package/src/lib/primitives/geometry/Arc2d.ts +2 -2
  219. package/src/lib/primitives/geometry/Circle2d.ts +2 -2
  220. package/src/lib/primitives/geometry/CubicBezier2d.ts +4 -1
  221. package/src/lib/primitives/geometry/Ellipse2d.ts +2 -2
  222. package/src/lib/primitives/geometry/geometry-constants.ts +2 -1
  223. package/src/lib/primitives/intersect.test.ts +946 -0
  224. package/src/lib/primitives/intersect.ts +12 -5
  225. package/src/lib/primitives/utils.ts +11 -0
  226. package/src/lib/utils/EditorAtom.ts +37 -0
  227. package/src/lib/utils/sync/LocalIndexedDb.test.ts +2 -1
  228. package/src/lib/utils/sync/TLLocalSyncClient.test.ts +15 -15
  229. package/src/lib/utils/sync/TLLocalSyncClient.ts +0 -1
  230. package/src/version.ts +3 -3
  231. package/dist-cjs/lib/utils/nearestMultiple.js.map +0 -7
  232. package/dist-esm/lib/utils/nearestMultiple.mjs +0 -14
  233. package/dist-esm/lib/utils/nearestMultiple.mjs.map +0 -7
  234. package/src/lib/test/currentToolIdMask.test.ts +0 -49
  235. 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 })
@@ -425,3 +426,410 @@ describe('getShapesAtPoint', () => {
425
426
  expect(hollowShapesWithHitInside[0].id).toBe(ids.hollowShape)
426
427
  })
427
428
  })
429
+
430
+ describe('selectAll', () => {
431
+ const selectAllIds = {
432
+ pageShape1: createShapeId('pageShape1'),
433
+ pageShape2: createShapeId('pageShape2'),
434
+ pageShape3: createShapeId('pageShape3'),
435
+ container1: createShapeId('container1'),
436
+ containerChild1: createShapeId('containerChild1'),
437
+ containerChild2: createShapeId('containerChild2'),
438
+ containerChild3: createShapeId('containerChild3'),
439
+ containerGrandchild1: createShapeId('containerGrandchild1'),
440
+ container2: createShapeId('container2'),
441
+ container2Child1: createShapeId('container2Child1'),
442
+ container2Child2: createShapeId('container2Child2'),
443
+ container2Grandchild1: createShapeId('container2Grandchild1'),
444
+ lockedShape: createShapeId('lockedShape'),
445
+ }
446
+
447
+ beforeEach(() => {
448
+ // Clear any existing shapes
449
+ editor.selectAll().deleteShapes(editor.getSelectedShapeIds())
450
+
451
+ // Create shapes directly on the page (no parentId means they're children of the page)
452
+ editor.createShapes([
453
+ {
454
+ id: selectAllIds.pageShape1,
455
+ type: 'my-custom-shape',
456
+ x: 100,
457
+ y: 100,
458
+ props: { w: 100, h: 100 },
459
+ },
460
+ {
461
+ id: selectAllIds.pageShape2,
462
+ type: 'my-custom-shape',
463
+ x: 300,
464
+ y: 100,
465
+ props: { w: 100, h: 100 },
466
+ },
467
+ {
468
+ id: selectAllIds.pageShape3,
469
+ type: 'my-custom-shape',
470
+ x: 500,
471
+ y: 100,
472
+ props: { w: 100, h: 100 },
473
+ },
474
+ {
475
+ id: selectAllIds.lockedShape,
476
+ type: 'my-custom-shape',
477
+ x: 700,
478
+ y: 100,
479
+ props: { w: 100, h: 100 },
480
+ isLocked: true,
481
+ },
482
+ ])
483
+
484
+ // Create a container shape (simulating a frame or group)
485
+ editor.createShape({
486
+ id: selectAllIds.container1,
487
+ type: 'my-custom-shape',
488
+ x: 100,
489
+ y: 300,
490
+ props: { w: 400, h: 200 },
491
+ })
492
+
493
+ // Create children inside the container (parentId set to container1)
494
+ editor.createShapes([
495
+ {
496
+ id: selectAllIds.containerChild1,
497
+ type: 'my-custom-shape',
498
+ parentId: selectAllIds.container1,
499
+ x: 120,
500
+ y: 320,
501
+ props: { w: 50, h: 50 },
502
+ },
503
+ {
504
+ id: selectAllIds.containerChild2,
505
+ type: 'my-custom-shape',
506
+ parentId: selectAllIds.container1,
507
+ x: 200,
508
+ y: 320,
509
+ props: { w: 50, h: 50 },
510
+ },
511
+ {
512
+ id: selectAllIds.containerChild3,
513
+ type: 'my-custom-shape',
514
+ parentId: selectAllIds.container1,
515
+ x: 280,
516
+ y: 320,
517
+ props: { w: 50, h: 50 },
518
+ },
519
+ ])
520
+
521
+ // Create a grandchild inside one of the container children
522
+ editor.createShape({
523
+ id: selectAllIds.containerGrandchild1,
524
+ type: 'my-custom-shape',
525
+ parentId: selectAllIds.containerChild3,
526
+ x: 290,
527
+ y: 330,
528
+ props: { w: 30, h: 30 },
529
+ })
530
+
531
+ // Create a second container (simulating a group)
532
+ editor.createShape({
533
+ id: selectAllIds.container2,
534
+ type: 'my-custom-shape',
535
+ x: 600,
536
+ y: 300,
537
+ props: { w: 200, h: 200 },
538
+ })
539
+
540
+ // Create children inside the second container
541
+ editor.createShapes([
542
+ {
543
+ id: selectAllIds.container2Child1,
544
+ type: 'my-custom-shape',
545
+ parentId: selectAllIds.container2,
546
+ x: 620,
547
+ y: 320,
548
+ props: { w: 50, h: 50 },
549
+ },
550
+ {
551
+ id: selectAllIds.container2Child2,
552
+ type: 'my-custom-shape',
553
+ parentId: selectAllIds.container2,
554
+ x: 680,
555
+ y: 320,
556
+ props: { w: 50, h: 50 },
557
+ },
558
+ ])
559
+
560
+ // Create a grandchild in the second container
561
+ editor.createShape({
562
+ id: selectAllIds.container2Grandchild1,
563
+ type: 'my-custom-shape',
564
+ parentId: selectAllIds.container2Child1,
565
+ x: 630,
566
+ y: 330,
567
+ props: { w: 30, h: 30 },
568
+ })
569
+
570
+ // Clear selection
571
+ editor.selectNone()
572
+ })
573
+
574
+ it('when no shapes are selected, selects all page-level shapes (excluding locked ones)', () => {
575
+ // Initially no shapes selected
576
+ expect(editor.getSelectedShapeIds()).toEqual([])
577
+
578
+ // Call selectAll
579
+ editor.selectAll()
580
+
581
+ // Should select all page-level shapes (excluding locked ones)
582
+ const selectedIds = editor.getSelectedShapeIds()
583
+ expect(Array.from(selectedIds).sort()).toEqual(
584
+ [
585
+ selectAllIds.pageShape1,
586
+ selectAllIds.pageShape2,
587
+ selectAllIds.pageShape3,
588
+ selectAllIds.container1,
589
+ selectAllIds.container2,
590
+ ].sort()
591
+ )
592
+
593
+ // Should NOT include locked shape or children/grandchildren
594
+ expect(selectedIds).not.toContain(selectAllIds.lockedShape)
595
+ expect(selectedIds).not.toContain(selectAllIds.containerChild1)
596
+ expect(selectedIds).not.toContain(selectAllIds.containerChild2)
597
+ expect(selectedIds).not.toContain(selectAllIds.containerChild3)
598
+ expect(selectedIds).not.toContain(selectAllIds.containerGrandchild1)
599
+ expect(selectedIds).not.toContain(selectAllIds.container2Child1)
600
+ expect(selectedIds).not.toContain(selectAllIds.container2Child2)
601
+ expect(selectedIds).not.toContain(selectAllIds.container2Grandchild1)
602
+ })
603
+
604
+ it('when shapes are selected only on the page, all children of the page should be selected (but not their descendants)', () => {
605
+ // Select some page-level shapes
606
+ editor.select(selectAllIds.pageShape1, selectAllIds.pageShape2)
607
+
608
+ // Call selectAll
609
+ editor.selectAll()
610
+
611
+ // Should select all page-level shapes (excluding locked ones), but not descendants
612
+ const selectedIds = editor.getSelectedShapeIds()
613
+ expect(Array.from(selectedIds).sort()).toEqual(
614
+ [
615
+ selectAllIds.pageShape1,
616
+ selectAllIds.pageShape2,
617
+ selectAllIds.pageShape3,
618
+ selectAllIds.container1,
619
+ selectAllIds.container2,
620
+ ].sort()
621
+ )
622
+
623
+ // Should NOT include children or grandchildren or locked shapes
624
+ expect(selectedIds).not.toContain(selectAllIds.containerChild1)
625
+ expect(selectedIds).not.toContain(selectAllIds.containerChild2)
626
+ expect(selectedIds).not.toContain(selectAllIds.containerChild3)
627
+ expect(selectedIds).not.toContain(selectAllIds.containerGrandchild1)
628
+ expect(selectedIds).not.toContain(selectAllIds.container2Child1)
629
+ expect(selectedIds).not.toContain(selectAllIds.container2Child2)
630
+ expect(selectedIds).not.toContain(selectAllIds.container2Grandchild1)
631
+ expect(selectedIds).not.toContain(selectAllIds.lockedShape)
632
+ })
633
+
634
+ it('when shapes are selected within a container, only children of the container should be selected (not their descendants)', () => {
635
+ // Select some container children
636
+ editor.select(selectAllIds.containerChild1, selectAllIds.containerChild2)
637
+
638
+ // Call selectAll
639
+ editor.selectAll()
640
+
641
+ // Should select all container children (but not their descendants)
642
+ const selectedIds = editor.getSelectedShapeIds()
643
+ expect(Array.from(selectedIds).sort()).toEqual(
644
+ [
645
+ selectAllIds.containerChild1,
646
+ selectAllIds.containerChild2,
647
+ selectAllIds.containerChild3,
648
+ ].sort()
649
+ )
650
+
651
+ // Should NOT include page-level shapes or grandchildren
652
+ expect(selectedIds).not.toContain(selectAllIds.pageShape1)
653
+ expect(selectedIds).not.toContain(selectAllIds.pageShape2)
654
+ expect(selectedIds).not.toContain(selectAllIds.pageShape3)
655
+ expect(selectedIds).not.toContain(selectAllIds.container1)
656
+ expect(selectedIds).not.toContain(selectAllIds.container2)
657
+ expect(selectedIds).not.toContain(selectAllIds.containerGrandchild1)
658
+ expect(selectedIds).not.toContain(selectAllIds.container2Child1)
659
+ expect(selectedIds).not.toContain(selectAllIds.container2Child2)
660
+ expect(selectedIds).not.toContain(selectAllIds.container2Grandchild1)
661
+ })
662
+
663
+ it('when shapes are selected within a second container, only children of that container should be selected', () => {
664
+ // Select some second container children
665
+ editor.select(selectAllIds.container2Child1)
666
+
667
+ // Call selectAll
668
+ editor.selectAll()
669
+
670
+ // Should select all second container children (but not their descendants)
671
+ const selectedIds = editor.getSelectedShapeIds()
672
+ expect(Array.from(selectedIds).sort()).toEqual(
673
+ [selectAllIds.container2Child1, selectAllIds.container2Child2].sort()
674
+ )
675
+
676
+ // Should NOT include page-level shapes or other container's children or grandchildren
677
+ expect(selectedIds).not.toContain(selectAllIds.pageShape1)
678
+ expect(selectedIds).not.toContain(selectAllIds.pageShape2)
679
+ expect(selectedIds).not.toContain(selectAllIds.pageShape3)
680
+ expect(selectedIds).not.toContain(selectAllIds.container1)
681
+ expect(selectedIds).not.toContain(selectAllIds.container2)
682
+ expect(selectedIds).not.toContain(selectAllIds.containerChild1)
683
+ expect(selectedIds).not.toContain(selectAllIds.containerChild2)
684
+ expect(selectedIds).not.toContain(selectAllIds.containerChild3)
685
+ expect(selectedIds).not.toContain(selectAllIds.containerGrandchild1)
686
+ expect(selectedIds).not.toContain(selectAllIds.container2Grandchild1)
687
+ })
688
+
689
+ it('when shapes are selected that belong to different parents, no change/history entry should be made', () => {
690
+ // Select shapes from different parents (page and container)
691
+ editor.select(selectAllIds.pageShape1, selectAllIds.containerChild1)
692
+
693
+ const initialSelectedIds = editor.getSelectedShapeIds()
694
+
695
+ // Spy on setSelectedShapes to verify it's not called
696
+ const setSelectedShapesSpy = vi.spyOn(editor, 'setSelectedShapes')
697
+
698
+ // Call selectAll
699
+ editor.selectAll()
700
+
701
+ // Selection should remain unchanged
702
+ expect(editor.getSelectedShapeIds()).toEqual(initialSelectedIds)
703
+
704
+ // setSelectedShapes should not have been called (the method returns early)
705
+ expect(setSelectedShapesSpy).not.toHaveBeenCalled()
706
+
707
+ setSelectedShapesSpy.mockRestore()
708
+ })
709
+
710
+ it('when shapes are selected that belong to different containers, no change/history entry should be made', () => {
711
+ // Select shapes from different containers
712
+ editor.select(selectAllIds.containerChild1, selectAllIds.container2Child1)
713
+
714
+ const initialSelectedIds = editor.getSelectedShapeIds()
715
+
716
+ // Spy on setSelectedShapes to verify it's not called
717
+ const setSelectedShapesSpy = vi.spyOn(editor, 'setSelectedShapes')
718
+
719
+ // Call selectAll
720
+ editor.selectAll()
721
+
722
+ // Selection should remain unchanged
723
+ expect(editor.getSelectedShapeIds()).toEqual(initialSelectedIds)
724
+
725
+ // setSelectedShapes should not have been called
726
+ expect(setSelectedShapesSpy).not.toHaveBeenCalled()
727
+
728
+ setSelectedShapesSpy.mockRestore()
729
+ })
730
+
731
+ it('should not select locked shapes', () => {
732
+ // Select a page-level shape
733
+ editor.select(selectAllIds.pageShape1)
734
+
735
+ // Call selectAll
736
+ editor.selectAll()
737
+
738
+ // Should select all page-level shapes except locked ones
739
+ const selectedIds = editor.getSelectedShapeIds()
740
+ expect(selectedIds).not.toContain(selectAllIds.lockedShape)
741
+ expect(selectedIds).toContain(selectAllIds.pageShape1)
742
+ expect(selectedIds).toContain(selectAllIds.pageShape2)
743
+ expect(selectedIds).toContain(selectAllIds.pageShape3)
744
+ expect(selectedIds).toContain(selectAllIds.container1)
745
+ expect(selectedIds).toContain(selectAllIds.container2)
746
+ })
747
+
748
+ it('should handle empty container by selecting all siblings at the same level', () => {
749
+ // Create an empty container
750
+ const emptyContainerId = createShapeId('emptyContainer')
751
+ editor.createShape({
752
+ id: emptyContainerId,
753
+ type: 'my-custom-shape',
754
+ x: 800,
755
+ y: 400,
756
+ props: { w: 100, h: 100 },
757
+ })
758
+
759
+ // Clear selection first
760
+ editor.selectNone()
761
+
762
+ // Select the empty container
763
+ editor.select(emptyContainerId)
764
+
765
+ // Call selectAll - since the empty container has no children, it should select all siblings (page-level shapes)
766
+ editor.selectAll()
767
+
768
+ // Should select all page-level shapes (including the empty container itself)
769
+ const selectedIds = editor.getSelectedShapeIds()
770
+ expect(Array.from(selectedIds).sort()).toEqual(
771
+ [
772
+ selectAllIds.pageShape1,
773
+ selectAllIds.pageShape2,
774
+ selectAllIds.pageShape3,
775
+ selectAllIds.container1,
776
+ selectAllIds.container2,
777
+ emptyContainerId,
778
+ ].sort()
779
+ )
780
+
781
+ // Should NOT include locked shapes or children/grandchildren
782
+ expect(selectedIds).not.toContain(selectAllIds.lockedShape)
783
+ expect(selectedIds).not.toContain(selectAllIds.containerChild1)
784
+ expect(selectedIds).not.toContain(selectAllIds.containerChild2)
785
+ expect(selectedIds).not.toContain(selectAllIds.containerChild3)
786
+ expect(selectedIds).not.toContain(selectAllIds.containerGrandchild1)
787
+ expect(selectedIds).not.toContain(selectAllIds.container2Child1)
788
+ expect(selectedIds).not.toContain(selectAllIds.container2Child2)
789
+ expect(selectedIds).not.toContain(selectAllIds.container2Grandchild1)
790
+ })
791
+
792
+ it('should work correctly when selecting all shapes of same parent type', () => {
793
+ // Select all container children
794
+ editor.select(
795
+ selectAllIds.containerChild1,
796
+ selectAllIds.containerChild2,
797
+ selectAllIds.containerChild3
798
+ )
799
+
800
+ // Call selectAll - should maintain the same selection since all children are already selected
801
+ editor.selectAll()
802
+
803
+ // Should still have all container children selected
804
+ const selectedIds = editor.getSelectedShapeIds()
805
+ expect(Array.from(selectedIds).sort()).toEqual(
806
+ [
807
+ selectAllIds.containerChild1,
808
+ selectAllIds.containerChild2,
809
+ selectAllIds.containerChild3,
810
+ ].sort()
811
+ )
812
+ })
813
+
814
+ it('should handle mixed selection levels gracefully by doing nothing', () => {
815
+ // Select a mix: page shape (parent=page), container (parent=page), and container child (parent=container1)
816
+ // These all have different parent IDs so selectAll should do nothing
817
+ editor.select(selectAllIds.pageShape1, selectAllIds.containerChild1)
818
+
819
+ const initialSelectedIds = Array.from(editor.getSelectedShapeIds())
820
+
821
+ // Spy on setSelectedShapes to verify it's not called
822
+ const setSelectedShapesSpy = vi.spyOn(editor, 'setSelectedShapes')
823
+
824
+ // Call selectAll
825
+ editor.selectAll()
826
+
827
+ // Selection should remain unchanged since shapes have different parents
828
+ expect(Array.from(editor.getSelectedShapeIds())).toEqual(initialSelectedIds)
829
+
830
+ // setSelectedShapes should not have been called
831
+ expect(setSelectedShapesSpy).not.toHaveBeenCalled()
832
+
833
+ setSelectedShapesSpy.mockRestore()
834
+ })
835
+ })