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
@@ -1,5 +1,5 @@
1
- import { Path, Rect2, PathCommandType, comparePathIndices, stepPathIndexBy, } from '@js-draw/math';
2
- import { styleFromJSON, styleToJSON } from '../rendering/RenderingStyle.mjs';
1
+ import { Path, Rect2, PathCommandType, comparePathIndices, stepPathIndexBy, Color4, } from '@js-draw/math';
2
+ import { styleFromJSON, styleToJSON, } from '../rendering/RenderingStyle.mjs';
3
3
  import AbstractComponent from './AbstractComponent.mjs';
4
4
  import { createRestyleComponentCommand, } from './RestylableComponent.mjs';
5
5
  import { pathFromRenderable, pathToRenderable, simplifyPathToFullScreenOrEmpty, } from '../rendering/RenderablePathSpec.mjs';
@@ -69,6 +69,35 @@ export default class Stroke extends AbstractComponent {
69
69
  }
70
70
  this.contentBBox ??= Rect2.empty;
71
71
  }
72
+ /**
73
+ * Creates a new `Stroke` from a {@link Path} and `style`. Strokes created
74
+ * with this method have transparent fill.
75
+ *
76
+ * Example:
77
+ * ```ts,runnable
78
+ * import { Editor, Stroke, Color4 } from 'js-draw';
79
+ * const editor = new Editor(document.body);
80
+ * ---visible---
81
+ * const stroke = Stroke.fromStroked('m0,0 l10,10', { width: 10, color: Color4.red });
82
+ * editor.dispatch(editor.image.addComponent(stroke));
83
+ * ```
84
+ * Notice that `path` can be a string that specifies an SVG path
85
+ *
86
+ * @see fromFilled
87
+ */
88
+ static fromStroked(path, style) {
89
+ if (typeof path === 'string') {
90
+ path = Path.fromString(path);
91
+ }
92
+ return new Stroke([pathToRenderable(path, { fill: Color4.transparent, stroke: style })]);
93
+ }
94
+ /** @see fromStroked */
95
+ static fromFilled(path, fill) {
96
+ if (typeof path === 'string') {
97
+ path = Path.fromString(path);
98
+ }
99
+ return new Stroke([pathToRenderable(path, { fill })]);
100
+ }
72
101
  getStyle() {
73
102
  if (this.parts.length === 0) {
74
103
  return {};
@@ -38,7 +38,7 @@ type TextElement = TextComponent | string;
38
38
  * };
39
39
  *
40
40
  * editor.dispatch(
41
- * editor.image.addElement(new TextComponent(['Hello, world'], positioning1, style)),
41
+ * editor.image.addComponent(new TextComponent(['Hello, world'], positioning1, style)),
42
42
  * );
43
43
  *
44
44
  *
@@ -49,7 +49,7 @@ type TextElement = TextComponent | string;
49
49
  * // is placed directly after 'Test'.
50
50
  * const positioning2 = Mat33.translation(Vec2.of(10, 50));
51
51
  * editor.dispatch(
52
- * editor.image.addElement(
52
+ * editor.image.addComponent(
53
53
  * new TextComponent([ new TextComponent(['Test'], positioning1, style), '[Test]' ], positioning2, style)
54
54
  * ),
55
55
  * );
@@ -40,7 +40,7 @@ const defaultTextStyle = {
40
40
  * };
41
41
  *
42
42
  * editor.dispatch(
43
- * editor.image.addElement(new TextComponent(['Hello, world'], positioning1, style)),
43
+ * editor.image.addComponent(new TextComponent(['Hello, world'], positioning1, style)),
44
44
  * );
45
45
  *
46
46
  *
@@ -51,7 +51,7 @@ const defaultTextStyle = {
51
51
  * // is placed directly after 'Test'.
52
52
  * const positioning2 = Mat33.translation(Vec2.of(10, 50));
53
53
  * editor.dispatch(
54
- * editor.image.addElement(
54
+ * editor.image.addComponent(
55
55
  * new TextComponent([ new TextComponent(['Test'], positioning1, style), '[Test]' ], positioning2, style)
56
56
  * ),
57
57
  * );
@@ -12,7 +12,7 @@ import makeShapeFitAutocorrect from './autocorrect/makeShapeFitAutocorrect.mjs
12
12
  export const makePolylineBuilder = makeShapeFitAutocorrect((initialPoint, viewport) => {
13
13
  // Fit to a value slightly smaller than the pixel size. A larger value can
14
14
  // cause the stroke to appear jagged at some zoom levels.
15
- const minFit = viewport.getSizeOfPixelOnCanvas() * 0.75;
15
+ const minFit = viewport.getSizeOfPixelOnCanvas() * 0.65;
16
16
  return new PolylineBuilder(initialPoint, minFit, viewport);
17
17
  });
18
18
  export default class PolylineBuilder {
@@ -22,7 +22,7 @@ export type EditorImageNotifier = EventDispatcher<EditorImageEventType, {
22
22
  */
23
23
  export type PreRenderComponentCallback = (component: AbstractComponent, componentsProcessed: number, totalComponents: number) => Promise<boolean>;
24
24
  /**
25
- * @summary Handles lookup/storage of elements in the image.
25
+ * Handles lookup/storage of elements in the image.
26
26
  *
27
27
  * `js-draw` images are made up of a collection of {@link AbstractComponent}s (which
28
28
  * includes {@link Stroke}s, {@link TextComponent}s, etc.). An `EditorImage`
@@ -30,9 +30,9 @@ export type PreRenderComponentCallback = (component: AbstractComponent, componen
30
30
  *
31
31
  * Here's how to do a few common operations:
32
32
  * - **Get all components in a {@link @js-draw/math!Rect2 | Rect2}**:
33
- * {@link EditorImage.getElementsIntersectingRegion}.
33
+ * {@link EditorImage.getComponentsIntersecting}.
34
34
  * - **Draw an `EditorImage` onto a canvas/SVG**: {@link EditorImage.render}.
35
- * - **Adding a new component**: {@link EditorImage.addElement}.
35
+ * - **Adding a new component**: {@link EditorImage.addComponent}.
36
36
  *
37
37
  * **Example**:
38
38
  * [[include:doc-pages/inline-examples/image-add-and-lookup.md]]
@@ -82,19 +82,23 @@ export default class EditorImage {
82
82
  * @returns all elements in the image, sorted by z-index (low to high).
83
83
  *
84
84
  * This can be slow for large images. If you only need all elemenst in part of the image,
85
- * consider using {@link getElementsIntersectingRegion} instead.
85
+ * consider using {@link getComponentsIntersecting} instead.
86
86
  *
87
87
  * **Note**: The result does not include background elements. See {@link getBackgroundComponents}.
88
88
  */
89
+ getAllComponents(): AbstractComponent[];
90
+ /** @deprecated in favor of {@link getAllComponents} */
89
91
  getAllElements(): AbstractComponent[];
90
92
  /** Returns the number of elements added to this image. @internal */
91
93
  estimateNumElements(): number;
94
+ /** @deprecated @see getComponentsIntersecting */
95
+ getElementsIntersectingRegion(region: Rect2, includeBackground?: boolean): AbstractComponent[];
92
96
  /**
93
97
  * @returns a list of `AbstractComponent`s intersecting `region`, sorted by increasing z-index.
94
98
  *
95
99
  * Components in the background layer are only included if `includeBackground` is `true`.
96
100
  */
97
- getElementsIntersectingRegion(region: Rect2, includeBackground?: boolean): AbstractComponent[];
101
+ getComponentsIntersecting(region: Rect2, includeBackground?: boolean): AbstractComponent[];
98
102
  /** Called whenever (just after) an element is completely removed. @internal */
99
103
  onDestroyElement(elem: AbstractComponent): void;
100
104
  /** Called just after an element is added. @internal */
@@ -105,7 +109,7 @@ export default class EditorImage {
105
109
  * @see {@link AbstractComponent.getId}
106
110
  */
107
111
  lookupElement(id: string): AbstractComponent | null;
108
- private addElementDirectly;
112
+ private addComponentDirectly;
109
113
  private removeElementDirectly;
110
114
  /**
111
115
  * Returns a command that adds the given element to the `EditorImage`.
@@ -118,10 +122,14 @@ export default class EditorImage {
118
122
  *
119
123
  * [[include:doc-pages/inline-examples/adding-a-stroke.md]]
120
124
  */
121
- static addElement(elem: AbstractComponent, applyByFlattening?: boolean): SerializableCommand;
122
- /** @see EditorImage.addElement */
125
+ static addComponent(elem: AbstractComponent, applyByFlattening?: boolean): SerializableCommand;
126
+ /** @see EditorImage.addComponent */
127
+ addComponent(component: AbstractComponent, applyByFlattening?: boolean): SerializableCommand;
128
+ /** Alias for {@link addComponent}. @deprecated Prefer `.addComponent` */
123
129
  addElement(elem: AbstractComponent, applyByFlattening?: boolean): SerializableCommand;
124
- private static AddElementCommand;
130
+ /** Alias for {@link addComponent}. @deprecated Prefer `.addComponent`. */
131
+ static addElement(elem: AbstractComponent, applyByFlattening?: boolean): SerializableCommand;
132
+ private static AddComponentCommand;
125
133
  /**
126
134
  * @returns a `Viewport` for rendering the image when importing/exporting.
127
135
  */
@@ -21,7 +21,7 @@ export var EditorImageEventType;
21
21
  })(EditorImageEventType || (EditorImageEventType = {}));
22
22
  let debugMode = false;
23
23
  /**
24
- * @summary Handles lookup/storage of elements in the image.
24
+ * Handles lookup/storage of elements in the image.
25
25
  *
26
26
  * `js-draw` images are made up of a collection of {@link AbstractComponent}s (which
27
27
  * includes {@link Stroke}s, {@link TextComponent}s, etc.). An `EditorImage`
@@ -29,9 +29,9 @@ let debugMode = false;
29
29
  *
30
30
  * Here's how to do a few common operations:
31
31
  * - **Get all components in a {@link @js-draw/math!Rect2 | Rect2}**:
32
- * {@link EditorImage.getElementsIntersectingRegion}.
32
+ * {@link EditorImage.getComponentsIntersecting}.
33
33
  * - **Draw an `EditorImage` onto a canvas/SVG**: {@link EditorImage.render}.
34
- * - **Adding a new component**: {@link EditorImage.addElement}.
34
+ * - **Adding a new component**: {@link EditorImage.addComponent}.
35
35
  *
36
36
  * **Example**:
37
37
  * [[include:doc-pages/inline-examples/image-add-and-lookup.md]]
@@ -78,7 +78,7 @@ class EditorImage {
78
78
  const parent = this.findParent(elem);
79
79
  if (parent) {
80
80
  parent.remove();
81
- this.addElementDirectly(elem);
81
+ this.addComponentDirectly(elem);
82
82
  }
83
83
  }
84
84
  /** @internal */
@@ -133,25 +133,33 @@ class EditorImage {
133
133
  * @returns all elements in the image, sorted by z-index (low to high).
134
134
  *
135
135
  * This can be slow for large images. If you only need all elemenst in part of the image,
136
- * consider using {@link getElementsIntersectingRegion} instead.
136
+ * consider using {@link getComponentsIntersecting} instead.
137
137
  *
138
138
  * **Note**: The result does not include background elements. See {@link getBackgroundComponents}.
139
139
  */
140
- getAllElements() {
140
+ getAllComponents() {
141
141
  const leaves = this.root.getLeaves();
142
142
  sortLeavesByZIndex(leaves);
143
143
  return leaves.map((leaf) => leaf.getContent());
144
144
  }
145
+ /** @deprecated in favor of {@link getAllComponents} */
146
+ getAllElements() {
147
+ return this.getAllComponents();
148
+ }
145
149
  /** Returns the number of elements added to this image. @internal */
146
150
  estimateNumElements() {
147
151
  return this.componentCount;
148
152
  }
153
+ /** @deprecated @see getComponentsIntersecting */
154
+ getElementsIntersectingRegion(region, includeBackground = false) {
155
+ return this.getComponentsIntersecting(region, includeBackground);
156
+ }
149
157
  /**
150
158
  * @returns a list of `AbstractComponent`s intersecting `region`, sorted by increasing z-index.
151
159
  *
152
160
  * Components in the background layer are only included if `includeBackground` is `true`.
153
161
  */
154
- getElementsIntersectingRegion(region, includeBackground = false) {
162
+ getComponentsIntersecting(region, includeBackground = false) {
155
163
  let leaves = this.root.getLeavesIntersectingRegion(region);
156
164
  if (includeBackground) {
157
165
  leaves = leaves.concat(this.background.getLeavesIntersectingRegion(region));
@@ -179,7 +187,7 @@ class EditorImage {
179
187
  lookupElement(id) {
180
188
  return this.componentsById[id] ?? null;
181
189
  }
182
- addElementDirectly(elem) {
190
+ addComponentDirectly(elem) {
183
191
  // Because onAddToImage can affect the element's bounding box,
184
192
  // this needs to be called before parentTree.addLeaf.
185
193
  elem.onAddToImage(this);
@@ -210,12 +218,20 @@ class EditorImage {
210
218
  *
211
219
  * [[include:doc-pages/inline-examples/adding-a-stroke.md]]
212
220
  */
213
- static addElement(elem, applyByFlattening = false) {
214
- return new _a.AddElementCommand(elem, applyByFlattening);
221
+ static addComponent(elem, applyByFlattening = false) {
222
+ return new _a.AddComponentCommand(elem, applyByFlattening);
223
+ }
224
+ /** @see EditorImage.addComponent */
225
+ addComponent(component, applyByFlattening) {
226
+ return _a.addComponent(component, applyByFlattening);
215
227
  }
216
- /** @see EditorImage.addElement */
228
+ /** Alias for {@link addComponent}. @deprecated Prefer `.addComponent` */
217
229
  addElement(elem, applyByFlattening) {
218
- return _a.addElement(elem, applyByFlattening);
230
+ return this.addComponent(elem, applyByFlattening);
231
+ }
232
+ /** Alias for {@link addComponent}. @deprecated Prefer `.addComponent`. */
233
+ static addElement(elem, applyByFlattening = false) {
234
+ return this.addComponent(elem, applyByFlattening);
219
235
  }
220
236
  /**
221
237
  * @returns a `Viewport` for rendering the image when importing/exporting.
@@ -346,7 +362,7 @@ class EditorImage {
346
362
  }
347
363
  _a = EditorImage;
348
364
  // A Command that can access private [EditorImage] functionality
349
- EditorImage.AddElementCommand = (_b = class extends SerializableCommand {
365
+ EditorImage.AddComponentCommand = (_b = class extends SerializableCommand {
350
366
  // If [applyByFlattening], then the rendered content of this element
351
367
  // is present on the display's wet ink canvas. As such, no re-render is necessary
352
368
  // the first time this command is applied (the surfaces are joined instead).
@@ -366,7 +382,7 @@ EditorImage.AddElementCommand = (_b = class extends SerializableCommand {
366
382
  }
367
383
  }
368
384
  apply(editor) {
369
- editor.image.addElementDirectly(this.element);
385
+ editor.image.addComponentDirectly(this.element);
370
386
  if (!this.applyByFlattening) {
371
387
  editor.queueRerender();
372
388
  }
@@ -380,7 +396,7 @@ EditorImage.AddElementCommand = (_b = class extends SerializableCommand {
380
396
  editor.queueRerender();
381
397
  }
382
398
  description(_editor, localization) {
383
- return localization.addElementAction(this.element.description(localization));
399
+ return localization.addComponentAction(this.element.description(localization));
384
400
  }
385
401
  serializeToJSON() {
386
402
  return {
@@ -388,13 +404,13 @@ EditorImage.AddElementCommand = (_b = class extends SerializableCommand {
388
404
  };
389
405
  }
390
406
  },
391
- __setFunctionName(_b, "AddElementCommand"),
407
+ __setFunctionName(_b, "AddComponentCommand"),
392
408
  (() => {
393
409
  SerializableCommand.register('add-element', (json, editor) => {
394
410
  const id = json.elemData.id;
395
411
  const foundElem = editor.image.lookupElement(id);
396
412
  const elem = foundElem ?? AbstractComponent.deserialize(json.elemData);
397
- const result = new _a.AddElementCommand(elem);
413
+ const result = new _a.AddComponentCommand(elem);
398
414
  result.serializedElem = json.elemData;
399
415
  return result;
400
416
  });
package/dist/mjs/lib.d.ts CHANGED
@@ -20,7 +20,7 @@ export * from './types';
20
20
  export * from './inputEvents';
21
21
  export { default as getLocalizationTable, matchingLocalizationTable, } from './localizations/getLocalizationTable';
22
22
  export * from './localization';
23
- export { default as SVGLoader } from './SVGLoader/SVGLoader';
23
+ export { default as SVGLoader, SVGLoaderPlugin } from './SVGLoader/SVGLoader';
24
24
  export { default as Viewport } from './Viewport';
25
25
  export * from '@js-draw/math';
26
26
  export * from './components/lib';
@@ -65,9 +65,9 @@ const localization = {
65
65
  toolEnabledAnnouncement: (toolName) => `${toolName} aktiviert`,
66
66
  toolDisabledAnnouncement: (toolName) => `${toolName} deaktiviert`,
67
67
  updatedViewport: 'Transformierte Ansicht',
68
- transformedElements: (elemCount) => `${elemCount} Element${1 === elemCount ? '' : 'e'} transformiert`,
68
+ transformedElements: (elemCount, action) => `${elemCount} Element${1 === elemCount ? '' : 'e'} transformiert (${action})`,
69
69
  resizeOutputCommand: (newSize) => `Bildgröße auf ${newSize.w}x${newSize.h} geändert`,
70
- addElementAction: (componentDescription) => `${componentDescription} hinzugefügt`,
70
+ addComponentAction: (componentDescription) => `${componentDescription} hinzugefügt`,
71
71
  eraseAction: (elemDescription, countErased) => `${countErased} ${elemDescription} gelöscht`,
72
72
  duplicateAction: (elemDescription, countErased) => `${countErased} ${elemDescription} dupliziert`,
73
73
  inverseOf: (actionDescription) => `${actionDescription} umgekehrt`,
@@ -1,11 +1,12 @@
1
1
  import { Color4 } from '@js-draw/math';
2
- interface RenderingStyle {
2
+ export interface StrokeStyle {
3
+ readonly color: Color4;
4
+ /** Note: The stroke `width` is twice the stroke radius. */
5
+ readonly width: number;
6
+ }
7
+ export interface RenderingStyle {
3
8
  readonly fill: Color4;
4
- readonly stroke?: {
5
- readonly color: Color4;
6
- /** Note: The stroke `width` is twice the stroke radius. */
7
- readonly width: number;
8
- };
9
+ readonly stroke?: StrokeStyle;
9
10
  }
10
11
  export default RenderingStyle;
11
12
  export declare const cloneStyle: (style: RenderingStyle) => {
@@ -4,5 +4,5 @@ export { default as SVGRenderer } from './renderers/SVGRenderer';
4
4
  export { default as CanvasRenderer } from './renderers/CanvasRenderer';
5
5
  export { default as Display, RenderingMode } from './Display';
6
6
  export { default as TextRenderingStyle } from './TextRenderingStyle';
7
- export { default as RenderingStyle } from './RenderingStyle';
7
+ export { default as RenderingStyle, StrokeStyle as StrokeRenerdingStyle } from './RenderingStyle';
8
8
  export { pathToRenderable, pathFromRenderable, visualEquivalent as pathVisualEquivalent, default as RenderablePathSpec, } from './RenderablePathSpec';
@@ -140,6 +140,8 @@ export default class AbstractRenderer {
140
140
  this.selfTransform = transform;
141
141
  }
142
142
  pushTransform(transform) {
143
+ // Draw all pending paths that used the previous transform (if any).
144
+ this.flushPath();
143
145
  this.transformStack.push(this.selfTransform);
144
146
  this.setTransform(this.getCanvasToScreenTransform().rightMul(transform));
145
147
  }
@@ -147,6 +149,8 @@ export default class AbstractRenderer {
147
149
  if (this.transformStack.length === 0) {
148
150
  throw new Error('Unable to pop more transforms than have been pushed!');
149
151
  }
152
+ // Draw all pending paths that used the old transform (if any):
153
+ this.flushPath();
150
154
  this.setTransform(this.transformStack.pop() ?? null);
151
155
  }
152
156
  // Get the matrix that transforms a vector on the canvas to a vector on this'
@@ -42,6 +42,15 @@ export default class CanvasRenderer extends AbstractRenderer {
42
42
  private clipLevels;
43
43
  startObject(boundingBox: Rect2, clip?: boolean): void;
44
44
  endObject(): void;
45
+ /**
46
+ * Returns a reference to the underlying `CanvasRenderingContext2D`.
47
+ * This can be used to render custom content not supported by {@link AbstractRenderer}.
48
+ * However, such content won't support {@link SVGRenderer} or {@link TextOnlyRenderer}
49
+ * by default.
50
+ *
51
+ * Use with caution.
52
+ */
53
+ drawWithRawRenderingContext(callback: (ctx: CanvasRenderingContext2D) => void): void;
45
54
  drawPoints(...points: Point2[]): void;
46
55
  isTooSmallToRender(rect: Rect2): boolean;
47
56
  static fromViewport(exportViewport: Viewport, options?: {
@@ -208,6 +208,20 @@ export default class CanvasRenderer extends AbstractRenderer {
208
208
  this.ignoringObject = false;
209
209
  }
210
210
  }
211
+ /**
212
+ * Returns a reference to the underlying `CanvasRenderingContext2D`.
213
+ * This can be used to render custom content not supported by {@link AbstractRenderer}.
214
+ * However, such content won't support {@link SVGRenderer} or {@link TextOnlyRenderer}
215
+ * by default.
216
+ *
217
+ * Use with caution.
218
+ */
219
+ drawWithRawRenderingContext(callback) {
220
+ this.ctx.save();
221
+ this.transformBy(this.getCanvasToScreenTransform());
222
+ callback(this.ctx);
223
+ this.ctx.restore();
224
+ }
211
225
  // @internal
212
226
  drawPoints(...points) {
213
227
  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`,
@@ -341,7 +341,12 @@ export default class SVGRenderer extends AbstractRenderer {
341
341
  this.elem.appendChild(elem);
342
342
  });
343
343
  }
344
- // Renders a **copy** of the given element.
344
+ /**
345
+ * Adds a **copy** of the given element directly to the container
346
+ * SVG element, **without applying transforms**.
347
+ *
348
+ * If `sanitize` is enabled, this does nothing.
349
+ */
345
350
  drawSVGElem(elem) {
346
351
  if (this.sanitize) {
347
352
  return;
@@ -355,6 +360,21 @@ export default class SVGRenderer extends AbstractRenderer {
355
360
  this.elem.appendChild(elemToDraw);
356
361
  this.objectElems?.push(elemToDraw);
357
362
  }
363
+ /**
364
+ * Allows rendering directly to the underlying SVG element. Rendered
365
+ * content is added to a `<g>` element that's passed as `parent` to `callback`.
366
+ *
367
+ * **Note**: Unlike {@link drawSVGElem}, this method can be used even if `sanitize` is `true`.
368
+ * In this case, it's the responsibility of `callback` to ensure that everything added
369
+ * to `parent` is safe to render.
370
+ */
371
+ drawWithSVGParent(callback) {
372
+ const parent = document.createElementNS(svgNameSpace, 'g');
373
+ this.transformFrom(Mat33.identity, parent, true);
374
+ callback(parent, { sanitize: this.sanitize });
375
+ this.elem.appendChild(parent);
376
+ this.objectElems?.push(parent);
377
+ }
358
378
  isTooSmallToRender(_rect) {
359
379
  return false;
360
380
  }
@@ -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;
@@ -12,7 +12,6 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
12
12
  var _AbstractToolbar_listeners, _AbstractToolbar_widgetsById, _AbstractToolbar_widgetList, _AbstractToolbar_updateColoris;
13
13
  import { EditorEventType } from '../types.mjs';
14
14
  import { coloris, close as closeColoris, init as colorisInit } from '@melloware/coloris';
15
- import { defaultToolbarLocalization } from './localization.mjs';
16
15
  import SelectionTool from '../tools/SelectionTool/SelectionTool.mjs';
17
16
  import PanZoomTool from '../tools/PanZoom.mjs';
18
17
  import TextTool from '../tools/TextTool.mjs';
@@ -39,14 +38,14 @@ import { assertIsObject, assertTruthy } from '../util/assertions.mjs';
39
38
  */
40
39
  class AbstractToolbar {
41
40
  /** @internal */
42
- constructor(editor, localizationTable = defaultToolbarLocalization) {
41
+ constructor(editor, localizationTable) {
43
42
  this.editor = editor;
44
- this.localizationTable = localizationTable;
45
43
  _AbstractToolbar_listeners.set(this, []);
46
44
  _AbstractToolbar_widgetsById.set(this, {});
47
45
  _AbstractToolbar_widgetList.set(this, []);
48
46
  _AbstractToolbar_updateColoris.set(this, null);
49
47
  this.closeColorPickerOverlay = null;
48
+ this.localizationTable = localizationTable ?? editor.localization;
50
49
  if (!AbstractToolbar.colorisStarted) {
51
50
  colorisInit();
52
51
  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;
@@ -1,4 +1,3 @@
1
- import { defaultToolbarLocalization } from './localization.mjs';
2
1
  import OverflowWidget from './widgets/OverflowWidget.mjs';
3
2
  import AbstractToolbar from './AbstractToolbar.mjs';
4
3
  import { toolbarCSSPrefix } from './constants.mjs';
@@ -29,11 +28,11 @@ import { toolbarCSSPrefix } from './constants.mjs';
29
28
  * - {@link AbstractToolbar.addExitButton}
30
29
  */
31
30
  export const makeDropdownToolbar = (editor) => {
32
- return new DropdownToolbar(editor, editor.getRootElement());
31
+ return new DropdownToolbar(editor, editor.getRootElement(), editor.localization);
33
32
  };
34
33
  export default class DropdownToolbar extends AbstractToolbar {
35
34
  /** @internal */
36
- constructor(editor, parent, localizationTable = defaultToolbarLocalization) {
35
+ constructor(editor, parent, localizationTable) {
37
36
  super(editor, localizationTable);
38
37
  // Flex-order of the next widget to be added.
39
38
  this.widgetOrderCounter = 0;
@@ -0,0 +1 @@
1
+ export {};
@@ -134,6 +134,8 @@ const createHelpPage = (helpItems, onItemClick, onBackgroundClick, context) => {
134
134
  clonedElement.style.margin = '0';
135
135
  const clonedElementContainer = document.createElement('div');
136
136
  clonedElementContainer.classList.add('cloned-element-container');
137
+ clonedElementContainer.role = 'group';
138
+ clonedElementContainer.ariaLabel = context.localization.helpControlsAccessibilityLabel;
137
139
  clonedElementContainer.style.position = 'absolute';
138
140
  clonedElementContainer.style.left = `${targetBBox.topLeft.x}px`;
139
141
  clonedElementContainer.style.top = `${targetBBox.topLeft.y}px`;
@@ -152,11 +154,11 @@ const createHelpPage = (helpItems, onItemClick, onBackgroundClick, context) => {
152
154
  };
153
155
  const onItemChange = () => {
154
156
  const helpTextElement = document.createElement('div');
155
- helpTextElement.innerText = currentItem?.helpText ?? '';
157
+ helpTextElement.textContent = currentItem?.helpText ?? '';
156
158
  // For tests
157
159
  helpTextElement.classList.add('current-item-help');
158
160
  const navigationHelpElement = document.createElement('div');
159
- navigationHelpElement.innerText = context.localization.helpScreenNavigationHelp;
161
+ navigationHelpElement.textContent = context.localization.helpScreenNavigationHelp;
160
162
  navigationHelpElement.classList.add('navigation-help');
161
163
  textLabel.replaceChildren(helpTextElement, ...(currentItemIndex === 0 ? [navigationHelpElement] : []));
162
164
  updateClonedElementStates();
@@ -273,8 +275,8 @@ class HelpDisplay {
273
275
  navigationButtonContainer.classList.add('navigation-buttons');
274
276
  const nextButton = document.createElement('button');
275
277
  const previousButton = document.createElement('button');
276
- nextButton.innerText = this.context.localization.next;
277
- previousButton.innerText = this.context.localization.previous;
278
+ nextButton.textContent = this.context.localization.next;
279
+ previousButton.textContent = this.context.localization.previous;
278
280
  nextButton.classList.add('next');
279
281
  previousButton.classList.add('previous');
280
282
  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;
@@ -5,4 +5,5 @@ export const defaultToolbarUtilsLocalization = {
5
5
  previous: 'Previous',
6
6
  close: 'Close',
7
7
  helpScreenNavigationHelp: 'Click on a control for more information.',
8
+ helpControlsAccessibilityLabel: 'Controls: Activate a control to show help.',
8
9
  };
@@ -62,7 +62,7 @@ class DocumentPropertiesWidget extends BaseWidget {
62
62
  setBackgroundType(backgroundType) {
63
63
  const prevBackgroundColor = this.editor.estimateBackgroundColor();
64
64
  const newBackground = new BackgroundComponent(backgroundType, prevBackgroundColor);
65
- const addBackgroundCommand = this.editor.image.addElement(newBackground);
65
+ const addBackgroundCommand = this.editor.image.addComponent(newBackground);
66
66
  return uniteCommands([this.removeBackgroundComponents(), addBackgroundCommand]);
67
67
  }
68
68
  /** Returns the type of the topmost background component */
@@ -270,7 +270,7 @@ class InsertImageWidget extends BaseWidget {
270
270
  const widthAdjustTransform = Mat33.scaling2D(originalWidth / newWidth);
271
271
  const commands = [];
272
272
  for (const component of newComponents) {
273
- commands.push(EditorImage.addElement(component), component.transformBy(originalTransform.rightMul(widthAdjustTransform)), component.setZIndex(editingImage.getZIndex()));
273
+ commands.push(EditorImage.addComponent(component), component.transformBy(originalTransform.rightMul(widthAdjustTransform)), component.setZIndex(editingImage.getZIndex()));
274
274
  }
275
275
  this.editor.dispatch(uniteCommands([...commands, eraseCommand]));
276
276
  selectionTools[0]?.setSelection(newComponents);
@@ -17,7 +17,7 @@ labelText, defaultId, choices) => {
17
17
  outerContainer.classList.add(`${toolbarCSSPrefix}grid-selector`);
18
18
  const selectedValue = MutableReactiveValue.fromInitialValue(defaultId);
19
19
  const menuContainer = document.createElement('div');
20
- menuContainer.setAttribute('role', 'menu');
20
+ menuContainer.role = 'group';
21
21
  menuContainer.id = `${toolbarCSSPrefix}-grid-select-id-${idCounter++}`;
22
22
  stopPropagationOfScrollingWheelEvents(menuContainer);
23
23
  const label = document.createElement('label');