@tldraw/editor 4.3.0-canary.c5efe11c58e0 → 4.3.0-canary.cb6779b4f066
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/README.md +1 -1
- package/dist-cjs/index.d.ts +448 -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 +346 -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 +448 -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 +347 -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 +452 -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,28 @@ 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
|
+
private readonly _spatialIndex: SpatialIndexManager
|
|
903
|
+
|
|
893
904
|
/**
|
|
894
905
|
* A manager for the any asynchronous events and making sure they're
|
|
895
906
|
* cleaned up upon disposal.
|
|
@@ -969,6 +980,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
969
980
|
this.disposables.clear()
|
|
970
981
|
this.store.dispose()
|
|
971
982
|
this.isDisposed = true
|
|
983
|
+
this.emit('dispose')
|
|
972
984
|
}
|
|
973
985
|
|
|
974
986
|
/* ------------------- Shape Utils ------------------ */
|
|
@@ -1060,7 +1072,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1060
1072
|
/* --------------------- History -------------------- */
|
|
1061
1073
|
|
|
1062
1074
|
/**
|
|
1063
|
-
* A manager for the
|
|
1075
|
+
* A manager for the editor's history.
|
|
1064
1076
|
*
|
|
1065
1077
|
* @readonly
|
|
1066
1078
|
*/
|
|
@@ -1084,14 +1096,18 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1084
1096
|
}
|
|
1085
1097
|
|
|
1086
1098
|
/**
|
|
1087
|
-
* Whether the
|
|
1099
|
+
* Whether the editor can undo.
|
|
1088
1100
|
*
|
|
1089
1101
|
* @public
|
|
1090
1102
|
*/
|
|
1091
|
-
@computed
|
|
1103
|
+
@computed canUndo(): boolean {
|
|
1092
1104
|
return this.history.getNumUndos() > 0
|
|
1093
1105
|
}
|
|
1094
1106
|
|
|
1107
|
+
getCanUndo() {
|
|
1108
|
+
return this.canUndo()
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1095
1111
|
/**
|
|
1096
1112
|
* Redo to the next mark.
|
|
1097
1113
|
*
|
|
@@ -1109,20 +1125,24 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1109
1125
|
return this
|
|
1110
1126
|
}
|
|
1111
1127
|
|
|
1112
|
-
clearHistory() {
|
|
1113
|
-
this.history.clear()
|
|
1114
|
-
return this
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
1128
|
/**
|
|
1118
|
-
* Whether the
|
|
1129
|
+
* Whether the editor can redo.
|
|
1119
1130
|
*
|
|
1120
1131
|
* @public
|
|
1121
1132
|
*/
|
|
1122
|
-
@computed
|
|
1133
|
+
@computed canRedo(): boolean {
|
|
1123
1134
|
return this.history.getNumRedos() > 0
|
|
1124
1135
|
}
|
|
1125
1136
|
|
|
1137
|
+
getCanRedo() {
|
|
1138
|
+
return this.canRedo()
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
clearHistory() {
|
|
1142
|
+
this.history.clear()
|
|
1143
|
+
return this
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1126
1146
|
/**
|
|
1127
1147
|
* Create a new "mark", or stopping point, in the undo redo history. Creating a mark will clear
|
|
1128
1148
|
* any redos. You typically want to do this just before a user interaction begins or is handled.
|
|
@@ -1296,7 +1316,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1296
1316
|
}),
|
|
1297
1317
|
selectionCount: this.getSelectedShapes().length,
|
|
1298
1318
|
editingShape: editingShapeId ? this.getShape(editingShapeId) : undefined,
|
|
1299
|
-
inputs: this.inputs,
|
|
1319
|
+
inputs: this.inputs.toJson(),
|
|
1300
1320
|
pageState: this.getCurrentPageState(),
|
|
1301
1321
|
instanceState: this.getInstanceState(),
|
|
1302
1322
|
collaboratorCount: this.getCollaboratorsOnCurrentPage().length,
|
|
@@ -1321,7 +1341,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1321
1341
|
* we're in a transaction that's about to be rolled back due to the same error we're currently
|
|
1322
1342
|
* reporting.
|
|
1323
1343
|
*
|
|
1324
|
-
* Instead, to listen to changes to this value, you need to listen to
|
|
1344
|
+
* Instead, to listen to changes to this value, you need to listen to editor's `crash` event.
|
|
1325
1345
|
*
|
|
1326
1346
|
* @internal
|
|
1327
1347
|
*/
|
|
@@ -2024,7 +2044,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2024
2044
|
}
|
|
2025
2045
|
|
|
2026
2046
|
/**
|
|
2027
|
-
* The id of the
|
|
2047
|
+
* The id of the editor's only selected shape.
|
|
2028
2048
|
*
|
|
2029
2049
|
* @returns Null if there is no shape or more than one selected shape, otherwise the selected shape's id.
|
|
2030
2050
|
*
|
|
@@ -2036,7 +2056,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2036
2056
|
}
|
|
2037
2057
|
|
|
2038
2058
|
/**
|
|
2039
|
-
* The
|
|
2059
|
+
* The editor's only selected shape.
|
|
2040
2060
|
*
|
|
2041
2061
|
* @returns Null if there is no shape or more than one selected shape, otherwise the selected shape.
|
|
2042
2062
|
*
|
|
@@ -2277,6 +2297,29 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2277
2297
|
return editingShapeId ? this.getShape(editingShapeId) : undefined
|
|
2278
2298
|
}
|
|
2279
2299
|
|
|
2300
|
+
/**
|
|
2301
|
+
* Whether the shape can be edited.
|
|
2302
|
+
*
|
|
2303
|
+
* @param shape - The shape (or shape id) to check if it can be edited.
|
|
2304
|
+
* @param info - The info about the edit start.
|
|
2305
|
+
*
|
|
2306
|
+
* @public
|
|
2307
|
+
* @returns true if the shape can be edited, false otherwise.
|
|
2308
|
+
*/
|
|
2309
|
+
canEditShape<T extends TLShape | TLShapeId>(shape: T | null, info?: TLEditStartInfo): shape is T {
|
|
2310
|
+
const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
|
|
2311
|
+
if (!id) return false // no shape
|
|
2312
|
+
if (id === this.getEditingShapeId()) return false // already editing this shape
|
|
2313
|
+
const _shape = this.getShape(id)
|
|
2314
|
+
if (!_shape) return false // no shape
|
|
2315
|
+
const util = this.getShapeUtil(_shape)
|
|
2316
|
+
const _info: TLEditStartInfo = info ?? { type: 'unknown' }
|
|
2317
|
+
if (!util.canEdit(_shape, _info)) return false // shape is not editable
|
|
2318
|
+
if (this.getIsReadonly() && !util.canEditInReadonly(_shape)) return false // readonly and no exception
|
|
2319
|
+
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.
|
|
2320
|
+
return true // shape is editable
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2280
2323
|
/**
|
|
2281
2324
|
* Set the current editing shape.
|
|
2282
2325
|
*
|
|
@@ -2292,44 +2335,59 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2292
2335
|
*/
|
|
2293
2336
|
setEditingShape(shape: TLShapeId | TLShape | null): this {
|
|
2294
2337
|
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
2338
|
|
|
2318
|
-
|
|
2339
|
+
if (!id) {
|
|
2340
|
+
// setting the editing shape to null
|
|
2319
2341
|
this.run(
|
|
2320
2342
|
() => {
|
|
2321
|
-
|
|
2322
|
-
this.
|
|
2343
|
+
// Clean up the previous editing shape
|
|
2344
|
+
const prevEditingShapeId = this.getEditingShapeId()
|
|
2323
2345
|
if (prevEditingShapeId) {
|
|
2324
2346
|
const prevEditingShape = this.getShape(prevEditingShapeId)
|
|
2325
2347
|
if (prevEditingShape) {
|
|
2326
2348
|
this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape)
|
|
2327
2349
|
}
|
|
2328
2350
|
}
|
|
2351
|
+
|
|
2352
|
+
// Clean up the editing shape state and rich text editor
|
|
2353
|
+
this._updateCurrentPageState({ editingShapeId: null })
|
|
2354
|
+
this._currentRichTextEditor.set(null)
|
|
2329
2355
|
},
|
|
2330
2356
|
{ history: 'ignore' }
|
|
2331
2357
|
)
|
|
2358
|
+
|
|
2359
|
+
return this
|
|
2332
2360
|
}
|
|
2361
|
+
|
|
2362
|
+
// id was provided but the next editing shape was not editable or didn't exist, so do nothing
|
|
2363
|
+
if (!this.canEditShape(id)) return this
|
|
2364
|
+
|
|
2365
|
+
// id was provided and the next editing shape is editable, so set the rich text editor to null
|
|
2366
|
+
this.run(
|
|
2367
|
+
() => {
|
|
2368
|
+
// Clean up the previous editing shape
|
|
2369
|
+
const prevEditingShapeId = this.getEditingShapeId()
|
|
2370
|
+
if (prevEditingShapeId) {
|
|
2371
|
+
const prevEditingShape = this.getShape(prevEditingShapeId)
|
|
2372
|
+
if (prevEditingShape) {
|
|
2373
|
+
this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape)
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
// Clean up the editing shape state and rich text editor
|
|
2378
|
+
this._updateCurrentPageState({ editingShapeId: null })
|
|
2379
|
+
this._currentRichTextEditor.set(null)
|
|
2380
|
+
|
|
2381
|
+
// Set the new editing shape
|
|
2382
|
+
this.select(id)
|
|
2383
|
+
this._updateCurrentPageState({ editingShapeId: id })
|
|
2384
|
+
|
|
2385
|
+
const nextEditingShape = this.getShape(id)! // shape should be there because canEditShape checked it. Possible small chance that onEditEnd deleted it?
|
|
2386
|
+
this.getShapeUtil(nextEditingShape).onEditStart?.(nextEditingShape)
|
|
2387
|
+
},
|
|
2388
|
+
{ history: 'ignore' }
|
|
2389
|
+
)
|
|
2390
|
+
|
|
2333
2391
|
return this
|
|
2334
2392
|
}
|
|
2335
2393
|
|
|
@@ -2533,6 +2591,26 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2533
2591
|
return this.getCurrentPageState().croppingShapeId
|
|
2534
2592
|
}
|
|
2535
2593
|
|
|
2594
|
+
/**
|
|
2595
|
+
* Whether the shape can be cropped.
|
|
2596
|
+
*
|
|
2597
|
+
* @param shape - The shape (or shape id) to check if it can be cropped.
|
|
2598
|
+
*
|
|
2599
|
+
* @public
|
|
2600
|
+
* @returns true if the shape can be cropped, false otherwise.
|
|
2601
|
+
*/
|
|
2602
|
+
canCropShape<T extends TLShape | TLShapeId>(shape: T | null): shape is T {
|
|
2603
|
+
if (!shape) return false
|
|
2604
|
+
const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
|
|
2605
|
+
if (!id) return false
|
|
2606
|
+
const _shape = this.getShape(id)
|
|
2607
|
+
if (!_shape) return false
|
|
2608
|
+
const util = this.getShapeUtil(_shape)
|
|
2609
|
+
if (!util.canCrop(_shape)) return false
|
|
2610
|
+
if (this.isShapeOrAncestorLocked(_shape)) return false
|
|
2611
|
+
return true
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2536
2614
|
/**
|
|
2537
2615
|
* Set the current cropping shape.
|
|
2538
2616
|
*
|
|
@@ -2554,12 +2632,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2554
2632
|
() => {
|
|
2555
2633
|
if (!id) {
|
|
2556
2634
|
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
|
-
}
|
|
2635
|
+
} else if (this.canCropShape(id)) {
|
|
2636
|
+
this.updateCurrentPageState({ croppingShapeId: id })
|
|
2563
2637
|
}
|
|
2564
2638
|
},
|
|
2565
2639
|
{ history: 'ignore' }
|
|
@@ -2669,6 +2743,52 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2669
2743
|
return this.getCamera().z
|
|
2670
2744
|
}
|
|
2671
2745
|
|
|
2746
|
+
private _debouncedZoomLevel = atom('debounced zoom level', 1)
|
|
2747
|
+
|
|
2748
|
+
/**
|
|
2749
|
+
* Get the debounced zoom level. When the camera is moving, this returns the zoom level
|
|
2750
|
+
* from when the camera started moving rather than the current zoom level. This can be
|
|
2751
|
+
* used to avoid expensive re-renders during camera movements.
|
|
2752
|
+
*
|
|
2753
|
+
* This behavior is controlled by the `useDebouncedZoom` option. When `useDebouncedZoom`
|
|
2754
|
+
* is `false`, this method always returns the current zoom level.
|
|
2755
|
+
*
|
|
2756
|
+
* @public
|
|
2757
|
+
*/
|
|
2758
|
+
@computed getDebouncedZoomLevel() {
|
|
2759
|
+
if (this.options.debouncedZoom) {
|
|
2760
|
+
if (this.getCameraState() === 'idle') {
|
|
2761
|
+
return this.getZoomLevel()
|
|
2762
|
+
} else {
|
|
2763
|
+
return this._debouncedZoomLevel.get()
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
return this.getZoomLevel()
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
@computed private _getAboveDebouncedZoomThreshold() {
|
|
2771
|
+
return this.getCurrentPageShapeIds().size > this.options.debouncedZoomThreshold
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
/**
|
|
2775
|
+
* Get the efficient zoom level. This returns the current zoom level if there are less than 300 shapes on the page,
|
|
2776
|
+
* otherwise it returns the debounced zoom level. This can be used to avoid expensive re-renders during camera movements.
|
|
2777
|
+
*
|
|
2778
|
+
* @public
|
|
2779
|
+
* @example
|
|
2780
|
+
* ```ts
|
|
2781
|
+
* editor.getEfficientZoomLevel()
|
|
2782
|
+
* ```
|
|
2783
|
+
*
|
|
2784
|
+
* @public
|
|
2785
|
+
*/
|
|
2786
|
+
@computed getEfficientZoomLevel() {
|
|
2787
|
+
return this._getAboveDebouncedZoomThreshold()
|
|
2788
|
+
? this.getDebouncedZoomLevel()
|
|
2789
|
+
: this.getZoomLevel()
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2672
2792
|
/**
|
|
2673
2793
|
* Get the camera's initial or reset zoom level.
|
|
2674
2794
|
*
|
|
@@ -2995,7 +3115,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2995
3115
|
|
|
2996
3116
|
// Dispatch a new pointer move because the pointer's page will have changed
|
|
2997
3117
|
// (its screen position will compute to a new page position given the new camera position)
|
|
2998
|
-
const
|
|
3118
|
+
const currentScreenPoint = this.inputs.getCurrentScreenPoint()
|
|
3119
|
+
const currentPagePoint = this.inputs.getCurrentPagePoint()
|
|
2999
3120
|
|
|
3000
3121
|
// compare the next page point (derived from the current camera) to the current page point
|
|
3001
3122
|
if (
|
|
@@ -3159,7 +3280,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3159
3280
|
* ```ts
|
|
3160
3281
|
* editor.zoomIn()
|
|
3161
3282
|
* editor.zoomIn(editor.getViewportScreenCenter(), { animation: { duration: 200 } })
|
|
3162
|
-
* editor.zoomIn(editor.inputs.
|
|
3283
|
+
* editor.zoomIn(editor.inputs.getCurrentScreenPoint(), { animation: { duration: 200 } })
|
|
3163
3284
|
* ```
|
|
3164
3285
|
*
|
|
3165
3286
|
* @param point - The screen point to zoom in on. Defaults to the screen center
|
|
@@ -3204,7 +3325,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3204
3325
|
* ```ts
|
|
3205
3326
|
* editor.zoomOut()
|
|
3206
3327
|
* editor.zoomOut(editor.getViewportScreenCenter(), { animation: { duration: 120 } })
|
|
3207
|
-
* editor.zoomOut(editor.inputs.
|
|
3328
|
+
* editor.zoomOut(editor.inputs.getCurrentScreenPoint(), { animation: { duration: 120 } })
|
|
3208
3329
|
* ```
|
|
3209
3330
|
*
|
|
3210
3331
|
* @param point - The point to zoom out on. Defaults to the viewport screen center.
|
|
@@ -3261,10 +3382,17 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3261
3382
|
|
|
3262
3383
|
const selectionPageBounds = this.getSelectionPageBounds()
|
|
3263
3384
|
if (selectionPageBounds) {
|
|
3264
|
-
this.
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3385
|
+
const currentZoom = this.getZoomLevel()
|
|
3386
|
+
// If already at 100%, zoom to fit the selection in the viewport
|
|
3387
|
+
// Otherwise, zoom to 100% centered on the selection
|
|
3388
|
+
if (Math.abs(currentZoom - 1) < 0.01) {
|
|
3389
|
+
this.zoomToBounds(selectionPageBounds, opts)
|
|
3390
|
+
} else {
|
|
3391
|
+
this.zoomToBounds(selectionPageBounds, {
|
|
3392
|
+
targetZoom: 1,
|
|
3393
|
+
...opts,
|
|
3394
|
+
})
|
|
3395
|
+
}
|
|
3268
3396
|
}
|
|
3269
3397
|
return this
|
|
3270
3398
|
}
|
|
@@ -3321,7 +3449,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3321
3449
|
|
|
3322
3450
|
const viewportScreenBounds = this.getViewportScreenBounds()
|
|
3323
3451
|
|
|
3324
|
-
const inset =
|
|
3452
|
+
const inset =
|
|
3453
|
+
opts?.inset ?? Math.min(this.options.zoomToFitPadding, viewportScreenBounds.width * 0.28)
|
|
3325
3454
|
|
|
3326
3455
|
const baseZoom = this.getBaseZoom()
|
|
3327
3456
|
const zoomMin = cameraOptions.zoomSteps[0]
|
|
@@ -3631,22 +3760,23 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3631
3760
|
if (_willSetInitialBounds) {
|
|
3632
3761
|
// If we have just received the initial bounds, don't center the camera.
|
|
3633
3762
|
this.updateInstanceState({ screenBounds: screenBounds.toJson(), insets })
|
|
3763
|
+
this.emit('resize', screenBounds.toJson())
|
|
3634
3764
|
this.setCamera(this.getCamera())
|
|
3635
3765
|
} else {
|
|
3636
3766
|
if (center && !this.getInstanceState().followingUserId) {
|
|
3637
3767
|
// Get the page center before the change, make the change, and restore it
|
|
3638
3768
|
const before = this.getViewportPageBounds().center
|
|
3639
3769
|
this.updateInstanceState({ screenBounds: screenBounds.toJson(), insets })
|
|
3770
|
+
this.emit('resize', screenBounds.toJson())
|
|
3640
3771
|
this.centerOnPoint(before)
|
|
3641
3772
|
} else {
|
|
3642
3773
|
// Otherwise,
|
|
3643
3774
|
this.updateInstanceState({ screenBounds: screenBounds.toJson(), insets })
|
|
3775
|
+
this.emit('resize', screenBounds.toJson())
|
|
3644
3776
|
this._setCamera(Vec.From({ ...this.getCamera() }))
|
|
3645
3777
|
}
|
|
3646
3778
|
}
|
|
3647
3779
|
|
|
3648
|
-
this._tickCameraState()
|
|
3649
|
-
|
|
3650
3780
|
return this
|
|
3651
3781
|
}
|
|
3652
3782
|
|
|
@@ -4052,18 +4182,19 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4052
4182
|
// box just for rendering, and we only update after the camera stops moving.
|
|
4053
4183
|
private _cameraState = atom('camera state', 'idle' as 'idle' | 'moving')
|
|
4054
4184
|
private _cameraStateTimeoutRemaining = 0
|
|
4055
|
-
_decayCameraStateTimeout(elapsed: number) {
|
|
4185
|
+
private _decayCameraStateTimeout(elapsed: number) {
|
|
4056
4186
|
this._cameraStateTimeoutRemaining -= elapsed
|
|
4057
4187
|
if (this._cameraStateTimeoutRemaining > 0) return
|
|
4058
4188
|
this.off('tick', this._decayCameraStateTimeout)
|
|
4059
4189
|
this._cameraState.set('idle')
|
|
4060
4190
|
}
|
|
4061
|
-
_tickCameraState() {
|
|
4191
|
+
private _tickCameraState() {
|
|
4062
4192
|
// always reset the timeout
|
|
4063
4193
|
this._cameraStateTimeoutRemaining = this.options.cameraMovingTimeoutMs
|
|
4064
4194
|
// If the state is idle, then start the tick
|
|
4065
4195
|
if (this._cameraState.__unsafe__getWithoutCapture() !== 'idle') return
|
|
4066
4196
|
this._cameraState.set('moving')
|
|
4197
|
+
this._debouncedZoomLevel.set(unsafe__withoutCapture(() => this.getCamera().z))
|
|
4067
4198
|
this.on('tick', this._decayCameraStateTimeout)
|
|
4068
4199
|
}
|
|
4069
4200
|
|
|
@@ -5010,6 +5141,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5010
5141
|
}
|
|
5011
5142
|
|
|
5012
5143
|
private _notVisibleShapes = notVisibleShapes(this)
|
|
5144
|
+
private _culledShapesCache: Set<TLShapeId> | null = null
|
|
5013
5145
|
|
|
5014
5146
|
/**
|
|
5015
5147
|
* Get culled shapes (those that should not render), taking into account which shapes are selected or editing.
|
|
@@ -5021,16 +5153,41 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5021
5153
|
const notVisibleShapes = this.getNotVisibleShapes()
|
|
5022
5154
|
const selectedShapeIds = this.getSelectedShapeIds()
|
|
5023
5155
|
const editingId = this.getEditingShapeId()
|
|
5024
|
-
const
|
|
5156
|
+
const nextValue = new Set<TLShapeId>(notVisibleShapes)
|
|
5025
5157
|
// we don't cull the shape we are editing
|
|
5026
5158
|
if (editingId) {
|
|
5027
|
-
|
|
5159
|
+
nextValue.delete(editingId)
|
|
5028
5160
|
}
|
|
5029
5161
|
// we also don't cull selected shapes
|
|
5030
5162
|
selectedShapeIds.forEach((id) => {
|
|
5031
|
-
|
|
5163
|
+
nextValue.delete(id)
|
|
5032
5164
|
})
|
|
5033
|
-
|
|
5165
|
+
|
|
5166
|
+
// Cache optimization: return same Set object if contents unchanged
|
|
5167
|
+
// This allows consumers to use === comparison and prevents unnecessary re-renders
|
|
5168
|
+
const prevValue = this._culledShapesCache
|
|
5169
|
+
if (prevValue) {
|
|
5170
|
+
// If sizes differ, contents must differ
|
|
5171
|
+
if (prevValue.size !== nextValue.size) {
|
|
5172
|
+
this._culledShapesCache = nextValue
|
|
5173
|
+
return nextValue
|
|
5174
|
+
}
|
|
5175
|
+
|
|
5176
|
+
// Check if all elements are the same
|
|
5177
|
+
for (const id of prevValue) {
|
|
5178
|
+
if (!nextValue.has(id)) {
|
|
5179
|
+
// Found a difference, update cache and return new set
|
|
5180
|
+
this._culledShapesCache = nextValue
|
|
5181
|
+
return nextValue
|
|
5182
|
+
}
|
|
5183
|
+
}
|
|
5184
|
+
|
|
5185
|
+
// Loop completed without finding differences - contents identical
|
|
5186
|
+
return prevValue
|
|
5187
|
+
}
|
|
5188
|
+
|
|
5189
|
+
this._culledShapesCache = nextValue
|
|
5190
|
+
return nextValue
|
|
5034
5191
|
}
|
|
5035
5192
|
|
|
5036
5193
|
/**
|
|
@@ -5097,11 +5254,18 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5097
5254
|
let inMarginClosestToEdgeDistance = Infinity
|
|
5098
5255
|
let inMarginClosestToEdgeHit: TLShape | null = null
|
|
5099
5256
|
|
|
5257
|
+
// Use larger margin for spatial search to account for edge distance checks
|
|
5258
|
+
const searchMargin = Math.max(innerMargin, outerMargin, this.options.hitTestMargin / zoomLevel)
|
|
5259
|
+
const candidateIds = this._spatialIndex.getShapeIdsAtPoint(point, searchMargin)
|
|
5260
|
+
|
|
5100
5261
|
const shapesToCheck = (
|
|
5101
5262
|
opts.renderingOnly
|
|
5102
5263
|
? this.getCurrentPageRenderingShapesSorted()
|
|
5103
5264
|
: this.getCurrentPageShapesSorted()
|
|
5104
5265
|
).filter((shape) => {
|
|
5266
|
+
// Frames have labels positioned above the shape (outside bounds), so always include them
|
|
5267
|
+
if (!candidateIds.has(shape.id) && !this.isShapeOfType(shape, 'frame')) return false
|
|
5268
|
+
|
|
5105
5269
|
if (
|
|
5106
5270
|
(shape.isLocked && !hitLocked) ||
|
|
5107
5271
|
this.isShapeHidden(shape) ||
|
|
@@ -5287,11 +5451,41 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5287
5451
|
point: VecLike,
|
|
5288
5452
|
opts = {} as { margin?: number; hitInside?: boolean }
|
|
5289
5453
|
): TLShape[] {
|
|
5454
|
+
const margin = opts.margin ?? 0
|
|
5455
|
+
const candidateIds = this._spatialIndex.getShapeIdsAtPoint(point, margin)
|
|
5456
|
+
|
|
5457
|
+
// Get all page shapes in z-index order and filter to candidates that pass isPointInShape
|
|
5458
|
+
// Frames are always checked because their labels can be outside their bounds
|
|
5290
5459
|
return this.getCurrentPageShapesSorted()
|
|
5291
|
-
.filter((shape) =>
|
|
5460
|
+
.filter((shape) => {
|
|
5461
|
+
if (this.isShapeHidden(shape)) return false
|
|
5462
|
+
if (!candidateIds.has(shape.id) && !this.isShapeOfType(shape, 'frame')) return false
|
|
5463
|
+
return this.isPointInShape(shape, point, opts)
|
|
5464
|
+
})
|
|
5292
5465
|
.reverse()
|
|
5293
5466
|
}
|
|
5294
5467
|
|
|
5468
|
+
/**
|
|
5469
|
+
* Get shape IDs within the given bounds.
|
|
5470
|
+
*
|
|
5471
|
+
* Note: Uses shape page bounds only. Frames with labels outside their bounds
|
|
5472
|
+
* may not be included even if the label is within the search bounds.
|
|
5473
|
+
*
|
|
5474
|
+
* Note: Results are unordered. If you need z-order, combine with sorted shapes:
|
|
5475
|
+
* ```ts
|
|
5476
|
+
* const candidates = editor.getShapeIdsInsideBounds(bounds)
|
|
5477
|
+
* const sorted = editor.getCurrentPageShapesSorted().filter(s => candidates.has(s.id))
|
|
5478
|
+
* ```
|
|
5479
|
+
*
|
|
5480
|
+
* @param bounds - The bounds to search within.
|
|
5481
|
+
* @returns Unordered set of shape IDs within the given bounds.
|
|
5482
|
+
*
|
|
5483
|
+
* @internal
|
|
5484
|
+
*/
|
|
5485
|
+
getShapeIdsInsideBounds(bounds: Box): Set<TLShapeId> {
|
|
5486
|
+
return this._spatialIndex.getShapeIdsInsideBounds(bounds)
|
|
5487
|
+
}
|
|
5488
|
+
|
|
5295
5489
|
/**
|
|
5296
5490
|
* Test whether a point (in the current page space) will will a shape. This method takes into account masks,
|
|
5297
5491
|
* such as when a shape is the child of a frame and is partially clipped by the frame.
|
|
@@ -7649,8 +7843,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7649
7843
|
// then if the shape is flipped in one axis only, we need to apply an extra rotation
|
|
7650
7844
|
// to make sure the shape is mirrored correctly
|
|
7651
7845
|
if (Math.sign(scale.x) * Math.sign(scale.y) < 0) {
|
|
7652
|
-
|
|
7653
|
-
rotation
|
|
7846
|
+
// We need to compute the new local rotation that will result in the negated page rotation.
|
|
7847
|
+
// For a shape with local rotation `localRot` and parent page rotation `parentRot`:
|
|
7848
|
+
// - pageRot = parentRot + localRot
|
|
7849
|
+
// - newPageRot = -pageRot (we want to negate the page rotation)
|
|
7850
|
+
// - newPageRot = parentRot + newLocalRot (parent hasn't changed)
|
|
7851
|
+
// - Therefore: newLocalRot = -pageRot - parentRot = -(parentRot + localRot) - parentRot = -localRot - 2*parentRot
|
|
7852
|
+
const parentRotation = this.getShapeParentTransform(id).rotation()
|
|
7853
|
+
const rotation = -options.initialShape.rotation - 2 * parentRotation
|
|
7654
7854
|
this.updateShapes([{ id, type, rotation }])
|
|
7655
7855
|
}
|
|
7656
7856
|
|
|
@@ -7670,9 +7870,13 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7670
7870
|
)
|
|
7671
7871
|
|
|
7672
7872
|
// now calculate how far away the shape is from where it needs to be
|
|
7673
|
-
const pageBounds = this.getShapePageBounds(id)!
|
|
7674
7873
|
const pageTransform = this.getShapePageTransform(id)!
|
|
7675
|
-
|
|
7874
|
+
// We need to use the local bounds center transformed to page space, not the axis-aligned
|
|
7875
|
+
// page bounds center. This is because the page bounds are axis-aligned and their center
|
|
7876
|
+
// changes when the rotation changes, but we want to use the same reference point as
|
|
7877
|
+
// preScaleShapePageCenter (which used initialBounds.center transformed by the page transform).
|
|
7878
|
+
const currentLocalBounds = this.getShapeGeometry(id).bounds
|
|
7879
|
+
const currentPageCenter = Mat.applyToPoint(pageTransform, currentLocalBounds.center)
|
|
7676
7880
|
const shapePageTransformOrigin = pageTransform.point()
|
|
7677
7881
|
if (!currentPageCenter || !shapePageTransformOrigin) return this
|
|
7678
7882
|
const pageDelta = Vec.Sub(postScaleShapePageCenter, currentPageCenter)
|
|
@@ -8120,7 +8324,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8120
8324
|
)
|
|
8121
8325
|
)
|
|
8122
8326
|
const sortedShapeIds = shapesToGroup.sort(sortByIndex).map((s) => s.id)
|
|
8123
|
-
const
|
|
8327
|
+
const childBounds = compact(shapesToGroup.map((shape) => this.getShapePageBounds(shape)))
|
|
8328
|
+
const pageBounds = Box.Common(childBounds)
|
|
8329
|
+
|
|
8330
|
+
if (!pageBounds.isValid()) {
|
|
8331
|
+
throw Error(`Editor.groupShapes: group bounds are invalid (NaN).`)
|
|
8332
|
+
}
|
|
8124
8333
|
|
|
8125
8334
|
const { x, y } = pageBounds.point
|
|
8126
8335
|
|
|
@@ -9145,6 +9354,30 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9145
9354
|
}
|
|
9146
9355
|
}
|
|
9147
9356
|
|
|
9357
|
+
if (point) {
|
|
9358
|
+
const shapesById = new Map<TLShapeId, TLShape>(shapes.map((shape) => [shape.id, shape]))
|
|
9359
|
+
const rootShapesFromContent = compact(rootShapeIds.map((id) => shapesById.get(id)))
|
|
9360
|
+
if (rootShapesFromContent.length > 0) {
|
|
9361
|
+
const targetParent = this.getShapeAtPoint(point, {
|
|
9362
|
+
hitInside: true,
|
|
9363
|
+
hitFrameInside: true,
|
|
9364
|
+
hitLocked: true,
|
|
9365
|
+
filter: (shape) => {
|
|
9366
|
+
const util = this.getShapeUtil(shape)
|
|
9367
|
+
if (!util.canReceiveNewChildrenOfType) return false
|
|
9368
|
+
return rootShapesFromContent.every((rootShape) =>
|
|
9369
|
+
util.canReceiveNewChildrenOfType!(shape, rootShape.type)
|
|
9370
|
+
)
|
|
9371
|
+
},
|
|
9372
|
+
})
|
|
9373
|
+
|
|
9374
|
+
// When pasting at a specific point (e.g. paste-at-cursor) prefer the
|
|
9375
|
+
// parent under the pointer so that we don't keep using the original
|
|
9376
|
+
// selection's parent (which can keep shapes clipped inside frames).
|
|
9377
|
+
pasteParentId = targetParent ? targetParent.id : currentPageId
|
|
9378
|
+
}
|
|
9379
|
+
}
|
|
9380
|
+
|
|
9148
9381
|
let isDuplicating = false
|
|
9149
9382
|
|
|
9150
9383
|
if (!isPageId(pasteParentId)) {
|
|
@@ -9482,126 +9715,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9482
9715
|
|
|
9483
9716
|
/* --------------------- Events --------------------- */
|
|
9484
9717
|
|
|
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
9718
|
/**
|
|
9606
9719
|
* Dispatch a cancel event.
|
|
9607
9720
|
*
|
|
@@ -9671,19 +9784,22 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9671
9784
|
// weird but true: what `inputs` calls screen-space is actually viewport space. so
|
|
9672
9785
|
// we need to convert back into true screen space first. we should fix this...
|
|
9673
9786
|
Vec.Add(
|
|
9674
|
-
this.inputs.
|
|
9787
|
+
this.inputs.getCurrentScreenPoint(),
|
|
9675
9788
|
this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!.screenBounds
|
|
9676
9789
|
),
|
|
9677
9790
|
pointerId: options?.pointerId ?? 0,
|
|
9678
9791
|
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:
|
|
9792
|
+
isPen: options?.isPen ?? this.inputs.getIsPen(),
|
|
9793
|
+
shiftKey: options?.shiftKey ?? this.inputs.getShiftKey(),
|
|
9794
|
+
altKey: options?.altKey ?? this.inputs.getAltKey(),
|
|
9795
|
+
ctrlKey: options?.ctrlKey ?? this.inputs.getCtrlKey(),
|
|
9796
|
+
metaKey: options?.metaKey ?? this.inputs.getMetaKey(),
|
|
9797
|
+
accelKey: false,
|
|
9685
9798
|
}
|
|
9686
9799
|
|
|
9800
|
+
// needs to be calculated second
|
|
9801
|
+
event.accelKey = options?.accelKey ?? this.inputs.getAccelKey()
|
|
9802
|
+
|
|
9687
9803
|
if (options?.immediate) {
|
|
9688
9804
|
this._flushEventForTick(event)
|
|
9689
9805
|
} else {
|
|
@@ -10056,16 +10172,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10056
10172
|
/** @internal */
|
|
10057
10173
|
@bind
|
|
10058
10174
|
_setShiftKeyTimeout() {
|
|
10059
|
-
this.inputs.
|
|
10175
|
+
this.inputs.setShiftKey(false)
|
|
10060
10176
|
this.dispatch({
|
|
10061
10177
|
type: 'keyboard',
|
|
10062
10178
|
name: 'key_up',
|
|
10063
10179
|
key: 'Shift',
|
|
10064
|
-
shiftKey: this.inputs.
|
|
10065
|
-
ctrlKey: this.inputs.
|
|
10066
|
-
altKey: this.inputs.
|
|
10067
|
-
metaKey: this.inputs.
|
|
10068
|
-
accelKey:
|
|
10180
|
+
shiftKey: this.inputs.getShiftKey(),
|
|
10181
|
+
ctrlKey: this.inputs.getCtrlKey(),
|
|
10182
|
+
altKey: this.inputs.getAltKey(),
|
|
10183
|
+
metaKey: this.inputs.getMetaKey(),
|
|
10184
|
+
accelKey: this.inputs.getAccelKey(),
|
|
10069
10185
|
code: 'ShiftLeft',
|
|
10070
10186
|
})
|
|
10071
10187
|
}
|
|
@@ -10076,16 +10192,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10076
10192
|
/** @internal */
|
|
10077
10193
|
@bind
|
|
10078
10194
|
_setAltKeyTimeout() {
|
|
10079
|
-
this.inputs.
|
|
10195
|
+
this.inputs.setAltKey(false)
|
|
10080
10196
|
this.dispatch({
|
|
10081
10197
|
type: 'keyboard',
|
|
10082
10198
|
name: 'key_up',
|
|
10083
10199
|
key: 'Alt',
|
|
10084
|
-
shiftKey: this.inputs.
|
|
10085
|
-
ctrlKey: this.inputs.
|
|
10086
|
-
altKey: this.inputs.
|
|
10087
|
-
metaKey: this.inputs.
|
|
10088
|
-
accelKey:
|
|
10200
|
+
shiftKey: this.inputs.getShiftKey(),
|
|
10201
|
+
ctrlKey: this.inputs.getCtrlKey(),
|
|
10202
|
+
altKey: this.inputs.getAltKey(),
|
|
10203
|
+
metaKey: this.inputs.getMetaKey(),
|
|
10204
|
+
accelKey: this.inputs.getAccelKey(),
|
|
10089
10205
|
code: 'AltLeft',
|
|
10090
10206
|
})
|
|
10091
10207
|
}
|
|
@@ -10096,16 +10212,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10096
10212
|
/** @internal */
|
|
10097
10213
|
@bind
|
|
10098
10214
|
_setCtrlKeyTimeout() {
|
|
10099
|
-
this.inputs.
|
|
10215
|
+
this.inputs.setCtrlKey(false)
|
|
10100
10216
|
this.dispatch({
|
|
10101
10217
|
type: 'keyboard',
|
|
10102
10218
|
name: 'key_up',
|
|
10103
10219
|
key: 'Ctrl',
|
|
10104
|
-
shiftKey: this.inputs.
|
|
10105
|
-
ctrlKey: this.inputs.
|
|
10106
|
-
altKey: this.inputs.
|
|
10107
|
-
metaKey: this.inputs.
|
|
10108
|
-
accelKey:
|
|
10220
|
+
shiftKey: this.inputs.getShiftKey(),
|
|
10221
|
+
ctrlKey: this.inputs.getCtrlKey(),
|
|
10222
|
+
altKey: this.inputs.getAltKey(),
|
|
10223
|
+
metaKey: this.inputs.getMetaKey(),
|
|
10224
|
+
accelKey: this.inputs.getAccelKey(),
|
|
10109
10225
|
code: 'ControlLeft',
|
|
10110
10226
|
})
|
|
10111
10227
|
}
|
|
@@ -10116,16 +10232,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10116
10232
|
/** @internal */
|
|
10117
10233
|
@bind
|
|
10118
10234
|
_setMetaKeyTimeout() {
|
|
10119
|
-
this.inputs.
|
|
10235
|
+
this.inputs.setMetaKey(false)
|
|
10120
10236
|
this.dispatch({
|
|
10121
10237
|
type: 'keyboard',
|
|
10122
10238
|
name: 'key_up',
|
|
10123
10239
|
key: 'Meta',
|
|
10124
|
-
shiftKey: this.inputs.
|
|
10125
|
-
ctrlKey: this.inputs.
|
|
10126
|
-
altKey: this.inputs.
|
|
10127
|
-
metaKey: this.inputs.
|
|
10128
|
-
accelKey:
|
|
10240
|
+
shiftKey: this.inputs.getShiftKey(),
|
|
10241
|
+
ctrlKey: this.inputs.getCtrlKey(),
|
|
10242
|
+
altKey: this.inputs.getAltKey(),
|
|
10243
|
+
metaKey: this.inputs.getMetaKey(),
|
|
10244
|
+
accelKey: this.inputs.getAccelKey(),
|
|
10129
10245
|
code: 'MetaLeft',
|
|
10130
10246
|
})
|
|
10131
10247
|
}
|
|
@@ -10133,9 +10249,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10133
10249
|
/** @internal */
|
|
10134
10250
|
private _restoreToolId = 'select'
|
|
10135
10251
|
|
|
10136
|
-
/** @internal */
|
|
10137
|
-
private _pinchStart = 1
|
|
10138
|
-
|
|
10139
10252
|
/** @internal */
|
|
10140
10253
|
private _didPinch = false
|
|
10141
10254
|
|
|
@@ -10242,56 +10355,54 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10242
10355
|
if (info.type === 'misc') {
|
|
10243
10356
|
// stop panning if the interaction is cancelled or completed
|
|
10244
10357
|
if (info.name === 'cancel' || info.name === 'complete') {
|
|
10245
|
-
this.inputs.
|
|
10358
|
+
this.inputs.setIsDragging(false)
|
|
10246
10359
|
|
|
10247
|
-
if (this.inputs.
|
|
10248
|
-
this.inputs.
|
|
10249
|
-
this.inputs.
|
|
10360
|
+
if (this.inputs.getIsPanning()) {
|
|
10361
|
+
this.inputs.setIsPanning(false)
|
|
10362
|
+
this.inputs.setIsSpacebarPanning(false)
|
|
10250
10363
|
this.setCursor({ type: this._prevCursor, rotation: 0 })
|
|
10251
10364
|
}
|
|
10252
10365
|
}
|
|
10253
10366
|
|
|
10254
|
-
this.emit('event', info)
|
|
10255
10367
|
this.root.handleEvent(info)
|
|
10368
|
+
this.emit('event', info)
|
|
10256
10369
|
return
|
|
10257
10370
|
}
|
|
10258
10371
|
|
|
10259
10372
|
if (info.shiftKey) {
|
|
10260
10373
|
clearTimeout(this._shiftKeyTimeout)
|
|
10261
10374
|
this._shiftKeyTimeout = -1
|
|
10262
|
-
inputs.
|
|
10263
|
-
} else if (!info.shiftKey && inputs.
|
|
10375
|
+
inputs.setShiftKey(true)
|
|
10376
|
+
} else if (!info.shiftKey && inputs.getShiftKey() && this._shiftKeyTimeout === -1) {
|
|
10264
10377
|
this._shiftKeyTimeout = this.timers.setTimeout(this._setShiftKeyTimeout, 150)
|
|
10265
10378
|
}
|
|
10266
10379
|
|
|
10267
10380
|
if (info.altKey) {
|
|
10268
10381
|
clearTimeout(this._altKeyTimeout)
|
|
10269
10382
|
this._altKeyTimeout = -1
|
|
10270
|
-
inputs.
|
|
10271
|
-
} else if (!info.altKey && inputs.
|
|
10383
|
+
inputs.setAltKey(true)
|
|
10384
|
+
} else if (!info.altKey && inputs.getAltKey() && this._altKeyTimeout === -1) {
|
|
10272
10385
|
this._altKeyTimeout = this.timers.setTimeout(this._setAltKeyTimeout, 150)
|
|
10273
10386
|
}
|
|
10274
10387
|
|
|
10275
10388
|
if (info.ctrlKey) {
|
|
10276
10389
|
clearTimeout(this._ctrlKeyTimeout)
|
|
10277
10390
|
this._ctrlKeyTimeout = -1
|
|
10278
|
-
inputs.
|
|
10279
|
-
} else if (!info.ctrlKey && inputs.
|
|
10391
|
+
inputs.setCtrlKey(true)
|
|
10392
|
+
} else if (!info.ctrlKey && inputs.getCtrlKey() && this._ctrlKeyTimeout === -1) {
|
|
10280
10393
|
this._ctrlKeyTimeout = this.timers.setTimeout(this._setCtrlKeyTimeout, 150)
|
|
10281
10394
|
}
|
|
10282
10395
|
|
|
10283
10396
|
if (info.metaKey) {
|
|
10284
10397
|
clearTimeout(this._metaKeyTimeout)
|
|
10285
10398
|
this._metaKeyTimeout = -1
|
|
10286
|
-
inputs.
|
|
10287
|
-
} else if (!info.metaKey && inputs.
|
|
10399
|
+
inputs.setMetaKey(true)
|
|
10400
|
+
} else if (!info.metaKey && inputs.getMetaKey() && this._metaKeyTimeout === -1) {
|
|
10288
10401
|
this._metaKeyTimeout = this.timers.setTimeout(this._setMetaKeyTimeout, 150)
|
|
10289
10402
|
}
|
|
10290
10403
|
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
if (!inputs.isPointing) {
|
|
10294
|
-
inputs.isDragging = false
|
|
10404
|
+
if (!inputs.getIsPointing()) {
|
|
10405
|
+
inputs.setIsDragging(false)
|
|
10295
10406
|
}
|
|
10296
10407
|
|
|
10297
10408
|
const instanceState = this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!
|
|
@@ -10302,29 +10413,29 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10302
10413
|
case 'pinch': {
|
|
10303
10414
|
if (cameraOptions.isLocked) return
|
|
10304
10415
|
clearTimeout(this._longPressTimeout)
|
|
10305
|
-
this.
|
|
10416
|
+
this.inputs.updateFromEvent(info)
|
|
10306
10417
|
|
|
10307
10418
|
switch (info.name) {
|
|
10308
10419
|
case 'pinch_start': {
|
|
10309
|
-
if (inputs.
|
|
10420
|
+
if (inputs.getIsPinching()) return
|
|
10310
10421
|
|
|
10311
|
-
if (!inputs.
|
|
10312
|
-
this._pinchStart = this.getCamera().z
|
|
10422
|
+
if (!inputs.getIsEditing()) {
|
|
10313
10423
|
if (!this._selectedShapeIdsAtPointerDown.length) {
|
|
10314
10424
|
this._selectedShapeIdsAtPointerDown = [...pageState.selectedShapeIds]
|
|
10315
10425
|
}
|
|
10316
10426
|
|
|
10317
10427
|
this._didPinch = true
|
|
10318
10428
|
|
|
10319
|
-
inputs.
|
|
10429
|
+
inputs.setIsPinching(true)
|
|
10320
10430
|
|
|
10321
10431
|
this.interrupt()
|
|
10322
10432
|
}
|
|
10323
10433
|
|
|
10434
|
+
this.emit('event', info)
|
|
10324
10435
|
return // Stop here!
|
|
10325
10436
|
}
|
|
10326
10437
|
case 'pinch': {
|
|
10327
|
-
if (!inputs.
|
|
10438
|
+
if (!inputs.getIsPinching()) return
|
|
10328
10439
|
|
|
10329
10440
|
const {
|
|
10330
10441
|
point: { z = 1 },
|
|
@@ -10355,13 +10466,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10355
10466
|
{ immediate: true }
|
|
10356
10467
|
)
|
|
10357
10468
|
|
|
10469
|
+
this.emit('event', info)
|
|
10358
10470
|
return // Stop here!
|
|
10359
10471
|
}
|
|
10360
10472
|
case 'pinch_end': {
|
|
10361
|
-
if (!inputs.
|
|
10473
|
+
if (!inputs.getIsPinching()) return this
|
|
10362
10474
|
|
|
10363
10475
|
// Stop pinching
|
|
10364
|
-
inputs.
|
|
10476
|
+
inputs.setIsPinching(false)
|
|
10365
10477
|
|
|
10366
10478
|
// Stash and clear the shapes that were selected when the pinch started
|
|
10367
10479
|
const { _selectedShapeIdsAtPointerDown: shapesToReselect } = this
|
|
@@ -10381,6 +10493,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10381
10493
|
}
|
|
10382
10494
|
}
|
|
10383
10495
|
|
|
10496
|
+
this.emit('event', info)
|
|
10384
10497
|
return // Stop here!
|
|
10385
10498
|
}
|
|
10386
10499
|
}
|
|
@@ -10388,7 +10501,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10388
10501
|
case 'wheel': {
|
|
10389
10502
|
if (cameraOptions.isLocked) return
|
|
10390
10503
|
|
|
10391
|
-
this.
|
|
10504
|
+
this.inputs.updateFromEvent(info)
|
|
10392
10505
|
|
|
10393
10506
|
const { panSpeed, zoomSpeed } = cameraOptions
|
|
10394
10507
|
let wheelBehavior = cameraOptions.wheelBehavior
|
|
@@ -10419,7 +10532,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10419
10532
|
switch (behavior) {
|
|
10420
10533
|
case 'zoom': {
|
|
10421
10534
|
// Zoom in on current screen point using the wheel delta
|
|
10422
|
-
const { x, y } = this.inputs.
|
|
10535
|
+
const { x, y } = this.inputs.getCurrentScreenPoint()
|
|
10423
10536
|
let delta = dz
|
|
10424
10537
|
|
|
10425
10538
|
// If we're forcing zoom, then we need to do the wheel normalization math here
|
|
@@ -10436,6 +10549,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10436
10549
|
immediate: true,
|
|
10437
10550
|
})
|
|
10438
10551
|
this.maybeTrackPerformance('Zooming')
|
|
10552
|
+
this.root.handleEvent(info)
|
|
10553
|
+
this.emit('event', info)
|
|
10439
10554
|
return
|
|
10440
10555
|
}
|
|
10441
10556
|
case 'pan': {
|
|
@@ -10444,6 +10559,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10444
10559
|
immediate: true,
|
|
10445
10560
|
})
|
|
10446
10561
|
this.maybeTrackPerformance('Panning')
|
|
10562
|
+
this.root.handleEvent(info)
|
|
10563
|
+
this.emit('event', info)
|
|
10447
10564
|
return
|
|
10448
10565
|
}
|
|
10449
10566
|
}
|
|
@@ -10452,9 +10569,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10452
10569
|
}
|
|
10453
10570
|
case 'pointer': {
|
|
10454
10571
|
// Ignore pointer events while we're pinching
|
|
10455
|
-
if (inputs.
|
|
10572
|
+
if (inputs.getIsPinching()) return
|
|
10456
10573
|
|
|
10457
|
-
this.
|
|
10574
|
+
this.inputs.updateFromEvent(info)
|
|
10458
10575
|
const { isPen } = info
|
|
10459
10576
|
const { isPenMode } = instanceState
|
|
10460
10577
|
|
|
@@ -10463,7 +10580,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10463
10580
|
// If we're in pen mode and the input is not a pen type, then stop here
|
|
10464
10581
|
if (isPenMode && !isPen) return
|
|
10465
10582
|
|
|
10466
|
-
if (!this.inputs.
|
|
10583
|
+
if (!this.inputs.getIsPanning()) {
|
|
10467
10584
|
// Start a long press timeout
|
|
10468
10585
|
this._longPressTimeout = this.timers.setTimeout(() => {
|
|
10469
10586
|
const vsb = this.getViewportScreenBounds()
|
|
@@ -10473,7 +10590,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10473
10590
|
// viewport bounds, and will be again when this event is handled...
|
|
10474
10591
|
// so we need to counter-adjust from the stored value so that the
|
|
10475
10592
|
// new value is set correctly.
|
|
10476
|
-
point: this.inputs.
|
|
10593
|
+
point: this.inputs.getOriginScreenPoint().clone().addXY(vsb.x, vsb.y),
|
|
10477
10594
|
name: 'long_press',
|
|
10478
10595
|
})
|
|
10479
10596
|
}, this.options.longPressDurationMs)
|
|
@@ -10490,8 +10607,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10490
10607
|
inputs.buttons.add(info.button)
|
|
10491
10608
|
|
|
10492
10609
|
// Start pointing and stop dragging
|
|
10493
|
-
inputs.
|
|
10494
|
-
inputs.
|
|
10610
|
+
inputs.setIsPointing(true)
|
|
10611
|
+
inputs.setIsDragging(false)
|
|
10495
10612
|
|
|
10496
10613
|
// If pen mode is off but we're not already in pen mode, turn that on
|
|
10497
10614
|
if (!isPenMode && isPen) this.updateInstanceState({ isPenMode: true })
|
|
@@ -10503,16 +10620,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10503
10620
|
this.setCurrentTool('eraser')
|
|
10504
10621
|
} else if (info.button === MIDDLE_MOUSE_BUTTON) {
|
|
10505
10622
|
// Middle mouse pan activates panning unless we're already panning (with spacebar)
|
|
10506
|
-
if (!this.inputs.
|
|
10623
|
+
if (!this.inputs.getIsPanning()) {
|
|
10507
10624
|
this._prevCursor = this.getInstanceState().cursor.type
|
|
10508
10625
|
}
|
|
10509
|
-
this.inputs.
|
|
10626
|
+
this.inputs.setIsPanning(true)
|
|
10510
10627
|
clearTimeout(this._longPressTimeout)
|
|
10511
10628
|
}
|
|
10512
10629
|
|
|
10513
10630
|
// We might be panning because we did a middle mouse click, or because we're holding spacebar and started a regular click
|
|
10514
10631
|
// Also stop here, we don't want the state chart to receive the event
|
|
10515
|
-
if (this.inputs.
|
|
10632
|
+
if (this.inputs.getIsPanning()) {
|
|
10516
10633
|
this.stopCameraAnimation()
|
|
10517
10634
|
this.setCursor({ type: 'grabbing', rotation: 0 })
|
|
10518
10635
|
return this
|
|
@@ -10527,9 +10644,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10527
10644
|
const { x: cx, y: cy, z: cz } = unsafe__withoutCapture(() => this.getCamera())
|
|
10528
10645
|
|
|
10529
10646
|
// If we've started panning, then clear any long press timeout
|
|
10530
|
-
if (this.inputs.
|
|
10647
|
+
if (this.inputs.getIsPanning() && this.inputs.getIsPointing()) {
|
|
10531
10648
|
// Handle spacebar / middle mouse button panning
|
|
10532
|
-
const
|
|
10649
|
+
const currentScreenPoint = this.inputs.getCurrentScreenPoint()
|
|
10650
|
+
const previousScreenPoint = this.inputs.getPreviousScreenPoint()
|
|
10533
10651
|
const offset = Vec.Sub(currentScreenPoint, previousScreenPoint)
|
|
10534
10652
|
this.setCamera(new Vec(cx + offset.x / cz, cy + offset.y / cz, cz), {
|
|
10535
10653
|
immediate: true,
|
|
@@ -10539,24 +10657,25 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10539
10657
|
}
|
|
10540
10658
|
|
|
10541
10659
|
if (
|
|
10542
|
-
inputs.
|
|
10543
|
-
!inputs.
|
|
10544
|
-
Vec.Dist2(
|
|
10660
|
+
inputs.getIsPointing() &&
|
|
10661
|
+
!inputs.getIsDragging() &&
|
|
10662
|
+
Vec.Dist2(inputs.getOriginPagePoint(), inputs.getCurrentPagePoint()) *
|
|
10663
|
+
this.getZoomLevel() >
|
|
10545
10664
|
(instanceState.isCoarsePointer
|
|
10546
10665
|
? this.options.coarseDragDistanceSquared
|
|
10547
10666
|
: this.options.dragDistanceSquared) /
|
|
10548
10667
|
cz
|
|
10549
10668
|
) {
|
|
10550
10669
|
// Start dragging
|
|
10551
|
-
inputs.
|
|
10670
|
+
inputs.setIsDragging(true)
|
|
10552
10671
|
clearTimeout(this._longPressTimeout)
|
|
10553
10672
|
}
|
|
10554
10673
|
break
|
|
10555
10674
|
}
|
|
10556
10675
|
case 'pointer_up': {
|
|
10557
10676
|
// Stop dragging / pointing
|
|
10558
|
-
inputs.
|
|
10559
|
-
inputs.
|
|
10677
|
+
inputs.setIsDragging(false)
|
|
10678
|
+
inputs.setIsPointing(false)
|
|
10560
10679
|
clearTimeout(this._longPressTimeout)
|
|
10561
10680
|
|
|
10562
10681
|
// Remove the button from the buttons set
|
|
@@ -10573,12 +10692,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10573
10692
|
info.button = 0
|
|
10574
10693
|
}
|
|
10575
10694
|
|
|
10576
|
-
if (inputs.
|
|
10695
|
+
if (inputs.getIsPanning()) {
|
|
10577
10696
|
if (!inputs.keys.has('Space')) {
|
|
10578
|
-
inputs.
|
|
10579
|
-
inputs.
|
|
10697
|
+
inputs.setIsPanning(false)
|
|
10698
|
+
inputs.setIsSpacebarPanning(false)
|
|
10580
10699
|
}
|
|
10581
|
-
const slideDirection = this.inputs.
|
|
10700
|
+
const slideDirection = this.inputs.getPointerVelocity()
|
|
10582
10701
|
const slideSpeed = Math.min(2, slideDirection.len())
|
|
10583
10702
|
|
|
10584
10703
|
switch (info.button) {
|
|
@@ -10622,43 +10741,48 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10622
10741
|
// Add the key from the keys set
|
|
10623
10742
|
inputs.keys.add(info.code)
|
|
10624
10743
|
|
|
10625
|
-
|
|
10626
|
-
|
|
10627
|
-
if (!
|
|
10628
|
-
this.
|
|
10629
|
-
|
|
10744
|
+
if (this.options.spacebarPanning) {
|
|
10745
|
+
// If the space key is pressed (but meta / control isn't!) activate panning
|
|
10746
|
+
if (info.code === 'Space' && !info.ctrlKey) {
|
|
10747
|
+
if (!this.inputs.getIsPanning()) {
|
|
10748
|
+
this._prevCursor = instanceState.cursor.type
|
|
10749
|
+
}
|
|
10630
10750
|
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
|
|
10634
|
-
|
|
10635
|
-
|
|
10751
|
+
this.inputs.setIsPanning(true)
|
|
10752
|
+
this.inputs.setIsSpacebarPanning(true)
|
|
10753
|
+
clearTimeout(this._longPressTimeout)
|
|
10754
|
+
this.setCursor({
|
|
10755
|
+
type: this.inputs.getIsPointing() ? 'grabbing' : 'grab',
|
|
10756
|
+
rotation: 0,
|
|
10757
|
+
})
|
|
10758
|
+
}
|
|
10636
10759
|
|
|
10637
|
-
|
|
10638
|
-
|
|
10639
|
-
|
|
10640
|
-
|
|
10641
|
-
|
|
10642
|
-
|
|
10643
|
-
|
|
10644
|
-
|
|
10645
|
-
|
|
10646
|
-
|
|
10647
|
-
|
|
10648
|
-
|
|
10649
|
-
|
|
10650
|
-
|
|
10651
|
-
|
|
10652
|
-
|
|
10653
|
-
|
|
10654
|
-
|
|
10760
|
+
if (this.inputs.getIsSpacebarPanning()) {
|
|
10761
|
+
let offset: Vec | undefined
|
|
10762
|
+
switch (info.code) {
|
|
10763
|
+
case 'ArrowUp': {
|
|
10764
|
+
offset = new Vec(0, -1)
|
|
10765
|
+
break
|
|
10766
|
+
}
|
|
10767
|
+
case 'ArrowRight': {
|
|
10768
|
+
offset = new Vec(1, 0)
|
|
10769
|
+
break
|
|
10770
|
+
}
|
|
10771
|
+
case 'ArrowDown': {
|
|
10772
|
+
offset = new Vec(0, 1)
|
|
10773
|
+
break
|
|
10774
|
+
}
|
|
10775
|
+
case 'ArrowLeft': {
|
|
10776
|
+
offset = new Vec(-1, 0)
|
|
10777
|
+
break
|
|
10778
|
+
}
|
|
10655
10779
|
}
|
|
10656
|
-
}
|
|
10657
10780
|
|
|
10658
|
-
|
|
10659
|
-
|
|
10660
|
-
|
|
10661
|
-
|
|
10781
|
+
if (offset) {
|
|
10782
|
+
const bounds = this.getViewportPageBounds()
|
|
10783
|
+
const next = bounds.clone().translate(offset.mulV({ x: bounds.w, y: bounds.h }))
|
|
10784
|
+
this._animateToViewport(next, { animation: { duration: 320 } })
|
|
10785
|
+
}
|
|
10662
10786
|
}
|
|
10663
10787
|
}
|
|
10664
10788
|
|
|
@@ -10668,15 +10792,17 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10668
10792
|
// Remove the key from the keys set
|
|
10669
10793
|
inputs.keys.delete(info.code)
|
|
10670
10794
|
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
if (
|
|
10674
|
-
|
|
10675
|
-
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10795
|
+
if (this.options.spacebarPanning) {
|
|
10796
|
+
// If we've lifted the space key,
|
|
10797
|
+
if (info.code === 'Space') {
|
|
10798
|
+
if (this.inputs.buttons.has(MIDDLE_MOUSE_BUTTON)) {
|
|
10799
|
+
// If we're still middle dragging, continue panning
|
|
10800
|
+
} else {
|
|
10801
|
+
// otherwise, stop panning
|
|
10802
|
+
this.inputs.setIsPanning(false)
|
|
10803
|
+
this.inputs.setIsSpacebarPanning(false)
|
|
10804
|
+
this.setCursor({ type: this._prevCursor, rotation: 0 })
|
|
10805
|
+
}
|
|
10680
10806
|
}
|
|
10681
10807
|
}
|
|
10682
10808
|
break
|