@tldraw/editor 4.3.0-canary.2362fd2ebe56 → 4.3.0-canary.2643056dfc8d
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.
- package/dist-cjs/index.d.ts +537 -120
- package/dist-cjs/index.js +8 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/components/ErrorBoundary.js.map +1 -1
- package/dist-cjs/lib/components/GeometryDebuggingView.js +1 -17
- package/dist-cjs/lib/components/GeometryDebuggingView.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +4 -5
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
- package/dist-cjs/lib/constants.js +1 -3
- package/dist-cjs/lib/constants.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +349 -280
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +16 -23
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +3 -3
- package/dist-cjs/lib/editor/derivations/parentsToChildren.js +12 -3
- package/dist-cjs/lib/editor/derivations/parentsToChildren.js.map +2 -2
- package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js +1 -1
- package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js +5 -6
- package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js +591 -0
- package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/SnapManager/SnapManager.js +1 -1
- package/dist-cjs/lib/editor/managers/SnapManager/SnapManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/SpatialIndexManager/RBushIndex.js +144 -0
- package/dist-cjs/lib/editor/managers/SpatialIndexManager/RBushIndex.js.map +7 -0
- package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js +181 -0
- package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/TickManager/TickManager.js +1 -22
- package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js +31 -23
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/group/DashedOutlineBox.js +1 -1
- package/dist-cjs/lib/editor/shapes/group/DashedOutlineBox.js.map +2 -2
- package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +3 -3
- package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +2 -2
- package/dist-cjs/lib/editor/types/emit-types.js.map +1 -1
- package/dist-cjs/lib/exports/parseCss.js +1 -1
- package/dist-cjs/lib/exports/parseCss.js.map +2 -2
- package/dist-cjs/lib/globals/environment.js +45 -9
- package/dist-cjs/lib/globals/environment.js.map +2 -2
- package/dist-cjs/lib/hooks/useCoarsePointer.js +14 -29
- package/dist-cjs/lib/hooks/useCoarsePointer.js.map +2 -2
- package/dist-cjs/lib/hooks/useEvent.js +1 -1
- package/dist-cjs/lib/hooks/useEvent.js.map +2 -2
- package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useGestureEvents.js +1 -1
- package/dist-cjs/lib/hooks/useGestureEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/usePassThroughMouseOverEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useScreenBounds.js.map +2 -2
- package/dist-cjs/lib/hooks/useStateAttribute.js +4 -1
- package/dist-cjs/lib/hooks/useStateAttribute.js.map +2 -2
- package/dist-cjs/lib/hooks/useTransform.js.map +1 -1
- package/dist-cjs/lib/hooks/useZoomCss.js +4 -8
- package/dist-cjs/lib/hooks/useZoomCss.js.map +2 -2
- package/dist-cjs/lib/options.js +6 -1
- package/dist-cjs/lib/options.js.map +2 -2
- package/dist-cjs/lib/primitives/Box.js +3 -0
- package/dist-cjs/lib/primitives/Box.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js +1 -0
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
- package/dist-cjs/lib/utils/rotation.js +1 -1
- package/dist-cjs/lib/utils/rotation.js.map +2 -2
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +537 -120
- package/dist-esm/index.mjs +9 -2
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/components/ErrorBoundary.mjs.map +1 -1
- package/dist-esm/lib/components/GeometryDebuggingView.mjs +1 -17
- package/dist-esm/lib/components/GeometryDebuggingView.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +4 -5
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
- package/dist-esm/lib/constants.mjs +1 -3
- package/dist-esm/lib/constants.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +350 -283
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +16 -23
- package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +3 -3
- package/dist-esm/lib/editor/derivations/parentsToChildren.mjs +13 -4
- package/dist-esm/lib/editor/derivations/parentsToChildren.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs +1 -1
- package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs +5 -6
- package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs +573 -0
- package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/SnapManager/SnapManager.mjs +1 -1
- package/dist-esm/lib/editor/managers/SnapManager/SnapManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/SpatialIndexManager/RBushIndex.mjs +114 -0
- package/dist-esm/lib/editor/managers/SpatialIndexManager/RBushIndex.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs +161 -0
- package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs +1 -22
- package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +31 -23
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/group/DashedOutlineBox.mjs +1 -1
- package/dist-esm/lib/editor/shapes/group/DashedOutlineBox.mjs.map +2 -2
- package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +3 -3
- package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +2 -2
- package/dist-esm/lib/exports/parseCss.mjs +1 -1
- package/dist-esm/lib/exports/parseCss.mjs.map +2 -2
- package/dist-esm/lib/globals/environment.mjs +45 -9
- package/dist-esm/lib/globals/environment.mjs.map +2 -2
- package/dist-esm/lib/hooks/useCoarsePointer.mjs +15 -30
- package/dist-esm/lib/hooks/useCoarsePointer.mjs.map +2 -2
- package/dist-esm/lib/hooks/useEvent.mjs +1 -1
- package/dist-esm/lib/hooks/useEvent.mjs.map +2 -2
- package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useGestureEvents.mjs +1 -1
- package/dist-esm/lib/hooks/useGestureEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/usePassThroughMouseOverEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useScreenBounds.mjs.map +2 -2
- package/dist-esm/lib/hooks/useStateAttribute.mjs +4 -1
- package/dist-esm/lib/hooks/useStateAttribute.mjs.map +2 -2
- package/dist-esm/lib/hooks/useTransform.mjs.map +1 -1
- package/dist-esm/lib/hooks/useZoomCss.mjs +4 -8
- package/dist-esm/lib/hooks/useZoomCss.mjs.map +2 -2
- package/dist-esm/lib/options.mjs +6 -1
- package/dist-esm/lib/options.mjs.map +2 -2
- package/dist-esm/lib/primitives/Box.mjs +3 -0
- package/dist-esm/lib/primitives/Box.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +1 -0
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
- package/dist-esm/lib/utils/rotation.mjs +1 -1
- package/dist-esm/lib/utils/rotation.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +14 -12
- package/package.json +21 -17
- package/src/index.ts +5 -1
- package/src/lib/components/ErrorBoundary.tsx +1 -1
- package/src/lib/components/GeometryDebuggingView.tsx +1 -19
- package/src/lib/components/default-components/DefaultCanvas.tsx +5 -8
- package/src/lib/config/TLUserPreferences.test.ts +40 -0
- package/src/lib/constants.ts +0 -2
- package/src/lib/editor/Editor.test.ts +140 -0
- package/src/lib/editor/Editor.ts +455 -326
- package/src/lib/editor/derivations/notVisibleShapes.ts +21 -33
- package/src/lib/editor/derivations/parentsToChildren.ts +18 -7
- package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +17 -31
- package/src/lib/editor/managers/ClickManager/ClickManager.ts +1 -1
- package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +129 -79
- package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.ts +10 -6
- package/src/lib/editor/managers/InputsManager/InputsManager.ts +566 -0
- package/src/lib/editor/managers/ScribbleManager/ScribbleManager.test.ts +0 -4
- package/src/lib/editor/managers/SnapManager/SnapManager.test.ts +12 -0
- package/src/lib/editor/managers/SnapManager/SnapManager.ts +1 -1
- package/src/lib/editor/managers/SpatialIndexManager/RBushIndex.ts +144 -0
- package/src/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.ts +215 -0
- package/src/lib/editor/managers/TickManager/TickManager.test.ts +40 -107
- package/src/lib/editor/managers/TickManager/TickManager.ts +2 -32
- package/src/lib/editor/shapes/ShapeUtil.ts +67 -24
- package/src/lib/editor/shapes/group/DashedOutlineBox.tsx +1 -1
- package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +3 -3
- package/src/lib/editor/types/emit-types.ts +3 -1
- package/src/lib/exports/parseCss.test.ts +1 -0
- package/src/lib/exports/parseCss.ts +1 -1
- package/src/lib/globals/environment.ts +65 -10
- package/src/lib/hooks/useCoarsePointer.ts +16 -59
- package/src/lib/hooks/useEvent.tsx +1 -1
- package/src/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.ts +1 -1
- package/src/lib/hooks/useGestureEvents.ts +2 -2
- package/src/lib/hooks/usePassThroughMouseOverEvents.ts +1 -1
- package/src/lib/hooks/usePassThroughWheelEvents.ts +1 -1
- package/src/lib/hooks/useScreenBounds.ts +1 -1
- package/src/lib/hooks/useStateAttribute.ts +4 -1
- package/src/lib/hooks/useTransform.ts +1 -1
- package/src/lib/hooks/useZoomCss.ts +3 -8
- package/src/lib/options.ts +32 -0
- package/src/lib/primitives/Box.ts +9 -0
- package/src/lib/primitives/geometry/Geometry2d.ts +1 -0
- package/src/lib/utils/rotation.ts +1 -1
- package/src/version.ts +3 -3
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -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,19 @@ 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'
|
|
152
|
+
import { SpatialIndexManager } from './managers/SpatialIndexManager/SpatialIndexManager'
|
|
154
153
|
import { TextManager } from './managers/TextManager/TextManager'
|
|
155
154
|
import { TickManager } from './managers/TickManager/TickManager'
|
|
156
155
|
import { UserPreferencesManager } from './managers/UserPreferencesManager/UserPreferencesManager'
|
|
157
|
-
import { ShapeUtil, TLGeometryOpts, TLResizeMode } from './shapes/ShapeUtil'
|
|
156
|
+
import { ShapeUtil, TLEditStartInfo, TLGeometryOpts, TLResizeMode } from './shapes/ShapeUtil'
|
|
158
157
|
import { RootState } from './tools/RootState'
|
|
159
158
|
import { StateNode, TLStateNodeConstructor } from './tools/StateNode'
|
|
160
159
|
import { TLContent } from './types/clipboard-types'
|
|
161
160
|
import { TLEventMap } from './types/emit-types'
|
|
162
|
-
import {
|
|
163
|
-
TLEventInfo,
|
|
164
|
-
TLPinchEventInfo,
|
|
165
|
-
TLPointerEventInfo,
|
|
166
|
-
TLWheelEventInfo,
|
|
167
|
-
} from './types/event-types'
|
|
161
|
+
import { TLEventInfo, TLPointerEventInfo } from './types/event-types'
|
|
168
162
|
import { TLExternalAsset, TLExternalContent } from './types/external-content'
|
|
169
163
|
import { TLHistoryBatchOptions } from './types/history-types'
|
|
170
164
|
import {
|
|
@@ -195,7 +189,7 @@ export type TLResizeShapeOptions = Partial<{
|
|
|
195
189
|
/** @public */
|
|
196
190
|
export interface TLEditorOptions {
|
|
197
191
|
/**
|
|
198
|
-
* The Store instance to use for keeping the
|
|
192
|
+
* The Store instance to use for keeping the editor's data. This may be prepopulated, e.g. by loading
|
|
199
193
|
* from a server or database.
|
|
200
194
|
*/
|
|
201
195
|
store: TLStore
|
|
@@ -315,6 +309,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
315
309
|
|
|
316
310
|
this.snaps = new SnapManager(this)
|
|
317
311
|
|
|
312
|
+
this.spatialIndex = new SpatialIndexManager(this)
|
|
313
|
+
this.disposables.add(() => this.spatialIndex.dispose())
|
|
314
|
+
|
|
318
315
|
this.disposables.add(this.timers.dispose)
|
|
319
316
|
|
|
320
317
|
this._cameraOptions.set({ ...DEFAULT_CAMERA_OPTIONS, ...cameraOptions })
|
|
@@ -333,6 +330,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
333
330
|
|
|
334
331
|
this._tickManager = new TickManager(this)
|
|
335
332
|
|
|
333
|
+
this.inputs = new InputsManager(this)
|
|
334
|
+
|
|
336
335
|
class NewRoot extends RootState {
|
|
337
336
|
static override initial = initialState ?? ''
|
|
338
337
|
}
|
|
@@ -867,7 +866,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
867
866
|
}
|
|
868
867
|
|
|
869
868
|
/**
|
|
870
|
-
* A set of functions to call when the
|
|
869
|
+
* A set of functions to call when the editor is disposed.
|
|
871
870
|
*
|
|
872
871
|
* @public
|
|
873
872
|
*/
|
|
@@ -880,16 +879,33 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
880
879
|
*/
|
|
881
880
|
isDisposed = false
|
|
882
881
|
|
|
883
|
-
/**
|
|
884
|
-
|
|
882
|
+
/**
|
|
883
|
+
* A manager for the editor's tick events.
|
|
884
|
+
*
|
|
885
|
+
* @internal */
|
|
886
|
+
private readonly _tickManager: TickManager
|
|
885
887
|
|
|
886
888
|
/**
|
|
887
|
-
* A manager for the
|
|
889
|
+
* A manager for the editor's input state.
|
|
890
|
+
*
|
|
891
|
+
* @public
|
|
892
|
+
*/
|
|
893
|
+
readonly inputs: InputsManager
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* A manager for the editor's snapping feature.
|
|
888
897
|
*
|
|
889
898
|
* @public
|
|
890
899
|
*/
|
|
891
900
|
readonly snaps: SnapManager
|
|
892
901
|
|
|
902
|
+
/**
|
|
903
|
+
* A manager for spatial indexing, enabling efficient shape location queries.
|
|
904
|
+
*
|
|
905
|
+
* @public
|
|
906
|
+
*/
|
|
907
|
+
readonly spatialIndex: SpatialIndexManager
|
|
908
|
+
|
|
893
909
|
/**
|
|
894
910
|
* A manager for the any asynchronous events and making sure they're
|
|
895
911
|
* cleaned up upon disposal.
|
|
@@ -969,6 +985,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
969
985
|
this.disposables.clear()
|
|
970
986
|
this.store.dispose()
|
|
971
987
|
this.isDisposed = true
|
|
988
|
+
this.emit('dispose')
|
|
972
989
|
}
|
|
973
990
|
|
|
974
991
|
/* ------------------- Shape Utils ------------------ */
|
|
@@ -1060,7 +1077,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1060
1077
|
/* --------------------- History -------------------- */
|
|
1061
1078
|
|
|
1062
1079
|
/**
|
|
1063
|
-
* A manager for the
|
|
1080
|
+
* A manager for the editor's history.
|
|
1064
1081
|
*
|
|
1065
1082
|
* @readonly
|
|
1066
1083
|
*/
|
|
@@ -1084,14 +1101,18 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1084
1101
|
}
|
|
1085
1102
|
|
|
1086
1103
|
/**
|
|
1087
|
-
* Whether the
|
|
1104
|
+
* Whether the editor can undo.
|
|
1088
1105
|
*
|
|
1089
1106
|
* @public
|
|
1090
1107
|
*/
|
|
1091
|
-
@computed
|
|
1108
|
+
@computed canUndo(): boolean {
|
|
1092
1109
|
return this.history.getNumUndos() > 0
|
|
1093
1110
|
}
|
|
1094
1111
|
|
|
1112
|
+
getCanUndo() {
|
|
1113
|
+
return this.canUndo()
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1095
1116
|
/**
|
|
1096
1117
|
* Redo to the next mark.
|
|
1097
1118
|
*
|
|
@@ -1109,20 +1130,24 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1109
1130
|
return this
|
|
1110
1131
|
}
|
|
1111
1132
|
|
|
1112
|
-
clearHistory() {
|
|
1113
|
-
this.history.clear()
|
|
1114
|
-
return this
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
1133
|
/**
|
|
1118
|
-
* Whether the
|
|
1134
|
+
* Whether the editor can redo.
|
|
1119
1135
|
*
|
|
1120
1136
|
* @public
|
|
1121
1137
|
*/
|
|
1122
|
-
@computed
|
|
1138
|
+
@computed canRedo(): boolean {
|
|
1123
1139
|
return this.history.getNumRedos() > 0
|
|
1124
1140
|
}
|
|
1125
1141
|
|
|
1142
|
+
getCanRedo() {
|
|
1143
|
+
return this.canRedo()
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
clearHistory() {
|
|
1147
|
+
this.history.clear()
|
|
1148
|
+
return this
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1126
1151
|
/**
|
|
1127
1152
|
* Create a new "mark", or stopping point, in the undo redo history. Creating a mark will clear
|
|
1128
1153
|
* any redos. You typically want to do this just before a user interaction begins or is handled.
|
|
@@ -1296,7 +1321,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1296
1321
|
}),
|
|
1297
1322
|
selectionCount: this.getSelectedShapes().length,
|
|
1298
1323
|
editingShape: editingShapeId ? this.getShape(editingShapeId) : undefined,
|
|
1299
|
-
inputs: this.inputs,
|
|
1324
|
+
inputs: this.inputs.toJson(),
|
|
1300
1325
|
pageState: this.getCurrentPageState(),
|
|
1301
1326
|
instanceState: this.getInstanceState(),
|
|
1302
1327
|
collaboratorCount: this.getCollaboratorsOnCurrentPage().length,
|
|
@@ -1321,7 +1346,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1321
1346
|
* we're in a transaction that's about to be rolled back due to the same error we're currently
|
|
1322
1347
|
* reporting.
|
|
1323
1348
|
*
|
|
1324
|
-
* Instead, to listen to changes to this value, you need to listen to
|
|
1349
|
+
* Instead, to listen to changes to this value, you need to listen to editor's `crash` event.
|
|
1325
1350
|
*
|
|
1326
1351
|
* @internal
|
|
1327
1352
|
*/
|
|
@@ -2024,7 +2049,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2024
2049
|
}
|
|
2025
2050
|
|
|
2026
2051
|
/**
|
|
2027
|
-
* The id of the
|
|
2052
|
+
* The id of the editor's only selected shape.
|
|
2028
2053
|
*
|
|
2029
2054
|
* @returns Null if there is no shape or more than one selected shape, otherwise the selected shape's id.
|
|
2030
2055
|
*
|
|
@@ -2036,7 +2061,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2036
2061
|
}
|
|
2037
2062
|
|
|
2038
2063
|
/**
|
|
2039
|
-
* The
|
|
2064
|
+
* The editor's only selected shape.
|
|
2040
2065
|
*
|
|
2041
2066
|
* @returns Null if there is no shape or more than one selected shape, otherwise the selected shape.
|
|
2042
2067
|
*
|
|
@@ -2277,6 +2302,29 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2277
2302
|
return editingShapeId ? this.getShape(editingShapeId) : undefined
|
|
2278
2303
|
}
|
|
2279
2304
|
|
|
2305
|
+
/**
|
|
2306
|
+
* Whether the shape can be edited.
|
|
2307
|
+
*
|
|
2308
|
+
* @param shape - The shape (or shape id) to check if it can be edited.
|
|
2309
|
+
* @param info - The info about the edit start.
|
|
2310
|
+
*
|
|
2311
|
+
* @public
|
|
2312
|
+
* @returns true if the shape can be edited, false otherwise.
|
|
2313
|
+
*/
|
|
2314
|
+
canEditShape<T extends TLShape | TLShapeId>(shape: T | null, info?: TLEditStartInfo): shape is T {
|
|
2315
|
+
const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
|
|
2316
|
+
if (!id) return false // no shape
|
|
2317
|
+
if (id === this.getEditingShapeId()) return false // already editing this shape
|
|
2318
|
+
const _shape = this.getShape(id)
|
|
2319
|
+
if (!_shape) return false // no shape
|
|
2320
|
+
const util = this.getShapeUtil(_shape)
|
|
2321
|
+
const _info: TLEditStartInfo = info ?? { type: 'unknown' }
|
|
2322
|
+
if (!util.canEdit(_shape, _info)) return false // shape is not editable
|
|
2323
|
+
if (this.getIsReadonly() && !util.canEditInReadonly(_shape)) return false // readonly and no exception
|
|
2324
|
+
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.
|
|
2325
|
+
return true // shape is editable
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2280
2328
|
/**
|
|
2281
2329
|
* Set the current editing shape.
|
|
2282
2330
|
*
|
|
@@ -2292,44 +2340,59 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2292
2340
|
*/
|
|
2293
2341
|
setEditingShape(shape: TLShapeId | TLShape | null): this {
|
|
2294
2342
|
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
2343
|
|
|
2318
|
-
|
|
2344
|
+
if (!id) {
|
|
2345
|
+
// setting the editing shape to null
|
|
2319
2346
|
this.run(
|
|
2320
2347
|
() => {
|
|
2321
|
-
|
|
2322
|
-
this.
|
|
2348
|
+
// Clean up the previous editing shape
|
|
2349
|
+
const prevEditingShapeId = this.getEditingShapeId()
|
|
2323
2350
|
if (prevEditingShapeId) {
|
|
2324
2351
|
const prevEditingShape = this.getShape(prevEditingShapeId)
|
|
2325
2352
|
if (prevEditingShape) {
|
|
2326
2353
|
this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape)
|
|
2327
2354
|
}
|
|
2328
2355
|
}
|
|
2356
|
+
|
|
2357
|
+
// Clean up the editing shape state and rich text editor
|
|
2358
|
+
this._updateCurrentPageState({ editingShapeId: null })
|
|
2359
|
+
this._currentRichTextEditor.set(null)
|
|
2329
2360
|
},
|
|
2330
2361
|
{ history: 'ignore' }
|
|
2331
2362
|
)
|
|
2363
|
+
|
|
2364
|
+
return this
|
|
2332
2365
|
}
|
|
2366
|
+
|
|
2367
|
+
// id was provided but the next editing shape was not editable or didn't exist, so do nothing
|
|
2368
|
+
if (!this.canEditShape(id)) return this
|
|
2369
|
+
|
|
2370
|
+
// id was provided and the next editing shape is editable, so set the rich text editor to null
|
|
2371
|
+
this.run(
|
|
2372
|
+
() => {
|
|
2373
|
+
// Clean up the previous editing shape
|
|
2374
|
+
const prevEditingShapeId = this.getEditingShapeId()
|
|
2375
|
+
if (prevEditingShapeId) {
|
|
2376
|
+
const prevEditingShape = this.getShape(prevEditingShapeId)
|
|
2377
|
+
if (prevEditingShape) {
|
|
2378
|
+
this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape)
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2382
|
+
// Clean up the editing shape state and rich text editor
|
|
2383
|
+
this._updateCurrentPageState({ editingShapeId: null })
|
|
2384
|
+
this._currentRichTextEditor.set(null)
|
|
2385
|
+
|
|
2386
|
+
// Set the new editing shape
|
|
2387
|
+
this.select(id)
|
|
2388
|
+
this._updateCurrentPageState({ editingShapeId: id })
|
|
2389
|
+
|
|
2390
|
+
const nextEditingShape = this.getShape(id)! // shape should be there because canEditShape checked it. Possible small chance that onEditEnd deleted it?
|
|
2391
|
+
this.getShapeUtil(nextEditingShape).onEditStart?.(nextEditingShape)
|
|
2392
|
+
},
|
|
2393
|
+
{ history: 'ignore' }
|
|
2394
|
+
)
|
|
2395
|
+
|
|
2333
2396
|
return this
|
|
2334
2397
|
}
|
|
2335
2398
|
|
|
@@ -2533,6 +2596,26 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2533
2596
|
return this.getCurrentPageState().croppingShapeId
|
|
2534
2597
|
}
|
|
2535
2598
|
|
|
2599
|
+
/**
|
|
2600
|
+
* Whether the shape can be cropped.
|
|
2601
|
+
*
|
|
2602
|
+
* @param shape - The shape (or shape id) to check if it can be cropped.
|
|
2603
|
+
*
|
|
2604
|
+
* @public
|
|
2605
|
+
* @returns true if the shape can be cropped, false otherwise.
|
|
2606
|
+
*/
|
|
2607
|
+
canCropShape<T extends TLShape | TLShapeId>(shape: T | null): shape is T {
|
|
2608
|
+
if (!shape) return false
|
|
2609
|
+
const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
|
|
2610
|
+
if (!id) return false
|
|
2611
|
+
const _shape = this.getShape(id)
|
|
2612
|
+
if (!_shape) return false
|
|
2613
|
+
const util = this.getShapeUtil(_shape)
|
|
2614
|
+
if (!util.canCrop(_shape)) return false
|
|
2615
|
+
if (this.isShapeOrAncestorLocked(_shape)) return false
|
|
2616
|
+
return true
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2536
2619
|
/**
|
|
2537
2620
|
* Set the current cropping shape.
|
|
2538
2621
|
*
|
|
@@ -2554,12 +2637,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2554
2637
|
() => {
|
|
2555
2638
|
if (!id) {
|
|
2556
2639
|
this.updateCurrentPageState({ croppingShapeId: null })
|
|
2557
|
-
} else {
|
|
2558
|
-
|
|
2559
|
-
const util = this.getShapeUtil(shape)
|
|
2560
|
-
if (shape && util.canCrop(shape)) {
|
|
2561
|
-
this.updateCurrentPageState({ croppingShapeId: id })
|
|
2562
|
-
}
|
|
2640
|
+
} else if (this.canCropShape(id)) {
|
|
2641
|
+
this.updateCurrentPageState({ croppingShapeId: id })
|
|
2563
2642
|
}
|
|
2564
2643
|
},
|
|
2565
2644
|
{ history: 'ignore' }
|
|
@@ -2669,6 +2748,52 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2669
2748
|
return this.getCamera().z
|
|
2670
2749
|
}
|
|
2671
2750
|
|
|
2751
|
+
private _debouncedZoomLevel = atom('debounced zoom level', 1)
|
|
2752
|
+
|
|
2753
|
+
/**
|
|
2754
|
+
* Get the debounced zoom level. When the camera is moving, this returns the zoom level
|
|
2755
|
+
* from when the camera started moving rather than the current zoom level. This can be
|
|
2756
|
+
* used to avoid expensive re-renders during camera movements.
|
|
2757
|
+
*
|
|
2758
|
+
* This behavior is controlled by the `useDebouncedZoom` option. When `useDebouncedZoom`
|
|
2759
|
+
* is `false`, this method always returns the current zoom level.
|
|
2760
|
+
*
|
|
2761
|
+
* @public
|
|
2762
|
+
*/
|
|
2763
|
+
@computed getDebouncedZoomLevel() {
|
|
2764
|
+
if (this.options.debouncedZoom) {
|
|
2765
|
+
if (this.getCameraState() === 'idle') {
|
|
2766
|
+
return this.getZoomLevel()
|
|
2767
|
+
} else {
|
|
2768
|
+
return this._debouncedZoomLevel.get()
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
|
|
2772
|
+
return this.getZoomLevel()
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
@computed private _getAboveDebouncedZoomThreshold() {
|
|
2776
|
+
return this.getCurrentPageShapeIds().size > this.options.debouncedZoomThreshold
|
|
2777
|
+
}
|
|
2778
|
+
|
|
2779
|
+
/**
|
|
2780
|
+
* Get the efficient zoom level. This returns the current zoom level if there are less than 300 shapes on the page,
|
|
2781
|
+
* otherwise it returns the debounced zoom level. This can be used to avoid expensive re-renders during camera movements.
|
|
2782
|
+
*
|
|
2783
|
+
* @public
|
|
2784
|
+
* @example
|
|
2785
|
+
* ```ts
|
|
2786
|
+
* editor.getEfficientZoomLevel()
|
|
2787
|
+
* ```
|
|
2788
|
+
*
|
|
2789
|
+
* @public
|
|
2790
|
+
*/
|
|
2791
|
+
@computed getEfficientZoomLevel() {
|
|
2792
|
+
return this._getAboveDebouncedZoomThreshold()
|
|
2793
|
+
? this.getDebouncedZoomLevel()
|
|
2794
|
+
: this.getZoomLevel()
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2672
2797
|
/**
|
|
2673
2798
|
* Get the camera's initial or reset zoom level.
|
|
2674
2799
|
*
|
|
@@ -2995,7 +3120,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2995
3120
|
|
|
2996
3121
|
// Dispatch a new pointer move because the pointer's page will have changed
|
|
2997
3122
|
// (its screen position will compute to a new page position given the new camera position)
|
|
2998
|
-
const
|
|
3123
|
+
const currentScreenPoint = this.inputs.getCurrentScreenPoint()
|
|
3124
|
+
const currentPagePoint = this.inputs.getCurrentPagePoint()
|
|
2999
3125
|
|
|
3000
3126
|
// compare the next page point (derived from the current camera) to the current page point
|
|
3001
3127
|
if (
|
|
@@ -3159,7 +3285,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3159
3285
|
* ```ts
|
|
3160
3286
|
* editor.zoomIn()
|
|
3161
3287
|
* editor.zoomIn(editor.getViewportScreenCenter(), { animation: { duration: 200 } })
|
|
3162
|
-
* editor.zoomIn(editor.inputs.
|
|
3288
|
+
* editor.zoomIn(editor.inputs.getCurrentScreenPoint(), { animation: { duration: 200 } })
|
|
3163
3289
|
* ```
|
|
3164
3290
|
*
|
|
3165
3291
|
* @param point - The screen point to zoom in on. Defaults to the screen center
|
|
@@ -3204,7 +3330,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3204
3330
|
* ```ts
|
|
3205
3331
|
* editor.zoomOut()
|
|
3206
3332
|
* editor.zoomOut(editor.getViewportScreenCenter(), { animation: { duration: 120 } })
|
|
3207
|
-
* editor.zoomOut(editor.inputs.
|
|
3333
|
+
* editor.zoomOut(editor.inputs.getCurrentScreenPoint(), { animation: { duration: 120 } })
|
|
3208
3334
|
* ```
|
|
3209
3335
|
*
|
|
3210
3336
|
* @param point - The point to zoom out on. Defaults to the viewport screen center.
|
|
@@ -3261,10 +3387,17 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3261
3387
|
|
|
3262
3388
|
const selectionPageBounds = this.getSelectionPageBounds()
|
|
3263
3389
|
if (selectionPageBounds) {
|
|
3264
|
-
this.
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3390
|
+
const currentZoom = this.getZoomLevel()
|
|
3391
|
+
// If already at 100%, zoom to fit the selection in the viewport
|
|
3392
|
+
// Otherwise, zoom to 100% centered on the selection
|
|
3393
|
+
if (Math.abs(currentZoom - 1) < 0.01) {
|
|
3394
|
+
this.zoomToBounds(selectionPageBounds, opts)
|
|
3395
|
+
} else {
|
|
3396
|
+
this.zoomToBounds(selectionPageBounds, {
|
|
3397
|
+
targetZoom: 1,
|
|
3398
|
+
...opts,
|
|
3399
|
+
})
|
|
3400
|
+
}
|
|
3268
3401
|
}
|
|
3269
3402
|
return this
|
|
3270
3403
|
}
|
|
@@ -3321,7 +3454,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3321
3454
|
|
|
3322
3455
|
const viewportScreenBounds = this.getViewportScreenBounds()
|
|
3323
3456
|
|
|
3324
|
-
const inset =
|
|
3457
|
+
const inset =
|
|
3458
|
+
opts?.inset ?? Math.min(this.options.zoomToFitPadding, viewportScreenBounds.width * 0.28)
|
|
3325
3459
|
|
|
3326
3460
|
const baseZoom = this.getBaseZoom()
|
|
3327
3461
|
const zoomMin = cameraOptions.zoomSteps[0]
|
|
@@ -3631,22 +3765,23 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3631
3765
|
if (_willSetInitialBounds) {
|
|
3632
3766
|
// If we have just received the initial bounds, don't center the camera.
|
|
3633
3767
|
this.updateInstanceState({ screenBounds: screenBounds.toJson(), insets })
|
|
3768
|
+
this.emit('resize', screenBounds.toJson())
|
|
3634
3769
|
this.setCamera(this.getCamera())
|
|
3635
3770
|
} else {
|
|
3636
3771
|
if (center && !this.getInstanceState().followingUserId) {
|
|
3637
3772
|
// Get the page center before the change, make the change, and restore it
|
|
3638
3773
|
const before = this.getViewportPageBounds().center
|
|
3639
3774
|
this.updateInstanceState({ screenBounds: screenBounds.toJson(), insets })
|
|
3775
|
+
this.emit('resize', screenBounds.toJson())
|
|
3640
3776
|
this.centerOnPoint(before)
|
|
3641
3777
|
} else {
|
|
3642
3778
|
// Otherwise,
|
|
3643
3779
|
this.updateInstanceState({ screenBounds: screenBounds.toJson(), insets })
|
|
3780
|
+
this.emit('resize', screenBounds.toJson())
|
|
3644
3781
|
this._setCamera(Vec.From({ ...this.getCamera() }))
|
|
3645
3782
|
}
|
|
3646
3783
|
}
|
|
3647
3784
|
|
|
3648
|
-
this._tickCameraState()
|
|
3649
|
-
|
|
3650
3785
|
return this
|
|
3651
3786
|
}
|
|
3652
3787
|
|
|
@@ -4052,18 +4187,19 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4052
4187
|
// box just for rendering, and we only update after the camera stops moving.
|
|
4053
4188
|
private _cameraState = atom('camera state', 'idle' as 'idle' | 'moving')
|
|
4054
4189
|
private _cameraStateTimeoutRemaining = 0
|
|
4055
|
-
_decayCameraStateTimeout(elapsed: number) {
|
|
4190
|
+
private _decayCameraStateTimeout(elapsed: number) {
|
|
4056
4191
|
this._cameraStateTimeoutRemaining -= elapsed
|
|
4057
4192
|
if (this._cameraStateTimeoutRemaining > 0) return
|
|
4058
4193
|
this.off('tick', this._decayCameraStateTimeout)
|
|
4059
4194
|
this._cameraState.set('idle')
|
|
4060
4195
|
}
|
|
4061
|
-
_tickCameraState() {
|
|
4196
|
+
private _tickCameraState() {
|
|
4062
4197
|
// always reset the timeout
|
|
4063
4198
|
this._cameraStateTimeoutRemaining = this.options.cameraMovingTimeoutMs
|
|
4064
4199
|
// If the state is idle, then start the tick
|
|
4065
4200
|
if (this._cameraState.__unsafe__getWithoutCapture() !== 'idle') return
|
|
4066
4201
|
this._cameraState.set('moving')
|
|
4202
|
+
this._debouncedZoomLevel.set(unsafe__withoutCapture(() => this.getCamera().z))
|
|
4067
4203
|
this.on('tick', this._decayCameraStateTimeout)
|
|
4068
4204
|
}
|
|
4069
4205
|
|
|
@@ -5010,6 +5146,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5010
5146
|
}
|
|
5011
5147
|
|
|
5012
5148
|
private _notVisibleShapes = notVisibleShapes(this)
|
|
5149
|
+
private _culledShapesCache: Set<TLShapeId> | null = null
|
|
5013
5150
|
|
|
5014
5151
|
/**
|
|
5015
5152
|
* Get culled shapes (those that should not render), taking into account which shapes are selected or editing.
|
|
@@ -5021,16 +5158,41 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5021
5158
|
const notVisibleShapes = this.getNotVisibleShapes()
|
|
5022
5159
|
const selectedShapeIds = this.getSelectedShapeIds()
|
|
5023
5160
|
const editingId = this.getEditingShapeId()
|
|
5024
|
-
const
|
|
5161
|
+
const nextValue = new Set<TLShapeId>(notVisibleShapes)
|
|
5025
5162
|
// we don't cull the shape we are editing
|
|
5026
5163
|
if (editingId) {
|
|
5027
|
-
|
|
5164
|
+
nextValue.delete(editingId)
|
|
5028
5165
|
}
|
|
5029
5166
|
// we also don't cull selected shapes
|
|
5030
5167
|
selectedShapeIds.forEach((id) => {
|
|
5031
|
-
|
|
5168
|
+
nextValue.delete(id)
|
|
5032
5169
|
})
|
|
5033
|
-
|
|
5170
|
+
|
|
5171
|
+
// Cache optimization: return same Set object if contents unchanged
|
|
5172
|
+
// This allows consumers to use === comparison and prevents unnecessary re-renders
|
|
5173
|
+
const prevValue = this._culledShapesCache
|
|
5174
|
+
if (prevValue) {
|
|
5175
|
+
// If sizes differ, contents must differ
|
|
5176
|
+
if (prevValue.size !== nextValue.size) {
|
|
5177
|
+
this._culledShapesCache = nextValue
|
|
5178
|
+
return nextValue
|
|
5179
|
+
}
|
|
5180
|
+
|
|
5181
|
+
// Check if all elements are the same
|
|
5182
|
+
for (const id of prevValue) {
|
|
5183
|
+
if (!nextValue.has(id)) {
|
|
5184
|
+
// Found a difference, update cache and return new set
|
|
5185
|
+
this._culledShapesCache = nextValue
|
|
5186
|
+
return nextValue
|
|
5187
|
+
}
|
|
5188
|
+
}
|
|
5189
|
+
|
|
5190
|
+
// Loop completed without finding differences - contents identical
|
|
5191
|
+
return prevValue
|
|
5192
|
+
}
|
|
5193
|
+
|
|
5194
|
+
this._culledShapesCache = nextValue
|
|
5195
|
+
return nextValue
|
|
5034
5196
|
}
|
|
5035
5197
|
|
|
5036
5198
|
/**
|
|
@@ -5097,11 +5259,18 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5097
5259
|
let inMarginClosestToEdgeDistance = Infinity
|
|
5098
5260
|
let inMarginClosestToEdgeHit: TLShape | null = null
|
|
5099
5261
|
|
|
5262
|
+
// Use larger margin for spatial search to account for edge distance checks
|
|
5263
|
+
const searchMargin = Math.max(innerMargin, outerMargin, this.options.hitTestMargin / zoomLevel)
|
|
5264
|
+
const candidateIds = this.spatialIndex.getShapeIdsAtPoint(point, searchMargin)
|
|
5265
|
+
|
|
5100
5266
|
const shapesToCheck = (
|
|
5101
5267
|
opts.renderingOnly
|
|
5102
5268
|
? this.getCurrentPageRenderingShapesSorted()
|
|
5103
5269
|
: this.getCurrentPageShapesSorted()
|
|
5104
5270
|
).filter((shape) => {
|
|
5271
|
+
// Frames have labels positioned above the shape (outside bounds), so always include them
|
|
5272
|
+
if (!candidateIds.has(shape.id) && !this.isShapeOfType(shape, 'frame')) return false
|
|
5273
|
+
|
|
5105
5274
|
if (
|
|
5106
5275
|
(shape.isLocked && !hitLocked) ||
|
|
5107
5276
|
this.isShapeHidden(shape) ||
|
|
@@ -5287,11 +5456,39 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5287
5456
|
point: VecLike,
|
|
5288
5457
|
opts = {} as { margin?: number; hitInside?: boolean }
|
|
5289
5458
|
): TLShape[] {
|
|
5459
|
+
const margin = opts.margin ?? 0
|
|
5460
|
+
const candidateIds = this.spatialIndex.getShapeIdsAtPoint(point, margin)
|
|
5461
|
+
|
|
5462
|
+
// Get all page shapes in z-index order and filter to candidates that pass isPointInShape
|
|
5463
|
+
// Frames are always checked because their labels can be outside their bounds
|
|
5290
5464
|
return this.getCurrentPageShapesSorted()
|
|
5291
|
-
.filter((shape) =>
|
|
5465
|
+
.filter((shape) => {
|
|
5466
|
+
if (this.isShapeHidden(shape)) return false
|
|
5467
|
+
if (!candidateIds.has(shape.id) && !this.isShapeOfType(shape, 'frame')) return false
|
|
5468
|
+
return this.isPointInShape(shape, point, opts)
|
|
5469
|
+
})
|
|
5292
5470
|
.reverse()
|
|
5293
5471
|
}
|
|
5294
5472
|
|
|
5473
|
+
/**
|
|
5474
|
+
* Get shape IDs within the given bounds.
|
|
5475
|
+
*
|
|
5476
|
+
* Note: Results are unordered. If you need z-order, combine with sorted shapes:
|
|
5477
|
+
* ```ts
|
|
5478
|
+
* const candidates = editor.getShapeIdsInsideBounds(bounds)
|
|
5479
|
+
* const sorted = editor.getCurrentPageShapesSorted().filter(s => candidates.has(s.id))
|
|
5480
|
+
* ```
|
|
5481
|
+
*
|
|
5482
|
+
* @param bounds - The bounds to search within.
|
|
5483
|
+
*
|
|
5484
|
+
* @returns Unordered set of shape IDs within the given bounds.
|
|
5485
|
+
*
|
|
5486
|
+
* @public
|
|
5487
|
+
*/
|
|
5488
|
+
getShapeIdsInsideBounds(bounds: Box): Set<TLShapeId> {
|
|
5489
|
+
return this.spatialIndex.getShapeIdsInsideBounds(bounds)
|
|
5490
|
+
}
|
|
5491
|
+
|
|
5295
5492
|
/**
|
|
5296
5493
|
* Test whether a point (in the current page space) will will a shape. This method takes into account masks,
|
|
5297
5494
|
* such as when a shape is the child of a frame and is partially clipped by the frame.
|
|
@@ -7649,8 +7846,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7649
7846
|
// then if the shape is flipped in one axis only, we need to apply an extra rotation
|
|
7650
7847
|
// to make sure the shape is mirrored correctly
|
|
7651
7848
|
if (Math.sign(scale.x) * Math.sign(scale.y) < 0) {
|
|
7652
|
-
|
|
7653
|
-
rotation
|
|
7849
|
+
// We need to compute the new local rotation that will result in the negated page rotation.
|
|
7850
|
+
// For a shape with local rotation `localRot` and parent page rotation `parentRot`:
|
|
7851
|
+
// - pageRot = parentRot + localRot
|
|
7852
|
+
// - newPageRot = -pageRot (we want to negate the page rotation)
|
|
7853
|
+
// - newPageRot = parentRot + newLocalRot (parent hasn't changed)
|
|
7854
|
+
// - Therefore: newLocalRot = -pageRot - parentRot = -(parentRot + localRot) - parentRot = -localRot - 2*parentRot
|
|
7855
|
+
const parentRotation = this.getShapeParentTransform(id).rotation()
|
|
7856
|
+
const rotation = -options.initialShape.rotation - 2 * parentRotation
|
|
7654
7857
|
this.updateShapes([{ id, type, rotation }])
|
|
7655
7858
|
}
|
|
7656
7859
|
|
|
@@ -7670,9 +7873,13 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7670
7873
|
)
|
|
7671
7874
|
|
|
7672
7875
|
// now calculate how far away the shape is from where it needs to be
|
|
7673
|
-
const pageBounds = this.getShapePageBounds(id)!
|
|
7674
7876
|
const pageTransform = this.getShapePageTransform(id)!
|
|
7675
|
-
|
|
7877
|
+
// We need to use the local bounds center transformed to page space, not the axis-aligned
|
|
7878
|
+
// page bounds center. This is because the page bounds are axis-aligned and their center
|
|
7879
|
+
// changes when the rotation changes, but we want to use the same reference point as
|
|
7880
|
+
// preScaleShapePageCenter (which used initialBounds.center transformed by the page transform).
|
|
7881
|
+
const currentLocalBounds = this.getShapeGeometry(id).bounds
|
|
7882
|
+
const currentPageCenter = Mat.applyToPoint(pageTransform, currentLocalBounds.center)
|
|
7676
7883
|
const shapePageTransformOrigin = pageTransform.point()
|
|
7677
7884
|
if (!currentPageCenter || !shapePageTransformOrigin) return this
|
|
7678
7885
|
const pageDelta = Vec.Sub(postScaleShapePageCenter, currentPageCenter)
|
|
@@ -8120,7 +8327,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8120
8327
|
)
|
|
8121
8328
|
)
|
|
8122
8329
|
const sortedShapeIds = shapesToGroup.sort(sortByIndex).map((s) => s.id)
|
|
8123
|
-
const
|
|
8330
|
+
const childBounds = compact(shapesToGroup.map((shape) => this.getShapePageBounds(shape)))
|
|
8331
|
+
const pageBounds = Box.Common(childBounds)
|
|
8332
|
+
|
|
8333
|
+
if (!pageBounds.isValid()) {
|
|
8334
|
+
throw Error(`Editor.groupShapes: group bounds are invalid (NaN).`)
|
|
8335
|
+
}
|
|
8124
8336
|
|
|
8125
8337
|
const { x, y } = pageBounds.point
|
|
8126
8338
|
|
|
@@ -9145,6 +9357,30 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9145
9357
|
}
|
|
9146
9358
|
}
|
|
9147
9359
|
|
|
9360
|
+
if (point) {
|
|
9361
|
+
const shapesById = new Map<TLShapeId, TLShape>(shapes.map((shape) => [shape.id, shape]))
|
|
9362
|
+
const rootShapesFromContent = compact(rootShapeIds.map((id) => shapesById.get(id)))
|
|
9363
|
+
if (rootShapesFromContent.length > 0) {
|
|
9364
|
+
const targetParent = this.getShapeAtPoint(point, {
|
|
9365
|
+
hitInside: true,
|
|
9366
|
+
hitFrameInside: true,
|
|
9367
|
+
hitLocked: true,
|
|
9368
|
+
filter: (shape) => {
|
|
9369
|
+
const util = this.getShapeUtil(shape)
|
|
9370
|
+
if (!util.canReceiveNewChildrenOfType) return false
|
|
9371
|
+
return rootShapesFromContent.every((rootShape) =>
|
|
9372
|
+
util.canReceiveNewChildrenOfType!(shape, rootShape.type)
|
|
9373
|
+
)
|
|
9374
|
+
},
|
|
9375
|
+
})
|
|
9376
|
+
|
|
9377
|
+
// When pasting at a specific point (e.g. paste-at-cursor) prefer the
|
|
9378
|
+
// parent under the pointer so that we don't keep using the original
|
|
9379
|
+
// selection's parent (which can keep shapes clipped inside frames).
|
|
9380
|
+
pasteParentId = targetParent ? targetParent.id : currentPageId
|
|
9381
|
+
}
|
|
9382
|
+
}
|
|
9383
|
+
|
|
9148
9384
|
let isDuplicating = false
|
|
9149
9385
|
|
|
9150
9386
|
if (!isPageId(pasteParentId)) {
|
|
@@ -9482,126 +9718,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9482
9718
|
|
|
9483
9719
|
/* --------------------- Events --------------------- */
|
|
9484
9720
|
|
|
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
9721
|
/**
|
|
9606
9722
|
* Dispatch a cancel event.
|
|
9607
9723
|
*
|
|
@@ -9671,19 +9787,22 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9671
9787
|
// weird but true: what `inputs` calls screen-space is actually viewport space. so
|
|
9672
9788
|
// we need to convert back into true screen space first. we should fix this...
|
|
9673
9789
|
Vec.Add(
|
|
9674
|
-
this.inputs.
|
|
9790
|
+
this.inputs.getCurrentScreenPoint(),
|
|
9675
9791
|
this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!.screenBounds
|
|
9676
9792
|
),
|
|
9677
9793
|
pointerId: options?.pointerId ?? 0,
|
|
9678
9794
|
button: options?.button ?? 0,
|
|
9679
|
-
isPen: options?.isPen ?? this.inputs.
|
|
9680
|
-
shiftKey: options?.shiftKey ?? this.inputs.
|
|
9681
|
-
altKey: options?.altKey ?? this.inputs.
|
|
9682
|
-
ctrlKey: options?.ctrlKey ?? this.inputs.
|
|
9683
|
-
metaKey: options?.metaKey ?? this.inputs.
|
|
9684
|
-
accelKey:
|
|
9795
|
+
isPen: options?.isPen ?? this.inputs.getIsPen(),
|
|
9796
|
+
shiftKey: options?.shiftKey ?? this.inputs.getShiftKey(),
|
|
9797
|
+
altKey: options?.altKey ?? this.inputs.getAltKey(),
|
|
9798
|
+
ctrlKey: options?.ctrlKey ?? this.inputs.getCtrlKey(),
|
|
9799
|
+
metaKey: options?.metaKey ?? this.inputs.getMetaKey(),
|
|
9800
|
+
accelKey: false,
|
|
9685
9801
|
}
|
|
9686
9802
|
|
|
9803
|
+
// needs to be calculated second
|
|
9804
|
+
event.accelKey = options?.accelKey ?? this.inputs.getAccelKey()
|
|
9805
|
+
|
|
9687
9806
|
if (options?.immediate) {
|
|
9688
9807
|
this._flushEventForTick(event)
|
|
9689
9808
|
} else {
|
|
@@ -10056,16 +10175,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10056
10175
|
/** @internal */
|
|
10057
10176
|
@bind
|
|
10058
10177
|
_setShiftKeyTimeout() {
|
|
10059
|
-
this.inputs.
|
|
10178
|
+
this.inputs.setShiftKey(false)
|
|
10060
10179
|
this.dispatch({
|
|
10061
10180
|
type: 'keyboard',
|
|
10062
10181
|
name: 'key_up',
|
|
10063
10182
|
key: 'Shift',
|
|
10064
|
-
shiftKey: this.inputs.
|
|
10065
|
-
ctrlKey: this.inputs.
|
|
10066
|
-
altKey: this.inputs.
|
|
10067
|
-
metaKey: this.inputs.
|
|
10068
|
-
accelKey:
|
|
10183
|
+
shiftKey: this.inputs.getShiftKey(),
|
|
10184
|
+
ctrlKey: this.inputs.getCtrlKey(),
|
|
10185
|
+
altKey: this.inputs.getAltKey(),
|
|
10186
|
+
metaKey: this.inputs.getMetaKey(),
|
|
10187
|
+
accelKey: this.inputs.getAccelKey(),
|
|
10069
10188
|
code: 'ShiftLeft',
|
|
10070
10189
|
})
|
|
10071
10190
|
}
|
|
@@ -10076,16 +10195,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10076
10195
|
/** @internal */
|
|
10077
10196
|
@bind
|
|
10078
10197
|
_setAltKeyTimeout() {
|
|
10079
|
-
this.inputs.
|
|
10198
|
+
this.inputs.setAltKey(false)
|
|
10080
10199
|
this.dispatch({
|
|
10081
10200
|
type: 'keyboard',
|
|
10082
10201
|
name: 'key_up',
|
|
10083
10202
|
key: 'Alt',
|
|
10084
|
-
shiftKey: this.inputs.
|
|
10085
|
-
ctrlKey: this.inputs.
|
|
10086
|
-
altKey: this.inputs.
|
|
10087
|
-
metaKey: this.inputs.
|
|
10088
|
-
accelKey:
|
|
10203
|
+
shiftKey: this.inputs.getShiftKey(),
|
|
10204
|
+
ctrlKey: this.inputs.getCtrlKey(),
|
|
10205
|
+
altKey: this.inputs.getAltKey(),
|
|
10206
|
+
metaKey: this.inputs.getMetaKey(),
|
|
10207
|
+
accelKey: this.inputs.getAccelKey(),
|
|
10089
10208
|
code: 'AltLeft',
|
|
10090
10209
|
})
|
|
10091
10210
|
}
|
|
@@ -10096,16 +10215,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10096
10215
|
/** @internal */
|
|
10097
10216
|
@bind
|
|
10098
10217
|
_setCtrlKeyTimeout() {
|
|
10099
|
-
this.inputs.
|
|
10218
|
+
this.inputs.setCtrlKey(false)
|
|
10100
10219
|
this.dispatch({
|
|
10101
10220
|
type: 'keyboard',
|
|
10102
10221
|
name: 'key_up',
|
|
10103
10222
|
key: 'Ctrl',
|
|
10104
|
-
shiftKey: this.inputs.
|
|
10105
|
-
ctrlKey: this.inputs.
|
|
10106
|
-
altKey: this.inputs.
|
|
10107
|
-
metaKey: this.inputs.
|
|
10108
|
-
accelKey:
|
|
10223
|
+
shiftKey: this.inputs.getShiftKey(),
|
|
10224
|
+
ctrlKey: this.inputs.getCtrlKey(),
|
|
10225
|
+
altKey: this.inputs.getAltKey(),
|
|
10226
|
+
metaKey: this.inputs.getMetaKey(),
|
|
10227
|
+
accelKey: this.inputs.getAccelKey(),
|
|
10109
10228
|
code: 'ControlLeft',
|
|
10110
10229
|
})
|
|
10111
10230
|
}
|
|
@@ -10116,16 +10235,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10116
10235
|
/** @internal */
|
|
10117
10236
|
@bind
|
|
10118
10237
|
_setMetaKeyTimeout() {
|
|
10119
|
-
this.inputs.
|
|
10238
|
+
this.inputs.setMetaKey(false)
|
|
10120
10239
|
this.dispatch({
|
|
10121
10240
|
type: 'keyboard',
|
|
10122
10241
|
name: 'key_up',
|
|
10123
10242
|
key: 'Meta',
|
|
10124
|
-
shiftKey: this.inputs.
|
|
10125
|
-
ctrlKey: this.inputs.
|
|
10126
|
-
altKey: this.inputs.
|
|
10127
|
-
metaKey: this.inputs.
|
|
10128
|
-
accelKey:
|
|
10243
|
+
shiftKey: this.inputs.getShiftKey(),
|
|
10244
|
+
ctrlKey: this.inputs.getCtrlKey(),
|
|
10245
|
+
altKey: this.inputs.getAltKey(),
|
|
10246
|
+
metaKey: this.inputs.getMetaKey(),
|
|
10247
|
+
accelKey: this.inputs.getAccelKey(),
|
|
10129
10248
|
code: 'MetaLeft',
|
|
10130
10249
|
})
|
|
10131
10250
|
}
|
|
@@ -10133,9 +10252,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10133
10252
|
/** @internal */
|
|
10134
10253
|
private _restoreToolId = 'select'
|
|
10135
10254
|
|
|
10136
|
-
/** @internal */
|
|
10137
|
-
private _pinchStart = 1
|
|
10138
|
-
|
|
10139
10255
|
/** @internal */
|
|
10140
10256
|
private _didPinch = false
|
|
10141
10257
|
|
|
@@ -10242,56 +10358,54 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10242
10358
|
if (info.type === 'misc') {
|
|
10243
10359
|
// stop panning if the interaction is cancelled or completed
|
|
10244
10360
|
if (info.name === 'cancel' || info.name === 'complete') {
|
|
10245
|
-
this.inputs.
|
|
10361
|
+
this.inputs.setIsDragging(false)
|
|
10246
10362
|
|
|
10247
|
-
if (this.inputs.
|
|
10248
|
-
this.inputs.
|
|
10249
|
-
this.inputs.
|
|
10363
|
+
if (this.inputs.getIsPanning()) {
|
|
10364
|
+
this.inputs.setIsPanning(false)
|
|
10365
|
+
this.inputs.setIsSpacebarPanning(false)
|
|
10250
10366
|
this.setCursor({ type: this._prevCursor, rotation: 0 })
|
|
10251
10367
|
}
|
|
10252
10368
|
}
|
|
10253
10369
|
|
|
10254
|
-
this.emit('event', info)
|
|
10255
10370
|
this.root.handleEvent(info)
|
|
10371
|
+
this.emit('event', info)
|
|
10256
10372
|
return
|
|
10257
10373
|
}
|
|
10258
10374
|
|
|
10259
10375
|
if (info.shiftKey) {
|
|
10260
10376
|
clearTimeout(this._shiftKeyTimeout)
|
|
10261
10377
|
this._shiftKeyTimeout = -1
|
|
10262
|
-
inputs.
|
|
10263
|
-
} else if (!info.shiftKey && inputs.
|
|
10378
|
+
inputs.setShiftKey(true)
|
|
10379
|
+
} else if (!info.shiftKey && inputs.getShiftKey() && this._shiftKeyTimeout === -1) {
|
|
10264
10380
|
this._shiftKeyTimeout = this.timers.setTimeout(this._setShiftKeyTimeout, 150)
|
|
10265
10381
|
}
|
|
10266
10382
|
|
|
10267
10383
|
if (info.altKey) {
|
|
10268
10384
|
clearTimeout(this._altKeyTimeout)
|
|
10269
10385
|
this._altKeyTimeout = -1
|
|
10270
|
-
inputs.
|
|
10271
|
-
} else if (!info.altKey && inputs.
|
|
10386
|
+
inputs.setAltKey(true)
|
|
10387
|
+
} else if (!info.altKey && inputs.getAltKey() && this._altKeyTimeout === -1) {
|
|
10272
10388
|
this._altKeyTimeout = this.timers.setTimeout(this._setAltKeyTimeout, 150)
|
|
10273
10389
|
}
|
|
10274
10390
|
|
|
10275
10391
|
if (info.ctrlKey) {
|
|
10276
10392
|
clearTimeout(this._ctrlKeyTimeout)
|
|
10277
10393
|
this._ctrlKeyTimeout = -1
|
|
10278
|
-
inputs.
|
|
10279
|
-
} else if (!info.ctrlKey && inputs.
|
|
10394
|
+
inputs.setCtrlKey(true)
|
|
10395
|
+
} else if (!info.ctrlKey && inputs.getCtrlKey() && this._ctrlKeyTimeout === -1) {
|
|
10280
10396
|
this._ctrlKeyTimeout = this.timers.setTimeout(this._setCtrlKeyTimeout, 150)
|
|
10281
10397
|
}
|
|
10282
10398
|
|
|
10283
10399
|
if (info.metaKey) {
|
|
10284
10400
|
clearTimeout(this._metaKeyTimeout)
|
|
10285
10401
|
this._metaKeyTimeout = -1
|
|
10286
|
-
inputs.
|
|
10287
|
-
} else if (!info.metaKey && inputs.
|
|
10402
|
+
inputs.setMetaKey(true)
|
|
10403
|
+
} else if (!info.metaKey && inputs.getMetaKey() && this._metaKeyTimeout === -1) {
|
|
10288
10404
|
this._metaKeyTimeout = this.timers.setTimeout(this._setMetaKeyTimeout, 150)
|
|
10289
10405
|
}
|
|
10290
10406
|
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
if (!inputs.isPointing) {
|
|
10294
|
-
inputs.isDragging = false
|
|
10407
|
+
if (!inputs.getIsPointing()) {
|
|
10408
|
+
inputs.setIsDragging(false)
|
|
10295
10409
|
}
|
|
10296
10410
|
|
|
10297
10411
|
const instanceState = this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!
|
|
@@ -10302,29 +10416,29 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10302
10416
|
case 'pinch': {
|
|
10303
10417
|
if (cameraOptions.isLocked) return
|
|
10304
10418
|
clearTimeout(this._longPressTimeout)
|
|
10305
|
-
this.
|
|
10419
|
+
this.inputs.updateFromEvent(info)
|
|
10306
10420
|
|
|
10307
10421
|
switch (info.name) {
|
|
10308
10422
|
case 'pinch_start': {
|
|
10309
|
-
if (inputs.
|
|
10423
|
+
if (inputs.getIsPinching()) return
|
|
10310
10424
|
|
|
10311
|
-
if (!inputs.
|
|
10312
|
-
this._pinchStart = this.getCamera().z
|
|
10425
|
+
if (!inputs.getIsEditing()) {
|
|
10313
10426
|
if (!this._selectedShapeIdsAtPointerDown.length) {
|
|
10314
10427
|
this._selectedShapeIdsAtPointerDown = [...pageState.selectedShapeIds]
|
|
10315
10428
|
}
|
|
10316
10429
|
|
|
10317
10430
|
this._didPinch = true
|
|
10318
10431
|
|
|
10319
|
-
inputs.
|
|
10432
|
+
inputs.setIsPinching(true)
|
|
10320
10433
|
|
|
10321
10434
|
this.interrupt()
|
|
10322
10435
|
}
|
|
10323
10436
|
|
|
10437
|
+
this.emit('event', info)
|
|
10324
10438
|
return // Stop here!
|
|
10325
10439
|
}
|
|
10326
10440
|
case 'pinch': {
|
|
10327
|
-
if (!inputs.
|
|
10441
|
+
if (!inputs.getIsPinching()) return
|
|
10328
10442
|
|
|
10329
10443
|
const {
|
|
10330
10444
|
point: { z = 1 },
|
|
@@ -10355,13 +10469,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10355
10469
|
{ immediate: true }
|
|
10356
10470
|
)
|
|
10357
10471
|
|
|
10472
|
+
this.emit('event', info)
|
|
10358
10473
|
return // Stop here!
|
|
10359
10474
|
}
|
|
10360
10475
|
case 'pinch_end': {
|
|
10361
|
-
if (!inputs.
|
|
10476
|
+
if (!inputs.getIsPinching()) return this
|
|
10362
10477
|
|
|
10363
10478
|
// Stop pinching
|
|
10364
|
-
inputs.
|
|
10479
|
+
inputs.setIsPinching(false)
|
|
10365
10480
|
|
|
10366
10481
|
// Stash and clear the shapes that were selected when the pinch started
|
|
10367
10482
|
const { _selectedShapeIdsAtPointerDown: shapesToReselect } = this
|
|
@@ -10381,6 +10496,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10381
10496
|
}
|
|
10382
10497
|
}
|
|
10383
10498
|
|
|
10499
|
+
this.emit('event', info)
|
|
10384
10500
|
return // Stop here!
|
|
10385
10501
|
}
|
|
10386
10502
|
}
|
|
@@ -10388,7 +10504,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10388
10504
|
case 'wheel': {
|
|
10389
10505
|
if (cameraOptions.isLocked) return
|
|
10390
10506
|
|
|
10391
|
-
this.
|
|
10507
|
+
this.inputs.updateFromEvent(info)
|
|
10392
10508
|
|
|
10393
10509
|
const { panSpeed, zoomSpeed } = cameraOptions
|
|
10394
10510
|
let wheelBehavior = cameraOptions.wheelBehavior
|
|
@@ -10419,7 +10535,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10419
10535
|
switch (behavior) {
|
|
10420
10536
|
case 'zoom': {
|
|
10421
10537
|
// Zoom in on current screen point using the wheel delta
|
|
10422
|
-
const { x, y } = this.inputs.
|
|
10538
|
+
const { x, y } = this.inputs.getCurrentScreenPoint()
|
|
10423
10539
|
let delta = dz
|
|
10424
10540
|
|
|
10425
10541
|
// If we're forcing zoom, then we need to do the wheel normalization math here
|
|
@@ -10436,6 +10552,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10436
10552
|
immediate: true,
|
|
10437
10553
|
})
|
|
10438
10554
|
this.maybeTrackPerformance('Zooming')
|
|
10555
|
+
this.root.handleEvent(info)
|
|
10556
|
+
this.emit('event', info)
|
|
10439
10557
|
return
|
|
10440
10558
|
}
|
|
10441
10559
|
case 'pan': {
|
|
@@ -10444,6 +10562,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10444
10562
|
immediate: true,
|
|
10445
10563
|
})
|
|
10446
10564
|
this.maybeTrackPerformance('Panning')
|
|
10565
|
+
this.root.handleEvent(info)
|
|
10566
|
+
this.emit('event', info)
|
|
10447
10567
|
return
|
|
10448
10568
|
}
|
|
10449
10569
|
}
|
|
@@ -10452,9 +10572,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10452
10572
|
}
|
|
10453
10573
|
case 'pointer': {
|
|
10454
10574
|
// Ignore pointer events while we're pinching
|
|
10455
|
-
if (inputs.
|
|
10575
|
+
if (inputs.getIsPinching()) return
|
|
10456
10576
|
|
|
10457
|
-
this.
|
|
10577
|
+
this.inputs.updateFromEvent(info)
|
|
10458
10578
|
const { isPen } = info
|
|
10459
10579
|
const { isPenMode } = instanceState
|
|
10460
10580
|
|
|
@@ -10463,7 +10583,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10463
10583
|
// If we're in pen mode and the input is not a pen type, then stop here
|
|
10464
10584
|
if (isPenMode && !isPen) return
|
|
10465
10585
|
|
|
10466
|
-
if (!this.inputs.
|
|
10586
|
+
if (!this.inputs.getIsPanning()) {
|
|
10467
10587
|
// Start a long press timeout
|
|
10468
10588
|
this._longPressTimeout = this.timers.setTimeout(() => {
|
|
10469
10589
|
const vsb = this.getViewportScreenBounds()
|
|
@@ -10473,7 +10593,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10473
10593
|
// viewport bounds, and will be again when this event is handled...
|
|
10474
10594
|
// so we need to counter-adjust from the stored value so that the
|
|
10475
10595
|
// new value is set correctly.
|
|
10476
|
-
point: this.inputs.
|
|
10596
|
+
point: this.inputs.getOriginScreenPoint().clone().addXY(vsb.x, vsb.y),
|
|
10477
10597
|
name: 'long_press',
|
|
10478
10598
|
})
|
|
10479
10599
|
}, this.options.longPressDurationMs)
|
|
@@ -10490,8 +10610,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10490
10610
|
inputs.buttons.add(info.button)
|
|
10491
10611
|
|
|
10492
10612
|
// Start pointing and stop dragging
|
|
10493
|
-
inputs.
|
|
10494
|
-
inputs.
|
|
10613
|
+
inputs.setIsPointing(true)
|
|
10614
|
+
inputs.setIsDragging(false)
|
|
10495
10615
|
|
|
10496
10616
|
// If pen mode is off but we're not already in pen mode, turn that on
|
|
10497
10617
|
if (!isPenMode && isPen) this.updateInstanceState({ isPenMode: true })
|
|
@@ -10503,16 +10623,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10503
10623
|
this.setCurrentTool('eraser')
|
|
10504
10624
|
} else if (info.button === MIDDLE_MOUSE_BUTTON) {
|
|
10505
10625
|
// Middle mouse pan activates panning unless we're already panning (with spacebar)
|
|
10506
|
-
if (!this.inputs.
|
|
10626
|
+
if (!this.inputs.getIsPanning()) {
|
|
10507
10627
|
this._prevCursor = this.getInstanceState().cursor.type
|
|
10508
10628
|
}
|
|
10509
|
-
this.inputs.
|
|
10629
|
+
this.inputs.setIsPanning(true)
|
|
10510
10630
|
clearTimeout(this._longPressTimeout)
|
|
10511
10631
|
}
|
|
10512
10632
|
|
|
10513
10633
|
// We might be panning because we did a middle mouse click, or because we're holding spacebar and started a regular click
|
|
10514
10634
|
// Also stop here, we don't want the state chart to receive the event
|
|
10515
|
-
if (this.inputs.
|
|
10635
|
+
if (this.inputs.getIsPanning()) {
|
|
10516
10636
|
this.stopCameraAnimation()
|
|
10517
10637
|
this.setCursor({ type: 'grabbing', rotation: 0 })
|
|
10518
10638
|
return this
|
|
@@ -10527,9 +10647,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10527
10647
|
const { x: cx, y: cy, z: cz } = unsafe__withoutCapture(() => this.getCamera())
|
|
10528
10648
|
|
|
10529
10649
|
// If we've started panning, then clear any long press timeout
|
|
10530
|
-
if (this.inputs.
|
|
10650
|
+
if (this.inputs.getIsPanning() && this.inputs.getIsPointing()) {
|
|
10531
10651
|
// Handle spacebar / middle mouse button panning
|
|
10532
|
-
const
|
|
10652
|
+
const currentScreenPoint = this.inputs.getCurrentScreenPoint()
|
|
10653
|
+
const previousScreenPoint = this.inputs.getPreviousScreenPoint()
|
|
10533
10654
|
const offset = Vec.Sub(currentScreenPoint, previousScreenPoint)
|
|
10534
10655
|
this.setCamera(new Vec(cx + offset.x / cz, cy + offset.y / cz, cz), {
|
|
10535
10656
|
immediate: true,
|
|
@@ -10539,24 +10660,25 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10539
10660
|
}
|
|
10540
10661
|
|
|
10541
10662
|
if (
|
|
10542
|
-
inputs.
|
|
10543
|
-
!inputs.
|
|
10544
|
-
Vec.Dist2(
|
|
10663
|
+
inputs.getIsPointing() &&
|
|
10664
|
+
!inputs.getIsDragging() &&
|
|
10665
|
+
Vec.Dist2(inputs.getOriginPagePoint(), inputs.getCurrentPagePoint()) *
|
|
10666
|
+
this.getZoomLevel() >
|
|
10545
10667
|
(instanceState.isCoarsePointer
|
|
10546
10668
|
? this.options.coarseDragDistanceSquared
|
|
10547
10669
|
: this.options.dragDistanceSquared) /
|
|
10548
10670
|
cz
|
|
10549
10671
|
) {
|
|
10550
10672
|
// Start dragging
|
|
10551
|
-
inputs.
|
|
10673
|
+
inputs.setIsDragging(true)
|
|
10552
10674
|
clearTimeout(this._longPressTimeout)
|
|
10553
10675
|
}
|
|
10554
10676
|
break
|
|
10555
10677
|
}
|
|
10556
10678
|
case 'pointer_up': {
|
|
10557
10679
|
// Stop dragging / pointing
|
|
10558
|
-
inputs.
|
|
10559
|
-
inputs.
|
|
10680
|
+
inputs.setIsDragging(false)
|
|
10681
|
+
inputs.setIsPointing(false)
|
|
10560
10682
|
clearTimeout(this._longPressTimeout)
|
|
10561
10683
|
|
|
10562
10684
|
// Remove the button from the buttons set
|
|
@@ -10573,12 +10695,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10573
10695
|
info.button = 0
|
|
10574
10696
|
}
|
|
10575
10697
|
|
|
10576
|
-
if (inputs.
|
|
10698
|
+
if (inputs.getIsPanning()) {
|
|
10577
10699
|
if (!inputs.keys.has('Space')) {
|
|
10578
|
-
inputs.
|
|
10579
|
-
inputs.
|
|
10700
|
+
inputs.setIsPanning(false)
|
|
10701
|
+
inputs.setIsSpacebarPanning(false)
|
|
10580
10702
|
}
|
|
10581
|
-
const slideDirection = this.inputs.
|
|
10703
|
+
const slideDirection = this.inputs.getPointerVelocity()
|
|
10582
10704
|
const slideSpeed = Math.min(2, slideDirection.len())
|
|
10583
10705
|
|
|
10584
10706
|
switch (info.button) {
|
|
@@ -10622,43 +10744,48 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10622
10744
|
// Add the key from the keys set
|
|
10623
10745
|
inputs.keys.add(info.code)
|
|
10624
10746
|
|
|
10625
|
-
|
|
10626
|
-
|
|
10627
|
-
if (!
|
|
10628
|
-
this.
|
|
10629
|
-
|
|
10747
|
+
if (this.options.spacebarPanning) {
|
|
10748
|
+
// If the space key is pressed (but meta / control isn't!) activate panning
|
|
10749
|
+
if (info.code === 'Space' && !info.ctrlKey) {
|
|
10750
|
+
if (!this.inputs.getIsPanning()) {
|
|
10751
|
+
this._prevCursor = instanceState.cursor.type
|
|
10752
|
+
}
|
|
10630
10753
|
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
|
|
10634
|
-
|
|
10635
|
-
|
|
10754
|
+
this.inputs.setIsPanning(true)
|
|
10755
|
+
this.inputs.setIsSpacebarPanning(true)
|
|
10756
|
+
clearTimeout(this._longPressTimeout)
|
|
10757
|
+
this.setCursor({
|
|
10758
|
+
type: this.inputs.getIsPointing() ? 'grabbing' : 'grab',
|
|
10759
|
+
rotation: 0,
|
|
10760
|
+
})
|
|
10761
|
+
}
|
|
10636
10762
|
|
|
10637
|
-
|
|
10638
|
-
|
|
10639
|
-
|
|
10640
|
-
|
|
10641
|
-
|
|
10642
|
-
|
|
10643
|
-
|
|
10644
|
-
|
|
10645
|
-
|
|
10646
|
-
|
|
10647
|
-
|
|
10648
|
-
|
|
10649
|
-
|
|
10650
|
-
|
|
10651
|
-
|
|
10652
|
-
|
|
10653
|
-
|
|
10654
|
-
|
|
10763
|
+
if (this.inputs.getIsSpacebarPanning()) {
|
|
10764
|
+
let offset: Vec | undefined
|
|
10765
|
+
switch (info.code) {
|
|
10766
|
+
case 'ArrowUp': {
|
|
10767
|
+
offset = new Vec(0, -1)
|
|
10768
|
+
break
|
|
10769
|
+
}
|
|
10770
|
+
case 'ArrowRight': {
|
|
10771
|
+
offset = new Vec(1, 0)
|
|
10772
|
+
break
|
|
10773
|
+
}
|
|
10774
|
+
case 'ArrowDown': {
|
|
10775
|
+
offset = new Vec(0, 1)
|
|
10776
|
+
break
|
|
10777
|
+
}
|
|
10778
|
+
case 'ArrowLeft': {
|
|
10779
|
+
offset = new Vec(-1, 0)
|
|
10780
|
+
break
|
|
10781
|
+
}
|
|
10655
10782
|
}
|
|
10656
|
-
}
|
|
10657
10783
|
|
|
10658
|
-
|
|
10659
|
-
|
|
10660
|
-
|
|
10661
|
-
|
|
10784
|
+
if (offset) {
|
|
10785
|
+
const bounds = this.getViewportPageBounds()
|
|
10786
|
+
const next = bounds.clone().translate(offset.mulV({ x: bounds.w, y: bounds.h }))
|
|
10787
|
+
this._animateToViewport(next, { animation: { duration: 320 } })
|
|
10788
|
+
}
|
|
10662
10789
|
}
|
|
10663
10790
|
}
|
|
10664
10791
|
|
|
@@ -10668,15 +10795,17 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10668
10795
|
// Remove the key from the keys set
|
|
10669
10796
|
inputs.keys.delete(info.code)
|
|
10670
10797
|
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
if (
|
|
10674
|
-
|
|
10675
|
-
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10798
|
+
if (this.options.spacebarPanning) {
|
|
10799
|
+
// If we've lifted the space key,
|
|
10800
|
+
if (info.code === 'Space') {
|
|
10801
|
+
if (this.inputs.buttons.has(MIDDLE_MOUSE_BUTTON)) {
|
|
10802
|
+
// If we're still middle dragging, continue panning
|
|
10803
|
+
} else {
|
|
10804
|
+
// otherwise, stop panning
|
|
10805
|
+
this.inputs.setIsPanning(false)
|
|
10806
|
+
this.inputs.setIsSpacebarPanning(false)
|
|
10807
|
+
this.setCursor({ type: this._prevCursor, rotation: 0 })
|
|
10808
|
+
}
|
|
10680
10809
|
}
|
|
10681
10810
|
}
|
|
10682
10811
|
break
|