@tldraw/editor 4.2.2 → 4.2.3

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 (200) hide show
  1. package/dist-cjs/index.d.ts +155 -498
  2. package/dist-cjs/index.js +1 -6
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/components/ErrorBoundary.js.map +1 -1
  5. package/dist-cjs/lib/components/GeometryDebuggingView.js +17 -1
  6. package/dist-cjs/lib/components/GeometryDebuggingView.js.map +2 -2
  7. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +3 -3
  8. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  9. package/dist-cjs/lib/constants.js +3 -1
  10. package/dist-cjs/lib/constants.js.map +2 -2
  11. package/dist-cjs/lib/editor/Editor.js +286 -292
  12. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  13. package/dist-cjs/lib/editor/bindings/BindingUtil.js.map +2 -2
  14. package/dist-cjs/lib/editor/derivations/bindingsIndex.js.map +2 -2
  15. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +17 -18
  16. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +3 -3
  17. package/dist-cjs/lib/editor/derivations/parentsToChildren.js +3 -12
  18. package/dist-cjs/lib/editor/derivations/parentsToChildren.js.map +2 -2
  19. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js +1 -1
  20. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +2 -2
  21. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js +6 -5
  22. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js.map +2 -2
  23. package/dist-cjs/lib/editor/managers/SnapManager/SnapManager.js +1 -1
  24. package/dist-cjs/lib/editor/managers/SnapManager/SnapManager.js.map +2 -2
  25. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js +22 -1
  26. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +2 -2
  27. package/dist-cjs/lib/editor/shapes/BaseBoxShapeUtil.js.map +1 -1
  28. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +23 -31
  29. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  30. package/dist-cjs/lib/editor/shapes/group/DashedOutlineBox.js +1 -1
  31. package/dist-cjs/lib/editor/shapes/group/DashedOutlineBox.js.map +2 -2
  32. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +2 -2
  33. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool.js.map +2 -2
  34. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +3 -3
  35. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +2 -2
  36. package/dist-cjs/lib/editor/types/emit-types.js.map +1 -1
  37. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  38. package/dist-cjs/lib/exports/parseCss.js +1 -1
  39. package/dist-cjs/lib/exports/parseCss.js.map +2 -2
  40. package/dist-cjs/lib/globals/environment.js +9 -45
  41. package/dist-cjs/lib/globals/environment.js.map +2 -2
  42. package/dist-cjs/lib/globals/menus.js +1 -1
  43. package/dist-cjs/lib/globals/menus.js.map +2 -2
  44. package/dist-cjs/lib/hooks/useCanvasEvents.js +3 -4
  45. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  46. package/dist-cjs/lib/hooks/useCoarsePointer.js +29 -14
  47. package/dist-cjs/lib/hooks/useCoarsePointer.js.map +2 -2
  48. package/dist-cjs/lib/hooks/useEvent.js +1 -1
  49. package/dist-cjs/lib/hooks/useEvent.js.map +2 -2
  50. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js.map +2 -2
  51. package/dist-cjs/lib/hooks/useGestureEvents.js +1 -1
  52. package/dist-cjs/lib/hooks/useGestureEvents.js.map +2 -2
  53. package/dist-cjs/lib/hooks/usePassThroughMouseOverEvents.js.map +2 -2
  54. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +2 -2
  55. package/dist-cjs/lib/hooks/useScreenBounds.js.map +2 -2
  56. package/dist-cjs/lib/hooks/useStateAttribute.js +1 -4
  57. package/dist-cjs/lib/hooks/useStateAttribute.js.map +2 -2
  58. package/dist-cjs/lib/hooks/useTransform.js.map +1 -1
  59. package/dist-cjs/lib/hooks/useZoomCss.js +8 -4
  60. package/dist-cjs/lib/hooks/useZoomCss.js.map +2 -2
  61. package/dist-cjs/lib/options.js +1 -6
  62. package/dist-cjs/lib/options.js.map +2 -2
  63. package/dist-cjs/lib/primitives/Box.js +0 -3
  64. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  65. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +0 -1
  66. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  67. package/dist-cjs/lib/utils/reparenting.js.map +2 -2
  68. package/dist-cjs/lib/utils/rotation.js +1 -1
  69. package/dist-cjs/lib/utils/rotation.js.map +2 -2
  70. package/dist-cjs/version.js +3 -3
  71. package/dist-cjs/version.js.map +1 -1
  72. package/dist-esm/index.d.mts +155 -498
  73. package/dist-esm/index.mjs +2 -7
  74. package/dist-esm/index.mjs.map +2 -2
  75. package/dist-esm/lib/components/ErrorBoundary.mjs.map +1 -1
  76. package/dist-esm/lib/components/GeometryDebuggingView.mjs +17 -1
  77. package/dist-esm/lib/components/GeometryDebuggingView.mjs.map +2 -2
  78. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +3 -3
  79. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  80. package/dist-esm/lib/constants.mjs +3 -1
  81. package/dist-esm/lib/constants.mjs.map +2 -2
  82. package/dist-esm/lib/editor/Editor.mjs +289 -293
  83. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  84. package/dist-esm/lib/editor/bindings/BindingUtil.mjs.map +2 -2
  85. package/dist-esm/lib/editor/derivations/bindingsIndex.mjs.map +2 -2
  86. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +17 -18
  87. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +3 -3
  88. package/dist-esm/lib/editor/derivations/parentsToChildren.mjs +4 -13
  89. package/dist-esm/lib/editor/derivations/parentsToChildren.mjs.map +2 -2
  90. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs +1 -1
  91. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +2 -2
  92. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs +6 -5
  93. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs.map +2 -2
  94. package/dist-esm/lib/editor/managers/SnapManager/SnapManager.mjs +1 -1
  95. package/dist-esm/lib/editor/managers/SnapManager/SnapManager.mjs.map +2 -2
  96. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs +22 -1
  97. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +2 -2
  98. package/dist-esm/lib/editor/shapes/BaseBoxShapeUtil.mjs.map +1 -1
  99. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +23 -31
  100. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  101. package/dist-esm/lib/editor/shapes/group/DashedOutlineBox.mjs +1 -1
  102. package/dist-esm/lib/editor/shapes/group/DashedOutlineBox.mjs.map +2 -2
  103. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +2 -2
  104. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool.mjs.map +2 -2
  105. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +3 -3
  106. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +2 -2
  107. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  108. package/dist-esm/lib/exports/parseCss.mjs +1 -1
  109. package/dist-esm/lib/exports/parseCss.mjs.map +2 -2
  110. package/dist-esm/lib/globals/environment.mjs +9 -45
  111. package/dist-esm/lib/globals/environment.mjs.map +2 -2
  112. package/dist-esm/lib/globals/menus.mjs +1 -1
  113. package/dist-esm/lib/globals/menus.mjs.map +2 -2
  114. package/dist-esm/lib/hooks/useCanvasEvents.mjs +3 -4
  115. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  116. package/dist-esm/lib/hooks/useCoarsePointer.mjs +30 -15
  117. package/dist-esm/lib/hooks/useCoarsePointer.mjs.map +2 -2
  118. package/dist-esm/lib/hooks/useEvent.mjs +1 -1
  119. package/dist-esm/lib/hooks/useEvent.mjs.map +2 -2
  120. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs.map +2 -2
  121. package/dist-esm/lib/hooks/useGestureEvents.mjs +1 -1
  122. package/dist-esm/lib/hooks/useGestureEvents.mjs.map +2 -2
  123. package/dist-esm/lib/hooks/usePassThroughMouseOverEvents.mjs.map +2 -2
  124. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +2 -2
  125. package/dist-esm/lib/hooks/useScreenBounds.mjs.map +2 -2
  126. package/dist-esm/lib/hooks/useStateAttribute.mjs +1 -4
  127. package/dist-esm/lib/hooks/useStateAttribute.mjs.map +2 -2
  128. package/dist-esm/lib/hooks/useTransform.mjs.map +1 -1
  129. package/dist-esm/lib/hooks/useZoomCss.mjs +8 -4
  130. package/dist-esm/lib/hooks/useZoomCss.mjs.map +2 -2
  131. package/dist-esm/lib/options.mjs +1 -6
  132. package/dist-esm/lib/options.mjs.map +2 -2
  133. package/dist-esm/lib/primitives/Box.mjs +0 -3
  134. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  135. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +0 -1
  136. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  137. package/dist-esm/lib/utils/reparenting.mjs.map +2 -2
  138. package/dist-esm/lib/utils/rotation.mjs +1 -1
  139. package/dist-esm/lib/utils/rotation.mjs.map +2 -2
  140. package/dist-esm/version.mjs +3 -3
  141. package/dist-esm/version.mjs.map +1 -1
  142. package/editor.css +12 -14
  143. package/package.json +16 -18
  144. package/src/index.ts +1 -4
  145. package/src/lib/components/ErrorBoundary.tsx +1 -1
  146. package/src/lib/components/GeometryDebuggingView.tsx +19 -1
  147. package/src/lib/components/default-components/DefaultCanvas.tsx +3 -4
  148. package/src/lib/constants.ts +2 -0
  149. package/src/lib/editor/Editor.test.ts +10 -150
  150. package/src/lib/editor/Editor.ts +379 -459
  151. package/src/lib/editor/bindings/BindingUtil.ts +9 -15
  152. package/src/lib/editor/derivations/bindingsIndex.ts +2 -2
  153. package/src/lib/editor/derivations/notVisibleShapes.ts +23 -37
  154. package/src/lib/editor/derivations/parentsToChildren.ts +7 -18
  155. package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +31 -17
  156. package/src/lib/editor/managers/ClickManager/ClickManager.ts +1 -1
  157. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +79 -129
  158. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.ts +6 -10
  159. package/src/lib/editor/managers/FontManager/FontManager.test.ts +4 -14
  160. package/src/lib/editor/managers/ScribbleManager/ScribbleManager.test.ts +4 -0
  161. package/src/lib/editor/managers/SnapManager/SnapManager.test.ts +0 -12
  162. package/src/lib/editor/managers/SnapManager/SnapManager.ts +4 -4
  163. package/src/lib/editor/managers/TickManager/TickManager.test.ts +107 -40
  164. package/src/lib/editor/managers/TickManager/TickManager.ts +32 -2
  165. package/src/lib/editor/shapes/BaseBoxShapeUtil.tsx +2 -2
  166. package/src/lib/editor/shapes/ShapeUtil.ts +32 -72
  167. package/src/lib/editor/shapes/group/DashedOutlineBox.tsx +1 -1
  168. package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +3 -1
  169. package/src/lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool.ts +1 -2
  170. package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +6 -6
  171. package/src/lib/editor/types/emit-types.ts +1 -3
  172. package/src/lib/exports/getSvgJsx.test.ts +19 -10
  173. package/src/lib/exports/getSvgJsx.tsx +5 -2
  174. package/src/lib/exports/parseCss.test.ts +0 -1
  175. package/src/lib/exports/parseCss.ts +1 -1
  176. package/src/lib/globals/environment.ts +10 -65
  177. package/src/lib/globals/menus.ts +1 -1
  178. package/src/lib/hooks/useCanvasEvents.ts +3 -4
  179. package/src/lib/hooks/useCoarsePointer.ts +59 -16
  180. package/src/lib/hooks/useEvent.tsx +1 -1
  181. package/src/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.ts +1 -1
  182. package/src/lib/hooks/useGestureEvents.ts +2 -2
  183. package/src/lib/hooks/usePassThroughMouseOverEvents.ts +1 -1
  184. package/src/lib/hooks/usePassThroughWheelEvents.ts +1 -1
  185. package/src/lib/hooks/useScreenBounds.ts +1 -1
  186. package/src/lib/hooks/useStateAttribute.ts +1 -4
  187. package/src/lib/hooks/useTransform.ts +1 -1
  188. package/src/lib/hooks/useZoomCss.ts +8 -3
  189. package/src/lib/options.ts +0 -32
  190. package/src/lib/primitives/Box.ts +0 -9
  191. package/src/lib/primitives/geometry/Geometry2d.ts +0 -1
  192. package/src/lib/utils/reparenting.ts +5 -5
  193. package/src/lib/utils/rotation.ts +1 -1
  194. package/src/version.ts +3 -3
  195. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js +0 -591
  196. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js.map +0 -7
  197. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs +0 -573
  198. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs.map +0 -7
  199. package/src/lib/config/TLUserPreferences.test.ts +0 -40
  200. package/src/lib/editor/managers/InputsManager/InputsManager.ts +0 -566
@@ -21,6 +21,7 @@ import {
21
21
  PageRecordType,
22
22
  StyleProp,
23
23
  StylePropValue,
24
+ TLArrowShape,
24
25
  TLAsset,
25
26
  TLAssetId,
26
27
  TLAssetPartial,
@@ -29,12 +30,12 @@ import {
29
30
  TLBindingId,
30
31
  TLBindingUpdate,
31
32
  TLCamera,
32
- TLCreateShapePartial,
33
33
  TLCursor,
34
34
  TLCursorType,
35
35
  TLDOCUMENT_ID,
36
36
  TLDocument,
37
37
  TLFrameShape,
38
+ TLGeoShape,
38
39
  TLGroupShape,
39
40
  TLHandle,
40
41
  TLINSTANCE_ID,
@@ -42,6 +43,8 @@ import {
42
43
  TLInstance,
43
44
  TLInstancePageState,
44
45
  TLInstancePresence,
46
+ TLNoteShape,
47
+ TLPOINTER_ID,
45
48
  TLPage,
46
49
  TLPageId,
47
50
  TLParentId,
@@ -51,6 +54,8 @@ import {
51
54
  TLShapePartial,
52
55
  TLStore,
53
56
  TLStoreSnapshot,
57
+ TLUnknownBinding,
58
+ TLUnknownShape,
54
59
  TLVideoAsset,
55
60
  createBindingId,
56
61
  createShapeId,
@@ -108,6 +113,7 @@ import {
108
113
  MIDDLE_MOUSE_BUTTON,
109
114
  RIGHT_MOUSE_BUTTON,
110
115
  STYLUS_ERASER_BUTTON,
116
+ ZOOM_TO_FIT_PADDING,
111
117
  } from '../constants'
112
118
  import { exportToSvg } from '../exports/exportToSvg'
113
119
  import { getSvgAsImage } from '../exports/getSvgAsImage'
@@ -133,6 +139,7 @@ import {
133
139
  parseDeepLinkString,
134
140
  } from '../utils/deepLinks'
135
141
  import { getIncrementedName } from '../utils/getIncrementedName'
142
+ import { isAccelKey } from '../utils/keyboard'
136
143
  import { getReorderingShapesChanges } from '../utils/reorderShapes'
137
144
  import { TLTextOptions, TiptapEditor } from '../utils/richText'
138
145
  import { applyRotationToSnapshotShapes, getRotationSnapshot } from '../utils/rotation'
@@ -146,18 +153,22 @@ import { EdgeScrollManager } from './managers/EdgeScrollManager/EdgeScrollManage
146
153
  import { FocusManager } from './managers/FocusManager/FocusManager'
147
154
  import { FontManager } from './managers/FontManager/FontManager'
148
155
  import { HistoryManager } from './managers/HistoryManager/HistoryManager'
149
- import { InputsManager } from './managers/InputsManager/InputsManager'
150
156
  import { ScribbleManager } from './managers/ScribbleManager/ScribbleManager'
151
157
  import { SnapManager } from './managers/SnapManager/SnapManager'
152
158
  import { TextManager } from './managers/TextManager/TextManager'
153
159
  import { TickManager } from './managers/TickManager/TickManager'
154
160
  import { UserPreferencesManager } from './managers/UserPreferencesManager/UserPreferencesManager'
155
- import { ShapeUtil, TLEditStartInfo, TLGeometryOpts, TLResizeMode } from './shapes/ShapeUtil'
161
+ import { ShapeUtil, TLGeometryOpts, TLResizeMode } from './shapes/ShapeUtil'
156
162
  import { RootState } from './tools/RootState'
157
163
  import { StateNode, TLStateNodeConstructor } from './tools/StateNode'
158
164
  import { TLContent } from './types/clipboard-types'
159
165
  import { TLEventMap } from './types/emit-types'
160
- import { TLEventInfo, TLPointerEventInfo } from './types/event-types'
166
+ import {
167
+ TLEventInfo,
168
+ TLPinchEventInfo,
169
+ TLPointerEventInfo,
170
+ TLWheelEventInfo,
171
+ } from './types/event-types'
161
172
  import { TLExternalAsset, TLExternalContent } from './types/external-content'
162
173
  import { TLHistoryBatchOptions } from './types/history-types'
163
174
  import {
@@ -188,7 +199,7 @@ export type TLResizeShapeOptions = Partial<{
188
199
  /** @public */
189
200
  export interface TLEditorOptions {
190
201
  /**
191
- * The Store instance to use for keeping the editor's data. This may be prepopulated, e.g. by loading
202
+ * The Store instance to use for keeping the app's data. This may be prepopulated, e.g. by loading
192
203
  * from a server or database.
193
204
  */
194
205
  store: TLStore
@@ -326,8 +337,6 @@ export class Editor extends EventEmitter<TLEventMap> {
326
337
 
327
338
  this._tickManager = new TickManager(this)
328
339
 
329
- this.inputs = new InputsManager(this)
330
-
331
340
  class NewRoot extends RootState {
332
341
  static override initial = initialState ?? ''
333
342
  }
@@ -438,7 +447,7 @@ export class Editor extends EventEmitter<TLEventMap> {
438
447
  let deletedBindings = new Map<TLBindingId, BindingOnDeleteOptions<any>>()
439
448
  const deletedShapeIds = new Set<TLShapeId>()
440
449
  const invalidParents = new Set<TLShapeId>()
441
- let invalidBindingTypes = new Set<TLBinding['type']>()
450
+ let invalidBindingTypes = new Set<string>()
442
451
  this.disposables.add(
443
452
  this.sideEffects.registerOperationCompleteHandler(() => {
444
453
  // this needs to be cleared here because further effects may delete more shapes
@@ -701,7 +710,7 @@ export class Editor extends EventEmitter<TLEventMap> {
701
710
  if (filtered.length > 0) {
702
711
  const commonGroupAncestor = this.findCommonAncestor(
703
712
  compact(filtered.map((id) => this.getShape(id))),
704
- (shape) => this.isShapeOfType(shape, 'group')
713
+ (shape) => this.isShapeOfType<TLGroupShape>(shape, 'group')
705
714
  )
706
715
 
707
716
  if (commonGroupAncestor) {
@@ -862,7 +871,7 @@ export class Editor extends EventEmitter<TLEventMap> {
862
871
  }
863
872
 
864
873
  /**
865
- * A set of functions to call when the editor is disposed.
874
+ * A set of functions to call when the app is disposed.
866
875
  *
867
876
  * @public
868
877
  */
@@ -875,21 +884,11 @@ export class Editor extends EventEmitter<TLEventMap> {
875
884
  */
876
885
  isDisposed = false
877
886
 
878
- /**
879
- * A manager for the editor's tick events.
880
- *
881
- * @internal */
882
- private readonly _tickManager: TickManager
883
-
884
- /**
885
- * A manager for the editor's input state.
886
- *
887
- * @public
888
- */
889
- readonly inputs: InputsManager
887
+ /** @internal */
888
+ private readonly _tickManager
890
889
 
891
890
  /**
892
- * A manager for the editor's snapping feature.
891
+ * A manager for the app's snapping feature.
893
892
  *
894
893
  * @public
895
894
  */
@@ -974,7 +973,6 @@ export class Editor extends EventEmitter<TLEventMap> {
974
973
  this.disposables.clear()
975
974
  this.store.dispose()
976
975
  this.isDisposed = true
977
- this.emit('dispose')
978
976
  }
979
977
 
980
978
  /* ------------------- Shape Utils ------------------ */
@@ -984,7 +982,7 @@ export class Editor extends EventEmitter<TLEventMap> {
984
982
  *
985
983
  * @public
986
984
  */
987
- shapeUtils: { readonly [K in string]?: ShapeUtil<TLShape> }
985
+ shapeUtils: { readonly [K in string]?: ShapeUtil<TLUnknownShape> }
988
986
 
989
987
  styleProps: { [key: string]: Map<StyleProp<any>, string> }
990
988
 
@@ -1003,8 +1001,8 @@ export class Editor extends EventEmitter<TLEventMap> {
1003
1001
  *
1004
1002
  * @public
1005
1003
  */
1006
- getShapeUtil<K extends TLShape['type']>(type: K): ShapeUtil<Extract<TLShape, { type: K }>>
1007
- getShapeUtil<S extends TLShape>(shape: S | TLShapePartial<S> | S['type']): ShapeUtil<S>
1004
+ getShapeUtil<S extends TLUnknownShape>(shape: S | TLShapePartial<S>): ShapeUtil<S>
1005
+ getShapeUtil<S extends TLUnknownShape>(type: S['type']): ShapeUtil<S>
1008
1006
  getShapeUtil<T extends ShapeUtil>(type: T extends ShapeUtil<infer R> ? R['type'] : string): T
1009
1007
  getShapeUtil(arg: string | { type: string }) {
1010
1008
  const type = typeof arg === 'string' ? arg : arg.type
@@ -1018,8 +1016,8 @@ export class Editor extends EventEmitter<TLEventMap> {
1018
1016
  *
1019
1017
  * @param shape - A shape, shape partial, or shape type.
1020
1018
  */
1021
- hasShapeUtil(shape: TLShape | TLShapePartial<TLShape>): boolean
1022
- hasShapeUtil(type: TLShape['type']): boolean
1019
+ hasShapeUtil<S extends TLUnknownShape>(shape: S | TLShapePartial<S>): boolean
1020
+ hasShapeUtil<S extends TLUnknownShape>(type: S['type']): boolean
1023
1021
  hasShapeUtil<T extends ShapeUtil>(
1024
1022
  type: T extends ShapeUtil<infer R> ? R['type'] : string
1025
1023
  ): boolean
@@ -1034,7 +1032,7 @@ export class Editor extends EventEmitter<TLEventMap> {
1034
1032
  *
1035
1033
  * @public
1036
1034
  */
1037
- bindingUtils: { readonly [K in string]?: BindingUtil<TLBinding> }
1035
+ bindingUtils: { readonly [K in string]?: BindingUtil<TLUnknownBinding> }
1038
1036
 
1039
1037
  /**
1040
1038
  * Get a binding util from a binding itself.
@@ -1051,8 +1049,8 @@ export class Editor extends EventEmitter<TLEventMap> {
1051
1049
  *
1052
1050
  * @public
1053
1051
  */
1054
- getBindingUtil<K extends TLBinding['type']>(type: K): BindingUtil<Extract<TLBinding, { type: K }>>
1055
- getBindingUtil<S extends TLBinding>(binding: S | { type: S['type'] }): BindingUtil<S>
1052
+ getBindingUtil<S extends TLUnknownBinding>(binding: S | { type: S['type'] }): BindingUtil<S>
1053
+ getBindingUtil<S extends TLUnknownBinding>(type: S['type']): BindingUtil<S>
1056
1054
  getBindingUtil<T extends BindingUtil>(
1057
1055
  type: T extends BindingUtil<infer R> ? R['type'] : string
1058
1056
  ): T
@@ -1066,7 +1064,7 @@ export class Editor extends EventEmitter<TLEventMap> {
1066
1064
  /* --------------------- History -------------------- */
1067
1065
 
1068
1066
  /**
1069
- * A manager for the editor's history.
1067
+ * A manager for the app's history.
1070
1068
  *
1071
1069
  * @readonly
1072
1070
  */
@@ -1090,18 +1088,14 @@ export class Editor extends EventEmitter<TLEventMap> {
1090
1088
  }
1091
1089
 
1092
1090
  /**
1093
- * Whether the editor can undo.
1091
+ * Whether the app can undo.
1094
1092
  *
1095
1093
  * @public
1096
1094
  */
1097
- @computed canUndo(): boolean {
1095
+ @computed getCanUndo(): boolean {
1098
1096
  return this.history.getNumUndos() > 0
1099
1097
  }
1100
1098
 
1101
- getCanUndo() {
1102
- return this.canUndo()
1103
- }
1104
-
1105
1099
  /**
1106
1100
  * Redo to the next mark.
1107
1101
  *
@@ -1119,24 +1113,20 @@ export class Editor extends EventEmitter<TLEventMap> {
1119
1113
  return this
1120
1114
  }
1121
1115
 
1116
+ clearHistory() {
1117
+ this.history.clear()
1118
+ return this
1119
+ }
1120
+
1122
1121
  /**
1123
- * Whether the editor can redo.
1122
+ * Whether the app can redo.
1124
1123
  *
1125
1124
  * @public
1126
1125
  */
1127
- @computed canRedo(): boolean {
1126
+ @computed getCanRedo(): boolean {
1128
1127
  return this.history.getNumRedos() > 0
1129
1128
  }
1130
1129
 
1131
- getCanRedo() {
1132
- return this.canRedo()
1133
- }
1134
-
1135
- clearHistory() {
1136
- this.history.clear()
1137
- return this
1138
- }
1139
-
1140
1130
  /**
1141
1131
  * Create a new "mark", or stopping point, in the undo redo history. Creating a mark will clear
1142
1132
  * any redos. You typically want to do this just before a user interaction begins or is handled.
@@ -1310,7 +1300,7 @@ export class Editor extends EventEmitter<TLEventMap> {
1310
1300
  }),
1311
1301
  selectionCount: this.getSelectedShapes().length,
1312
1302
  editingShape: editingShapeId ? this.getShape(editingShapeId) : undefined,
1313
- inputs: this.inputs.toJson(),
1303
+ inputs: this.inputs,
1314
1304
  pageState: this.getCurrentPageState(),
1315
1305
  instanceState: this.getInstanceState(),
1316
1306
  collaboratorCount: this.getCollaboratorsOnCurrentPage().length,
@@ -1335,7 +1325,7 @@ export class Editor extends EventEmitter<TLEventMap> {
1335
1325
  * we're in a transaction that's about to be rolled back due to the same error we're currently
1336
1326
  * reporting.
1337
1327
  *
1338
- * Instead, to listen to changes to this value, you need to listen to editor's `crash` event.
1328
+ * Instead, to listen to changes to this value, you need to listen to app's `crash` event.
1339
1329
  *
1340
1330
  * @internal
1341
1331
  */
@@ -2038,7 +2028,7 @@ export class Editor extends EventEmitter<TLEventMap> {
2038
2028
  }
2039
2029
 
2040
2030
  /**
2041
- * The id of the editor's only selected shape.
2031
+ * The id of the app's only selected shape.
2042
2032
  *
2043
2033
  * @returns Null if there is no shape or more than one selected shape, otherwise the selected shape's id.
2044
2034
  *
@@ -2050,7 +2040,7 @@ export class Editor extends EventEmitter<TLEventMap> {
2050
2040
  }
2051
2041
 
2052
2042
  /**
2053
- * The editor's only selected shape.
2043
+ * The app's only selected shape.
2054
2044
  *
2055
2045
  * @returns Null if there is no shape or more than one selected shape, otherwise the selected shape.
2056
2046
  *
@@ -2230,7 +2220,7 @@ export class Editor extends EventEmitter<TLEventMap> {
2230
2220
  throw Error(`Editor.setFocusedGroup: Shape with id ${id} does not exist`)
2231
2221
  }
2232
2222
 
2233
- if (!this.isShapeOfType(shape, 'group')) {
2223
+ if (!this.isShapeOfType<TLGroupShape>(shape, 'group')) {
2234
2224
  throw Error(
2235
2225
  `Editor.setFocusedGroup: Cannot set focused group to shape of type ${shape.type}`
2236
2226
  )
@@ -2258,7 +2248,7 @@ export class Editor extends EventEmitter<TLEventMap> {
2258
2248
  if (focusedGroup) {
2259
2249
  // If we have a focused layer, look for an ancestor of the focused shape that is a group
2260
2250
  const match = this.findShapeAncestor(focusedGroup, (shape) =>
2261
- this.isShapeOfType(shape, 'group')
2251
+ this.isShapeOfType<TLGroupShape>(shape, 'group')
2262
2252
  )
2263
2253
  // If we have an ancestor that can become a focused layer, set it as the focused layer
2264
2254
  this.setFocusedGroup(match?.id ?? null)
@@ -2291,29 +2281,6 @@ export class Editor extends EventEmitter<TLEventMap> {
2291
2281
  return editingShapeId ? this.getShape(editingShapeId) : undefined
2292
2282
  }
2293
2283
 
2294
- /**
2295
- * Whether the shape can be edited.
2296
- *
2297
- * @param shape - The shape (or shape id) to check if it can be edited.
2298
- * @param info - The info about the edit start.
2299
- *
2300
- * @public
2301
- * @returns true if the shape can be edited, false otherwise.
2302
- */
2303
- canEditShape<T extends TLShape | TLShapeId>(shape: T | null, info?: TLEditStartInfo): shape is T {
2304
- const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
2305
- if (!id) return false // no shape
2306
- if (id === this.getEditingShapeId()) return false // already editing this shape
2307
- const _shape = this.getShape(id)
2308
- if (!_shape) return false // no shape
2309
- const util = this.getShapeUtil(_shape)
2310
- const _info: TLEditStartInfo = info ?? { type: 'unknown' }
2311
- if (!util.canEdit(_shape, _info)) return false // shape is not editable
2312
- if (this.getIsReadonly() && !util.canEditInReadonly(_shape)) return false // readonly and no exception
2313
- if (this.isShapeOrAncestorLocked(_shape) && !util.canEditWhileLocked(_shape)) return false // locked and no exception. Note here: we're not distinguishing between a locked shape and a shape that is the descendant of a locked shape.
2314
- return true // shape is editable
2315
- }
2316
-
2317
2284
  /**
2318
2285
  * Set the current editing shape.
2319
2286
  *
@@ -2329,59 +2296,44 @@ export class Editor extends EventEmitter<TLEventMap> {
2329
2296
  */
2330
2297
  setEditingShape(shape: TLShapeId | TLShape | null): this {
2331
2298
  const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
2299
+ this.setRichTextEditor(null)
2300
+ const prevEditingShapeId = this.getEditingShapeId()
2301
+ if (id !== prevEditingShapeId) {
2302
+ if (id) {
2303
+ const shape = this.getShape(id)
2304
+ if (shape && this.getShapeUtil(shape).canEdit(shape)) {
2305
+ this.run(
2306
+ () => {
2307
+ this._updateCurrentPageState({ editingShapeId: id })
2308
+ if (prevEditingShapeId) {
2309
+ const prevEditingShape = this.getShape(prevEditingShapeId)
2310
+ if (prevEditingShape) {
2311
+ this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape)
2312
+ }
2313
+ }
2314
+ this.getShapeUtil(shape).onEditStart?.(shape)
2315
+ },
2316
+ { history: 'ignore' }
2317
+ )
2318
+ return this
2319
+ }
2320
+ }
2332
2321
 
2333
- if (!id) {
2334
- // setting the editing shape to null
2322
+ // Either we just set the editing id to null, or the shape was missing or not editable
2335
2323
  this.run(
2336
2324
  () => {
2337
- // Clean up the previous editing shape
2338
- const prevEditingShapeId = this.getEditingShapeId()
2325
+ this._updateCurrentPageState({ editingShapeId: null })
2326
+ this._currentRichTextEditor.set(null)
2339
2327
  if (prevEditingShapeId) {
2340
2328
  const prevEditingShape = this.getShape(prevEditingShapeId)
2341
2329
  if (prevEditingShape) {
2342
2330
  this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape)
2343
2331
  }
2344
2332
  }
2345
-
2346
- // Clean up the editing shape state and rich text editor
2347
- this._updateCurrentPageState({ editingShapeId: null })
2348
- this._currentRichTextEditor.set(null)
2349
2333
  },
2350
2334
  { history: 'ignore' }
2351
2335
  )
2352
-
2353
- return this
2354
2336
  }
2355
-
2356
- // id was provided but the next editing shape was not editable or didn't exist, so do nothing
2357
- if (!this.canEditShape(id)) return this
2358
-
2359
- // id was provided and the next editing shape is editable, so set the rich text editor to null
2360
- this.run(
2361
- () => {
2362
- // Clean up the previous editing shape
2363
- const prevEditingShapeId = this.getEditingShapeId()
2364
- if (prevEditingShapeId) {
2365
- const prevEditingShape = this.getShape(prevEditingShapeId)
2366
- if (prevEditingShape) {
2367
- this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape)
2368
- }
2369
- }
2370
-
2371
- // Clean up the editing shape state and rich text editor
2372
- this._updateCurrentPageState({ editingShapeId: null })
2373
- this._currentRichTextEditor.set(null)
2374
-
2375
- // Set the new editing shape
2376
- this.select(id)
2377
- this._updateCurrentPageState({ editingShapeId: id })
2378
-
2379
- const nextEditingShape = this.getShape(id)! // shape should be there because canEditShape checked it. Possible small chance that onEditEnd deleted it?
2380
- this.getShapeUtil(nextEditingShape).onEditStart?.(nextEditingShape)
2381
- },
2382
- { history: 'ignore' }
2383
- )
2384
-
2385
2337
  return this
2386
2338
  }
2387
2339
 
@@ -2585,26 +2537,6 @@ export class Editor extends EventEmitter<TLEventMap> {
2585
2537
  return this.getCurrentPageState().croppingShapeId
2586
2538
  }
2587
2539
 
2588
- /**
2589
- * Whether the shape can be cropped.
2590
- *
2591
- * @param shape - The shape (or shape id) to check if it can be cropped.
2592
- *
2593
- * @public
2594
- * @returns true if the shape can be cropped, false otherwise.
2595
- */
2596
- canCropShape<T extends TLShape | TLShapeId>(shape: T | null): shape is T {
2597
- if (!shape) return false
2598
- const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
2599
- if (!id) return false
2600
- const _shape = this.getShape(id)
2601
- if (!_shape) return false
2602
- const util = this.getShapeUtil(_shape)
2603
- if (!util.canCrop(_shape)) return false
2604
- if (this.isShapeOrAncestorLocked(_shape)) return false
2605
- return true
2606
- }
2607
-
2608
2540
  /**
2609
2541
  * Set the current cropping shape.
2610
2542
  *
@@ -2626,8 +2558,12 @@ export class Editor extends EventEmitter<TLEventMap> {
2626
2558
  () => {
2627
2559
  if (!id) {
2628
2560
  this.updateCurrentPageState({ croppingShapeId: null })
2629
- } else if (this.canCropShape(id)) {
2630
- this.updateCurrentPageState({ croppingShapeId: id })
2561
+ } else {
2562
+ const shape = this.getShape(id)!
2563
+ const util = this.getShapeUtil(shape)
2564
+ if (shape && util.canCrop(shape)) {
2565
+ this.updateCurrentPageState({ croppingShapeId: id })
2566
+ }
2631
2567
  }
2632
2568
  },
2633
2569
  { history: 'ignore' }
@@ -2737,52 +2673,6 @@ export class Editor extends EventEmitter<TLEventMap> {
2737
2673
  return this.getCamera().z
2738
2674
  }
2739
2675
 
2740
- private _debouncedZoomLevel = atom('debounced zoom level', 1)
2741
-
2742
- /**
2743
- * Get the debounced zoom level. When the camera is moving, this returns the zoom level
2744
- * from when the camera started moving rather than the current zoom level. This can be
2745
- * used to avoid expensive re-renders during camera movements.
2746
- *
2747
- * This behavior is controlled by the `useDebouncedZoom` option. When `useDebouncedZoom`
2748
- * is `false`, this method always returns the current zoom level.
2749
- *
2750
- * @public
2751
- */
2752
- @computed getDebouncedZoomLevel() {
2753
- if (this.options.debouncedZoom) {
2754
- if (this.getCameraState() === 'idle') {
2755
- return this.getZoomLevel()
2756
- } else {
2757
- return this._debouncedZoomLevel.get()
2758
- }
2759
- }
2760
-
2761
- return this.getZoomLevel()
2762
- }
2763
-
2764
- @computed private _getAboveDebouncedZoomThreshold() {
2765
- return this.getCurrentPageShapeIds().size > this.options.debouncedZoomThreshold
2766
- }
2767
-
2768
- /**
2769
- * Get the efficient zoom level. This returns the current zoom level if there are less than 300 shapes on the page,
2770
- * otherwise it returns the debounced zoom level. This can be used to avoid expensive re-renders during camera movements.
2771
- *
2772
- * @public
2773
- * @example
2774
- * ```ts
2775
- * editor.getEfficientZoomLevel()
2776
- * ```
2777
- *
2778
- * @public
2779
- */
2780
- @computed getEfficientZoomLevel() {
2781
- return this._getAboveDebouncedZoomThreshold()
2782
- ? this.getDebouncedZoomLevel()
2783
- : this.getZoomLevel()
2784
- }
2785
-
2786
2676
  /**
2787
2677
  * Get the camera's initial or reset zoom level.
2788
2678
  *
@@ -3109,8 +2999,7 @@ export class Editor extends EventEmitter<TLEventMap> {
3109
2999
 
3110
3000
  // Dispatch a new pointer move because the pointer's page will have changed
3111
3001
  // (its screen position will compute to a new page position given the new camera position)
3112
- const currentScreenPoint = this.inputs.getCurrentScreenPoint()
3113
- const currentPagePoint = this.inputs.getCurrentPagePoint()
3002
+ const { currentScreenPoint, currentPagePoint } = this.inputs
3114
3003
 
3115
3004
  // compare the next page point (derived from the current camera) to the current page point
3116
3005
  if (
@@ -3274,7 +3163,7 @@ export class Editor extends EventEmitter<TLEventMap> {
3274
3163
  * ```ts
3275
3164
  * editor.zoomIn()
3276
3165
  * editor.zoomIn(editor.getViewportScreenCenter(), { animation: { duration: 200 } })
3277
- * editor.zoomIn(editor.inputs.getCurrentScreenPoint(), { animation: { duration: 200 } })
3166
+ * editor.zoomIn(editor.inputs.currentScreenPoint, { animation: { duration: 200 } })
3278
3167
  * ```
3279
3168
  *
3280
3169
  * @param point - The screen point to zoom in on. Defaults to the screen center
@@ -3319,7 +3208,7 @@ export class Editor extends EventEmitter<TLEventMap> {
3319
3208
  * ```ts
3320
3209
  * editor.zoomOut()
3321
3210
  * editor.zoomOut(editor.getViewportScreenCenter(), { animation: { duration: 120 } })
3322
- * editor.zoomOut(editor.inputs.getCurrentScreenPoint(), { animation: { duration: 120 } })
3211
+ * editor.zoomOut(editor.inputs.currentScreenPoint, { animation: { duration: 120 } })
3323
3212
  * ```
3324
3213
  *
3325
3214
  * @param point - The point to zoom out on. Defaults to the viewport screen center.
@@ -3376,17 +3265,10 @@ export class Editor extends EventEmitter<TLEventMap> {
3376
3265
 
3377
3266
  const selectionPageBounds = this.getSelectionPageBounds()
3378
3267
  if (selectionPageBounds) {
3379
- const currentZoom = this.getZoomLevel()
3380
- // If already at 100%, zoom to fit the selection in the viewport
3381
- // Otherwise, zoom to 100% centered on the selection
3382
- if (Math.abs(currentZoom - 1) < 0.01) {
3383
- this.zoomToBounds(selectionPageBounds, opts)
3384
- } else {
3385
- this.zoomToBounds(selectionPageBounds, {
3386
- targetZoom: 1,
3387
- ...opts,
3388
- })
3389
- }
3268
+ this.zoomToBounds(selectionPageBounds, {
3269
+ targetZoom: Math.max(1, this.getZoomLevel()),
3270
+ ...opts,
3271
+ })
3390
3272
  }
3391
3273
  return this
3392
3274
  }
@@ -3443,8 +3325,7 @@ export class Editor extends EventEmitter<TLEventMap> {
3443
3325
 
3444
3326
  const viewportScreenBounds = this.getViewportScreenBounds()
3445
3327
 
3446
- const inset =
3447
- opts?.inset ?? Math.min(this.options.zoomToFitPadding, viewportScreenBounds.width * 0.28)
3328
+ const inset = opts?.inset ?? Math.min(ZOOM_TO_FIT_PADDING, viewportScreenBounds.width * 0.28)
3448
3329
 
3449
3330
  const baseZoom = this.getBaseZoom()
3450
3331
  const zoomMin = cameraOptions.zoomSteps[0]
@@ -3754,23 +3635,22 @@ export class Editor extends EventEmitter<TLEventMap> {
3754
3635
  if (_willSetInitialBounds) {
3755
3636
  // If we have just received the initial bounds, don't center the camera.
3756
3637
  this.updateInstanceState({ screenBounds: screenBounds.toJson(), insets })
3757
- this.emit('resize', screenBounds.toJson())
3758
3638
  this.setCamera(this.getCamera())
3759
3639
  } else {
3760
3640
  if (center && !this.getInstanceState().followingUserId) {
3761
3641
  // Get the page center before the change, make the change, and restore it
3762
3642
  const before = this.getViewportPageBounds().center
3763
3643
  this.updateInstanceState({ screenBounds: screenBounds.toJson(), insets })
3764
- this.emit('resize', screenBounds.toJson())
3765
3644
  this.centerOnPoint(before)
3766
3645
  } else {
3767
3646
  // Otherwise,
3768
3647
  this.updateInstanceState({ screenBounds: screenBounds.toJson(), insets })
3769
- this.emit('resize', screenBounds.toJson())
3770
3648
  this._setCamera(Vec.From({ ...this.getCamera() }))
3771
3649
  }
3772
3650
  }
3773
3651
 
3652
+ this._tickCameraState()
3653
+
3774
3654
  return this
3775
3655
  }
3776
3656
 
@@ -4176,19 +4056,18 @@ export class Editor extends EventEmitter<TLEventMap> {
4176
4056
  // box just for rendering, and we only update after the camera stops moving.
4177
4057
  private _cameraState = atom('camera state', 'idle' as 'idle' | 'moving')
4178
4058
  private _cameraStateTimeoutRemaining = 0
4179
- private _decayCameraStateTimeout(elapsed: number) {
4059
+ _decayCameraStateTimeout(elapsed: number) {
4180
4060
  this._cameraStateTimeoutRemaining -= elapsed
4181
4061
  if (this._cameraStateTimeoutRemaining > 0) return
4182
4062
  this.off('tick', this._decayCameraStateTimeout)
4183
4063
  this._cameraState.set('idle')
4184
4064
  }
4185
- private _tickCameraState() {
4065
+ _tickCameraState() {
4186
4066
  // always reset the timeout
4187
4067
  this._cameraStateTimeoutRemaining = this.options.cameraMovingTimeoutMs
4188
4068
  // If the state is idle, then start the tick
4189
4069
  if (this._cameraState.__unsafe__getWithoutCapture() !== 'idle') return
4190
4070
  this._cameraState.set('moving')
4191
- this._debouncedZoomLevel.set(unsafe__withoutCapture(() => this.getCamera().z))
4192
4071
  this.on('tick', this._decayCameraStateTimeout)
4193
4072
  }
4194
4073
 
@@ -5248,10 +5127,10 @@ export class Editor extends EventEmitter<TLEventMap> {
5248
5127
 
5249
5128
  // Check labels first
5250
5129
  if (
5251
- this.isShapeOfType(shape, 'frame') ||
5252
- ((this.isShapeOfType(shape, 'note') ||
5253
- this.isShapeOfType(shape, 'arrow') ||
5254
- (this.isShapeOfType(shape, 'geo') && shape.props.fill === 'none')) &&
5130
+ this.isShapeOfType<TLFrameShape>(shape, 'frame') ||
5131
+ ((this.isShapeOfType<TLNoteShape>(shape, 'note') ||
5132
+ this.isShapeOfType<TLArrowShape>(shape, 'arrow') ||
5133
+ (this.isShapeOfType<TLGeoShape>(shape, 'geo') && shape.props.fill === 'none')) &&
5255
5134
  this.getShapeUtil(shape).getText(shape)?.trim())
5256
5135
  ) {
5257
5136
  for (const childGeometry of (geometry as Group2d).children) {
@@ -5261,7 +5140,7 @@ export class Editor extends EventEmitter<TLEventMap> {
5261
5140
  }
5262
5141
  }
5263
5142
 
5264
- if (this.isShapeOfType(shape, 'frame')) {
5143
+ if (this.isShapeOfType<TLFrameShape>(shape, 'frame')) {
5265
5144
  // On the rare case that we've hit a frame (not its label), test again hitInside to be forced true;
5266
5145
  // this prevents clicks from passing through the body of a frame to shapes behind it.
5267
5146
 
@@ -5542,7 +5421,7 @@ export class Editor extends EventEmitter<TLEventMap> {
5542
5421
  *
5543
5422
  * @example
5544
5423
  * ```ts
5545
- * const isArrowShape = isShapeOfType(someShape, 'arrow')
5424
+ * const isArrowShape = isShapeOfType<TLArrowShape>(someShape, 'arrow')
5546
5425
  * ```
5547
5426
  *
5548
5427
  * @param util - the TLShapeUtil constructor to test against
@@ -5550,16 +5429,15 @@ export class Editor extends EventEmitter<TLEventMap> {
5550
5429
  *
5551
5430
  * @public
5552
5431
  */
5553
- isShapeOfType<K extends TLShape['type']>(
5554
- shape: TLShape,
5555
- type: K
5556
- ): shape is Extract<TLShape, { type: K }>
5557
- isShapeOfType<T extends TLShape>(
5558
- shape: TLShape,
5432
+ isShapeOfType<T extends TLUnknownShape>(shape: TLUnknownShape, type: T['type']): shape is T
5433
+ isShapeOfType<T extends TLUnknownShape>(
5434
+ shapeId: TLUnknownShape['id'],
5435
+ type: T['type']
5436
+ ): shapeId is T['id']
5437
+ isShapeOfType<T extends TLUnknownShape>(
5438
+ arg: TLUnknownShape | TLUnknownShape['id'],
5559
5439
  type: T['type']
5560
- ): shape is Extract<TLShape, { type: T['type'] }>
5561
- isShapeOfType<T extends TLShape = TLShape>(shapeId: TLShapeId, type: T['type']): boolean
5562
- isShapeOfType(arg: TLShape | TLShapeId, type: TLShape['type']) {
5440
+ ) {
5563
5441
  const shape = typeof arg === 'string' ? this.getShape(arg) : arg
5564
5442
  if (!shape) return false
5565
5443
  return shape.type === type
@@ -5955,7 +5833,7 @@ export class Editor extends EventEmitter<TLEventMap> {
5955
5833
 
5956
5834
  while (node) {
5957
5835
  if (
5958
- this.isShapeOfType(node, 'group') &&
5836
+ this.isShapeOfType<TLGroupShape>(node, 'group') &&
5959
5837
  focusedGroup?.id !== node.id &&
5960
5838
  !this.hasAncestor(focusedGroup, node.id) &&
5961
5839
  (filter?.(node) ?? true)
@@ -5997,15 +5875,7 @@ export class Editor extends EventEmitter<TLEventMap> {
5997
5875
  * Get all bindings of a certain type _from_ a particular shape. These are the bindings whose
5998
5876
  * `fromId` matched the shape's ID.
5999
5877
  */
6000
- getBindingsFromShape<K extends TLBinding['type']>(
6001
- shape: TLShape | TLShapeId,
6002
- type: K
6003
- ): Extract<TLBinding, { type: K }>[]
6004
- getBindingsFromShape<Binding extends TLBinding = TLBinding>(
6005
- shape: TLShape | TLShapeId,
6006
- type: Binding['type']
6007
- ): Binding[]
6008
- getBindingsFromShape<Binding extends TLBinding = TLBinding>(
5878
+ getBindingsFromShape<Binding extends TLUnknownBinding = TLBinding>(
6009
5879
  shape: TLShape | TLShapeId,
6010
5880
  type: Binding['type']
6011
5881
  ): Binding[] {
@@ -6019,15 +5889,7 @@ export class Editor extends EventEmitter<TLEventMap> {
6019
5889
  * Get all bindings of a certain type _to_ a particular shape. These are the bindings whose
6020
5890
  * `toId` matches the shape's ID.
6021
5891
  */
6022
- getBindingsToShape<K extends TLBinding['type']>(
6023
- shape: TLShape | TLShapeId,
6024
- type: K
6025
- ): Extract<TLBinding, { type: K }>[]
6026
- getBindingsToShape<Binding extends TLBinding = TLBinding>(
6027
- shape: TLShape | TLShapeId,
6028
- type: Binding['type']
6029
- ): Binding[]
6030
- getBindingsToShape<Binding extends TLBinding = TLBinding>(
5892
+ getBindingsToShape<Binding extends TLUnknownBinding = TLBinding>(
6031
5893
  shape: TLShape | TLShapeId,
6032
5894
  type: Binding['type']
6033
5895
  ): Binding[] {
@@ -6041,15 +5903,7 @@ export class Editor extends EventEmitter<TLEventMap> {
6041
5903
  * Get all bindings involving a particular shape. This includes bindings where the shape is the
6042
5904
  * `fromId` or `toId`. If a type is provided, only bindings of that type are returned.
6043
5905
  */
6044
- getBindingsInvolvingShape<K extends TLBinding['type']>(
6045
- shape: TLShape | TLShapeId,
6046
- type: K
6047
- ): Extract<TLBinding, { type: K }>[]
6048
- getBindingsInvolvingShape<Binding extends TLBinding = TLBinding>(
6049
- shape: TLShape | TLShapeId,
6050
- type?: Binding['type']
6051
- ): Binding[]
6052
- getBindingsInvolvingShape<Binding extends TLBinding = TLBinding>(
5906
+ getBindingsInvolvingShape<Binding extends TLUnknownBinding = TLBinding>(
6053
5907
  shape: TLShape | TLShapeId,
6054
5908
  type?: Binding['type']
6055
5909
  ): Binding[] {
@@ -6071,7 +5925,7 @@ export class Editor extends EventEmitter<TLEventMap> {
6071
5925
  if (!fromShape || !toShape) continue
6072
5926
  if (!this.canBindShapes({ fromShape, toShape, binding: partial })) continue
6073
5927
 
6074
- const util = this.getBindingUtil(partial.type)
5928
+ const util = this.getBindingUtil<TLUnknownBinding>(partial.type)
6075
5929
  const defaultProps = util.getDefaultProps()
6076
5930
  const binding = this.store.schema.types.binding.create({
6077
5931
  ...partial,
@@ -6176,7 +6030,7 @@ export class Editor extends EventEmitter<TLEventMap> {
6176
6030
  const toShapeType = typeof toShape === 'string' ? toShape : toShape.type
6177
6031
  const bindingType = typeof binding === 'string' ? binding : binding.type
6178
6032
 
6179
- const canBindOpts = { fromShapeType, toShapeType, bindingType } as const
6033
+ const canBindOpts = { fromShapeType, toShapeType, bindingType }
6180
6034
 
6181
6035
  if (fromShapeType === toShapeType) {
6182
6036
  return this.getShapeUtil(fromShapeType).canBind(canBindOpts)
@@ -6717,7 +6571,7 @@ export class Editor extends EventEmitter<TLEventMap> {
6717
6571
  const shapesToFlipFirstPass = compact(ids.map((id) => this.getShape(id)))
6718
6572
 
6719
6573
  for (const shape of shapesToFlipFirstPass) {
6720
- if (this.isShapeOfType(shape, 'group')) {
6574
+ if (this.isShapeOfType<TLGroupShape>(shape, 'group')) {
6721
6575
  const childrenOfGroups = compact(
6722
6576
  this.getSortedChildIdsForParent(shape.id).map((id) => this.getShape(id))
6723
6577
  )
@@ -7774,14 +7628,8 @@ export class Editor extends EventEmitter<TLEventMap> {
7774
7628
  // then if the shape is flipped in one axis only, we need to apply an extra rotation
7775
7629
  // to make sure the shape is mirrored correctly
7776
7630
  if (Math.sign(scale.x) * Math.sign(scale.y) < 0) {
7777
- // We need to compute the new local rotation that will result in the negated page rotation.
7778
- // For a shape with local rotation `localRot` and parent page rotation `parentRot`:
7779
- // - pageRot = parentRot + localRot
7780
- // - newPageRot = -pageRot (we want to negate the page rotation)
7781
- // - newPageRot = parentRot + newLocalRot (parent hasn't changed)
7782
- // - Therefore: newLocalRot = -pageRot - parentRot = -(parentRot + localRot) - parentRot = -localRot - 2*parentRot
7783
- const parentRotation = this.getShapeParentTransform(id).rotation()
7784
- const rotation = -options.initialShape.rotation - 2 * parentRotation
7631
+ let { rotation } = Mat.Decompose(options.initialPageTransform)
7632
+ rotation -= 2 * rotation
7785
7633
  this.updateShapes([{ id, type, rotation }])
7786
7634
  }
7787
7635
 
@@ -7801,13 +7649,9 @@ export class Editor extends EventEmitter<TLEventMap> {
7801
7649
  )
7802
7650
 
7803
7651
  // now calculate how far away the shape is from where it needs to be
7652
+ const pageBounds = this.getShapePageBounds(id)!
7804
7653
  const pageTransform = this.getShapePageTransform(id)!
7805
- // We need to use the local bounds center transformed to page space, not the axis-aligned
7806
- // page bounds center. This is because the page bounds are axis-aligned and their center
7807
- // changes when the rotation changes, but we want to use the same reference point as
7808
- // preScaleShapePageCenter (which used initialBounds.center transformed by the page transform).
7809
- const currentLocalBounds = this.getShapeGeometry(id).bounds
7810
- const currentPageCenter = Mat.applyToPoint(pageTransform, currentLocalBounds.center)
7654
+ const currentPageCenter = pageBounds.center
7811
7655
  const shapePageTransformOrigin = pageTransform.point()
7812
7656
  if (!currentPageCenter || !shapePageTransformOrigin) return this
7813
7657
  const pageDelta = Vec.Sub(postScaleShapePageCenter, currentPageCenter)
@@ -7848,7 +7692,9 @@ export class Editor extends EventEmitter<TLEventMap> {
7848
7692
  *
7849
7693
  * @public
7850
7694
  */
7851
- canCreateShape(shape: OptionalKeys<TLShapePartial<TLShape>, 'id'> | TLShape['id']): boolean {
7695
+ canCreateShape<T extends TLUnknownShape>(
7696
+ shape: OptionalKeys<TLShapePartial<T>, 'id'> | T['id']
7697
+ ): boolean {
7852
7698
  return this.canCreateShapes([shape])
7853
7699
  }
7854
7700
 
@@ -7859,8 +7705,8 @@ export class Editor extends EventEmitter<TLEventMap> {
7859
7705
  *
7860
7706
  * @public
7861
7707
  */
7862
- canCreateShapes(
7863
- shapes: (TLShape['id'] | OptionalKeys<TLShapePartial<TLShape>, 'id'>)[]
7708
+ canCreateShapes<T extends TLUnknownShape>(
7709
+ shapes: (T['id'] | OptionalKeys<TLShapePartial<T>, 'id'>)[]
7864
7710
  ): boolean {
7865
7711
  return shapes.length + this.getCurrentPageShapeIds().size <= this.options.maxShapesPerPage
7866
7712
  }
@@ -7878,7 +7724,7 @@ export class Editor extends EventEmitter<TLEventMap> {
7878
7724
  *
7879
7725
  * @public
7880
7726
  */
7881
- createShape<TShape extends TLShape>(shape: TLCreateShapePartial<TShape>): this {
7727
+ createShape<T extends TLUnknownShape>(shape: OptionalKeys<TLShapePartial<T>, 'id'>): this {
7882
7728
  this.createShapes([shape])
7883
7729
  return this
7884
7730
  }
@@ -7896,7 +7742,7 @@ export class Editor extends EventEmitter<TLEventMap> {
7896
7742
  *
7897
7743
  * @public
7898
7744
  */
7899
- createShapes<TShape extends TLShape = TLShape>(shapes: TLCreateShapePartial<TShape>[]): this {
7745
+ createShapes<T extends TLUnknownShape>(shapes: OptionalKeys<TLShapePartial<T>, 'id'>[]): this {
7900
7746
  if (!Array.isArray(shapes)) {
7901
7747
  throw Error('Editor.createShapes: must provide an array of shapes or shape partials')
7902
7748
  }
@@ -8255,12 +8101,7 @@ export class Editor extends EventEmitter<TLEventMap> {
8255
8101
  )
8256
8102
  )
8257
8103
  const sortedShapeIds = shapesToGroup.sort(sortByIndex).map((s) => s.id)
8258
- const childBounds = compact(shapesToGroup.map((shape) => this.getShapePageBounds(shape)))
8259
- const pageBounds = Box.Common(childBounds)
8260
-
8261
- if (!pageBounds.isValid()) {
8262
- throw Error(`Editor.groupShapes: group bounds are invalid (NaN).`)
8263
- }
8104
+ const pageBounds = Box.Common(compact(shapesToGroup.map((id) => this.getShapePageBounds(id))))
8264
8105
 
8265
8106
  const { x, y } = pageBounds.point
8266
8107
 
@@ -8282,7 +8123,7 @@ export class Editor extends EventEmitter<TLEventMap> {
8282
8123
  const highestIndex = shapesWithRootParent[shapesWithRootParent.length - 1]?.index
8283
8124
 
8284
8125
  this.run(() => {
8285
- this.createShapes([
8126
+ this.createShapes<TLGroupShape>([
8286
8127
  {
8287
8128
  id: groupId,
8288
8129
  type: 'group',
@@ -8352,7 +8193,7 @@ export class Editor extends EventEmitter<TLEventMap> {
8352
8193
  const groups: TLGroupShape[] = []
8353
8194
 
8354
8195
  shapesToUngroup.forEach((shape) => {
8355
- if (this.isShapeOfType(shape, 'group')) {
8196
+ if (this.isShapeOfType<TLGroupShape>(shape, 'group')) {
8356
8197
  groups.push(shape)
8357
8198
  } else {
8358
8199
  idsToSelect.add(shape.id)
@@ -8398,7 +8239,7 @@ export class Editor extends EventEmitter<TLEventMap> {
8398
8239
  *
8399
8240
  * @public
8400
8241
  */
8401
- updateShape<T extends TLShape = TLShape>(partial: TLShapePartial<T> | null | undefined) {
8242
+ updateShape<T extends TLUnknownShape>(partial: TLShapePartial<T> | null | undefined) {
8402
8243
  this.updateShapes([partial])
8403
8244
  return this
8404
8245
  }
@@ -8415,7 +8256,7 @@ export class Editor extends EventEmitter<TLEventMap> {
8415
8256
  *
8416
8257
  * @public
8417
8258
  */
8418
- updateShapes<T extends TLShape>(partials: (TLShapePartial<T> | null | undefined)[]) {
8259
+ updateShapes<T extends TLUnknownShape>(partials: (TLShapePartial<T> | null | undefined)[]) {
8419
8260
  const compactedPartials: TLShapePartial<T>[] = Array(partials.length)
8420
8261
 
8421
8262
  for (let i = 0, n = partials.length; i < n; i++) {
@@ -8567,7 +8408,7 @@ export class Editor extends EventEmitter<TLEventMap> {
8567
8408
  * @internal
8568
8409
  */
8569
8410
  private _extractSharedStyles(shape: TLShape, sharedStyleMap: SharedStyleMap) {
8570
- if (this.isShapeOfType(shape, 'group')) {
8411
+ if (this.isShapeOfType<TLGroupShape>(shape, 'group')) {
8571
8412
  // For groups, ignore the styles of the group shape and instead include the styles of the
8572
8413
  // group's children. These are the shapes that would have their styles changed if the
8573
8414
  // user called `setStyle` on the current selection.
@@ -8687,7 +8528,7 @@ export class Editor extends EventEmitter<TLEventMap> {
8687
8528
  // For groups, ignore the opacity of the group shape and instead include
8688
8529
  // the opacity of the group's children. These are the shapes that would have
8689
8530
  // their opacity changed if the user called `setOpacity` on the current selection.
8690
- if (this.isShapeOfType(shape, 'group')) {
8531
+ if (this.isShapeOfType<TLGroupShape>(shape, 'group')) {
8691
8532
  for (const childId of this.getSortedChildIdsForParent(shape.id)) {
8692
8533
  addShape(childId)
8693
8534
  }
@@ -8748,7 +8589,7 @@ export class Editor extends EventEmitter<TLEventMap> {
8748
8589
  // We can have many deep levels of grouped shape
8749
8590
  // Making a recursive function to look through all the levels
8750
8591
  const addShapeById = (shape: TLShape) => {
8751
- if (this.isShapeOfType(shape, 'group')) {
8592
+ if (this.isShapeOfType<TLGroupShape>(shape, 'group')) {
8752
8593
  const childIds = this.getSortedChildIdsForParent(shape)
8753
8594
  for (const childId of childIds) {
8754
8595
  addShapeById(this.getShape(childId)!)
@@ -8832,7 +8673,7 @@ export class Editor extends EventEmitter<TLEventMap> {
8832
8673
  // We can have many deep levels of grouped shape
8833
8674
  // Making a recursive function to look through all the levels
8834
8675
  const addShapeById = (shape: TLShape) => {
8835
- if (this.isShapeOfType(shape, 'group')) {
8676
+ if (this.isShapeOfType<TLGroupShape>(shape, 'group')) {
8836
8677
  const childIds = this.getSortedChildIdsForParent(shape.id)
8837
8678
  for (const childId of childIds) {
8838
8679
  addShapeById(this.getShape(childId)!)
@@ -9257,7 +9098,7 @@ export class Editor extends EventEmitter<TLEventMap> {
9257
9098
  for (const shape of this.getSelectedShapes()) {
9258
9099
  if (lowestDepth === 0) break
9259
9100
 
9260
- const isFrame = this.isShapeOfType(shape, 'frame')
9101
+ const isFrame = this.isShapeOfType<TLFrameShape>(shape, 'frame')
9261
9102
  const ancestors = this.getShapeAncestors(shape)
9262
9103
  if (isFrame) ancestors.push(shape)
9263
9104
 
@@ -9285,30 +9126,6 @@ export class Editor extends EventEmitter<TLEventMap> {
9285
9126
  }
9286
9127
  }
9287
9128
 
9288
- if (point) {
9289
- const shapesById = new Map<TLShapeId, TLShape>(shapes.map((shape) => [shape.id, shape]))
9290
- const rootShapesFromContent = compact(rootShapeIds.map((id) => shapesById.get(id)))
9291
- if (rootShapesFromContent.length > 0) {
9292
- const targetParent = this.getShapeAtPoint(point, {
9293
- hitInside: true,
9294
- hitFrameInside: true,
9295
- hitLocked: true,
9296
- filter: (shape) => {
9297
- const util = this.getShapeUtil(shape)
9298
- if (!util.canReceiveNewChildrenOfType) return false
9299
- return rootShapesFromContent.every((rootShape) =>
9300
- util.canReceiveNewChildrenOfType!(shape, rootShape.type)
9301
- )
9302
- },
9303
- })
9304
-
9305
- // When pasting at a specific point (e.g. paste-at-cursor) prefer the
9306
- // parent under the pointer so that we don't keep using the original
9307
- // selection's parent (which can keep shapes clipped inside frames).
9308
- pasteParentId = targetParent ? targetParent.id : currentPageId
9309
- }
9310
- }
9311
-
9312
9129
  let isDuplicating = false
9313
9130
 
9314
9131
  if (!isPageId(pasteParentId)) {
@@ -9320,8 +9137,8 @@ export class Editor extends EventEmitter<TLEventMap> {
9320
9137
  if (rootShapeIds.length === 1) {
9321
9138
  const rootShape = shapes.find((s) => s.id === rootShapeIds[0])!
9322
9139
  if (
9323
- this.isShapeOfType(parent, 'frame') &&
9324
- this.isShapeOfType(rootShape, 'frame') &&
9140
+ this.isShapeOfType<TLFrameShape>(parent, 'frame') &&
9141
+ this.isShapeOfType<TLFrameShape>(rootShape, 'frame') &&
9325
9142
  rootShape.props.w === parent?.props.w &&
9326
9143
  rootShape.props.h === parent?.props.h
9327
9144
  ) {
@@ -9496,11 +9313,11 @@ export class Editor extends EventEmitter<TLEventMap> {
9496
9313
  const onlyRoot = rootShapes[0] as TLFrameShape
9497
9314
  // If the old bounds are in the viewport...
9498
9315
  // todo: replace frame references with shapes that can accept children
9499
- if (this.isShapeOfType(onlyRoot, 'frame')) {
9316
+ if (this.isShapeOfType<TLFrameShape>(onlyRoot, 'frame')) {
9500
9317
  while (
9501
9318
  this.getShapesAtPoint(point).some(
9502
9319
  (shape) =>
9503
- this.isShapeOfType(shape, 'frame') &&
9320
+ this.isShapeOfType<TLFrameShape>(shape, 'frame') &&
9504
9321
  shape.props.w === onlyRoot.props.w &&
9505
9322
  shape.props.h === onlyRoot.props.h
9506
9323
  )
@@ -9646,6 +9463,126 @@ export class Editor extends EventEmitter<TLEventMap> {
9646
9463
 
9647
9464
  /* --------------------- Events --------------------- */
9648
9465
 
9466
+ /**
9467
+ * The app's current input state.
9468
+ *
9469
+ * @public
9470
+ */
9471
+ inputs = {
9472
+ /** The most recent pointer down's position in the current page space. */
9473
+ originPagePoint: new Vec(),
9474
+ /** The most recent pointer down's position in screen space. */
9475
+ originScreenPoint: new Vec(),
9476
+ /** The previous pointer position in the current page space. */
9477
+ previousPagePoint: new Vec(),
9478
+ /** The previous pointer position in screen space. */
9479
+ previousScreenPoint: new Vec(),
9480
+ /** The most recent pointer position in the current page space. */
9481
+ currentPagePoint: new Vec(),
9482
+ /** The most recent pointer position in screen space. */
9483
+ currentScreenPoint: new Vec(),
9484
+ /** A set containing the currently pressed keys. */
9485
+ keys: new Set<string>(),
9486
+ /** A set containing the currently pressed buttons. */
9487
+ buttons: new Set<number>(),
9488
+ /** Whether the input is from a pe. */
9489
+ isPen: false,
9490
+ /** Whether the shift key is currently pressed. */
9491
+ shiftKey: false,
9492
+ /** Whether the meta key is currently pressed. */
9493
+ metaKey: false,
9494
+ /** Whether the control or command key is currently pressed. */
9495
+ ctrlKey: false,
9496
+ /** Whether the alt or option key is currently pressed. */
9497
+ altKey: false,
9498
+ /** Whether the user is dragging. */
9499
+ isDragging: false,
9500
+ /** Whether the user is pointing. */
9501
+ isPointing: false,
9502
+ /** Whether the user is pinching. */
9503
+ isPinching: false,
9504
+ /** Whether the user is editing. */
9505
+ isEditing: false,
9506
+ /** Whether the user is panning. */
9507
+ isPanning: false,
9508
+ /** Whether the user is spacebar panning. */
9509
+ isSpacebarPanning: false,
9510
+ /** Velocity of mouse pointer, in pixels per millisecond */
9511
+ pointerVelocity: new Vec(),
9512
+ }
9513
+
9514
+ /**
9515
+ * Update the input points from a pointer, pinch, or wheel event.
9516
+ *
9517
+ * @param info - The event info.
9518
+ */
9519
+ private _updateInputsFromEvent(
9520
+ info: TLPointerEventInfo | TLPinchEventInfo | TLWheelEventInfo
9521
+ ): void {
9522
+ const {
9523
+ pointerVelocity,
9524
+ previousScreenPoint,
9525
+ previousPagePoint,
9526
+ currentScreenPoint,
9527
+ currentPagePoint,
9528
+ originScreenPoint,
9529
+ originPagePoint,
9530
+ } = this.inputs
9531
+
9532
+ const { screenBounds } = this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!
9533
+ const { x: cx, y: cy, z: cz } = unsafe__withoutCapture(() => this.getCamera())
9534
+
9535
+ const sx = info.point.x - screenBounds.x
9536
+ const sy = info.point.y - screenBounds.y
9537
+ const sz = info.point.z ?? 0.5
9538
+
9539
+ previousScreenPoint.setTo(currentScreenPoint)
9540
+ previousPagePoint.setTo(currentPagePoint)
9541
+
9542
+ // The "screen bounds" is relative to the user's actual screen.
9543
+ // The "screen point" is relative to the "screen bounds";
9544
+ // it will be 0,0 when its actual screen position is equal
9545
+ // to screenBounds.point. This is confusing!
9546
+ currentScreenPoint.set(sx, sy)
9547
+ const nx = sx / cz - cx
9548
+ const ny = sy / cz - cy
9549
+ if (isFinite(nx) && isFinite(ny)) {
9550
+ currentPagePoint.set(nx, ny, sz)
9551
+ }
9552
+
9553
+ this.inputs.isPen = info.type === 'pointer' && info.isPen
9554
+
9555
+ // Reset velocity on pointer down, or when a pinch starts or ends
9556
+ if (info.name === 'pointer_down' || this.inputs.isPinching) {
9557
+ pointerVelocity.set(0, 0)
9558
+ originScreenPoint.setTo(currentScreenPoint)
9559
+ originPagePoint.setTo(currentPagePoint)
9560
+ }
9561
+
9562
+ // todo: We only have to do this if there are multiple users in the document
9563
+ this.run(
9564
+ () => {
9565
+ this.store.put([
9566
+ {
9567
+ id: TLPOINTER_ID,
9568
+ typeName: 'pointer',
9569
+ x: currentPagePoint.x,
9570
+ y: currentPagePoint.y,
9571
+ lastActivityTimestamp:
9572
+ // If our pointer moved only because we're following some other user, then don't
9573
+ // update our last activity timestamp; otherwise, update it to the current timestamp.
9574
+ info.type === 'pointer' && info.pointerId === INTERNAL_POINTER_IDS.CAMERA_MOVE
9575
+ ? (this.store.unsafeGetWithoutCapture(TLPOINTER_ID)?.lastActivityTimestamp ??
9576
+ this._tickManager.now)
9577
+ : this._tickManager.now,
9578
+ meta: {},
9579
+ },
9580
+ ])
9581
+ },
9582
+ { history: 'ignore' }
9583
+ )
9584
+ }
9585
+
9649
9586
  /**
9650
9587
  * Dispatch a cancel event.
9651
9588
  *
@@ -9715,22 +9652,19 @@ export class Editor extends EventEmitter<TLEventMap> {
9715
9652
  // weird but true: what `inputs` calls screen-space is actually viewport space. so
9716
9653
  // we need to convert back into true screen space first. we should fix this...
9717
9654
  Vec.Add(
9718
- this.inputs.getCurrentScreenPoint(),
9655
+ this.inputs.currentScreenPoint,
9719
9656
  this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!.screenBounds
9720
9657
  ),
9721
9658
  pointerId: options?.pointerId ?? 0,
9722
9659
  button: options?.button ?? 0,
9723
- isPen: options?.isPen ?? this.inputs.getIsPen(),
9724
- shiftKey: options?.shiftKey ?? this.inputs.getShiftKey(),
9725
- altKey: options?.altKey ?? this.inputs.getAltKey(),
9726
- ctrlKey: options?.ctrlKey ?? this.inputs.getCtrlKey(),
9727
- metaKey: options?.metaKey ?? this.inputs.getMetaKey(),
9728
- accelKey: false,
9660
+ isPen: options?.isPen ?? this.inputs.isPen,
9661
+ shiftKey: options?.shiftKey ?? this.inputs.shiftKey,
9662
+ altKey: options?.altKey ?? this.inputs.altKey,
9663
+ ctrlKey: options?.ctrlKey ?? this.inputs.ctrlKey,
9664
+ metaKey: options?.metaKey ?? this.inputs.metaKey,
9665
+ accelKey: options?.accelKey ?? isAccelKey(this.inputs),
9729
9666
  }
9730
9667
 
9731
- // needs to be calculated second
9732
- event.accelKey = options?.accelKey ?? this.inputs.getAccelKey()
9733
-
9734
9668
  if (options?.immediate) {
9735
9669
  this._flushEventForTick(event)
9736
9670
  } else {
@@ -10103,16 +10037,16 @@ export class Editor extends EventEmitter<TLEventMap> {
10103
10037
  /** @internal */
10104
10038
  @bind
10105
10039
  _setShiftKeyTimeout() {
10106
- this.inputs.setShiftKey(false)
10040
+ this.inputs.shiftKey = false
10107
10041
  this.dispatch({
10108
10042
  type: 'keyboard',
10109
10043
  name: 'key_up',
10110
10044
  key: 'Shift',
10111
- shiftKey: this.inputs.getShiftKey(),
10112
- ctrlKey: this.inputs.getCtrlKey(),
10113
- altKey: this.inputs.getAltKey(),
10114
- metaKey: this.inputs.getMetaKey(),
10115
- accelKey: this.inputs.getAccelKey(),
10045
+ shiftKey: this.inputs.shiftKey,
10046
+ ctrlKey: this.inputs.ctrlKey,
10047
+ altKey: this.inputs.altKey,
10048
+ metaKey: this.inputs.metaKey,
10049
+ accelKey: isAccelKey(this.inputs),
10116
10050
  code: 'ShiftLeft',
10117
10051
  })
10118
10052
  }
@@ -10123,16 +10057,16 @@ export class Editor extends EventEmitter<TLEventMap> {
10123
10057
  /** @internal */
10124
10058
  @bind
10125
10059
  _setAltKeyTimeout() {
10126
- this.inputs.setAltKey(false)
10060
+ this.inputs.altKey = false
10127
10061
  this.dispatch({
10128
10062
  type: 'keyboard',
10129
10063
  name: 'key_up',
10130
10064
  key: 'Alt',
10131
- shiftKey: this.inputs.getShiftKey(),
10132
- ctrlKey: this.inputs.getCtrlKey(),
10133
- altKey: this.inputs.getAltKey(),
10134
- metaKey: this.inputs.getMetaKey(),
10135
- accelKey: this.inputs.getAccelKey(),
10065
+ shiftKey: this.inputs.shiftKey,
10066
+ ctrlKey: this.inputs.ctrlKey,
10067
+ altKey: this.inputs.altKey,
10068
+ metaKey: this.inputs.metaKey,
10069
+ accelKey: isAccelKey(this.inputs),
10136
10070
  code: 'AltLeft',
10137
10071
  })
10138
10072
  }
@@ -10143,16 +10077,16 @@ export class Editor extends EventEmitter<TLEventMap> {
10143
10077
  /** @internal */
10144
10078
  @bind
10145
10079
  _setCtrlKeyTimeout() {
10146
- this.inputs.setCtrlKey(false)
10080
+ this.inputs.ctrlKey = false
10147
10081
  this.dispatch({
10148
10082
  type: 'keyboard',
10149
10083
  name: 'key_up',
10150
10084
  key: 'Ctrl',
10151
- shiftKey: this.inputs.getShiftKey(),
10152
- ctrlKey: this.inputs.getCtrlKey(),
10153
- altKey: this.inputs.getAltKey(),
10154
- metaKey: this.inputs.getMetaKey(),
10155
- accelKey: this.inputs.getAccelKey(),
10085
+ shiftKey: this.inputs.shiftKey,
10086
+ ctrlKey: this.inputs.ctrlKey,
10087
+ altKey: this.inputs.altKey,
10088
+ metaKey: this.inputs.metaKey,
10089
+ accelKey: isAccelKey(this.inputs),
10156
10090
  code: 'ControlLeft',
10157
10091
  })
10158
10092
  }
@@ -10163,16 +10097,16 @@ export class Editor extends EventEmitter<TLEventMap> {
10163
10097
  /** @internal */
10164
10098
  @bind
10165
10099
  _setMetaKeyTimeout() {
10166
- this.inputs.setMetaKey(false)
10100
+ this.inputs.metaKey = false
10167
10101
  this.dispatch({
10168
10102
  type: 'keyboard',
10169
10103
  name: 'key_up',
10170
10104
  key: 'Meta',
10171
- shiftKey: this.inputs.getShiftKey(),
10172
- ctrlKey: this.inputs.getCtrlKey(),
10173
- altKey: this.inputs.getAltKey(),
10174
- metaKey: this.inputs.getMetaKey(),
10175
- accelKey: this.inputs.getAccelKey(),
10105
+ shiftKey: this.inputs.shiftKey,
10106
+ ctrlKey: this.inputs.ctrlKey,
10107
+ altKey: this.inputs.altKey,
10108
+ metaKey: this.inputs.metaKey,
10109
+ accelKey: isAccelKey(this.inputs),
10176
10110
  code: 'MetaLeft',
10177
10111
  })
10178
10112
  }
@@ -10180,6 +10114,9 @@ export class Editor extends EventEmitter<TLEventMap> {
10180
10114
  /** @internal */
10181
10115
  private _restoreToolId = 'select'
10182
10116
 
10117
+ /** @internal */
10118
+ private _pinchStart = 1
10119
+
10183
10120
  /** @internal */
10184
10121
  private _didPinch = false
10185
10122
 
@@ -10286,54 +10223,55 @@ export class Editor extends EventEmitter<TLEventMap> {
10286
10223
  if (info.type === 'misc') {
10287
10224
  // stop panning if the interaction is cancelled or completed
10288
10225
  if (info.name === 'cancel' || info.name === 'complete') {
10289
- this.inputs.setIsDragging(false)
10226
+ this.inputs.isDragging = false
10290
10227
 
10291
- if (this.inputs.getIsPanning()) {
10292
- this.inputs.setIsPanning(false)
10293
- this.inputs.setIsSpacebarPanning(false)
10228
+ if (this.inputs.isPanning) {
10229
+ this.inputs.isPanning = false
10230
+ this.inputs.isSpacebarPanning = false
10294
10231
  this.setCursor({ type: this._prevCursor, rotation: 0 })
10295
10232
  }
10296
10233
  }
10297
10234
 
10298
10235
  this.root.handleEvent(info)
10299
- this.emit('event', info)
10300
10236
  return
10301
10237
  }
10302
10238
 
10303
10239
  if (info.shiftKey) {
10304
10240
  clearTimeout(this._shiftKeyTimeout)
10305
10241
  this._shiftKeyTimeout = -1
10306
- inputs.setShiftKey(true)
10307
- } else if (!info.shiftKey && inputs.getShiftKey() && this._shiftKeyTimeout === -1) {
10242
+ inputs.shiftKey = true
10243
+ } else if (!info.shiftKey && inputs.shiftKey && this._shiftKeyTimeout === -1) {
10308
10244
  this._shiftKeyTimeout = this.timers.setTimeout(this._setShiftKeyTimeout, 150)
10309
10245
  }
10310
10246
 
10311
10247
  if (info.altKey) {
10312
10248
  clearTimeout(this._altKeyTimeout)
10313
10249
  this._altKeyTimeout = -1
10314
- inputs.setAltKey(true)
10315
- } else if (!info.altKey && inputs.getAltKey() && this._altKeyTimeout === -1) {
10250
+ inputs.altKey = true
10251
+ } else if (!info.altKey && inputs.altKey && this._altKeyTimeout === -1) {
10316
10252
  this._altKeyTimeout = this.timers.setTimeout(this._setAltKeyTimeout, 150)
10317
10253
  }
10318
10254
 
10319
10255
  if (info.ctrlKey) {
10320
10256
  clearTimeout(this._ctrlKeyTimeout)
10321
10257
  this._ctrlKeyTimeout = -1
10322
- inputs.setCtrlKey(true)
10323
- } else if (!info.ctrlKey && inputs.getCtrlKey() && this._ctrlKeyTimeout === -1) {
10258
+ inputs.ctrlKey = true
10259
+ } else if (!info.ctrlKey && inputs.ctrlKey && this._ctrlKeyTimeout === -1) {
10324
10260
  this._ctrlKeyTimeout = this.timers.setTimeout(this._setCtrlKeyTimeout, 150)
10325
10261
  }
10326
10262
 
10327
10263
  if (info.metaKey) {
10328
10264
  clearTimeout(this._metaKeyTimeout)
10329
10265
  this._metaKeyTimeout = -1
10330
- inputs.setMetaKey(true)
10331
- } else if (!info.metaKey && inputs.getMetaKey() && this._metaKeyTimeout === -1) {
10266
+ inputs.metaKey = true
10267
+ } else if (!info.metaKey && inputs.metaKey && this._metaKeyTimeout === -1) {
10332
10268
  this._metaKeyTimeout = this.timers.setTimeout(this._setMetaKeyTimeout, 150)
10333
10269
  }
10334
10270
 
10335
- if (!inputs.getIsPointing()) {
10336
- inputs.setIsDragging(false)
10271
+ const { originPagePoint, currentPagePoint } = inputs
10272
+
10273
+ if (!inputs.isPointing) {
10274
+ inputs.isDragging = false
10337
10275
  }
10338
10276
 
10339
10277
  const instanceState = this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!
@@ -10344,29 +10282,29 @@ export class Editor extends EventEmitter<TLEventMap> {
10344
10282
  case 'pinch': {
10345
10283
  if (cameraOptions.isLocked) return
10346
10284
  clearTimeout(this._longPressTimeout)
10347
- this.inputs.updateFromEvent(info)
10285
+ this._updateInputsFromEvent(info)
10348
10286
 
10349
10287
  switch (info.name) {
10350
10288
  case 'pinch_start': {
10351
- if (inputs.getIsPinching()) return
10289
+ if (inputs.isPinching) return
10352
10290
 
10353
- if (!inputs.getIsEditing()) {
10291
+ if (!inputs.isEditing) {
10292
+ this._pinchStart = this.getCamera().z
10354
10293
  if (!this._selectedShapeIdsAtPointerDown.length) {
10355
10294
  this._selectedShapeIdsAtPointerDown = [...pageState.selectedShapeIds]
10356
10295
  }
10357
10296
 
10358
10297
  this._didPinch = true
10359
10298
 
10360
- inputs.setIsPinching(true)
10299
+ inputs.isPinching = true
10361
10300
 
10362
10301
  this.interrupt()
10363
10302
  }
10364
10303
 
10365
- this.emit('event', info)
10366
10304
  return // Stop here!
10367
10305
  }
10368
10306
  case 'pinch': {
10369
- if (!inputs.getIsPinching()) return
10307
+ if (!inputs.isPinching) return
10370
10308
 
10371
10309
  const {
10372
10310
  point: { z = 1 },
@@ -10397,14 +10335,13 @@ export class Editor extends EventEmitter<TLEventMap> {
10397
10335
  { immediate: true }
10398
10336
  )
10399
10337
 
10400
- this.emit('event', info)
10401
10338
  return // Stop here!
10402
10339
  }
10403
10340
  case 'pinch_end': {
10404
- if (!inputs.getIsPinching()) return this
10341
+ if (!inputs.isPinching) return this
10405
10342
 
10406
10343
  // Stop pinching
10407
- inputs.setIsPinching(false)
10344
+ inputs.isPinching = false
10408
10345
 
10409
10346
  // Stash and clear the shapes that were selected when the pinch started
10410
10347
  const { _selectedShapeIdsAtPointerDown: shapesToReselect } = this
@@ -10424,7 +10361,6 @@ export class Editor extends EventEmitter<TLEventMap> {
10424
10361
  }
10425
10362
  }
10426
10363
 
10427
- this.emit('event', info)
10428
10364
  return // Stop here!
10429
10365
  }
10430
10366
  }
@@ -10432,7 +10368,7 @@ export class Editor extends EventEmitter<TLEventMap> {
10432
10368
  case 'wheel': {
10433
10369
  if (cameraOptions.isLocked) return
10434
10370
 
10435
- this.inputs.updateFromEvent(info)
10371
+ this._updateInputsFromEvent(info)
10436
10372
 
10437
10373
  const { panSpeed, zoomSpeed } = cameraOptions
10438
10374
  let wheelBehavior = cameraOptions.wheelBehavior
@@ -10463,7 +10399,7 @@ export class Editor extends EventEmitter<TLEventMap> {
10463
10399
  switch (behavior) {
10464
10400
  case 'zoom': {
10465
10401
  // Zoom in on current screen point using the wheel delta
10466
- const { x, y } = this.inputs.getCurrentScreenPoint()
10402
+ const { x, y } = this.inputs.currentScreenPoint
10467
10403
  let delta = dz
10468
10404
 
10469
10405
  // If we're forcing zoom, then we need to do the wheel normalization math here
@@ -10480,8 +10416,6 @@ export class Editor extends EventEmitter<TLEventMap> {
10480
10416
  immediate: true,
10481
10417
  })
10482
10418
  this.maybeTrackPerformance('Zooming')
10483
- this.root.handleEvent(info)
10484
- this.emit('event', info)
10485
10419
  return
10486
10420
  }
10487
10421
  case 'pan': {
@@ -10490,8 +10424,6 @@ export class Editor extends EventEmitter<TLEventMap> {
10490
10424
  immediate: true,
10491
10425
  })
10492
10426
  this.maybeTrackPerformance('Panning')
10493
- this.root.handleEvent(info)
10494
- this.emit('event', info)
10495
10427
  return
10496
10428
  }
10497
10429
  }
@@ -10500,9 +10432,9 @@ export class Editor extends EventEmitter<TLEventMap> {
10500
10432
  }
10501
10433
  case 'pointer': {
10502
10434
  // Ignore pointer events while we're pinching
10503
- if (inputs.getIsPinching()) return
10435
+ if (inputs.isPinching) return
10504
10436
 
10505
- this.inputs.updateFromEvent(info)
10437
+ this._updateInputsFromEvent(info)
10506
10438
  const { isPen } = info
10507
10439
  const { isPenMode } = instanceState
10508
10440
 
@@ -10511,7 +10443,7 @@ export class Editor extends EventEmitter<TLEventMap> {
10511
10443
  // If we're in pen mode and the input is not a pen type, then stop here
10512
10444
  if (isPenMode && !isPen) return
10513
10445
 
10514
- if (!this.inputs.getIsPanning()) {
10446
+ if (!this.inputs.isPanning) {
10515
10447
  // Start a long press timeout
10516
10448
  this._longPressTimeout = this.timers.setTimeout(() => {
10517
10449
  const vsb = this.getViewportScreenBounds()
@@ -10521,7 +10453,7 @@ export class Editor extends EventEmitter<TLEventMap> {
10521
10453
  // viewport bounds, and will be again when this event is handled...
10522
10454
  // so we need to counter-adjust from the stored value so that the
10523
10455
  // new value is set correctly.
10524
- point: this.inputs.getOriginScreenPoint().clone().addXY(vsb.x, vsb.y),
10456
+ point: this.inputs.originScreenPoint.clone().addXY(vsb.x, vsb.y),
10525
10457
  name: 'long_press',
10526
10458
  })
10527
10459
  }, this.options.longPressDurationMs)
@@ -10538,8 +10470,8 @@ export class Editor extends EventEmitter<TLEventMap> {
10538
10470
  inputs.buttons.add(info.button)
10539
10471
 
10540
10472
  // Start pointing and stop dragging
10541
- inputs.setIsPointing(true)
10542
- inputs.setIsDragging(false)
10473
+ inputs.isPointing = true
10474
+ inputs.isDragging = false
10543
10475
 
10544
10476
  // If pen mode is off but we're not already in pen mode, turn that on
10545
10477
  if (!isPenMode && isPen) this.updateInstanceState({ isPenMode: true })
@@ -10551,16 +10483,16 @@ export class Editor extends EventEmitter<TLEventMap> {
10551
10483
  this.setCurrentTool('eraser')
10552
10484
  } else if (info.button === MIDDLE_MOUSE_BUTTON) {
10553
10485
  // Middle mouse pan activates panning unless we're already panning (with spacebar)
10554
- if (!this.inputs.getIsPanning()) {
10486
+ if (!this.inputs.isPanning) {
10555
10487
  this._prevCursor = this.getInstanceState().cursor.type
10556
10488
  }
10557
- this.inputs.setIsPanning(true)
10489
+ this.inputs.isPanning = true
10558
10490
  clearTimeout(this._longPressTimeout)
10559
10491
  }
10560
10492
 
10561
10493
  // We might be panning because we did a middle mouse click, or because we're holding spacebar and started a regular click
10562
10494
  // Also stop here, we don't want the state chart to receive the event
10563
- if (this.inputs.getIsPanning()) {
10495
+ if (this.inputs.isPanning) {
10564
10496
  this.stopCameraAnimation()
10565
10497
  this.setCursor({ type: 'grabbing', rotation: 0 })
10566
10498
  return this
@@ -10575,10 +10507,9 @@ export class Editor extends EventEmitter<TLEventMap> {
10575
10507
  const { x: cx, y: cy, z: cz } = unsafe__withoutCapture(() => this.getCamera())
10576
10508
 
10577
10509
  // If we've started panning, then clear any long press timeout
10578
- if (this.inputs.getIsPanning() && this.inputs.getIsPointing()) {
10510
+ if (this.inputs.isPanning && this.inputs.isPointing) {
10579
10511
  // Handle spacebar / middle mouse button panning
10580
- const currentScreenPoint = this.inputs.getCurrentScreenPoint()
10581
- const previousScreenPoint = this.inputs.getPreviousScreenPoint()
10512
+ const { currentScreenPoint, previousScreenPoint } = this.inputs
10582
10513
  const offset = Vec.Sub(currentScreenPoint, previousScreenPoint)
10583
10514
  this.setCamera(new Vec(cx + offset.x / cz, cy + offset.y / cz, cz), {
10584
10515
  immediate: true,
@@ -10588,25 +10519,24 @@ export class Editor extends EventEmitter<TLEventMap> {
10588
10519
  }
10589
10520
 
10590
10521
  if (
10591
- inputs.getIsPointing() &&
10592
- !inputs.getIsDragging() &&
10593
- Vec.Dist2(inputs.getOriginPagePoint(), inputs.getCurrentPagePoint()) *
10594
- this.getZoomLevel() >
10522
+ inputs.isPointing &&
10523
+ !inputs.isDragging &&
10524
+ Vec.Dist2(originPagePoint, currentPagePoint) * this.getZoomLevel() >
10595
10525
  (instanceState.isCoarsePointer
10596
10526
  ? this.options.coarseDragDistanceSquared
10597
10527
  : this.options.dragDistanceSquared) /
10598
10528
  cz
10599
10529
  ) {
10600
10530
  // Start dragging
10601
- inputs.setIsDragging(true)
10531
+ inputs.isDragging = true
10602
10532
  clearTimeout(this._longPressTimeout)
10603
10533
  }
10604
10534
  break
10605
10535
  }
10606
10536
  case 'pointer_up': {
10607
10537
  // Stop dragging / pointing
10608
- inputs.setIsDragging(false)
10609
- inputs.setIsPointing(false)
10538
+ inputs.isDragging = false
10539
+ inputs.isPointing = false
10610
10540
  clearTimeout(this._longPressTimeout)
10611
10541
 
10612
10542
  // Remove the button from the buttons set
@@ -10623,12 +10553,12 @@ export class Editor extends EventEmitter<TLEventMap> {
10623
10553
  info.button = 0
10624
10554
  }
10625
10555
 
10626
- if (inputs.getIsPanning()) {
10556
+ if (inputs.isPanning) {
10627
10557
  if (!inputs.keys.has('Space')) {
10628
- inputs.setIsPanning(false)
10629
- inputs.setIsSpacebarPanning(false)
10558
+ inputs.isPanning = false
10559
+ inputs.isSpacebarPanning = false
10630
10560
  }
10631
- const slideDirection = this.inputs.getPointerVelocity()
10561
+ const slideDirection = this.inputs.pointerVelocity
10632
10562
  const slideSpeed = Math.min(2, slideDirection.len())
10633
10563
 
10634
10564
  switch (info.button) {
@@ -10672,49 +10602,44 @@ export class Editor extends EventEmitter<TLEventMap> {
10672
10602
  // Add the key from the keys set
10673
10603
  inputs.keys.add(info.code)
10674
10604
 
10675
- if (this.options.spacebarPanning) {
10676
- // If the space key is pressed (but meta / control isn't!) activate panning
10677
- if (info.code === 'Space' && !info.ctrlKey) {
10678
- if (!this.inputs.getIsPanning()) {
10679
- this._prevCursor = instanceState.cursor.type
10680
- }
10681
-
10682
- this.inputs.setIsPanning(true)
10683
- this.inputs.setIsSpacebarPanning(true)
10684
- clearTimeout(this._longPressTimeout)
10685
- this.setCursor({
10686
- type: this.inputs.getIsPointing() ? 'grabbing' : 'grab',
10687
- rotation: 0,
10688
- })
10605
+ // If the space key is pressed (but meta / control isn't!) activate panning
10606
+ if (info.code === 'Space' && !info.ctrlKey) {
10607
+ if (!this.inputs.isPanning) {
10608
+ this._prevCursor = instanceState.cursor.type
10689
10609
  }
10690
10610
 
10691
- if (this.inputs.getIsSpacebarPanning()) {
10692
- let offset: Vec | undefined
10693
- switch (info.code) {
10694
- case 'ArrowUp': {
10695
- offset = new Vec(0, -1)
10696
- break
10697
- }
10698
- case 'ArrowRight': {
10699
- offset = new Vec(1, 0)
10700
- break
10701
- }
10702
- case 'ArrowDown': {
10703
- offset = new Vec(0, 1)
10704
- break
10705
- }
10706
- case 'ArrowLeft': {
10707
- offset = new Vec(-1, 0)
10708
- break
10709
- }
10710
- }
10611
+ this.inputs.isPanning = true
10612
+ this.inputs.isSpacebarPanning = true
10613
+ clearTimeout(this._longPressTimeout)
10614
+ this.setCursor({ type: this.inputs.isPointing ? 'grabbing' : 'grab', rotation: 0 })
10615
+ }
10711
10616
 
10712
- if (offset) {
10713
- const bounds = this.getViewportPageBounds()
10714
- const next = bounds.clone().translate(offset.mulV({ x: bounds.w, y: bounds.h }))
10715
- this._animateToViewport(next, { animation: { duration: 320 } })
10617
+ if (this.inputs.isSpacebarPanning) {
10618
+ let offset: Vec | undefined
10619
+ switch (info.code) {
10620
+ case 'ArrowUp': {
10621
+ offset = new Vec(0, -1)
10622
+ break
10623
+ }
10624
+ case 'ArrowRight': {
10625
+ offset = new Vec(1, 0)
10626
+ break
10627
+ }
10628
+ case 'ArrowDown': {
10629
+ offset = new Vec(0, 1)
10630
+ break
10631
+ }
10632
+ case 'ArrowLeft': {
10633
+ offset = new Vec(-1, 0)
10634
+ break
10716
10635
  }
10717
10636
  }
10637
+
10638
+ if (offset) {
10639
+ const bounds = this.getViewportPageBounds()
10640
+ const next = bounds.clone().translate(offset.mulV({ x: bounds.w, y: bounds.h }))
10641
+ this._animateToViewport(next, { animation: { duration: 320 } })
10642
+ }
10718
10643
  }
10719
10644
 
10720
10645
  break
@@ -10723,17 +10648,15 @@ export class Editor extends EventEmitter<TLEventMap> {
10723
10648
  // Remove the key from the keys set
10724
10649
  inputs.keys.delete(info.code)
10725
10650
 
10726
- if (this.options.spacebarPanning) {
10727
- // If we've lifted the space key,
10728
- if (info.code === 'Space') {
10729
- if (this.inputs.buttons.has(MIDDLE_MOUSE_BUTTON)) {
10730
- // If we're still middle dragging, continue panning
10731
- } else {
10732
- // otherwise, stop panning
10733
- this.inputs.setIsPanning(false)
10734
- this.inputs.setIsSpacebarPanning(false)
10735
- this.setCursor({ type: this._prevCursor, rotation: 0 })
10736
- }
10651
+ // If we've lifted the space key,
10652
+ if (info.code === 'Space') {
10653
+ if (this.inputs.buttons.has(MIDDLE_MOUSE_BUTTON)) {
10654
+ // If we're still middle dragging, continue panning
10655
+ } else {
10656
+ // otherwise, stop panning
10657
+ this.inputs.isPanning = false
10658
+ this.inputs.isSpacebarPanning = false
10659
+ this.setCursor({ type: this._prevCursor, rotation: 0 })
10737
10660
  }
10738
10661
  }
10739
10662
  break
@@ -10807,10 +10730,7 @@ function alertMaxShapes(editor: Editor, pageId = editor.getCurrentPageId()) {
10807
10730
 
10808
10731
  function applyPartialToRecordWithProps<
10809
10732
  T extends UnknownRecord & { type: string; props: object; meta: object },
10810
- >(
10811
- prev: T,
10812
- partial?: T extends T ? Omit<Partial<T>, 'props'> & { props?: Partial<T['props']> } : never
10813
- ): T {
10733
+ >(prev: T, partial?: Partial<T> & { props?: Partial<T['props']> }): T {
10814
10734
  if (!partial) return prev
10815
10735
  let next = null as null | T
10816
10736
  const entries = Object.entries(partial)