js-draw 1.11.2 → 1.13.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 (49) hide show
  1. package/dist/Editor.css +66 -118
  2. package/dist/bundle.js +2 -2
  3. package/dist/bundledStyles.js +1 -1
  4. package/dist/cjs/Editor.d.ts +4 -2
  5. package/dist/cjs/Editor.js +61 -39
  6. package/dist/cjs/components/AbstractComponent.d.ts +7 -0
  7. package/dist/cjs/components/AbstractComponent.js +7 -5
  8. package/dist/cjs/components/util/StrokeSmoother.js +11 -4
  9. package/dist/cjs/localizations/es.js +11 -22
  10. package/dist/cjs/rendering/Display.d.ts +2 -0
  11. package/dist/cjs/rendering/Display.js +4 -0
  12. package/dist/cjs/toolbar/DropdownToolbar.d.ts +3 -1
  13. package/dist/cjs/toolbar/DropdownToolbar.js +2 -0
  14. package/dist/cjs/toolbar/EdgeToolbar.js +6 -5
  15. package/dist/cjs/toolbar/widgets/BaseWidget.js +2 -0
  16. package/dist/cjs/toolbar/widgets/components/makeGridSelector.js +10 -0
  17. package/dist/cjs/util/addLongPressOrHoverCssClasses.d.ts +10 -0
  18. package/dist/cjs/util/addLongPressOrHoverCssClasses.js +34 -0
  19. package/dist/cjs/util/listenForLongPressOrHover.d.ts +13 -0
  20. package/dist/cjs/util/listenForLongPressOrHover.js +80 -0
  21. package/dist/cjs/util/listenForLongPressOrHover.test.d.ts +1 -0
  22. package/dist/cjs/util/mitLicenseAttribution.d.ts +2 -0
  23. package/dist/cjs/util/mitLicenseAttribution.js +28 -0
  24. package/dist/cjs/version.js +1 -1
  25. package/dist/mjs/Editor.d.ts +4 -2
  26. package/dist/mjs/Editor.mjs +61 -39
  27. package/dist/mjs/components/AbstractComponent.d.ts +7 -0
  28. package/dist/mjs/components/AbstractComponent.mjs +7 -5
  29. package/dist/mjs/components/util/StrokeSmoother.mjs +11 -4
  30. package/dist/mjs/localizations/es.mjs +11 -22
  31. package/dist/mjs/rendering/Display.d.ts +2 -0
  32. package/dist/mjs/rendering/Display.mjs +4 -0
  33. package/dist/mjs/toolbar/DropdownToolbar.d.ts +3 -1
  34. package/dist/mjs/toolbar/DropdownToolbar.mjs +2 -0
  35. package/dist/mjs/toolbar/EdgeToolbar.mjs +6 -5
  36. package/dist/mjs/toolbar/widgets/BaseWidget.mjs +2 -0
  37. package/dist/mjs/toolbar/widgets/components/makeGridSelector.mjs +10 -0
  38. package/dist/mjs/util/addLongPressOrHoverCssClasses.d.ts +10 -0
  39. package/dist/mjs/util/addLongPressOrHoverCssClasses.mjs +29 -0
  40. package/dist/mjs/util/listenForLongPressOrHover.d.ts +13 -0
  41. package/dist/mjs/util/listenForLongPressOrHover.mjs +78 -0
  42. package/dist/mjs/util/listenForLongPressOrHover.test.d.ts +1 -0
  43. package/dist/mjs/util/mitLicenseAttribution.d.ts +2 -0
  44. package/dist/mjs/util/mitLicenseAttribution.mjs +26 -0
  45. package/dist/mjs/version.mjs +1 -1
  46. package/package.json +2 -2
  47. package/src/toolbar/EdgeToolbar.scss +19 -97
  48. package/src/toolbar/utils/labelVisibleOnHover.scss +114 -0
  49. package/src/toolbar/widgets/components/makeGridSelector.scss +1 -0
@@ -20,6 +20,7 @@ const ToolbarShortcutHandler_1 = __importDefault(require("../../tools/ToolbarSho
20
20
  const inputEvents_1 = require("../../inputEvents");
21
21
  const constants_1 = require("../constants");
22
22
  const DropdownLayoutManager_1 = __importDefault(require("./layout/DropdownLayoutManager"));
23
+ const addLongPressOrHoverCssClasses_1 = __importDefault(require("../../util/addLongPressOrHoverCssClasses"));
23
24
  /**
24
25
  * A set of labels that allow toolbar themes to treat buttons differently.
25
26
  */
@@ -66,6 +67,7 @@ class BaseWidget {
66
67
  this.button.oncontextmenu = event => {
67
68
  event.preventDefault();
68
69
  };
70
+ (0, addLongPressOrHoverCssClasses_1.default)(this.button);
69
71
  }
70
72
  /**
71
73
  * Should return a constant true or false value. If true (the default),
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const ReactiveValue_1 = require("../../../util/ReactiveValue");
7
7
  const stopPropagationOfScrollingWheelEvents_1 = __importDefault(require("../../../util/stopPropagationOfScrollingWheelEvents"));
8
+ const addLongPressOrHoverCssClasses_1 = __importDefault(require("../../../util/addLongPressOrHoverCssClasses"));
8
9
  const constants_1 = require("../../constants");
9
10
  let idCounter = 0;
10
11
  /**
@@ -36,6 +37,9 @@ labelText, defaultId, choices) => {
36
37
  const button = document.createElement('input');
37
38
  button.type = 'radio';
38
39
  button.id = `${constants_1.toolbarCSSPrefix}-grid-select-button-${idCounter++}`;
40
+ // Some toolbars only show the label on hover. Having long press or hover
41
+ // CSS classes are helpful here.
42
+ (0, addLongPressOrHoverCssClasses_1.default)(buttonContainer);
39
43
  // Clicking any part of labelContainer triggers the radio button.
40
44
  const labelContainer = document.createElement('label');
41
45
  const rebuildLabel = () => {
@@ -81,6 +85,12 @@ labelText, defaultId, choices) => {
81
85
  button.onblur = () => {
82
86
  buttonContainer.classList.remove('focus-visible');
83
87
  };
88
+ // Prevent the right-click menu from being shown on long-press
89
+ // (important for some toolbars that use long-press gestures to
90
+ // show grid selector labels).
91
+ buttonContainer.oncontextmenu = event => {
92
+ event.preventDefault();
93
+ };
84
94
  buttonContainer.replaceChildren(button, labelContainer);
85
95
  menuContainer.appendChild(buttonContainer);
86
96
  // Set whether the current button is checked
@@ -0,0 +1,10 @@
1
+ /**
2
+ * When a pointer is inside `element`, after a delay, adds the `has-long-press-or-hover`
3
+ * CSS class to `element`.
4
+ *
5
+ * When no pointers are inside `element`, adds the CSS class `no-long-press-or-hover`.
6
+ */
7
+ declare const addLongPressOrHoverCssClasses: (element: HTMLElement) => {
8
+ removeEventListeners: () => void;
9
+ };
10
+ export default addLongPressOrHoverCssClasses;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const listenForLongPressOrHover_1 = __importDefault(require("./listenForLongPressOrHover"));
7
+ /**
8
+ * When a pointer is inside `element`, after a delay, adds the `has-long-press-or-hover`
9
+ * CSS class to `element`.
10
+ *
11
+ * When no pointers are inside `element`, adds the CSS class `no-long-press-or-hover`.
12
+ */
13
+ const addLongPressOrHoverCssClasses = (element) => {
14
+ const hasLongPressClass = 'has-long-press-or-hover';
15
+ const noLongPressClass = 'no-long-press-or-hover';
16
+ element.classList.add('no-long-press-or-hover');
17
+ const { removeListeners } = (0, listenForLongPressOrHover_1.default)(element, {
18
+ onStart() {
19
+ element.classList.remove(noLongPressClass);
20
+ element.classList.add(hasLongPressClass);
21
+ },
22
+ onEnd() {
23
+ element.classList.add(noLongPressClass);
24
+ element.classList.remove(hasLongPressClass);
25
+ },
26
+ });
27
+ return {
28
+ removeEventListeners: () => {
29
+ element.classList.remove(noLongPressClass);
30
+ removeListeners();
31
+ },
32
+ };
33
+ };
34
+ exports.default = addLongPressOrHoverCssClasses;
@@ -0,0 +1,13 @@
1
+ type Options = {
2
+ onStart: () => void;
3
+ onEnd: () => void;
4
+ longPressTimeout?: number;
5
+ };
6
+ /**
7
+ * Calls `options.onStart` at the start of a long press or hover.
8
+ * Calls `options.onEnd` when no pointers are within the container.
9
+ */
10
+ declare const listenForLongPressOrHover: (target: HTMLElement, options: Options) => {
11
+ removeListeners: () => void;
12
+ };
13
+ export default listenForLongPressOrHover;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * Calls `options.onStart` at the start of a long press or hover.
5
+ * Calls `options.onEnd` when no pointers are within the container.
6
+ */
7
+ const listenForLongPressOrHover = (target, options) => {
8
+ const pointersInside = new Map();
9
+ let timeoutId = null;
10
+ let isLongPressInProgress = false;
11
+ const updateTimeout = () => {
12
+ if (pointersInside.size === 0) {
13
+ if (isLongPressInProgress) {
14
+ isLongPressInProgress = false;
15
+ options.onEnd();
16
+ }
17
+ else if (timeoutId !== null) {
18
+ clearTimeout(timeoutId);
19
+ timeoutId = null;
20
+ }
21
+ }
22
+ else {
23
+ const nowTime = Date.now();
24
+ let timeSinceFirstPointer = 0; // ms
25
+ for (const record of pointersInside.values()) {
26
+ const timeSince = nowTime - record.timeEnter;
27
+ timeSinceFirstPointer = Math.max(timeSince, timeSinceFirstPointer);
28
+ }
29
+ const longPressTimeout = options.longPressTimeout ?? 700; // ms
30
+ if (timeoutId !== null) {
31
+ clearTimeout(timeoutId);
32
+ timeoutId = null;
33
+ }
34
+ const timeLeft = longPressTimeout - timeSinceFirstPointer;
35
+ if (timeLeft <= 0) {
36
+ options.onStart();
37
+ isLongPressInProgress = true;
38
+ }
39
+ else {
40
+ timeoutId = setTimeout(() => {
41
+ timeoutId = null;
42
+ updateTimeout();
43
+ }, timeLeft);
44
+ }
45
+ }
46
+ };
47
+ // Detects long press
48
+ const pointerEventListener = (event) => {
49
+ const eventRecord = {
50
+ timeEnter: Date.now(),
51
+ };
52
+ if (event.type === 'pointerenter') {
53
+ pointersInside.set(event.pointerId, eventRecord);
54
+ }
55
+ else if (event.type === 'pointerleave' || event.type === 'pointercancel') {
56
+ // In some cases (for example, a click with a stylus on Android/Chrome), moving the pen
57
+ // over the target, clicking, then moving the pen out of the target produces input
58
+ // similar to this:
59
+ // - pointerenter (pointerId: 4)
60
+ // - pointerleave (pointerId: 4)
61
+ // - pointerenter (pointerId: 6)
62
+ // - pointerenter (pointerId: 1)
63
+ // - pointerleave (pointerId: 6)
64
+ // Observe that no pointerleave event was fired for the pointer with ID 1.
65
+ pointersInside.clear();
66
+ }
67
+ updateTimeout();
68
+ };
69
+ target.addEventListener('pointerenter', pointerEventListener);
70
+ target.addEventListener('pointerleave', pointerEventListener);
71
+ target.addEventListener('pointercancel', pointerEventListener);
72
+ return {
73
+ removeListeners: () => {
74
+ target.removeEventListener('pointerenter', pointerEventListener);
75
+ target.removeEventListener('pointerleave', pointerEventListener);
76
+ target.removeEventListener('pointercancel', pointerEventListener);
77
+ },
78
+ };
79
+ };
80
+ exports.default = listenForLongPressOrHover;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ declare const mitLicenseAttribution: (copyright: string) => string;
2
+ export default mitLicenseAttribution;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const mitLicenseAttribution = (copyright) => {
4
+ const removeSingleLineBreaks = (text) => text.replace(/([^\n])[\n]([^\n])/g, '$1 $2');
5
+ return removeSingleLineBreaks(`
6
+ MIT License
7
+
8
+ Copyright (c) ${copyright}
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.`);
27
+ };
28
+ exports.default = mitLicenseAttribution;
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = {
4
- number: '1.11.2',
4
+ number: '1.13.1',
5
5
  };
@@ -253,6 +253,8 @@ export declare class Editor {
253
253
  addToolbar(defaultLayout?: boolean): AbstractToolbar;
254
254
  private registerListeners;
255
255
  private updateEditorSizeVariables;
256
+ /** @internal */
257
+ protected handleHTMLWheelEvent(event: WheelEvent): boolean | undefined;
256
258
  private pointers;
257
259
  private getPointerList;
258
260
  /**
@@ -312,9 +314,9 @@ export declare class Editor {
312
314
  remove: () => void;
313
315
  };
314
316
  /** @internal */
315
- protected handleHTMLKeyDownEvent(htmlEvent: KeyboardEvent): void;
317
+ protected handleHTMLKeyDownEvent(htmlEvent: KeyboardEvent): boolean;
316
318
  /** @internal */
317
- protected handleHTMLKeyUpEvent(htmlEvent: KeyboardEvent): void;
319
+ protected handleHTMLKeyUpEvent(htmlEvent: KeyboardEvent): boolean;
318
320
  /**
319
321
  * Adds event listners for keypresses (and drop events) on `elem` and forwards those
320
322
  * events to the editor.
@@ -28,6 +28,7 @@ import version from './version.mjs';
28
28
  import { editorImageToSVGSync, editorImageToSVGAsync } from './image/export/editorImageToSVG.mjs';
29
29
  import { MutableReactiveValue } from './util/ReactiveValue.mjs';
30
30
  import listenForKeyboardEventsFrom from './util/listenForKeyboardEventsFrom.mjs';
31
+ import mitLicenseAttribution from './util/mitLicenseAttribution.mjs';
31
32
  /**
32
33
  * The main entrypoint for the full editor.
33
34
  *
@@ -238,41 +239,7 @@ export class Editor {
238
239
  this.handleKeyEventsFrom(this.renderingRegion);
239
240
  this.handlePointerEventsFrom(this.accessibilityAnnounceArea);
240
241
  this.container.addEventListener('wheel', evt => {
241
- let delta = Vec3.of(evt.deltaX, evt.deltaY, evt.deltaZ);
242
- // Process wheel events if the ctrl key is down, even if disabled -- we do want to handle
243
- // pinch-zooming.
244
- if (!evt.ctrlKey && !evt.metaKey) {
245
- if (!this.settings.wheelEventsEnabled) {
246
- return;
247
- }
248
- else if (this.settings.wheelEventsEnabled === 'only-if-focused') {
249
- const focusedChild = this.container.querySelector(':focus');
250
- if (!focusedChild) {
251
- return;
252
- }
253
- }
254
- }
255
- if (evt.deltaMode === WheelEvent.DOM_DELTA_LINE) {
256
- delta = delta.times(15);
257
- }
258
- else if (evt.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
259
- delta = delta.times(100);
260
- }
261
- if (evt.ctrlKey || evt.metaKey) {
262
- delta = Vec3.of(0, 0, evt.deltaY);
263
- }
264
- // Ensure that `pos` is relative to `this.renderingRegion`
265
- const bbox = this.renderingRegion.getBoundingClientRect();
266
- const pos = Vec2.of(evt.clientX, evt.clientY).minus(Vec2.of(bbox.left, bbox.top));
267
- if (this.toolController.dispatchInputEvent({
268
- kind: InputEvtType.WheelEvt,
269
- delta,
270
- screenPos: pos,
271
- })) {
272
- evt.preventDefault();
273
- return true;
274
- }
275
- return false;
242
+ this.handleHTMLWheelEvent(evt);
276
243
  });
277
244
  const handleResize = () => {
278
245
  this.viewport.updateScreenSize(Vec2.of(this.display.width, this.display.height));
@@ -316,6 +283,44 @@ export class Editor {
316
283
  this.container.style.setProperty('--editor-current-display-width-px', `${this.renderingRegion.clientWidth}px`);
317
284
  this.container.style.setProperty('--editor-current-display-height-px', `${this.renderingRegion.clientHeight}px`);
318
285
  }
286
+ /** @internal */
287
+ handleHTMLWheelEvent(event) {
288
+ let delta = Vec3.of(event.deltaX, event.deltaY, event.deltaZ);
289
+ // Process wheel events if the ctrl key is down, even if disabled -- we do want to handle
290
+ // pinch-zooming.
291
+ if (!event.ctrlKey && !event.metaKey) {
292
+ if (!this.settings.wheelEventsEnabled) {
293
+ return;
294
+ }
295
+ else if (this.settings.wheelEventsEnabled === 'only-if-focused') {
296
+ const focusedChild = this.container.querySelector(':focus');
297
+ if (!focusedChild) {
298
+ return;
299
+ }
300
+ }
301
+ }
302
+ if (event.deltaMode === WheelEvent.DOM_DELTA_LINE) {
303
+ delta = delta.times(15);
304
+ }
305
+ else if (event.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
306
+ delta = delta.times(100);
307
+ }
308
+ if (event.ctrlKey || event.metaKey) {
309
+ delta = Vec3.of(0, 0, event.deltaY);
310
+ }
311
+ // Ensure that `pos` is relative to `this.renderingRegion`
312
+ const bbox = this.renderingRegion.getBoundingClientRect();
313
+ const pos = Vec2.of(event.clientX, event.clientY).minus(Vec2.of(bbox.left, bbox.top));
314
+ if (this.toolController.dispatchInputEvent({
315
+ kind: InputEvtType.WheelEvt,
316
+ delta,
317
+ screenPos: pos,
318
+ })) {
319
+ event.preventDefault();
320
+ return true;
321
+ }
322
+ return false;
323
+ }
319
324
  getPointerList() {
320
325
  const nowTime = performance.now();
321
326
  const res = [];
@@ -624,14 +629,18 @@ export class Editor {
624
629
  const event = keyPressEventFromHTMLEvent(htmlEvent);
625
630
  if (this.toolController.dispatchInputEvent(event)) {
626
631
  htmlEvent.preventDefault();
632
+ return true;
627
633
  }
628
634
  else if (event.key === 't' || event.key === 'T') {
629
635
  htmlEvent.preventDefault();
630
636
  this.display.rerenderAsText();
637
+ return true;
631
638
  }
632
639
  else if (event.key === 'Escape') {
633
640
  this.renderingRegion.blur();
641
+ return true;
634
642
  }
643
+ return false;
635
644
  }
636
645
  /** @internal */
637
646
  handleHTMLKeyUpEvent(htmlEvent) {
@@ -639,7 +648,9 @@ export class Editor {
639
648
  const event = keyUpEventFromHTMLEvent(htmlEvent);
640
649
  if (this.toolController.dispatchInputEvent(event)) {
641
650
  htmlEvent.preventDefault();
651
+ return true;
642
652
  }
653
+ return false;
643
654
  }
644
655
  /**
645
656
  * Adds event listners for keypresses (and drop events) on `elem` and forwards those
@@ -1159,8 +1170,9 @@ export class Editor {
1159
1170
  ` ${this.viewport.getScaleFactor()}x zoom, ${180 / Math.PI * this.viewport.getRotationAngle()}° rotation`,
1160
1171
  ` ${this.image.estimateNumElements()} components`,
1161
1172
  ` auto-resize: ${this.image.getAutoresizeEnabled() ? 'enabled' : 'disabled'}`,
1162
- ` ${this.getImportExportRect().w}x${this.getImportExportRect().h} image size`,
1163
- ` ${screenSize.x}x${screenSize.y} screen size`,
1173
+ ` image size: ${this.getImportExportRect().w}x${this.getImportExportRect().h}`,
1174
+ ` screen size: ${screenSize.x}x${screenSize.y}`,
1175
+ ` device pixel ratio: ${this.display.getDevicePixelRatio()}`,
1164
1176
  ' cache:',
1165
1177
  ` ${this.display.getCache().getDebugInfo()
1166
1178
  // Indent
@@ -1173,9 +1185,19 @@ export class Editor {
1173
1185
  text: [
1174
1186
  `This image editor is powered by js-draw v${version.number}.`,
1175
1187
  '',
1176
- 'js-draw uses several libraries at runtime. Particularly noteworthy are:',
1188
+ 'At runtime, js-draw uses',
1177
1189
  ' - The Coloris color picker: https://github.com/mdbassit/Coloris',
1178
- ' - The bezier.js Bézier curve library: https://github.com/Pomax/bezierjs'
1190
+ ' - The bezier.js Bézier curve library: https://github.com/Pomax/bezierjs',
1191
+ '',
1192
+ 'Both are licensed under the MIT license:',
1193
+ '',
1194
+ '',
1195
+ '== Coloris ==',
1196
+ mitLicenseAttribution('2021 Mohammed Bassit'),
1197
+ '',
1198
+ '',
1199
+ '== Bezier.js ==',
1200
+ mitLicenseAttribution('2023 Mike "Pomax" Kamermans'),
1179
1201
  ].join('\n'),
1180
1202
  minimized: true,
1181
1203
  });
@@ -114,6 +114,13 @@ export default abstract class AbstractComponent {
114
114
  intersectsRect(rect: Rect2): boolean;
115
115
  protected abstract serializeToJSON(): any[] | Record<string, any> | number | string | null;
116
116
  protected abstract applyTransformation(affineTransfm: Mat33): void;
117
+ /**
118
+ * Returns a command that, when applied, transforms this by [affineTransfm] and
119
+ * updates the editor.
120
+ *
121
+ * The transformed component is also moved to the top (use
122
+ * {@link AbstractComponent.setZIndexAndTransformBy} to avoid this behavior).
123
+ */
117
124
  transformBy(affineTransfm: Mat33): SerializableCommand;
118
125
  setZIndex(newZIndex: number): SerializableCommand;
119
126
  /**
@@ -136,11 +136,13 @@ class AbstractComponent {
136
136
  const testLines = rect.getEdges();
137
137
  return testLines.some(edge => this.intersects(edge));
138
138
  }
139
- // Returns a command that, when applied, transforms this by [affineTransfm] and
140
- // updates the editor.
141
- //
142
- // The transformed component is also moved to the top (use {@link setZIndexAndTransformBy} to
143
- // avoid this behavior).
139
+ /**
140
+ * Returns a command that, when applied, transforms this by [affineTransfm] and
141
+ * updates the editor.
142
+ *
143
+ * The transformed component is also moved to the top (use
144
+ * {@link AbstractComponent.setZIndexAndTransformBy} to avoid this behavior).
145
+ */
144
146
  transformBy(affineTransfm) {
145
147
  return new AbstractComponent.TransformElementCommand(affineTransfm, this.getId(), this);
146
148
  }
@@ -144,7 +144,7 @@ export class StrokeSmoother {
144
144
  }
145
145
  let exitingVec = this.computeExitingVec();
146
146
  // Find the intersection between the entering vector and the exiting vector
147
- const maxRelativeLength = 2.4;
147
+ const maxRelativeLength = 1.7;
148
148
  const segmentStart = this.buffer[0];
149
149
  const segmentEnd = newPoint.pos;
150
150
  const startEndDist = segmentEnd.minus(segmentStart).magnitude();
@@ -165,11 +165,18 @@ export class StrokeSmoother {
165
165
  if (intersection) {
166
166
  controlPoint = intersection.point;
167
167
  }
168
- // No intersection or the intersection is one of the end points?
169
- if (!controlPoint || segmentStart.eq(controlPoint) || segmentEnd.eq(controlPoint)) {
168
+ // No intersection?
169
+ if (!controlPoint) {
170
+ // Estimate the control point position based on the entering tangent line
171
+ controlPoint = segmentStart
172
+ .lerp(segmentEnd, 0.5)
173
+ .lerp(segmentStart.plus(enteringVec.times(startEndDist)), 0.25);
174
+ }
175
+ // Equal to an endpoint?
176
+ else if (segmentStart.eq(controlPoint) || segmentEnd.eq(controlPoint)) {
170
177
  // Position the control point closer to the first -- the connecting
171
178
  // segment will be roughly a line.
172
- controlPoint = segmentStart.plus(enteringVec.times(startEndDist / 4));
179
+ controlPoint = segmentStart.plus(enteringVec.times(startEndDist / 5));
173
180
  }
174
181
  console.assert(!segmentStart.eq(controlPoint, 1e-11), 'Start and control points are equal!');
175
182
  console.assert(!controlPoint.eq(segmentEnd, 1e-11), 'Control and end points are equal!');
@@ -1,5 +1,5 @@
1
1
  import { defaultEditorLocalization } from '../localization.mjs';
2
- // A partial Spanish localization, created with /scripts/markdownTranslationFormToTs.ts
2
+ // A partial Spanish localization
3
3
  const localization = {
4
4
  ...defaultEditorLocalization,
5
5
  pen: 'Lapiz',
@@ -7,6 +7,8 @@ const localization = {
7
7
  select: 'Selecciona',
8
8
  handTool: 'Mover',
9
9
  image: 'Imagen',
10
+ inputAltText: 'Texto alternativo',
11
+ resetImage: 'Reiniciar',
10
12
  chooseFile: 'Seleccionar archivo',
11
13
  cancel: 'Cancelar',
12
14
  resetView: 'Reiniciar vista',
@@ -28,7 +30,9 @@ const localization = {
28
30
  backgroundColor: 'Color de fondo',
29
31
  imageWidthOption: 'Ancho',
30
32
  imageHeightOption: 'Alto',
33
+ enableAutoresizeOption: 'Redimensionar automático',
31
34
  toggleOverflow: 'Más',
35
+ about: 'Acerca de',
32
36
  touchPanning: 'Mover la pantalla con un dedo',
33
37
  roundedTipPen: 'Lapiz Redondeado',
34
38
  arrowPen: 'Flecha',
@@ -44,10 +48,11 @@ const localization = {
44
48
  colorChangedAnnouncement: (color) => { return `Color fue cambiado a ${color}`; },
45
49
  imageSize: (size, units) => `Tamaño del imagen: ${size} ${units}`,
46
50
  imageLoadError: (message) => `Error cargando imagen: ${message}`,
47
- penTool: (penId) => { return `Lapiz ${penId}`; },
51
+ penTool: (penId) => `Lapiz ${penId}`,
48
52
  selectionTool: 'Selecciona',
49
53
  eraserTool: 'Borrador',
50
54
  touchPanTool: 'Instrumento de mover la pantalla con un dedo',
55
+ undoRedoTool: 'Deshace/rehace',
51
56
  pipetteTool: 'Seleccione un color de la pantalla',
52
57
  keyboardPanZoom: 'Mover la pantalla con el teclado',
53
58
  textTool: 'Texto',
@@ -55,29 +60,13 @@ const localization = {
55
60
  findLabel: 'Buscar',
56
61
  toNextMatch: 'Próxima',
57
62
  closeDialog: 'Cerrar',
58
- focusedFoundText: (matchIdx, totalMatches) => `Viewing match ${matchIdx} of ${totalMatches}`,
59
63
  anyDevicePanning: 'Mover la pantalla con todo dispotivo',
60
64
  copied: (count, description) => `Copied ${count} ${description}`,
61
65
  pasted: (count, description) => `Pasted ${count} ${description}`,
62
- toolEnabledAnnouncement: (toolName) => `${toolName} enabled`,
63
- toolDisabledAnnouncement: (toolName) => `${toolName} disabled`,
64
- transformedElements: (elemCount) => `Transformed ${elemCount} element${elemCount === 1 ? '' : 's'}`,
65
- resizeOutputCommand: (newSize) => `Resized image to ${newSize.w}x${newSize.h}`,
66
- addElementAction: (componentDescription) => `Added ${componentDescription}`,
67
- eraseAction: (componentDescription, numElems) => `Erased ${numElems} ${componentDescription}`,
68
- duplicateAction: (componentDescription, numElems) => `Duplicated ${numElems} ${componentDescription}`,
69
- unionOf: (actionDescription, actionCount) => `Union: ${actionCount} ${actionDescription}`,
70
- inverseOf: (actionDescription) => `Inverse of ${actionDescription}`,
71
- rotatedBy: (degrees) => `Rotated by ${Math.abs(degrees)} degrees ${degrees < 0 ? 'clockwise' : 'counter-clockwise'}`,
72
- selectedElements: (count) => `Selected ${count} element${count === 1 ? '' : 's'}`,
73
- filledBackgroundWithColor: (color) => `Filled background (${color})`,
74
- text: (text) => `Text object: ${text}`,
75
- imageNode: (label) => `Image: ${label}`,
76
- restyledElement: (elementDescription) => `Restyled ${elementDescription}`,
77
- pathNodeCount: (count) => `There are ${count} visible path objects.`,
78
- textNodeCount: (count) => `There are ${count} visible text nodes.`,
79
- imageNodeCount: (nodeCount) => `There are ${nodeCount} visible image nodes.`,
80
- textNode: (content) => `Text: ${content}`,
66
+ toolEnabledAnnouncement: (toolName) => `${toolName} fue activado`,
67
+ toolDisabledAnnouncement: (toolName) => `${toolName} fue desactivado`,
68
+ resizeOutputCommand: (newSize) => `Tamaño de imagen fue cambiado a ${newSize.w}x${newSize.h}`,
69
+ eraseAction: (componentDescription, numElems) => `Borrado: ${numElems} ${componentDescription}`,
81
70
  rerenderAsText: 'Redibuja la pantalla al texto',
82
71
  loading: (percentage) => `Cargando: ${percentage}%...`,
83
72
  imageEditor: 'Editor de dibujos',
@@ -55,6 +55,8 @@ export default class Display {
55
55
  * @internal
56
56
  */
57
57
  setDevicePixelRatio(dpr: number): Promise<void> | undefined;
58
+ /** @internal */
59
+ getDevicePixelRatio(): number;
58
60
  /**
59
61
  * Rerenders the text-based display.
60
62
  * The text-based display is intended for screen readers and can be navigated to by pressing `tab`.
@@ -199,6 +199,10 @@ export default class Display {
199
199
  }
200
200
  return undefined;
201
201
  }
202
+ /** @internal */
203
+ getDevicePixelRatio() {
204
+ return this.devicePixelRatio;
205
+ }
202
206
  /**
203
207
  * Rerenders the text-based display.
204
208
  * The text-based display is intended for screen readers and can be navigated to by pressing `tab`.
@@ -21,12 +21,14 @@ import AbstractToolbar, { SpacerOptions } from './AbstractToolbar';
21
21
  * });
22
22
  * ```
23
23
  *
24
+ * Returns a subclass of {@link AbstractToolbar}.
25
+ *
24
26
  * @see
25
27
  * - {@link makeEdgeToolbar}
26
28
  * - {@link AbstractToolbar.addSaveButton}
27
29
  * - {@link AbstractToolbar.addExitButton}
28
30
  */
29
- export declare const makeDropdownToolbar: (editor: Editor) => AbstractToolbar;
31
+ export declare const makeDropdownToolbar: (editor: Editor) => DropdownToolbar;
30
32
  export default class DropdownToolbar extends AbstractToolbar {
31
33
  protected container: HTMLElement;
32
34
  private resizeObserver;
@@ -21,6 +21,8 @@ import { toolbarCSSPrefix } from './constants.mjs';
21
21
  * });
22
22
  * ```
23
23
  *
24
+ * Returns a subclass of {@link AbstractToolbar}.
25
+ *
24
26
  * @see
25
27
  * - {@link makeEdgeToolbar}
26
28
  * - {@link AbstractToolbar.addSaveButton}
@@ -136,8 +136,10 @@ export default class EdgeToolbar extends AbstractToolbar {
136
136
  this.sidebarContainer.style.animation = `${animationProperties} ${toolbarCSSPrefix}-edgemenu-transition-in`;
137
137
  this.menuContainer.style.animation = `${animationProperties} ${toolbarCSSPrefix}-edgemenu-container-transition-in`;
138
138
  this.menuContainer.style.opacity = '1';
139
- // Focus the close button when first shown.
140
- this.closeButton.focus();
139
+ // Focus the close button when first shown, but prevent scroll because the button
140
+ // is likely at the bottom of the screen (and we want the full sidebar to remain
141
+ // visible).
142
+ this.closeButton.focus({ preventScroll: true, });
141
143
  }
142
144
  else {
143
145
  this.closeColorPickers();
@@ -265,9 +267,8 @@ export default class EdgeToolbar extends AbstractToolbar {
265
267
  if (event.target === this.menuContainer) {
266
268
  if (eventName === 'pointerdown') {
267
269
  this.sidebarVisible.set(false);
268
- }
269
- if (eventName === 'pointerup') {
270
- this.editor.focus();
270
+ // A delay seems necessary for the editor
271
+ setTimeout(() => this.editor.focus(), 0);
271
272
  }
272
273
  return true;
273
274
  }
@@ -14,6 +14,7 @@ import ToolbarShortcutHandler from '../../tools/ToolbarShortcutHandler.mjs';
14
14
  import { keyPressEventFromHTMLEvent, keyUpEventFromHTMLEvent } from '../../inputEvents.mjs';
15
15
  import { toolbarCSSPrefix } from '../constants.mjs';
16
16
  import DropdownLayoutManager from './layout/DropdownLayoutManager.mjs';
17
+ import addLongPressOrHoverCssClasses from '../../util/addLongPressOrHoverCssClasses.mjs';
17
18
  /**
18
19
  * A set of labels that allow toolbar themes to treat buttons differently.
19
20
  */
@@ -60,6 +61,7 @@ class BaseWidget {
60
61
  this.button.oncontextmenu = event => {
61
62
  event.preventDefault();
62
63
  };
64
+ addLongPressOrHoverCssClasses(this.button);
63
65
  }
64
66
  /**
65
67
  * Should return a constant true or false value. If true (the default),