js-draw 1.24.1 → 1.25.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. package/README.md +15 -15
  2. package/dist/bundle.js +1 -1
  3. package/dist/cjs/Editor.d.ts +12 -0
  4. package/dist/cjs/Editor.js +1 -0
  5. package/dist/cjs/commands/invertCommand.test.d.ts +1 -0
  6. package/dist/cjs/components/TextComponent.d.ts +11 -0
  7. package/dist/cjs/components/TextComponent.js +14 -3
  8. package/dist/cjs/testing/fillHtmlInput.d.ts +6 -0
  9. package/dist/cjs/testing/fillHtmlInput.js +22 -0
  10. package/dist/cjs/testing/sendKeyPressRelease.d.ts +2 -2
  11. package/dist/cjs/testing/sendKeyPressRelease.js +15 -3
  12. package/dist/cjs/tools/PasteHandler.d.ts +1 -1
  13. package/dist/cjs/tools/PasteHandler.js +12 -4
  14. package/dist/cjs/tools/PasteHandler.test.d.ts +1 -0
  15. package/dist/cjs/tools/TextTool.js +4 -0
  16. package/dist/cjs/util/ClipboardHandler.js +23 -1
  17. package/dist/cjs/version.js +1 -1
  18. package/dist/mjs/Editor.d.ts +12 -0
  19. package/dist/mjs/Editor.mjs +1 -0
  20. package/dist/mjs/commands/invertCommand.test.d.ts +1 -0
  21. package/dist/mjs/components/TextComponent.d.ts +11 -0
  22. package/dist/mjs/components/TextComponent.mjs +14 -3
  23. package/dist/mjs/testing/fillHtmlInput.d.ts +6 -0
  24. package/dist/mjs/testing/fillHtmlInput.mjs +17 -0
  25. package/dist/mjs/testing/sendKeyPressRelease.d.ts +2 -2
  26. package/dist/mjs/testing/sendKeyPressRelease.mjs +12 -3
  27. package/dist/mjs/tools/PasteHandler.d.ts +1 -1
  28. package/dist/mjs/tools/PasteHandler.mjs +12 -4
  29. package/dist/mjs/tools/PasteHandler.test.d.ts +1 -0
  30. package/dist/mjs/tools/TextTool.mjs +4 -0
  31. package/dist/mjs/util/ClipboardHandler.mjs +23 -1
  32. package/dist/mjs/version.mjs +1 -1
  33. package/package.json +4 -4
@@ -121,6 +121,18 @@ export interface EditorSettings {
121
121
  */
122
122
  showImagePicker?: ShowCustomFilePickerCallback;
123
123
  } | null;
124
+ /**
125
+ * Allows changing how js-draw interacts with the clipboard.
126
+ *
127
+ * **Note**: Even when a custom `clipboardApi` is specified, if a `ClipboardEvent` is available
128
+ * (e.g. from when a user pastes with ctrl+v), the `ClipboardEvent` will be preferred.
129
+ */
130
+ clipboardApi: {
131
+ /** Called to read data to the clipboard. Keys in the result are MIME types. Values are the data associated with that type. */
132
+ read(): Promise<Map<string, Blob | string>>;
133
+ /** Called to write data to the clipboard. Keys in `data` are MIME types. Values are the data associated with that type. */
134
+ write(data: Map<string, Blob | Promise<Blob> | string>): void | Promise<void>;
135
+ } | null;
124
136
  }
125
137
  /**
126
138
  * The main entrypoint for the full editor.
@@ -159,6 +159,7 @@ class Editor {
159
159
  image: {
160
160
  showImagePicker: settings.image?.showImagePicker ?? undefined,
161
161
  },
162
+ clipboardApi: settings.clipboardApi ?? null,
162
163
  };
163
164
  // Validate settings
164
165
  if (this.settings.minZoom > this.settings.maxZoom) {
@@ -0,0 +1 @@
1
+ export {};
@@ -76,6 +76,17 @@ export default class TextComponent extends AbstractComponent implements Restylea
76
76
  private static getFontHeight;
77
77
  private computeUntransformedBBoxOfPart;
78
78
  private recomputeBBox;
79
+ /**
80
+ * Renders a TextComponent or a TextComponent child onto a `canvas`.
81
+ *
82
+ * `visibleRect` can be provided as a performance optimization. If not the top-level
83
+ * text node, `baseTransform` (specifies the transformation of the parent text component
84
+ * in canvas space) should also be provided.
85
+ *
86
+ * Note that passing a `baseTransform` is preferable to transforming `visibleRect`. At high
87
+ * zoom levels, transforming `visibleRect` by the inverse of the parent transform can lead to
88
+ * inaccuracy due to precision loss.
89
+ */
79
90
  private renderInternal;
80
91
  render(canvas: AbstractRenderer, visibleRect?: Rect2): void;
81
92
  getProportionalRenderingTime(): number;
@@ -154,11 +154,22 @@ class TextComponent extends AbstractComponent_1.default {
154
154
  }
155
155
  this.contentBBox = bbox ?? math_1.Rect2.empty;
156
156
  }
157
- renderInternal(canvas, visibleRect) {
157
+ /**
158
+ * Renders a TextComponent or a TextComponent child onto a `canvas`.
159
+ *
160
+ * `visibleRect` can be provided as a performance optimization. If not the top-level
161
+ * text node, `baseTransform` (specifies the transformation of the parent text component
162
+ * in canvas space) should also be provided.
163
+ *
164
+ * Note that passing a `baseTransform` is preferable to transforming `visibleRect`. At high
165
+ * zoom levels, transforming `visibleRect` by the inverse of the parent transform can lead to
166
+ * inaccuracy due to precision loss.
167
+ */
168
+ renderInternal(canvas, visibleRect, baseTransform = math_1.Mat33.identity) {
158
169
  const cursor = new TextComponent.TextCursor(this.transform, this.style);
159
170
  for (const textObject of this.textObjects) {
160
171
  const { transform, bbox } = cursor.update(textObject);
161
- if (visibleRect && !visibleRect.intersects(bbox)) {
172
+ if (visibleRect && !visibleRect.intersects(bbox.transformedBoundingBox(baseTransform))) {
162
173
  continue;
163
174
  }
164
175
  if (typeof textObject === 'string') {
@@ -166,7 +177,7 @@ class TextComponent extends AbstractComponent_1.default {
166
177
  }
167
178
  else {
168
179
  canvas.pushTransform(transform);
169
- textObject.renderInternal(canvas, visibleRect?.transformedBoundingBox(transform.inverse()));
180
+ textObject.renderInternal(canvas, visibleRect, baseTransform.rightMul(transform));
170
181
  canvas.popTransform();
171
182
  }
172
183
  }
@@ -0,0 +1,6 @@
1
+ interface Options {
2
+ clear?: boolean;
3
+ }
4
+ /** Sets the content of the given `input` or textarea to be `text`. */
5
+ declare const fillInput: (input: HTMLInputElement | HTMLTextAreaElement, text: string, { clear }?: Options) => void;
6
+ export default fillInput;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const sendKeyPressRelease_1 = __importDefault(require("./sendKeyPressRelease"));
7
+ /** Sets the content of the given `input` or textarea to be `text`. */
8
+ const fillInput = (input, text, { clear = false } = {}) => {
9
+ const dispatchUpdate = () => {
10
+ input.dispatchEvent(new InputEvent('input'));
11
+ };
12
+ if (clear) {
13
+ input.value = '';
14
+ dispatchUpdate();
15
+ }
16
+ for (const character of text.split('')) {
17
+ input.value += character;
18
+ (0, sendKeyPressRelease_1.default)(input, character);
19
+ dispatchUpdate();
20
+ }
21
+ };
22
+ exports.default = fillInput;
@@ -1,3 +1,3 @@
1
- import type Editor from '../Editor';
2
- declare const sendKeyPressRelease: (editor: Editor, key: string) => void;
1
+ import Editor from '../Editor';
2
+ declare const sendKeyPressRelease: (target: Editor | HTMLElement, key: string) => void;
3
3
  export default sendKeyPressRelease;
@@ -1,8 +1,20 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ const Editor_1 = __importDefault(require("../Editor"));
3
7
  const inputEvents_1 = require("../inputEvents");
4
- const sendKeyPressRelease = (editor, key) => {
5
- editor.sendKeyboardEvent(inputEvents_1.InputEvtType.KeyPressEvent, key);
6
- editor.sendKeyboardEvent(inputEvents_1.InputEvtType.KeyUpEvent, key);
8
+ const guessKeyCodeFromKey_1 = __importDefault(require("../util/guessKeyCodeFromKey"));
9
+ const sendKeyPressRelease = (target, key) => {
10
+ if (target instanceof Editor_1.default) {
11
+ target.sendKeyboardEvent(inputEvents_1.InputEvtType.KeyPressEvent, key);
12
+ target.sendKeyboardEvent(inputEvents_1.InputEvtType.KeyUpEvent, key);
13
+ }
14
+ else {
15
+ const code = (0, guessKeyCodeFromKey_1.default)(key);
16
+ target.dispatchEvent(new KeyboardEvent('keydown', { key, code }));
17
+ target.dispatchEvent(new KeyboardEvent('keyup', { key, code }));
18
+ }
7
19
  };
8
20
  exports.default = sendKeyPressRelease;
@@ -15,7 +15,7 @@ import BaseTool from './BaseTool';
15
15
  export default class PasteHandler extends BaseTool {
16
16
  private editor;
17
17
  constructor(editor: Editor);
18
- onPaste(event: PasteEvent): boolean;
18
+ onPaste(event: PasteEvent, onComplete?: () => void): boolean;
19
19
  private addComponentsFromPaste;
20
20
  private doSVGPaste;
21
21
  private doTextPaste;
@@ -26,12 +26,20 @@ class PasteHandler extends BaseTool_1.default {
26
26
  this.editor = editor;
27
27
  }
28
28
  // @internal
29
- onPaste(event) {
29
+ onPaste(event, onComplete) {
30
30
  const mime = event.mime.toLowerCase();
31
31
  const svgData = (() => {
32
32
  if (mime === 'image/svg+xml') {
33
33
  return event.data;
34
34
  }
35
+ // In some environments, it isn't possible to write non-text data to the
36
+ // clipboard. To support these cases, auto-detect text/plain SVG data.
37
+ if (mime === 'text/plain') {
38
+ const trimmedData = event.data.trim();
39
+ if (trimmedData.startsWith('<svg') && trimmedData.endsWith('</svg>')) {
40
+ return trimmedData;
41
+ }
42
+ }
35
43
  if (mime !== 'text/html') {
36
44
  return false;
37
45
  }
@@ -49,15 +57,15 @@ class PasteHandler extends BaseTool_1.default {
49
57
  return event.data.substring(event.data.search(/<svg/i), svgEnd);
50
58
  })();
51
59
  if (svgData) {
52
- void this.doSVGPaste(svgData);
60
+ void this.doSVGPaste(svgData).then(onComplete);
53
61
  return true;
54
62
  }
55
63
  else if (mime === 'text/plain') {
56
- void this.doTextPaste(event.data);
64
+ void this.doTextPaste(event.data).then(onComplete);
57
65
  return true;
58
66
  }
59
67
  else if (mime === 'image/png' || mime === 'image/jpeg') {
60
- void this.doImagePaste(event.data);
68
+ void this.doImagePaste(event.data).then(onComplete);
61
69
  return true;
62
70
  }
63
71
  return false;
@@ -0,0 +1 @@
1
+ export {};
@@ -174,6 +174,10 @@ class TextTool extends BaseTool_1.default {
174
174
  }, 0);
175
175
  };
176
176
  this.textInputElem.onkeyup = (evt) => {
177
+ // In certain input modes, the <enter> key is used to select characters.
178
+ // When in this mode, prevent <enter> from submitting:
179
+ if (evt.isComposing)
180
+ return;
177
181
  if (evt.key === 'Enter' && !evt.shiftKey) {
178
182
  this.flushInput();
179
183
  this.editor.focus();
@@ -75,6 +75,7 @@ class ClipboardHandler {
75
75
  const supportedMIMEs = ['image/svg+xml', 'text/html', 'image/png', 'image/jpeg', 'text/plain'];
76
76
  let files = [];
77
77
  const textData = new Map();
78
+ const editorSettings = editor.getCurrentSettings();
78
79
  if (hasEvent) {
79
80
  // NOTE: On some browsers, .getData and .files must be used before any async operations.
80
81
  files = [...clipboardData.files];
@@ -85,6 +86,21 @@ class ClipboardHandler {
85
86
  }
86
87
  }
87
88
  }
89
+ else if (editorSettings.clipboardApi) {
90
+ const clipboardData = await editorSettings.clipboardApi.read();
91
+ for (const [type, data] of clipboardData.entries()) {
92
+ if (typeof data === 'string') {
93
+ textData.set(type, data);
94
+ }
95
+ else {
96
+ let blob = data;
97
+ if (blob.type !== type) {
98
+ blob = new Blob([blob], { type });
99
+ }
100
+ files.push(blob);
101
+ }
102
+ }
103
+ }
88
104
  else {
89
105
  const clipboardData = await navigator.clipboard.read();
90
106
  for (const item of clipboardData) {
@@ -238,7 +254,13 @@ class ClipboardHandler {
238
254
  return navigator.clipboard.write([new ClipboardItem(browserMimeToData)]);
239
255
  };
240
256
  const supportsClipboardApi = typeof ClipboardItem !== 'undefined' && typeof navigator?.clipboard?.write !== 'undefined';
241
- if (!__classPrivateFieldGet(this, _ClipboardHandler_preferClipboardEvents, "f") && supportsClipboardApi && (hasNonTextMimeTypes || !event)) {
257
+ const prefersClipboardApi = !__classPrivateFieldGet(this, _ClipboardHandler_preferClipboardEvents, "f") && supportsClipboardApi && (hasNonTextMimeTypes || !event);
258
+ const editorSettings = this.editor.getCurrentSettings();
259
+ if (prefersClipboardApi && editorSettings.clipboardApi) {
260
+ const writeResult = editorSettings.clipboardApi.write(mimeToData);
261
+ return writeResult ?? Promise.resolve();
262
+ }
263
+ else if (prefersClipboardApi) {
242
264
  let clipboardApiPromise = null;
243
265
  const fallBackToCopyEvent = (reason) => {
244
266
  console.warn('Unable to copy to the clipboard API. Future calls to .copy will use ClipboardEvents if possible.', reason);
@@ -6,5 +6,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  * @internal
7
7
  */
8
8
  exports.default = {
9
- number: '1.24.1',
9
+ number: '1.25.0',
10
10
  };
@@ -121,6 +121,18 @@ export interface EditorSettings {
121
121
  */
122
122
  showImagePicker?: ShowCustomFilePickerCallback;
123
123
  } | null;
124
+ /**
125
+ * Allows changing how js-draw interacts with the clipboard.
126
+ *
127
+ * **Note**: Even when a custom `clipboardApi` is specified, if a `ClipboardEvent` is available
128
+ * (e.g. from when a user pastes with ctrl+v), the `ClipboardEvent` will be preferred.
129
+ */
130
+ clipboardApi: {
131
+ /** Called to read data to the clipboard. Keys in the result are MIME types. Values are the data associated with that type. */
132
+ read(): Promise<Map<string, Blob | string>>;
133
+ /** Called to write data to the clipboard. Keys in `data` are MIME types. Values are the data associated with that type. */
134
+ write(data: Map<string, Blob | Promise<Blob> | string>): void | Promise<void>;
135
+ } | null;
124
136
  }
125
137
  /**
126
138
  * The main entrypoint for the full editor.
@@ -120,6 +120,7 @@ export class Editor {
120
120
  image: {
121
121
  showImagePicker: settings.image?.showImagePicker ?? undefined,
122
122
  },
123
+ clipboardApi: settings.clipboardApi ?? null,
123
124
  };
124
125
  // Validate settings
125
126
  if (this.settings.minZoom > this.settings.maxZoom) {
@@ -0,0 +1 @@
1
+ export {};
@@ -76,6 +76,17 @@ export default class TextComponent extends AbstractComponent implements Restylea
76
76
  private static getFontHeight;
77
77
  private computeUntransformedBBoxOfPart;
78
78
  private recomputeBBox;
79
+ /**
80
+ * Renders a TextComponent or a TextComponent child onto a `canvas`.
81
+ *
82
+ * `visibleRect` can be provided as a performance optimization. If not the top-level
83
+ * text node, `baseTransform` (specifies the transformation of the parent text component
84
+ * in canvas space) should also be provided.
85
+ *
86
+ * Note that passing a `baseTransform` is preferable to transforming `visibleRect`. At high
87
+ * zoom levels, transforming `visibleRect` by the inverse of the parent transform can lead to
88
+ * inaccuracy due to precision loss.
89
+ */
79
90
  private renderInternal;
80
91
  render(canvas: AbstractRenderer, visibleRect?: Rect2): void;
81
92
  getProportionalRenderingTime(): number;
@@ -148,11 +148,22 @@ class TextComponent extends AbstractComponent {
148
148
  }
149
149
  this.contentBBox = bbox ?? Rect2.empty;
150
150
  }
151
- renderInternal(canvas, visibleRect) {
151
+ /**
152
+ * Renders a TextComponent or a TextComponent child onto a `canvas`.
153
+ *
154
+ * `visibleRect` can be provided as a performance optimization. If not the top-level
155
+ * text node, `baseTransform` (specifies the transformation of the parent text component
156
+ * in canvas space) should also be provided.
157
+ *
158
+ * Note that passing a `baseTransform` is preferable to transforming `visibleRect`. At high
159
+ * zoom levels, transforming `visibleRect` by the inverse of the parent transform can lead to
160
+ * inaccuracy due to precision loss.
161
+ */
162
+ renderInternal(canvas, visibleRect, baseTransform = Mat33.identity) {
152
163
  const cursor = new TextComponent.TextCursor(this.transform, this.style);
153
164
  for (const textObject of this.textObjects) {
154
165
  const { transform, bbox } = cursor.update(textObject);
155
- if (visibleRect && !visibleRect.intersects(bbox)) {
166
+ if (visibleRect && !visibleRect.intersects(bbox.transformedBoundingBox(baseTransform))) {
156
167
  continue;
157
168
  }
158
169
  if (typeof textObject === 'string') {
@@ -160,7 +171,7 @@ class TextComponent extends AbstractComponent {
160
171
  }
161
172
  else {
162
173
  canvas.pushTransform(transform);
163
- textObject.renderInternal(canvas, visibleRect?.transformedBoundingBox(transform.inverse()));
174
+ textObject.renderInternal(canvas, visibleRect, baseTransform.rightMul(transform));
164
175
  canvas.popTransform();
165
176
  }
166
177
  }
@@ -0,0 +1,6 @@
1
+ interface Options {
2
+ clear?: boolean;
3
+ }
4
+ /** Sets the content of the given `input` or textarea to be `text`. */
5
+ declare const fillInput: (input: HTMLInputElement | HTMLTextAreaElement, text: string, { clear }?: Options) => void;
6
+ export default fillInput;
@@ -0,0 +1,17 @@
1
+ import sendKeyPressRelease from './sendKeyPressRelease.mjs';
2
+ /** Sets the content of the given `input` or textarea to be `text`. */
3
+ const fillInput = (input, text, { clear = false } = {}) => {
4
+ const dispatchUpdate = () => {
5
+ input.dispatchEvent(new InputEvent('input'));
6
+ };
7
+ if (clear) {
8
+ input.value = '';
9
+ dispatchUpdate();
10
+ }
11
+ for (const character of text.split('')) {
12
+ input.value += character;
13
+ sendKeyPressRelease(input, character);
14
+ dispatchUpdate();
15
+ }
16
+ };
17
+ export default fillInput;
@@ -1,3 +1,3 @@
1
- import type Editor from '../Editor';
2
- declare const sendKeyPressRelease: (editor: Editor, key: string) => void;
1
+ import Editor from '../Editor';
2
+ declare const sendKeyPressRelease: (target: Editor | HTMLElement, key: string) => void;
3
3
  export default sendKeyPressRelease;
@@ -1,6 +1,15 @@
1
+ import Editor from '../Editor.mjs';
1
2
  import { InputEvtType } from '../inputEvents.mjs';
2
- const sendKeyPressRelease = (editor, key) => {
3
- editor.sendKeyboardEvent(InputEvtType.KeyPressEvent, key);
4
- editor.sendKeyboardEvent(InputEvtType.KeyUpEvent, key);
3
+ import guessKeyCodeFromKey from '../util/guessKeyCodeFromKey.mjs';
4
+ const sendKeyPressRelease = (target, key) => {
5
+ if (target instanceof Editor) {
6
+ target.sendKeyboardEvent(InputEvtType.KeyPressEvent, key);
7
+ target.sendKeyboardEvent(InputEvtType.KeyUpEvent, key);
8
+ }
9
+ else {
10
+ const code = guessKeyCodeFromKey(key);
11
+ target.dispatchEvent(new KeyboardEvent('keydown', { key, code }));
12
+ target.dispatchEvent(new KeyboardEvent('keyup', { key, code }));
13
+ }
5
14
  };
6
15
  export default sendKeyPressRelease;
@@ -15,7 +15,7 @@ import BaseTool from './BaseTool';
15
15
  export default class PasteHandler extends BaseTool {
16
16
  private editor;
17
17
  constructor(editor: Editor);
18
- onPaste(event: PasteEvent): boolean;
18
+ onPaste(event: PasteEvent, onComplete?: () => void): boolean;
19
19
  private addComponentsFromPaste;
20
20
  private doSVGPaste;
21
21
  private doTextPaste;
@@ -21,12 +21,20 @@ export default class PasteHandler extends BaseTool {
21
21
  this.editor = editor;
22
22
  }
23
23
  // @internal
24
- onPaste(event) {
24
+ onPaste(event, onComplete) {
25
25
  const mime = event.mime.toLowerCase();
26
26
  const svgData = (() => {
27
27
  if (mime === 'image/svg+xml') {
28
28
  return event.data;
29
29
  }
30
+ // In some environments, it isn't possible to write non-text data to the
31
+ // clipboard. To support these cases, auto-detect text/plain SVG data.
32
+ if (mime === 'text/plain') {
33
+ const trimmedData = event.data.trim();
34
+ if (trimmedData.startsWith('<svg') && trimmedData.endsWith('</svg>')) {
35
+ return trimmedData;
36
+ }
37
+ }
30
38
  if (mime !== 'text/html') {
31
39
  return false;
32
40
  }
@@ -44,15 +52,15 @@ export default class PasteHandler extends BaseTool {
44
52
  return event.data.substring(event.data.search(/<svg/i), svgEnd);
45
53
  })();
46
54
  if (svgData) {
47
- void this.doSVGPaste(svgData);
55
+ void this.doSVGPaste(svgData).then(onComplete);
48
56
  return true;
49
57
  }
50
58
  else if (mime === 'text/plain') {
51
- void this.doTextPaste(event.data);
59
+ void this.doTextPaste(event.data).then(onComplete);
52
60
  return true;
53
61
  }
54
62
  else if (mime === 'image/png' || mime === 'image/jpeg') {
55
- void this.doImagePaste(event.data);
63
+ void this.doImagePaste(event.data).then(onComplete);
56
64
  return true;
57
65
  }
58
66
  return false;
@@ -0,0 +1 @@
1
+ export {};
@@ -169,6 +169,10 @@ export default class TextTool extends BaseTool {
169
169
  }, 0);
170
170
  };
171
171
  this.textInputElem.onkeyup = (evt) => {
172
+ // In certain input modes, the <enter> key is used to select characters.
173
+ // When in this mode, prevent <enter> from submitting:
174
+ if (evt.isComposing)
175
+ return;
172
176
  if (evt.key === 'Enter' && !evt.shiftKey) {
173
177
  this.flushInput();
174
178
  this.editor.focus();
@@ -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
- if (!__classPrivateFieldGet(this, _ClipboardHandler_preferClipboardEvents, "f") && supportsClipboardApi && (hasNonTextMimeTypes || !event)) {
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);
@@ -4,5 +4,5 @@
4
4
  * @internal
5
5
  */
6
6
  export default {
7
- number: '1.24.1',
7
+ number: '1.25.0',
8
8
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "1.24.1",
3
+ "version": "1.25.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.24.1",
67
+ "@js-draw/math": "^1.25.0",
68
68
  "@melloware/coloris": "0.22.0"
69
69
  },
70
70
  "devDependencies": {
71
- "@js-draw/build-tool": "^1.24.1",
71
+ "@js-draw/build-tool": "^1.25.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": "ef847374748e32d6d96d993a2236a99d9109a32c"
89
+ "gitHead": "8ecc7be6d8b1b00c25fe7d3ed6c5fee239451dfa"
90
90
  }