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
@@ -6,13 +6,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const math_1 = require("@js-draw/math");
7
7
  const RestylableComponent_1 = require("../../components/RestylableComponent");
8
8
  const uniteCommands_1 = __importDefault(require("../../commands/uniteCommands"));
9
+ const SelectionTool_1 = require("../../tools/SelectionTool/SelectionTool");
9
10
  const types_1 = require("../../types");
10
11
  const makeColorInput_1 = __importDefault(require("./components/makeColorInput"));
11
- const ActionButtonWidget_1 = __importDefault(require("./ActionButtonWidget"));
12
12
  const BaseToolWidget_1 = __importDefault(require("./BaseToolWidget"));
13
13
  const keybindings_1 = require("./keybindings");
14
14
  const makeSeparator_1 = __importDefault(require("./components/makeSeparator"));
15
15
  const constants_1 = require("../constants");
16
+ const BaseWidget_1 = __importDefault(require("./BaseWidget"));
17
+ const makeButtonGrid_1 = __importDefault(require("./components/makeButtonGrid"));
18
+ const ReactiveValue_1 = require("../../util/ReactiveValue");
16
19
  const makeFormatMenu = (editor, selectionTool, localizationTable) => {
17
20
  const container = document.createElement('div');
18
21
  container.classList.add('selection-format-menu', `${constants_1.toolbarCSSPrefix}spacedList`, `${constants_1.toolbarCSSPrefix}indentedList`);
@@ -68,48 +71,63 @@ const makeFormatMenu = (editor, selectionTool, localizationTable) => {
68
71
  },
69
72
  };
70
73
  };
74
+ class LassoSelectToggle extends BaseWidget_1.default {
75
+ constructor(editor, tool, localizationTable) {
76
+ super(editor, 'selection-mode-toggle', localizationTable);
77
+ this.tool = tool;
78
+ editor.notifier.on(types_1.EditorEventType.ToolUpdated, (toolEvt) => {
79
+ if (toolEvt.kind === types_1.EditorEventType.ToolUpdated && toolEvt.tool === tool) {
80
+ this.setSelected(tool.modeValue.get() === SelectionTool_1.SelectionMode.Lasso);
81
+ }
82
+ });
83
+ this.setSelected(false);
84
+ }
85
+ shouldAutoDisableInReadOnlyEditor() {
86
+ return false;
87
+ }
88
+ setModeFlag(enabled) {
89
+ this.tool.modeValue.set(enabled ? SelectionTool_1.SelectionMode.Lasso : SelectionTool_1.SelectionMode.Rectangle);
90
+ }
91
+ handleClick() {
92
+ this.setModeFlag(!this.isSelected());
93
+ }
94
+ getTitle() {
95
+ return this.localizationTable.selectionTool__lassoSelect;
96
+ }
97
+ createIcon() {
98
+ return this.editor.icons.makeSelectionIcon(SelectionTool_1.SelectionMode.Lasso);
99
+ }
100
+ fillDropdown(_dropdown) {
101
+ return false;
102
+ }
103
+ getHelpText() {
104
+ return this.localizationTable.selectionTool__lassoSelect__help;
105
+ }
106
+ }
71
107
  class SelectionToolWidget extends BaseToolWidget_1.default {
72
108
  constructor(editor, tool, localization) {
73
109
  super(editor, tool, 'selection-tool-widget', localization);
74
110
  this.tool = tool;
75
111
  this.updateFormatMenu = () => { };
76
- const resizeButton = new ActionButtonWidget_1.default(editor, 'resize-btn', () => editor.icons.makeResizeImageToSelectionIcon(), this.localizationTable.resizeImageToSelection, () => {
77
- this.resizeImageToSelection();
78
- }, localization);
79
- resizeButton.setHelpText(this.localizationTable.selectionDropdown__resizeToHelpText);
80
- const deleteButton = new ActionButtonWidget_1.default(editor, 'delete-btn', () => editor.icons.makeDeleteSelectionIcon(), this.localizationTable.deleteSelection, () => {
81
- const selection = this.tool.getSelection();
82
- this.editor.dispatch(selection.deleteSelectedObjects());
83
- this.tool.clearSelection();
84
- }, localization);
85
- deleteButton.setHelpText(this.localizationTable.selectionDropdown__deleteHelpText);
86
- const duplicateButton = new ActionButtonWidget_1.default(editor, 'duplicate-btn', () => editor.icons.makeDuplicateSelectionIcon(), this.localizationTable.duplicateSelection, async () => {
112
+ this.addSubWidget(new LassoSelectToggle(editor, tool, this.localizationTable));
113
+ const hasSelection = () => {
87
114
  const selection = this.tool.getSelection();
88
- this.editor.dispatch(await selection.duplicateSelectedObjects());
89
- this.setDropdownVisible(false);
90
- }, localization);
91
- duplicateButton.setHelpText(this.localizationTable.selectionDropdown__duplicateHelpText);
92
- this.addSubWidget(resizeButton);
93
- this.addSubWidget(deleteButton);
94
- this.addSubWidget(duplicateButton);
95
- const updateDisabled = (disabled) => {
96
- resizeButton.setDisabled(disabled);
97
- deleteButton.setDisabled(disabled);
98
- duplicateButton.setDisabled(disabled);
115
+ return !!selection && selection.getSelectedItemCount() > 0;
99
116
  };
100
- updateDisabled(true);
117
+ this.hasSelectionValue = ReactiveValue_1.MutableReactiveValue.fromInitialValue(hasSelection());
101
118
  // Enable/disable actions based on whether items are selected
102
119
  this.editor.notifier.on(types_1.EditorEventType.ToolUpdated, (toolEvt) => {
103
120
  if (toolEvt.kind !== types_1.EditorEventType.ToolUpdated) {
104
121
  throw new Error('Invalid event type!');
105
122
  }
106
123
  if (toolEvt.tool === this.tool) {
107
- const selection = this.tool.getSelection();
108
- const hasSelection = selection && selection.getSelectedItemCount() > 0;
109
- updateDisabled(!hasSelection);
124
+ this.hasSelectionValue.set(hasSelection());
110
125
  this.updateFormatMenu();
111
126
  }
112
127
  });
128
+ tool.modeValue.onUpdate(() => {
129
+ this.updateIcon();
130
+ });
113
131
  }
114
132
  resizeImageToSelection() {
115
133
  const selection = this.tool.getSelection();
@@ -135,16 +153,66 @@ class SelectionToolWidget extends BaseToolWidget_1.default {
135
153
  return this.localizationTable.select;
136
154
  }
137
155
  createIcon() {
138
- return this.editor.icons.makeSelectionIcon();
156
+ return this.editor.icons.makeSelectionIcon(this.tool.modeValue.get());
139
157
  }
140
158
  getHelpText() {
141
159
  return this.localizationTable.selectionDropdown__baseHelpText;
142
160
  }
161
+ createSelectionActions(helpDisplay) {
162
+ const icons = this.editor.icons;
163
+ const grid = (0, makeButtonGrid_1.default)([
164
+ {
165
+ icon: () => icons.makeDeleteSelectionIcon(),
166
+ label: this.localizationTable.deleteSelection,
167
+ onCreated: (button) => {
168
+ helpDisplay?.registerTextHelpForElement(button, this.localizationTable.selectionDropdown__deleteHelpText);
169
+ },
170
+ onClick: () => {
171
+ const selection = this.tool.getSelection();
172
+ this.editor.dispatch(selection.deleteSelectedObjects());
173
+ this.tool.clearSelection();
174
+ },
175
+ enabled: this.hasSelectionValue,
176
+ },
177
+ {
178
+ icon: () => icons.makeDuplicateSelectionIcon(),
179
+ label: this.localizationTable.duplicateSelection,
180
+ onCreated: (button) => {
181
+ helpDisplay?.registerTextHelpForElement(button, this.localizationTable.selectionDropdown__duplicateHelpText);
182
+ },
183
+ onClick: async () => {
184
+ const selection = this.tool.getSelection();
185
+ const command = await selection?.duplicateSelectedObjects();
186
+ if (command) {
187
+ this.editor.dispatch(command);
188
+ }
189
+ },
190
+ enabled: this.hasSelectionValue,
191
+ },
192
+ {
193
+ icon: () => icons.makeResizeImageToSelectionIcon(),
194
+ label: this.localizationTable.resizeImageToSelection,
195
+ onCreated: (button) => {
196
+ helpDisplay?.registerTextHelpForElement(button, this.localizationTable.selectionDropdown__resizeToHelpText);
197
+ },
198
+ onClick: () => {
199
+ this.resizeImageToSelection();
200
+ },
201
+ enabled: this.hasSelectionValue,
202
+ },
203
+ ], 3);
204
+ return { container: grid.container };
205
+ }
143
206
  fillDropdown(dropdown, helpDisplay) {
144
207
  super.fillDropdown(dropdown, helpDisplay);
145
208
  const controlsContainer = document.createElement('div');
146
209
  controlsContainer.classList.add(`${constants_1.toolbarCSSPrefix}nonbutton-controls-main-list`);
147
210
  dropdown.appendChild(controlsContainer);
211
+ // Actions (duplicate, delete, etc.)
212
+ (0, makeSeparator_1.default)().addTo(controlsContainer);
213
+ const actions = this.createSelectionActions(helpDisplay);
214
+ controlsContainer.appendChild(actions.container);
215
+ // Formatting
148
216
  (0, makeSeparator_1.default)(this.localizationTable.reformatSelection).addTo(controlsContainer);
149
217
  const formatMenu = makeFormatMenu(this.editor, this.tool, this.localizationTable);
150
218
  formatMenu.addTo(controlsContainer);
@@ -155,5 +223,18 @@ class SelectionToolWidget extends BaseToolWidget_1.default {
155
223
  formatMenu.update();
156
224
  return true;
157
225
  }
226
+ serializeState() {
227
+ return {
228
+ ...super.serializeState(),
229
+ selectionMode: this.tool.modeValue.get(),
230
+ };
231
+ }
232
+ deserializeFrom(state) {
233
+ super.deserializeFrom(state);
234
+ const isValidSelectionMode = Object.values(SelectionTool_1.SelectionMode).includes(state.selectionMode);
235
+ if (isValidSelectionMode) {
236
+ this.tool.modeValue.set(state.selectionMode);
237
+ }
238
+ }
158
239
  }
159
240
  exports.default = SelectionToolWidget;
@@ -0,0 +1,17 @@
1
+ import { ReactiveValue } from '../../../util/ReactiveValue';
2
+ import { IconElemType } from '../../IconProvider';
3
+ interface Button {
4
+ icon: () => IconElemType;
5
+ label: string;
6
+ onClick: () => void;
7
+ onCreated?: (button: HTMLElement) => void;
8
+ enabled?: ReactiveValue<boolean>;
9
+ }
10
+ /**
11
+ * Creates HTML `button` elements from `buttonSpecs` and displays them in a
12
+ * grid with `columnCount` columns.
13
+ */
14
+ declare const makeButtonGrid: (buttonSpecs: Button[], columnCount: number) => {
15
+ container: HTMLDivElement;
16
+ };
17
+ export default makeButtonGrid;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const addLongPressOrHoverCssClasses_1 = __importDefault(require("../../../util/addLongPressOrHoverCssClasses"));
7
+ /**
8
+ * Creates HTML `button` elements from `buttonSpecs` and displays them in a
9
+ * grid with `columnCount` columns.
10
+ */
11
+ const makeButtonGrid = (buttonSpecs, columnCount) => {
12
+ const container = document.createElement('div');
13
+ container.classList.add('toolbar-button-grid');
14
+ container.style.setProperty('--column-count', `${columnCount}`);
15
+ const makeButton = (buttonSpec) => {
16
+ const buttonElement = document.createElement('button');
17
+ buttonElement.classList.add('button');
18
+ const iconElement = buttonSpec.icon();
19
+ iconElement.classList.add('icon');
20
+ const labelElement = document.createElement('label');
21
+ labelElement.textContent = buttonSpec.label;
22
+ labelElement.classList.add('button-label-text');
23
+ buttonElement.onclick = buttonSpec.onClick;
24
+ if (buttonSpec.enabled) {
25
+ buttonSpec.enabled.onUpdateAndNow((enabled) => {
26
+ buttonElement.disabled = !enabled;
27
+ });
28
+ }
29
+ buttonElement.replaceChildren(iconElement, labelElement);
30
+ container.appendChild(buttonElement);
31
+ (0, addLongPressOrHoverCssClasses_1.default)(buttonElement);
32
+ buttonSpec.onCreated?.(buttonElement);
33
+ return buttonElement;
34
+ };
35
+ buttonSpecs.map(makeButton);
36
+ return {
37
+ container,
38
+ };
39
+ };
40
+ exports.default = makeButtonGrid;
@@ -20,7 +20,7 @@ export default class Selection {
20
20
  private innerContainer;
21
21
  private backgroundElem;
22
22
  private hasParent;
23
- constructor(startPoint: Point2, editor: Editor, showContextMenu: (anchor: Point2) => void);
23
+ constructor(selectedElems: AbstractComponent[], editor: Editor, showContextMenu: (anchor: Point2) => void);
24
24
  getBackgroundElem(): HTMLElement;
25
25
  getTransform(): Mat33;
26
26
  get preTransformRegion(): Rect2;
@@ -43,7 +43,6 @@ export default class Selection {
43
43
  sendToBack(): SerializableCommand | null;
44
44
  private static ApplyTransformationCommand;
45
45
  private previewTransformCmds;
46
- resolveToObjects(): boolean;
47
46
  recomputeRegion(): boolean;
48
47
  padRegion(): void;
49
48
  getMinCanvasSize(): number;
@@ -63,10 +62,10 @@ export default class Selection {
63
62
  private selectionDuplicatedAnimationTimeout;
64
63
  private runSelectionDuplicatedAnimation;
65
64
  duplicateSelectedObjects(): Promise<Command>;
65
+ snapSelectedObjectsToGrid(): void;
66
66
  setHandlesVisible(showHandles: boolean): void;
67
67
  addTo(elem: HTMLElement): void;
68
68
  setToPoint(point: Point2): void;
69
69
  cancelSelection(): void;
70
- setSelectedObjects(objects: AbstractComponent[], bbox: Rect2): void;
71
70
  getSelectedObjects(): AbstractComponent[];
72
71
  }
@@ -57,7 +57,7 @@ const updateChunkSize = 100;
57
57
  const maxPreviewElemCount = 500;
58
58
  // @internal
59
59
  class Selection {
60
- constructor(startPoint, editor, showContextMenu) {
60
+ constructor(selectedElems, editor, showContextMenu) {
61
61
  this.editor = editor;
62
62
  // The last-computed bounding box of selected content
63
63
  // @see getTightBoundingBox
@@ -71,7 +71,9 @@ class Selection {
71
71
  this.activeHandle = null;
72
72
  this.backgroundDragging = false;
73
73
  this.selectionDuplicatedAnimationTimeout = null;
74
- this.originalRegion = new math_1.Rect2(startPoint.x, startPoint.y, 0, 0);
74
+ selectedElems = [...selectedElems];
75
+ this.selectedElems = selectedElems;
76
+ this.originalRegion = math_1.Rect2.empty;
75
77
  this.transformers = {
76
78
  drag: new TransformMode_1.DragTransformer(editor, this),
77
79
  resize: new TransformMode_1.ResizeTransformer(editor, this),
@@ -121,6 +123,7 @@ class Selection {
121
123
  for (const widget of this.childwidgets) {
122
124
  widget.addTo(this.backgroundElem);
123
125
  }
126
+ this.recomputeRegion();
124
127
  this.updateUI();
125
128
  }
126
129
  // @internal Intended for unit tests
@@ -243,34 +246,6 @@ class Selection {
243
246
  wetInkRenderer.popTransform();
244
247
  this.updateUI();
245
248
  }
246
- // Find the objects corresponding to this in the document,
247
- // select them.
248
- // Returns false iff nothing was selected.
249
- resolveToObjects() {
250
- let singleItemSelectionMode = false;
251
- this.transform = math_1.Mat33.identity;
252
- // Grow the rectangle, if necessary
253
- if (this.region.w === 0 || this.region.h === 0) {
254
- const padding = this.editor.viewport.visibleRect.maxDimension / 200;
255
- this.originalRegion = math_1.Rect2.bboxOf(this.region.corners, padding);
256
- // Only select one item if the rectangle was very small.
257
- singleItemSelectionMode = true;
258
- }
259
- this.selectedElems = this.editor.image
260
- .getElementsIntersectingRegion(this.region)
261
- .filter((elem) => {
262
- return elem.intersectsRect(this.region) && elem.isSelectable();
263
- });
264
- if (singleItemSelectionMode && this.selectedElems.length > 0) {
265
- this.selectedElems = [this.selectedElems[this.selectedElems.length - 1]];
266
- }
267
- // Find the bounding box of all selected elements.
268
- if (!this.recomputeRegion()) {
269
- return false;
270
- }
271
- this.updateUI();
272
- return true;
273
- }
274
249
  // Recompute this' region from the selected elements.
275
250
  // Returns false if the selection is empty.
276
251
  recomputeRegion() {
@@ -392,6 +367,10 @@ class Selection {
392
367
  });
393
368
  }
394
369
  onDragStart(pointer) {
370
+ // If empty, it isn't possible to drag
371
+ if (this.selectedElems.length === 0) {
372
+ return false;
373
+ }
395
374
  // Clear the HTML selection (prevent HTML drag and drop being triggered by this drag)
396
375
  document.getSelection()?.removeAllRanges();
397
376
  this.activeHandle = null;
@@ -517,6 +496,16 @@ class Selection {
517
496
  }
518
497
  return command;
519
498
  }
499
+ snapSelectedObjectsToGrid() {
500
+ const viewport = this.editor.viewport;
501
+ // Snap the top left corner of what we have selected.
502
+ const topLeftOfBBox = this.computeTightBoundingBox().topLeft;
503
+ const snappedTopLeft = viewport.snapToGrid(topLeftOfBBox);
504
+ const snapDelta = snappedTopLeft.minus(topLeftOfBBox);
505
+ const oldTransform = this.getTransform();
506
+ this.setTransform(oldTransform.rightMul(math_1.Mat33.translation(snapDelta)));
507
+ this.finalizeTransform();
508
+ }
520
509
  setHandlesVisible(showHandles) {
521
510
  if (!showHandles) {
522
511
  this.innerContainer.classList.add('-hide-handles');
@@ -545,16 +534,6 @@ class Selection {
545
534
  this.selectionTightBoundingBox = null;
546
535
  this.hasParent = false;
547
536
  }
548
- setSelectedObjects(objects, bbox) {
549
- this.addRemoveSelectionFromImage(true);
550
- this.originalRegion = bbox;
551
- this.selectionTightBoundingBox = bbox;
552
- this.selectedElems = objects.filter((object) => object.isSelectable());
553
- // Enforce increasing z-index invariant
554
- this.selectedElems.sort((a, b) => a.getZIndex() - b.getZIndex());
555
- this.padRegion();
556
- this.updateUI();
557
- }
558
537
  getSelectedObjects() {
559
538
  return [...this.selectedElems];
560
539
  }
@@ -0,0 +1,17 @@
1
+ import { Path, Point2 } from '@js-draw/math';
2
+ import Viewport from '../../../Viewport';
3
+ import EditorImage from '../../../image/EditorImage';
4
+ import AbstractComponent from '../../../components/AbstractComponent';
5
+ import SelectionBuilder from './SelectionBuilder';
6
+ /**
7
+ * Creates lasso selections.
8
+ */
9
+ export default class LassoSelectionBuilder extends SelectionBuilder {
10
+ private viewport;
11
+ private boundaryPoints;
12
+ private lastPoint;
13
+ constructor(startPoint: Point2, viewport: Viewport);
14
+ onPointerMove(canvasPoint: Point2): void;
15
+ previewPath(): Path;
16
+ resolveInternal(image: EditorImage): AbstractComponent[];
17
+ }
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const math_1 = require("@js-draw/math");
7
+ const math_2 = require("@js-draw/math");
8
+ const SelectionBuilder_1 = __importDefault(require("./SelectionBuilder"));
9
+ /**
10
+ * Creates lasso selections.
11
+ */
12
+ class LassoSelectionBuilder extends SelectionBuilder_1.default {
13
+ constructor(startPoint, viewport) {
14
+ super();
15
+ this.viewport = viewport;
16
+ this.boundaryPoints = [];
17
+ this.boundaryPoints.push(startPoint);
18
+ this.lastPoint = startPoint;
19
+ }
20
+ onPointerMove(canvasPoint) {
21
+ const lastBoundaryPoint = this.boundaryPoints[this.boundaryPoints.length - 1];
22
+ const minBoundaryDist = this.viewport.getSizeOfPixelOnCanvas() * 8;
23
+ if (lastBoundaryPoint.distanceTo(canvasPoint) >= minBoundaryDist) {
24
+ this.boundaryPoints.push(canvasPoint);
25
+ }
26
+ this.lastPoint = canvasPoint;
27
+ }
28
+ previewPath() {
29
+ const pathCommands = this.boundaryPoints.map((point) => {
30
+ return { kind: math_2.PathCommandType.LineTo, point };
31
+ });
32
+ pathCommands.push({
33
+ kind: math_2.PathCommandType.LineTo,
34
+ point: this.lastPoint,
35
+ });
36
+ return new math_1.Path(this.boundaryPoints[0], pathCommands).asClosed();
37
+ }
38
+ resolveInternal(image) {
39
+ const path = this.previewPath();
40
+ const lines = path.polylineApproximation();
41
+ const candidates = image.getElementsIntersectingRegion(path.bbox);
42
+ const componentIsInSelection = (component) => {
43
+ if (path.closedContainsRect(component.getExactBBox())) {
44
+ return true;
45
+ }
46
+ let hasKeyPoint = false;
47
+ for (const point of component.keyPoints()) {
48
+ if (path.closedContainsPoint(point)) {
49
+ hasKeyPoint = true;
50
+ break;
51
+ }
52
+ }
53
+ if (!hasKeyPoint) {
54
+ return false;
55
+ }
56
+ // Only select if completely contained within the lasso
57
+ for (const line of lines) {
58
+ if (component.intersects(line)) {
59
+ return false;
60
+ }
61
+ }
62
+ return true;
63
+ };
64
+ return candidates.filter(componentIsInSelection);
65
+ }
66
+ }
67
+ exports.default = LassoSelectionBuilder;
@@ -0,0 +1,13 @@
1
+ import { Path, Point2 } from '@js-draw/math';
2
+ import EditorImage from '../../../image/EditorImage';
3
+ import SelectionBuilder from './SelectionBuilder';
4
+ /**
5
+ * Creates rectangle selections
6
+ */
7
+ export default class RectSelectionBuilder extends SelectionBuilder {
8
+ private rect;
9
+ constructor(startPoint: Point2);
10
+ onPointerMove(canvasPoint: Point2): void;
11
+ previewPath(): Path;
12
+ resolveInternal(image: EditorImage): import("../../../lib").AbstractComponent[];
13
+ }
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const math_1 = require("@js-draw/math");
7
+ const SelectionBuilder_1 = __importDefault(require("./SelectionBuilder"));
8
+ /**
9
+ * Creates rectangle selections
10
+ */
11
+ class RectSelectionBuilder extends SelectionBuilder_1.default {
12
+ constructor(startPoint) {
13
+ super();
14
+ this.rect = math_1.Rect2.fromCorners(startPoint, startPoint);
15
+ }
16
+ onPointerMove(canvasPoint) {
17
+ this.rect = this.rect.grownToPoint(canvasPoint);
18
+ }
19
+ previewPath() {
20
+ return math_1.Path.fromRect(this.rect);
21
+ }
22
+ resolveInternal(image) {
23
+ return image.getElementsIntersectingRegion(this.rect).filter((element) => {
24
+ // Filter out the case where the selection rectangle is completely contained
25
+ // within the element (and does not intersect it).
26
+ // This is useful, for example, if a very large stroke is used as the background
27
+ // for another drawing. This prevents the very large stroke from being selected
28
+ // unless the selection touches one of its edges.
29
+ return element.intersectsRect(this.rect);
30
+ });
31
+ }
32
+ }
33
+ exports.default = RectSelectionBuilder;
@@ -0,0 +1,15 @@
1
+ import { Color4, Path, Point2 } from '@js-draw/math';
2
+ import AbstractRenderer from '../../../rendering/renderers/AbstractRenderer';
3
+ import EditorImage from '../../../image/EditorImage';
4
+ import AbstractComponent from '../../../components/AbstractComponent';
5
+ import Viewport from '../../../Viewport';
6
+ export default abstract class SelectionBuilder {
7
+ abstract onPointerMove(canvasPoint: Point2): void;
8
+ abstract previewPath(): Path;
9
+ /** Returns the components currently in the selection bounds. Used by {@link resolve}. */
10
+ protected abstract resolveInternal(image: EditorImage): AbstractComponent[];
11
+ /** Renders a preview of the selection bounds */
12
+ render(renderer: AbstractRenderer, color: Color4): void;
13
+ /** Converts the selection preview into a set of selected elements */
14
+ resolve(image: EditorImage, viewport: Viewport): AbstractComponent[];
15
+ }
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const RenderablePathSpec_1 = require("../../../rendering/RenderablePathSpec");
4
+ class SelectionBuilder {
5
+ /** Renders a preview of the selection bounds */
6
+ render(renderer, color) {
7
+ renderer.drawPath((0, RenderablePathSpec_1.pathToRenderable)(this.previewPath(), { fill: color }));
8
+ }
9
+ /** Converts the selection preview into a set of selected elements */
10
+ resolve(image, viewport) {
11
+ const path = this.previewPath();
12
+ const filterComponents = (components) => {
13
+ return components.filter((component) => {
14
+ return component.isSelectable();
15
+ });
16
+ };
17
+ let components;
18
+ // If the bounding box is very small, search for items **near** the bounding box,
19
+ // rather than in the bounding box.
20
+ const clickSize = viewport.getSizeOfPixelOnCanvas() * 3;
21
+ const isClick = path.bbox.maxDimension <= clickSize;
22
+ if (isClick) {
23
+ const searchRegionSize = viewport.visibleRect.maxDimension / 200;
24
+ const minSizeBox = path.bbox.grownBy(searchRegionSize);
25
+ components = image.getElementsIntersectingRegion(minSizeBox).filter((component) => {
26
+ return minSizeBox.containsRect(component.getBBox()) || component.intersectsRect(minSizeBox);
27
+ });
28
+ components = filterComponents(components);
29
+ if (components.length > 1) {
30
+ components = [components[0]];
31
+ }
32
+ }
33
+ else {
34
+ components = filterComponents(this.resolveInternal(image));
35
+ }
36
+ return components;
37
+ }
38
+ }
39
+ exports.default = SelectionBuilder;
@@ -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
  }