js-draw 1.24.2 → 1.26.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/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
|
|