js-draw 0.24.1 → 0.25.1

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 (72) hide show
  1. package/dist/bundle.js +2 -2
  2. package/dist/bundledStyles.js +1 -1
  3. package/dist/cjs/Pointer.js +5 -3
  4. package/dist/cjs/localizations/de.js +1 -1
  5. package/dist/cjs/localizations/es.js +1 -1
  6. package/dist/cjs/rendering/renderers/CanvasRenderer.js +2 -0
  7. package/dist/cjs/rendering/renderers/SVGRenderer.d.ts +1 -1
  8. package/dist/cjs/rendering/renderers/SVGRenderer.js +4 -2
  9. package/dist/cjs/testing/getUniquePointerId.d.ts +4 -0
  10. package/dist/cjs/testing/getUniquePointerId.js +16 -0
  11. package/dist/cjs/testing/sendPenEvent.d.ts +1 -1
  12. package/dist/cjs/testing/sendPenEvent.js +4 -1
  13. package/dist/cjs/testing/sendTouchEvent.js +2 -9
  14. package/dist/cjs/toolbar/IconProvider.d.ts +1 -1
  15. package/dist/cjs/toolbar/IconProvider.js +76 -10
  16. package/dist/cjs/toolbar/localization.d.ts +2 -2
  17. package/dist/cjs/toolbar/localization.js +2 -2
  18. package/dist/cjs/toolbar/widgets/BaseToolWidget.d.ts +2 -0
  19. package/dist/cjs/toolbar/widgets/BaseToolWidget.js +7 -0
  20. package/dist/cjs/toolbar/widgets/PenToolWidget.d.ts +3 -1
  21. package/dist/cjs/toolbar/widgets/PenToolWidget.js +125 -41
  22. package/dist/cjs/toolbar/widgets/SelectionToolWidget.js +4 -0
  23. package/dist/cjs/tools/BaseTool.d.ts +17 -1
  24. package/dist/cjs/tools/BaseTool.js +18 -0
  25. package/dist/cjs/tools/Pen.d.ts +5 -2
  26. package/dist/cjs/tools/Pen.js +37 -4
  27. package/dist/cjs/tools/ToolController.js +14 -2
  28. package/dist/mjs/Pointer.mjs +5 -3
  29. package/dist/mjs/localizations/de.mjs +1 -1
  30. package/dist/mjs/localizations/es.mjs +1 -1
  31. package/dist/mjs/rendering/renderers/CanvasRenderer.mjs +2 -0
  32. package/dist/mjs/rendering/renderers/SVGRenderer.d.ts +1 -1
  33. package/dist/mjs/rendering/renderers/SVGRenderer.mjs +4 -2
  34. package/dist/mjs/testing/getUniquePointerId.d.ts +4 -0
  35. package/dist/mjs/testing/getUniquePointerId.mjs +14 -0
  36. package/dist/mjs/testing/sendPenEvent.d.ts +1 -1
  37. package/dist/mjs/testing/sendPenEvent.mjs +4 -1
  38. package/dist/mjs/testing/sendTouchEvent.mjs +2 -9
  39. package/dist/mjs/toolbar/IconProvider.d.ts +1 -1
  40. package/dist/mjs/toolbar/IconProvider.mjs +76 -10
  41. package/dist/mjs/toolbar/localization.d.ts +2 -2
  42. package/dist/mjs/toolbar/localization.mjs +2 -2
  43. package/dist/mjs/toolbar/widgets/BaseToolWidget.d.ts +2 -0
  44. package/dist/mjs/toolbar/widgets/BaseToolWidget.mjs +7 -0
  45. package/dist/mjs/toolbar/widgets/PenToolWidget.d.ts +3 -1
  46. package/dist/mjs/toolbar/widgets/PenToolWidget.mjs +125 -41
  47. package/dist/mjs/toolbar/widgets/SelectionToolWidget.mjs +4 -0
  48. package/dist/mjs/tools/BaseTool.d.ts +17 -1
  49. package/dist/mjs/tools/BaseTool.mjs +18 -0
  50. package/dist/mjs/tools/Pen.d.ts +5 -2
  51. package/dist/mjs/tools/Pen.mjs +37 -4
  52. package/dist/mjs/tools/ToolController.mjs +14 -2
  53. package/package.json +2 -2
  54. package/src/Pointer.ts +5 -2
  55. package/src/localizations/de.ts +2 -2
  56. package/src/localizations/es.ts +1 -1
  57. package/src/rendering/renderers/CanvasRenderer.ts +2 -0
  58. package/src/rendering/renderers/SVGRenderer.ts +6 -3
  59. package/src/testing/getUniquePointerId.ts +18 -0
  60. package/src/testing/sendPenEvent.ts +6 -1
  61. package/src/testing/sendTouchEvent.ts +2 -9
  62. package/src/toolbar/IconProvider.ts +92 -23
  63. package/src/toolbar/localization.ts +4 -4
  64. package/src/toolbar/toolbar.css +1 -0
  65. package/src/toolbar/widgets/BaseToolWidget.ts +10 -1
  66. package/src/toolbar/widgets/PenToolWidget.css +53 -0
  67. package/src/toolbar/widgets/PenToolWidget.ts +156 -44
  68. package/src/toolbar/widgets/SelectionToolWidget.ts +4 -0
  69. package/src/tools/BaseTool.ts +22 -1
  70. package/src/tools/Pen.test.ts +68 -0
  71. package/src/tools/Pen.ts +42 -4
  72. package/src/tools/ToolController.ts +17 -2
@@ -42,6 +42,7 @@ var Pen = /** @class */ (function (_super) {
42
42
  _this.builder = null;
43
43
  _this.lastPoint = null;
44
44
  _this.startPoint = null;
45
+ _this.currentDeviceType = null;
45
46
  _this.snapToGridEnabled = false;
46
47
  _this.angleLockEnabled = false;
47
48
  return _this;
@@ -98,8 +99,8 @@ var Pen = /** @class */ (function (_super) {
98
99
  this.lastPoint = point;
99
100
  this.previewStroke();
100
101
  };
101
- Pen.prototype.onPointerDown = function (_a) {
102
- var current = _a.current, allPointers = _a.allPointers;
102
+ Pen.prototype.onPointerDown = function (event) {
103
+ var current = event.current, allPointers = event.allPointers;
103
104
  var isEraser = current.device === PointerDevice.Eraser;
104
105
  var anyDeviceIsStylus = false;
105
106
  for (var _i = 0, allPointers_1 = allPointers; _i < allPointers_1.length; _i++) {
@@ -109,24 +110,54 @@ var Pen = /** @class */ (function (_super) {
109
110
  break;
110
111
  }
111
112
  }
113
+ // Avoid canceling an existing stroke
114
+ if (this.builder && !this.eventCanCancelStroke(event)) {
115
+ return true;
116
+ }
112
117
  if ((allPointers.length === 1 && !isEraser) || anyDeviceIsStylus) {
113
118
  this.startPoint = this.toStrokePoint(current);
114
119
  this.builder = this.builderFactory(this.startPoint, this.editor.viewport);
120
+ this.currentDeviceType = current.device;
115
121
  return true;
116
122
  }
117
123
  return false;
118
124
  };
125
+ Pen.prototype.eventCanCancelStroke = function (event) {
126
+ var _a, _b;
127
+ // If there has been a delay since the last input event,
128
+ // it's always okay to cancel
129
+ var lastInputTime = (_b = (_a = this.lastPoint) === null || _a === void 0 ? void 0 : _a.time) !== null && _b !== void 0 ? _b : 0;
130
+ if (event.current.timeStamp - lastInputTime > 1000) {
131
+ return true;
132
+ }
133
+ var isPenStroke = this.currentDeviceType === PointerDevice.Pen;
134
+ var isTouchEvent = event.current.device === PointerDevice.Touch;
135
+ // Don't allow pen strokes to be cancelled by touch events.
136
+ if (isPenStroke && isTouchEvent) {
137
+ return false;
138
+ }
139
+ return true;
140
+ };
141
+ Pen.prototype.eventCanBeDeliveredToNonActiveTool = function (event) {
142
+ return this.eventCanCancelStroke(event);
143
+ };
119
144
  Pen.prototype.onPointerMove = function (_a) {
120
145
  var current = _a.current;
121
146
  if (!this.builder)
122
147
  return;
148
+ if (current.device !== this.currentDeviceType)
149
+ return;
123
150
  this.addPointToStroke(this.toStrokePoint(current));
124
151
  };
125
152
  Pen.prototype.onPointerUp = function (_a) {
126
153
  var _b, _c;
127
154
  var current = _a.current;
128
- if (!this.builder) {
129
- return;
155
+ if (!this.builder)
156
+ return false;
157
+ if (current.device !== this.currentDeviceType) {
158
+ // this.builder still exists, so we're handling events from another
159
+ // device type.
160
+ return true;
130
161
  }
131
162
  // onPointerUp events can have zero pressure. Use the last pressure instead.
132
163
  var currentPoint = this.toStrokePoint(current);
@@ -135,8 +166,10 @@ var Pen = /** @class */ (function (_super) {
135
166
  if (current.isPrimary) {
136
167
  this.finalizeStroke();
137
168
  }
169
+ return false;
138
170
  };
139
171
  Pen.prototype.onGestureCancel = function () {
172
+ this.builder = null;
140
173
  this.editor.clearWetInk();
141
174
  };
142
175
  Pen.prototype.finalizeStroke = function () {
@@ -106,8 +106,15 @@ var ToolController = /** @class */ (function () {
106
106
  var _a, _b;
107
107
  var handled = false;
108
108
  if (event.kind === InputEvtType.PointerDownEvt) {
109
+ var canOnlySendToActiveTool = false;
110
+ if (this.activeTool && !this.activeTool.eventCanBeDeliveredToNonActiveTool(event)) {
111
+ canOnlySendToActiveTool = true;
112
+ }
109
113
  for (var _i = 0, _c = this.tools; _i < _c.length; _i++) {
110
114
  var tool = _c[_i];
115
+ if (canOnlySendToActiveTool && tool !== this.activeTool) {
116
+ continue;
117
+ }
111
118
  if (tool.isEnabled() && tool.onPointerDown(event)) {
112
119
  if (this.activeTool !== tool) {
113
120
  (_a = this.activeTool) === null || _a === void 0 ? void 0 : _a.onGestureCancel();
@@ -119,8 +126,13 @@ var ToolController = /** @class */ (function () {
119
126
  }
120
127
  }
121
128
  else if (event.kind === InputEvtType.PointerUpEvt) {
122
- (_b = this.activeTool) === null || _b === void 0 ? void 0 : _b.onPointerUp(event);
123
- this.activeTool = null;
129
+ var upResult = (_b = this.activeTool) === null || _b === void 0 ? void 0 : _b.onPointerUp(event);
130
+ var continueHandlingEvents = upResult && event.allPointers.length > 1;
131
+ // Should the active tool continue handling events (without an additional pointer down?)
132
+ if (!continueHandlingEvents) {
133
+ // No -- Remove the current tool
134
+ this.activeTool = null;
135
+ }
124
136
  handled = true;
125
137
  }
126
138
  else if (event.kind === InputEvtType.PointerMoveEvt) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "0.24.1",
3
+ "version": "0.25.1",
4
4
  "description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
5
5
  "types": "./dist/mjs/lib.d.ts",
6
6
  "main": "./dist/cjs/lib.js",
@@ -79,7 +79,7 @@
79
79
  "bezier-js": "6.1.3"
80
80
  },
81
81
  "devDependencies": {
82
- "@js-draw/build-tool": "^0.24.0",
82
+ "@js-draw/build-tool": "^0.25.0",
83
83
  "@types/bezier-js": "4.1.0",
84
84
  "@types/jest": "29.5.2",
85
85
  "@types/jsdom": "21.1.1"
package/src/Pointer.ts CHANGED
@@ -122,9 +122,12 @@ export default class Pointer {
122
122
  if (device === PointerDevice.PrimaryButtonMouse) {
123
123
  if (evt.buttons & 0x2) {
124
124
  device = PointerDevice.RightButtonMouse;
125
- } else if (!(evt.buttons & 0x1)) {
126
- device = PointerDevice.Other;
127
125
  }
126
+ // Commented out to work around a bug in old versions of Chrome:
127
+ // Left mouse up events were being given type "other".
128
+ // else if (!(evt.buttons & 0x1)) {
129
+ // device = PointerDevice.Other;
130
+ //}
128
131
  }
129
132
 
130
133
  return new Pointer(
@@ -38,8 +38,8 @@ const localization: EditorLocalization = {
38
38
  anyDevicePanning: 'Ansicht mit jedem Eingabegerät verschieben',
39
39
 
40
40
  selectPenType: 'Objekt-Typ: ',
41
- freehandPen: 'Freihand',
42
- pressureSensitiveFreehandPen: 'Stift (druckempfindlich)',
41
+ roundedTipPen: 'Freihand',
42
+ flatTipPen: 'Stift (druckempfindlich)',
43
43
  arrowPen: 'Pfeil',
44
44
  linePen: 'Linie',
45
45
  outlinedRectanglePen: 'Umrissenes Rechteck',
@@ -30,7 +30,7 @@ const localization: EditorLocalization = {
30
30
  filledRectanglePen: 'Rectángulo sin borde',
31
31
  linePen: 'Línea',
32
32
  arrowPen: 'Flecha',
33
- freehandPen: 'Dibuja sin restricción de forma',
33
+ roundedTipPen: 'Lapiz Redondeado',
34
34
  selectPenType: 'Forma de dibuja:',
35
35
  handTool: 'Mover',
36
36
  zoom: 'Zoom',
@@ -116,6 +116,7 @@ export default class CanvasRenderer extends AbstractRenderer {
116
116
  }
117
117
 
118
118
  protected endPath(style: RenderingStyle) {
119
+ this.ctx.save();
119
120
  this.ctx.fillStyle = style.fill.toHexString();
120
121
  this.ctx.fill();
121
122
 
@@ -128,6 +129,7 @@ export default class CanvasRenderer extends AbstractRenderer {
128
129
  }
129
130
 
130
131
  this.ctx.closePath();
132
+ this.ctx.restore();
131
133
  }
132
134
 
133
135
  protected lineTo(point: Point2) {
@@ -104,10 +104,11 @@ export default class SVGRenderer extends AbstractRenderer {
104
104
  }
105
105
  }
106
106
 
107
- // Push [this.fullPath] to the SVG
108
- private addPathToSVG() {
107
+ // Push `this.fullPath` to the SVG. Returns the path added to the SVG, if any.
108
+ // @internal
109
+ protected addPathToSVG() {
109
110
  if (!this.lastPathStyle || this.lastPathString.length === 0) {
110
- return;
111
+ return null;
111
112
  }
112
113
 
113
114
  const pathElem = document.createElementNS(svgNameSpace, 'path');
@@ -127,6 +128,8 @@ export default class SVGRenderer extends AbstractRenderer {
127
128
 
128
129
  this.elem.appendChild(pathElem);
129
130
  this.objectElems?.push(pathElem);
131
+
132
+ return pathElem;
130
133
  }
131
134
 
132
135
  public override drawPath(pathSpec: RenderablePathSpec) {
@@ -0,0 +1,18 @@
1
+ import Pointer from '../Pointer';
2
+
3
+ /** Returns the smallest ID not used by the pointers in the given list. */
4
+ const getUniquePointerId = (pointers: Pointer[]) => {
5
+ let ptrId = 0;
6
+
7
+ const pointerIds = pointers.map(ptr => ptr.id);
8
+ pointerIds.sort();
9
+ for (const pointerId of pointerIds) {
10
+ if (ptrId === pointerId) {
11
+ ptrId = pointerId + 1;
12
+ }
13
+ }
14
+
15
+ return ptrId;
16
+ };
17
+
18
+ export default getUniquePointerId;
@@ -2,6 +2,7 @@ import Editor from '../Editor';
2
2
  import { Point2 } from '../math/Vec2';
3
3
  import Pointer from '../Pointer';
4
4
  import { InputEvtType } from '../types';
5
+ import getUniquePointerId from './getUniquePointerId';
5
6
 
6
7
  /**
7
8
  * Dispatch a pen event to the currently selected tool.
@@ -16,8 +17,10 @@ const sendPenEvent = (
16
17
 
17
18
  allPointers?: Pointer[]
18
19
  ) => {
20
+ const id = getUniquePointerId(allPointers ?? []);
21
+
19
22
  const mainPointer = Pointer.ofCanvasPoint(
20
- point, eventType !== InputEvtType.PointerUpEvt, editor.viewport
23
+ point, eventType !== InputEvtType.PointerUpEvt, editor.viewport, id
21
24
  );
22
25
 
23
26
  editor.toolController.dispatchInputEvent({
@@ -27,5 +30,7 @@ const sendPenEvent = (
27
30
  ],
28
31
  current: mainPointer,
29
32
  });
33
+
34
+ return mainPointer;
30
35
  };
31
36
  export default sendPenEvent;
@@ -2,6 +2,7 @@ import Editor from '../Editor';
2
2
  import { Vec2 } from '../math/Vec2';
3
3
  import Pointer, { PointerDevice } from '../Pointer';
4
4
  import { InputEvtType } from '../types';
5
+ import getUniquePointerId from './getUniquePointerId';
5
6
 
6
7
  /**
7
8
  * Dispatch a touch event to the currently selected tool. Intended for unit tests.
@@ -47,17 +48,9 @@ const sendTouchEvent = (
47
48
  ) => {
48
49
  const canvasPos = editor.viewport.screenToCanvas(screenPos);
49
50
 
50
- let ptrId = 0;
51
- let maxPtrId = 0;
52
-
53
51
  // Get a unique ID for the main pointer
54
52
  // (try to use id=0, but don't use it if it's already in use).
55
- for (const pointer of allOtherPointers ?? []) {
56
- maxPtrId = Math.max(pointer.id, maxPtrId);
57
- if (pointer.id === ptrId) {
58
- ptrId = maxPtrId + 1;
59
- }
60
- }
53
+ const ptrId = getUniquePointerId(allOtherPointers ?? []);
61
54
 
62
55
  const mainPointer = Pointer.ofCanvasPoint(
63
56
  canvasPos, eventType !== InputEvtType.PointerUpEvt, editor.viewport, ptrId, PointerDevice.Touch
@@ -16,20 +16,28 @@ const iconColorFill = `
16
16
  const iconColorStrokeFill = `
17
17
  style='fill: var(--icon-color); stroke: var(--icon-color);'
18
18
  `;
19
- const checkerboardPatternDef = `
20
- <pattern
21
- id='checkerboard'
22
- viewBox='0,0,10,10'
23
- width='20%'
24
- height='20%'
25
- patternUnits='userSpaceOnUse'
26
- >
27
- <rect x=0 y=0 width=10 height=10 fill='white'/>
28
- <rect x=0 y=0 width=5 height=5 fill='gray'/>
29
- <rect x=5 y=5 width=5 height=5 fill='gray'/>
30
- </pattern>
31
- `;
32
- const checkerboardPatternRef = 'url(#checkerboard)';
19
+
20
+ let checkerboardIdCounter = 0;
21
+ const makeCheckerboardPattern = () => {
22
+ 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
+ `;
36
+ const patternRef = `url(#${id})`;
37
+
38
+ return { patternDef, patternRef };
39
+ };
40
+
33
41
 
34
42
  /**
35
43
  * Provides icons that can be used in the toolbar, etc.
@@ -451,13 +459,15 @@ export default class IconProvider {
451
459
  Color4.fromString(color), tipThickness / 40 - 0.1
452
460
  ).toHexString();
453
461
 
462
+ const checkerboardPattern = makeCheckerboardPattern();
463
+
454
464
  const ink = `
455
465
  <path
456
- fill="${checkerboardPatternRef}"
466
+ fill="${checkerboardPattern.patternRef}"
457
467
  d="${inkTipPath}"
458
468
  />
459
469
  <path
460
- fill="${checkerboardPatternRef}"
470
+ fill="${checkerboardPattern.patternRef}"
461
471
  d="${inkTrailPath}"
462
472
  />
463
473
  <path
@@ -472,7 +482,7 @@ export default class IconProvider {
472
482
 
473
483
  const penTip = `
474
484
  <path
475
- fill="${checkerboardPatternRef}"
485
+ fill="${checkerboardPattern.patternRef}"
476
486
  d="${penTipPath}"
477
487
  />
478
488
  <path
@@ -500,7 +510,7 @@ export default class IconProvider {
500
510
 
501
511
  <!-- color bubble -->
502
512
  <path
503
- fill="${checkerboardPatternRef}"
513
+ fill="${checkerboardPattern.patternRef}"
504
514
  d="${colorBubblePath}"
505
515
  />
506
516
  <path
@@ -511,7 +521,7 @@ export default class IconProvider {
511
521
 
512
522
  icon.innerHTML = `
513
523
  <defs>
514
- ${checkerboardPatternDef}
524
+ ${checkerboardPattern.patternDef}
515
525
  </defs>
516
526
  <g>
517
527
  ${ink}
@@ -522,7 +532,13 @@ export default class IconProvider {
522
532
  return icon;
523
533
  }
524
534
 
525
- public makeIconFromFactory(pen: Pen, factory: ComponentBuilderFactory): IconType {
535
+ public makeIconFromFactory(
536
+ pen: Pen,
537
+ factory: ComponentBuilderFactory,
538
+
539
+ // If true, attempts to guess the location of a transparency grid
540
+ includeTransparencyGrid: boolean = false
541
+ ): IconType {
526
542
  // Increase the thickness we use to generate the icon less with larger actual thicknesses.
527
543
  // We want the icon to be recognisable with a large range of thicknesses.
528
544
  const thickness = Math.sqrt(pen.getThickness()) * 3;
@@ -549,9 +565,60 @@ export default class IconProvider {
549
565
  icon.setAttribute('viewBox', '0 0 100 100');
550
566
  viewport.updateScreenSize(Vec2.of(100, 100));
551
567
 
552
- const renderer = new SVGRenderer(icon, viewport);
568
+ let renderer;
569
+
570
+ if (includeTransparencyGrid) {
571
+ const checkerboardPattern = makeCheckerboardPattern();
572
+
573
+ const defs = document.createElementNS(svgNamespace, 'defs');
574
+ defs.innerHTML = checkerboardPattern.patternDef;
575
+ icon.appendChild(defs);
576
+
577
+ const background = document.createElementNS(svgNamespace, 'g');
578
+ icon.appendChild(background);
579
+
580
+ renderer = new class extends SVGRenderer {
581
+ public constructor() {
582
+ super(icon, viewport);
583
+ }
584
+
585
+ protected override addPathToSVG() {
586
+ const addedPath = super.addPathToSVG();
587
+
588
+ if (addedPath) {
589
+ // Add a copy of the path on the background
590
+ const copy = addedPath.cloneNode(true) as SVGPathElement;
591
+ copy.style.zIndex = '-1';
592
+
593
+ // Make the
594
+ if (copy.hasAttribute('fill')
595
+ && copy.getAttribute('fill') !== 'transparent'
596
+ && copy.getAttribute('fill') !== 'none') {
597
+ copy.setAttribute('fill', checkerboardPattern.patternRef);
598
+ }
599
+
600
+ if (copy.hasAttribute('stroke')) {
601
+ copy.setAttribute('stroke', checkerboardPattern.patternRef);
602
+ }
603
+
604
+ background.appendChild(copy);
605
+ }
606
+
607
+ return addedPath;
608
+ }
609
+ }();
610
+ } else {
611
+ renderer = new SVGRenderer(icon, viewport);
612
+ }
553
613
  builder.preview(renderer);
554
614
 
615
+ // If only a single path was rendered, try to give it a checkerboard background to
616
+ // emphasize transparency. TODO: This is very fragile
617
+
618
+
619
+ const bbox = builder.getBBox();
620
+ icon.setAttribute('viewBox', `${bbox.x} ${bbox.y} ${bbox.w} ${bbox.h}`);
621
+
555
622
  return icon;
556
623
  }
557
624
 
@@ -583,8 +650,10 @@ export default class IconProvider {
583
650
  pipette.style.fill = 'var(--icon-color)';
584
651
 
585
652
  if (color) {
653
+ const checkerboardPattern = makeCheckerboardPattern();
654
+
586
655
  const defs = document.createElementNS(svgNamespace, 'defs');
587
- defs.innerHTML = checkerboardPatternDef;
656
+ defs.innerHTML = checkerboardPattern.patternDef;
588
657
  icon.appendChild(defs);
589
658
 
590
659
  const fluidBackground = document.createElementNS(svgNamespace, 'path');
@@ -598,7 +667,7 @@ export default class IconProvider {
598
667
  fluidBackground.setAttribute('d', fluidPathData);
599
668
 
600
669
  fluid.style.fill = color.toHexString();
601
- fluidBackground.style.fill = checkerboardPatternRef;
670
+ fluidBackground.style.fill = checkerboardPattern.patternRef;
602
671
 
603
672
  icon.appendChild(fluidBackground);
604
673
  icon.appendChild(fluid);
@@ -15,8 +15,8 @@ export interface ToolbarLocalization {
15
15
  chooseFile: string;
16
16
  cancel: string;
17
17
  submit: string;
18
- freehandPen: string;
19
- pressureSensitiveFreehandPen: string;
18
+ roundedTipPen: string;
19
+ flatTipPen: string;
20
20
  selectPenType: string;
21
21
  colorLabel: string;
22
22
  pen: string;
@@ -92,8 +92,8 @@ export const defaultToolbarLocalization: ToolbarLocalization = {
92
92
 
93
93
  touchPanning: 'Touchscreen panning',
94
94
 
95
- freehandPen: 'Freehand',
96
- pressureSensitiveFreehandPen: 'Freehand (pressure sensitive)',
95
+ roundedTipPen: 'Rounded Tip',
96
+ flatTipPen: 'Flat Tip',
97
97
  arrowPen: 'Arrow',
98
98
  linePen: 'Line',
99
99
  outlinedRectanglePen: 'Outlined rectangle',
@@ -1,5 +1,6 @@
1
1
  @import url(./widgets/InsertImageWidget.css);
2
2
  @import url(./widgets/OverflowWidget.css);
3
+ @import url(./widgets/PenToolWidget.css);
3
4
 
4
5
 
5
6
  .toolbar-root {
@@ -1,6 +1,6 @@
1
1
  import Editor from '../../Editor';
2
2
  import BaseTool from '../../tools/BaseTool';
3
- import { EditorEventType } from '../../types';
3
+ import { EditorEventType, KeyPressEvent } from '../../types';
4
4
  import { ToolbarLocalization } from '../localization';
5
5
  import BaseWidget from './BaseWidget';
6
6
 
@@ -47,6 +47,15 @@ export default abstract class BaseToolWidget extends BaseWidget {
47
47
  }
48
48
  }
49
49
 
50
+ protected override onKeyPress(event: KeyPressEvent): boolean {
51
+ if (this.isSelected() && event.key === ' ' && this.hasDropdown) {
52
+ this.handleClick();
53
+ return true;
54
+ }
55
+
56
+ return false;
57
+ }
58
+
50
59
  public override addTo(parent: HTMLElement) {
51
60
  const result = super.addTo(parent);
52
61
  this.setSelected(this.targetTool.isEnabled());
@@ -0,0 +1,53 @@
1
+
2
+ .toolbar-pen-type-selector > div {
3
+ display: flex;
4
+ flex-direction: row;
5
+
6
+ max-width: 350px;
7
+ flex-wrap: wrap;
8
+
9
+ --button-size: 56px;
10
+ }
11
+
12
+ .toolbar-pen-type-selector .pen-type-button {
13
+ display: flex;
14
+ flex-direction: column-reverse;
15
+ box-sizing: border-box;
16
+
17
+ flex-shrink: 1;
18
+ margin: 2px;
19
+ }
20
+
21
+ .toolbar-pen-type-selector .pen-type-button:focus-within {
22
+ outline: 2px solid var(--primary-foreground-color);
23
+ }
24
+
25
+ .toolbar-pen-type-selector .pen-type-button input {
26
+ opacity: 0;
27
+ height: 0;
28
+ }
29
+
30
+ .toolbar-pen-type-selector .pen-type-button label {
31
+ display: flex;
32
+ flex-direction: column;
33
+
34
+ width: var(--button-size);
35
+ height: var(--button-size);
36
+
37
+ font-size: 0.7rem;
38
+ align-items: center;
39
+ justify-content: center;
40
+ padding: 4px;
41
+ }
42
+
43
+
44
+ .toolbar-pen-type-selector .pen-type-button .icon {
45
+ flex-grow: 1;
46
+ flex-shrink: 1;
47
+ width: 100%;
48
+ }
49
+
50
+ .toolbar-pen-type-selector .pen-type-button.checked {
51
+ background-color: var(--secondary-background-color);
52
+ color: var(--secondary-foreground-color);
53
+ }