@tldraw/editor 5.1.1 → 5.2.0-canary.0878dbd31f0d
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 +7 -1
- package/dist-cjs/index.d.ts +33 -50
- package/dist-cjs/index.js +3 -4
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +4 -1
- package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +3 -3
- package/dist-cjs/lib/components/default-components/DefaultLoadingScreen.js +2 -2
- package/dist-cjs/lib/components/default-components/DefaultLoadingScreen.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultShapeErrorFallback.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultShapeErrorFallback.js.map +3 -3
- package/dist-cjs/lib/components/default-components/DefaultSvgDefs.js +2 -2
- package/dist-cjs/lib/components/default-components/DefaultSvgDefs.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +57 -18
- package/dist-cjs/lib/editor/Editor.js.map +3 -3
- package/dist-cjs/lib/editor/derivations/bindingsIndex.js +2 -2
- package/dist-cjs/lib/editor/derivations/bindingsIndex.js.map +2 -2
- package/dist-cjs/lib/editor/derivations/parentsToChildren.js +2 -2
- package/dist-cjs/lib/editor/derivations/parentsToChildren.js.map +2 -2
- package/dist-cjs/lib/editor/derivations/shapeIdsInCurrentPage.js +2 -2
- package/dist-cjs/lib/editor/derivations/shapeIdsInCurrentPage.js.map +2 -2
- package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js +8 -58
- package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js +3 -3
- package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js +1 -2
- package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +15 -2
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
- package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js +79 -0
- package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js.map +7 -0
- package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
- package/dist-cjs/lib/editor/types/event-types.js +0 -2
- package/dist-cjs/lib/editor/types/event-types.js.map +2 -2
- package/dist-cjs/lib/hooks/usePresence.js.map +2 -2
- package/dist-cjs/lib/license/LicenseProvider.js +3 -1
- package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
- package/dist-cjs/lib/primitives/utils.js +2 -2
- package/dist-cjs/lib/primitives/utils.js.map +2 -2
- package/dist-cjs/lib/utils/dom.js +5 -3
- package/dist-cjs/lib/utils/dom.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 +33 -50
- package/dist-esm/index.mjs +2 -6
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +4 -1
- package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +3 -3
- package/dist-esm/lib/components/default-components/DefaultLoadingScreen.mjs +2 -2
- package/dist-esm/lib/components/default-components/DefaultLoadingScreen.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultShapeErrorFallback.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultShapeErrorFallback.mjs.map +3 -3
- package/dist-esm/lib/components/default-components/DefaultSvgDefs.mjs +2 -2
- package/dist-esm/lib/components/default-components/DefaultSvgDefs.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +57 -18
- package/dist-esm/lib/editor/Editor.mjs.map +3 -3
- package/dist-esm/lib/editor/derivations/bindingsIndex.mjs +2 -2
- package/dist-esm/lib/editor/derivations/bindingsIndex.mjs.map +2 -2
- package/dist-esm/lib/editor/derivations/parentsToChildren.mjs +2 -2
- package/dist-esm/lib/editor/derivations/parentsToChildren.mjs.map +2 -2
- package/dist-esm/lib/editor/derivations/shapeIdsInCurrentPage.mjs +2 -2
- package/dist-esm/lib/editor/derivations/shapeIdsInCurrentPage.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs +8 -58
- package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs +3 -3
- package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs +1 -2
- package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +15 -2
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
- package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs +59 -0
- package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs.map +7 -0
- package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
- package/dist-esm/lib/editor/types/event-types.mjs +0 -2
- package/dist-esm/lib/editor/types/event-types.mjs.map +2 -2
- package/dist-esm/lib/hooks/usePresence.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseProvider.mjs +3 -1
- package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
- package/dist-esm/lib/primitives/utils.mjs +2 -2
- package/dist-esm/lib/primitives/utils.mjs.map +2 -2
- package/dist-esm/lib/utils/dom.mjs +5 -3
- package/dist-esm/lib/utils/dom.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +2 -0
- package/package.json +8 -8
- package/src/index.ts +1 -5
- package/src/lib/components/default-components/DefaultErrorFallback.tsx +4 -1
- package/src/lib/components/default-components/DefaultLoadingScreen.tsx +1 -1
- package/src/lib/components/default-components/DefaultShapeErrorFallback.tsx +4 -3
- package/src/lib/components/default-components/DefaultSvgDefs.tsx +1 -1
- package/src/lib/editor/Editor.ts +92 -29
- package/src/lib/editor/derivations/bindingsIndex.ts +1 -1
- package/src/lib/editor/derivations/parentsToChildren.ts +1 -1
- package/src/lib/editor/derivations/shapeIdsInCurrentPage.ts +1 -1
- package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +54 -74
- package/src/lib/editor/managers/ClickManager/ClickManager.ts +15 -65
- package/src/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.test.ts +43 -16
- package/src/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.ts +8 -5
- package/src/lib/editor/managers/FocusManager/FocusManager.test.ts +4 -4
- package/src/lib/editor/managers/FocusManager/FocusManager.ts +1 -2
- package/src/lib/editor/managers/FontManager/FontManager.test.ts +13 -9
- package/src/lib/editor/managers/TextManager/TextManager.test.ts +16 -14
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +12 -2
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +27 -2
- package/src/lib/editor/overlays/strokeShapeIndicators.ts +86 -0
- package/src/lib/editor/tools/StateNode.ts +0 -2
- package/src/lib/editor/types/event-types.ts +2 -6
- package/src/lib/hooks/usePresence.ts +2 -2
- package/src/lib/license/LicenseProvider.tsx +3 -1
- package/src/lib/primitives/utils.ts +1 -1
- package/src/lib/utils/dom.ts +5 -3
- package/src/version.ts +3 -3
- package/dist-cjs/lib/editor/overlays/ShapeIndicatorOverlayUtil.js +0 -161
- package/dist-cjs/lib/editor/overlays/ShapeIndicatorOverlayUtil.js.map +0 -7
- package/dist-esm/lib/editor/overlays/ShapeIndicatorOverlayUtil.mjs +0 -141
- package/dist-esm/lib/editor/overlays/ShapeIndicatorOverlayUtil.mjs.map +0 -7
- package/src/lib/editor/overlays/ShapeIndicatorOverlayUtil.ts +0 -216
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -3068,8 +3068,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3068
3068
|
return baseCamera
|
|
3069
3069
|
}
|
|
3070
3070
|
|
|
3071
|
-
private _getFollowingPresence(targetUserId:
|
|
3072
|
-
const visited = [this.user.
|
|
3071
|
+
private _getFollowingPresence(targetUserId: TLUserId | null) {
|
|
3072
|
+
const visited = [this.user.getRecordId()]
|
|
3073
3073
|
const collaborators = this.getCollaborators()
|
|
3074
3074
|
let leaderPresence = null as null | TLInstancePresence
|
|
3075
3075
|
while (targetUserId && !visited.includes(targetUserId)) {
|
|
@@ -3336,6 +3336,15 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3336
3336
|
|
|
3337
3337
|
let { x, y, z = currentCamera.z } = point
|
|
3338
3338
|
|
|
3339
|
+
// `requested` kept the caller's focal point (e.g. the cursor) fixed at
|
|
3340
|
+
// zoom `rz`. When `rz` gets clamped, keep that same focal point fixed at
|
|
3341
|
+
// the clamped zoom `z` rather than snapping to the viewport center.
|
|
3342
|
+
const preserveFocalPoint = (current: number, requested: number, rz: number, z: number) => {
|
|
3343
|
+
const cz = currentCamera.z
|
|
3344
|
+
if (rz === cz) return current
|
|
3345
|
+
return current + ((requested - current) * (1 / z - 1 / cz)) / (1 / rz - 1 / cz)
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3339
3348
|
// If force is true, then we'll set the camera to the point regardless of
|
|
3340
3349
|
// the camera options, so that we can handle gestures that permit elasticity
|
|
3341
3350
|
// or decay, or animations that occur while the camera is locked.
|
|
@@ -3378,17 +3387,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3378
3387
|
}
|
|
3379
3388
|
|
|
3380
3389
|
if (z < minZ || z > maxZ) {
|
|
3381
|
-
// We're trying to zoom out past the minimum zoom level,
|
|
3382
|
-
//
|
|
3383
|
-
//
|
|
3384
|
-
|
|
3385
|
-
const
|
|
3386
|
-
const cyA = -cy + vsb.h / cz / 2
|
|
3390
|
+
// We're trying to zoom out past the minimum zoom level, or in
|
|
3391
|
+
// past the maximum zoom level, so clamp the zoom while keeping
|
|
3392
|
+
// the caller's focal point fixed. Axis constraints below still
|
|
3393
|
+
// apply on top of this.
|
|
3394
|
+
const rz = z
|
|
3387
3395
|
z = clamp(z, minZ, maxZ)
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
x = cx + cxB - cxA
|
|
3391
|
-
y = cy + cyB - cyA
|
|
3396
|
+
x = preserveFocalPoint(currentCamera.x, x, rz, z)
|
|
3397
|
+
y = preserveFocalPoint(currentCamera.y, y, rz, z)
|
|
3392
3398
|
}
|
|
3393
3399
|
|
|
3394
3400
|
// Calculate available space
|
|
@@ -3477,12 +3483,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3477
3483
|
}
|
|
3478
3484
|
}
|
|
3479
3485
|
} else {
|
|
3480
|
-
// constrain the zoom,
|
|
3486
|
+
// constrain the zoom, keeping the caller's focal point fixed
|
|
3481
3487
|
if (z > zoomMax || z < zoomMin) {
|
|
3482
|
-
const
|
|
3488
|
+
const rz = z
|
|
3483
3489
|
z = clamp(z, zoomMin, zoomMax)
|
|
3484
|
-
x =
|
|
3485
|
-
y =
|
|
3490
|
+
x = preserveFocalPoint(currentCamera.x, x, rz, z)
|
|
3491
|
+
y = preserveFocalPoint(currentCamera.y, y, rz, z)
|
|
3486
3492
|
}
|
|
3487
3493
|
}
|
|
3488
3494
|
}
|
|
@@ -4023,16 +4029,34 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4023
4029
|
|
|
4024
4030
|
this.once('stop-camera-animation', cancel)
|
|
4025
4031
|
|
|
4032
|
+
const dirZ = direction.z ?? 0
|
|
4033
|
+
|
|
4026
4034
|
const moveCamera = (elapsed: number) => {
|
|
4027
4035
|
const { x: cx, y: cy, z: cz } = this.getCamera()
|
|
4028
|
-
|
|
4036
|
+
|
|
4037
|
+
// Pan movement from x/y direction
|
|
4038
|
+
const dx = (direction.x * (currentSpeed * elapsed)) / cz
|
|
4039
|
+
const dy = (direction.y * (currentSpeed * elapsed)) / cz
|
|
4040
|
+
|
|
4041
|
+
let newCx = cx + dx
|
|
4042
|
+
let newCy = cy + dy
|
|
4043
|
+
let newCz = cz
|
|
4044
|
+
|
|
4045
|
+
// animate zoom if z direction is passed in
|
|
4046
|
+
if (dirZ !== 0) {
|
|
4047
|
+
newCz = cz * (1 + dirZ * currentSpeed * elapsed)
|
|
4048
|
+
// Adjust x/y to keep the viewport center fixed while zooming
|
|
4049
|
+
const center = this.getViewportScreenCenter()
|
|
4050
|
+
newCx += center.x / newCz - center.x / cz
|
|
4051
|
+
newCy += center.y / newCz - center.y / cz
|
|
4052
|
+
}
|
|
4029
4053
|
|
|
4030
4054
|
// Apply friction
|
|
4031
4055
|
currentSpeed *= 1 - friction
|
|
4032
4056
|
if (currentSpeed < speedThreshold) {
|
|
4033
4057
|
cancel()
|
|
4034
4058
|
} else {
|
|
4035
|
-
this._setCamera(new Vec(
|
|
4059
|
+
this._setCamera(new Vec(newCx, newCy, newCz))
|
|
4036
4060
|
}
|
|
4037
4061
|
}
|
|
4038
4062
|
|
|
@@ -4054,7 +4078,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4054
4078
|
* @param opts - The camera move options.
|
|
4055
4079
|
* @public
|
|
4056
4080
|
*/
|
|
4057
|
-
zoomToUser(userId:
|
|
4081
|
+
zoomToUser(userId: TLUserId, opts: TLCameraMoveOptions = { animation: { duration: 500 } }): this {
|
|
4058
4082
|
const presence = this.getCollaborators().find((c) => c.userId === userId)
|
|
4059
4083
|
|
|
4060
4084
|
if (!presence) return this
|
|
@@ -4426,11 +4450,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4426
4450
|
*
|
|
4427
4451
|
* @public
|
|
4428
4452
|
*/
|
|
4429
|
-
startFollowingUser(userId:
|
|
4453
|
+
startFollowingUser(userId: TLUserId): this {
|
|
4430
4454
|
// if we were already following someone, stop following them
|
|
4431
4455
|
this.stopFollowingUser()
|
|
4432
4456
|
|
|
4433
|
-
const thisUserId = this.user.
|
|
4457
|
+
const thisUserId = this.user.getExternalId()
|
|
4434
4458
|
|
|
4435
4459
|
if (!thisUserId) {
|
|
4436
4460
|
console.warn('You should set the userId for the current instance before following a user')
|
|
@@ -10768,6 +10792,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10768
10792
|
/** @internal */
|
|
10769
10793
|
private _selectedShapeIdsAtPointerDown: TLShapeId[] = []
|
|
10770
10794
|
|
|
10795
|
+
/**
|
|
10796
|
+
* Whether `_selectedShapeIdsAtPointerDown` holds a pre-gesture selection
|
|
10797
|
+
* captured by a `pointer_down` (the touch path) that a following pinch
|
|
10798
|
+
* should restore. False when no pointer_down preceded the pinch (the
|
|
10799
|
+
* Safari trackpad path uses gesture events), in which case `pinch_start`
|
|
10800
|
+
* captures the live selection instead.
|
|
10801
|
+
* @internal
|
|
10802
|
+
*/
|
|
10803
|
+
private _didCaptureSelectionAtPointerDown = false
|
|
10804
|
+
|
|
10771
10805
|
/** @internal */
|
|
10772
10806
|
private _longPressTimeout = -1 as any
|
|
10773
10807
|
|
|
@@ -10933,16 +10967,28 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10933
10967
|
if (inputs.getIsPinching()) return
|
|
10934
10968
|
|
|
10935
10969
|
if (!inputs.getIsEditing()) {
|
|
10936
|
-
//
|
|
10937
|
-
//
|
|
10938
|
-
//
|
|
10939
|
-
|
|
10970
|
+
// If a pointer_down already captured the pre-gesture selection,
|
|
10971
|
+
// keep it: on touch, the first finger's pointer_down can change
|
|
10972
|
+
// the selection before the second finger starts the pinch, and we
|
|
10973
|
+
// want to restore the selection from before that change. When no
|
|
10974
|
+
// pointer_down preceded the pinch (Safari delivers trackpad pinches
|
|
10975
|
+
// as gesture events), capture the live selection now.
|
|
10976
|
+
if (!this._didCaptureSelectionAtPointerDown) {
|
|
10977
|
+
this._selectedShapeIdsAtPointerDown = [...pageState.selectedShapeIds]
|
|
10978
|
+
}
|
|
10940
10979
|
|
|
10941
10980
|
this._didPinch = true
|
|
10942
10981
|
|
|
10943
10982
|
inputs.setIsPinching(true)
|
|
10944
10983
|
|
|
10945
10984
|
this.interrupt()
|
|
10985
|
+
|
|
10986
|
+
// If the first finger changed the selection, roll it back now rather
|
|
10987
|
+
// than waiting for the pinch to end, so the pre-gesture selection is
|
|
10988
|
+
// what's shown during the pinch.
|
|
10989
|
+
if (this._didCaptureSelectionAtPointerDown) {
|
|
10990
|
+
this.setSelectedShapes(this._selectedShapeIdsAtPointerDown)
|
|
10991
|
+
}
|
|
10946
10992
|
}
|
|
10947
10993
|
|
|
10948
10994
|
this.emit('event', info)
|
|
@@ -10994,6 +11040,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10994
11040
|
const { _selectedShapeIdsAtPointerDown: shapesToReselect } = this
|
|
10995
11041
|
this.setSelectedShapes(this._selectedShapeIdsAtPointerDown)
|
|
10996
11042
|
this._selectedShapeIdsAtPointerDown = []
|
|
11043
|
+
this._didCaptureSelectionAtPointerDown = false
|
|
10997
11044
|
|
|
10998
11045
|
if (this._didPinch) {
|
|
10999
11046
|
this._didPinch = false
|
|
@@ -11122,8 +11169,15 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
11122
11169
|
}, this.options.longPressDurationMs)
|
|
11123
11170
|
}
|
|
11124
11171
|
|
|
11125
|
-
// Save the selected ids at
|
|
11126
|
-
|
|
11172
|
+
// Save the selected ids at the start of an interaction so a pinch can
|
|
11173
|
+
// restore the pre-gesture selection. Only capture on the first pointer:
|
|
11174
|
+
// on touch, the second finger's pointer_down arrives after the first
|
|
11175
|
+
// has already changed the selection, and we want the earlier snapshot.
|
|
11176
|
+
// Cleared on pointer_up / pinch_end.
|
|
11177
|
+
if (!this._didCaptureSelectionAtPointerDown) {
|
|
11178
|
+
this._selectedShapeIdsAtPointerDown = this.getSelectedShapeIds()
|
|
11179
|
+
this._didCaptureSelectionAtPointerDown = true
|
|
11180
|
+
}
|
|
11127
11181
|
|
|
11128
11182
|
// Firefox bug fix...
|
|
11129
11183
|
// If it's a left-mouse-click, we store the pointer id for later user
|
|
@@ -11248,6 +11302,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
11248
11302
|
if (this.inputs.getIsRightPointing() && !this.inputs.getIsPanning()) {
|
|
11249
11303
|
this.inputs.setIsRightPointing(false)
|
|
11250
11304
|
this._selectedShapeIdsAtPointerDown = []
|
|
11305
|
+
this._didCaptureSelectionAtPointerDown = false
|
|
11251
11306
|
break // fall through to state chart dispatch as right_click
|
|
11252
11307
|
}
|
|
11253
11308
|
|
|
@@ -11291,15 +11346,22 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
11291
11346
|
// Don't pass right-click panning events to the state chart
|
|
11292
11347
|
// as it causes unintended shape selection on release
|
|
11293
11348
|
if (slideSpeed > 0) {
|
|
11294
|
-
this.slideCamera({
|
|
11349
|
+
this.slideCamera({
|
|
11350
|
+
speed: slideSpeed,
|
|
11351
|
+
direction: { x: slideDirection.x, y: slideDirection.y, z: 0 },
|
|
11352
|
+
})
|
|
11295
11353
|
}
|
|
11296
11354
|
this._selectedShapeIdsAtPointerDown = []
|
|
11355
|
+
this._didCaptureSelectionAtPointerDown = false
|
|
11297
11356
|
return this
|
|
11298
11357
|
}
|
|
11299
11358
|
}
|
|
11300
11359
|
|
|
11301
11360
|
if (slideSpeed > 0) {
|
|
11302
|
-
this.slideCamera({
|
|
11361
|
+
this.slideCamera({
|
|
11362
|
+
speed: slideSpeed,
|
|
11363
|
+
direction: { x: slideDirection.x, y: slideDirection.y, z: 0 },
|
|
11364
|
+
})
|
|
11303
11365
|
}
|
|
11304
11366
|
} else {
|
|
11305
11367
|
if (info.button === STYLUS_ERASER_BUTTON) {
|
|
@@ -11312,6 +11374,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
11312
11374
|
// Clear the stashed selection so the next pinch captures fresh state.
|
|
11313
11375
|
// This fixes Safari pinch zoom restoring outdated selections.
|
|
11314
11376
|
this._selectedShapeIdsAtPointerDown = []
|
|
11377
|
+
this._didCaptureSelectionAtPointerDown = false
|
|
11315
11378
|
|
|
11316
11379
|
break
|
|
11317
11380
|
}
|
|
@@ -29,7 +29,7 @@ function fromScratch(bindingsQuery: Computed<TLBinding[], unknown>) {
|
|
|
29
29
|
return shapesToBindings
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export
|
|
32
|
+
export function bindingsIndex(editor: Editor): Computed<TLBindingsIndex> {
|
|
33
33
|
const { store } = editor
|
|
34
34
|
const bindingsHistory = store.query.filterHistory('binding')
|
|
35
35
|
const bindingsQuery = store.query.records('binding')
|
|
@@ -22,7 +22,7 @@ function fromScratch(
|
|
|
22
22
|
return result
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
export
|
|
25
|
+
export function parentsToChildren(store: TLStore) {
|
|
26
26
|
const shapeIdsQuery = store.query.ids<'shape'>('shape')
|
|
27
27
|
const shapeHistory = store.query.filterHistory('shape')
|
|
28
28
|
|
|
@@ -33,7 +33,7 @@ const isShapeInPage = (store: TLStore, pageId: TLPageId, shape: TLShape): boolea
|
|
|
33
33
|
* @param store - The tldraw store.
|
|
34
34
|
* @param getCurrentPageId - A function that returns the current page id.
|
|
35
35
|
*/
|
|
36
|
-
export
|
|
36
|
+
export function deriveShapeIdsInCurrentPage(store: TLStore, getCurrentPageId: () => TLPageId) {
|
|
37
37
|
const shapesIndex = store.query.ids('shape')
|
|
38
38
|
let lastPageId: null | TLPageId = null
|
|
39
39
|
function fromScratch() {
|
|
@@ -122,7 +122,7 @@ describe('ClickManager', () => {
|
|
|
122
122
|
expect(result.type).toBe('click')
|
|
123
123
|
expect(result.name).toBe('double_click')
|
|
124
124
|
expect(result.phase).toBe('down')
|
|
125
|
-
expect(clickManager.clickState).toBe('
|
|
125
|
+
expect(clickManager.clickState).toBe('pendingOverflow')
|
|
126
126
|
})
|
|
127
127
|
|
|
128
128
|
it('should generate double_click up event on pointer_up after double_click down', () => {
|
|
@@ -139,12 +139,34 @@ describe('ClickManager', () => {
|
|
|
139
139
|
expect(result.phase).toBe('up')
|
|
140
140
|
})
|
|
141
141
|
|
|
142
|
-
it('should dispatch double_click settle event after timeout in
|
|
142
|
+
it('should dispatch double_click settle-down event after timeout in pendingOverflow (pointer held)', () => {
|
|
143
143
|
const firstDown = createPointerEvent('pointer_down', { x: 100, y: 100 })
|
|
144
144
|
const secondDown = createPointerEvent('pointer_down', { x: 100, y: 100 })
|
|
145
145
|
|
|
146
146
|
clickManager.handlePointerEvent(firstDown)
|
|
147
147
|
clickManager.handlePointerEvent(secondDown)
|
|
148
|
+
// no pointer_up between or after — pointer is still down at settle time
|
|
149
|
+
|
|
150
|
+
vi.advanceTimersByTime(350)
|
|
151
|
+
|
|
152
|
+
expect(editor.dispatch).toHaveBeenCalledWith(
|
|
153
|
+
expect.objectContaining({
|
|
154
|
+
type: 'click',
|
|
155
|
+
name: 'double_click',
|
|
156
|
+
phase: 'settle-down',
|
|
157
|
+
})
|
|
158
|
+
)
|
|
159
|
+
expect(clickManager.clickState).toBe('idle')
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('should dispatch double_click settle-up event after timeout in pendingOverflow (pointer released)', () => {
|
|
163
|
+
const down = createPointerEvent('pointer_down', { x: 100, y: 100 })
|
|
164
|
+
const up = createPointerEvent('pointer_up', { x: 100, y: 100 })
|
|
165
|
+
|
|
166
|
+
clickManager.handlePointerEvent(down)
|
|
167
|
+
clickManager.handlePointerEvent(up)
|
|
168
|
+
clickManager.handlePointerEvent(down)
|
|
169
|
+
clickManager.handlePointerEvent(up)
|
|
148
170
|
|
|
149
171
|
vi.advanceTimersByTime(350)
|
|
150
172
|
|
|
@@ -152,124 +174,84 @@ describe('ClickManager', () => {
|
|
|
152
174
|
expect.objectContaining({
|
|
153
175
|
type: 'click',
|
|
154
176
|
name: 'double_click',
|
|
155
|
-
phase: 'settle',
|
|
177
|
+
phase: 'settle-up',
|
|
156
178
|
})
|
|
157
179
|
)
|
|
158
180
|
expect(clickManager.clickState).toBe('idle')
|
|
159
181
|
})
|
|
160
182
|
})
|
|
161
183
|
|
|
162
|
-
describe('
|
|
163
|
-
it('should
|
|
184
|
+
describe('overflow click handling', () => {
|
|
185
|
+
it('should enter overflow on the third pointer_down without emitting another click', () => {
|
|
164
186
|
const firstDown = createPointerEvent('pointer_down', { x: 100, y: 100 })
|
|
165
187
|
const secondDown = createPointerEvent('pointer_down', { x: 100, y: 100 })
|
|
166
188
|
const thirdDown = createPointerEvent('pointer_down', { x: 100, y: 100 })
|
|
167
189
|
|
|
168
190
|
clickManager.handlePointerEvent(firstDown)
|
|
169
191
|
clickManager.handlePointerEvent(secondDown)
|
|
170
|
-
const result = clickManager.handlePointerEvent(thirdDown)
|
|
192
|
+
const result = clickManager.handlePointerEvent(thirdDown)
|
|
171
193
|
|
|
172
|
-
expect(result
|
|
173
|
-
expect(
|
|
174
|
-
expect(result.phase).toBe('down')
|
|
175
|
-
expect(clickManager.clickState).toBe('pendingQuadruple')
|
|
194
|
+
expect(result).toBe(thirdDown)
|
|
195
|
+
expect(clickManager.clickState).toBe('overflow')
|
|
176
196
|
})
|
|
177
197
|
|
|
178
|
-
it('should
|
|
198
|
+
it('should keep overflow active on further clicks', () => {
|
|
179
199
|
const pointerDown = createPointerEvent('pointer_down', { x: 100, y: 100 })
|
|
180
200
|
|
|
181
201
|
clickManager.handlePointerEvent(pointerDown) // first
|
|
182
202
|
clickManager.handlePointerEvent(pointerDown) // second (double_click)
|
|
183
|
-
clickManager.handlePointerEvent(pointerDown) // third (
|
|
184
|
-
const result = clickManager.handlePointerEvent(pointerDown)
|
|
185
|
-
|
|
186
|
-
expect(result.type).toBe('click')
|
|
187
|
-
expect(result.name).toBe('quadruple_click')
|
|
188
|
-
expect(result.phase).toBe('down')
|
|
189
|
-
expect(clickManager.clickState).toBe('pendingOverflow')
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
it('should handle overflow state after quadruple click', () => {
|
|
193
|
-
const pointerDown = createPointerEvent('pointer_down', { x: 100, y: 100 })
|
|
194
|
-
|
|
195
|
-
clickManager.handlePointerEvent(pointerDown) // first
|
|
196
|
-
clickManager.handlePointerEvent(pointerDown) // second
|
|
197
|
-
clickManager.handlePointerEvent(pointerDown) // third
|
|
198
|
-
clickManager.handlePointerEvent(pointerDown) // fourth
|
|
199
|
-
const result = clickManager.handlePointerEvent(pointerDown) // fifth
|
|
203
|
+
clickManager.handlePointerEvent(pointerDown) // third (overflow)
|
|
204
|
+
const result = clickManager.handlePointerEvent(pointerDown) // fourth
|
|
200
205
|
|
|
201
206
|
expect(result).toBe(pointerDown)
|
|
202
207
|
expect(clickManager.clickState).toBe('overflow')
|
|
203
208
|
})
|
|
204
209
|
|
|
205
|
-
it('should
|
|
206
|
-
const pointerDown = createPointerEvent('pointer_down', { x: 100, y: 100 })
|
|
207
|
-
const pointerUp = createPointerEvent('pointer_up', { x: 100, y: 100 })
|
|
208
|
-
|
|
209
|
-
clickManager.handlePointerEvent(pointerDown) // first
|
|
210
|
-
clickManager.handlePointerEvent(pointerDown) // second
|
|
211
|
-
clickManager.handlePointerEvent(pointerDown) // third
|
|
212
|
-
const result = clickManager.handlePointerEvent(pointerUp) as TLClickEventInfo
|
|
213
|
-
|
|
214
|
-
expect(result.type).toBe('click')
|
|
215
|
-
expect(result.name).toBe('triple_click')
|
|
216
|
-
expect(result.phase).toBe('up')
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
it('should generate quadruple_click up event on pointer_up after quadruple_click down', () => {
|
|
210
|
+
it('should not emit double_click up events while in overflow', () => {
|
|
220
211
|
const pointerDown = createPointerEvent('pointer_down', { x: 100, y: 100 })
|
|
221
212
|
const pointerUp = createPointerEvent('pointer_up', { x: 100, y: 100 })
|
|
222
213
|
|
|
223
214
|
clickManager.handlePointerEvent(pointerDown) // first
|
|
224
215
|
clickManager.handlePointerEvent(pointerDown) // second
|
|
225
216
|
clickManager.handlePointerEvent(pointerDown) // third
|
|
226
|
-
clickManager.handlePointerEvent(
|
|
227
|
-
const result = clickManager.handlePointerEvent(pointerUp) as TLClickEventInfo
|
|
217
|
+
const result = clickManager.handlePointerEvent(pointerUp)
|
|
228
218
|
|
|
229
|
-
expect(result
|
|
230
|
-
expect(
|
|
231
|
-
expect(result.phase).toBe('up')
|
|
219
|
+
expect(result).toBe(pointerUp)
|
|
220
|
+
expect(clickManager.clickState).toBe('overflow')
|
|
232
221
|
})
|
|
233
|
-
})
|
|
234
222
|
|
|
235
|
-
|
|
236
|
-
it('should dispatch triple_click settle event after timeout in pendingQuadruple', () => {
|
|
223
|
+
it('should return to idle after overflow timeout without dispatching a settle event', () => {
|
|
237
224
|
const pointerDown = createPointerEvent('pointer_down', { x: 100, y: 100 })
|
|
238
225
|
|
|
239
226
|
clickManager.handlePointerEvent(pointerDown) // first
|
|
240
227
|
clickManager.handlePointerEvent(pointerDown) // second
|
|
241
|
-
clickManager.handlePointerEvent(pointerDown) // third
|
|
228
|
+
clickManager.handlePointerEvent(pointerDown) // third -> overflow
|
|
242
229
|
|
|
243
230
|
vi.advanceTimersByTime(350)
|
|
244
231
|
|
|
245
|
-
expect(editor.dispatch).
|
|
246
|
-
expect.objectContaining({
|
|
247
|
-
type: 'click',
|
|
248
|
-
name: 'triple_click',
|
|
249
|
-
phase: 'settle',
|
|
250
|
-
})
|
|
251
|
-
)
|
|
232
|
+
expect(editor.dispatch).not.toHaveBeenCalled()
|
|
252
233
|
expect(clickManager.clickState).toBe('idle')
|
|
253
234
|
})
|
|
235
|
+
})
|
|
254
236
|
|
|
255
|
-
|
|
256
|
-
|
|
237
|
+
describe('timeout behavior and settle events', () => {
|
|
238
|
+
it('should track press/release state across the pending window (settle-down then release → settle-up)', () => {
|
|
239
|
+
const down = createPointerEvent('pointer_down', { x: 100, y: 100 })
|
|
240
|
+
const up = createPointerEvent('pointer_up', { x: 100, y: 100 })
|
|
257
241
|
|
|
258
|
-
clickManager.handlePointerEvent(
|
|
259
|
-
clickManager.handlePointerEvent(
|
|
260
|
-
clickManager.handlePointerEvent(
|
|
261
|
-
clickManager.handlePointerEvent(
|
|
242
|
+
clickManager.handlePointerEvent(down)
|
|
243
|
+
clickManager.handlePointerEvent(up)
|
|
244
|
+
clickManager.handlePointerEvent(down) // second press — pointer is down...
|
|
245
|
+
clickManager.handlePointerEvent(up) // ...but released before timeout
|
|
262
246
|
|
|
263
247
|
vi.advanceTimersByTime(350)
|
|
264
248
|
|
|
265
249
|
expect(editor.dispatch).toHaveBeenCalledWith(
|
|
266
250
|
expect.objectContaining({
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
phase: 'settle',
|
|
251
|
+
name: 'double_click',
|
|
252
|
+
phase: 'settle-up',
|
|
270
253
|
})
|
|
271
254
|
)
|
|
272
|
-
expect(clickManager.clickState).toBe('idle')
|
|
273
255
|
})
|
|
274
256
|
|
|
275
257
|
it('should use different timeout durations for different states', () => {
|
|
@@ -316,7 +298,7 @@ describe('ClickManager', () => {
|
|
|
316
298
|
|
|
317
299
|
expect(result.type).toBe('click')
|
|
318
300
|
expect(result.name).toBe('double_click')
|
|
319
|
-
expect(clickManager.clickState).toBe('
|
|
301
|
+
expect(clickManager.clickState).toBe('pendingOverflow')
|
|
320
302
|
})
|
|
321
303
|
})
|
|
322
304
|
|
|
@@ -396,7 +378,7 @@ describe('ClickManager', () => {
|
|
|
396
378
|
|
|
397
379
|
clickManager.handlePointerEvent(pointerDown)
|
|
398
380
|
clickManager.handlePointerEvent(pointerDown) // double click
|
|
399
|
-
expect(clickManager.clickState).toBe('
|
|
381
|
+
expect(clickManager.clickState).toBe('pendingOverflow')
|
|
400
382
|
|
|
401
383
|
clickManager.cancelDoubleClickTimeout()
|
|
402
384
|
|
|
@@ -416,9 +398,7 @@ describe('ClickManager', () => {
|
|
|
416
398
|
// Get to overflow state
|
|
417
399
|
clickManager.handlePointerEvent(pointerDown) // 1
|
|
418
400
|
clickManager.handlePointerEvent(pointerDown) // 2
|
|
419
|
-
clickManager.handlePointerEvent(pointerDown) // 3
|
|
420
|
-
clickManager.handlePointerEvent(pointerDown) // 4
|
|
421
|
-
clickManager.handlePointerEvent(pointerDown) // 5 -> overflow
|
|
401
|
+
clickManager.handlePointerEvent(pointerDown) // 3 -> overflow
|
|
422
402
|
|
|
423
403
|
expect(clickManager.clickState).toBe('overflow')
|
|
424
404
|
|
|
@@ -4,13 +4,7 @@ import type { Editor } from '../../Editor'
|
|
|
4
4
|
import { TLClickEventInfo, TLPointerEventInfo } from '../../types/event-types'
|
|
5
5
|
|
|
6
6
|
/** @public */
|
|
7
|
-
export type TLClickState =
|
|
8
|
-
| 'idle'
|
|
9
|
-
| 'pendingDouble'
|
|
10
|
-
| 'pendingTriple'
|
|
11
|
-
| 'pendingQuadruple'
|
|
12
|
-
| 'pendingOverflow'
|
|
13
|
-
| 'overflow'
|
|
7
|
+
export type TLClickState = 'idle' | 'pendingDouble' | 'pendingOverflow' | 'overflow'
|
|
14
8
|
|
|
15
9
|
const MAX_CLICK_DISTANCE = 40
|
|
16
10
|
|
|
@@ -26,6 +20,8 @@ export class ClickManager {
|
|
|
26
20
|
|
|
27
21
|
private _previousScreenPoint?: Vec
|
|
28
22
|
|
|
23
|
+
private _isPressingWhilePending = false
|
|
24
|
+
|
|
29
25
|
@bind
|
|
30
26
|
_getClickTimeout(state: TLClickState, id = uniqueId()) {
|
|
31
27
|
this._clickId = id
|
|
@@ -34,30 +30,12 @@ export class ClickManager {
|
|
|
34
30
|
() => {
|
|
35
31
|
if (this._clickState === state && this._clickId === id) {
|
|
36
32
|
switch (this._clickState) {
|
|
37
|
-
case 'pendingTriple': {
|
|
38
|
-
this.editor.dispatch({
|
|
39
|
-
...this.lastPointerInfo,
|
|
40
|
-
type: 'click',
|
|
41
|
-
name: 'double_click',
|
|
42
|
-
phase: 'settle',
|
|
43
|
-
})
|
|
44
|
-
break
|
|
45
|
-
}
|
|
46
|
-
case 'pendingQuadruple': {
|
|
47
|
-
this.editor.dispatch({
|
|
48
|
-
...this.lastPointerInfo,
|
|
49
|
-
type: 'click',
|
|
50
|
-
name: 'triple_click',
|
|
51
|
-
phase: 'settle',
|
|
52
|
-
})
|
|
53
|
-
break
|
|
54
|
-
}
|
|
55
33
|
case 'pendingOverflow': {
|
|
56
34
|
this.editor.dispatch({
|
|
57
35
|
...this.lastPointerInfo,
|
|
58
36
|
type: 'click',
|
|
59
|
-
name: '
|
|
60
|
-
phase: 'settle',
|
|
37
|
+
name: 'double_click',
|
|
38
|
+
phase: this._isPressingWhilePending ? 'settle-down' : 'settle-up',
|
|
61
39
|
})
|
|
62
40
|
break
|
|
63
41
|
}
|
|
@@ -100,6 +78,8 @@ export class ClickManager {
|
|
|
100
78
|
if (!this._clickState) return info
|
|
101
79
|
this._clickScreenPoint = Vec.From(info.point)
|
|
102
80
|
|
|
81
|
+
this._isPressingWhilePending = true
|
|
82
|
+
|
|
103
83
|
if (
|
|
104
84
|
this._previousScreenPoint &&
|
|
105
85
|
Vec.Dist2(this._previousScreenPoint, this._clickScreenPoint) > MAX_CLICK_DISTANCE ** 2
|
|
@@ -113,32 +93,12 @@ export class ClickManager {
|
|
|
113
93
|
|
|
114
94
|
switch (this._clickState) {
|
|
115
95
|
case 'pendingDouble': {
|
|
116
|
-
this._clickState = 'pendingTriple'
|
|
117
|
-
this._clickTimeout = this._getClickTimeout(this._clickState)
|
|
118
|
-
return {
|
|
119
|
-
...info,
|
|
120
|
-
type: 'click',
|
|
121
|
-
name: 'double_click',
|
|
122
|
-
phase: 'down',
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
case 'pendingTriple': {
|
|
126
|
-
this._clickState = 'pendingQuadruple'
|
|
127
|
-
this._clickTimeout = this._getClickTimeout(this._clickState)
|
|
128
|
-
return {
|
|
129
|
-
...info,
|
|
130
|
-
type: 'click',
|
|
131
|
-
name: 'triple_click',
|
|
132
|
-
phase: 'down',
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
case 'pendingQuadruple': {
|
|
136
96
|
this._clickState = 'pendingOverflow'
|
|
137
97
|
this._clickTimeout = this._getClickTimeout(this._clickState)
|
|
138
98
|
return {
|
|
139
99
|
...info,
|
|
140
100
|
type: 'click',
|
|
141
|
-
name: '
|
|
101
|
+
name: 'double_click',
|
|
142
102
|
phase: 'down',
|
|
143
103
|
}
|
|
144
104
|
}
|
|
@@ -159,30 +119,17 @@ export class ClickManager {
|
|
|
159
119
|
}
|
|
160
120
|
case 'pointer_up': {
|
|
161
121
|
if (!this._clickState) return info
|
|
122
|
+
|
|
162
123
|
this._clickScreenPoint = Vec.From(info.point)
|
|
163
124
|
|
|
125
|
+
this._isPressingWhilePending = false
|
|
126
|
+
|
|
164
127
|
switch (this._clickState) {
|
|
165
|
-
case 'pendingTriple': {
|
|
166
|
-
return {
|
|
167
|
-
...this.lastPointerInfo,
|
|
168
|
-
type: 'click',
|
|
169
|
-
name: 'double_click',
|
|
170
|
-
phase: 'up',
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
case 'pendingQuadruple': {
|
|
174
|
-
return {
|
|
175
|
-
...this.lastPointerInfo,
|
|
176
|
-
type: 'click',
|
|
177
|
-
name: 'triple_click',
|
|
178
|
-
phase: 'up',
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
128
|
case 'pendingOverflow': {
|
|
182
129
|
return {
|
|
183
130
|
...this.lastPointerInfo,
|
|
184
131
|
type: 'click',
|
|
185
|
-
name: '
|
|
132
|
+
name: 'double_click',
|
|
186
133
|
phase: 'up',
|
|
187
134
|
}
|
|
188
135
|
}
|
|
@@ -219,5 +166,8 @@ export class ClickManager {
|
|
|
219
166
|
cancelDoubleClickTimeout() {
|
|
220
167
|
this._clickTimeout = clearTimeout(this._clickTimeout)
|
|
221
168
|
this._clickState = 'idle'
|
|
169
|
+
// when a double click is cancelled, we are no longer pending any further
|
|
170
|
+
// clicks, so we set this to false even if the user is still pressing
|
|
171
|
+
this._isPressingWhilePending = false
|
|
222
172
|
}
|
|
223
173
|
}
|