js-draw 1.18.0 → 1.20.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (146) hide show
  1. package/README.md +51 -0
  2. package/dist/Editor.css +78 -5
  3. package/dist/bundle.js +2 -2
  4. package/dist/bundledStyles.js +1 -1
  5. package/dist/cjs/Editor.d.ts +20 -1
  6. package/dist/cjs/Editor.js +6 -0
  7. package/dist/cjs/{SVGLoader.d.ts → SVGLoader/index.d.ts} +1 -1
  8. package/dist/cjs/{SVGLoader.js → SVGLoader/index.js} +15 -30
  9. package/dist/cjs/SVGLoader/utils/determineFontSize.d.ts +3 -0
  10. package/dist/cjs/SVGLoader/utils/determineFontSize.js +27 -0
  11. package/dist/cjs/Viewport.d.ts +33 -1
  12. package/dist/cjs/components/TextComponent.js +3 -1
  13. package/dist/cjs/image/EditorImage.d.ts +2 -1
  14. package/dist/cjs/image/EditorImage.js +101 -5
  15. package/dist/cjs/rendering/caching/RenderingCacheNode.js +20 -15
  16. package/dist/cjs/rendering/renderers/CanvasRenderer.js +4 -4
  17. package/dist/cjs/testing/findNodeWithText.d.ts +3 -0
  18. package/dist/cjs/testing/findNodeWithText.js +16 -0
  19. package/dist/cjs/testing/firstElementAncestorOfNode.d.ts +3 -0
  20. package/dist/cjs/testing/firstElementAncestorOfNode.js +13 -0
  21. package/dist/cjs/testing/sendKeyPressRelease.d.ts +3 -0
  22. package/dist/cjs/testing/sendKeyPressRelease.js +8 -0
  23. package/dist/cjs/testing/sendPenEvent.d.ts +2 -2
  24. package/dist/cjs/testing/sendPenEvent.js +26 -3
  25. package/dist/cjs/toolbar/localization.d.ts +3 -0
  26. package/dist/cjs/toolbar/localization.js +3 -0
  27. package/dist/cjs/toolbar/widgets/BaseWidget.d.ts +1 -0
  28. package/dist/cjs/toolbar/widgets/BaseWidget.js +1 -0
  29. package/dist/cjs/toolbar/widgets/InsertImageWidget/ImageWrapper.d.ts +23 -0
  30. package/dist/cjs/toolbar/widgets/InsertImageWidget/ImageWrapper.js +65 -0
  31. package/dist/cjs/toolbar/widgets/InsertImageWidget/fileToImages.d.ts +3 -0
  32. package/dist/cjs/toolbar/widgets/InsertImageWidget/fileToImages.js +21 -0
  33. package/dist/cjs/toolbar/widgets/InsertImageWidget/index.d.ts +37 -0
  34. package/dist/cjs/toolbar/widgets/InsertImageWidget/index.js +289 -0
  35. package/dist/cjs/toolbar/widgets/TextToolWidget.js +5 -3
  36. package/dist/cjs/toolbar/widgets/TextToolWidget.test.d.ts +1 -0
  37. package/dist/cjs/toolbar/widgets/components/makeFileInput.d.ts +12 -2
  38. package/dist/cjs/toolbar/widgets/components/makeFileInput.js +113 -45
  39. package/dist/cjs/toolbar/widgets/components/makeFileInput.test.d.ts +1 -0
  40. package/dist/cjs/toolbar/widgets/components/makeSnappedList.d.ts +15 -0
  41. package/dist/cjs/toolbar/widgets/components/makeSnappedList.js +168 -0
  42. package/dist/cjs/tools/Eraser.d.ts +7 -2
  43. package/dist/cjs/tools/Eraser.js +76 -6
  44. package/dist/cjs/tools/PanZoom.d.ts +54 -0
  45. package/dist/cjs/tools/PanZoom.js +54 -2
  46. package/dist/cjs/tools/SelectionTool/Selection.d.ts +2 -2
  47. package/dist/cjs/tools/SelectionTool/Selection.js +20 -20
  48. package/dist/cjs/tools/SelectionTool/SelectionHandle.d.ts +8 -2
  49. package/dist/cjs/tools/SelectionTool/SelectionHandle.js +6 -0
  50. package/dist/cjs/tools/SelectionTool/SelectionTool.js +1 -1
  51. package/dist/cjs/tools/SelectionTool/types.d.ts +19 -0
  52. package/dist/cjs/tools/TextTool.js +2 -1
  53. package/dist/cjs/tools/TextTool.test.d.ts +1 -0
  54. package/dist/cjs/tools/ToolController.d.ts +2 -0
  55. package/dist/cjs/tools/ToolController.js +10 -1
  56. package/dist/cjs/util/ReactiveValue.d.ts +6 -0
  57. package/dist/cjs/util/ReactiveValue.js +16 -0
  58. package/dist/cjs/util/bytesToSizeString.d.ts +8 -0
  59. package/dist/cjs/util/bytesToSizeString.js +26 -0
  60. package/dist/cjs/util/bytesToSizeString.test.d.ts +1 -0
  61. package/dist/cjs/util/stopPropagationOfScrollingWheelEvents.js +10 -6
  62. package/dist/cjs/util/waitForAll.d.ts +2 -0
  63. package/dist/cjs/util/waitForAll.js +2 -0
  64. package/dist/cjs/util/waitForImageLoaded.js +3 -0
  65. package/dist/cjs/util/waitForTimeout.d.ts +1 -0
  66. package/dist/cjs/util/waitForTimeout.js +1 -1
  67. package/dist/cjs/version.js +1 -1
  68. package/dist/mjs/Editor.d.ts +20 -1
  69. package/dist/mjs/Editor.mjs +6 -0
  70. package/dist/mjs/{SVGLoader.d.ts → SVGLoader/index.d.ts} +1 -1
  71. package/dist/mjs/{SVGLoader.mjs → SVGLoader/index.mjs} +15 -30
  72. package/dist/mjs/SVGLoader/index.test.d.ts +1 -0
  73. package/dist/mjs/SVGLoader/utils/determineFontSize.d.ts +3 -0
  74. package/dist/mjs/SVGLoader/utils/determineFontSize.mjs +25 -0
  75. package/dist/mjs/Viewport.d.ts +33 -1
  76. package/dist/mjs/components/TextComponent.mjs +3 -1
  77. package/dist/mjs/image/EditorImage.d.ts +2 -1
  78. package/dist/mjs/image/EditorImage.mjs +101 -5
  79. package/dist/mjs/rendering/caching/RenderingCacheNode.mjs +20 -15
  80. package/dist/mjs/rendering/renderers/CanvasRenderer.mjs +4 -4
  81. package/dist/mjs/testing/findNodeWithText.d.ts +3 -0
  82. package/dist/mjs/testing/findNodeWithText.mjs +14 -0
  83. package/dist/mjs/testing/firstElementAncestorOfNode.d.ts +3 -0
  84. package/dist/mjs/testing/firstElementAncestorOfNode.mjs +11 -0
  85. package/dist/mjs/testing/sendKeyPressRelease.d.ts +3 -0
  86. package/dist/mjs/testing/sendKeyPressRelease.mjs +6 -0
  87. package/dist/mjs/testing/sendPenEvent.d.ts +2 -2
  88. package/dist/mjs/testing/sendPenEvent.mjs +3 -3
  89. package/dist/mjs/toolbar/localization.d.ts +3 -0
  90. package/dist/mjs/toolbar/localization.mjs +3 -0
  91. package/dist/mjs/toolbar/widgets/BaseWidget.d.ts +1 -0
  92. package/dist/mjs/toolbar/widgets/BaseWidget.mjs +1 -0
  93. package/dist/mjs/toolbar/widgets/InsertImageWidget/ImageWrapper.d.ts +23 -0
  94. package/dist/mjs/toolbar/widgets/InsertImageWidget/ImageWrapper.mjs +61 -0
  95. package/dist/mjs/toolbar/widgets/InsertImageWidget/fileToImages.d.ts +3 -0
  96. package/dist/mjs/toolbar/widgets/InsertImageWidget/fileToImages.mjs +16 -0
  97. package/dist/mjs/toolbar/widgets/InsertImageWidget/index.d.ts +37 -0
  98. package/dist/mjs/toolbar/widgets/InsertImageWidget/index.mjs +284 -0
  99. package/dist/mjs/toolbar/widgets/InsertImageWidget/index.test.d.ts +1 -0
  100. package/dist/mjs/toolbar/widgets/TextToolWidget.mjs +5 -3
  101. package/dist/mjs/toolbar/widgets/TextToolWidget.test.d.ts +1 -0
  102. package/dist/mjs/toolbar/widgets/components/makeFileInput.d.ts +12 -2
  103. package/dist/mjs/toolbar/widgets/components/makeFileInput.mjs +113 -45
  104. package/dist/mjs/toolbar/widgets/components/makeFileInput.test.d.ts +1 -0
  105. package/dist/mjs/toolbar/widgets/components/makeSnappedList.d.ts +15 -0
  106. package/dist/mjs/toolbar/widgets/components/makeSnappedList.mjs +163 -0
  107. package/dist/mjs/tools/Eraser.d.ts +7 -2
  108. package/dist/mjs/tools/Eraser.mjs +76 -6
  109. package/dist/mjs/tools/PanZoom.d.ts +54 -0
  110. package/dist/mjs/tools/PanZoom.mjs +54 -2
  111. package/dist/mjs/tools/SelectionTool/Selection.d.ts +2 -2
  112. package/dist/mjs/tools/SelectionTool/Selection.mjs +20 -20
  113. package/dist/mjs/tools/SelectionTool/SelectionHandle.d.ts +8 -2
  114. package/dist/mjs/tools/SelectionTool/SelectionHandle.mjs +6 -0
  115. package/dist/mjs/tools/SelectionTool/SelectionTool.mjs +1 -1
  116. package/dist/mjs/tools/SelectionTool/types.d.ts +19 -0
  117. package/dist/mjs/tools/TextTool.mjs +2 -1
  118. package/dist/mjs/tools/TextTool.test.d.ts +1 -0
  119. package/dist/mjs/tools/ToolController.d.ts +2 -0
  120. package/dist/mjs/tools/ToolController.mjs +10 -1
  121. package/dist/mjs/util/ReactiveValue.d.ts +6 -0
  122. package/dist/mjs/util/ReactiveValue.mjs +16 -0
  123. package/dist/mjs/util/bytesToSizeString.d.ts +8 -0
  124. package/dist/mjs/util/bytesToSizeString.mjs +24 -0
  125. package/dist/mjs/util/bytesToSizeString.test.d.ts +1 -0
  126. package/dist/mjs/util/stopPropagationOfScrollingWheelEvents.mjs +10 -6
  127. package/dist/mjs/util/waitForAll.d.ts +2 -0
  128. package/dist/mjs/util/waitForAll.mjs +2 -0
  129. package/dist/mjs/util/waitForImageLoaded.mjs +3 -0
  130. package/dist/mjs/util/waitForTimeout.d.ts +1 -0
  131. package/dist/mjs/util/waitForTimeout.mjs +1 -1
  132. package/dist/mjs/version.mjs +1 -1
  133. package/package.json +4 -4
  134. package/src/toolbar/EdgeToolbar.scss +8 -3
  135. package/src/toolbar/toolbar.scss +1 -7
  136. package/src/toolbar/widgets/{InsertImageWidget.scss → InsertImageWidget/index.scss} +3 -2
  137. package/src/toolbar/widgets/components/components.scss +2 -1
  138. package/src/toolbar/widgets/components/makeFileInput.scss +14 -1
  139. package/src/toolbar/widgets/components/makeSnappedList.scss +74 -0
  140. package/src/toolbar/widgets/widgets.scss +7 -0
  141. package/dist/cjs/toolbar/widgets/InsertImageWidget.d.ts +0 -22
  142. package/dist/cjs/toolbar/widgets/InsertImageWidget.js +0 -269
  143. package/dist/mjs/toolbar/widgets/InsertImageWidget.d.ts +0 -22
  144. package/dist/mjs/toolbar/widgets/InsertImageWidget.mjs +0 -264
  145. /package/dist/cjs/{SVGLoader.test.d.ts → SVGLoader/index.test.d.ts} +0 -0
  146. /package/dist/{mjs/SVGLoader.test.d.ts → cjs/toolbar/widgets/InsertImageWidget/index.test.d.ts} +0 -0
@@ -0,0 +1,168 @@
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 stopPropagationOfScrollingWheelEvents_1 = __importDefault(require("../../../util/stopPropagationOfScrollingWheelEvents"));
7
+ const ReactiveValue_1 = require("../../../util/ReactiveValue");
8
+ /**
9
+ * Creates a list that snaps to each item and reports the selected item.
10
+ */
11
+ const makeSnappedList = (itemsValue) => {
12
+ const container = document.createElement('div');
13
+ container.classList.add('toolbar-snapped-scroll-list');
14
+ const scroller = document.createElement('div');
15
+ scroller.classList.add('scroller');
16
+ const visibleIndex = ReactiveValue_1.MutableReactiveValue.fromInitialValue(0);
17
+ let observer = null;
18
+ const makePageMarkers = () => {
19
+ const markerContainer = document.createElement('div');
20
+ markerContainer.classList.add('page-markers');
21
+ // Keyboard focus should go to the main scrolling list.
22
+ // TODO: Does it make sense for the page marker list to be focusable?
23
+ markerContainer.setAttribute('tabindex', '-1');
24
+ const markers = [];
25
+ const pairedItems = ReactiveValue_1.ReactiveValue.union([visibleIndex, itemsValue]);
26
+ pairedItems.onUpdateAndNow(([currentVisibleIndex, items]) => {
27
+ let addedOrRemovedMarkers = false;
28
+ // Items may have been removed from the list of pages. Make the markers reflect that.
29
+ while (items.length < markers.length) {
30
+ markers.pop();
31
+ addedOrRemovedMarkers = true;
32
+ }
33
+ let activeMarker;
34
+ for (let i = 0; i < items.length; i++) {
35
+ let marker;
36
+ if (i >= markers.length) {
37
+ marker = document.createElement('div');
38
+ // Use a separate content element to increase the clickable size of
39
+ // the marker.
40
+ const content = document.createElement('div');
41
+ content.classList.add('content');
42
+ marker.replaceChildren(content);
43
+ markers.push(marker);
44
+ addedOrRemovedMarkers = true;
45
+ }
46
+ else {
47
+ marker = markers[i];
48
+ }
49
+ marker.classList.add('marker');
50
+ if (i === currentVisibleIndex) {
51
+ marker.classList.add('-active');
52
+ activeMarker = marker;
53
+ }
54
+ else {
55
+ marker.classList.remove('-active');
56
+ }
57
+ const markerIndex = i;
58
+ marker.onclick = () => {
59
+ wrappedItems.get()[markerIndex]?.element?.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
60
+ };
61
+ }
62
+ // Only call .replaceChildren when necessary -- doing so on every change would
63
+ // break transitions.
64
+ if (addedOrRemovedMarkers) {
65
+ markerContainer.replaceChildren(...markers);
66
+ }
67
+ // Handles the case where there are many markers and the current is offscreen
68
+ if (activeMarker && markerContainer.scrollHeight > container.clientHeight) {
69
+ activeMarker.scrollIntoView({ block: 'nearest' });
70
+ }
71
+ if (markers.length === 1) {
72
+ markerContainer.classList.add('-one-element');
73
+ }
74
+ else {
75
+ markerContainer.classList.remove('-one-element');
76
+ }
77
+ });
78
+ return markerContainer;
79
+ };
80
+ const createObserver = () => {
81
+ observer = new IntersectionObserver((entries) => {
82
+ for (const entry of entries) {
83
+ if (entry.isIntersecting && entry.intersectionRatio > 0.7) {
84
+ const indexString = entry.target.getAttribute('data-item-index');
85
+ if (indexString === null)
86
+ throw new Error('Could not find attribute data-item-index');
87
+ const index = Number(indexString);
88
+ visibleIndex.set(index);
89
+ break;
90
+ }
91
+ }
92
+ }, {
93
+ // Element to use as the boudning box with which to intersect.
94
+ // See https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
95
+ root: scroller,
96
+ // Fraction of an element that must be visible to trigger the callback:
97
+ threshold: 0.9,
98
+ });
99
+ };
100
+ const destroyObserver = () => {
101
+ if (observer) {
102
+ observer.disconnect();
103
+ visibleIndex.set(0);
104
+ observer = null;
105
+ }
106
+ };
107
+ const wrappedItems = ReactiveValue_1.ReactiveValue.map(itemsValue, items => {
108
+ return items.map((item, index) => {
109
+ const wrapper = document.createElement('div');
110
+ if (item.element.parentElement)
111
+ item.element.remove();
112
+ wrapper.appendChild(item.element);
113
+ wrapper.classList.add('item');
114
+ wrapper.setAttribute('data-item-index', `${index}`);
115
+ return {
116
+ element: wrapper,
117
+ data: item.data,
118
+ };
119
+ });
120
+ });
121
+ const lastItems = [];
122
+ wrappedItems.onUpdateAndNow(items => {
123
+ visibleIndex.set(-1);
124
+ for (const item of lastItems) {
125
+ observer?.unobserve(item.element);
126
+ }
127
+ scroller.replaceChildren();
128
+ // An observer is only necessary if there are multiple items to scroll through.
129
+ if (items.length > 1) {
130
+ createObserver();
131
+ }
132
+ else {
133
+ destroyObserver();
134
+ }
135
+ // Different styling is applied when empty
136
+ if (items.length === 0) {
137
+ container.classList.add('-empty');
138
+ }
139
+ else {
140
+ container.classList.remove('-empty');
141
+ }
142
+ for (const item of items) {
143
+ scroller.appendChild(item.element);
144
+ }
145
+ visibleIndex.set(0);
146
+ if (observer) {
147
+ for (const item of items) {
148
+ observer.observe(item.element);
149
+ }
150
+ }
151
+ });
152
+ const visibleItem = ReactiveValue_1.ReactiveValue.map(visibleIndex, index => {
153
+ const values = itemsValue.get();
154
+ if (0 <= index && index < values.length) {
155
+ return values[index].data;
156
+ }
157
+ return null;
158
+ });
159
+ // makeSnappedList is generally shown within the toolbar. This allows users to
160
+ // scroll it with a touchpad.
161
+ (0, stopPropagationOfScrollingWheelEvents_1.default)(scroller);
162
+ container.replaceChildren(makePageMarkers(), scroller);
163
+ return {
164
+ container,
165
+ visibleItem,
166
+ };
167
+ };
168
+ exports.default = makeSnappedList;
@@ -1,4 +1,4 @@
1
- import { KeyPressEvent, PointerEvt } from '../inputEvents';
1
+ import { GestureCancelEvt, KeyPressEvent, PointerEvt } from '../inputEvents';
2
2
  import BaseTool from './BaseTool';
3
3
  import Editor from '../Editor';
4
4
  import { MutableReactiveValue } from '../util/ReactiveValue';
@@ -22,6 +22,11 @@ export default class Eraser extends BaseTool {
22
22
  private eraseCommands;
23
23
  private addCommands;
24
24
  constructor(editor: Editor, description: string, options?: InitialEraserOptions);
25
+ /**
26
+ * @returns a tool that briefly enables the eraser when a physical eraser is used.
27
+ * This tool should be added to the tool list after the primary tools.
28
+ */
29
+ makeEraserSwitcherTool(): BaseTool;
25
30
  private clearPreview;
26
31
  private getSizeOnCanvas;
27
32
  private drawPreviewAt;
@@ -36,7 +41,7 @@ export default class Eraser extends BaseTool {
36
41
  onPointerDown(event: PointerEvt): boolean;
37
42
  onPointerMove(event: PointerEvt): void;
38
43
  onPointerUp(event: PointerEvt): void;
39
- onGestureCancel(): void;
44
+ onGestureCancel(_event: GestureCancelEvt): void;
40
45
  onKeyPress(event: KeyPressEvent): boolean;
41
46
  /** Returns the side-length of the tip of this eraser. */
42
47
  getThickness(): number;
@@ -19,12 +19,59 @@ var EraserMode;
19
19
  EraserMode["PartialStroke"] = "partial-stroke";
20
20
  EraserMode["FullStroke"] = "full-stroke";
21
21
  })(EraserMode || (exports.EraserMode = EraserMode = {}));
22
+ /** Handles switching from other primary tools to the eraser and back */
23
+ class EraserSwitcher extends BaseTool_1.default {
24
+ constructor(editor, eraser) {
25
+ super(editor.notifier, editor.localization.changeTool);
26
+ this.editor = editor;
27
+ this.eraser = eraser;
28
+ }
29
+ onPointerDown(event) {
30
+ if (event.allPointers.length === 1 && event.current.device === Pointer_1.PointerDevice.Eraser) {
31
+ const toolController = this.editor.toolController;
32
+ const enabledPrimaryTools = toolController.getPrimaryTools().filter(tool => tool.isEnabled());
33
+ if (enabledPrimaryTools.length) {
34
+ this.previousEnabledTool = enabledPrimaryTools[0];
35
+ }
36
+ else {
37
+ this.previousEnabledTool = null;
38
+ }
39
+ this.previousEraserEnabledState = this.eraser.isEnabled();
40
+ this.eraser.setEnabled(true);
41
+ if (this.eraser.onPointerDown(event)) {
42
+ return true;
43
+ }
44
+ else {
45
+ this.restoreOriginalTool();
46
+ }
47
+ }
48
+ return false;
49
+ }
50
+ onPointerMove(event) {
51
+ this.eraser.onPointerMove(event);
52
+ }
53
+ restoreOriginalTool() {
54
+ this.eraser.setEnabled(this.previousEraserEnabledState);
55
+ if (this.previousEnabledTool) {
56
+ this.previousEnabledTool.setEnabled(true);
57
+ }
58
+ }
59
+ onPointerUp(event) {
60
+ this.eraser.onPointerUp(event);
61
+ this.restoreOriginalTool();
62
+ }
63
+ onGestureCancel(event) {
64
+ this.eraser.onGestureCancel(event);
65
+ this.restoreOriginalTool();
66
+ }
67
+ }
22
68
  class Eraser extends BaseTool_1.default {
23
69
  constructor(editor, description, options) {
24
70
  super(editor.notifier, description);
25
71
  this.editor = editor;
26
72
  this.lastPoint = null;
27
73
  this.isFirstEraseEvt = true;
74
+ this.toAdd = new Set();
28
75
  // Commands that each remove one element
29
76
  this.eraseCommands = [];
30
77
  this.addCommands = [];
@@ -45,6 +92,13 @@ class Eraser extends BaseTool_1.default {
45
92
  });
46
93
  });
47
94
  }
95
+ /**
96
+ * @returns a tool that briefly enables the eraser when a physical eraser is used.
97
+ * This tool should be added to the tool list after the primary tools.
98
+ */
99
+ makeEraserSwitcherTool() {
100
+ return new EraserSwitcher(this.editor, this);
101
+ }
48
102
  clearPreview() {
49
103
  this.editor.clearWetInk();
50
104
  }
@@ -127,15 +181,17 @@ class Eraser extends BaseTool_1.default {
127
181
  newAddCommands.forEach(command => command.apply(this.editor));
128
182
  const finalToErase = [];
129
183
  for (const item of toErase) {
130
- if (this.toAdd.includes(item)) {
131
- this.toAdd = this.toAdd.filter(i => i !== item);
184
+ if (this.toAdd.has(item)) {
185
+ this.toAdd.delete(item);
132
186
  }
133
187
  else {
134
188
  finalToErase.push(item);
135
189
  }
136
190
  }
137
191
  this.toRemove.push(...finalToErase);
138
- this.toAdd.push(...toAdd);
192
+ for (const item of toAdd) {
193
+ this.toAdd.add(item);
194
+ }
139
195
  this.eraseCommands.push(new Erase_1.default(finalToErase));
140
196
  this.addCommands.push(...newAddCommands);
141
197
  }
@@ -146,7 +202,7 @@ class Eraser extends BaseTool_1.default {
146
202
  if (event.allPointers.length === 1 || event.current.device === Pointer_1.PointerDevice.Eraser) {
147
203
  this.lastPoint = event.current.canvasPos;
148
204
  this.toRemove = [];
149
- this.toAdd = [];
205
+ this.toAdd.clear();
150
206
  this.isFirstEraseEvt = true;
151
207
  this.drawPreviewAt(event.current.canvasPos);
152
208
  return true;
@@ -162,7 +218,21 @@ class Eraser extends BaseTool_1.default {
162
218
  const commands = [];
163
219
  if (this.addCommands.length > 0) {
164
220
  this.addCommands.forEach(cmd => cmd.unapply(this.editor));
165
- commands.push(...this.toAdd.map(a => EditorImage_1.default.addElement(a)));
221
+ // Remove items from toAdd that are also present in toRemove -- adding, then
222
+ // removing these does nothing, and can break undo/redo.
223
+ for (const item of this.toAdd) {
224
+ if (this.toRemove.includes(item)) {
225
+ this.toAdd.delete(item);
226
+ this.toRemove = this.toRemove.filter(other => other !== item);
227
+ }
228
+ }
229
+ for (const item of this.toRemove) {
230
+ if (this.toAdd.has(item)) {
231
+ this.toAdd.delete(item);
232
+ this.toRemove = this.toRemove.filter(other => other !== item);
233
+ }
234
+ }
235
+ commands.push(...[...this.toAdd].map(a => EditorImage_1.default.addElement(a)));
166
236
  this.addCommands = [];
167
237
  }
168
238
  if (this.eraseCommands.length > 0) {
@@ -180,7 +250,7 @@ class Eraser extends BaseTool_1.default {
180
250
  }
181
251
  this.clearPreview();
182
252
  }
183
- onGestureCancel() {
253
+ onGestureCancel(_event) {
184
254
  this.addCommands.forEach(cmd => cmd.unapply(this.editor));
185
255
  this.eraseCommands.forEach(cmd => cmd.unapply(this.editor));
186
256
  this.eraseCommands = [];
@@ -10,13 +10,27 @@ interface PinchData {
10
10
  dist: number;
11
11
  }
12
12
  export declare enum PanZoomMode {
13
+ /** Touch gestures with a single pointer. Ignores non-touch gestures. */
13
14
  OneFingerTouchGestures = 1,
15
+ /** Touch gestures with exactly two pointers. Ignores non-touch gestures. */
14
16
  TwoFingerTouchGestures = 2,
15
17
  RightClickDrags = 4,
18
+ /** Single-pointer gestures of *any* type (including touch). */
16
19
  SinglePointerGestures = 8,
20
+ /** Keyboard navigation (e.g. LeftArrow to move left). */
17
21
  Keyboard = 16,
22
+ /** If provided, prevents **this** tool from rotating the viewport (other tools may still do so). */
18
23
  RotationLocked = 32
19
24
  }
25
+ /**
26
+ * This tool moves the viewport in response to touchpad, touchscreen, mouse, and keyboard events.
27
+ *
28
+ * Which events are handled, and which are skipped, are determined by the tool's `mode`. For example,
29
+ * a `PanZoom` tool with `mode = PanZoomMode.TwoFingerTouchGestures|PanZoomMode.RightClickDrags` would
30
+ * respond to right-click drag events and two-finger touch gestures.
31
+ *
32
+ * @see {@link setModeEnabled}
33
+ */
20
34
  export default class PanZoom extends BaseTool {
21
35
  private editor;
22
36
  private mode;
@@ -58,8 +72,48 @@ export default class PanZoom extends BaseTool {
58
72
  onWheel({ delta, screenPos }: WheelEvt): boolean;
59
73
  onKeyPress(event: KeyPressEvent): boolean;
60
74
  private isRotationLocked;
75
+ /**
76
+ * Changes the types of gestures used by this pan/zoom tool.
77
+ *
78
+ * @see {@link PanZoomMode} {@link setMode}
79
+ *
80
+ * @example
81
+ * ```ts,runnable
82
+ * import { Editor, PanZoomTool, PanZoomMode } from 'js-draw';
83
+ *
84
+ * const editor = new Editor(document.body);
85
+ *
86
+ * // By default, there are multiple PanZoom tools that handle different events.
87
+ * // This gets all PanZoomTools.
88
+ * const panZoomToolList = editor.toolController.getMatchingTools(PanZoomTool);
89
+ *
90
+ * // The first PanZoomTool is the highest priority -- by default,
91
+ * // this tool is responsible for handling multi-finger touch gestures.
92
+ * //
93
+ * // Lower-priority PanZoomTools handle one-finger touch gestures and
94
+ * // key-presses.
95
+ * const panZoomTool = panZoomToolList[0];
96
+ *
97
+ * // Lock rotation for multi-finger touch gestures.
98
+ * panZoomTool.setModeEnabled(PanZoomMode.RotationLocked, true);
99
+ * ```
100
+ */
61
101
  setModeEnabled(mode: PanZoomMode, enabled: boolean): void;
102
+ /**
103
+ * Sets all modes for this tool using a bitmask.
104
+ *
105
+ * @see {@link setModeEnabled}
106
+ *
107
+ * @example
108
+ * ```ts
109
+ * tool.setMode(PanZoomMode.RotationLocked|PanZoomMode.TwoFingerTouchGestures);
110
+ * ```
111
+ */
62
112
  setMode(mode: PanZoomMode): void;
113
+ /**
114
+ * Returns a bitmask indicating the currently-enabled modes.
115
+ * @see {@link setModeEnabled}
116
+ */
63
117
  getMode(): PanZoomMode;
64
118
  }
65
119
  export {};
@@ -13,11 +13,16 @@ const BaseTool_1 = __importDefault(require("./BaseTool"));
13
13
  const keybindings_1 = require("./keybindings");
14
14
  var PanZoomMode;
15
15
  (function (PanZoomMode) {
16
+ /** Touch gestures with a single pointer. Ignores non-touch gestures. */
16
17
  PanZoomMode[PanZoomMode["OneFingerTouchGestures"] = 1] = "OneFingerTouchGestures";
18
+ /** Touch gestures with exactly two pointers. Ignores non-touch gestures. */
17
19
  PanZoomMode[PanZoomMode["TwoFingerTouchGestures"] = 2] = "TwoFingerTouchGestures";
18
20
  PanZoomMode[PanZoomMode["RightClickDrags"] = 4] = "RightClickDrags";
21
+ /** Single-pointer gestures of *any* type (including touch). */
19
22
  PanZoomMode[PanZoomMode["SinglePointerGestures"] = 8] = "SinglePointerGestures";
23
+ /** Keyboard navigation (e.g. LeftArrow to move left). */
20
24
  PanZoomMode[PanZoomMode["Keyboard"] = 16] = "Keyboard";
25
+ /** If provided, prevents **this** tool from rotating the viewport (other tools may still do so). */
21
26
  PanZoomMode[PanZoomMode["RotationLocked"] = 32] = "RotationLocked";
22
27
  })(PanZoomMode || (exports.PanZoomMode = PanZoomMode = {}));
23
28
  class InertialScroller {
@@ -65,6 +70,15 @@ class InertialScroller {
65
70
  }
66
71
  }
67
72
  }
73
+ /**
74
+ * This tool moves the viewport in response to touchpad, touchscreen, mouse, and keyboard events.
75
+ *
76
+ * Which events are handled, and which are skipped, are determined by the tool's `mode`. For example,
77
+ * a `PanZoom` tool with `mode = PanZoomMode.TwoFingerTouchGestures|PanZoomMode.RightClickDrags` would
78
+ * respond to right-click drag events and two-finger touch gestures.
79
+ *
80
+ * @see {@link setModeEnabled}
81
+ */
68
82
  class PanZoom extends BaseTool_1.default {
69
83
  constructor(editor, mode, description) {
70
84
  super(editor.notifier, description);
@@ -428,8 +442,32 @@ class PanZoom extends BaseTool_1.default {
428
442
  isRotationLocked() {
429
443
  return !!(this.mode & PanZoomMode.RotationLocked);
430
444
  }
431
- // Sets whether the given `mode` is enabled. `mode` should be a single
432
- // mode from the `PanZoomMode` enum.
445
+ /**
446
+ * Changes the types of gestures used by this pan/zoom tool.
447
+ *
448
+ * @see {@link PanZoomMode} {@link setMode}
449
+ *
450
+ * @example
451
+ * ```ts,runnable
452
+ * import { Editor, PanZoomTool, PanZoomMode } from 'js-draw';
453
+ *
454
+ * const editor = new Editor(document.body);
455
+ *
456
+ * // By default, there are multiple PanZoom tools that handle different events.
457
+ * // This gets all PanZoomTools.
458
+ * const panZoomToolList = editor.toolController.getMatchingTools(PanZoomTool);
459
+ *
460
+ * // The first PanZoomTool is the highest priority -- by default,
461
+ * // this tool is responsible for handling multi-finger touch gestures.
462
+ * //
463
+ * // Lower-priority PanZoomTools handle one-finger touch gestures and
464
+ * // key-presses.
465
+ * const panZoomTool = panZoomToolList[0];
466
+ *
467
+ * // Lock rotation for multi-finger touch gestures.
468
+ * panZoomTool.setModeEnabled(PanZoomMode.RotationLocked, true);
469
+ * ```
470
+ */
433
471
  setModeEnabled(mode, enabled) {
434
472
  let newMode = this.mode;
435
473
  if (enabled) {
@@ -440,6 +478,16 @@ class PanZoom extends BaseTool_1.default {
440
478
  }
441
479
  this.setMode(newMode);
442
480
  }
481
+ /**
482
+ * Sets all modes for this tool using a bitmask.
483
+ *
484
+ * @see {@link setModeEnabled}
485
+ *
486
+ * @example
487
+ * ```ts
488
+ * tool.setMode(PanZoomMode.RotationLocked|PanZoomMode.TwoFingerTouchGestures);
489
+ * ```
490
+ */
443
491
  setMode(mode) {
444
492
  if (mode !== this.mode) {
445
493
  this.mode = mode;
@@ -449,6 +497,10 @@ class PanZoom extends BaseTool_1.default {
449
497
  });
450
498
  }
451
499
  }
500
+ /**
501
+ * Returns a bitmask indicating the currently-enabled modes.
502
+ * @see {@link setModeEnabled}
503
+ */
452
504
  getMode() {
453
505
  return this.mode;
454
506
  }
@@ -10,7 +10,7 @@ import AbstractComponent from '../../components/AbstractComponent';
10
10
  import Command from '../../commands/Command';
11
11
  export default class Selection {
12
12
  private editor;
13
- private handles;
13
+ private childwidgets;
14
14
  private originalRegion;
15
15
  private selectionTightBoundingBox;
16
16
  private transformers;
@@ -52,7 +52,7 @@ export default class Selection {
52
52
  private removedFromImage;
53
53
  private addRemoveSelectionFromImage;
54
54
  private removeDeletedElemsFromSelection;
55
- private targetHandle;
55
+ private activeHandle;
56
56
  private backgroundDragging;
57
57
  onDragStart(pointer: Pointer): boolean;
58
58
  onDragUpdate(pointer: Pointer): void;
@@ -57,7 +57,7 @@ class Selection {
57
57
  this.hasParent = true;
58
58
  // Maps IDs to whether we removed the component from the image
59
59
  this.removedFromImage = {};
60
- this.targetHandle = null;
60
+ this.activeHandle = null;
61
61
  this.backgroundDragging = false;
62
62
  this.selectionDuplicatedAnimationTimeout = null;
63
63
  this.originalRegion = new math_1.Rect2(startPoint.x, startPoint.y, 0, 0);
@@ -99,14 +99,14 @@ class Selection {
99
99
  side: math_1.Vec2.of(0.5, 0),
100
100
  icon: this.editor.icons.makeRotateIcon(),
101
101
  }, this, this.editor.viewport, (startPoint) => this.transformers.rotate.onDragStart(startPoint), (currentPoint) => this.transformers.rotate.onDragUpdate(currentPoint), () => this.transformers.rotate.onDragEnd());
102
- this.handles = [
102
+ this.childwidgets = [
103
103
  resizeBothHandle,
104
104
  ...resizeHorizontalHandles,
105
105
  resizeVerticalHandle,
106
106
  rotationHandle,
107
107
  ];
108
- for (const handle of this.handles) {
109
- handle.addTo(this.backgroundElem);
108
+ for (const widget of this.childwidgets) {
109
+ widget.addTo(this.backgroundElem);
110
110
  }
111
111
  this.updateUI();
112
112
  }
@@ -321,8 +321,8 @@ class Selection {
321
321
  else {
322
322
  this.innerContainer.classList.remove('-empty');
323
323
  }
324
- for (const handle of this.handles) {
325
- handle.updatePosition();
324
+ for (const widget of this.childwidgets) {
325
+ widget.updatePosition(this.getScreenRegion());
326
326
  }
327
327
  }
328
328
  // Add/remove the contents of this seleciton from the editor.
@@ -376,16 +376,16 @@ class Selection {
376
376
  onDragStart(pointer) {
377
377
  // Clear the HTML selection (prevent HTML drag and drop being triggered by this drag)
378
378
  document.getSelection()?.removeAllRanges();
379
- this.targetHandle = null;
379
+ this.activeHandle = null;
380
380
  let result = false;
381
381
  this.backgroundDragging = false;
382
382
  if (this.region.containsPoint(pointer.canvasPos)) {
383
383
  this.backgroundDragging = true;
384
384
  result = true;
385
385
  }
386
- for (const handle of this.handles) {
387
- if (handle.containsPoint(pointer.canvasPos)) {
388
- this.targetHandle = handle;
386
+ for (const widget of this.childwidgets) {
387
+ if (widget.containsPoint(pointer.canvasPos)) {
388
+ this.activeHandle = widget;
389
389
  this.backgroundDragging = false;
390
390
  result = true;
391
391
  }
@@ -394,8 +394,8 @@ class Selection {
394
394
  this.removeDeletedElemsFromSelection();
395
395
  this.addRemoveSelectionFromImage(false);
396
396
  }
397
- if (this.targetHandle) {
398
- this.targetHandle.handleDragStart(pointer);
397
+ if (this.activeHandle) {
398
+ this.activeHandle.handleDragStart(pointer);
399
399
  }
400
400
  if (this.backgroundDragging) {
401
401
  this.transformers.drag.onDragStart(pointer.canvasPos);
@@ -406,25 +406,25 @@ class Selection {
406
406
  if (this.backgroundDragging) {
407
407
  this.transformers.drag.onDragUpdate(pointer.canvasPos);
408
408
  }
409
- if (this.targetHandle) {
410
- this.targetHandle.handleDragUpdate(pointer);
409
+ if (this.activeHandle) {
410
+ this.activeHandle.handleDragUpdate(pointer);
411
411
  }
412
412
  }
413
413
  onDragEnd() {
414
414
  if (this.backgroundDragging) {
415
415
  this.transformers.drag.onDragEnd();
416
416
  }
417
- else if (this.targetHandle) {
418
- this.targetHandle.handleDragEnd();
417
+ else if (this.activeHandle) {
418
+ this.activeHandle.handleDragEnd();
419
419
  }
420
420
  this.addRemoveSelectionFromImage(true);
421
421
  this.backgroundDragging = false;
422
- this.targetHandle = null;
422
+ this.activeHandle = null;
423
423
  this.updateUI();
424
424
  }
425
425
  onDragCancel() {
426
426
  this.backgroundDragging = false;
427
- this.targetHandle = null;
427
+ this.activeHandle = null;
428
428
  this.setTransform(math_1.Mat33.identity);
429
429
  this.addRemoveSelectionFromImage(true);
430
430
  this.updateUI();
@@ -452,7 +452,7 @@ class Selection {
452
452
  return false;
453
453
  }
454
454
  deleteSelectedObjects() {
455
- if (this.backgroundDragging || this.targetHandle) {
455
+ if (this.backgroundDragging || this.activeHandle) {
456
456
  this.onDragEnd();
457
457
  }
458
458
  return new Erase_1.default(this.selectedElems);
@@ -469,7 +469,7 @@ class Selection {
469
469
  }, animationDuration);
470
470
  }
471
471
  async duplicateSelectedObjects() {
472
- const wasTransforming = this.backgroundDragging || this.targetHandle;
472
+ const wasTransforming = this.backgroundDragging || this.activeHandle;
473
473
  let tmpApplyCommand = null;
474
474
  if (!wasTransforming) {
475
475
  this.runSelectionDuplicatedAnimation();
@@ -2,6 +2,7 @@ import { Point2, Vec2 } from '@js-draw/math';
2
2
  import Selection from './Selection';
3
3
  import Pointer from '../../Pointer';
4
4
  import Viewport from '../../Viewport';
5
+ import { SelectionBoxChild } from './types';
5
6
  export declare enum HandleAction {
6
7
  ResizeXY = "resize-xy",
7
8
  Rotate = "rotate",
@@ -17,7 +18,7 @@ export declare const handleSize = 30;
17
18
  export type DragStartCallback = (startPoint: Point2) => void;
18
19
  export type DragUpdateCallback = (canvasPoint: Point2) => void;
19
20
  export type DragEndCallback = () => Promise<void> | void;
20
- export default class SelectionHandle {
21
+ export default class SelectionHandle implements SelectionBoxChild {
21
22
  readonly presentation: HandlePresentation;
22
23
  private readonly parent;
23
24
  private readonly viewport;
@@ -34,6 +35,11 @@ export default class SelectionHandle {
34
35
  * element visible on the screen.
35
36
  */
36
37
  addTo(container: HTMLElement): void;
38
+ /**
39
+ * Removes this element from its container. Should only be called
40
+ * after {@link addTo}.
41
+ */
42
+ remove(): void;
37
43
  /**
38
44
  * Returns this handle's bounding box relative to the top left of the
39
45
  * selection box.
@@ -48,7 +54,7 @@ export default class SelectionHandle {
48
54
  /** @returns true iff `point` (in editor **canvas** coordinates) is in this. */
49
55
  containsPoint(point: Point2): boolean;
50
56
  private dragLastPos;
51
- handleDragStart(pointer: Pointer): void;
57
+ handleDragStart(pointer: Pointer): boolean;
52
58
  handleDragUpdate(pointer: Pointer): void;
53
59
  handleDragEnd(): void | Promise<void>;
54
60
  setSnapToGrid(snap: boolean): void;