js-draw 1.2.2 → 1.3.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 (107) hide show
  1. package/README.md +29 -29
  2. package/dist/Editor.css +65 -4
  3. package/dist/bundle.js +2 -2
  4. package/dist/bundledStyles.js +1 -1
  5. package/dist/cjs/Editor.d.ts +73 -40
  6. package/dist/cjs/Editor.js +90 -24
  7. package/dist/cjs/EditorImage.d.ts +58 -6
  8. package/dist/cjs/EditorImage.js +336 -60
  9. package/dist/cjs/SVGLoader.d.ts +10 -4
  10. package/dist/cjs/SVGLoader.js +30 -10
  11. package/dist/cjs/UndoRedoHistory.d.ts +2 -2
  12. package/dist/cjs/UndoRedoHistory.js +4 -2
  13. package/dist/cjs/Viewport.d.ts +2 -1
  14. package/dist/cjs/Viewport.js +12 -3
  15. package/dist/cjs/commands/Command.d.ts +1 -0
  16. package/dist/cjs/commands/Command.js +1 -0
  17. package/dist/cjs/commands/Erase.js +1 -1
  18. package/dist/cjs/commands/SerializableCommand.d.ts +1 -1
  19. package/dist/cjs/commands/SerializableCommand.js +16 -2
  20. package/dist/cjs/commands/localization.d.ts +2 -0
  21. package/dist/cjs/commands/localization.js +2 -0
  22. package/dist/cjs/components/AbstractComponent.d.ts +38 -0
  23. package/dist/cjs/components/AbstractComponent.js +31 -0
  24. package/dist/cjs/components/BackgroundComponent.d.ts +10 -1
  25. package/dist/cjs/components/BackgroundComponent.js +60 -6
  26. package/dist/cjs/components/SVGGlobalAttributesObject.d.ts +2 -1
  27. package/dist/cjs/components/SVGGlobalAttributesObject.js +30 -1
  28. package/dist/cjs/components/Stroke.d.ts +1 -0
  29. package/dist/cjs/components/Stroke.js +44 -0
  30. package/dist/cjs/components/UnknownSVGObject.d.ts +2 -1
  31. package/dist/cjs/components/UnknownSVGObject.js +30 -1
  32. package/dist/cjs/lib.d.ts +2 -45
  33. package/dist/cjs/lib.js +2 -45
  34. package/dist/cjs/rendering/RenderingStyle.d.ts +1 -0
  35. package/dist/cjs/rendering/renderers/AbstractRenderer.js +1 -1
  36. package/dist/cjs/shortcuts/KeyboardShortcutManager.d.ts +2 -2
  37. package/dist/cjs/shortcuts/KeyboardShortcutManager.js +2 -2
  38. package/dist/cjs/toolbar/localization.d.ts +1 -0
  39. package/dist/cjs/toolbar/localization.js +1 -0
  40. package/dist/cjs/toolbar/widgets/BaseWidget.js +5 -0
  41. package/dist/cjs/toolbar/widgets/DocumentPropertiesWidget.js +54 -25
  42. package/dist/cjs/toolbar/widgets/components/makeGridSelector.js +8 -0
  43. package/dist/cjs/tools/PanZoom.js +13 -8
  44. package/dist/cjs/tools/ScrollbarTool.d.ts +18 -0
  45. package/dist/cjs/tools/ScrollbarTool.js +85 -0
  46. package/dist/cjs/tools/SelectionTool/SelectionTool.selecting.test.d.ts +1 -0
  47. package/dist/cjs/tools/ToolController.js +2 -0
  48. package/dist/cjs/types.d.ts +3 -1
  49. package/dist/cjs/util/assertions.d.ts +4 -0
  50. package/dist/cjs/util/assertions.js +12 -1
  51. package/dist/cjs/version.js +1 -1
  52. package/dist/mjs/Editor.d.ts +73 -40
  53. package/dist/mjs/Editor.mjs +90 -24
  54. package/dist/mjs/EditorImage.d.ts +58 -6
  55. package/dist/mjs/EditorImage.mjs +313 -61
  56. package/dist/mjs/SVGLoader.d.ts +10 -4
  57. package/dist/mjs/SVGLoader.mjs +29 -9
  58. package/dist/mjs/UndoRedoHistory.d.ts +2 -2
  59. package/dist/mjs/UndoRedoHistory.mjs +4 -2
  60. package/dist/mjs/Viewport.d.ts +2 -1
  61. package/dist/mjs/Viewport.mjs +12 -3
  62. package/dist/mjs/commands/Command.d.ts +1 -0
  63. package/dist/mjs/commands/Command.mjs +1 -0
  64. package/dist/mjs/commands/Erase.mjs +1 -1
  65. package/dist/mjs/commands/SerializableCommand.d.ts +1 -1
  66. package/dist/mjs/commands/SerializableCommand.mjs +16 -2
  67. package/dist/mjs/commands/localization.d.ts +2 -0
  68. package/dist/mjs/commands/localization.mjs +2 -0
  69. package/dist/mjs/components/AbstractComponent.d.ts +38 -0
  70. package/dist/mjs/components/AbstractComponent.mjs +30 -0
  71. package/dist/mjs/components/BackgroundComponent.d.ts +10 -1
  72. package/dist/mjs/components/BackgroundComponent.mjs +37 -6
  73. package/dist/mjs/components/SVGGlobalAttributesObject.d.ts +2 -1
  74. package/dist/mjs/components/SVGGlobalAttributesObject.mjs +7 -1
  75. package/dist/mjs/components/Stroke.d.ts +1 -0
  76. package/dist/mjs/components/Stroke.mjs +44 -0
  77. package/dist/mjs/components/UnknownSVGObject.d.ts +2 -1
  78. package/dist/mjs/components/UnknownSVGObject.mjs +7 -1
  79. package/dist/mjs/lib.d.ts +2 -45
  80. package/dist/mjs/lib.mjs +2 -45
  81. package/dist/mjs/rendering/RenderingStyle.d.ts +1 -0
  82. package/dist/mjs/rendering/renderers/AbstractRenderer.mjs +1 -1
  83. package/dist/mjs/shortcuts/KeyboardShortcutManager.d.ts +2 -2
  84. package/dist/mjs/shortcuts/KeyboardShortcutManager.mjs +2 -2
  85. package/dist/mjs/toolbar/localization.d.ts +1 -0
  86. package/dist/mjs/toolbar/localization.mjs +1 -0
  87. package/dist/mjs/toolbar/widgets/BaseWidget.mjs +5 -0
  88. package/dist/mjs/toolbar/widgets/DocumentPropertiesWidget.mjs +54 -25
  89. package/dist/mjs/toolbar/widgets/components/makeGridSelector.mjs +8 -0
  90. package/dist/mjs/tools/PanZoom.mjs +13 -8
  91. package/dist/mjs/tools/ScrollbarTool.d.ts +18 -0
  92. package/dist/mjs/tools/ScrollbarTool.mjs +79 -0
  93. package/dist/mjs/tools/SelectionTool/SelectionTool.selecting.test.d.ts +1 -0
  94. package/dist/mjs/tools/ToolController.mjs +2 -0
  95. package/dist/mjs/types.d.ts +3 -1
  96. package/dist/mjs/util/assertions.d.ts +4 -0
  97. package/dist/mjs/util/assertions.mjs +10 -0
  98. package/dist/mjs/version.mjs +1 -1
  99. package/package.json +3 -4
  100. package/src/Editor.scss +8 -0
  101. package/src/dialogs/dialogs.scss +2 -1
  102. package/src/toolbar/EdgeToolbar.scss +4 -1
  103. package/src/toolbar/widgets/DocumentPropertiesWidget.scss +12 -0
  104. package/src/toolbar/widgets/components/makeGridSelector.scss +1 -1
  105. package/src/tools/ScrollbarTool.scss +57 -0
  106. package/src/tools/{SoundUITool.css → SoundUITool.scss} +4 -0
  107. package/src/tools/tools.scss +2 -1
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.assertIsNumberArray = exports.assertIsNumber = exports.assertUnreachable = void 0;
3
+ exports.assertIsBoolean = exports.assertIsNumberArray = exports.assertIsNumber = exports.assertUnreachable = void 0;
4
4
  /**
5
5
  * Compile-time assertion that a branch of code is unreachable.
6
6
  * @internal
@@ -49,3 +49,14 @@ const assertIsNumberArray = (values, allowNaN = false) => {
49
49
  return true;
50
50
  };
51
51
  exports.assertIsNumberArray = assertIsNumberArray;
52
+ /**
53
+ * Throws an exception if `typeof value` is not a boolean.
54
+ */
55
+ const assertIsBoolean = (value) => {
56
+ if (typeof value !== 'boolean') {
57
+ throw new Error('Given value is not a boolean');
58
+ // return false;
59
+ }
60
+ return true;
61
+ };
62
+ exports.assertIsBoolean = assertIsBoolean;
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = {
4
- number: '1.2.2',
4
+ number: '1.3.0',
5
5
  };
@@ -16,7 +16,13 @@ import KeyBinding from './shortcuts/KeyBinding';
16
16
  import AbstractToolbar from './toolbar/AbstractToolbar';
17
17
  import RenderablePathSpec from './rendering/RenderablePathSpec';
18
18
  import { AboutDialogEntry } from './dialogs/makeAboutDialog';
19
- /** Provides settings to an instance of an editor. See the Editor {@link Editor.constructor}. */
19
+ /**
20
+ * Provides settings to an instance of an editor. See the Editor {@link Editor.constructor}.
21
+ *
22
+ * ## Example
23
+ *
24
+ * [[include:doc-pages/inline-examples/settings-example-1.md]]
25
+ */
20
26
  export interface EditorSettings {
21
27
  /** Defaults to `RenderingMode.CanvasRenderer` */
22
28
  renderingMode: RenderingMode;
@@ -36,41 +42,21 @@ export interface EditorSettings {
36
42
  * Overrides for keyboard shortcuts. For example,
37
43
  * ```ts
38
44
  * {
39
- * 'some.shortcut.id': [ ShortcutManager.keyboardShortcutFromString('ctrl+a') ],
45
+ * 'some.shortcut.id': [ KeyBinding.keyboardShortcutFromString('ctrl+a') ],
40
46
  * 'another.shortcut.id': [ ]
41
47
  * }
42
48
  * ```
43
49
  * where shortcut IDs map to lists of associated keybindings.
50
+ *
51
+ * @see
52
+ * - {@link KeyBinding}
53
+ * - {@link KeyboardShortcutManager}
44
54
  */
45
55
  keyboardShortcutOverrides: Record<string, Array<KeyBinding>>;
46
56
  /**
47
57
  * Provides a set of icons for the editor.
48
58
  *
49
59
  * See, for example, the `@js-draw/material-icons` package.
50
- *
51
- * @example
52
- * ```ts,runnable
53
- * import * as jsdraw from 'js-draw';
54
- * import MaterialIconProvider from '@js-draw/material-icons';
55
- * import 'js-draw/styles';
56
- *
57
- * const settings: Partial<jsdraw.EditorSettings> = {
58
- * // Default to material icons
59
- * iconProvider: new MaterialIconProvider(),
60
- *
61
- * // Only scroll the editor if it's focused.
62
- * wheelEventsEnabled: 'only-if-focused',
63
- * };
64
- *
65
- * // Add an editor to the document, using the above settings
66
- * const editor = new jsdraw.Editor(document.body, settings);
67
- *
68
- * // Add a toolbar to the editor
69
- * const toolbar = jsdraw.makeEdgeToolbar(editor);
70
- *
71
- * // Add the default tool items
72
- * toolbar.addDefaults();
73
- * ```
74
60
  */
75
61
  iconProvider: IconProvider;
76
62
  /**
@@ -81,20 +67,22 @@ export interface EditorSettings {
81
67
  /**
82
68
  * The main entrypoint for the full editor.
83
69
  *
84
- * @example
70
+ * ## Example
85
71
  * To create an editor with a toolbar,
86
- * ```
72
+ * ```ts,runnable
73
+ * import { Editor } from 'js-draw';
74
+ *
87
75
  * const editor = new Editor(document.body);
88
76
  *
89
77
  * const toolbar = editor.addToolbar();
90
- * toolbar.addActionButton('Save', () => {
78
+ * toolbar.addSaveButton(() => {
91
79
  * const saveData = editor.toSVG().outerHTML;
92
80
  * // Do something with saveData...
93
81
  * });
94
82
  * ```
95
83
  *
96
84
  * See also
97
- * [`docs/example/example.ts`](https://github.com/personalizedrefrigerator/js-draw/blob/main/docs/demo/example.ts#L15).
85
+ * [`docs/example/example.ts`](https://github.com/personalizedrefrigerator/js-draw/blob/main/docs/demo/example.ts).
98
86
  */
99
87
  export declare class Editor {
100
88
  private container;
@@ -120,12 +108,13 @@ export declare class Editor {
120
108
  * Data structure for adding/removing/querying objects in the image.
121
109
  *
122
110
  * @example
123
- * ```
111
+ * ```ts,runnable
112
+ * import { Editor, Stroke, Path, Color4, pathToRenderable } from 'js-draw';
124
113
  * const editor = new Editor(document.body);
125
114
  *
126
115
  * // Create a path.
127
116
  * const stroke = new Stroke([
128
- * Path.fromString('M0,0 L30,30 z').toRenderable({ fill: Color4.black }),
117
+ * pathToRenderable(Path.fromString('M0,0 L100,100 L300,30 z'), { fill: Color4.red }),
129
118
  * ]);
130
119
  * const addElementCommand = editor.image.addElement(stroke);
131
120
  *
@@ -166,7 +155,9 @@ export declare class Editor {
166
155
  private settings;
167
156
  /**
168
157
  * @example
169
- * ```
158
+ * ```ts,runnable
159
+ * import { Editor } from 'js-draw';
160
+ *
170
161
  * const container = document.body;
171
162
  *
172
163
  * // Create an editor
@@ -179,13 +170,16 @@ export declare class Editor {
179
170
  * // Add the default toolbar
180
171
  * const toolbar = editor.addToolbar();
181
172
  *
182
- * // Add a save button
173
+ * const createCustomIcon = () => {
174
+ * // Create/return an icon here.
175
+ * };
176
+ *
177
+ * // Add a custom button
183
178
  * toolbar.addActionButton({
184
- * label: 'Save'
185
- * icon: createSaveIcon(),
179
+ * label: 'Custom Button'
180
+ * icon: createCustomIcon(),
186
181
  * }, () => {
187
- * const saveData = editor.toSVG().outerHTML;
188
- * // Do something with saveData
182
+ * // Do something here
189
183
  * });
190
184
  * ```
191
185
  */
@@ -330,6 +324,9 @@ export declare class Editor {
330
324
  /**
331
325
  * Clears the wet ink display.
332
326
  *
327
+ * The wet ink display can be used by the currently active tool to display a preview
328
+ * of an in-progress action.
329
+ *
333
330
  * @see {@link Display.getWetInkRenderer}
334
331
  */
335
332
  clearWetInk(): void;
@@ -341,12 +338,19 @@ export declare class Editor {
341
338
  * Creates an element that will be positioned on top of the dry/wet ink
342
339
  * renderers.
343
340
  *
341
+ * So as not to change the position of other overlays, `overlay` should either
342
+ * be styled to have 0 height or have `position: absolute`.
343
+ *
344
344
  * This is useful for displaying content on top of the rendered content
345
345
  * (e.g. a selection box).
346
346
  */
347
347
  createHTMLOverlay(overlay: HTMLElement): {
348
348
  remove: () => void;
349
349
  };
350
+ /**
351
+ * Creates a CSS stylesheet with `content` and applies it to the document
352
+ * (and thus, to this editor).
353
+ */
350
354
  addStyleSheet(content: string): HTMLStyleElement;
351
355
  /**
352
356
  * Dispatch a keyboard event to the currently selected tool.
@@ -370,7 +374,21 @@ export declare class Editor {
370
374
  */
371
375
  sendPenEvent(eventType: InputEvtType.PointerDownEvt | InputEvtType.PointerMoveEvt | InputEvtType.PointerUpEvt, point: Point2, allPointers?: Pointer[]): void;
372
376
  addAndCenterComponents(components: AbstractComponent[], selectComponents?: boolean): Promise<void>;
377
+ /**
378
+ * Get a data URL (e.g. as produced by `HTMLCanvasElement::toDataURL`).
379
+ * If `format` is not `image/png`, a PNG image URL may still be returned (as in the
380
+ * case of `HTMLCanvasElement::toDataURL`).
381
+ *
382
+ * The export resolution is the same as the size of the drawing canvas, unless `outputSize`
383
+ * is given.
384
+ */
373
385
  toDataURL(format?: 'image/png' | 'image/jpeg' | 'image/webp', outputSize?: Vec2): string;
386
+ /**
387
+ * Converts the editor's content into an SVG image.
388
+ *
389
+ * @see
390
+ * {@link SVGRenderer}
391
+ */
374
392
  toSVG(): SVGElement;
375
393
  /**
376
394
  * Load editor data from an `ImageLoader` (e.g. an {@link SVGLoader}).
@@ -393,8 +411,23 @@ export declare class Editor {
393
411
  /**
394
412
  * Alias for `loadFrom(SVGLoader.fromString)`.
395
413
  *
396
- * This is particularly useful when accessing a bundled version of the editor,
397
- * where `SVGLoader.fromString` is unavailable.
414
+ * @example
415
+ * ```ts,runnable
416
+ * import {Editor} from 'js-draw';
417
+ * const editor = new Editor(document.body);
418
+ *
419
+ * ---visible---
420
+ * await editor.loadFromSVG(`
421
+ * <svg viewBox="5 23 52 30" width="52" height="16" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
422
+ * <text style="
423
+ * transform: matrix(0.181846, 0.1, 0, 0.181846, 11.4, 33.2);
424
+ * font-family: serif;
425
+ * font-size: 32px;
426
+ * fill: rgb(100, 140, 61);
427
+ * ">An SVG image!</text>
428
+ * </svg>
429
+ * `);
430
+ * ```
398
431
  */
399
432
  loadFromSVG(svgData: string, sanitize?: boolean): Promise<void>;
400
433
  private closeAboutDialog;
@@ -8,7 +8,7 @@ import EventDispatcher from './EventDispatcher.mjs';
8
8
  import { Vec2, Vec3, Color4, Mat33, toRoundedString } from '@js-draw/math';
9
9
  import Display, { RenderingMode } from './rendering/Display.mjs';
10
10
  import SVGRenderer from './rendering/renderers/SVGRenderer.mjs';
11
- import SVGLoader from './SVGLoader.mjs';
11
+ import SVGLoader, { svgLoaderAutoresizeClassName } from './SVGLoader.mjs';
12
12
  import Pointer from './Pointer.mjs';
13
13
  import getLocalizationTable from './localizations/getLocalizationTable.mjs';
14
14
  import IconProvider from './toolbar/IconProvider.mjs';
@@ -29,25 +29,29 @@ import version from './version.mjs';
29
29
  /**
30
30
  * The main entrypoint for the full editor.
31
31
  *
32
- * @example
32
+ * ## Example
33
33
  * To create an editor with a toolbar,
34
- * ```
34
+ * ```ts,runnable
35
+ * import { Editor } from 'js-draw';
36
+ *
35
37
  * const editor = new Editor(document.body);
36
38
  *
37
39
  * const toolbar = editor.addToolbar();
38
- * toolbar.addActionButton('Save', () => {
40
+ * toolbar.addSaveButton(() => {
39
41
  * const saveData = editor.toSVG().outerHTML;
40
42
  * // Do something with saveData...
41
43
  * });
42
44
  * ```
43
45
  *
44
46
  * See also
45
- * [`docs/example/example.ts`](https://github.com/personalizedrefrigerator/js-draw/blob/main/docs/demo/example.ts#L15).
47
+ * [`docs/example/example.ts`](https://github.com/personalizedrefrigerator/js-draw/blob/main/docs/demo/example.ts).
46
48
  */
47
49
  export class Editor {
48
50
  /**
49
51
  * @example
50
- * ```
52
+ * ```ts,runnable
53
+ * import { Editor } from 'js-draw';
54
+ *
51
55
  * const container = document.body;
52
56
  *
53
57
  * // Create an editor
@@ -60,13 +64,16 @@ export class Editor {
60
64
  * // Add the default toolbar
61
65
  * const toolbar = editor.addToolbar();
62
66
  *
63
- * // Add a save button
67
+ * const createCustomIcon = () => {
68
+ * // Create/return an icon here.
69
+ * };
70
+ *
71
+ * // Add a custom button
64
72
  * toolbar.addActionButton({
65
- * label: 'Save'
66
- * icon: createSaveIcon(),
73
+ * label: 'Custom Button'
74
+ * icon: createCustomIcon(),
67
75
  * }, () => {
68
- * const saveData = editor.toSVG().outerHTML;
69
- * // Do something with saveData
76
+ * // Do something here
70
77
  * });
71
78
  * ```
72
79
  */
@@ -99,6 +106,10 @@ export class Editor {
99
106
  iconProvider: settings.iconProvider ?? new IconProvider(),
100
107
  notices: [],
101
108
  };
109
+ // Validate settings
110
+ if (this.settings.minZoom > this.settings.maxZoom) {
111
+ throw new Error('Minimum zoom must be lesser than maximum zoom!');
112
+ }
102
113
  this.icons = this.settings.iconProvider;
103
114
  this.shortcuts = new KeyboardShortcutManager(this.settings.keyboardShortcutOverrides);
104
115
  this.container = document.createElement('div');
@@ -153,6 +164,10 @@ export class Editor {
153
164
  if (oldZoom <= this.settings.maxZoom && oldZoom >= this.settings.minZoom) {
154
165
  resetTransform = evt.oldTransform;
155
166
  }
167
+ else {
168
+ // If 1x zoom isn't acceptable, try a zoom between the minimum and maximum.
169
+ resetTransform = Mat33.scaling2D((this.settings.minZoom + this.settings.maxZoom) / 2);
170
+ }
156
171
  this.viewport.resetTransform(resetTransform);
157
172
  }
158
173
  }
@@ -206,6 +221,7 @@ export class Editor {
206
221
  registerListeners() {
207
222
  this.handlePointerEventsFrom(this.renderingRegion);
208
223
  this.handleKeyEventsFrom(this.renderingRegion);
224
+ this.handlePointerEventsFrom(this.accessibilityAnnounceArea);
209
225
  this.container.addEventListener('wheel', evt => {
210
226
  let delta = Vec3.of(evt.deltaX, evt.deltaY, evt.deltaZ);
211
227
  // Process wheel events if the ctrl key is down, even if disabled -- we do want to handle
@@ -282,6 +298,8 @@ export class Editor {
282
298
  // still fill the screen.
283
299
  this.container.style.setProperty('--editor-current-width-px', `${this.container.clientWidth}px`);
284
300
  this.container.style.setProperty('--editor-current-height-px', `${this.container.clientHeight}px`);
301
+ this.container.style.setProperty('--editor-current-display-width-px', `${this.renderingRegion.clientWidth}px`);
302
+ this.container.style.setProperty('--editor-current-display-height-px', `${this.renderingRegion.clientHeight}px`);
285
303
  }
286
304
  getPointerList() {
287
305
  const nowTime = performance.now();
@@ -702,12 +720,12 @@ export class Editor {
702
720
  this.display.setDraftMode(false);
703
721
  this.hideLoadingWarning();
704
722
  }
705
- // @see {@link #asyncApplyOrUnapplyCommands }
723
+ // @see {@link asyncApplyOrUnapplyCommands }
706
724
  asyncApplyCommands(commands, chunkSize) {
707
725
  return this.asyncApplyOrUnapplyCommands(commands, true, chunkSize);
708
726
  }
709
727
  // If `unapplyInReverseOrder`, commands are reversed before unapplying.
710
- // @see {@link #asyncApplyOrUnapplyCommands }
728
+ // @see {@link asyncApplyOrUnapplyCommands }
711
729
  asyncUnapplyCommands(commands, chunkSize, unapplyInReverseOrder = false) {
712
730
  if (unapplyInReverseOrder) {
713
731
  commands = [...commands]; // copy
@@ -754,8 +772,8 @@ export class Editor {
754
772
  }
755
773
  const renderer = this.display.getDryInkRenderer();
756
774
  this.image.renderWithCache(renderer, this.display.getCache(), this.viewport);
757
- if (showImageBounds) {
758
- // Draw a rectangle around the region that will be visible on save
775
+ // Draw a rectangle around the region that will be visible on save
776
+ if (showImageBounds && !this.image.getAutoresizeEnabled()) {
759
777
  const exportRectFill = { fill: Color4.fromHex('#44444455') };
760
778
  const exportRectStrokeWidth = 5 * this.viewport.getSizeOfPixelOnCanvas();
761
779
  renderer.drawRect(this.getImportExportRect(), exportRectStrokeWidth, exportRectFill);
@@ -778,6 +796,9 @@ export class Editor {
778
796
  /**
779
797
  * Clears the wet ink display.
780
798
  *
799
+ * The wet ink display can be used by the currently active tool to display a preview
800
+ * of an in-progress action.
801
+ *
781
802
  * @see {@link Display.getWetInkRenderer}
782
803
  */
783
804
  clearWetInk() {
@@ -793,16 +814,23 @@ export class Editor {
793
814
  * Creates an element that will be positioned on top of the dry/wet ink
794
815
  * renderers.
795
816
  *
817
+ * So as not to change the position of other overlays, `overlay` should either
818
+ * be styled to have 0 height or have `position: absolute`.
819
+ *
796
820
  * This is useful for displaying content on top of the rendered content
797
821
  * (e.g. a selection box).
798
822
  */
799
823
  createHTMLOverlay(overlay) {
800
- overlay.classList.add('overlay');
824
+ overlay.classList.add('overlay', 'js-draw-editor-overlay');
801
825
  this.container.appendChild(overlay);
802
826
  return {
803
827
  remove: () => overlay.remove(),
804
828
  };
805
829
  }
830
+ /**
831
+ * Creates a CSS stylesheet with `content` and applies it to the document
832
+ * (and thus, to this editor).
833
+ */
806
834
  addStyleSheet(content) {
807
835
  const styleSheet = document.createElement('style');
808
836
  styleSheet.innerText = content;
@@ -883,11 +911,14 @@ export class Editor {
883
911
  }
884
912
  }
885
913
  }
886
- // Get a data URL (e.g. as produced by `HTMLCanvasElement::toDataURL`).
887
- // If `format` is not `image/png`, a PNG image URL may still be returned (as in the
888
- // case of `HTMLCanvasElement::toDataURL`).
889
- //
890
- // The export resolution is the same as the size of the drawing canvas.
914
+ /**
915
+ * Get a data URL (e.g. as produced by `HTMLCanvasElement::toDataURL`).
916
+ * If `format` is not `image/png`, a PNG image URL may still be returned (as in the
917
+ * case of `HTMLCanvasElement::toDataURL`).
918
+ *
919
+ * The export resolution is the same as the size of the drawing canvas, unless `outputSize`
920
+ * is given.
921
+ */
891
922
  toDataURL(format = 'image/png', outputSize) {
892
923
  const canvas = document.createElement('canvas');
893
924
  const importExportViewport = this.image.getImportExportViewport();
@@ -904,6 +935,12 @@ export class Editor {
904
935
  const dataURL = canvas.toDataURL(format);
905
936
  return dataURL;
906
937
  }
938
+ /**
939
+ * Converts the editor's content into an SVG image.
940
+ *
941
+ * @see
942
+ * {@link SVGRenderer}
943
+ */
907
944
  toSVG() {
908
945
  const importExportViewport = this.image.getImportExportViewport().getTemporaryClone();
909
946
  const sanitize = false;
@@ -919,6 +956,12 @@ export class Editor {
919
956
  result.setAttribute('viewBox', [rect.x, rect.y, rect.w, rect.h].map(part => toRoundedString(part)).join(' '));
920
957
  result.setAttribute('width', toRoundedString(rect.w));
921
958
  result.setAttribute('height', toRoundedString(rect.h));
959
+ if (this.image.getAutoresizeEnabled()) {
960
+ result.classList.add(svgLoaderAutoresizeClassName);
961
+ }
962
+ else {
963
+ result.classList.remove(svgLoaderAutoresizeClassName);
964
+ }
922
965
  return result;
923
966
  }
924
967
  /**
@@ -931,6 +974,7 @@ export class Editor {
931
974
  this.display.setDraftMode(true);
932
975
  const originalBackgrounds = this.image.getBackgroundComponents();
933
976
  const eraseBackgroundCommand = new Erase(originalBackgrounds);
977
+ let autoresizeEnabled = false;
934
978
  await loader.start(async (component) => {
935
979
  await this.dispatchNoAnnounce(EditorImage.addElement(component));
936
980
  }, (countProcessed, totalToProcess) => {
@@ -940,10 +984,17 @@ export class Editor {
940
984
  return untilNextAnimationFrame();
941
985
  }
942
986
  return null;
943
- }, (importExportRect) => {
987
+ }, (importExportRect, options) => {
944
988
  this.dispatchNoAnnounce(this.setImportExportRect(importExportRect), false);
945
989
  this.dispatchNoAnnounce(this.viewport.zoomTo(importExportRect), false);
990
+ if (options) {
991
+ autoresizeEnabled = options.autoresize;
992
+ }
946
993
  });
994
+ // TODO: Move this call into the callback above. Currently, this would cause
995
+ // decrease in performance as the main background would be repeatedly added
996
+ // and removed from the editor every time another component is added.
997
+ this.dispatchNoAnnounce(this.image.setAutoresizeEnabled(autoresizeEnabled), false);
947
998
  // Ensure that we don't have multiple overlapping BackgroundComponents. Remove
948
999
  // old BackgroundComponents.
949
1000
  // Overlapping BackgroundComponents may cause changing the background color to
@@ -1004,8 +1055,23 @@ export class Editor {
1004
1055
  /**
1005
1056
  * Alias for `loadFrom(SVGLoader.fromString)`.
1006
1057
  *
1007
- * This is particularly useful when accessing a bundled version of the editor,
1008
- * where `SVGLoader.fromString` is unavailable.
1058
+ * @example
1059
+ * ```ts,runnable
1060
+ * import {Editor} from 'js-draw';
1061
+ * const editor = new Editor(document.body);
1062
+ *
1063
+ * ---visible---
1064
+ * await editor.loadFromSVG(`
1065
+ * <svg viewBox="5 23 52 30" width="52" height="16" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
1066
+ * <text style="
1067
+ * transform: matrix(0.181846, 0.1, 0, 0.181846, 11.4, 33.2);
1068
+ * font-family: serif;
1069
+ * font-size: 32px;
1070
+ * fill: rgb(100, 140, 61);
1071
+ * ">An SVG image!</text>
1072
+ * </svg>
1073
+ * `);
1074
+ * ```
1009
1075
  */
1010
1076
  async loadFromSVG(svgData, sanitize = false) {
1011
1077
  const loader = SVGLoader.fromString(svgData, sanitize);
@@ -5,9 +5,11 @@ import { Rect2 } from '@js-draw/math';
5
5
  import RenderingCache from './rendering/caching/RenderingCache';
6
6
  import SerializableCommand from './commands/SerializableCommand';
7
7
  import EventDispatcher from './EventDispatcher';
8
+ import Command from './commands/Command';
8
9
  export declare const sortLeavesByZIndex: (leaves: Array<ImageNode>) => void;
9
10
  export declare enum EditorImageEventType {
10
- ExportViewportChanged = 0
11
+ ExportViewportChanged = 0,
12
+ AutoresizeModeChanged = 1
11
13
  }
12
14
  export type EditorImageNotifier = EventDispatcher<EditorImageEventType, {
13
15
  image: EditorImage;
@@ -19,6 +21,7 @@ export default class EditorImage {
19
21
  private componentCount;
20
22
  /** Viewport for the exported/imported image. */
21
23
  private importExportViewport;
24
+ private shouldAutoresizeExportViewport;
22
25
  readonly notifier: EditorImageNotifier;
23
26
  constructor();
24
27
  getBackgroundComponents(): AbstractComponent[];
@@ -35,14 +38,20 @@ export default class EditorImage {
35
38
  render(renderer: AbstractRenderer, viewport: Viewport | null): void;
36
39
  /** Renders all nodes, even ones not within the viewport. @internal */
37
40
  renderAll(renderer: AbstractRenderer): void;
38
- /** @returns all elements in the image, sorted by z-index. This can be slow for large images. */
41
+ /**
42
+ * @returns all elements in the image, sorted by z-index. This can be slow for large images.
43
+ *
44
+ * Does not include background elements. See {@link getBackgroundComponents}.
45
+ */
39
46
  getAllElements(): AbstractComponent[];
40
47
  /** Returns the number of elements added to this image. @internal */
41
48
  estimateNumElements(): number;
42
49
  /** @returns a list of `AbstractComponent`s intersecting `region`, sorted by z-index. */
43
- getElementsIntersectingRegion(region: Rect2): AbstractComponent[];
44
- /** Called whenever an element is completely removed. @internal */
50
+ getElementsIntersectingRegion(region: Rect2, includeBackground?: boolean): AbstractComponent[];
51
+ /** Called whenever (just after) an element is completely removed. @internal */
45
52
  onDestroyElement(elem: AbstractComponent): void;
53
+ /** Called just after an element is added. @internal */
54
+ private onElementAdded;
46
55
  /**
47
56
  * @returns the AbstractComponent with `id`, if it exists.
48
57
  *
@@ -66,11 +75,40 @@ export default class EditorImage {
66
75
  * @returns a `Viewport` for rendering the image when importing/exporting.
67
76
  */
68
77
  getImportExportViewport(): Viewport;
78
+ /**
79
+ * @see {@link setImportExportRect}
80
+ */
81
+ getImportExportRect(): Rect2;
82
+ /**
83
+ * Sets the import/export rectangle to the given `imageRect`. Disables
84
+ * autoresize (if it was previously enabled).
85
+ */
69
86
  setImportExportRect(imageRect: Rect2): SerializableCommand;
87
+ getAutoresizeEnabled(): boolean;
88
+ /** Returns a `Command` that sets whether the image should autoresize. */
89
+ setAutoresizeEnabled(autoresize: boolean): Command;
90
+ private setAutoresizeEnabledDirectly;
91
+ /** Updates the size/position of the viewport */
92
+ private autoresizeExportViewport;
93
+ private settingExportRect;
94
+ /**
95
+ * Sets the import/export viewport directly, without returning a `Command`.
96
+ * As such, this is not undoable.
97
+ *
98
+ * See setImportExportRect
99
+ *
100
+ * Returns true if changes to the viewport were made (and thus
101
+ * ExportViewportChanged was fired.)
102
+ */
103
+ private setExportRectDirectly;
104
+ private onExportViewportChanged;
70
105
  private static SetImportExportRectCommand;
71
106
  }
72
107
  type TooSmallToRenderCheck = (rect: Rect2) => boolean;
73
- /** Part of the Editor's image. @internal */
108
+ /**
109
+ * Part of the Editor's image. Does not handle fullscreen/invisible components.
110
+ * @internal
111
+ */
74
112
  export declare class ImageNode {
75
113
  private parent;
76
114
  private content;
@@ -84,17 +122,31 @@ export declare class ImageNode {
84
122
  onContentChange(): void;
85
123
  getContent(): AbstractComponent | null;
86
124
  getParent(): ImageNode | null;
87
- private getChildrenIntersectingRegion;
125
+ protected getChildrenIntersectingRegion(region: Rect2, isTooSmallFilter?: TooSmallToRenderCheck): ImageNode[];
88
126
  getChildrenOrSelfIntersectingRegion(region: Rect2): ImageNode[];
89
127
  getLeavesIntersectingRegion(region: Rect2, isTooSmall?: TooSmallToRenderCheck): ImageNode[];
90
128
  getChildWithContent(target: AbstractComponent): ImageNode | null;
91
129
  getLeaves(): ImageNode[];
92
130
  addLeaf(leaf: AbstractComponent): ImageNode;
131
+ protected static createLeafNode(parent: ImageNode, content: AbstractComponent): ImageNode;
93
132
  getBBox(): Rect2;
94
133
  recomputeBBox(bubbleUp: boolean): void;
134
+ private unionBBoxWith;
95
135
  private updateParents;
96
136
  private rebalance;
137
+ protected removeChild(child: ImageNode): void;
97
138
  remove(): void;
98
139
  render(renderer: AbstractRenderer, visibleRect?: Rect2): void;
140
+ renderDebugBoundingBoxes(renderer: AbstractRenderer, visibleRect: Rect2, depth?: number): void;
141
+ }
142
+ /** An `ImageNode` that can properly handle fullscreen/data components. @internal */
143
+ export declare class RootImageNode extends ImageNode {
144
+ private fullscreenChildren;
145
+ private dataComponents;
146
+ protected getChildrenIntersectingRegion(region: Rect2, _isTooSmall?: TooSmallToRenderCheck): ImageNode[];
147
+ getLeaves(): ImageNode[];
148
+ removeChild(child: ImageNode): void;
149
+ getChildWithContent(target: AbstractComponent): ImageNode | null;
150
+ addLeaf(leafContent: AbstractComponent): ImageNode;
99
151
  }
100
152
  export {};