js-draw 1.18.0 → 1.19.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. package/dist/Editor.css +35 -3
  2. package/dist/bundle.js +2 -2
  3. package/dist/bundledStyles.js +1 -1
  4. package/dist/cjs/Editor.d.ts +20 -1
  5. package/dist/cjs/Editor.js +6 -0
  6. package/dist/cjs/{SVGLoader.d.ts → SVGLoader/index.d.ts} +1 -1
  7. package/dist/cjs/{SVGLoader.js → SVGLoader/index.js} +12 -29
  8. package/dist/cjs/SVGLoader/utils/determineFontSize.d.ts +3 -0
  9. package/dist/cjs/SVGLoader/utils/determineFontSize.js +27 -0
  10. package/dist/cjs/Viewport.d.ts +33 -1
  11. package/dist/cjs/components/TextComponent.js +3 -1
  12. package/dist/cjs/rendering/caching/RenderingCacheNode.js +20 -15
  13. package/dist/cjs/testing/findNodeWithText.d.ts +3 -0
  14. package/dist/cjs/testing/findNodeWithText.js +16 -0
  15. package/dist/cjs/testing/firstElementAncestorOfNode.d.ts +3 -0
  16. package/dist/cjs/testing/firstElementAncestorOfNode.js +13 -0
  17. package/dist/cjs/testing/sendKeyPressRelease.d.ts +3 -0
  18. package/dist/cjs/testing/sendKeyPressRelease.js +8 -0
  19. package/dist/cjs/testing/sendPenEvent.d.ts +2 -2
  20. package/dist/cjs/testing/sendPenEvent.js +26 -3
  21. package/dist/cjs/toolbar/localization.d.ts +2 -0
  22. package/dist/cjs/toolbar/localization.js +2 -0
  23. package/dist/cjs/toolbar/widgets/BaseWidget.d.ts +1 -0
  24. package/dist/cjs/toolbar/widgets/BaseWidget.js +1 -0
  25. package/dist/cjs/toolbar/widgets/InsertImageWidget/ImageWrapper.d.ts +22 -0
  26. package/dist/cjs/toolbar/widgets/InsertImageWidget/ImageWrapper.js +58 -0
  27. package/dist/cjs/toolbar/widgets/InsertImageWidget/fileToImages.d.ts +3 -0
  28. package/dist/cjs/toolbar/widgets/InsertImageWidget/fileToImages.js +21 -0
  29. package/dist/cjs/toolbar/widgets/InsertImageWidget/index.d.ts +37 -0
  30. package/dist/cjs/toolbar/widgets/InsertImageWidget/index.js +281 -0
  31. package/dist/cjs/toolbar/widgets/TextToolWidget.js +5 -3
  32. package/dist/cjs/toolbar/widgets/TextToolWidget.test.d.ts +1 -0
  33. package/dist/cjs/toolbar/widgets/components/makeFileInput.d.ts +12 -2
  34. package/dist/cjs/toolbar/widgets/components/makeFileInput.js +102 -45
  35. package/dist/cjs/toolbar/widgets/components/makeFileInput.test.d.ts +1 -0
  36. package/dist/cjs/toolbar/widgets/components/makeSnappedList.d.ts +15 -0
  37. package/dist/cjs/toolbar/widgets/components/makeSnappedList.js +103 -0
  38. package/dist/cjs/tools/Eraser.d.ts +7 -2
  39. package/dist/cjs/tools/Eraser.js +54 -1
  40. package/dist/cjs/tools/SelectionTool/Selection.d.ts +2 -2
  41. package/dist/cjs/tools/SelectionTool/Selection.js +20 -20
  42. package/dist/cjs/tools/SelectionTool/SelectionHandle.d.ts +8 -2
  43. package/dist/cjs/tools/SelectionTool/SelectionHandle.js +6 -0
  44. package/dist/cjs/tools/SelectionTool/SelectionTool.js +1 -1
  45. package/dist/cjs/tools/SelectionTool/types.d.ts +19 -0
  46. package/dist/cjs/tools/TextTool.js +2 -1
  47. package/dist/cjs/tools/TextTool.test.d.ts +1 -0
  48. package/dist/cjs/tools/ToolController.d.ts +2 -0
  49. package/dist/cjs/tools/ToolController.js +10 -1
  50. package/dist/cjs/util/ReactiveValue.d.ts +2 -0
  51. package/dist/cjs/util/ReactiveValue.js +11 -0
  52. package/dist/cjs/util/bytesToSizeString.d.ts +8 -0
  53. package/dist/cjs/util/bytesToSizeString.js +26 -0
  54. package/dist/cjs/util/bytesToSizeString.test.d.ts +1 -0
  55. package/dist/cjs/util/stopPropagationOfScrollingWheelEvents.js +10 -6
  56. package/dist/cjs/util/waitForAll.d.ts +2 -0
  57. package/dist/cjs/util/waitForAll.js +2 -0
  58. package/dist/cjs/util/waitForImageLoaded.js +3 -0
  59. package/dist/cjs/util/waitForTimeout.d.ts +1 -0
  60. package/dist/cjs/util/waitForTimeout.js +1 -1
  61. package/dist/cjs/version.js +1 -1
  62. package/dist/mjs/Editor.d.ts +20 -1
  63. package/dist/mjs/Editor.mjs +6 -0
  64. package/dist/mjs/{SVGLoader.d.ts → SVGLoader/index.d.ts} +1 -1
  65. package/dist/mjs/{SVGLoader.mjs → SVGLoader/index.mjs} +12 -29
  66. package/dist/mjs/SVGLoader/index.test.d.ts +1 -0
  67. package/dist/mjs/SVGLoader/utils/determineFontSize.d.ts +3 -0
  68. package/dist/mjs/SVGLoader/utils/determineFontSize.mjs +25 -0
  69. package/dist/mjs/Viewport.d.ts +33 -1
  70. package/dist/mjs/components/TextComponent.mjs +3 -1
  71. package/dist/mjs/rendering/caching/RenderingCacheNode.mjs +20 -15
  72. package/dist/mjs/testing/findNodeWithText.d.ts +3 -0
  73. package/dist/mjs/testing/findNodeWithText.mjs +14 -0
  74. package/dist/mjs/testing/firstElementAncestorOfNode.d.ts +3 -0
  75. package/dist/mjs/testing/firstElementAncestorOfNode.mjs +11 -0
  76. package/dist/mjs/testing/sendKeyPressRelease.d.ts +3 -0
  77. package/dist/mjs/testing/sendKeyPressRelease.mjs +6 -0
  78. package/dist/mjs/testing/sendPenEvent.d.ts +2 -2
  79. package/dist/mjs/testing/sendPenEvent.mjs +3 -3
  80. package/dist/mjs/toolbar/localization.d.ts +2 -0
  81. package/dist/mjs/toolbar/localization.mjs +2 -0
  82. package/dist/mjs/toolbar/widgets/BaseWidget.d.ts +1 -0
  83. package/dist/mjs/toolbar/widgets/BaseWidget.mjs +1 -0
  84. package/dist/mjs/toolbar/widgets/InsertImageWidget/ImageWrapper.d.ts +22 -0
  85. package/dist/mjs/toolbar/widgets/InsertImageWidget/ImageWrapper.mjs +54 -0
  86. package/dist/mjs/toolbar/widgets/InsertImageWidget/fileToImages.d.ts +3 -0
  87. package/dist/mjs/toolbar/widgets/InsertImageWidget/fileToImages.mjs +16 -0
  88. package/dist/mjs/toolbar/widgets/InsertImageWidget/index.d.ts +37 -0
  89. package/dist/mjs/toolbar/widgets/InsertImageWidget/index.mjs +276 -0
  90. package/dist/mjs/toolbar/widgets/InsertImageWidget/index.test.d.ts +1 -0
  91. package/dist/mjs/toolbar/widgets/TextToolWidget.mjs +5 -3
  92. package/dist/mjs/toolbar/widgets/TextToolWidget.test.d.ts +1 -0
  93. package/dist/mjs/toolbar/widgets/components/makeFileInput.d.ts +12 -2
  94. package/dist/mjs/toolbar/widgets/components/makeFileInput.mjs +102 -45
  95. package/dist/mjs/toolbar/widgets/components/makeFileInput.test.d.ts +1 -0
  96. package/dist/mjs/toolbar/widgets/components/makeSnappedList.d.ts +15 -0
  97. package/dist/mjs/toolbar/widgets/components/makeSnappedList.mjs +98 -0
  98. package/dist/mjs/tools/Eraser.d.ts +7 -2
  99. package/dist/mjs/tools/Eraser.mjs +54 -1
  100. package/dist/mjs/tools/SelectionTool/Selection.d.ts +2 -2
  101. package/dist/mjs/tools/SelectionTool/Selection.mjs +20 -20
  102. package/dist/mjs/tools/SelectionTool/SelectionHandle.d.ts +8 -2
  103. package/dist/mjs/tools/SelectionTool/SelectionHandle.mjs +6 -0
  104. package/dist/mjs/tools/SelectionTool/SelectionTool.mjs +1 -1
  105. package/dist/mjs/tools/SelectionTool/types.d.ts +19 -0
  106. package/dist/mjs/tools/TextTool.mjs +2 -1
  107. package/dist/mjs/tools/TextTool.test.d.ts +1 -0
  108. package/dist/mjs/tools/ToolController.d.ts +2 -0
  109. package/dist/mjs/tools/ToolController.mjs +10 -1
  110. package/dist/mjs/util/ReactiveValue.d.ts +2 -0
  111. package/dist/mjs/util/ReactiveValue.mjs +11 -0
  112. package/dist/mjs/util/bytesToSizeString.d.ts +8 -0
  113. package/dist/mjs/util/bytesToSizeString.mjs +24 -0
  114. package/dist/mjs/util/bytesToSizeString.test.d.ts +1 -0
  115. package/dist/mjs/util/stopPropagationOfScrollingWheelEvents.mjs +10 -6
  116. package/dist/mjs/util/waitForAll.d.ts +2 -0
  117. package/dist/mjs/util/waitForAll.mjs +2 -0
  118. package/dist/mjs/util/waitForImageLoaded.mjs +3 -0
  119. package/dist/mjs/util/waitForTimeout.d.ts +1 -0
  120. package/dist/mjs/util/waitForTimeout.mjs +1 -1
  121. package/dist/mjs/version.mjs +1 -1
  122. package/package.json +4 -4
  123. package/src/toolbar/toolbar.scss +1 -7
  124. package/src/toolbar/widgets/{InsertImageWidget.scss → InsertImageWidget/index.scss} +3 -2
  125. package/src/toolbar/widgets/components/components.scss +2 -1
  126. package/src/toolbar/widgets/components/makeFileInput.scss +14 -1
  127. package/src/toolbar/widgets/components/makeSnappedList.scss +28 -0
  128. package/src/toolbar/widgets/widgets.scss +7 -0
  129. package/dist/cjs/toolbar/widgets/InsertImageWidget.d.ts +0 -22
  130. package/dist/cjs/toolbar/widgets/InsertImageWidget.js +0 -269
  131. package/dist/mjs/toolbar/widgets/InsertImageWidget.d.ts +0 -22
  132. package/dist/mjs/toolbar/widgets/InsertImageWidget.mjs +0 -264
  133. /package/dist/cjs/{SVGLoader.test.d.ts → SVGLoader/index.test.d.ts} +0 -0
  134. /package/dist/{mjs/SVGLoader.test.d.ts → cjs/toolbar/widgets/InsertImageWidget/index.test.d.ts} +0 -0
@@ -0,0 +1,98 @@
1
+ import stopPropagationOfScrollingWheelEvents from '../../../util/stopPropagationOfScrollingWheelEvents.mjs';
2
+ import { MutableReactiveValue, ReactiveValue } from '../../../util/ReactiveValue.mjs';
3
+ /**
4
+ * Creates a list that snaps to each item and reports the selected item.
5
+ */
6
+ const makeSnappedList = (itemsValue) => {
7
+ const container = document.createElement('div');
8
+ container.classList.add('toolbar-snapped-scroll-list');
9
+ const visibleIndex = MutableReactiveValue.fromInitialValue(0);
10
+ let observer = null;
11
+ const createObserver = () => {
12
+ observer = new IntersectionObserver((entries) => {
13
+ for (const entry of entries) {
14
+ if (entry.isIntersecting && entry.intersectionRatio > 0.7) {
15
+ const indexString = entry.target.getAttribute('data-item-index');
16
+ if (indexString === null)
17
+ throw new Error('Could not find attribute data-item-index');
18
+ const index = Number(indexString);
19
+ visibleIndex.set(index);
20
+ break;
21
+ }
22
+ }
23
+ }, {
24
+ // Element to use as the boudning box with which to intersect.
25
+ // See https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
26
+ root: container,
27
+ // Fraction of an element that must be visible to trigger the callback:
28
+ threshold: 0.9,
29
+ });
30
+ };
31
+ const destroyObserver = () => {
32
+ if (observer) {
33
+ observer.disconnect();
34
+ visibleIndex.set(0);
35
+ observer = null;
36
+ }
37
+ };
38
+ const wrappedItems = ReactiveValue.map(itemsValue, items => {
39
+ return items.map((item, index) => {
40
+ const wrapper = document.createElement('div');
41
+ if (item.element.parentElement)
42
+ item.element.remove();
43
+ wrapper.appendChild(item.element);
44
+ wrapper.classList.add('item');
45
+ wrapper.setAttribute('data-item-index', `${index}`);
46
+ return {
47
+ element: wrapper,
48
+ data: item.data,
49
+ };
50
+ });
51
+ });
52
+ const lastItems = [];
53
+ wrappedItems.onUpdateAndNow(items => {
54
+ visibleIndex.set(-1);
55
+ for (const item of lastItems) {
56
+ observer?.unobserve(item.element);
57
+ }
58
+ container.replaceChildren();
59
+ // An observer is only necessary if there are multiple items to scroll through.
60
+ if (items.length > 1) {
61
+ createObserver();
62
+ }
63
+ else {
64
+ destroyObserver();
65
+ }
66
+ // Different styling is applied when empty
67
+ if (items.length === 0) {
68
+ container.classList.add('-empty');
69
+ }
70
+ else {
71
+ container.classList.remove('-empty');
72
+ }
73
+ for (const item of items) {
74
+ container.appendChild(item.element);
75
+ }
76
+ visibleIndex.set(0);
77
+ if (observer) {
78
+ for (const item of items) {
79
+ observer.observe(item.element);
80
+ }
81
+ }
82
+ });
83
+ const visibleItem = ReactiveValue.map(visibleIndex, index => {
84
+ const values = itemsValue.get();
85
+ if (0 <= index && index < values.length) {
86
+ return values[index].data;
87
+ }
88
+ return null;
89
+ });
90
+ // makeSnappedList is generally shown within the toolbar. This allows users to
91
+ // scroll it with a touchpad.
92
+ stopPropagationOfScrollingWheelEvents(container);
93
+ return {
94
+ container,
95
+ visibleItem,
96
+ };
97
+ };
98
+ export 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;
@@ -13,6 +13,52 @@ export var EraserMode;
13
13
  EraserMode["PartialStroke"] = "partial-stroke";
14
14
  EraserMode["FullStroke"] = "full-stroke";
15
15
  })(EraserMode || (EraserMode = {}));
16
+ /** Handles switching from other primary tools to the eraser and back */
17
+ class EraserSwitcher extends BaseTool {
18
+ constructor(editor, eraser) {
19
+ super(editor.notifier, editor.localization.changeTool);
20
+ this.editor = editor;
21
+ this.eraser = eraser;
22
+ }
23
+ onPointerDown(event) {
24
+ if (event.allPointers.length === 1 && event.current.device === PointerDevice.Eraser) {
25
+ const toolController = this.editor.toolController;
26
+ const enabledPrimaryTools = toolController.getPrimaryTools().filter(tool => tool.isEnabled());
27
+ if (enabledPrimaryTools.length) {
28
+ this.previousEnabledTool = enabledPrimaryTools[0];
29
+ }
30
+ else {
31
+ this.previousEnabledTool = null;
32
+ }
33
+ this.previousEraserEnabledState = this.eraser.isEnabled();
34
+ this.eraser.setEnabled(true);
35
+ if (this.eraser.onPointerDown(event)) {
36
+ return true;
37
+ }
38
+ else {
39
+ this.restoreOriginalTool();
40
+ }
41
+ }
42
+ return false;
43
+ }
44
+ onPointerMove(event) {
45
+ this.eraser.onPointerMove(event);
46
+ }
47
+ restoreOriginalTool() {
48
+ this.eraser.setEnabled(this.previousEraserEnabledState);
49
+ if (this.previousEnabledTool) {
50
+ this.previousEnabledTool.setEnabled(true);
51
+ }
52
+ }
53
+ onPointerUp(event) {
54
+ this.eraser.onPointerUp(event);
55
+ this.restoreOriginalTool();
56
+ }
57
+ onGestureCancel(event) {
58
+ this.eraser.onGestureCancel(event);
59
+ this.restoreOriginalTool();
60
+ }
61
+ }
16
62
  export default class Eraser extends BaseTool {
17
63
  constructor(editor, description, options) {
18
64
  super(editor.notifier, description);
@@ -39,6 +85,13 @@ export default class Eraser extends BaseTool {
39
85
  });
40
86
  });
41
87
  }
88
+ /**
89
+ * @returns a tool that briefly enables the eraser when a physical eraser is used.
90
+ * This tool should be added to the tool list after the primary tools.
91
+ */
92
+ makeEraserSwitcherTool() {
93
+ return new EraserSwitcher(this.editor, this);
94
+ }
42
95
  clearPreview() {
43
96
  this.editor.clearWetInk();
44
97
  }
@@ -174,7 +227,7 @@ export default class Eraser extends BaseTool {
174
227
  }
175
228
  this.clearPreview();
176
229
  }
177
- onGestureCancel() {
230
+ onGestureCancel(_event) {
178
231
  this.addCommands.forEach(cmd => cmd.unapply(this.editor));
179
232
  this.eraseCommands.forEach(cmd => cmd.unapply(this.editor));
180
233
  this.eraseCommands = [];
@@ -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;
@@ -29,7 +29,7 @@ class Selection {
29
29
  this.hasParent = true;
30
30
  // Maps IDs to whether we removed the component from the image
31
31
  this.removedFromImage = {};
32
- this.targetHandle = null;
32
+ this.activeHandle = null;
33
33
  this.backgroundDragging = false;
34
34
  this.selectionDuplicatedAnimationTimeout = null;
35
35
  this.originalRegion = new Rect2(startPoint.x, startPoint.y, 0, 0);
@@ -71,14 +71,14 @@ class Selection {
71
71
  side: Vec2.of(0.5, 0),
72
72
  icon: this.editor.icons.makeRotateIcon(),
73
73
  }, this, this.editor.viewport, (startPoint) => this.transformers.rotate.onDragStart(startPoint), (currentPoint) => this.transformers.rotate.onDragUpdate(currentPoint), () => this.transformers.rotate.onDragEnd());
74
- this.handles = [
74
+ this.childwidgets = [
75
75
  resizeBothHandle,
76
76
  ...resizeHorizontalHandles,
77
77
  resizeVerticalHandle,
78
78
  rotationHandle,
79
79
  ];
80
- for (const handle of this.handles) {
81
- handle.addTo(this.backgroundElem);
80
+ for (const widget of this.childwidgets) {
81
+ widget.addTo(this.backgroundElem);
82
82
  }
83
83
  this.updateUI();
84
84
  }
@@ -293,8 +293,8 @@ class Selection {
293
293
  else {
294
294
  this.innerContainer.classList.remove('-empty');
295
295
  }
296
- for (const handle of this.handles) {
297
- handle.updatePosition();
296
+ for (const widget of this.childwidgets) {
297
+ widget.updatePosition(this.getScreenRegion());
298
298
  }
299
299
  }
300
300
  // Add/remove the contents of this seleciton from the editor.
@@ -348,16 +348,16 @@ class Selection {
348
348
  onDragStart(pointer) {
349
349
  // Clear the HTML selection (prevent HTML drag and drop being triggered by this drag)
350
350
  document.getSelection()?.removeAllRanges();
351
- this.targetHandle = null;
351
+ this.activeHandle = null;
352
352
  let result = false;
353
353
  this.backgroundDragging = false;
354
354
  if (this.region.containsPoint(pointer.canvasPos)) {
355
355
  this.backgroundDragging = true;
356
356
  result = true;
357
357
  }
358
- for (const handle of this.handles) {
359
- if (handle.containsPoint(pointer.canvasPos)) {
360
- this.targetHandle = handle;
358
+ for (const widget of this.childwidgets) {
359
+ if (widget.containsPoint(pointer.canvasPos)) {
360
+ this.activeHandle = widget;
361
361
  this.backgroundDragging = false;
362
362
  result = true;
363
363
  }
@@ -366,8 +366,8 @@ class Selection {
366
366
  this.removeDeletedElemsFromSelection();
367
367
  this.addRemoveSelectionFromImage(false);
368
368
  }
369
- if (this.targetHandle) {
370
- this.targetHandle.handleDragStart(pointer);
369
+ if (this.activeHandle) {
370
+ this.activeHandle.handleDragStart(pointer);
371
371
  }
372
372
  if (this.backgroundDragging) {
373
373
  this.transformers.drag.onDragStart(pointer.canvasPos);
@@ -378,25 +378,25 @@ class Selection {
378
378
  if (this.backgroundDragging) {
379
379
  this.transformers.drag.onDragUpdate(pointer.canvasPos);
380
380
  }
381
- if (this.targetHandle) {
382
- this.targetHandle.handleDragUpdate(pointer);
381
+ if (this.activeHandle) {
382
+ this.activeHandle.handleDragUpdate(pointer);
383
383
  }
384
384
  }
385
385
  onDragEnd() {
386
386
  if (this.backgroundDragging) {
387
387
  this.transformers.drag.onDragEnd();
388
388
  }
389
- else if (this.targetHandle) {
390
- this.targetHandle.handleDragEnd();
389
+ else if (this.activeHandle) {
390
+ this.activeHandle.handleDragEnd();
391
391
  }
392
392
  this.addRemoveSelectionFromImage(true);
393
393
  this.backgroundDragging = false;
394
- this.targetHandle = null;
394
+ this.activeHandle = null;
395
395
  this.updateUI();
396
396
  }
397
397
  onDragCancel() {
398
398
  this.backgroundDragging = false;
399
- this.targetHandle = null;
399
+ this.activeHandle = null;
400
400
  this.setTransform(Mat33.identity);
401
401
  this.addRemoveSelectionFromImage(true);
402
402
  this.updateUI();
@@ -424,7 +424,7 @@ class Selection {
424
424
  return false;
425
425
  }
426
426
  deleteSelectedObjects() {
427
- if (this.backgroundDragging || this.targetHandle) {
427
+ if (this.backgroundDragging || this.activeHandle) {
428
428
  this.onDragEnd();
429
429
  }
430
430
  return new Erase(this.selectedElems);
@@ -441,7 +441,7 @@ class Selection {
441
441
  }, animationDuration);
442
442
  }
443
443
  async duplicateSelectedObjects() {
444
- const wasTransforming = this.backgroundDragging || this.targetHandle;
444
+ const wasTransforming = this.backgroundDragging || this.activeHandle;
445
445
  let tmpApplyCommand = null;
446
446
  if (!wasTransforming) {
447
447
  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;
@@ -61,6 +61,11 @@ export default class SelectionHandle {
61
61
  addTo(container) {
62
62
  container.appendChild(this.element);
63
63
  }
64
+ /**
65
+ * Removes this element from its container. Should only be called
66
+ * after {@link addTo}.
67
+ */
68
+ remove() { this.element.remove(); }
64
69
  /**
65
70
  * Returns this handle's bounding box relative to the top left of the
66
71
  * selection box.
@@ -109,6 +114,7 @@ export default class SelectionHandle {
109
114
  handleDragStart(pointer) {
110
115
  this.onDragStart(pointer.canvasPos);
111
116
  this.dragLastPos = pointer.canvasPos;
117
+ return true;
112
118
  }
113
119
  handleDragUpdate(pointer) {
114
120
  if (!this.dragLastPos) {
@@ -398,7 +398,7 @@ class SelectionTool extends BaseTool {
398
398
  resolve(blob);
399
399
  }
400
400
  else {
401
- reject('Failed to convert canvas to blob.');
401
+ reject(new Error('Failed to convert canvas to blob.'));
402
402
  }
403
403
  }, 'image/png');
404
404
  }));
@@ -1,3 +1,5 @@
1
+ import type { Rect2, Point2 } from '@js-draw/math';
2
+ import Pointer from '../../Pointer';
1
3
  export declare enum ResizeMode {
2
4
  Both = 0,
3
5
  HorizontalOnly = 1,
@@ -7,3 +9,20 @@ export declare enum TransformMode {
7
9
  Snap = 0,
8
10
  NoSnap = 1
9
11
  }
12
+ /**
13
+ * Represents a child of the selection that should move with the selection
14
+ * and handle events.
15
+ *
16
+ * Although selection children should be `HTMLElement`s, the selection may be
17
+ * hidden behind an invisible element. As such, these elements should handle
18
+ * drag start/update/end events.
19
+ */
20
+ export interface SelectionBoxChild {
21
+ updatePosition(selectionScreenBBox: Rect2): void;
22
+ containsPoint(point: Point2): boolean;
23
+ addTo(container: HTMLElement): void;
24
+ remove(): void;
25
+ handleDragStart(pointer: Pointer): boolean;
26
+ handleDragUpdate(pointer: Pointer): void;
27
+ handleDragEnd(): void;
28
+ }
@@ -18,9 +18,10 @@ export default class TextTool extends BaseTool {
18
18
  this.textMeasuringCtx = null;
19
19
  this.textScale = Vec2.of(1, 1);
20
20
  this.removeExistingCommand = null;
21
+ const editorFonts = editor.getCurrentSettings().text?.fonts ?? [];
21
22
  this.textStyleValue = ReactiveValue.fromInitialValue({
22
23
  size: 32,
23
- fontFamily: 'sans-serif',
24
+ fontFamily: editorFonts.length > 0 ? editorFonts[0] : 'sans-serif',
24
25
  renderingStyle: {
25
26
  fill: Color4.purple,
26
27
  },
@@ -0,0 +1 @@
1
+ export {};
@@ -55,6 +55,8 @@ export default class ToolController implements InputEventListener {
55
55
  insertToolsAfter(insertAfter: BaseTool, toolsToInsert: BaseTool[]): void;
56
56
  /** @see {@link insertToolsAfter} */
57
57
  insertToolsBefore(insertBefore: BaseTool, toolsToInsert: BaseTool[]): void;
58
+ /** @internal */
59
+ changeActiveToolTo(tool: BaseTool): void;
58
60
  private onEventInternal;
59
61
  /** Alias for {@link dispatchInputEvent}. */
60
62
  onEvent(event: InputEvt): boolean;
@@ -34,6 +34,7 @@ export default class ToolController {
34
34
  const secondaryPenTool = new Pen(editor, localization.penTool(2), { color: Color4.clay, thickness: 4 });
35
35
  // Stabilize the secondary pen tool.
36
36
  secondaryPenTool.setInputMapper(new InputStabilizer(editor.viewport));
37
+ const eraser = new Eraser(editor, localization.eraserTool);
37
38
  const primaryTools = [
38
39
  // Three pens
39
40
  primaryPenTool,
@@ -44,7 +45,7 @@ export default class ToolController {
44
45
  thickness: 40,
45
46
  factory: makePressureSensitiveFreehandLineBuilder
46
47
  }),
47
- new Eraser(editor, localization.eraserTool),
48
+ eraser,
48
49
  new SelectionTool(editor, localization.selectionTool),
49
50
  new TextTool(editor, localization.textTool, localization),
50
51
  new PanZoom(editor, PanZoomMode.SinglePointerGestures, localization.anyDevicePanning),
@@ -62,6 +63,7 @@ export default class ToolController {
62
63
  new UndoRedoShortcut(editor),
63
64
  new ToolbarShortcutHandler(editor),
64
65
  new ToolSwitcherShortcut(editor),
66
+ eraser.makeEraserSwitcherTool(),
65
67
  new FindTool(editor),
66
68
  new PasteHandler(editor),
67
69
  new SelectAllShortcutHandler(editor),
@@ -180,6 +182,13 @@ export default class ToolController {
180
182
  insertToolsBefore(insertBefore, toolsToInsert) {
181
183
  this.insertTools(insertBefore, toolsToInsert, 'before');
182
184
  }
185
+ /** @internal */
186
+ changeActiveToolTo(tool) {
187
+ if (!tool.isEnabled()) {
188
+ tool.setEnabled(true);
189
+ }
190
+ this.activeTool = tool;
191
+ }
183
192
  // @internal use `dispatchEvent` rather than calling `onEvent` directly.
184
193
  onEventInternal(event) {
185
194
  const isEditorReadOnly = this.isEditorReadOnly.get();
@@ -33,6 +33,8 @@ export declare abstract class ReactiveValue<T> {
33
33
  * @see {@link onUpdate}.
34
34
  */
35
35
  abstract onUpdateAndNow(callback: UpdateCallback<T>): ListenerResult;
36
+ /** Returns a promise that resolves when this value is next changed. */
37
+ waitForNextUpdate(): Promise<T>;
36
38
  /** Creates a `ReactiveValue` with an initial value, `initialValue`. */
37
39
  static fromInitialValue<T>(initialValue: T): MutableReactiveValue<T>;
38
40
  /** Returns a `ReactiveValue` that is **known** will never change. */
@@ -35,6 +35,15 @@ const noOpSetUpdateListener = () => {
35
35
  * Avoid extending this class from an external library, as that may not be stable.
36
36
  */
37
37
  export class ReactiveValue {
38
+ /** Returns a promise that resolves when this value is next changed. */
39
+ waitForNextUpdate() {
40
+ return new Promise(resolve => {
41
+ const listener = this.onUpdate(value => {
42
+ listener.remove();
43
+ resolve(value);
44
+ });
45
+ });
46
+ }
38
47
  /** Creates a `ReactiveValue` with an initial value, `initialValue`. */
39
48
  static fromInitialValue(initialValue) {
40
49
  return new ReactiveValueImpl(initialValue);
@@ -48,6 +57,8 @@ export class ReactiveValue {
48
57
  callback(value);
49
58
  return noOpUpdateListenerResult;
50
59
  },
60
+ // Never resolves -- immutable.
61
+ waitForNextUpdate: () => new Promise(() => { }),
51
62
  };
52
63
  }
53
64
  /**
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Returns a size in bytes, KiB, or MiB with units suffix.
3
+ */
4
+ declare const bytesToSizeString: (sizeBytes: number) => {
5
+ size: number;
6
+ units: string;
7
+ };
8
+ export default bytesToSizeString;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Returns a size in bytes, KiB, or MiB with units suffix.
3
+ */
4
+ const bytesToSizeString = (sizeBytes) => {
5
+ const sizeInKiB = sizeBytes / 1024;
6
+ const sizeInMiB = sizeInKiB / 1024;
7
+ const sizeInGiB = sizeInMiB / 1024;
8
+ let units = 'B';
9
+ let size = sizeBytes;
10
+ if (sizeInGiB >= 1) {
11
+ size = sizeInGiB;
12
+ units = 'GiB';
13
+ }
14
+ else if (sizeInMiB >= 1) {
15
+ size = sizeInMiB;
16
+ units = 'MiB';
17
+ }
18
+ else if (sizeInKiB >= 1) {
19
+ size = sizeInKiB;
20
+ units = 'KiB';
21
+ }
22
+ return { size, units };
23
+ };
24
+ export default bytesToSizeString;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,13 +1,17 @@
1
1
  const stopPropagationOfScrollingWheelEvents = (scrollingContainer) => {
2
+ const scrollsAxis = (delta, clientSize, scrollOffset, scrollSize) => {
3
+ const hasScroll = clientSize !== scrollSize && delta !== 0;
4
+ const eventScrollsPastStart = scrollOffset + delta <= 0;
5
+ const scrollEnd = scrollOffset + clientSize;
6
+ const eventScrollsPastEnd = scrollEnd + delta > scrollSize;
7
+ return hasScroll && !eventScrollsPastStart && !eventScrollsPastEnd;
8
+ };
2
9
  scrollingContainer.onwheel = (event) => {
3
- const hasScroll = scrollingContainer.clientWidth !== scrollingContainer.scrollWidth
4
- && event.deltaX !== 0;
5
- const eventScrollsPastLeft = scrollingContainer.scrollLeft + event.deltaX <= 0;
6
- const scrollRight = scrollingContainer.scrollLeft + scrollingContainer.clientWidth;
7
- const eventScrollsPastRight = scrollRight + event.deltaX > scrollingContainer.scrollWidth;
10
+ const scrollsX = scrollsAxis(event.deltaX, scrollingContainer.clientWidth, scrollingContainer.scrollLeft, scrollingContainer.scrollWidth);
11
+ const scrollsY = scrollsAxis(event.deltaY, scrollingContainer.clientHeight, scrollingContainer.scrollTop, scrollingContainer.scrollHeight);
8
12
  // Stop the editor from receiving the event if it will scroll the pen type selector
9
13
  // instead.
10
- if (hasScroll && !eventScrollsPastLeft && !eventScrollsPastRight) {
14
+ if (scrollsX || scrollsY) {
11
15
  event.stopPropagation();
12
16
  }
13
17
  };
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Resolves when all given promises have resolved. If no promises are given,
3
3
  * does not return a Promise.
4
+ *
5
+ * If all elements of `results` are known to be `Promise`s, use `Promise.all`.
4
6
  */
5
7
  declare const waitForAll: (results: (Promise<void> | void)[]) => Promise<void> | void;
6
8
  export default waitForAll;
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Resolves when all given promises have resolved. If no promises are given,
3
3
  * does not return a Promise.
4
+ *
5
+ * If all elements of `results` are known to be `Promise`s, use `Promise.all`.
4
6
  */
5
7
  const waitForAll = (results) => {
6
8
  // If any are Promises...
@@ -2,7 +2,10 @@ const waitForImageLoad = async (image) => {
2
2
  if (!image.complete) {
3
3
  await new Promise((resolve, reject) => {
4
4
  image.onload = event => resolve(event);
5
+ // TODO(v2): Return a `new Error(event.message)`
6
+ // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- Forwarding an error-like object.
5
7
  image.onerror = event => reject(event);
8
+ // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- Forwarding an error-like object.
6
9
  image.onabort = event => reject(event);
7
10
  });
8
11
  }
@@ -1,2 +1,3 @@
1
+ /** Returns a promise that resolves after `timeout` milliseconds. */
1
2
  declare const waitForTimeout: (timeout: number) => Promise<void>;
2
3
  export default waitForTimeout;
@@ -1,4 +1,4 @@
1
- // Returns a promise that resolves after `timeout` milliseconds.
1
+ /** Returns a promise that resolves after `timeout` milliseconds. */
2
2
  const waitForTimeout = (timeout) => {
3
3
  return new Promise(resolve => {
4
4
  setTimeout(() => resolve(), timeout);
@@ -4,5 +4,5 @@
4
4
  * @internal
5
5
  */
6
6
  export default {
7
- number: '1.18.0',
7
+ number: '1.19.1',
8
8
  };