@tldraw/editor 4.3.0-canary.8fb8d4ef1572 → 4.3.0-canary.9433fac41058

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 (172) hide show
  1. package/dist-cjs/index.d.ts +443 -120
  2. package/dist-cjs/index.js +6 -1
  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 +1 -17
  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 +1 -3
  10. package/dist-cjs/lib/constants.js.map +2 -2
  11. package/dist-cjs/lib/editor/Editor.js +292 -274
  12. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  13. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +18 -17
  14. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +3 -3
  15. package/dist-cjs/lib/editor/derivations/parentsToChildren.js +12 -3
  16. package/dist-cjs/lib/editor/derivations/parentsToChildren.js.map +2 -2
  17. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js +1 -1
  18. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +2 -2
  19. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js +5 -6
  20. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js.map +2 -2
  21. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js +591 -0
  22. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js.map +7 -0
  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 +1 -22
  26. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +2 -2
  27. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +31 -23
  28. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  29. package/dist-cjs/lib/editor/shapes/group/DashedOutlineBox.js +1 -1
  30. package/dist-cjs/lib/editor/shapes/group/DashedOutlineBox.js.map +2 -2
  31. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +3 -3
  32. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +2 -2
  33. package/dist-cjs/lib/editor/types/emit-types.js.map +1 -1
  34. package/dist-cjs/lib/exports/parseCss.js +1 -1
  35. package/dist-cjs/lib/exports/parseCss.js.map +2 -2
  36. package/dist-cjs/lib/globals/environment.js +45 -9
  37. package/dist-cjs/lib/globals/environment.js.map +2 -2
  38. package/dist-cjs/lib/globals/menus.js +1 -1
  39. package/dist-cjs/lib/globals/menus.js.map +2 -2
  40. package/dist-cjs/lib/hooks/useCoarsePointer.js +14 -29
  41. package/dist-cjs/lib/hooks/useCoarsePointer.js.map +2 -2
  42. package/dist-cjs/lib/hooks/useEvent.js +1 -1
  43. package/dist-cjs/lib/hooks/useEvent.js.map +2 -2
  44. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js.map +2 -2
  45. package/dist-cjs/lib/hooks/useGestureEvents.js +1 -1
  46. package/dist-cjs/lib/hooks/useGestureEvents.js.map +2 -2
  47. package/dist-cjs/lib/hooks/usePassThroughMouseOverEvents.js.map +2 -2
  48. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +2 -2
  49. package/dist-cjs/lib/hooks/useScreenBounds.js.map +2 -2
  50. package/dist-cjs/lib/hooks/useStateAttribute.js +4 -1
  51. package/dist-cjs/lib/hooks/useStateAttribute.js.map +2 -2
  52. package/dist-cjs/lib/hooks/useTransform.js.map +1 -1
  53. package/dist-cjs/lib/hooks/useZoomCss.js +4 -8
  54. package/dist-cjs/lib/hooks/useZoomCss.js.map +2 -2
  55. package/dist-cjs/lib/options.js +6 -1
  56. package/dist-cjs/lib/options.js.map +2 -2
  57. package/dist-cjs/lib/primitives/Box.js +3 -0
  58. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  59. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +1 -0
  60. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  61. package/dist-cjs/lib/utils/rotation.js +1 -1
  62. package/dist-cjs/lib/utils/rotation.js.map +2 -2
  63. package/dist-cjs/version.js +3 -3
  64. package/dist-cjs/version.js.map +1 -1
  65. package/dist-esm/index.d.mts +443 -120
  66. package/dist-esm/index.mjs +7 -2
  67. package/dist-esm/index.mjs.map +2 -2
  68. package/dist-esm/lib/components/ErrorBoundary.mjs.map +1 -1
  69. package/dist-esm/lib/components/GeometryDebuggingView.mjs +1 -17
  70. package/dist-esm/lib/components/GeometryDebuggingView.mjs.map +2 -2
  71. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +3 -3
  72. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  73. package/dist-esm/lib/constants.mjs +1 -3
  74. package/dist-esm/lib/constants.mjs.map +2 -2
  75. package/dist-esm/lib/editor/Editor.mjs +293 -277
  76. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  77. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +18 -17
  78. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +3 -3
  79. package/dist-esm/lib/editor/derivations/parentsToChildren.mjs +13 -4
  80. package/dist-esm/lib/editor/derivations/parentsToChildren.mjs.map +2 -2
  81. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs +1 -1
  82. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +2 -2
  83. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs +5 -6
  84. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs.map +2 -2
  85. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs +573 -0
  86. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs.map +7 -0
  87. package/dist-esm/lib/editor/managers/SnapManager/SnapManager.mjs +1 -1
  88. package/dist-esm/lib/editor/managers/SnapManager/SnapManager.mjs.map +2 -2
  89. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs +1 -22
  90. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +2 -2
  91. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +31 -23
  92. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  93. package/dist-esm/lib/editor/shapes/group/DashedOutlineBox.mjs +1 -1
  94. package/dist-esm/lib/editor/shapes/group/DashedOutlineBox.mjs.map +2 -2
  95. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +3 -3
  96. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +2 -2
  97. package/dist-esm/lib/exports/parseCss.mjs +1 -1
  98. package/dist-esm/lib/exports/parseCss.mjs.map +2 -2
  99. package/dist-esm/lib/globals/environment.mjs +45 -9
  100. package/dist-esm/lib/globals/environment.mjs.map +2 -2
  101. package/dist-esm/lib/globals/menus.mjs +1 -1
  102. package/dist-esm/lib/globals/menus.mjs.map +2 -2
  103. package/dist-esm/lib/hooks/useCoarsePointer.mjs +15 -30
  104. package/dist-esm/lib/hooks/useCoarsePointer.mjs.map +2 -2
  105. package/dist-esm/lib/hooks/useEvent.mjs +1 -1
  106. package/dist-esm/lib/hooks/useEvent.mjs.map +2 -2
  107. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs.map +2 -2
  108. package/dist-esm/lib/hooks/useGestureEvents.mjs +1 -1
  109. package/dist-esm/lib/hooks/useGestureEvents.mjs.map +2 -2
  110. package/dist-esm/lib/hooks/usePassThroughMouseOverEvents.mjs.map +2 -2
  111. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +2 -2
  112. package/dist-esm/lib/hooks/useScreenBounds.mjs.map +2 -2
  113. package/dist-esm/lib/hooks/useStateAttribute.mjs +4 -1
  114. package/dist-esm/lib/hooks/useStateAttribute.mjs.map +2 -2
  115. package/dist-esm/lib/hooks/useTransform.mjs.map +1 -1
  116. package/dist-esm/lib/hooks/useZoomCss.mjs +4 -8
  117. package/dist-esm/lib/hooks/useZoomCss.mjs.map +2 -2
  118. package/dist-esm/lib/options.mjs +6 -1
  119. package/dist-esm/lib/options.mjs.map +2 -2
  120. package/dist-esm/lib/primitives/Box.mjs +3 -0
  121. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  122. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +1 -0
  123. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  124. package/dist-esm/lib/utils/rotation.mjs +1 -1
  125. package/dist-esm/lib/utils/rotation.mjs.map +2 -2
  126. package/dist-esm/version.mjs +3 -3
  127. package/dist-esm/version.mjs.map +1 -1
  128. package/editor.css +14 -12
  129. package/package.json +18 -16
  130. package/src/index.ts +4 -1
  131. package/src/lib/components/ErrorBoundary.tsx +1 -1
  132. package/src/lib/components/GeometryDebuggingView.tsx +1 -19
  133. package/src/lib/components/default-components/DefaultCanvas.tsx +4 -3
  134. package/src/lib/config/TLUserPreferences.test.ts +40 -0
  135. package/src/lib/constants.ts +0 -2
  136. package/src/lib/editor/Editor.test.ts +140 -0
  137. package/src/lib/editor/Editor.ts +378 -320
  138. package/src/lib/editor/derivations/notVisibleShapes.ts +37 -23
  139. package/src/lib/editor/derivations/parentsToChildren.ts +18 -7
  140. package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +17 -31
  141. package/src/lib/editor/managers/ClickManager/ClickManager.ts +1 -1
  142. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +129 -79
  143. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.ts +10 -6
  144. package/src/lib/editor/managers/InputsManager/InputsManager.ts +566 -0
  145. package/src/lib/editor/managers/ScribbleManager/ScribbleManager.test.ts +0 -4
  146. package/src/lib/editor/managers/SnapManager/SnapManager.test.ts +12 -0
  147. package/src/lib/editor/managers/SnapManager/SnapManager.ts +1 -1
  148. package/src/lib/editor/managers/TickManager/TickManager.test.ts +40 -107
  149. package/src/lib/editor/managers/TickManager/TickManager.ts +2 -32
  150. package/src/lib/editor/shapes/ShapeUtil.ts +67 -24
  151. package/src/lib/editor/shapes/group/DashedOutlineBox.tsx +1 -1
  152. package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +3 -3
  153. package/src/lib/editor/types/emit-types.ts +3 -1
  154. package/src/lib/exports/parseCss.test.ts +1 -0
  155. package/src/lib/exports/parseCss.ts +1 -1
  156. package/src/lib/globals/environment.ts +65 -10
  157. package/src/lib/globals/menus.ts +1 -1
  158. package/src/lib/hooks/useCoarsePointer.ts +16 -59
  159. package/src/lib/hooks/useEvent.tsx +1 -1
  160. package/src/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.ts +1 -1
  161. package/src/lib/hooks/useGestureEvents.ts +2 -2
  162. package/src/lib/hooks/usePassThroughMouseOverEvents.ts +1 -1
  163. package/src/lib/hooks/usePassThroughWheelEvents.ts +1 -1
  164. package/src/lib/hooks/useScreenBounds.ts +1 -1
  165. package/src/lib/hooks/useStateAttribute.ts +4 -1
  166. package/src/lib/hooks/useTransform.ts +1 -1
  167. package/src/lib/hooks/useZoomCss.ts +3 -8
  168. package/src/lib/options.ts +32 -0
  169. package/src/lib/primitives/Box.ts +9 -0
  170. package/src/lib/primitives/geometry/Geometry2d.ts +1 -0
  171. package/src/lib/utils/rotation.ts +1 -1
  172. package/src/version.ts +3 -3
@@ -42,7 +42,6 @@ import {
42
42
  TLInstance,
43
43
  TLInstancePageState,
44
44
  TLInstancePresence,
45
- TLPOINTER_ID,
46
45
  TLPage,
47
46
  TLPageId,
48
47
  TLParentId,
@@ -109,7 +108,6 @@ import {
109
108
  MIDDLE_MOUSE_BUTTON,
110
109
  RIGHT_MOUSE_BUTTON,
111
110
  STYLUS_ERASER_BUTTON,
112
- ZOOM_TO_FIT_PADDING,
113
111
  } from '../constants'
114
112
  import { exportToSvg } from '../exports/exportToSvg'
115
113
  import { getSvgAsImage } from '../exports/getSvgAsImage'
@@ -135,7 +133,6 @@ import {
135
133
  parseDeepLinkString,
136
134
  } from '../utils/deepLinks'
137
135
  import { getIncrementedName } from '../utils/getIncrementedName'
138
- import { isAccelKey } from '../utils/keyboard'
139
136
  import { getReorderingShapesChanges } from '../utils/reorderShapes'
140
137
  import { TLTextOptions, TiptapEditor } from '../utils/richText'
141
138
  import { applyRotationToSnapshotShapes, getRotationSnapshot } from '../utils/rotation'
@@ -149,22 +146,18 @@ import { EdgeScrollManager } from './managers/EdgeScrollManager/EdgeScrollManage
149
146
  import { FocusManager } from './managers/FocusManager/FocusManager'
150
147
  import { FontManager } from './managers/FontManager/FontManager'
151
148
  import { HistoryManager } from './managers/HistoryManager/HistoryManager'
149
+ import { InputsManager } from './managers/InputsManager/InputsManager'
152
150
  import { ScribbleManager } from './managers/ScribbleManager/ScribbleManager'
153
151
  import { SnapManager } from './managers/SnapManager/SnapManager'
154
152
  import { TextManager } from './managers/TextManager/TextManager'
155
153
  import { TickManager } from './managers/TickManager/TickManager'
156
154
  import { UserPreferencesManager } from './managers/UserPreferencesManager/UserPreferencesManager'
157
- import { ShapeUtil, TLGeometryOpts, TLResizeMode } from './shapes/ShapeUtil'
155
+ import { ShapeUtil, TLEditStartInfo, TLGeometryOpts, TLResizeMode } from './shapes/ShapeUtil'
158
156
  import { RootState } from './tools/RootState'
159
157
  import { StateNode, TLStateNodeConstructor } from './tools/StateNode'
160
158
  import { TLContent } from './types/clipboard-types'
161
159
  import { TLEventMap } from './types/emit-types'
162
- import {
163
- TLEventInfo,
164
- TLPinchEventInfo,
165
- TLPointerEventInfo,
166
- TLWheelEventInfo,
167
- } from './types/event-types'
160
+ import { TLEventInfo, TLPointerEventInfo } from './types/event-types'
168
161
  import { TLExternalAsset, TLExternalContent } from './types/external-content'
169
162
  import { TLHistoryBatchOptions } from './types/history-types'
170
163
  import {
@@ -195,7 +188,7 @@ export type TLResizeShapeOptions = Partial<{
195
188
  /** @public */
196
189
  export interface TLEditorOptions {
197
190
  /**
198
- * The Store instance to use for keeping the app's data. This may be prepopulated, e.g. by loading
191
+ * The Store instance to use for keeping the editor's data. This may be prepopulated, e.g. by loading
199
192
  * from a server or database.
200
193
  */
201
194
  store: TLStore
@@ -333,6 +326,8 @@ export class Editor extends EventEmitter<TLEventMap> {
333
326
 
334
327
  this._tickManager = new TickManager(this)
335
328
 
329
+ this.inputs = new InputsManager(this)
330
+
336
331
  class NewRoot extends RootState {
337
332
  static override initial = initialState ?? ''
338
333
  }
@@ -867,7 +862,7 @@ export class Editor extends EventEmitter<TLEventMap> {
867
862
  }
868
863
 
869
864
  /**
870
- * A set of functions to call when the app is disposed.
865
+ * A set of functions to call when the editor is disposed.
871
866
  *
872
867
  * @public
873
868
  */
@@ -880,11 +875,21 @@ export class Editor extends EventEmitter<TLEventMap> {
880
875
  */
881
876
  isDisposed = false
882
877
 
883
- /** @internal */
884
- private readonly _tickManager
878
+ /**
879
+ * A manager for the editor's tick events.
880
+ *
881
+ * @internal */
882
+ private readonly _tickManager: TickManager
885
883
 
886
884
  /**
887
- * A manager for the app's snapping feature.
885
+ * A manager for the editor's input state.
886
+ *
887
+ * @public
888
+ */
889
+ readonly inputs: InputsManager
890
+
891
+ /**
892
+ * A manager for the editor's snapping feature.
888
893
  *
889
894
  * @public
890
895
  */
@@ -969,6 +974,7 @@ export class Editor extends EventEmitter<TLEventMap> {
969
974
  this.disposables.clear()
970
975
  this.store.dispose()
971
976
  this.isDisposed = true
977
+ this.emit('dispose')
972
978
  }
973
979
 
974
980
  /* ------------------- Shape Utils ------------------ */
@@ -1060,7 +1066,7 @@ export class Editor extends EventEmitter<TLEventMap> {
1060
1066
  /* --------------------- History -------------------- */
1061
1067
 
1062
1068
  /**
1063
- * A manager for the app's history.
1069
+ * A manager for the editor's history.
1064
1070
  *
1065
1071
  * @readonly
1066
1072
  */
@@ -1084,14 +1090,18 @@ export class Editor extends EventEmitter<TLEventMap> {
1084
1090
  }
1085
1091
 
1086
1092
  /**
1087
- * Whether the app can undo.
1093
+ * Whether the editor can undo.
1088
1094
  *
1089
1095
  * @public
1090
1096
  */
1091
- @computed getCanUndo(): boolean {
1097
+ @computed canUndo(): boolean {
1092
1098
  return this.history.getNumUndos() > 0
1093
1099
  }
1094
1100
 
1101
+ getCanUndo() {
1102
+ return this.canUndo()
1103
+ }
1104
+
1095
1105
  /**
1096
1106
  * Redo to the next mark.
1097
1107
  *
@@ -1109,20 +1119,24 @@ export class Editor extends EventEmitter<TLEventMap> {
1109
1119
  return this
1110
1120
  }
1111
1121
 
1112
- clearHistory() {
1113
- this.history.clear()
1114
- return this
1115
- }
1116
-
1117
1122
  /**
1118
- * Whether the app can redo.
1123
+ * Whether the editor can redo.
1119
1124
  *
1120
1125
  * @public
1121
1126
  */
1122
- @computed getCanRedo(): boolean {
1127
+ @computed canRedo(): boolean {
1123
1128
  return this.history.getNumRedos() > 0
1124
1129
  }
1125
1130
 
1131
+ getCanRedo() {
1132
+ return this.canRedo()
1133
+ }
1134
+
1135
+ clearHistory() {
1136
+ this.history.clear()
1137
+ return this
1138
+ }
1139
+
1126
1140
  /**
1127
1141
  * Create a new "mark", or stopping point, in the undo redo history. Creating a mark will clear
1128
1142
  * any redos. You typically want to do this just before a user interaction begins or is handled.
@@ -1296,7 +1310,7 @@ export class Editor extends EventEmitter<TLEventMap> {
1296
1310
  }),
1297
1311
  selectionCount: this.getSelectedShapes().length,
1298
1312
  editingShape: editingShapeId ? this.getShape(editingShapeId) : undefined,
1299
- inputs: this.inputs,
1313
+ inputs: this.inputs.toJson(),
1300
1314
  pageState: this.getCurrentPageState(),
1301
1315
  instanceState: this.getInstanceState(),
1302
1316
  collaboratorCount: this.getCollaboratorsOnCurrentPage().length,
@@ -1321,7 +1335,7 @@ export class Editor extends EventEmitter<TLEventMap> {
1321
1335
  * we're in a transaction that's about to be rolled back due to the same error we're currently
1322
1336
  * reporting.
1323
1337
  *
1324
- * Instead, to listen to changes to this value, you need to listen to app's `crash` event.
1338
+ * Instead, to listen to changes to this value, you need to listen to editor's `crash` event.
1325
1339
  *
1326
1340
  * @internal
1327
1341
  */
@@ -2024,7 +2038,7 @@ export class Editor extends EventEmitter<TLEventMap> {
2024
2038
  }
2025
2039
 
2026
2040
  /**
2027
- * The id of the app's only selected shape.
2041
+ * The id of the editor's only selected shape.
2028
2042
  *
2029
2043
  * @returns Null if there is no shape or more than one selected shape, otherwise the selected shape's id.
2030
2044
  *
@@ -2036,7 +2050,7 @@ export class Editor extends EventEmitter<TLEventMap> {
2036
2050
  }
2037
2051
 
2038
2052
  /**
2039
- * The app's only selected shape.
2053
+ * The editor's only selected shape.
2040
2054
  *
2041
2055
  * @returns Null if there is no shape or more than one selected shape, otherwise the selected shape.
2042
2056
  *
@@ -2277,6 +2291,29 @@ export class Editor extends EventEmitter<TLEventMap> {
2277
2291
  return editingShapeId ? this.getShape(editingShapeId) : undefined
2278
2292
  }
2279
2293
 
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
+
2280
2317
  /**
2281
2318
  * Set the current editing shape.
2282
2319
  *
@@ -2292,44 +2329,59 @@ export class Editor extends EventEmitter<TLEventMap> {
2292
2329
  */
2293
2330
  setEditingShape(shape: TLShapeId | TLShape | null): this {
2294
2331
  const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
2295
- this.setRichTextEditor(null)
2296
- const prevEditingShapeId = this.getEditingShapeId()
2297
- if (id !== prevEditingShapeId) {
2298
- if (id) {
2299
- const shape = this.getShape(id)
2300
- if (shape && this.getShapeUtil(shape).canEdit(shape)) {
2301
- this.run(
2302
- () => {
2303
- this._updateCurrentPageState({ editingShapeId: id })
2304
- if (prevEditingShapeId) {
2305
- const prevEditingShape = this.getShape(prevEditingShapeId)
2306
- if (prevEditingShape) {
2307
- this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape)
2308
- }
2309
- }
2310
- this.getShapeUtil(shape).onEditStart?.(shape)
2311
- },
2312
- { history: 'ignore' }
2313
- )
2314
- return this
2315
- }
2316
- }
2317
2332
 
2318
- // Either we just set the editing id to null, or the shape was missing or not editable
2333
+ if (!id) {
2334
+ // setting the editing shape to null
2319
2335
  this.run(
2320
2336
  () => {
2321
- this._updateCurrentPageState({ editingShapeId: null })
2322
- this._currentRichTextEditor.set(null)
2337
+ // Clean up the previous editing shape
2338
+ const prevEditingShapeId = this.getEditingShapeId()
2323
2339
  if (prevEditingShapeId) {
2324
2340
  const prevEditingShape = this.getShape(prevEditingShapeId)
2325
2341
  if (prevEditingShape) {
2326
2342
  this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape)
2327
2343
  }
2328
2344
  }
2345
+
2346
+ // Clean up the editing shape state and rich text editor
2347
+ this._updateCurrentPageState({ editingShapeId: null })
2348
+ this._currentRichTextEditor.set(null)
2329
2349
  },
2330
2350
  { history: 'ignore' }
2331
2351
  )
2352
+
2353
+ return this
2332
2354
  }
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
+
2333
2385
  return this
2334
2386
  }
2335
2387
 
@@ -2533,6 +2585,26 @@ export class Editor extends EventEmitter<TLEventMap> {
2533
2585
  return this.getCurrentPageState().croppingShapeId
2534
2586
  }
2535
2587
 
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
+
2536
2608
  /**
2537
2609
  * Set the current cropping shape.
2538
2610
  *
@@ -2554,12 +2626,8 @@ export class Editor extends EventEmitter<TLEventMap> {
2554
2626
  () => {
2555
2627
  if (!id) {
2556
2628
  this.updateCurrentPageState({ croppingShapeId: null })
2557
- } else {
2558
- const shape = this.getShape(id)!
2559
- const util = this.getShapeUtil(shape)
2560
- if (shape && util.canCrop(shape)) {
2561
- this.updateCurrentPageState({ croppingShapeId: id })
2562
- }
2629
+ } else if (this.canCropShape(id)) {
2630
+ this.updateCurrentPageState({ croppingShapeId: id })
2563
2631
  }
2564
2632
  },
2565
2633
  { history: 'ignore' }
@@ -2669,6 +2737,52 @@ export class Editor extends EventEmitter<TLEventMap> {
2669
2737
  return this.getCamera().z
2670
2738
  }
2671
2739
 
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
+
2672
2786
  /**
2673
2787
  * Get the camera's initial or reset zoom level.
2674
2788
  *
@@ -2995,7 +3109,8 @@ export class Editor extends EventEmitter<TLEventMap> {
2995
3109
 
2996
3110
  // Dispatch a new pointer move because the pointer's page will have changed
2997
3111
  // (its screen position will compute to a new page position given the new camera position)
2998
- const { currentScreenPoint, currentPagePoint } = this.inputs
3112
+ const currentScreenPoint = this.inputs.getCurrentScreenPoint()
3113
+ const currentPagePoint = this.inputs.getCurrentPagePoint()
2999
3114
 
3000
3115
  // compare the next page point (derived from the current camera) to the current page point
3001
3116
  if (
@@ -3159,7 +3274,7 @@ export class Editor extends EventEmitter<TLEventMap> {
3159
3274
  * ```ts
3160
3275
  * editor.zoomIn()
3161
3276
  * editor.zoomIn(editor.getViewportScreenCenter(), { animation: { duration: 200 } })
3162
- * editor.zoomIn(editor.inputs.currentScreenPoint, { animation: { duration: 200 } })
3277
+ * editor.zoomIn(editor.inputs.getCurrentScreenPoint(), { animation: { duration: 200 } })
3163
3278
  * ```
3164
3279
  *
3165
3280
  * @param point - The screen point to zoom in on. Defaults to the screen center
@@ -3204,7 +3319,7 @@ export class Editor extends EventEmitter<TLEventMap> {
3204
3319
  * ```ts
3205
3320
  * editor.zoomOut()
3206
3321
  * editor.zoomOut(editor.getViewportScreenCenter(), { animation: { duration: 120 } })
3207
- * editor.zoomOut(editor.inputs.currentScreenPoint, { animation: { duration: 120 } })
3322
+ * editor.zoomOut(editor.inputs.getCurrentScreenPoint(), { animation: { duration: 120 } })
3208
3323
  * ```
3209
3324
  *
3210
3325
  * @param point - The point to zoom out on. Defaults to the viewport screen center.
@@ -3261,10 +3376,17 @@ export class Editor extends EventEmitter<TLEventMap> {
3261
3376
 
3262
3377
  const selectionPageBounds = this.getSelectionPageBounds()
3263
3378
  if (selectionPageBounds) {
3264
- this.zoomToBounds(selectionPageBounds, {
3265
- targetZoom: Math.max(1, this.getZoomLevel()),
3266
- ...opts,
3267
- })
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
3390
  }
3269
3391
  return this
3270
3392
  }
@@ -3321,7 +3443,8 @@ export class Editor extends EventEmitter<TLEventMap> {
3321
3443
 
3322
3444
  const viewportScreenBounds = this.getViewportScreenBounds()
3323
3445
 
3324
- const inset = opts?.inset ?? Math.min(ZOOM_TO_FIT_PADDING, viewportScreenBounds.width * 0.28)
3446
+ const inset =
3447
+ opts?.inset ?? Math.min(this.options.zoomToFitPadding, viewportScreenBounds.width * 0.28)
3325
3448
 
3326
3449
  const baseZoom = this.getBaseZoom()
3327
3450
  const zoomMin = cameraOptions.zoomSteps[0]
@@ -3631,22 +3754,23 @@ export class Editor extends EventEmitter<TLEventMap> {
3631
3754
  if (_willSetInitialBounds) {
3632
3755
  // If we have just received the initial bounds, don't center the camera.
3633
3756
  this.updateInstanceState({ screenBounds: screenBounds.toJson(), insets })
3757
+ this.emit('resize', screenBounds.toJson())
3634
3758
  this.setCamera(this.getCamera())
3635
3759
  } else {
3636
3760
  if (center && !this.getInstanceState().followingUserId) {
3637
3761
  // Get the page center before the change, make the change, and restore it
3638
3762
  const before = this.getViewportPageBounds().center
3639
3763
  this.updateInstanceState({ screenBounds: screenBounds.toJson(), insets })
3764
+ this.emit('resize', screenBounds.toJson())
3640
3765
  this.centerOnPoint(before)
3641
3766
  } else {
3642
3767
  // Otherwise,
3643
3768
  this.updateInstanceState({ screenBounds: screenBounds.toJson(), insets })
3769
+ this.emit('resize', screenBounds.toJson())
3644
3770
  this._setCamera(Vec.From({ ...this.getCamera() }))
3645
3771
  }
3646
3772
  }
3647
3773
 
3648
- this._tickCameraState()
3649
-
3650
3774
  return this
3651
3775
  }
3652
3776
 
@@ -4052,18 +4176,19 @@ export class Editor extends EventEmitter<TLEventMap> {
4052
4176
  // box just for rendering, and we only update after the camera stops moving.
4053
4177
  private _cameraState = atom('camera state', 'idle' as 'idle' | 'moving')
4054
4178
  private _cameraStateTimeoutRemaining = 0
4055
- _decayCameraStateTimeout(elapsed: number) {
4179
+ private _decayCameraStateTimeout(elapsed: number) {
4056
4180
  this._cameraStateTimeoutRemaining -= elapsed
4057
4181
  if (this._cameraStateTimeoutRemaining > 0) return
4058
4182
  this.off('tick', this._decayCameraStateTimeout)
4059
4183
  this._cameraState.set('idle')
4060
4184
  }
4061
- _tickCameraState() {
4185
+ private _tickCameraState() {
4062
4186
  // always reset the timeout
4063
4187
  this._cameraStateTimeoutRemaining = this.options.cameraMovingTimeoutMs
4064
4188
  // If the state is idle, then start the tick
4065
4189
  if (this._cameraState.__unsafe__getWithoutCapture() !== 'idle') return
4066
4190
  this._cameraState.set('moving')
4191
+ this._debouncedZoomLevel.set(unsafe__withoutCapture(() => this.getCamera().z))
4067
4192
  this.on('tick', this._decayCameraStateTimeout)
4068
4193
  }
4069
4194
 
@@ -7649,8 +7774,14 @@ export class Editor extends EventEmitter<TLEventMap> {
7649
7774
  // then if the shape is flipped in one axis only, we need to apply an extra rotation
7650
7775
  // to make sure the shape is mirrored correctly
7651
7776
  if (Math.sign(scale.x) * Math.sign(scale.y) < 0) {
7652
- let { rotation } = Mat.Decompose(options.initialPageTransform)
7653
- rotation -= 2 * rotation
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
7654
7785
  this.updateShapes([{ id, type, rotation }])
7655
7786
  }
7656
7787
 
@@ -7670,9 +7801,13 @@ export class Editor extends EventEmitter<TLEventMap> {
7670
7801
  )
7671
7802
 
7672
7803
  // now calculate how far away the shape is from where it needs to be
7673
- const pageBounds = this.getShapePageBounds(id)!
7674
7804
  const pageTransform = this.getShapePageTransform(id)!
7675
- const currentPageCenter = pageBounds.center
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)
7676
7811
  const shapePageTransformOrigin = pageTransform.point()
7677
7812
  if (!currentPageCenter || !shapePageTransformOrigin) return this
7678
7813
  const pageDelta = Vec.Sub(postScaleShapePageCenter, currentPageCenter)
@@ -8120,7 +8255,12 @@ export class Editor extends EventEmitter<TLEventMap> {
8120
8255
  )
8121
8256
  )
8122
8257
  const sortedShapeIds = shapesToGroup.sort(sortByIndex).map((s) => s.id)
8123
- const pageBounds = Box.Common(compact(shapesToGroup.map((id) => this.getShapePageBounds(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
+ }
8124
8264
 
8125
8265
  const { x, y } = pageBounds.point
8126
8266
 
@@ -9145,6 +9285,30 @@ export class Editor extends EventEmitter<TLEventMap> {
9145
9285
  }
9146
9286
  }
9147
9287
 
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
+
9148
9312
  let isDuplicating = false
9149
9313
 
9150
9314
  if (!isPageId(pasteParentId)) {
@@ -9482,126 +9646,6 @@ export class Editor extends EventEmitter<TLEventMap> {
9482
9646
 
9483
9647
  /* --------------------- Events --------------------- */
9484
9648
 
9485
- /**
9486
- * The app's current input state.
9487
- *
9488
- * @public
9489
- */
9490
- inputs = {
9491
- /** The most recent pointer down's position in the current page space. */
9492
- originPagePoint: new Vec(),
9493
- /** The most recent pointer down's position in screen space. */
9494
- originScreenPoint: new Vec(),
9495
- /** The previous pointer position in the current page space. */
9496
- previousPagePoint: new Vec(),
9497
- /** The previous pointer position in screen space. */
9498
- previousScreenPoint: new Vec(),
9499
- /** The most recent pointer position in the current page space. */
9500
- currentPagePoint: new Vec(),
9501
- /** The most recent pointer position in screen space. */
9502
- currentScreenPoint: new Vec(),
9503
- /** A set containing the currently pressed keys. */
9504
- keys: new Set<string>(),
9505
- /** A set containing the currently pressed buttons. */
9506
- buttons: new Set<number>(),
9507
- /** Whether the input is from a pe. */
9508
- isPen: false,
9509
- /** Whether the shift key is currently pressed. */
9510
- shiftKey: false,
9511
- /** Whether the meta key is currently pressed. */
9512
- metaKey: false,
9513
- /** Whether the control or command key is currently pressed. */
9514
- ctrlKey: false,
9515
- /** Whether the alt or option key is currently pressed. */
9516
- altKey: false,
9517
- /** Whether the user is dragging. */
9518
- isDragging: false,
9519
- /** Whether the user is pointing. */
9520
- isPointing: false,
9521
- /** Whether the user is pinching. */
9522
- isPinching: false,
9523
- /** Whether the user is editing. */
9524
- isEditing: false,
9525
- /** Whether the user is panning. */
9526
- isPanning: false,
9527
- /** Whether the user is spacebar panning. */
9528
- isSpacebarPanning: false,
9529
- /** Velocity of mouse pointer, in pixels per millisecond */
9530
- pointerVelocity: new Vec(),
9531
- }
9532
-
9533
- /**
9534
- * Update the input points from a pointer, pinch, or wheel event.
9535
- *
9536
- * @param info - The event info.
9537
- */
9538
- private _updateInputsFromEvent(
9539
- info: TLPointerEventInfo | TLPinchEventInfo | TLWheelEventInfo
9540
- ): void {
9541
- const {
9542
- pointerVelocity,
9543
- previousScreenPoint,
9544
- previousPagePoint,
9545
- currentScreenPoint,
9546
- currentPagePoint,
9547
- originScreenPoint,
9548
- originPagePoint,
9549
- } = this.inputs
9550
-
9551
- const { screenBounds } = this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!
9552
- const { x: cx, y: cy, z: cz } = unsafe__withoutCapture(() => this.getCamera())
9553
-
9554
- const sx = info.point.x - screenBounds.x
9555
- const sy = info.point.y - screenBounds.y
9556
- const sz = info.point.z ?? 0.5
9557
-
9558
- previousScreenPoint.setTo(currentScreenPoint)
9559
- previousPagePoint.setTo(currentPagePoint)
9560
-
9561
- // The "screen bounds" is relative to the user's actual screen.
9562
- // The "screen point" is relative to the "screen bounds";
9563
- // it will be 0,0 when its actual screen position is equal
9564
- // to screenBounds.point. This is confusing!
9565
- currentScreenPoint.set(sx, sy)
9566
- const nx = sx / cz - cx
9567
- const ny = sy / cz - cy
9568
- if (isFinite(nx) && isFinite(ny)) {
9569
- currentPagePoint.set(nx, ny, sz)
9570
- }
9571
-
9572
- this.inputs.isPen = info.type === 'pointer' && info.isPen
9573
-
9574
- // Reset velocity on pointer down, or when a pinch starts or ends
9575
- if (info.name === 'pointer_down' || this.inputs.isPinching) {
9576
- pointerVelocity.set(0, 0)
9577
- originScreenPoint.setTo(currentScreenPoint)
9578
- originPagePoint.setTo(currentPagePoint)
9579
- }
9580
-
9581
- // todo: We only have to do this if there are multiple users in the document
9582
- this.run(
9583
- () => {
9584
- this.store.put([
9585
- {
9586
- id: TLPOINTER_ID,
9587
- typeName: 'pointer',
9588
- x: currentPagePoint.x,
9589
- y: currentPagePoint.y,
9590
- lastActivityTimestamp:
9591
- // If our pointer moved only because we're following some other user, then don't
9592
- // update our last activity timestamp; otherwise, update it to the current timestamp.
9593
- info.type === 'pointer' && info.pointerId === INTERNAL_POINTER_IDS.CAMERA_MOVE
9594
- ? (this.store.unsafeGetWithoutCapture(TLPOINTER_ID)?.lastActivityTimestamp ??
9595
- this._tickManager.now)
9596
- : this._tickManager.now,
9597
- meta: {},
9598
- },
9599
- ])
9600
- },
9601
- { history: 'ignore' }
9602
- )
9603
- }
9604
-
9605
9649
  /**
9606
9650
  * Dispatch a cancel event.
9607
9651
  *
@@ -9671,19 +9715,22 @@ export class Editor extends EventEmitter<TLEventMap> {
9671
9715
  // weird but true: what `inputs` calls screen-space is actually viewport space. so
9672
9716
  // we need to convert back into true screen space first. we should fix this...
9673
9717
  Vec.Add(
9674
- this.inputs.currentScreenPoint,
9718
+ this.inputs.getCurrentScreenPoint(),
9675
9719
  this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!.screenBounds
9676
9720
  ),
9677
9721
  pointerId: options?.pointerId ?? 0,
9678
9722
  button: options?.button ?? 0,
9679
- isPen: options?.isPen ?? this.inputs.isPen,
9680
- shiftKey: options?.shiftKey ?? this.inputs.shiftKey,
9681
- altKey: options?.altKey ?? this.inputs.altKey,
9682
- ctrlKey: options?.ctrlKey ?? this.inputs.ctrlKey,
9683
- metaKey: options?.metaKey ?? this.inputs.metaKey,
9684
- accelKey: options?.accelKey ?? isAccelKey(this.inputs),
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,
9685
9729
  }
9686
9730
 
9731
+ // needs to be calculated second
9732
+ event.accelKey = options?.accelKey ?? this.inputs.getAccelKey()
9733
+
9687
9734
  if (options?.immediate) {
9688
9735
  this._flushEventForTick(event)
9689
9736
  } else {
@@ -10056,16 +10103,16 @@ export class Editor extends EventEmitter<TLEventMap> {
10056
10103
  /** @internal */
10057
10104
  @bind
10058
10105
  _setShiftKeyTimeout() {
10059
- this.inputs.shiftKey = false
10106
+ this.inputs.setShiftKey(false)
10060
10107
  this.dispatch({
10061
10108
  type: 'keyboard',
10062
10109
  name: 'key_up',
10063
10110
  key: 'Shift',
10064
- shiftKey: this.inputs.shiftKey,
10065
- ctrlKey: this.inputs.ctrlKey,
10066
- altKey: this.inputs.altKey,
10067
- metaKey: this.inputs.metaKey,
10068
- accelKey: isAccelKey(this.inputs),
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(),
10069
10116
  code: 'ShiftLeft',
10070
10117
  })
10071
10118
  }
@@ -10076,16 +10123,16 @@ export class Editor extends EventEmitter<TLEventMap> {
10076
10123
  /** @internal */
10077
10124
  @bind
10078
10125
  _setAltKeyTimeout() {
10079
- this.inputs.altKey = false
10126
+ this.inputs.setAltKey(false)
10080
10127
  this.dispatch({
10081
10128
  type: 'keyboard',
10082
10129
  name: 'key_up',
10083
10130
  key: 'Alt',
10084
- shiftKey: this.inputs.shiftKey,
10085
- ctrlKey: this.inputs.ctrlKey,
10086
- altKey: this.inputs.altKey,
10087
- metaKey: this.inputs.metaKey,
10088
- accelKey: isAccelKey(this.inputs),
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(),
10089
10136
  code: 'AltLeft',
10090
10137
  })
10091
10138
  }
@@ -10096,16 +10143,16 @@ export class Editor extends EventEmitter<TLEventMap> {
10096
10143
  /** @internal */
10097
10144
  @bind
10098
10145
  _setCtrlKeyTimeout() {
10099
- this.inputs.ctrlKey = false
10146
+ this.inputs.setCtrlKey(false)
10100
10147
  this.dispatch({
10101
10148
  type: 'keyboard',
10102
10149
  name: 'key_up',
10103
10150
  key: 'Ctrl',
10104
- shiftKey: this.inputs.shiftKey,
10105
- ctrlKey: this.inputs.ctrlKey,
10106
- altKey: this.inputs.altKey,
10107
- metaKey: this.inputs.metaKey,
10108
- accelKey: isAccelKey(this.inputs),
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(),
10109
10156
  code: 'ControlLeft',
10110
10157
  })
10111
10158
  }
@@ -10116,16 +10163,16 @@ export class Editor extends EventEmitter<TLEventMap> {
10116
10163
  /** @internal */
10117
10164
  @bind
10118
10165
  _setMetaKeyTimeout() {
10119
- this.inputs.metaKey = false
10166
+ this.inputs.setMetaKey(false)
10120
10167
  this.dispatch({
10121
10168
  type: 'keyboard',
10122
10169
  name: 'key_up',
10123
10170
  key: 'Meta',
10124
- shiftKey: this.inputs.shiftKey,
10125
- ctrlKey: this.inputs.ctrlKey,
10126
- altKey: this.inputs.altKey,
10127
- metaKey: this.inputs.metaKey,
10128
- accelKey: isAccelKey(this.inputs),
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(),
10129
10176
  code: 'MetaLeft',
10130
10177
  })
10131
10178
  }
@@ -10133,9 +10180,6 @@ export class Editor extends EventEmitter<TLEventMap> {
10133
10180
  /** @internal */
10134
10181
  private _restoreToolId = 'select'
10135
10182
 
10136
- /** @internal */
10137
- private _pinchStart = 1
10138
-
10139
10183
  /** @internal */
10140
10184
  private _didPinch = false
10141
10185
 
@@ -10242,55 +10286,54 @@ export class Editor extends EventEmitter<TLEventMap> {
10242
10286
  if (info.type === 'misc') {
10243
10287
  // stop panning if the interaction is cancelled or completed
10244
10288
  if (info.name === 'cancel' || info.name === 'complete') {
10245
- this.inputs.isDragging = false
10289
+ this.inputs.setIsDragging(false)
10246
10290
 
10247
- if (this.inputs.isPanning) {
10248
- this.inputs.isPanning = false
10249
- this.inputs.isSpacebarPanning = false
10291
+ if (this.inputs.getIsPanning()) {
10292
+ this.inputs.setIsPanning(false)
10293
+ this.inputs.setIsSpacebarPanning(false)
10250
10294
  this.setCursor({ type: this._prevCursor, rotation: 0 })
10251
10295
  }
10252
10296
  }
10253
10297
 
10254
10298
  this.root.handleEvent(info)
10299
+ this.emit('event', info)
10255
10300
  return
10256
10301
  }
10257
10302
 
10258
10303
  if (info.shiftKey) {
10259
10304
  clearTimeout(this._shiftKeyTimeout)
10260
10305
  this._shiftKeyTimeout = -1
10261
- inputs.shiftKey = true
10262
- } else if (!info.shiftKey && inputs.shiftKey && this._shiftKeyTimeout === -1) {
10306
+ inputs.setShiftKey(true)
10307
+ } else if (!info.shiftKey && inputs.getShiftKey() && this._shiftKeyTimeout === -1) {
10263
10308
  this._shiftKeyTimeout = this.timers.setTimeout(this._setShiftKeyTimeout, 150)
10264
10309
  }
10265
10310
 
10266
10311
  if (info.altKey) {
10267
10312
  clearTimeout(this._altKeyTimeout)
10268
10313
  this._altKeyTimeout = -1
10269
- inputs.altKey = true
10270
- } else if (!info.altKey && inputs.altKey && this._altKeyTimeout === -1) {
10314
+ inputs.setAltKey(true)
10315
+ } else if (!info.altKey && inputs.getAltKey() && this._altKeyTimeout === -1) {
10271
10316
  this._altKeyTimeout = this.timers.setTimeout(this._setAltKeyTimeout, 150)
10272
10317
  }
10273
10318
 
10274
10319
  if (info.ctrlKey) {
10275
10320
  clearTimeout(this._ctrlKeyTimeout)
10276
10321
  this._ctrlKeyTimeout = -1
10277
- inputs.ctrlKey = true
10278
- } else if (!info.ctrlKey && inputs.ctrlKey && this._ctrlKeyTimeout === -1) {
10322
+ inputs.setCtrlKey(true)
10323
+ } else if (!info.ctrlKey && inputs.getCtrlKey() && this._ctrlKeyTimeout === -1) {
10279
10324
  this._ctrlKeyTimeout = this.timers.setTimeout(this._setCtrlKeyTimeout, 150)
10280
10325
  }
10281
10326
 
10282
10327
  if (info.metaKey) {
10283
10328
  clearTimeout(this._metaKeyTimeout)
10284
10329
  this._metaKeyTimeout = -1
10285
- inputs.metaKey = true
10286
- } else if (!info.metaKey && inputs.metaKey && this._metaKeyTimeout === -1) {
10330
+ inputs.setMetaKey(true)
10331
+ } else if (!info.metaKey && inputs.getMetaKey() && this._metaKeyTimeout === -1) {
10287
10332
  this._metaKeyTimeout = this.timers.setTimeout(this._setMetaKeyTimeout, 150)
10288
10333
  }
10289
10334
 
10290
- const { originPagePoint, currentPagePoint } = inputs
10291
-
10292
- if (!inputs.isPointing) {
10293
- inputs.isDragging = false
10335
+ if (!inputs.getIsPointing()) {
10336
+ inputs.setIsDragging(false)
10294
10337
  }
10295
10338
 
10296
10339
  const instanceState = this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!
@@ -10301,29 +10344,29 @@ export class Editor extends EventEmitter<TLEventMap> {
10301
10344
  case 'pinch': {
10302
10345
  if (cameraOptions.isLocked) return
10303
10346
  clearTimeout(this._longPressTimeout)
10304
- this._updateInputsFromEvent(info)
10347
+ this.inputs.updateFromEvent(info)
10305
10348
 
10306
10349
  switch (info.name) {
10307
10350
  case 'pinch_start': {
10308
- if (inputs.isPinching) return
10351
+ if (inputs.getIsPinching()) return
10309
10352
 
10310
- if (!inputs.isEditing) {
10311
- this._pinchStart = this.getCamera().z
10353
+ if (!inputs.getIsEditing()) {
10312
10354
  if (!this._selectedShapeIdsAtPointerDown.length) {
10313
10355
  this._selectedShapeIdsAtPointerDown = [...pageState.selectedShapeIds]
10314
10356
  }
10315
10357
 
10316
10358
  this._didPinch = true
10317
10359
 
10318
- inputs.isPinching = true
10360
+ inputs.setIsPinching(true)
10319
10361
 
10320
10362
  this.interrupt()
10321
10363
  }
10322
10364
 
10365
+ this.emit('event', info)
10323
10366
  return // Stop here!
10324
10367
  }
10325
10368
  case 'pinch': {
10326
- if (!inputs.isPinching) return
10369
+ if (!inputs.getIsPinching()) return
10327
10370
 
10328
10371
  const {
10329
10372
  point: { z = 1 },
@@ -10354,13 +10397,14 @@ export class Editor extends EventEmitter<TLEventMap> {
10354
10397
  { immediate: true }
10355
10398
  )
10356
10399
 
10400
+ this.emit('event', info)
10357
10401
  return // Stop here!
10358
10402
  }
10359
10403
  case 'pinch_end': {
10360
- if (!inputs.isPinching) return this
10404
+ if (!inputs.getIsPinching()) return this
10361
10405
 
10362
10406
  // Stop pinching
10363
- inputs.isPinching = false
10407
+ inputs.setIsPinching(false)
10364
10408
 
10365
10409
  // Stash and clear the shapes that were selected when the pinch started
10366
10410
  const { _selectedShapeIdsAtPointerDown: shapesToReselect } = this
@@ -10380,6 +10424,7 @@ export class Editor extends EventEmitter<TLEventMap> {
10380
10424
  }
10381
10425
  }
10382
10426
 
10427
+ this.emit('event', info)
10383
10428
  return // Stop here!
10384
10429
  }
10385
10430
  }
@@ -10387,7 +10432,7 @@ export class Editor extends EventEmitter<TLEventMap> {
10387
10432
  case 'wheel': {
10388
10433
  if (cameraOptions.isLocked) return
10389
10434
 
10390
- this._updateInputsFromEvent(info)
10435
+ this.inputs.updateFromEvent(info)
10391
10436
 
10392
10437
  const { panSpeed, zoomSpeed } = cameraOptions
10393
10438
  let wheelBehavior = cameraOptions.wheelBehavior
@@ -10418,7 +10463,7 @@ export class Editor extends EventEmitter<TLEventMap> {
10418
10463
  switch (behavior) {
10419
10464
  case 'zoom': {
10420
10465
  // Zoom in on current screen point using the wheel delta
10421
- const { x, y } = this.inputs.currentScreenPoint
10466
+ const { x, y } = this.inputs.getCurrentScreenPoint()
10422
10467
  let delta = dz
10423
10468
 
10424
10469
  // If we're forcing zoom, then we need to do the wheel normalization math here
@@ -10435,6 +10480,8 @@ export class Editor extends EventEmitter<TLEventMap> {
10435
10480
  immediate: true,
10436
10481
  })
10437
10482
  this.maybeTrackPerformance('Zooming')
10483
+ this.root.handleEvent(info)
10484
+ this.emit('event', info)
10438
10485
  return
10439
10486
  }
10440
10487
  case 'pan': {
@@ -10443,6 +10490,8 @@ export class Editor extends EventEmitter<TLEventMap> {
10443
10490
  immediate: true,
10444
10491
  })
10445
10492
  this.maybeTrackPerformance('Panning')
10493
+ this.root.handleEvent(info)
10494
+ this.emit('event', info)
10446
10495
  return
10447
10496
  }
10448
10497
  }
@@ -10451,9 +10500,9 @@ export class Editor extends EventEmitter<TLEventMap> {
10451
10500
  }
10452
10501
  case 'pointer': {
10453
10502
  // Ignore pointer events while we're pinching
10454
- if (inputs.isPinching) return
10503
+ if (inputs.getIsPinching()) return
10455
10504
 
10456
- this._updateInputsFromEvent(info)
10505
+ this.inputs.updateFromEvent(info)
10457
10506
  const { isPen } = info
10458
10507
  const { isPenMode } = instanceState
10459
10508
 
@@ -10462,7 +10511,7 @@ export class Editor extends EventEmitter<TLEventMap> {
10462
10511
  // If we're in pen mode and the input is not a pen type, then stop here
10463
10512
  if (isPenMode && !isPen) return
10464
10513
 
10465
- if (!this.inputs.isPanning) {
10514
+ if (!this.inputs.getIsPanning()) {
10466
10515
  // Start a long press timeout
10467
10516
  this._longPressTimeout = this.timers.setTimeout(() => {
10468
10517
  const vsb = this.getViewportScreenBounds()
@@ -10472,7 +10521,7 @@ export class Editor extends EventEmitter<TLEventMap> {
10472
10521
  // viewport bounds, and will be again when this event is handled...
10473
10522
  // so we need to counter-adjust from the stored value so that the
10474
10523
  // new value is set correctly.
10475
- point: this.inputs.originScreenPoint.clone().addXY(vsb.x, vsb.y),
10524
+ point: this.inputs.getOriginScreenPoint().clone().addXY(vsb.x, vsb.y),
10476
10525
  name: 'long_press',
10477
10526
  })
10478
10527
  }, this.options.longPressDurationMs)
@@ -10489,8 +10538,8 @@ export class Editor extends EventEmitter<TLEventMap> {
10489
10538
  inputs.buttons.add(info.button)
10490
10539
 
10491
10540
  // Start pointing and stop dragging
10492
- inputs.isPointing = true
10493
- inputs.isDragging = false
10541
+ inputs.setIsPointing(true)
10542
+ inputs.setIsDragging(false)
10494
10543
 
10495
10544
  // If pen mode is off but we're not already in pen mode, turn that on
10496
10545
  if (!isPenMode && isPen) this.updateInstanceState({ isPenMode: true })
@@ -10502,16 +10551,16 @@ export class Editor extends EventEmitter<TLEventMap> {
10502
10551
  this.setCurrentTool('eraser')
10503
10552
  } else if (info.button === MIDDLE_MOUSE_BUTTON) {
10504
10553
  // Middle mouse pan activates panning unless we're already panning (with spacebar)
10505
- if (!this.inputs.isPanning) {
10554
+ if (!this.inputs.getIsPanning()) {
10506
10555
  this._prevCursor = this.getInstanceState().cursor.type
10507
10556
  }
10508
- this.inputs.isPanning = true
10557
+ this.inputs.setIsPanning(true)
10509
10558
  clearTimeout(this._longPressTimeout)
10510
10559
  }
10511
10560
 
10512
10561
  // We might be panning because we did a middle mouse click, or because we're holding spacebar and started a regular click
10513
10562
  // Also stop here, we don't want the state chart to receive the event
10514
- if (this.inputs.isPanning) {
10563
+ if (this.inputs.getIsPanning()) {
10515
10564
  this.stopCameraAnimation()
10516
10565
  this.setCursor({ type: 'grabbing', rotation: 0 })
10517
10566
  return this
@@ -10526,9 +10575,10 @@ export class Editor extends EventEmitter<TLEventMap> {
10526
10575
  const { x: cx, y: cy, z: cz } = unsafe__withoutCapture(() => this.getCamera())
10527
10576
 
10528
10577
  // If we've started panning, then clear any long press timeout
10529
- if (this.inputs.isPanning && this.inputs.isPointing) {
10578
+ if (this.inputs.getIsPanning() && this.inputs.getIsPointing()) {
10530
10579
  // Handle spacebar / middle mouse button panning
10531
- const { currentScreenPoint, previousScreenPoint } = this.inputs
10580
+ const currentScreenPoint = this.inputs.getCurrentScreenPoint()
10581
+ const previousScreenPoint = this.inputs.getPreviousScreenPoint()
10532
10582
  const offset = Vec.Sub(currentScreenPoint, previousScreenPoint)
10533
10583
  this.setCamera(new Vec(cx + offset.x / cz, cy + offset.y / cz, cz), {
10534
10584
  immediate: true,
@@ -10538,24 +10588,25 @@ export class Editor extends EventEmitter<TLEventMap> {
10538
10588
  }
10539
10589
 
10540
10590
  if (
10541
- inputs.isPointing &&
10542
- !inputs.isDragging &&
10543
- Vec.Dist2(originPagePoint, currentPagePoint) * this.getZoomLevel() >
10591
+ inputs.getIsPointing() &&
10592
+ !inputs.getIsDragging() &&
10593
+ Vec.Dist2(inputs.getOriginPagePoint(), inputs.getCurrentPagePoint()) *
10594
+ this.getZoomLevel() >
10544
10595
  (instanceState.isCoarsePointer
10545
10596
  ? this.options.coarseDragDistanceSquared
10546
10597
  : this.options.dragDistanceSquared) /
10547
10598
  cz
10548
10599
  ) {
10549
10600
  // Start dragging
10550
- inputs.isDragging = true
10601
+ inputs.setIsDragging(true)
10551
10602
  clearTimeout(this._longPressTimeout)
10552
10603
  }
10553
10604
  break
10554
10605
  }
10555
10606
  case 'pointer_up': {
10556
10607
  // Stop dragging / pointing
10557
- inputs.isDragging = false
10558
- inputs.isPointing = false
10608
+ inputs.setIsDragging(false)
10609
+ inputs.setIsPointing(false)
10559
10610
  clearTimeout(this._longPressTimeout)
10560
10611
 
10561
10612
  // Remove the button from the buttons set
@@ -10572,12 +10623,12 @@ export class Editor extends EventEmitter<TLEventMap> {
10572
10623
  info.button = 0
10573
10624
  }
10574
10625
 
10575
- if (inputs.isPanning) {
10626
+ if (inputs.getIsPanning()) {
10576
10627
  if (!inputs.keys.has('Space')) {
10577
- inputs.isPanning = false
10578
- inputs.isSpacebarPanning = false
10628
+ inputs.setIsPanning(false)
10629
+ inputs.setIsSpacebarPanning(false)
10579
10630
  }
10580
- const slideDirection = this.inputs.pointerVelocity
10631
+ const slideDirection = this.inputs.getPointerVelocity()
10581
10632
  const slideSpeed = Math.min(2, slideDirection.len())
10582
10633
 
10583
10634
  switch (info.button) {
@@ -10621,43 +10672,48 @@ export class Editor extends EventEmitter<TLEventMap> {
10621
10672
  // Add the key from the keys set
10622
10673
  inputs.keys.add(info.code)
10623
10674
 
10624
- // If the space key is pressed (but meta / control isn't!) activate panning
10625
- if (info.code === 'Space' && !info.ctrlKey) {
10626
- if (!this.inputs.isPanning) {
10627
- this._prevCursor = instanceState.cursor.type
10628
- }
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
+ }
10629
10681
 
10630
- this.inputs.isPanning = true
10631
- this.inputs.isSpacebarPanning = true
10632
- clearTimeout(this._longPressTimeout)
10633
- this.setCursor({ type: this.inputs.isPointing ? 'grabbing' : 'grab', rotation: 0 })
10634
- }
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
+ })
10689
+ }
10635
10690
 
10636
- if (this.inputs.isSpacebarPanning) {
10637
- let offset: Vec | undefined
10638
- switch (info.code) {
10639
- case 'ArrowUp': {
10640
- offset = new Vec(0, -1)
10641
- break
10642
- }
10643
- case 'ArrowRight': {
10644
- offset = new Vec(1, 0)
10645
- break
10646
- }
10647
- case 'ArrowDown': {
10648
- offset = new Vec(0, 1)
10649
- break
10650
- }
10651
- case 'ArrowLeft': {
10652
- offset = new Vec(-1, 0)
10653
- break
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
+ }
10654
10710
  }
10655
- }
10656
10711
 
10657
- if (offset) {
10658
- const bounds = this.getViewportPageBounds()
10659
- const next = bounds.clone().translate(offset.mulV({ x: bounds.w, y: bounds.h }))
10660
- this._animateToViewport(next, { animation: { duration: 320 } })
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 } })
10716
+ }
10661
10717
  }
10662
10718
  }
10663
10719
 
@@ -10667,15 +10723,17 @@ export class Editor extends EventEmitter<TLEventMap> {
10667
10723
  // Remove the key from the keys set
10668
10724
  inputs.keys.delete(info.code)
10669
10725
 
10670
- // If we've lifted the space key,
10671
- if (info.code === 'Space') {
10672
- if (this.inputs.buttons.has(MIDDLE_MOUSE_BUTTON)) {
10673
- // If we're still middle dragging, continue panning
10674
- } else {
10675
- // otherwise, stop panning
10676
- this.inputs.isPanning = false
10677
- this.inputs.isSpacebarPanning = false
10678
- this.setCursor({ type: this._prevCursor, rotation: 0 })
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
+ }
10679
10737
  }
10680
10738
  }
10681
10739
  break