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
@@ -139,7 +139,7 @@ export default class Eraser extends BaseTool {
139
139
  const line = new LineSegment2(this.lastPoint, currentPoint);
140
140
  const region = Rect2.union(line.bbox, eraserRect);
141
141
  const intersectingElems = this.editor.image
142
- .getElementsIntersectingRegion(region)
142
+ .getComponentsIntersecting(region)
143
143
  .filter((component) => {
144
144
  return component.intersects(line) || component.intersectsRect(eraserRect);
145
145
  });
@@ -178,7 +178,7 @@ export default class Eraser extends BaseTool {
178
178
  toAdd.push(...targetElem.withRegionErased(erasePath, this.editor.viewport));
179
179
  }
180
180
  const eraseCommand = new Erase(toErase);
181
- const newAddCommands = toAdd.map((elem) => EditorImage.addElement(elem));
181
+ const newAddCommands = toAdd.map((elem) => EditorImage.addComponent(elem));
182
182
  eraseCommand.apply(this.editor);
183
183
  newAddCommands.forEach((command) => command.apply(this.editor));
184
184
  const finalToErase = [];
@@ -234,7 +234,7 @@ export default class Eraser extends BaseTool {
234
234
  this.toRemove = this.toRemove.filter((other) => other !== item);
235
235
  }
236
236
  }
237
- commands.push(...[...this.toAdd].map((a) => EditorImage.addElement(a)));
237
+ commands.push(...[...this.toAdd].map((a) => EditorImage.addComponent(a)));
238
238
  this.addCommands = [];
239
239
  }
240
240
  if (this.eraseCommands.length > 0) {
@@ -22,7 +22,7 @@ export default class FindTool extends BaseTool {
22
22
  }
23
23
  getMatches(searchFor) {
24
24
  const lowerSearchFor = searchFor.toLocaleLowerCase();
25
- const matchingComponents = this.editor.image.getAllElements().filter((component) => {
25
+ const matchingComponents = this.editor.image.getAllComponents().filter((component) => {
26
26
  let text = '';
27
27
  if (component instanceof TextComponent) {
28
28
  text = component.getText();
@@ -71,7 +71,10 @@ export default class PasteHandler extends BaseTool {
71
71
  async doSVGPaste(data) {
72
72
  this.editor.showLoadingWarning(0);
73
73
  try {
74
- const loader = SVGLoader.fromString(data, true);
74
+ const loader = SVGLoader.fromString(data, {
75
+ sanitize: true,
76
+ plugins: this.editor.getCurrentSettings().svg?.loaderPlugins ?? [],
77
+ });
75
78
  const components = [];
76
79
  await loader.start((component) => {
77
80
  components.push(component);
@@ -213,7 +213,7 @@ export default class Pen extends BaseTool {
213
213
  this.editor.announceForAccessibility(this.editor.localization.autocorrectedTo(stroke.description(this.editor.localization)));
214
214
  }
215
215
  const canFlatten = true;
216
- const action = EditorImage.addElement(stroke, canFlatten);
216
+ const action = EditorImage.addComponent(stroke, canFlatten);
217
217
  this.editor.dispatch(action);
218
218
  }
219
219
  else {
@@ -17,7 +17,7 @@ export default class SelectAllShortcutHandler extends BaseTool {
17
17
  if (selectionTools.length > 0) {
18
18
  const selectionTool = selectionTools[0];
19
19
  selectionTool.setEnabled(true);
20
- selectionTool.setSelection(this.editor.image.getAllElements());
20
+ selectionTool.setSelection(this.editor.image.getAllComponents());
21
21
  return true;
22
22
  }
23
23
  }
@@ -15,6 +15,8 @@ import { ResizeMode } from './types.mjs';
15
15
  import EditorImage from '../../image/EditorImage.mjs';
16
16
  import uniteCommands from '../../commands/uniteCommands.mjs';
17
17
  import SelectionMenuShortcut from './SelectionMenuShortcut.mjs';
18
+ import { assertIsNumberArray, assertIsStringArray } from '../../util/assertions.mjs';
19
+ import describeTransformation from '../../util/describeTransformation.mjs';
18
20
  const updateChunkSize = 100;
19
21
  const maxPreviewElemCount = 500;
20
22
  // @internal
@@ -148,7 +150,7 @@ class Selection {
148
150
  return 0;
149
151
  }
150
152
  const selectedBottommostZIndex = this.selectedElems[0].getZIndex();
151
- const visibleObjects = this.editor.image.getElementsIntersectingRegion(this.region);
153
+ const visibleObjects = this.editor.image.getComponentsIntersecting(this.region);
152
154
  const topMostVisibleZIndex = visibleObjects[visibleObjects.length - 1]?.getZIndex() ?? selectedBottommostZIndex;
153
155
  const deltaZIndex = topMostVisibleZIndex + 1 - selectedBottommostZIndex;
154
156
  return deltaZIndex;
@@ -167,13 +169,13 @@ class Selection {
167
169
  // z-index of the just-transformed commands.
168
170
  if (this.selectedElems.length > 0) {
169
171
  const deltaZIndex = this.getDeltaZIndexToMoveSelectionToTop();
170
- transformPromise = this.editor.dispatch(new _a.ApplyTransformationCommand(this, selectedElems, fullTransform, deltaZIndex));
172
+ transformPromise = this.editor.dispatch(new _a.ApplyTransformationCommand(this, selectedElems, this.originalRegion.center, fullTransform, deltaZIndex));
171
173
  }
172
174
  return transformPromise;
173
175
  }
174
176
  /** Sends all selected elements to the bottom of the visible image. */
175
177
  sendToBack() {
176
- const visibleObjects = this.editor.image.getElementsIntersectingRegion(this.editor.viewport.visibleRect);
178
+ const visibleObjects = this.editor.image.getComponentsIntersecting(this.editor.viewport.visibleRect);
177
179
  // VisibleObjects and selectedElems should both be sorted by z-index
178
180
  const lowestVisibleZIndex = visibleObjects[0]?.getZIndex() ?? 0;
179
181
  const highestSelectedZIndex = this.selectedElems[this.selectedElems.length - 1]?.getZIndex() ?? 0;
@@ -300,7 +302,7 @@ class Selection {
300
302
  // If we're making things visible and the selected object wasn't previously
301
303
  // visible,
302
304
  else if (!parent && this.removedFromImage[elem.getId()]) {
303
- EditorImage.addElement(elem).apply(this.editor);
305
+ EditorImage.addComponent(elem).apply(this.editor);
304
306
  this.removedFromImage[elem.getId()] = false;
305
307
  delete this.removedFromImage[elem.getId()];
306
308
  }
@@ -438,14 +440,14 @@ class Selection {
438
440
  // Don't update the selection's focus when redoing/undoing
439
441
  const selectionToUpdate = null;
440
442
  const deltaZIndex = this.getDeltaZIndexToMoveSelectionToTop();
441
- tmpApplyCommand = new _a.ApplyTransformationCommand(selectionToUpdate, this.selectedElems, this.transform, deltaZIndex);
443
+ tmpApplyCommand = new _a.ApplyTransformationCommand(selectionToUpdate, this.selectedElems, this.region.center, this.transform, deltaZIndex);
442
444
  // Transform to ensure that the duplicates are in the correct location
443
445
  await tmpApplyCommand.apply(this.editor);
444
446
  // Show items again
445
447
  this.addRemoveSelectionFromImage(true);
446
448
  // With the transformation applied, create the duplicates
447
449
  command = uniteCommands(this.selectedElems.map((elem) => {
448
- return EditorImage.addElement(elem.clone());
450
+ return EditorImage.addComponent(elem.clone());
449
451
  }));
450
452
  // Move the selected objects back to the correct location.
451
453
  await tmpApplyCommand?.unapply(this.editor);
@@ -503,21 +505,31 @@ class Selection {
503
505
  _a = Selection;
504
506
  (() => {
505
507
  SerializableCommand.register('selection-tool-transform', (json, _editor) => {
508
+ const rawTransformArray = json.transform;
509
+ const rawCenterArray = json.selectionCenter ?? [0, 0];
510
+ const rawElementIds = json.elems ?? [];
511
+ assertIsNumberArray(rawTransformArray);
512
+ assertIsNumberArray(rawCenterArray);
513
+ assertIsStringArray(rawElementIds);
506
514
  // The selection box is lost when serializing/deserializing. No need to store box rotation
507
- const fullTransform = new Mat33(...json.transform);
508
- const elemIds = json.elems ?? [];
515
+ const fullTransform = new Mat33(...rawTransformArray);
516
+ const elemIds = rawElementIds;
509
517
  const deltaZIndex = parseInt(json.deltaZIndex ?? 0);
510
- return new _a.ApplyTransformationCommand(null, elemIds, fullTransform, deltaZIndex);
518
+ const center = Vec2.of(rawCenterArray[0] ?? 0, rawCenterArray[1] ?? 0);
519
+ return new _a.ApplyTransformationCommand(null, elemIds, center, fullTransform, deltaZIndex);
511
520
  });
512
521
  })();
513
522
  Selection.ApplyTransformationCommand = class extends SerializableCommand {
514
523
  constructor(selection,
515
524
  // If a `string[]`, selectedElems is a list of element IDs.
516
525
  selectedElems,
526
+ // Information used to describe the transformation
527
+ selectionCenter,
517
528
  // Full transformation used to transform elements.
518
529
  fullTransform, deltaZIndex) {
519
530
  super('selection-tool-transform');
520
531
  this.selection = selection;
532
+ this.selectionCenter = selectionCenter;
521
533
  this.fullTransform = fullTransform;
522
534
  this.deltaZIndex = deltaZIndex;
523
535
  const isIDList = (arr) => {
@@ -585,10 +597,11 @@ Selection.ApplyTransformationCommand = class extends SerializableCommand {
585
597
  elems: this.selectedElemIds,
586
598
  transform: this.fullTransform.toArray(),
587
599
  deltaZIndex: this.deltaZIndex,
600
+ selectionCenter: this.selectionCenter.asArray(),
588
601
  };
589
602
  }
590
603
  description(_editor, localizationTable) {
591
- return localizationTable.transformedElements(this.selectedElemIds.length);
604
+ return localizationTable.transformedElements(this.selectedElemIds.length, describeTransformation(this.selectionCenter, this.fullTransform, false, localizationTable));
592
605
  }
593
606
  };
594
607
  export default Selection;
@@ -33,7 +33,7 @@ export default class LassoSelectionBuilder extends SelectionBuilder {
33
33
  resolveInternal(image) {
34
34
  const path = this.previewPath();
35
35
  const lines = path.polylineApproximation();
36
- const candidates = image.getElementsIntersectingRegion(path.bbox);
36
+ const candidates = image.getComponentsIntersecting(path.bbox);
37
37
  const componentIsInSelection = (component) => {
38
38
  if (path.closedContainsRect(component.getExactBBox())) {
39
39
  return true;
@@ -15,7 +15,7 @@ export default class RectSelectionBuilder extends SelectionBuilder {
15
15
  return Path.fromRect(this.rect);
16
16
  }
17
17
  resolveInternal(image) {
18
- return image.getElementsIntersectingRegion(this.rect).filter((element) => {
18
+ return image.getComponentsIntersecting(this.rect).filter((element) => {
19
19
  // Filter out the case where the selection rectangle is completely contained
20
20
  // within the element (and does not intersect it).
21
21
  // This is useful, for example, if a very large stroke is used as the background
@@ -20,7 +20,7 @@ export default class SelectionBuilder {
20
20
  if (isClick) {
21
21
  const searchRegionSize = viewport.visibleRect.maxDimension / 200;
22
22
  const minSizeBox = path.bbox.grownBy(searchRegionSize);
23
- components = image.getElementsIntersectingRegion(minSizeBox).filter((component) => {
23
+ components = image.getComponentsIntersecting(minSizeBox).filter((component) => {
24
24
  return minSizeBox.containsRect(component.getBBox()) || component.intersectsRect(minSizeBox);
25
25
  });
26
26
  components = filterComponents(components);
@@ -253,7 +253,7 @@ export default class SelectionTool extends BaseTool {
253
253
  return true;
254
254
  }
255
255
  else if (shortcucts.matchesShortcut(selectAllKeyboardShortcut, event)) {
256
- this.setSelection(this.editor.image.getAllElements());
256
+ this.setSelection(this.editor.image.getAllComponents());
257
257
  return true;
258
258
  }
259
259
  else if (event.ctrlKey) {
@@ -456,7 +456,8 @@ export default class SelectionTool extends BaseTool {
456
456
  this.handleOverlay.style.display = enabled ? 'block' : 'none';
457
457
  if (enabled) {
458
458
  this.handleOverlay.tabIndex = 0;
459
- this.handleOverlay.setAttribute('aria-label', this.editor.localization.selectionToolKeyboardShortcuts);
459
+ this.handleOverlay.role = 'group';
460
+ this.handleOverlay.ariaLabel = this.editor.localization.selectionToolKeyboardShortcuts;
460
461
  }
461
462
  else {
462
463
  this.handleOverlay.tabIndex = -1;
@@ -151,7 +151,7 @@ export default class SoundUITool extends BaseTool {
151
151
  this.soundFeedback?.setColor(this.editor.display.getColorAt(current.screenPos) ?? Color4.black);
152
152
  const pointerMotionLine = new LineSegment2(this.lastPointerPos, current.canvasPos);
153
153
  const collisions = this.editor.image
154
- .getElementsIntersectingRegion(pointerMotionLine.bbox)
154
+ .getComponentsIntersecting(pointerMotionLine.bbox)
155
155
  .filter((component) => component.intersects(pointerMotionLine));
156
156
  this.lastPointerPos = current.canvasPos;
157
157
  if (collisions.length > 0) {
@@ -98,7 +98,7 @@ export default class TextTool extends BaseTool {
98
98
  const scrollCorrectionCanvas = this.editor.viewport.screenToCanvasTransform.transformVec3(scrollCorrectionScreen);
99
99
  const scrollTransform = Mat33.translation(scrollCorrectionCanvas);
100
100
  const textComponent = TextComponent.fromLines(content.split('\n'), scrollTransform.rightMul(this.contentTransform.get()), this.textStyle);
101
- const action = EditorImage.addElement(textComponent);
101
+ const action = EditorImage.addComponent(textComponent);
102
102
  if (this.removeExistingCommand) {
103
103
  // Unapply so that `removeExistingCommand` can be added to the undo stack.
104
104
  this.removeExistingCommand.unapply(this.editor);
@@ -208,7 +208,7 @@ export default class TextTool extends BaseTool {
208
208
  const canvasPos = current.canvasPos;
209
209
  const halfTestRegionSize = Vec2.of(4, 4).times(this.editor.viewport.getSizeOfPixelOnCanvas());
210
210
  const testRegion = Rect2.fromCorners(canvasPos.minus(halfTestRegionSize), canvasPos.plus(halfTestRegionSize));
211
- const targetNodes = this.editor.image.getElementsIntersectingRegion(testRegion);
211
+ const targetNodes = this.editor.image.getComponentsIntersecting(testRegion);
212
212
  let targetTextNodes = targetNodes.filter((node) => node instanceof TextComponent);
213
213
  // Don't try to edit text nodes that contain the viewport (this allows us
214
214
  // 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
  */
@@ -24,6 +24,12 @@ export function assertIsNumber(value, allowNaN = false) {
24
24
  throw new Error('Given value is not a number');
25
25
  }
26
26
  }
27
+ /** Throws an `Error` if the given `value` is not a `string`. */
28
+ export function assertIsString(value) {
29
+ if (typeof value !== 'string') {
30
+ throw new Error('Given value is not a string');
31
+ }
32
+ }
27
33
  export function assertIsArray(values) {
28
34
  if (!Array.isArray(values)) {
29
35
  throw new Error('Asserting isArray: Given entity is not an array');
@@ -39,6 +45,16 @@ export function assertIsNumberArray(values, allowNaN = false) {
39
45
  assertIsNumber(value, allowNaN);
40
46
  }
41
47
  }
48
+ /**
49
+ * Throws if any of `values` is not of type `string`.
50
+ */
51
+ export function assertIsStringArray(values) {
52
+ assertIsArray(values);
53
+ assertIsNumber(values.length);
54
+ for (const value of values) {
55
+ assertIsString(value);
56
+ }
57
+ }
42
58
  /**
43
59
  * Throws an exception if `typeof value` is not a boolean.
44
60
  */
@@ -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,42 @@
1
+ import { Vec2 } from '@js-draw/math';
2
+ const describeTransformation = (
3
+ // The location of the object before being transformed
4
+ origin,
5
+ // The transformation
6
+ transform,
7
+ // If true, moving the object right, for example, reads as "moved left"
8
+ invertDirections, localizationTable) => {
9
+ // Describe the transformation's affect on the viewport (note that transformation transforms
10
+ // the **elements** within the viewport). Assumes the transformation only does rotation/scale/translation.
11
+ const linearTransformedVec = transform.transformVec3(Vec2.unitX);
12
+ const affineTransformedVec = transform.transformVec2(origin);
13
+ const scale = linearTransformedVec.magnitude();
14
+ const clockwiseRotation = -(180 / Math.PI) * linearTransformedVec.angle();
15
+ const translation = affineTransformedVec.minus(origin);
16
+ const result = [];
17
+ if (scale > 1.2) {
18
+ result.push(localizationTable.zoomedIn);
19
+ }
20
+ else if (scale < 0.8) {
21
+ result.push(localizationTable.zoomedOut);
22
+ }
23
+ if (Math.floor(Math.abs(clockwiseRotation)) > 0) {
24
+ const roundedRotation = Math.round(invertDirections ? -clockwiseRotation : clockwiseRotation);
25
+ result.push(localizationTable.rotatedBy(roundedRotation));
26
+ }
27
+ const minTranslation = 1e-4;
28
+ if (translation.x > minTranslation) {
29
+ result.push(invertDirections ? localizationTable.movedLeft : localizationTable.movedRight);
30
+ }
31
+ else if (translation.x < -minTranslation) {
32
+ result.push(invertDirections ? localizationTable.movedRight : localizationTable.movedLeft);
33
+ }
34
+ if (translation.y < -minTranslation) {
35
+ result.push(invertDirections ? localizationTable.movedDown : localizationTable.movedUp);
36
+ }
37
+ else if (translation.y > minTranslation) {
38
+ result.push(invertDirections ? localizationTable.movedUp : localizationTable.movedDown);
39
+ }
40
+ return result.join('; ');
41
+ };
42
+ export default describeTransformation;
@@ -4,5 +4,6 @@
4
4
  * @internal
5
5
  */
6
6
  export default {
7
- number: '1.27.1',
7
+ // Note: Auto-updated by prebuild.js:
8
+ number: '1.28.0',
8
9
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "1.27.1",
3
+ "version": "1.28.0",
4
4
  "description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
5
5
  "types": "./dist/mjs/lib.d.ts",
6
6
  "main": "./dist/cjs/lib.js",
@@ -64,11 +64,11 @@
64
64
  "postpack": "ts-node tools/copyREADME.ts revert"
65
65
  },
66
66
  "dependencies": {
67
- "@js-draw/math": "^1.27.1",
67
+ "@js-draw/math": "^1.28.0",
68
68
  "@melloware/coloris": "0.22.0"
69
69
  },
70
70
  "devDependencies": {
71
- "@js-draw/build-tool": "^1.27.1",
71
+ "@js-draw/build-tool": "^1.28.0",
72
72
  "@types/jest": "29.5.5",
73
73
  "@types/jsdom": "21.1.3"
74
74
  },
@@ -86,5 +86,5 @@
86
86
  "freehand",
87
87
  "svg"
88
88
  ],
89
- "gitHead": "e11574b7b329e7867358c400457a3d99b1d2c469"
89
+ "gitHead": "03242acbf2250f5a41aa86a84146bb08583cf955"
90
90
  }
@@ -291,9 +291,15 @@
291
291
  &.-highlight-next > .next,
292
292
  &.-highlight-previous > .previous {
293
293
  font-weight: bold;
294
- background-color: rgba(200, 200, 200, 0.1);
295
294
  font-size: 1.4em;
296
295
  }
296
+
297
+ &.-highlight-next > .next,
298
+ &.-highlight-previous > .previous,
299
+ .next:hover,
300
+ .previous:hover {
301
+ background-color: rgba(200, 200, 200, 0.1);
302
+ }
297
303
  }
298
304
 
299
305
  .navigation-help {