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
@@ -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
  }
@@ -13,15 +13,14 @@ const Erase_1 = __importDefault(require("../commands/Erase"));
13
13
  const uniteCommands_1 = __importDefault(require("../commands/uniteCommands"));
14
14
  const ReactiveValue_1 = require("../util/ReactiveValue");
15
15
  const overlayCSSClass = 'textEditorOverlay';
16
+ /** A tool that allows users to enter and edit text. */
16
17
  class TextTool extends BaseTool_1.default {
17
18
  constructor(editor, description, localizationTable) {
18
19
  super(editor.notifier, description);
19
20
  this.editor = editor;
20
21
  this.localizationTable = localizationTable;
21
22
  this.textInputElem = null;
22
- this.textTargetPosition = null;
23
23
  this.textMeasuringCtx = null;
24
- this.textScale = math_1.Vec2.of(1, 1);
25
24
  this.removeExistingCommand = null;
26
25
  const editorFonts = editor.getCurrentSettings().text?.fonts ?? [];
27
26
  this.textStyleValue = ReactiveValue_1.ReactiveValue.fromInitialValue({
@@ -39,18 +38,10 @@ class TextTool extends BaseTool_1.default {
39
38
  tool: this,
40
39
  });
41
40
  });
41
+ this.contentTransform = ReactiveValue_1.ReactiveValue.fromInitialValue(math_1.Mat33.identity);
42
42
  this.textEditOverlay = document.createElement('div');
43
43
  this.textEditOverlay.classList.add(overlayCSSClass);
44
44
  this.editor.addStyleSheet(`
45
- .${overlayCSSClass} {
46
- height: 0;
47
- overflow: visible;
48
-
49
- /* Allows absolutely-positioned textareas to scroll with
50
- the containing overlay. */
51
- position: relative;
52
- }
53
-
54
45
  .${overlayCSSClass} textarea {
55
46
  background-color: rgba(0, 0, 0, 0);
56
47
 
@@ -66,8 +57,7 @@ class TextTool extends BaseTool_1.default {
66
57
  min-height: 1.1em;
67
58
  }
68
59
  `);
69
- this.editor.createHTMLOverlay(this.textEditOverlay);
70
- this.editor.notifier.on(types_1.EditorEventType.ViewportChanged, () => this.updateTextInput());
60
+ this.anchorControl = this.editor.anchorElementToCanvas(this.textEditOverlay, this.contentTransform);
71
61
  }
72
62
  initTextMeasuringCanvas() {
73
63
  this.textMeasuringCtx ??= document.createElement('canvas').getContext('2d');
@@ -87,24 +77,32 @@ class TextTool extends BaseTool_1.default {
87
77
  // If [removeInput], the HTML input element is removed. Otherwise, its value
88
78
  // is cleared.
89
79
  flushInput(removeInput = true) {
90
- if (this.textInputElem && this.textTargetPosition) {
91
- const content = this.textInputElem.value.trimEnd();
92
- this.textInputElem.value = '';
93
- if (removeInput) {
94
- // In some browsers, .remove() triggers a .blur event (synchronously).
95
- // Clear this.textInputElem before removal
96
- const input = this.textInputElem;
97
- this.textInputElem = null;
98
- input.remove();
99
- }
100
- if (content === '') {
101
- return;
102
- }
103
- const textTransform = math_1.Mat33.translation(this.textTargetPosition)
104
- .rightMul(this.getTextScaleMatrix())
105
- .rightMul(math_1.Mat33.scaling2D(this.editor.viewport.getSizeOfPixelOnCanvas()))
106
- .rightMul(math_1.Mat33.zRotation(this.textRotation));
107
- const textComponent = TextComponent_1.default.fromLines(content.split('\n'), textTransform, this.textStyle);
80
+ if (!this.textInputElem)
81
+ return;
82
+ // Determine the scroll first -- removing the input (and other DOM changes)
83
+ // also change the scroll.
84
+ const scrollingRegion = this.textEditOverlay.parentElement;
85
+ const containerScroll = math_1.Vec2.of(scrollingRegion?.scrollLeft ?? 0, scrollingRegion?.scrollTop ?? 0);
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
+ // When the text is long, it can cause its container to scroll so that the
97
+ // editing caret is in view.
98
+ // So that the text added to the document is in the same position as the text
99
+ // shown in the editor, account for this scroll when computing the transform:
100
+ const scrollCorrectionScreen = containerScroll.times(-1);
101
+ // Uses .transformVec3 to avoid also translating the scroll correction (treating
102
+ // it as a point):
103
+ const scrollCorrectionCanvas = this.editor.viewport.screenToCanvasTransform.transformVec3(scrollCorrectionScreen);
104
+ const scrollTransform = math_1.Mat33.translation(scrollCorrectionCanvas);
105
+ const textComponent = TextComponent_1.default.fromLines(content.split('\n'), scrollTransform.rightMul(this.contentTransform.get()), this.textStyle);
108
106
  const action = EditorImage_1.default.addElement(textComponent);
109
107
  if (this.removeExistingCommand) {
110
108
  // Unapply so that `removeExistingCommand` can be added to the undo stack.
@@ -117,16 +115,10 @@ class TextTool extends BaseTool_1.default {
117
115
  }
118
116
  }
119
117
  }
120
- getTextScaleMatrix() {
121
- return math_1.Mat33.scaling2D(this.textScale.times(1 / this.editor.viewport.getSizeOfPixelOnCanvas()));
122
- }
123
118
  updateTextInput() {
124
- if (!this.textInputElem || !this.textTargetPosition) {
125
- this.textInputElem?.remove();
119
+ if (!this.textInputElem) {
126
120
  return;
127
121
  }
128
- const viewport = this.editor.viewport;
129
- const textScreenPos = viewport.canvasToScreen(this.textTargetPosition);
130
122
  this.textInputElem.placeholder = this.localizationTable.enterTextToInsert;
131
123
  this.textInputElem.style.fontFamily = this.textStyle.fontFamily;
132
124
  this.textInputElem.style.fontStyle = this.textStyle.fontStyle ?? '';
@@ -134,9 +126,6 @@ class TextTool extends BaseTool_1.default {
134
126
  this.textInputElem.style.fontWeight = this.textStyle.fontWeight ?? '';
135
127
  this.textInputElem.style.fontSize = `${this.textStyle.size}px`;
136
128
  this.textInputElem.style.color = this.textStyle.renderingStyle.fill.toHexString();
137
- this.textInputElem.style.position = 'absolute';
138
- this.textInputElem.style.left = `${textScreenPos.x}px`;
139
- this.textInputElem.style.top = `${textScreenPos.y}px`;
140
129
  this.textInputElem.style.margin = '0';
141
130
  this.textInputElem.style.width = `${this.textInputElem.scrollWidth}px`;
142
131
  this.textInputElem.style.height = `${this.textInputElem.scrollHeight}px`;
@@ -145,9 +134,7 @@ class TextTool extends BaseTool_1.default {
145
134
  const tallText = 'Testing!';
146
135
  const ascent = this.getTextAscent(tallText, this.textStyle);
147
136
  const vertAdjust = ascent;
148
- const rotation = this.textRotation + viewport.getRotationAngle();
149
- const scale = this.getTextScaleMatrix();
150
- this.textInputElem.style.transform = `${scale.toCSSMatrix()} rotate(${(rotation * 180) / Math.PI}deg) translate(0, ${-vertAdjust}px)`;
137
+ this.textInputElem.style.transform = `translate(0, ${-vertAdjust}px)`;
151
138
  this.textInputElem.style.transformOrigin = 'top left';
152
139
  // Match the line height of default rendered text.
153
140
  const lineHeight = Math.floor(this.textStyle.size);
@@ -158,9 +145,14 @@ class TextTool extends BaseTool_1.default {
158
145
  this.textInputElem = document.createElement('textarea');
159
146
  this.textInputElem.value = initialText;
160
147
  this.textInputElem.style.display = 'inline-block';
161
- this.textTargetPosition = this.editor.viewport.roundPoint(textCanvasPos);
162
- this.textRotation = -this.editor.viewport.getRotationAngle();
163
- this.textScale = math_1.Vec2.of(1, 1).times(this.editor.viewport.getSizeOfPixelOnCanvas());
148
+ const textTargetPosition = this.editor.viewport.roundPoint(textCanvasPos);
149
+ const textRotation = -this.editor.viewport.getRotationAngle();
150
+ const textScale = math_1.Vec2.of(1, 1).times(this.editor.viewport.getSizeOfPixelOnCanvas());
151
+ this.contentTransform.set(
152
+ // Scale, then rotate, then translate:
153
+ math_1.Mat33.translation(textTargetPosition)
154
+ .rightMul(math_1.Mat33.zRotation(textRotation))
155
+ .rightMul(math_1.Mat33.scaling2D(textScale)));
164
156
  this.updateTextInput();
165
157
  // Update the input size/position/etc. after the placeHolder has had time to appear.
166
158
  setTimeout(() => this.updateTextInput(), 0);
@@ -229,10 +221,7 @@ class TextTool extends BaseTool_1.default {
229
221
  this.removeExistingCommand = new Erase_1.default([targetNode]);
230
222
  this.removeExistingCommand.apply(this.editor);
231
223
  this.startTextInput(targetNode.getBaselinePos(), targetNode.getText());
232
- const transform = targetNode.getTransform();
233
- this.textRotation = transform.transformVec3(math_1.Vec2.unitX).angle();
234
- const scaleFactor = transform.transformVec3(math_1.Vec2.unitX).magnitude();
235
- this.textScale = math_1.Vec2.of(1, 1).times(scaleFactor);
224
+ this.contentTransform.set(targetNode.getTransform());
236
225
  this.updateTextInput();
237
226
  }
238
227
  else {
@@ -283,5 +272,10 @@ class TextTool extends BaseTool_1.default {
283
272
  setTextStyle(style) {
284
273
  this.textStyleValue.set(style);
285
274
  }
275
+ // @internal
276
+ onDestroy() {
277
+ super.onDestroy();
278
+ this.anchorControl.remove();
279
+ }
286
280
  }
287
281
  exports.default = TextTool;
@@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
25
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
26
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
37
  };
@@ -14,11 +14,11 @@ class UndoRedoShortcut extends BaseTool_1.default {
14
14
  // @internal
15
15
  onKeyPress(event) {
16
16
  if (this.editor.shortcuts.matchesShortcut(keybindings_1.undoKeyboardShortcutId, event)) {
17
- this.editor.history.undo();
17
+ void this.editor.history.undo();
18
18
  return true;
19
19
  }
20
20
  else if (this.editor.shortcuts.matchesShortcut(keybindings_1.redoKeyboardShortcutId, event)) {
21
- this.editor.history.redo();
21
+ void this.editor.history.redo();
22
22
  return true;
23
23
  }
24
24
  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';
@@ -3,7 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.ToolbarShortcutHandler = exports.SoundUITool = exports.PasteHandler = exports.EraserMode = exports.EraserTool = exports.SelectAllShortcutHandler = exports.SelectionTool = exports.TextTool = exports.PenTool = exports.PanZoomMode = exports.PanZoomTool = exports.ToolSwitcherShortcut = exports.UndoRedoShortcut = exports.ToolEnabledGroup = exports.ToolController = exports.BaseTool = void 0;
6
+ exports.ToolbarShortcutHandler = exports.SoundUITool = exports.PasteHandler = exports.EraserMode = exports.EraserTool = exports.SelectAllShortcutHandler = exports.SelectionTool = exports.TextTool = exports.PenTool = exports.PanZoomMode = exports.PanZoomTool = exports.ToolSwitcherShortcut = exports.UndoRedoShortcut = exports.ToolEnabledGroup = exports.ToolController = exports.BaseTool = exports.InputMapper = void 0;
7
+ var InputMapper_1 = require("./InputFilter/InputMapper");
8
+ Object.defineProperty(exports, "InputMapper", { enumerable: true, get: function () { return __importDefault(InputMapper_1).default; } });
7
9
  var BaseTool_1 = require("./BaseTool");
8
10
  Object.defineProperty(exports, "BaseTool", { enumerable: true, get: function () { return __importDefault(BaseTool_1).default; } });
9
11
  var ToolController_1 = require("./ToolController");
@@ -1,4 +1,5 @@
1
1
  "use strict";
2
+ /* eslint-disable @typescript-eslint/no-redundant-type-constituents -- Used for clarity */
2
3
  var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
3
4
  if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
4
5
  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");
@@ -12,7 +12,7 @@ const cloneElementWithStyles = (element) => {
12
12
  for (let index = 0; index < originalComputedStyle.length; index++) {
13
13
  const propertyName = originalComputedStyle.item(index);
14
14
  const propertyValue = originalComputedStyle.getPropertyValue(propertyName);
15
- clonedElement.style.setProperty(propertyName, propertyValue);
15
+ clonedElement.style?.setProperty(propertyName, propertyValue);
16
16
  }
17
17
  for (let i = 0; i < originalElement.children.length; i++) {
18
18
  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,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSvgPaths = exports.createSvgElements = exports.createSvgElement = exports.ElementNamespace = void 0;
4
+ var ElementNamespace;
5
+ (function (ElementNamespace) {
6
+ ElementNamespace["Html"] = "html";
7
+ ElementNamespace["Svg"] = "svg";
8
+ })(ElementNamespace || (exports.ElementNamespace = ElementNamespace = {}));
9
+ /**
10
+ * Shorthand for creating an element with `document.createElement`, then assigning properties.
11
+ *
12
+ * Non-HTML elements (e.g. `svg` elements) should use the `elementType` parameter to select
13
+ * the element namespace.
14
+ */
15
+ const createElement = (tag, props, elementType = ElementNamespace.Html) => {
16
+ let elem;
17
+ if (elementType === ElementNamespace.Html) {
18
+ elem = document.createElement(tag);
19
+ }
20
+ else if (elementType === ElementNamespace.Svg) {
21
+ elem = document.createElementNS('http://www.w3.org/2000/svg', tag);
22
+ }
23
+ else {
24
+ throw new Error(`Unknown element type ${elementType}`);
25
+ }
26
+ for (const [key, value] of Object.entries(props)) {
27
+ if (key === 'children')
28
+ continue;
29
+ if (typeof value !== 'string' && typeof value !== 'number') {
30
+ throw new Error(`Unsupported value type ${typeof value}`);
31
+ }
32
+ elem.setAttribute(key, value.toString());
33
+ }
34
+ if (props.children) {
35
+ for (const item of props.children) {
36
+ elem.appendChild(item);
37
+ }
38
+ }
39
+ return elem;
40
+ };
41
+ const createSvgElement = (tag, props) => {
42
+ return createElement(tag, props, ElementNamespace.Svg);
43
+ };
44
+ exports.createSvgElement = createSvgElement;
45
+ const createSvgElements = (tag, elements) => {
46
+ return elements.map((props) => (0, exports.createSvgElement)(tag, props));
47
+ };
48
+ exports.createSvgElements = createSvgElements;
49
+ const createSvgPaths = (...paths) => {
50
+ return (0, exports.createSvgElements)('path', paths);
51
+ };
52
+ exports.createSvgPaths = createSvgPaths;
53
+ exports.default = createElement;
@@ -6,5 +6,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  * @internal
7
7
  */
8
8
  exports.default = {
9
- number: '1.22.0',
9
+ number: '1.24.1',
10
10
  };
@@ -5,7 +5,7 @@ import { HTMLPointerEventFilter, InputEvtType, PointerEvtType } from './inputEve
5
5
  import Command from './commands/Command';
6
6
  import UndoRedoHistory from './UndoRedoHistory';
7
7
  import Viewport from './Viewport';
8
- import { Point2, Vec2, Color4, Rect2 } from '@js-draw/math';
8
+ import { Point2, Vec2, Color4, Mat33, Rect2 } from '@js-draw/math';
9
9
  import Display, { RenderingMode } from './rendering/Display';
10
10
  import Pointer from './Pointer';
11
11
  import { EditorLocalization } from './localization';
@@ -411,9 +411,7 @@ export declare class Editor {
411
411
  * Use this to show finalized commands that don't need to have `announceForAccessibility`
412
412
  * called.
413
413
  *
414
- * Prefer `command.apply(editor)` for incomplete commands. `dispatchNoAnnounce` may allow
415
- * clients to listen for the application of commands (e.g. `SerializableCommand`s so they can
416
- * be sent across the network), while `apply` does not.
414
+ * If `addToHistory` is `false`, this is equivalent to `command.apply(editor)`.
417
415
  *
418
416
  * @example
419
417
  * ```
@@ -488,6 +486,12 @@ export declare class Editor {
488
486
  createHTMLOverlay(overlay: HTMLElement): {
489
487
  remove: () => void;
490
488
  };
489
+ /**
490
+ * Anchors the given `element` to the canvas with a given position/transformation in canvas space.
491
+ */
492
+ anchorElementToCanvas(element: HTMLElement, canvasTransform: Mat33 | ReactiveValue<Mat33>): {
493
+ remove: () => void;
494
+ };
491
495
  /**
492
496
  * Creates a CSS stylesheet with `content` and applies it to the document
493
497
  * (and thus, to this editor).
@@ -25,7 +25,7 @@ import guessKeyCodeFromKey from './util/guessKeyCodeFromKey.mjs';
25
25
  import makeAboutDialog from './dialogs/makeAboutDialog.mjs';
26
26
  import version from './version.mjs';
27
27
  import { editorImageToSVGSync, editorImageToSVGAsync } from './image/export/editorImageToSVG.mjs';
28
- import { MutableReactiveValue } from './util/ReactiveValue.mjs';
28
+ import ReactiveValue, { MutableReactiveValue } from './util/ReactiveValue.mjs';
29
29
  import listenForKeyboardEventsFrom from './util/listenForKeyboardEventsFrom.mjs';
30
30
  import mitLicenseAttribution from './util/mitLicenseAttribution.mjs';
31
31
  import ClipboardHandler from './util/ClipboardHandler.mjs';
@@ -108,7 +108,7 @@ export class Editor {
108
108
  maxZoom: settings.maxZoom ?? 1e12,
109
109
  keyboardShortcutOverrides: settings.keyboardShortcutOverrides ?? {},
110
110
  iconProvider: settings.iconProvider ?? new IconProvider(),
111
- notices: [],
111
+ notices: settings.notices ?? [],
112
112
  appInfo: settings.appInfo ? { ...settings.appInfo } : null,
113
113
  pens: {
114
114
  additionalPenTypes: settings.pens?.additionalPenTypes ?? [],
@@ -473,7 +473,7 @@ export class Editor {
473
473
  /** @internal */
474
474
  async handleDrop(evt) {
475
475
  evt.preventDefault();
476
- this.handlePaste(evt);
476
+ await this.handlePaste(evt);
477
477
  }
478
478
  /** @internal */
479
479
  async handlePaste(evt) {
@@ -726,9 +726,7 @@ export class Editor {
726
726
  * Use this to show finalized commands that don't need to have `announceForAccessibility`
727
727
  * called.
728
728
  *
729
- * Prefer `command.apply(editor)` for incomplete commands. `dispatchNoAnnounce` may allow
730
- * clients to listen for the application of commands (e.g. `SerializableCommand`s so they can
731
- * be sent across the network), while `apply` does not.
729
+ * If `addToHistory` is `false`, this is equivalent to `command.apply(editor)`.
732
730
  *
733
731
  * @example
734
732
  * ```
@@ -886,6 +884,58 @@ export class Editor {
886
884
  remove: () => overlay.remove(),
887
885
  };
888
886
  }
887
+ /**
888
+ * Anchors the given `element` to the canvas with a given position/transformation in canvas space.
889
+ */
890
+ anchorElementToCanvas(element, canvasTransform) {
891
+ if (canvasTransform instanceof Mat33) {
892
+ canvasTransform = ReactiveValue.fromImmutable(canvasTransform);
893
+ }
894
+ // The element hierarchy looks like this:
895
+ // overlay > contentWrapper > content
896
+ //
897
+ // Both contentWrapper and overlay are present to:
898
+ // 1. overlay: Positions the content at the top left of the viewport. The overlay
899
+ // has `height: 0` to allow other overlays to also be aligned with the viewport's
900
+ // top left.
901
+ // 2. contentWrapper: Has the same width/height as the editor's visible region and
902
+ // has `overflow: hidden`. This prevents the anchored element from being visible
903
+ // when not in the visible region of the canvas.
904
+ const overlay = document.createElement('div');
905
+ overlay.classList.add('anchored-element-overlay');
906
+ const contentWrapper = document.createElement('div');
907
+ contentWrapper.classList.add('content-wrapper');
908
+ element.classList.add('content');
909
+ // Updates CSS variables that specify the position/rotation/scale of the content.
910
+ const updateElementPositioning = () => {
911
+ const transform = canvasTransform.get();
912
+ const canvasRotation = transform.transformVec3(Vec2.unitX).angle();
913
+ const screenRotation = canvasRotation + this.viewport.getRotationAngle();
914
+ const screenTransform = this.viewport.canvasToScreenTransform.rightMul(canvasTransform.get());
915
+ overlay.style.setProperty('--full-transform', screenTransform.toCSSMatrix());
916
+ const translation = screenTransform.transformVec2(Vec2.zero);
917
+ overlay.style.setProperty('--position-x', `${translation.x}px`);
918
+ overlay.style.setProperty('--position-y', `${translation.y}px`);
919
+ overlay.style.setProperty('--rotation', `${(screenRotation * 180) / Math.PI}deg`);
920
+ overlay.style.setProperty('--scale', `${screenTransform.getScaleFactor()}`);
921
+ };
922
+ updateElementPositioning();
923
+ // The anchored element needs to be updated both when the user moves the canvas
924
+ // and when the anchored element's transform changes.
925
+ const updateListener = canvasTransform.onUpdate(updateElementPositioning);
926
+ const viewportListener = this.notifier.on(EditorEventType.ViewportChanged, updateElementPositioning);
927
+ contentWrapper.appendChild(element);
928
+ overlay.appendChild(contentWrapper);
929
+ overlay.classList.add('overlay', 'js-draw-editor-overlay');
930
+ this.renderingRegion.insertAdjacentElement('afterend', overlay);
931
+ return {
932
+ remove: () => {
933
+ overlay.remove();
934
+ updateListener.remove();
935
+ viewportListener.remove();
936
+ },
937
+ };
938
+ }
889
939
  /**
890
940
  * Creates a CSS stylesheet with `content` and applies it to the document
891
941
  * (and thus, to this editor).
@@ -532,6 +532,7 @@ export default class SVGLoader {
532
532
  const { svgElem, cleanUp } = (() => {
533
533
  // If the user requested an iframe load (the default) try to load with an iframe.
534
534
  // There are some cases (e.g. in a sandboxed iframe) where this doesn't work.
535
+ // TODO(v2): Use domParserLoad by default.
535
536
  if (!domParserLoad) {
536
537
  try {
537
538
  const sandbox = document.createElement('iframe');
@@ -571,6 +572,7 @@ export default class SVGLoader {
571
572
  `);
572
573
  sandboxDoc.close();
573
574
  const svgElem = sandboxDoc.createElementNS('http://www.w3.org/2000/svg', 'svg');
575
+ // eslint-disable-next-line no-unsanitized/property -- setting innerHTML in a sandboxed document.
574
576
  svgElem.innerHTML = text;
575
577
  sandboxDoc.body.appendChild(svgElem);
576
578
  const cleanUp = () => {
@@ -9,7 +9,9 @@ type TransformChangeCallback = (oldTransform: Mat33, newTransform: Mat33) => voi
9
9
  export declare class Viewport {
10
10
  private onTransformChangeCallback;
11
11
  private static ViewportTransform;
12
+ /** Converts from canvas to screen coordinates */
12
13
  private transform;
14
+ /** Converts from screen to canvas coordinates */
13
15
  private inverseTransform;
14
16
  private screenRect;
15
17
  constructor(onTransformChangeCallback: TransformChangeCallback);
@@ -53,7 +55,7 @@ export declare class Viewport {
53
55
  /**
54
56
  * Snaps `canvasPos` to the nearest grid cell corner.
55
57
  *
56
- * @see {@link getGridSize} and {@link getScaleFactorToNearestPowerOf}.
58
+ * @see {@link getGridSize}.
57
59
  */
58
60
  snapToGrid(canvasPos: Point2): {
59
61
  readonly x: number;
@@ -100,7 +100,7 @@ export class Viewport {
100
100
  /**
101
101
  * Snaps `canvasPos` to the nearest grid cell corner.
102
102
  *
103
- * @see {@link getGridSize} and {@link getScaleFactorToNearestPowerOf}.
103
+ * @see {@link getGridSize}.
104
104
  */
105
105
  snapToGrid(canvasPos) {
106
106
  const scaleFactor = this.getScaleFactorToNearestPowerOf(2);
@@ -127,7 +127,6 @@ export class Viewport {
127
127
  }
128
128
  // The separate function type definition seems necessary here.
129
129
  // See https://stackoverflow.com/a/58163623/17055750.
130
- // eslint-disable-next-line no-dupe-class-members
131
130
  static roundPoint(point, tolerance) {
132
131
  const scaleFactor = 10 ** Math.floor(Math.log10(tolerance));
133
132
  const roundComponent = (component) => {
@@ -122,7 +122,7 @@ export default abstract class AbstractComponent {
122
122
  * updates the editor.
123
123
  *
124
124
  * The transformed component is also moved to the top (use
125
- * {@link AbstractComponent.setZIndexAndTransformBy} to avoid this behavior).
125
+ * {@link AbstractComponent#setZIndexAndTransformBy} to avoid this behavior).
126
126
  */
127
127
  transformBy(affineTransfm: Mat33): SerializableCommand;
128
128
  setZIndex(newZIndex: number): SerializableCommand;
@@ -163,5 +163,5 @@ export default abstract class AbstractComponent {
163
163
  data: string | number | any[] | Record<string, any>;
164
164
  };
165
165
  private static isNotDeserializable;
166
- static deserialize(json: string | any): AbstractComponent;
166
+ static deserialize(json: any): AbstractComponent;
167
167
  }
@@ -156,7 +156,7 @@ class AbstractComponent {
156
156
  * updates the editor.
157
157
  *
158
158
  * The transformed component is also moved to the top (use
159
- * {@link AbstractComponent.setZIndexAndTransformBy} to avoid this behavior).
159
+ * {@link AbstractComponent#setZIndexAndTransformBy} to avoid this behavior).
160
160
  */
161
161
  transformBy(affineTransfm) {
162
162
  return new AbstractComponent.TransformElementCommand(affineTransfm, this.getId(), this);
@@ -5,7 +5,7 @@ import Viewport from '../../Viewport';
5
5
  import AbstractComponent from '../AbstractComponent';
6
6
  import { ComponentBuilder, ComponentBuilderFactory } from './types';
7
7
  /**
8
- * Creates a stroke builder that generates arrows circles.
8
+ * Creates a stroke builder that generates arrows.
9
9
  *
10
10
  * Example:
11
11
  * [[include:doc-pages/inline-examples/changing-pen-types.md]]
@@ -2,7 +2,7 @@ import { Path, PathCommandType } from '@js-draw/math';
2
2
  import Stroke from '../Stroke.mjs';
3
3
  import makeSnapToGridAutocorrect from './autocorrect/makeSnapToGridAutocorrect.mjs';
4
4
  /**
5
- * Creates a stroke builder that generates arrows circles.
5
+ * Creates a stroke builder that generates arrows.
6
6
  *
7
7
  * Example:
8
8
  * [[include:doc-pages/inline-examples/changing-pen-types.md]]