@tldraw/editor 4.3.0-canary.244925359412 → 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 +536 -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 +536 -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 +4 -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 +1 -0
- 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]
|
|
@@ -3648,8 +3782,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3648
3782
|
}
|
|
3649
3783
|
}
|
|
3650
3784
|
|
|
3651
|
-
this._tickCameraState()
|
|
3652
|
-
|
|
3653
3785
|
return this
|
|
3654
3786
|
}
|
|
3655
3787
|
|
|
@@ -4055,18 +4187,19 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4055
4187
|
// box just for rendering, and we only update after the camera stops moving.
|
|
4056
4188
|
private _cameraState = atom('camera state', 'idle' as 'idle' | 'moving')
|
|
4057
4189
|
private _cameraStateTimeoutRemaining = 0
|
|
4058
|
-
_decayCameraStateTimeout(elapsed: number) {
|
|
4190
|
+
private _decayCameraStateTimeout(elapsed: number) {
|
|
4059
4191
|
this._cameraStateTimeoutRemaining -= elapsed
|
|
4060
4192
|
if (this._cameraStateTimeoutRemaining > 0) return
|
|
4061
4193
|
this.off('tick', this._decayCameraStateTimeout)
|
|
4062
4194
|
this._cameraState.set('idle')
|
|
4063
4195
|
}
|
|
4064
|
-
_tickCameraState() {
|
|
4196
|
+
private _tickCameraState() {
|
|
4065
4197
|
// always reset the timeout
|
|
4066
4198
|
this._cameraStateTimeoutRemaining = this.options.cameraMovingTimeoutMs
|
|
4067
4199
|
// If the state is idle, then start the tick
|
|
4068
4200
|
if (this._cameraState.__unsafe__getWithoutCapture() !== 'idle') return
|
|
4069
4201
|
this._cameraState.set('moving')
|
|
4202
|
+
this._debouncedZoomLevel.set(unsafe__withoutCapture(() => this.getCamera().z))
|
|
4070
4203
|
this.on('tick', this._decayCameraStateTimeout)
|
|
4071
4204
|
}
|
|
4072
4205
|
|
|
@@ -5013,6 +5146,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5013
5146
|
}
|
|
5014
5147
|
|
|
5015
5148
|
private _notVisibleShapes = notVisibleShapes(this)
|
|
5149
|
+
private _culledShapesCache: Set<TLShapeId> | null = null
|
|
5016
5150
|
|
|
5017
5151
|
/**
|
|
5018
5152
|
* Get culled shapes (those that should not render), taking into account which shapes are selected or editing.
|
|
@@ -5024,16 +5158,41 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5024
5158
|
const notVisibleShapes = this.getNotVisibleShapes()
|
|
5025
5159
|
const selectedShapeIds = this.getSelectedShapeIds()
|
|
5026
5160
|
const editingId = this.getEditingShapeId()
|
|
5027
|
-
const
|
|
5161
|
+
const nextValue = new Set<TLShapeId>(notVisibleShapes)
|
|
5028
5162
|
// we don't cull the shape we are editing
|
|
5029
5163
|
if (editingId) {
|
|
5030
|
-
|
|
5164
|
+
nextValue.delete(editingId)
|
|
5031
5165
|
}
|
|
5032
5166
|
// we also don't cull selected shapes
|
|
5033
5167
|
selectedShapeIds.forEach((id) => {
|
|
5034
|
-
|
|
5168
|
+
nextValue.delete(id)
|
|
5035
5169
|
})
|
|
5036
|
-
|
|
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
|
|
5037
5196
|
}
|
|
5038
5197
|
|
|
5039
5198
|
/**
|
|
@@ -5100,11 +5259,18 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5100
5259
|
let inMarginClosestToEdgeDistance = Infinity
|
|
5101
5260
|
let inMarginClosestToEdgeHit: TLShape | null = null
|
|
5102
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
|
+
|
|
5103
5266
|
const shapesToCheck = (
|
|
5104
5267
|
opts.renderingOnly
|
|
5105
5268
|
? this.getCurrentPageRenderingShapesSorted()
|
|
5106
5269
|
: this.getCurrentPageShapesSorted()
|
|
5107
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
|
+
|
|
5108
5274
|
if (
|
|
5109
5275
|
(shape.isLocked && !hitLocked) ||
|
|
5110
5276
|
this.isShapeHidden(shape) ||
|
|
@@ -5290,11 +5456,39 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5290
5456
|
point: VecLike,
|
|
5291
5457
|
opts = {} as { margin?: number; hitInside?: boolean }
|
|
5292
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
|
|
5293
5464
|
return this.getCurrentPageShapesSorted()
|
|
5294
|
-
.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
|
+
})
|
|
5295
5470
|
.reverse()
|
|
5296
5471
|
}
|
|
5297
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
|
+
|
|
5298
5492
|
/**
|
|
5299
5493
|
* Test whether a point (in the current page space) will will a shape. This method takes into account masks,
|
|
5300
5494
|
* such as when a shape is the child of a frame and is partially clipped by the frame.
|
|
@@ -7652,8 +7846,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7652
7846
|
// then if the shape is flipped in one axis only, we need to apply an extra rotation
|
|
7653
7847
|
// to make sure the shape is mirrored correctly
|
|
7654
7848
|
if (Math.sign(scale.x) * Math.sign(scale.y) < 0) {
|
|
7655
|
-
|
|
7656
|
-
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
|
|
7657
7857
|
this.updateShapes([{ id, type, rotation }])
|
|
7658
7858
|
}
|
|
7659
7859
|
|
|
@@ -7673,9 +7873,13 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7673
7873
|
)
|
|
7674
7874
|
|
|
7675
7875
|
// now calculate how far away the shape is from where it needs to be
|
|
7676
|
-
const pageBounds = this.getShapePageBounds(id)!
|
|
7677
7876
|
const pageTransform = this.getShapePageTransform(id)!
|
|
7678
|
-
|
|
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)
|
|
7679
7883
|
const shapePageTransformOrigin = pageTransform.point()
|
|
7680
7884
|
if (!currentPageCenter || !shapePageTransformOrigin) return this
|
|
7681
7885
|
const pageDelta = Vec.Sub(postScaleShapePageCenter, currentPageCenter)
|
|
@@ -8123,7 +8327,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8123
8327
|
)
|
|
8124
8328
|
)
|
|
8125
8329
|
const sortedShapeIds = shapesToGroup.sort(sortByIndex).map((s) => s.id)
|
|
8126
|
-
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
|
+
}
|
|
8127
8336
|
|
|
8128
8337
|
const { x, y } = pageBounds.point
|
|
8129
8338
|
|
|
@@ -9148,6 +9357,30 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9148
9357
|
}
|
|
9149
9358
|
}
|
|
9150
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
|
+
|
|
9151
9384
|
let isDuplicating = false
|
|
9152
9385
|
|
|
9153
9386
|
if (!isPageId(pasteParentId)) {
|
|
@@ -9485,126 +9718,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9485
9718
|
|
|
9486
9719
|
/* --------------------- Events --------------------- */
|
|
9487
9720
|
|
|
9488
|
-
/**
|
|
9489
|
-
* The app's current input state.
|
|
9490
|
-
*
|
|
9491
|
-
* @public
|
|
9492
|
-
*/
|
|
9493
|
-
inputs = {
|
|
9494
|
-
/** The most recent pointer down's position in the current page space. */
|
|
9495
|
-
originPagePoint: new Vec(),
|
|
9496
|
-
/** The most recent pointer down's position in screen space. */
|
|
9497
|
-
originScreenPoint: new Vec(),
|
|
9498
|
-
/** The previous pointer position in the current page space. */
|
|
9499
|
-
previousPagePoint: new Vec(),
|
|
9500
|
-
/** The previous pointer position in screen space. */
|
|
9501
|
-
previousScreenPoint: new Vec(),
|
|
9502
|
-
/** The most recent pointer position in the current page space. */
|
|
9503
|
-
currentPagePoint: new Vec(),
|
|
9504
|
-
/** The most recent pointer position in screen space. */
|
|
9505
|
-
currentScreenPoint: new Vec(),
|
|
9506
|
-
/** A set containing the currently pressed keys. */
|
|
9507
|
-
keys: new Set<string>(),
|
|
9508
|
-
/** A set containing the currently pressed buttons. */
|
|
9509
|
-
buttons: new Set<number>(),
|
|
9510
|
-
/** Whether the input is from a pe. */
|
|
9511
|
-
isPen: false,
|
|
9512
|
-
/** Whether the shift key is currently pressed. */
|
|
9513
|
-
shiftKey: false,
|
|
9514
|
-
/** Whether the meta key is currently pressed. */
|
|
9515
|
-
metaKey: false,
|
|
9516
|
-
/** Whether the control or command key is currently pressed. */
|
|
9517
|
-
ctrlKey: false,
|
|
9518
|
-
/** Whether the alt or option key is currently pressed. */
|
|
9519
|
-
altKey: false,
|
|
9520
|
-
/** Whether the user is dragging. */
|
|
9521
|
-
isDragging: false,
|
|
9522
|
-
/** Whether the user is pointing. */
|
|
9523
|
-
isPointing: false,
|
|
9524
|
-
/** Whether the user is pinching. */
|
|
9525
|
-
isPinching: false,
|
|
9526
|
-
/** Whether the user is editing. */
|
|
9527
|
-
isEditing: false,
|
|
9528
|
-
/** Whether the user is panning. */
|
|
9529
|
-
isPanning: false,
|
|
9530
|
-
/** Whether the user is spacebar panning. */
|
|
9531
|
-
isSpacebarPanning: false,
|
|
9532
|
-
/** Velocity of mouse pointer, in pixels per millisecond */
|
|
9533
|
-
pointerVelocity: new Vec(),
|
|
9534
|
-
}
|
|
9535
|
-
|
|
9536
|
-
/**
|
|
9537
|
-
* Update the input points from a pointer, pinch, or wheel event.
|
|
9538
|
-
*
|
|
9539
|
-
* @param info - The event info.
|
|
9540
|
-
*/
|
|
9541
|
-
private _updateInputsFromEvent(
|
|
9542
|
-
info: TLPointerEventInfo | TLPinchEventInfo | TLWheelEventInfo
|
|
9543
|
-
): void {
|
|
9544
|
-
const {
|
|
9545
|
-
pointerVelocity,
|
|
9546
|
-
previousScreenPoint,
|
|
9547
|
-
previousPagePoint,
|
|
9548
|
-
currentScreenPoint,
|
|
9549
|
-
currentPagePoint,
|
|
9550
|
-
originScreenPoint,
|
|
9551
|
-
originPagePoint,
|
|
9552
|
-
} = this.inputs
|
|
9553
|
-
|
|
9554
|
-
const { screenBounds } = this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!
|
|
9555
|
-
const { x: cx, y: cy, z: cz } = unsafe__withoutCapture(() => this.getCamera())
|
|
9556
|
-
|
|
9557
|
-
const sx = info.point.x - screenBounds.x
|
|
9558
|
-
const sy = info.point.y - screenBounds.y
|
|
9559
|
-
const sz = info.point.z ?? 0.5
|
|
9560
|
-
|
|
9561
|
-
previousScreenPoint.setTo(currentScreenPoint)
|
|
9562
|
-
previousPagePoint.setTo(currentPagePoint)
|
|
9563
|
-
|
|
9564
|
-
// The "screen bounds" is relative to the user's actual screen.
|
|
9565
|
-
// The "screen point" is relative to the "screen bounds";
|
|
9566
|
-
// it will be 0,0 when its actual screen position is equal
|
|
9567
|
-
// to screenBounds.point. This is confusing!
|
|
9568
|
-
currentScreenPoint.set(sx, sy)
|
|
9569
|
-
const nx = sx / cz - cx
|
|
9570
|
-
const ny = sy / cz - cy
|
|
9571
|
-
if (isFinite(nx) && isFinite(ny)) {
|
|
9572
|
-
currentPagePoint.set(nx, ny, sz)
|
|
9573
|
-
}
|
|
9574
|
-
|
|
9575
|
-
this.inputs.isPen = info.type === 'pointer' && info.isPen
|
|
9576
|
-
|
|
9577
|
-
// Reset velocity on pointer down, or when a pinch starts or ends
|
|
9578
|
-
if (info.name === 'pointer_down' || this.inputs.isPinching) {
|
|
9579
|
-
pointerVelocity.set(0, 0)
|
|
9580
|
-
originScreenPoint.setTo(currentScreenPoint)
|
|
9581
|
-
originPagePoint.setTo(currentPagePoint)
|
|
9582
|
-
}
|
|
9583
|
-
|
|
9584
|
-
// todo: We only have to do this if there are multiple users in the document
|
|
9585
|
-
this.run(
|
|
9586
|
-
() => {
|
|
9587
|
-
this.store.put([
|
|
9588
|
-
{
|
|
9589
|
-
id: TLPOINTER_ID,
|
|
9590
|
-
typeName: 'pointer',
|
|
9591
|
-
x: currentPagePoint.x,
|
|
9592
|
-
y: currentPagePoint.y,
|
|
9593
|
-
lastActivityTimestamp:
|
|
9594
|
-
// If our pointer moved only because we're following some other user, then don't
|
|
9595
|
-
// update our last activity timestamp; otherwise, update it to the current timestamp.
|
|
9596
|
-
info.type === 'pointer' && info.pointerId === INTERNAL_POINTER_IDS.CAMERA_MOVE
|
|
9597
|
-
? (this.store.unsafeGetWithoutCapture(TLPOINTER_ID)?.lastActivityTimestamp ??
|
|
9598
|
-
this._tickManager.now)
|
|
9599
|
-
: this._tickManager.now,
|
|
9600
|
-
meta: {},
|
|
9601
|
-
},
|
|
9602
|
-
])
|
|
9603
|
-
},
|
|
9604
|
-
{ history: 'ignore' }
|
|
9605
|
-
)
|
|
9606
|
-
}
|
|
9607
|
-
|
|
9608
9721
|
/**
|
|
9609
9722
|
* Dispatch a cancel event.
|
|
9610
9723
|
*
|
|
@@ -9674,19 +9787,22 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9674
9787
|
// weird but true: what `inputs` calls screen-space is actually viewport space. so
|
|
9675
9788
|
// we need to convert back into true screen space first. we should fix this...
|
|
9676
9789
|
Vec.Add(
|
|
9677
|
-
this.inputs.
|
|
9790
|
+
this.inputs.getCurrentScreenPoint(),
|
|
9678
9791
|
this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!.screenBounds
|
|
9679
9792
|
),
|
|
9680
9793
|
pointerId: options?.pointerId ?? 0,
|
|
9681
9794
|
button: options?.button ?? 0,
|
|
9682
|
-
isPen: options?.isPen ?? this.inputs.
|
|
9683
|
-
shiftKey: options?.shiftKey ?? this.inputs.
|
|
9684
|
-
altKey: options?.altKey ?? this.inputs.
|
|
9685
|
-
ctrlKey: options?.ctrlKey ?? this.inputs.
|
|
9686
|
-
metaKey: options?.metaKey ?? this.inputs.
|
|
9687
|
-
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,
|
|
9688
9801
|
}
|
|
9689
9802
|
|
|
9803
|
+
// needs to be calculated second
|
|
9804
|
+
event.accelKey = options?.accelKey ?? this.inputs.getAccelKey()
|
|
9805
|
+
|
|
9690
9806
|
if (options?.immediate) {
|
|
9691
9807
|
this._flushEventForTick(event)
|
|
9692
9808
|
} else {
|
|
@@ -10059,16 +10175,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10059
10175
|
/** @internal */
|
|
10060
10176
|
@bind
|
|
10061
10177
|
_setShiftKeyTimeout() {
|
|
10062
|
-
this.inputs.
|
|
10178
|
+
this.inputs.setShiftKey(false)
|
|
10063
10179
|
this.dispatch({
|
|
10064
10180
|
type: 'keyboard',
|
|
10065
10181
|
name: 'key_up',
|
|
10066
10182
|
key: 'Shift',
|
|
10067
|
-
shiftKey: this.inputs.
|
|
10068
|
-
ctrlKey: this.inputs.
|
|
10069
|
-
altKey: this.inputs.
|
|
10070
|
-
metaKey: this.inputs.
|
|
10071
|
-
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(),
|
|
10072
10188
|
code: 'ShiftLeft',
|
|
10073
10189
|
})
|
|
10074
10190
|
}
|
|
@@ -10079,16 +10195,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10079
10195
|
/** @internal */
|
|
10080
10196
|
@bind
|
|
10081
10197
|
_setAltKeyTimeout() {
|
|
10082
|
-
this.inputs.
|
|
10198
|
+
this.inputs.setAltKey(false)
|
|
10083
10199
|
this.dispatch({
|
|
10084
10200
|
type: 'keyboard',
|
|
10085
10201
|
name: 'key_up',
|
|
10086
10202
|
key: 'Alt',
|
|
10087
|
-
shiftKey: this.inputs.
|
|
10088
|
-
ctrlKey: this.inputs.
|
|
10089
|
-
altKey: this.inputs.
|
|
10090
|
-
metaKey: this.inputs.
|
|
10091
|
-
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(),
|
|
10092
10208
|
code: 'AltLeft',
|
|
10093
10209
|
})
|
|
10094
10210
|
}
|
|
@@ -10099,16 +10215,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10099
10215
|
/** @internal */
|
|
10100
10216
|
@bind
|
|
10101
10217
|
_setCtrlKeyTimeout() {
|
|
10102
|
-
this.inputs.
|
|
10218
|
+
this.inputs.setCtrlKey(false)
|
|
10103
10219
|
this.dispatch({
|
|
10104
10220
|
type: 'keyboard',
|
|
10105
10221
|
name: 'key_up',
|
|
10106
10222
|
key: 'Ctrl',
|
|
10107
|
-
shiftKey: this.inputs.
|
|
10108
|
-
ctrlKey: this.inputs.
|
|
10109
|
-
altKey: this.inputs.
|
|
10110
|
-
metaKey: this.inputs.
|
|
10111
|
-
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(),
|
|
10112
10228
|
code: 'ControlLeft',
|
|
10113
10229
|
})
|
|
10114
10230
|
}
|
|
@@ -10119,16 +10235,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10119
10235
|
/** @internal */
|
|
10120
10236
|
@bind
|
|
10121
10237
|
_setMetaKeyTimeout() {
|
|
10122
|
-
this.inputs.
|
|
10238
|
+
this.inputs.setMetaKey(false)
|
|
10123
10239
|
this.dispatch({
|
|
10124
10240
|
type: 'keyboard',
|
|
10125
10241
|
name: 'key_up',
|
|
10126
10242
|
key: 'Meta',
|
|
10127
|
-
shiftKey: this.inputs.
|
|
10128
|
-
ctrlKey: this.inputs.
|
|
10129
|
-
altKey: this.inputs.
|
|
10130
|
-
metaKey: this.inputs.
|
|
10131
|
-
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(),
|
|
10132
10248
|
code: 'MetaLeft',
|
|
10133
10249
|
})
|
|
10134
10250
|
}
|
|
@@ -10136,9 +10252,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10136
10252
|
/** @internal */
|
|
10137
10253
|
private _restoreToolId = 'select'
|
|
10138
10254
|
|
|
10139
|
-
/** @internal */
|
|
10140
|
-
private _pinchStart = 1
|
|
10141
|
-
|
|
10142
10255
|
/** @internal */
|
|
10143
10256
|
private _didPinch = false
|
|
10144
10257
|
|
|
@@ -10245,56 +10358,54 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10245
10358
|
if (info.type === 'misc') {
|
|
10246
10359
|
// stop panning if the interaction is cancelled or completed
|
|
10247
10360
|
if (info.name === 'cancel' || info.name === 'complete') {
|
|
10248
|
-
this.inputs.
|
|
10361
|
+
this.inputs.setIsDragging(false)
|
|
10249
10362
|
|
|
10250
|
-
if (this.inputs.
|
|
10251
|
-
this.inputs.
|
|
10252
|
-
this.inputs.
|
|
10363
|
+
if (this.inputs.getIsPanning()) {
|
|
10364
|
+
this.inputs.setIsPanning(false)
|
|
10365
|
+
this.inputs.setIsSpacebarPanning(false)
|
|
10253
10366
|
this.setCursor({ type: this._prevCursor, rotation: 0 })
|
|
10254
10367
|
}
|
|
10255
10368
|
}
|
|
10256
10369
|
|
|
10257
|
-
this.emit('event', info)
|
|
10258
10370
|
this.root.handleEvent(info)
|
|
10371
|
+
this.emit('event', info)
|
|
10259
10372
|
return
|
|
10260
10373
|
}
|
|
10261
10374
|
|
|
10262
10375
|
if (info.shiftKey) {
|
|
10263
10376
|
clearTimeout(this._shiftKeyTimeout)
|
|
10264
10377
|
this._shiftKeyTimeout = -1
|
|
10265
|
-
inputs.
|
|
10266
|
-
} else if (!info.shiftKey && inputs.
|
|
10378
|
+
inputs.setShiftKey(true)
|
|
10379
|
+
} else if (!info.shiftKey && inputs.getShiftKey() && this._shiftKeyTimeout === -1) {
|
|
10267
10380
|
this._shiftKeyTimeout = this.timers.setTimeout(this._setShiftKeyTimeout, 150)
|
|
10268
10381
|
}
|
|
10269
10382
|
|
|
10270
10383
|
if (info.altKey) {
|
|
10271
10384
|
clearTimeout(this._altKeyTimeout)
|
|
10272
10385
|
this._altKeyTimeout = -1
|
|
10273
|
-
inputs.
|
|
10274
|
-
} else if (!info.altKey && inputs.
|
|
10386
|
+
inputs.setAltKey(true)
|
|
10387
|
+
} else if (!info.altKey && inputs.getAltKey() && this._altKeyTimeout === -1) {
|
|
10275
10388
|
this._altKeyTimeout = this.timers.setTimeout(this._setAltKeyTimeout, 150)
|
|
10276
10389
|
}
|
|
10277
10390
|
|
|
10278
10391
|
if (info.ctrlKey) {
|
|
10279
10392
|
clearTimeout(this._ctrlKeyTimeout)
|
|
10280
10393
|
this._ctrlKeyTimeout = -1
|
|
10281
|
-
inputs.
|
|
10282
|
-
} else if (!info.ctrlKey && inputs.
|
|
10394
|
+
inputs.setCtrlKey(true)
|
|
10395
|
+
} else if (!info.ctrlKey && inputs.getCtrlKey() && this._ctrlKeyTimeout === -1) {
|
|
10283
10396
|
this._ctrlKeyTimeout = this.timers.setTimeout(this._setCtrlKeyTimeout, 150)
|
|
10284
10397
|
}
|
|
10285
10398
|
|
|
10286
10399
|
if (info.metaKey) {
|
|
10287
10400
|
clearTimeout(this._metaKeyTimeout)
|
|
10288
10401
|
this._metaKeyTimeout = -1
|
|
10289
|
-
inputs.
|
|
10290
|
-
} else if (!info.metaKey && inputs.
|
|
10402
|
+
inputs.setMetaKey(true)
|
|
10403
|
+
} else if (!info.metaKey && inputs.getMetaKey() && this._metaKeyTimeout === -1) {
|
|
10291
10404
|
this._metaKeyTimeout = this.timers.setTimeout(this._setMetaKeyTimeout, 150)
|
|
10292
10405
|
}
|
|
10293
10406
|
|
|
10294
|
-
|
|
10295
|
-
|
|
10296
|
-
if (!inputs.isPointing) {
|
|
10297
|
-
inputs.isDragging = false
|
|
10407
|
+
if (!inputs.getIsPointing()) {
|
|
10408
|
+
inputs.setIsDragging(false)
|
|
10298
10409
|
}
|
|
10299
10410
|
|
|
10300
10411
|
const instanceState = this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!
|
|
@@ -10305,29 +10416,29 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10305
10416
|
case 'pinch': {
|
|
10306
10417
|
if (cameraOptions.isLocked) return
|
|
10307
10418
|
clearTimeout(this._longPressTimeout)
|
|
10308
|
-
this.
|
|
10419
|
+
this.inputs.updateFromEvent(info)
|
|
10309
10420
|
|
|
10310
10421
|
switch (info.name) {
|
|
10311
10422
|
case 'pinch_start': {
|
|
10312
|
-
if (inputs.
|
|
10423
|
+
if (inputs.getIsPinching()) return
|
|
10313
10424
|
|
|
10314
|
-
if (!inputs.
|
|
10315
|
-
this._pinchStart = this.getCamera().z
|
|
10425
|
+
if (!inputs.getIsEditing()) {
|
|
10316
10426
|
if (!this._selectedShapeIdsAtPointerDown.length) {
|
|
10317
10427
|
this._selectedShapeIdsAtPointerDown = [...pageState.selectedShapeIds]
|
|
10318
10428
|
}
|
|
10319
10429
|
|
|
10320
10430
|
this._didPinch = true
|
|
10321
10431
|
|
|
10322
|
-
inputs.
|
|
10432
|
+
inputs.setIsPinching(true)
|
|
10323
10433
|
|
|
10324
10434
|
this.interrupt()
|
|
10325
10435
|
}
|
|
10326
10436
|
|
|
10437
|
+
this.emit('event', info)
|
|
10327
10438
|
return // Stop here!
|
|
10328
10439
|
}
|
|
10329
10440
|
case 'pinch': {
|
|
10330
|
-
if (!inputs.
|
|
10441
|
+
if (!inputs.getIsPinching()) return
|
|
10331
10442
|
|
|
10332
10443
|
const {
|
|
10333
10444
|
point: { z = 1 },
|
|
@@ -10358,13 +10469,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10358
10469
|
{ immediate: true }
|
|
10359
10470
|
)
|
|
10360
10471
|
|
|
10472
|
+
this.emit('event', info)
|
|
10361
10473
|
return // Stop here!
|
|
10362
10474
|
}
|
|
10363
10475
|
case 'pinch_end': {
|
|
10364
|
-
if (!inputs.
|
|
10476
|
+
if (!inputs.getIsPinching()) return this
|
|
10365
10477
|
|
|
10366
10478
|
// Stop pinching
|
|
10367
|
-
inputs.
|
|
10479
|
+
inputs.setIsPinching(false)
|
|
10368
10480
|
|
|
10369
10481
|
// Stash and clear the shapes that were selected when the pinch started
|
|
10370
10482
|
const { _selectedShapeIdsAtPointerDown: shapesToReselect } = this
|
|
@@ -10384,6 +10496,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10384
10496
|
}
|
|
10385
10497
|
}
|
|
10386
10498
|
|
|
10499
|
+
this.emit('event', info)
|
|
10387
10500
|
return // Stop here!
|
|
10388
10501
|
}
|
|
10389
10502
|
}
|
|
@@ -10391,7 +10504,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10391
10504
|
case 'wheel': {
|
|
10392
10505
|
if (cameraOptions.isLocked) return
|
|
10393
10506
|
|
|
10394
|
-
this.
|
|
10507
|
+
this.inputs.updateFromEvent(info)
|
|
10395
10508
|
|
|
10396
10509
|
const { panSpeed, zoomSpeed } = cameraOptions
|
|
10397
10510
|
let wheelBehavior = cameraOptions.wheelBehavior
|
|
@@ -10422,7 +10535,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10422
10535
|
switch (behavior) {
|
|
10423
10536
|
case 'zoom': {
|
|
10424
10537
|
// Zoom in on current screen point using the wheel delta
|
|
10425
|
-
const { x, y } = this.inputs.
|
|
10538
|
+
const { x, y } = this.inputs.getCurrentScreenPoint()
|
|
10426
10539
|
let delta = dz
|
|
10427
10540
|
|
|
10428
10541
|
// If we're forcing zoom, then we need to do the wheel normalization math here
|
|
@@ -10439,6 +10552,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10439
10552
|
immediate: true,
|
|
10440
10553
|
})
|
|
10441
10554
|
this.maybeTrackPerformance('Zooming')
|
|
10555
|
+
this.root.handleEvent(info)
|
|
10556
|
+
this.emit('event', info)
|
|
10442
10557
|
return
|
|
10443
10558
|
}
|
|
10444
10559
|
case 'pan': {
|
|
@@ -10447,6 +10562,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10447
10562
|
immediate: true,
|
|
10448
10563
|
})
|
|
10449
10564
|
this.maybeTrackPerformance('Panning')
|
|
10565
|
+
this.root.handleEvent(info)
|
|
10566
|
+
this.emit('event', info)
|
|
10450
10567
|
return
|
|
10451
10568
|
}
|
|
10452
10569
|
}
|
|
@@ -10455,9 +10572,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10455
10572
|
}
|
|
10456
10573
|
case 'pointer': {
|
|
10457
10574
|
// Ignore pointer events while we're pinching
|
|
10458
|
-
if (inputs.
|
|
10575
|
+
if (inputs.getIsPinching()) return
|
|
10459
10576
|
|
|
10460
|
-
this.
|
|
10577
|
+
this.inputs.updateFromEvent(info)
|
|
10461
10578
|
const { isPen } = info
|
|
10462
10579
|
const { isPenMode } = instanceState
|
|
10463
10580
|
|
|
@@ -10466,7 +10583,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10466
10583
|
// If we're in pen mode and the input is not a pen type, then stop here
|
|
10467
10584
|
if (isPenMode && !isPen) return
|
|
10468
10585
|
|
|
10469
|
-
if (!this.inputs.
|
|
10586
|
+
if (!this.inputs.getIsPanning()) {
|
|
10470
10587
|
// Start a long press timeout
|
|
10471
10588
|
this._longPressTimeout = this.timers.setTimeout(() => {
|
|
10472
10589
|
const vsb = this.getViewportScreenBounds()
|
|
@@ -10476,7 +10593,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10476
10593
|
// viewport bounds, and will be again when this event is handled...
|
|
10477
10594
|
// so we need to counter-adjust from the stored value so that the
|
|
10478
10595
|
// new value is set correctly.
|
|
10479
|
-
point: this.inputs.
|
|
10596
|
+
point: this.inputs.getOriginScreenPoint().clone().addXY(vsb.x, vsb.y),
|
|
10480
10597
|
name: 'long_press',
|
|
10481
10598
|
})
|
|
10482
10599
|
}, this.options.longPressDurationMs)
|
|
@@ -10493,8 +10610,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10493
10610
|
inputs.buttons.add(info.button)
|
|
10494
10611
|
|
|
10495
10612
|
// Start pointing and stop dragging
|
|
10496
|
-
inputs.
|
|
10497
|
-
inputs.
|
|
10613
|
+
inputs.setIsPointing(true)
|
|
10614
|
+
inputs.setIsDragging(false)
|
|
10498
10615
|
|
|
10499
10616
|
// If pen mode is off but we're not already in pen mode, turn that on
|
|
10500
10617
|
if (!isPenMode && isPen) this.updateInstanceState({ isPenMode: true })
|
|
@@ -10506,16 +10623,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10506
10623
|
this.setCurrentTool('eraser')
|
|
10507
10624
|
} else if (info.button === MIDDLE_MOUSE_BUTTON) {
|
|
10508
10625
|
// Middle mouse pan activates panning unless we're already panning (with spacebar)
|
|
10509
|
-
if (!this.inputs.
|
|
10626
|
+
if (!this.inputs.getIsPanning()) {
|
|
10510
10627
|
this._prevCursor = this.getInstanceState().cursor.type
|
|
10511
10628
|
}
|
|
10512
|
-
this.inputs.
|
|
10629
|
+
this.inputs.setIsPanning(true)
|
|
10513
10630
|
clearTimeout(this._longPressTimeout)
|
|
10514
10631
|
}
|
|
10515
10632
|
|
|
10516
10633
|
// We might be panning because we did a middle mouse click, or because we're holding spacebar and started a regular click
|
|
10517
10634
|
// Also stop here, we don't want the state chart to receive the event
|
|
10518
|
-
if (this.inputs.
|
|
10635
|
+
if (this.inputs.getIsPanning()) {
|
|
10519
10636
|
this.stopCameraAnimation()
|
|
10520
10637
|
this.setCursor({ type: 'grabbing', rotation: 0 })
|
|
10521
10638
|
return this
|
|
@@ -10530,9 +10647,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10530
10647
|
const { x: cx, y: cy, z: cz } = unsafe__withoutCapture(() => this.getCamera())
|
|
10531
10648
|
|
|
10532
10649
|
// If we've started panning, then clear any long press timeout
|
|
10533
|
-
if (this.inputs.
|
|
10650
|
+
if (this.inputs.getIsPanning() && this.inputs.getIsPointing()) {
|
|
10534
10651
|
// Handle spacebar / middle mouse button panning
|
|
10535
|
-
const
|
|
10652
|
+
const currentScreenPoint = this.inputs.getCurrentScreenPoint()
|
|
10653
|
+
const previousScreenPoint = this.inputs.getPreviousScreenPoint()
|
|
10536
10654
|
const offset = Vec.Sub(currentScreenPoint, previousScreenPoint)
|
|
10537
10655
|
this.setCamera(new Vec(cx + offset.x / cz, cy + offset.y / cz, cz), {
|
|
10538
10656
|
immediate: true,
|
|
@@ -10542,24 +10660,25 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10542
10660
|
}
|
|
10543
10661
|
|
|
10544
10662
|
if (
|
|
10545
|
-
inputs.
|
|
10546
|
-
!inputs.
|
|
10547
|
-
Vec.Dist2(
|
|
10663
|
+
inputs.getIsPointing() &&
|
|
10664
|
+
!inputs.getIsDragging() &&
|
|
10665
|
+
Vec.Dist2(inputs.getOriginPagePoint(), inputs.getCurrentPagePoint()) *
|
|
10666
|
+
this.getZoomLevel() >
|
|
10548
10667
|
(instanceState.isCoarsePointer
|
|
10549
10668
|
? this.options.coarseDragDistanceSquared
|
|
10550
10669
|
: this.options.dragDistanceSquared) /
|
|
10551
10670
|
cz
|
|
10552
10671
|
) {
|
|
10553
10672
|
// Start dragging
|
|
10554
|
-
inputs.
|
|
10673
|
+
inputs.setIsDragging(true)
|
|
10555
10674
|
clearTimeout(this._longPressTimeout)
|
|
10556
10675
|
}
|
|
10557
10676
|
break
|
|
10558
10677
|
}
|
|
10559
10678
|
case 'pointer_up': {
|
|
10560
10679
|
// Stop dragging / pointing
|
|
10561
|
-
inputs.
|
|
10562
|
-
inputs.
|
|
10680
|
+
inputs.setIsDragging(false)
|
|
10681
|
+
inputs.setIsPointing(false)
|
|
10563
10682
|
clearTimeout(this._longPressTimeout)
|
|
10564
10683
|
|
|
10565
10684
|
// Remove the button from the buttons set
|
|
@@ -10576,12 +10695,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10576
10695
|
info.button = 0
|
|
10577
10696
|
}
|
|
10578
10697
|
|
|
10579
|
-
if (inputs.
|
|
10698
|
+
if (inputs.getIsPanning()) {
|
|
10580
10699
|
if (!inputs.keys.has('Space')) {
|
|
10581
|
-
inputs.
|
|
10582
|
-
inputs.
|
|
10700
|
+
inputs.setIsPanning(false)
|
|
10701
|
+
inputs.setIsSpacebarPanning(false)
|
|
10583
10702
|
}
|
|
10584
|
-
const slideDirection = this.inputs.
|
|
10703
|
+
const slideDirection = this.inputs.getPointerVelocity()
|
|
10585
10704
|
const slideSpeed = Math.min(2, slideDirection.len())
|
|
10586
10705
|
|
|
10587
10706
|
switch (info.button) {
|
|
@@ -10625,43 +10744,48 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10625
10744
|
// Add the key from the keys set
|
|
10626
10745
|
inputs.keys.add(info.code)
|
|
10627
10746
|
|
|
10628
|
-
|
|
10629
|
-
|
|
10630
|
-
if (!
|
|
10631
|
-
this.
|
|
10632
|
-
|
|
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
|
+
}
|
|
10633
10753
|
|
|
10634
|
-
|
|
10635
|
-
|
|
10636
|
-
|
|
10637
|
-
|
|
10638
|
-
|
|
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
|
+
}
|
|
10639
10762
|
|
|
10640
|
-
|
|
10641
|
-
|
|
10642
|
-
|
|
10643
|
-
|
|
10644
|
-
|
|
10645
|
-
|
|
10646
|
-
|
|
10647
|
-
|
|
10648
|
-
|
|
10649
|
-
|
|
10650
|
-
|
|
10651
|
-
|
|
10652
|
-
|
|
10653
|
-
|
|
10654
|
-
|
|
10655
|
-
|
|
10656
|
-
|
|
10657
|
-
|
|
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
|
+
}
|
|
10658
10782
|
}
|
|
10659
|
-
}
|
|
10660
10783
|
|
|
10661
|
-
|
|
10662
|
-
|
|
10663
|
-
|
|
10664
|
-
|
|
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
|
+
}
|
|
10665
10789
|
}
|
|
10666
10790
|
}
|
|
10667
10791
|
|
|
@@ -10671,15 +10795,17 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10671
10795
|
// Remove the key from the keys set
|
|
10672
10796
|
inputs.keys.delete(info.code)
|
|
10673
10797
|
|
|
10674
|
-
|
|
10675
|
-
|
|
10676
|
-
if (
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
|
|
10681
|
-
|
|
10682
|
-
|
|
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
|
+
}
|
|
10683
10809
|
}
|
|
10684
10810
|
}
|
|
10685
10811
|
break
|