js-draw 1.10.0 → 1.11.1
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/Editor.css +6 -2
- package/dist/bundle.js +3 -3
- package/dist/bundledStyles.js +1 -1
- package/dist/cjs/Editor.d.ts +7 -0
- package/dist/cjs/Editor.js +18 -4
- package/dist/cjs/commands/invertCommand.js +5 -0
- package/dist/cjs/components/AbstractComponent.d.ts +8 -0
- package/dist/cjs/components/AbstractComponent.js +28 -8
- package/dist/cjs/components/BackgroundComponent.d.ts +1 -1
- package/dist/cjs/components/ImageComponent.d.ts +1 -1
- package/dist/cjs/components/SVGGlobalAttributesObject.d.ts +1 -1
- package/dist/cjs/components/Stroke.d.ts +1 -1
- package/dist/cjs/components/builders/types.d.ts +11 -0
- package/dist/cjs/rendering/Display.js +3 -1
- package/dist/cjs/rendering/renderers/DummyRenderer.d.ts +1 -0
- package/dist/cjs/rendering/renderers/DummyRenderer.js +3 -0
- package/dist/cjs/toolbar/AbstractToolbar.d.ts +18 -2
- package/dist/cjs/toolbar/AbstractToolbar.js +46 -30
- package/dist/cjs/toolbar/widgets/BaseWidget.js +1 -1
- package/dist/cjs/toolbar/widgets/ExitActionWidget.d.ts +12 -0
- package/dist/cjs/toolbar/widgets/ExitActionWidget.js +32 -0
- package/dist/cjs/toolbar/widgets/HandToolWidget.d.ts +4 -3
- package/dist/cjs/toolbar/widgets/HandToolWidget.js +24 -13
- package/dist/cjs/toolbar/widgets/InsertImageWidget.js +1 -1
- package/dist/cjs/toolbar/widgets/keybindings.d.ts +1 -0
- package/dist/cjs/toolbar/widgets/keybindings.js +4 -1
- package/dist/cjs/toolbar/widgets/layout/types.d.ts +1 -1
- package/dist/cjs/tools/FindTool.js +1 -1
- package/dist/cjs/tools/Pen.js +13 -2
- package/dist/cjs/tools/SelectionTool/Selection.d.ts +4 -0
- package/dist/cjs/tools/SelectionTool/Selection.js +56 -12
- package/dist/cjs/tools/SelectionTool/SelectionTool.d.ts +1 -0
- package/dist/cjs/tools/SelectionTool/SelectionTool.js +35 -3
- package/dist/cjs/tools/ToolSwitcherShortcut.d.ts +0 -1
- package/dist/cjs/tools/ToolSwitcherShortcut.js +0 -1
- package/dist/cjs/tools/keybindings.d.ts +1 -0
- package/dist/cjs/tools/keybindings.js +3 -1
- package/dist/cjs/tools/localization.d.ts +2 -0
- package/dist/cjs/tools/localization.js +2 -0
- package/dist/cjs/util/listenForKeyboardEventsFrom.d.ts +5 -0
- package/dist/cjs/util/listenForKeyboardEventsFrom.js +5 -1
- package/dist/cjs/version.js +1 -1
- package/dist/mjs/Editor.d.ts +7 -0
- package/dist/mjs/Editor.mjs +18 -4
- package/dist/mjs/commands/invertCommand.mjs +5 -0
- package/dist/mjs/components/AbstractComponent.d.ts +8 -0
- package/dist/mjs/components/AbstractComponent.mjs +28 -8
- package/dist/mjs/components/BackgroundComponent.d.ts +1 -1
- package/dist/mjs/components/ImageComponent.d.ts +1 -1
- package/dist/mjs/components/SVGGlobalAttributesObject.d.ts +1 -1
- package/dist/mjs/components/Stroke.d.ts +1 -1
- package/dist/mjs/components/builders/types.d.ts +11 -0
- package/dist/mjs/rendering/Display.mjs +3 -1
- package/dist/mjs/rendering/renderers/DummyRenderer.d.ts +1 -0
- package/dist/mjs/rendering/renderers/DummyRenderer.mjs +3 -0
- package/dist/mjs/toolbar/AbstractToolbar.d.ts +18 -2
- package/dist/mjs/toolbar/AbstractToolbar.mjs +46 -30
- package/dist/mjs/toolbar/widgets/BaseWidget.mjs +1 -1
- package/dist/mjs/toolbar/widgets/ExitActionWidget.d.ts +12 -0
- package/dist/mjs/toolbar/widgets/ExitActionWidget.mjs +27 -0
- package/dist/mjs/toolbar/widgets/HandToolWidget.d.ts +4 -3
- package/dist/mjs/toolbar/widgets/HandToolWidget.mjs +24 -13
- package/dist/mjs/toolbar/widgets/InsertImageWidget.mjs +1 -1
- package/dist/mjs/toolbar/widgets/keybindings.d.ts +1 -0
- package/dist/mjs/toolbar/widgets/keybindings.mjs +3 -0
- package/dist/mjs/toolbar/widgets/layout/types.d.ts +1 -1
- package/dist/mjs/tools/FindTool.mjs +1 -1
- package/dist/mjs/tools/Pen.mjs +13 -2
- package/dist/mjs/tools/SelectionTool/Selection.d.ts +4 -0
- package/dist/mjs/tools/SelectionTool/Selection.mjs +56 -12
- package/dist/mjs/tools/SelectionTool/SelectionTool.d.ts +1 -0
- package/dist/mjs/tools/SelectionTool/SelectionTool.mjs +36 -4
- package/dist/mjs/tools/ToolSwitcherShortcut.d.ts +0 -1
- package/dist/mjs/tools/ToolSwitcherShortcut.mjs +0 -1
- package/dist/mjs/tools/keybindings.d.ts +1 -0
- package/dist/mjs/tools/keybindings.mjs +2 -0
- package/dist/mjs/tools/localization.d.ts +2 -0
- package/dist/mjs/tools/localization.mjs +2 -0
- package/dist/mjs/util/listenForKeyboardEventsFrom.d.ts +5 -0
- package/dist/mjs/util/listenForKeyboardEventsFrom.mjs +5 -1
- package/dist/mjs/version.mjs +1 -1
- package/package.json +5 -5
- package/src/toolbar/AbstractToolbar.scss +3 -2
- package/src/toolbar/widgets/components/makeColorInput.scss +8 -0
@@ -169,7 +169,7 @@ class InsertImageWidget extends BaseWidget {
|
|
169
169
|
this.image?.reset();
|
170
170
|
};
|
171
171
|
this.statusView.replaceChildren(sizeText);
|
172
|
-
const largeImageThreshold = 0.
|
172
|
+
const largeImageThreshold = 0.12; // MiB
|
173
173
|
if (sizeInMiB > largeImageThreshold) {
|
174
174
|
this.statusView.appendChild(decreaseSizeButton);
|
175
175
|
}
|
@@ -1,3 +1,4 @@
|
|
1
1
|
export declare const resizeImageToSelectionKeyboardShortcut = "jsdraw.toolbar.SelectionTool.resizeImageToSelection";
|
2
2
|
export declare const selectStrokeTypeKeyboardShortcutIds: string[];
|
3
3
|
export declare const saveKeyboardShortcut = "jsdraw.toolbar.SaveActionWidget.save";
|
4
|
+
export declare const exitKeyboardShortcut = "jsdraw.toolbar.ExitActionWidget.exit";
|
@@ -11,3 +11,6 @@ for (let i = 0; i < selectStrokeTypeKeyboardShortcutIds.length; i++) {
|
|
11
11
|
// Save
|
12
12
|
export const saveKeyboardShortcut = 'jsdraw.toolbar.SaveActionWidget.save';
|
13
13
|
KeyboardShortcutManager.registerDefaultKeyboardShortcut(saveKeyboardShortcut, ['ctrlOrMeta+KeyS'], 'Save');
|
14
|
+
// Exit
|
15
|
+
export const exitKeyboardShortcut = 'jsdraw.toolbar.ExitActionWidget.exit';
|
16
|
+
KeyboardShortcutManager.registerDefaultKeyboardShortcut(exitKeyboardShortcut, ['Alt+KeyQ'], 'Exit');
|
@@ -34,7 +34,7 @@ export default class FindTool extends BaseTool {
|
|
34
34
|
}
|
35
35
|
if (matchIdx < matches.length) {
|
36
36
|
const undoable = false;
|
37
|
-
this.editor.dispatch(this.editor.viewport.zoomTo(matches[matchIdx], true, true), undoable);
|
37
|
+
void this.editor.dispatch(this.editor.viewport.zoomTo(matches[matchIdx], true, true), undoable);
|
38
38
|
this.editor.announceForAccessibility(this.editor.localization.focusedFoundText(matchIdx + 1, matches.length));
|
39
39
|
}
|
40
40
|
}
|
package/dist/mjs/tools/Pen.mjs
CHANGED
@@ -97,8 +97,8 @@ export default class Pen extends BaseTool {
|
|
97
97
|
this.currentDeviceType = current.device;
|
98
98
|
if (this.shapeAutocompletionEnabled) {
|
99
99
|
const stationaryDetectionConfig = {
|
100
|
-
maxSpeed: 5,
|
101
|
-
maxRadius:
|
100
|
+
maxSpeed: 8.5,
|
101
|
+
maxRadius: 11,
|
102
102
|
minTimeSeconds: 0.5, // s
|
103
103
|
};
|
104
104
|
this.stationaryDetector = new StationaryPenDetector(current, stationaryDetectionConfig, pointer => this.autocorrectShape(pointer));
|
@@ -141,6 +141,7 @@ export default class Pen extends BaseTool {
|
|
141
141
|
if (this.autocorrectedShape) {
|
142
142
|
this.removedAutocorrectedShapeTime = performance.now();
|
143
143
|
this.autocorrectedShape = null;
|
144
|
+
this.editor.announceForAccessibility(this.editor.localization.autocorrectionCanceled);
|
144
145
|
}
|
145
146
|
}
|
146
147
|
}
|
@@ -187,6 +188,13 @@ export default class Pen extends BaseTool {
|
|
187
188
|
if (!this.builder || !correctedShape) {
|
188
189
|
return;
|
189
190
|
}
|
191
|
+
// Don't complete to empty shapes.
|
192
|
+
const bboxArea = correctedShape.getBBox().area;
|
193
|
+
if (bboxArea === 0 || !isFinite(bboxArea)) {
|
194
|
+
return;
|
195
|
+
}
|
196
|
+
const shapeDescription = correctedShape.description(this.editor.localization);
|
197
|
+
this.editor.announceForAccessibility(this.editor.localization.autocorrectedTo(shapeDescription));
|
190
198
|
this.autocorrectedShape = correctedShape;
|
191
199
|
this.lastAutocorrectedShape = correctedShape;
|
192
200
|
this.previewStroke();
|
@@ -201,6 +209,9 @@ export default class Pen extends BaseTool {
|
|
201
209
|
const stroke = this.autocorrectedShape ?? this.builder.build();
|
202
210
|
this.previewStroke();
|
203
211
|
if (stroke.getBBox().area > 0) {
|
212
|
+
if (stroke === this.autocorrectedShape) {
|
213
|
+
this.editor.announceForAccessibility(this.editor.localization.autocorrectedTo(stroke.description(this.editor.localization)));
|
214
|
+
}
|
204
215
|
const canFlatten = true;
|
205
216
|
const action = EditorImage.addElement(stroke, canFlatten);
|
206
217
|
this.editor.dispatch(action);
|
@@ -2,6 +2,7 @@
|
|
2
2
|
* @internal
|
3
3
|
* @packageDocumentation
|
4
4
|
*/
|
5
|
+
import SerializableCommand from '../../commands/SerializableCommand';
|
5
6
|
import Editor from '../../Editor';
|
6
7
|
import { Mat33, Rect2, Point2 } from '@js-draw/math';
|
7
8
|
import Pointer from '../../Pointer';
|
@@ -36,7 +37,10 @@ export default class Selection {
|
|
36
37
|
getScreenRegion(): Rect2;
|
37
38
|
get screenRegionRotation(): number;
|
38
39
|
setTransform(transform: Mat33, preview?: boolean): void;
|
40
|
+
private getDeltaZIndexToMoveSelectionToTop;
|
39
41
|
finalizeTransform(): void | Promise<void>;
|
42
|
+
/** Sends all selected elements to the bottom of the visible image. */
|
43
|
+
sendToBack(): SerializableCommand | null;
|
40
44
|
private static ApplyTransformationCommand;
|
41
45
|
private previewTransformCmds;
|
42
46
|
resolveToObjects(): boolean;
|
@@ -13,6 +13,7 @@ import Duplicate from '../../commands/Duplicate.mjs';
|
|
13
13
|
import { DragTransformer, ResizeTransformer, RotateTransformer } from './TransformMode.mjs';
|
14
14
|
import { ResizeMode } from './types.mjs';
|
15
15
|
import EditorImage from '../../image/EditorImage.mjs';
|
16
|
+
import uniteCommands from '../../commands/uniteCommands.mjs';
|
16
17
|
const updateChunkSize = 100;
|
17
18
|
const maxPreviewElemCount = 500;
|
18
19
|
// @internal
|
@@ -23,6 +24,7 @@ class Selection {
|
|
23
24
|
// @see getTightBoundingBox
|
24
25
|
this.selectionTightBoundingBox = null;
|
25
26
|
this.transform = Mat33.identity;
|
27
|
+
// invariant: sorted by increasing z-index
|
26
28
|
this.selectedElems = [];
|
27
29
|
this.hasParent = true;
|
28
30
|
// Maps IDs to whether we removed the component from the image
|
@@ -133,6 +135,16 @@ class Selection {
|
|
133
135
|
this.previewTransformCmds();
|
134
136
|
}
|
135
137
|
}
|
138
|
+
getDeltaZIndexToMoveSelectionToTop() {
|
139
|
+
if (this.selectedElems.length === 0) {
|
140
|
+
return 0;
|
141
|
+
}
|
142
|
+
const selectedBottommostZIndex = this.selectedElems[0].getZIndex();
|
143
|
+
const visibleObjects = this.editor.image.getElementsIntersectingRegion(this.region);
|
144
|
+
const topMostVisibleZIndex = visibleObjects[visibleObjects.length - 1]?.getZIndex() ?? selectedBottommostZIndex;
|
145
|
+
const deltaZIndex = (topMostVisibleZIndex + 1) - selectedBottommostZIndex;
|
146
|
+
return deltaZIndex;
|
147
|
+
}
|
136
148
|
// Applies the current transformation to the selection
|
137
149
|
finalizeTransform() {
|
138
150
|
const fullTransform = this.transform;
|
@@ -141,17 +153,35 @@ class Selection {
|
|
141
153
|
this.originalRegion = this.originalRegion.transformedBoundingBox(this.transform);
|
142
154
|
this.transform = Mat33.identity;
|
143
155
|
this.scrollTo();
|
156
|
+
let transformPromise = undefined;
|
144
157
|
// Make the commands undo-able.
|
145
158
|
// Don't check for non-empty transforms because this breaks changing the
|
146
159
|
// z-index of the just-transformed commands.
|
147
|
-
|
148
|
-
|
149
|
-
|
160
|
+
if (this.selectedElems.length > 0) {
|
161
|
+
const deltaZIndex = this.getDeltaZIndexToMoveSelectionToTop();
|
162
|
+
transformPromise = this.editor.dispatch(new _a.ApplyTransformationCommand(this, selectedElems, fullTransform, deltaZIndex));
|
163
|
+
}
|
150
164
|
// Clear renderings of any in-progress transformations
|
151
165
|
const wetInkRenderer = this.editor.display.getWetInkRenderer();
|
152
166
|
wetInkRenderer.clear();
|
153
167
|
return transformPromise;
|
154
168
|
}
|
169
|
+
/** Sends all selected elements to the bottom of the visible image. */
|
170
|
+
sendToBack() {
|
171
|
+
const visibleObjects = this.editor.image.getElementsIntersectingRegion(this.editor.viewport.visibleRect);
|
172
|
+
// VisibleObjects and selectedElems should both be sorted by z-index
|
173
|
+
const lowestVisibleZIndex = visibleObjects[0]?.getZIndex() ?? 0;
|
174
|
+
const highestSelectedZIndex = this.selectedElems[this.selectedElems.length - 1]?.getZIndex() ?? 0;
|
175
|
+
const targetHighestZIndex = lowestVisibleZIndex - 1;
|
176
|
+
const deltaZIndex = targetHighestZIndex - highestSelectedZIndex;
|
177
|
+
if (deltaZIndex !== 0) {
|
178
|
+
const commands = this.selectedElems.map(elem => {
|
179
|
+
return elem.setZIndex(elem.getZIndex() + deltaZIndex);
|
180
|
+
});
|
181
|
+
return uniteCommands(commands, updateChunkSize);
|
182
|
+
}
|
183
|
+
return null;
|
184
|
+
}
|
155
185
|
// Preview the effects of the current transformation on the selection
|
156
186
|
previewTransformCmds() {
|
157
187
|
if (this.selectedElems.length === 0) {
|
@@ -165,7 +195,7 @@ class Selection {
|
|
165
195
|
const wetInkRenderer = this.editor.display.getWetInkRenderer();
|
166
196
|
wetInkRenderer.clear();
|
167
197
|
wetInkRenderer.pushTransform(this.transform);
|
168
|
-
const viewportVisibleRect = this.editor.viewport.visibleRect;
|
198
|
+
const viewportVisibleRect = this.editor.viewport.visibleRect.union(this.region);
|
169
199
|
const visibleRect = viewportVisibleRect.transformedBoundingBox(this.transform.inverse());
|
170
200
|
for (const elem of this.selectedElems) {
|
171
201
|
elem.render(wetInkRenderer, visibleRect);
|
@@ -411,7 +441,8 @@ class Selection {
|
|
411
441
|
if (wasTransforming) {
|
412
442
|
// Don't update the selection's focus when redoing/undoing
|
413
443
|
const selectionToUpdate = null;
|
414
|
-
|
444
|
+
const deltaZIndex = this.getDeltaZIndexToMoveSelectionToTop();
|
445
|
+
tmpApplyCommand = new _a.ApplyTransformationCommand(selectionToUpdate, this.selectedElems, this.transform, deltaZIndex);
|
415
446
|
// Transform to ensure that the duplicates are in the correct location
|
416
447
|
await tmpApplyCommand.apply(this.editor);
|
417
448
|
// Show items again
|
@@ -452,6 +483,8 @@ class Selection {
|
|
452
483
|
this.originalRegion = bbox;
|
453
484
|
this.selectionTightBoundingBox = bbox;
|
454
485
|
this.selectedElems = objects.filter(object => object.isSelectable());
|
486
|
+
// Enforce increasing z-index invariant
|
487
|
+
this.selectedElems.sort((a, b) => a.getZIndex() - b.getZIndex());
|
455
488
|
this.padRegion();
|
456
489
|
this.updateUI();
|
457
490
|
}
|
@@ -465,7 +498,8 @@ _a = Selection;
|
|
465
498
|
// The selection box is lost when serializing/deserializing. No need to store box rotation
|
466
499
|
const fullTransform = new Mat33(...json.transform);
|
467
500
|
const elemIds = (json.elems ?? []);
|
468
|
-
|
501
|
+
const deltaZIndex = parseInt(json.deltaZIndex ?? 0);
|
502
|
+
return new _a.ApplyTransformationCommand(null, elemIds, fullTransform, deltaZIndex);
|
469
503
|
});
|
470
504
|
})();
|
471
505
|
Selection.ApplyTransformationCommand = class extends SerializableCommand {
|
@@ -473,10 +507,11 @@ Selection.ApplyTransformationCommand = class extends SerializableCommand {
|
|
473
507
|
// If a `string[]`, selectedElems is a list of element IDs.
|
474
508
|
selectedElems,
|
475
509
|
// Full transformation used to transform elements.
|
476
|
-
fullTransform) {
|
510
|
+
fullTransform, deltaZIndex) {
|
477
511
|
super('selection-tool-transform');
|
478
512
|
this.selection = selection;
|
479
513
|
this.fullTransform = fullTransform;
|
514
|
+
this.deltaZIndex = deltaZIndex;
|
480
515
|
const isIDList = (arr) => {
|
481
516
|
return typeof arr[0] === 'string';
|
482
517
|
};
|
@@ -487,11 +522,11 @@ Selection.ApplyTransformationCommand = class extends SerializableCommand {
|
|
487
522
|
else {
|
488
523
|
this.selectedElemIds = selectedElems.map(elem => elem.getId());
|
489
524
|
this.transformCommands = selectedElems.map(elem => {
|
490
|
-
return elem.
|
525
|
+
return elem.setZIndexAndTransformBy(this.fullTransform, elem.getZIndex() + deltaZIndex);
|
491
526
|
});
|
492
527
|
}
|
493
528
|
}
|
494
|
-
resolveToElems(editor) {
|
529
|
+
resolveToElems(editor, isUndoing) {
|
495
530
|
if (this.transformCommands) {
|
496
531
|
return;
|
497
532
|
}
|
@@ -500,11 +535,19 @@ Selection.ApplyTransformationCommand = class extends SerializableCommand {
|
|
500
535
|
if (!elem) {
|
501
536
|
throw new Error(`Unable to find element with ID, ${id}.`);
|
502
537
|
}
|
503
|
-
|
538
|
+
let originalZIndex = elem.getZIndex();
|
539
|
+
let targetZIndex = elem.getZIndex() + this.deltaZIndex;
|
540
|
+
// If the command has already been applied, the element should currently
|
541
|
+
// have the target z-index.
|
542
|
+
if (isUndoing) {
|
543
|
+
targetZIndex = elem.getZIndex();
|
544
|
+
originalZIndex = elem.getZIndex() - this.deltaZIndex;
|
545
|
+
}
|
546
|
+
return elem.setZIndexAndTransformBy(this.fullTransform, targetZIndex, originalZIndex);
|
504
547
|
});
|
505
548
|
}
|
506
549
|
async apply(editor) {
|
507
|
-
this.resolveToElems(editor);
|
550
|
+
this.resolveToElems(editor, false);
|
508
551
|
this.selection?.setTransform(this.fullTransform, false);
|
509
552
|
this.selection?.updateUI();
|
510
553
|
await editor.asyncApplyCommands(this.transformCommands, updateChunkSize);
|
@@ -513,7 +556,7 @@ Selection.ApplyTransformationCommand = class extends SerializableCommand {
|
|
513
556
|
this.selection?.updateUI();
|
514
557
|
}
|
515
558
|
async unapply(editor) {
|
516
|
-
this.resolveToElems(editor);
|
559
|
+
this.resolveToElems(editor, true);
|
517
560
|
this.selection?.setTransform(this.fullTransform.inverse(), false);
|
518
561
|
this.selection?.updateUI();
|
519
562
|
await editor.asyncUnapplyCommands(this.transformCommands, updateChunkSize, true);
|
@@ -525,6 +568,7 @@ Selection.ApplyTransformationCommand = class extends SerializableCommand {
|
|
525
568
|
return {
|
526
569
|
elems: this.selectedElemIds,
|
527
570
|
transform: this.fullTransform.toArray(),
|
571
|
+
deltaZIndex: this.deltaZIndex,
|
528
572
|
};
|
529
573
|
}
|
530
574
|
description(_editor, localizationTable) {
|
@@ -28,6 +28,7 @@ export default class SelectionTool extends BaseTool {
|
|
28
28
|
private onSelectionUpdated;
|
29
29
|
private zoomToSelection;
|
30
30
|
private static handleableKeys;
|
31
|
+
private hasUnfinalizedTransformFromKeyPress;
|
31
32
|
onKeyPress(event: KeyPressEvent): boolean;
|
32
33
|
onKeyUp(evt: KeyUpEvent): boolean;
|
33
34
|
onCopy(event: CopyEvent): boolean;
|
@@ -5,7 +5,7 @@ import BaseTool from '../BaseTool.mjs';
|
|
5
5
|
import SVGRenderer from '../../rendering/renderers/SVGRenderer.mjs';
|
6
6
|
import Selection from './Selection.mjs';
|
7
7
|
import TextComponent from '../../components/TextComponent.mjs';
|
8
|
-
import { duplicateSelectionShortcut, selectAllKeyboardShortcut, snapToGridKeyboardShortcutId } from '../keybindings.mjs';
|
8
|
+
import { duplicateSelectionShortcut, selectAllKeyboardShortcut, sendToBackSelectionShortcut, snapToGridKeyboardShortcutId } from '../keybindings.mjs';
|
9
9
|
import ToPointerAutoscroller from './ToPointerAutoscroller.mjs';
|
10
10
|
export const cssPrefix = 'selection-tool-';
|
11
11
|
// Allows users to select/transform portions of the `EditorImage`.
|
@@ -21,6 +21,9 @@ class SelectionTool extends BaseTool {
|
|
21
21
|
this.lastPointer = null;
|
22
22
|
this.selectionBoxHandlingEvt = false;
|
23
23
|
this.lastSelectedObjects = [];
|
24
|
+
// Whether the last keypress corresponded to an action that didn't transform the
|
25
|
+
// selection (and thus does not need to be finalized on onKeyUp).
|
26
|
+
this.hasUnfinalizedTransformFromKeyPress = false;
|
24
27
|
this.autoscroller = new ToPointerAutoscroller(editor.viewport, (scrollBy) => {
|
25
28
|
editor.dispatch(Viewport.transformBy(Mat33.translation(scrollBy)), false);
|
26
29
|
// Update the selection box/content to match the new viewport.
|
@@ -213,7 +216,8 @@ class SelectionTool extends BaseTool {
|
|
213
216
|
this.snapToGrid = true;
|
214
217
|
return true;
|
215
218
|
}
|
216
|
-
if (this.selectionBox && shortcucts.matchesShortcut(duplicateSelectionShortcut, event)
|
219
|
+
if (this.selectionBox && (shortcucts.matchesShortcut(duplicateSelectionShortcut, event)
|
220
|
+
|| shortcucts.matchesShortcut(sendToBackSelectionShortcut, event))) {
|
217
221
|
// Handle duplication on key up — we don't want to accidentally duplicate
|
218
222
|
// many times.
|
219
223
|
return true;
|
@@ -227,9 +231,11 @@ class SelectionTool extends BaseTool {
|
|
227
231
|
// Pass it to another tool, if apliccable.
|
228
232
|
return false;
|
229
233
|
}
|
230
|
-
else if (event.key === 'Shift') {
|
234
|
+
else if (event.shiftKey || event.key === 'Shift') {
|
231
235
|
this.shiftKeyPressed = true;
|
232
|
-
|
236
|
+
if (event.key === 'Shift') {
|
237
|
+
return true;
|
238
|
+
}
|
233
239
|
}
|
234
240
|
let rotationSteps = 0;
|
235
241
|
let xTranslateSteps = 0;
|
@@ -297,6 +303,8 @@ class SelectionTool extends BaseTool {
|
|
297
303
|
const oldTransform = this.selectionBox.getTransform();
|
298
304
|
this.selectionBox.setTransform(oldTransform.rightMul(transform));
|
299
305
|
this.selectionBox.scrollTo();
|
306
|
+
// The transformation needs to be finalized at some point (on key up)
|
307
|
+
this.hasUnfinalizedTransformFromKeyPress = true;
|
300
308
|
}
|
301
309
|
if (this.selectionBox && !handled && (event.key === 'Delete' || event.key === 'Backspace')) {
|
302
310
|
this.editor.dispatch(this.selectionBox.deleteSelectedObjects());
|
@@ -322,12 +330,32 @@ class SelectionTool extends BaseTool {
|
|
322
330
|
});
|
323
331
|
return true;
|
324
332
|
}
|
333
|
+
if (this.selectionBox && shortcucts.matchesShortcut(sendToBackSelectionShortcut, evt)) {
|
334
|
+
const sendToBackCommand = this.selectionBox.sendToBack();
|
335
|
+
if (sendToBackCommand) {
|
336
|
+
this.editor.dispatch(sendToBackCommand);
|
337
|
+
}
|
338
|
+
return true;
|
339
|
+
}
|
340
|
+
// Here, we check if shiftKey === false because, as of this writing,
|
341
|
+
// evt.shiftKey is an optional property. Being falsey could just mean
|
342
|
+
// that it wasn't set.
|
343
|
+
if (evt.shiftKey === false) {
|
344
|
+
this.shiftKeyPressed = false;
|
345
|
+
// Don't return immediately -- event may be otherwise handled
|
346
|
+
}
|
347
|
+
// Also check for key === 'Shift' (for the case where shiftKey is undefined)
|
325
348
|
if (evt.key === 'Shift') {
|
326
349
|
this.shiftKeyPressed = false;
|
327
350
|
return true;
|
328
351
|
}
|
352
|
+
// If we don't need to finalize the transform
|
353
|
+
if (!this.hasUnfinalizedTransformFromKeyPress) {
|
354
|
+
return true;
|
355
|
+
}
|
329
356
|
if (this.selectionBox && SelectionTool.handleableKeys.some(key => key === evt.key)) {
|
330
357
|
this.selectionBox.finalizeTransform();
|
358
|
+
this.hasUnfinalizedTransformFromKeyPress = false;
|
331
359
|
return true;
|
332
360
|
}
|
333
361
|
return false;
|
@@ -361,7 +389,11 @@ class SelectionTool extends BaseTool {
|
|
361
389
|
return true;
|
362
390
|
}
|
363
391
|
setEnabled(enabled) {
|
392
|
+
const wasEnabled = this.isEnabled();
|
364
393
|
super.setEnabled(enabled);
|
394
|
+
if (wasEnabled === enabled) {
|
395
|
+
return;
|
396
|
+
}
|
365
397
|
// Clear the selection
|
366
398
|
this.selectionBox?.cancelSelection();
|
367
399
|
this.onSelectionUpdated();
|
@@ -15,3 +15,4 @@ export declare const zoomInKeyboardShortcutId = "jsdraw.tools.PanZoom.zoomIn";
|
|
15
15
|
export declare const zoomOutKeyboardShortcutId = "jsdraw.tools.PanZoom.zoomOut";
|
16
16
|
export declare const selectAllKeyboardShortcut = "jsdraw.tools.SelectionTool.selectAll";
|
17
17
|
export declare const duplicateSelectionShortcut = "jsdraw.tools.SelectionTool.duplicateSelection";
|
18
|
+
export declare const sendToBackSelectionShortcut = "jsdraw.tools.SelectionTool.sendToBack";
|
@@ -39,3 +39,5 @@ export const selectAllKeyboardShortcut = 'jsdraw.tools.SelectionTool.selectAll';
|
|
39
39
|
KeyboardShortcutManager.registerDefaultKeyboardShortcut(selectAllKeyboardShortcut, ['CtrlOrMeta+KeyA'], 'Select all');
|
40
40
|
export const duplicateSelectionShortcut = 'jsdraw.tools.SelectionTool.duplicateSelection';
|
41
41
|
KeyboardShortcutManager.registerDefaultKeyboardShortcut(duplicateSelectionShortcut, ['CtrlOrMeta+KeyD'], 'Duplicate selection');
|
42
|
+
export const sendToBackSelectionShortcut = 'jsdraw.tools.SelectionTool.sendToBack';
|
43
|
+
KeyboardShortcutManager.registerDefaultKeyboardShortcut(sendToBackSelectionShortcut, ['End'], 'Send to back');
|
@@ -9,6 +9,8 @@ export interface ToolLocalization {
|
|
9
9
|
undoRedoTool: string;
|
10
10
|
pipetteTool: string;
|
11
11
|
rightClickDragPanTool: string;
|
12
|
+
autocorrectedTo: (description: string) => string;
|
13
|
+
autocorrectionCanceled: string;
|
12
14
|
textTool: string;
|
13
15
|
enterTextToInsert: string;
|
14
16
|
changeTool: string;
|
@@ -9,6 +9,8 @@ export const defaultToolLocalization = {
|
|
9
9
|
rightClickDragPanTool: 'Right-click drag',
|
10
10
|
pipetteTool: 'Pick color from screen',
|
11
11
|
keyboardPanZoom: 'Keyboard pan/zoom shortcuts',
|
12
|
+
autocorrectedTo: (strokeDescription) => `Autocorrected to ${strokeDescription}`,
|
13
|
+
autocorrectionCanceled: 'Autocorrect cancelled',
|
12
14
|
textTool: 'Text',
|
13
15
|
enterTextToInsert: 'Text to insert',
|
14
16
|
changeTool: 'Change tool',
|
@@ -2,6 +2,11 @@ interface Callbacks {
|
|
2
2
|
filter(event: KeyboardEvent): boolean;
|
3
3
|
handleKeyDown(event: KeyboardEvent): void;
|
4
4
|
handleKeyUp(event: KeyboardEvent): void;
|
5
|
+
/**
|
6
|
+
* Should return `true` iff `source` is also registered as an event listener source.
|
7
|
+
* If `false` and focus leaves the original source, keyup events are fired.
|
8
|
+
*/
|
9
|
+
getHandlesKeyEventsFrom(source: Node): boolean;
|
5
10
|
}
|
6
11
|
/**
|
7
12
|
* Calls `callbacks` when different keys are known to be pressed.
|
@@ -65,7 +65,11 @@ const listenForKeyboardEventsFrom = (elem, callbacks) => {
|
|
65
65
|
handleKeyEvent(htmlEvent);
|
66
66
|
});
|
67
67
|
elem.addEventListener('focusout', (focusEvent) => {
|
68
|
-
|
68
|
+
let stillHasFocus = false;
|
69
|
+
if (focusEvent.relatedTarget) {
|
70
|
+
const relatedTarget = focusEvent.relatedTarget;
|
71
|
+
stillHasFocus = elem.contains(relatedTarget) || callbacks.getHandlesKeyEventsFrom(relatedTarget);
|
72
|
+
}
|
69
73
|
if (!stillHasFocus) {
|
70
74
|
for (const event of keysDown) {
|
71
75
|
callbacks.handleKeyUp(new KeyboardEvent('keyup', {
|
package/dist/mjs/version.mjs
CHANGED
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "js-draw",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.11.1",
|
4
4
|
"description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
|
5
5
|
"types": "./dist/mjs/lib.d.ts",
|
6
6
|
"main": "./dist/cjs/lib.js",
|
@@ -64,11 +64,11 @@
|
|
64
64
|
"postpack": "ts-node tools/copyREADME.ts revert"
|
65
65
|
},
|
66
66
|
"dependencies": {
|
67
|
-
"@js-draw/math": "^1.
|
68
|
-
"@melloware/coloris": "0.
|
67
|
+
"@js-draw/math": "^1.11.1",
|
68
|
+
"@melloware/coloris": "0.22.0"
|
69
69
|
},
|
70
70
|
"devDependencies": {
|
71
|
-
"@js-draw/build-tool": "^1.
|
71
|
+
"@js-draw/build-tool": "^1.11.1",
|
72
72
|
"@types/jest": "29.5.5",
|
73
73
|
"@types/jsdom": "21.1.3"
|
74
74
|
},
|
@@ -86,5 +86,5 @@
|
|
86
86
|
"freehand",
|
87
87
|
"svg"
|
88
88
|
],
|
89
|
-
"gitHead": "
|
89
|
+
"gitHead": "695cfe01116839842668233a14fa858ad4ae0bac"
|
90
90
|
}
|
@@ -45,7 +45,8 @@
|
|
45
45
|
}
|
46
46
|
|
47
47
|
.toolbar-button.disabled {
|
48
|
-
filter:
|
48
|
+
filter: sepia(0.2);
|
49
|
+
opacity: 0.45;
|
49
50
|
cursor: unset;
|
50
51
|
}
|
51
52
|
|
@@ -103,7 +104,7 @@
|
|
103
104
|
|
104
105
|
.toolbar-root button:disabled {
|
105
106
|
cursor: inherit;
|
106
|
-
|
107
|
+
opacity: 0.5;
|
107
108
|
}
|
108
109
|
|
109
110
|
.toolbar-root .toolbar-icon {
|
@@ -29,6 +29,14 @@
|
|
29
29
|
display: inline-flex;
|
30
30
|
flex-direction: row;
|
31
31
|
|
32
|
+
.coloris_input {
|
33
|
+
// Ensure that the region that can be clicked to open the input is roughly
|
34
|
+
// the full height of the container.
|
35
|
+
// Because the color picker is always shown below or above the input, 5px is
|
36
|
+
// subtracted to make the picker better align with the input's container.
|
37
|
+
height: calc(100% - 6px);
|
38
|
+
}
|
39
|
+
|
32
40
|
&.picker-open .clr-field {
|
33
41
|
// Work around what seems to be a Coloris bug -- clicking on the input button
|
34
42
|
// keeps the color picker open (while clicking anywhere else closes the picker).
|