js-draw 1.25.0 → 1.27.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. package/LICENSE +1 -1
  2. package/dist/Editor.css +1 -1935
  3. package/dist/bundle.js +478 -4
  4. package/dist/bundledStyles.js +1 -1
  5. package/dist/cjs/Editor.d.ts +0 -2
  6. package/dist/cjs/Editor.js +1 -1
  7. package/dist/cjs/bundle/bundled.js +2 -1
  8. package/dist/cjs/components/AbstractComponent.d.ts +15 -0
  9. package/dist/cjs/components/AbstractComponent.js +16 -0
  10. package/dist/cjs/components/Stroke.d.ts +1 -0
  11. package/dist/cjs/components/Stroke.js +7 -0
  12. package/dist/cjs/image/EditorImage.d.ts +2 -1
  13. package/dist/cjs/image/EditorImage.js +21 -6
  14. package/dist/cjs/toolbar/AbstractToolbar.js +9 -2
  15. package/dist/cjs/toolbar/IconProvider.d.ts +2 -1
  16. package/dist/cjs/toolbar/IconProvider.js +18 -8
  17. package/dist/cjs/toolbar/localization.d.ts +2 -0
  18. package/dist/cjs/toolbar/localization.js +2 -0
  19. package/dist/cjs/toolbar/widgets/BaseWidget.js +6 -1
  20. package/dist/cjs/toolbar/widgets/HandToolWidget.js +3 -3
  21. package/dist/cjs/toolbar/widgets/SelectionToolWidget.d.ts +7 -0
  22. package/dist/cjs/toolbar/widgets/SelectionToolWidget.js +109 -28
  23. package/dist/cjs/toolbar/widgets/components/makeButtonGrid.d.ts +17 -0
  24. package/dist/cjs/toolbar/widgets/components/makeButtonGrid.js +40 -0
  25. package/dist/cjs/tools/SelectionTool/Selection.d.ts +2 -3
  26. package/dist/cjs/tools/SelectionTool/Selection.js +30 -46
  27. package/dist/cjs/tools/SelectionTool/SelectionBuilders/LassoSelectionBuilder.d.ts +17 -0
  28. package/dist/cjs/tools/SelectionTool/SelectionBuilders/LassoSelectionBuilder.js +67 -0
  29. package/dist/cjs/tools/SelectionTool/SelectionBuilders/RectSelectionBuilder.d.ts +13 -0
  30. package/dist/cjs/tools/SelectionTool/SelectionBuilders/RectSelectionBuilder.js +33 -0
  31. package/dist/cjs/tools/SelectionTool/SelectionBuilders/SelectionBuilder.d.ts +15 -0
  32. package/dist/cjs/tools/SelectionTool/SelectionBuilders/SelectionBuilder.js +39 -0
  33. package/dist/cjs/tools/SelectionTool/SelectionMenuShortcut.d.ts +3 -1
  34. package/dist/cjs/tools/SelectionTool/SelectionMenuShortcut.js +13 -4
  35. package/dist/cjs/tools/SelectionTool/SelectionTool.d.ts +10 -2
  36. package/dist/cjs/tools/SelectionTool/SelectionTool.js +68 -55
  37. package/dist/cjs/tools/SelectionTool/types.d.ts +4 -0
  38. package/dist/cjs/tools/SelectionTool/types.js +6 -1
  39. package/dist/cjs/tools/TextTool.js +5 -2
  40. package/dist/cjs/tools/lib.d.ts +1 -1
  41. package/dist/cjs/tools/lib.js +2 -1
  42. package/dist/cjs/util/ReactiveValue.js +2 -6
  43. package/dist/cjs/util/assertions.d.ts +7 -6
  44. package/dist/cjs/util/assertions.js +35 -29
  45. package/dist/cjs/version.js +1 -1
  46. package/dist/mjs/Editor.d.ts +0 -2
  47. package/dist/mjs/Editor.mjs +1 -1
  48. package/dist/mjs/bundle/bundled.mjs +2 -1
  49. package/dist/mjs/components/AbstractComponent.d.ts +15 -0
  50. package/dist/mjs/components/AbstractComponent.mjs +16 -0
  51. package/dist/mjs/components/Stroke.d.ts +1 -0
  52. package/dist/mjs/components/Stroke.mjs +7 -0
  53. package/dist/mjs/image/EditorImage.d.ts +2 -1
  54. package/dist/mjs/image/EditorImage.mjs +21 -6
  55. package/dist/mjs/toolbar/AbstractToolbar.mjs +9 -2
  56. package/dist/mjs/toolbar/IconProvider.d.ts +2 -1
  57. package/dist/mjs/toolbar/IconProvider.mjs +18 -8
  58. package/dist/mjs/toolbar/localization.d.ts +2 -0
  59. package/dist/mjs/toolbar/localization.mjs +2 -0
  60. package/dist/mjs/toolbar/widgets/BaseWidget.mjs +6 -1
  61. package/dist/mjs/toolbar/widgets/HandToolWidget.mjs +3 -3
  62. package/dist/mjs/toolbar/widgets/SelectionToolWidget.d.ts +7 -0
  63. package/dist/mjs/toolbar/widgets/SelectionToolWidget.mjs +109 -28
  64. package/dist/mjs/toolbar/widgets/components/makeButtonGrid.d.ts +17 -0
  65. package/dist/mjs/toolbar/widgets/components/makeButtonGrid.mjs +35 -0
  66. package/dist/mjs/tools/SelectionTool/Selection.d.ts +2 -3
  67. package/dist/mjs/tools/SelectionTool/Selection.mjs +30 -46
  68. package/dist/mjs/tools/SelectionTool/SelectionBuilders/LassoSelectionBuilder.d.ts +17 -0
  69. package/dist/mjs/tools/SelectionTool/SelectionBuilders/LassoSelectionBuilder.mjs +61 -0
  70. package/dist/mjs/tools/SelectionTool/SelectionBuilders/RectSelectionBuilder.d.ts +13 -0
  71. package/dist/mjs/tools/SelectionTool/SelectionBuilders/RectSelectionBuilder.mjs +27 -0
  72. package/dist/mjs/tools/SelectionTool/SelectionBuilders/SelectionBuilder.d.ts +15 -0
  73. package/dist/mjs/tools/SelectionTool/SelectionBuilders/SelectionBuilder.mjs +36 -0
  74. package/dist/mjs/tools/SelectionTool/SelectionMenuShortcut.d.ts +3 -1
  75. package/dist/mjs/tools/SelectionTool/SelectionMenuShortcut.mjs +13 -4
  76. package/dist/mjs/tools/SelectionTool/SelectionTool.d.ts +10 -2
  77. package/dist/mjs/tools/SelectionTool/SelectionTool.mjs +68 -55
  78. package/dist/mjs/tools/SelectionTool/types.d.ts +4 -0
  79. package/dist/mjs/tools/SelectionTool/types.mjs +5 -0
  80. package/dist/mjs/tools/TextTool.mjs +5 -2
  81. package/dist/mjs/tools/lib.d.ts +1 -1
  82. package/dist/mjs/tools/lib.mjs +1 -1
  83. package/dist/mjs/util/ReactiveValue.mjs +2 -6
  84. package/dist/mjs/util/assertions.d.ts +7 -6
  85. package/dist/mjs/util/assertions.mjs +28 -24
  86. package/dist/mjs/version.mjs +1 -1
  87. package/package.json +4 -4
  88. package/src/toolbar/EdgeToolbar.scss +6 -1
  89. package/src/toolbar/widgets/components/components.scss +1 -0
  90. package/src/toolbar/widgets/components/makeButtonGrid.scss +25 -0
  91. package/src/tools/SelectionTool/SelectionTool.scss +12 -1
  92. package/src/tools/util/createMenuOverlay.scss +5 -3
@@ -4,15 +4,17 @@ const math_1 = require("@js-draw/math");
4
4
  const SelectionTool_1 = require("./SelectionTool");
5
5
  const verticalOffset = 40;
6
6
  class SelectionMenuShortcut {
7
- constructor(parent, viewport, showContextMenu, localization) {
7
+ constructor(parent, viewport, icon, showContextMenu, localization) {
8
8
  this.parent = parent;
9
9
  this.viewport = viewport;
10
+ this.icon = icon;
10
11
  this.localization = localization;
11
12
  this.lastDragPointer = null;
12
13
  this.element = document.createElement('div');
13
14
  this.element.classList.add(`${SelectionTool_1.cssPrefix}handle`, `${SelectionTool_1.cssPrefix}selection-menu`);
14
15
  this.element.style.setProperty('--vertical-offset', `${verticalOffset}px`);
15
16
  this.onClick = () => {
17
+ this.button?.focus({ preventScroll: true });
16
18
  const anchor = this.getBBoxCanvasCoords().center;
17
19
  showContextMenu(anchor);
18
20
  };
@@ -21,16 +23,22 @@ class SelectionMenuShortcut {
21
23
  }
22
24
  initUI() {
23
25
  const button = document.createElement('button');
24
- button.textContent = '...';
26
+ this.icon.classList.add('icon');
27
+ button.replaceChildren(this.icon);
25
28
  button.ariaLabel = this.localization.selectionMenu__show;
26
29
  button.title = button.ariaLabel;
30
+ this.button = button;
27
31
  // To prevent editor event handlers from conflicting with those for the button,
28
32
  // don't register a [click] handler. An onclick handler can be fired incorrectly
29
33
  // in this case (in Chrome) after onClick is fired in onDragEnd, leading to a double
30
34
  // on-click action.
31
35
  button.onkeydown = (event) => {
32
- if (event.key === 'Enter')
36
+ if (event.key === 'Enter') {
37
+ // .preventDefault prevents [Enter] from activating the first item in the
38
+ // selection menu.
39
+ event.preventDefault();
33
40
  this.onClick();
41
+ }
34
42
  };
35
43
  this.element.appendChild(button);
36
44
  // Update the bounding box of this in response to the new button.
@@ -60,7 +68,8 @@ class SelectionMenuShortcut {
60
68
  const contentCanvasSize = this.getElementScreenSize().times(toCanvasScale);
61
69
  const handleSizeCanvas = verticalOffset / this.viewport.getScaleFactor();
62
70
  const topLeft = math_1.Vec2.of(parentCanvasRect.x, parentCanvasRect.y - handleSizeCanvas);
63
- return new math_1.Rect2(topLeft.x, topLeft.y, contentCanvasSize.x, contentCanvasSize.y);
71
+ const minSize = math_1.Vec2.of(48, 48).times(toCanvasScale);
72
+ return new math_1.Rect2(topLeft.x, topLeft.y, contentCanvasSize.x, contentCanvasSize.y).grownToSize(minSize);
64
73
  }
65
74
  updatePosition() {
66
75
  const bbox = this.getBBoxParentCoords();
@@ -3,13 +3,18 @@ import Editor from '../../Editor';
3
3
  import { ContextMenuEvt, CopyEvent, KeyPressEvent, KeyUpEvent, PointerEvt } from '../../inputEvents';
4
4
  import BaseTool from '../BaseTool';
5
5
  import Selection from './Selection';
6
+ import { MutableReactiveValue } from '../../util/ReactiveValue';
7
+ import { SelectionMode } from './types';
6
8
  export declare const cssPrefix = "selection-tool-";
9
+ export { SelectionMode };
7
10
  export default class SelectionTool extends BaseTool {
8
11
  private editor;
12
+ readonly modeValue: MutableReactiveValue<SelectionMode>;
13
+ private selectionBuilder;
9
14
  private handleOverlay;
10
15
  private prevSelectionBox;
11
16
  private selectionBox;
12
- private rebuildSelectionScheduled;
17
+ private removeSelectionScheduled;
13
18
  private startPoint;
14
19
  private expandingSelectionBox;
15
20
  private shiftKeyPressed;
@@ -17,8 +22,8 @@ export default class SelectionTool extends BaseTool {
17
22
  private lastPointer;
18
23
  private autoscroller;
19
24
  constructor(editor: Editor, description: string);
25
+ private getSelectionColor;
20
26
  private makeSelectionBox;
21
- private snapSelectionToGrid;
22
27
  private showContextMenu;
23
28
  onContextMenu(event: ContextMenuEvt): boolean;
24
29
  private selectionBoxHandlingEvt;
@@ -36,7 +41,10 @@ export default class SelectionTool extends BaseTool {
36
41
  onCopy(event: CopyEvent): boolean;
37
42
  setEnabled(enabled: boolean): void;
38
43
  getSelection(): Selection | null;
44
+ /** @returns true if the selection is currently being created by the user. */
45
+ isSelecting(): boolean;
39
46
  getSelectedObjects(): AbstractComponent[];
40
47
  setSelection(objects: AbstractComponent[]): void;
48
+ private clearSelectionNoUpdateEvent;
41
49
  clearSelection(): void;
42
50
  }
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.cssPrefix = void 0;
6
+ exports.SelectionMode = exports.cssPrefix = void 0;
7
7
  const math_1 = require("@js-draw/math");
8
8
  const types_1 = require("../../types");
9
9
  const Viewport_1 = __importDefault(require("../../Viewport"));
@@ -15,6 +15,11 @@ const TextComponent_1 = __importDefault(require("../../components/TextComponent"
15
15
  const keybindings_1 = require("../keybindings");
16
16
  const ToPointerAutoscroller_1 = __importDefault(require("./ToPointerAutoscroller"));
17
17
  const showSelectionContextMenu_1 = __importDefault(require("./util/showSelectionContextMenu"));
18
+ const ReactiveValue_1 = require("../../util/ReactiveValue");
19
+ const types_2 = require("./types");
20
+ Object.defineProperty(exports, "SelectionMode", { enumerable: true, get: function () { return types_2.SelectionMode; } });
21
+ const LassoSelectionBuilder_1 = __importDefault(require("./SelectionBuilders/LassoSelectionBuilder"));
22
+ const RectSelectionBuilder_1 = __importDefault(require("./SelectionBuilders/RectSelectionBuilder"));
18
23
  exports.cssPrefix = 'selection-tool-';
19
24
  // Allows users to select/transform portions of the `EditorImage`.
20
25
  // With respect to `extend`ing, `SelectionTool` is not stable.
@@ -25,7 +30,7 @@ class SelectionTool extends BaseTool_1.default {
25
30
  // True if clearing and recreating the selectionBox has been deferred. This is used to prevent the selection
26
31
  // from vanishing on pointerdown events that are intended to form other gestures (e.g. long press) that would
27
32
  // ultimately restore the selection.
28
- this.rebuildSelectionScheduled = false;
33
+ this.removeSelectionScheduled = false;
29
34
  this.startPoint = null; // canvas position
30
35
  this.expandingSelectionBox = false;
31
36
  this.shiftKeyPressed = false;
@@ -39,6 +44,13 @@ class SelectionTool extends BaseTool_1.default {
39
44
  // Whether the last keypress corresponded to an action that didn't transform the
40
45
  // selection (and thus does not need to be finalized on onKeyUp).
41
46
  this.hasUnfinalizedTransformFromKeyPress = false;
47
+ this.modeValue = ReactiveValue_1.MutableReactiveValue.fromInitialValue(types_2.SelectionMode.Rectangle);
48
+ this.modeValue.onUpdate(() => {
49
+ this.editor.notifier.dispatch(types_1.EditorEventType.ToolUpdated, {
50
+ kind: types_1.EditorEventType.ToolUpdated,
51
+ tool: this,
52
+ });
53
+ });
42
54
  this.autoscroller = new ToPointerAutoscroller_1.default(editor.viewport, (scrollBy) => {
43
55
  editor.dispatch(Viewport_1.default.transformBy(math_1.Mat33.translation(scrollBy)), false);
44
56
  // Update the selection box/content to match the new viewport.
@@ -67,26 +79,19 @@ class SelectionTool extends BaseTool_1.default {
67
79
  this.editor.handleKeyEventsFrom(this.handleOverlay);
68
80
  this.editor.handlePointerEventsFrom(this.handleOverlay);
69
81
  }
70
- makeSelectionBox(selectionStartPos) {
82
+ getSelectionColor() {
83
+ const colorString = getComputedStyle(this.handleOverlay).getPropertyValue('--selection-background-color');
84
+ return math_1.Color4.fromString(colorString).withAlpha(0.5);
85
+ }
86
+ makeSelectionBox(selectedObjects) {
71
87
  this.prevSelectionBox = this.selectionBox;
72
- this.selectionBox = new Selection_1.default(selectionStartPos, this.editor, this.showContextMenu);
88
+ this.selectionBox = new Selection_1.default(selectedObjects, this.editor, this.showContextMenu);
73
89
  if (!this.expandingSelectionBox) {
74
90
  // Remove any previous selection rects
75
91
  this.prevSelectionBox?.cancelSelection();
76
92
  }
77
93
  this.selectionBox.addTo(this.handleOverlay);
78
94
  }
79
- snapSelectionToGrid() {
80
- if (!this.selectionBox)
81
- throw new Error('No selection to snap!');
82
- // Snap the top left corner of what we have selected.
83
- const topLeftOfBBox = this.selectionBox.computeTightBoundingBox().topLeft;
84
- const snappedTopLeft = this.editor.viewport.snapToGrid(topLeftOfBBox);
85
- const snapDelta = snappedTopLeft.minus(topLeftOfBBox);
86
- const oldTransform = this.selectionBox.getTransform();
87
- this.selectionBox.setTransform(oldTransform.rightMul(math_1.Mat33.translation(snapDelta)));
88
- this.selectionBox.finalizeTransform();
89
- }
90
95
  onContextMenu(event) {
91
96
  const canShowSelectionMenu = this.selectionBox
92
97
  ?.getScreenRegion()
@@ -105,7 +110,7 @@ class SelectionTool extends BaseTool_1.default {
105
110
  let transforming = false;
106
111
  if (this.selectionBox) {
107
112
  if (snapToGrid) {
108
- this.snapSelectionToGrid();
113
+ this.selectionBox.snapSelectedObjectsToGrid();
109
114
  }
110
115
  const dragStartResult = this.selectionBox.onDragStart(current);
111
116
  if (dragStartResult) {
@@ -117,7 +122,13 @@ class SelectionTool extends BaseTool_1.default {
117
122
  if (!transforming) {
118
123
  // Shift key: Combine the new and old selection boxes at the end of the gesture.
119
124
  this.expandingSelectionBox = this.shiftKeyPressed;
120
- this.rebuildSelectionScheduled = true;
125
+ this.removeSelectionScheduled = !this.expandingSelectionBox;
126
+ if (this.modeValue.get() === types_2.SelectionMode.Lasso) {
127
+ this.selectionBuilder = new LassoSelectionBuilder_1.default(current.canvasPos, this.editor.viewport);
128
+ }
129
+ else {
130
+ this.selectionBuilder = new RectSelectionBuilder_1.default(current.canvasPos);
131
+ }
121
132
  }
122
133
  else {
123
134
  // Only autoscroll if we're transforming an existing selection
@@ -132,13 +143,12 @@ class SelectionTool extends BaseTool_1.default {
132
143
  }
133
144
  onMainPointerUpdated(currentPointer) {
134
145
  this.lastPointer = currentPointer;
135
- if (this.rebuildSelectionScheduled) {
136
- this.rebuildSelectionScheduled = false;
137
- this.makeSelectionBox(this.startPoint ?? currentPointer.canvasPos);
138
- this.selectionBox?.setHandlesVisible(false);
146
+ if (this.removeSelectionScheduled) {
147
+ this.removeSelectionScheduled = false;
148
+ this.handleOverlay.replaceChildren();
149
+ this.prevSelectionBox = this.selectionBox;
150
+ this.selectionBox = null;
139
151
  }
140
- if (!this.selectionBox)
141
- return;
142
152
  this.autoscroller.onPointerMove(currentPointer.screenPos);
143
153
  if (!this.expandingSelectionBox && this.shiftKeyPressed && this.startPoint) {
144
154
  const screenPos = this.editor.viewport.canvasToScreen(this.startPoint);
@@ -148,47 +158,46 @@ class SelectionTool extends BaseTool_1.default {
148
158
  currentPointer = currentPointer.snappedToGrid(this.editor.viewport);
149
159
  }
150
160
  if (this.selectionBoxHandlingEvt) {
151
- this.selectionBox.onDragUpdate(currentPointer);
161
+ this.selectionBox?.onDragUpdate(currentPointer);
152
162
  }
153
163
  else {
154
- this.selectionBox.setToPoint(currentPointer.canvasPos);
164
+ this.selectionBuilder?.onPointerMove(currentPointer.canvasPos);
165
+ this.editor.clearWetInk();
166
+ this.selectionBuilder?.render(this.editor.display.getWetInkRenderer(), this.getSelectionColor());
155
167
  }
156
168
  }
157
169
  onPointerUp(event) {
158
170
  this.onMainPointerUpdated(event.current);
159
171
  this.autoscroller.stop();
160
- if (!this.selectionBox)
161
- return;
162
- this.selectionBox.setHandlesVisible(true);
163
- // Were we expanding the previous selection?
164
- if (this.expandingSelectionBox && this.prevSelectionBox) {
165
- // If so, finish expanding.
166
- this.expandingSelectionBox = false;
167
- this.selectionBox.resolveToObjects();
168
- this.setSelection([
169
- ...this.selectionBox.getSelectedObjects(),
170
- ...this.prevSelectionBox.getSelectedObjects(),
171
- ]);
172
+ if (this.selectionBoxHandlingEvt) {
173
+ this.selectionBox?.onDragEnd();
172
174
  }
173
- else {
174
- if (!this.selectionBoxHandlingEvt) {
175
- // Expand/shrink the selection rectangle, if applicable
176
- this.selectionBox.resolveToObjects();
177
- this.onSelectionUpdated();
175
+ else if (this.selectionBuilder) {
176
+ const newSelection = this.selectionBuilder.resolve(this.editor.image, this.editor.viewport);
177
+ this.selectionBuilder = null;
178
+ this.editor.clearWetInk();
179
+ if (this.expandingSelectionBox && this.selectionBox) {
180
+ this.setSelection([...this.selectionBox.getSelectedObjects(), ...newSelection]);
178
181
  }
179
182
  else {
180
- this.selectionBox.onDragEnd();
183
+ this.setSelection(newSelection);
181
184
  }
182
- this.selectionBoxHandlingEvt = false;
183
- this.lastPointer = null;
184
185
  }
186
+ this.expandingSelectionBox = false;
187
+ this.removeSelectionScheduled = false;
188
+ this.selectionBoxHandlingEvt = false;
189
+ this.lastPointer = null;
185
190
  }
186
191
  onGestureCancel() {
192
+ if (this.selectionBuilder) {
193
+ this.selectionBuilder = null;
194
+ this.editor.clearWetInk();
195
+ }
187
196
  this.autoscroller.stop();
188
197
  if (this.selectionBoxHandlingEvt) {
189
198
  this.selectionBox?.onDragCancel();
190
199
  }
191
- else if (!this.rebuildSelectionScheduled) {
200
+ else if (!this.removeSelectionScheduled) {
192
201
  // Revert to the previous selection, if any.
193
202
  this.selectionBox?.cancelSelection();
194
203
  this.selectionBox = this.prevSelectionBox;
@@ -196,7 +205,7 @@ class SelectionTool extends BaseTool_1.default {
196
205
  this.selectionBox?.recomputeRegion();
197
206
  this.prevSelectionBox = null;
198
207
  }
199
- this.rebuildSelectionScheduled = false;
208
+ this.removeSelectionScheduled = false;
200
209
  this.expandingSelectionBox = false;
201
210
  this.lastPointer = null;
202
211
  this.selectionBoxHandlingEvt = false;
@@ -464,6 +473,10 @@ class SelectionTool extends BaseTool_1.default {
464
473
  getSelection() {
465
474
  return this.selectionBox;
466
475
  }
476
+ /** @returns true if the selection is currently being created by the user. */
477
+ isSelecting() {
478
+ return !!this.selectionBuilder;
479
+ }
467
480
  getSelectedObjects() {
468
481
  return this.selectionBox?.getSelectedObjects() ?? [];
469
482
  }
@@ -489,20 +502,20 @@ class SelectionTool extends BaseTool_1.default {
489
502
  bbox = object.getBBox();
490
503
  }
491
504
  }
492
- if (!bbox) {
493
- return;
494
- }
495
- this.clearSelection();
496
- if (!this.selectionBox) {
497
- this.makeSelectionBox(bbox.topLeft);
505
+ this.clearSelectionNoUpdateEvent();
506
+ if (bbox) {
507
+ this.makeSelectionBox(objects);
498
508
  }
499
- this.selectionBox.setSelectedObjects(objects, bbox);
500
509
  this.onSelectionUpdated();
501
510
  }
502
- clearSelection() {
511
+ // Equivalent to .clearSelection, but does not dispatch an update event
512
+ clearSelectionNoUpdateEvent() {
503
513
  this.handleOverlay.replaceChildren();
504
514
  this.prevSelectionBox = this.selectionBox;
505
515
  this.selectionBox = null;
516
+ }
517
+ clearSelection() {
518
+ this.clearSelectionNoUpdateEvent();
506
519
  this.onSelectionUpdated();
507
520
  }
508
521
  }
@@ -1,5 +1,9 @@
1
1
  import type { Rect2, Point2 } from '@js-draw/math';
2
2
  import Pointer from '../../Pointer';
3
+ export declare enum SelectionMode {
4
+ Lasso = "lasso",
5
+ Rectangle = "rect"
6
+ }
3
7
  export declare enum ResizeMode {
4
8
  Both = 0,
5
9
  HorizontalOnly = 1,
@@ -1,6 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TransformMode = exports.ResizeMode = void 0;
3
+ exports.TransformMode = exports.ResizeMode = exports.SelectionMode = void 0;
4
+ var SelectionMode;
5
+ (function (SelectionMode) {
6
+ SelectionMode["Lasso"] = "lasso";
7
+ SelectionMode["Rectangle"] = "rect";
8
+ })(SelectionMode || (exports.SelectionMode = SelectionMode = {}));
4
9
  var ResizeMode;
5
10
  (function (ResizeMode) {
6
11
  ResizeMode[ResizeMode["Both"] = 0] = "Both";
@@ -163,12 +163,15 @@ class TextTool extends BaseTool_1.default {
163
163
  }
164
164
  };
165
165
  this.textInputElem.onblur = () => {
166
+ const input = this.textInputElem;
166
167
  // Delay removing the input -- flushInput may be called within a blur()
167
168
  // event handler
168
169
  const removeInput = false;
169
- const input = this.textInputElem;
170
170
  this.flushInput(removeInput);
171
171
  this.textInputElem = null;
172
+ if (input) {
173
+ input.classList.add('-hiding');
174
+ }
172
175
  setTimeout(() => {
173
176
  input?.remove();
174
177
  }, 0);
@@ -208,7 +211,7 @@ class TextTool extends BaseTool_1.default {
208
211
  if (allPointers.length === 1) {
209
212
  // Are we clicking on a text node?
210
213
  const canvasPos = current.canvasPos;
211
- const halfTestRegionSize = math_1.Vec2.of(2.5, 2.5).times(this.editor.viewport.getSizeOfPixelOnCanvas());
214
+ const halfTestRegionSize = math_1.Vec2.of(4, 4).times(this.editor.viewport.getSizeOfPixelOnCanvas());
212
215
  const testRegion = math_1.Rect2.fromCorners(canvasPos.minus(halfTestRegionSize), canvasPos.plus(halfTestRegionSize));
213
216
  const targetNodes = this.editor.image.getElementsIntersectingRegion(testRegion);
214
217
  let targetTextNodes = targetNodes.filter((node) => node instanceof TextComponent_1.default);
@@ -7,7 +7,7 @@ export { default as ToolSwitcherShortcut } from './ToolSwitcherShortcut';
7
7
  export { default as PanZoomTool, PanZoomMode } from './PanZoom';
8
8
  export { default as PenTool, PenStyle } from './Pen';
9
9
  export { default as TextTool } from './TextTool';
10
- export { default as SelectionTool } from './SelectionTool/SelectionTool';
10
+ export { default as SelectionTool, SelectionMode } from './SelectionTool/SelectionTool';
11
11
  export { default as SelectAllShortcutHandler } from './SelectionTool/SelectAllShortcutHandler';
12
12
  export { default as EraserTool, EraserMode } from './Eraser';
13
13
  export { default as PasteHandler } from './PasteHandler';
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.ToolbarShortcutHandler = exports.SoundUITool = exports.PasteHandler = exports.EraserMode = exports.EraserTool = exports.SelectAllShortcutHandler = exports.SelectionTool = exports.TextTool = exports.PenTool = exports.PanZoomMode = exports.PanZoomTool = exports.ToolSwitcherShortcut = exports.UndoRedoShortcut = exports.ToolEnabledGroup = exports.ToolController = exports.BaseTool = exports.InputMapper = void 0;
6
+ exports.ToolbarShortcutHandler = exports.SoundUITool = exports.PasteHandler = exports.EraserMode = exports.EraserTool = exports.SelectAllShortcutHandler = exports.SelectionMode = exports.SelectionTool = exports.TextTool = exports.PenTool = exports.PanZoomMode = exports.PanZoomTool = exports.ToolSwitcherShortcut = exports.UndoRedoShortcut = exports.ToolEnabledGroup = exports.ToolController = exports.BaseTool = exports.InputMapper = void 0;
7
7
  var InputMapper_1 = require("./InputFilter/InputMapper");
8
8
  Object.defineProperty(exports, "InputMapper", { enumerable: true, get: function () { return __importDefault(InputMapper_1).default; } });
9
9
  var BaseTool_1 = require("./BaseTool");
@@ -25,6 +25,7 @@ var TextTool_1 = require("./TextTool");
25
25
  Object.defineProperty(exports, "TextTool", { enumerable: true, get: function () { return __importDefault(TextTool_1).default; } });
26
26
  var SelectionTool_1 = require("./SelectionTool/SelectionTool");
27
27
  Object.defineProperty(exports, "SelectionTool", { enumerable: true, get: function () { return __importDefault(SelectionTool_1).default; } });
28
+ Object.defineProperty(exports, "SelectionMode", { enumerable: true, get: function () { return SelectionTool_1.SelectionMode; } });
28
29
  var SelectAllShortcutHandler_1 = require("./SelectionTool/SelectAllShortcutHandler");
29
30
  Object.defineProperty(exports, "SelectAllShortcutHandler", { enumerable: true, get: function () { return __importDefault(SelectAllShortcutHandler_1).default; } });
30
31
  var Eraser_1 = require("./Eraser");
@@ -72,9 +72,7 @@ class ReactiveValue {
72
72
  */
73
73
  static fromCallback(callback, sourceValues) {
74
74
  const result = new ReactiveValueImpl(callback());
75
- const resultRef = window.WeakRef
76
- ? new window.WeakRef(result)
77
- : { deref: () => result };
75
+ const resultRef = typeof WeakRef !== 'undefined' ? new WeakRef(result) : { deref: () => result };
78
76
  for (const value of sourceValues) {
79
77
  const listener = value.onUpdate(() => {
80
78
  // Use resultRef to allow `result` to be garbage collected
@@ -118,9 +116,7 @@ exports.ReactiveValue = ReactiveValue;
118
116
  class MutableReactiveValue extends ReactiveValue {
119
117
  static fromProperty(sourceValue, propertyName) {
120
118
  const child = ReactiveValue.fromInitialValue(sourceValue.get()[propertyName]);
121
- const childRef = window.WeakRef
122
- ? new window.WeakRef(child)
123
- : { deref: () => child };
119
+ const childRef = typeof WeakRef !== 'undefined' ? new WeakRef(child) : { deref: () => child };
124
120
  // When the source is updated...
125
121
  const sourceListener = sourceValue.onUpdate((newValue) => {
126
122
  const childValue = childRef.deref();
@@ -2,7 +2,7 @@
2
2
  * Compile-time assertion that a branch of code is unreachable.
3
3
  * @internal
4
4
  */
5
- export declare const assertUnreachable: (key: never) => never;
5
+ export declare function assertUnreachable(key: never): never;
6
6
  /**
7
7
  * Throws an exception if the typeof given value is not a number or `value` is NaN.
8
8
  *
@@ -13,15 +13,16 @@ export declare const assertUnreachable: (key: never) => never;
13
13
  *
14
14
  * assertIsNumber('hello, world'); // throws an Error.
15
15
  * ```
16
- *
17
- *
18
16
  */
19
- export declare const assertIsNumber: (value: any, allowNaN?: boolean) => value is number;
17
+ export declare function assertIsNumber(value: unknown, allowNaN?: boolean): asserts value is number;
18
+ export declare function assertIsArray(values: unknown): asserts values is unknown[];
20
19
  /**
21
20
  * Throws if any of `values` is not of type number.
22
21
  */
23
- export declare const assertIsNumberArray: (values: any[], allowNaN?: boolean) => values is number[];
22
+ export declare function assertIsNumberArray(values: unknown, allowNaN?: boolean): asserts values is number[];
24
23
  /**
25
24
  * Throws an exception if `typeof value` is not a boolean.
26
25
  */
27
- export declare const assertIsBoolean: (value: any) => value is boolean;
26
+ export declare function assertIsBoolean(value: unknown): asserts value is boolean;
27
+ export declare function assertTruthy<T>(value: T | null | undefined | false | 0): asserts value is T;
28
+ export declare function assertIsObject(value: unknown): asserts value is Record<string, unknown>;
@@ -1,15 +1,22 @@
1
1
  "use strict";
2
+ // Note: Arrow functions cannot be used for type assertions. See
3
+ // https://github.com/microsoft/TypeScript/issues/34523
2
4
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.assertIsBoolean = exports.assertIsNumberArray = exports.assertIsNumber = exports.assertUnreachable = void 0;
5
+ exports.assertUnreachable = assertUnreachable;
6
+ exports.assertIsNumber = assertIsNumber;
7
+ exports.assertIsArray = assertIsArray;
8
+ exports.assertIsNumberArray = assertIsNumberArray;
9
+ exports.assertIsBoolean = assertIsBoolean;
10
+ exports.assertTruthy = assertTruthy;
11
+ exports.assertIsObject = assertIsObject;
4
12
  /**
5
13
  * Compile-time assertion that a branch of code is unreachable.
6
14
  * @internal
7
15
  */
8
- const assertUnreachable = (key) => {
16
+ function assertUnreachable(key) {
9
17
  // See https://stackoverflow.com/a/39419171/17055750
10
18
  throw new Error(`Should be unreachable. Key: ${key}.`);
11
- };
12
- exports.assertUnreachable = assertUnreachable;
19
+ }
13
20
  /**
14
21
  * Throws an exception if the typeof given value is not a number or `value` is NaN.
15
22
  *
@@ -20,43 +27,42 @@ exports.assertUnreachable = assertUnreachable;
20
27
  *
21
28
  * assertIsNumber('hello, world'); // throws an Error.
22
29
  * ```
23
- *
24
- *
25
30
  */
26
- const assertIsNumber = (value, allowNaN = false) => {
31
+ function assertIsNumber(value, allowNaN = false) {
27
32
  if (typeof value !== 'number' || (!allowNaN && isNaN(value))) {
28
33
  throw new Error('Given value is not a number');
29
- // return false;
30
34
  }
31
- return true;
32
- };
33
- exports.assertIsNumber = assertIsNumber;
35
+ }
36
+ function assertIsArray(values) {
37
+ if (!Array.isArray(values)) {
38
+ throw new Error('Asserting isArray: Given entity is not an array');
39
+ }
40
+ }
34
41
  /**
35
42
  * Throws if any of `values` is not of type number.
36
43
  */
37
- const assertIsNumberArray = (values, allowNaN = false) => {
38
- if (typeof values !== 'object') {
39
- throw new Error('Asserting isNumberArray: Given entity is not an array');
40
- }
41
- if (!(0, exports.assertIsNumber)(values['length'])) {
42
- return false;
43
- }
44
+ function assertIsNumberArray(values, allowNaN = false) {
45
+ assertIsArray(values);
46
+ assertIsNumber(values.length);
44
47
  for (const value of values) {
45
- if (!(0, exports.assertIsNumber)(value, allowNaN)) {
46
- return false;
47
- }
48
+ assertIsNumber(value, allowNaN);
48
49
  }
49
- return true;
50
- };
51
- exports.assertIsNumberArray = assertIsNumberArray;
50
+ }
52
51
  /**
53
52
  * Throws an exception if `typeof value` is not a boolean.
54
53
  */
55
- const assertIsBoolean = (value) => {
54
+ function assertIsBoolean(value) {
56
55
  if (typeof value !== 'boolean') {
57
56
  throw new Error('Given value is not a boolean');
58
- // return false;
59
57
  }
60
- return true;
61
- };
62
- exports.assertIsBoolean = assertIsBoolean;
58
+ }
59
+ function assertTruthy(value) {
60
+ if (!value) {
61
+ throw new Error(`${JSON.stringify(value)} is not truthy`);
62
+ }
63
+ }
64
+ function assertIsObject(value) {
65
+ if (typeof value !== 'object') {
66
+ throw new Error(`AssertIsObject: Given entity is not an object (type = ${typeof value})`);
67
+ }
68
+ }
@@ -6,5 +6,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  * @internal
7
7
  */
8
8
  exports.default = {
9
- number: '1.25.0',
9
+ number: '1.27.1',
10
10
  };
@@ -116,8 +116,6 @@ export interface EditorSettings {
116
116
  *
117
117
  * If not given, the default file picker shown by a [file input](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file)
118
118
  * is shown.
119
- *
120
- * @beta -- API may change between minor releases.
121
119
  */
122
120
  showImagePicker?: ShowCustomFilePickerCallback;
123
121
  } | null;
@@ -1320,7 +1320,7 @@ export class Editor {
1320
1320
  '',
1321
1321
  '',
1322
1322
  '== js-draw ==',
1323
- mitLicenseAttribution('2023-2024 Henry Heino'),
1323
+ mitLicenseAttribution('2023-2025 Henry Heino'),
1324
1324
  '',
1325
1325
  ].join('\n'),
1326
1326
  minimized: true,
@@ -1,4 +1,5 @@
1
- // Main entrypoint for Webpack when building a bundle for release.
1
+ // Main entrypoint for the bundler (ESBuild/Webpack/etc.) when creating the bundled
2
+ // portion of a release.
2
3
  import '../styles';
3
4
  import Editor from '../Editor.mjs';
4
5
  export * from '../lib.mjs';
@@ -4,6 +4,7 @@ import { LineSegment2, Mat33, Path, Rect2 } from '@js-draw/math';
4
4
  import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
5
5
  import { ImageComponentLocalization } from './localization';
6
6
  import Viewport from '../Viewport';
7
+ import { Point2 } from '@js-draw/math';
7
8
  export type LoadSaveData = string[] | Record<symbol, string | number>;
8
9
  export type LoadSaveDataTable = Record<string, Array<LoadSaveData>>;
9
10
  export type DeserializeCallback = (data: string) => AbstractComponent;
@@ -27,6 +28,12 @@ export declare enum ComponentSizingMode {
27
28
  }
28
29
  /**
29
30
  * A base class for everything that can be added to an {@link EditorImage}.
31
+ *
32
+ * In addition to the `abstract` methods, there are a few methods that should be
33
+ * overridden when creating a selectable/erasable subclass:
34
+ * - {@link keyPoints}: Overriding this may improve how the component interacts with the selection tool.
35
+ * - {@link withRegionErased}: Override/implement this to allow the component to be partially erased
36
+ * by the partial stroke eraser.
30
37
  */
31
38
  export default abstract class AbstractComponent {
32
39
  private readonly componentKind;
@@ -113,6 +120,14 @@ export default abstract class AbstractComponent {
113
120
  * this function.
114
121
  */
115
122
  intersectsRect(rect: Rect2): boolean;
123
+ /**
124
+ * Returns a selection of points within this object. Each contiguous section
125
+ * of this object should have a point in the returned array.
126
+ *
127
+ * Subclasses should override this method if the center of the bounding box is
128
+ * not contained within the object.
129
+ */
130
+ keyPoints(): Point2[];
116
131
  isSelectable(): boolean;
117
132
  isBackground(): boolean;
118
133
  getProportionalRenderingTime(): number;