js-draw 1.24.2 → 1.26.0
Sign up to get free protection for your applications and to get access to all the features.
- package/LICENSE +1 -1
- package/README.md +15 -15
- package/dist/Editor.css +1 -1935
- package/dist/bundle.js +473 -4
- package/dist/bundledStyles.js +1 -1
- package/dist/cjs/Editor.d.ts +12 -0
- package/dist/cjs/Editor.js +2 -1
- package/dist/cjs/bundle/bundled.js +2 -1
- package/dist/cjs/commands/invertCommand.test.d.ts +1 -0
- package/dist/cjs/image/EditorImage.d.ts +2 -1
- package/dist/cjs/image/EditorImage.js +21 -6
- package/dist/cjs/testing/fillHtmlInput.d.ts +6 -0
- package/dist/cjs/testing/fillHtmlInput.js +22 -0
- package/dist/cjs/testing/sendKeyPressRelease.d.ts +2 -2
- package/dist/cjs/testing/sendKeyPressRelease.js +15 -3
- package/dist/cjs/toolbar/AbstractToolbar.js +9 -2
- package/dist/cjs/toolbar/widgets/BaseWidget.js +6 -1
- package/dist/cjs/toolbar/widgets/HandToolWidget.js +3 -3
- package/dist/cjs/tools/PasteHandler.d.ts +1 -1
- package/dist/cjs/tools/PasteHandler.js +12 -4
- package/dist/cjs/tools/PasteHandler.test.d.ts +1 -0
- package/dist/cjs/tools/SelectionTool/Selection.js +11 -6
- package/dist/cjs/tools/SelectionTool/SelectionMenuShortcut.d.ts +3 -1
- package/dist/cjs/tools/SelectionTool/SelectionMenuShortcut.js +13 -4
- package/dist/cjs/tools/TextTool.js +9 -2
- package/dist/cjs/util/ClipboardHandler.js +23 -1
- package/dist/cjs/util/assertions.d.ts +7 -6
- package/dist/cjs/util/assertions.js +35 -29
- package/dist/cjs/version.js +1 -1
- package/dist/mjs/Editor.d.ts +12 -0
- package/dist/mjs/Editor.mjs +2 -1
- package/dist/mjs/bundle/bundled.mjs +2 -1
- package/dist/mjs/commands/invertCommand.test.d.ts +1 -0
- package/dist/mjs/image/EditorImage.d.ts +2 -1
- package/dist/mjs/image/EditorImage.mjs +21 -6
- package/dist/mjs/testing/fillHtmlInput.d.ts +6 -0
- package/dist/mjs/testing/fillHtmlInput.mjs +17 -0
- package/dist/mjs/testing/sendKeyPressRelease.d.ts +2 -2
- package/dist/mjs/testing/sendKeyPressRelease.mjs +12 -3
- package/dist/mjs/toolbar/AbstractToolbar.mjs +9 -2
- package/dist/mjs/toolbar/widgets/BaseWidget.mjs +6 -1
- package/dist/mjs/toolbar/widgets/HandToolWidget.mjs +3 -3
- package/dist/mjs/tools/PasteHandler.d.ts +1 -1
- package/dist/mjs/tools/PasteHandler.mjs +12 -4
- package/dist/mjs/tools/PasteHandler.test.d.ts +1 -0
- package/dist/mjs/tools/SelectionTool/Selection.mjs +11 -6
- package/dist/mjs/tools/SelectionTool/SelectionMenuShortcut.d.ts +3 -1
- package/dist/mjs/tools/SelectionTool/SelectionMenuShortcut.mjs +13 -4
- package/dist/mjs/tools/TextTool.mjs +9 -2
- package/dist/mjs/util/ClipboardHandler.mjs +23 -1
- package/dist/mjs/util/assertions.d.ts +7 -6
- package/dist/mjs/util/assertions.mjs +28 -24
- package/dist/mjs/version.mjs +1 -1
- package/package.json +4 -4
- package/src/tools/SelectionTool/SelectionTool.scss +11 -1
- package/src/tools/util/createMenuOverlay.scss +2 -1
@@ -72,13 +72,13 @@ class Selection {
|
|
72
72
|
side: Vec2.of(0.5, 0),
|
73
73
|
icon: this.editor.icons.makeRotateIcon(),
|
74
74
|
}, this, this.editor.viewport, (startPoint) => this.transformers.rotate.onDragStart(startPoint), (currentPoint) => this.transformers.rotate.onDragUpdate(currentPoint), () => this.transformers.rotate.onDragEnd());
|
75
|
-
const menuToggleButton = new SelectionMenuShortcut(this, this.editor.viewport, showContextMenu, this.editor.localization);
|
75
|
+
const menuToggleButton = new SelectionMenuShortcut(this, this.editor.viewport, this.editor.icons.makeOverflowIcon(), showContextMenu, this.editor.localization);
|
76
76
|
this.childwidgets = [
|
77
|
+
menuToggleButton,
|
77
78
|
resizeBothHandle,
|
78
79
|
...resizeHorizontalHandles,
|
79
80
|
resizeVerticalHandle,
|
80
81
|
rotationHandle,
|
81
|
-
menuToggleButton,
|
82
82
|
];
|
83
83
|
for (const widget of this.childwidgets) {
|
84
84
|
widget.addTo(this.backgroundElem);
|
@@ -454,6 +454,7 @@ class Selection {
|
|
454
454
|
if (!wasTransforming) {
|
455
455
|
this.runSelectionDuplicatedAnimation();
|
456
456
|
}
|
457
|
+
let command;
|
457
458
|
if (wasTransforming) {
|
458
459
|
// Don't update the selection's focus when redoing/undoing
|
459
460
|
const selectionToUpdate = null;
|
@@ -463,16 +464,20 @@ class Selection {
|
|
463
464
|
await tmpApplyCommand.apply(this.editor);
|
464
465
|
// Show items again
|
465
466
|
this.addRemoveSelectionFromImage(true);
|
466
|
-
|
467
|
-
|
468
|
-
|
467
|
+
// With the transformation applied, create the duplicates
|
468
|
+
command = uniteCommands(this.selectedElems.map((elem) => {
|
469
|
+
return EditorImage.addElement(elem.clone());
|
470
|
+
}));
|
469
471
|
// Move the selected objects back to the correct location.
|
470
472
|
await tmpApplyCommand?.unapply(this.editor);
|
471
473
|
this.addRemoveSelectionFromImage(false);
|
472
474
|
this.previewTransformCmds();
|
473
475
|
this.updateUI();
|
474
476
|
}
|
475
|
-
|
477
|
+
else {
|
478
|
+
command = new Duplicate(this.selectedElems);
|
479
|
+
}
|
480
|
+
return command;
|
476
481
|
}
|
477
482
|
setHandlesVisible(showHandles) {
|
478
483
|
if (!showHandles) {
|
@@ -11,10 +11,12 @@ type OnShowContextMenu = (anchor: Point2) => void;
|
|
11
11
|
export default class SelectionMenuShortcut implements SelectionBoxChild {
|
12
12
|
private readonly parent;
|
13
13
|
private readonly viewport;
|
14
|
+
private readonly icon;
|
14
15
|
private localization;
|
15
16
|
private element;
|
17
|
+
private button;
|
16
18
|
private onClick;
|
17
|
-
constructor(parent: Selection, viewport: Viewport, showContextMenu: OnShowContextMenu, localization: ToolLocalization);
|
19
|
+
constructor(parent: Selection, viewport: Viewport, icon: Element, showContextMenu: OnShowContextMenu, localization: ToolLocalization);
|
18
20
|
private initUI;
|
19
21
|
addTo(container: HTMLElement): void;
|
20
22
|
remove(): void;
|
@@ -2,15 +2,17 @@ import { Rect2, Vec2 } from '@js-draw/math';
|
|
2
2
|
import { cssPrefix } from './SelectionTool.mjs';
|
3
3
|
const verticalOffset = 40;
|
4
4
|
export default class SelectionMenuShortcut {
|
5
|
-
constructor(parent, viewport, showContextMenu, localization) {
|
5
|
+
constructor(parent, viewport, icon, showContextMenu, localization) {
|
6
6
|
this.parent = parent;
|
7
7
|
this.viewport = viewport;
|
8
|
+
this.icon = icon;
|
8
9
|
this.localization = localization;
|
9
10
|
this.lastDragPointer = null;
|
10
11
|
this.element = document.createElement('div');
|
11
12
|
this.element.classList.add(`${cssPrefix}handle`, `${cssPrefix}selection-menu`);
|
12
13
|
this.element.style.setProperty('--vertical-offset', `${verticalOffset}px`);
|
13
14
|
this.onClick = () => {
|
15
|
+
this.button?.focus({ preventScroll: true });
|
14
16
|
const anchor = this.getBBoxCanvasCoords().center;
|
15
17
|
showContextMenu(anchor);
|
16
18
|
};
|
@@ -19,16 +21,22 @@ export default class SelectionMenuShortcut {
|
|
19
21
|
}
|
20
22
|
initUI() {
|
21
23
|
const button = document.createElement('button');
|
22
|
-
|
24
|
+
this.icon.classList.add('icon');
|
25
|
+
button.replaceChildren(this.icon);
|
23
26
|
button.ariaLabel = this.localization.selectionMenu__show;
|
24
27
|
button.title = button.ariaLabel;
|
28
|
+
this.button = button;
|
25
29
|
// To prevent editor event handlers from conflicting with those for the button,
|
26
30
|
// don't register a [click] handler. An onclick handler can be fired incorrectly
|
27
31
|
// in this case (in Chrome) after onClick is fired in onDragEnd, leading to a double
|
28
32
|
// on-click action.
|
29
33
|
button.onkeydown = (event) => {
|
30
|
-
if (event.key === 'Enter')
|
34
|
+
if (event.key === 'Enter') {
|
35
|
+
// .preventDefault prevents [Enter] from activating the first item in the
|
36
|
+
// selection menu.
|
37
|
+
event.preventDefault();
|
31
38
|
this.onClick();
|
39
|
+
}
|
32
40
|
};
|
33
41
|
this.element.appendChild(button);
|
34
42
|
// Update the bounding box of this in response to the new button.
|
@@ -58,7 +66,8 @@ export default class SelectionMenuShortcut {
|
|
58
66
|
const contentCanvasSize = this.getElementScreenSize().times(toCanvasScale);
|
59
67
|
const handleSizeCanvas = verticalOffset / this.viewport.getScaleFactor();
|
60
68
|
const topLeft = Vec2.of(parentCanvasRect.x, parentCanvasRect.y - handleSizeCanvas);
|
61
|
-
|
69
|
+
const minSize = Vec2.of(48, 48).times(toCanvasScale);
|
70
|
+
return new Rect2(topLeft.x, topLeft.y, contentCanvasSize.x, contentCanvasSize.y).grownToSize(minSize);
|
62
71
|
}
|
63
72
|
updatePosition() {
|
64
73
|
const bbox = this.getBBoxParentCoords();
|
@@ -158,17 +158,24 @@ export default class TextTool extends BaseTool {
|
|
158
158
|
}
|
159
159
|
};
|
160
160
|
this.textInputElem.onblur = () => {
|
161
|
+
const input = this.textInputElem;
|
161
162
|
// Delay removing the input -- flushInput may be called within a blur()
|
162
163
|
// event handler
|
163
164
|
const removeInput = false;
|
164
|
-
const input = this.textInputElem;
|
165
165
|
this.flushInput(removeInput);
|
166
166
|
this.textInputElem = null;
|
167
|
+
if (input) {
|
168
|
+
input.classList.add('-hiding');
|
169
|
+
}
|
167
170
|
setTimeout(() => {
|
168
171
|
input?.remove();
|
169
172
|
}, 0);
|
170
173
|
};
|
171
174
|
this.textInputElem.onkeyup = (evt) => {
|
175
|
+
// In certain input modes, the <enter> key is used to select characters.
|
176
|
+
// When in this mode, prevent <enter> from submitting:
|
177
|
+
if (evt.isComposing)
|
178
|
+
return;
|
172
179
|
if (evt.key === 'Enter' && !evt.shiftKey) {
|
173
180
|
this.flushInput();
|
174
181
|
this.editor.focus();
|
@@ -199,7 +206,7 @@ export default class TextTool extends BaseTool {
|
|
199
206
|
if (allPointers.length === 1) {
|
200
207
|
// Are we clicking on a text node?
|
201
208
|
const canvasPos = current.canvasPos;
|
202
|
-
const halfTestRegionSize = Vec2.of(
|
209
|
+
const halfTestRegionSize = Vec2.of(4, 4).times(this.editor.viewport.getSizeOfPixelOnCanvas());
|
203
210
|
const testRegion = Rect2.fromCorners(canvasPos.minus(halfTestRegionSize), canvasPos.plus(halfTestRegionSize));
|
204
211
|
const targetNodes = this.editor.image.getElementsIntersectingRegion(testRegion);
|
205
212
|
let targetTextNodes = targetNodes.filter((node) => node instanceof TextComponent);
|
@@ -70,6 +70,7 @@ class ClipboardHandler {
|
|
70
70
|
const supportedMIMEs = ['image/svg+xml', 'text/html', 'image/png', 'image/jpeg', 'text/plain'];
|
71
71
|
let files = [];
|
72
72
|
const textData = new Map();
|
73
|
+
const editorSettings = editor.getCurrentSettings();
|
73
74
|
if (hasEvent) {
|
74
75
|
// NOTE: On some browsers, .getData and .files must be used before any async operations.
|
75
76
|
files = [...clipboardData.files];
|
@@ -80,6 +81,21 @@ class ClipboardHandler {
|
|
80
81
|
}
|
81
82
|
}
|
82
83
|
}
|
84
|
+
else if (editorSettings.clipboardApi) {
|
85
|
+
const clipboardData = await editorSettings.clipboardApi.read();
|
86
|
+
for (const [type, data] of clipboardData.entries()) {
|
87
|
+
if (typeof data === 'string') {
|
88
|
+
textData.set(type, data);
|
89
|
+
}
|
90
|
+
else {
|
91
|
+
let blob = data;
|
92
|
+
if (blob.type !== type) {
|
93
|
+
blob = new Blob([blob], { type });
|
94
|
+
}
|
95
|
+
files.push(blob);
|
96
|
+
}
|
97
|
+
}
|
98
|
+
}
|
83
99
|
else {
|
84
100
|
const clipboardData = await navigator.clipboard.read();
|
85
101
|
for (const item of clipboardData) {
|
@@ -233,7 +249,13 @@ class ClipboardHandler {
|
|
233
249
|
return navigator.clipboard.write([new ClipboardItem(browserMimeToData)]);
|
234
250
|
};
|
235
251
|
const supportsClipboardApi = typeof ClipboardItem !== 'undefined' && typeof navigator?.clipboard?.write !== 'undefined';
|
236
|
-
|
252
|
+
const prefersClipboardApi = !__classPrivateFieldGet(this, _ClipboardHandler_preferClipboardEvents, "f") && supportsClipboardApi && (hasNonTextMimeTypes || !event);
|
253
|
+
const editorSettings = this.editor.getCurrentSettings();
|
254
|
+
if (prefersClipboardApi && editorSettings.clipboardApi) {
|
255
|
+
const writeResult = editorSettings.clipboardApi.write(mimeToData);
|
256
|
+
return writeResult ?? Promise.resolve();
|
257
|
+
}
|
258
|
+
else if (prefersClipboardApi) {
|
237
259
|
let clipboardApiPromise = null;
|
238
260
|
const fallBackToCopyEvent = (reason) => {
|
239
261
|
console.warn('Unable to copy to the clipboard API. Future calls to .copy will use ClipboardEvents if possible.', reason);
|
@@ -2,7 +2,7 @@
|
|
2
2
|
* Compile-time assertion that a branch of code is unreachable.
|
3
3
|
* @internal
|
4
4
|
*/
|
5
|
-
export declare
|
5
|
+
export declare function assertUnreachable(key: never): never;
|
6
6
|
/**
|
7
7
|
* Throws an exception if the typeof given value is not a number or `value` is NaN.
|
8
8
|
*
|
@@ -13,15 +13,16 @@ export declare const assertUnreachable: (key: never) => never;
|
|
13
13
|
*
|
14
14
|
* assertIsNumber('hello, world'); // throws an Error.
|
15
15
|
* ```
|
16
|
-
*
|
17
|
-
*
|
18
16
|
*/
|
19
|
-
export declare
|
17
|
+
export declare function assertIsNumber(value: unknown, allowNaN?: boolean): asserts value is number;
|
18
|
+
export declare function assertIsArray(values: unknown): asserts values is unknown[];
|
20
19
|
/**
|
21
20
|
* Throws if any of `values` is not of type number.
|
22
21
|
*/
|
23
|
-
export declare
|
22
|
+
export declare function assertIsNumberArray(values: unknown, allowNaN?: boolean): asserts values is number[];
|
24
23
|
/**
|
25
24
|
* Throws an exception if `typeof value` is not a boolean.
|
26
25
|
*/
|
27
|
-
export declare
|
26
|
+
export declare function assertIsBoolean(value: unknown): asserts value is boolean;
|
27
|
+
export declare function assertTruthy<T>(value: T | null | undefined | false | 0): asserts value is T;
|
28
|
+
export declare function assertIsObject(value: unknown): asserts value is Record<string, unknown>;
|
@@ -1,11 +1,13 @@
|
|
1
|
+
// Note: Arrow functions cannot be used for type assertions. See
|
2
|
+
// https://github.com/microsoft/TypeScript/issues/34523
|
1
3
|
/**
|
2
4
|
* Compile-time assertion that a branch of code is unreachable.
|
3
5
|
* @internal
|
4
6
|
*/
|
5
|
-
export
|
7
|
+
export function assertUnreachable(key) {
|
6
8
|
// See https://stackoverflow.com/a/39419171/17055750
|
7
9
|
throw new Error(`Should be unreachable. Key: ${key}.`);
|
8
|
-
}
|
10
|
+
}
|
9
11
|
/**
|
10
12
|
* Throws an exception if the typeof given value is not a number or `value` is NaN.
|
11
13
|
*
|
@@ -16,40 +18,42 @@ export const assertUnreachable = (key) => {
|
|
16
18
|
*
|
17
19
|
* assertIsNumber('hello, world'); // throws an Error.
|
18
20
|
* ```
|
19
|
-
*
|
20
|
-
*
|
21
21
|
*/
|
22
|
-
export
|
22
|
+
export function assertIsNumber(value, allowNaN = false) {
|
23
23
|
if (typeof value !== 'number' || (!allowNaN && isNaN(value))) {
|
24
24
|
throw new Error('Given value is not a number');
|
25
|
-
// return false;
|
26
25
|
}
|
27
|
-
|
28
|
-
|
26
|
+
}
|
27
|
+
export function assertIsArray(values) {
|
28
|
+
if (!Array.isArray(values)) {
|
29
|
+
throw new Error('Asserting isArray: Given entity is not an array');
|
30
|
+
}
|
31
|
+
}
|
29
32
|
/**
|
30
33
|
* Throws if any of `values` is not of type number.
|
31
34
|
*/
|
32
|
-
export
|
33
|
-
|
34
|
-
|
35
|
-
}
|
36
|
-
if (!assertIsNumber(values['length'])) {
|
37
|
-
return false;
|
38
|
-
}
|
35
|
+
export function assertIsNumberArray(values, allowNaN = false) {
|
36
|
+
assertIsArray(values);
|
37
|
+
assertIsNumber(values.length);
|
39
38
|
for (const value of values) {
|
40
|
-
|
41
|
-
return false;
|
42
|
-
}
|
39
|
+
assertIsNumber(value, allowNaN);
|
43
40
|
}
|
44
|
-
|
45
|
-
};
|
41
|
+
}
|
46
42
|
/**
|
47
43
|
* Throws an exception if `typeof value` is not a boolean.
|
48
44
|
*/
|
49
|
-
export
|
45
|
+
export function assertIsBoolean(value) {
|
50
46
|
if (typeof value !== 'boolean') {
|
51
47
|
throw new Error('Given value is not a boolean');
|
52
|
-
// return false;
|
53
48
|
}
|
54
|
-
|
55
|
-
|
49
|
+
}
|
50
|
+
export function assertTruthy(value) {
|
51
|
+
if (!value) {
|
52
|
+
throw new Error(`${JSON.stringify(value)} is not truthy`);
|
53
|
+
}
|
54
|
+
}
|
55
|
+
export function assertIsObject(value) {
|
56
|
+
if (typeof value !== 'object') {
|
57
|
+
throw new Error(`AssertIsObject: Given entity is not an object (type = ${typeof value})`);
|
58
|
+
}
|
59
|
+
}
|
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.26.0",
|
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.
|
67
|
+
"@js-draw/math": "^1.26.0",
|
68
68
|
"@melloware/coloris": "0.22.0"
|
69
69
|
},
|
70
70
|
"devDependencies": {
|
71
|
-
"@js-draw/build-tool": "^1.
|
71
|
+
"@js-draw/build-tool": "^1.26.0",
|
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": "6529cad584ca93a992f2576a43f25af48da3d707"
|
90
90
|
}
|
@@ -85,12 +85,17 @@
|
|
85
85
|
> button {
|
86
86
|
max-height: var(--vertical-offset);
|
87
87
|
background-color: var(--background-color-1);
|
88
|
+
|
89
|
+
width: 24px;
|
90
|
+
height: 24px;
|
91
|
+
padding: 6px;
|
88
92
|
font-size: 14px;
|
93
|
+
user-select: none;
|
94
|
+
-webkit-user-select: none;
|
89
95
|
|
90
96
|
color: var(--foreground-color-1);
|
91
97
|
border: 0.5px solid var(--foreground-color-1);
|
92
98
|
border-radius: 3px;
|
93
|
-
padding: 3px;
|
94
99
|
opacity: 0.8;
|
95
100
|
|
96
101
|
&:hover,
|
@@ -102,6 +107,11 @@
|
|
102
107
|
}
|
103
108
|
|
104
109
|
transition: 0.2s ease opacity;
|
110
|
+
|
111
|
+
> .icon {
|
112
|
+
width: 100%;
|
113
|
+
height: 100%;
|
114
|
+
}
|
105
115
|
}
|
106
116
|
}
|
107
117
|
|
@@ -32,6 +32,7 @@
|
|
32
32
|
overflow: clip;
|
33
33
|
border-radius: 6px;
|
34
34
|
box-shadow: 0px 1px 2px var(--shadow-color);
|
35
|
+
background-color: var(--background-color-1);
|
35
36
|
}
|
36
37
|
|
37
38
|
&::backdrop {
|
@@ -47,7 +48,7 @@
|
|
47
48
|
padding-top: 6px;
|
48
49
|
padding-bottom: 6px;
|
49
50
|
|
50
|
-
background-color:
|
51
|
+
background-color: transparent;
|
51
52
|
color: var(--foreground-color-1);
|
52
53
|
--icon-color: currentColor;
|
53
54
|
|