js-draw 0.1.3 → 0.1.4
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.
- package/CHANGELOG.md +5 -0
- package/README.md +19 -10
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +2 -1
- package/dist/src/Editor.js +18 -3
- package/dist/src/SVGLoader.js +1 -1
- package/dist/src/components/Text.js +3 -1
- package/dist/src/toolbar/HTMLToolbar.js +27 -1
- package/dist/src/toolbar/icons.js +1 -0
- package/dist/src/toolbar/localization.d.ts +1 -0
- package/dist/src/toolbar/localization.js +1 -0
- package/dist/src/tools/TextTool.d.ts +1 -0
- package/dist/src/tools/TextTool.js +22 -3
- package/dist-test/test-dist-bundle.html +8 -1
- package/package.json +1 -1
- package/src/Editor.css +2 -0
- package/src/Editor.ts +19 -4
- package/src/SVGLoader.ts +1 -1
- package/src/components/Text.ts +5 -1
- package/src/toolbar/HTMLToolbar.ts +34 -2
- package/src/toolbar/icons.ts +1 -0
- package/src/toolbar/localization.ts +2 -0
- package/src/toolbar/toolbar.css +6 -3
- package/src/tools/TextTool.ts +22 -3
package/dist/src/Editor.d.ts
CHANGED
@@ -14,7 +14,7 @@ import { EditorLocalization } from './localization';
|
|
14
14
|
export interface EditorSettings {
|
15
15
|
renderingMode: RenderingMode;
|
16
16
|
localization: Partial<EditorLocalization>;
|
17
|
-
wheelEventsEnabled: boolean;
|
17
|
+
wheelEventsEnabled: boolean | 'only-if-focused';
|
18
18
|
}
|
19
19
|
export declare class Editor {
|
20
20
|
private container;
|
@@ -48,6 +48,7 @@ export declare class Editor {
|
|
48
48
|
rerender(showImageBounds?: boolean): void;
|
49
49
|
drawWetInk(...path: RenderablePathSpec[]): void;
|
50
50
|
clearWetInk(): void;
|
51
|
+
focus(): void;
|
51
52
|
createHTMLOverlay(overlay: HTMLElement): {
|
52
53
|
remove: () => void;
|
53
54
|
};
|
package/dist/src/Editor.js
CHANGED
@@ -183,13 +183,24 @@ export class Editor {
|
|
183
183
|
})) {
|
184
184
|
evt.preventDefault();
|
185
185
|
}
|
186
|
+
else if (evt.key === 'Escape') {
|
187
|
+
this.renderingRegion.blur();
|
188
|
+
}
|
186
189
|
});
|
187
190
|
this.container.addEventListener('wheel', evt => {
|
188
191
|
let delta = Vec3.of(evt.deltaX, evt.deltaY, evt.deltaZ);
|
189
|
-
// Process wheel events if the ctrl key is down -- we do want to handle
|
192
|
+
// Process wheel events if the ctrl key is down, even if disabled -- we do want to handle
|
190
193
|
// pinch-zooming.
|
191
|
-
if (!
|
192
|
-
|
194
|
+
if (!evt.ctrlKey) {
|
195
|
+
if (!this.settings.wheelEventsEnabled) {
|
196
|
+
return;
|
197
|
+
}
|
198
|
+
else if (this.settings.wheelEventsEnabled === 'only-if-focused') {
|
199
|
+
const focusedChild = this.container.querySelector(':focus');
|
200
|
+
if (!focusedChild) {
|
201
|
+
return;
|
202
|
+
}
|
203
|
+
}
|
193
204
|
}
|
194
205
|
if (evt.deltaMode === WheelEvent.DOM_DELTA_LINE) {
|
195
206
|
delta = delta.times(15);
|
@@ -297,6 +308,10 @@ export class Editor {
|
|
297
308
|
clearWetInk() {
|
298
309
|
this.display.getWetInkRenderer().clear();
|
299
310
|
}
|
311
|
+
// Focuses the region used for text input
|
312
|
+
focus() {
|
313
|
+
this.renderingRegion.focus();
|
314
|
+
}
|
300
315
|
createHTMLOverlay(overlay) {
|
301
316
|
overlay.classList.add('overlay');
|
302
317
|
this.container.appendChild(overlay);
|
package/dist/src/SVGLoader.js
CHANGED
@@ -162,7 +162,7 @@ export default class SVGLoader {
|
|
162
162
|
}
|
163
163
|
const style = {
|
164
164
|
size: fontSize,
|
165
|
-
fontFamily: computedStyles.fontFamily || 'sans',
|
165
|
+
fontFamily: computedStyles.fontFamily || 'sans-serif',
|
166
166
|
renderingStyle: {
|
167
167
|
fill: Color4.fromString(computedStyles.fill)
|
168
168
|
},
|
@@ -11,10 +11,12 @@ export default class Text extends AbstractComponent {
|
|
11
11
|
}
|
12
12
|
static applyTextStyles(ctx, style) {
|
13
13
|
var _a, _b;
|
14
|
+
// Quote the font family if necessary.
|
15
|
+
const fontFamily = style.fontFamily.match(/\s/) ? style.fontFamily.replace(/["]/g, '\\"') : style.fontFamily;
|
14
16
|
ctx.font = [
|
15
17
|
((_a = style.size) !== null && _a !== void 0 ? _a : 12) + 'px',
|
16
18
|
(_b = style.fontWeight) !== null && _b !== void 0 ? _b : '',
|
17
|
-
|
19
|
+
`${fontFamily}`,
|
18
20
|
style.fontWeight
|
19
21
|
].join(' ');
|
20
22
|
ctx.textAlign = 'left';
|
@@ -338,25 +338,51 @@ class TextToolWidget extends ToolbarWidget {
|
|
338
338
|
return makeTextIcon(textStyle);
|
339
339
|
}
|
340
340
|
fillDropdown(dropdown) {
|
341
|
+
const fontRow = document.createElement('div');
|
341
342
|
const colorRow = document.createElement('div');
|
343
|
+
const fontInput = document.createElement('select');
|
344
|
+
const fontLabel = document.createElement('label');
|
342
345
|
const colorInput = document.createElement('input');
|
343
346
|
const colorLabel = document.createElement('label');
|
347
|
+
const fontsInInput = new Set();
|
348
|
+
const addFontToInput = (fontName) => {
|
349
|
+
const option = document.createElement('option');
|
350
|
+
option.value = fontName;
|
351
|
+
option.textContent = fontName;
|
352
|
+
fontInput.appendChild(option);
|
353
|
+
fontsInInput.add(fontName);
|
354
|
+
};
|
355
|
+
fontLabel.innerText = this.localizationTable.fontLabel;
|
344
356
|
colorLabel.innerText = this.localizationTable.colorLabel;
|
345
357
|
colorInput.classList.add('coloris_input');
|
346
358
|
colorInput.type = 'button';
|
347
359
|
colorInput.id = `${toolbarCSSPrefix}-text-color-input-${TextToolWidget.idCounter++}`;
|
348
360
|
colorLabel.setAttribute('for', colorInput.id);
|
361
|
+
addFontToInput('monospace');
|
362
|
+
addFontToInput('serif');
|
363
|
+
addFontToInput('sans-serif');
|
364
|
+
fontInput.id = `${toolbarCSSPrefix}-text-font-input-${TextToolWidget.idCounter++}`;
|
365
|
+
fontLabel.setAttribute('for', fontInput.id);
|
366
|
+
fontInput.onchange = () => {
|
367
|
+
this.tool.setFontFamily(fontInput.value);
|
368
|
+
};
|
349
369
|
colorInput.oninput = () => {
|
350
370
|
this.tool.setColor(Color4.fromString(colorInput.value));
|
351
371
|
};
|
352
372
|
colorRow.appendChild(colorLabel);
|
353
373
|
colorRow.appendChild(colorInput);
|
374
|
+
fontRow.appendChild(fontLabel);
|
375
|
+
fontRow.appendChild(fontInput);
|
354
376
|
this.updateDropdownInputs = () => {
|
355
377
|
const style = this.tool.getTextStyle();
|
356
378
|
colorInput.value = style.renderingStyle.fill.toHexString();
|
379
|
+
if (!fontsInInput.has(style.fontFamily)) {
|
380
|
+
addFontToInput(style.fontFamily);
|
381
|
+
}
|
382
|
+
fontInput.value = style.fontFamily;
|
357
383
|
};
|
358
384
|
this.updateDropdownInputs();
|
359
|
-
dropdown.
|
385
|
+
dropdown.replaceChildren(colorRow, fontRow);
|
360
386
|
return true;
|
361
387
|
}
|
362
388
|
}
|
@@ -125,6 +125,7 @@ export const makeTextIcon = (textStyle) => {
|
|
125
125
|
textNode.setAttribute('x', '50');
|
126
126
|
textNode.setAttribute('y', '75');
|
127
127
|
textNode.style.fontSize = '65px';
|
128
|
+
textNode.style.filter = 'drop-shadow(0px 0px 10px var(--primary-shadow-color))';
|
128
129
|
icon.appendChild(textNode);
|
129
130
|
return icon;
|
130
131
|
};
|
@@ -21,6 +21,7 @@ export default class TextTool extends BaseTool {
|
|
21
21
|
private startTextInput;
|
22
22
|
setEnabled(enabled: boolean): void;
|
23
23
|
onPointerDown({ current, allPointers }: PointerEvt): boolean;
|
24
|
+
onGestureCancel(): void;
|
24
25
|
private dispatchUpdateEvent;
|
25
26
|
setFontFamily(fontFamily: string): void;
|
26
27
|
setColor(color: Color4): void;
|
@@ -70,6 +70,7 @@ export default class TextTool extends BaseTool {
|
|
70
70
|
(_a = this.textInputElem) === null || _a === void 0 ? void 0 : _a.remove();
|
71
71
|
return;
|
72
72
|
}
|
73
|
+
const viewport = this.editor.viewport;
|
73
74
|
const textScreenPos = this.editor.viewport.canvasToScreen(this.textTargetPosition);
|
74
75
|
this.textInputElem.type = 'text';
|
75
76
|
this.textInputElem.placeholder = this.localizationTable.enterTextToInsert;
|
@@ -80,8 +81,12 @@ export default class TextTool extends BaseTool {
|
|
80
81
|
this.textInputElem.style.color = this.textStyle.renderingStyle.fill.toHexString();
|
81
82
|
this.textInputElem.style.position = 'relative';
|
82
83
|
this.textInputElem.style.left = `${textScreenPos.x}px`;
|
84
|
+
this.textInputElem.style.top = `${textScreenPos.y}px`;
|
85
|
+
this.textInputElem.style.margin = '0';
|
86
|
+
const rotation = viewport.getRotationAngle();
|
83
87
|
const ascent = this.getTextAscent(this.textInputElem.value || 'W', this.textStyle);
|
84
|
-
this.textInputElem.style.
|
88
|
+
this.textInputElem.style.transform = `rotate(${rotation * 180 / Math.PI}deg) translate(0, ${-ascent}px)`;
|
89
|
+
this.textInputElem.style.transformOrigin = 'top left';
|
85
90
|
}
|
86
91
|
startTextInput(textCanvasPos, initialText) {
|
87
92
|
this.flushInput();
|
@@ -96,15 +101,25 @@ export default class TextTool extends BaseTool {
|
|
96
101
|
}
|
97
102
|
};
|
98
103
|
this.textInputElem.onblur = () => {
|
99
|
-
|
104
|
+
// Don't remove the input within the context of a blur event handler.
|
105
|
+
// Doing so causes errors.
|
106
|
+
setTimeout(() => this.flushInput(), 0);
|
100
107
|
};
|
101
108
|
this.textInputElem.onkeyup = (evt) => {
|
109
|
+
var _a;
|
102
110
|
if (evt.key === 'Enter') {
|
103
111
|
this.flushInput();
|
112
|
+
this.editor.focus();
|
113
|
+
}
|
114
|
+
else if (evt.key === 'Escape') {
|
115
|
+
// Cancel input.
|
116
|
+
(_a = this.textInputElem) === null || _a === void 0 ? void 0 : _a.remove();
|
117
|
+
this.textInputElem = null;
|
118
|
+
this.editor.focus();
|
104
119
|
}
|
105
120
|
};
|
106
121
|
this.textEditOverlay.replaceChildren(this.textInputElem);
|
107
|
-
setTimeout(() => this.textInputElem.focus(),
|
122
|
+
setTimeout(() => { var _a; return (_a = this.textInputElem) === null || _a === void 0 ? void 0 : _a.focus(); }, 0);
|
108
123
|
}
|
109
124
|
setEnabled(enabled) {
|
110
125
|
super.setEnabled(enabled);
|
@@ -123,6 +138,10 @@ export default class TextTool extends BaseTool {
|
|
123
138
|
}
|
124
139
|
return false;
|
125
140
|
}
|
141
|
+
onGestureCancel() {
|
142
|
+
this.flushInput();
|
143
|
+
this.editor.focus();
|
144
|
+
}
|
126
145
|
dispatchUpdateEvent() {
|
127
146
|
this.updateTextInput();
|
128
147
|
this.editor.notifier.dispatch(EditorEventType.ToolUpdated, {
|
@@ -27,9 +27,16 @@
|
|
27
27
|
wheelEventsEnabled: false,
|
28
28
|
});
|
29
29
|
editor1.addToolbar();
|
30
|
+
editor1.loadFromSVG('<svg><text>Wheel events disabled.</text></svg>');
|
30
31
|
|
31
|
-
const editor2 = new jsdraw.Editor(document.body
|
32
|
+
const editor2 = new jsdraw.Editor(document.body, {
|
33
|
+
wheelEventsEnabled: 'only-if-focused',
|
34
|
+
});
|
32
35
|
editor2.addToolbar();
|
36
|
+
editor2.loadFromSVG('<svg><text>Wheel events enabled, only if focused.</text></svg>');
|
37
|
+
|
38
|
+
const editor3 = new jsdraw.Editor(document.body);
|
39
|
+
editor3.addToolbar();
|
33
40
|
</script>
|
34
41
|
</body>
|
35
42
|
</html>
|
package/package.json
CHANGED
package/src/Editor.css
CHANGED
@@ -8,6 +8,7 @@
|
|
8
8
|
--secondary-background-color: #faf;
|
9
9
|
--primary-foreground-color: black;
|
10
10
|
--secondary-foreground-color: black;
|
11
|
+
--primary-shadow-color: rgba(0, 0, 0, 0.5);
|
11
12
|
}
|
12
13
|
|
13
14
|
@media (prefers-color-scheme: dark) {
|
@@ -17,6 +18,7 @@
|
|
17
18
|
--secondary-background-color: #607;
|
18
19
|
--primary-foreground-color: white;
|
19
20
|
--secondary-foreground-color: white;
|
21
|
+
--primary-shadow-color: rgba(250, 250, 250, 0.5);
|
20
22
|
}
|
21
23
|
}
|
22
24
|
|
package/src/Editor.ts
CHANGED
@@ -29,7 +29,7 @@ export interface EditorSettings {
|
|
29
29
|
// True if touchpad/mousewheel scrolling should scroll the editor instead of the document.
|
30
30
|
// This does not include pinch-zoom events.
|
31
31
|
// Defaults to true.
|
32
|
-
wheelEventsEnabled: boolean;
|
32
|
+
wheelEventsEnabled: boolean|'only-if-focused';
|
33
33
|
}
|
34
34
|
|
35
35
|
export class Editor {
|
@@ -245,16 +245,26 @@ export class Editor {
|
|
245
245
|
ctrlKey: evt.ctrlKey,
|
246
246
|
})) {
|
247
247
|
evt.preventDefault();
|
248
|
+
} else if (evt.key === 'Escape') {
|
249
|
+
this.renderingRegion.blur();
|
248
250
|
}
|
249
251
|
});
|
250
252
|
|
251
253
|
this.container.addEventListener('wheel', evt => {
|
252
254
|
let delta = Vec3.of(evt.deltaX, evt.deltaY, evt.deltaZ);
|
253
255
|
|
254
|
-
// Process wheel events if the ctrl key is down -- we do want to handle
|
256
|
+
// Process wheel events if the ctrl key is down, even if disabled -- we do want to handle
|
255
257
|
// pinch-zooming.
|
256
|
-
if (!
|
257
|
-
|
258
|
+
if (!evt.ctrlKey) {
|
259
|
+
if (!this.settings.wheelEventsEnabled) {
|
260
|
+
return;
|
261
|
+
} else if (this.settings.wheelEventsEnabled === 'only-if-focused') {
|
262
|
+
const focusedChild = this.container.querySelector(':focus');
|
263
|
+
|
264
|
+
if (!focusedChild) {
|
265
|
+
return;
|
266
|
+
}
|
267
|
+
}
|
258
268
|
}
|
259
269
|
|
260
270
|
if (evt.deltaMode === WheelEvent.DOM_DELTA_LINE) {
|
@@ -399,6 +409,11 @@ export class Editor {
|
|
399
409
|
this.display.getWetInkRenderer().clear();
|
400
410
|
}
|
401
411
|
|
412
|
+
// Focuses the region used for text input
|
413
|
+
public focus() {
|
414
|
+
this.renderingRegion.focus();
|
415
|
+
}
|
416
|
+
|
402
417
|
public createHTMLOverlay(overlay: HTMLElement) {
|
403
418
|
overlay.classList.add('overlay');
|
404
419
|
this.container.appendChild(overlay);
|
package/src/SVGLoader.ts
CHANGED
@@ -198,7 +198,7 @@ export default class SVGLoader implements ImageLoader {
|
|
198
198
|
}
|
199
199
|
const style: TextStyle = {
|
200
200
|
size: fontSize,
|
201
|
-
fontFamily: computedStyles.fontFamily || 'sans',
|
201
|
+
fontFamily: computedStyles.fontFamily || 'sans-serif',
|
202
202
|
renderingStyle: {
|
203
203
|
fill: Color4.fromString(computedStyles.fill)
|
204
204
|
},
|
package/src/components/Text.ts
CHANGED
@@ -23,12 +23,16 @@ export default class Text extends AbstractComponent {
|
|
23
23
|
}
|
24
24
|
|
25
25
|
public static applyTextStyles(ctx: CanvasRenderingContext2D, style: TextStyle) {
|
26
|
+
// Quote the font family if necessary.
|
27
|
+
const fontFamily = style.fontFamily.match(/\s/) ? style.fontFamily.replace(/["]/g, '\\"') : style.fontFamily;
|
28
|
+
|
26
29
|
ctx.font = [
|
27
30
|
(style.size ?? 12) + 'px',
|
28
31
|
style.fontWeight ?? '',
|
29
|
-
|
32
|
+
`${fontFamily}`,
|
30
33
|
style.fontWeight
|
31
34
|
].join(' ');
|
35
|
+
|
32
36
|
ctx.textAlign = 'left';
|
33
37
|
}
|
34
38
|
|
@@ -428,10 +428,25 @@ class TextToolWidget extends ToolbarWidget {
|
|
428
428
|
|
429
429
|
private static idCounter: number = 0;
|
430
430
|
protected fillDropdown(dropdown: HTMLElement): boolean {
|
431
|
+
const fontRow = document.createElement('div');
|
431
432
|
const colorRow = document.createElement('div');
|
433
|
+
|
434
|
+
const fontInput = document.createElement('select');
|
435
|
+
const fontLabel = document.createElement('label');
|
436
|
+
|
432
437
|
const colorInput = document.createElement('input');
|
433
438
|
const colorLabel = document.createElement('label');
|
434
439
|
|
440
|
+
const fontsInInput = new Set();
|
441
|
+
const addFontToInput = (fontName: string) => {
|
442
|
+
const option = document.createElement('option');
|
443
|
+
option.value = fontName;
|
444
|
+
option.textContent = fontName;
|
445
|
+
fontInput.appendChild(option);
|
446
|
+
fontsInInput.add(fontName);
|
447
|
+
};
|
448
|
+
|
449
|
+
fontLabel.innerText = this.localizationTable.fontLabel;
|
435
450
|
colorLabel.innerText = this.localizationTable.colorLabel;
|
436
451
|
|
437
452
|
colorInput.classList.add('coloris_input');
|
@@ -439,6 +454,16 @@ class TextToolWidget extends ToolbarWidget {
|
|
439
454
|
colorInput.id = `${toolbarCSSPrefix}-text-color-input-${TextToolWidget.idCounter++}`;
|
440
455
|
colorLabel.setAttribute('for', colorInput.id);
|
441
456
|
|
457
|
+
addFontToInput('monospace');
|
458
|
+
addFontToInput('serif');
|
459
|
+
addFontToInput('sans-serif');
|
460
|
+
fontInput.id = `${toolbarCSSPrefix}-text-font-input-${TextToolWidget.idCounter++}`;
|
461
|
+
fontLabel.setAttribute('for', fontInput.id);
|
462
|
+
|
463
|
+
fontInput.onchange = () => {
|
464
|
+
this.tool.setFontFamily(fontInput.value);
|
465
|
+
};
|
466
|
+
|
442
467
|
colorInput.oninput = () => {
|
443
468
|
this.tool.setColor(Color4.fromString(colorInput.value));
|
444
469
|
};
|
@@ -446,14 +471,21 @@ class TextToolWidget extends ToolbarWidget {
|
|
446
471
|
colorRow.appendChild(colorLabel);
|
447
472
|
colorRow.appendChild(colorInput);
|
448
473
|
|
474
|
+
fontRow.appendChild(fontLabel);
|
475
|
+
fontRow.appendChild(fontInput);
|
476
|
+
|
449
477
|
this.updateDropdownInputs = () => {
|
450
478
|
const style = this.tool.getTextStyle();
|
451
479
|
colorInput.value = style.renderingStyle.fill.toHexString();
|
480
|
+
|
481
|
+
if (!fontsInInput.has(style.fontFamily)) {
|
482
|
+
addFontToInput(style.fontFamily);
|
483
|
+
}
|
484
|
+
fontInput.value = style.fontFamily;
|
452
485
|
};
|
453
486
|
this.updateDropdownInputs();
|
454
487
|
|
455
|
-
dropdown.
|
456
|
-
|
488
|
+
dropdown.replaceChildren(colorRow, fontRow);
|
457
489
|
return true;
|
458
490
|
}
|
459
491
|
}
|
package/src/toolbar/icons.ts
CHANGED
@@ -143,6 +143,7 @@ export const makeTextIcon = (textStyle: TextStyle) => {
|
|
143
143
|
textNode.setAttribute('x', '50');
|
144
144
|
textNode.setAttribute('y', '75');
|
145
145
|
textNode.style.fontSize = '65px';
|
146
|
+
textNode.style.filter = 'drop-shadow(0px 0px 10px var(--primary-shadow-color))';
|
146
147
|
|
147
148
|
icon.appendChild(textNode);
|
148
149
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
|
2
2
|
|
3
3
|
export interface ToolbarLocalization {
|
4
|
+
fontLabel: string;
|
4
5
|
anyDevicePanning: string;
|
5
6
|
touchPanning: string;
|
6
7
|
outlinedRectanglePen: string;
|
@@ -32,6 +33,7 @@ export const defaultToolbarLocalization: ToolbarLocalization = {
|
|
32
33
|
handTool: 'Pan',
|
33
34
|
thicknessLabel: 'Thickness: ',
|
34
35
|
colorLabel: 'Color: ',
|
36
|
+
fontLabel: 'Font: ',
|
35
37
|
resizeImageToSelection: 'Resize image to selection',
|
36
38
|
deleteSelection: 'Delete selection',
|
37
39
|
undo: 'Undo',
|
package/src/toolbar/toolbar.css
CHANGED
@@ -12,6 +12,9 @@
|
|
12
12
|
flex-direction: row;
|
13
13
|
justify-content: center;
|
14
14
|
|
15
|
+
/* Display above selection dialogs, etc. */
|
16
|
+
z-index: 1000;
|
17
|
+
|
15
18
|
font-family: system-ui, -apple-system, sans-serif;
|
16
19
|
}
|
17
20
|
|
@@ -41,13 +44,13 @@
|
|
41
44
|
background-color: var(--primary-background-color);
|
42
45
|
color: var(--primary-foreground-color);
|
43
46
|
border: none;
|
44
|
-
box-shadow: 0px 0px 2px var(--primary-
|
47
|
+
box-shadow: 0px 0px 2px var(--primary-shadow-color);
|
45
48
|
|
46
49
|
transition: background-color 0.25s ease, box-shadow 0.25s ease, opacity 0.3s ease;
|
47
50
|
}
|
48
51
|
|
49
52
|
.toolbar-button:hover, .toolbar-root button:not(:disabled):hover {
|
50
|
-
box-shadow: 0px 2px 4px var(--primary-
|
53
|
+
box-shadow: 0px 2px 4px var(--primary-shadow-color);
|
51
54
|
}
|
52
55
|
|
53
56
|
.toolbar-root button:disabled {
|
@@ -90,7 +93,7 @@
|
|
90
93
|
/* Prevent overlap/being displayed under the undo/redo buttons */
|
91
94
|
z-index: 2;
|
92
95
|
background-color: var(--primary-background-color);
|
93
|
-
box-shadow: 0px 3px 3px var(--primary-
|
96
|
+
box-shadow: 0px 3px 3px var(--primary-shadow-color);
|
94
97
|
}
|
95
98
|
|
96
99
|
.toolbar-buttonGroup {
|
package/src/tools/TextTool.ts
CHANGED
@@ -92,6 +92,7 @@ export default class TextTool extends BaseTool {
|
|
92
92
|
return;
|
93
93
|
}
|
94
94
|
|
95
|
+
const viewport = this.editor.viewport;
|
95
96
|
const textScreenPos = this.editor.viewport.canvasToScreen(this.textTargetPosition);
|
96
97
|
this.textInputElem.type = 'text';
|
97
98
|
this.textInputElem.placeholder = this.localizationTable.enterTextToInsert;
|
@@ -103,8 +104,13 @@ export default class TextTool extends BaseTool {
|
|
103
104
|
|
104
105
|
this.textInputElem.style.position = 'relative';
|
105
106
|
this.textInputElem.style.left = `${textScreenPos.x}px`;
|
107
|
+
this.textInputElem.style.top = `${textScreenPos.y}px`;
|
108
|
+
this.textInputElem.style.margin = '0';
|
109
|
+
|
110
|
+
const rotation = viewport.getRotationAngle();
|
106
111
|
const ascent = this.getTextAscent(this.textInputElem.value || 'W', this.textStyle);
|
107
|
-
this.textInputElem.style.
|
112
|
+
this.textInputElem.style.transform = `rotate(${rotation * 180 / Math.PI}deg) translate(0, ${-ascent}px)`;
|
113
|
+
this.textInputElem.style.transformOrigin = 'top left';
|
108
114
|
}
|
109
115
|
|
110
116
|
private startTextInput(textCanvasPos: Vec2, initialText: string) {
|
@@ -121,16 +127,24 @@ export default class TextTool extends BaseTool {
|
|
121
127
|
}
|
122
128
|
};
|
123
129
|
this.textInputElem.onblur = () => {
|
124
|
-
|
130
|
+
// Don't remove the input within the context of a blur event handler.
|
131
|
+
// Doing so causes errors.
|
132
|
+
setTimeout(() => this.flushInput(), 0);
|
125
133
|
};
|
126
134
|
this.textInputElem.onkeyup = (evt) => {
|
127
135
|
if (evt.key === 'Enter') {
|
128
136
|
this.flushInput();
|
137
|
+
this.editor.focus();
|
138
|
+
} else if (evt.key === 'Escape') {
|
139
|
+
// Cancel input.
|
140
|
+
this.textInputElem?.remove();
|
141
|
+
this.textInputElem = null;
|
142
|
+
this.editor.focus();
|
129
143
|
}
|
130
144
|
};
|
131
145
|
|
132
146
|
this.textEditOverlay.replaceChildren(this.textInputElem);
|
133
|
-
setTimeout(() => this.textInputElem
|
147
|
+
setTimeout(() => this.textInputElem?.focus(), 0);
|
134
148
|
}
|
135
149
|
|
136
150
|
public setEnabled(enabled: boolean) {
|
@@ -156,6 +170,11 @@ export default class TextTool extends BaseTool {
|
|
156
170
|
return false;
|
157
171
|
}
|
158
172
|
|
173
|
+
public onGestureCancel(): void {
|
174
|
+
this.flushInput();
|
175
|
+
this.editor.focus();
|
176
|
+
}
|
177
|
+
|
159
178
|
private dispatchUpdateEvent() {
|
160
179
|
this.updateTextInput();
|
161
180
|
this.editor.notifier.dispatch(EditorEventType.ToolUpdated, {
|