js-draw 1.9.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/dist/Editor.css +48 -1
  2. package/dist/bundle.js +2 -2
  3. package/dist/bundledStyles.js +1 -1
  4. package/dist/cjs/Editor.d.ts +41 -0
  5. package/dist/cjs/Editor.js +33 -13
  6. package/dist/cjs/Pointer.js +1 -1
  7. package/dist/cjs/Viewport.d.ts +6 -0
  8. package/dist/cjs/Viewport.js +6 -1
  9. package/dist/cjs/commands/Erase.d.ts +22 -2
  10. package/dist/cjs/commands/Erase.js +22 -2
  11. package/dist/cjs/commands/uniteCommands.d.ts +36 -0
  12. package/dist/cjs/commands/uniteCommands.js +36 -0
  13. package/dist/cjs/components/ImageComponent.d.ts +12 -0
  14. package/dist/cjs/components/ImageComponent.js +16 -9
  15. package/dist/cjs/components/Stroke.d.ts +16 -2
  16. package/dist/cjs/components/Stroke.js +17 -1
  17. package/dist/cjs/components/builders/ArrowBuilder.js +3 -3
  18. package/dist/cjs/components/builders/CircleBuilder.js +3 -3
  19. package/dist/cjs/components/builders/FreehandLineBuilder.js +3 -3
  20. package/dist/cjs/components/builders/LineBuilder.js +3 -3
  21. package/dist/cjs/components/builders/PressureSensitiveFreehandLineBuilder.js +3 -3
  22. package/dist/cjs/components/builders/RectangleBuilder.js +5 -6
  23. package/dist/cjs/components/builders/autocorrect/makeShapeFitAutocorrect.d.ts +3 -0
  24. package/dist/cjs/components/builders/autocorrect/makeShapeFitAutocorrect.js +168 -0
  25. package/dist/cjs/components/builders/autocorrect/makeSnapToGridAutocorrect.d.ts +3 -0
  26. package/dist/cjs/components/builders/autocorrect/makeSnapToGridAutocorrect.js +46 -0
  27. package/dist/cjs/components/builders/types.d.ts +1 -0
  28. package/dist/cjs/image/EditorImage.d.ts +32 -1
  29. package/dist/cjs/image/EditorImage.js +32 -1
  30. package/dist/cjs/rendering/Display.js +8 -1
  31. package/dist/cjs/rendering/RenderablePathSpec.d.ts +5 -1
  32. package/dist/cjs/rendering/RenderablePathSpec.js +4 -0
  33. package/dist/cjs/toolbar/IconProvider.d.ts +2 -0
  34. package/dist/cjs/toolbar/IconProvider.js +17 -0
  35. package/dist/cjs/toolbar/localization.d.ts +3 -0
  36. package/dist/cjs/toolbar/localization.js +4 -1
  37. package/dist/cjs/toolbar/widgets/InsertImageWidget.d.ts +2 -1
  38. package/dist/cjs/toolbar/widgets/InsertImageWidget.js +102 -22
  39. package/dist/cjs/toolbar/widgets/PenToolWidget.d.ts +1 -2
  40. package/dist/cjs/toolbar/widgets/PenToolWidget.js +50 -20
  41. package/dist/cjs/tools/Pen.d.ts +9 -0
  42. package/dist/cjs/tools/Pen.js +77 -3
  43. package/dist/cjs/tools/TextTool.js +5 -1
  44. package/dist/cjs/tools/util/StationaryPenDetector.d.ts +22 -0
  45. package/dist/cjs/tools/util/StationaryPenDetector.js +95 -0
  46. package/dist/cjs/util/ReactiveValue.d.ts +2 -0
  47. package/dist/cjs/util/ReactiveValue.js +2 -0
  48. package/dist/cjs/util/lib.d.ts +1 -0
  49. package/dist/cjs/util/lib.js +4 -1
  50. package/dist/cjs/util/waitForImageLoaded.d.ts +2 -0
  51. package/dist/cjs/util/waitForImageLoaded.js +12 -0
  52. package/dist/cjs/version.js +1 -1
  53. package/dist/mjs/Editor.d.ts +41 -0
  54. package/dist/mjs/Editor.mjs +33 -13
  55. package/dist/mjs/Pointer.mjs +1 -1
  56. package/dist/mjs/Viewport.d.ts +6 -0
  57. package/dist/mjs/Viewport.mjs +6 -1
  58. package/dist/mjs/commands/Erase.d.ts +22 -2
  59. package/dist/mjs/commands/Erase.mjs +22 -2
  60. package/dist/mjs/commands/uniteCommands.d.ts +36 -0
  61. package/dist/mjs/commands/uniteCommands.mjs +36 -0
  62. package/dist/mjs/components/ImageComponent.d.ts +12 -0
  63. package/dist/mjs/components/ImageComponent.mjs +16 -9
  64. package/dist/mjs/components/Stroke.d.ts +16 -2
  65. package/dist/mjs/components/Stroke.mjs +17 -1
  66. package/dist/mjs/components/builders/ArrowBuilder.mjs +3 -2
  67. package/dist/mjs/components/builders/CircleBuilder.mjs +3 -2
  68. package/dist/mjs/components/builders/FreehandLineBuilder.mjs +3 -2
  69. package/dist/mjs/components/builders/LineBuilder.mjs +3 -2
  70. package/dist/mjs/components/builders/PressureSensitiveFreehandLineBuilder.mjs +3 -2
  71. package/dist/mjs/components/builders/RectangleBuilder.mjs +5 -4
  72. package/dist/mjs/components/builders/autocorrect/makeShapeFitAutocorrect.d.ts +3 -0
  73. package/dist/mjs/components/builders/autocorrect/makeShapeFitAutocorrect.mjs +166 -0
  74. package/dist/mjs/components/builders/autocorrect/makeSnapToGridAutocorrect.d.ts +3 -0
  75. package/dist/mjs/components/builders/autocorrect/makeSnapToGridAutocorrect.mjs +44 -0
  76. package/dist/mjs/components/builders/types.d.ts +1 -0
  77. package/dist/mjs/image/EditorImage.d.ts +32 -1
  78. package/dist/mjs/image/EditorImage.mjs +32 -1
  79. package/dist/mjs/rendering/Display.mjs +8 -1
  80. package/dist/mjs/rendering/RenderablePathSpec.d.ts +5 -1
  81. package/dist/mjs/rendering/RenderablePathSpec.mjs +4 -0
  82. package/dist/mjs/toolbar/IconProvider.d.ts +2 -0
  83. package/dist/mjs/toolbar/IconProvider.mjs +17 -0
  84. package/dist/mjs/toolbar/localization.d.ts +3 -0
  85. package/dist/mjs/toolbar/localization.mjs +4 -1
  86. package/dist/mjs/toolbar/widgets/InsertImageWidget.d.ts +2 -1
  87. package/dist/mjs/toolbar/widgets/InsertImageWidget.mjs +102 -22
  88. package/dist/mjs/toolbar/widgets/PenToolWidget.d.ts +1 -2
  89. package/dist/mjs/toolbar/widgets/PenToolWidget.mjs +50 -20
  90. package/dist/mjs/tools/Pen.d.ts +9 -0
  91. package/dist/mjs/tools/Pen.mjs +77 -3
  92. package/dist/mjs/tools/TextTool.mjs +5 -1
  93. package/dist/mjs/tools/util/StationaryPenDetector.d.ts +22 -0
  94. package/dist/mjs/tools/util/StationaryPenDetector.mjs +92 -0
  95. package/dist/mjs/util/ReactiveValue.d.ts +2 -0
  96. package/dist/mjs/util/ReactiveValue.mjs +2 -0
  97. package/dist/mjs/util/lib.d.ts +1 -0
  98. package/dist/mjs/util/lib.mjs +1 -0
  99. package/dist/mjs/util/waitForImageLoaded.d.ts +2 -0
  100. package/dist/mjs/util/waitForImageLoaded.mjs +10 -0
  101. package/dist/mjs/version.mjs +1 -1
  102. package/package.json +3 -3
  103. package/src/Editor.scss +7 -0
  104. package/src/toolbar/AbstractToolbar.scss +20 -0
  105. package/src/toolbar/toolbar.scss +1 -1
  106. package/src/toolbar/widgets/InsertImageWidget.scss +6 -1
  107. package/src/toolbar/widgets/PenToolWidget.scss +33 -0
  108. package/src/tools/SelectionTool/SelectionTool.scss +6 -0
  109. package/src/toolbar/widgets/PenToolWidget.css +0 -2
@@ -24,8 +24,7 @@ export default class PenToolWidget extends BaseToolWidget {
24
24
  private createIconForRecord;
25
25
  protected createIcon(): Element;
26
26
  private createPenTypeSelector;
27
- private setInputStabilizationEnabled;
28
- protected createStabilizationOption(): {
27
+ protected createStrokeCorrectionOptions(): {
29
28
  update: () => void;
30
29
  addTo: (parent: HTMLElement) => void;
31
30
  };
@@ -147,27 +147,51 @@ class PenToolWidget extends BaseToolWidget {
147
147
  },
148
148
  };
149
149
  }
150
- setInputStabilizationEnabled(enabled) {
151
- this.tool.setHasStabilization(enabled);
152
- }
153
- createStabilizationOption() {
154
- const stabilizationOption = document.createElement('div');
155
- const stabilizationCheckbox = document.createElement('input');
156
- const stabilizationLabel = document.createElement('label');
157
- stabilizationLabel.innerText = this.localizationTable.inputStabilization;
158
- stabilizationCheckbox.type = 'checkbox';
159
- stabilizationCheckbox.id = `${toolbarCSSPrefix}-penInputStabilizationCheckbox-${PenToolWidget.idCounter++}`;
160
- stabilizationLabel.htmlFor = stabilizationCheckbox.id;
161
- stabilizationOption.replaceChildren(stabilizationLabel, stabilizationCheckbox);
162
- stabilizationCheckbox.oninput = () => {
163
- this.setInputStabilizationEnabled(stabilizationCheckbox.checked);
150
+ createStrokeCorrectionOptions() {
151
+ const container = document.createElement('div');
152
+ container.classList.add('action-button-row', `${toolbarCSSPrefix}-pen-tool-toggle-buttons`);
153
+ const addToggleButton = (labelText, icon) => {
154
+ const button = document.createElement('button');
155
+ button.classList.add(`${toolbarCSSPrefix}-toggle-button`);
156
+ const iconElement = icon.cloneNode(true);
157
+ iconElement.classList.add('icon');
158
+ const label = document.createElement('span');
159
+ label.innerText = labelText;
160
+ button.replaceChildren(iconElement, label);
161
+ button.setAttribute('role', 'switch');
162
+ container.appendChild(button);
163
+ let checked = false;
164
+ let onChangeListener = (_checked) => { };
165
+ const result = {
166
+ setChecked(newChecked) {
167
+ checked = newChecked;
168
+ button.setAttribute('aria-checked', `${checked}`);
169
+ onChangeListener(checked);
170
+ },
171
+ setOnInputListener(listener) {
172
+ onChangeListener = listener;
173
+ },
174
+ };
175
+ button.onclick = () => {
176
+ result.setChecked(!checked);
177
+ };
178
+ return result;
164
179
  };
180
+ const stabilizationOption = addToggleButton(this.localizationTable.inputStabilization, this.editor.icons.makeStrokeSmoothingIcon());
181
+ stabilizationOption.setOnInputListener(enabled => {
182
+ this.tool.setHasStabilization(enabled);
183
+ });
184
+ const autocorrectOption = addToggleButton(this.localizationTable.strokeAutocorrect, this.editor.icons.makeShapeAutocorrectIcon());
185
+ autocorrectOption.setOnInputListener(enabled => {
186
+ this.tool.setStrokeAutocorrectEnabled(enabled);
187
+ });
165
188
  return {
166
189
  update: () => {
167
- stabilizationCheckbox.checked = !!this.tool.getInputMapper();
190
+ stabilizationOption.setChecked(!!this.tool.getInputMapper());
191
+ autocorrectOption.setChecked(this.tool.getStrokeAutocorrectionEnabled());
168
192
  },
169
193
  addTo: (parent) => {
170
- parent.appendChild(stabilizationOption);
194
+ parent.appendChild(container);
171
195
  }
172
196
  };
173
197
  }
@@ -189,20 +213,22 @@ class PenToolWidget extends BaseToolWidget {
189
213
  colorLabel.setAttribute('for', colorInput.id);
190
214
  colorRow.appendChild(colorLabel);
191
215
  colorRow.appendChild(colorInputContainer);
192
- const stabilizationOption = this.createStabilizationOption();
216
+ const toggleButtonRow = this.createStrokeCorrectionOptions();
193
217
  this.updateInputs = () => {
194
218
  setColorInputValue(this.tool.getColor());
195
219
  setThickness(this.tool.getThickness());
196
220
  penTypeSelect.updateIcons();
197
221
  // Update the selected stroke factory.
198
222
  penTypeSelect.setValue(this.getCurrentPenTypeIdx());
199
- stabilizationOption.update();
223
+ toggleButtonRow.update();
200
224
  };
201
225
  this.updateInputs();
202
226
  container.replaceChildren(colorRow, thicknessRow);
203
227
  penTypeSelect.addTo(container);
204
- stabilizationOption.addTo(container);
205
228
  dropdown.replaceChildren(container);
229
+ // Add the toggle button row *outside* of the main content (use different
230
+ // spacing with respect to the sides of the container).
231
+ toggleButtonRow.addTo(dropdown);
206
232
  return true;
207
233
  }
208
234
  onKeyPress(event) {
@@ -232,6 +258,7 @@ class PenToolWidget extends BaseToolWidget {
232
258
  thickness: this.tool.getThickness(),
233
259
  strokeFactoryId: this.getCurrentPenType()?.id,
234
260
  inputStabilization: !!this.tool.getInputMapper(),
261
+ strokeAutocorrect: this.tool.getStrokeAutocorrectionEnabled(),
235
262
  };
236
263
  }
237
264
  deserializeFrom(state) {
@@ -262,7 +289,10 @@ class PenToolWidget extends BaseToolWidget {
262
289
  }
263
290
  }
264
291
  if (state.inputStabilization !== undefined) {
265
- this.setInputStabilizationEnabled(!!state.inputStabilization);
292
+ this.tool.setHasStabilization(!!state.inputStabilization);
293
+ }
294
+ if (state.strokeAutocorrect !== undefined) {
295
+ this.tool.setStrokeAutocorrectEnabled(!!state.strokeAutocorrect);
266
296
  }
267
297
  }
268
298
  }
@@ -19,6 +19,11 @@ export default class Pen extends BaseTool {
19
19
  private currentDeviceType;
20
20
  private styleValue;
21
21
  private style;
22
+ private shapeAutocompletionEnabled;
23
+ private autocorrectedShape;
24
+ private lastAutocorrectedShape;
25
+ private removedAutocorrectedShapeTime;
26
+ private stationaryDetector;
22
27
  constructor(editor: Editor, description: string, style: Partial<PenStyle>);
23
28
  private getPressureMultiplier;
24
29
  protected toStrokePoint(pointer: Pointer): StrokeDataPoint;
@@ -30,12 +35,16 @@ export default class Pen extends BaseTool {
30
35
  onPointerMove({ current }: PointerEvt): void;
31
36
  onPointerUp({ current }: PointerEvt): boolean;
32
37
  onGestureCancel(): void;
38
+ private removedAutocorrectedShapeRecently;
39
+ private autocorrectShape;
33
40
  private finalizeStroke;
34
41
  private noteUpdated;
35
42
  setColor(color: Color4): void;
36
43
  setThickness(thickness: number): void;
37
44
  setStrokeFactory(factory: ComponentBuilderFactory): void;
38
45
  setHasStabilization(hasStabilization: boolean): void;
46
+ setStrokeAutocorrectEnabled(enabled: boolean): void;
47
+ getStrokeAutocorrectionEnabled(): boolean;
39
48
  getThickness(): number;
40
49
  getColor(): Color4;
41
50
  getStrokeFactory(): ComponentBuilderFactory;
@@ -8,6 +8,7 @@ import { undoKeyboardShortcutId } from './keybindings.mjs';
8
8
  import { decreaseSizeKeyboardShortcutId, increaseSizeKeyboardShortcutId } from './keybindings.mjs';
9
9
  import InputStabilizer from './InputFilter/InputStabilizer.mjs';
10
10
  import { ReactiveValue } from '../util/ReactiveValue.mjs';
11
+ import StationaryPenDetector from './util/StationaryPenDetector.mjs';
11
12
  export default class Pen extends BaseTool {
12
13
  constructor(editor, description, style) {
13
14
  super(editor.notifier, description);
@@ -16,6 +17,11 @@ export default class Pen extends BaseTool {
16
17
  this.lastPoint = null;
17
18
  this.startPoint = null;
18
19
  this.currentDeviceType = null;
20
+ this.shapeAutocompletionEnabled = false;
21
+ this.autocorrectedShape = null;
22
+ this.lastAutocorrectedShape = null;
23
+ this.removedAutocorrectedShapeTime = 0;
24
+ this.stationaryDetector = null;
19
25
  this.styleValue = ReactiveValue.fromInitialValue({
20
26
  factory: makeFreehandLineBuilder,
21
27
  color: Color4.blue,
@@ -53,7 +59,14 @@ export default class Pen extends BaseTool {
53
59
  // Displays the stroke that is currently being built with the display's `wetInkRenderer`.
54
60
  previewStroke() {
55
61
  this.editor.clearWetInk();
56
- this.builder?.preview(this.editor.display.getWetInkRenderer());
62
+ const wetInkRenderer = this.editor.display.getWetInkRenderer();
63
+ if (this.autocorrectedShape) {
64
+ const visibleRect = this.editor.viewport.visibleRect;
65
+ this.autocorrectedShape.render(wetInkRenderer, visibleRect);
66
+ }
67
+ else {
68
+ this.builder?.preview(wetInkRenderer);
69
+ }
57
70
  }
58
71
  // Throws if no stroke builder exists.
59
72
  addPointToStroke(point) {
@@ -82,6 +95,19 @@ export default class Pen extends BaseTool {
82
95
  this.startPoint = this.toStrokePoint(current);
83
96
  this.builder = this.style.factory(this.startPoint, this.editor.viewport);
84
97
  this.currentDeviceType = current.device;
98
+ if (this.shapeAutocompletionEnabled) {
99
+ const stationaryDetectionConfig = {
100
+ maxSpeed: 5,
101
+ maxRadius: 10,
102
+ minTimeSeconds: 0.5, // s
103
+ };
104
+ this.stationaryDetector = new StationaryPenDetector(current, stationaryDetectionConfig, pointer => this.autocorrectShape(pointer));
105
+ }
106
+ else {
107
+ this.stationaryDetector = null;
108
+ }
109
+ this.lastAutocorrectedShape = null;
110
+ this.removedAutocorrectedShapeTime = 0;
85
111
  return true;
86
112
  }
87
113
  return false;
@@ -109,7 +135,14 @@ export default class Pen extends BaseTool {
109
135
  return;
110
136
  if (current.device !== this.currentDeviceType)
111
137
  return;
112
- this.addPointToStroke(this.toStrokePoint(current));
138
+ const isStationary = this.stationaryDetector?.onPointerMove(current);
139
+ if (!isStationary) {
140
+ this.addPointToStroke(this.toStrokePoint(current));
141
+ if (this.autocorrectedShape) {
142
+ this.removedAutocorrectedShapeTime = performance.now();
143
+ this.autocorrectedShape = null;
144
+ }
145
+ }
113
146
  }
114
147
  onPointerUp({ current }) {
115
148
  if (!this.builder)
@@ -119,6 +152,7 @@ export default class Pen extends BaseTool {
119
152
  // device type.
120
153
  return true;
121
154
  }
155
+ this.stationaryDetector?.onPointerUp(current);
122
156
  // onPointerUp events can have zero pressure. Use the last pressure instead.
123
157
  const currentPoint = this.toStrokePoint(current);
124
158
  const strokePoint = {
@@ -134,10 +168,37 @@ export default class Pen extends BaseTool {
134
168
  onGestureCancel() {
135
169
  this.builder = null;
136
170
  this.editor.clearWetInk();
171
+ this.stationaryDetector?.destroy();
172
+ this.stationaryDetector = null;
173
+ }
174
+ removedAutocorrectedShapeRecently() {
175
+ return this.removedAutocorrectedShapeTime > performance.now() - 320;
176
+ }
177
+ async autocorrectShape(_lastPointer) {
178
+ if (!this.builder || !this.builder.autocorrectShape)
179
+ return;
180
+ if (!this.shapeAutocompletionEnabled)
181
+ return;
182
+ // If already corrected, do nothing
183
+ if (this.autocorrectedShape)
184
+ return;
185
+ // Activate stroke fitting
186
+ const correctedShape = await this.builder.autocorrectShape();
187
+ if (!this.builder || !correctedShape) {
188
+ return;
189
+ }
190
+ this.autocorrectedShape = correctedShape;
191
+ this.lastAutocorrectedShape = correctedShape;
192
+ this.previewStroke();
137
193
  }
138
194
  finalizeStroke() {
139
195
  if (this.builder) {
140
- const stroke = this.builder.build();
196
+ // If autocorrectedShape was cleared recently enough, it was
197
+ // probably by mistake. Reset it.
198
+ if (this.lastAutocorrectedShape && this.removedAutocorrectedShapeRecently()) {
199
+ this.autocorrectedShape = this.lastAutocorrectedShape;
200
+ }
201
+ const stroke = this.autocorrectedShape ?? this.builder.build();
141
202
  this.previewStroke();
142
203
  if (stroke.getBBox().area > 0) {
143
204
  const canFlatten = true;
@@ -150,7 +211,11 @@ export default class Pen extends BaseTool {
150
211
  }
151
212
  this.builder = null;
152
213
  this.lastPoint = null;
214
+ this.autocorrectedShape = null;
215
+ this.lastAutocorrectedShape = null;
153
216
  this.editor.clearWetInk();
217
+ this.stationaryDetector?.destroy();
218
+ this.stationaryDetector = null;
154
219
  }
155
220
  noteUpdated() {
156
221
  this.editor.notifier.dispatch(EditorEventType.ToolUpdated, {
@@ -196,6 +261,15 @@ export default class Pen extends BaseTool {
196
261
  }
197
262
  this.noteUpdated();
198
263
  }
264
+ setStrokeAutocorrectEnabled(enabled) {
265
+ if (enabled !== this.shapeAutocompletionEnabled) {
266
+ this.shapeAutocompletionEnabled = enabled;
267
+ this.noteUpdated();
268
+ }
269
+ }
270
+ getStrokeAutocorrectionEnabled() {
271
+ return this.shapeAutocompletionEnabled;
272
+ }
199
273
  getThickness() { return this.style.thickness; }
200
274
  getColor() { return this.style.color; }
201
275
  getStrokeFactory() { return this.style.factory; }
@@ -39,6 +39,10 @@ export default class TextTool extends BaseTool {
39
39
  .${overlayCSSClass} {
40
40
  height: 0;
41
41
  overflow: visible;
42
+
43
+ /* Allows absolutely-positioned textareas to scroll with
44
+ the containing overlay. */
45
+ position: relative;
42
46
  }
43
47
 
44
48
  .${overlayCSSClass} textarea {
@@ -121,7 +125,7 @@ export default class TextTool extends BaseTool {
121
125
  this.textInputElem.style.fontWeight = this.textStyle.fontWeight ?? '';
122
126
  this.textInputElem.style.fontSize = `${this.textStyle.size}px`;
123
127
  this.textInputElem.style.color = this.textStyle.renderingStyle.fill.toHexString();
124
- this.textInputElem.style.position = 'relative';
128
+ this.textInputElem.style.position = 'absolute';
125
129
  this.textInputElem.style.left = `${textScreenPos.x}px`;
126
130
  this.textInputElem.style.top = `${textScreenPos.y}px`;
127
131
  this.textInputElem.style.margin = '0';
@@ -0,0 +1,22 @@
1
+ import Pointer from '../../Pointer';
2
+ interface Config {
3
+ maxSpeed: number;
4
+ minTimeSeconds: number;
5
+ maxRadius: number;
6
+ }
7
+ type OnStationaryCallback = (lastPointer: Pointer) => void;
8
+ export default class StationaryPenDetector {
9
+ private config;
10
+ private onStationary;
11
+ private stationaryStartPointer;
12
+ private lastPointer;
13
+ private averageVelocity;
14
+ private timeout;
15
+ constructor(startPointer: Pointer, config: Config, onStationary: OnStationaryCallback);
16
+ onPointerMove(currentPointer: Pointer): boolean | undefined;
17
+ onPointerUp(pointer: Pointer): void;
18
+ destroy(): void;
19
+ private cancelStationaryTimeout;
20
+ private setStationaryTimeout;
21
+ }
22
+ export {};
@@ -0,0 +1,92 @@
1
+ import { Vec2 } from '@js-draw/math';
2
+ export default class StationaryPenDetector {
3
+ // Only handles one pen. As such, `startPointer` should be the same device/finger
4
+ // as `updatedPointer` in `onPointerMove`.
5
+ //
6
+ // A new `StationaryPenDetector` should be created for each gesture.
7
+ constructor(startPointer, config, onStationary) {
8
+ this.config = config;
9
+ this.onStationary = onStationary;
10
+ this.timeout = null;
11
+ this.stationaryStartPointer = startPointer;
12
+ this.lastPointer = startPointer;
13
+ this.averageVelocity = Vec2.zero;
14
+ }
15
+ // Returns true if stationary
16
+ onPointerMove(currentPointer) {
17
+ if (!this.stationaryStartPointer) {
18
+ // Destoroyed
19
+ return;
20
+ }
21
+ if (currentPointer.id !== this.stationaryStartPointer.id) {
22
+ return false;
23
+ }
24
+ // dx: "Δx" Displacement from last.
25
+ const dxFromLast = currentPointer.screenPos.minus(this.lastPointer.screenPos);
26
+ const dxFromStationaryStart = currentPointer.screenPos.minus(this.stationaryStartPointer.screenPos);
27
+ // dt: Delta time:
28
+ // /1000: Convert to s.
29
+ let dtFromLast = (currentPointer.timeStamp - this.lastPointer.timeStamp) / 1000; // s
30
+ // Don't divide by zero
31
+ if (dtFromLast === 0) {
32
+ dtFromLast = 1;
33
+ }
34
+ const currentVelocity = dxFromLast.times(1 / dtFromLast); // px/s
35
+ // Slight smoothing of the velocity to prevent input jitter from affecting the
36
+ // velocity too significantly.
37
+ this.averageVelocity = this.averageVelocity.lerp(currentVelocity, 0.5); // px/s
38
+ const dtFromStart = currentPointer.timeStamp - this.stationaryStartPointer.timeStamp; // ms
39
+ // If not stationary
40
+ if (dxFromStationaryStart.length() > this.config.maxRadius
41
+ || this.averageVelocity.length() > this.config.maxSpeed
42
+ || dtFromStart < this.config.minTimeSeconds) {
43
+ this.stationaryStartPointer = currentPointer;
44
+ this.lastPointer = currentPointer;
45
+ this.setStationaryTimeout(this.config.minTimeSeconds * 1000);
46
+ return false;
47
+ }
48
+ const stationaryTimeoutMs = this.config.minTimeSeconds * 1000 - dtFromStart;
49
+ this.lastPointer = currentPointer;
50
+ return stationaryTimeoutMs <= 0;
51
+ }
52
+ onPointerUp(pointer) {
53
+ if (pointer.id !== this.stationaryStartPointer?.id) {
54
+ this.cancelStationaryTimeout();
55
+ }
56
+ }
57
+ destroy() {
58
+ this.cancelStationaryTimeout();
59
+ this.stationaryStartPointer = null;
60
+ }
61
+ cancelStationaryTimeout() {
62
+ if (this.timeout !== null) {
63
+ clearTimeout(this.timeout);
64
+ this.timeout = null;
65
+ }
66
+ }
67
+ setStationaryTimeout(timeoutMs) {
68
+ if (this.timeout !== null) {
69
+ return;
70
+ }
71
+ if (timeoutMs <= 0) {
72
+ this.onStationary(this.lastPointer);
73
+ }
74
+ else {
75
+ this.timeout = setTimeout(() => {
76
+ this.timeout = null;
77
+ if (!this.stationaryStartPointer) {
78
+ // Destroyed
79
+ return;
80
+ }
81
+ const timeSinceStationaryStart = performance.now() - this.stationaryStartPointer.timeStamp;
82
+ const timeRemaining = this.config.minTimeSeconds * 1000 - timeSinceStationaryStart;
83
+ if (timeRemaining <= 0) {
84
+ this.onStationary(this.lastPointer);
85
+ }
86
+ else {
87
+ this.setStationaryTimeout(timeRemaining);
88
+ }
89
+ }, timeoutMs);
90
+ }
91
+ }
92
+ }
@@ -13,6 +13,8 @@ type UpdateCallback<T> = (value: T) => void;
13
13
  *
14
14
  * Static methods in the `ReactiveValue` and `MutableReactiveValue` classes are
15
15
  * constructors (e.g. `fromImmutable`).
16
+ *
17
+ * Avoid extending this class from an external library, as that may not be stable.
16
18
  */
17
19
  export declare abstract class ReactiveValue<T> {
18
20
  /**
@@ -31,6 +31,8 @@ const noOpSetUpdateListener = () => {
31
31
  *
32
32
  * Static methods in the `ReactiveValue` and `MutableReactiveValue` classes are
33
33
  * constructors (e.g. `fromImmutable`).
34
+ *
35
+ * Avoid extending this class from an external library, as that may not be stable.
34
36
  */
35
37
  export class ReactiveValue {
36
38
  /** Creates a `ReactiveValue` with an initial value, `initialValue`. */
@@ -1 +1,2 @@
1
1
  export { default as adjustEditorThemeForContrast } from './adjustEditorThemeForContrast';
2
+ export { ReactiveValue, MutableReactiveValue } from './ReactiveValue';
@@ -1 +1,2 @@
1
1
  export { default as adjustEditorThemeForContrast } from './adjustEditorThemeForContrast.mjs';
2
+ export { ReactiveValue, MutableReactiveValue } from './ReactiveValue.mjs';
@@ -0,0 +1,2 @@
1
+ declare const waitForImageLoad: (image: HTMLImageElement) => Promise<void>;
2
+ export default waitForImageLoad;
@@ -0,0 +1,10 @@
1
+ const waitForImageLoad = async (image) => {
2
+ if (!image.complete) {
3
+ await new Promise((resolve, reject) => {
4
+ image.onload = event => resolve(event);
5
+ image.onerror = event => reject(event);
6
+ image.onabort = event => reject(event);
7
+ });
8
+ }
9
+ };
10
+ export default waitForImageLoad;
@@ -1,3 +1,3 @@
1
1
  export default {
2
- number: '1.9.0',
2
+ number: '1.10.0',
3
3
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
5
5
  "types": "./dist/mjs/lib.d.ts",
6
6
  "main": "./dist/cjs/lib.js",
@@ -64,7 +64,7 @@
64
64
  "postpack": "ts-node tools/copyREADME.ts revert"
65
65
  },
66
66
  "dependencies": {
67
- "@js-draw/math": "^1.9.0",
67
+ "@js-draw/math": "^1.10.0",
68
68
  "@melloware/coloris": "0.21.0"
69
69
  },
70
70
  "devDependencies": {
@@ -86,5 +86,5 @@
86
86
  "freehand",
87
87
  "svg"
88
88
  ],
89
- "gitHead": "e824c37e9f216852cf096976e3a74fb4f177ead3"
89
+ "gitHead": "ccf1d0634e902c731fcd794df11cd001c3a30585"
90
90
  }
package/src/Editor.scss CHANGED
@@ -74,6 +74,13 @@
74
74
  min-height: 220px;
75
75
  min-width: 100px;
76
76
 
77
+ // A vertical writing mode breaks assumptions about how toolbars/overlays
78
+ // are aligned, resulting in unusable toolbars/text.
79
+ //
80
+ // TODO: Fix this.
81
+ //
82
+ writing-mode: horizontal-tb;
83
+
77
84
  box-sizing: border-box;
78
85
 
79
86
  display: flex;
@@ -217,6 +217,26 @@
217
217
  justify-content: center;
218
218
  }
219
219
 
220
+ .toolbar-element .toolbar--toggle-button {
221
+ color: var(--foreground-color-1);
222
+ font-weight: normal;
223
+
224
+ &[aria-checked=true] {
225
+ background: var(--selection-background-color);
226
+ color: var(--selection-foreground-color);
227
+ }
228
+
229
+ > .icon {
230
+ width: 25px;
231
+ height: 25px;
232
+ margin: 0 5px;
233
+ }
234
+
235
+ > * {
236
+ vertical-align: middle;
237
+ }
238
+ }
239
+
220
240
  .toolbar-closeColorPickerOverlay {
221
241
  display: none;
222
242
  position: fixed;
@@ -1,6 +1,6 @@
1
1
  @use "./widgets/InsertImageWidget.scss";
2
2
  @use "./widgets/OverflowWidget.css";
3
- @use "./widgets/PenToolWidget.css";
3
+ @use "./widgets/PenToolWidget.scss";
4
4
  @use "./widgets/HandToolWidget.scss";
5
5
  @use "./widgets/SelectionToolWidget.scss";
6
6
  @use "./widgets/DocumentPropertiesWidget.scss";
@@ -12,7 +12,7 @@ $image-widget-selector: '.insert-image-widget-dropdown-content';
12
12
 
13
13
  img {
14
14
  max-width: min(50vw, 75%);
15
- max-height: 50vh;
15
+ max-height: min(300px, 50vh);
16
16
 
17
17
  /* Center */
18
18
  display: block;
@@ -20,6 +20,11 @@ $image-widget-selector: '.insert-image-widget-dropdown-content';
20
20
  margin-right: auto;
21
21
  }
22
22
 
23
+ .insert-image-image-status-view {
24
+ display: flex;
25
+ justify-content: space-between;
26
+ }
27
+
23
28
  .action-button-row {
24
29
  margin-top: 4px;
25
30
  display: flex;
@@ -0,0 +1,33 @@
1
+
2
+
3
+ // Repeat selector for extra specificity
4
+ :root .toolbar--pen-tool-toggle-buttons.toolbar--pen-tool-toggle-buttons {
5
+ display: flex;
6
+ justify-content: stretch;
7
+ padding-top: 0;
8
+ padding-bottom: 5px;
9
+
10
+ // Some styles rely on left being start.
11
+ direction: ltr;
12
+
13
+ & > * {
14
+ flex-grow: 1;
15
+ text-align: start;
16
+ margin-inline-end: 5px;
17
+
18
+ .icon {
19
+ margin: 0;
20
+ margin-inline-start: 4px;
21
+ margin-inline-end: 10px;
22
+ }
23
+ }
24
+
25
+ & > :nth-child(1) {
26
+ direction: ltr;
27
+ }
28
+
29
+ & > :last-child {
30
+ // Reverse the last (show icon on the right)
31
+ direction: rtl;
32
+ }
33
+ }
@@ -83,6 +83,12 @@
83
83
  .overlay.handleOverlay {
84
84
  touch-action: none;
85
85
 
86
+ // The selection tool makes some assumptions about margin-left, margin-top,
87
+ // and the initial (unmodified) position of the selection.
88
+ //
89
+ // For now, as we're not showing text in the selection box, force LTR.
90
+ direction: ltr;
91
+
86
92
  // When expanding a selection with shift+click&drag, multiple selection boxes
87
93
  // can be present in the same handleOverlay. As such, so that other overlayed
88
94
  // selection boxes are in the correct place, the outer container needs to have
@@ -1,2 +0,0 @@
1
-
2
-