js-draw 0.1.12 → 0.2.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 (122) hide show
  1. package/.eslintrc.js +1 -0
  2. package/.firebaserc +5 -0
  3. package/.github/workflows/firebase-hosting-merge.yml +25 -0
  4. package/.github/workflows/firebase-hosting-pull-request.yml +22 -0
  5. package/.github/workflows/github-pages.yml +52 -0
  6. package/CHANGELOG.md +6 -0
  7. package/README.md +11 -6
  8. package/dist/bundle.js +1 -1
  9. package/dist/src/Color4.d.ts +19 -0
  10. package/dist/src/Color4.js +24 -3
  11. package/dist/src/Editor.d.ts +129 -2
  12. package/dist/src/Editor.js +94 -17
  13. package/dist/src/EditorImage.d.ts +7 -2
  14. package/dist/src/EditorImage.js +41 -25
  15. package/dist/src/EventDispatcher.d.ts +18 -0
  16. package/dist/src/EventDispatcher.js +19 -4
  17. package/dist/src/Pointer.js +3 -2
  18. package/dist/src/UndoRedoHistory.js +15 -2
  19. package/dist/src/Viewport.js +4 -1
  20. package/dist/src/bundle/bundled.d.ts +1 -2
  21. package/dist/src/bundle/bundled.js +1 -2
  22. package/dist/src/commands/Duplicate.d.ts +1 -1
  23. package/dist/src/commands/Duplicate.js +3 -4
  24. package/dist/src/commands/Erase.d.ts +1 -1
  25. package/dist/src/commands/Erase.js +6 -5
  26. package/dist/src/commands/SerializableCommand.d.ts +4 -5
  27. package/dist/src/commands/SerializableCommand.js +12 -4
  28. package/dist/src/commands/invertCommand.d.ts +4 -0
  29. package/dist/src/commands/invertCommand.js +44 -0
  30. package/dist/src/commands/lib.d.ts +6 -0
  31. package/dist/src/commands/lib.js +6 -0
  32. package/dist/src/commands/localization.d.ts +1 -0
  33. package/dist/src/commands/localization.js +1 -0
  34. package/dist/src/components/AbstractComponent.d.ts +13 -8
  35. package/dist/src/components/AbstractComponent.js +26 -15
  36. package/dist/src/components/SVGGlobalAttributesObject.d.ts +1 -1
  37. package/dist/src/components/SVGGlobalAttributesObject.js +7 -1
  38. package/dist/src/components/Stroke.d.ts +12 -2
  39. package/dist/src/components/Stroke.js +10 -7
  40. package/dist/src/components/Text.d.ts +2 -2
  41. package/dist/src/components/Text.js +6 -6
  42. package/dist/src/components/UnknownSVGObject.d.ts +1 -1
  43. package/dist/src/components/UnknownSVGObject.js +6 -1
  44. package/dist/src/components/lib.d.ts +4 -0
  45. package/dist/src/components/lib.js +4 -0
  46. package/dist/src/lib.d.ts +25 -0
  47. package/dist/src/lib.js +25 -0
  48. package/dist/src/math/Mat33.d.ts +47 -1
  49. package/dist/src/math/Mat33.js +48 -20
  50. package/dist/src/math/Path.js +3 -3
  51. package/dist/src/math/Rect2.d.ts +2 -2
  52. package/dist/src/math/Vec3.d.ts +62 -0
  53. package/dist/src/math/Vec3.js +62 -14
  54. package/dist/src/math/lib.d.ts +7 -0
  55. package/dist/src/math/lib.js +7 -0
  56. package/dist/src/math/rounding.js +1 -0
  57. package/dist/src/rendering/Display.d.ts +44 -0
  58. package/dist/src/rendering/Display.js +45 -6
  59. package/dist/src/rendering/caching/CacheRecord.d.ts +1 -0
  60. package/dist/src/rendering/caching/CacheRecord.js +3 -0
  61. package/dist/src/rendering/caching/CacheRecordManager.d.ts +4 -3
  62. package/dist/src/rendering/caching/CacheRecordManager.js +16 -4
  63. package/dist/src/rendering/caching/RenderingCache.d.ts +2 -3
  64. package/dist/src/rendering/caching/RenderingCache.js +9 -10
  65. package/dist/src/rendering/caching/types.d.ts +1 -3
  66. package/dist/src/rendering/renderers/CanvasRenderer.js +1 -1
  67. package/dist/src/toolbar/HTMLToolbar.js +1 -0
  68. package/dist/src/toolbar/makeColorInput.js +1 -1
  69. package/dist/src/toolbar/widgets/PenWidget.js +1 -0
  70. package/dist/src/tools/Pen.d.ts +1 -2
  71. package/dist/src/tools/Pen.js +8 -1
  72. package/dist/src/tools/PipetteTool.js +1 -0
  73. package/dist/src/tools/SelectionTool.js +45 -22
  74. package/dist/src/types.d.ts +17 -6
  75. package/dist/src/types.js +7 -5
  76. package/firebase.json +16 -0
  77. package/package.json +118 -101
  78. package/src/Color4.ts +23 -2
  79. package/src/Editor.ts +147 -25
  80. package/src/EditorImage.ts +45 -27
  81. package/src/EventDispatcher.ts +21 -6
  82. package/src/Pointer.ts +3 -2
  83. package/src/UndoRedoHistory.ts +18 -2
  84. package/src/Viewport.ts +5 -2
  85. package/src/bundle/bundled.ts +1 -2
  86. package/src/commands/Duplicate.ts +3 -4
  87. package/src/commands/Erase.ts +6 -5
  88. package/src/commands/SerializableCommand.ts +17 -9
  89. package/src/commands/invertCommand.ts +51 -0
  90. package/src/commands/lib.ts +14 -0
  91. package/src/commands/localization.ts +2 -0
  92. package/src/components/AbstractComponent.ts +31 -20
  93. package/src/components/SVGGlobalAttributesObject.ts +8 -1
  94. package/src/components/Stroke.test.ts +1 -1
  95. package/src/components/Stroke.ts +11 -7
  96. package/src/components/Text.ts +6 -7
  97. package/src/components/UnknownSVGObject.ts +7 -1
  98. package/src/components/lib.ts +9 -0
  99. package/src/lib.ts +28 -0
  100. package/src/math/Mat33.ts +48 -20
  101. package/src/math/Path.ts +3 -3
  102. package/src/math/Rect2.ts +2 -2
  103. package/src/math/Vec3.ts +62 -14
  104. package/src/math/lib.ts +15 -0
  105. package/src/math/rounding.ts +2 -0
  106. package/src/rendering/Display.ts +46 -6
  107. package/src/rendering/caching/CacheRecord.test.ts +1 -1
  108. package/src/rendering/caching/CacheRecord.ts +4 -0
  109. package/src/rendering/caching/CacheRecordManager.ts +33 -7
  110. package/src/rendering/caching/RenderingCache.ts +10 -15
  111. package/src/rendering/caching/types.ts +1 -6
  112. package/src/rendering/renderers/CanvasRenderer.ts +1 -1
  113. package/src/toolbar/HTMLToolbar.ts +1 -0
  114. package/src/toolbar/makeColorInput.ts +1 -1
  115. package/src/toolbar/widgets/PenWidget.ts +2 -0
  116. package/src/tools/PanZoom.ts +0 -1
  117. package/src/tools/Pen.ts +11 -2
  118. package/src/tools/PipetteTool.ts +2 -0
  119. package/src/tools/SelectionTool.ts +46 -18
  120. package/src/types.ts +19 -3
  121. package/tsconfig.json +4 -1
  122. package/typedoc.json +20 -0
@@ -1,15 +1,34 @@
1
1
  export default class Color4 {
2
+ /** Red component. Should be in the range [0, 1]. */
2
3
  readonly r: number;
4
+ /** Green component. `g` ∈ [0, 1] */
3
5
  readonly g: number;
6
+ /** Blue component. `b` ∈ [0, 1] */
4
7
  readonly b: number;
8
+ /** Alpha/transparent component. `a` ∈ [0, 1] */
5
9
  readonly a: number;
6
10
  private constructor();
11
+ /**
12
+ * Create a color from red, green, blue components. The color is fully opaque (`a = 1.0`).
13
+ *
14
+ * Each component should be in the range [0, 1].
15
+ */
7
16
  static ofRGB(red: number, green: number, blue: number): Color4;
8
17
  static ofRGBA(red: number, green: number, blue: number, alpha: number): Color4;
9
18
  static fromHex(hexString: string): Color4;
19
+ /** Like fromHex, but can handle additional colors if an `HTMLCanvasElement` is available. */
10
20
  static fromString(text: string): Color4;
21
+ /** @returns true if `this` and `other` are approximately equal. */
11
22
  eq(other: Color4 | null | undefined): boolean;
12
23
  private hexString;
24
+ /**
25
+ * @returns a hexadecimal color string representation of `this`, in the form `#rrggbbaa`.
26
+ *
27
+ * @example
28
+ * ```
29
+ * Color4.red.toHexString(); // -> #ff0000ff
30
+ * ```
31
+ */
13
32
  toHexString(): string;
14
33
  static transparent: Color4;
15
34
  static red: Color4;
@@ -1,12 +1,24 @@
1
1
  export default class Color4 {
2
- constructor(r, g, b, a) {
2
+ constructor(
3
+ /** Red component. Should be in the range [0, 1]. */
4
+ r,
5
+ /** Green component. `g` ∈ [0, 1] */
6
+ g,
7
+ /** Blue component. `b` ∈ [0, 1] */
8
+ b,
9
+ /** Alpha/transparent component. `a` ∈ [0, 1] */
10
+ a) {
3
11
  this.r = r;
4
12
  this.g = g;
5
13
  this.b = b;
6
14
  this.a = a;
7
15
  this.hexString = null;
8
16
  }
9
- // Each component should be in the range [0, 1]
17
+ /**
18
+ * Create a color from red, green, blue components. The color is fully opaque (`a = 1.0`).
19
+ *
20
+ * Each component should be in the range [0, 1].
21
+ */
10
22
  static ofRGB(red, green, blue) {
11
23
  return Color4.ofRGBA(red, green, blue, 1.0);
12
24
  }
@@ -46,7 +58,7 @@ export default class Color4 {
46
58
  }
47
59
  return Color4.ofRGBA(components[0], components[1], components[2], components[3]);
48
60
  }
49
- // Like fromHex, but can handle additional colors if an HTML5Canvas is available.
61
+ /** Like fromHex, but can handle additional colors if an `HTMLCanvasElement` is available. */
50
62
  static fromString(text) {
51
63
  if (text.startsWith('#')) {
52
64
  return Color4.fromHex(text);
@@ -67,12 +79,21 @@ export default class Color4 {
67
79
  return Color4.ofRGBA(red, green, blue, alpha);
68
80
  }
69
81
  }
82
+ /** @returns true if `this` and `other` are approximately equal. */
70
83
  eq(other) {
71
84
  if (other == null) {
72
85
  return false;
73
86
  }
74
87
  return this.toHexString() === other.toHexString();
75
88
  }
89
+ /**
90
+ * @returns a hexadecimal color string representation of `this`, in the form `#rrggbbaa`.
91
+ *
92
+ * @example
93
+ * ```
94
+ * Color4.red.toHexString(); // -> #ff0000ff
95
+ * ```
96
+ */
76
97
  toHexString() {
77
98
  if (this.hexString) {
78
99
  return this.hexString;
@@ -1,3 +1,20 @@
1
+ /**
2
+ * The main entrypoint for the full editor.
3
+ *
4
+ * @example
5
+ * To create an editor with a toolbar,
6
+ * ```
7
+ * const editor = new Editor(document.body);
8
+ *
9
+ * const toolbar = editor.addToolbar();
10
+ * toolbar.addActionButton('Save', () => {
11
+ * const saveData = editor.toSVG().outerHTML;
12
+ * // Do something with saveData...
13
+ * });
14
+ * ```
15
+ *
16
+ * @packageDocumentation
17
+ */
1
18
  import EditorImage from './EditorImage';
2
19
  import ToolController from './tools/ToolController';
3
20
  import { InputEvtType, EditorNotifier, ImageLoader } from './types';
@@ -12,39 +29,143 @@ import Pointer from './Pointer';
12
29
  import Rect2 from './math/Rect2';
13
30
  import { EditorLocalization } from './localization';
14
31
  export interface EditorSettings {
32
+ /** Defaults to `RenderingMode.CanvasRenderer` */
15
33
  renderingMode: RenderingMode;
34
+ /** Uses a default English localization if a translation is not given. */
16
35
  localization: Partial<EditorLocalization>;
36
+ /**
37
+ * `true` if touchpad/mousewheel scrolling should scroll the editor instead of the document.
38
+ * This does not include pinch-zoom events.
39
+ * Defaults to true.
40
+ */
17
41
  wheelEventsEnabled: boolean | 'only-if-focused';
42
+ /** Minimum zoom fraction (e.g. 0.5 → 50% zoom). */
18
43
  minZoom: number;
19
44
  maxZoom: number;
20
45
  }
21
46
  export declare class Editor {
22
47
  private container;
23
48
  private renderingRegion;
24
- history: UndoRedoHistory;
25
49
  display: Display;
50
+ /**
51
+ * Handles undo/redo.
52
+ *
53
+ * @example
54
+ * ```
55
+ * const editor = new Editor(document.body);
56
+ *
57
+ * // Do something undoable.
58
+ * // ...
59
+ *
60
+ * // Undo the last action
61
+ * editor.history.undo();
62
+ * ```
63
+ */
64
+ history: UndoRedoHistory;
65
+ /**
66
+ * Data structure for adding/removing/querying objects in the image.
67
+ *
68
+ * @example
69
+ * ```
70
+ * const editor = new Editor(document.body);
71
+ *
72
+ * // Create a path.
73
+ * const stroke = new Stroke([
74
+ * Path.fromString('M0,0 L30,30 z').toRenderable({ fill: Color4.black }),
75
+ * ]);
76
+ * const addElementCommand = editor.image.addElement(stroke);
77
+ *
78
+ * // Add the stroke to the editor
79
+ * editor.dispatch(addElementCommand);
80
+ * ```
81
+ */
26
82
  image: EditorImage;
83
+ /** Viewport for the exported/imported image. */
27
84
  private importExportViewport;
85
+ /** @internal */
28
86
  localization: EditorLocalization;
29
87
  viewport: Viewport;
30
88
  toolController: ToolController;
89
+ /**
90
+ * Global event dispatcher/subscriber.
91
+ * @see {@link types.EditorEventType}
92
+ */
31
93
  notifier: EditorNotifier;
32
94
  private loadingWarning;
33
95
  private accessibilityAnnounceArea;
34
96
  private accessibilityControlArea;
35
97
  private settings;
98
+ /**
99
+ * @example
100
+ * ```
101
+ * const container = document.body;
102
+ *
103
+ * // Create an editor
104
+ * const editor = new Editor(container, {
105
+ * // 2e-10 and 1e12 are the default values for minimum/maximum zoom.
106
+ * minZoom: 2e-10,
107
+ * maxZoom: 1e12,
108
+ * });
109
+ *
110
+ * // Add the default toolbar
111
+ * const toolbar = editor.addToolbar();
112
+ * toolbar.addActionButton({
113
+ * label: 'Save'
114
+ * icon: createSaveIcon(),
115
+ * }, () => {
116
+ * const saveData = editor.toSVG().outerHTML;
117
+ * // Do something with saveData
118
+ * });
119
+ * ```
120
+ */
36
121
  constructor(parent: HTMLElement, settings?: Partial<EditorSettings>);
122
+ /**
123
+ * @returns a reference to the editor's container.
124
+ *
125
+ * @example
126
+ * ```
127
+ * editor.getRootElement().style.height = '500px';
128
+ * ```
129
+ */
37
130
  getRootElement(): HTMLElement;
131
+ /** @param fractionLoaded - should be a number from 0 to 1, where 1 represents completely loaded. */
38
132
  showLoadingWarning(fractionLoaded: number): void;
39
133
  hideLoadingWarning(): void;
40
134
  private previousAccessibilityAnnouncement;
41
135
  announceForAccessibility(message: string): void;
136
+ /**
137
+ * Creates a toolbar. If `defaultLayout` is true, default buttons are used.
138
+ * @returns a reference to the toolbar.
139
+ */
42
140
  addToolbar(defaultLayout?: boolean): HTMLToolbar;
43
141
  private registerListeners;
142
+ /** Adds event listners for keypresses to `elem` and forwards those events to the editor. */
44
143
  handleKeyEventsFrom(elem: HTMLElement): void;
144
+ /** `apply` a command. `command` will be announced for accessibility. */
45
145
  dispatch(command: Command, addToHistory?: boolean): void;
146
+ /**
147
+ * Dispatches a command without announcing it. By default, does not add to history.
148
+ * Use this to show finalized commands that don't need to have `announceForAccessibility`
149
+ * called.
150
+ *
151
+ * Prefer `command.apply(editor)` for incomplete commands. `dispatchNoAnnounce` may allow
152
+ * clients to listen for the application of commands (e.g. `SerializableCommand`s so they can
153
+ * be sent across the network), while `apply` does not.
154
+ *
155
+ * @example
156
+ * ```
157
+ * const addToHistory = false;
158
+ * editor.dispatchNoAnnounce(editor.viewport.zoomTo(someRectangle), addToHistory);
159
+ * ```
160
+ */
46
161
  dispatchNoAnnounce(command: Command, addToHistory?: boolean): void;
47
- private asyncApplyOrUnapplyCommands;
162
+ /**
163
+ * Apply a large transformation in chunks.
164
+ * If `apply` is `false`, the commands are unapplied.
165
+ * Triggers a re-render after each `updateChunkSize`-sized group of commands
166
+ * has been applied.
167
+ */
168
+ asyncApplyOrUnapplyCommands(commands: Command[], apply: boolean, updateChunkSize: number): Promise<void>;
48
169
  asyncApplyCommands(commands: Command[], chunkSize: number): Promise<void>;
49
170
  asyncUnapplyCommands(commands: Command[], chunkSize: number): Promise<void>;
50
171
  private announceUndoCallback;
@@ -64,6 +185,12 @@ export declare class Editor {
64
185
  loadFrom(loader: ImageLoader): Promise<void>;
65
186
  getImportExportRect(): Rect2;
66
187
  setImportExportRect(imageRect: Rect2): Command;
188
+ /**
189
+ * Alias for loadFrom(SVGLoader.fromString).
190
+ *
191
+ * This is particularly useful when accessing a bundled version of the editor,
192
+ * where `SVGLoader.fromString` is unavailable.
193
+ */
67
194
  loadFromSVG(svgData: string): Promise<void>;
68
195
  }
69
196
  export default Editor;
@@ -1,3 +1,20 @@
1
+ /**
2
+ * The main entrypoint for the full editor.
3
+ *
4
+ * @example
5
+ * To create an editor with a toolbar,
6
+ * ```
7
+ * const editor = new Editor(document.body);
8
+ *
9
+ * const toolbar = editor.addToolbar();
10
+ * toolbar.addActionButton('Save', () => {
11
+ * const saveData = editor.toSVG().outerHTML;
12
+ * // Do something with saveData...
13
+ * });
14
+ * ```
15
+ *
16
+ * @packageDocumentation
17
+ */
1
18
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
19
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
20
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -24,7 +41,31 @@ import SVGLoader from './SVGLoader';
24
41
  import Pointer from './Pointer';
25
42
  import Mat33 from './math/Mat33';
26
43
  import getLocalizationTable from './localizations/getLocalizationTable';
44
+ // { @inheritDoc Editor! }
27
45
  export class Editor {
46
+ /**
47
+ * @example
48
+ * ```
49
+ * const container = document.body;
50
+ *
51
+ * // Create an editor
52
+ * const editor = new Editor(container, {
53
+ * // 2e-10 and 1e12 are the default values for minimum/maximum zoom.
54
+ * minZoom: 2e-10,
55
+ * maxZoom: 1e12,
56
+ * });
57
+ *
58
+ * // Add the default toolbar
59
+ * const toolbar = editor.addToolbar();
60
+ * toolbar.addActionButton({
61
+ * label: 'Save'
62
+ * icon: createSaveIcon(),
63
+ * }, () => {
64
+ * const saveData = editor.toSVG().outerHTML;
65
+ * // Do something with saveData
66
+ * });
67
+ * ```
68
+ */
28
69
  constructor(parent, settings = {}) {
29
70
  var _a, _b, _c, _d;
30
71
  this.previousAccessibilityAnnouncement = '';
@@ -96,13 +137,18 @@ export class Editor {
96
137
  }
97
138
  });
98
139
  }
99
- // Returns a reference to this' container.
100
- // Example usage:
101
- // editor.getRootElement().style.height = '500px';
140
+ /**
141
+ * @returns a reference to the editor's container.
142
+ *
143
+ * @example
144
+ * ```
145
+ * editor.getRootElement().style.height = '500px';
146
+ * ```
147
+ */
102
148
  getRootElement() {
103
149
  return this.container;
104
150
  }
105
- // [fractionLoaded] should be a number from 0 to 1, where 1 represents completely loaded.
151
+ /** @param fractionLoaded - should be a number from 0 to 1, where 1 represents completely loaded. */
106
152
  showLoadingWarning(fractionLoaded) {
107
153
  const loadingPercent = Math.round(fractionLoaded * 100);
108
154
  this.loadingWarning.innerText = this.localization.loading(loadingPercent);
@@ -112,6 +158,8 @@ export class Editor {
112
158
  this.loadingWarning.style.display = 'none';
113
159
  this.announceForAccessibility(this.localization.doneLoading);
114
160
  }
161
+ // Announce `message` for screen readers. If `message` is the same as the previous
162
+ // message, it is re-announced.
115
163
  announceForAccessibility(message) {
116
164
  // Force re-announcing an announcement if announced again.
117
165
  if (message === this.previousAccessibilityAnnouncement) {
@@ -120,6 +168,10 @@ export class Editor {
120
168
  this.accessibilityAnnounceArea.innerText = message;
121
169
  this.previousAccessibilityAnnouncement = message;
122
170
  }
171
+ /**
172
+ * Creates a toolbar. If `defaultLayout` is true, default buttons are used.
173
+ * @returns a reference to the toolbar.
174
+ */
123
175
  addToolbar(defaultLayout = true) {
124
176
  const toolbar = new HTMLToolbar(this, this.container, this.localization);
125
177
  if (defaultLayout) {
@@ -254,8 +306,7 @@ export class Editor {
254
306
  this.accessibilityControlArea.value = '';
255
307
  });
256
308
  }
257
- // Adds event listners for keypresses to [elem] and forwards those events to the
258
- // editor.
309
+ /** Adds event listners for keypresses to `elem` and forwards those events to the editor. */
259
310
  handleKeyEventsFrom(elem) {
260
311
  elem.addEventListener('keydown', evt => {
261
312
  if (evt.key === 't' || evt.key === 'T') {
@@ -283,7 +334,7 @@ export class Editor {
283
334
  }
284
335
  });
285
336
  }
286
- // Adds to history by default
337
+ /** `apply` a command. `command` will be announced for accessibility. */
287
338
  dispatch(command, addToHistory = true) {
288
339
  if (addToHistory) {
289
340
  // .push applies [command] to this
@@ -294,7 +345,21 @@ export class Editor {
294
345
  }
295
346
  this.announceForAccessibility(command.description(this, this.localization));
296
347
  }
297
- // Dispatches a command without announcing it. By default, does not add to history.
348
+ /**
349
+ * Dispatches a command without announcing it. By default, does not add to history.
350
+ * Use this to show finalized commands that don't need to have `announceForAccessibility`
351
+ * called.
352
+ *
353
+ * Prefer `command.apply(editor)` for incomplete commands. `dispatchNoAnnounce` may allow
354
+ * clients to listen for the application of commands (e.g. `SerializableCommand`s so they can
355
+ * be sent across the network), while `apply` does not.
356
+ *
357
+ * @example
358
+ * ```
359
+ * const addToHistory = false;
360
+ * editor.dispatchNoAnnounce(editor.viewport.zoomTo(someRectangle), addToHistory);
361
+ * ```
362
+ */
298
363
  dispatchNoAnnounce(command, addToHistory = false) {
299
364
  if (addToHistory) {
300
365
  this.history.push(command);
@@ -303,10 +368,12 @@ export class Editor {
303
368
  command.apply(this);
304
369
  }
305
370
  }
306
- // Apply a large transformation in chunks.
307
- // If [apply] is false, the commands are unapplied.
308
- // Triggers a re-render after each [updateChunkSize]-sized group of commands
309
- // has been applied.
371
+ /**
372
+ * Apply a large transformation in chunks.
373
+ * If `apply` is `false`, the commands are unapplied.
374
+ * Triggers a re-render after each `updateChunkSize`-sized group of commands
375
+ * has been applied.
376
+ */
310
377
  asyncApplyOrUnapplyCommands(commands, apply, updateChunkSize) {
311
378
  return __awaiter(this, void 0, void 0, function* () {
312
379
  this.display.setDraftMode(true);
@@ -333,12 +400,16 @@ export class Editor {
333
400
  this.hideLoadingWarning();
334
401
  });
335
402
  }
403
+ // @see {@link #asyncApplyOrUnapplyCommands }
336
404
  asyncApplyCommands(commands, chunkSize) {
337
405
  return this.asyncApplyOrUnapplyCommands(commands, true, chunkSize);
338
406
  }
407
+ // @see {@link #asyncApplyOrUnapplyCommands }
339
408
  asyncUnapplyCommands(commands, chunkSize) {
340
409
  return this.asyncApplyOrUnapplyCommands(commands, false, chunkSize);
341
410
  }
411
+ // Schedule a re-render for some time in the near future. Does not schedule an additional
412
+ // re-render if a re-render is already queued.
342
413
  queueRerender() {
343
414
  if (!this.rerenderQueued) {
344
415
  this.rerenderQueued = true;
@@ -372,10 +443,12 @@ export class Editor {
372
443
  clearWetInk() {
373
444
  this.display.getWetInkRenderer().clear();
374
445
  }
375
- // Focuses the region used for text input
446
+ // Focuses the region used for text input/key commands.
376
447
  focus() {
377
448
  this.renderingRegion.focus();
378
449
  }
450
+ // Creates an element that will be positioned on top of the dry/wet ink
451
+ // renderers.
379
452
  createHTMLOverlay(overlay) {
380
453
  overlay.classList.add('overlay');
381
454
  this.container.appendChild(overlay);
@@ -390,7 +463,7 @@ export class Editor {
390
463
  return styleSheet;
391
464
  }
392
465
  // Dispatch a pen event to the currently selected tool.
393
- // Intented for unit tests.
466
+ // Intended primarially for unit tests.
394
467
  sendPenEvent(eventType, point, allPointers) {
395
468
  const mainPointer = Pointer.ofCanvasPoint(point, eventType !== InputEvtType.PointerUpEvt, this.viewport);
396
469
  this.toolController.dispatchInputEvent({
@@ -452,7 +525,7 @@ export class Editor {
452
525
  getImportExportRect() {
453
526
  return this.importExportViewport.visibleRect;
454
527
  }
455
- // Resize the output SVG
528
+ // Resize the output SVG to match `imageRect`.
456
529
  setImportExportRect(imageRect) {
457
530
  const origSize = this.importExportViewport.visibleRect.size;
458
531
  const origTransform = this.importExportViewport.canvasToScreenTransform;
@@ -474,8 +547,12 @@ export class Editor {
474
547
  }
475
548
  };
476
549
  }
477
- // Alias for loadFrom(SVGLoader.fromString).
478
- // This is particularly useful when accessing a bundled version of the editor.
550
+ /**
551
+ * Alias for loadFrom(SVGLoader.fromString).
552
+ *
553
+ * This is particularly useful when accessing a bundled version of the editor,
554
+ * where `SVGLoader.fromString` is unavailable.
555
+ */
479
556
  loadFromSVG(svgData) {
480
557
  return __awaiter(this, void 0, void 0, function* () {
481
558
  const loader = SVGLoader.fromString(svgData);
@@ -1,26 +1,31 @@
1
1
  import AbstractRenderer from './rendering/renderers/AbstractRenderer';
2
- import Command from './commands/Command';
3
2
  import Viewport from './Viewport';
4
3
  import AbstractComponent from './components/AbstractComponent';
5
4
  import Rect2 from './math/Rect2';
6
5
  import RenderingCache from './rendering/caching/RenderingCache';
6
+ import SerializableCommand from './commands/SerializableCommand';
7
7
  export declare const sortLeavesByZIndex: (leaves: Array<ImageNode>) => void;
8
8
  export default class EditorImage {
9
9
  private root;
10
10
  private componentsById;
11
11
  constructor();
12
12
  findParent(elem: AbstractComponent): ImageNode | null;
13
+ /** @internal */
13
14
  renderWithCache(screenRenderer: AbstractRenderer, cache: RenderingCache, viewport: Viewport): void;
15
+ /** @internal */
14
16
  render(renderer: AbstractRenderer, viewport: Viewport): void;
17
+ /** Renders all nodes, even ones not within the viewport. @internal */
15
18
  renderAll(renderer: AbstractRenderer): void;
16
19
  getElementsIntersectingRegion(region: Rect2): AbstractComponent[];
20
+ /** @internal */
17
21
  onDestroyElement(elem: AbstractComponent): void;
18
22
  lookupElement(id: string): AbstractComponent | null;
19
23
  private addElementDirectly;
20
- static addElement(elem: AbstractComponent, applyByFlattening?: boolean): Command;
24
+ static addElement(elem: AbstractComponent, applyByFlattening?: boolean): SerializableCommand;
21
25
  private static AddElementCommand;
22
26
  }
23
27
  declare type TooSmallToRenderCheck = (rect: Rect2) => boolean;
28
+ /** Part of the Editor's image. @internal */
24
29
  export declare class ImageNode {
25
30
  private parent;
26
31
  private content;
@@ -2,11 +2,13 @@ var _a;
2
2
  import AbstractComponent from './components/AbstractComponent';
3
3
  import Rect2 from './math/Rect2';
4
4
  import SerializableCommand from './commands/SerializableCommand';
5
+ // @internal
5
6
  export const sortLeavesByZIndex = (leaves) => {
6
7
  leaves.sort((a, b) => a.getContent().getZIndex() - b.getContent().getZIndex());
7
8
  };
8
9
  // Handles lookup/storage of elements in the image
9
10
  export default class EditorImage {
11
+ // @internal
10
12
  constructor() {
11
13
  this.root = new ImageNode();
12
14
  this.componentsById = {};
@@ -21,13 +23,15 @@ export default class EditorImage {
21
23
  }
22
24
  return null;
23
25
  }
26
+ /** @internal */
24
27
  renderWithCache(screenRenderer, cache, viewport) {
25
28
  cache.render(screenRenderer, this.root, viewport);
26
29
  }
30
+ /** @internal */
27
31
  render(renderer, viewport) {
28
32
  this.root.render(renderer, viewport.visibleRect);
29
33
  }
30
- // Renders all nodes, even ones not within the viewport
34
+ /** Renders all nodes, even ones not within the viewport. @internal */
31
35
  renderAll(renderer) {
32
36
  const leaves = this.root.getLeaves();
33
37
  sortLeavesByZIndex(leaves);
@@ -40,6 +44,7 @@ export default class EditorImage {
40
44
  sortLeavesByZIndex(leaves);
41
45
  return leaves.map(leaf => leaf.getContent());
42
46
  }
47
+ /** @internal */
43
48
  onDestroyElement(elem) {
44
49
  delete this.componentsById[elem.getId()];
45
50
  }
@@ -83,24 +88,25 @@ EditorImage.AddElementCommand = (_a = class extends SerializableCommand {
83
88
  container === null || container === void 0 ? void 0 : container.remove();
84
89
  editor.queueRerender();
85
90
  }
86
- description(editor, localization) {
91
+ description(_editor, localization) {
87
92
  return localization.addElementAction(this.element.description(localization));
88
93
  }
89
- serializeToString() {
90
- return JSON.stringify({
94
+ serializeToJSON() {
95
+ return {
91
96
  elemData: this.element.serialize(),
92
- });
97
+ };
93
98
  }
94
99
  },
95
100
  (() => {
96
- SerializableCommand.register('add-element', (data, _editor) => {
97
- const json = JSON.parse(data);
98
- const elem = AbstractComponent.deserialize(json.elemData);
101
+ SerializableCommand.register('add-element', (json, editor) => {
102
+ const id = json.elemData.id;
103
+ const foundElem = editor.image.lookupElement(id);
104
+ const elem = foundElem !== null && foundElem !== void 0 ? foundElem : AbstractComponent.deserialize(json.elemData);
99
105
  return new EditorImage.AddElementCommand(elem);
100
106
  });
101
107
  })(),
102
108
  _a);
103
- // TODO: Assign leaf nodes to CacheNodes. When leaf nodes are modified, the corresponding CacheNodes can be updated.
109
+ /** Part of the Editor's image. @internal */
104
110
  export class ImageNode {
105
111
  constructor(parent = null) {
106
112
  this.parent = parent;
@@ -136,16 +142,22 @@ export class ImageNode {
136
142
  // Returns a list of `ImageNode`s with content (and thus no children).
137
143
  getLeavesIntersectingRegion(region, isTooSmall) {
138
144
  const result = [];
139
- // Don't render if too small
140
- if (isTooSmall === null || isTooSmall === void 0 ? void 0 : isTooSmall(this.bbox)) {
141
- return [];
142
- }
143
- if (this.content !== null && this.getBBox().intersects(region)) {
144
- result.push(this);
145
- }
146
- const children = this.getChildrenIntersectingRegion(region);
147
- for (const child of children) {
148
- result.push(...child.getLeavesIntersectingRegion(region, isTooSmall));
145
+ let current;
146
+ const workList = [];
147
+ workList.push(this);
148
+ const toNext = () => {
149
+ current = undefined;
150
+ const next = workList.pop();
151
+ if (next && !(isTooSmall === null || isTooSmall === void 0 ? void 0 : isTooSmall(next.bbox))) {
152
+ current = next;
153
+ if (current.content !== null && current.getBBox().intersection(region)) {
154
+ result.push(current);
155
+ }
156
+ workList.push(...current.getChildrenIntersectingRegion(region));
157
+ }
158
+ };
159
+ while (workList.length > 0) {
160
+ toNext();
149
161
  }
150
162
  return result;
151
163
  }
@@ -180,13 +192,17 @@ export class ImageNode {
180
192
  // share a parent.
181
193
  const leafBBox = leaf.getBBox();
182
194
  if (leafBBox.containsRect(this.getBBox())) {
183
- // Create a node for this' children and for the new content..
184
195
  const nodeForNewLeaf = new ImageNode(this);
185
- const nodeForChildren = new ImageNode(this);
186
- nodeForChildren.children = this.children;
187
- this.children = [nodeForNewLeaf, nodeForChildren];
188
- nodeForChildren.recomputeBBox(true);
189
- nodeForChildren.updateParents();
196
+ if (this.children.length < this.targetChildCount) {
197
+ this.children.push(nodeForNewLeaf);
198
+ }
199
+ else {
200
+ const nodeForChildren = new ImageNode(this);
201
+ nodeForChildren.children = this.children;
202
+ this.children = [nodeForNewLeaf, nodeForChildren];
203
+ nodeForChildren.recomputeBBox(true);
204
+ nodeForChildren.updateParents();
205
+ }
190
206
  return nodeForNewLeaf.addLeaf(leaf);
191
207
  }
192
208
  const containingNodes = this.children.filter(child => child.getBBox().containsRect(leafBBox));
@@ -1,3 +1,20 @@
1
+ /**
2
+ * Handles notifying listeners of events.
3
+ *
4
+ * `EventKeyType` is used to distinguish events (e.g. a `ClickEvent` vs a `TouchEvent`)
5
+ * while `EventMessageType` is the type of the data sent with an event (can be `void`).
6
+ *
7
+ * @example
8
+ * ```
9
+ * const dispatcher = new EventDispatcher<'event1'|'event2'|'event3', void>();
10
+ * dispatcher.on('event1', () => {
11
+ * console.log('Event 1 triggered.');
12
+ * });
13
+ * dispatcher.dispatch('event1');
14
+ * ```
15
+ *
16
+ * @packageDocumentation
17
+ */
1
18
  declare type CallbackHandler<EventType> = (data: EventType) => void;
2
19
  export default class EventDispatcher<EventKeyType extends string | symbol | number, EventMessageType> {
3
20
  private listeners;
@@ -6,6 +23,7 @@ export default class EventDispatcher<EventKeyType extends string | symbol | numb
6
23
  on(eventName: EventKeyType, callback: CallbackHandler<EventMessageType>): {
7
24
  remove: () => boolean;
8
25
  };
26
+ /** Removes an event listener. This is equivalent to calling `.remove()` on the object returned by `.on`. */
9
27
  off(eventName: EventKeyType, callback: CallbackHandler<EventMessageType>): void;
10
28
  }
11
29
  export {};