js-draw 1.15.0 → 1.16.1

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.
@@ -71,9 +71,11 @@ export default class PanZoom extends BaseTool {
71
71
  this.initialRotationSnapAngle = 0.22; // radians
72
72
  this.afterRotationStartSnapAngle = 0.07; // radians
73
73
  this.pinchZoomStartThreshold = 1.08; // scale factor
74
+ // Last timestamp at which a pointerdown event was received
74
75
  this.lastPointerDownTimestamp = 0;
75
76
  this.initialTouchAngle = 0;
76
77
  this.initialViewportRotation = 0;
78
+ this.initialViewportScale = 0;
77
79
  // Set to `true` only when scaling has started (if two fingers are down and have moved
78
80
  // far enough).
79
81
  this.isScaling = false;
@@ -113,11 +115,12 @@ export default class PanZoom extends BaseTool {
113
115
  const isRightClick = this.allPointersAreOfType(pointers, PointerDevice.RightButtonMouse);
114
116
  if (allAreTouch && pointers.length === 2 && this.mode & PanZoomMode.TwoFingerTouchGestures) {
115
117
  const { screenCenter, angle, dist } = this.computePinchData(pointers[0], pointers[1]);
116
- this.lastDist = dist;
117
- this.startDist = dist;
118
+ this.lastTouchDist = dist;
119
+ this.startTouchDist = dist;
118
120
  this.lastScreenCenter = screenCenter;
119
121
  this.initialTouchAngle = angle;
120
122
  this.initialViewportRotation = this.editor.viewport.getRotationAngle();
123
+ this.initialViewportScale = this.editor.viewport.getScaleFactor();
121
124
  this.isScaling = false;
122
125
  // We're initially rotated if `initialViewportRotation` isn't near a multiple of pi/2.
123
126
  // In other words, if sin(2 initialViewportRotation) is near zero.
@@ -193,6 +196,22 @@ export default class PanZoom extends BaseTool {
193
196
  }
194
197
  return fullRotation - this.editor.viewport.getRotationAngle();
195
198
  }
199
+ /**
200
+ * Given a scale update, `scaleFactor`, returns a new scale factor snapped
201
+ * to a power of two (if within some tolerance of that scale).
202
+ */
203
+ toSnappedScaleFactor(touchDist) {
204
+ // scaleFactor is applied to the current transformation of the viewport.
205
+ const newScale = this.initialViewportScale * touchDist / this.startTouchDist;
206
+ const currentScale = this.editor.viewport.getScaleFactor();
207
+ const logNewScale = Math.log(newScale) / Math.log(10);
208
+ const roundedLogNewScale = Math.round(logNewScale);
209
+ const logTolerance = 0.04;
210
+ if (Math.abs(roundedLogNewScale - logNewScale) < logTolerance) {
211
+ return Math.pow(10, roundedLogNewScale) / currentScale;
212
+ }
213
+ return touchDist / this.lastTouchDist;
214
+ }
196
215
  handleTwoFingerMove(allPointers) {
197
216
  const { screenCenter, canvasCenter, angle, dist } = this.computePinchData(allPointers[0], allPointers[1]);
198
217
  const delta = this.getCenterDelta(screenCenter);
@@ -209,25 +228,25 @@ export default class PanZoom extends BaseTool {
209
228
  this.isRotating = true;
210
229
  }
211
230
  this.updateVelocity(screenCenter);
212
- let scaleFactor = 1;
213
- if (this.isScaling) {
214
- scaleFactor = dist / this.lastDist;
215
- }
216
- else {
217
- const initialScaleFactor = dist / this.startDist;
231
+ if (!this.isScaling) {
232
+ const initialScaleFactor = dist / this.startTouchDist;
218
233
  // Only start scaling if scaling done so far exceeds some threshold.
219
234
  const upperBound = this.pinchZoomStartThreshold;
220
235
  const lowerBound = 1 / this.pinchZoomStartThreshold;
221
236
  if (initialScaleFactor > upperBound || initialScaleFactor < lowerBound) {
222
- scaleFactor = initialScaleFactor;
223
237
  this.isScaling = true;
224
238
  }
225
239
  }
240
+ let scaleFactor = 1;
241
+ if (this.isScaling) {
242
+ scaleFactor = this.toSnappedScaleFactor(dist);
243
+ // Don't set lastDist until we start scaling --
244
+ this.lastTouchDist = dist;
245
+ }
226
246
  const transformUpdate = Mat33.translation(delta)
227
247
  .rightMul(Mat33.scaling2D(scaleFactor, canvasCenter))
228
248
  .rightMul(Mat33.zRotation(deltaRotation, canvasCenter));
229
249
  this.lastScreenCenter = screenCenter;
230
- this.lastDist = dist;
231
250
  this.transform = Viewport.transformBy(this.transform.transform.rightMul(transformUpdate));
232
251
  return transformUpdate;
233
252
  }
@@ -62,6 +62,10 @@ export default class PasteHandler extends BaseTool {
62
62
  });
63
63
  const defaultTextStyle = { size: 12, fontFamily: 'sans', renderingStyle: { fill: Color4.red } };
64
64
  const pastedTextStyle = textTools[0]?.getTextStyle() ?? defaultTextStyle;
65
+ // Don't paste text that would be invisible.
66
+ if (text.trim() === '') {
67
+ return;
68
+ }
65
69
  const lines = text.split('\n');
66
70
  await this.addComponentsFromPaste([TextComponent.fromLines(lines, Mat33.identity, pastedTextStyle)]);
67
71
  }
@@ -80,6 +80,7 @@ class Selection {
80
80
  for (const handle of this.handles) {
81
81
  handle.addTo(this.backgroundElem);
82
82
  }
83
+ this.updateUI();
83
84
  }
84
85
  // @internal Intended for unit tests
85
86
  getBackgroundElem() {
@@ -285,6 +286,13 @@ class Selection {
285
286
  else {
286
287
  this.innerContainer.classList.remove(perpendicularClassName);
287
288
  }
289
+ // Hide handles when empty
290
+ if (screenRegion.width === 0 && screenRegion.height === 0) {
291
+ this.innerContainer.classList.add('-empty');
292
+ }
293
+ else {
294
+ this.innerContainer.classList.remove('-empty');
295
+ }
288
296
  for (const handle of this.handles) {
289
297
  handle.updatePosition();
290
298
  }
@@ -533,7 +541,11 @@ Selection.ApplyTransformationCommand = class extends SerializableCommand {
533
541
  this.transformCommands = this.selectedElemIds.map(id => {
534
542
  const elem = editor.image.lookupElement(id);
535
543
  if (!elem) {
536
- throw new Error(`Unable to find element with ID, ${id}.`);
544
+ // There may be valid reasons for an element lookup to fail:
545
+ // For example, if the element was deleted remotely and the remote deletion
546
+ // hasn't been undone.
547
+ console.warn(`Unable to find element with ID, ${id}.`);
548
+ return null;
537
549
  }
538
550
  let originalZIndex = elem.getZIndex();
539
551
  let targetZIndex = elem.getZIndex() + this.deltaZIndex;
@@ -544,7 +556,9 @@ Selection.ApplyTransformationCommand = class extends SerializableCommand {
544
556
  originalZIndex = elem.getZIndex() - this.deltaZIndex;
545
557
  }
546
558
  return elem.setZIndexAndTransformBy(this.fullTransform, targetZIndex, originalZIndex);
547
- });
559
+ }).filter(// Remove all null commands
560
+ // Remove all null commands
561
+ command => command !== null);
548
562
  }
549
563
  async apply(editor) {
550
564
  this.resolveToElems(editor, false);
@@ -1,3 +1,3 @@
1
1
  export default {
2
- number: '1.15.0',
2
+ number: '1.16.1',
3
3
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "1.15.0",
3
+ "version": "1.16.1",
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.11.1",
67
+ "@js-draw/math": "^1.16.0",
68
68
  "@melloware/coloris": "0.22.0"
69
69
  },
70
70
  "devDependencies": {
@@ -86,5 +86,5 @@
86
86
  "freehand",
87
87
  "svg"
88
88
  ],
89
- "gitHead": "d723c028faa43c7661df54606064f6237a8a767b"
89
+ "gitHead": "7a1d4ea8bae0a2549494fce672b9d43ce97a68a8"
90
90
  }
package/src/Editor.scss CHANGED
@@ -118,6 +118,10 @@
118
118
  max-width: inherit;
119
119
  min-height: 0px;
120
120
  max-height: inherit;
121
+
122
+ user-select: none;
123
+ -webkit-user-select: none;
124
+ -webkit-user-drag: none;
121
125
  }
122
126
 
123
127
  .imageEditorContainer .loadingMessage {
@@ -137,7 +141,10 @@
137
141
  width: 0;
138
142
  height: 0;
139
143
  overflow: hidden;
144
+
140
145
  pointer-events: none;
146
+ user-select: none;
147
+ -webkit-user-select: none;
141
148
  }
142
149
 
143
150
  .imageEditorContainer .textRendererOutputContainer {
@@ -146,6 +153,9 @@
146
153
  width: 0.001px;
147
154
  height: 0.001px;
148
155
  overflow: hidden;
156
+
157
+ -webkit-user-select: none;
158
+ user-select: none;
149
159
  }
150
160
 
151
161
  .imageEditorContainer .textRendererOutputContainer:focus-within {
@@ -335,6 +335,7 @@
335
335
 
336
336
  touch-action: none;
337
337
  user-select: none;
338
+ -webkit-user-select: none;
338
339
 
339
340
  background-color: var(--background-color-2);
340
341
  color: var(--foreground-color-2);
@@ -356,6 +357,7 @@
356
357
 
357
358
  input, textarea {
358
359
  user-select: auto;
360
+ -webkit-user-select: auto;
359
361
  }
360
362
 
361
363
  .toolbar-toolContainer {
@@ -117,6 +117,10 @@
117
117
  // handles.
118
118
  pointer-events: all;
119
119
  }
120
+
121
+ &.-empty {
122
+ opacity: 0;
123
+ }
120
124
  }
121
125
  }
122
126
 
@@ -2,7 +2,10 @@
2
2
  .js-draw-sound-ui-toggle {
3
3
  width: 0px;
4
4
  height: 0px;
5
- overflow: hidden;
5
+ overflow: hidden;
6
+
7
+ user-select: none;
8
+ -webkit-user-select: none;
6
9
  }
7
10
 
8
11
  .js-draw-sound-ui-toggle button {