js-draw 1.22.0 → 1.24.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. package/README.md +1 -1
  2. package/dist/Editor.css +21 -0
  3. package/dist/bundle.js +2 -2
  4. package/dist/bundledStyles.js +1 -1
  5. package/dist/cjs/Editor.d.ts +8 -4
  6. package/dist/cjs/Editor.js +73 -13
  7. package/dist/cjs/SVGLoader/SVGLoader.js +19 -7
  8. package/dist/cjs/Viewport.d.ts +3 -1
  9. package/dist/cjs/Viewport.js +1 -2
  10. package/dist/cjs/components/AbstractComponent.d.ts +2 -2
  11. package/dist/cjs/components/AbstractComponent.js +1 -1
  12. package/dist/cjs/components/BackgroundComponent.js +17 -7
  13. package/dist/cjs/components/SVGGlobalAttributesObject.js +17 -7
  14. package/dist/cjs/components/UnknownSVGObject.js +17 -7
  15. package/dist/cjs/components/builders/ArrowBuilder.d.ts +1 -1
  16. package/dist/cjs/components/builders/ArrowBuilder.js +1 -1
  17. package/dist/cjs/components/lib.js +17 -7
  18. package/dist/cjs/image/EditorImage.d.ts +30 -7
  19. package/dist/cjs/image/EditorImage.js +47 -14
  20. package/dist/cjs/rendering/renderers/CanvasRenderer.d.ts +2 -25
  21. package/dist/cjs/rendering/renderers/CanvasRenderer.js +2 -25
  22. package/dist/cjs/rendering/renderers/SVGRenderer.js +2 -2
  23. package/dist/cjs/testing/sendPenEvent.js +17 -7
  24. package/dist/cjs/testing/sendTouchEvent.js +17 -7
  25. package/dist/cjs/toolbar/AbstractToolbar.d.ts +19 -0
  26. package/dist/cjs/toolbar/AbstractToolbar.js +19 -0
  27. package/dist/cjs/toolbar/EdgeToolbar.d.ts +1 -1
  28. package/dist/cjs/toolbar/IconProvider.d.ts +5 -1
  29. package/dist/cjs/toolbar/IconProvider.js +112 -146
  30. package/dist/cjs/toolbar/localization.js +2 -2
  31. package/dist/cjs/toolbar/widgets/BaseWidget.d.ts +1 -1
  32. package/dist/cjs/toolbar/widgets/BaseWidget.js +1 -1
  33. package/dist/cjs/toolbar/widgets/DocumentPropertiesWidget.js +17 -7
  34. package/dist/cjs/toolbar/widgets/HandToolWidget.js +17 -7
  35. package/dist/cjs/tools/InputFilter/ContextMenuRecognizer.js +17 -7
  36. package/dist/cjs/tools/PanZoom.js +1 -1
  37. package/dist/cjs/tools/Pen.d.ts +13 -0
  38. package/dist/cjs/tools/Pen.js +30 -7
  39. package/dist/cjs/tools/SelectionTool/Selection.js +17 -7
  40. package/dist/cjs/tools/SelectionTool/SelectionMenuShortcut.js +1 -1
  41. package/dist/cjs/tools/SelectionTool/util/makeClipboardErrorHandlers.d.ts +2 -2
  42. package/dist/cjs/tools/SelectionTool/util/makeClipboardErrorHandlers.js +1 -1
  43. package/dist/cjs/tools/SoundUITool.js +1 -1
  44. package/dist/cjs/tools/TextTool.d.ts +4 -4
  45. package/dist/cjs/tools/TextTool.js +45 -51
  46. package/dist/cjs/tools/ToolController.js +17 -7
  47. package/dist/cjs/tools/UndoRedoShortcut.js +2 -2
  48. package/dist/cjs/tools/lib.d.ts +1 -0
  49. package/dist/cjs/tools/lib.js +3 -1
  50. package/dist/cjs/util/ClipboardHandler.js +1 -0
  51. package/dist/cjs/util/cloneElementWithStyles.js +1 -1
  52. package/dist/cjs/util/createElement.d.ts +62 -0
  53. package/dist/cjs/util/createElement.js +53 -0
  54. package/dist/cjs/version.js +1 -1
  55. package/dist/mjs/Editor.d.ts +8 -4
  56. package/dist/mjs/Editor.mjs +56 -6
  57. package/dist/mjs/SVGLoader/SVGLoader.mjs +2 -0
  58. package/dist/mjs/Viewport.d.ts +3 -1
  59. package/dist/mjs/Viewport.mjs +1 -2
  60. package/dist/mjs/components/AbstractComponent.d.ts +2 -2
  61. package/dist/mjs/components/AbstractComponent.mjs +1 -1
  62. package/dist/mjs/components/builders/ArrowBuilder.d.ts +1 -1
  63. package/dist/mjs/components/builders/ArrowBuilder.mjs +1 -1
  64. package/dist/mjs/image/EditorImage.d.ts +30 -7
  65. package/dist/mjs/image/EditorImage.mjs +30 -7
  66. package/dist/mjs/rendering/renderers/CanvasRenderer.d.ts +2 -25
  67. package/dist/mjs/rendering/renderers/CanvasRenderer.mjs +2 -25
  68. package/dist/mjs/rendering/renderers/SVGRenderer.mjs +2 -2
  69. package/dist/mjs/toolbar/AbstractToolbar.d.ts +19 -0
  70. package/dist/mjs/toolbar/AbstractToolbar.mjs +19 -0
  71. package/dist/mjs/toolbar/EdgeToolbar.d.ts +1 -1
  72. package/dist/mjs/toolbar/IconProvider.d.ts +5 -1
  73. package/dist/mjs/toolbar/IconProvider.mjs +112 -146
  74. package/dist/mjs/toolbar/localization.mjs +2 -2
  75. package/dist/mjs/toolbar/widgets/BaseWidget.d.ts +1 -1
  76. package/dist/mjs/toolbar/widgets/BaseWidget.mjs +1 -1
  77. package/dist/mjs/tools/PanZoom.mjs +1 -1
  78. package/dist/mjs/tools/Pen.d.ts +13 -0
  79. package/dist/mjs/tools/Pen.mjs +13 -0
  80. package/dist/mjs/tools/SelectionTool/SelectionMenuShortcut.mjs +1 -1
  81. package/dist/mjs/tools/SelectionTool/util/makeClipboardErrorHandlers.d.ts +2 -2
  82. package/dist/mjs/tools/SelectionTool/util/makeClipboardErrorHandlers.mjs +1 -1
  83. package/dist/mjs/tools/SoundUITool.mjs +1 -1
  84. package/dist/mjs/tools/TextTool.d.ts +4 -4
  85. package/dist/mjs/tools/TextTool.mjs +45 -51
  86. package/dist/mjs/tools/UndoRedoShortcut.mjs +2 -2
  87. package/dist/mjs/tools/lib.d.ts +1 -0
  88. package/dist/mjs/tools/lib.mjs +1 -0
  89. package/dist/mjs/util/ClipboardHandler.mjs +1 -0
  90. package/dist/mjs/util/cloneElementWithStyles.mjs +1 -1
  91. package/dist/mjs/util/createElement.d.ts +62 -0
  92. package/dist/mjs/util/createElement.mjs +47 -0
  93. package/dist/mjs/version.mjs +1 -1
  94. package/package.json +4 -4
  95. package/src/Editor.scss +31 -0
@@ -98,7 +98,7 @@ export default abstract class BaseWidget {
98
98
  addTo(parent: HTMLElement): HTMLElement;
99
99
  /**
100
100
  * Remove this. This allows the widget to be added to a toolbar again
101
- * in the future using {@link addTo}.
101
+ * in the future using `addTo`.
102
102
  */
103
103
  remove(): void;
104
104
  focus(): void;
@@ -300,7 +300,7 @@ class BaseWidget {
300
300
  }
301
301
  /**
302
302
  * Remove this. This allows the widget to be added to a toolbar again
303
- * in the future using {@link addTo}.
303
+ * in the future using `addTo`.
304
304
  */
305
305
  remove() {
306
306
  this.container.remove();
@@ -427,7 +427,7 @@ export default class PanZoom extends BaseTool {
427
427
  translation = translation.times(-1);
428
428
  rotation = rotation * -1;
429
429
  scale = 1 / scale;
430
- // Work around an issue that seems to be related to rotation matricies losing precision on inversion.
430
+ // Work around an issue that seems to be related to rotation matrices losing precision on inversion.
431
431
  // TODO: Figure out why and implement a better solution.
432
432
  if (rotation !== 0) {
433
433
  rotation += 0.0001;
@@ -11,6 +11,12 @@ export interface PenStyle {
11
11
  readonly thickness: number;
12
12
  readonly factory: ComponentBuilderFactory;
13
13
  }
14
+ /**
15
+ * A tool that allows drawing shapes and freehand lines.
16
+ *
17
+ * To change the type of shape drawn by the pen (e.g. to switch to the rectangle
18
+ * pen type), see {@link setStrokeFactory}.
19
+ */
14
20
  export default class Pen extends BaseTool {
15
21
  private editor;
16
22
  protected builder: ComponentBuilder | null;
@@ -42,6 +48,13 @@ export default class Pen extends BaseTool {
42
48
  private noteUpdated;
43
49
  setColor(color: Color4): void;
44
50
  setThickness(thickness: number): void;
51
+ /**
52
+ * Changes the type of stroke created by the pen. The given `factory` can be one of the built-in
53
+ * stroke factories (e.g. {@link makeFreehandLineBuilder}) or a custom stroke factory.
54
+ *
55
+ * Example:
56
+ * [[include:doc-pages/inline-examples/changing-pen-types.md]]
57
+ */
45
58
  setStrokeFactory(factory: ComponentBuilderFactory): void;
46
59
  setHasStabilization(hasStabilization: boolean): void;
47
60
  setStrokeAutocorrectEnabled(enabled: boolean): void;
@@ -9,6 +9,12 @@ import { decreaseSizeKeyboardShortcutId, increaseSizeKeyboardShortcutId } from
9
9
  import InputStabilizer from './InputFilter/InputStabilizer.mjs';
10
10
  import { ReactiveValue } from '../util/ReactiveValue.mjs';
11
11
  import StationaryPenDetector, { defaultStationaryDetectionConfig, } from './util/StationaryPenDetector.mjs';
12
+ /**
13
+ * A tool that allows drawing shapes and freehand lines.
14
+ *
15
+ * To change the type of shape drawn by the pen (e.g. to switch to the rectangle
16
+ * pen type), see {@link setStrokeFactory}.
17
+ */
12
18
  export default class Pen extends BaseTool {
13
19
  constructor(editor, description, style) {
14
20
  super(editor.notifier, description);
@@ -244,6 +250,13 @@ export default class Pen extends BaseTool {
244
250
  });
245
251
  }
246
252
  }
253
+ /**
254
+ * Changes the type of stroke created by the pen. The given `factory` can be one of the built-in
255
+ * stroke factories (e.g. {@link makeFreehandLineBuilder}) or a custom stroke factory.
256
+ *
257
+ * Example:
258
+ * [[include:doc-pages/inline-examples/changing-pen-types.md]]
259
+ */
247
260
  setStrokeFactory(factory) {
248
261
  if (factory !== this.style.factory) {
249
262
  this.styleValue.set({
@@ -10,7 +10,7 @@ export default class SelectionMenuShortcut {
10
10
  this.element = document.createElement('div');
11
11
  this.element.classList.add(`${cssPrefix}handle`, `${cssPrefix}selection-menu`);
12
12
  this.element.style.setProperty('--vertical-offset', `${verticalOffset}px`);
13
- this.onClick = async () => {
13
+ this.onClick = () => {
14
14
  const anchor = this.getBBoxCanvasCoords().center;
15
15
  showContextMenu(anchor);
16
16
  };
@@ -1,6 +1,6 @@
1
1
  import Editor from '../../../Editor';
2
2
  declare const makeClipboardErrorHandlers: (editor: Editor) => {
3
- onCopyError(error: Error | unknown): Promise<void>;
4
- onPasteError(error: Error | unknown): void;
3
+ onCopyError(error: unknown): void;
4
+ onPasteError(error: unknown): void;
5
5
  };
6
6
  export default makeClipboardErrorHandlers;
@@ -16,7 +16,7 @@ const makeClipboardErrorHandlers = (editor) => {
16
16
  return dialog;
17
17
  };
18
18
  return {
19
- async onCopyError(error) {
19
+ onCopyError(error) {
20
20
  const dialog = makeErrorDialog(error);
21
21
  const textboxLabel = document.createElement('label');
22
22
  textboxLabel.textContent = editor.localization.copyPasteError__copyRetry;
@@ -73,7 +73,7 @@ class SoundFeedback {
73
73
  this.boundaryGain.gain.linearRampToValueAtTime(0, this.ctx.currentTime + 0.25);
74
74
  }
75
75
  close() {
76
- this.ctx.close();
76
+ void this.ctx.close();
77
77
  this.closed = true;
78
78
  }
79
79
  }
@@ -5,23 +5,22 @@ import BaseTool from './BaseTool';
5
5
  import { ToolLocalization } from './localization';
6
6
  import TextRenderingStyle from '../rendering/TextRenderingStyle';
7
7
  import { MutableReactiveValue } from '../util/ReactiveValue';
8
+ /** A tool that allows users to enter and edit text. */
8
9
  export default class TextTool extends BaseTool {
9
10
  private editor;
10
11
  private localizationTable;
11
12
  private textStyleValue;
12
13
  private textStyle;
14
+ private anchorControl;
15
+ private contentTransform;
13
16
  private textEditOverlay;
14
17
  private textInputElem;
15
- private textTargetPosition;
16
18
  private textMeasuringCtx;
17
- private textRotation;
18
- private textScale;
19
19
  private removeExistingCommand;
20
20
  constructor(editor: Editor, description: string, localizationTable: ToolLocalization);
21
21
  private initTextMeasuringCanvas;
22
22
  private getTextAscent;
23
23
  private flushInput;
24
- private getTextScaleMatrix;
25
24
  private updateTextInput;
26
25
  private startTextInput;
27
26
  setEnabled(enabled: boolean): void;
@@ -33,4 +32,5 @@ export default class TextTool extends BaseTool {
33
32
  getTextStyle(): TextRenderingStyle;
34
33
  getStyleValue(): MutableReactiveValue<TextRenderingStyle>;
35
34
  private setTextStyle;
35
+ onDestroy(): void;
36
36
  }
@@ -8,15 +8,14 @@ import Erase from '../commands/Erase.mjs';
8
8
  import uniteCommands from '../commands/uniteCommands.mjs';
9
9
  import { ReactiveValue } from '../util/ReactiveValue.mjs';
10
10
  const overlayCSSClass = 'textEditorOverlay';
11
+ /** A tool that allows users to enter and edit text. */
11
12
  export default class TextTool extends BaseTool {
12
13
  constructor(editor, description, localizationTable) {
13
14
  super(editor.notifier, description);
14
15
  this.editor = editor;
15
16
  this.localizationTable = localizationTable;
16
17
  this.textInputElem = null;
17
- this.textTargetPosition = null;
18
18
  this.textMeasuringCtx = null;
19
- this.textScale = Vec2.of(1, 1);
20
19
  this.removeExistingCommand = null;
21
20
  const editorFonts = editor.getCurrentSettings().text?.fonts ?? [];
22
21
  this.textStyleValue = ReactiveValue.fromInitialValue({
@@ -34,18 +33,10 @@ export default class TextTool extends BaseTool {
34
33
  tool: this,
35
34
  });
36
35
  });
36
+ this.contentTransform = ReactiveValue.fromInitialValue(Mat33.identity);
37
37
  this.textEditOverlay = document.createElement('div');
38
38
  this.textEditOverlay.classList.add(overlayCSSClass);
39
39
  this.editor.addStyleSheet(`
40
- .${overlayCSSClass} {
41
- height: 0;
42
- overflow: visible;
43
-
44
- /* Allows absolutely-positioned textareas to scroll with
45
- the containing overlay. */
46
- position: relative;
47
- }
48
-
49
40
  .${overlayCSSClass} textarea {
50
41
  background-color: rgba(0, 0, 0, 0);
51
42
 
@@ -61,8 +52,7 @@ export default class TextTool extends BaseTool {
61
52
  min-height: 1.1em;
62
53
  }
63
54
  `);
64
- this.editor.createHTMLOverlay(this.textEditOverlay);
65
- this.editor.notifier.on(EditorEventType.ViewportChanged, () => this.updateTextInput());
55
+ this.anchorControl = this.editor.anchorElementToCanvas(this.textEditOverlay, this.contentTransform);
66
56
  }
67
57
  initTextMeasuringCanvas() {
68
58
  this.textMeasuringCtx ??= document.createElement('canvas').getContext('2d');
@@ -82,24 +72,32 @@ export default class TextTool extends BaseTool {
82
72
  // If [removeInput], the HTML input element is removed. Otherwise, its value
83
73
  // is cleared.
84
74
  flushInput(removeInput = true) {
85
- if (this.textInputElem && this.textTargetPosition) {
86
- const content = this.textInputElem.value.trimEnd();
87
- this.textInputElem.value = '';
88
- if (removeInput) {
89
- // In some browsers, .remove() triggers a .blur event (synchronously).
90
- // Clear this.textInputElem before removal
91
- const input = this.textInputElem;
92
- this.textInputElem = null;
93
- input.remove();
94
- }
95
- if (content === '') {
96
- return;
97
- }
98
- const textTransform = Mat33.translation(this.textTargetPosition)
99
- .rightMul(this.getTextScaleMatrix())
100
- .rightMul(Mat33.scaling2D(this.editor.viewport.getSizeOfPixelOnCanvas()))
101
- .rightMul(Mat33.zRotation(this.textRotation));
102
- const textComponent = TextComponent.fromLines(content.split('\n'), textTransform, this.textStyle);
75
+ if (!this.textInputElem)
76
+ return;
77
+ // Determine the scroll first -- removing the input (and other DOM changes)
78
+ // also change the scroll.
79
+ const scrollingRegion = this.textEditOverlay.parentElement;
80
+ const containerScroll = Vec2.of(scrollingRegion?.scrollLeft ?? 0, scrollingRegion?.scrollTop ?? 0);
81
+ const content = this.textInputElem.value.trimEnd();
82
+ this.textInputElem.value = '';
83
+ if (removeInput) {
84
+ // In some browsers, .remove() triggers a .blur event (synchronously).
85
+ // Clear this.textInputElem before removal
86
+ const input = this.textInputElem;
87
+ this.textInputElem = null;
88
+ input.remove();
89
+ }
90
+ if (content !== '') {
91
+ // When the text is long, it can cause its container to scroll so that the
92
+ // editing caret is in view.
93
+ // So that the text added to the document is in the same position as the text
94
+ // shown in the editor, account for this scroll when computing the transform:
95
+ const scrollCorrectionScreen = containerScroll.times(-1);
96
+ // Uses .transformVec3 to avoid also translating the scroll correction (treating
97
+ // it as a point):
98
+ const scrollCorrectionCanvas = this.editor.viewport.screenToCanvasTransform.transformVec3(scrollCorrectionScreen);
99
+ const scrollTransform = Mat33.translation(scrollCorrectionCanvas);
100
+ const textComponent = TextComponent.fromLines(content.split('\n'), scrollTransform.rightMul(this.contentTransform.get()), this.textStyle);
103
101
  const action = EditorImage.addElement(textComponent);
104
102
  if (this.removeExistingCommand) {
105
103
  // Unapply so that `removeExistingCommand` can be added to the undo stack.
@@ -112,16 +110,10 @@ export default class TextTool extends BaseTool {
112
110
  }
113
111
  }
114
112
  }
115
- getTextScaleMatrix() {
116
- return Mat33.scaling2D(this.textScale.times(1 / this.editor.viewport.getSizeOfPixelOnCanvas()));
117
- }
118
113
  updateTextInput() {
119
- if (!this.textInputElem || !this.textTargetPosition) {
120
- this.textInputElem?.remove();
114
+ if (!this.textInputElem) {
121
115
  return;
122
116
  }
123
- const viewport = this.editor.viewport;
124
- const textScreenPos = viewport.canvasToScreen(this.textTargetPosition);
125
117
  this.textInputElem.placeholder = this.localizationTable.enterTextToInsert;
126
118
  this.textInputElem.style.fontFamily = this.textStyle.fontFamily;
127
119
  this.textInputElem.style.fontStyle = this.textStyle.fontStyle ?? '';
@@ -129,9 +121,6 @@ export default class TextTool extends BaseTool {
129
121
  this.textInputElem.style.fontWeight = this.textStyle.fontWeight ?? '';
130
122
  this.textInputElem.style.fontSize = `${this.textStyle.size}px`;
131
123
  this.textInputElem.style.color = this.textStyle.renderingStyle.fill.toHexString();
132
- this.textInputElem.style.position = 'absolute';
133
- this.textInputElem.style.left = `${textScreenPos.x}px`;
134
- this.textInputElem.style.top = `${textScreenPos.y}px`;
135
124
  this.textInputElem.style.margin = '0';
136
125
  this.textInputElem.style.width = `${this.textInputElem.scrollWidth}px`;
137
126
  this.textInputElem.style.height = `${this.textInputElem.scrollHeight}px`;
@@ -140,9 +129,7 @@ export default class TextTool extends BaseTool {
140
129
  const tallText = 'Testing!';
141
130
  const ascent = this.getTextAscent(tallText, this.textStyle);
142
131
  const vertAdjust = ascent;
143
- const rotation = this.textRotation + viewport.getRotationAngle();
144
- const scale = this.getTextScaleMatrix();
145
- this.textInputElem.style.transform = `${scale.toCSSMatrix()} rotate(${(rotation * 180) / Math.PI}deg) translate(0, ${-vertAdjust}px)`;
132
+ this.textInputElem.style.transform = `translate(0, ${-vertAdjust}px)`;
146
133
  this.textInputElem.style.transformOrigin = 'top left';
147
134
  // Match the line height of default rendered text.
148
135
  const lineHeight = Math.floor(this.textStyle.size);
@@ -153,9 +140,14 @@ export default class TextTool extends BaseTool {
153
140
  this.textInputElem = document.createElement('textarea');
154
141
  this.textInputElem.value = initialText;
155
142
  this.textInputElem.style.display = 'inline-block';
156
- this.textTargetPosition = this.editor.viewport.roundPoint(textCanvasPos);
157
- this.textRotation = -this.editor.viewport.getRotationAngle();
158
- this.textScale = Vec2.of(1, 1).times(this.editor.viewport.getSizeOfPixelOnCanvas());
143
+ const textTargetPosition = this.editor.viewport.roundPoint(textCanvasPos);
144
+ const textRotation = -this.editor.viewport.getRotationAngle();
145
+ const textScale = Vec2.of(1, 1).times(this.editor.viewport.getSizeOfPixelOnCanvas());
146
+ this.contentTransform.set(
147
+ // Scale, then rotate, then translate:
148
+ Mat33.translation(textTargetPosition)
149
+ .rightMul(Mat33.zRotation(textRotation))
150
+ .rightMul(Mat33.scaling2D(textScale)));
159
151
  this.updateTextInput();
160
152
  // Update the input size/position/etc. after the placeHolder has had time to appear.
161
153
  setTimeout(() => this.updateTextInput(), 0);
@@ -224,10 +216,7 @@ export default class TextTool extends BaseTool {
224
216
  this.removeExistingCommand = new Erase([targetNode]);
225
217
  this.removeExistingCommand.apply(this.editor);
226
218
  this.startTextInput(targetNode.getBaselinePos(), targetNode.getText());
227
- const transform = targetNode.getTransform();
228
- this.textRotation = transform.transformVec3(Vec2.unitX).angle();
229
- const scaleFactor = transform.transformVec3(Vec2.unitX).magnitude();
230
- this.textScale = Vec2.of(1, 1).times(scaleFactor);
219
+ this.contentTransform.set(targetNode.getTransform());
231
220
  this.updateTextInput();
232
221
  }
233
222
  else {
@@ -278,4 +267,9 @@ export default class TextTool extends BaseTool {
278
267
  setTextStyle(style) {
279
268
  this.textStyleValue.set(style);
280
269
  }
270
+ // @internal
271
+ onDestroy() {
272
+ super.onDestroy();
273
+ this.anchorControl.remove();
274
+ }
281
275
  }
@@ -9,11 +9,11 @@ export default class UndoRedoShortcut extends BaseTool {
9
9
  // @internal
10
10
  onKeyPress(event) {
11
11
  if (this.editor.shortcuts.matchesShortcut(undoKeyboardShortcutId, event)) {
12
- this.editor.history.undo();
12
+ void this.editor.history.undo();
13
13
  return true;
14
14
  }
15
15
  else if (this.editor.shortcuts.matchesShortcut(redoKeyboardShortcutId, event)) {
16
- this.editor.history.redo();
16
+ void this.editor.history.redo();
17
17
  return true;
18
18
  }
19
19
  return false;
@@ -1,3 +1,4 @@
1
+ export { default as InputMapper } from './InputFilter/InputMapper';
1
2
  export { default as BaseTool } from './BaseTool';
2
3
  export { default as ToolController } from './ToolController';
3
4
  export { default as ToolEnabledGroup } from './ToolEnabledGroup';
@@ -1,3 +1,4 @@
1
+ export { default as InputMapper } from './InputFilter/InputMapper.mjs';
1
2
  export { default as BaseTool } from './BaseTool.mjs';
2
3
  export { default as ToolController } from './ToolController.mjs';
3
4
  export { default as ToolEnabledGroup } from './ToolEnabledGroup.mjs';
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @typescript-eslint/no-redundant-type-constituents -- Used for clarity */
1
2
  var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
3
  if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
4
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
@@ -10,7 +10,7 @@ const cloneElementWithStyles = (element) => {
10
10
  for (let index = 0; index < originalComputedStyle.length; index++) {
11
11
  const propertyName = originalComputedStyle.item(index);
12
12
  const propertyValue = originalComputedStyle.getPropertyValue(propertyName);
13
- clonedElement.style.setProperty(propertyName, propertyValue);
13
+ clonedElement.style?.setProperty(propertyName, propertyValue);
14
14
  }
15
15
  for (let i = 0; i < originalElement.children.length; i++) {
16
16
  const originalChild = originalElement.children.item(i);
@@ -0,0 +1,62 @@
1
+ type ElementTagNames = keyof HTMLElementTagNameMap | keyof SVGElementTagNameMap;
2
+ /**
3
+ * Maps from known elment tag names to options that can be set with .setAttribute.
4
+ * New elements/properties should be added as necessary.
5
+ */
6
+ interface ElementToPropertiesMap {
7
+ path: {
8
+ d: string;
9
+ fill: string;
10
+ stroke: string;
11
+ transform: string;
12
+ };
13
+ rect: {
14
+ stroke: string;
15
+ fill: string;
16
+ x: number;
17
+ y: number;
18
+ width: number;
19
+ height: number;
20
+ transform: string;
21
+ };
22
+ pattern: {
23
+ viewBox: string;
24
+ width: string;
25
+ height: string;
26
+ patternUnits: 'userSpaceOnUse';
27
+ };
28
+ stop: {
29
+ offset: string;
30
+ 'stop-color': string;
31
+ };
32
+ svg: {
33
+ viewBox: `${number} ${number} ${number} ${number}`;
34
+ };
35
+ }
36
+ type EmptyObject = Record<never, never>;
37
+ type ElementProperties<Tag extends ElementTagNames> = Tag extends keyof ElementToPropertiesMap ? Partial<ElementToPropertiesMap[Tag]> : EmptyObject;
38
+ /** Contains options for creating an element with tag = `Tag`. */
39
+ type ElementConfig<Tag extends ElementTagNames> = ElementProperties<Tag> & {
40
+ id?: string;
41
+ children?: (HTMLElement | SVGElement)[];
42
+ };
43
+ /**
44
+ * Maps from element tag names (e.g. `Tag='button'`) to the corresponding element type
45
+ * (e.g. `HTMLButtonElement`).
46
+ */
47
+ type ElementTagToType<Tag extends ElementTagNames> = Tag extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[Tag] : Tag extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[Tag] : never;
48
+ export declare enum ElementNamespace {
49
+ Html = "html",
50
+ Svg = "svg"
51
+ }
52
+ /**
53
+ * Shorthand for creating an element with `document.createElement`, then assigning properties.
54
+ *
55
+ * Non-HTML elements (e.g. `svg` elements) should use the `elementType` parameter to select
56
+ * the element namespace.
57
+ */
58
+ declare const createElement: <Tag extends ElementTagNames>(tag: Tag, props: ElementConfig<Tag>, elementType?: ElementNamespace) => ElementTagToType<Tag>;
59
+ export declare const createSvgElement: <Tag extends keyof SVGElementTagNameMap>(tag: Tag, props: ElementConfig<Tag>) => ElementTagToType<Tag>;
60
+ export declare const createSvgElements: <Tag extends keyof SVGElementTagNameMap>(tag: Tag, elements: ElementConfig<Tag>[]) => ElementTagToType<Tag>[];
61
+ export declare const createSvgPaths: (...paths: ElementConfig<"path">[]) => SVGPathElement[];
62
+ export default createElement;
@@ -0,0 +1,47 @@
1
+ export var ElementNamespace;
2
+ (function (ElementNamespace) {
3
+ ElementNamespace["Html"] = "html";
4
+ ElementNamespace["Svg"] = "svg";
5
+ })(ElementNamespace || (ElementNamespace = {}));
6
+ /**
7
+ * Shorthand for creating an element with `document.createElement`, then assigning properties.
8
+ *
9
+ * Non-HTML elements (e.g. `svg` elements) should use the `elementType` parameter to select
10
+ * the element namespace.
11
+ */
12
+ const createElement = (tag, props, elementType = ElementNamespace.Html) => {
13
+ let elem;
14
+ if (elementType === ElementNamespace.Html) {
15
+ elem = document.createElement(tag);
16
+ }
17
+ else if (elementType === ElementNamespace.Svg) {
18
+ elem = document.createElementNS('http://www.w3.org/2000/svg', tag);
19
+ }
20
+ else {
21
+ throw new Error(`Unknown element type ${elementType}`);
22
+ }
23
+ for (const [key, value] of Object.entries(props)) {
24
+ if (key === 'children')
25
+ continue;
26
+ if (typeof value !== 'string' && typeof value !== 'number') {
27
+ throw new Error(`Unsupported value type ${typeof value}`);
28
+ }
29
+ elem.setAttribute(key, value.toString());
30
+ }
31
+ if (props.children) {
32
+ for (const item of props.children) {
33
+ elem.appendChild(item);
34
+ }
35
+ }
36
+ return elem;
37
+ };
38
+ export const createSvgElement = (tag, props) => {
39
+ return createElement(tag, props, ElementNamespace.Svg);
40
+ };
41
+ export const createSvgElements = (tag, elements) => {
42
+ return elements.map((props) => createSvgElement(tag, props));
43
+ };
44
+ export const createSvgPaths = (...paths) => {
45
+ return createSvgElements('path', paths);
46
+ };
47
+ export default createElement;
@@ -4,5 +4,5 @@
4
4
  * @internal
5
5
  */
6
6
  export default {
7
- number: '1.22.0',
7
+ number: '1.24.1',
8
8
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "1.22.0",
3
+ "version": "1.24.1",
4
4
  "description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
5
5
  "types": "./dist/mjs/lib.d.ts",
6
6
  "main": "./dist/cjs/lib.js",
@@ -64,11 +64,11 @@
64
64
  "postpack": "ts-node tools/copyREADME.ts revert"
65
65
  },
66
66
  "dependencies": {
67
- "@js-draw/math": "^1.22.0",
67
+ "@js-draw/math": "^1.24.1",
68
68
  "@melloware/coloris": "0.22.0"
69
69
  },
70
70
  "devDependencies": {
71
- "@js-draw/build-tool": "^1.22.0",
71
+ "@js-draw/build-tool": "^1.24.1",
72
72
  "@types/jest": "29.5.5",
73
73
  "@types/jsdom": "21.1.3"
74
74
  },
@@ -86,5 +86,5 @@
86
86
  "freehand",
87
87
  "svg"
88
88
  ],
89
- "gitHead": "c922cf6e44d078133100e01383ba1bacdebe01bd"
89
+ "gitHead": "ef847374748e32d6d96d993a2236a99d9109a32c"
90
90
  }
package/src/Editor.scss CHANGED
@@ -165,6 +165,37 @@
165
165
  z-index: 5;
166
166
  }
167
167
 
168
+ // See Editor.anchorElementToCanvas
169
+ .imageEditorContainer .anchored-element-overlay {
170
+ overflow: visible;
171
+ height: 0;
172
+
173
+ > .content-wrapper {
174
+ width: var(--editor-current-display-width-px);
175
+ height: var(--editor-current-display-height-px);
176
+ overflow: hidden;
177
+ // Display 'position: absolute' children relative to this.
178
+ position: relative;
179
+
180
+ // Disable pointer events: If the parent (or the container) has
181
+ // captured pointers and the container is removed, this prevents
182
+ // us from receiving the following events (e.g. in Firefox).
183
+ pointer-events: none;
184
+
185
+ > .content {
186
+ position: absolute;
187
+ left: var(--position-x);
188
+ top: var(--position-y);
189
+ transform: scale(var(--scale)) rotate(var(--rotation));
190
+ transform-origin: left top;
191
+ margin: 0;
192
+
193
+ // We *do* want pointer events for the positioned content.
194
+ pointer-events: all;
195
+ }
196
+ }
197
+ }
198
+
168
199
  // TODO: Apply this change during a future major release.
169
200
  // So as not to change the position of other overlays, all overlays should have
170
201
  // 0 height.