js-draw 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/bundle.js +1 -1
  3. package/dist/src/Editor.d.ts +9 -6
  4. package/dist/src/Editor.js +9 -3
  5. package/dist/src/EditorImage.d.ts +3 -0
  6. package/dist/src/EditorImage.js +7 -0
  7. package/dist/src/SVGLoader.js +5 -6
  8. package/dist/src/components/AbstractComponent.d.ts +1 -0
  9. package/dist/src/components/AbstractComponent.js +4 -0
  10. package/dist/src/components/SVGGlobalAttributesObject.d.ts +1 -0
  11. package/dist/src/components/SVGGlobalAttributesObject.js +3 -0
  12. package/dist/src/components/Text.d.ts +3 -5
  13. package/dist/src/components/Text.js +19 -10
  14. package/dist/src/components/UnknownSVGObject.d.ts +1 -0
  15. package/dist/src/components/UnknownSVGObject.js +3 -0
  16. package/dist/src/components/builders/FreehandLineBuilder.js +3 -3
  17. package/dist/src/rendering/renderers/SVGRenderer.js +1 -1
  18. package/dist/src/testing/beforeEachFile.js +4 -0
  19. package/dist/src/toolbar/HTMLToolbar.js +2 -3
  20. package/dist/src/toolbar/IconProvider.d.ts +24 -0
  21. package/dist/src/toolbar/IconProvider.js +415 -0
  22. package/dist/src/toolbar/lib.d.ts +1 -1
  23. package/dist/src/toolbar/lib.js +1 -2
  24. package/dist/src/toolbar/localization.d.ts +0 -1
  25. package/dist/src/toolbar/localization.js +0 -1
  26. package/dist/src/toolbar/makeColorInput.js +1 -2
  27. package/dist/src/toolbar/widgets/BaseWidget.d.ts +2 -0
  28. package/dist/src/toolbar/widgets/BaseWidget.js +16 -2
  29. package/dist/src/toolbar/widgets/EraserToolWidget.js +1 -2
  30. package/dist/src/toolbar/widgets/HandToolWidget.d.ts +5 -3
  31. package/dist/src/toolbar/widgets/HandToolWidget.js +35 -12
  32. package/dist/src/toolbar/widgets/PenToolWidget.d.ts +2 -0
  33. package/dist/src/toolbar/widgets/PenToolWidget.js +16 -3
  34. package/dist/src/toolbar/widgets/SelectionToolWidget.d.ts +3 -0
  35. package/dist/src/toolbar/widgets/SelectionToolWidget.js +20 -7
  36. package/dist/src/toolbar/widgets/TextToolWidget.js +1 -2
  37. package/dist/src/tools/PanZoom.d.ts +1 -1
  38. package/dist/src/tools/PanZoom.js +4 -1
  39. package/dist/src/tools/SelectionTool/SelectionTool.d.ts +3 -0
  40. package/dist/src/tools/SelectionTool/SelectionTool.js +66 -3
  41. package/dist/src/tools/ToolController.js +3 -0
  42. package/dist/src/tools/ToolbarShortcutHandler.d.ts +12 -0
  43. package/dist/src/tools/ToolbarShortcutHandler.js +23 -0
  44. package/dist/src/tools/lib.d.ts +1 -0
  45. package/dist/src/tools/lib.js +1 -0
  46. package/dist/src/tools/localization.d.ts +1 -0
  47. package/dist/src/tools/localization.js +1 -0
  48. package/dist/src/types.d.ts +4 -2
  49. package/package.json +1 -1
  50. package/src/Editor.ts +17 -7
  51. package/src/EditorImage.ts +9 -0
  52. package/src/SVGLoader.test.ts +37 -0
  53. package/src/SVGLoader.ts +5 -6
  54. package/src/components/AbstractComponent.ts +5 -0
  55. package/src/components/SVGGlobalAttributesObject.ts +4 -0
  56. package/src/components/Text.test.ts +1 -16
  57. package/src/components/Text.ts +21 -11
  58. package/src/components/UnknownSVGObject.ts +4 -0
  59. package/src/components/builders/FreehandLineBuilder.ts +3 -3
  60. package/src/rendering/renderers/SVGRenderer.ts +1 -1
  61. package/src/testing/beforeEachFile.ts +6 -1
  62. package/src/toolbar/HTMLToolbar.ts +2 -3
  63. package/src/toolbar/IconProvider.ts +476 -0
  64. package/src/toolbar/lib.ts +1 -1
  65. package/src/toolbar/localization.ts +0 -2
  66. package/src/toolbar/makeColorInput.ts +1 -2
  67. package/src/toolbar/widgets/BaseWidget.ts +20 -3
  68. package/src/toolbar/widgets/EraserToolWidget.ts +1 -2
  69. package/src/toolbar/widgets/HandToolWidget.ts +42 -20
  70. package/src/toolbar/widgets/PenToolWidget.ts +20 -4
  71. package/src/toolbar/widgets/SelectionToolWidget.ts +24 -8
  72. package/src/toolbar/widgets/TextToolWidget.ts +1 -2
  73. package/src/tools/PanZoom.ts +4 -1
  74. package/src/tools/SelectionTool/SelectionTool.css +2 -1
  75. package/src/tools/SelectionTool/SelectionTool.test.ts +40 -0
  76. package/src/tools/SelectionTool/SelectionTool.ts +73 -4
  77. package/src/tools/ToolController.ts +3 -0
  78. package/src/tools/ToolbarShortcutHandler.ts +34 -0
  79. package/src/tools/UndoRedoShortcut.test.ts +3 -0
  80. package/src/tools/lib.ts +1 -0
  81. package/src/tools/localization.ts +4 -0
  82. package/src/types.ts +13 -8
  83. package/typedoc.json +5 -1
  84. package/dist/src/toolbar/icons.d.ts +0 -20
  85. package/dist/src/toolbar/icons.js +0 -385
  86. package/src/toolbar/icons.ts +0 -443
@@ -0,0 +1,415 @@
1
+ import Color4 from '../Color4';
2
+ import EventDispatcher from '../EventDispatcher';
3
+ import { Vec2 } from '../math/Vec2';
4
+ import SVGRenderer from '../rendering/renderers/SVGRenderer';
5
+ import Viewport from '../Viewport';
6
+ const svgNamespace = 'http://www.w3.org/2000/svg';
7
+ const iconColorFill = `
8
+ style='fill: var(--icon-color);'
9
+ `;
10
+ const iconColorStrokeFill = `
11
+ style='fill: var(--icon-color); stroke: var(--icon-color);'
12
+ `;
13
+ const checkerboardPatternDef = `
14
+ <pattern
15
+ id='checkerboard'
16
+ viewBox='0,0,10,10'
17
+ width='20%'
18
+ height='20%'
19
+ patternUnits='userSpaceOnUse'
20
+ >
21
+ <rect x=0 y=0 width=10 height=10 fill='white'/>
22
+ <rect x=0 y=0 width=5 height=5 fill='gray'/>
23
+ <rect x=5 y=5 width=5 height=5 fill='gray'/>
24
+ </pattern>
25
+ `;
26
+ const checkerboardPatternRef = 'url(#checkerboard)';
27
+ // Provides icons that can be used in the toolbar, etc.
28
+ // Extend this class and override methods to customize icons.
29
+ export default class IconProvider {
30
+ constructor() {
31
+ this.makeZoomIcon = () => {
32
+ const icon = document.createElementNS(svgNamespace, 'svg');
33
+ icon.setAttribute('viewBox', '0 0 100 100');
34
+ const addTextNode = (text, x, y) => {
35
+ const textNode = document.createElementNS(svgNamespace, 'text');
36
+ textNode.appendChild(document.createTextNode(text));
37
+ textNode.setAttribute('x', x.toString());
38
+ textNode.setAttribute('y', y.toString());
39
+ textNode.style.textAlign = 'center';
40
+ textNode.style.textAnchor = 'middle';
41
+ textNode.style.fontSize = '55px';
42
+ textNode.style.fill = 'var(--icon-color)';
43
+ textNode.style.fontFamily = 'monospace';
44
+ icon.appendChild(textNode);
45
+ };
46
+ addTextNode('+', 40, 45);
47
+ addTextNode('-', 70, 75);
48
+ return icon;
49
+ };
50
+ }
51
+ makeUndoIcon() {
52
+ return this.makeRedoIcon(true);
53
+ }
54
+ // @param mirror - reflect across the x-axis @internal
55
+ // @returns a redo icon.
56
+ makeRedoIcon(mirror = false) {
57
+ const icon = document.createElementNS(svgNamespace, 'svg');
58
+ icon.innerHTML = `
59
+ <style>
60
+ .toolbar-svg-undo-redo-icon {
61
+ stroke: var(--icon-color);
62
+ stroke-width: 12;
63
+ stroke-linejoin: round;
64
+ stroke-linecap: round;
65
+ fill: none;
66
+
67
+ transform-origin: center;
68
+ }
69
+ </style>
70
+ <path
71
+ d='M20,20 A15,15 0 0 1 70,80 L80,90 L60,70 L65,90 L87,90 L65,80'
72
+ class='toolbar-svg-undo-redo-icon'
73
+ style='${mirror ? 'transform: scale(-1, 1);' : ''}'/>
74
+ `;
75
+ icon.setAttribute('viewBox', '0 0 100 100');
76
+ return icon;
77
+ }
78
+ makeDropdownIcon() {
79
+ const icon = document.createElementNS(svgNamespace, 'svg');
80
+ icon.innerHTML = `
81
+ <g>
82
+ <path
83
+ d='M5,10 L50,90 L95,10 Z'
84
+ ${iconColorFill}
85
+ />
86
+ </g>
87
+ `;
88
+ icon.setAttribute('viewBox', '0 0 100 100');
89
+ return icon;
90
+ }
91
+ makeEraserIcon() {
92
+ const icon = document.createElementNS(svgNamespace, 'svg');
93
+ // Draw an eraser-like shape
94
+ icon.innerHTML = `
95
+ <g>
96
+ <rect x=10 y=50 width=80 height=30 rx=10 fill='pink' />
97
+ <rect
98
+ x=10 y=10 width=80 height=50
99
+ ${iconColorFill}
100
+ />
101
+ </g>
102
+ `;
103
+ icon.setAttribute('viewBox', '0 0 100 100');
104
+ return icon;
105
+ }
106
+ makeSelectionIcon() {
107
+ const icon = document.createElementNS(svgNamespace, 'svg');
108
+ // Draw a cursor-like shape
109
+ icon.innerHTML = `
110
+ <g>
111
+ <rect x=10 y=10 width=70 height=70 fill='pink' stroke='black'/>
112
+ <rect x=75 y=75 width=10 height=10 fill='white' stroke='black'/>
113
+ </g>
114
+ `;
115
+ icon.setAttribute('viewBox', '0 0 100 100');
116
+ return icon;
117
+ }
118
+ makeIconFromPath(pathData, fill = 'var(--icon-color)', strokeColor = 'none', strokeWidth = '0px') {
119
+ const icon = document.createElementNS(svgNamespace, 'svg');
120
+ const path = document.createElementNS(svgNamespace, 'path');
121
+ path.setAttribute('d', pathData);
122
+ path.style.fill = fill;
123
+ path.style.stroke = strokeColor;
124
+ path.style.strokeWidth = strokeWidth;
125
+ icon.appendChild(path);
126
+ icon.setAttribute('viewBox', '0 0 100 100');
127
+ return icon;
128
+ }
129
+ makeHandToolIcon() {
130
+ const fill = 'none';
131
+ const strokeColor = 'var(--icon-color)';
132
+ const strokeWidth = '3';
133
+ // Draw a cursor-like shape (like some of the other icons, made with Inkscape)
134
+ return this.makeIconFromPath(`
135
+ m 10,60
136
+ 5,30
137
+ H 90
138
+ V 30
139
+ C 90,20 75,20 75,30
140
+ V 60
141
+ 20
142
+ C 75,10 60,10 60,20
143
+ V 60
144
+ 15
145
+ C 60,5 45,5 45,15
146
+ V 60
147
+ 25
148
+ C 45,15 30,15 30,25
149
+ V 60
150
+ 75
151
+ L 25,60
152
+ C 20,45 10,50 10,60
153
+ Z
154
+ `, fill, strokeColor, strokeWidth);
155
+ }
156
+ makeTouchPanningIcon() {
157
+ const fill = 'none';
158
+ const strokeColor = 'var(--icon-color)';
159
+ const strokeWidth = '3';
160
+ return this.makeIconFromPath(`
161
+ M 5,5.5
162
+ V 17.2
163
+ L 16.25,5.46
164
+ Z
165
+
166
+ m 33.75,0
167
+ L 50,17
168
+ V 5.5
169
+ Z
170
+
171
+ M 5,40.7
172
+ v 11.7
173
+ h 11.25
174
+ z
175
+
176
+ M 26,19
177
+ C 19.8,19.4 17.65,30.4 21.9,34.8
178
+ L 50,70
179
+ H 27.5
180
+ c -11.25,0 -11.25,17.6 0,17.6
181
+ H 61.25
182
+ C 94.9,87.8 95,87.6 95,40.7 78.125,23 67,29 55.6,46.5
183
+ L 33.1,23
184
+ C 30.3125,20.128192 27.9,19 25.830078,19.119756
185
+ Z
186
+ `, fill, strokeColor, strokeWidth);
187
+ }
188
+ makeAllDevicePanningIcon() {
189
+ const fill = 'none';
190
+ const strokeColor = 'var(--icon-color)';
191
+ const strokeWidth = '3';
192
+ return this.makeIconFromPath(`
193
+ M 5 5
194
+ L 5 17.5
195
+ 17.5 5
196
+ 5 5
197
+ z
198
+
199
+ M 42.5 5
200
+ L 55 17.5
201
+ 55 5
202
+ 42.5 5
203
+ z
204
+
205
+ M 70 10
206
+ L 70 21
207
+ 61 15
208
+ 55.5 23
209
+ 66 30
210
+ 56 37
211
+ 61 45
212
+ 70 39
213
+ 70 50
214
+ 80 50
215
+ 80 39
216
+ 89 45
217
+ 95 36
218
+ 84 30
219
+ 95 23
220
+ 89 15
221
+ 80 21
222
+ 80 10
223
+ 70 10
224
+ z
225
+
226
+ M 27.5 26.25
227
+ L 27.5 91.25
228
+ L 43.75 83.125
229
+ L 52 99
230
+ L 68 91
231
+ L 60 75
232
+ L 76.25 66.875
233
+ L 27.5 26.25
234
+ z
235
+
236
+ M 5 42.5
237
+ L 5 55
238
+ L 17.5 55
239
+ L 5 42.5
240
+ z
241
+ `, fill, strokeColor, strokeWidth);
242
+ }
243
+ makeTextIcon(textStyle) {
244
+ var _a, _b;
245
+ const icon = document.createElementNS(svgNamespace, 'svg');
246
+ icon.setAttribute('viewBox', '0 0 100 100');
247
+ const textNode = document.createElementNS(svgNamespace, 'text');
248
+ textNode.appendChild(document.createTextNode('T'));
249
+ textNode.style.fontFamily = textStyle.fontFamily;
250
+ textNode.style.fontWeight = (_a = textStyle.fontWeight) !== null && _a !== void 0 ? _a : '';
251
+ textNode.style.fontVariant = (_b = textStyle.fontVariant) !== null && _b !== void 0 ? _b : '';
252
+ textNode.style.fill = textStyle.renderingStyle.fill.toHexString();
253
+ textNode.style.textAnchor = 'middle';
254
+ textNode.setAttribute('x', '50');
255
+ textNode.setAttribute('y', '75');
256
+ textNode.style.fontSize = '65px';
257
+ textNode.style.filter = 'drop-shadow(0px 0px 10px var(--primary-shadow-color))';
258
+ icon.appendChild(textNode);
259
+ return icon;
260
+ }
261
+ makePenIcon(tipThickness, color) {
262
+ if (color instanceof Color4) {
263
+ color = color.toHexString();
264
+ }
265
+ const icon = document.createElementNS(svgNamespace, 'svg');
266
+ icon.setAttribute('viewBox', '0 0 100 100');
267
+ const halfThickness = tipThickness / 2;
268
+ // Draw a pen-like shape
269
+ const primaryStrokeTipPath = `M14,63 L${50 - halfThickness},95 L${50 + halfThickness},90 L88,60 Z`;
270
+ const backgroundStrokeTipPath = `M14,63 L${50 - halfThickness},85 L${50 + halfThickness},83 L88,60 Z`;
271
+ icon.innerHTML = `
272
+ <defs>
273
+ ${checkerboardPatternDef}
274
+ </defs>
275
+ <g>
276
+ <!-- Pen grip -->
277
+ <path
278
+ d='M10,10 L90,10 L90,60 L${50 + halfThickness},80 L${50 - halfThickness},80 L10,60 Z'
279
+ ${iconColorStrokeFill}
280
+ />
281
+ </g>
282
+ <g>
283
+ <!-- Checkerboard background for slightly transparent pens -->
284
+ <path d='${backgroundStrokeTipPath}' fill='${checkerboardPatternRef}'/>
285
+
286
+ <!-- Actual pen tip -->
287
+ <path
288
+ d='${primaryStrokeTipPath}'
289
+ fill='${color}'
290
+ stroke='${color}'
291
+ />
292
+ </g>
293
+ `;
294
+ return icon;
295
+ }
296
+ makeIconFromFactory(pen, factory) {
297
+ const toolThickness = pen.getThickness();
298
+ const nowTime = (new Date()).getTime();
299
+ const startPoint = {
300
+ pos: Vec2.of(10, 10),
301
+ width: toolThickness / 5,
302
+ color: pen.getColor(),
303
+ time: nowTime - 100,
304
+ };
305
+ const endPoint = {
306
+ pos: Vec2.of(90, 90),
307
+ width: toolThickness / 5,
308
+ color: pen.getColor(),
309
+ time: nowTime,
310
+ };
311
+ const viewport = new Viewport(new EventDispatcher());
312
+ const builder = factory(startPoint, viewport);
313
+ builder.addPoint(endPoint);
314
+ const icon = document.createElementNS(svgNamespace, 'svg');
315
+ icon.setAttribute('viewBox', '0 0 100 100');
316
+ viewport.updateScreenSize(Vec2.of(100, 100));
317
+ const renderer = new SVGRenderer(icon, viewport);
318
+ builder.preview(renderer);
319
+ return icon;
320
+ }
321
+ makePipetteIcon(color) {
322
+ const icon = document.createElementNS(svgNamespace, 'svg');
323
+ const pipette = document.createElementNS(svgNamespace, 'path');
324
+ pipette.setAttribute('d', `
325
+ M 47,6
326
+ C 35,5 25,15 35,30
327
+ c -9.2,1.3 -15,0 -15,3
328
+ 0,2 5,5 15,7
329
+ V 81
330
+ L 40,90
331
+ h 6
332
+ L 40,80
333
+ V 40
334
+ h 15
335
+ v 40
336
+ l -6,10
337
+ h 6
338
+ l 5,-9.2
339
+ V 40
340
+ C 70,38 75,35 75,33
341
+ 75,30 69.2,31.2 60,30
342
+ 65,15 65,5 47,6
343
+ Z
344
+ `);
345
+ pipette.style.fill = 'var(--icon-color)';
346
+ if (color) {
347
+ const defs = document.createElementNS(svgNamespace, 'defs');
348
+ defs.innerHTML = checkerboardPatternDef;
349
+ icon.appendChild(defs);
350
+ const fluidBackground = document.createElementNS(svgNamespace, 'path');
351
+ const fluid = document.createElementNS(svgNamespace, 'path');
352
+ const fluidPathData = `
353
+ m 40,50 c 5,5 10,0 15,-5 V 80 L 50,90 H 45 L 40,80 Z
354
+ `;
355
+ fluid.setAttribute('d', fluidPathData);
356
+ fluidBackground.setAttribute('d', fluidPathData);
357
+ fluid.style.fill = color.toHexString();
358
+ fluidBackground.style.fill = checkerboardPatternRef;
359
+ icon.appendChild(fluidBackground);
360
+ icon.appendChild(fluid);
361
+ }
362
+ icon.appendChild(pipette);
363
+ icon.setAttribute('viewBox', '0 0 100 100');
364
+ return icon;
365
+ }
366
+ makeResizeViewportIcon() {
367
+ return this.makeIconFromPath(`
368
+ M 75 5 75 10 90 10 90 25 95 25 95 5 75 5 z
369
+ M 15 15 15 30 20 30 20 20 30 20 30 15 15 15 z
370
+ M 84 15 82 17 81 16 81 20 85 20 84 19 86 17 84 15 z
371
+ M 26 24 24 26 26 28 25 29 29 29 29 25 28 26 26 24 z
372
+ M 25 71 26 72 24 74 26 76 28 74 29 75 29 71 25 71 z
373
+ M 15 75 15 85 25 85 25 80 20 80 20 75 15 75 z
374
+ M 90 75 90 90 75 90 75 95 95 95 95 75 90 75 z
375
+ M 81 81 81 85 82 84 84 86 86 84 84 82 85 81 81 81 z
376
+ `);
377
+ }
378
+ makeDuplicateSelectionIcon() {
379
+ return this.makeIconFromPath(`
380
+ M 45,10 45,55 90,55 90,10 45,10 z
381
+ M 10,25 10,90 70,90 70,60 40,60 40,25 10,25 z
382
+ `);
383
+ }
384
+ makeDeleteSelectionIcon() {
385
+ const strokeWidth = '5px';
386
+ const strokeColor = 'var(--icon-color)';
387
+ const fillColor = 'none';
388
+ return this.makeIconFromPath(`
389
+ M 10,10 90,90
390
+ M 10,90 90,10
391
+ `, fillColor, strokeColor, strokeWidth);
392
+ }
393
+ makeSaveIcon() {
394
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
395
+ svg.innerHTML = `
396
+ <style>
397
+ .toolbar-save-icon {
398
+ stroke: var(--icon-color);
399
+ stroke-width: 10;
400
+ stroke-linejoin: round;
401
+ stroke-linecap: round;
402
+ fill: none;
403
+ }
404
+ </style>
405
+ <path
406
+ d='
407
+ M 15,55 30,70 85,20
408
+ '
409
+ class='toolbar-save-icon'
410
+ />
411
+ `;
412
+ svg.setAttribute('viewBox', '0 0 100 100');
413
+ return svg;
414
+ }
415
+ }
@@ -1,3 +1,3 @@
1
1
  export * from './widgets/lib';
2
- export * as icons from './icons';
3
2
  export * from './makeColorInput';
3
+ export { default as IconProvider } from './IconProvider';
@@ -1,4 +1,3 @@
1
1
  export * from './widgets/lib';
2
- import * as icons_1 from './icons';
3
- export { icons_1 as icons };
4
2
  export * from './makeColorInput';
3
+ export { default as IconProvider } from './IconProvider';
@@ -1,6 +1,5 @@
1
1
  export interface ToolbarLocalization {
2
2
  fontLabel: string;
3
- anyDevicePanning: string;
4
3
  touchPanning: string;
5
4
  outlinedRectanglePen: string;
6
5
  filledRectanglePen: string;
@@ -18,7 +18,6 @@ export const defaultToolbarLocalization = {
18
18
  clickToPickColorAnnouncement: 'Click on the screen to pick a color',
19
19
  selectionToolKeyboardShortcuts: 'Selection tool: Use arrow keys to move selected items, lowercase/uppercase ‘i’ and ‘o’ to resize.',
20
20
  touchPanning: 'Touchscreen panning',
21
- anyDevicePanning: 'Any device panning',
22
21
  freehandPen: 'Freehand',
23
22
  arrowPen: 'Arrow',
24
23
  linePen: 'Line',
@@ -1,7 +1,6 @@
1
1
  import Color4 from '../Color4';
2
2
  import PipetteTool from '../tools/PipetteTool';
3
3
  import { EditorEventType } from '../types';
4
- import { makePipetteIcon } from './icons';
5
4
  // Returns [ color input, input container ].
6
5
  export const makeColorInput = (editor, onColorChange) => {
7
6
  const colorInputContainer = document.createElement('span');
@@ -56,7 +55,7 @@ const addPipetteTool = (editor, container, onColorChange) => {
56
55
  pipetteButton.title = editor.localization.pickColorFromScreen;
57
56
  pipetteButton.setAttribute('alt', pipetteButton.title);
58
57
  const updatePipetteIcon = (color) => {
59
- pipetteButton.replaceChildren(makePipetteIcon(color));
58
+ pipetteButton.replaceChildren(editor.icons.makePipetteIcon(color));
60
59
  };
61
60
  updatePipetteIcon();
62
61
  const pipetteTool = editor.toolController.getMatchingTools(PipetteTool)[0];
@@ -1,4 +1,5 @@
1
1
  import Editor from '../../Editor';
2
+ import { KeyPressEvent } from '../../types';
2
3
  import { ToolbarLocalization } from '../localization';
3
4
  export default abstract class BaseWidget {
4
5
  #private;
@@ -18,6 +19,7 @@ export default abstract class BaseWidget {
18
19
  protected abstract createIcon(): Element;
19
20
  protected fillDropdown(dropdown: HTMLElement): boolean;
20
21
  protected setupActionBtnClickListener(button: HTMLElement): void;
22
+ protected onKeyPress(_event: KeyPressEvent): boolean;
21
23
  protected abstract handleClick(): void;
22
24
  protected get hasDropdown(): boolean;
23
25
  protected addSubWidget(widget: BaseWidget): void;
@@ -10,9 +10,9 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
12
  var _BaseWidget_hasDropdown;
13
+ import ToolbarShortcutHandler from '../../tools/ToolbarShortcutHandler';
13
14
  import { EditorEventType, InputEvtType } from '../../types';
14
15
  import { toolbarCSSPrefix } from '../HTMLToolbar';
15
- import { makeDropdownIcon } from '../icons';
16
16
  export default class BaseWidget {
17
17
  constructor(editor, localizationTable) {
18
18
  this.editor = editor;
@@ -33,6 +33,12 @@ export default class BaseWidget {
33
33
  this.label = document.createElement('label');
34
34
  this.button.setAttribute('role', 'button');
35
35
  this.button.tabIndex = 0;
36
+ const toolbarShortcutHandlers = this.editor.toolController.getMatchingTools(ToolbarShortcutHandler);
37
+ // If the onKeyPress function has been extended and the editor is configured to send keypress events to
38
+ // toolbar widgets,
39
+ if (toolbarShortcutHandlers.length > 0 && this.onKeyPress !== BaseWidget.prototype.onKeyPress) {
40
+ toolbarShortcutHandlers[0].registerListener(event => this.onKeyPress(event));
41
+ }
36
42
  }
37
43
  // Add content to the widget's associated dropdown menu.
38
44
  // Returns true if such a menu should be created, false otherwise.
@@ -62,6 +68,7 @@ export default class BaseWidget {
62
68
  kind: InputEvtType.KeyPressEvent,
63
69
  key: evt.key,
64
70
  ctrlKey: evt.ctrlKey,
71
+ altKey: evt.altKey,
65
72
  });
66
73
  }
67
74
  };
@@ -73,6 +80,7 @@ export default class BaseWidget {
73
80
  kind: InputEvtType.KeyUpEvent,
74
81
  key: evt.key,
75
82
  ctrlKey: evt.ctrlKey,
83
+ altKey: evt.altKey,
76
84
  });
77
85
  };
78
86
  button.onclick = () => {
@@ -81,6 +89,12 @@ export default class BaseWidget {
81
89
  }
82
90
  };
83
91
  }
92
+ // Add a listener that is triggered when a key is pressed.
93
+ // Listeners will fire regardless of whether this widget is selected and require that
94
+ // {@link lib!Editor.toolController} to have an enabled {@link lib!ToolbarShortcutHandler} tool.
95
+ onKeyPress(_event) {
96
+ return false;
97
+ }
84
98
  get hasDropdown() {
85
99
  return __classPrivateFieldGet(this, _BaseWidget_hasDropdown, "f");
86
100
  }
@@ -191,7 +205,7 @@ export default class BaseWidget {
191
205
  return this.container.classList.contains('selected');
192
206
  }
193
207
  createDropdownIcon() {
194
- const icon = makeDropdownIcon();
208
+ const icon = this.editor.icons.makeDropdownIcon();
195
209
  icon.classList.add(`${toolbarCSSPrefix}showHideDropdownIcon`);
196
210
  return icon;
197
211
  }
@@ -1,11 +1,10 @@
1
- import { makeEraserIcon } from '../icons';
2
1
  import BaseToolWidget from './BaseToolWidget';
3
2
  export default class EraserToolWidget extends BaseToolWidget {
4
3
  getTitle() {
5
4
  return this.localizationTable.eraser;
6
5
  }
7
6
  createIcon() {
8
- return makeEraserIcon();
7
+ return this.editor.icons.makeEraserIcon();
9
8
  }
10
9
  fillDropdown(_dropdown) {
11
10
  // No dropdown associated with the eraser
@@ -3,11 +3,13 @@ import PanZoom from '../../tools/PanZoom';
3
3
  import { ToolbarLocalization } from '../localization';
4
4
  import BaseToolWidget from './BaseToolWidget';
5
5
  export default class HandToolWidget extends BaseToolWidget {
6
- protected tool: PanZoom;
6
+ protected overridePanZoomTool: PanZoom;
7
7
  private touchPanningWidget;
8
- constructor(editor: Editor, tool: PanZoom, localizationTable: ToolbarLocalization);
8
+ private allowTogglingBaseTool;
9
+ constructor(editor: Editor, overridePanZoomTool: PanZoom, localizationTable: ToolbarLocalization);
10
+ private static getPrimaryHandTool;
9
11
  protected getTitle(): string;
10
12
  protected createIcon(): Element;
11
- setSelected(_selected: boolean): void;
12
13
  protected handleClick(): void;
14
+ setSelected(selected: boolean): void;
13
15
  }
@@ -1,9 +1,8 @@
1
1
  import Mat33 from '../../math/Mat33';
2
- import { PanZoomMode } from '../../tools/PanZoom';
2
+ import PanZoom, { PanZoomMode } from '../../tools/PanZoom';
3
3
  import { EditorEventType } from '../../types';
4
4
  import Viewport from '../../Viewport';
5
5
  import { toolbarCSSPrefix } from '../HTMLToolbar';
6
- import { makeAllDevicePanningIcon, makeHandToolIcon, makeTouchPanningIcon, makeZoomIcon } from '../icons';
7
6
  import BaseToolWidget from './BaseToolWidget';
8
7
  import BaseWidget from './BaseWidget';
9
8
  const makeZoomControl = (localizationTable, editor) => {
@@ -66,7 +65,7 @@ class ZoomWidget extends BaseWidget {
66
65
  return this.localizationTable.zoom;
67
66
  }
68
67
  createIcon() {
69
- return makeZoomIcon();
68
+ return this.editor.icons.makeZoomIcon();
70
69
  }
71
70
  handleClick() {
72
71
  this.setDropdownVisible(!this.isDropdownVisible());
@@ -117,24 +116,48 @@ class HandModeWidget extends BaseWidget {
117
116
  }
118
117
  }
119
118
  export default class HandToolWidget extends BaseToolWidget {
120
- constructor(editor, tool, localizationTable) {
119
+ constructor(editor,
120
+ // Pan zoom tool that overrides all other tools (enabling this tool for a device
121
+ // causes that device to pan/zoom instead of interact with the primary tools)
122
+ overridePanZoomTool, localizationTable) {
123
+ const primaryHandTool = HandToolWidget.getPrimaryHandTool(editor.toolController);
124
+ const tool = primaryHandTool !== null && primaryHandTool !== void 0 ? primaryHandTool : overridePanZoomTool;
121
125
  super(editor, tool, localizationTable);
122
- this.tool = tool;
123
- this.container.classList.add('dropdownShowable');
124
- this.touchPanningWidget = new HandModeWidget(editor, localizationTable, tool, PanZoomMode.OneFingerTouchGestures, makeTouchPanningIcon, localizationTable.touchPanning);
126
+ this.overridePanZoomTool = overridePanZoomTool;
127
+ // Only allow toggling a hand tool if we're using the primary hand tool and not the override
128
+ // hand tool for this button.
129
+ this.allowTogglingBaseTool = primaryHandTool !== null;
130
+ // Allow showing/hiding the dropdown, even if `overridePanZoomTool` isn't enabled.
131
+ if (!this.allowTogglingBaseTool) {
132
+ this.container.classList.add('dropdownShowable');
133
+ }
134
+ // Controls for the overriding hand tool.
135
+ this.touchPanningWidget = new HandModeWidget(editor, localizationTable, overridePanZoomTool, PanZoomMode.OneFingerTouchGestures, () => this.editor.icons.makeTouchPanningIcon(), localizationTable.touchPanning);
125
136
  this.addSubWidget(this.touchPanningWidget);
126
- this.addSubWidget(new HandModeWidget(editor, localizationTable, tool, PanZoomMode.SinglePointerGestures, makeAllDevicePanningIcon, localizationTable.anyDevicePanning));
127
137
  this.addSubWidget(new ZoomWidget(editor, localizationTable));
128
138
  }
139
+ static getPrimaryHandTool(toolController) {
140
+ const primaryPanZoomToolList = toolController.getPrimaryTools().filter(tool => tool instanceof PanZoom);
141
+ const primaryPanZoomTool = primaryPanZoomToolList[0];
142
+ return primaryPanZoomTool;
143
+ }
129
144
  getTitle() {
130
145
  return this.localizationTable.handTool;
131
146
  }
132
147
  createIcon() {
133
- return makeHandToolIcon();
134
- }
135
- setSelected(_selected) {
148
+ return this.editor.icons.makeHandToolIcon();
136
149
  }
137
150
  handleClick() {
138
- this.setDropdownVisible(!this.isDropdownVisible());
151
+ if (this.allowTogglingBaseTool) {
152
+ super.handleClick();
153
+ }
154
+ else {
155
+ this.setDropdownVisible(!this.isDropdownVisible());
156
+ }
157
+ }
158
+ setSelected(selected) {
159
+ if (this.allowTogglingBaseTool) {
160
+ super.setSelected(selected);
161
+ }
139
162
  }
140
163
  }
@@ -1,6 +1,7 @@
1
1
  import { ComponentBuilderFactory } from '../../components/builders/types';
2
2
  import Editor from '../../Editor';
3
3
  import Pen from '../../tools/Pen';
4
+ import { KeyPressEvent } from '../../types';
4
5
  import { ToolbarLocalization } from '../localization';
5
6
  import BaseToolWidget from './BaseToolWidget';
6
7
  export interface PenTypeRecord {
@@ -16,4 +17,5 @@ export default class PenToolWidget extends BaseToolWidget {
16
17
  protected createIcon(): Element;
17
18
  private static idCounter;
18
19
  protected fillDropdown(dropdown: HTMLElement): boolean;
20
+ protected onKeyPress(event: KeyPressEvent): boolean;
19
21
  }