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
@@ -22,7 +22,20 @@ export type EditorImageNotifier = EventDispatcher<EditorImageEventType, {
22
22
  */
23
23
  export type PreRenderComponentCallback = (component: AbstractComponent, componentsProcessed: number, totalComponents: number) => Promise<boolean>;
24
24
  /**
25
- * Handles lookup/storage of elements in the image.
25
+ * @summary Handles lookup/storage of elements in the image.
26
+ *
27
+ * `js-draw` images are made up of a collection of {@link AbstractComponent}s (which
28
+ * includes {@link Stroke}s, {@link TextComponent}s, etc.). An `EditorImage`
29
+ * is the data structure that stores these components.
30
+ *
31
+ * Here's how to do a few common operations:
32
+ * - **Get all components in a {@link @js-draw/math!Rect2 | Rect2}**:
33
+ * {@link EditorImage.getElementsIntersectingRegion}.
34
+ * - **Draw an `EditorImage` onto a canvas/SVG**: {@link EditorImage.render}.
35
+ * - **Adding a new component**: {@link EditorImage.addElement}.
36
+ *
37
+ * **Example**:
38
+ * [[include:doc-pages/inline-examples/image-add-and-lookup.md]]
26
39
  */
27
40
  export default class EditorImage {
28
41
  private root;
@@ -40,10 +53,13 @@ export default class EditorImage {
40
53
  /** @internal */
41
54
  renderWithCache(screenRenderer: AbstractRenderer, cache: RenderingCache, viewport: Viewport): void;
42
55
  /**
43
- * Renders all nodes visible from `viewport` (or all nodes if `viewport = null`).
56
+ * Renders this image to the given `renderer`.
44
57
  *
45
- * `viewport` is used to improve rendering performance. If given, it must match
46
- * the viewport used by the `renderer` (if any).
58
+ * If `viewport` is non-null, only components that can be seen from that viewport
59
+ * will be rendered. If `viewport` is `null`, **all** components are rendered.
60
+ *
61
+ * **Example**:
62
+ * [[include:doc-pages/inline-examples/canvas-renderer.md]]
47
63
  */
48
64
  render(renderer: AbstractRenderer, viewport: Viewport | null): void;
49
65
  /**
@@ -63,14 +79,21 @@ export default class EditorImage {
63
79
  */
64
80
  renderAll(renderer: AbstractRenderer): void;
65
81
  /**
66
- * @returns all elements in the image, sorted by z-index. This can be slow for large images.
82
+ * @returns all elements in the image, sorted by z-index (low to high).
67
83
  *
68
- * Does not include background elements. See {@link getBackgroundComponents}.
84
+ * This can be slow for large images. If you only need all elemenst in part of the image,
85
+ * consider using {@link getElementsIntersectingRegion} instead.
86
+ *
87
+ * **Note**: The result does not include background elements. See {@link getBackgroundComponents}.
69
88
  */
70
89
  getAllElements(): AbstractComponent[];
71
90
  /** Returns the number of elements added to this image. @internal */
72
91
  estimateNumElements(): number;
73
- /** @returns a list of `AbstractComponent`s intersecting `region`, sorted by z-index. */
92
+ /**
93
+ * @returns a list of `AbstractComponent`s intersecting `region`, sorted by increasing z-index.
94
+ *
95
+ * Components in the background layer are only included if `includeBackground` is `true`.
96
+ */
74
97
  getElementsIntersectingRegion(region: Rect2, includeBackground?: boolean): AbstractComponent[];
75
98
  /** Called whenever (just after) an element is completely removed. @internal */
76
99
  onDestroyElement(elem: AbstractComponent): void;
@@ -21,7 +21,20 @@ export var EditorImageEventType;
21
21
  })(EditorImageEventType || (EditorImageEventType = {}));
22
22
  let debugMode = false;
23
23
  /**
24
- * Handles lookup/storage of elements in the image.
24
+ * @summary Handles lookup/storage of elements in the image.
25
+ *
26
+ * `js-draw` images are made up of a collection of {@link AbstractComponent}s (which
27
+ * includes {@link Stroke}s, {@link TextComponent}s, etc.). An `EditorImage`
28
+ * is the data structure that stores these components.
29
+ *
30
+ * Here's how to do a few common operations:
31
+ * - **Get all components in a {@link @js-draw/math!Rect2 | Rect2}**:
32
+ * {@link EditorImage.getElementsIntersectingRegion}.
33
+ * - **Draw an `EditorImage` onto a canvas/SVG**: {@link EditorImage.render}.
34
+ * - **Adding a new component**: {@link EditorImage.addElement}.
35
+ *
36
+ * **Example**:
37
+ * [[include:doc-pages/inline-examples/image-add-and-lookup.md]]
25
38
  */
26
39
  class EditorImage {
27
40
  // @internal
@@ -80,10 +93,13 @@ class EditorImage {
80
93
  }
81
94
  }
82
95
  /**
83
- * Renders all nodes visible from `viewport` (or all nodes if `viewport = null`).
96
+ * Renders this image to the given `renderer`.
84
97
  *
85
- * `viewport` is used to improve rendering performance. If given, it must match
86
- * the viewport used by the `renderer` (if any).
98
+ * If `viewport` is non-null, only components that can be seen from that viewport
99
+ * will be rendered. If `viewport` is `null`, **all** components are rendered.
100
+ *
101
+ * **Example**:
102
+ * [[include:doc-pages/inline-examples/canvas-renderer.md]]
87
103
  */
88
104
  render(renderer, viewport) {
89
105
  this.background.render(renderer, viewport?.visibleRect);
@@ -114,9 +130,12 @@ class EditorImage {
114
130
  this.render(renderer, null);
115
131
  }
116
132
  /**
117
- * @returns all elements in the image, sorted by z-index. This can be slow for large images.
133
+ * @returns all elements in the image, sorted by z-index (low to high).
118
134
  *
119
- * Does not include background elements. See {@link getBackgroundComponents}.
135
+ * This can be slow for large images. If you only need all elemenst in part of the image,
136
+ * consider using {@link getElementsIntersectingRegion} instead.
137
+ *
138
+ * **Note**: The result does not include background elements. See {@link getBackgroundComponents}.
120
139
  */
121
140
  getAllElements() {
122
141
  const leaves = this.root.getLeaves();
@@ -127,7 +146,11 @@ class EditorImage {
127
146
  estimateNumElements() {
128
147
  return this.componentCount;
129
148
  }
130
- /** @returns a list of `AbstractComponent`s intersecting `region`, sorted by z-index. */
149
+ /**
150
+ * @returns a list of `AbstractComponent`s intersecting `region`, sorted by increasing z-index.
151
+ *
152
+ * Components in the background layer are only included if `includeBackground` is `true`.
153
+ */
131
154
  getElementsIntersectingRegion(region, includeBackground = false) {
132
155
  let leaves = this.root.getLeavesIntersectingRegion(region);
133
156
  if (includeBackground) {
@@ -7,31 +7,8 @@ import RenderablePathSpec from '../RenderablePathSpec';
7
7
  /**
8
8
  * Renders onto a `CanvasRenderingContext2D`.
9
9
  *
10
- * @example
11
- * ```ts,runnable
12
- * import {Editor,CanvasRenderer} from 'js-draw';
13
- *
14
- * // Create an editor and load initial data -- don't add to the body (hidden editor).
15
- * const editor = new Editor(document.createElement('div'));
16
- * await editor.loadFromSVG('<svg><path d="m0,0 l100,5 l-50,60 l30,20 z" fill="green"/></svg>');
17
- * ---visible---
18
- * // Given some editor.
19
- * // Set up the canvas to be drawn onto.
20
- * const canvas = document.createElement('canvas');
21
- * const ctx = canvas.getContext('2d');
22
- *
23
- * // Ensure that the canvas can fit the entire rendering
24
- * const viewport = editor.image.getImportExportViewport();
25
- * canvas.width = viewport.getScreenRectSize().x;
26
- * canvas.height = viewport.getScreenRectSize().y;
27
- *
28
- * // Render editor.image onto the renderer
29
- * const renderer = new CanvasRenderer(ctx, viewport);
30
- * editor.image.render(renderer, viewport);
31
- *
32
- * // Add the rendered canvas to the document.
33
- * document.body.appendChild(canvas);
34
- * ```
10
+ * **Example**:
11
+ * [[include:doc-pages/inline-examples/canvas-renderer.md]]
35
12
  */
36
13
  export default class CanvasRenderer extends AbstractRenderer {
37
14
  private ctx;
@@ -5,31 +5,8 @@ import { visualEquivalent } from '../RenderablePathSpec.mjs';
5
5
  /**
6
6
  * Renders onto a `CanvasRenderingContext2D`.
7
7
  *
8
- * @example
9
- * ```ts,runnable
10
- * import {Editor,CanvasRenderer} from 'js-draw';
11
- *
12
- * // Create an editor and load initial data -- don't add to the body (hidden editor).
13
- * const editor = new Editor(document.createElement('div'));
14
- * await editor.loadFromSVG('<svg><path d="m0,0 l100,5 l-50,60 l30,20 z" fill="green"/></svg>');
15
- * ---visible---
16
- * // Given some editor.
17
- * // Set up the canvas to be drawn onto.
18
- * const canvas = document.createElement('canvas');
19
- * const ctx = canvas.getContext('2d');
20
- *
21
- * // Ensure that the canvas can fit the entire rendering
22
- * const viewport = editor.image.getImportExportViewport();
23
- * canvas.width = viewport.getScreenRectSize().x;
24
- * canvas.height = viewport.getScreenRectSize().y;
25
- *
26
- * // Render editor.image onto the renderer
27
- * const renderer = new CanvasRenderer(ctx, viewport);
28
- * editor.image.render(renderer, viewport);
29
- *
30
- * // Add the rendered canvas to the document.
31
- * document.body.appendChild(canvas);
32
- * ```
8
+ * **Example**:
9
+ * [[include:doc-pages/inline-examples/canvas-renderer.md]]
33
10
  */
34
11
  export default class CanvasRenderer extends AbstractRenderer {
35
12
  /**
@@ -43,7 +43,7 @@ export default class SVGRenderer extends AbstractRenderer {
43
43
  if (!this.elem.querySelector(`#${renderedStylesheetId}`)) {
44
44
  // Default to rounded strokes.
45
45
  const styleSheet = document.createElementNS('http://www.w3.org/2000/svg', 'style');
46
- styleSheet.innerHTML = `
46
+ styleSheet.appendChild(document.createTextNode(`
47
47
  path {
48
48
  stroke-linecap: round;
49
49
  stroke-linejoin: round;
@@ -52,7 +52,7 @@ export default class SVGRenderer extends AbstractRenderer {
52
52
  text {
53
53
  white-space: pre;
54
54
  }
55
- `.replace(/\s+/g, '');
55
+ `.replace(/\s+/g, '')));
56
56
  styleSheet.setAttribute('id', renderedStylesheetId);
57
57
  this.elem.appendChild(styleSheet);
58
58
  }
@@ -110,6 +110,25 @@ export default abstract class AbstractToolbar {
110
110
  * as being the value of `mustBeToplevel`.
111
111
  *
112
112
  * @return The added button.
113
+ *
114
+ * **Example**:
115
+ * ```ts,runnable
116
+ * import { Editor } from 'js-draw';
117
+ * const editor = new Editor(document.body);
118
+ * const toolbar = editor.addToolbar();
119
+ *
120
+ * function makeTrashIcon() {
121
+ * const container = document.createElement('div');
122
+ * container.textContent = '🗑️';
123
+ * return container;
124
+ * }
125
+ *
126
+ * toolbar.addActionButton({
127
+ * icon: makeTrashIcon(), // can be any Element not in the DOM
128
+ * label: 'Delete all',
129
+ * }, () => {
130
+ * alert('to-do!');
131
+ * });
113
132
  */
114
133
  addActionButton(title: string | ActionButtonIcon, command: () => void, options?: ToolbarActionButtonOptions | boolean): BaseWidget;
115
134
  /**
@@ -264,6 +264,25 @@ class AbstractToolbar {
264
264
  * as being the value of `mustBeToplevel`.
265
265
  *
266
266
  * @return The added button.
267
+ *
268
+ * **Example**:
269
+ * ```ts,runnable
270
+ * import { Editor } from 'js-draw';
271
+ * const editor = new Editor(document.body);
272
+ * const toolbar = editor.addToolbar();
273
+ *
274
+ * function makeTrashIcon() {
275
+ * const container = document.createElement('div');
276
+ * container.textContent = '🗑️';
277
+ * return container;
278
+ * }
279
+ *
280
+ * toolbar.addActionButton({
281
+ * icon: makeTrashIcon(), // can be any Element not in the DOM
282
+ * label: 'Delete all',
283
+ * }, () => {
284
+ * alert('to-do!');
285
+ * });
267
286
  */
268
287
  addActionButton(title, command, options = true) {
269
288
  const widget = this.makeActionButton(title, command, options);
@@ -49,7 +49,7 @@ export default class EdgeToolbar extends AbstractToolbar {
49
49
  constructor(editor: Editor, parent: HTMLElement, localizationTable: ToolbarLocalization);
50
50
  private listenForVisibilityChanges;
51
51
  private onToolbarRowResize;
52
- addSpacer(_options?: Partial<SpacerOptions> | undefined): void;
52
+ addSpacer(_options?: Partial<SpacerOptions>): void;
53
53
  addUndoRedoButtons(): void;
54
54
  addDefaults(): void;
55
55
  private updateWidgetCSSClasses;
@@ -80,9 +80,13 @@ export default class IconProvider {
80
80
  * @returns An object with both the definition of a checkerboard pattern and the syntax to
81
81
  * reference that pattern. The defs provided by this function should be wrapped within a
82
82
  * `<defs></defs>` element.
83
+ *
84
+ * **Note**: This function's return value includes both `patternDefElement` (which returns
85
+ * an Element) and a (deprecated) `patternDef` string. Avoid using the `patternDef` result.
83
86
  */
84
87
  protected makeCheckerboardPattern(): {
85
- patternDef: string;
88
+ patternDefElement: SVGElement;
89
+ readonly patternDef: string;
86
90
  patternRef: string;
87
91
  };
88
92
  /**
@@ -10,31 +10,32 @@ import Viewport from '../Viewport.mjs';
10
10
  import { makeFreehandLineBuilder } from '../components/builders/FreehandLineBuilder.mjs';
11
11
  import { makePolylineBuilder } from '../components/builders/PolylineBuilder.mjs';
12
12
  import { EraserMode } from '../tools/Eraser.mjs';
13
+ import { createSvgElement, createSvgElements, createSvgPaths } from '../util/createElement.mjs';
13
14
  const svgNamespace = 'http://www.w3.org/2000/svg';
14
- const iconColorFill = `
15
- style='fill: var(--icon-color);'
16
- `;
17
- const iconColorStrokeFill = `
18
- style='fill: var(--icon-color); stroke: var(--icon-color);'
19
- `;
20
15
  let checkerboardIdCounter = 0;
21
16
  const makeCheckerboardPattern = () => {
22
17
  const id = `checkerboard-${checkerboardIdCounter++}`;
23
- const patternDef = `
24
- <pattern
25
- id='${id}'
26
- viewBox='0,0,10,10'
27
- width='20%'
28
- height='20%'
29
- patternUnits='userSpaceOnUse'
30
- >
31
- <rect x='0' y='0' width='10' height='10' fill='white'/>
32
- <rect x='0' y='0' width='5' height='5' fill='gray'/>
33
- <rect x='5' y='5' width='5' height='5' fill='gray'/>
34
- </pattern>
35
- `;
18
+ const patternElement = createSvgElement('pattern', {
19
+ id: id,
20
+ viewBox: '0,0,10,10',
21
+ width: '20%',
22
+ height: '20%',
23
+ patternUnits: 'userSpaceOnUse',
24
+ children: createSvgElements('rect', [
25
+ { x: 0, y: 0, width: 10, height: 10, fill: 'white' },
26
+ { x: 0, y: 0, width: 5, height: 5, fill: 'gray' },
27
+ { x: 5, y: 5, width: 5, height: 5, fill: 'gray' },
28
+ ]),
29
+ });
36
30
  const patternRef = `url(#${id})`;
37
- return { patternDef, patternRef };
31
+ return {
32
+ patternDefElement: patternElement,
33
+ // @deprecated use patternDefElement
34
+ get patternDef() {
35
+ return patternElement.innerHTML;
36
+ },
37
+ patternRef,
38
+ };
38
39
  };
39
40
  const makeRedoIcon = (mirror) => {
40
41
  const icon = document.createElementNS(svgNamespace, 'svg');
@@ -50,11 +51,14 @@ const makeRedoIcon = (mirror) => {
50
51
  transform-origin: center;
51
52
  }
52
53
  </style>
53
- <path
54
- d='M20,20 A15,15 0 0 1 70,80 L80,90 L60,70 L65,90 L87,90 L65,80'
55
- class='toolbar-svg-undo-redo-icon'
56
- style='${mirror ? 'transform: scale(-1, 1);' : ''}'/>
57
54
  `;
55
+ const path = document.createElementNS(svgNamespace, 'path');
56
+ path.setAttribute('d', 'M20,20 A15,15 0 0 1 70,80 L80,90 L60,70 L65,90 L87,90 L65,80');
57
+ path.classList.add('toolbar-svg-undo-redo-icon');
58
+ if (mirror) {
59
+ path.style.transform = 'scale(-1, 1)';
60
+ }
61
+ icon.appendChild(path);
58
62
  icon.setAttribute('viewBox', '0 0 100 100');
59
63
  return icon;
60
64
  };
@@ -102,65 +106,62 @@ class IconProvider {
102
106
  return makeRedoIcon(false);
103
107
  }
104
108
  makeDropdownIcon() {
105
- const icon = document.createElementNS(svgNamespace, 'svg');
106
- icon.innerHTML = `
107
- <g>
108
- <path
109
- d='M5,10 L50,90 L95,10 Z'
110
- ${iconColorFill}
111
- />
112
- </g>
113
- `;
109
+ const icon = this.makeIconFromPath('M5,10 L50,90 L95,10 Z');
114
110
  icon.setAttribute('viewBox', '-10 -10 110 110');
115
111
  return icon;
116
112
  }
117
113
  makeEraserIcon(eraserSize, mode) {
118
- const icon = document.createElementNS(svgNamespace, 'svg');
119
114
  eraserSize ??= 10;
120
115
  const scaledSize = eraserSize / 4;
121
116
  const eraserColor = '#ff70af';
122
117
  // Draw an eraser-like shape. Created with Inkscape
123
- icon.innerHTML = `
124
- <defs>
125
- <linearGradient id="dash-pattern">
126
- <stop offset="80%" stop-color="${eraserColor}"/>
127
- <stop offset="85%" stop-color="white"/>
128
- <stop offset="90%" stop-color="${eraserColor}"/>
129
- </linearGradient>
130
- </defs>
131
- <g>
132
- <path
133
- style="fill:${mode === EraserMode.PartialStroke ? 'url(#dash-pattern)' : eraserColor}"
134
- stroke="black"
135
- transform="rotate(41.35)"
136
- d="M 52.5 27
137
- C 50 28.9 48.9 31.7 48.9 34.8
138
- L 48.9 39.8
139
- C 48.9 45.3 53.4 49.8 58.9 49.8
140
- L 103.9 49.8
141
- C 105.8 49.8 107.6 49.2 109.1 48.3
142
- L 110.2 ${scaledSize + 49.5} L 159.7 ${scaledSize + 5}
143
- L 157.7 ${-scaledSize + 5.2} L 112.4 ${49.5 - scaledSize}
144
- C 113.4 43.5 113.9 41.7 113.9 39.8
145
- L 113.9 34.8
146
- C 113.9 29.3 109.4 24.8 103.9 24.8
147
- L 58.9 24.8
148
- C 56.5 24.8 54.3 25.7 52.5 27
149
- z "
150
- id="path438" />
151
-
152
- <rect
153
- stroke="#cc8077"
154
- ${iconColorFill}
155
- id="rect218"
156
- width="65"
157
- height="75"
158
- x="48.9"
159
- y="-38.7"
160
- transform="rotate(41.35)" />
161
- </g>
162
- `;
163
- icon.setAttribute('viewBox', '0 0 120 120');
118
+ const icon = createSvgElement('svg', {
119
+ viewBox: '0 0 120 120',
120
+ children: [
121
+ createSvgElement('defs', {
122
+ children: [
123
+ createSvgElement('linearGradient', {
124
+ id: 'dash-pattern',
125
+ children: createSvgElements('stop', [
126
+ { offset: '80%', 'stop-color': eraserColor },
127
+ { offset: '85%', 'stop-color': 'white' },
128
+ { offset: '90%', 'stop-color': eraserColor },
129
+ ]),
130
+ }),
131
+ ],
132
+ }),
133
+ createSvgElement('path', {
134
+ fill: mode === EraserMode.PartialStroke ? 'url(#dash-pattern)' : eraserColor,
135
+ stroke: 'black',
136
+ transform: 'rotate(41.35)',
137
+ d: `
138
+ M 52.5 27
139
+ C 50 28.9 48.9 31.7 48.9 34.8
140
+ L 48.9 39.8
141
+ C 48.9 45.3 53.4 49.8 58.9 49.8
142
+ L 103.9 49.8
143
+ C 105.8 49.8 107.6 49.2 109.1 48.3
144
+ L 110.2 ${scaledSize + 49.5} L 159.7 ${scaledSize + 5}
145
+ L 157.7 ${-scaledSize + 5.2} L 112.4 ${49.5 - scaledSize}
146
+ C 113.4 43.5 113.9 41.7 113.9 39.8
147
+ L 113.9 34.8
148
+ C 113.9 29.3 109.4 24.8 103.9 24.8
149
+ L 58.9 24.8
150
+ C 56.5 24.8 54.3 25.7 52.5 27
151
+ z
152
+ `,
153
+ }),
154
+ createSvgElement('rect', {
155
+ stroke: '#cc8077',
156
+ fill: 'var(--icon-color)',
157
+ width: 65,
158
+ height: 75,
159
+ x: 48.9,
160
+ y: -38.7,
161
+ transform: 'rotate(41.35)',
162
+ }),
163
+ ],
164
+ });
164
165
  return icon;
165
166
  }
166
167
  makeSelectionIcon() {
@@ -168,8 +169,8 @@ class IconProvider {
168
169
  // Draw a cursor-like shape
169
170
  icon.innerHTML = `
170
171
  <g>
171
- <rect x=10 y=10 width=70 height=70 fill='pink' stroke='black'/>
172
- <rect x=75 y=75 width=10 height=10 fill='white' stroke='black'/>
172
+ <rect x="10" y="10" width="70" height="70" fill="pink" stroke="black"/>
173
+ <rect x="75" y="75" width="10" height="10" fill="white" stroke="black"/>
173
174
  </g>
174
175
  `;
175
176
  icon.setAttribute('viewBox', '0 0 100 100');
@@ -432,8 +433,6 @@ class IconProvider {
432
433
  const strokeSize = Math.round(Math.sqrt(penStyle.thickness) * 4);
433
434
  const color = penStyle.color;
434
435
  const rounded = this.isRoundedTipPen(penStyle);
435
- const icon = document.createElementNS(svgNamespace, 'svg');
436
- icon.setAttribute('viewBox', '0 0 100 100');
437
436
  const tipThickness = strokeSize / 2;
438
437
  const inkTipPath = `
439
438
  M ${15 - tipThickness},${80 - tipThickness}
@@ -466,71 +465,35 @@ class IconProvider {
466
465
  const pencilTipColor = Color4.fromHex('#f4d7d7');
467
466
  const tipColor = pencilTipColor.mix(color, tipThickness / 40 - 0.1).toHexString();
468
467
  const checkerboardPattern = makeCheckerboardPattern();
469
- const ink = `
470
- <path
471
- fill="${checkerboardPattern.patternRef}"
472
- d="${inkTipPath}"
473
- />
474
- <path
475
- fill="${checkerboardPattern.patternRef}"
476
- d="${inkTrailPath}"
477
- />
478
- <path
479
- fill="${color}"
480
- d="${inkTipPath}"
481
- />
482
- <path
483
- fill="${color}"
484
- d="${inkTrailPath}"
485
- />
486
- `;
487
- const penTip = `
488
- <path
489
- fill="${checkerboardPattern.patternRef}"
490
- d="${penTipPath}"
491
- />
492
- <path
493
- fill="${tipColor}"
494
- stroke="${color}"
495
- d="${penTipPath}"
496
- />
497
- `;
498
- const grip = `
499
- <path
500
- ${iconColorStrokeFill}
501
- d="${gripMainPath}"
502
- />
503
-
504
- <!-- shadows -->
505
- <path
506
- fill="rgba(150, 150, 150, 0.3)"
507
- d="${gripShadow1Path}"
508
- />
509
- <path
510
- fill="rgba(100, 100, 100, 0.2)"
511
- d="${gripShadow2Path}"
512
- />
513
-
514
- <!-- color bubble -->
515
- <path
516
- fill="${checkerboardPattern.patternRef}"
517
- d="${colorBubblePath}"
518
- />
519
- <path
520
- fill="${color}"
521
- d="${colorBubblePath}"
522
- />
523
- `;
524
- icon.innerHTML = `
525
- <defs>
526
- ${checkerboardPattern.patternDef}
527
- </defs>
528
- <g>
529
- ${ink}
530
- ${penTip}
531
- ${grip}
532
- </g>
533
- `;
468
+ const colorString = color.toHexString();
469
+ const ink = createSvgPaths({
470
+ fill: checkerboardPattern.patternRef,
471
+ d: inkTipPath,
472
+ }, {
473
+ fill: checkerboardPattern.patternRef,
474
+ d: inkTrailPath,
475
+ }, {
476
+ fill: colorString,
477
+ d: inkTipPath,
478
+ }, {
479
+ fill: colorString,
480
+ d: inkTrailPath,
481
+ });
482
+ const penTip = createSvgPaths({ fill: checkerboardPattern.patternRef, d: penTipPath }, { fill: tipColor, stroke: colorString, d: penTipPath });
483
+ const grip = createSvgPaths({ fill: 'var(--icon-color)', stroke: 'var(--icon-color)', d: gripMainPath },
484
+ // Shadows
485
+ { fill: 'rgba(150, 150, 150, 0.3)', d: gripShadow1Path }, { fill: 'rgba(100, 100, 100, 0.2)', d: gripShadow2Path },
486
+ // Color bubble
487
+ { fill: checkerboardPattern.patternRef, d: colorBubblePath }, { fill: colorString, d: colorBubblePath });
488
+ const icon = document.createElementNS(svgNamespace, 'svg');
489
+ icon.setAttribute('viewBox', '0 0 100 100');
490
+ const iconMainContent = createSvgElement('g', {
491
+ children: [ink, penTip, grip].flat(),
492
+ });
493
+ const defs = createSvgElement('defs', {
494
+ children: [checkerboardPattern.patternDefElement],
495
+ });
496
+ icon.replaceChildren(defs, iconMainContent);
534
497
  return icon;
535
498
  }
536
499
  makeIconFromFactory(penStyle) {
@@ -562,7 +525,7 @@ class IconProvider {
562
525
  if (includeTransparencyGrid) {
563
526
  const checkerboardPattern = makeCheckerboardPattern();
564
527
  const defs = document.createElementNS(svgNamespace, 'defs');
565
- defs.innerHTML = checkerboardPattern.patternDef;
528
+ defs.appendChild(checkerboardPattern.patternDefElement);
566
529
  icon.appendChild(defs);
567
530
  const background = document.createElementNS(svgNamespace, 'g');
568
531
  icon.appendChild(background);
@@ -636,7 +599,7 @@ class IconProvider {
636
599
  if (color) {
637
600
  const checkerboardPattern = makeCheckerboardPattern();
638
601
  const defs = document.createElementNS(svgNamespace, 'defs');
639
- defs.innerHTML = checkerboardPattern.patternDef;
602
+ defs.appendChild(checkerboardPattern.patternDefElement);
640
603
  icon.appendChild(defs);
641
604
  const fluidBackground = document.createElementNS(svgNamespace, 'path');
642
605
  const fluid = document.createElementNS(svgNamespace, 'path');
@@ -841,6 +804,9 @@ class IconProvider {
841
804
  * @returns An object with both the definition of a checkerboard pattern and the syntax to
842
805
  * reference that pattern. The defs provided by this function should be wrapped within a
843
806
  * `<defs></defs>` element.
807
+ *
808
+ * **Note**: This function's return value includes both `patternDefElement` (which returns
809
+ * an Element) and a (deprecated) `patternDef` string. Avoid using the `patternDef` result.
844
810
  */
845
811
  makeCheckerboardPattern() {
846
812
  return makeCheckerboardPattern();
@@ -45,7 +45,7 @@ export const defaultToolbarLocalization = {
45
45
  about: 'About',
46
46
  inputStabilization: 'Stabilization',
47
47
  strokeAutocorrect: 'Autocorrect',
48
- touchPanning: 'Touchscreen panning',
48
+ touchPanning: 'Scroll with touch',
49
49
  roundedTipPen: 'Round',
50
50
  roundedTipPen2: 'Polyline',
51
51
  flatTipPen: 'Flat',
@@ -72,7 +72,7 @@ export const defaultToolbarLocalization = {
72
72
  handDropdown__zoomOutHelpText: 'Zooms out.',
73
73
  handDropdown__resetViewHelpText: 'Resets the zoom level to 100% and resets scroll.',
74
74
  handDropdown__zoomDisplayHelpText: 'Shows the current zoom level. 100% shows the image at its actual size.',
75
- handDropdown__touchPanningHelpText: 'When enabled, touch gestures move the image rather than select or draw.',
75
+ handDropdown__touchPanningHelpText: 'When enabled, touchscreen gestures move the image rather than select or draw.',
76
76
  handDropdown__lockRotationHelpText: 'When enabled, prevents touch gestures from rotating the screen.',
77
77
  eraserDropdown__baseHelpText: 'This tool removes strokes, images, and text under the cursor.',
78
78
  eraserDropdown__thicknessHelpText: 'Changes the size of the eraser.',