js-draw 1.12.0 → 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.
- package/dist/Editor.css +66 -118
- package/dist/bundle.js +2 -2
- package/dist/bundledStyles.js +1 -1
- package/dist/cjs/Editor.d.ts +4 -2
- package/dist/cjs/Editor.js +48 -37
- package/dist/cjs/components/AbstractComponent.d.ts +7 -0
- package/dist/cjs/components/AbstractComponent.js +7 -5
- package/dist/cjs/components/util/StrokeSmoother.d.ts +0 -1
- package/dist/cjs/components/util/StrokeSmoother.js +6 -17
- package/dist/cjs/localizations/es.js +11 -22
- package/dist/cjs/rendering/Display.d.ts +2 -0
- package/dist/cjs/rendering/Display.js +4 -0
- package/dist/cjs/toolbar/DropdownToolbar.d.ts +3 -1
- package/dist/cjs/toolbar/DropdownToolbar.js +2 -0
- package/dist/cjs/toolbar/EdgeToolbar.js +6 -5
- package/dist/cjs/toolbar/widgets/BaseWidget.js +2 -0
- package/dist/cjs/toolbar/widgets/components/makeGridSelector.js +10 -0
- package/dist/cjs/util/addLongPressOrHoverCssClasses.d.ts +10 -0
- package/dist/cjs/util/addLongPressOrHoverCssClasses.js +34 -0
- package/dist/cjs/util/listenForLongPressOrHover.d.ts +13 -0
- package/dist/cjs/util/listenForLongPressOrHover.js +80 -0
- package/dist/cjs/util/listenForLongPressOrHover.test.d.ts +1 -0
- package/dist/cjs/version.js +1 -1
- package/dist/mjs/Editor.d.ts +4 -2
- package/dist/mjs/Editor.mjs +48 -37
- package/dist/mjs/components/AbstractComponent.d.ts +7 -0
- package/dist/mjs/components/AbstractComponent.mjs +7 -5
- package/dist/mjs/components/util/StrokeSmoother.d.ts +0 -1
- package/dist/mjs/components/util/StrokeSmoother.mjs +6 -17
- package/dist/mjs/localizations/es.mjs +11 -22
- package/dist/mjs/rendering/Display.d.ts +2 -0
- package/dist/mjs/rendering/Display.mjs +4 -0
- package/dist/mjs/toolbar/DropdownToolbar.d.ts +3 -1
- package/dist/mjs/toolbar/DropdownToolbar.mjs +2 -0
- package/dist/mjs/toolbar/EdgeToolbar.mjs +6 -5
- package/dist/mjs/toolbar/widgets/BaseWidget.mjs +2 -0
- package/dist/mjs/toolbar/widgets/components/makeGridSelector.mjs +10 -0
- package/dist/mjs/util/addLongPressOrHoverCssClasses.d.ts +10 -0
- package/dist/mjs/util/addLongPressOrHoverCssClasses.mjs +29 -0
- package/dist/mjs/util/listenForLongPressOrHover.d.ts +13 -0
- package/dist/mjs/util/listenForLongPressOrHover.mjs +78 -0
- package/dist/mjs/util/listenForLongPressOrHover.test.d.ts +1 -0
- package/dist/mjs/version.mjs +1 -1
- package/package.json +2 -2
- package/src/toolbar/EdgeToolbar.scss +19 -97
- package/src/toolbar/utils/labelVisibleOnHover.scss +114 -0
- 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 {};
|
package/dist/cjs/version.js
CHANGED
package/dist/mjs/Editor.d.ts
CHANGED
@@ -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):
|
317
|
+
protected handleHTMLKeyDownEvent(htmlEvent: KeyboardEvent): boolean;
|
316
318
|
/** @internal */
|
317
|
-
protected handleHTMLKeyUpEvent(htmlEvent: KeyboardEvent):
|
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.
|
package/dist/mjs/Editor.mjs
CHANGED
@@ -239,41 +239,7 @@ export class Editor {
|
|
239
239
|
this.handleKeyEventsFrom(this.renderingRegion);
|
240
240
|
this.handlePointerEventsFrom(this.accessibilityAnnounceArea);
|
241
241
|
this.container.addEventListener('wheel', evt => {
|
242
|
-
|
243
|
-
// Process wheel events if the ctrl key is down, even if disabled -- we do want to handle
|
244
|
-
// pinch-zooming.
|
245
|
-
if (!evt.ctrlKey && !evt.metaKey) {
|
246
|
-
if (!this.settings.wheelEventsEnabled) {
|
247
|
-
return;
|
248
|
-
}
|
249
|
-
else if (this.settings.wheelEventsEnabled === 'only-if-focused') {
|
250
|
-
const focusedChild = this.container.querySelector(':focus');
|
251
|
-
if (!focusedChild) {
|
252
|
-
return;
|
253
|
-
}
|
254
|
-
}
|
255
|
-
}
|
256
|
-
if (evt.deltaMode === WheelEvent.DOM_DELTA_LINE) {
|
257
|
-
delta = delta.times(15);
|
258
|
-
}
|
259
|
-
else if (evt.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
|
260
|
-
delta = delta.times(100);
|
261
|
-
}
|
262
|
-
if (evt.ctrlKey || evt.metaKey) {
|
263
|
-
delta = Vec3.of(0, 0, evt.deltaY);
|
264
|
-
}
|
265
|
-
// Ensure that `pos` is relative to `this.renderingRegion`
|
266
|
-
const bbox = this.renderingRegion.getBoundingClientRect();
|
267
|
-
const pos = Vec2.of(evt.clientX, evt.clientY).minus(Vec2.of(bbox.left, bbox.top));
|
268
|
-
if (this.toolController.dispatchInputEvent({
|
269
|
-
kind: InputEvtType.WheelEvt,
|
270
|
-
delta,
|
271
|
-
screenPos: pos,
|
272
|
-
})) {
|
273
|
-
evt.preventDefault();
|
274
|
-
return true;
|
275
|
-
}
|
276
|
-
return false;
|
242
|
+
this.handleHTMLWheelEvent(evt);
|
277
243
|
});
|
278
244
|
const handleResize = () => {
|
279
245
|
this.viewport.updateScreenSize(Vec2.of(this.display.width, this.display.height));
|
@@ -317,6 +283,44 @@ export class Editor {
|
|
317
283
|
this.container.style.setProperty('--editor-current-display-width-px', `${this.renderingRegion.clientWidth}px`);
|
318
284
|
this.container.style.setProperty('--editor-current-display-height-px', `${this.renderingRegion.clientHeight}px`);
|
319
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
|
+
}
|
320
324
|
getPointerList() {
|
321
325
|
const nowTime = performance.now();
|
322
326
|
const res = [];
|
@@ -625,14 +629,18 @@ export class Editor {
|
|
625
629
|
const event = keyPressEventFromHTMLEvent(htmlEvent);
|
626
630
|
if (this.toolController.dispatchInputEvent(event)) {
|
627
631
|
htmlEvent.preventDefault();
|
632
|
+
return true;
|
628
633
|
}
|
629
634
|
else if (event.key === 't' || event.key === 'T') {
|
630
635
|
htmlEvent.preventDefault();
|
631
636
|
this.display.rerenderAsText();
|
637
|
+
return true;
|
632
638
|
}
|
633
639
|
else if (event.key === 'Escape') {
|
634
640
|
this.renderingRegion.blur();
|
641
|
+
return true;
|
635
642
|
}
|
643
|
+
return false;
|
636
644
|
}
|
637
645
|
/** @internal */
|
638
646
|
handleHTMLKeyUpEvent(htmlEvent) {
|
@@ -640,7 +648,9 @@ export class Editor {
|
|
640
648
|
const event = keyUpEventFromHTMLEvent(htmlEvent);
|
641
649
|
if (this.toolController.dispatchInputEvent(event)) {
|
642
650
|
htmlEvent.preventDefault();
|
651
|
+
return true;
|
643
652
|
}
|
653
|
+
return false;
|
644
654
|
}
|
645
655
|
/**
|
646
656
|
* Adds event listners for keypresses (and drop events) on `elem` and forwards those
|
@@ -1160,8 +1170,9 @@ export class Editor {
|
|
1160
1170
|
` ${this.viewport.getScaleFactor()}x zoom, ${180 / Math.PI * this.viewport.getRotationAngle()}° rotation`,
|
1161
1171
|
` ${this.image.estimateNumElements()} components`,
|
1162
1172
|
` auto-resize: ${this.image.getAutoresizeEnabled() ? 'enabled' : 'disabled'}`,
|
1163
|
-
` ${this.getImportExportRect().w}x${this.getImportExportRect().h}
|
1164
|
-
` ${screenSize.x}x${screenSize.y}
|
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()}`,
|
1165
1176
|
' cache:',
|
1166
1177
|
` ${this.display.getCache().getDebugInfo()
|
1167
1178
|
// Indent
|
@@ -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
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
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
|
}
|
@@ -12,7 +12,6 @@ export class StrokeSmoother {
|
|
12
12
|
this.maxFitAllowed = maxFitAllowed;
|
13
13
|
this.onCurveAdded = onCurveAdded;
|
14
14
|
this.isFirstSegment = true;
|
15
|
-
this.centerOfMass = null;
|
16
15
|
this.lastExitingVec = null;
|
17
16
|
this.currentCurve = null;
|
18
17
|
this.lastPoint = this.startPoint;
|
@@ -58,7 +57,6 @@ export class StrokeSmoother {
|
|
58
57
|
this.buffer[this.buffer.length - 2], lastPoint,
|
59
58
|
];
|
60
59
|
this.currentCurve = null;
|
61
|
-
this.centerOfMass = null;
|
62
60
|
this.isFirstSegment = false;
|
63
61
|
}
|
64
62
|
// Returns [upper curve, connector, lower curve]
|
@@ -106,20 +104,10 @@ export class StrokeSmoother {
|
|
106
104
|
if (shouldSnapToInitial) {
|
107
105
|
return;
|
108
106
|
}
|
109
|
-
if (!this.centerOfMass) {
|
110
|
-
this.centerOfMass = newPoint.pos;
|
111
|
-
}
|
112
|
-
else {
|
113
|
-
this.centerOfMass = this.centerOfMass
|
114
|
-
.times(this.buffer.length)
|
115
|
-
.plus(newPoint.pos).times(1 / (this.buffer.length + 1));
|
116
|
-
}
|
117
|
-
const toCenterOfMass = this.centerOfMass.minus(newPoint.pos);
|
118
107
|
const deltaTimeSeconds = deltaTime / 1000;
|
119
108
|
const velocity = newPoint.pos.minus(this.lastPoint.pos).times(1 / deltaTimeSeconds);
|
120
109
|
// TODO: Do we need momentum smoothing? (this.momentum.lerp(velocity, 0.9);)
|
121
|
-
|
122
|
-
this.momentum = velocity.plus(toCenterOfMass.times(k));
|
110
|
+
this.momentum = velocity;
|
123
111
|
}
|
124
112
|
const lastPoint = this.lastPoint ?? newPoint;
|
125
113
|
this.lastPoint = newPoint;
|
@@ -156,7 +144,7 @@ export class StrokeSmoother {
|
|
156
144
|
}
|
157
145
|
let exitingVec = this.computeExitingVec();
|
158
146
|
// Find the intersection between the entering vector and the exiting vector
|
159
|
-
const maxRelativeLength = 1.
|
147
|
+
const maxRelativeLength = 1.7;
|
160
148
|
const segmentStart = this.buffer[0];
|
161
149
|
const segmentEnd = newPoint.pos;
|
162
150
|
const startEndDist = segmentEnd.minus(segmentStart).magnitude();
|
@@ -185,9 +173,10 @@ export class StrokeSmoother {
|
|
185
173
|
.lerp(segmentStart.plus(enteringVec.times(startEndDist)), 0.25);
|
186
174
|
}
|
187
175
|
// Equal to an endpoint?
|
188
|
-
if (segmentStart.eq(controlPoint) || segmentEnd.eq(controlPoint)) {
|
189
|
-
// Position the control point
|
190
|
-
|
176
|
+
else if (segmentStart.eq(controlPoint) || segmentEnd.eq(controlPoint)) {
|
177
|
+
// Position the control point closer to the first -- the connecting
|
178
|
+
// segment will be roughly a line.
|
179
|
+
controlPoint = segmentStart.plus(enteringVec.times(startEndDist / 5));
|
191
180
|
}
|
192
181
|
console.assert(!segmentStart.eq(controlPoint, 1e-11), 'Start and control points are equal!');
|
193
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
|
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) =>
|
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}
|
63
|
-
toolDisabledAnnouncement: (toolName) => `${toolName}
|
64
|
-
|
65
|
-
|
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) =>
|
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;
|
@@ -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
|
-
|
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
|
-
|
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),
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import { MutableReactiveValue } from '../../../util/ReactiveValue.mjs';
|
2
2
|
import stopPropagationOfScrollingWheelEvents from '../../../util/stopPropagationOfScrollingWheelEvents.mjs';
|
3
|
+
import addLongPressOrHoverCssClasses from '../../../util/addLongPressOrHoverCssClasses.mjs';
|
3
4
|
import { toolbarCSSPrefix } from '../../constants.mjs';
|
4
5
|
let idCounter = 0;
|
5
6
|
/**
|
@@ -31,6 +32,9 @@ labelText, defaultId, choices) => {
|
|
31
32
|
const button = document.createElement('input');
|
32
33
|
button.type = 'radio';
|
33
34
|
button.id = `${toolbarCSSPrefix}-grid-select-button-${idCounter++}`;
|
35
|
+
// Some toolbars only show the label on hover. Having long press or hover
|
36
|
+
// CSS classes are helpful here.
|
37
|
+
addLongPressOrHoverCssClasses(buttonContainer);
|
34
38
|
// Clicking any part of labelContainer triggers the radio button.
|
35
39
|
const labelContainer = document.createElement('label');
|
36
40
|
const rebuildLabel = () => {
|
@@ -76,6 +80,12 @@ labelText, defaultId, choices) => {
|
|
76
80
|
button.onblur = () => {
|
77
81
|
buttonContainer.classList.remove('focus-visible');
|
78
82
|
};
|
83
|
+
// Prevent the right-click menu from being shown on long-press
|
84
|
+
// (important for some toolbars that use long-press gestures to
|
85
|
+
// show grid selector labels).
|
86
|
+
buttonContainer.oncontextmenu = event => {
|
87
|
+
event.preventDefault();
|
88
|
+
};
|
79
89
|
buttonContainer.replaceChildren(button, labelContainer);
|
80
90
|
menuContainer.appendChild(buttonContainer);
|
81
91
|
// Set whether the current button is checked
|