js-draw 1.27.1 → 1.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/README.md +1 -1
  2. package/build-config.json +2 -1
  3. package/dist/Editor.css +1 -1
  4. package/dist/bundle.js +28 -28
  5. package/dist/bundledStyles.js +1 -1
  6. package/dist/cjs/Editor.d.ts +7 -2
  7. package/dist/cjs/Editor.js +11 -5
  8. package/dist/cjs/SVGLoader/SVGLoader.d.ts +21 -0
  9. package/dist/cjs/SVGLoader/SVGLoader.js +74 -47
  10. package/dist/cjs/SVGLoader/SVGLoader.plugins.test.d.ts +1 -0
  11. package/dist/cjs/Viewport.js +2 -32
  12. package/dist/cjs/commands/Duplicate.d.ts +7 -4
  13. package/dist/cjs/commands/Duplicate.js +48 -7
  14. package/dist/cjs/commands/Duplicate.test.d.ts +1 -0
  15. package/dist/cjs/commands/Erase.d.ts +1 -1
  16. package/dist/cjs/commands/Erase.js +2 -2
  17. package/dist/cjs/commands/localization.d.ts +2 -2
  18. package/dist/cjs/commands/localization.js +2 -2
  19. package/dist/cjs/components/AbstractComponent.d.ts +7 -0
  20. package/dist/cjs/components/AbstractComponent.js +16 -2
  21. package/dist/cjs/components/Stroke.d.ts +21 -1
  22. package/dist/cjs/components/Stroke.js +29 -0
  23. package/dist/cjs/components/TextComponent.d.ts +2 -2
  24. package/dist/cjs/components/TextComponent.js +2 -2
  25. package/dist/cjs/components/builders/PolylineBuilder.js +1 -1
  26. package/dist/cjs/image/EditorImage.d.ts +17 -9
  27. package/dist/cjs/image/EditorImage.js +33 -17
  28. package/dist/cjs/lib.d.ts +1 -1
  29. package/dist/cjs/localizations/de.js +2 -2
  30. package/dist/cjs/rendering/RenderingStyle.d.ts +7 -6
  31. package/dist/cjs/rendering/lib.d.ts +1 -1
  32. package/dist/cjs/rendering/renderers/AbstractRenderer.js +4 -0
  33. package/dist/cjs/rendering/renderers/CanvasRenderer.d.ts +9 -0
  34. package/dist/cjs/rendering/renderers/CanvasRenderer.js +14 -0
  35. package/dist/cjs/rendering/renderers/SVGRenderer.d.ts +18 -0
  36. package/dist/cjs/rendering/renderers/SVGRenderer.js +21 -1
  37. package/dist/cjs/toolbar/AbstractToolbar.d.ts +2 -2
  38. package/dist/cjs/toolbar/AbstractToolbar.js +2 -3
  39. package/dist/cjs/toolbar/DropdownToolbar.d.ts +1 -1
  40. package/dist/cjs/toolbar/DropdownToolbar.js +2 -3
  41. package/dist/cjs/toolbar/DropdownToolbar.test.d.ts +1 -0
  42. package/dist/cjs/toolbar/utils/HelpDisplay.js +6 -4
  43. package/dist/cjs/toolbar/utils/localization.d.ts +1 -0
  44. package/dist/cjs/toolbar/utils/localization.js +1 -0
  45. package/dist/cjs/toolbar/widgets/DocumentPropertiesWidget.js +1 -1
  46. package/dist/cjs/toolbar/widgets/InsertImageWidget/InsertImageWidget.js +1 -1
  47. package/dist/cjs/toolbar/widgets/components/makeGridSelector.js +1 -1
  48. package/dist/cjs/tools/Eraser.js +3 -3
  49. package/dist/cjs/tools/FindTool.js +1 -1
  50. package/dist/cjs/tools/PasteHandler.js +4 -1
  51. package/dist/cjs/tools/Pen.js +1 -1
  52. package/dist/cjs/tools/SelectionTool/SelectAllShortcutHandler.js +1 -1
  53. package/dist/cjs/tools/SelectionTool/Selection.js +23 -10
  54. package/dist/cjs/tools/SelectionTool/SelectionBuilders/LassoSelectionBuilder.js +1 -1
  55. package/dist/cjs/tools/SelectionTool/SelectionBuilders/RectSelectionBuilder.js +1 -1
  56. package/dist/cjs/tools/SelectionTool/SelectionBuilders/SelectionBuilder.js +1 -1
  57. package/dist/cjs/tools/SelectionTool/SelectionTool.js +3 -2
  58. package/dist/cjs/tools/SoundUITool.js +1 -1
  59. package/dist/cjs/tools/TextTool.js +2 -2
  60. package/dist/cjs/util/assertions.d.ts +6 -0
  61. package/dist/cjs/util/assertions.js +18 -0
  62. package/dist/cjs/util/describeTransformation.d.ts +12 -0
  63. package/dist/cjs/util/describeTransformation.js +44 -0
  64. package/dist/cjs/version.js +2 -1
  65. package/dist/mjs/Editor.d.ts +7 -2
  66. package/dist/mjs/Editor.mjs +11 -5
  67. package/dist/mjs/SVGLoader/SVGLoader.d.ts +21 -0
  68. package/dist/mjs/SVGLoader/SVGLoader.mjs +74 -47
  69. package/dist/mjs/SVGLoader/SVGLoader.plugins.test.d.ts +1 -0
  70. package/dist/mjs/Viewport.mjs +2 -32
  71. package/dist/mjs/commands/Duplicate.d.ts +7 -4
  72. package/dist/mjs/commands/Duplicate.mjs +48 -7
  73. package/dist/mjs/commands/Duplicate.test.d.ts +1 -0
  74. package/dist/mjs/commands/Erase.d.ts +1 -1
  75. package/dist/mjs/commands/Erase.mjs +2 -2
  76. package/dist/mjs/commands/localization.d.ts +2 -2
  77. package/dist/mjs/commands/localization.mjs +2 -2
  78. package/dist/mjs/components/AbstractComponent.d.ts +7 -0
  79. package/dist/mjs/components/AbstractComponent.mjs +17 -3
  80. package/dist/mjs/components/Stroke.d.ts +21 -1
  81. package/dist/mjs/components/Stroke.mjs +31 -2
  82. package/dist/mjs/components/TextComponent.d.ts +2 -2
  83. package/dist/mjs/components/TextComponent.mjs +2 -2
  84. package/dist/mjs/components/builders/PolylineBuilder.mjs +1 -1
  85. package/dist/mjs/image/EditorImage.d.ts +17 -9
  86. package/dist/mjs/image/EditorImage.mjs +33 -17
  87. package/dist/mjs/lib.d.ts +1 -1
  88. package/dist/mjs/localizations/de.mjs +2 -2
  89. package/dist/mjs/rendering/RenderingStyle.d.ts +7 -6
  90. package/dist/mjs/rendering/lib.d.ts +1 -1
  91. package/dist/mjs/rendering/renderers/AbstractRenderer.mjs +4 -0
  92. package/dist/mjs/rendering/renderers/CanvasRenderer.d.ts +9 -0
  93. package/dist/mjs/rendering/renderers/CanvasRenderer.mjs +14 -0
  94. package/dist/mjs/rendering/renderers/SVGRenderer.d.ts +18 -0
  95. package/dist/mjs/rendering/renderers/SVGRenderer.mjs +21 -1
  96. package/dist/mjs/toolbar/AbstractToolbar.d.ts +2 -2
  97. package/dist/mjs/toolbar/AbstractToolbar.mjs +2 -3
  98. package/dist/mjs/toolbar/DropdownToolbar.d.ts +1 -1
  99. package/dist/mjs/toolbar/DropdownToolbar.mjs +2 -3
  100. package/dist/mjs/toolbar/DropdownToolbar.test.d.ts +1 -0
  101. package/dist/mjs/toolbar/utils/HelpDisplay.mjs +6 -4
  102. package/dist/mjs/toolbar/utils/localization.d.ts +1 -0
  103. package/dist/mjs/toolbar/utils/localization.mjs +1 -0
  104. package/dist/mjs/toolbar/widgets/DocumentPropertiesWidget.mjs +1 -1
  105. package/dist/mjs/toolbar/widgets/InsertImageWidget/InsertImageWidget.mjs +1 -1
  106. package/dist/mjs/toolbar/widgets/components/makeGridSelector.mjs +1 -1
  107. package/dist/mjs/tools/Eraser.mjs +3 -3
  108. package/dist/mjs/tools/FindTool.mjs +1 -1
  109. package/dist/mjs/tools/PasteHandler.mjs +4 -1
  110. package/dist/mjs/tools/Pen.mjs +1 -1
  111. package/dist/mjs/tools/SelectionTool/SelectAllShortcutHandler.mjs +1 -1
  112. package/dist/mjs/tools/SelectionTool/Selection.mjs +23 -10
  113. package/dist/mjs/tools/SelectionTool/SelectionBuilders/LassoSelectionBuilder.mjs +1 -1
  114. package/dist/mjs/tools/SelectionTool/SelectionBuilders/RectSelectionBuilder.mjs +1 -1
  115. package/dist/mjs/tools/SelectionTool/SelectionBuilders/SelectionBuilder.mjs +1 -1
  116. package/dist/mjs/tools/SelectionTool/SelectionTool.mjs +3 -2
  117. package/dist/mjs/tools/SoundUITool.mjs +1 -1
  118. package/dist/mjs/tools/TextTool.mjs +2 -2
  119. package/dist/mjs/util/assertions.d.ts +6 -0
  120. package/dist/mjs/util/assertions.mjs +16 -0
  121. package/dist/mjs/util/describeTransformation.d.ts +12 -0
  122. package/dist/mjs/util/describeTransformation.mjs +42 -0
  123. package/dist/mjs/version.mjs +2 -1
  124. package/package.json +4 -4
  125. package/src/toolbar/utils/HelpDisplay.scss +7 -1
@@ -213,6 +213,20 @@ class CanvasRenderer extends AbstractRenderer_1.default {
213
213
  this.ignoringObject = false;
214
214
  }
215
215
  }
216
+ /**
217
+ * Returns a reference to the underlying `CanvasRenderingContext2D`.
218
+ * This can be used to render custom content not supported by {@link AbstractRenderer}.
219
+ * However, such content won't support {@link SVGRenderer} or {@link TextOnlyRenderer}
220
+ * by default.
221
+ *
222
+ * Use with caution.
223
+ */
224
+ drawWithRawRenderingContext(callback) {
225
+ this.ctx.save();
226
+ this.transformBy(this.getCanvasToScreenTransform());
227
+ callback(this.ctx);
228
+ this.ctx.restore();
229
+ }
216
230
  // @internal
217
231
  drawPoints(...points) {
218
232
  const pointRadius = 10;
@@ -15,6 +15,9 @@ type FromViewportOptions = {
15
15
  */
16
16
  useViewBoxForPositioning?: boolean;
17
17
  };
18
+ type DrawWithSVGParentContext = {
19
+ sanitize: boolean;
20
+ };
18
21
  /**
19
22
  * Renders onto an `SVGElement`.
20
23
  *
@@ -57,7 +60,22 @@ export default class SVGRenderer extends AbstractRenderer {
57
60
  protected traceCubicBezierCurve(_controlPoint1: Point2, _controlPoint2: Point2, _endPoint: Point2): void;
58
61
  protected traceQuadraticBezierCurve(_controlPoint: Point2, _endPoint: Point2): void;
59
62
  drawPoints(...points: Point2[]): void;
63
+ /**
64
+ * Adds a **copy** of the given element directly to the container
65
+ * SVG element, **without applying transforms**.
66
+ *
67
+ * If `sanitize` is enabled, this does nothing.
68
+ */
60
69
  drawSVGElem(elem: SVGElement): void;
70
+ /**
71
+ * Allows rendering directly to the underlying SVG element. Rendered
72
+ * content is added to a `<g>` element that's passed as `parent` to `callback`.
73
+ *
74
+ * **Note**: Unlike {@link drawSVGElem}, this method can be used even if `sanitize` is `true`.
75
+ * In this case, it's the responsibility of `callback` to ensure that everything added
76
+ * to `parent` is safe to render.
77
+ */
78
+ drawWithSVGParent(callback: (parent: SVGGElement, context: DrawWithSVGParentContext) => void): void;
61
79
  isTooSmallToRender(_rect: Rect2): boolean;
62
80
  /**
63
81
  * Creates a new SVG element and `SVGRenerer` with `width`, `height`, `viewBox`,
@@ -347,7 +347,12 @@ class SVGRenderer extends AbstractRenderer_1.default {
347
347
  this.elem.appendChild(elem);
348
348
  });
349
349
  }
350
- // Renders a **copy** of the given element.
350
+ /**
351
+ * Adds a **copy** of the given element directly to the container
352
+ * SVG element, **without applying transforms**.
353
+ *
354
+ * If `sanitize` is enabled, this does nothing.
355
+ */
351
356
  drawSVGElem(elem) {
352
357
  if (this.sanitize) {
353
358
  return;
@@ -361,6 +366,21 @@ class SVGRenderer extends AbstractRenderer_1.default {
361
366
  this.elem.appendChild(elemToDraw);
362
367
  this.objectElems?.push(elemToDraw);
363
368
  }
369
+ /**
370
+ * Allows rendering directly to the underlying SVG element. Rendered
371
+ * content is added to a `<g>` element that's passed as `parent` to `callback`.
372
+ *
373
+ * **Note**: Unlike {@link drawSVGElem}, this method can be used even if `sanitize` is `true`.
374
+ * In this case, it's the responsibility of `callback` to ensure that everything added
375
+ * to `parent` is safe to render.
376
+ */
377
+ drawWithSVGParent(callback) {
378
+ const parent = document.createElementNS(svgNameSpace, 'g');
379
+ this.transformFrom(math_1.Mat33.identity, parent, true);
380
+ callback(parent, { sanitize: this.sanitize });
381
+ this.elem.appendChild(parent);
382
+ this.objectElems?.push(parent);
383
+ }
364
384
  isTooSmallToRender(_rect) {
365
385
  return false;
366
386
  }
@@ -21,10 +21,10 @@ export type ToolbarActionButtonOptions = {
21
21
  export default abstract class AbstractToolbar {
22
22
  #private;
23
23
  protected editor: Editor;
24
- protected localizationTable: ToolbarLocalization;
25
24
  private static colorisStarted;
25
+ protected localizationTable: ToolbarLocalization;
26
26
  /** @internal */
27
- constructor(editor: Editor, localizationTable?: ToolbarLocalization);
27
+ constructor(editor: Editor, localizationTable: ToolbarLocalization);
28
28
  private closeColorPickerOverlay;
29
29
  private setupCloseColorPickerOverlay;
30
30
  setupColorPickers(): void;
@@ -17,7 +17,6 @@ var _AbstractToolbar_listeners, _AbstractToolbar_widgetsById, _AbstractToolbar_w
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  const types_1 = require("../types");
19
19
  const coloris_1 = require("@melloware/coloris");
20
- const localization_1 = require("./localization");
21
20
  const SelectionTool_1 = __importDefault(require("../tools/SelectionTool/SelectionTool"));
22
21
  const PanZoom_1 = __importDefault(require("../tools/PanZoom"));
23
22
  const TextTool_1 = __importDefault(require("../tools/TextTool"));
@@ -44,14 +43,14 @@ const assertions_1 = require("../util/assertions");
44
43
  */
45
44
  class AbstractToolbar {
46
45
  /** @internal */
47
- constructor(editor, localizationTable = localization_1.defaultToolbarLocalization) {
46
+ constructor(editor, localizationTable) {
48
47
  this.editor = editor;
49
- this.localizationTable = localizationTable;
50
48
  _AbstractToolbar_listeners.set(this, []);
51
49
  _AbstractToolbar_widgetsById.set(this, {});
52
50
  _AbstractToolbar_widgetList.set(this, []);
53
51
  _AbstractToolbar_updateColoris.set(this, null);
54
52
  this.closeColorPickerOverlay = null;
53
+ this.localizationTable = localizationTable ?? editor.localization;
55
54
  if (!AbstractToolbar.colorisStarted) {
56
55
  (0, coloris_1.init)();
57
56
  AbstractToolbar.colorisStarted = true;
@@ -35,7 +35,7 @@ export default class DropdownToolbar extends AbstractToolbar {
35
35
  private widgetOrderCounter;
36
36
  private overflowWidget;
37
37
  /** @internal */
38
- constructor(editor: Editor, parent: HTMLElement, localizationTable?: ToolbarLocalization);
38
+ constructor(editor: Editor, parent: HTMLElement, localizationTable: ToolbarLocalization);
39
39
  private reLayoutQueued;
40
40
  private queueReLayout;
41
41
  private reLayout;
@@ -4,7 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.makeDropdownToolbar = void 0;
7
- const localization_1 = require("./localization");
8
7
  const OverflowWidget_1 = __importDefault(require("./widgets/OverflowWidget"));
9
8
  const AbstractToolbar_1 = __importDefault(require("./AbstractToolbar"));
10
9
  const constants_1 = require("./constants");
@@ -35,12 +34,12 @@ const constants_1 = require("./constants");
35
34
  * - {@link AbstractToolbar.addExitButton}
36
35
  */
37
36
  const makeDropdownToolbar = (editor) => {
38
- return new DropdownToolbar(editor, editor.getRootElement());
37
+ return new DropdownToolbar(editor, editor.getRootElement(), editor.localization);
39
38
  };
40
39
  exports.makeDropdownToolbar = makeDropdownToolbar;
41
40
  class DropdownToolbar extends AbstractToolbar_1.default {
42
41
  /** @internal */
43
- constructor(editor, parent, localizationTable = localization_1.defaultToolbarLocalization) {
42
+ constructor(editor, parent, localizationTable) {
44
43
  super(editor, localizationTable);
45
44
  // Flex-order of the next widget to be added.
46
45
  this.widgetOrderCounter = 0;
@@ -0,0 +1 @@
1
+ export {};
@@ -139,6 +139,8 @@ const createHelpPage = (helpItems, onItemClick, onBackgroundClick, context) => {
139
139
  clonedElement.style.margin = '0';
140
140
  const clonedElementContainer = document.createElement('div');
141
141
  clonedElementContainer.classList.add('cloned-element-container');
142
+ clonedElementContainer.role = 'group';
143
+ clonedElementContainer.ariaLabel = context.localization.helpControlsAccessibilityLabel;
142
144
  clonedElementContainer.style.position = 'absolute';
143
145
  clonedElementContainer.style.left = `${targetBBox.topLeft.x}px`;
144
146
  clonedElementContainer.style.top = `${targetBBox.topLeft.y}px`;
@@ -157,11 +159,11 @@ const createHelpPage = (helpItems, onItemClick, onBackgroundClick, context) => {
157
159
  };
158
160
  const onItemChange = () => {
159
161
  const helpTextElement = document.createElement('div');
160
- helpTextElement.innerText = currentItem?.helpText ?? '';
162
+ helpTextElement.textContent = currentItem?.helpText ?? '';
161
163
  // For tests
162
164
  helpTextElement.classList.add('current-item-help');
163
165
  const navigationHelpElement = document.createElement('div');
164
- navigationHelpElement.innerText = context.localization.helpScreenNavigationHelp;
166
+ navigationHelpElement.textContent = context.localization.helpScreenNavigationHelp;
165
167
  navigationHelpElement.classList.add('navigation-help');
166
168
  textLabel.replaceChildren(helpTextElement, ...(currentItemIndex === 0 ? [navigationHelpElement] : []));
167
169
  updateClonedElementStates();
@@ -278,8 +280,8 @@ class HelpDisplay {
278
280
  navigationButtonContainer.classList.add('navigation-buttons');
279
281
  const nextButton = document.createElement('button');
280
282
  const previousButton = document.createElement('button');
281
- nextButton.innerText = this.context.localization.next;
282
- previousButton.innerText = this.context.localization.previous;
283
+ nextButton.textContent = this.context.localization.next;
284
+ previousButton.textContent = this.context.localization.previous;
283
285
  nextButton.classList.add('next');
284
286
  previousButton.classList.add('previous');
285
287
  const updateButtonVisibility = () => {
@@ -1,6 +1,7 @@
1
1
  export interface ToolbarUtilsLocalization {
2
2
  help: string;
3
3
  helpScreenNavigationHelp: string;
4
+ helpControlsAccessibilityLabel: string;
4
5
  helpHidden: string;
5
6
  next: string;
6
7
  previous: string;
@@ -8,4 +8,5 @@ exports.defaultToolbarUtilsLocalization = {
8
8
  previous: 'Previous',
9
9
  close: 'Close',
10
10
  helpScreenNavigationHelp: 'Click on a control for more information.',
11
+ helpControlsAccessibilityLabel: 'Controls: Activate a control to show help.',
11
12
  };
@@ -100,7 +100,7 @@ class DocumentPropertiesWidget extends BaseWidget_1.default {
100
100
  setBackgroundType(backgroundType) {
101
101
  const prevBackgroundColor = this.editor.estimateBackgroundColor();
102
102
  const newBackground = new BackgroundComponent_1.default(backgroundType, prevBackgroundColor);
103
- const addBackgroundCommand = this.editor.image.addElement(newBackground);
103
+ const addBackgroundCommand = this.editor.image.addComponent(newBackground);
104
104
  return (0, uniteCommands_1.default)([this.removeBackgroundComponents(), addBackgroundCommand]);
105
105
  }
106
106
  /** Returns the type of the topmost background component */
@@ -275,7 +275,7 @@ class InsertImageWidget extends BaseWidget_1.default {
275
275
  const widthAdjustTransform = math_1.Mat33.scaling2D(originalWidth / newWidth);
276
276
  const commands = [];
277
277
  for (const component of newComponents) {
278
- commands.push(EditorImage_1.default.addElement(component), component.transformBy(originalTransform.rightMul(widthAdjustTransform)), component.setZIndex(editingImage.getZIndex()));
278
+ commands.push(EditorImage_1.default.addComponent(component), component.transformBy(originalTransform.rightMul(widthAdjustTransform)), component.setZIndex(editingImage.getZIndex()));
279
279
  }
280
280
  this.editor.dispatch((0, uniteCommands_1.default)([...commands, eraseCommand]));
281
281
  selectionTools[0]?.setSelection(newComponents);
@@ -22,7 +22,7 @@ labelText, defaultId, choices) => {
22
22
  outerContainer.classList.add(`${constants_1.toolbarCSSPrefix}grid-selector`);
23
23
  const selectedValue = ReactiveValue_1.MutableReactiveValue.fromInitialValue(defaultId);
24
24
  const menuContainer = document.createElement('div');
25
- menuContainer.setAttribute('role', 'menu');
25
+ menuContainer.role = 'group';
26
26
  menuContainer.id = `${constants_1.toolbarCSSPrefix}-grid-select-id-${idCounter++}`;
27
27
  (0, stopPropagationOfScrollingWheelEvents_1.default)(menuContainer);
28
28
  const label = document.createElement('label');
@@ -145,7 +145,7 @@ class Eraser extends BaseTool_1.default {
145
145
  const line = new math_1.LineSegment2(this.lastPoint, currentPoint);
146
146
  const region = math_1.Rect2.union(line.bbox, eraserRect);
147
147
  const intersectingElems = this.editor.image
148
- .getElementsIntersectingRegion(region)
148
+ .getComponentsIntersecting(region)
149
149
  .filter((component) => {
150
150
  return component.intersects(line) || component.intersectsRect(eraserRect);
151
151
  });
@@ -184,7 +184,7 @@ class Eraser extends BaseTool_1.default {
184
184
  toAdd.push(...targetElem.withRegionErased(erasePath, this.editor.viewport));
185
185
  }
186
186
  const eraseCommand = new Erase_1.default(toErase);
187
- const newAddCommands = toAdd.map((elem) => EditorImage_1.default.addElement(elem));
187
+ const newAddCommands = toAdd.map((elem) => EditorImage_1.default.addComponent(elem));
188
188
  eraseCommand.apply(this.editor);
189
189
  newAddCommands.forEach((command) => command.apply(this.editor));
190
190
  const finalToErase = [];
@@ -240,7 +240,7 @@ class Eraser extends BaseTool_1.default {
240
240
  this.toRemove = this.toRemove.filter((other) => other !== item);
241
241
  }
242
242
  }
243
- commands.push(...[...this.toAdd].map((a) => EditorImage_1.default.addElement(a)));
243
+ commands.push(...[...this.toAdd].map((a) => EditorImage_1.default.addComponent(a)));
244
244
  this.addCommands = [];
245
245
  }
246
246
  if (this.eraseCommands.length > 0) {
@@ -27,7 +27,7 @@ class FindTool extends BaseTool_1.default {
27
27
  }
28
28
  getMatches(searchFor) {
29
29
  const lowerSearchFor = searchFor.toLocaleLowerCase();
30
- const matchingComponents = this.editor.image.getAllElements().filter((component) => {
30
+ const matchingComponents = this.editor.image.getAllComponents().filter((component) => {
31
31
  let text = '';
32
32
  if (component instanceof TextComponent_1.default) {
33
33
  text = component.getText();
@@ -76,7 +76,10 @@ class PasteHandler extends BaseTool_1.default {
76
76
  async doSVGPaste(data) {
77
77
  this.editor.showLoadingWarning(0);
78
78
  try {
79
- const loader = SVGLoader_1.default.fromString(data, true);
79
+ const loader = SVGLoader_1.default.fromString(data, {
80
+ sanitize: true,
81
+ plugins: this.editor.getCurrentSettings().svg?.loaderPlugins ?? [],
82
+ });
80
83
  const components = [];
81
84
  await loader.start((component) => {
82
85
  components.push(component);
@@ -251,7 +251,7 @@ class Pen extends BaseTool_1.default {
251
251
  this.editor.announceForAccessibility(this.editor.localization.autocorrectedTo(stroke.description(this.editor.localization)));
252
252
  }
253
253
  const canFlatten = true;
254
- const action = EditorImage_1.default.addElement(stroke, canFlatten);
254
+ const action = EditorImage_1.default.addComponent(stroke, canFlatten);
255
255
  this.editor.dispatch(action);
256
256
  }
257
257
  else {
@@ -22,7 +22,7 @@ class SelectAllShortcutHandler extends BaseTool_1.default {
22
22
  if (selectionTools.length > 0) {
23
23
  const selectionTool = selectionTools[0];
24
24
  selectionTool.setEnabled(true);
25
- selectionTool.setSelection(this.editor.image.getAllElements());
25
+ selectionTool.setSelection(this.editor.image.getAllComponents());
26
26
  return true;
27
27
  }
28
28
  }
@@ -53,6 +53,8 @@ const types_1 = require("./types");
53
53
  const EditorImage_1 = __importDefault(require("../../image/EditorImage"));
54
54
  const uniteCommands_1 = __importDefault(require("../../commands/uniteCommands"));
55
55
  const SelectionMenuShortcut_1 = __importDefault(require("./SelectionMenuShortcut"));
56
+ const assertions_1 = require("../../util/assertions");
57
+ const describeTransformation_1 = __importDefault(require("../../util/describeTransformation"));
56
58
  const updateChunkSize = 100;
57
59
  const maxPreviewElemCount = 500;
58
60
  // @internal
@@ -186,7 +188,7 @@ class Selection {
186
188
  return 0;
187
189
  }
188
190
  const selectedBottommostZIndex = this.selectedElems[0].getZIndex();
189
- const visibleObjects = this.editor.image.getElementsIntersectingRegion(this.region);
191
+ const visibleObjects = this.editor.image.getComponentsIntersecting(this.region);
190
192
  const topMostVisibleZIndex = visibleObjects[visibleObjects.length - 1]?.getZIndex() ?? selectedBottommostZIndex;
191
193
  const deltaZIndex = topMostVisibleZIndex + 1 - selectedBottommostZIndex;
192
194
  return deltaZIndex;
@@ -205,13 +207,13 @@ class Selection {
205
207
  // z-index of the just-transformed commands.
206
208
  if (this.selectedElems.length > 0) {
207
209
  const deltaZIndex = this.getDeltaZIndexToMoveSelectionToTop();
208
- transformPromise = this.editor.dispatch(new _a.ApplyTransformationCommand(this, selectedElems, fullTransform, deltaZIndex));
210
+ transformPromise = this.editor.dispatch(new _a.ApplyTransformationCommand(this, selectedElems, this.originalRegion.center, fullTransform, deltaZIndex));
209
211
  }
210
212
  return transformPromise;
211
213
  }
212
214
  /** Sends all selected elements to the bottom of the visible image. */
213
215
  sendToBack() {
214
- const visibleObjects = this.editor.image.getElementsIntersectingRegion(this.editor.viewport.visibleRect);
216
+ const visibleObjects = this.editor.image.getComponentsIntersecting(this.editor.viewport.visibleRect);
215
217
  // VisibleObjects and selectedElems should both be sorted by z-index
216
218
  const lowestVisibleZIndex = visibleObjects[0]?.getZIndex() ?? 0;
217
219
  const highestSelectedZIndex = this.selectedElems[this.selectedElems.length - 1]?.getZIndex() ?? 0;
@@ -338,7 +340,7 @@ class Selection {
338
340
  // If we're making things visible and the selected object wasn't previously
339
341
  // visible,
340
342
  else if (!parent && this.removedFromImage[elem.getId()]) {
341
- EditorImage_1.default.addElement(elem).apply(this.editor);
343
+ EditorImage_1.default.addComponent(elem).apply(this.editor);
342
344
  this.removedFromImage[elem.getId()] = false;
343
345
  delete this.removedFromImage[elem.getId()];
344
346
  }
@@ -476,14 +478,14 @@ class Selection {
476
478
  // Don't update the selection's focus when redoing/undoing
477
479
  const selectionToUpdate = null;
478
480
  const deltaZIndex = this.getDeltaZIndexToMoveSelectionToTop();
479
- tmpApplyCommand = new _a.ApplyTransformationCommand(selectionToUpdate, this.selectedElems, this.transform, deltaZIndex);
481
+ tmpApplyCommand = new _a.ApplyTransformationCommand(selectionToUpdate, this.selectedElems, this.region.center, this.transform, deltaZIndex);
480
482
  // Transform to ensure that the duplicates are in the correct location
481
483
  await tmpApplyCommand.apply(this.editor);
482
484
  // Show items again
483
485
  this.addRemoveSelectionFromImage(true);
484
486
  // With the transformation applied, create the duplicates
485
487
  command = (0, uniteCommands_1.default)(this.selectedElems.map((elem) => {
486
- return EditorImage_1.default.addElement(elem.clone());
488
+ return EditorImage_1.default.addComponent(elem.clone());
487
489
  }));
488
490
  // Move the selected objects back to the correct location.
489
491
  await tmpApplyCommand?.unapply(this.editor);
@@ -541,21 +543,31 @@ class Selection {
541
543
  _a = Selection;
542
544
  (() => {
543
545
  SerializableCommand_1.default.register('selection-tool-transform', (json, _editor) => {
546
+ const rawTransformArray = json.transform;
547
+ const rawCenterArray = json.selectionCenter ?? [0, 0];
548
+ const rawElementIds = json.elems ?? [];
549
+ (0, assertions_1.assertIsNumberArray)(rawTransformArray);
550
+ (0, assertions_1.assertIsNumberArray)(rawCenterArray);
551
+ (0, assertions_1.assertIsStringArray)(rawElementIds);
544
552
  // The selection box is lost when serializing/deserializing. No need to store box rotation
545
- const fullTransform = new math_1.Mat33(...json.transform);
546
- const elemIds = json.elems ?? [];
553
+ const fullTransform = new math_1.Mat33(...rawTransformArray);
554
+ const elemIds = rawElementIds;
547
555
  const deltaZIndex = parseInt(json.deltaZIndex ?? 0);
548
- return new _a.ApplyTransformationCommand(null, elemIds, fullTransform, deltaZIndex);
556
+ const center = math_1.Vec2.of(rawCenterArray[0] ?? 0, rawCenterArray[1] ?? 0);
557
+ return new _a.ApplyTransformationCommand(null, elemIds, center, fullTransform, deltaZIndex);
549
558
  });
550
559
  })();
551
560
  Selection.ApplyTransformationCommand = class extends SerializableCommand_1.default {
552
561
  constructor(selection,
553
562
  // If a `string[]`, selectedElems is a list of element IDs.
554
563
  selectedElems,
564
+ // Information used to describe the transformation
565
+ selectionCenter,
555
566
  // Full transformation used to transform elements.
556
567
  fullTransform, deltaZIndex) {
557
568
  super('selection-tool-transform');
558
569
  this.selection = selection;
570
+ this.selectionCenter = selectionCenter;
559
571
  this.fullTransform = fullTransform;
560
572
  this.deltaZIndex = deltaZIndex;
561
573
  const isIDList = (arr) => {
@@ -623,10 +635,11 @@ Selection.ApplyTransformationCommand = class extends SerializableCommand_1.defau
623
635
  elems: this.selectedElemIds,
624
636
  transform: this.fullTransform.toArray(),
625
637
  deltaZIndex: this.deltaZIndex,
638
+ selectionCenter: this.selectionCenter.asArray(),
626
639
  };
627
640
  }
628
641
  description(_editor, localizationTable) {
629
- return localizationTable.transformedElements(this.selectedElemIds.length);
642
+ return localizationTable.transformedElements(this.selectedElemIds.length, (0, describeTransformation_1.default)(this.selectionCenter, this.fullTransform, false, localizationTable));
630
643
  }
631
644
  };
632
645
  exports.default = Selection;
@@ -38,7 +38,7 @@ class LassoSelectionBuilder extends SelectionBuilder_1.default {
38
38
  resolveInternal(image) {
39
39
  const path = this.previewPath();
40
40
  const lines = path.polylineApproximation();
41
- const candidates = image.getElementsIntersectingRegion(path.bbox);
41
+ const candidates = image.getComponentsIntersecting(path.bbox);
42
42
  const componentIsInSelection = (component) => {
43
43
  if (path.closedContainsRect(component.getExactBBox())) {
44
44
  return true;
@@ -20,7 +20,7 @@ class RectSelectionBuilder extends SelectionBuilder_1.default {
20
20
  return math_1.Path.fromRect(this.rect);
21
21
  }
22
22
  resolveInternal(image) {
23
- return image.getElementsIntersectingRegion(this.rect).filter((element) => {
23
+ return image.getComponentsIntersecting(this.rect).filter((element) => {
24
24
  // Filter out the case where the selection rectangle is completely contained
25
25
  // within the element (and does not intersect it).
26
26
  // This is useful, for example, if a very large stroke is used as the background
@@ -22,7 +22,7 @@ class SelectionBuilder {
22
22
  if (isClick) {
23
23
  const searchRegionSize = viewport.visibleRect.maxDimension / 200;
24
24
  const minSizeBox = path.bbox.grownBy(searchRegionSize);
25
- components = image.getElementsIntersectingRegion(minSizeBox).filter((component) => {
25
+ components = image.getComponentsIntersecting(minSizeBox).filter((component) => {
26
26
  return minSizeBox.containsRect(component.getBBox()) || component.intersectsRect(minSizeBox);
27
27
  });
28
28
  components = filterComponents(components);
@@ -259,7 +259,7 @@ class SelectionTool extends BaseTool_1.default {
259
259
  return true;
260
260
  }
261
261
  else if (shortcucts.matchesShortcut(keybindings_1.selectAllKeyboardShortcut, event)) {
262
- this.setSelection(this.editor.image.getAllElements());
262
+ this.setSelection(this.editor.image.getAllComponents());
263
263
  return true;
264
264
  }
265
265
  else if (event.ctrlKey) {
@@ -462,7 +462,8 @@ class SelectionTool extends BaseTool_1.default {
462
462
  this.handleOverlay.style.display = enabled ? 'block' : 'none';
463
463
  if (enabled) {
464
464
  this.handleOverlay.tabIndex = 0;
465
- this.handleOverlay.setAttribute('aria-label', this.editor.localization.selectionToolKeyboardShortcuts);
465
+ this.handleOverlay.role = 'group';
466
+ this.handleOverlay.ariaLabel = this.editor.localization.selectionToolKeyboardShortcuts;
466
467
  }
467
468
  else {
468
469
  this.handleOverlay.tabIndex = -1;
@@ -156,7 +156,7 @@ class SoundUITool extends BaseTool_1.default {
156
156
  this.soundFeedback?.setColor(this.editor.display.getColorAt(current.screenPos) ?? math_1.Color4.black);
157
157
  const pointerMotionLine = new math_1.LineSegment2(this.lastPointerPos, current.canvasPos);
158
158
  const collisions = this.editor.image
159
- .getElementsIntersectingRegion(pointerMotionLine.bbox)
159
+ .getComponentsIntersecting(pointerMotionLine.bbox)
160
160
  .filter((component) => component.intersects(pointerMotionLine));
161
161
  this.lastPointerPos = current.canvasPos;
162
162
  if (collisions.length > 0) {
@@ -103,7 +103,7 @@ class TextTool extends BaseTool_1.default {
103
103
  const scrollCorrectionCanvas = this.editor.viewport.screenToCanvasTransform.transformVec3(scrollCorrectionScreen);
104
104
  const scrollTransform = math_1.Mat33.translation(scrollCorrectionCanvas);
105
105
  const textComponent = TextComponent_1.default.fromLines(content.split('\n'), scrollTransform.rightMul(this.contentTransform.get()), this.textStyle);
106
- const action = EditorImage_1.default.addElement(textComponent);
106
+ const action = EditorImage_1.default.addComponent(textComponent);
107
107
  if (this.removeExistingCommand) {
108
108
  // Unapply so that `removeExistingCommand` can be added to the undo stack.
109
109
  this.removeExistingCommand.unapply(this.editor);
@@ -213,7 +213,7 @@ class TextTool extends BaseTool_1.default {
213
213
  const canvasPos = current.canvasPos;
214
214
  const halfTestRegionSize = math_1.Vec2.of(4, 4).times(this.editor.viewport.getSizeOfPixelOnCanvas());
215
215
  const testRegion = math_1.Rect2.fromCorners(canvasPos.minus(halfTestRegionSize), canvasPos.plus(halfTestRegionSize));
216
- const targetNodes = this.editor.image.getElementsIntersectingRegion(testRegion);
216
+ const targetNodes = this.editor.image.getComponentsIntersecting(testRegion);
217
217
  let targetTextNodes = targetNodes.filter((node) => node instanceof TextComponent_1.default);
218
218
  // Don't try to edit text nodes that contain the viewport (this allows us
219
219
  // to zoom in on text nodes and add text on top of them.)
@@ -15,11 +15,17 @@ export declare function assertUnreachable(key: never): never;
15
15
  * ```
16
16
  */
17
17
  export declare function assertIsNumber(value: unknown, allowNaN?: boolean): asserts value is number;
18
+ /** Throws an `Error` if the given `value` is not a `string`. */
19
+ export declare function assertIsString(value: unknown): asserts value is string;
18
20
  export declare function assertIsArray(values: unknown): asserts values is unknown[];
19
21
  /**
20
22
  * Throws if any of `values` is not of type number.
21
23
  */
22
24
  export declare function assertIsNumberArray(values: unknown, allowNaN?: boolean): asserts values is number[];
25
+ /**
26
+ * Throws if any of `values` is not of type `string`.
27
+ */
28
+ export declare function assertIsStringArray(values: unknown): asserts values is string[];
23
29
  /**
24
30
  * Throws an exception if `typeof value` is not a boolean.
25
31
  */
@@ -4,8 +4,10 @@
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.assertUnreachable = assertUnreachable;
6
6
  exports.assertIsNumber = assertIsNumber;
7
+ exports.assertIsString = assertIsString;
7
8
  exports.assertIsArray = assertIsArray;
8
9
  exports.assertIsNumberArray = assertIsNumberArray;
10
+ exports.assertIsStringArray = assertIsStringArray;
9
11
  exports.assertIsBoolean = assertIsBoolean;
10
12
  exports.assertTruthy = assertTruthy;
11
13
  exports.assertIsObject = assertIsObject;
@@ -33,6 +35,12 @@ function assertIsNumber(value, allowNaN = false) {
33
35
  throw new Error('Given value is not a number');
34
36
  }
35
37
  }
38
+ /** Throws an `Error` if the given `value` is not a `string`. */
39
+ function assertIsString(value) {
40
+ if (typeof value !== 'string') {
41
+ throw new Error('Given value is not a string');
42
+ }
43
+ }
36
44
  function assertIsArray(values) {
37
45
  if (!Array.isArray(values)) {
38
46
  throw new Error('Asserting isArray: Given entity is not an array');
@@ -48,6 +56,16 @@ function assertIsNumberArray(values, allowNaN = false) {
48
56
  assertIsNumber(value, allowNaN);
49
57
  }
50
58
  }
59
+ /**
60
+ * Throws if any of `values` is not of type `string`.
61
+ */
62
+ function assertIsStringArray(values) {
63
+ assertIsArray(values);
64
+ assertIsNumber(values.length);
65
+ for (const value of values) {
66
+ assertIsString(value);
67
+ }
68
+ }
51
69
  /**
52
70
  * Throws an exception if `typeof value` is not a boolean.
53
71
  */
@@ -0,0 +1,12 @@
1
+ import { Mat33, Vec2 } from '@js-draw/math';
2
+ interface Descriptions {
3
+ zoomedIn: string;
4
+ zoomedOut: string;
5
+ movedLeft: string;
6
+ movedRight: string;
7
+ movedUp: string;
8
+ movedDown: string;
9
+ rotatedBy: (deg: number) => string;
10
+ }
11
+ declare const describeTransformation: (origin: Vec2, transform: Mat33, invertDirections: boolean, localizationTable: Descriptions) => string;
12
+ export default describeTransformation;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const math_1 = require("@js-draw/math");
4
+ const describeTransformation = (
5
+ // The location of the object before being transformed
6
+ origin,
7
+ // The transformation
8
+ transform,
9
+ // If true, moving the object right, for example, reads as "moved left"
10
+ invertDirections, localizationTable) => {
11
+ // Describe the transformation's affect on the viewport (note that transformation transforms
12
+ // the **elements** within the viewport). Assumes the transformation only does rotation/scale/translation.
13
+ const linearTransformedVec = transform.transformVec3(math_1.Vec2.unitX);
14
+ const affineTransformedVec = transform.transformVec2(origin);
15
+ const scale = linearTransformedVec.magnitude();
16
+ const clockwiseRotation = -(180 / Math.PI) * linearTransformedVec.angle();
17
+ const translation = affineTransformedVec.minus(origin);
18
+ const result = [];
19
+ if (scale > 1.2) {
20
+ result.push(localizationTable.zoomedIn);
21
+ }
22
+ else if (scale < 0.8) {
23
+ result.push(localizationTable.zoomedOut);
24
+ }
25
+ if (Math.floor(Math.abs(clockwiseRotation)) > 0) {
26
+ const roundedRotation = Math.round(invertDirections ? -clockwiseRotation : clockwiseRotation);
27
+ result.push(localizationTable.rotatedBy(roundedRotation));
28
+ }
29
+ const minTranslation = 1e-4;
30
+ if (translation.x > minTranslation) {
31
+ result.push(invertDirections ? localizationTable.movedLeft : localizationTable.movedRight);
32
+ }
33
+ else if (translation.x < -minTranslation) {
34
+ result.push(invertDirections ? localizationTable.movedRight : localizationTable.movedLeft);
35
+ }
36
+ if (translation.y < -minTranslation) {
37
+ result.push(invertDirections ? localizationTable.movedDown : localizationTable.movedUp);
38
+ }
39
+ else if (translation.y > minTranslation) {
40
+ result.push(invertDirections ? localizationTable.movedUp : localizationTable.movedDown);
41
+ }
42
+ return result.join('; ');
43
+ };
44
+ exports.default = describeTransformation;
@@ -6,5 +6,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  * @internal
7
7
  */
8
8
  exports.default = {
9
- number: '1.27.1',
9
+ // Note: Auto-updated by prebuild.js:
10
+ number: '1.28.0',
10
11
  };