js-draw 1.26.0 → 1.27.1

Sign up to get free protection for your applications and to get access to all the features.
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
  }