Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

js-draw

Package Overview
Dependencies
Maintainers
1
Versions
120
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

js-draw - npm Package Compare versions

Comparing version 0.1.9 to 0.1.10

dist/src/localizations/en.d.ts

6

CHANGELOG.md

@@ -0,1 +1,7 @@

# 0.1.10
* Keyboard shortcuts for the selection tool.
* Scroll the selection into view while moving it with the keyboard/mouse.
* Fix toolbar buttons not activating when focused and enter/space is pressed.
* Partial Spanish localization.
# 0.1.9

@@ -2,0 +8,0 @@ * Fix regression -- color picker hides just after clicking it.

3

dist/src/bundle/bundled.d.ts
import '../styles';
import Editor from '../Editor';
import getLocalizationTable from '../localizations/getLocalizationTable';
export default Editor;
export { Editor };
export { Editor, getLocalizationTable };
// Main entrypoint for Webpack when building a bundle for release.
import '../styles';
import Editor from '../Editor';
import getLocalizationTable from '../localizations/getLocalizationTable';
export default Editor;
export { Editor };
export { Editor, getLocalizationTable };

@@ -40,2 +40,3 @@ import EditorImage from './EditorImage';

private registerListeners;
handleKeyEventsFrom(elem: HTMLElement): void;
dispatch(command: Command, addToHistory?: boolean): void;

@@ -42,0 +43,0 @@ dispatchNoAnnounce(command: Command, addToHistory?: boolean): void;

@@ -26,7 +26,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {

import Mat33 from './geometry/Mat33';
import { defaultEditorLocalization } from './localization';
import getLocalizationTable from './localizations/getLocalizationTable';
export class Editor {
constructor(parent, settings = {}) {
var _a, _b;
this.localization = defaultEditorLocalization;
this.announceUndoCallback = (command) => {

@@ -39,3 +38,3 @@ this.announceForAccessibility(this.localization.undoAnnouncement(command.description(this.localization)));

this.rerenderQueued = false;
this.localization = Object.assign(Object.assign({}, this.localization), settings.localization);
this.localization = Object.assign(Object.assign({}, getLocalizationTable()), settings.localization);
// Fill default settings.

@@ -181,14 +180,3 @@ this.settings = {

});
this.renderingRegion.addEventListener('keydown', evt => {
if (this.toolController.dispatchInputEvent({
kind: InputEvtType.KeyPressEvent,
key: evt.key,
ctrlKey: evt.ctrlKey,
})) {
evt.preventDefault();
}
else if (evt.key === 'Escape') {
this.renderingRegion.blur();
}
});
this.handleKeyEventsFrom(this.renderingRegion);
this.container.addEventListener('wheel', evt => {

@@ -240,2 +228,27 @@ let delta = Vec3.of(evt.deltaX, evt.deltaY, evt.deltaZ);

}
// Adds event listners for keypresses to [elem] and forwards those events to the
// editor.
handleKeyEventsFrom(elem) {
elem.addEventListener('keydown', evt => {
if (this.toolController.dispatchInputEvent({
kind: InputEvtType.KeyPressEvent,
key: evt.key,
ctrlKey: evt.ctrlKey,
})) {
evt.preventDefault();
}
else if (evt.key === 'Escape') {
this.renderingRegion.blur();
}
});
elem.addEventListener('keyup', evt => {
if (this.toolController.dispatchInputEvent({
kind: InputEvtType.KeyUpEvent,
key: evt.key,
ctrlKey: evt.ctrlKey,
})) {
evt.preventDefault();
}
});
}
// Adds to history by default

@@ -242,0 +255,0 @@ dispatch(command, addToHistory = true) {

@@ -18,3 +18,4 @@ import Rect2 from './Rect2';

intersection(other: LineSegment2): IntersectionResult | null;
closestPointTo(target: Point2): import("./Vec3").default;
}
export {};

@@ -100,2 +100,18 @@ import Rect2 from './Rect2';

}
// Returns the closest point on this to [target]
closestPointTo(target) {
// Distance from P1 along this' direction.
const projectedDistFromP1 = target.minus(this.p1).dot(this.direction);
const projectedDistFromP2 = this.length - projectedDistFromP1;
const projection = this.p1.plus(this.direction.times(projectedDistFromP1));
if (projectedDistFromP1 > 0 && projectedDistFromP1 < this.length) {
return projection;
}
if (Math.abs(projectedDistFromP2) < Math.abs(projectedDistFromP1)) {
return this.p2;
}
else {
return this.p1;
}
}
}

@@ -33,2 +33,3 @@ import LineSegment2 from './LineSegment2';

grownBy(margin: number): Rect2;
getClosestPointOnBoundaryTo(target: Point2): import("./Vec3").default;
get corners(): Point2[];

@@ -35,0 +36,0 @@ get maxDimension(): number;

@@ -28,2 +28,3 @@ import LineSegment2 from './LineSegment2';

}
// Returns a copy of this with the given size (but same top-left).
resizedTo(size) {

@@ -113,2 +114,17 @@ return new Rect2(this.x, this.y, size.x, size.y);

}
getClosestPointOnBoundaryTo(target) {
const closestEdgePoints = this.getEdges().map(edge => {
return edge.closestPointTo(target);
});
let closest = null;
let closestDist = null;
for (const point of closestEdgePoints) {
const dist = point.minus(target).length();
if (closestDist === null || dist < closestDist) {
closest = point;
closestDist = dist;
}
}
return closest;
}
get corners() {

@@ -115,0 +131,0 @@ return [

@@ -111,3 +111,3 @@ import { ToolType } from '../tools/ToolController';

const undoButton = this.addActionButton({
label: 'Undo',
label: this.localizationTable.undo,
icon: makeUndoIcon()

@@ -118,3 +118,3 @@ }, () => {

const redoButton = this.addActionButton({
label: 'Redo',
label: this.localizationTable.redo,
icon: makeRedoIcon(),

@@ -162,7 +162,5 @@ }, () => {

}
for (const tool of toolController.getMatchingTools(ToolType.PanZoom)) {
if (!(tool instanceof PanZoom)) {
throw new Error('All SelectionTools must have kind === ToolType.PanZoom');
}
(new HandToolWidget(this.editor, tool, this.localizationTable)).addTo(this.container);
const panZoomTool = toolController.getMatchingTools(ToolType.PanZoom)[0];
if (panZoomTool && panZoomTool instanceof PanZoom) {
(new HandToolWidget(this.editor, panZoomTool, this.localizationTable)).addTo(this.container);
}

@@ -169,0 +167,0 @@ this.setupColorPickers();

@@ -6,7 +6,7 @@ import EventDispatcher from '../EventDispatcher';

const svgNamespace = 'http://www.w3.org/2000/svg';
const primaryForegroundFill = `
style='fill: var(--primary-foreground-color);'
const iconColorFill = `
style='fill: var(--icon-color);'
`;
const primaryForegroundStrokeFill = `
style='fill: var(--primary-foreground-color); stroke: var(--primary-foreground-color);'
const iconColorStrokeFill = `
style='fill: var(--icon-color); stroke: var(--icon-color);'
`;

@@ -35,3 +35,3 @@ const checkerboardPatternDef = `

.toolbar-svg-undo-redo-icon {
stroke: var(--primary-foreground-color);
stroke: var(--icon-color);
stroke-width: 12;

@@ -59,3 +59,3 @@ stroke-linejoin: round;

d='M5,10 L50,90 L95,10 Z'
${primaryForegroundFill}
${iconColorFill}
/>

@@ -75,3 +75,3 @@ </g>

x=10 y=10 width=80 height=50
${primaryForegroundFill}
${iconColorFill}
/>

@@ -123,3 +123,3 @@ </g>

style='
stroke: var(--primary-foreground-color);
stroke: var(--icon-color);
stroke-width: 2;

@@ -166,3 +166,3 @@ '

style='
stroke: var(--primary-foreground-color);
stroke: var(--icon-color);
stroke-width: 2;

@@ -231,3 +231,3 @@ '

style='
stroke: var(--primary-foreground-color);
stroke: var(--icon-color);
stroke-width: 2;

@@ -251,3 +251,3 @@ '

textNode.style.fontSize = '55px';
textNode.style.fill = 'var(--primary-foreground-color)';
textNode.style.fill = 'var(--icon-color)';
textNode.style.fontFamily = 'monospace';

@@ -293,3 +293,3 @@ icon.appendChild(textNode);

d='M10,10 L90,10 L90,60 L${50 + halfThickness},80 L${50 - halfThickness},80 L10,60 Z'
${primaryForegroundStrokeFill}
${iconColorStrokeFill}
/>

@@ -360,3 +360,3 @@ </g>

`);
pipette.style.fill = 'var(--primary-foreground-color)';
pipette.style.fill = 'var(--icon-color)';
if (color) {

@@ -363,0 +363,0 @@ const defs = document.createElementNS(svgNamespace, 'defs');

@@ -24,2 +24,3 @@ export interface ToolbarLocalization {

zoom: string;
selectionToolKeyboardShortcuts: string;
dropdownShown: (toolName: string) => string;

@@ -26,0 +27,0 @@ dropdownHidden: (toolName: string) => string;

@@ -17,2 +17,3 @@ export const defaultToolbarLocalization = {

pickColorFronScreen: 'Pick color from screen',
selectionToolKeyboardShortcuts: 'Selection tool: Use arrow keys to move selected items, lowercase/uppercase ‘i’ and ‘o’ to resize.',
touchPanning: 'Touchscreen panning',

@@ -19,0 +20,0 @@ anyDevicePanning: 'Any device panning',

@@ -13,2 +13,3 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {

var _BaseWidget_hasDropdown;
import { InputEvtType } from '../../types';
import { toolbarCSSPrefix } from '../HTMLToolbar';

@@ -48,2 +49,30 @@ import { makeDropdownIcon } from '../icons';

setupActionBtnClickListener(button) {
const clickTriggers = { enter: true, ' ': true, };
button.onkeydown = (evt) => {
let handled = false;
if (evt.key in clickTriggers) {
if (!this.disabled) {
this.handleClick();
handled = true;
}
}
// If we didn't do anything with the event, send it to the editor.
if (!handled) {
this.editor.toolController.dispatchInputEvent({
kind: InputEvtType.KeyPressEvent,
key: evt.key,
ctrlKey: evt.ctrlKey,
});
}
};
button.onkeyup = evt => {
if (evt.key in clickTriggers) {
return;
}
this.editor.toolController.dispatchInputEvent({
kind: InputEvtType.KeyUpEvent,
key: evt.key,
ctrlKey: evt.ctrlKey,
});
};
button.onclick = () => {

@@ -50,0 +79,0 @@ if (!this.disabled) {

@@ -1,2 +0,2 @@

import { PointerEvtListener, WheelEvt, PointerEvt, EditorNotifier, KeyPressEvent } from '../types';
import { PointerEvtListener, WheelEvt, PointerEvt, EditorNotifier, KeyPressEvent, KeyUpEvent } from '../types';
import { ToolType } from './ToolController';

@@ -17,2 +17,3 @@ import ToolEnabledGroup from './ToolEnabledGroup';

onKeyPress(_event: KeyPressEvent): boolean;
onKeyUp(_event: KeyUpEvent): boolean;
setEnabled(enabled: boolean): void;

@@ -19,0 +20,0 @@ isEnabled(): boolean;

@@ -19,2 +19,5 @@ import { EditorEventType } from '../types';

}
onKeyUp(_event) {
return false;
}
setEnabled(enabled) {

@@ -21,0 +24,0 @@ var _a;

export interface ToolLocalization {
keyboardPanZoom: string;
penTool: (penId: number) => string;

@@ -3,0 +4,0 @@ selectionTool: string;

@@ -10,2 +10,3 @@ export const defaultToolLocalization = {

pipetteTool: 'Pick color from screen',
keyboardPanZoom: 'Keyboard pan/zoom shortcuts',
textTool: 'Text',

@@ -12,0 +13,0 @@ enterTextToInsert: 'Text to insert',

@@ -17,3 +17,4 @@ import { Editor } from '../Editor';

RightClickDrags = 4,
SinglePointerGestures = 8
SinglePointerGestures = 8,
Keyboard = 16
}

@@ -20,0 +21,0 @@ export default class PanZoom extends BaseTool {

@@ -15,2 +15,3 @@ import Mat33 from '../geometry/Mat33';

PanZoomMode[PanZoomMode["SinglePointerGestures"] = 8] = "SinglePointerGestures";
PanZoomMode[PanZoomMode["Keyboard"] = 16] = "Keyboard";
})(PanZoomMode || (PanZoomMode = {}));

@@ -137,2 +138,5 @@ export default class PanZoom extends BaseTool {

onKeyPress({ key }) {
if (!(this.mode & PanZoomMode.Keyboard)) {
return false;
}
let translation = Vec2.zero;

@@ -139,0 +143,0 @@ let scale = 1;

import Command from '../commands/Command';
import Editor from '../Editor';
import Mat33 from '../geometry/Mat33';
import Rect2 from '../geometry/Rect2';
import { Point2, Vec2 } from '../geometry/Vec2';
import { PointerEvt } from '../types';
import { KeyPressEvent, KeyUpEvent, PointerEvt } from '../types';
import BaseTool from './BaseTool';

@@ -23,3 +24,4 @@ import { ToolType } from './ToolController';

private computeTransformCommands;
finishDragging(): void;
transformPreview(transform: Mat33): void;
finalizeTransform(): void;
private static ApplyTransformationCommand;

@@ -36,2 +38,3 @@ private previewTransformCmds;

updateUI(): void;
scrollTo(): void;
deleteSelectedObjects(): Command;

@@ -50,4 +53,8 @@ duplicateSelectedObjects(): Command;

private onGestureEnd;
private zoomToSelection;
onPointerUp(event: PointerEvt): void;
onGestureCancel(): void;
private static handleableKeys;
onKeyPress(event: KeyPressEvent): boolean;
onKeyUp(evt: KeyUpEvent): boolean;
setEnabled(enabled: boolean): void;

@@ -54,0 +61,0 @@ getSelection(): Selection | null;

@@ -18,2 +18,3 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {

import { EditorEventType } from '../types';
import Viewport from '../Viewport';
import BaseTool from './BaseTool';

@@ -147,9 +148,9 @@ import { ToolType } from './ToolController';

this.handleBackgroundDrag(deltaPosition);
}, () => this.finishDragging());
}, () => this.finalizeTransform());
makeDraggable(resizeCorner, (deltaPosition) => {
this.handleResizeCornerDrag(deltaPosition);
}, () => this.finishDragging());
}, () => this.finalizeTransform());
makeDraggable(this.rotateCircle, (_deltaPosition, offset) => {
this.handleRotateCircleDrag(offset);
}, () => this.finishDragging());
}, () => this.finalizeTransform());
}

@@ -164,5 +165,3 @@ // Note a small change in the position of this' background while dragging

deltaPosition = this.editor.viewport.roundPoint(deltaPosition);
this.region = this.region.translatedBy(deltaPosition);
this.transform = this.transform.rightMul(Mat33.translation(deltaPosition));
this.previewTransformCmds();
this.transformPreview(Mat33.translation(deltaPosition));
}

@@ -176,14 +175,7 @@ handleResizeCornerDrag(deltaPosition) {

if (newSize.y > 0 && newSize.x > 0) {
this.region = this.region.resizedTo(newSize);
const scaleFactor = Vec2.of(this.region.w / oldWidth, this.region.h / oldHeight);
const currentTransfm = Mat33.scaling2D(scaleFactor, this.region.topLeft);
this.transform = this.transform.rightMul(currentTransfm);
this.previewTransformCmds();
const scaleFactor = Vec2.of(newSize.x / oldWidth, newSize.y / oldHeight);
this.transformPreview(Mat33.scaling2D(scaleFactor, this.region.topLeft));
}
}
handleRotateCircleDrag(offset) {
this.boxRotation = this.boxRotation % (2 * Math.PI);
if (this.boxRotation < 0) {
this.boxRotation += 2 * Math.PI;
}
let targetRotation = offset.angle();

@@ -205,5 +197,3 @@ targetRotation = targetRotation % (2 * Math.PI);

}
this.transform = this.transform.rightMul(Mat33.zRotation(deltaRotation, this.region.center));
this.boxRotation += deltaRotation;
this.previewTransformCmds();
this.transformPreview(Mat33.zRotation(deltaRotation, this.region.center));
}

@@ -215,4 +205,21 @@ computeTransformCommands() {

}
// Applies, previews, but doesn't finalize the given transformation.
transformPreview(transform) {
this.transform = this.transform.rightMul(transform);
const deltaRotation = transform.transformVec3(Vec2.unitX).angle();
transform = transform.rightMul(Mat33.zRotation(-deltaRotation, this.region.center));
this.boxRotation += deltaRotation;
this.boxRotation = this.boxRotation % (2 * Math.PI);
if (this.boxRotation < 0) {
this.boxRotation += 2 * Math.PI;
}
const newSize = transform.transformVec3(this.region.size);
const translation = transform.transformVec2(this.region.topLeft).minus(this.region.topLeft);
this.region = this.region.resizedTo(newSize);
this.region = this.region.translatedBy(translation);
this.previewTransformCmds();
this.scrollTo();
}
// Applies the current transformation to the selection
finishDragging() {
finalizeTransform() {
this.transformationCommands.forEach(cmd => {

@@ -337,2 +344,12 @@ cmd.unapply(this.editor);

}
// Scroll the viewport to this. Does not zoom
scrollTo() {
const viewport = this.editor.viewport;
const visibleRect = viewport.visibleRect;
if (!visibleRect.containsPoint(this.region.center)) {
const closestPoint = visibleRect.getClosestPointOnBoundaryTo(this.region.center);
const delta = this.region.center.minus(closestPoint);
this.editor.dispatchNoAnnounce(new Viewport.ViewportTransform(Mat33.translation(delta.times(-1))), false);
}
}
deleteSelectedObjects() {

@@ -394,2 +411,3 @@ return new Erase(this.selectedElems);

});
this.editor.handleKeyEventsFrom(this.handleOverlay);
}

@@ -424,2 +442,7 @@ onPointerDown(event) {

this.editor.announceForAccessibility(this.editor.localization.selectedElements(this.selectionBox.getSelectedItemCount()));
this.zoomToSelection();
}
}
zoomToSelection() {
if (this.selectionBox) {
const selectionRect = this.selectionBox.region;

@@ -442,2 +465,76 @@ this.editor.dispatchNoAnnounce(this.editor.viewport.zoomTo(selectionRect, false), false);

}
onKeyPress(event) {
let rotationSteps = 0;
let xTranslateSteps = 0;
let yTranslateSteps = 0;
let xScaleSteps = 0;
let yScaleSteps = 0;
switch (event.key) {
case 'a':
case 'h':
case 'ArrowLeft':
xTranslateSteps -= 1;
break;
case 'd':
case 'l':
case 'ArrowRight':
xTranslateSteps += 1;
break;
case 'q':
case 'k':
case 'ArrowUp':
yTranslateSteps -= 1;
break;
case 'e':
case 'j':
case 'ArrowDown':
yTranslateSteps += 1;
break;
case 'r':
rotationSteps += 1;
break;
case 'R':
rotationSteps -= 1;
break;
case 'i':
xScaleSteps -= 1;
break;
case 'I':
xScaleSteps += 1;
break;
case 'o':
yScaleSteps -= 1;
break;
case 'O':
yScaleSteps += 1;
break;
}
let handled = xTranslateSteps !== 0
|| yTranslateSteps !== 0
|| rotationSteps !== 0
|| xScaleSteps !== 0
|| yScaleSteps !== 0;
if (!this.selectionBox) {
handled = false;
}
else if (handled) {
const translateStepSize = 10 * this.editor.viewport.getSizeOfPixelOnCanvas();
const rotateStepSize = Math.PI / 8;
const scaleStepSize = translateStepSize / 2;
const region = this.selectionBox.region;
const scaledSize = this.selectionBox.region.size.plus(Vec2.of(xScaleSteps, yScaleSteps).times(scaleStepSize));
const transform = Mat33.scaling2D(Vec2.of(
// Don't more-than-half the size of the selection
Math.max(0.5, scaledSize.x / region.size.x), Math.max(0.5, scaledSize.y / region.size.y)), region.topLeft).rightMul(Mat33.zRotation(rotationSteps * rotateStepSize, region.center)).rightMul(Mat33.translation(Vec2.of(xTranslateSteps, yTranslateSteps).times(translateStepSize)));
this.selectionBox.transformPreview(transform);
}
return handled;
}
onKeyUp(evt) {
if (this.selectionBox && SelectionTool.handleableKeys.some(key => key === evt.key)) {
this.selectionBox.finalizeTransform();
return true;
}
return false;
}
setEnabled(enabled) {

@@ -449,2 +546,9 @@ super.setEnabled(enabled);

this.handleOverlay.style.display = enabled ? 'block' : 'none';
if (enabled) {
this.handleOverlay.tabIndex = 0;
this.handleOverlay.ariaLabel = this.editor.localization.selectionToolKeyboardShortcuts;
}
else {
this.handleOverlay.tabIndex = -1;
}
}

@@ -465,1 +569,9 @@ // Get the object responsible for displaying this' selection.

}
SelectionTool.handleableKeys = [
'a', 'h', 'ArrowLeft',
'd', 'l', 'ArrowRight',
'q', 'k', 'ArrowUp',
'e', 'j', 'ArrowDown',
'r', 'R',
'i', 'I', 'o', 'O',
];

@@ -26,2 +26,3 @@ import { InputEvtType, EditorEventType } from '../types';

const panZoomTool = new PanZoom(editor, PanZoomMode.TwoFingerTouchGestures | PanZoomMode.RightClickDrags, localization.touchPanTool);
const keyboardPanZoomTool = new PanZoom(editor, PanZoomMode.Keyboard, localization.keyboardPanZoom);
const primaryPenTool = new Pen(editor, localization.penTool(1), { color: Color4.purple, thickness: 16 });

@@ -42,2 +43,3 @@ const primaryTools = [

...primaryTools,
keyboardPanZoomTool,
new UndoRedoShortcut(editor),

@@ -81,4 +83,5 @@ ];

}
else if (event.kind === InputEvtType.WheelEvt || event.kind === InputEvtType.KeyPressEvent) {
else if (event.kind === InputEvtType.WheelEvt || event.kind === InputEvtType.KeyPressEvent || event.kind === InputEvtType.KeyUpEvent) {
const isKeyPressEvt = event.kind === InputEvtType.KeyPressEvent;
const isKeyReleaseEvt = event.kind === InputEvtType.KeyUpEvent;
const isWheelEvt = event.kind === InputEvtType.WheelEvt;

@@ -91,3 +94,4 @@ for (const tool of this.tools) {

const keyPressResult = isKeyPressEvt && tool.onKeyPress(event);
handled = keyPressResult || wheelResult;
const keyReleaseResult = isKeyReleaseEvt && tool.onKeyUp(event);
handled = keyPressResult || wheelResult || keyReleaseResult;
if (handled) {

@@ -94,0 +98,0 @@ break;

@@ -22,3 +22,4 @@ import EventDispatcher from './EventDispatcher';

WheelEvt = 4,
KeyPressEvent = 5
KeyPressEvent = 5,
KeyUpEvent = 6
}

@@ -35,2 +36,7 @@ export interface WheelEvt {

}
export interface KeyUpEvent {
readonly kind: InputEvtType.KeyUpEvent;
readonly key: string;
readonly ctrlKey: boolean;
}
export interface GestureCancelEvt {

@@ -53,3 +59,3 @@ readonly kind: InputEvtType.GestureCancelEvt;

export declare type PointerEvt = PointerDownEvt | PointerMoveEvt | PointerUpEvt;
export declare type InputEvt = KeyPressEvent | WheelEvt | GestureCancelEvt | PointerEvt;
export declare type InputEvt = KeyPressEvent | KeyUpEvent | WheelEvt | GestureCancelEvt | PointerEvt;
export declare type EditorNotifier = EventDispatcher<EditorEventType, EditorEventDataType>;

@@ -56,0 +62,0 @@ export declare enum EditorEventType {

@@ -10,2 +10,3 @@ // Types related to the image editor

InputEvtType[InputEvtType["KeyPressEvent"] = 5] = "KeyPressEvent";
InputEvtType[InputEvtType["KeyUpEvent"] = 6] = "KeyUpEvent";
})(InputEvtType || (InputEvtType = {}));

@@ -12,0 +13,0 @@ export var EditorEventType;

@@ -23,2 +23,5 @@ import { EditorEventType } from './types';

this.undoStack.push(command);
for (const elem of this.redoStack) {
elem.onDrop(this.editor);
}
this.redoStack = [];

@@ -25,0 +28,0 @@ this.fireUpdateEvent();

{
"name": "js-draw",
"version": "0.1.9",
"version": "0.1.10",
"description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",

@@ -12,2 +12,10 @@ "main": "dist/src/Editor.js",

},
"./localizations/getLocalizationTable": {
"types": "./dist/src/localizations/getLocalizationTable.d.ts",
"default": "./dist/src/localizations/getLocalizationTable.js"
},
"./getLocalizationTable": {
"types": "./dist/src/localizations/getLocalizationTable.d.ts",
"default": "./dist/src/localizations/getLocalizationTable.js"
},
"./styles": {

@@ -14,0 +22,0 @@ "default": "./src/styles.js"

@@ -121,7 +121,19 @@ # js-draw

See [src/localization.ts](src/localization.ts) for a list of strings.
If a user's language is available in [src/localizations/](src/localizations/) (as determined by `navigator.languages`), that localization will be used.
Some of the default strings in the editor might be overridden like this:
To override the default language, use `getLocalizationTable([ 'custom locale here' ])`. For example,
```ts
const editor = new Editor(document.body, {
// Force the Spanish (Español) localizaiton
localization: getLocalizationTable([ 'es' ]),
});
```
<details><summary>Creating a custom localization</summary>
See [src/localization.ts](src/localization.ts) for a list of strings that can be translated.
Many of the default strings in the editor might be overridden like this:
```ts
const editor = new Editor(document.body, {
// Example partial Spanish localization

@@ -145,3 +157,2 @@ localization: {

select: 'Selecciona',
touchDrawing: 'Dibuja con un dedo',
thicknessLabel: 'Tamaño: ',

@@ -155,2 +166,3 @@ colorLabel: 'Color: ',

</details>

@@ -157,0 +169,0 @@ ## Changing the editor's color theme

@@ -5,4 +5,5 @@ // Main entrypoint for Webpack when building a bundle for release.

import Editor from '../Editor';
import getLocalizationTable from '../localizations/getLocalizationTable';
export default Editor;
export { Editor };
export { Editor, getLocalizationTable };

@@ -20,3 +20,4 @@

import Rect2 from './geometry/Rect2';
import { defaultEditorLocalization, EditorLocalization } from './localization';
import { EditorLocalization } from './localization';
import getLocalizationTable from './localizations/getLocalizationTable';

@@ -47,3 +48,3 @@ export interface EditorSettings {

private importExportViewport: Viewport;
public localization: EditorLocalization = defaultEditorLocalization;
public localization: EditorLocalization;

@@ -64,3 +65,3 @@ public viewport: Viewport;

this.localization = {
...this.localization,
...getLocalizationTable(),
...settings.localization,

@@ -244,13 +245,3 @@ };

this.renderingRegion.addEventListener('keydown', evt => {
if (this.toolController.dispatchInputEvent({
kind: InputEvtType.KeyPressEvent,
key: evt.key,
ctrlKey: evt.ctrlKey,
})) {
evt.preventDefault();
} else if (evt.key === 'Escape') {
this.renderingRegion.blur();
}
});
this.handleKeyEventsFrom(this.renderingRegion);

@@ -314,2 +305,28 @@ this.container.addEventListener('wheel', evt => {

// Adds event listners for keypresses to [elem] and forwards those events to the
// editor.
public handleKeyEventsFrom(elem: HTMLElement) {
elem.addEventListener('keydown', evt => {
if (this.toolController.dispatchInputEvent({
kind: InputEvtType.KeyPressEvent,
key: evt.key,
ctrlKey: evt.ctrlKey,
})) {
evt.preventDefault();
} else if (evt.key === 'Escape') {
this.renderingRegion.blur();
}
});
elem.addEventListener('keyup', evt => {
if (this.toolController.dispatchInputEvent({
kind: InputEvtType.KeyUpEvent,
key: evt.key,
ctrlKey: evt.ctrlKey,
})) {
evt.preventDefault();
}
});
}
// Adds to history by default

@@ -316,0 +333,0 @@ public dispatch(command: Command, addToHistory: boolean = true) {

@@ -77,2 +77,17 @@ import LineSegment2 from './LineSegment2';

});
it('Closest point to (0,0) on the line x = 1 should be (1,0)', () => {
const line = new LineSegment2(Vec2.of(1, 100), Vec2.of(1, -100));
expect(line.closestPointTo(Vec2.zero)).objEq(Vec2.of(1, 0));
});
it('Closest point from (-1,2) to segment((1,1) -> (2,4)) should be (1,1)', () => {
const line = new LineSegment2(Vec2.of(1, 1), Vec2.of(2, 4));
expect(line.closestPointTo(Vec2.of(-1, 2))).objEq(Vec2.of(1, 1));
});
it('Closest point from (5,2) to segment((1,1) -> (2,4)) should be (2,4)', () => {
const line = new LineSegment2(Vec2.of(1, 1), Vec2.of(2, 4));
expect(line.closestPointTo(Vec2.of(5, 2))).objEq(Vec2.of(2, 4));
});
});

@@ -10,2 +10,3 @@ import Rect2 from './Rect2';

export default class LineSegment2 {
// invariant: ||direction|| = 1
public readonly direction: Vec2;

@@ -128,2 +129,21 @@ public readonly length: number;

}
// Returns the closest point on this to [target]
public closestPointTo(target: Point2) {
// Distance from P1 along this' direction.
const projectedDistFromP1 = target.minus(this.p1).dot(this.direction);
const projectedDistFromP2 = this.length - projectedDistFromP1;
const projection = this.p1.plus(this.direction.times(projectedDistFromP1));
if (projectedDistFromP1 > 0 && projectedDistFromP1 < this.length) {
return projection;
}
if (Math.abs(projectedDistFromP2) < Math.abs(projectedDistFromP1)) {
return this.p2;
} else {
return this.p1;
}
}
}

@@ -150,12 +150,25 @@

});
it('division of rectangle', () => {
expect(new Rect2(0, 0, 2, 1).divideIntoGrid(2, 2)).toMatchObject(
[
new Rect2(0, 0, 1, 0.5), new Rect2(1, 0, 1, 0.5),
new Rect2(0, 0.5, 1, 0.5), new Rect2(1, 0.5, 1, 0.5),
]
);
});
});
it('division of rectangle', () => {
expect(new Rect2(0, 0, 2, 1).divideIntoGrid(2, 2)).toMatchObject(
[
new Rect2(0, 0, 1, 0.5), new Rect2(1, 0, 1, 0.5),
new Rect2(0, 0.5, 1, 0.5), new Rect2(1, 0.5, 1, 0.5),
]
);
describe('should correctly return the closest point on the edge of a rectangle', () => {
it('with the unit square', () => {
const rect = Rect2.unitSquare;
expect(rect.getClosestPointOnBoundaryTo(Vec2.zero)).objEq(Vec2.zero);
expect(rect.getClosestPointOnBoundaryTo(Vec2.of(-1, -1))).objEq(Vec2.zero);
expect(rect.getClosestPointOnBoundaryTo(Vec2.of(-1, 0.5))).objEq(Vec2.of(0, 0.5));
expect(rect.getClosestPointOnBoundaryTo(Vec2.of(1, 0.5))).objEq(Vec2.of(1, 0.5));
expect(rect.getClosestPointOnBoundaryTo(Vec2.of(0.6, 0.6))).objEq(Vec2.of(1, 0.6));
expect(rect.getClosestPointOnBoundaryTo(Vec2.of(2, 0.5))).objEq(Vec2.of(1, 0.5));
expect(rect.getClosestPointOnBoundaryTo(Vec2.of(0.6, 0.6))).objEq(Vec2.of(1, 0.6));
});
});
});

@@ -54,2 +54,3 @@ import LineSegment2 from './LineSegment2';

// Returns a copy of this with the given size (but same top-left).
public resizedTo(size: Vec2): Rect2 {

@@ -167,2 +168,19 @@ return new Rect2(this.x, this.y, size.x, size.y);

public getClosestPointOnBoundaryTo(target: Point2) {
const closestEdgePoints = this.getEdges().map(edge => {
return edge.closestPointTo(target);
});
let closest: Point2|null = null;
let closestDist: number|null = null;
for (const point of closestEdgePoints) {
const dist = point.minus(target).length();
if (closestDist === null || dist < closestDist) {
closest = point;
closestDist = dist;
}
}
return closest!;
}
public get corners(): Point2[] {

@@ -177,3 +195,3 @@ return [

public get maxDimension(): number {
public get maxDimension() {
return Math.max(this.w, this.h);

@@ -180,0 +198,0 @@ }

@@ -141,3 +141,3 @@ import Editor from '../Editor';

const undoButton = this.addActionButton({
label: 'Undo',
label: this.localizationTable.undo,
icon: makeUndoIcon()

@@ -148,3 +148,3 @@ }, () => {

const redoButton = this.addActionButton({
label: 'Redo',
label: this.localizationTable.redo,
icon: makeRedoIcon(),

@@ -205,8 +205,5 @@ }, () => {

for (const tool of toolController.getMatchingTools(ToolType.PanZoom)) {
if (!(tool instanceof PanZoom)) {
throw new Error('All SelectionTools must have kind === ToolType.PanZoom');
}
(new HandToolWidget(this.editor, tool, this.localizationTable)).addTo(this.container);
const panZoomTool = toolController.getMatchingTools(ToolType.PanZoom)[0];
if (panZoomTool && panZoomTool instanceof PanZoom) {
(new HandToolWidget(this.editor, panZoomTool, this.localizationTable)).addTo(this.container);
}

@@ -213,0 +210,0 @@

@@ -12,7 +12,7 @@ import Color4 from '../Color4';

const svgNamespace = 'http://www.w3.org/2000/svg';
const primaryForegroundFill = `
style='fill: var(--primary-foreground-color);'
const iconColorFill = `
style='fill: var(--icon-color);'
`;
const primaryForegroundStrokeFill = `
style='fill: var(--primary-foreground-color); stroke: var(--primary-foreground-color);'
const iconColorStrokeFill = `
style='fill: var(--icon-color); stroke: var(--icon-color);'
`;

@@ -43,3 +43,3 @@ const checkerboardPatternDef = `

.toolbar-svg-undo-redo-icon {
stroke: var(--primary-foreground-color);
stroke: var(--icon-color);
stroke-width: 12;

@@ -68,3 +68,3 @@ stroke-linejoin: round;

d='M5,10 L50,90 L95,10 Z'
${primaryForegroundFill}
${iconColorFill}
/>

@@ -86,3 +86,3 @@ </g>

x=10 y=10 width=80 height=50
${primaryForegroundFill}
${iconColorFill}
/>

@@ -139,3 +139,3 @@ </g>

style='
stroke: var(--primary-foreground-color);
stroke: var(--icon-color);
stroke-width: 2;

@@ -183,3 +183,3 @@ '

style='
stroke: var(--primary-foreground-color);
stroke: var(--icon-color);
stroke-width: 2;

@@ -250,3 +250,3 @@ '

style='
stroke: var(--primary-foreground-color);
stroke: var(--icon-color);
stroke-width: 2;

@@ -273,3 +273,3 @@ '

textNode.style.fontSize = '55px';
textNode.style.fill = 'var(--primary-foreground-color)';
textNode.style.fill = 'var(--icon-color)';
textNode.style.fontFamily = 'monospace';

@@ -326,3 +326,3 @@

d='M10,10 L90,10 L90,60 L${50 + halfThickness},80 L${50 - halfThickness},80 L10,60 Z'
${primaryForegroundStrokeFill}
${iconColorStrokeFill}
/>

@@ -401,3 +401,3 @@ </g>

`);
pipette.style.fill = 'var(--primary-foreground-color)';
pipette.style.fill = 'var(--icon-color)';

@@ -404,0 +404,0 @@ if (color) {

@@ -26,2 +26,3 @@

zoom: string;
selectionToolKeyboardShortcuts: string;

@@ -50,2 +51,3 @@ dropdownShown: (toolName: string)=> string;

pickColorFronScreen: 'Pick color from screen',
selectionToolKeyboardShortcuts: 'Selection tool: Use arrow keys to move selected items, lowercase/uppercase ‘i’ and ‘o’ to resize.',

@@ -52,0 +54,0 @@ touchPanning: 'Touchscreen panning',

import Editor from '../../Editor';
import { InputEvtType } from '../../types';
import { toolbarCSSPrefix } from '../HTMLToolbar';

@@ -53,2 +54,35 @@ import { makeDropdownIcon } from '../icons';

protected setupActionBtnClickListener(button: HTMLElement) {
const clickTriggers = { enter: true, ' ': true, };
button.onkeydown = (evt) => {
let handled = false;
if (evt.key in clickTriggers) {
if (!this.disabled) {
this.handleClick();
handled = true;
}
}
// If we didn't do anything with the event, send it to the editor.
if (!handled) {
this.editor.toolController.dispatchInputEvent({
kind: InputEvtType.KeyPressEvent,
key: evt.key,
ctrlKey: evt.ctrlKey,
});
}
};
button.onkeyup = evt => {
if (evt.key in clickTriggers) {
return;
}
this.editor.toolController.dispatchInputEvent({
kind: InputEvtType.KeyUpEvent,
key: evt.key,
ctrlKey: evt.ctrlKey,
});
};
button.onclick = () => {

@@ -55,0 +89,0 @@ if (!this.disabled) {

@@ -1,2 +0,2 @@

import { PointerEvtListener, WheelEvt, PointerEvt, EditorNotifier, EditorEventType, KeyPressEvent } from '../types';
import { PointerEvtListener, WheelEvt, PointerEvt, EditorNotifier, EditorEventType, KeyPressEvent, KeyUpEvent } from '../types';
import { ToolType } from './ToolController';

@@ -27,2 +27,6 @@ import ToolEnabledGroup from './ToolEnabledGroup';

public onKeyUp(_event: KeyUpEvent): boolean {
return false;
}
public setEnabled(enabled: boolean) {

@@ -29,0 +33,0 @@ this.enabled = enabled;

export interface ToolLocalization {
keyboardPanZoom: string;
penTool: (penId: number)=>string;

@@ -28,2 +29,3 @@ selectionTool: string;

pipetteTool: 'Pick color from screen',
keyboardPanZoom: 'Keyboard pan/zoom shortcuts',

@@ -30,0 +32,0 @@ textTool: 'Text',

@@ -19,2 +19,3 @@

export enum PanZoomMode {

@@ -25,2 +26,3 @@ OneFingerTouchGestures = 0x1,

SinglePointerGestures = 0x1 << 3,
Keyboard = 0x1 << 4,
}

@@ -185,2 +187,6 @@

public onKeyPress({ key }: KeyPressEvent): boolean {
if (!(this.mode & PanZoomMode.Keyboard)) {
return false;
}
let translation = Vec2.zero;

@@ -187,0 +193,0 @@ let scale = 1;

@@ -69,3 +69,3 @@ /* @jest-environment jsdom */

selection!.handleBackgroundDrag(Vec2.of(5, 5));
selection!.finishDragging();
selection!.finalizeTransform();

@@ -84,2 +84,25 @@ expect(testStroke.getBBox().topLeft).toMatchObject({

});
it('moving the selection with a keyboard should move the view to keep the selection in view', () => {
const { addTestStrokeCommand } = createSquareStroke();
const editor = createEditor();
editor.dispatch(addTestStrokeCommand);
// Select the stroke
const selectionTool = getSelectionTool(editor);
selectionTool.setEnabled(true);
editor.sendPenEvent(InputEvtType.PointerDownEvt, Vec2.of(0, 0));
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(10, 10));
editor.sendPenEvent(InputEvtType.PointerUpEvt, Vec2.of(100, 100));
const selection = selectionTool.getSelection();
if (selection === null) {
// Throw to allow TypeScript's non-null checker to understand that selection
// must be non-null after this.
throw new Error('Selection should be non-null.');
}
selection.handleBackgroundDrag(Vec2.of(0, -1000));
expect(editor.viewport.visibleRect.containsPoint(selection.region.center)).toBe(true);
});
});

@@ -11,3 +11,4 @@ import Command from '../commands/Command';

import { EditorLocalization } from '../localization';
import { EditorEventType, PointerEvt } from '../types';
import { EditorEventType, KeyPressEvent, KeyUpEvent, PointerEvt } from '../types';
import Viewport from '../Viewport';
import BaseTool from './BaseTool';

@@ -167,11 +168,11 @@ import { ToolType } from './ToolController';

this.handleBackgroundDrag(deltaPosition);
}, () => this.finishDragging());
}, () => this.finalizeTransform());
makeDraggable(resizeCorner, (deltaPosition) => {
this.handleResizeCornerDrag(deltaPosition);
}, () => this.finishDragging());
}, () => this.finalizeTransform());
makeDraggable(this.rotateCircle, (_deltaPosition, offset) => {
this.handleRotateCircleDrag(offset);
}, () => this.finishDragging());
}, () => this.finalizeTransform());
}

@@ -191,6 +192,3 @@

this.region = this.region.translatedBy(deltaPosition);
this.transform = this.transform.rightMul(Mat33.translation(deltaPosition));
this.previewTransformCmds();
this.transformPreview(Mat33.translation(deltaPosition));
}

@@ -209,8 +207,5 @@

if (newSize.y > 0 && newSize.x > 0) {
this.region = this.region.resizedTo(newSize);
const scaleFactor = Vec2.of(this.region.w / oldWidth, this.region.h / oldHeight);
const scaleFactor = Vec2.of(newSize.x / oldWidth, newSize.y / oldHeight);
const currentTransfm = Mat33.scaling2D(scaleFactor, this.region.topLeft);
this.transform = this.transform.rightMul(currentTransfm);
this.previewTransformCmds();
this.transformPreview(Mat33.scaling2D(scaleFactor, this.region.topLeft));
}

@@ -220,7 +215,2 @@ }

public handleRotateCircleDrag(offset: Vec2) {
this.boxRotation = this.boxRotation % (2 * Math.PI);
if (this.boxRotation < 0) {
this.boxRotation += 2 * Math.PI;
}
let targetRotation = offset.angle();

@@ -245,5 +235,3 @@ targetRotation = targetRotation % (2 * Math.PI);

this.transform = this.transform.rightMul(Mat33.zRotation(deltaRotation, this.region.center));
this.boxRotation += deltaRotation;
this.previewTransformCmds();
this.transformPreview(Mat33.zRotation(deltaRotation, this.region.center));
}

@@ -257,4 +245,25 @@

// Applies, previews, but doesn't finalize the given transformation.
public transformPreview(transform: Mat33) {
this.transform = this.transform.rightMul(transform);
const deltaRotation = transform.transformVec3(Vec2.unitX).angle();
transform = transform.rightMul(Mat33.zRotation(-deltaRotation, this.region.center));
this.boxRotation += deltaRotation;
this.boxRotation = this.boxRotation % (2 * Math.PI);
if (this.boxRotation < 0) {
this.boxRotation += 2 * Math.PI;
}
const newSize = transform.transformVec3(this.region.size);
const translation = transform.transformVec2(this.region.topLeft).minus(this.region.topLeft);
this.region = this.region.resizedTo(newSize);
this.region = this.region.translatedBy(translation);
this.previewTransformCmds();
this.scrollTo();
}
// Applies the current transformation to the selection
public finishDragging() {
public finalizeTransform() {
this.transformationCommands.forEach(cmd => {

@@ -331,3 +340,2 @@ cmd.unapply(this.editor);

public appendBackgroundBoxTo(elem: HTMLElement) {

@@ -455,2 +463,15 @@ if (this.backgroundBox.parentElement) {

// Scroll the viewport to this. Does not zoom
public scrollTo() {
const viewport = this.editor.viewport;
const visibleRect = viewport.visibleRect;
if (!visibleRect.containsPoint(this.region.center)) {
const closestPoint = visibleRect.getClosestPointOnBoundaryTo(this.region.center);
const delta = this.region.center.minus(closestPoint);
this.editor.dispatchNoAnnounce(
new Viewport.ViewportTransform(Mat33.translation(delta.times(-1))), false
);
}
}
public deleteSelectedObjects(): Command {

@@ -485,2 +506,4 @@ return new Erase(this.selectedElems);

});
this.editor.handleKeyEventsFrom(this.handleOverlay);
}

@@ -525,3 +548,8 @@

);
this.zoomToSelection();
}
}
private zoomToSelection() {
if (this.selectionBox) {
const selectionRect = this.selectionBox.region;

@@ -546,2 +574,102 @@ this.editor.dispatchNoAnnounce(this.editor.viewport.zoomTo(selectionRect, false), false);

private static handleableKeys = [
'a', 'h', 'ArrowLeft',
'd', 'l', 'ArrowRight',
'q', 'k', 'ArrowUp',
'e', 'j', 'ArrowDown',
'r', 'R',
'i', 'I', 'o', 'O',
];
public onKeyPress(event: KeyPressEvent): boolean {
let rotationSteps = 0;
let xTranslateSteps = 0;
let yTranslateSteps = 0;
let xScaleSteps = 0;
let yScaleSteps = 0;
switch (event.key) {
case 'a':
case 'h':
case 'ArrowLeft':
xTranslateSteps -= 1;
break;
case 'd':
case 'l':
case 'ArrowRight':
xTranslateSteps += 1;
break;
case 'q':
case 'k':
case 'ArrowUp':
yTranslateSteps -= 1;
break;
case 'e':
case 'j':
case 'ArrowDown':
yTranslateSteps += 1;
break;
case 'r':
rotationSteps += 1;
break;
case 'R':
rotationSteps -= 1;
break;
case 'i':
xScaleSteps -= 1;
break;
case 'I':
xScaleSteps += 1;
break;
case 'o':
yScaleSteps -= 1;
break;
case 'O':
yScaleSteps += 1;
break;
}
let handled = xTranslateSteps !== 0
|| yTranslateSteps !== 0
|| rotationSteps !== 0
|| xScaleSteps !== 0
|| yScaleSteps !== 0;
if (!this.selectionBox) {
handled = false;
} else if (handled) {
const translateStepSize = 10 * this.editor.viewport.getSizeOfPixelOnCanvas();
const rotateStepSize = Math.PI / 8;
const scaleStepSize = translateStepSize / 2;
const region = this.selectionBox.region;
const scaledSize = this.selectionBox.region.size.plus(
Vec2.of(xScaleSteps, yScaleSteps).times(scaleStepSize)
);
const transform = Mat33.scaling2D(
Vec2.of(
// Don't more-than-half the size of the selection
Math.max(0.5, scaledSize.x / region.size.x),
Math.max(0.5, scaledSize.y / region.size.y)
),
region.topLeft
).rightMul(Mat33.zRotation(
rotationSteps * rotateStepSize, region.center
)).rightMul(Mat33.translation(
Vec2.of(xTranslateSteps, yTranslateSteps).times(translateStepSize)
));
this.selectionBox.transformPreview(transform);
}
return handled;
}
public onKeyUp(evt: KeyUpEvent) {
if (this.selectionBox && SelectionTool.handleableKeys.some(key => key === evt.key)) {
this.selectionBox.finalizeTransform();
return true;
}
return false;
}
public setEnabled(enabled: boolean) {

@@ -555,2 +683,9 @@ super.setEnabled(enabled);

this.handleOverlay.style.display = enabled ? 'block' : 'none';
if (enabled) {
this.handleOverlay.tabIndex = 0;
this.handleOverlay.ariaLabel = this.editor.localization.selectionToolKeyboardShortcuts;
} else {
this.handleOverlay.tabIndex = -1;
}
}

@@ -557,0 +692,0 @@

@@ -33,2 +33,3 @@ import { InputEvtType, InputEvt, EditorEventType } from '../types';

const panZoomTool = new PanZoom(editor, PanZoomMode.TwoFingerTouchGestures | PanZoomMode.RightClickDrags, localization.touchPanTool);
const keyboardPanZoomTool = new PanZoom(editor, PanZoomMode.Keyboard, localization.keyboardPanZoom);
const primaryPenTool = new Pen(editor, localization.penTool(1), { color: Color4.purple, thickness: 16 });

@@ -52,2 +53,3 @@ const primaryTools = [

...primaryTools,
keyboardPanZoomTool,
new UndoRedoShortcut(editor),

@@ -93,5 +95,6 @@ ];

} else if (
event.kind === InputEvtType.WheelEvt || event.kind === InputEvtType.KeyPressEvent
event.kind === InputEvtType.WheelEvt || event.kind === InputEvtType.KeyPressEvent || event.kind === InputEvtType.KeyUpEvent
) {
const isKeyPressEvt = event.kind === InputEvtType.KeyPressEvent;
const isKeyReleaseEvt = event.kind === InputEvtType.KeyUpEvent;
const isWheelEvt = event.kind === InputEvtType.WheelEvt;

@@ -105,3 +108,4 @@ for (const tool of this.tools) {

const keyPressResult = isKeyPressEvt && tool.onKeyPress(event);
handled = keyPressResult || wheelResult;
const keyReleaseResult = isKeyReleaseEvt && tool.onKeyUp(event);
handled = keyPressResult || wheelResult || keyReleaseResult;

@@ -108,0 +112,0 @@ if (handled) {

@@ -27,2 +27,3 @@ // Types related to the image editor

export enum InputEvtType {

@@ -36,2 +37,3 @@ PointerDownEvt,

KeyPressEvent,
KeyUpEvent
}

@@ -54,2 +56,8 @@

export interface KeyUpEvent {
readonly kind: InputEvtType.KeyUpEvent;
readonly key: string;
readonly ctrlKey: boolean;
}
// Event triggered when pointer capture is taken by a different [PointerEvtListener].

@@ -78,3 +86,3 @@ export interface GestureCancelEvt {

export type PointerEvt = PointerDownEvt | PointerMoveEvt | PointerUpEvt;
export type InputEvt = KeyPressEvent | WheelEvt | GestureCancelEvt | PointerEvt;
export type InputEvt = KeyPressEvent | KeyUpEvent | WheelEvt | GestureCancelEvt | PointerEvt;

@@ -81,0 +89,0 @@ export type EditorNotifier = EventDispatcher<EditorEventType, EditorEventDataType>;

@@ -35,2 +35,6 @@ import Editor from './Editor';

this.undoStack.push(command);
for (const elem of this.redoStack) {
elem.onDrop(this.editor);
}
this.redoStack = [];

@@ -37,0 +41,0 @@ this.fireUpdateEvent();

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc