@tldraw/editor 4.3.0-next.f4772c19540d → 4.4.0-canary.1e3b436e33e4

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 (206) hide show
  1. package/README.md +1 -1
  2. package/dist-cjs/index.d.ts +503 -155
  3. package/dist-cjs/index.js +8 -1
  4. package/dist-cjs/index.js.map +2 -2
  5. package/dist-cjs/lib/components/ErrorBoundary.js.map +1 -1
  6. package/dist-cjs/lib/components/GeometryDebuggingView.js +1 -17
  7. package/dist-cjs/lib/components/GeometryDebuggingView.js.map +2 -2
  8. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +4 -5
  9. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  10. package/dist-cjs/lib/constants.js +1 -3
  11. package/dist-cjs/lib/constants.js.map +2 -2
  12. package/dist-cjs/lib/editor/Editor.js +346 -291
  13. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  14. package/dist-cjs/lib/editor/bindings/BindingUtil.js.map +2 -2
  15. package/dist-cjs/lib/editor/derivations/bindingsIndex.js.map +2 -2
  16. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +16 -23
  17. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +3 -3
  18. package/dist-cjs/lib/editor/derivations/parentsToChildren.js +12 -3
  19. package/dist-cjs/lib/editor/derivations/parentsToChildren.js.map +2 -2
  20. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js +1 -1
  21. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +2 -2
  22. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js +5 -6
  23. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js.map +2 -2
  24. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js +591 -0
  25. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js.map +7 -0
  26. package/dist-cjs/lib/editor/managers/SnapManager/SnapManager.js +1 -1
  27. package/dist-cjs/lib/editor/managers/SnapManager/SnapManager.js.map +2 -2
  28. package/dist-cjs/lib/editor/managers/SpatialIndexManager/RBushIndex.js +144 -0
  29. package/dist-cjs/lib/editor/managers/SpatialIndexManager/RBushIndex.js.map +7 -0
  30. package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js +181 -0
  31. package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js.map +7 -0
  32. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js +1 -22
  33. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +2 -2
  34. package/dist-cjs/lib/editor/shapes/BaseBoxShapeUtil.js.map +1 -1
  35. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +31 -23
  36. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  37. package/dist-cjs/lib/editor/shapes/group/DashedOutlineBox.js +1 -1
  38. package/dist-cjs/lib/editor/shapes/group/DashedOutlineBox.js.map +2 -2
  39. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +2 -2
  40. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool.js.map +2 -2
  41. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +3 -3
  42. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +2 -2
  43. package/dist-cjs/lib/editor/types/emit-types.js.map +1 -1
  44. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  45. package/dist-cjs/lib/exports/parseCss.js +1 -1
  46. package/dist-cjs/lib/exports/parseCss.js.map +2 -2
  47. package/dist-cjs/lib/globals/environment.js +45 -9
  48. package/dist-cjs/lib/globals/environment.js.map +2 -2
  49. package/dist-cjs/lib/globals/menus.js +1 -1
  50. package/dist-cjs/lib/globals/menus.js.map +2 -2
  51. package/dist-cjs/lib/hooks/useCoarsePointer.js +14 -29
  52. package/dist-cjs/lib/hooks/useCoarsePointer.js.map +2 -2
  53. package/dist-cjs/lib/hooks/useEvent.js +1 -1
  54. package/dist-cjs/lib/hooks/useEvent.js.map +2 -2
  55. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js.map +2 -2
  56. package/dist-cjs/lib/hooks/useGestureEvents.js +1 -1
  57. package/dist-cjs/lib/hooks/useGestureEvents.js.map +2 -2
  58. package/dist-cjs/lib/hooks/usePassThroughMouseOverEvents.js.map +2 -2
  59. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +2 -2
  60. package/dist-cjs/lib/hooks/useScreenBounds.js.map +2 -2
  61. package/dist-cjs/lib/hooks/useStateAttribute.js +4 -1
  62. package/dist-cjs/lib/hooks/useStateAttribute.js.map +2 -2
  63. package/dist-cjs/lib/hooks/useTransform.js.map +1 -1
  64. package/dist-cjs/lib/hooks/useZoomCss.js +4 -8
  65. package/dist-cjs/lib/hooks/useZoomCss.js.map +2 -2
  66. package/dist-cjs/lib/options.js +6 -1
  67. package/dist-cjs/lib/options.js.map +2 -2
  68. package/dist-cjs/lib/primitives/Box.js +3 -0
  69. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  70. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +1 -0
  71. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  72. package/dist-cjs/lib/utils/reparenting.js.map +2 -2
  73. package/dist-cjs/lib/utils/rotation.js +1 -1
  74. package/dist-cjs/lib/utils/rotation.js.map +2 -2
  75. package/dist-cjs/version.js +3 -3
  76. package/dist-cjs/version.js.map +1 -1
  77. package/dist-esm/index.d.mts +503 -155
  78. package/dist-esm/index.mjs +9 -2
  79. package/dist-esm/index.mjs.map +2 -2
  80. package/dist-esm/lib/components/ErrorBoundary.mjs.map +1 -1
  81. package/dist-esm/lib/components/GeometryDebuggingView.mjs +1 -17
  82. package/dist-esm/lib/components/GeometryDebuggingView.mjs.map +2 -2
  83. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +4 -5
  84. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  85. package/dist-esm/lib/constants.mjs +1 -3
  86. package/dist-esm/lib/constants.mjs.map +2 -2
  87. package/dist-esm/lib/editor/Editor.mjs +347 -294
  88. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  89. package/dist-esm/lib/editor/bindings/BindingUtil.mjs.map +2 -2
  90. package/dist-esm/lib/editor/derivations/bindingsIndex.mjs.map +2 -2
  91. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +16 -23
  92. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +3 -3
  93. package/dist-esm/lib/editor/derivations/parentsToChildren.mjs +13 -4
  94. package/dist-esm/lib/editor/derivations/parentsToChildren.mjs.map +2 -2
  95. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs +1 -1
  96. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +2 -2
  97. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs +5 -6
  98. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs.map +2 -2
  99. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs +573 -0
  100. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs.map +7 -0
  101. package/dist-esm/lib/editor/managers/SnapManager/SnapManager.mjs +1 -1
  102. package/dist-esm/lib/editor/managers/SnapManager/SnapManager.mjs.map +2 -2
  103. package/dist-esm/lib/editor/managers/SpatialIndexManager/RBushIndex.mjs +114 -0
  104. package/dist-esm/lib/editor/managers/SpatialIndexManager/RBushIndex.mjs.map +7 -0
  105. package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs +161 -0
  106. package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs.map +7 -0
  107. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs +1 -22
  108. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +2 -2
  109. package/dist-esm/lib/editor/shapes/BaseBoxShapeUtil.mjs.map +1 -1
  110. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +31 -23
  111. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  112. package/dist-esm/lib/editor/shapes/group/DashedOutlineBox.mjs +1 -1
  113. package/dist-esm/lib/editor/shapes/group/DashedOutlineBox.mjs.map +2 -2
  114. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +2 -2
  115. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool.mjs.map +2 -2
  116. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +3 -3
  117. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +2 -2
  118. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  119. package/dist-esm/lib/exports/parseCss.mjs +1 -1
  120. package/dist-esm/lib/exports/parseCss.mjs.map +2 -2
  121. package/dist-esm/lib/globals/environment.mjs +45 -9
  122. package/dist-esm/lib/globals/environment.mjs.map +2 -2
  123. package/dist-esm/lib/globals/menus.mjs +1 -1
  124. package/dist-esm/lib/globals/menus.mjs.map +2 -2
  125. package/dist-esm/lib/hooks/useCoarsePointer.mjs +15 -30
  126. package/dist-esm/lib/hooks/useCoarsePointer.mjs.map +2 -2
  127. package/dist-esm/lib/hooks/useEvent.mjs +1 -1
  128. package/dist-esm/lib/hooks/useEvent.mjs.map +2 -2
  129. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs.map +2 -2
  130. package/dist-esm/lib/hooks/useGestureEvents.mjs +1 -1
  131. package/dist-esm/lib/hooks/useGestureEvents.mjs.map +2 -2
  132. package/dist-esm/lib/hooks/usePassThroughMouseOverEvents.mjs.map +2 -2
  133. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +2 -2
  134. package/dist-esm/lib/hooks/useScreenBounds.mjs.map +2 -2
  135. package/dist-esm/lib/hooks/useStateAttribute.mjs +4 -1
  136. package/dist-esm/lib/hooks/useStateAttribute.mjs.map +2 -2
  137. package/dist-esm/lib/hooks/useTransform.mjs.map +1 -1
  138. package/dist-esm/lib/hooks/useZoomCss.mjs +4 -8
  139. package/dist-esm/lib/hooks/useZoomCss.mjs.map +2 -2
  140. package/dist-esm/lib/options.mjs +6 -1
  141. package/dist-esm/lib/options.mjs.map +2 -2
  142. package/dist-esm/lib/primitives/Box.mjs +3 -0
  143. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  144. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +1 -0
  145. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  146. package/dist-esm/lib/utils/reparenting.mjs.map +2 -2
  147. package/dist-esm/lib/utils/rotation.mjs +1 -1
  148. package/dist-esm/lib/utils/rotation.mjs.map +2 -2
  149. package/dist-esm/version.mjs +3 -3
  150. package/dist-esm/version.mjs.map +1 -1
  151. package/editor.css +14 -12
  152. package/package.json +21 -17
  153. package/src/index.ts +5 -1
  154. package/src/lib/components/ErrorBoundary.tsx +1 -1
  155. package/src/lib/components/GeometryDebuggingView.tsx +1 -19
  156. package/src/lib/components/default-components/DefaultCanvas.tsx +5 -8
  157. package/src/lib/config/TLUserPreferences.test.ts +40 -0
  158. package/src/lib/constants.ts +0 -2
  159. package/src/lib/editor/Editor.test.ts +150 -10
  160. package/src/lib/editor/Editor.ts +533 -384
  161. package/src/lib/editor/bindings/BindingUtil.ts +15 -9
  162. package/src/lib/editor/derivations/bindingsIndex.ts +2 -2
  163. package/src/lib/editor/derivations/notVisibleShapes.ts +21 -33
  164. package/src/lib/editor/derivations/parentsToChildren.ts +18 -7
  165. package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +17 -31
  166. package/src/lib/editor/managers/ClickManager/ClickManager.ts +1 -1
  167. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +129 -79
  168. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.ts +10 -6
  169. package/src/lib/editor/managers/FontManager/FontManager.test.ts +14 -4
  170. package/src/lib/editor/managers/InputsManager/InputsManager.ts +566 -0
  171. package/src/lib/editor/managers/ScribbleManager/ScribbleManager.test.ts +0 -4
  172. package/src/lib/editor/managers/SnapManager/SnapManager.test.ts +12 -0
  173. package/src/lib/editor/managers/SnapManager/SnapManager.ts +4 -4
  174. package/src/lib/editor/managers/SpatialIndexManager/RBushIndex.ts +144 -0
  175. package/src/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.ts +215 -0
  176. package/src/lib/editor/managers/TickManager/TickManager.test.ts +40 -107
  177. package/src/lib/editor/managers/TickManager/TickManager.ts +2 -32
  178. package/src/lib/editor/shapes/BaseBoxShapeUtil.tsx +2 -2
  179. package/src/lib/editor/shapes/ShapeUtil.ts +72 -32
  180. package/src/lib/editor/shapes/group/DashedOutlineBox.tsx +1 -1
  181. package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +1 -3
  182. package/src/lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool.ts +2 -1
  183. package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +6 -6
  184. package/src/lib/editor/types/emit-types.ts +3 -1
  185. package/src/lib/exports/getSvgJsx.test.ts +10 -19
  186. package/src/lib/exports/getSvgJsx.tsx +2 -5
  187. package/src/lib/exports/parseCss.test.ts +1 -0
  188. package/src/lib/exports/parseCss.ts +1 -1
  189. package/src/lib/globals/environment.ts +65 -10
  190. package/src/lib/globals/menus.ts +1 -1
  191. package/src/lib/hooks/useCoarsePointer.ts +16 -59
  192. package/src/lib/hooks/useEvent.tsx +1 -1
  193. package/src/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.ts +1 -1
  194. package/src/lib/hooks/useGestureEvents.ts +2 -2
  195. package/src/lib/hooks/usePassThroughMouseOverEvents.ts +1 -1
  196. package/src/lib/hooks/usePassThroughWheelEvents.ts +1 -1
  197. package/src/lib/hooks/useScreenBounds.ts +1 -1
  198. package/src/lib/hooks/useStateAttribute.ts +4 -1
  199. package/src/lib/hooks/useTransform.ts +1 -1
  200. package/src/lib/hooks/useZoomCss.ts +3 -8
  201. package/src/lib/options.ts +32 -0
  202. package/src/lib/primitives/Box.ts +9 -0
  203. package/src/lib/primitives/geometry/Geometry2d.ts +1 -0
  204. package/src/lib/utils/reparenting.ts +5 -5
  205. package/src/lib/utils/rotation.ts +1 -1
  206. package/src/version.ts +3 -3
@@ -26,10 +26,7 @@ import { TLClickEventInfo } from '../types/event-types'
26
26
  import { TLResizeHandle } from '../types/selection-types'
27
27
 
28
28
  /** @public */
29
- export interface TLShapeUtilConstructor<
30
- T extends TLUnknownShape,
31
- U extends ShapeUtil<T> = ShapeUtil<T>,
32
- > {
29
+ export interface TLShapeUtilConstructor<T extends TLShape, U extends ShapeUtil<T> = ShapeUtil<T>> {
33
30
  new (editor: Editor): U
34
31
  type: T['type']
35
32
  props?: RecordProps<T>
@@ -42,11 +39,11 @@ export interface TLShapeUtilConstructor<
42
39
  *
43
40
  * @public
44
41
  */
45
- export interface TLShapeUtilCanBindOpts<Shape extends TLUnknownShape = TLUnknownShape> {
42
+ export interface TLShapeUtilCanBindOpts<Shape extends TLShape = TLShape> {
46
43
  /** The type of shape referenced by the `fromId` of the binding. */
47
- fromShapeType: string
44
+ fromShapeType: TLShape['type']
48
45
  /** The type of shape referenced by the `toId` of the binding. */
49
- toShapeType: string
46
+ toShapeType: TLShape['type']
50
47
  /** The type of binding. */
51
48
  bindingType: string
52
49
  }
@@ -79,7 +76,7 @@ export interface TLShapeUtilCanvasSvgDef {
79
76
  }
80
77
 
81
78
  /** @public */
82
- export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
79
+ export abstract class ShapeUtil<Shape extends TLShape = TLShape> {
83
80
  /** Configure this shape utils {@link ShapeUtil.options | `options`}. */
84
81
  static configure<T extends TLShapeUtilConstructor<any, any>>(
85
82
  this: T,
@@ -193,7 +190,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
193
190
  * @param shape - The shape.
194
191
  * @public
195
192
  */
196
- canSnap(_shape: Shape): boolean {
193
+ canSnap(shape: Shape): boolean {
197
194
  return true
198
195
  }
199
196
 
@@ -203,7 +200,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
203
200
  * @param shape - The shape.
204
201
  * @public
205
202
  */
206
- canTabTo(_shape: Shape): boolean {
203
+ canTabTo(shape: Shape): boolean {
207
204
  return true
208
205
  }
209
206
 
@@ -212,7 +209,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
212
209
  *
213
210
  * @public
214
211
  */
215
- canScroll(_shape: Shape): boolean {
212
+ canScroll(shape: Shape): boolean {
216
213
  return false
217
214
  }
218
215
 
@@ -230,7 +227,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
230
227
  *
231
228
  * @public
232
229
  */
233
- canEdit(_shape: Shape): boolean {
230
+ canEdit(shape: Shape, info: TLEditStartInfo): boolean {
234
231
  return false
235
232
  }
236
233
 
@@ -239,7 +236,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
239
236
  *
240
237
  * @public
241
238
  */
242
- canResize(_shape: Shape): boolean {
239
+ canResize(shape: Shape): boolean {
243
240
  return true
244
241
  }
245
242
 
@@ -248,7 +245,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
248
245
  *
249
246
  * @public
250
247
  */
251
- canResizeChildren(_shape: Shape): boolean {
248
+ canResizeChildren(shape: Shape): boolean {
252
249
  return true
253
250
  }
254
251
 
@@ -257,7 +254,16 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
257
254
  *
258
255
  * @public
259
256
  */
260
- canEditInReadonly(_shape: Shape): boolean {
257
+ canEditInReadonly(shape: Shape): boolean {
258
+ return false
259
+ }
260
+
261
+ /**
262
+ * Whether the shape can be edited while locked or while an ancestor is locked.
263
+ *
264
+ * @public
265
+ */
266
+ canEditWhileLocked(shape: Shape): boolean {
261
267
  return false
262
268
  }
263
269
 
@@ -266,7 +272,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
266
272
  *
267
273
  * @public
268
274
  */
269
- canCrop(_shape: Shape): boolean {
275
+ canCrop(shape: Shape): boolean {
270
276
  return false
271
277
  }
272
278
 
@@ -279,7 +285,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
279
285
  *
280
286
  * @public
281
287
  */
282
- canBeLaidOut(_shape: Shape, _info: TLShapeUtilCanBeLaidOutOpts): boolean {
288
+ canBeLaidOut(shape: Shape, info: TLShapeUtilCanBeLaidOutOpts): boolean {
283
289
  return true
284
290
  }
285
291
 
@@ -290,7 +296,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
290
296
  *
291
297
  * @param shape - The shape.
292
298
  */
293
- canCull(_shape: Shape): boolean {
299
+ canCull(shape: Shape): boolean {
294
300
  return true
295
301
  }
296
302
 
@@ -303,13 +309,33 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
303
309
  *
304
310
  * @internal
305
311
  */
306
- providesBackgroundForChildren(_shape: Shape): boolean {
312
+ providesBackgroundForChildren(shape: Shape): boolean {
307
313
  return false
308
314
  }
309
315
 
310
316
  /**
311
317
  * Get the clip path to apply to this shape's children.
312
318
  *
319
+ * The returned points should define the **inner** clip boundary - the area where
320
+ * children will be visible. If your shape has a stroke, you should inset the clip
321
+ * path by half the stroke width so children are clipped to the inner edge of the
322
+ * stroke rather than its center line.
323
+ *
324
+ * @example
325
+ * ```ts
326
+ * override getClipPath(shape: MyShape): Vec[] | undefined {
327
+ * const strokeWidth = 2
328
+ * const inset = strokeWidth / 2
329
+ * // Return points inset by half the stroke width
330
+ * return [
331
+ * new Vec(inset, inset),
332
+ * new Vec(shape.props.w - inset, inset),
333
+ * new Vec(shape.props.w - inset, shape.props.h - inset),
334
+ * new Vec(inset, shape.props.h - inset),
335
+ * ]
336
+ * }
337
+ * ```
338
+ *
313
339
  * @param shape - The shape to get the clip path for
314
340
  * @returns Array of points defining the clipping polygon in local coordinates, or undefined if no clipping
315
341
  * @public
@@ -336,14 +362,14 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
336
362
  * @returns boolean indicating if this shape should hide in the minimap
337
363
  * @public
338
364
  */
339
- hideInMinimap?(_shape: Shape): boolean
365
+ hideInMinimap?(shape: Shape): boolean
340
366
 
341
367
  /**
342
368
  * Whether the shape should hide its resize handles when selected.
343
369
  *
344
370
  * @public
345
371
  */
346
- hideResizeHandles(_shape: Shape): boolean {
372
+ hideResizeHandles(shape: Shape): boolean {
347
373
  return false
348
374
  }
349
375
 
@@ -352,7 +378,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
352
378
  *
353
379
  * @public
354
380
  */
355
- hideRotateHandle(_shape: Shape): boolean {
381
+ hideRotateHandle(shape: Shape): boolean {
356
382
  return false
357
383
  }
358
384
 
@@ -361,7 +387,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
361
387
  *
362
388
  * @public
363
389
  */
364
- hideSelectionBoundsBg(_shape: Shape): boolean {
390
+ hideSelectionBoundsBg(shape: Shape): boolean {
365
391
  return false
366
392
  }
367
393
 
@@ -370,7 +396,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
370
396
  *
371
397
  * @public
372
398
  */
373
- hideSelectionBoundsFg(_shape: Shape): boolean {
399
+ hideSelectionBoundsFg(shape: Shape): boolean {
374
400
  return false
375
401
  }
376
402
 
@@ -379,7 +405,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
379
405
  *
380
406
  * @public
381
407
  */
382
- isAspectRatioLocked(_shape: Shape): boolean {
408
+ isAspectRatioLocked(shape: Shape): boolean {
383
409
  return false
384
410
  }
385
411
 
@@ -390,10 +416,10 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
390
416
  * useful in cases like annotating on top of an image, where you usually want to avoid extra
391
417
  * padding around the image if you don't need it.
392
418
  *
393
- * @param _shape - The shape to check
419
+ * @param shape - The shape to check
394
420
  * @returns True if this shape should be treated as an export bounds container
395
421
  */
396
- isExportBoundsContainer(_shape: Shape): boolean {
422
+ isExportBoundsContainer(shape: Shape): boolean {
397
423
  return false
398
424
  }
399
425
 
@@ -442,7 +468,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
442
468
  * @param type - The shape type.
443
469
  * @public
444
470
  */
445
- canReceiveNewChildrenOfType(_shape: Shape, _type: TLShape['type']) {
471
+ canReceiveNewChildrenOfType(shape: Shape, _type: TLShape['type']) {
446
472
  return false
447
473
  }
448
474
 
@@ -490,7 +516,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
490
516
  * Get the geometry to use when snapping to this this shape in translate/resize operations. See
491
517
  * {@link BoundsSnapGeometry} for details.
492
518
  */
493
- getBoundsSnapGeometry(_shape: Shape): BoundsSnapGeometry {
519
+ getBoundsSnapGeometry(shape: Shape): BoundsSnapGeometry {
494
520
  return {}
495
521
  }
496
522
 
@@ -498,15 +524,15 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
498
524
  * Get the geometry to use when snapping handles to this shape. See {@link HandleSnapGeometry}
499
525
  * for details.
500
526
  */
501
- getHandleSnapGeometry(_shape: Shape): HandleSnapGeometry {
527
+ getHandleSnapGeometry(shape: Shape): HandleSnapGeometry {
502
528
  return {}
503
529
  }
504
530
 
505
- getText(_shape: Shape): string | undefined {
531
+ getText(shape: Shape): string | undefined {
506
532
  return undefined
507
533
  }
508
534
 
509
- getAriaDescriptor(_shape: Shape): string | undefined {
535
+ getAriaDescriptor(shape: Shape): string | undefined {
510
536
  return undefined
511
537
  }
512
538
 
@@ -934,3 +960,17 @@ export interface TLHandleDragInfo<T extends TLShape> {
934
960
  isCreatingShape: boolean
935
961
  initial?: T | undefined
936
962
  }
963
+
964
+ /* --------------------------------- Editing -------------------------------- */
965
+
966
+ /** @public */
967
+ export interface TLEditStartInfo {
968
+ type:
969
+ | 'press_enter'
970
+ | 'click'
971
+ | 'double-click'
972
+ | 'double-click-edge'
973
+ | 'double-click-corner'
974
+ | 'click-header'
975
+ | 'unknown'
976
+ }
@@ -6,7 +6,7 @@ import { getPerfectDashProps } from '../shared/getPerfectDashProps'
6
6
  export function DashedOutlineBox({ bounds, className }: { bounds: Box; className: string }) {
7
7
  const editor = useEditor()
8
8
 
9
- const zoomLevel = useValue('zoom level', () => editor.getZoomLevel(), [editor])
9
+ const zoomLevel = useValue('zoom level', () => editor.getEfficientZoomLevel(), [editor])
10
10
 
11
11
  return (
12
12
  <g className={className} pointerEvents="none" strokeLinecap="round" strokeLinejoin="round">
@@ -55,9 +55,7 @@ export class GroupShapeUtil extends ShapeUtil<TLGroupShape> {
55
55
  const isHintingOtherGroup =
56
56
  hintingShapeIds.length > 0 &&
57
57
  hintingShapeIds.some(
58
- (id) =>
59
- id !== shape.id &&
60
- this.editor.isShapeOfType<TLGroupShape>(this.editor.getShape(id)!, 'group')
58
+ (id) => id !== shape.id && this.editor.isShapeOfType(this.editor.getShape(id)!, 'group')
61
59
  )
62
60
 
63
61
  const isFocused = this.editor.getCurrentPageState().focusedGroupId !== shape.id
@@ -1,4 +1,5 @@
1
1
  import { TLShape } from '@tldraw/tlschema'
2
+ import { TLBaseBoxShape } from '../../shapes/BaseBoxShapeUtil'
2
3
  import { StateNode, TLStateNodeConstructor } from '../StateNode'
3
4
  import { Idle } from './children/Idle'
4
5
  import { Pointing } from './children/Pointing'
@@ -11,7 +12,7 @@ export abstract class BaseBoxShapeTool extends StateNode {
11
12
  return [Idle, Pointing]
12
13
  }
13
14
 
14
- abstract override shapeType: string
15
+ abstract override shapeType: TLBaseBoxShape['type']
15
16
 
16
17
  onCreate?(_shape: TLShape | null): void | null
17
18
  }
@@ -12,8 +12,8 @@ export class Pointing extends StateNode {
12
12
 
13
13
  override onPointerMove(info: TLPointerEventInfo) {
14
14
  const { editor } = this
15
- if (editor.inputs.isDragging) {
16
- const { originPagePoint } = editor.inputs
15
+ if (editor.inputs.getIsDragging()) {
16
+ const originPagePoint = editor.inputs.getOriginPagePoint()
17
17
 
18
18
  const shapeType = (this.parent as BaseBoxShapeTool)!.shapeType
19
19
 
@@ -23,7 +23,7 @@ export class Pointing extends StateNode {
23
23
  const newPoint = maybeSnapToGrid(originPagePoint, editor)
24
24
 
25
25
  // Allow this to trigger the max shapes reached alert
26
- this.editor.createShapes<TLBaseBoxShape>([
26
+ this.editor.createShapes([
27
27
  {
28
28
  id,
29
29
  type: shapeType,
@@ -78,7 +78,7 @@ export class Pointing extends StateNode {
78
78
  }
79
79
 
80
80
  complete() {
81
- const { originPagePoint } = this.editor.inputs
81
+ const originPagePoint = this.editor.inputs.getOriginPagePoint()
82
82
 
83
83
  const shapeType = (this.parent as BaseBoxShapeTool)!.shapeType as TLBaseBoxShape['type']
84
84
 
@@ -88,7 +88,7 @@ export class Pointing extends StateNode {
88
88
 
89
89
  // Allow this to trigger the max shapes reached alert
90
90
  // todo: add scale here when dynamic size is enabled (is this still needed?)
91
- this.editor.createShapes<TLBaseBoxShape>([
91
+ this.editor.createShapes([
92
92
  {
93
93
  id,
94
94
  type: shapeType,
@@ -127,7 +127,7 @@ export class Pointing extends StateNode {
127
127
  ;(next as TLBaseBoxShape & { props: { scale: number } }).props.scale = scale
128
128
  }
129
129
 
130
- this.editor.updateShape<TLBaseBoxShape>(next)
130
+ this.editor.updateShape(next)
131
131
 
132
132
  this.editor.setSelectedShapes([id])
133
133
 
@@ -1,5 +1,5 @@
1
1
  import { HistoryEntry } from '@tldraw/store'
2
- import { TLPageId, TLRecord, TLShapeId } from '@tldraw/tlschema'
2
+ import { BoxModel, TLPageId, TLRecord, TLShapeId } from '@tldraw/tlschema'
3
3
  import { TLEventInfo } from './event-types'
4
4
 
5
5
  /** @public */
@@ -16,12 +16,14 @@ export interface TLEventMap {
16
16
  event: [TLEventInfo]
17
17
  tick: [number]
18
18
  frame: [number]
19
+ resize: [BoxModel]
19
20
  'select-all-text': [{ shapeId: TLShapeId }]
20
21
  'place-caret': [{ shapeId: TLShapeId; point: { x: number; y: number } }]
21
22
  'created-shapes': [TLRecord[]]
22
23
  'edited-shapes': [TLRecord[]]
23
24
  'deleted-shapes': [TLShapeId[]]
24
25
  edit: []
26
+ dispose: []
25
27
  }
26
28
 
27
29
  /** @public */
@@ -1,30 +1,21 @@
1
- import {
2
- Geometry2d,
3
- RecordProps,
4
- Rectangle2d,
5
- ShapeUtil,
6
- T,
7
- TLBaseShape,
8
- createShapeId,
9
- } from '../..'
1
+ import { Geometry2d, RecordProps, Rectangle2d, ShapeUtil, T, TLShape, createShapeId } from '../..'
10
2
  import { createTLStore } from '../config/createTLStore'
11
3
  import { Editor } from '../editor/Editor'
12
4
  import { Box } from '../primitives/Box'
13
5
  import { getExportDefaultBounds } from './getSvgJsx'
14
6
 
15
- type ITestShape = TLBaseShape<
16
- 'test-shape',
17
- {
18
- w: number
19
- h: number
20
- x: number
21
- y: number
22
- isContainer?: boolean
7
+ const TEST_SHAPE_TYPE = 'test-shape'
8
+
9
+ declare module '@tldraw/tlschema' {
10
+ export interface TLGlobalShapePropsMap {
11
+ [TEST_SHAPE_TYPE]: { w: number; h: number; x: number; y: number; isContainer?: boolean }
23
12
  }
24
- >
13
+ }
14
+
15
+ type ITestShape = TLShape<typeof TEST_SHAPE_TYPE>
25
16
 
26
17
  class TestShape extends ShapeUtil<ITestShape> {
27
- static override type = 'test-shape' as const
18
+ static override type = TEST_SHAPE_TYPE
28
19
  static override props: RecordProps<ITestShape> = {
29
20
  w: T.number,
30
21
  h: T.number,
@@ -1,7 +1,6 @@
1
1
  import { useAtom, useValue } from '@tldraw/state-react'
2
2
  import {
3
3
  TLFrameShape,
4
- TLGroupShape,
5
4
  TLShape,
6
5
  TLShapeId,
7
6
  getColorValue,
@@ -58,9 +57,7 @@ export function getSvgJsx(editor: Editor, ids: TLShapeId[], opts: TLImageExportO
58
57
 
59
58
  // --- Common bounding box of all shapes
60
59
  const singleFrameShapeId =
61
- ids.length === 1 && editor.isShapeOfType<TLFrameShape>(editor.getShape(ids[0])!, 'frame')
62
- ? ids[0]
63
- : null
60
+ ids.length === 1 && editor.isShapeOfType(editor.getShape(ids[0])!, 'frame') ? ids[0] : null
64
61
 
65
62
  let bbox: null | Box = null
66
63
  if (opts.bounds) {
@@ -272,7 +269,7 @@ function SvgExport({
272
269
 
273
270
  const shape = editor.getShape(id)!
274
271
 
275
- if (editor.isShapeOfType<TLGroupShape>(shape, 'group')) return []
272
+ if (editor.isShapeOfType(shape, 'group')) return []
276
273
 
277
274
  const elements = []
278
275
  const util = editor.getShapeUtil(shape)
@@ -364,4 +364,5 @@ test('parseCssValueUrls', () => {
364
364
  },
365
365
  ]
366
366
  `)
367
+ expect(parseCssValueUrls(`url(#arrowhead)`)).toMatchInlineSnapshot(`[]`)
367
368
  })
@@ -108,5 +108,5 @@ export function parseCssValueUrls(value: string) {
108
108
  return Array.from(value.matchAll(urlsRegex), (m) => ({
109
109
  original: m[0],
110
110
  url: m[1] || m[2] || m[3],
111
- }))
111
+ })).filter((m) => !m.url.startsWith('#'))
112
112
  }
@@ -1,5 +1,9 @@
1
+ import { atom } from '@tldraw/state'
2
+
1
3
  /**
2
4
  * An object that contains information about the current device and environment.
5
+ * This object is not reactive and will not update automatically when the environment changes,
6
+ * so only include values that are fixed, such as the user's browser and operating system.
3
7
  *
4
8
  * @public
5
9
  */
@@ -14,15 +18,66 @@ const tlenv = {
14
18
  hasCanvasSupport: false,
15
19
  }
16
20
 
17
- if (typeof window !== 'undefined' && 'navigator' in window) {
18
- tlenv.isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
19
- tlenv.isIos = !!navigator.userAgent.match(/iPad/i) || !!navigator.userAgent.match(/iPhone/i)
20
- tlenv.isChromeForIos = /crios.*safari/i.test(navigator.userAgent)
21
- tlenv.isFirefox = /firefox/i.test(navigator.userAgent)
22
- tlenv.isAndroid = /android/i.test(navigator.userAgent)
23
- tlenv.isDarwin = window.navigator.userAgent.toLowerCase().indexOf('mac') > -1
24
- tlenv.hasCanvasSupport =
25
- typeof window !== 'undefined' && 'Promise' in window && 'HTMLCanvasElement' in window
21
+ let isForcedFinePointer = false
22
+
23
+ if (typeof window !== 'undefined') {
24
+ if ('navigator' in window) {
25
+ tlenv.isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
26
+ tlenv.isIos = !!navigator.userAgent.match(/iPad/i) || !!navigator.userAgent.match(/iPhone/i)
27
+ tlenv.isChromeForIos = /crios.*safari/i.test(navigator.userAgent)
28
+ tlenv.isFirefox = /firefox/i.test(navigator.userAgent)
29
+ tlenv.isAndroid = /android/i.test(navigator.userAgent)
30
+ tlenv.isDarwin = window.navigator.userAgent.toLowerCase().indexOf('mac') > -1
31
+ }
32
+ tlenv.hasCanvasSupport = 'Promise' in window && 'HTMLCanvasElement' in window
33
+ isForcedFinePointer = tlenv.isFirefox && !tlenv.isAndroid && !tlenv.isIos
34
+ }
35
+
36
+ /**
37
+ * An atom that contains information about the current device and environment.
38
+ * This object is reactive and will update automatically when the environment changes.
39
+ * Use it for values that may change over time, such as the pointer type.
40
+ *
41
+ * @public
42
+ */
43
+ const tlenvReactive = atom('tlenvReactive', {
44
+ // Whether the user's device has a coarse pointer. This is dynamic on many systems, especially
45
+ // on touch-screen laptops, which will become "coarse" if the user touches the screen.
46
+ // See https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/pointer#coarse
47
+ isCoarsePointer: false,
48
+ })
49
+
50
+ if (typeof window !== 'undefined' && !isForcedFinePointer) {
51
+ const mql = window.matchMedia && window.matchMedia('(any-pointer: coarse)')
52
+
53
+ const isCurrentCoarsePointer = () => tlenvReactive.__unsafe__getWithoutCapture().isCoarsePointer
54
+
55
+ if (mql) {
56
+ // 1. Update the coarse pointer automatically when the media query changes
57
+ const updateIsCoarsePointer = () => {
58
+ const isCoarsePointer = mql.matches
59
+ if (isCoarsePointer !== isCurrentCoarsePointer()) {
60
+ tlenvReactive.update((prev) => ({ ...prev, isCoarsePointer: isCoarsePointer }))
61
+ }
62
+ }
63
+ updateIsCoarsePointer()
64
+ mql.addEventListener('change', updateIsCoarsePointer)
65
+ }
66
+
67
+ // 2. Also update the coarse pointer state when a pointer down event occurs. We need `capture: true`
68
+ // here because the tldraw component itself stops propagation on pointer events it receives.
69
+ window.addEventListener(
70
+ 'pointerdown',
71
+ (e: PointerEvent) => {
72
+ // when the user interacts with a mouse, we assume they have a fine pointer.
73
+ // otherwise, we assume they have a coarse pointer.
74
+ const isCoarseEvent = e.pointerType !== 'mouse'
75
+ if (isCoarseEvent !== isCurrentCoarsePointer()) {
76
+ tlenvReactive.update((prev) => ({ ...prev, isCoarsePointer: isCoarseEvent }))
77
+ }
78
+ },
79
+ { capture: true }
80
+ )
26
81
  }
27
82
 
28
- export { tlenv }
83
+ export { tlenv, tlenvReactive }
@@ -148,7 +148,7 @@ export const tlmenus = {
148
148
  * @public
149
149
  */
150
150
  isMenuOpen(id: string, contextId?: string): boolean {
151
- return this.getOpenMenus(contextId).includes(id)
151
+ return this.getOpenMenus(contextId).includes(`${id}-${contextId}`)
152
152
  },
153
153
 
154
154
  /**
@@ -1,66 +1,23 @@
1
- import { useEffect } from 'react'
2
- import { tlenv } from '../globals/environment'
1
+ import { unsafe__withoutCapture } from '@tldraw/state'
2
+ import { useReactor } from '@tldraw/state-react'
3
+ import { tlenvReactive } from '../globals/environment'
3
4
  import { useEditor } from './useEditor'
4
5
 
5
6
  /** @internal */
6
7
  export function useCoarsePointer() {
7
8
  const editor = useEditor()
8
9
 
9
- useEffect(() => {
10
- // We'll track our own state for the pointer type
11
- let isCoarse = editor.getInstanceState().isCoarsePointer
12
-
13
- // 1.
14
- // We'll use pointer events to detect coarse pointer.
15
-
16
- const handlePointerDown = (e: PointerEvent) => {
17
- // when the user interacts with a mouse, we assume they have a fine pointer.
18
- // otherwise, we assume they have a coarse pointer.
19
- const isCoarseEvent = e.pointerType !== 'mouse'
20
- if (isCoarse === isCoarseEvent) return
21
- isCoarse = isCoarseEvent
22
- editor.updateInstanceState({ isCoarsePointer: isCoarseEvent })
23
- }
24
-
25
- // we need `capture: true` here because the tldraw component itself stops propagation on
26
- // pointer events it receives.
27
- window.addEventListener('pointerdown', handlePointerDown, { capture: true })
28
-
29
- // 2.
30
- // We can also use the media query to detect / set the initial pointer type
31
- // and update the state if the pointer type changes.
32
-
33
- // We want the touch / mouse events to run even if the browser does not
34
- // support matchMedia. We'll have to handle the media query changes
35
- // conditionally in the code below.
36
- const mql = window.matchMedia && window.matchMedia('(any-pointer: coarse)')
37
-
38
- // This is a workaround for a Firefox bug where we don't correctly
39
- // detect coarse VS fine pointer. For now, let's assume that you have a fine
40
- // pointer if you're on Firefox on desktop.
41
- const isForcedFinePointer = tlenv.isFirefox && !tlenv.isAndroid && !tlenv.isIos
42
-
43
- const handleMediaQueryChange = () => {
44
- const next = isForcedFinePointer ? false : mql.matches // get the value from the media query
45
- if (isCoarse !== next) return // bail if the value hasn't changed
46
- isCoarse = next // update the local value
47
- editor.updateInstanceState({ isCoarsePointer: next }) // update the value in state
48
- }
49
-
50
- if (mql) {
51
- // set up the listener
52
- mql.addEventListener('change', handleMediaQueryChange)
53
-
54
- // and run the handler once to set the initial value
55
- handleMediaQueryChange()
56
- }
57
-
58
- return () => {
59
- window.removeEventListener('pointerdown', handlePointerDown, { capture: true })
60
-
61
- if (mql) {
62
- mql.removeEventListener('change', handleMediaQueryChange)
63
- }
64
- }
65
- }, [editor])
10
+ // When the coarse pointer state changes, update the instance state
11
+ useReactor(
12
+ 'coarse pointer change',
13
+ () => {
14
+ const isCoarsePointer = tlenvReactive.get().isCoarsePointer
15
+ const isInstanceStateCoarsePointer = unsafe__withoutCapture(
16
+ () => editor.getInstanceState().isCoarsePointer
17
+ )
18
+ if (isCoarsePointer === isInstanceStateCoarsePointer) return
19
+ editor.updateInstanceState({ isCoarsePointer: isCoarsePointer })
20
+ },
21
+ [editor]
22
+ )
66
23
  }
@@ -27,7 +27,7 @@ import { useCallback, useDebugValue, useLayoutEffect, useRef } from 'react'
27
27
  export function useEvent<Args extends Array<unknown>, Result>(
28
28
  handler: (...args: Args) => Result
29
29
  ): (...args: Args) => Result {
30
- const handlerRef = useRef<(...args: Args) => Result>()
30
+ const handlerRef = useRef<((...args: Args) => Result) | undefined>(undefined)
31
31
 
32
32
  // In a real implementation, this would run before layout effects
33
33
  useLayoutEffect(() => {
@@ -9,7 +9,7 @@ const IGNORED_TAGS = ['textarea', 'input']
9
9
  * want this for drawing operations and can disable it by setting 'disableDoubleTapZoom' in the main
10
10
  * editor.
11
11
  */
12
- export function useFixSafariDoubleTapZoomPencilEvents(ref: React.RefObject<HTMLElement>) {
12
+ export function useFixSafariDoubleTapZoomPencilEvents(ref: React.RefObject<HTMLElement | null>) {
13
13
  const editor = useEditor()
14
14
 
15
15
  useEffect(() => {