js-draw 0.20.0 → 0.22.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 (89) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +4 -4
  3. package/dist/bundle.js +1 -1
  4. package/dist/bundledStyles.js +1 -1
  5. package/dist/cjs/src/Color4.js +2 -2
  6. package/dist/cjs/src/Editor.d.ts +5 -4
  7. package/dist/cjs/src/Editor.js +25 -7
  8. package/dist/cjs/src/EditorImage.js +8 -2
  9. package/dist/cjs/src/Viewport.js +1 -1
  10. package/dist/cjs/src/commands/Command.js +1 -1
  11. package/dist/cjs/src/commands/SerializableCommand.js +1 -1
  12. package/dist/cjs/src/components/AbstractComponent.d.ts +13 -1
  13. package/dist/cjs/src/components/AbstractComponent.js +25 -10
  14. package/dist/cjs/src/components/Stroke.d.ts +1 -0
  15. package/dist/cjs/src/components/Stroke.js +14 -1
  16. package/dist/cjs/src/components/TextComponent.js +1 -1
  17. package/dist/cjs/src/components/util/StrokeSmoother.js +12 -14
  18. package/dist/cjs/src/localizations/de.js +1 -1
  19. package/dist/cjs/src/math/LineSegment2.d.ts +2 -0
  20. package/dist/cjs/src/math/LineSegment2.js +4 -0
  21. package/dist/cjs/src/math/Mat33.js +1 -1
  22. package/dist/cjs/src/math/Path.d.ts +24 -3
  23. package/dist/cjs/src/math/Path.js +225 -4
  24. package/dist/cjs/src/math/Rect2.js +5 -4
  25. package/dist/cjs/src/math/Vec3.js +1 -1
  26. package/dist/cjs/src/math/polynomial/QuadraticBezier.d.ts +1 -1
  27. package/dist/cjs/src/math/polynomial/QuadraticBezier.js +3 -4
  28. package/dist/cjs/src/rendering/Display.d.ts +1 -1
  29. package/dist/cjs/src/rendering/Display.js +1 -2
  30. package/dist/cjs/src/toolbar/HTMLToolbar.d.ts +2 -0
  31. package/dist/cjs/src/toolbar/HTMLToolbar.js +71 -5
  32. package/dist/cjs/src/toolbar/widgets/BaseToolWidget.d.ts +0 -1
  33. package/dist/cjs/src/toolbar/widgets/BaseToolWidget.js +0 -1
  34. package/dist/cjs/src/toolbar/widgets/BaseWidget.d.ts +5 -0
  35. package/dist/cjs/src/toolbar/widgets/BaseWidget.js +17 -5
  36. package/dist/cjs/src/toolbar/widgets/DocumentPropertiesWidget.js +1 -1
  37. package/dist/cjs/src/toolbar/widgets/EraserToolWidget.js +1 -1
  38. package/dist/cjs/src/toolbar/widgets/InsertImageWidget.js +1 -1
  39. package/dist/cjs/src/toolbar/widgets/PenToolWidget.js +1 -1
  40. package/dist/cjs/src/toolbar/widgets/TextToolWidget.js +1 -1
  41. package/dist/cjs/src/tools/SelectionTool/Selection.js +5 -2
  42. package/dist/cjs/src/tools/SelectionTool/SelectionTool.js +1 -1
  43. package/dist/cjs/src/types.d.ts +2 -0
  44. package/dist/mjs/src/Color4.mjs +2 -1
  45. package/dist/mjs/src/Editor.d.ts +5 -4
  46. package/dist/mjs/src/Editor.mjs +25 -7
  47. package/dist/mjs/src/EditorImage.mjs +10 -2
  48. package/dist/mjs/src/Viewport.mjs +2 -1
  49. package/dist/mjs/src/commands/Command.mjs +2 -1
  50. package/dist/mjs/src/commands/SerializableCommand.mjs +2 -1
  51. package/dist/mjs/src/components/AbstractComponent.d.ts +13 -1
  52. package/dist/mjs/src/components/AbstractComponent.mjs +26 -10
  53. package/dist/mjs/src/components/Stroke.d.ts +1 -0
  54. package/dist/mjs/src/components/Stroke.mjs +14 -1
  55. package/dist/mjs/src/components/TextComponent.mjs +2 -1
  56. package/dist/mjs/src/components/util/StrokeSmoother.mjs +12 -14
  57. package/dist/mjs/src/localizations/de.mjs +1 -1
  58. package/dist/mjs/src/math/LineSegment2.d.ts +2 -0
  59. package/dist/mjs/src/math/LineSegment2.mjs +4 -0
  60. package/dist/mjs/src/math/Mat33.mjs +2 -1
  61. package/dist/mjs/src/math/Path.d.ts +24 -3
  62. package/dist/mjs/src/math/Path.mjs +226 -4
  63. package/dist/mjs/src/math/Rect2.mjs +6 -4
  64. package/dist/mjs/src/math/Vec3.mjs +2 -1
  65. package/dist/mjs/src/math/polynomial/QuadraticBezier.d.ts +1 -1
  66. package/dist/mjs/src/math/polynomial/QuadraticBezier.mjs +3 -4
  67. package/dist/mjs/src/rendering/Display.d.ts +1 -1
  68. package/dist/mjs/src/rendering/Display.mjs +1 -2
  69. package/dist/mjs/src/toolbar/HTMLToolbar.d.ts +2 -0
  70. package/dist/mjs/src/toolbar/HTMLToolbar.mjs +73 -6
  71. package/dist/mjs/src/toolbar/widgets/BaseToolWidget.d.ts +0 -1
  72. package/dist/mjs/src/toolbar/widgets/BaseToolWidget.mjs +0 -1
  73. package/dist/mjs/src/toolbar/widgets/BaseWidget.d.ts +5 -0
  74. package/dist/mjs/src/toolbar/widgets/BaseWidget.mjs +17 -5
  75. package/dist/mjs/src/toolbar/widgets/DocumentPropertiesWidget.mjs +2 -1
  76. package/dist/mjs/src/toolbar/widgets/EraserToolWidget.mjs +2 -1
  77. package/dist/mjs/src/toolbar/widgets/InsertImageWidget.mjs +2 -1
  78. package/dist/mjs/src/toolbar/widgets/PenToolWidget.mjs +2 -1
  79. package/dist/mjs/src/toolbar/widgets/TextToolWidget.mjs +2 -1
  80. package/dist/mjs/src/tools/SelectionTool/Selection.mjs +6 -2
  81. package/dist/mjs/src/tools/SelectionTool/SelectionTool.mjs +2 -1
  82. package/dist/mjs/src/types.d.ts +2 -0
  83. package/package.json +12 -12
  84. package/src/Coloris.css +52 -0
  85. package/src/Editor.css +12 -0
  86. package/src/toolbar/toolbar.css +16 -0
  87. package/tsconfig-typedoc.json +7 -0
  88. package/tsconfig.json +2 -0
  89. package/typedoc.json +1 -0
@@ -10,6 +10,7 @@ const LineSegment2_1 = __importDefault(require("./LineSegment2"));
10
10
  const Mat33_1 = __importDefault(require("./Mat33"));
11
11
  const Rect2_1 = __importDefault(require("./Rect2"));
12
12
  const Vec2_1 = require("./Vec2");
13
+ const Vec3_1 = __importDefault(require("./Vec3"));
13
14
  var PathCommandType;
14
15
  (function (PathCommandType) {
15
16
  PathCommandType[PathCommandType["LineTo"] = 0] = "LineTo";
@@ -17,6 +18,23 @@ var PathCommandType;
17
18
  PathCommandType[PathCommandType["CubicBezierTo"] = 2] = "CubicBezierTo";
18
19
  PathCommandType[PathCommandType["QuadraticBezierTo"] = 3] = "QuadraticBezierTo";
19
20
  })(PathCommandType = exports.PathCommandType || (exports.PathCommandType = {}));
21
+ // Returns the bounding box of one path segment.
22
+ const getPartBBox = (part) => {
23
+ let partBBox;
24
+ if (part instanceof LineSegment2_1.default) {
25
+ partBBox = part.bbox;
26
+ }
27
+ else if (part instanceof bezier_js_1.Bezier) {
28
+ const bbox = part.bbox();
29
+ const width = bbox.x.max - bbox.x.min;
30
+ const height = bbox.y.max - bbox.y.min;
31
+ partBBox = new Rect2_1.default(bbox.x.min, bbox.y.min, width, height);
32
+ }
33
+ else {
34
+ partBBox = new Rect2_1.default(part.x, part.y, 0, 0);
35
+ }
36
+ return partBBox;
37
+ };
20
38
  class Path {
21
39
  constructor(startPoint, parts) {
22
40
  this.startPoint = startPoint;
@@ -32,6 +50,13 @@ class Path {
32
50
  this.bbox = this.bbox.union(Path.computeBBoxForSegment(startPoint, part));
33
51
  }
34
52
  }
53
+ getExactBBox() {
54
+ const bboxes = [];
55
+ for (const part of this.geometry) {
56
+ bboxes.push(getPartBBox(part));
57
+ }
58
+ return Rect2_1.default.union(...bboxes);
59
+ }
35
60
  // Lazy-loads and returns this path's geometry
36
61
  get geometry() {
37
62
  if (this.cachedGeometry) {
@@ -54,6 +79,7 @@ class Path {
54
79
  startPoint = part.point;
55
80
  break;
56
81
  case PathCommandType.MoveTo:
82
+ geometry.push(part.point);
57
83
  startPoint = part.point;
58
84
  break;
59
85
  }
@@ -109,11 +135,197 @@ class Path {
109
135
  }
110
136
  return Rect2_1.default.bboxOf(points);
111
137
  }
112
- intersection(line) {
113
- if (!line.bbox.intersects(this.bbox)) {
138
+ /**
139
+ * Let `S` be a closed path a distance `strokeRadius` from this path.
140
+ *
141
+ * @returns Approximate intersections of `line` with `S` using ray marching, starting from
142
+ * both end points of `line` and each point in `additionalRaymarchStartPoints`.
143
+ */
144
+ raymarchIntersectionWith(line, strokeRadius, additionalRaymarchStartPoints = []) {
145
+ var _a, _b;
146
+ // No intersection between bounding boxes: No possible intersection
147
+ // of the interior.
148
+ if (!line.bbox.intersects(this.bbox.grownBy(strokeRadius))) {
114
149
  return [];
115
150
  }
151
+ const lineLength = line.length;
152
+ const partDistFunctionRecords = [];
153
+ // Determine distance functions for all parts that the given line could possibly intersect with
154
+ for (const part of this.geometry) {
155
+ const bbox = getPartBBox(part).grownBy(strokeRadius);
156
+ if (!bbox.intersects(line.bbox)) {
157
+ continue;
158
+ }
159
+ // Signed distance function
160
+ let partDist;
161
+ if (part instanceof LineSegment2_1.default) {
162
+ partDist = (point) => part.distance(point);
163
+ }
164
+ else if (part instanceof Vec3_1.default) {
165
+ partDist = (point) => part.minus(point).magnitude();
166
+ }
167
+ else {
168
+ partDist = (point) => {
169
+ return part.project(point).d;
170
+ };
171
+ }
172
+ // Part signed distance function (negative result implies `point` is
173
+ // inside the shape).
174
+ const partSdf = (point) => partDist(point) - strokeRadius;
175
+ // If the line can't possibly intersect the part,
176
+ if (partSdf(line.p1) > lineLength && partSdf(line.p2) > lineLength) {
177
+ continue;
178
+ }
179
+ partDistFunctionRecords.push({
180
+ part,
181
+ distFn: partDist,
182
+ bbox,
183
+ });
184
+ }
185
+ // If no distance functions, there are no intersections.
186
+ if (partDistFunctionRecords.length === 0) {
187
+ return [];
188
+ }
189
+ // Returns the minimum distance to a part in this stroke, where only parts that the given
190
+ // line could intersect are considered.
191
+ const sdf = (point) => {
192
+ let minDist = Infinity;
193
+ let minDistPart = null;
194
+ const uncheckedDistFunctions = [];
195
+ // First pass: only curves for which the current point is inside
196
+ // the bounding box.
197
+ for (const distFnRecord of partDistFunctionRecords) {
198
+ const { part, distFn, bbox } = distFnRecord;
199
+ // Check later if the current point isn't in the bounding box.
200
+ if (!bbox.containsPoint(point)) {
201
+ uncheckedDistFunctions.push(distFnRecord);
202
+ continue;
203
+ }
204
+ const currentDist = distFn(point);
205
+ if (currentDist <= minDist) {
206
+ minDist = currentDist;
207
+ minDistPart = part;
208
+ }
209
+ }
210
+ // Second pass: Everything else
211
+ for (const { part, distFn, bbox } of uncheckedDistFunctions) {
212
+ // Skip if impossible for the distance to the target to be lesser than
213
+ // the current minimum.
214
+ if (!bbox.grownBy(minDist).containsPoint(point)) {
215
+ continue;
216
+ }
217
+ const currentDist = distFn(point);
218
+ if (currentDist <= minDist) {
219
+ minDist = currentDist;
220
+ minDistPart = part;
221
+ }
222
+ }
223
+ return [minDistPart, minDist - strokeRadius];
224
+ };
225
+ // Raymarch:
226
+ const maxRaymarchSteps = 7;
227
+ // Start raymarching from each of these points. This allows detection of multiple
228
+ // intersections.
229
+ const startPoints = [
230
+ line.p1, ...additionalRaymarchStartPoints, line.p2
231
+ ];
232
+ // Converts a point ON THE LINE to a parameter
233
+ const pointToParameter = (point) => {
234
+ // Because line.direction is a unit vector, this computes the length
235
+ // of the projection of the vector(line.p1->point) onto line.direction.
236
+ //
237
+ // Note that this can be negative if the given point is outside of the given
238
+ // line segment.
239
+ return point.minus(line.p1).dot(line.direction);
240
+ };
241
+ // Sort start points by parameter on the line.
242
+ // This allows us to determine whether the current value of a parameter
243
+ // drops down to a value already tested.
244
+ startPoints.sort((a, b) => {
245
+ const t_a = pointToParameter(a);
246
+ const t_b = pointToParameter(b);
247
+ // Sort in increasing order
248
+ return t_a - t_b;
249
+ });
116
250
  const result = [];
251
+ const stoppingThreshold = strokeRadius / 1000;
252
+ // Returns the maximum x value explored
253
+ const raymarchFrom = (startPoint,
254
+ // Direction to march in (multiplies line.direction)
255
+ directionMultiplier,
256
+ // Terminate if the current point corresponds to a parameter
257
+ // below this.
258
+ minimumLineParameter) => {
259
+ let currentPoint = startPoint;
260
+ let [lastPart, lastDist] = sdf(currentPoint);
261
+ let lastParameter = pointToParameter(currentPoint);
262
+ if (lastDist > lineLength) {
263
+ return lastParameter;
264
+ }
265
+ const direction = line.direction.times(directionMultiplier);
266
+ for (let i = 0; i < maxRaymarchSteps; i++) {
267
+ // Step in the direction of the edge of the shape.
268
+ const step = lastDist;
269
+ currentPoint = currentPoint.plus(direction.times(step));
270
+ lastParameter = pointToParameter(currentPoint);
271
+ // If we're below the minimum parameter, stop. We've already tried
272
+ // this.
273
+ if (lastParameter <= minimumLineParameter) {
274
+ return lastParameter;
275
+ }
276
+ const [currentPart, signedDist] = sdf(currentPoint);
277
+ // Ensure we're stepping in the correct direction.
278
+ // Note that because we could start with a negative distance and work towards a
279
+ // positive distance, we need absolute values here.
280
+ if (Math.abs(signedDist) > Math.abs(lastDist)) {
281
+ // If not, stop.
282
+ return null;
283
+ }
284
+ lastDist = signedDist;
285
+ lastPart = currentPart;
286
+ // Is the distance close enough that we can stop early?
287
+ if (Math.abs(lastDist) < stoppingThreshold) {
288
+ break;
289
+ }
290
+ }
291
+ // Ensure that the point we ended with is on the line.
292
+ const isOnLineSegment = lastParameter >= 0 && lastParameter <= lineLength;
293
+ if (lastPart && isOnLineSegment && Math.abs(lastDist) < stoppingThreshold) {
294
+ result.push({
295
+ point: currentPoint,
296
+ parameterValue: NaN,
297
+ curve: lastPart,
298
+ });
299
+ }
300
+ return lastParameter;
301
+ };
302
+ // The maximum value of the line's parameter explored so far (0 corresponds to
303
+ // line.p1)
304
+ let maxLineT = 0;
305
+ // Raymarch for each start point.
306
+ //
307
+ // Use a for (i from 0 to length) loop because startPoints may be added
308
+ // during iteration.
309
+ for (let i = 0; i < startPoints.length; i++) {
310
+ const startPoint = startPoints[i];
311
+ // Try raymarching in both directions.
312
+ maxLineT = Math.max(maxLineT, (_a = raymarchFrom(startPoint, 1, maxLineT)) !== null && _a !== void 0 ? _a : maxLineT);
313
+ maxLineT = Math.max(maxLineT, (_b = raymarchFrom(startPoint, -1, maxLineT)) !== null && _b !== void 0 ? _b : maxLineT);
314
+ }
315
+ return result;
316
+ }
317
+ /**
318
+ * Returns a list of intersections with this path. If `strokeRadius` is given,
319
+ * intersections are approximated with the surface `strokeRadius` away from this.
320
+ *
321
+ * If `strokeRadius > 0`, the resultant `parameterValue` has no defined value.
322
+ */
323
+ intersection(line, strokeRadius) {
324
+ let result = [];
325
+ // Is any intersection between shapes within the bounding boxes impossible?
326
+ if (!line.bbox.intersects(this.bbox.grownBy(strokeRadius !== null && strokeRadius !== void 0 ? strokeRadius : 0))) {
327
+ return [];
328
+ }
117
329
  for (const part of this.geometry) {
118
330
  if (part instanceof LineSegment2_1.default) {
119
331
  const intersection = part.intersection(line);
@@ -125,7 +337,7 @@ class Path {
125
337
  });
126
338
  }
127
339
  }
128
- else {
340
+ else if (part instanceof bezier_js_1.Bezier) {
129
341
  const intersectionPoints = part.intersects(line).map(t => {
130
342
  // We're using the .intersects(line) function, which is documented
131
343
  // to always return numbers. However, to satisfy the type checker (and
@@ -148,6 +360,15 @@ class Path {
148
360
  result.push(...intersectionPoints);
149
361
  }
150
362
  }
363
+ // If given a non-zero strokeWidth, attempt to raymarch.
364
+ // Even if raymarching, we need to collect starting points.
365
+ // We use the above-calculated intersections for this.
366
+ const doRaymarching = strokeRadius && strokeRadius > 1e-8;
367
+ if (doRaymarching) {
368
+ // Starting points for raymarching (in addition to the end points of the line).
369
+ const startPoints = result.map(intersection => intersection.point);
370
+ result = this.raymarchIntersectionWith(line, strokeRadius, startPoints);
371
+ }
151
372
  return result;
152
373
  }
153
374
  static mapPathCommand(part, mapping) {
@@ -651,5 +872,5 @@ class Path {
651
872
  return result;
652
873
  }
653
874
  }
654
- exports.default = Path;
655
875
  Path.empty = new Path(Vec2_1.Vec2.zero, []);
876
+ exports.default = Path;
@@ -73,9 +73,7 @@ class Rect2 {
73
73
  }
74
74
  // Returns a new rectangle containing both [this] and [other].
75
75
  union(other) {
76
- const topLeft = this.topLeft.zip(other.topLeft, Math.min);
77
- const bottomRight = this.bottomRight.zip(other.bottomRight, Math.max);
78
- return Rect2.fromCorners(topLeft, bottomRight);
76
+ return Rect2.union(this, other);
79
77
  }
80
78
  // Returns a the subdivision of this into [columns] columns
81
79
  // and [rows] rows. For example,
@@ -113,6 +111,9 @@ class Rect2 {
113
111
  }
114
112
  // Returns this grown by [margin] in both the x and y directions.
115
113
  grownBy(margin) {
114
+ if (margin === 0) {
115
+ return this;
116
+ }
116
117
  return new Rect2(this.x - margin, this.y - margin, this.w + margin * 2, this.h + margin * 2);
117
118
  }
118
119
  getClosestPointOnBoundaryTo(target) {
@@ -229,6 +230,6 @@ class Rect2 {
229
230
  return new Rect2(template.x, template.y, width, height);
230
231
  }
231
232
  }
232
- exports.default = Rect2;
233
233
  Rect2.empty = new Rect2(0, 0, 0, 0);
234
234
  Rect2.unitSquare = new Rect2(0, 0, 1, 1);
235
+ exports.default = Rect2;
@@ -170,8 +170,8 @@ class Vec3 {
170
170
  return `Vec(${this.x}, ${this.y}, ${this.z})`;
171
171
  }
172
172
  }
173
- exports.default = Vec3;
174
173
  Vec3.unitX = Vec3.of(1, 0, 0);
175
174
  Vec3.unitY = Vec3.of(0, 1, 0);
176
175
  Vec3.unitZ = Vec3.of(0, 0, 1);
177
176
  Vec3.zero = Vec3.of(0, 0, 0);
177
+ exports.default = Vec3;
@@ -21,7 +21,7 @@ export default class QuadraticBezier {
21
21
  */
22
22
  approximateDistance(point: Point2): number;
23
23
  /**
24
- * @returns the exact distance from `point` to this.
24
+ * @returns the (more) exact distance from `point` to this.
25
25
  */
26
26
  distance(point: Point2): number;
27
27
  normal(t: number): Vec2;
@@ -97,15 +97,14 @@ class QuadraticBezier {
97
97
  return Math.sqrt(Math.min(sqrDist1, sqrDist2, sqrDist3, sqrDist4));
98
98
  }
99
99
  /**
100
- * @returns the exact distance from `point` to this.
100
+ * @returns the (more) exact distance from `point` to this.
101
101
  */
102
102
  distance(point) {
103
103
  if (!this.bezierJs) {
104
104
  this.bezierJs = new bezier_js_1.Bezier([this.p0.xy, this.p1.xy, this.p2.xy]);
105
105
  }
106
- const proj = Vec2_1.Vec2.ofXY(this.bezierJs.project(point.xy));
107
- const dist = proj.minus(point).magnitude();
108
- return dist;
106
+ // .d: Distance
107
+ return this.bezierJs.project(point.xy).d;
109
108
  }
110
109
  normal(t) {
111
110
  const tangent = this.derivativeAt(t);
@@ -53,7 +53,7 @@ export default class Display {
53
53
  */
54
54
  rerenderAsText(): void;
55
55
  /**
56
- * Clears the drawing surfaces and otherwise prepares for a rerender.
56
+ * Clears the main drawing surface and otherwise prepares for a rerender.
57
57
  *
58
58
  * @returns the dry ink renderer.
59
59
  */
@@ -176,14 +176,13 @@ class Display {
176
176
  }
177
177
  }
178
178
  /**
179
- * Clears the drawing surfaces and otherwise prepares for a rerender.
179
+ * Clears the main drawing surface and otherwise prepares for a rerender.
180
180
  *
181
181
  * @returns the dry ink renderer.
182
182
  */
183
183
  startRerender() {
184
184
  var _a;
185
185
  (_a = this.resizeSurfacesCallback) === null || _a === void 0 ? void 0 : _a.call(this);
186
- this.wetInkRenderer.clear();
187
186
  this.dryInkRenderer.clear();
188
187
  return this.dryInkRenderer;
189
188
  }
@@ -22,6 +22,8 @@ export default class HTMLToolbar {
22
22
  private updateColoris;
23
23
  /** @internal */
24
24
  constructor(editor: Editor, parent: HTMLElement, localizationTable?: ToolbarLocalization);
25
+ private closeColorPickerOverlay;
26
+ private setupCloseColorPickerOverlay;
25
27
  setupColorPickers(): void;
26
28
  private reLayoutQueued;
27
29
  private queueReLayout;
@@ -22,6 +22,7 @@ const ActionButtonWidget_1 = __importDefault(require("./widgets/ActionButtonWidg
22
22
  const InsertImageWidget_1 = __importDefault(require("./widgets/InsertImageWidget"));
23
23
  const DocumentPropertiesWidget_1 = __importDefault(require("./widgets/DocumentPropertiesWidget"));
24
24
  const OverflowWidget_1 = __importDefault(require("./widgets/OverflowWidget"));
25
+ const Vec2_1 = require("../math/Vec2");
25
26
  exports.toolbarCSSPrefix = 'toolbar-';
26
27
  class HTMLToolbar {
27
28
  /** @internal */
@@ -36,6 +37,7 @@ class HTMLToolbar {
36
37
  // Widget to toggle overflow menu.
37
38
  this.overflowWidget = null;
38
39
  this.updateColoris = null;
40
+ this.closeColorPickerOverlay = null;
39
41
  this.reLayoutQueued = false;
40
42
  this.container = document.createElement('div');
41
43
  this.container.classList.add(`${exports.toolbarCSSPrefix}root`);
@@ -56,6 +58,68 @@ class HTMLToolbar {
56
58
  console.warn('ResizeObserver not supported. Toolbar will not resize.');
57
59
  }
58
60
  }
61
+ setupCloseColorPickerOverlay() {
62
+ if (this.closeColorPickerOverlay)
63
+ return;
64
+ this.closeColorPickerOverlay = document.createElement('div');
65
+ this.closeColorPickerOverlay.className = `${exports.toolbarCSSPrefix}closeColorPickerOverlay`;
66
+ this.editor.createHTMLOverlay(this.closeColorPickerOverlay);
67
+ // Buffer events: Send events to the editor only if the pointer has moved enough to
68
+ // suggest that the user is attempting to draw, rather than click to close the color picker.
69
+ let eventBuffer = [];
70
+ let gestureStartPos = null;
71
+ // Hide the color picker when attempting to draw on the overlay.
72
+ this.listeners.push(this.editor.handlePointerEventsFrom(this.closeColorPickerOverlay, (eventName, event) => {
73
+ var _a, _b;
74
+ // Position of the current event.
75
+ const currentPos = Vec2_1.Vec2.of(event.pageX, event.pageY);
76
+ // Whether to send the current event to the editor
77
+ let sendToEditor = true;
78
+ if (eventName === 'pointerdown') {
79
+ (0, coloris_1.close)();
80
+ // Buffer the event, but don't send it to the editor yet.
81
+ // We don't want to send single-click events, but we do want to send full strokes.
82
+ eventBuffer = [];
83
+ eventBuffer.push([eventName, event]);
84
+ gestureStartPos = currentPos;
85
+ // Capture the pointer so we receive future events even if the overlay is hidden.
86
+ (_a = this.closeColorPickerOverlay) === null || _a === void 0 ? void 0 : _a.setPointerCapture(event.pointerId);
87
+ // Don't send to the editor.
88
+ sendToEditor = false;
89
+ }
90
+ else if (eventName === 'pointermove') {
91
+ // Skip if the pointer hasn't moved enough to not be a "click".
92
+ const strokeStartThreshold = 10;
93
+ if (gestureStartPos && currentPos.minus(gestureStartPos).magnitude() < strokeStartThreshold) {
94
+ eventBuffer.push([eventName, event]);
95
+ sendToEditor = false;
96
+ }
97
+ else {
98
+ // Send all buffered events to the editor -- start the stroke.
99
+ for (const [eventName, event] of eventBuffer) {
100
+ this.editor.handleHTMLPointerEvent(eventName, event);
101
+ }
102
+ eventBuffer = [];
103
+ sendToEditor = true;
104
+ }
105
+ }
106
+ // Otherwise, if we received a pointerup/pointercancel without flushing all pointerevents from the
107
+ // buffer, the gesture wasn't recognised as a stroke. Thus, the editor isn't expecting a pointerup/
108
+ // pointercancel event.
109
+ else if ((eventName === 'pointerup' || eventName === 'pointercancel') && eventBuffer.length > 0) {
110
+ (_b = this.closeColorPickerOverlay) === null || _b === void 0 ? void 0 : _b.releasePointerCapture(event.pointerId);
111
+ eventBuffer = [];
112
+ // Don't send to the editor.
113
+ sendToEditor = false;
114
+ }
115
+ // Transfer focus to the editor to allow keyboard events to be handled.
116
+ if (eventName === 'pointerup') {
117
+ this.editor.focus();
118
+ }
119
+ // Forward all other events to the editor.
120
+ return sendToEditor;
121
+ }));
122
+ }
59
123
  // @internal
60
124
  setupColorPickers() {
61
125
  // Much of the setup only needs to be done once.
@@ -63,9 +127,7 @@ class HTMLToolbar {
63
127
  this.updateColoris();
64
128
  return;
65
129
  }
66
- const closePickerOverlay = document.createElement('div');
67
- closePickerOverlay.className = `${exports.toolbarCSSPrefix}closeColorPickerOverlay`;
68
- this.editor.createHTMLOverlay(closePickerOverlay);
130
+ this.setupCloseColorPickerOverlay();
69
131
  const maxSwatchLen = 12;
70
132
  const swatches = [
71
133
  Color4_1.default.red.toHexString(),
@@ -110,7 +172,9 @@ class HTMLToolbar {
110
172
  }
111
173
  // Show/hide the overlay. Making the overlay visible gives users a surface to click
112
174
  // on that shows/hides the color picker.
113
- closePickerOverlay.style.display = event.open ? 'block' : 'none';
175
+ if (this.closeColorPickerOverlay) {
176
+ this.closeColorPickerOverlay.style.display = event.open ? 'block' : 'none';
177
+ }
114
178
  }));
115
179
  // Add newly-selected colors to the swatch.
116
180
  this.listeners.push(this.editor.notifier.on(types_1.EditorEventType.ColorPickerColorSelected, event => {
@@ -372,12 +436,14 @@ class HTMLToolbar {
372
436
  this.addDefaultActionButtons();
373
437
  }
374
438
  remove() {
439
+ var _a;
375
440
  this.container.remove();
376
441
  this.resizeObserver.disconnect();
442
+ (_a = this.closeColorPickerOverlay) === null || _a === void 0 ? void 0 : _a.remove();
377
443
  for (const listener of this.listeners) {
378
444
  listener.remove();
379
445
  }
380
446
  }
381
447
  }
382
- exports.default = HTMLToolbar;
383
448
  HTMLToolbar.colorisStarted = false;
449
+ exports.default = HTMLToolbar;
@@ -3,7 +3,6 @@ import BaseTool from '../../tools/BaseTool';
3
3
  import { ToolbarLocalization } from '../localization';
4
4
  import BaseWidget from './BaseWidget';
5
5
  export default abstract class BaseToolWidget extends BaseWidget {
6
- protected editor: Editor;
7
6
  protected targetTool: BaseTool;
8
7
  constructor(editor: Editor, targetTool: BaseTool, id: string, localizationTable?: ToolbarLocalization);
9
8
  protected handleClick(): void;
@@ -8,7 +8,6 @@ const BaseWidget_1 = __importDefault(require("./BaseWidget"));
8
8
  class BaseToolWidget extends BaseWidget_1.default {
9
9
  constructor(editor, targetTool, id, localizationTable) {
10
10
  super(editor, id, localizationTable);
11
- this.editor = editor;
12
11
  this.targetTool = targetTool;
13
12
  editor.notifier.on(types_1.EditorEventType.ToolEnabled, toolEvt => {
14
13
  if (toolEvt.kind !== types_1.EditorEventType.ToolEnabled) {
@@ -37,6 +37,11 @@ export default abstract class BaseWidget {
37
37
  protected get hasDropdown(): boolean;
38
38
  protected addSubWidget(widget: BaseWidget): void;
39
39
  private toolbarWidgetToggleListener;
40
+ /**
41
+ * Adds this to `parent`. This can only be called once for each ToolbarWidget.
42
+ * Returns the element that was just added to `parent`.
43
+ * @internal
44
+ */
40
45
  addTo(parent: HTMLElement): HTMLElement;
41
46
  protected updateIcon(): void;
42
47
  setDisabled(disabled: boolean): void;
@@ -140,14 +140,26 @@ class BaseWidget {
140
140
  const id = widget.getUniqueIdIn(this.subWidgets);
141
141
  this.subWidgets[id] = widget;
142
142
  }
143
- // Adds this to [parent]. This can only be called once for each ToolbarWidget.
144
- // Returns the element that was just added to `parent`.
145
- // @internal
143
+ /**
144
+ * Adds this to `parent`. This can only be called once for each ToolbarWidget.
145
+ * Returns the element that was just added to `parent`.
146
+ * @internal
147
+ */
146
148
  addTo(parent) {
147
- this.label.innerText = this.getTitle();
148
- this.setupActionBtnClickListener(this.button);
149
+ // Update title and icon
149
150
  this.icon = null;
150
151
  this.updateIcon();
152
+ this.label.innerText = this.getTitle();
153
+ const longLabelCSSClass = 'long-label';
154
+ if (this.label.innerText.length > 7) {
155
+ this.label.classList.add(longLabelCSSClass);
156
+ }
157
+ else {
158
+ this.label.classList.remove(longLabelCSSClass);
159
+ }
160
+ // Click functionality
161
+ this.setupActionBtnClickListener(this.button);
162
+ // Clear anything already in this.container.
151
163
  this.container.replaceChildren();
152
164
  this.button.replaceChildren(this.icon, this.label);
153
165
  this.container.appendChild(this.button);
@@ -197,5 +197,5 @@ class DocumentPropertiesWidget extends BaseWidget_1.default {
197
197
  return true;
198
198
  }
199
199
  }
200
- exports.default = DocumentPropertiesWidget;
201
200
  DocumentPropertiesWidget.idCounter = 0;
201
+ exports.default = DocumentPropertiesWidget;
@@ -59,5 +59,5 @@ class EraserToolWidget extends BaseToolWidget_1.default {
59
59
  }
60
60
  }
61
61
  }
62
- exports.default = EraserToolWidget;
63
62
  EraserToolWidget.nextThicknessInputId = 0;
63
+ exports.default = EraserToolWidget;
@@ -172,5 +172,5 @@ class InsertImageWidget extends ActionButtonWidget_1.default {
172
172
  });
173
173
  }
174
174
  }
175
- exports.default = InsertImageWidget;
176
175
  InsertImageWidget.nextInputId = 0;
176
+ exports.default = InsertImageWidget;
@@ -222,5 +222,5 @@ class PenToolWidget extends BaseToolWidget_1.default {
222
222
  }
223
223
  }
224
224
  }
225
- exports.default = PenToolWidget;
226
225
  PenToolWidget.idCounter = 0;
226
+ exports.default = PenToolWidget;
@@ -111,5 +111,5 @@ class TextToolWidget extends BaseToolWidget_1.default {
111
111
  super.deserializeFrom(state);
112
112
  }
113
113
  }
114
- exports.default = TextToolWidget;
115
114
  TextToolWidget.idCounter = 0;
115
+ exports.default = TextToolWidget;
@@ -153,6 +153,9 @@ class Selection {
153
153
  this.transform = Mat33_1.default.identity;
154
154
  // Make the commands undo-able
155
155
  this.editor.dispatch(new Selection.ApplyTransformationCommand(this, selectedElems, fullTransform));
156
+ // Clear renderings of any in-progress transformations
157
+ const wetInkRenderer = this.editor.display.getWetInkRenderer();
158
+ wetInkRenderer.clear();
156
159
  }
157
160
  // Preview the effects of the current transformation on the selection
158
161
  previewTransformCmds() {
@@ -406,7 +409,6 @@ class Selection {
406
409
  return this.selectedElems;
407
410
  }
408
411
  }
409
- exports.default = Selection;
410
412
  _a = Selection;
411
413
  (() => {
412
414
  SerializableCommand_1.default.register('selection-tool-transform', (json, _editor) => {
@@ -471,7 +473,7 @@ Selection.ApplyTransformationCommand = class extends SerializableCommand_1.defau
471
473
  (_b = this.selection) === null || _b === void 0 ? void 0 : _b.setTransform(this.fullTransform.inverse(), false);
472
474
  (_c = this.selection) === null || _c === void 0 ? void 0 : _c.updateUI();
473
475
  yield editor.asyncUnapplyCommands(this.transformCommands, updateChunkSize, true);
474
- (_d = this.selection) === null || _d === void 0 ? void 0 : _d.setTransform(Mat33_1.default.identity);
476
+ (_d = this.selection) === null || _d === void 0 ? void 0 : _d.setTransform(Mat33_1.default.identity, false);
475
477
  (_e = this.selection) === null || _e === void 0 ? void 0 : _e.recomputeRegion();
476
478
  (_f = this.selection) === null || _f === void 0 ? void 0 : _f.updateUI();
477
479
  });
@@ -486,3 +488,4 @@ Selection.ApplyTransformationCommand = class extends SerializableCommand_1.defau
486
488
  return localizationTable.transformedElements(this.selectedElemIds.length);
487
489
  }
488
490
  };
491
+ exports.default = Selection;
@@ -393,7 +393,6 @@ class SelectionTool extends BaseTool_1.default {
393
393
  this.onSelectionUpdated();
394
394
  }
395
395
  }
396
- exports.default = SelectionTool;
397
396
  SelectionTool.handleableKeys = [
398
397
  'a', 'h', 'ArrowLeft',
399
398
  'd', 'l', 'ArrowRight',
@@ -403,3 +402,4 @@ SelectionTool.handleableKeys = [
403
402
  'i', 'I', 'o', 'O',
404
403
  'Control', 'Meta',
405
404
  ];
405
+ exports.default = SelectionTool;