js-draw 0.9.3 → 0.10.0
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/.firebase/hosting.ZG9jcw.cache +338 -0
- package/.github/ISSUE_TEMPLATE/translation.yml +9 -1
- package/CHANGELOG.md +8 -0
- package/build_tools/buildTranslationTemplate.ts +6 -4
- package/dist/build_tools/buildTranslationTemplate.js +5 -4
- package/dist/bundle.js +1 -1
- package/dist/src/Color4.d.ts +1 -0
- package/dist/src/Color4.js +34 -15
- package/dist/src/Editor.d.ts +2 -2
- package/dist/src/Editor.js +2 -3
- package/dist/src/EditorImage.d.ts +1 -1
- package/dist/src/EventDispatcher.d.ts +1 -1
- package/dist/src/SVGLoader.d.ts +2 -2
- package/dist/src/SVGLoader.js +25 -11
- package/dist/src/UndoRedoHistory.d.ts +2 -2
- package/dist/src/Viewport.d.ts +1 -1
- package/dist/src/commands/SerializableCommand.d.ts +1 -1
- package/dist/src/components/AbstractComponent.d.ts +3 -3
- package/dist/src/components/SVGGlobalAttributesObject.d.ts +1 -1
- package/dist/src/components/builders/FreehandLineBuilder.js +3 -3
- package/dist/src/components/builders/PressureSensitiveFreehandLineBuilder.js +1 -1
- package/dist/src/components/builders/types.d.ts +1 -1
- package/dist/src/components/util/StrokeSmoother.d.ts +1 -1
- package/dist/src/math/Mat33.d.ts +1 -1
- package/dist/src/math/Path.d.ts +1 -1
- package/dist/src/math/Vec2.d.ts +2 -2
- package/dist/src/rendering/caching/testUtils.d.ts +1 -1
- package/dist/src/rendering/caching/types.d.ts +2 -2
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +1 -0
- package/dist/src/rendering/renderers/SVGRenderer.js +23 -6
- package/dist/src/toolbar/HTMLToolbar.d.ts +6 -1
- package/dist/src/toolbar/HTMLToolbar.js +24 -27
- package/dist/src/toolbar/IconProvider.d.ts +1 -1
- package/dist/src/toolbar/makeColorInput.d.ts +2 -2
- package/dist/src/toolbar/widgets/ActionButtonWidget.d.ts +3 -3
- package/dist/src/toolbar/widgets/BaseWidget.d.ts +2 -2
- package/dist/src/toolbar/widgets/BaseWidget.js +9 -4
- package/dist/src/tools/BaseTool.js +4 -4
- package/dist/src/tools/PanZoom.d.ts +5 -1
- package/dist/src/tools/PanZoom.js +108 -10
- package/dist/src/tools/PipetteTool.d.ts +1 -1
- package/dist/src/tools/SelectionTool/SelectionHandle.d.ts +3 -3
- package/dist/src/tools/SelectionTool/SelectionHandle.js +1 -1
- package/dist/src/tools/ToolbarShortcutHandler.d.ts +1 -1
- package/dist/src/types.d.ts +8 -8
- package/dist/src/{language → util}/assertions.d.ts +0 -0
- package/dist/src/{language → util}/assertions.js +1 -0
- package/dist/src/util/untilNextAnimationFrame.d.ts +3 -0
- package/dist/src/util/untilNextAnimationFrame.js +7 -0
- package/package.json +1 -1
- package/src/Color4.test.ts +7 -0
- package/src/Color4.ts +47 -18
- package/src/Editor.toSVG.test.ts +84 -0
- package/src/Editor.ts +2 -3
- package/src/SVGLoader.ts +26 -10
- package/src/components/builders/FreehandLineBuilder.ts +3 -3
- package/src/components/builders/PressureSensitiveFreehandLineBuilder.ts +1 -1
- package/src/rendering/renderers/SVGRenderer.ts +23 -5
- package/src/toolbar/HTMLToolbar.ts +33 -30
- package/src/toolbar/widgets/ActionButtonWidget.ts +2 -2
- package/src/toolbar/widgets/BaseWidget.ts +9 -4
- package/src/tools/PanZoom.ts +124 -7
- package/src/tools/SelectionTool/SelectionHandle.ts +1 -1
- package/src/{language → util}/assertions.ts +1 -0
- package/src/util/untilNextAnimationFrame.ts +9 -0
@@ -55,7 +55,7 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
55
55
|
fill: Color4.transparent,
|
56
56
|
stroke: {
|
57
57
|
color: this.startPoint.color,
|
58
|
-
width: this.roundDistance(this.averageWidth),
|
58
|
+
width: this.roundDistance(this.averageWidth / 2),
|
59
59
|
}
|
60
60
|
};
|
61
61
|
}
|
@@ -108,7 +108,7 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
108
108
|
}
|
109
109
|
|
110
110
|
private getMinFit(): number {
|
111
|
-
let minFit = Math.min(this.minFitAllowed, this.averageWidth /
|
111
|
+
let minFit = Math.min(this.minFitAllowed, this.averageWidth / 5);
|
112
112
|
|
113
113
|
if (minFit < 1e-10) {
|
114
114
|
minFit = this.minFitAllowed;
|
@@ -135,7 +135,7 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
135
135
|
return [];
|
136
136
|
}
|
137
137
|
|
138
|
-
const width = Viewport.roundPoint(this.startPoint.width /
|
138
|
+
const width = Viewport.roundPoint(this.startPoint.width / 9, Math.min(this.minFitAllowed, this.startPoint.width / 5));
|
139
139
|
const center = this.roundPoint(this.startPoint.pos);
|
140
140
|
|
141
141
|
// Start on the right, cycle clockwise:
|
@@ -205,7 +205,7 @@ export default class PressureSensitiveFreehandLineBuilder implements ComponentBu
|
|
205
205
|
}
|
206
206
|
|
207
207
|
private roundPoint(point: Point2): Point2 {
|
208
|
-
let minFit = Math.min(this.minFitAllowed, this.curveStartWidth /
|
208
|
+
let minFit = Math.min(this.minFitAllowed, this.curveStartWidth / 3);
|
209
209
|
|
210
210
|
if (minFit < 1e-10) {
|
211
211
|
minFit = this.minFitAllowed;
|
@@ -150,13 +150,29 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
150
150
|
|
151
151
|
private textContainer: SVGTextElement|null = null;
|
152
152
|
private textContainerTransform: Mat33|null = null;
|
153
|
+
private textParentStyle: TextStyle|null = null;
|
153
154
|
public drawText(text: string, transform: Mat33, style: TextStyle): void {
|
154
155
|
const applyTextStyles = (elem: SVGTextElement|SVGTSpanElement, style: TextStyle) => {
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
156
|
+
if (style.fontFamily !== this.textParentStyle?.fontFamily) {
|
157
|
+
elem.style.fontFamily = style.fontFamily;
|
158
|
+
}
|
159
|
+
if (style.fontVariant !== this.textParentStyle?.fontVariant) {
|
160
|
+
elem.style.fontVariant = style.fontVariant ?? '';
|
161
|
+
}
|
162
|
+
if (style.fontWeight !== this.textParentStyle?.fontWeight) {
|
163
|
+
elem.style.fontWeight = style.fontWeight ?? '';
|
164
|
+
}
|
165
|
+
if (style.size !== this.textParentStyle?.size) {
|
166
|
+
elem.style.fontSize = style.size + 'px';
|
167
|
+
}
|
168
|
+
|
169
|
+
const fillString = style.renderingStyle.fill.toHexString();
|
170
|
+
// TODO: Uncomment at some future major version release --- currently causes incompatibility due
|
171
|
+
// to an SVG parsing bug in older versions.
|
172
|
+
//const parentFillString = this.textParentStyle?.renderingStyle?.fill?.toHexString();
|
173
|
+
//if (fillString !== parentFillString) {
|
174
|
+
elem.style.fill = fillString;
|
175
|
+
//}
|
160
176
|
|
161
177
|
if (style.renderingStyle.stroke) {
|
162
178
|
const strokeStyle = style.renderingStyle.stroke;
|
@@ -181,6 +197,7 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
181
197
|
if (this.objectLevel > 0) {
|
182
198
|
this.textContainer = container;
|
183
199
|
this.textContainerTransform = transform;
|
200
|
+
this.textParentStyle = style;
|
184
201
|
}
|
185
202
|
} else {
|
186
203
|
const elem = document.createElementNS(svgNameSpace, 'tspan');
|
@@ -218,6 +235,7 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
218
235
|
this.lastPathString = [];
|
219
236
|
this.lastPathStyle = null;
|
220
237
|
this.textContainer = null;
|
238
|
+
this.textParentStyle = null;
|
221
239
|
this.objectElems = [];
|
222
240
|
}
|
223
241
|
|
@@ -16,6 +16,7 @@ import SelectionToolWidget from './widgets/SelectionToolWidget';
|
|
16
16
|
import TextToolWidget from './widgets/TextToolWidget';
|
17
17
|
import HandToolWidget from './widgets/HandToolWidget';
|
18
18
|
import BaseWidget from './widgets/BaseWidget';
|
19
|
+
import { ActionButtonWidget } from './lib';
|
19
20
|
|
20
21
|
export const toolbarCSSPrefix = 'toolbar-';
|
21
22
|
|
@@ -158,57 +159,59 @@ export default class HTMLToolbar {
|
|
158
159
|
}
|
159
160
|
}
|
160
161
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
iconElem.classList.add('toolbar-icon');
|
175
|
-
|
176
|
-
button.replaceChildren(iconElem, labelElem);
|
177
|
-
}
|
162
|
+
/**
|
163
|
+
* Adds an action button with `title` to this toolbar (or to the given `parent` element).
|
164
|
+
*
|
165
|
+
* @return The added button.
|
166
|
+
*/
|
167
|
+
public addActionButton(title: string|ActionButtonIcon, command: ()=> void): BaseWidget {
|
168
|
+
const titleString = typeof title === 'string' ? title : title.label;
|
169
|
+
const widgetId = 'action-button';
|
170
|
+
|
171
|
+
const makeIcon = () => {
|
172
|
+
if (typeof title === 'string') {
|
173
|
+
return null;
|
174
|
+
}
|
178
175
|
|
179
|
-
|
180
|
-
|
176
|
+
return title.icon;
|
177
|
+
};
|
181
178
|
|
182
|
-
|
179
|
+
const widget = new ActionButtonWidget(
|
180
|
+
this.editor,
|
181
|
+
widgetId,
|
182
|
+
makeIcon,
|
183
|
+
titleString,
|
184
|
+
command,
|
185
|
+
this.editor.localization
|
186
|
+
);
|
187
|
+
|
188
|
+
this.addWidget(widget);
|
189
|
+
return widget;
|
183
190
|
}
|
184
191
|
|
185
192
|
public addUndoRedoButtons() {
|
186
|
-
const undoRedoGroup = document.createElement('div');
|
187
|
-
undoRedoGroup.classList.add(`${toolbarCSSPrefix}buttonGroup`);
|
188
|
-
|
189
193
|
const undoButton = this.addActionButton({
|
190
194
|
label: this.localizationTable.undo,
|
191
195
|
icon: this.editor.icons.makeUndoIcon()
|
192
196
|
}, () => {
|
193
197
|
this.editor.history.undo();
|
194
|
-
}
|
198
|
+
});
|
195
199
|
const redoButton = this.addActionButton({
|
196
200
|
label: this.localizationTable.redo,
|
197
201
|
icon: this.editor.icons.makeRedoIcon(),
|
198
202
|
}, () => {
|
199
203
|
this.editor.history.redo();
|
200
|
-
}
|
201
|
-
this.container.appendChild(undoRedoGroup);
|
204
|
+
});
|
202
205
|
|
203
|
-
undoButton.
|
204
|
-
redoButton.
|
206
|
+
undoButton.setDisabled(true);
|
207
|
+
redoButton.setDisabled(true);
|
205
208
|
this.editor.notifier.on(EditorEventType.UndoRedoStackUpdated, event => {
|
206
209
|
if (event.kind !== EditorEventType.UndoRedoStackUpdated) {
|
207
210
|
throw new Error('Wrong event type!');
|
208
211
|
}
|
209
212
|
|
210
|
-
undoButton.
|
211
|
-
redoButton.
|
213
|
+
undoButton.setDisabled(event.undoStackSize === 0);
|
214
|
+
redoButton.setDisabled(event.redoStackSize === 0);
|
212
215
|
});
|
213
216
|
}
|
214
217
|
|
@@ -7,7 +7,7 @@ export default class ActionButtonWidget extends BaseWidget {
|
|
7
7
|
editor: Editor,
|
8
8
|
id: string,
|
9
9
|
|
10
|
-
protected makeIcon: ()=> Element,
|
10
|
+
protected makeIcon: ()=> Element|null,
|
11
11
|
protected title: string,
|
12
12
|
protected clickAction: ()=>void,
|
13
13
|
|
@@ -24,7 +24,7 @@ export default class ActionButtonWidget extends BaseWidget {
|
|
24
24
|
return this.title;
|
25
25
|
}
|
26
26
|
|
27
|
-
protected createIcon(): Element {
|
27
|
+
protected createIcon(): Element|null {
|
28
28
|
return this.makeIcon();
|
29
29
|
}
|
30
30
|
|
@@ -78,7 +78,7 @@ export default abstract class BaseWidget {
|
|
78
78
|
}
|
79
79
|
|
80
80
|
protected abstract getTitle(): string;
|
81
|
-
protected abstract createIcon(): Element;
|
81
|
+
protected abstract createIcon(): Element|null;
|
82
82
|
|
83
83
|
// Add content to the widget's associated dropdown menu.
|
84
84
|
// Returns true if such a menu should be created, false otherwise.
|
@@ -200,9 +200,14 @@ export default abstract class BaseWidget {
|
|
200
200
|
|
201
201
|
protected updateIcon() {
|
202
202
|
const newIcon = this.createIcon();
|
203
|
-
|
204
|
-
|
205
|
-
|
203
|
+
|
204
|
+
if (newIcon) {
|
205
|
+
this.icon?.replaceWith(newIcon);
|
206
|
+
this.icon = newIcon;
|
207
|
+
this.icon.classList.add(`${toolbarCSSPrefix}icon`);
|
208
|
+
} else {
|
209
|
+
this.icon?.remove();
|
210
|
+
}
|
206
211
|
}
|
207
212
|
|
208
213
|
public setDisabled(disabled: boolean) {
|
package/src/tools/PanZoom.ts
CHANGED
@@ -5,6 +5,7 @@ import { Point2, Vec2 } from '../math/Vec2';
|
|
5
5
|
import Vec3 from '../math/Vec3';
|
6
6
|
import Pointer, { PointerDevice } from '../Pointer';
|
7
7
|
import { EditorEventType, KeyPressEvent, PointerEvt, WheelEvt } from '../types';
|
8
|
+
import untilNextAnimationFrame from '../util/untilNextAnimationFrame';
|
8
9
|
import { Viewport, ViewportTransform } from '../Viewport';
|
9
10
|
import BaseTool from './BaseTool';
|
10
11
|
|
@@ -25,12 +26,68 @@ export enum PanZoomMode {
|
|
25
26
|
RotationLocked = 0x1 << 5,
|
26
27
|
}
|
27
28
|
|
29
|
+
type ScrollByCallback = (delta: Vec2) => void;
|
30
|
+
|
31
|
+
class InertialScroller {
|
32
|
+
private running: boolean = false;
|
33
|
+
|
34
|
+
public constructor(
|
35
|
+
private initialVelocity: Vec2,
|
36
|
+
private scrollBy: ScrollByCallback,
|
37
|
+
private onComplete: ()=> void
|
38
|
+
) {
|
39
|
+
this.start();
|
40
|
+
}
|
41
|
+
|
42
|
+
private async start() {
|
43
|
+
if (this.running) {
|
44
|
+
return;
|
45
|
+
}
|
46
|
+
|
47
|
+
let currentVelocity = this.initialVelocity;
|
48
|
+
let lastTime = (new Date()).getTime();
|
49
|
+
this.running = true;
|
50
|
+
|
51
|
+
const maxSpeed = 8000; // units/s
|
52
|
+
const minSpeed = 200; // units/s
|
53
|
+
if (currentVelocity.magnitude() > maxSpeed) {
|
54
|
+
currentVelocity = currentVelocity.normalized().times(maxSpeed);
|
55
|
+
}
|
56
|
+
|
57
|
+
while (this.running && currentVelocity.magnitude() > minSpeed) {
|
58
|
+
const nowTime = (new Date()).getTime();
|
59
|
+
const dt = (nowTime - lastTime) / 1000;
|
60
|
+
|
61
|
+
currentVelocity = currentVelocity.times(Math.pow(1/8, dt));
|
62
|
+
this.scrollBy(currentVelocity.times(dt));
|
63
|
+
|
64
|
+
await untilNextAnimationFrame();
|
65
|
+
lastTime = nowTime;
|
66
|
+
}
|
67
|
+
|
68
|
+
if (this.running) {
|
69
|
+
this.stop();
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
public stop(): void {
|
74
|
+
if (this.running) {
|
75
|
+
this.running = false;
|
76
|
+
this.onComplete();
|
77
|
+
}
|
78
|
+
}
|
79
|
+
}
|
80
|
+
|
28
81
|
export default class PanZoom extends BaseTool {
|
29
82
|
private transform: ViewportTransform|null = null;
|
30
83
|
|
31
84
|
private lastAngle: number;
|
32
85
|
private lastDist: number;
|
33
86
|
private lastScreenCenter: Point2;
|
87
|
+
private lastTimestamp: number;
|
88
|
+
|
89
|
+
private inertialScroller: InertialScroller|null = null;
|
90
|
+
private velocity: Vec2|null = null;
|
34
91
|
|
35
92
|
public constructor(private editor: Editor, private mode: PanZoomMode, description: string) {
|
36
93
|
super(editor.notifier, description);
|
@@ -54,6 +111,8 @@ export default class PanZoom extends BaseTool {
|
|
54
111
|
public onPointerDown({ allPointers: pointers }: PointerEvt): boolean {
|
55
112
|
let handlingGesture = false;
|
56
113
|
|
114
|
+
this.inertialScroller?.stop();
|
115
|
+
|
57
116
|
const allAreTouch = this.allPointersAreOfType(pointers, PointerDevice.Touch);
|
58
117
|
const isRightClick = this.allPointersAreOfType(pointers, PointerDevice.RightButtonMouse);
|
59
118
|
|
@@ -73,6 +132,7 @@ export default class PanZoom extends BaseTool {
|
|
73
132
|
}
|
74
133
|
|
75
134
|
if (handlingGesture) {
|
135
|
+
this.lastTimestamp = (new Date()).getTime();
|
76
136
|
this.transform ??= Viewport.transformBy(Mat33.identity);
|
77
137
|
this.editor.display.setDraftMode(true);
|
78
138
|
}
|
@@ -80,6 +140,23 @@ export default class PanZoom extends BaseTool {
|
|
80
140
|
return handlingGesture;
|
81
141
|
}
|
82
142
|
|
143
|
+
private updateVelocity(currentCenter: Point2) {
|
144
|
+
const deltaPos = currentCenter.minus(this.lastScreenCenter);
|
145
|
+
const deltaTime = ((new Date()).getTime() - this.lastTimestamp) / 1000;
|
146
|
+
const currentVelocity = deltaPos.times(1 / deltaTime);
|
147
|
+
let smoothedVelocity = currentVelocity;
|
148
|
+
|
149
|
+
if (deltaTime === 0) {
|
150
|
+
return;
|
151
|
+
}
|
152
|
+
|
153
|
+
if (this.velocity) {
|
154
|
+
smoothedVelocity = this.velocity.lerp(smoothedVelocity, 0.5);
|
155
|
+
}
|
156
|
+
|
157
|
+
this.velocity = smoothedVelocity;
|
158
|
+
}
|
159
|
+
|
83
160
|
// Returns the change in position of the center of the given group of pointers.
|
84
161
|
// Assumes this.lastScreenCenter has been set appropriately.
|
85
162
|
private getCenterDelta(screenCenter: Point2): Vec2 {
|
@@ -98,6 +175,8 @@ export default class PanZoom extends BaseTool {
|
|
98
175
|
rotation = 0;
|
99
176
|
}
|
100
177
|
|
178
|
+
this.updateVelocity(screenCenter);
|
179
|
+
|
101
180
|
const transformUpdate = Mat33.translation(delta)
|
102
181
|
.rightMul(Mat33.scaling2D(dist / this.lastDist, canvasCenter))
|
103
182
|
.rightMul(Mat33.zRotation(rotation, canvasCenter));
|
@@ -116,6 +195,7 @@ export default class PanZoom extends BaseTool {
|
|
116
195
|
Mat33.translation(delta)
|
117
196
|
)
|
118
197
|
);
|
198
|
+
this.updateVelocity(pointer.screenPos);
|
119
199
|
this.lastScreenCenter = pointer.screenPos;
|
120
200
|
}
|
121
201
|
|
@@ -130,19 +210,52 @@ export default class PanZoom extends BaseTool {
|
|
130
210
|
}
|
131
211
|
lastTransform.unapply(this.editor);
|
132
212
|
this.transform.apply(this.editor);
|
213
|
+
|
214
|
+
this.lastTimestamp = (new Date()).getTime();
|
133
215
|
}
|
134
216
|
|
135
|
-
public onPointerUp(
|
136
|
-
|
137
|
-
this.transform
|
138
|
-
|
217
|
+
public onPointerUp(event: PointerEvt): void {
|
218
|
+
const onComplete = () => {
|
219
|
+
if (this.transform) {
|
220
|
+
this.transform.unapply(this.editor);
|
221
|
+
this.editor.dispatch(this.transform, false);
|
222
|
+
}
|
223
|
+
|
224
|
+
this.editor.display.setDraftMode(false);
|
225
|
+
this.transform = null;
|
226
|
+
this.velocity = Vec2.zero;
|
227
|
+
};
|
228
|
+
|
229
|
+
const shouldInertialScroll =
|
230
|
+
event.current.device === PointerDevice.Touch && event.allPointers.length === 1;
|
231
|
+
|
232
|
+
if (shouldInertialScroll && this.velocity !== null) {
|
233
|
+
this.inertialScroller?.stop();
|
234
|
+
|
235
|
+
this.inertialScroller = new InertialScroller(this.velocity, (scrollDelta: Vec2) => {
|
236
|
+
if (!this.transform) {
|
237
|
+
return;
|
238
|
+
}
|
239
|
+
|
240
|
+
const canvasDelta = this.editor.viewport.screenToCanvasTransform.transformVec3(scrollDelta);
|
241
|
+
|
242
|
+
// Scroll by scrollDelta
|
243
|
+
this.transform.unapply(this.editor);
|
244
|
+
this.transform = Viewport.transformBy(
|
245
|
+
this.transform.transform.rightMul(
|
246
|
+
Mat33.translation(canvasDelta)
|
247
|
+
)
|
248
|
+
);
|
249
|
+
this.transform.apply(this.editor);
|
250
|
+
}, onComplete);
|
251
|
+
} else {
|
252
|
+
onComplete();
|
139
253
|
}
|
140
|
-
|
141
|
-
this.editor.display.setDraftMode(false);
|
142
|
-
this.transform = null;
|
143
254
|
}
|
144
255
|
|
145
256
|
public onGestureCancel(): void {
|
257
|
+
this.inertialScroller?.stop();
|
258
|
+
this.velocity = Vec2.zero;
|
146
259
|
this.transform?.unapply(this.editor);
|
147
260
|
this.editor.display.setDraftMode(false);
|
148
261
|
this.transform = null;
|
@@ -166,6 +279,8 @@ export default class PanZoom extends BaseTool {
|
|
166
279
|
}
|
167
280
|
|
168
281
|
public onWheel({ delta, screenPos }: WheelEvt): boolean {
|
282
|
+
this.inertialScroller?.stop();
|
283
|
+
|
169
284
|
// Reset the transformation -- wheel events are individual events, so we don't
|
170
285
|
// need to unapply/reapply.
|
171
286
|
this.transform = Viewport.transformBy(Mat33.identity);
|
@@ -190,6 +305,8 @@ export default class PanZoom extends BaseTool {
|
|
190
305
|
}
|
191
306
|
|
192
307
|
public onKeyPress({ key, ctrlKey, altKey }: KeyPressEvent): boolean {
|
308
|
+
this.inertialScroller?.stop();
|
309
|
+
|
193
310
|
if (!(this.mode & PanZoomMode.Keyboard)) {
|
194
311
|
return false;
|
195
312
|
}
|