@tldraw/editor 3.16.0-canary.ca347c5375a5 → 3.16.0-canary.cb18f446a36f

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 (221) hide show
  1. package/dist-cjs/index.d.ts +193 -114
  2. package/dist-cjs/index.js +5 -5
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/TldrawEditor.js +9 -9
  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/Shape.js +7 -10
  9. package/dist-cjs/lib/components/Shape.js.map +2 -2
  10. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +14 -23
  11. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  12. package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js +1 -1
  13. package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js.map +1 -1
  14. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +1 -1
  15. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +2 -2
  16. package/dist-cjs/lib/components/default-components/DefaultScribble.js +1 -1
  17. package/dist-cjs/lib/components/default-components/DefaultScribble.js.map +2 -2
  18. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js +9 -1
  19. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js.map +2 -2
  20. package/dist-cjs/lib/config/TLUserPreferences.js +9 -3
  21. package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
  22. package/dist-cjs/lib/editor/Editor.js +115 -137
  23. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  24. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +4 -0
  25. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +2 -2
  26. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js +4 -2
  27. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js.map +2 -2
  28. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +9 -4
  29. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  30. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +23 -0
  31. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  32. package/dist-cjs/lib/editor/types/misc-types.js.map +1 -1
  33. package/dist-cjs/lib/exports/getSvgJsx.js +35 -16
  34. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  35. package/dist-cjs/lib/hooks/useCanvasEvents.js +47 -38
  36. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  37. package/dist-cjs/lib/hooks/useDocumentEvents.js +5 -5
  38. package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
  39. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js +1 -2
  40. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js.map +2 -2
  41. package/dist-cjs/lib/hooks/useGestureEvents.js +1 -1
  42. package/dist-cjs/lib/hooks/useGestureEvents.js.map +2 -2
  43. package/dist-cjs/lib/hooks/useHandleEvents.js +6 -6
  44. package/dist-cjs/lib/hooks/useHandleEvents.js.map +2 -2
  45. package/dist-cjs/lib/hooks/usePassThroughMouseOverEvents.js +4 -1
  46. package/dist-cjs/lib/hooks/usePassThroughMouseOverEvents.js.map +2 -2
  47. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js +4 -1
  48. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +2 -2
  49. package/dist-cjs/lib/hooks/useSelectionEvents.js +8 -8
  50. package/dist-cjs/lib/hooks/useSelectionEvents.js.map +2 -2
  51. package/dist-cjs/lib/{utils/nearestMultiple.js → hooks/useStateAttribute.js} +15 -14
  52. package/dist-cjs/lib/hooks/useStateAttribute.js.map +7 -0
  53. package/dist-cjs/lib/license/LicenseManager.js +143 -53
  54. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  55. package/dist-cjs/lib/license/LicenseProvider.js +39 -1
  56. package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
  57. package/dist-cjs/lib/license/Watermark.js +144 -75
  58. package/dist-cjs/lib/license/Watermark.js.map +3 -3
  59. package/dist-cjs/lib/license/useLicenseManagerState.js.map +2 -2
  60. package/dist-cjs/lib/options.js +7 -0
  61. package/dist-cjs/lib/options.js.map +2 -2
  62. package/dist-cjs/lib/primitives/Box.js +3 -0
  63. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  64. package/dist-cjs/lib/primitives/Vec.js +0 -4
  65. package/dist-cjs/lib/primitives/Vec.js.map +2 -2
  66. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +50 -20
  67. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  68. package/dist-cjs/lib/primitives/geometry/Group2d.js +8 -1
  69. package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
  70. package/dist-cjs/lib/utils/EditorAtom.js +45 -0
  71. package/dist-cjs/lib/utils/EditorAtom.js.map +7 -0
  72. package/dist-cjs/lib/utils/dom.js.map +2 -2
  73. package/dist-cjs/lib/utils/getPointerInfo.js +2 -3
  74. package/dist-cjs/lib/utils/getPointerInfo.js.map +2 -2
  75. package/dist-cjs/lib/utils/reparenting.js +2 -35
  76. package/dist-cjs/lib/utils/reparenting.js.map +3 -3
  77. package/dist-cjs/version.js +3 -3
  78. package/dist-cjs/version.js.map +1 -1
  79. package/dist-esm/index.d.mts +193 -114
  80. package/dist-esm/index.mjs +5 -5
  81. package/dist-esm/index.mjs.map +2 -2
  82. package/dist-esm/lib/TldrawEditor.mjs +9 -9
  83. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  84. package/dist-esm/lib/components/MenuClickCapture.mjs +0 -5
  85. package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
  86. package/dist-esm/lib/components/Shape.mjs +7 -10
  87. package/dist-esm/lib/components/Shape.mjs.map +2 -2
  88. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +14 -23
  89. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  90. package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs +1 -1
  91. package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs.map +1 -1
  92. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +1 -1
  93. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +2 -2
  94. package/dist-esm/lib/components/default-components/DefaultScribble.mjs +1 -1
  95. package/dist-esm/lib/components/default-components/DefaultScribble.mjs.map +2 -2
  96. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs +9 -1
  97. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +2 -2
  98. package/dist-esm/lib/config/TLUserPreferences.mjs +9 -3
  99. package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
  100. package/dist-esm/lib/editor/Editor.mjs +115 -137
  101. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  102. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +4 -0
  103. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +2 -2
  104. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs +4 -2
  105. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs.map +2 -2
  106. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +9 -4
  107. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  108. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +23 -0
  109. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  110. package/dist-esm/lib/exports/getSvgJsx.mjs +36 -16
  111. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  112. package/dist-esm/lib/hooks/useCanvasEvents.mjs +49 -45
  113. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  114. package/dist-esm/lib/hooks/useDocumentEvents.mjs +6 -6
  115. package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
  116. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs +1 -2
  117. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs.map +2 -2
  118. package/dist-esm/lib/hooks/useGestureEvents.mjs +2 -2
  119. package/dist-esm/lib/hooks/useGestureEvents.mjs.map +2 -2
  120. package/dist-esm/lib/hooks/useHandleEvents.mjs +6 -6
  121. package/dist-esm/lib/hooks/useHandleEvents.mjs.map +2 -2
  122. package/dist-esm/lib/hooks/usePassThroughMouseOverEvents.mjs +4 -1
  123. package/dist-esm/lib/hooks/usePassThroughMouseOverEvents.mjs.map +2 -2
  124. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs +4 -1
  125. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +2 -2
  126. package/dist-esm/lib/hooks/useSelectionEvents.mjs +9 -14
  127. package/dist-esm/lib/hooks/useSelectionEvents.mjs.map +2 -2
  128. package/dist-esm/lib/hooks/useStateAttribute.mjs +15 -0
  129. package/dist-esm/lib/hooks/useStateAttribute.mjs.map +7 -0
  130. package/dist-esm/lib/license/LicenseManager.mjs +144 -54
  131. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  132. package/dist-esm/lib/license/LicenseProvider.mjs +39 -2
  133. package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
  134. package/dist-esm/lib/license/Watermark.mjs +145 -76
  135. package/dist-esm/lib/license/Watermark.mjs.map +3 -3
  136. package/dist-esm/lib/license/useLicenseManagerState.mjs.map +2 -2
  137. package/dist-esm/lib/options.mjs +7 -0
  138. package/dist-esm/lib/options.mjs.map +2 -2
  139. package/dist-esm/lib/primitives/Box.mjs +4 -1
  140. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  141. package/dist-esm/lib/primitives/Vec.mjs +0 -4
  142. package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
  143. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +53 -21
  144. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  145. package/dist-esm/lib/primitives/geometry/Group2d.mjs +8 -1
  146. package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
  147. package/dist-esm/lib/utils/EditorAtom.mjs +25 -0
  148. package/dist-esm/lib/utils/EditorAtom.mjs.map +7 -0
  149. package/dist-esm/lib/utils/dom.mjs.map +2 -2
  150. package/dist-esm/lib/utils/getPointerInfo.mjs +2 -3
  151. package/dist-esm/lib/utils/getPointerInfo.mjs.map +2 -2
  152. package/dist-esm/lib/utils/reparenting.mjs +3 -40
  153. package/dist-esm/lib/utils/reparenting.mjs.map +2 -2
  154. package/dist-esm/version.mjs +3 -3
  155. package/dist-esm/version.mjs.map +1 -1
  156. package/editor.css +308 -290
  157. package/package.json +14 -37
  158. package/src/index.ts +4 -9
  159. package/src/lib/TldrawEditor.tsx +14 -21
  160. package/src/lib/components/MenuClickCapture.tsx +0 -8
  161. package/src/lib/components/Shape.tsx +6 -12
  162. package/src/lib/components/default-components/DefaultCanvas.tsx +11 -22
  163. package/src/lib/components/default-components/DefaultCollaboratorHint.tsx +1 -1
  164. package/src/lib/components/default-components/DefaultErrorFallback.tsx +1 -1
  165. package/src/lib/components/default-components/DefaultScribble.tsx +1 -1
  166. package/src/lib/components/default-components/DefaultShapeIndicator.tsx +5 -1
  167. package/src/lib/config/TLUserPreferences.ts +8 -1
  168. package/src/lib/editor/Editor.test.ts +102 -11
  169. package/src/lib/editor/Editor.ts +157 -197
  170. package/src/lib/editor/derivations/notVisibleShapes.ts +6 -0
  171. package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +15 -14
  172. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +16 -15
  173. package/src/lib/editor/managers/FocusManager/FocusManager.test.ts +49 -48
  174. package/src/lib/editor/managers/FocusManager/FocusManager.ts +6 -2
  175. package/src/lib/editor/managers/FontManager/FontManager.test.ts +24 -23
  176. package/src/lib/editor/managers/HistoryManager/HistoryManager.test.ts +7 -6
  177. package/src/lib/editor/managers/ScribbleManager/ScribbleManager.test.ts +12 -11
  178. package/src/lib/editor/managers/SnapManager/SnapManager.test.ts +57 -50
  179. package/src/lib/editor/managers/TextManager/TextManager.test.ts +51 -26
  180. package/src/lib/editor/managers/TickManager/TickManager.test.ts +14 -13
  181. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +34 -26
  182. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +6 -1
  183. package/src/lib/editor/shapes/ShapeUtil.ts +46 -0
  184. package/src/lib/editor/types/misc-types.ts +54 -7
  185. package/src/lib/exports/getSvgJsx.test.ts +868 -0
  186. package/src/lib/exports/getSvgJsx.tsx +78 -21
  187. package/src/lib/hooks/useCanvasEvents.ts +62 -55
  188. package/src/lib/hooks/useDocumentEvents.ts +6 -6
  189. package/src/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.ts +1 -1
  190. package/src/lib/hooks/useGestureEvents.ts +2 -2
  191. package/src/lib/hooks/useHandleEvents.ts +6 -6
  192. package/src/lib/hooks/usePassThroughMouseOverEvents.ts +4 -1
  193. package/src/lib/hooks/usePassThroughWheelEvents.ts +6 -1
  194. package/src/lib/hooks/useSelectionEvents.ts +9 -14
  195. package/src/lib/hooks/useStateAttribute.ts +15 -0
  196. package/src/lib/license/LicenseManager.test.ts +724 -383
  197. package/src/lib/license/LicenseManager.ts +204 -58
  198. package/src/lib/license/LicenseProvider.tsx +74 -2
  199. package/src/lib/license/Watermark.test.tsx +2 -1
  200. package/src/lib/license/Watermark.tsx +152 -77
  201. package/src/lib/license/useLicenseManagerState.ts +2 -2
  202. package/src/lib/options.ts +8 -0
  203. package/src/lib/primitives/Box.test.ts +126 -0
  204. package/src/lib/primitives/Box.ts +10 -1
  205. package/src/lib/primitives/Vec.ts +0 -5
  206. package/src/lib/primitives/geometry/Geometry2d.test.ts +420 -0
  207. package/src/lib/primitives/geometry/Geometry2d.ts +78 -21
  208. package/src/lib/primitives/geometry/Group2d.ts +10 -1
  209. package/src/lib/test/InFrontOfTheCanvas.test.tsx +187 -0
  210. package/src/lib/utils/EditorAtom.ts +37 -0
  211. package/src/lib/utils/dom.test.ts +103 -0
  212. package/src/lib/utils/dom.ts +8 -1
  213. package/src/lib/utils/getPointerInfo.ts +3 -2
  214. package/src/lib/utils/reparenting.ts +3 -69
  215. package/src/lib/utils/sync/LocalIndexedDb.test.ts +2 -1
  216. package/src/lib/utils/sync/TLLocalSyncClient.test.ts +15 -15
  217. package/src/version.ts +3 -3
  218. package/dist-cjs/lib/utils/nearestMultiple.js.map +0 -7
  219. package/dist-esm/lib/utils/nearestMultiple.mjs +0 -14
  220. package/dist-esm/lib/utils/nearestMultiple.mjs.map +0 -7
  221. package/src/lib/utils/nearestMultiple.ts +0 -13
@@ -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'
@@ -176,6 +175,7 @@ import {
176
175
  RequiredKeys,
177
176
  TLCameraMoveOptions,
178
177
  TLCameraOptions,
178
+ TLGetShapeAtPointOptions,
179
179
  TLImageExportOptions,
180
180
  TLSvgExportOptions,
181
181
  TLUpdatePointerOptions,
@@ -243,16 +243,6 @@ export interface TLEditorOptions {
243
243
  options?: Partial<TldrawOptions>
244
244
  licenseKey?: string
245
245
  fontAssetUrls?: { [key: string]: string | undefined }
246
- /**
247
- * A predicate that should return true if the given shape should be hidden.
248
- *
249
- * @deprecated Use {@link Editor#getShapeVisibility} instead.
250
- *
251
- * @param shape - The shape to check.
252
- * @param editor - The editor instance.
253
- */
254
- isShapeHidden?(shape: TLShape, editor: Editor): boolean
255
-
256
246
  /**
257
247
  * Provides a way to hide shapes.
258
248
  *
@@ -308,21 +298,12 @@ export class Editor extends EventEmitter<TLEventMap> {
308
298
  autoFocus,
309
299
  inferDarkMode,
310
300
  options,
311
- // eslint-disable-next-line @typescript-eslint/no-deprecated
312
- isShapeHidden,
313
301
  getShapeVisibility,
314
302
  fontAssetUrls,
315
303
  }: TLEditorOptions) {
316
304
  super()
317
- assert(
318
- !(isShapeHidden && getShapeVisibility),
319
- 'Cannot use both isShapeHidden and getShapeVisibility'
320
- )
321
305
 
322
- this._getShapeVisibility = isShapeHidden
323
- ? // eslint-disable-next-line @typescript-eslint/no-deprecated
324
- (shape: TLShape, editor: Editor) => (isShapeHidden(shape, editor) ? 'hidden' : 'inherit')
325
- : getShapeVisibility
306
+ this._getShapeVisibility = getShapeVisibility
326
307
 
327
308
  this.options = { ...defaultTldrawOptions, ...options }
328
309
 
@@ -362,6 +343,8 @@ export class Editor extends EventEmitter<TLEventMap> {
362
343
  this.root = new NewRoot(this)
363
344
  this.root.children = {}
364
345
 
346
+ this.markEventAsHandled = this.markEventAsHandled.bind(this)
347
+
365
348
  const allShapeUtils = checkShapesAndAddCore(shapeUtils)
366
349
 
367
350
  const _shapeUtils = {} as Record<string, ShapeUtil<any>>
@@ -906,14 +889,6 @@ export class Editor extends EventEmitter<TLEventMap> {
906
889
  */
907
890
  readonly fonts: FontManager
908
891
 
909
- /**
910
- * A manager for the editor's environment.
911
- *
912
- * @deprecated This is deprecated and will be removed in a future version. Use the `tlenv` global export instead.
913
- * @public
914
- */
915
- readonly environment = tlenv
916
-
917
892
  /**
918
893
  * A manager for the editor's scribbles.
919
894
  *
@@ -1118,35 +1093,6 @@ export class Editor extends EventEmitter<TLEventMap> {
1118
1093
  return this.history.getNumRedos() > 0
1119
1094
  }
1120
1095
 
1121
- /**
1122
- * Create a new "mark", or stopping point, in the undo redo history. Creating a mark will clear
1123
- * any redos.
1124
- *
1125
- * @example
1126
- * ```ts
1127
- * editor.mark()
1128
- * editor.mark('flip shapes')
1129
- * ```
1130
- *
1131
- * @param markId - The mark's id, usually the reason for adding the mark.
1132
- *
1133
- * @public
1134
- * @deprecated use {@link Editor.markHistoryStoppingPoint} instead
1135
- */
1136
- mark(markId?: string): this {
1137
- if (typeof markId === 'string') {
1138
- console.warn(
1139
- `[tldraw] \`editor.history.mark("${markId}")\` is deprecated. Please use \`const myMarkId = editor.markHistoryStoppingPoint()\` instead.`
1140
- )
1141
- } else {
1142
- console.warn(
1143
- '[tldraw] `editor.mark()` is deprecated. Use `editor.markHistoryStoppingPoint()` instead.'
1144
- )
1145
- }
1146
- this.history._mark(markId ?? uniqueId())
1147
- return this
1148
- }
1149
-
1150
1096
  /**
1151
1097
  * Create a new "mark", or stopping point, in the undo redo history. Creating a mark will clear
1152
1098
  * any redos. You typically want to do this just before a user interaction begins or is handled.
@@ -1271,13 +1217,6 @@ export class Editor extends EventEmitter<TLEventMap> {
1271
1217
  return this
1272
1218
  }
1273
1219
 
1274
- /**
1275
- * @deprecated Use `Editor.run` instead.
1276
- */
1277
- batch(fn: () => void, opts?: TLEditorRunOptions): this {
1278
- return this.run(fn, opts)
1279
- }
1280
-
1281
1220
  /* --------------------- Errors --------------------- */
1282
1221
 
1283
1222
  /** @internal */
@@ -1579,54 +1518,6 @@ export class Editor extends EventEmitter<TLEventMap> {
1579
1518
 
1580
1519
  menus = tlmenus.forContext(this.contextId)
1581
1520
 
1582
- /**
1583
- * @deprecated Use `editor.menus.getOpenMenus` instead.
1584
- *
1585
- * @public
1586
- */
1587
- @computed getOpenMenus(): string[] {
1588
- return this.menus.getOpenMenus()
1589
- }
1590
-
1591
- /**
1592
- * @deprecated Use `editor.menus.addOpenMenu` instead.
1593
- *
1594
- * @public
1595
- */
1596
- addOpenMenu(id: string): this {
1597
- this.menus.addOpenMenu(id)
1598
- return this
1599
- }
1600
-
1601
- /**
1602
- * @deprecated Use `editor.menus.deleteOpenMenu` instead.
1603
- *
1604
- * @public
1605
- */
1606
- deleteOpenMenu(id: string): this {
1607
- this.menus.deleteOpenMenu(id)
1608
- return this
1609
- }
1610
-
1611
- /**
1612
- * @deprecated Use `editor.menus.clearOpenMenus` instead.
1613
- *
1614
- * @public
1615
- */
1616
- clearOpenMenus(): this {
1617
- this.menus.clearOpenMenus()
1618
- return this
1619
- }
1620
-
1621
- /**
1622
- * @deprecated Use `editor.menus.hasAnyOpenMenus` instead.
1623
- *
1624
- * @public
1625
- */
1626
- @computed getIsMenuOpen(): boolean {
1627
- return this.menus.hasAnyOpenMenus()
1628
- }
1629
-
1630
1521
  /* --------------------- Cursor --------------------- */
1631
1522
 
1632
1523
  /**
@@ -4791,8 +4682,10 @@ export class Editor extends EventEmitter<TLEventMap> {
4791
4682
  return this.store.createComputedCache<Box, TLShape>('pageBoundsCache', (shape) => {
4792
4683
  const pageTransform = this.getShapePageTransform(shape)
4793
4684
  if (!pageTransform) return undefined
4794
- const geometry = this.getShapeGeometry(shape)
4795
- return Box.FromPoints(pageTransform.applyToPoints(geometry.vertices))
4685
+
4686
+ return Box.FromPoints(
4687
+ pageTransform.applyToPoints(this.getShapeGeometry(shape).boundsVertices)
4688
+ )
4796
4689
  })
4797
4690
  }
4798
4691
 
@@ -4859,27 +4752,25 @@ export class Editor extends EventEmitter<TLEventMap> {
4859
4752
  return this.store.createComputedCache('pageMaskCache', (shape) => {
4860
4753
  if (isPageId(shape.parentId)) return undefined
4861
4754
 
4862
- const frameAncestors = this.getShapeAncestors(shape.id).filter((shape) =>
4863
- this.isShapeOfType<TLFrameShape>(shape, 'frame')
4864
- )
4865
-
4866
- if (frameAncestors.length === 0) return undefined
4867
-
4868
- const pageMask = frameAncestors
4869
- .map<Vec[] | undefined>((s) => {
4870
- // Apply the frame transform to the frame outline to get the frame outline in the current page space
4871
- const geometry = this.getShapeGeometry(s.id)
4872
- const pageTransform = this.getShapePageTransform(s.id)
4873
- return pageTransform.applyToPoints(geometry.vertices)
4874
- })
4875
- .reduce((acc, b) => {
4876
- if (!(b && acc)) return undefined
4877
- const intersection = intersectPolygonPolygon(acc, b)
4878
- if (intersection) {
4879
- return intersection.map(Vec.Cast)
4880
- }
4881
- return []
4882
- })
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
+ })
4883
4774
 
4884
4775
  return pageMask
4885
4776
  })
@@ -5154,20 +5045,7 @@ export class Editor extends EventEmitter<TLEventMap> {
5154
5045
  *
5155
5046
  * @returns The shape at the given point, or undefined if there is no shape at the point.
5156
5047
  */
5157
- getShapeAtPoint(
5158
- point: VecLike,
5159
- opts = {} as {
5160
- renderingOnly?: boolean
5161
- margin?: number
5162
- hitInside?: boolean
5163
- hitLocked?: boolean
5164
- // TODO: we probably need to rename this, we don't quite _always_
5165
- // respect this esp. in the part below that does "Check labels first"
5166
- hitLabels?: boolean
5167
- hitFrameInside?: boolean
5168
- filter?(shape: TLShape): boolean
5169
- }
5170
- ): TLShape | undefined {
5048
+ getShapeAtPoint(point: VecLike, opts: TLGetShapeAtPointOptions = {}): TLShape | undefined {
5171
5049
  const zoomLevel = this.getZoomLevel()
5172
5050
  const viewportPageBounds = this.getViewportPageBounds()
5173
5051
  const {
@@ -5179,6 +5057,8 @@ export class Editor extends EventEmitter<TLEventMap> {
5179
5057
  hitFrameInside = false,
5180
5058
  } = opts
5181
5059
 
5060
+ const [innerMargin, outerMargin] = Array.isArray(margin) ? margin : [margin, margin]
5061
+
5182
5062
  let inHollowSmallestArea = Infinity
5183
5063
  let inHollowSmallestAreaHit: TLShape | null = null
5184
5064
 
@@ -5198,7 +5078,7 @@ export class Editor extends EventEmitter<TLEventMap> {
5198
5078
  return false
5199
5079
  const pageMask = this.getShapeMask(shape)
5200
5080
  if (pageMask && !pointInPolygon(point, pageMask)) return false
5201
- if (filter) return filter(shape)
5081
+ if (filter && !filter(shape)) return false
5202
5082
  return true
5203
5083
  })
5204
5084
 
@@ -5224,13 +5104,18 @@ export class Editor extends EventEmitter<TLEventMap> {
5224
5104
  }
5225
5105
  }
5226
5106
 
5227
- if (this.isShapeOfType(shape, 'frame')) {
5107
+ if (this.isShapeOfType<TLFrameShape>(shape, 'frame')) {
5228
5108
  // On the rare case that we've hit a frame (not its label), test again hitInside to be forced true;
5229
5109
  // this prevents clicks from passing through the body of a frame to shapes behind it.
5230
5110
 
5231
5111
  // If the hit is within the frame's outer margin, then select the frame
5232
- const distance = geometry.distanceToPoint(pointInShapeSpace, hitInside)
5233
- if (Math.abs(distance) <= margin) {
5112
+ const distance = geometry.distanceToPoint(pointInShapeSpace, hitFrameInside)
5113
+ if (
5114
+ hitFrameInside
5115
+ ? (distance > 0 && distance <= outerMargin) ||
5116
+ (distance <= 0 && distance > -innerMargin)
5117
+ : distance > 0 && distance <= outerMargin
5118
+ ) {
5234
5119
  return inMarginClosestToEdgeHit || shape
5235
5120
  }
5236
5121
 
@@ -5269,11 +5154,11 @@ export class Editor extends EventEmitter<TLEventMap> {
5269
5154
  // If the margin is zero and the geometry has a very small width or height,
5270
5155
  // then check the actual distance. This is to prevent a bug where straight
5271
5156
  // lines would never pass the broad phase (point-in-bounds) check.
5272
- if (margin === 0 && (geometry.bounds.w < 1 || geometry.bounds.h < 1)) {
5157
+ if (outerMargin === 0 && (geometry.bounds.w < 1 || geometry.bounds.h < 1)) {
5273
5158
  distance = geometry.distanceToPoint(pointInShapeSpace, hitInside)
5274
5159
  } else {
5275
5160
  // Broad phase
5276
- if (geometry.bounds.containsPoint(pointInShapeSpace, margin)) {
5161
+ if (geometry.bounds.containsPoint(pointInShapeSpace, outerMargin)) {
5277
5162
  // Narrow phase (actual distance)
5278
5163
  distance = geometry.distanceToPoint(pointInShapeSpace, hitInside)
5279
5164
  } else {
@@ -5288,7 +5173,8 @@ export class Editor extends EventEmitter<TLEventMap> {
5288
5173
  // the shape or negative if inside of the shape. If the distance
5289
5174
  // is greater than the margin, then it's a miss. Otherwise...
5290
5175
 
5291
- if (distance <= margin) {
5176
+ // Are we close to the shape's edge?
5177
+ if (distance <= outerMargin || (hitInside && distance <= 0 && distance > -innerMargin)) {
5292
5178
  if (geometry.isFilled || (isGroup && geometry.children[0].isFilled)) {
5293
5179
  // If the shape is filled, then it's a hit. Remember, we're
5294
5180
  // starting from the TOP-MOST shape in z-index order, so any
@@ -5298,11 +5184,21 @@ export class Editor extends EventEmitter<TLEventMap> {
5298
5184
  // If the shape is bigger than the viewport, then skip it.
5299
5185
  if (this.getShapePageBounds(shape)!.contains(viewportPageBounds)) continue
5300
5186
 
5301
- // For hollow shapes...
5302
- if (Math.abs(distance) < margin) {
5303
- // We want to preference shapes where we're inside of the
5304
- // shape margin; and we would want to hit the shape with the
5305
- // edge closest to the point.
5187
+ // If we're close to the edge of the shape, and if it's the closest edge among
5188
+ // all the edges that we've gotten close to so far, then we will want to hit the
5189
+ // shape unless we hit something else or closer in later iterations.
5190
+ if (
5191
+ hitInside
5192
+ ? // On hitInside, the distance will be negative for hits inside
5193
+ // If the distance is positive, check against the outer margin
5194
+ (distance > 0 && distance <= outerMargin) ||
5195
+ // If the distance is negative, check against the inner margin
5196
+ (distance <= 0 && distance > -innerMargin)
5197
+ : // If hitInside is false, then sadly _we do not know_ whether the
5198
+ // point is inside or outside of the shape, so we check against
5199
+ // the max of the two margins
5200
+ Math.abs(distance) <= Math.max(innerMargin, outerMargin)
5201
+ ) {
5306
5202
  if (Math.abs(distance) < inMarginClosestToEdgeDistance) {
5307
5203
  inMarginClosestToEdgeDistance = Math.abs(distance)
5308
5204
  inMarginClosestToEdgeHit = shape
@@ -5324,6 +5220,7 @@ export class Editor extends EventEmitter<TLEventMap> {
5324
5220
  } else {
5325
5221
  // For open shapes (e.g. lines or draw shapes) always use the margin.
5326
5222
  // If the distance is less than the margin, return the shape as the hit.
5223
+ // Use the editor's configurable hit test margin.
5327
5224
  if (distance < this.options.hitTestMargin / zoomLevel) {
5328
5225
  return shape
5329
5226
  }
@@ -5834,11 +5731,6 @@ export class Editor extends EventEmitter<TLEventMap> {
5834
5731
  return shapeIds
5835
5732
  }
5836
5733
 
5837
- /** @deprecated Use {@link Editor.getDraggingOverShape} instead */
5838
- getDroppingOverShape(point: Vec, droppingShapes: TLShape[]): TLShape | undefined {
5839
- return this.getDraggingOverShape(point, droppingShapes)
5840
- }
5841
-
5842
5734
  /**
5843
5735
  * Get the shape that some shapes should be dropped on at a given point.
5844
5736
  *
@@ -6326,7 +6218,17 @@ export class Editor extends EventEmitter<TLEventMap> {
6326
6218
 
6327
6219
  this.createShapes(shapesToCreate)
6328
6220
  this.createBindings(bindingsToCreate)
6329
- 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
+ )
6330
6232
 
6331
6233
  if (offset !== undefined) {
6332
6234
  // If we've offset the duplicated shapes, check to see whether their new bounds is entirely
@@ -7380,7 +7282,6 @@ export class Editor extends EventEmitter<TLEventMap> {
7380
7282
  if (
7381
7283
  !this.getShapeUtil(shape).canBeLaidOut?.(shape, {
7382
7284
  type: 'stretch',
7383
- shapes: shapesToStretchFirstPass,
7384
7285
  })
7385
7286
  ) {
7386
7287
  continue
@@ -7851,25 +7752,32 @@ export class Editor extends EventEmitter<TLEventMap> {
7851
7752
  ) {
7852
7753
  let parentId: TLParentId = this.getFocusedGroupId()
7853
7754
 
7854
- for (let i = currentPageShapesSorted.length - 1; i >= 0; i--) {
7855
- const parent = currentPageShapesSorted[i]
7856
- const util = this.getShapeUtil(parent)
7857
- if (
7858
- util.canReceiveNewChildrenOfType(parent, partial.type) &&
7859
- !this.isShapeHidden(parent) &&
7860
- this.isPointInShape(
7861
- parent,
7862
- // If no parent is provided, then we can treat the
7863
- // shape's provided x/y as being in the page's space.
7864
- { x: partial.x ?? 0, y: partial.y ?? 0 },
7865
- {
7866
- margin: 0,
7867
- hitInside: true,
7868
- }
7869
- )
7870
- ) {
7871
- parentId = parent.id
7872
- break
7755
+ const isPositioned = partial.x !== undefined && partial.y !== undefined
7756
+
7757
+ // If the shape has been explicitly positioned, we'll try to find a parent at
7758
+ // that position. If not, we'll assume the user isn't deliberately placing the
7759
+ // shape and the positioning will be handled later by another system.
7760
+ if (isPositioned) {
7761
+ for (let i = currentPageShapesSorted.length - 1; i >= 0; i--) {
7762
+ const parent = currentPageShapesSorted[i]
7763
+ const util = this.getShapeUtil(parent)
7764
+ if (
7765
+ util.canReceiveNewChildrenOfType(parent, partial.type) &&
7766
+ !this.isShapeHidden(parent) &&
7767
+ this.isPointInShape(
7768
+ parent,
7769
+ // If no parent is provided, then we can treat the
7770
+ // shape's provided x/y as being in the page's space.
7771
+ { x: partial.x ?? 0, y: partial.y ?? 0 },
7772
+ {
7773
+ margin: 0,
7774
+ hitInside: true,
7775
+ }
7776
+ )
7777
+ ) {
7778
+ parentId = parent.id
7779
+ break
7780
+ }
7873
7781
  }
7874
7782
  }
7875
7783
 
@@ -8927,8 +8835,13 @@ export class Editor extends EventEmitter<TLEventMap> {
8927
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.
8928
8836
  *
8929
8837
  * @param info - Info about the external content.
8838
+ * @param opts - Options for handling external content, including force flag to bypass readonly checks.
8930
8839
  */
8931
- 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
8932
8845
  return this.externalContentHandlers[info.type]?.(info as any)
8933
8846
  }
8934
8847
 
@@ -8936,8 +8849,13 @@ export class Editor extends EventEmitter<TLEventMap> {
8936
8849
  * Handle replacing external content.
8937
8850
  *
8938
8851
  * @param info - Info about the external content.
8852
+ * @param opts - Options for handling external content, including force flag to bypass readonly checks.
8939
8853
  */
8940
- 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
8941
8859
  return this.externalContentHandlers[info.type]?.(info as any)
8942
8860
  }
8943
8861
 
@@ -9438,13 +9356,6 @@ export class Editor extends EventEmitter<TLEventMap> {
9438
9356
  }
9439
9357
  }
9440
9358
 
9441
- /** @deprecated Use {@link Editor.getSvgString} or {@link Editor.getSvgElement} instead. */
9442
- async getSvg(shapes: TLShapeId[] | TLShape[], opts: TLSvgExportOptions = {}) {
9443
- const result = await this.getSvgElement(shapes, opts)
9444
- if (!result) return undefined
9445
- return result.svg
9446
- }
9447
-
9448
9359
  /**
9449
9360
  * Get an exported image of the given shapes.
9450
9361
  *
@@ -9496,6 +9407,24 @@ export class Editor extends EventEmitter<TLEventMap> {
9496
9407
  }
9497
9408
  }
9498
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
+
9499
9428
  /* --------------------- Events --------------------- */
9500
9429
 
9501
9430
  /**
@@ -10170,6 +10099,37 @@ export class Editor extends EventEmitter<TLEventMap> {
10170
10099
  /** @internal */
10171
10100
  private performanceTrackerTimeout = -1 as any
10172
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
+
10173
10133
  /**
10174
10134
  * Dispatch an event to the editor.
10175
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')