js-draw 1.26.0 → 1.27.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.
Files changed (67) hide show
  1. package/dist/Editor.css +1 -1
  2. package/dist/bundle.js +42 -37
  3. package/dist/bundledStyles.js +1 -1
  4. package/dist/cjs/Editor.d.ts +0 -2
  5. package/dist/cjs/components/AbstractComponent.d.ts +15 -0
  6. package/dist/cjs/components/AbstractComponent.js +16 -0
  7. package/dist/cjs/components/Stroke.d.ts +1 -0
  8. package/dist/cjs/components/Stroke.js +7 -0
  9. package/dist/cjs/toolbar/IconProvider.d.ts +2 -1
  10. package/dist/cjs/toolbar/IconProvider.js +18 -8
  11. package/dist/cjs/toolbar/localization.d.ts +2 -0
  12. package/dist/cjs/toolbar/localization.js +2 -0
  13. package/dist/cjs/toolbar/widgets/SelectionToolWidget.d.ts +7 -0
  14. package/dist/cjs/toolbar/widgets/SelectionToolWidget.js +109 -28
  15. package/dist/cjs/toolbar/widgets/components/makeButtonGrid.d.ts +17 -0
  16. package/dist/cjs/toolbar/widgets/components/makeButtonGrid.js +40 -0
  17. package/dist/cjs/tools/SelectionTool/Selection.d.ts +2 -3
  18. package/dist/cjs/tools/SelectionTool/Selection.js +19 -40
  19. package/dist/cjs/tools/SelectionTool/SelectionBuilders/LassoSelectionBuilder.d.ts +17 -0
  20. package/dist/cjs/tools/SelectionTool/SelectionBuilders/LassoSelectionBuilder.js +67 -0
  21. package/dist/cjs/tools/SelectionTool/SelectionBuilders/RectSelectionBuilder.d.ts +13 -0
  22. package/dist/cjs/tools/SelectionTool/SelectionBuilders/RectSelectionBuilder.js +33 -0
  23. package/dist/cjs/tools/SelectionTool/SelectionBuilders/SelectionBuilder.d.ts +15 -0
  24. package/dist/cjs/tools/SelectionTool/SelectionBuilders/SelectionBuilder.js +39 -0
  25. package/dist/cjs/tools/SelectionTool/SelectionTool.d.ts +10 -2
  26. package/dist/cjs/tools/SelectionTool/SelectionTool.js +68 -55
  27. package/dist/cjs/tools/SelectionTool/types.d.ts +4 -0
  28. package/dist/cjs/tools/SelectionTool/types.js +6 -1
  29. package/dist/cjs/tools/lib.d.ts +1 -1
  30. package/dist/cjs/tools/lib.js +2 -1
  31. package/dist/cjs/util/ReactiveValue.js +2 -6
  32. package/dist/cjs/version.js +1 -1
  33. package/dist/mjs/Editor.d.ts +0 -2
  34. package/dist/mjs/components/AbstractComponent.d.ts +15 -0
  35. package/dist/mjs/components/AbstractComponent.mjs +16 -0
  36. package/dist/mjs/components/Stroke.d.ts +1 -0
  37. package/dist/mjs/components/Stroke.mjs +7 -0
  38. package/dist/mjs/toolbar/IconProvider.d.ts +2 -1
  39. package/dist/mjs/toolbar/IconProvider.mjs +18 -8
  40. package/dist/mjs/toolbar/localization.d.ts +2 -0
  41. package/dist/mjs/toolbar/localization.mjs +2 -0
  42. package/dist/mjs/toolbar/widgets/SelectionToolWidget.d.ts +7 -0
  43. package/dist/mjs/toolbar/widgets/SelectionToolWidget.mjs +109 -28
  44. package/dist/mjs/toolbar/widgets/components/makeButtonGrid.d.ts +17 -0
  45. package/dist/mjs/toolbar/widgets/components/makeButtonGrid.mjs +35 -0
  46. package/dist/mjs/tools/SelectionTool/Selection.d.ts +2 -3
  47. package/dist/mjs/tools/SelectionTool/Selection.mjs +19 -40
  48. package/dist/mjs/tools/SelectionTool/SelectionBuilders/LassoSelectionBuilder.d.ts +17 -0
  49. package/dist/mjs/tools/SelectionTool/SelectionBuilders/LassoSelectionBuilder.mjs +61 -0
  50. package/dist/mjs/tools/SelectionTool/SelectionBuilders/RectSelectionBuilder.d.ts +13 -0
  51. package/dist/mjs/tools/SelectionTool/SelectionBuilders/RectSelectionBuilder.mjs +27 -0
  52. package/dist/mjs/tools/SelectionTool/SelectionBuilders/SelectionBuilder.d.ts +15 -0
  53. package/dist/mjs/tools/SelectionTool/SelectionBuilders/SelectionBuilder.mjs +36 -0
  54. package/dist/mjs/tools/SelectionTool/SelectionTool.d.ts +10 -2
  55. package/dist/mjs/tools/SelectionTool/SelectionTool.mjs +68 -55
  56. package/dist/mjs/tools/SelectionTool/types.d.ts +4 -0
  57. package/dist/mjs/tools/SelectionTool/types.mjs +5 -0
  58. package/dist/mjs/tools/lib.d.ts +1 -1
  59. package/dist/mjs/tools/lib.mjs +1 -1
  60. package/dist/mjs/util/ReactiveValue.mjs +2 -6
  61. package/dist/mjs/version.mjs +1 -1
  62. package/package.json +4 -4
  63. package/src/toolbar/EdgeToolbar.scss +6 -1
  64. package/src/toolbar/widgets/components/components.scss +1 -0
  65. package/src/toolbar/widgets/components/makeButtonGrid.scss +25 -0
  66. package/src/tools/SelectionTool/SelectionTool.scss +1 -0
  67. package/src/tools/util/createMenuOverlay.scss +3 -2
@@ -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";
@@ -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();
@@ -6,5 +6,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  * @internal
7
7
  */
8
8
  exports.default = {
9
- number: '1.26.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;
@@ -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;
@@ -28,6 +28,12 @@ export var ComponentSizingMode;
28
28
  })(ComponentSizingMode || (ComponentSizingMode = {}));
29
29
  /**
30
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.
31
37
  */
32
38
  class AbstractComponent {
33
39
  constructor(
@@ -136,6 +142,16 @@ class AbstractComponent {
136
142
  const testLines = rect.getEdges();
137
143
  return testLines.some((edge) => this.intersects(edge));
138
144
  }
145
+ /**
146
+ * Returns a selection of points within this object. Each contiguous section
147
+ * of this object should have a point in the returned array.
148
+ *
149
+ * Subclasses should override this method if the center of the bounding box is
150
+ * not contained within the object.
151
+ */
152
+ keyPoints() {
153
+ return [this.getBBox().center];
154
+ }
139
155
  // @returns true iff this component can be selected (e.g. by the selection tool.)
140
156
  isSelectable() {
141
157
  return true;
@@ -54,6 +54,7 @@ export default class Stroke extends AbstractComponent implements RestyleableComp
54
54
  /** @beta -- May fail for concave `path`s */
55
55
  withRegionErased(eraserPath: Path, viewport: Viewport): Stroke[];
56
56
  intersects(line: LineSegment2): boolean;
57
+ keyPoints(): import("@js-draw/math").Vec3[];
57
58
  intersectsRect(rect: Rect2): boolean;
58
59
  private simplifiedPath;
59
60
  private computeSimplifiedPathFor;
@@ -294,6 +294,13 @@ export default class Stroke extends AbstractComponent {
294
294
  }
295
295
  return false;
296
296
  }
297
+ keyPoints() {
298
+ return this.parts
299
+ .map((part) => {
300
+ return part.startPoint;
301
+ })
302
+ .flat();
303
+ }
297
304
  intersectsRect(rect) {
298
305
  // AbstractComponent::intersectsRect can be inexact for strokes with non-zero
299
306
  // stroke radius (has many false negatives). As such, additional checks are
@@ -2,6 +2,7 @@ import { Color4 } from '@js-draw/math';
2
2
  import TextRenderingStyle from '../rendering/TextRenderingStyle';
3
3
  import { PenStyle } from '../tools/Pen';
4
4
  import { EraserMode } from '../tools/Eraser';
5
+ import { SelectionMode } from '../tools/SelectionTool/types';
5
6
  export type IconElemType = HTMLImageElement | SVGElement;
6
7
  /**
7
8
  * Provides icons that can be used in the toolbar and other locations.
@@ -41,7 +42,7 @@ export default class IconProvider {
41
42
  makeRedoIcon(): IconElemType;
42
43
  makeDropdownIcon(): IconElemType;
43
44
  makeEraserIcon(eraserSize?: number, mode?: EraserMode): IconElemType;
44
- makeSelectionIcon(): IconElemType;
45
+ makeSelectionIcon(mode?: SelectionMode): IconElemType;
45
46
  makeRotateIcon(): IconElemType;
46
47
  makeHandToolIcon(): IconElemType;
47
48
  makeTouchPanningIcon(): IconElemType;
@@ -11,6 +11,7 @@ import { makeFreehandLineBuilder } from '../components/builders/FreehandLineBu
11
11
  import { makePolylineBuilder } from '../components/builders/PolylineBuilder.mjs';
12
12
  import { EraserMode } from '../tools/Eraser.mjs';
13
13
  import { createSvgElement, createSvgElements, createSvgPaths } from '../util/createElement.mjs';
14
+ import { SelectionMode } from '../tools/SelectionTool/types.mjs';
14
15
  const svgNamespace = 'http://www.w3.org/2000/svg';
15
16
  let checkerboardIdCounter = 0;
16
17
  const makeCheckerboardPattern = () => {
@@ -164,15 +165,24 @@ class IconProvider {
164
165
  });
165
166
  return icon;
166
167
  }
167
- makeSelectionIcon() {
168
+ makeSelectionIcon(mode = SelectionMode.Rectangle) {
168
169
  const icon = document.createElementNS(svgNamespace, 'svg');
169
- // Draw a cursor-like shape
170
- icon.innerHTML = `
171
- <g>
172
- <rect x="10" y="10" width="70" height="70" fill="pink" stroke="black"/>
173
- <rect x="75" y="75" width="10" height="10" fill="white" stroke="black"/>
174
- </g>
175
- `;
170
+ if (mode === SelectionMode.Rectangle) {
171
+ icon.innerHTML = `
172
+ <g>
173
+ <rect x="10" y="10" width="70" height="70" fill="pink" stroke="black" stroke-dasharray="32 9"/>
174
+ <rect x="75" y="75" width="10" height="10" fill="white" stroke="black"/>
175
+ </g>
176
+ `;
177
+ }
178
+ else {
179
+ icon.innerHTML = `
180
+ <g>
181
+ <rect x="10" y="10" width="76" height="76" rx="50" stroke-dasharray="32 9" fill="pink" stroke="black"/>
182
+ <rect x="71" y="71" width="10" height="10" fill="white" stroke="black"/>
183
+ </g>
184
+ `;
185
+ }
176
186
  icon.setAttribute('viewBox', '0 0 100 100');
177
187
  return icon;
178
188
  }
@@ -44,6 +44,8 @@ export interface ToolbarLocalization extends ToolbarUtilsLocalization {
44
44
  resetView: string;
45
45
  reformatSelection: string;
46
46
  selectionToolKeyboardShortcuts: string;
47
+ selectionTool__lassoSelect: string;
48
+ selectionTool__lassoSelect__help: string;
47
49
  paste: string;
48
50
  documentProperties: string;
49
51
  backgroundColor: string;
@@ -34,6 +34,8 @@ export const defaultToolbarLocalization = {
34
34
  pickColorFromScreen: 'Pick color from screen',
35
35
  clickToPickColorAnnouncement: 'Click on the screen to pick a color',
36
36
  colorSelectionCanceledAnnouncement: 'Color selection canceled',
37
+ selectionTool__lassoSelect: 'Freeform selection',
38
+ selectionTool__lassoSelect__help: 'When enabled, dragging creates a freeform (lasso) selection.',
37
39
  selectionToolKeyboardShortcuts: 'Selection tool: Use arrow keys to move selected items, lowercase/uppercase ‘i’ and ‘o’ to resize.',
38
40
  documentProperties: 'Page',
39
41
  backgroundColor: 'Background color',
@@ -4,14 +4,21 @@ import { KeyPressEvent } from '../../inputEvents';
4
4
  import { ToolbarLocalization } from '../localization';
5
5
  import BaseToolWidget from './BaseToolWidget';
6
6
  import HelpDisplay from '../utils/HelpDisplay';
7
+ import { SavedToolbuttonState } from './BaseWidget';
7
8
  export default class SelectionToolWidget extends BaseToolWidget {
8
9
  private tool;
9
10
  private updateFormatMenu;
11
+ private hasSelectionValue;
10
12
  constructor(editor: Editor, tool: SelectionTool, localization?: ToolbarLocalization);
11
13
  private resizeImageToSelection;
12
14
  protected onKeyPress(event: KeyPressEvent): boolean;
13
15
  protected getTitle(): string;
14
16
  protected createIcon(): Element;
15
17
  protected getHelpText(): string;
18
+ protected createSelectionActions(helpDisplay?: HelpDisplay): {
19
+ container: HTMLDivElement;
20
+ };
16
21
  protected fillDropdown(dropdown: HTMLElement, helpDisplay?: HelpDisplay): boolean;
22
+ serializeState(): SavedToolbuttonState;
23
+ deserializeFrom(state: SavedToolbuttonState): void;
17
24
  }