js-draw 0.3.1 → 0.3.2

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.
Files changed (84) hide show
  1. package/.github/ISSUE_TEMPLATE/translation.md +4 -1
  2. package/CHANGELOG.md +8 -0
  3. package/dist/bundle.js +1 -1
  4. package/dist/src/Editor.d.ts +4 -1
  5. package/dist/src/Editor.js +117 -2
  6. package/dist/src/EditorImage.js +4 -1
  7. package/dist/src/SVGLoader.d.ts +4 -1
  8. package/dist/src/SVGLoader.js +78 -33
  9. package/dist/src/UndoRedoHistory.d.ts +1 -0
  10. package/dist/src/UndoRedoHistory.js +6 -0
  11. package/dist/src/Viewport.d.ts +1 -0
  12. package/dist/src/Viewport.js +12 -4
  13. package/dist/src/commands/lib.d.ts +2 -1
  14. package/dist/src/commands/lib.js +2 -1
  15. package/dist/src/commands/localization.d.ts +1 -0
  16. package/dist/src/commands/localization.js +1 -0
  17. package/dist/src/commands/uniteCommands.d.ts +4 -0
  18. package/dist/src/commands/uniteCommands.js +105 -0
  19. package/dist/src/components/AbstractComponent.d.ts +2 -0
  20. package/dist/src/components/AbstractComponent.js +41 -5
  21. package/dist/src/components/ImageComponent.d.ts +27 -0
  22. package/dist/src/components/ImageComponent.js +129 -0
  23. package/dist/src/components/builders/FreehandLineBuilder.js +2 -2
  24. package/dist/src/components/lib.d.ts +4 -2
  25. package/dist/src/components/lib.js +4 -2
  26. package/dist/src/components/localization.d.ts +2 -0
  27. package/dist/src/components/localization.js +2 -0
  28. package/dist/src/math/LineSegment2.d.ts +2 -0
  29. package/dist/src/math/LineSegment2.js +3 -0
  30. package/dist/src/rendering/localization.d.ts +3 -0
  31. package/dist/src/rendering/localization.js +3 -0
  32. package/dist/src/rendering/renderers/AbstractRenderer.d.ts +7 -0
  33. package/dist/src/rendering/renderers/CanvasRenderer.d.ts +2 -1
  34. package/dist/src/rendering/renderers/CanvasRenderer.js +7 -0
  35. package/dist/src/rendering/renderers/DummyRenderer.d.ts +3 -1
  36. package/dist/src/rendering/renderers/DummyRenderer.js +5 -0
  37. package/dist/src/rendering/renderers/SVGRenderer.d.ts +5 -2
  38. package/dist/src/rendering/renderers/SVGRenderer.js +45 -20
  39. package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +3 -1
  40. package/dist/src/rendering/renderers/TextOnlyRenderer.js +8 -1
  41. package/dist/src/tools/BaseTool.d.ts +3 -1
  42. package/dist/src/tools/BaseTool.js +6 -0
  43. package/dist/src/tools/PasteHandler.d.ts +16 -0
  44. package/dist/src/tools/PasteHandler.js +142 -0
  45. package/dist/src/tools/SelectionTool.d.ts +7 -1
  46. package/dist/src/tools/SelectionTool.js +63 -5
  47. package/dist/src/tools/ToolController.js +36 -27
  48. package/dist/src/tools/lib.d.ts +1 -0
  49. package/dist/src/tools/lib.js +1 -0
  50. package/dist/src/tools/localization.d.ts +3 -0
  51. package/dist/src/tools/localization.js +3 -0
  52. package/dist/src/types.d.ts +13 -2
  53. package/dist/src/types.js +2 -0
  54. package/package.json +1 -1
  55. package/src/Editor.ts +131 -2
  56. package/src/EditorImage.ts +7 -1
  57. package/src/SVGLoader.ts +90 -36
  58. package/src/UndoRedoHistory.test.ts +33 -0
  59. package/src/UndoRedoHistory.ts +8 -0
  60. package/src/Viewport.ts +13 -4
  61. package/src/commands/lib.ts +2 -0
  62. package/src/commands/localization.ts +2 -0
  63. package/src/commands/uniteCommands.test.ts +23 -0
  64. package/src/commands/uniteCommands.ts +121 -0
  65. package/src/components/AbstractComponent.ts +55 -9
  66. package/src/components/ImageComponent.ts +153 -0
  67. package/src/components/builders/FreehandLineBuilder.ts +2 -2
  68. package/src/components/lib.ts +7 -2
  69. package/src/components/localization.ts +4 -0
  70. package/src/math/LineSegment2.test.ts +9 -0
  71. package/src/math/LineSegment2.ts +5 -0
  72. package/src/rendering/localization.ts +6 -0
  73. package/src/rendering/renderers/AbstractRenderer.ts +16 -0
  74. package/src/rendering/renderers/CanvasRenderer.ts +10 -1
  75. package/src/rendering/renderers/DummyRenderer.ts +6 -1
  76. package/src/rendering/renderers/SVGRenderer.ts +50 -21
  77. package/src/rendering/renderers/TextOnlyRenderer.ts +10 -2
  78. package/src/tools/BaseTool.ts +9 -1
  79. package/src/tools/PasteHandler.ts +156 -0
  80. package/src/tools/SelectionTool.ts +80 -8
  81. package/src/tools/ToolController.ts +51 -44
  82. package/src/tools/lib.ts +1 -0
  83. package/src/tools/localization.ts +8 -0
  84. package/src/types.ts +16 -2
@@ -0,0 +1,156 @@
1
+ /**
2
+ * A tool that handles paste events.
3
+ * @packageDocumentation
4
+ */
5
+
6
+ import Editor from '../Editor';
7
+ import { AbstractComponent, TextComponent } from '../components/lib';
8
+ import { Command, uniteCommands } from '../commands/lib';
9
+ import SVGLoader from '../SVGLoader';
10
+ import { PasteEvent } from '../types';
11
+ import { Mat33, Rect2, Vec2 } from '../math/lib';
12
+ import BaseTool from './BaseTool';
13
+ import EditorImage from '../EditorImage';
14
+ import SelectionTool from './SelectionTool';
15
+ import TextTool from './TextTool';
16
+ import Color4 from '../Color4';
17
+ import { TextStyle } from '../components/Text';
18
+ import ImageComponent from '../components/ImageComponent';
19
+
20
+ // { @inheritDoc PasteHandler! }
21
+ export default class PasteHandler extends BaseTool {
22
+ public constructor(private editor: Editor) {
23
+ super(editor.notifier, editor.localization.pasteHandler);
24
+ }
25
+
26
+ public onPaste(event: PasteEvent): boolean {
27
+ const mime = event.mime.toLowerCase();
28
+
29
+ if (mime === 'image/svg+xml') {
30
+ void this.doSVGPaste(event.data);
31
+ return true;
32
+ }
33
+ else if (mime === 'text/plain') {
34
+ void this.doTextPaste(event.data);
35
+ return true;
36
+ }
37
+ else if (mime === 'image/png' || mime === 'image/jpeg') {
38
+ void this.doImagePaste(event.data);
39
+ return true;
40
+ }
41
+
42
+ return false;
43
+ }
44
+
45
+ private async addComponentsFromPaste(components: AbstractComponent[]) {
46
+ let bbox: Rect2|null = null;
47
+ for (const component of components) {
48
+ if (bbox) {
49
+ bbox = bbox.union(component.getBBox());
50
+ } else {
51
+ bbox = component.getBBox();
52
+ }
53
+ }
54
+
55
+ if (!bbox) {
56
+ return;
57
+ }
58
+
59
+ // Find a transform that scales/moves bbox onto the screen.
60
+ const visibleRect = this.editor.viewport.visibleRect;
61
+ const scaleRatioX = visibleRect.width / bbox.width;
62
+ const scaleRatioY = visibleRect.height / bbox.height;
63
+
64
+ let scaleRatio = scaleRatioX;
65
+ if (bbox.width * scaleRatio > visibleRect.width || bbox.height * scaleRatio > visibleRect.height) {
66
+ scaleRatio = scaleRatioY;
67
+ }
68
+ scaleRatio *= 2 / 3;
69
+
70
+ const transfm = Mat33.translation(
71
+ visibleRect.center.minus(bbox.center)
72
+ ).rightMul(
73
+ Mat33.scaling2D(scaleRatio, bbox.center)
74
+ );
75
+
76
+ const commands: Command[] = [];
77
+ for (const component of components) {
78
+ // To allow deserialization, we need to add first, then transform.
79
+ commands.push(EditorImage.addElement(component));
80
+ commands.push(component.transformBy(transfm));
81
+ }
82
+
83
+ const applyChunkSize = 100;
84
+ this.editor.dispatch(uniteCommands(commands, applyChunkSize), true);
85
+
86
+ for (const selectionTool of this.editor.toolController.getMatchingTools(SelectionTool)) {
87
+ selectionTool.setEnabled(true);
88
+ selectionTool.setSelection(components);
89
+ }
90
+ }
91
+
92
+ private async doSVGPaste(data: string) {
93
+ const sanitize = true;
94
+ const loader = SVGLoader.fromString(data, sanitize);
95
+
96
+ const components: AbstractComponent[] = [];
97
+
98
+ await loader.start((component) => {
99
+ components.push(component);
100
+ },
101
+ (_countProcessed: number, _totalToProcess: number) => null);
102
+
103
+ await this.addComponentsFromPaste(components);
104
+ }
105
+
106
+ private async doTextPaste(text: string) {
107
+ const textTools = this.editor.toolController.getMatchingTools(TextTool);
108
+
109
+ textTools.sort((a, b) => {
110
+ if (!a.isEnabled() && b.isEnabled()) {
111
+ return -1;
112
+ }
113
+
114
+ if (!b.isEnabled() && a.isEnabled()) {
115
+ return 1;
116
+ }
117
+
118
+ return 0;
119
+ });
120
+
121
+ const defaultTextStyle: TextStyle = { size: 12, fontFamily: 'sans', renderingStyle: { fill: Color4.red } };
122
+ const pastedTextStyle: TextStyle = textTools[0]?.getTextStyle() ?? defaultTextStyle;
123
+
124
+ const lines = text.split('\n');
125
+ let lastComponent: TextComponent|null = null;
126
+ const components: TextComponent[] = [];
127
+
128
+ for (const line of lines) {
129
+ let position = Vec2.zero;
130
+ if (lastComponent) {
131
+ const lineMargin = Math.floor(pastedTextStyle.size);
132
+ position = lastComponent.getBBox().bottomLeft.plus(Vec2.unitY.times(lineMargin));
133
+ }
134
+
135
+ const component = new TextComponent([ line ], Mat33.translation(position), pastedTextStyle);
136
+ components.push(component);
137
+ lastComponent = component;
138
+ }
139
+
140
+ if (components.length === 1) {
141
+ await this.addComponentsFromPaste([ components[0] ]);
142
+ } else {
143
+ // Wrap the existing `TextComponent`s --- dragging one component should drag all.
144
+ await this.addComponentsFromPaste([
145
+ new TextComponent(components, Mat33.identity, pastedTextStyle)
146
+ ]);
147
+ }
148
+ }
149
+
150
+ private async doImagePaste(dataURL: string) {
151
+ const image = new Image();
152
+ image.src = dataURL;
153
+ const component = await ImageComponent.fromImage(image, Mat33.identity);
154
+ await this.addComponentsFromPaste([ component ]);
155
+ }
156
+ }
@@ -11,10 +11,11 @@ import Mat33 from '../math/Mat33';
11
11
  import Rect2 from '../math/Rect2';
12
12
  import { Point2, Vec2 } from '../math/Vec2';
13
13
  import { EditorLocalization } from '../localization';
14
- import { EditorEventType, KeyPressEvent, KeyUpEvent, PointerEvt } from '../types';
14
+ import { CopyEvent, EditorEventType, KeyPressEvent, KeyUpEvent, PointerEvt } from '../types';
15
15
  import Viewport from '../Viewport';
16
16
  import BaseTool from './BaseTool';
17
17
  import SerializableCommand from '../commands/SerializableCommand';
18
+ import SVGRenderer from '../rendering/renderers/SVGRenderer';
18
19
 
19
20
  const handleScreenSize = 30;
20
21
  const styles = `
@@ -381,6 +382,16 @@ class Selection {
381
382
  this.region = Rect2.empty;
382
383
  }
383
384
 
385
+ public setSelectedObjects(objects: AbstractComponent[], bbox: Rect2) {
386
+ this.region = bbox;
387
+ this.selectedElems = objects;
388
+ this.updateUI();
389
+ }
390
+
391
+ public getSelectedObjects(): AbstractComponent[] {
392
+ return this.selectedElems;
393
+ }
394
+
384
395
  // Find the objects corresponding to this in the document,
385
396
  // select them.
386
397
  // Returns false iff nothing was selected.
@@ -528,15 +539,19 @@ export default class SelectionTool extends BaseTool {
528
539
  this.editor.handleKeyEventsFrom(this.handleOverlay);
529
540
  }
530
541
 
542
+ private makeSelectionBox(selectionStartPos: Point2) {
543
+ this.prevSelectionBox = this.selectionBox;
544
+ this.selectionBox = new Selection(
545
+ selectionStartPos, this.editor
546
+ );
547
+ // Remove any previous selection rects
548
+ this.handleOverlay.replaceChildren();
549
+ this.selectionBox.appendBackgroundBoxTo(this.handleOverlay);
550
+ }
551
+
531
552
  public onPointerDown(event: PointerEvt): boolean {
532
553
  if (event.allPointers.length === 1 && event.current.isPrimary) {
533
- this.prevSelectionBox = this.selectionBox;
534
- this.selectionBox = new Selection(
535
- event.current.canvasPos, this.editor
536
- );
537
- // Remove any previous selection rects
538
- this.handleOverlay.replaceChildren();
539
- this.selectionBox.appendBackgroundBoxTo(this.handleOverlay);
554
+ this.makeSelectionBox(event.current.canvasPos);
540
555
 
541
556
  return true;
542
557
  }
@@ -679,6 +694,12 @@ export default class SelectionTool extends BaseTool {
679
694
  this.selectionBox.transformPreview(transform);
680
695
  }
681
696
 
697
+ if (this.selectionBox && !handled && (event.key === 'Delete' || event.key === 'Backspace')) {
698
+ this.editor.dispatch(this.selectionBox.deleteSelectedObjects());
699
+ this.clearSelection();
700
+ handled = true;
701
+ }
702
+
682
703
  return handled;
683
704
  }
684
705
 
@@ -690,6 +711,35 @@ export default class SelectionTool extends BaseTool {
690
711
  return false;
691
712
  }
692
713
 
714
+ public onCopy(event: CopyEvent): boolean {
715
+ if (!this.selectionBox) {
716
+ return false;
717
+ }
718
+
719
+ const selectedElems = this.selectionBox.getSelectedObjects();
720
+ const bbox = this.selectionBox.region;
721
+ if (selectedElems.length === 0) {
722
+ return false;
723
+ }
724
+
725
+ const exportViewport = new Viewport(this.editor.notifier);
726
+ exportViewport.updateScreenSize(Vec2.of(bbox.w, bbox.h));
727
+ exportViewport.resetTransform(Mat33.translation(bbox.topLeft));
728
+
729
+ const svgNameSpace = 'http://www.w3.org/2000/svg';
730
+ const exportElem = document.createElementNS(svgNameSpace, 'svg');
731
+
732
+ const sanitize = true;
733
+ const renderer = new SVGRenderer(exportElem, exportViewport, sanitize);
734
+
735
+ for (const elem of selectedElems) {
736
+ elem.render(renderer);
737
+ }
738
+
739
+ event.setData('image/svg+xml', exportElem.outerHTML);
740
+ return true;
741
+ }
742
+
693
743
  public setEnabled(enabled: boolean) {
694
744
  super.setEnabled(enabled);
695
745
 
@@ -712,6 +762,28 @@ export default class SelectionTool extends BaseTool {
712
762
  return this.selectionBox;
713
763
  }
714
764
 
765
+ public setSelection(objects: AbstractComponent[]) {
766
+ let bbox: Rect2|null = null;
767
+ for (const object of objects) {
768
+ if (bbox) {
769
+ bbox = bbox.union(object.getBBox());
770
+ } else {
771
+ bbox = object.getBBox();
772
+ }
773
+ }
774
+
775
+ if (!bbox) {
776
+ return;
777
+ }
778
+
779
+ this.clearSelection();
780
+ if (!this.selectionBox) {
781
+ this.makeSelectionBox(bbox.topLeft);
782
+ }
783
+
784
+ this.selectionBox!.setSelectedObjects(objects, bbox);
785
+ }
786
+
715
787
  public clearSelection() {
716
788
  this.handleOverlay.replaceChildren();
717
789
  this.prevSelectionBox = this.selectionBox;
@@ -12,17 +12,18 @@ import UndoRedoShortcut from './UndoRedoShortcut';
12
12
  import TextTool from './TextTool';
13
13
  import PipetteTool from './PipetteTool';
14
14
  import ToolSwitcherShortcut from './ToolSwitcherShortcut';
15
+ import PasteHandler from './PasteHandler';
15
16
 
16
17
  export default class ToolController {
17
18
  private tools: BaseTool[];
18
19
  private activeTool: BaseTool|null = null;
19
20
  private primaryToolGroup: ToolEnabledGroup;
20
-
21
+
21
22
  /** @internal */
22
23
  public constructor(editor: Editor, localization: ToolLocalization) {
23
24
  const primaryToolGroup = new ToolEnabledGroup();
24
25
  this.primaryToolGroup = primaryToolGroup;
25
-
26
+
26
27
  const panZoomTool = new PanZoom(editor, PanZoomMode.TwoFingerTouchGestures | PanZoomMode.RightClickDrags, localization.touchPanTool);
27
28
  const keyboardPanZoomTool = new PanZoom(editor, PanZoomMode.Keyboard, localization.keyboardPanZoom);
28
29
  const primaryPenTool = new Pen(editor, localization.penTool(1), { color: Color4.purple, thickness: 16 });
@@ -30,10 +31,10 @@ export default class ToolController {
30
31
  // Three pens
31
32
  primaryPenTool,
32
33
  new Pen(editor, localization.penTool(2), { color: Color4.clay, thickness: 4 }),
33
-
34
+
34
35
  // Highlighter-like pen with width=64
35
36
  new Pen(editor, localization.penTool(3), { color: Color4.ofRGBA(1, 1, 0, 0.5), thickness: 64 }),
36
-
37
+
37
38
  new Eraser(editor, localization.eraserTool),
38
39
  new SelectionTool(editor, localization.selectionTool),
39
40
  new TextTool(editor, localization.textTool, localization),
@@ -45,11 +46,12 @@ export default class ToolController {
45
46
  keyboardPanZoomTool,
46
47
  new UndoRedoShortcut(editor),
47
48
  new ToolSwitcherShortcut(editor),
49
+ new PasteHandler(editor),
48
50
  ];
49
51
  primaryTools.forEach(tool => tool.setToolGroup(primaryToolGroup));
50
52
  panZoomTool.setEnabled(true);
51
53
  primaryPenTool.setEnabled(true);
52
-
54
+
53
55
  editor.notifier.on(EditorEventType.ToolEnabled, event => {
54
56
  if (event.kind === EditorEventType.ToolEnabled) {
55
57
  editor.announceForAccessibility(localization.toolEnabledAnnouncement(event.tool.description));
@@ -60,17 +62,17 @@ export default class ToolController {
60
62
  editor.announceForAccessibility(localization.toolDisabledAnnouncement(event.tool.description));
61
63
  }
62
64
  });
63
-
65
+
64
66
  this.activeTool = null;
65
67
  }
66
-
68
+
67
69
  // Replaces the current set of tools with `tools`. This should only be done before
68
70
  // the creation of the app's toolbar (if using `HTMLToolbar`).
69
71
  public setTools(tools: BaseTool[], primaryToolGroup?: ToolEnabledGroup) {
70
72
  this.tools = tools;
71
73
  this.primaryToolGroup = primaryToolGroup ?? new ToolEnabledGroup();
72
74
  }
73
-
75
+
74
76
  // Add a tool that acts like one of the primary tools (only one primary tool can be enabled at a time).
75
77
  // This should be called before creating the app's toolbar.
76
78
  public addPrimaryTool(tool: BaseTool) {
@@ -78,22 +80,22 @@ export default class ToolController {
78
80
  if (tool.isEnabled()) {
79
81
  this.primaryToolGroup.notifyEnabled(tool);
80
82
  }
81
-
83
+
82
84
  this.addTool(tool);
83
85
  }
84
-
86
+
85
87
  public getPrimaryTools(): BaseTool[] {
86
88
  return this.tools.filter(tool => {
87
89
  return tool.getToolGroup() === this.primaryToolGroup;
88
90
  });
89
91
  }
90
-
92
+
91
93
  // Add a tool to the end of this' tool list (the added tool receives events after tools already added to this).
92
94
  // This should be called before creating the app's toolbar.
93
95
  public addTool(tool: BaseTool) {
94
96
  this.tools.push(tool);
95
97
  }
96
-
98
+
97
99
  // Returns true if the event was handled
98
100
  public dispatchInputEvent(event: InputEvt): boolean {
99
101
  let handled = false;
@@ -103,7 +105,7 @@ export default class ToolController {
103
105
  if (this.activeTool !== tool) {
104
106
  this.activeTool?.onGestureCancel();
105
107
  }
106
-
108
+
107
109
  this.activeTool = tool;
108
110
  handled = true;
109
111
  break;
@@ -113,49 +115,54 @@ export default class ToolController {
113
115
  this.activeTool?.onPointerUp(event);
114
116
  this.activeTool = null;
115
117
  handled = true;
116
- } else if (
117
- event.kind === InputEvtType.WheelEvt || event.kind === InputEvtType.KeyPressEvent || event.kind === InputEvtType.KeyUpEvent
118
- ) {
119
- const isKeyPressEvt = event.kind === InputEvtType.KeyPressEvent;
120
- const isKeyReleaseEvt = event.kind === InputEvtType.KeyUpEvent;
121
- const isWheelEvt = event.kind === InputEvtType.WheelEvt;
118
+ } else if (event.kind === InputEvtType.PointerMoveEvt) {
119
+ if (this.activeTool !== null) {
120
+ this.activeTool.onPointerMove(event);
121
+ handled = true;
122
+ }
123
+ } else if (event.kind === InputEvtType.GestureCancelEvt) {
124
+ if (this.activeTool !== null) {
125
+ this.activeTool.onGestureCancel();
126
+ this.activeTool = null;
127
+ }
128
+ } else {
129
+ let allCasesHandledGuard: never;
130
+
122
131
  for (const tool of this.tools) {
123
132
  if (!tool.isEnabled()) {
124
133
  continue;
125
134
  }
126
-
127
- const wheelResult = isWheelEvt && tool.onWheel(event);
128
- const keyPressResult = isKeyPressEvt && tool.onKeyPress(event);
129
- const keyReleaseResult = isKeyReleaseEvt && tool.onKeyUp(event);
130
- handled = keyPressResult || wheelResult || keyReleaseResult;
135
+
136
+ switch (event.kind) {
137
+ case InputEvtType.KeyPressEvent:
138
+ handled = tool.onKeyPress(event);
139
+ break;
140
+ case InputEvtType.KeyUpEvent:
141
+ handled = tool.onKeyUp(event);
142
+ break;
143
+ case InputEvtType.WheelEvt:
144
+ handled = tool.onWheel(event);
145
+ break;
146
+ case InputEvtType.CopyEvent:
147
+ handled = tool.onCopy(event);
148
+ break;
149
+ case InputEvtType.PasteEvent:
150
+ handled = tool.onPaste(event);
151
+ break;
152
+ default:
153
+ allCasesHandledGuard = event;
154
+ return allCasesHandledGuard;
155
+ }
131
156
 
132
157
  if (handled) {
133
158
  break;
134
159
  }
135
160
  }
136
- } else if (this.activeTool !== null) {
137
- let allCasesHandledGuard: never;
138
-
139
- switch (event.kind) {
140
- case InputEvtType.PointerMoveEvt:
141
- this.activeTool.onPointerMove(event);
142
- break;
143
- case InputEvtType.GestureCancelEvt:
144
- this.activeTool.onGestureCancel();
145
- this.activeTool = null;
146
- break;
147
- default:
148
- allCasesHandledGuard = event;
149
- return allCasesHandledGuard;
150
- }
151
- handled = true;
152
- } else {
153
- handled = false;
154
161
  }
155
-
162
+
156
163
  return handled;
157
164
  }
158
-
165
+
159
166
  public getMatchingTools<Type extends BaseTool>(type: new (...args: any[])=>Type): Type[] {
160
167
  return this.tools.filter(tool => tool instanceof type) as Type[];
161
168
  }
package/src/tools/lib.ts CHANGED
@@ -15,4 +15,5 @@ export { default as PenTool, PenStyle } from './Pen';
15
15
  export { default as TextTool } from './TextTool';
16
16
  export { default as SelectionTool } from './SelectionTool';
17
17
  export { default as EraserTool } from './Eraser';
18
+ export { default as PasteHandler } from './PasteHandler';
18
19
 
@@ -13,6 +13,10 @@ export interface ToolLocalization {
13
13
  textTool: string;
14
14
  enterTextToInsert: string;
15
15
  changeTool: string;
16
+ pasteHandler: string;
17
+
18
+ copied: (count: number, description: string) => string;
19
+ pasted: (count: number, description: string) => string;
16
20
 
17
21
  toolEnabledAnnouncement: (toolName: string) => string;
18
22
  toolDisabledAnnouncement: (toolName: string) => string;
@@ -32,6 +36,10 @@ export const defaultToolLocalization: ToolLocalization = {
32
36
  textTool: 'Text',
33
37
  enterTextToInsert: 'Text to insert',
34
38
  changeTool: 'Change tool',
39
+ pasteHandler: 'Copy paste handler',
40
+
41
+ copied: (count: number, description: string) => `Copied ${count} ${description}`,
42
+ pasted: (count: number, description: string) => `Pasted ${count} ${description}`,
35
43
 
36
44
  toolEnabledAnnouncement: (toolName) => `${toolName} enabled`,
37
45
  toolDisabledAnnouncement: (toolName) => `${toolName} disabled`,
package/src/types.ts CHANGED
@@ -35,7 +35,10 @@ export enum InputEvtType {
35
35
 
36
36
  WheelEvt,
37
37
  KeyPressEvent,
38
- KeyUpEvent
38
+ KeyUpEvent,
39
+
40
+ CopyEvent,
41
+ PasteEvent,
39
42
  }
40
43
 
41
44
  // [delta.x] is horizontal scroll,
@@ -59,6 +62,17 @@ export interface KeyUpEvent {
59
62
  readonly ctrlKey: boolean;
60
63
  }
61
64
 
65
+ export interface CopyEvent {
66
+ readonly kind: InputEvtType.CopyEvent;
67
+ setData(mime: string, data: string): void;
68
+ }
69
+
70
+ export interface PasteEvent {
71
+ readonly kind: InputEvtType.PasteEvent;
72
+ readonly data: string;
73
+ readonly mime: string;
74
+ }
75
+
62
76
  // Event triggered when pointer capture is taken by a different [PointerEvtListener].
63
77
  export interface GestureCancelEvt {
64
78
  readonly kind: InputEvtType.GestureCancelEvt;
@@ -82,7 +96,7 @@ export interface PointerUpEvt extends PointerEvtBase {
82
96
  }
83
97
 
84
98
  export type PointerEvt = PointerDownEvt | PointerMoveEvt | PointerUpEvt;
85
- export type InputEvt = KeyPressEvent | KeyUpEvent | WheelEvt | GestureCancelEvt | PointerEvt;
99
+ export type InputEvt = KeyPressEvent | KeyUpEvent | WheelEvt | GestureCancelEvt | PointerEvt | CopyEvent | PasteEvent;
86
100
 
87
101
  export type EditorNotifier = EventDispatcher<EditorEventType, EditorEventDataType>;
88
102