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
@@ -6,9 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const ReactiveValue_1 = __importDefault(require("../../../util/ReactiveValue"));
7
7
  let idCounter = 0;
8
8
  /**
9
- * Creates a stylized file input.
9
+ * Creates a stylized file input. This file input can either use the system file picker, or a custom
10
+ * one specified by `customPickerAction`.
10
11
  */
11
- const makeFileInput = (labelText, context, accepts = '*') => {
12
+ const makeFileInput = (labelText, context, { accepts = '*', allowMultiSelect = false, customPickerAction } = {}) => {
12
13
  const container = document.createElement('div');
13
14
  const label = document.createElement('label');
14
15
  const input = document.createElement('input');
@@ -18,7 +19,9 @@ const makeFileInput = (labelText, context, accepts = '*') => {
18
19
  container.classList.add('toolbar--file-input-container');
19
20
  label.appendChild(document.createTextNode(labelText));
20
21
  input.accept = accepts;
21
- input.type = 'file';
22
+ input.type = customPickerAction ? 'button' : 'file';
23
+ input.classList.add('file-input');
24
+ input.multiple = allowMultiSelect;
22
25
  // Associate the label with the input
23
26
  const inputId = `js-draw-file-input-${idCounter++}`;
24
27
  input.setAttribute('id', inputId);
@@ -29,48 +32,22 @@ const makeFileInput = (labelText, context, accepts = '*') => {
29
32
  label.appendChild(descriptionBox);
30
33
  container.replaceChildren(label, input);
31
34
  const selectedFiles = ReactiveValue_1.default.fromInitialValue([]);
32
- // Support droping files
33
- label.addEventListener('dragover', event => {
34
- event.preventDefault();
35
- label.classList.add('drag-target');
36
- });
37
- label.addEventListener('dragenter', event => {
38
- event.preventDefault();
39
- label.classList.add('drag-target');
40
- });
41
- label.addEventListener('dragleave', event => {
42
- event.preventDefault();
43
- // Ensure the event wasn't targeting a child.
44
- // See https://stackoverflow.com/a/54271161 and
45
- // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/relatedTarget
46
- const enteringElement = event.relatedTarget;
47
- if (!enteringElement || !label.contains(enteringElement)) {
48
- label.classList.remove('drag-target');
49
- }
50
- });
51
- // See https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop#process_the_drop
52
- label.addEventListener('drop', event => {
53
- event.preventDefault();
54
- label.classList.remove('drag-target');
55
- const fileList = [];
56
- if (event.dataTransfer) {
57
- fileList.push(...event.dataTransfer.files);
58
- }
59
- selectedFiles.set(fileList);
60
- });
61
- input.addEventListener('change', () => {
62
- const fileList = input.files ?? [];
63
- selectedFiles.set([...fileList]);
64
- });
65
- selectedFiles.onUpdate(files => {
66
- if (files.length === 0 && input.files && input.files.length > 0) {
67
- input.value = '';
35
+ let loading = false;
36
+ let cancelLoading = null;
37
+ const updateStatusText = () => {
38
+ const files = selectedFiles.get();
39
+ if (loading) {
40
+ descriptionText.textContent = context.localization.fileInput__loading;
41
+ if (cancelLoading) {
42
+ const cancelText = document.createElement('b');
43
+ cancelText.textContent = context.localization.cancel;
44
+ cancelText.classList.add('cancel-button');
45
+ descriptionText.appendChild(cancelText);
46
+ }
47
+ icon.style.display = 'none';
68
48
  }
69
- });
70
- // Update the status text and hide/show the icon.
71
- selectedFiles.onUpdateAndNow(files => {
72
- if (files.length > 0) {
73
- descriptionText.innerText = files.map(file => file.name).join('\n');
49
+ else if (files.length > 0) {
50
+ descriptionText.textContent = files.map(file => file.name).join('\n');
74
51
  // Only show the icon when there are files
75
52
  icon.style.display = 'none';
76
53
  }
@@ -90,7 +67,7 @@ const makeFileInput = (labelText, context, accepts = '*') => {
90
67
  // Inside a {{pair of curly braces}}?
91
68
  if (i % 2 === 1) {
92
69
  const boldedText = document.createElement('b');
93
- boldedText.innerText = segments[i];
70
+ boldedText.textContent = segments[i];
94
71
  descriptionText.appendChild(boldedText);
95
72
  }
96
73
  else {
@@ -98,7 +75,87 @@ const makeFileInput = (labelText, context, accepts = '*') => {
98
75
  }
99
76
  }
100
77
  }
78
+ };
79
+ const addFileEventListeners = () => {
80
+ // Support dropping files
81
+ label.addEventListener('dragover', event => {
82
+ event.preventDefault();
83
+ label.classList.add('drag-target');
84
+ });
85
+ label.addEventListener('dragenter', event => {
86
+ event.preventDefault();
87
+ label.classList.add('drag-target');
88
+ });
89
+ label.addEventListener('dragleave', event => {
90
+ event.preventDefault();
91
+ // Ensure the event wasn't targeting a child.
92
+ // See https://stackoverflow.com/a/54271161 and
93
+ // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/relatedTarget
94
+ const enteringElement = event.relatedTarget;
95
+ if (!enteringElement || !label.contains(enteringElement)) {
96
+ label.classList.remove('drag-target');
97
+ }
98
+ });
99
+ // See https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop#process_the_drop
100
+ label.addEventListener('drop', event => {
101
+ event.preventDefault();
102
+ label.classList.remove('drag-target');
103
+ const fileList = [];
104
+ if (event.dataTransfer) {
105
+ fileList.push(...event.dataTransfer.files);
106
+ }
107
+ selectedFiles.set(fileList);
108
+ });
109
+ input.addEventListener('change', () => {
110
+ const fileList = input.files ?? [];
111
+ selectedFiles.set([...fileList]);
112
+ });
113
+ };
114
+ addFileEventListeners();
115
+ // Support for custom file pickers
116
+ if (customPickerAction) {
117
+ const promptForFiles = async () => {
118
+ if (loading) {
119
+ cancelLoading?.();
120
+ return;
121
+ }
122
+ container.classList.add('-loading');
123
+ loading = true;
124
+ updateStatusText();
125
+ try {
126
+ const data = await customPickerAction({
127
+ setOnCancelCallback: (onCancel) => {
128
+ if (!loading) {
129
+ throw new Error('Task already completed. Can\'t register cancel handler.');
130
+ }
131
+ cancelLoading = () => {
132
+ cancelLoading = null;
133
+ updateStatusText();
134
+ onCancel();
135
+ };
136
+ updateStatusText();
137
+ },
138
+ });
139
+ if (data) {
140
+ selectedFiles.set(data);
141
+ }
142
+ }
143
+ finally {
144
+ container.classList.remove('-loading');
145
+ loading = false;
146
+ updateStatusText();
147
+ }
148
+ };
149
+ input.onclick = promptForFiles;
150
+ }
151
+ selectedFiles.onUpdate(files => {
152
+ if (files.length === 0 && input.files && input.files.length > 0) {
153
+ input.value = '';
154
+ }
155
+ cancelLoading?.();
101
156
  });
157
+ // Update the status text and hide/show the icon.
158
+ selectedFiles.onUpdateAndNow(updateStatusText);
102
159
  return {
103
160
  container,
104
161
  input,
@@ -0,0 +1,15 @@
1
+ import { ReactiveValue } from '../../../util/ReactiveValue';
2
+ export interface SnappedListItem<DataType> {
3
+ element: HTMLElement;
4
+ data: DataType;
5
+ }
6
+ type SnappedListItems<DataType> = Array<SnappedListItem<DataType>>;
7
+ export interface SnappedListControl<DataType> {
8
+ container: HTMLElement;
9
+ visibleItem: ReactiveValue<DataType | null>;
10
+ }
11
+ /**
12
+ * Creates a list that snaps to each item and reports the selected item.
13
+ */
14
+ declare const makeSnappedList: <DataType>(itemsValue: ReactiveValue<SnappedListItems<DataType>>) => SnappedListControl<DataType>;
15
+ export default makeSnappedList;
@@ -0,0 +1,103 @@
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 visibleIndex = ReactiveValue_1.MutableReactiveValue.fromInitialValue(0);
15
+ let observer = null;
16
+ const createObserver = () => {
17
+ observer = new IntersectionObserver((entries) => {
18
+ for (const entry of entries) {
19
+ if (entry.isIntersecting && entry.intersectionRatio > 0.7) {
20
+ const indexString = entry.target.getAttribute('data-item-index');
21
+ if (indexString === null)
22
+ throw new Error('Could not find attribute data-item-index');
23
+ const index = Number(indexString);
24
+ visibleIndex.set(index);
25
+ break;
26
+ }
27
+ }
28
+ }, {
29
+ // Element to use as the boudning box with which to intersect.
30
+ // See https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
31
+ root: container,
32
+ // Fraction of an element that must be visible to trigger the callback:
33
+ threshold: 0.9,
34
+ });
35
+ };
36
+ const destroyObserver = () => {
37
+ if (observer) {
38
+ observer.disconnect();
39
+ visibleIndex.set(0);
40
+ observer = null;
41
+ }
42
+ };
43
+ const wrappedItems = ReactiveValue_1.ReactiveValue.map(itemsValue, items => {
44
+ return items.map((item, index) => {
45
+ const wrapper = document.createElement('div');
46
+ if (item.element.parentElement)
47
+ item.element.remove();
48
+ wrapper.appendChild(item.element);
49
+ wrapper.classList.add('item');
50
+ wrapper.setAttribute('data-item-index', `${index}`);
51
+ return {
52
+ element: wrapper,
53
+ data: item.data,
54
+ };
55
+ });
56
+ });
57
+ const lastItems = [];
58
+ wrappedItems.onUpdateAndNow(items => {
59
+ visibleIndex.set(-1);
60
+ for (const item of lastItems) {
61
+ observer?.unobserve(item.element);
62
+ }
63
+ container.replaceChildren();
64
+ // An observer is only necessary if there are multiple items to scroll through.
65
+ if (items.length > 1) {
66
+ createObserver();
67
+ }
68
+ else {
69
+ destroyObserver();
70
+ }
71
+ // Different styling is applied when empty
72
+ if (items.length === 0) {
73
+ container.classList.add('-empty');
74
+ }
75
+ else {
76
+ container.classList.remove('-empty');
77
+ }
78
+ for (const item of items) {
79
+ container.appendChild(item.element);
80
+ }
81
+ visibleIndex.set(0);
82
+ if (observer) {
83
+ for (const item of items) {
84
+ observer.observe(item.element);
85
+ }
86
+ }
87
+ });
88
+ const visibleItem = ReactiveValue_1.ReactiveValue.map(visibleIndex, index => {
89
+ const values = itemsValue.get();
90
+ if (0 <= index && index < values.length) {
91
+ return values[index].data;
92
+ }
93
+ return null;
94
+ });
95
+ // makeSnappedList is generally shown within the toolbar. This allows users to
96
+ // scroll it with a touchpad.
97
+ (0, stopPropagationOfScrollingWheelEvents_1.default)(container);
98
+ return {
99
+ container,
100
+ visibleItem,
101
+ };
102
+ };
103
+ 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,6 +19,52 @@ 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);
@@ -45,6 +91,13 @@ class Eraser extends BaseTool_1.default {
45
91
  });
46
92
  });
47
93
  }
94
+ /**
95
+ * @returns a tool that briefly enables the eraser when a physical eraser is used.
96
+ * This tool should be added to the tool list after the primary tools.
97
+ */
98
+ makeEraserSwitcherTool() {
99
+ return new EraserSwitcher(this.editor, this);
100
+ }
48
101
  clearPreview() {
49
102
  this.editor.clearWetInk();
50
103
  }
@@ -180,7 +233,7 @@ class Eraser extends BaseTool_1.default {
180
233
  }
181
234
  this.clearPreview();
182
235
  }
183
- onGestureCancel() {
236
+ onGestureCancel(_event) {
184
237
  this.addCommands.forEach(cmd => cmd.unapply(this.editor));
185
238
  this.eraseCommands.forEach(cmd => cmd.unapply(this.editor));
186
239
  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;
@@ -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;
@@ -64,6 +64,11 @@ class SelectionHandle {
64
64
  addTo(container) {
65
65
  container.appendChild(this.element);
66
66
  }
67
+ /**
68
+ * Removes this element from its container. Should only be called
69
+ * after {@link addTo}.
70
+ */
71
+ remove() { this.element.remove(); }
67
72
  /**
68
73
  * Returns this handle's bounding box relative to the top left of the
69
74
  * selection box.
@@ -112,6 +117,7 @@ class SelectionHandle {
112
117
  handleDragStart(pointer) {
113
118
  this.onDragStart(pointer.canvasPos);
114
119
  this.dragLastPos = pointer.canvasPos;
120
+ return true;
115
121
  }
116
122
  handleDragUpdate(pointer) {
117
123
  if (!this.dragLastPos) {
@@ -404,7 +404,7 @@ class SelectionTool extends BaseTool_1.default {
404
404
  resolve(blob);
405
405
  }
406
406
  else {
407
- reject('Failed to convert canvas to blob.');
407
+ reject(new Error('Failed to convert canvas to blob.'));
408
408
  }
409
409
  }, 'image/png');
410
410
  }));
@@ -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
+ }
@@ -23,9 +23,10 @@ class TextTool extends BaseTool_1.default {
23
23
  this.textMeasuringCtx = null;
24
24
  this.textScale = math_1.Vec2.of(1, 1);
25
25
  this.removeExistingCommand = null;
26
+ const editorFonts = editor.getCurrentSettings().text?.fonts ?? [];
26
27
  this.textStyleValue = ReactiveValue_1.ReactiveValue.fromInitialValue({
27
28
  size: 32,
28
- fontFamily: 'sans-serif',
29
+ fontFamily: editorFonts.length > 0 ? editorFonts[0] : 'sans-serif',
29
30
  renderingStyle: {
30
31
  fill: math_1.Color4.purple,
31
32
  },
@@ -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;