@tldraw/driver 5.3.2 → 5.4.0-canary.02cd0bd3b597
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 +14 -18
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/Driver.js +61 -93
- package/dist-cjs/lib/Driver.js.map +2 -2
- package/dist-esm/index.d.mts +14 -18
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/Driver.mjs +61 -93
- package/dist-esm/lib/Driver.mjs.map +2 -2
- package/package.json +3 -3
- package/src/lib/Driver.ts +75 -115
package/src/lib/Driver.ts
CHANGED
|
@@ -28,6 +28,19 @@ export type PointerEventInit = Partial<TLPointerEventInfo> | TLShapeId
|
|
|
28
28
|
/** Modifier keys for events. @public */
|
|
29
29
|
export type EventModifiers = Partial<Pick<TLPointerEventInfo, 'shiftKey' | 'ctrlKey' | 'altKey'>>
|
|
30
30
|
|
|
31
|
+
const KEY_CODES: Record<string, string> = {
|
|
32
|
+
Shift: 'ShiftLeft',
|
|
33
|
+
Alt: 'AltLeft',
|
|
34
|
+
Control: 'CtrlLeft',
|
|
35
|
+
Meta: 'MetaLeft',
|
|
36
|
+
' ': 'Space',
|
|
37
|
+
Enter: 'Enter',
|
|
38
|
+
ArrowRight: 'ArrowRight',
|
|
39
|
+
ArrowLeft: 'ArrowLeft',
|
|
40
|
+
ArrowUp: 'ArrowUp',
|
|
41
|
+
ArrowDown: 'ArrowDown',
|
|
42
|
+
}
|
|
43
|
+
|
|
31
44
|
/**
|
|
32
45
|
* Driver wraps an Editor instance and provides an imperative API for driving it
|
|
33
46
|
* programmatically. Useful for scripting, automation, REPL usage, and testing.
|
|
@@ -38,7 +51,8 @@ export type EventModifiers = Partial<Pick<TLPointerEventInfo, 'shiftKey' | 'ctrl
|
|
|
38
51
|
*/
|
|
39
52
|
export class Driver {
|
|
40
53
|
/** The underlying Editor instance. */
|
|
41
|
-
private _cleanup: (() => void) | null
|
|
54
|
+
private _cleanup: (() => void) | null
|
|
55
|
+
private _lastCreatedShapes: TLShape[] = []
|
|
42
56
|
|
|
43
57
|
constructor(public readonly editor: Editor) {
|
|
44
58
|
this._cleanup = this.editor.sideEffects.registerAfterCreateHandler('shape', (record) => {
|
|
@@ -58,8 +72,6 @@ export class Driver {
|
|
|
58
72
|
/** Local clipboard content. Used by copy, cut, and paste. */
|
|
59
73
|
clipboard: TLContent | null = null
|
|
60
74
|
|
|
61
|
-
private _lastCreatedShapes: TLShape[] = []
|
|
62
|
-
|
|
63
75
|
/**
|
|
64
76
|
* Get the last created shapes.
|
|
65
77
|
* @param count - The number of shapes to get.
|
|
@@ -116,10 +128,7 @@ export class Driver {
|
|
|
116
128
|
*/
|
|
117
129
|
cut(ids = this.editor.getSelectedShapeIds()) {
|
|
118
130
|
if (ids.length > 0) {
|
|
119
|
-
|
|
120
|
-
if (content) {
|
|
121
|
-
this.clipboard = content
|
|
122
|
-
}
|
|
131
|
+
this.copy(ids)
|
|
123
132
|
this.editor.deleteShapes(ids)
|
|
124
133
|
}
|
|
125
134
|
return this
|
|
@@ -174,10 +183,7 @@ export class Driver {
|
|
|
174
183
|
*/
|
|
175
184
|
getPageRotationById(id: TLShapeId): number {
|
|
176
185
|
const pageTransform = this.editor.getShapePageTransform(id)
|
|
177
|
-
|
|
178
|
-
return Mat.Decompose(pageTransform).rotation
|
|
179
|
-
}
|
|
180
|
-
return 0
|
|
186
|
+
return pageTransform ? pageTransform.rotation() : 0
|
|
181
187
|
}
|
|
182
188
|
|
|
183
189
|
/**
|
|
@@ -199,6 +205,17 @@ export class Driver {
|
|
|
199
205
|
|
|
200
206
|
/* --------------- Event building --------------- */
|
|
201
207
|
|
|
208
|
+
private getModifierKeys() {
|
|
209
|
+
const { inputs } = this.editor
|
|
210
|
+
return {
|
|
211
|
+
shiftKey: inputs.getShiftKey(),
|
|
212
|
+
ctrlKey: inputs.getCtrlKey(),
|
|
213
|
+
altKey: inputs.getAltKey(),
|
|
214
|
+
metaKey: inputs.getMetaKey(),
|
|
215
|
+
accelKey: isAccelKey(inputs),
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
202
219
|
/**
|
|
203
220
|
* Builds a TLPointerEventInfo object for input simulation.
|
|
204
221
|
* @param x - Screen x coordinate. Defaults to current pointer.
|
|
@@ -221,10 +238,7 @@ export class Driver {
|
|
|
221
238
|
name: 'pointer_down',
|
|
222
239
|
type: 'pointer',
|
|
223
240
|
pointerId: 1,
|
|
224
|
-
|
|
225
|
-
ctrlKey: this.editor.inputs.getCtrlKey(),
|
|
226
|
-
altKey: this.editor.inputs.getAltKey(),
|
|
227
|
-
metaKey: this.editor.inputs.getMetaKey(),
|
|
241
|
+
...this.getModifierKeys(),
|
|
228
242
|
accelKey: isAccelKey({ ...this.editor.inputs.toJson(), ...modifiers }),
|
|
229
243
|
point: { x, y, z: null },
|
|
230
244
|
button: 0,
|
|
@@ -268,26 +282,9 @@ export class Driver {
|
|
|
268
282
|
}
|
|
269
283
|
return {
|
|
270
284
|
...flags,
|
|
271
|
-
accelKey: flags.accelKey ?? (
|
|
285
|
+
accelKey: flags.accelKey ?? isAccelKey(flags),
|
|
272
286
|
name,
|
|
273
|
-
code:
|
|
274
|
-
key === 'Shift'
|
|
275
|
-
? 'ShiftLeft'
|
|
276
|
-
: key === 'Alt'
|
|
277
|
-
? 'AltLeft'
|
|
278
|
-
: key === 'Control'
|
|
279
|
-
? 'CtrlLeft'
|
|
280
|
-
: key === 'Meta'
|
|
281
|
-
? 'MetaLeft'
|
|
282
|
-
: key === ' '
|
|
283
|
-
? 'Space'
|
|
284
|
-
: key === 'Enter' ||
|
|
285
|
-
key === 'ArrowRight' ||
|
|
286
|
-
key === 'ArrowLeft' ||
|
|
287
|
-
key === 'ArrowUp' ||
|
|
288
|
-
key === 'ArrowDown'
|
|
289
|
-
? key
|
|
290
|
-
: 'Key' + key[0].toUpperCase() + key.slice(1),
|
|
287
|
+
code: KEY_CODES[key] ?? 'Key' + key[0].toUpperCase() + key.slice(1),
|
|
291
288
|
type: 'keyboard',
|
|
292
289
|
key,
|
|
293
290
|
}
|
|
@@ -428,8 +425,7 @@ export class Driver {
|
|
|
428
425
|
options?: PointerEventInit,
|
|
429
426
|
modifiers?: EventModifiers
|
|
430
427
|
) {
|
|
431
|
-
this.
|
|
432
|
-
this.pointerUp(x, y, options, modifiers)
|
|
428
|
+
this.click(x, y, options, modifiers)
|
|
433
429
|
this.editor.dispatch({
|
|
434
430
|
...this.getPointerEventInfo(x, y, options, modifiers),
|
|
435
431
|
type: 'click',
|
|
@@ -463,7 +459,7 @@ export class Driver {
|
|
|
463
459
|
* @param options - Partial keyboard event overrides.
|
|
464
460
|
*/
|
|
465
461
|
keyDown(key: string, options = {} as Partial<Omit<TLKeyboardEventInfo, 'key'>>) {
|
|
466
|
-
this.editor.dispatch(
|
|
462
|
+
this.editor.dispatch(this.getKeyboardEventInfo(key, 'key_down', options))
|
|
467
463
|
this.forceTick()
|
|
468
464
|
return this
|
|
469
465
|
}
|
|
@@ -474,7 +470,7 @@ export class Driver {
|
|
|
474
470
|
* @param options - Partial keyboard event overrides.
|
|
475
471
|
*/
|
|
476
472
|
keyRepeat(key: string, options = {} as Partial<Omit<TLKeyboardEventInfo, 'key'>>) {
|
|
477
|
-
this.editor.dispatch(
|
|
473
|
+
this.editor.dispatch(this.getKeyboardEventInfo(key, 'key_repeat', options))
|
|
478
474
|
this.forceTick()
|
|
479
475
|
return this
|
|
480
476
|
}
|
|
@@ -485,15 +481,15 @@ export class Driver {
|
|
|
485
481
|
* @param options - Partial keyboard event overrides.
|
|
486
482
|
*/
|
|
487
483
|
keyUp(key: string, options = {} as Partial<Omit<TLKeyboardEventInfo, 'key'>>) {
|
|
488
|
-
this.editor.dispatch(
|
|
489
|
-
|
|
484
|
+
this.editor.dispatch(
|
|
485
|
+
this.getKeyboardEventInfo(key, 'key_up', {
|
|
490
486
|
shiftKey: this.editor.inputs.getShiftKey() && key !== 'Shift',
|
|
491
487
|
ctrlKey: this.editor.inputs.getCtrlKey() && !(key === 'Control' || key === 'Meta'),
|
|
492
488
|
altKey: this.editor.inputs.getAltKey() && key !== 'Alt',
|
|
493
489
|
metaKey: this.editor.inputs.getMetaKey() && key !== 'Meta',
|
|
494
490
|
...options,
|
|
495
|
-
})
|
|
496
|
-
|
|
491
|
+
})
|
|
492
|
+
)
|
|
497
493
|
this.forceTick()
|
|
498
494
|
return this
|
|
499
495
|
}
|
|
@@ -510,11 +506,7 @@ export class Driver {
|
|
|
510
506
|
type: 'wheel',
|
|
511
507
|
name: 'wheel',
|
|
512
508
|
point: new Vec(currentScreenPoint.x, currentScreenPoint.y),
|
|
513
|
-
|
|
514
|
-
ctrlKey: this.editor.inputs.getCtrlKey(),
|
|
515
|
-
altKey: this.editor.inputs.getAltKey(),
|
|
516
|
-
metaKey: this.editor.inputs.getMetaKey(),
|
|
517
|
-
accelKey: isAccelKey(this.editor.inputs),
|
|
509
|
+
...this.getModifierKeys(),
|
|
518
510
|
...options,
|
|
519
511
|
delta: { x: dx, y: dy },
|
|
520
512
|
})
|
|
@@ -537,6 +529,26 @@ export class Driver {
|
|
|
537
529
|
return this
|
|
538
530
|
}
|
|
539
531
|
|
|
532
|
+
private pinch(
|
|
533
|
+
name: TLPinchEventInfo['name'],
|
|
534
|
+
x: number,
|
|
535
|
+
y: number,
|
|
536
|
+
z: number,
|
|
537
|
+
dx: number,
|
|
538
|
+
dy: number,
|
|
539
|
+
dz: number,
|
|
540
|
+
options: Partial<Omit<TLPinchEventInfo, 'point' | 'delta' | 'offset'>>
|
|
541
|
+
) {
|
|
542
|
+
this.editor.dispatch({
|
|
543
|
+
type: 'pinch',
|
|
544
|
+
name,
|
|
545
|
+
...this.getModifierKeys(),
|
|
546
|
+
...options,
|
|
547
|
+
point: { x, y, z },
|
|
548
|
+
delta: { x: dx, y: dy, z: dz },
|
|
549
|
+
})
|
|
550
|
+
}
|
|
551
|
+
|
|
540
552
|
/**
|
|
541
553
|
* Dispatches a pinch start event.
|
|
542
554
|
* @param x - Screen x coordinate. Defaults to current pointer.
|
|
@@ -556,18 +568,7 @@ export class Driver {
|
|
|
556
568
|
dz: number,
|
|
557
569
|
options = {} as Partial<Omit<TLPinchEventInfo, 'point' | 'delta' | 'offset'>>
|
|
558
570
|
) {
|
|
559
|
-
this.
|
|
560
|
-
type: 'pinch',
|
|
561
|
-
name: 'pinch_start',
|
|
562
|
-
shiftKey: this.editor.inputs.getShiftKey(),
|
|
563
|
-
ctrlKey: this.editor.inputs.getCtrlKey(),
|
|
564
|
-
altKey: this.editor.inputs.getAltKey(),
|
|
565
|
-
metaKey: this.editor.inputs.getMetaKey(),
|
|
566
|
-
accelKey: isAccelKey(this.editor.inputs),
|
|
567
|
-
...options,
|
|
568
|
-
point: { x, y, z },
|
|
569
|
-
delta: { x: dx, y: dy, z: dz },
|
|
570
|
-
})
|
|
571
|
+
this.pinch('pinch_start', x, y, z, dx, dy, dz, options)
|
|
571
572
|
this.forceTick()
|
|
572
573
|
return this
|
|
573
574
|
}
|
|
@@ -591,18 +592,7 @@ export class Driver {
|
|
|
591
592
|
dz: number,
|
|
592
593
|
options = {} as Partial<Omit<TLPinchEventInfo, 'point' | 'delta' | 'offset'>>
|
|
593
594
|
) {
|
|
594
|
-
this.
|
|
595
|
-
type: 'pinch',
|
|
596
|
-
name: 'pinch',
|
|
597
|
-
shiftKey: this.editor.inputs.getShiftKey(),
|
|
598
|
-
ctrlKey: this.editor.inputs.getCtrlKey(),
|
|
599
|
-
altKey: this.editor.inputs.getAltKey(),
|
|
600
|
-
metaKey: this.editor.inputs.getMetaKey(),
|
|
601
|
-
accelKey: isAccelKey(this.editor.inputs),
|
|
602
|
-
...options,
|
|
603
|
-
point: { x, y, z },
|
|
604
|
-
delta: { x: dx, y: dy, z: dz },
|
|
605
|
-
})
|
|
595
|
+
this.pinch('pinch', x, y, z, dx, dy, dz, options)
|
|
606
596
|
return this
|
|
607
597
|
}
|
|
608
598
|
|
|
@@ -625,33 +615,13 @@ export class Driver {
|
|
|
625
615
|
dz: number,
|
|
626
616
|
options = {} as Partial<Omit<TLPinchEventInfo, 'point' | 'delta' | 'offset'>>
|
|
627
617
|
) {
|
|
628
|
-
this.
|
|
629
|
-
type: 'pinch',
|
|
630
|
-
name: 'pinch_end',
|
|
631
|
-
shiftKey: this.editor.inputs.getShiftKey(),
|
|
632
|
-
ctrlKey: this.editor.inputs.getCtrlKey(),
|
|
633
|
-
altKey: this.editor.inputs.getAltKey(),
|
|
634
|
-
metaKey: this.editor.inputs.getMetaKey(),
|
|
635
|
-
accelKey: isAccelKey(this.editor.inputs),
|
|
636
|
-
...options,
|
|
637
|
-
point: { x, y, z },
|
|
638
|
-
delta: { x: dx, y: dy, z: dz },
|
|
639
|
-
})
|
|
618
|
+
this.pinch('pinch_end', x, y, z, dx, dy, dz, options)
|
|
640
619
|
this.forceTick()
|
|
641
620
|
return this
|
|
642
621
|
}
|
|
643
622
|
|
|
644
623
|
/* --------------- Interaction helpers --------------- */
|
|
645
624
|
|
|
646
|
-
/**
|
|
647
|
-
* Converts a point from page coordinates to screen coordinates.
|
|
648
|
-
* Pointer events operate in screen space, so page-space points must be
|
|
649
|
-
* converted before being passed to pointerDown/Move/Up.
|
|
650
|
-
*/
|
|
651
|
-
private pageToScreen(point: VecLike) {
|
|
652
|
-
return this.editor.pageToScreen(point)
|
|
653
|
-
}
|
|
654
|
-
|
|
655
625
|
/**
|
|
656
626
|
* Simulates rotating the current selection by the given angle in radians via the rotation handle.
|
|
657
627
|
* @param angleRadians - Rotation angle in radians.
|
|
@@ -668,19 +638,16 @@ export class Driver {
|
|
|
668
638
|
|
|
669
639
|
this.editor.setCurrentTool('select')
|
|
670
640
|
|
|
671
|
-
const
|
|
672
|
-
|
|
641
|
+
const bounds = this.editor.getSelectionRotatedPageBounds()!
|
|
642
|
+
const handlePoint = bounds
|
|
673
643
|
.getHandlePoint(ROTATE_CORNER_TO_SELECTION_CORNER[handle])
|
|
674
644
|
.clone()
|
|
675
|
-
.rotWith(
|
|
676
|
-
this.editor.getSelectionRotatedPageBounds()!.point,
|
|
677
|
-
this.editor.getSelectionRotation()
|
|
678
|
-
)
|
|
645
|
+
.rotWith(bounds.point, this.editor.getSelectionRotation())
|
|
679
646
|
|
|
680
647
|
const targetHandlePoint = Vec.RotWith(handlePoint, this.getSelectionPageCenter()!, angleRadians)
|
|
681
648
|
|
|
682
|
-
const screenHandle = this.pageToScreen(handlePoint)
|
|
683
|
-
const screenTarget = this.pageToScreen(targetHandlePoint)
|
|
649
|
+
const screenHandle = this.editor.pageToScreen(handlePoint)
|
|
650
|
+
const screenTarget = this.editor.pageToScreen(targetHandlePoint)
|
|
684
651
|
|
|
685
652
|
this.pointerDown(screenHandle.x, screenHandle.y, { target: 'selection', handle })
|
|
686
653
|
this.pointerMove(screenTarget.x, screenTarget.y, { shiftKey })
|
|
@@ -701,18 +668,18 @@ export class Driver {
|
|
|
701
668
|
this.editor.setCurrentTool('select')
|
|
702
669
|
|
|
703
670
|
const center = this.getSelectionPageCenter()!
|
|
704
|
-
const screenCenter = this.pageToScreen(center)
|
|
671
|
+
const screenCenter = this.editor.pageToScreen(center)
|
|
705
672
|
|
|
706
673
|
this.pointerDown(screenCenter.x, screenCenter.y, this.editor.getSelectedShapeIds()[0])
|
|
707
674
|
const numSteps = 10
|
|
708
675
|
for (let i = 1; i < numSteps; i++) {
|
|
709
|
-
const p = this.pageToScreen({
|
|
676
|
+
const p = this.editor.pageToScreen({
|
|
710
677
|
x: center.x + (i * dx) / numSteps,
|
|
711
678
|
y: center.y + (i * dy) / numSteps,
|
|
712
679
|
})
|
|
713
680
|
this.pointerMove(p.x, p.y, options)
|
|
714
681
|
}
|
|
715
|
-
const endScreen = this.pageToScreen({ x: center.x + dx, y: center.y + dy })
|
|
682
|
+
const endScreen = this.editor.pageToScreen({ x: center.x + dx, y: center.y + dy })
|
|
716
683
|
this.pointerUp(endScreen.x, endScreen.y, options)
|
|
717
684
|
return this
|
|
718
685
|
}
|
|
@@ -734,6 +701,7 @@ export class Driver {
|
|
|
734
701
|
}
|
|
735
702
|
this.editor.setCurrentTool('select')
|
|
736
703
|
const bounds = this.editor.getSelectionRotatedPageBounds()!
|
|
704
|
+
const rotation = this.editor.getSelectionRotation()
|
|
737
705
|
const preRotationHandlePoint = bounds.getHandlePoint(handle)
|
|
738
706
|
|
|
739
707
|
const preRotationScaleOriginPoint = options?.altKey
|
|
@@ -748,19 +716,11 @@ export class Driver {
|
|
|
748
716
|
preRotationScaleOriginPoint
|
|
749
717
|
)
|
|
750
718
|
|
|
751
|
-
const handlePoint = Vec.RotWith(
|
|
752
|
-
|
|
753
|
-
bounds.point,
|
|
754
|
-
this.editor.getSelectionRotation()
|
|
755
|
-
)
|
|
756
|
-
const targetHandlePoint = Vec.RotWith(
|
|
757
|
-
preRotationTargetHandlePoint,
|
|
758
|
-
bounds.point,
|
|
759
|
-
this.editor.getSelectionRotation()
|
|
760
|
-
)
|
|
719
|
+
const handlePoint = Vec.RotWith(preRotationHandlePoint, bounds.point, rotation)
|
|
720
|
+
const targetHandlePoint = Vec.RotWith(preRotationTargetHandlePoint, bounds.point, rotation)
|
|
761
721
|
|
|
762
|
-
const screenHandle = this.pageToScreen(handlePoint)
|
|
763
|
-
const screenTarget = this.pageToScreen(targetHandlePoint)
|
|
722
|
+
const screenHandle = this.editor.pageToScreen(handlePoint)
|
|
723
|
+
const screenTarget = this.editor.pageToScreen(targetHandlePoint)
|
|
764
724
|
|
|
765
725
|
this.pointerDown(screenHandle.x, screenHandle.y, { target: 'selection', handle }, options)
|
|
766
726
|
this.pointerMove(screenTarget.x, screenTarget.y, options)
|