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
119
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.5.0 to 0.6.0

dist/src/toolbar/IconProvider.d.ts

11

CHANGELOG.md

@@ -0,1 +1,12 @@

# 0.6.0
* Selection tool:
* Shift+click extends a selection
* `ctrl+d` duplicates selected objects
* `ctrl+r` resizes the image to the selected region
* `ctrl+a` selects everything (when the selection tool is enabled)
* Panning tool: Toggle all device panning by clicking on the hand button.
* `HandToolWidget` now expects, but does not require, a primary hand tool to work properly. See `ToolController#addPrimaryTool`.
* **Breaiking changes:**
* Icons are no longer accessible through `import {makeFooIcon} from '...'`. Use `editor.icons.makeFooIcon` instead.
# 0.5.0

@@ -2,0 +13,0 @@ * Increase contrast between selection box/background

1

dist/src/components/AbstractComponent.d.ts

@@ -31,2 +31,3 @@ import SerializableCommand from '../commands/SerializableCommand';

transformBy(affineTransfm: Mat33): SerializableCommand;
isSelectable(): boolean;
private static transformElementCommandId;

@@ -33,0 +34,0 @@ private static UnresolvedTransformElementCommand;

@@ -51,2 +51,6 @@ var _a;

}
// @returns true iff this component can be selected (e.g. by the selection tool.)
isSelectable() {
return true;
}
// Returns a copy of this component.

@@ -53,0 +57,0 @@ clone() {

4

dist/src/components/builders/FreehandLineBuilder.js

@@ -315,4 +315,4 @@ import { Bezier } from 'bezier-js';

if (!enteringVec) {
let sampleIdx = Math.ceil(this.buffer.length / 3);
if (sampleIdx === 0) {
let sampleIdx = Math.ceil(this.buffer.length / 2);
if (sampleIdx === 0 || sampleIdx >= this.buffer.length) {
sampleIdx = this.buffer.length - 1;

@@ -319,0 +319,0 @@ }

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

protected applyTransformation(_affineTransfm: Mat33): void;
isSelectable(): boolean;
protected createClone(): SVGGlobalAttributesObject;

@@ -17,0 +18,0 @@ description(localization: ImageComponentLocalization): string;

@@ -32,2 +32,5 @@ //

}
isSelectable() {
return false;
}
createClone() {

@@ -34,0 +37,0 @@ return new SVGGlobalAttributesObject(this.attrs);

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

}
declare type GetTextDimensCallback = (text: string, style: TextStyle) => Rect2;
export default class Text extends AbstractComponent {

@@ -21,7 +20,7 @@ protected readonly textObjects: Array<string | Text>;

private readonly style;
private readonly getTextDimens;
protected contentBBox: Rect2;
constructor(textObjects: Array<string | Text>, transform: Mat33, style: TextStyle, getTextDimens?: GetTextDimensCallback);
constructor(textObjects: Array<string | Text>, transform: Mat33, style: TextStyle);
static applyTextStyles(ctx: CanvasRenderingContext2D, style: TextStyle): void;
private static textMeasuringCtx;
private static estimateTextDimens;
private static getTextDimens;

@@ -37,4 +36,3 @@ private computeBBoxOfPart;

protected serializeToJSON(): Record<string, any>;
static deserializeFromString(json: any, getTextDimens?: GetTextDimensCallback): Text;
static deserializeFromString(json: any): Text;
}
export {};

@@ -8,6 +8,3 @@ import LineSegment2 from '../math/LineSegment2';

export default class Text extends AbstractComponent {
constructor(textObjects, transform, style,
// If not given, an HtmlCanvasElement is used to determine text boundaries.
// @internal
getTextDimens = Text.getTextDimens) {
constructor(textObjects, transform, style) {
super(componentTypeId);

@@ -17,3 +14,2 @@ this.textObjects = textObjects;

this.style = style;
this.getTextDimens = getTextDimens;
this.recomputeBBox();

@@ -33,5 +29,17 @@ }

}
// Roughly estimate the bounding box of `text`. Use if no CanvasRenderingContext2D is available.
static estimateTextDimens(text, style) {
const widthEst = text.length * style.size;
const heightEst = style.size;
// Text is drawn with (0, 0) as its baseline. As such, the majority of the text's height should
// be above (0, 0).
return new Rect2(0, -heightEst * 2 / 3, widthEst, heightEst);
}
// Returns the bounding box of `text`. This is approximate if no Canvas is available.
static getTextDimens(text, style) {
var _a;
(_a = Text.textMeasuringCtx) !== null && _a !== void 0 ? _a : (Text.textMeasuringCtx = document.createElement('canvas').getContext('2d'));
var _a, _b;
(_a = Text.textMeasuringCtx) !== null && _a !== void 0 ? _a : (Text.textMeasuringCtx = (_b = document.createElement('canvas').getContext('2d')) !== null && _b !== void 0 ? _b : null);
if (!Text.textMeasuringCtx) {
return this.estimateTextDimens(text, style);
}
const ctx = Text.textMeasuringCtx;

@@ -47,3 +55,3 @@ Text.applyTextStyles(ctx, style);

if (typeof part === 'string') {
const textBBox = this.getTextDimens(part, this.style);
const textBBox = Text.getTextDimens(part, this.style);
return textBBox.transformedBoundingBox(this.transform);

@@ -145,3 +153,3 @@ }

}
static deserializeFromString(json, getTextDimens = Text.getTextDimens) {
static deserializeFromString(json) {
const style = {

@@ -167,5 +175,6 @@ renderingStyle: styleFromJSON(json.style.renderingStyle),

const transform = new Mat33(...transformData);
return new Text(textObjects, transform, style, getTextDimens);
return new Text(textObjects, transform, style);
}
}
Text.textMeasuringCtx = null;
AbstractComponent.registerComponent(componentTypeId, (data) => Text.deserializeFromString(data));

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

protected applyTransformation(_affineTransfm: Mat33): void;
isSelectable(): boolean;
protected createClone(): AbstractComponent;

@@ -16,0 +17,0 @@ description(localization: ImageComponentLocalization): string;

@@ -28,2 +28,5 @@ //

}
isSelectable() {
return false;
}
createClone() {

@@ -30,0 +33,0 @@ return new UnknownSVGObject(this.svgObject.cloneNode(true));

@@ -31,2 +31,3 @@ /**

import { EditorLocalization } from './localization';
import IconProvider from './toolbar/IconProvider';
declare type HTMLPointerEventType = 'pointerdown' | 'pointermove' | 'pointerup' | 'pointercancel';

@@ -48,2 +49,3 @@ declare type HTMLPointerEventFilter = (eventName: HTMLPointerEventType, event: PointerEvent) => boolean;

maxZoom: number;
iconProvider: IconProvider;
}

@@ -86,9 +88,10 @@ export declare class Editor {

*/
image: EditorImage;
readonly image: EditorImage;
/** Viewport for the exported/imported image. */
private importExportViewport;
/** @internal */
localization: EditorLocalization;
viewport: Viewport;
toolController: ToolController;
readonly localization: EditorLocalization;
readonly icons: IconProvider;
readonly viewport: Viewport;
readonly toolController: ToolController;
/**

@@ -98,3 +101,3 @@ * Global event dispatcher/subscriber.

*/
notifier: EditorNotifier;
readonly notifier: EditorNotifier;
private loadingWarning;

@@ -101,0 +104,0 @@ private accessibilityAnnounceArea;

@@ -44,2 +44,3 @@ /**

import getLocalizationTable from './localizations/getLocalizationTable';
import IconProvider from './toolbar/IconProvider';
// { @inheritDoc Editor! }

@@ -71,3 +72,3 @@ export class Editor {

constructor(parent, settings = {}) {
var _a, _b, _c, _d;
var _a, _b, _c, _d, _e;
this.eventListenerTargets = [];

@@ -91,3 +92,5 @@ this.previousAccessibilityAnnouncement = '';

maxZoom: (_d = settings.maxZoom) !== null && _d !== void 0 ? _d : 1e12,
iconProvider: (_e = settings.iconProvider) !== null && _e !== void 0 ? _e : new IconProvider(),
};
this.icons = this.settings.iconProvider;
this.container = document.createElement('div');

@@ -94,0 +97,0 @@ this.renderingRegion = document.createElement('div');

@@ -19,2 +19,5 @@ import AbstractRenderer from './rendering/renderers/AbstractRenderer';

renderAll(renderer: AbstractRenderer): void;
/** @returns all elements in the image, sorted by z-index. This can be slow for large images. */
getAllElements(): AbstractComponent[];
/** @returns a list of `AbstractComponent`s intersecting `region`, sorted by z-index. */
getElementsIntersectingRegion(region: Rect2): AbstractComponent[];

@@ -21,0 +24,0 @@ /** @internal */

@@ -42,2 +42,9 @@ var _a;

}
/** @returns all elements in the image, sorted by z-index. This can be slow for large images. */
getAllElements() {
const leaves = this.root.getLeaves();
sortLeavesByZIndex(leaves);
return leaves.map(leaf => leaf.getContent());
}
/** @returns a list of `AbstractComponent`s intersecting `region`, sorted by z-index. */
getElementsIntersectingRegion(region) {

@@ -44,0 +51,0 @@ const leaves = this.root.getLeavesIntersectingRegion(region);

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

}
if (supportedStyleAttrs) {
if (supportedStyleAttrs && node.style) {
for (const attr of node.style) {

@@ -161,5 +161,5 @@ if (attr === '' || !attr) {

const elemY = elem.getAttribute('y');
if (elemX && elemY) {
const x = parseFloat(elemX);
const y = parseFloat(elemY);
if (elemX || elemY) {
const x = parseFloat(elemX !== null && elemX !== void 0 ? elemX : '0');
const y = parseFloat(elemY !== null && elemY !== void 0 ? elemY : '0');
if (!isNaN(x) && !isNaN(y)) {

@@ -209,3 +209,3 @@ supportedAttrs === null || supportedAttrs === void 0 ? void 0 : supportedAttrs.push('x', 'y');

renderingStyle: {
fill: Color4.fromString(computedStyles.fill)
fill: Color4.fromString(computedStyles.fill || elem.style.fill || '#000')
},

@@ -345,3 +345,2 @@ };

}
// TODO: Handling unsafe data! Tripple-check that this is secure!
// @param sanitize - if `true`, don't store unknown attributes.

@@ -348,0 +347,0 @@ static fromString(text, sanitize = false) {

import loadExpectExtensions from './loadExpectExtensions';
loadExpectExtensions();
jest.useFakeTimers();
// jsdom doesn't support HTMLCanvasElement#getContext — it logs an error
// to the console. Make it return null so we can handle a non-existent Canvas
// at runtime (e.g. use something else, if available).
HTMLCanvasElement.prototype.getContext = () => null;

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

import { defaultToolbarLocalization } from './localization';
import { makeRedoIcon, makeUndoIcon } from './icons';
import SelectionTool from '../tools/SelectionTool/SelectionTool';

@@ -127,3 +126,3 @@ import PanZoomTool from '../tools/PanZoom';

label: this.localizationTable.undo,
icon: makeUndoIcon()
icon: this.editor.icons.makeUndoIcon()
}, () => {

@@ -134,3 +133,3 @@ this.editor.history.undo();

label: this.localizationTable.redo,
icon: makeRedoIcon(),
icon: this.editor.icons.makeRedoIcon(),
}, () => {

@@ -137,0 +136,0 @@ this.editor.history.redo();

export * from './widgets/lib';
export * as icons from './icons';
export * from './makeColorInput';
export { default as IconProvider } from './IconProvider';
export * from './widgets/lib';
import * as icons_1 from './icons';
export { icons_1 as icons };
export * from './makeColorInput';
export { default as IconProvider } from './IconProvider';
export interface ToolbarLocalization {
fontLabel: string;
anyDevicePanning: string;
touchPanning: string;

@@ -5,0 +4,0 @@ outlinedRectanglePen: string;

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

touchPanning: 'Touchscreen panning',
anyDevicePanning: 'Any device panning',
freehandPen: 'Freehand',

@@ -24,0 +23,0 @@ arrowPen: 'Arrow',

import Color4 from '../Color4';
import PipetteTool from '../tools/PipetteTool';
import { EditorEventType } from '../types';
import { makePipetteIcon } from './icons';
// Returns [ color input, input container ].

@@ -59,3 +58,3 @@ export const makeColorInput = (editor, onColorChange) => {

const updatePipetteIcon = (color) => {
pipetteButton.replaceChildren(makePipetteIcon(color));
pipetteButton.replaceChildren(editor.icons.makePipetteIcon(color));
};

@@ -62,0 +61,0 @@ updatePipetteIcon();

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

import { toolbarCSSPrefix } from '../HTMLToolbar';
import { makeDropdownIcon } from '../icons';
export default class BaseWidget {

@@ -210,3 +209,3 @@ constructor(editor, localizationTable) {

createDropdownIcon() {
const icon = makeDropdownIcon();
const icon = this.editor.icons.makeDropdownIcon();
icon.classList.add(`${toolbarCSSPrefix}showHideDropdownIcon`);

@@ -213,0 +212,0 @@ return icon;

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

import { makeEraserIcon } from '../icons';
import BaseToolWidget from './BaseToolWidget';

@@ -8,3 +7,3 @@ export default class EraserToolWidget extends BaseToolWidget {

createIcon() {
return makeEraserIcon();
return this.editor.icons.makeEraserIcon();
}

@@ -11,0 +10,0 @@ fillDropdown(_dropdown) {

@@ -6,9 +6,11 @@ import Editor from '../../Editor';

export default class HandToolWidget extends BaseToolWidget {
protected tool: PanZoom;
protected overridePanZoomTool: PanZoom;
private touchPanningWidget;
constructor(editor: Editor, tool: PanZoom, localizationTable: ToolbarLocalization);
private allowTogglingBaseTool;
constructor(editor: Editor, overridePanZoomTool: PanZoom, localizationTable: ToolbarLocalization);
private static getPrimaryHandTool;
protected getTitle(): string;
protected createIcon(): Element;
setSelected(_selected: boolean): void;
protected handleClick(): void;
setSelected(selected: boolean): void;
}
import Mat33 from '../../math/Mat33';
import { PanZoomMode } from '../../tools/PanZoom';
import PanZoom, { PanZoomMode } from '../../tools/PanZoom';
import { EditorEventType } from '../../types';
import Viewport from '../../Viewport';
import { toolbarCSSPrefix } from '../HTMLToolbar';
import { makeAllDevicePanningIcon, makeHandToolIcon, makeTouchPanningIcon, makeZoomIcon } from '../icons';
import BaseToolWidget from './BaseToolWidget';

@@ -69,3 +68,3 @@ import BaseWidget from './BaseWidget';

createIcon() {
return makeZoomIcon();
return this.editor.icons.makeZoomIcon();
}

@@ -121,11 +120,27 @@ handleClick() {

export default class HandToolWidget extends BaseToolWidget {
constructor(editor, tool, localizationTable) {
constructor(editor,
// Pan zoom tool that overrides all other tools (enabling this tool for a device
// causes that device to pan/zoom instead of interact with the primary tools)
overridePanZoomTool, localizationTable) {
const primaryHandTool = HandToolWidget.getPrimaryHandTool(editor.toolController);
const tool = primaryHandTool !== null && primaryHandTool !== void 0 ? primaryHandTool : overridePanZoomTool;
super(editor, tool, localizationTable);
this.tool = tool;
this.container.classList.add('dropdownShowable');
this.touchPanningWidget = new HandModeWidget(editor, localizationTable, tool, PanZoomMode.OneFingerTouchGestures, makeTouchPanningIcon, localizationTable.touchPanning);
this.overridePanZoomTool = overridePanZoomTool;
// Only allow toggling a hand tool if we're using the primary hand tool and not the override
// hand tool for this button.
this.allowTogglingBaseTool = primaryHandTool !== null;
// Allow showing/hiding the dropdown, even if `overridePanZoomTool` isn't enabled.
if (!this.allowTogglingBaseTool) {
this.container.classList.add('dropdownShowable');
}
// Controls for the overriding hand tool.
this.touchPanningWidget = new HandModeWidget(editor, localizationTable, overridePanZoomTool, PanZoomMode.OneFingerTouchGestures, () => this.editor.icons.makeTouchPanningIcon(), localizationTable.touchPanning);
this.addSubWidget(this.touchPanningWidget);
this.addSubWidget(new HandModeWidget(editor, localizationTable, tool, PanZoomMode.SinglePointerGestures, makeAllDevicePanningIcon, localizationTable.anyDevicePanning));
this.addSubWidget(new ZoomWidget(editor, localizationTable));
}
static getPrimaryHandTool(toolController) {
const primaryPanZoomToolList = toolController.getPrimaryTools().filter(tool => tool instanceof PanZoom);
const primaryPanZoomTool = primaryPanZoomToolList[0];
return primaryPanZoomTool;
}
getTitle() {

@@ -135,9 +150,17 @@ return this.localizationTable.handTool;

createIcon() {
return makeHandToolIcon();
return this.editor.icons.makeHandToolIcon();
}
setSelected(_selected) {
}
handleClick() {
this.setDropdownVisible(!this.isDropdownVisible());
if (this.allowTogglingBaseTool) {
super.handleClick();
}
else {
this.setDropdownVisible(!this.isDropdownVisible());
}
}
setSelected(selected) {
if (this.allowTogglingBaseTool) {
super.setSelected(selected);
}
}
}

@@ -7,3 +7,2 @@ import { makeArrowBuilder } from '../../components/builders/ArrowBuilder';

import { toolbarCSSPrefix } from '../HTMLToolbar';
import { makeIconFromFactory, makePenIcon } from '../icons';
import makeColorInput from '../makeColorInput';

@@ -59,7 +58,7 @@ import BaseToolWidget from './BaseToolWidget';

const color = this.tool.getColor();
return makePenIcon(scale, color.toHexString());
return this.editor.icons.makePenIcon(scale, color.toHexString());
}
else {
const strokeFactory = this.tool.getStrokeFactory();
return makeIconFromFactory(this.tool, strokeFactory);
return this.editor.icons.makeIconFromFactory(this.tool, strokeFactory);
}

@@ -66,0 +65,0 @@ }

import Editor from '../../Editor';
import SelectionTool from '../../tools/SelectionTool/SelectionTool';
import { KeyPressEvent } from '../../types';
import { ToolbarLocalization } from '../localization';

@@ -8,4 +9,6 @@ import BaseToolWidget from './BaseToolWidget';

constructor(editor: Editor, tool: SelectionTool, localization: ToolbarLocalization);
private resizeImageToSelection;
protected onKeyPress(event: KeyPressEvent): boolean;
protected getTitle(): string;
protected createIcon(): Element;
}
import { EditorEventType } from '../../types';
import { makeDeleteSelectionIcon, makeDuplicateSelectionIcon, makeResizeViewportIcon, makeSelectionIcon } from '../icons';
import ActionButtonWidget from './ActionButtonWidget';

@@ -9,7 +8,6 @@ import BaseToolWidget from './BaseToolWidget';

this.tool = tool;
const resizeButton = new ActionButtonWidget(editor, localization, makeResizeViewportIcon, this.localizationTable.resizeImageToSelection, () => {
const selection = this.tool.getSelection();
this.editor.dispatch(this.editor.setImportExportRect(selection.region));
const resizeButton = new ActionButtonWidget(editor, localization, () => editor.icons.makeResizeViewportIcon(), this.localizationTable.resizeImageToSelection, () => {
this.resizeImageToSelection();
});
const deleteButton = new ActionButtonWidget(editor, localization, makeDeleteSelectionIcon, this.localizationTable.deleteSelection, () => {
const deleteButton = new ActionButtonWidget(editor, localization, () => editor.icons.makeDeleteSelectionIcon(), this.localizationTable.deleteSelection, () => {
const selection = this.tool.getSelection();

@@ -19,3 +17,3 @@ this.editor.dispatch(selection.deleteSelectedObjects());

});
const duplicateButton = new ActionButtonWidget(editor, localization, makeDuplicateSelectionIcon, this.localizationTable.duplicateSelection, () => {
const duplicateButton = new ActionButtonWidget(editor, localization, () => editor.icons.makeDuplicateSelectionIcon(), this.localizationTable.duplicateSelection, () => {
const selection = this.tool.getSelection();

@@ -45,2 +43,17 @@ this.editor.dispatch(selection.duplicateSelectedObjects());

}
resizeImageToSelection() {
const selection = this.tool.getSelection();
if (selection) {
this.editor.dispatch(this.editor.setImportExportRect(selection.region));
}
}
onKeyPress(event) {
// Resize image to selection:
// Other keys are handled directly by the selection tool.
if (event.ctrlKey && event.key === 'r') {
this.resizeImageToSelection();
return true;
}
return false;
}
getTitle() {

@@ -50,4 +63,4 @@ return this.localizationTable.select;

createIcon() {
return makeSelectionIcon();
return this.editor.icons.makeSelectionIcon();
}
}
import { EditorEventType } from '../../types';
import { toolbarCSSPrefix } from '../HTMLToolbar';
import { makeTextIcon } from '../icons';
import makeColorInput from '../makeColorInput';

@@ -24,3 +23,3 @@ import BaseToolWidget from './BaseToolWidget';

const textStyle = this.tool.getTextStyle();
return makeTextIcon(textStyle);
return this.editor.icons.makeTextIcon(textStyle);
}

@@ -27,0 +26,0 @@ fillDropdown(dropdown) {

@@ -15,2 +15,3 @@ export interface ToolLocalization {

pasteHandler: string;
anyDevicePanning: string;
copied: (count: number, description: string) => string;

@@ -17,0 +18,0 @@ pasted: (count: number, description: string) => string;

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

pasteHandler: 'Copy paste handler',
anyDevicePanning: 'Any device panning',
copied: (count, description) => `Copied ${count} ${description}`,

@@ -17,0 +18,0 @@ pasted: (count, description) => `Pasted ${count} ${description}`,

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

onWheel({ delta, screenPos }: WheelEvt): boolean;
onKeyPress({ key }: KeyPressEvent): boolean;
onKeyPress({ key, ctrlKey, altKey }: KeyPressEvent): boolean;
setMode(mode: PanZoomMode): void;

@@ -41,0 +41,0 @@ getMode(): PanZoomMode;

@@ -137,6 +137,9 @@ import Mat33 from '../math/Mat33';

}
onKeyPress({ key }) {
onKeyPress({ key, ctrlKey, altKey }) {
if (!(this.mode & PanZoomMode.Keyboard)) {
return false;
}
if (ctrlKey || altKey) {
return false;
}
// No need to keep the same the transform for keyboard events.

@@ -143,0 +146,0 @@ this.transform = Viewport.transformBy(Mat33.identity);

@@ -13,2 +13,4 @@ import AbstractComponent from '../../components/AbstractComponent';

private lastEvtTarget;
private expandingSelectionBox;
private shiftKeyPressed;
constructor(editor: Editor, description: string);

@@ -30,4 +32,5 @@ private makeSelectionBox;

getSelection(): Selection | null;
getSelectedObjects(): AbstractComponent[];
setSelection(objects: AbstractComponent[]): void;
clearSelection(): void;
}

@@ -19,2 +19,4 @@ // Allows users to select/transform portions of the `EditorImage`.

this.lastEvtTarget = null;
this.expandingSelectionBox = false;
this.shiftKeyPressed = false;
this.selectionBoxHandlingEvt = false;

@@ -38,6 +40,9 @@ this.handleOverlay = document.createElement('div');

makeSelectionBox(selectionStartPos) {
var _a;
this.prevSelectionBox = this.selectionBox;
this.selectionBox = new Selection(selectionStartPos, this.editor);
// Remove any previous selection rects
this.handleOverlay.replaceChildren();
if (!this.expandingSelectionBox) {
// Remove any previous selection rects
(_a = this.prevSelectionBox) === null || _a === void 0 ? void 0 : _a.cancelSelection();
}
this.selectionBox.addTo(this.handleOverlay);

@@ -50,4 +55,7 @@ }

this.selectionBoxHandlingEvt = true;
this.expandingSelectionBox = false;
}
else {
// Shift key: Combine the new and old selection boxes at the end of the gesture.
this.expandingSelectionBox = this.shiftKeyPressed;
this.makeSelectionBox(event.current.canvasPos);

@@ -87,2 +95,3 @@ }

}
// Called after a gestureCancel and a pointerUp
onGestureEnd() {

@@ -112,3 +121,15 @@ this.lastEvtTarget = null;

this.selectionBox.setToPoint(event.current.canvasPos);
this.onGestureEnd();
// Were we expanding the previous selection?
if (this.expandingSelectionBox && this.prevSelectionBox) {
// If so, finish expanding.
this.expandingSelectionBox = false;
this.selectionBox.resolveToObjects();
this.setSelection([
...this.selectionBox.getSelectedObjects(),
...this.prevSelectionBox.getSelectedObjects(),
]);
}
else {
this.onGestureEnd();
}
}

@@ -126,4 +147,24 @@ onGestureCancel() {

}
this.expandingSelectionBox = false;
}
onKeyPress(event) {
if (this.selectionBox && event.ctrlKey && event.key === 'd') {
// Handle duplication on key up — we don't want to accidentally duplicate
// many times.
return true;
}
else if (event.key === 'a' && event.ctrlKey) {
// Handle ctrl+A on key up.
// Return early to prevent 'a' from moving the selection/view.
return true;
}
else if (event.ctrlKey) {
// Don't transform the selection with, for example, ctrl+i.
// Pass it to another tool, if apliccable.
return false;
}
else if (event.key === 'Shift') {
this.shiftKeyPressed = true;
return true;
}
let rotationSteps = 0;

@@ -203,2 +244,16 @@ let xTranslateSteps = 0;

onKeyUp(evt) {
if (evt.key === 'Shift') {
this.shiftKeyPressed = false;
return true;
}
else if (evt.ctrlKey) {
if (this.selectionBox && evt.key === 'd') {
this.editor.dispatch(this.selectionBox.duplicateSelectedObjects());
return true;
}
else if (evt.key === 'a') {
this.setSelection(this.editor.image.getAllElements());
return true;
}
}
if (this.selectionBox && SelectionTool.handleableKeys.some(key => key === evt.key)) {

@@ -254,6 +309,14 @@ this.selectionBox.finalizeTransform();

// Get the object responsible for displaying this' selection.
// @internal
getSelection() {
return this.selectionBox;
}
getSelectedObjects() {
var _a, _b;
return (_b = (_a = this.selectionBox) === null || _a === void 0 ? void 0 : _a.getSelectedObjects()) !== null && _b !== void 0 ? _b : [];
}
// Select the given `objects`. Any non-selectable objects in `objects` are ignored.
setSelection(objects) {
// Only select selectable objects.
objects = objects.filter(obj => obj.isSelectable());
let bbox = null;

@@ -260,0 +323,0 @@ for (const object of objects) {

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

new TextTool(editor, localization.textTool, localization),
new PanZoom(editor, PanZoomMode.SinglePointerGestures, localization.anyDevicePanning)
];

@@ -34,0 +35,0 @@ this.tools = [

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

@@ -5,0 +5,0 @@ "main": "./dist/src/lib.d.ts",

@@ -92,2 +92,7 @@ import SerializableCommand from '../commands/SerializableCommand';

// @returns true iff this component can be selected (e.g. by the selection tool.)
public isSelectable(): boolean {
return true;
}
private static transformElementCommandId = 'transform-element';

@@ -94,0 +99,0 @@

@@ -418,4 +418,4 @@ import { Bezier } from 'bezier-js';

if (!enteringVec) {
let sampleIdx = Math.ceil(this.buffer.length / 3);
if (sampleIdx === 0) {
let sampleIdx = Math.ceil(this.buffer.length / 2);
if (sampleIdx === 0 || sampleIdx >= this.buffer.length) {
sampleIdx = this.buffer.length - 1;

@@ -422,0 +422,0 @@ }

@@ -46,2 +46,6 @@ //

public isSelectable() {
return false;
}
protected createClone() {

@@ -48,0 +52,0 @@ return new SVGGlobalAttributesObject(this.attrs);

import Color4 from '../Color4';
import Mat33 from '../math/Mat33';
import Rect2 from '../math/Rect2';
import AbstractComponent from './AbstractComponent';
import Text, { TextStyle } from './Text';
const estimateTextBounds = (text: string, style: TextStyle): Rect2 => {
const widthEst = text.length * style.size;
const heightEst = style.size;
// Text is drawn with (0, 0) as its baseline. As such, the majority of the text's height should
// be above (0, 0).
return new Rect2(0, -heightEst * 2/3, widthEst, heightEst);
};
// Don't use the default Canvas-based text bounding code. The canvas-based code may not work
// with jsdom.
AbstractComponent.registerComponent('text', (data: string) => Text.deserializeFromString(data, estimateTextBounds));
describe('Text', () => {

@@ -27,5 +14,3 @@ it('should be serializable', () => {

};
const text = new Text(
[ 'Foo' ], Mat33.identity, style, estimateTextBounds
);
const text = new Text([ 'Foo' ], Mat33.identity, style);
const serialized = text.serialize();

@@ -32,0 +17,0 @@ const deserialized = AbstractComponent.deserialize(serialized) as Text;

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

type GetTextDimensCallback = (text: string, style: TextStyle) => Rect2;
const componentTypeId = 'text';

@@ -28,6 +26,2 @@ export default class Text extends AbstractComponent {

private readonly style: TextStyle,
// If not given, an HtmlCanvasElement is used to determine text boundaries.
// @internal
private readonly getTextDimens: GetTextDimensCallback = Text.getTextDimens,
) {

@@ -52,5 +46,21 @@ super(componentTypeId);

private static textMeasuringCtx: CanvasRenderingContext2D;
private static textMeasuringCtx: CanvasRenderingContext2D|null = null;
// Roughly estimate the bounding box of `text`. Use if no CanvasRenderingContext2D is available.
private static estimateTextDimens(text: string, style: TextStyle): Rect2 {
const widthEst = text.length * style.size;
const heightEst = style.size;
// Text is drawn with (0, 0) as its baseline. As such, the majority of the text's height should
// be above (0, 0).
return new Rect2(0, -heightEst * 2/3, widthEst, heightEst);
}
// Returns the bounding box of `text`. This is approximate if no Canvas is available.
private static getTextDimens(text: string, style: TextStyle): Rect2 {
Text.textMeasuringCtx ??= document.createElement('canvas').getContext('2d')!;
Text.textMeasuringCtx ??= document.createElement('canvas').getContext('2d') ?? null;
if (!Text.textMeasuringCtx) {
return this.estimateTextDimens(text, style);
}
const ctx = Text.textMeasuringCtx;

@@ -69,3 +79,3 @@ Text.applyTextStyles(ctx, style);

if (typeof part === 'string') {
const textBBox = this.getTextDimens(part, this.style);
const textBBox = Text.getTextDimens(part, this.style);
return textBBox.transformedBoundingBox(this.transform);

@@ -185,3 +195,3 @@ } else {

public static deserializeFromString(json: any, getTextDimens: GetTextDimensCallback = Text.getTextDimens): Text {
public static deserializeFromString(json: any): Text {
const style: TextStyle = {

@@ -211,3 +221,3 @@ renderingStyle: styleFromJSON(json.style.renderingStyle),

return new Text(textObjects, transform, style, getTextDimens);
return new Text(textObjects, transform, style);
}

@@ -214,0 +224,0 @@ }

@@ -40,2 +40,6 @@ //

public isSelectable() {
return false;
}
protected createClone(): AbstractComponent {

@@ -42,0 +46,0 @@ return new UnknownSVGObject(this.svgObject.cloneNode(true) as SVGElement);

@@ -40,2 +40,3 @@ /**

import getLocalizationTable from './localizations/getLocalizationTable';
import IconProvider from './toolbar/IconProvider';

@@ -62,2 +63,4 @@ type HTMLPointerEventType = 'pointerdown'|'pointermove'|'pointerup'|'pointercancel';

maxZoom: number,
iconProvider: IconProvider,
}

@@ -106,3 +109,3 @@

*/
public image: EditorImage;
public readonly image: EditorImage;

@@ -113,6 +116,7 @@ /** Viewport for the exported/imported image. */

/** @internal */
public localization: EditorLocalization;
public readonly localization: EditorLocalization;
public viewport: Viewport;
public toolController: ToolController;
public readonly icons: IconProvider;
public readonly viewport: Viewport;
public readonly toolController: ToolController;

@@ -123,3 +127,3 @@ /**

*/
public notifier: EditorNotifier;
public readonly notifier: EditorNotifier;

@@ -172,3 +176,5 @@ private loadingWarning: HTMLElement;

maxZoom: settings.maxZoom ?? 1e12,
iconProvider: settings.iconProvider ?? new IconProvider(),
};
this.icons = this.settings.iconProvider;

@@ -175,0 +181,0 @@ this.container = document.createElement('div');

@@ -57,2 +57,11 @@ import Editor from './Editor';

/** @returns all elements in the image, sorted by z-index. This can be slow for large images. */
public getAllElements() {
const leaves = this.root.getLeaves();
sortLeavesByZIndex(leaves);
return leaves.map(leaf => leaf.getContent()!);
}
/** @returns a list of `AbstractComponent`s intersecting `region`, sorted by z-index. */
public getElementsIntersectingRegion(region: Rect2): AbstractComponent[] {

@@ -59,0 +68,0 @@ const leaves = this.root.getLeavesIntersectingRegion(region);

@@ -127,3 +127,3 @@ import Color4 from './Color4';

if (supportedStyleAttrs) {
if (supportedStyleAttrs && node.style) {
for (const attr of node.style) {

@@ -202,5 +202,5 @@ if (attr === '' || !attr) {

const elemY = elem.getAttribute('y');
if (elemX && elemY) {
const x = parseFloat(elemX);
const y = parseFloat(elemY);
if (elemX || elemY) {
const x = parseFloat(elemX ?? '0');
const y = parseFloat(elemY ?? '0');
if (!isNaN(x) && !isNaN(y)) {

@@ -250,3 +250,3 @@ supportedAttrs?.push('x', 'y');

renderingStyle: {
fill: Color4.fromString(computedStyles.fill)
fill: Color4.fromString(computedStyles.fill || elem.style.fill || '#000')
},

@@ -412,3 +412,2 @@ };

// TODO: Handling unsafe data! Tripple-check that this is secure!
// @param sanitize - if `true`, don't store unknown attributes.

@@ -415,0 +414,0 @@ public static fromString(text: string, sanitize: boolean = false): SVGLoader {

import loadExpectExtensions from './loadExpectExtensions';
loadExpectExtensions();
jest.useFakeTimers();
jest.useFakeTimers();
// jsdom doesn't support HTMLCanvasElement#getContext — it logs an error
// to the console. Make it return null so we can handle a non-existent Canvas
// at runtime (e.g. use something else, if available).
HTMLCanvasElement.prototype.getContext = () => null;

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

import { ActionButtonIcon } from './types';
import { makeRedoIcon, makeUndoIcon } from './icons';
import SelectionTool from '../tools/SelectionTool/SelectionTool';

@@ -160,3 +159,3 @@ import PanZoomTool from '../tools/PanZoom';

label: this.localizationTable.undo,
icon: makeUndoIcon()
icon: this.editor.icons.makeUndoIcon()
}, () => {

@@ -167,3 +166,3 @@ this.editor.history.undo();

label: this.localizationTable.redo,
icon: makeRedoIcon(),
icon: this.editor.icons.makeRedoIcon(),
}, () => {

@@ -170,0 +169,0 @@ this.editor.history.redo();

export * from './widgets/lib';
export * as icons from './icons';
export * from './makeColorInput';
export { default as IconProvider } from './IconProvider';

@@ -5,3 +5,2 @@

fontLabel: string;
anyDevicePanning: string;
touchPanning: string;

@@ -58,3 +57,2 @@ outlinedRectanglePen: string;

touchPanning: 'Touchscreen panning',
anyDevicePanning: 'Any device panning',

@@ -61,0 +59,0 @@ freehandPen: 'Freehand',

@@ -5,3 +5,2 @@ import Color4 from '../Color4';

import { EditorEventType } from '../types';
import { makePipetteIcon } from './icons';

@@ -76,3 +75,3 @@ type OnColorChangeListener = (color: Color4)=>void;

const updatePipetteIcon = (color?: Color4) => {
pipetteButton.replaceChildren(makePipetteIcon(color));
pipetteButton.replaceChildren(editor.icons.makePipetteIcon(color));
};

@@ -79,0 +78,0 @@ updatePipetteIcon();

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

import { toolbarCSSPrefix } from '../HTMLToolbar';
import { makeDropdownIcon } from '../icons';
import { ToolbarLocalization } from '../localization';

@@ -252,3 +251,3 @@

private createDropdownIcon(): Element {
const icon = makeDropdownIcon();
const icon = this.editor.icons.makeDropdownIcon();
icon.classList.add(`${toolbarCSSPrefix}showHideDropdownIcon`);

@@ -255,0 +254,0 @@ return icon;

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

import { makeEraserIcon } from '../icons';
import BaseToolWidget from './BaseToolWidget';

@@ -9,3 +8,3 @@

protected createIcon(): Element {
return makeEraserIcon();
return this.editor.icons.makeEraserIcon();
}

@@ -12,0 +11,0 @@

import Editor from '../../Editor';
import Mat33 from '../../math/Mat33';
import PanZoom, { PanZoomMode } from '../../tools/PanZoom';
import ToolController from '../../tools/ToolController';
import { EditorEventType } from '../../types';
import Viewport from '../../Viewport';
import { toolbarCSSPrefix } from '../HTMLToolbar';
import { makeAllDevicePanningIcon, makeHandToolIcon, makeTouchPanningIcon, makeZoomIcon } from '../icons';
import { ToolbarLocalization } from '../localization';

@@ -89,3 +89,3 @@ import BaseToolWidget from './BaseToolWidget';

protected createIcon(): Element {
return makeZoomIcon();
return this.editor.icons.makeZoomIcon();
}

@@ -153,13 +153,32 @@

private touchPanningWidget: HandModeWidget;
private allowTogglingBaseTool: boolean;
public constructor(
editor: Editor, protected tool: PanZoom, localizationTable: ToolbarLocalization
editor: Editor,
// Pan zoom tool that overrides all other tools (enabling this tool for a device
// causes that device to pan/zoom instead of interact with the primary tools)
protected overridePanZoomTool: PanZoom,
localizationTable: ToolbarLocalization,
) {
const primaryHandTool = HandToolWidget.getPrimaryHandTool(editor.toolController);
const tool = primaryHandTool ?? overridePanZoomTool;
super(editor, tool, localizationTable);
this.container.classList.add('dropdownShowable');
// Only allow toggling a hand tool if we're using the primary hand tool and not the override
// hand tool for this button.
this.allowTogglingBaseTool = primaryHandTool !== null;
// Allow showing/hiding the dropdown, even if `overridePanZoomTool` isn't enabled.
if (!this.allowTogglingBaseTool) {
this.container.classList.add('dropdownShowable');
}
// Controls for the overriding hand tool.
this.touchPanningWidget = new HandModeWidget(
editor, localizationTable,
tool, PanZoomMode.OneFingerTouchGestures,
makeTouchPanningIcon,
overridePanZoomTool, PanZoomMode.OneFingerTouchGestures,
() => this.editor.icons.makeTouchPanningIcon(),

@@ -171,12 +190,2 @@ localizationTable.touchPanning

this.addSubWidget(
new HandModeWidget(
editor, localizationTable,
tool, PanZoomMode.SinglePointerGestures,
makeAllDevicePanningIcon,
localizationTable.anyDevicePanning
)
);
this.addSubWidget(
new ZoomWidget(editor, localizationTable)

@@ -186,2 +195,8 @@ );

private static getPrimaryHandTool(toolController: ToolController): PanZoom|null {
const primaryPanZoomToolList = toolController.getPrimaryTools().filter(tool => tool instanceof PanZoom);
const primaryPanZoomTool = primaryPanZoomToolList[0];
return primaryPanZoomTool as PanZoom|null;
}
protected getTitle(): string {

@@ -192,11 +207,18 @@ return this.localizationTable.handTool;

protected createIcon(): Element {
return makeHandToolIcon();
return this.editor.icons.makeHandToolIcon();
}
public setSelected(_selected: boolean): void {
protected handleClick(): void {
if (this.allowTogglingBaseTool) {
super.handleClick();
} else {
this.setDropdownVisible(!this.isDropdownVisible());
}
}
protected handleClick() {
this.setDropdownVisible(!this.isDropdownVisible());
public setSelected(selected: boolean): void {
if (this.allowTogglingBaseTool) {
super.setSelected(selected);
}
}
}

@@ -10,3 +10,2 @@ import { makeArrowBuilder } from '../../components/builders/ArrowBuilder';

import { toolbarCSSPrefix } from '../HTMLToolbar';
import { makeIconFromFactory, makePenIcon } from '../icons';
import { ToolbarLocalization } from '../localization';

@@ -81,6 +80,6 @@ import makeColorInput from '../makeColorInput';

const color = this.tool.getColor();
return makePenIcon(scale, color.toHexString());
return this.editor.icons.makePenIcon(scale, color.toHexString());
} else {
const strokeFactory = this.tool.getStrokeFactory();
return makeIconFromFactory(this.tool, strokeFactory);
return this.editor.icons.makeIconFromFactory(this.tool, strokeFactory);
}

@@ -87,0 +86,0 @@ }

import Editor from '../../Editor';
import SelectionTool from '../../tools/SelectionTool/SelectionTool';
import { EditorEventType } from '../../types';
import { makeDeleteSelectionIcon, makeDuplicateSelectionIcon, makeResizeViewportIcon, makeSelectionIcon } from '../icons';
import { EditorEventType, KeyPressEvent } from '../../types';
import { ToolbarLocalization } from '../localization';

@@ -17,7 +16,6 @@ import ActionButtonWidget from './ActionButtonWidget';

editor, localization,
makeResizeViewportIcon,
() => editor.icons.makeResizeViewportIcon(),
this.localizationTable.resizeImageToSelection,
() => {
const selection = this.tool.getSelection();
this.editor.dispatch(this.editor.setImportExportRect(selection!.region));
this.resizeImageToSelection();
},

@@ -27,3 +25,3 @@ );

editor, localization,
makeDeleteSelectionIcon,
() => editor.icons.makeDeleteSelectionIcon(),
this.localizationTable.deleteSelection,

@@ -38,3 +36,3 @@ () => {

editor, localization,
makeDuplicateSelectionIcon,
() => editor.icons.makeDuplicateSelectionIcon(),
this.localizationTable.duplicateSelection,

@@ -73,2 +71,20 @@ () => {

private resizeImageToSelection() {
const selection = this.tool.getSelection();
if (selection) {
this.editor.dispatch(this.editor.setImportExportRect(selection.region));
}
}
protected onKeyPress(event: KeyPressEvent): boolean {
// Resize image to selection:
// Other keys are handled directly by the selection tool.
if (event.ctrlKey && event.key === 'r') {
this.resizeImageToSelection();
return true;
}
return false;
}
protected getTitle(): string {

@@ -79,4 +95,4 @@ return this.localizationTable.select;

protected createIcon(): Element {
return makeSelectionIcon();
return this.editor.icons.makeSelectionIcon();
}
}

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

import { toolbarCSSPrefix } from '../HTMLToolbar';
import { makeTextIcon } from '../icons';
import { ToolbarLocalization } from '../localization';

@@ -30,3 +29,3 @@ import makeColorInput from '../makeColorInput';

const textStyle = this.tool.getTextStyle();
return makeTextIcon(textStyle);
return this.editor.icons.makeTextIcon(textStyle);
}

@@ -33,0 +32,0 @@

@@ -18,2 +18,4 @@

anyDevicePanning: string;
copied: (count: number, description: string) => string;

@@ -42,2 +44,4 @@ pasted: (count: number, description: string) => string;

anyDevicePanning: 'Any device panning',
copied: (count: number, description: string) => `Copied ${count} ${description}`,

@@ -44,0 +48,0 @@ pasted: (count: number, description: string) => `Pasted ${count} ${description}`,

@@ -185,6 +185,9 @@

public onKeyPress({ key }: KeyPressEvent): boolean {
public onKeyPress({ key, ctrlKey, altKey }: KeyPressEvent): boolean {
if (!(this.mode & PanZoomMode.Keyboard)) {
return false;
}
if (ctrlKey || altKey) {
return false;
}

@@ -191,0 +194,0 @@ // No need to keep the same the transform for keyboard events.

@@ -103,2 +103,42 @@ import Color4 from '../../Color4';

});
it('shift+click should expand an existing selection', () => {
const { addTestStrokeCommand: stroke1Command } = createSquareStroke(50);
const { addTestStrokeCommand: stroke2Command } = createSquareStroke(500);
const editor = createEditor();
editor.dispatch(stroke1Command);
editor.dispatch(stroke2Command);
// Select the first stroke
const selectionTool = getSelectionTool(editor);
selectionTool.setEnabled(true);
// Select the smaller rectangle
editor.sendPenEvent(InputEvtType.PointerDownEvt, Vec2.of(40, 40));
editor.sendPenEvent(InputEvtType.PointerUpEvt, Vec2.of(100, 100));
expect(selectionTool.getSelectedObjects()).toHaveLength(1);
// Shift key down.
editor.sendKeyboardEvent(InputEvtType.KeyPressEvent, 'Shift');
// Select the larger stroke.
editor.sendPenEvent(InputEvtType.PointerDownEvt, Vec2.of(200, 200));
editor.sendPenEvent(InputEvtType.PointerUpEvt, Vec2.of(600, 600));
expect(selectionTool.getSelectedObjects()).toHaveLength(2);
editor.sendKeyboardEvent(InputEvtType.KeyUpEvent, 'Shift');
// Select the larger stroke without shift pressed
editor.sendPenEvent(InputEvtType.PointerDownEvt, Vec2.of(200, 200));
editor.sendPenEvent(InputEvtType.PointerUpEvt, Vec2.of(600, 600));
expect(selectionTool.getSelectedObjects()).toHaveLength(1);
// Select nothing
editor.sendPenEvent(InputEvtType.PointerDownEvt, Vec2.of(200, 200));
editor.sendPenEvent(InputEvtType.PointerUpEvt, Vec2.of(201, 201));
expect(selectionTool.getSelectedObjects()).toHaveLength(0);
});
});

@@ -26,2 +26,5 @@ // Allows users to select/transform portions of the `EditorImage`.

private expandingSelectionBox: boolean = false;
private shiftKeyPressed: boolean = false;
public constructor(private editor: Editor, description: string) {

@@ -54,4 +57,7 @@ super(editor.notifier, description);

);
// Remove any previous selection rects
this.handleOverlay.replaceChildren();
if (!this.expandingSelectionBox) {
// Remove any previous selection rects
this.prevSelectionBox?.cancelSelection();
}
this.selectionBox.addTo(this.handleOverlay);

@@ -65,3 +71,7 @@ }

this.selectionBoxHandlingEvt = true;
} else {
this.expandingSelectionBox = false;
}
else {
// Shift key: Combine the new and old selection boxes at the end of the gesture.
this.expandingSelectionBox = this.shiftKeyPressed;
this.makeSelectionBox(event.current.canvasPos);

@@ -105,2 +115,3 @@ }

// Called after a gestureCancel and a pointerUp
private onGestureEnd() {

@@ -134,3 +145,15 @@ this.lastEvtTarget = null;

this.selectionBox.setToPoint(event.current.canvasPos);
this.onGestureEnd();
// Were we expanding the previous selection?
if (this.expandingSelectionBox && this.prevSelectionBox) {
// If so, finish expanding.
this.expandingSelectionBox = false;
this.selectionBox.resolveToObjects();
this.setSelection([
...this.selectionBox.getSelectedObjects(),
...this.prevSelectionBox.getSelectedObjects(),
]);
} else {
this.onGestureEnd();
}
}

@@ -147,2 +170,4 @@

}
this.expandingSelectionBox = false;
}

@@ -159,2 +184,22 @@

public onKeyPress(event: KeyPressEvent): boolean {
if (this.selectionBox && event.ctrlKey && event.key === 'd') {
// Handle duplication on key up — we don't want to accidentally duplicate
// many times.
return true;
}
else if (event.key === 'a' && event.ctrlKey) {
// Handle ctrl+A on key up.
// Return early to prevent 'a' from moving the selection/view.
return true;
}
else if (event.ctrlKey) {
// Don't transform the selection with, for example, ctrl+i.
// Pass it to another tool, if apliccable.
return false;
}
else if (event.key === 'Shift') {
this.shiftKeyPressed = true;
return true;
}
let rotationSteps = 0;

@@ -255,2 +300,17 @@ let xTranslateSteps = 0;

public onKeyUp(evt: KeyUpEvent) {
if (evt.key === 'Shift') {
this.shiftKeyPressed = false;
return true;
}
else if (evt.ctrlKey) {
if (this.selectionBox && evt.key === 'd') {
this.editor.dispatch(this.selectionBox.duplicateSelectedObjects());
return true;
}
else if (evt.key === 'a') {
this.setSelection(this.editor.image.getAllElements());
return true;
}
}
if (this.selectionBox && SelectionTool.handleableKeys.some(key => key === evt.key)) {

@@ -318,2 +378,3 @@ this.selectionBox.finalizeTransform();

// Get the object responsible for displaying this' selection.
// @internal
public getSelection(): Selection|null {

@@ -323,3 +384,11 @@ return this.selectionBox;

public getSelectedObjects(): AbstractComponent[] {
return this.selectionBox?.getSelectedObjects() ?? [];
}
// Select the given `objects`. Any non-selectable objects in `objects` are ignored.
public setSelection(objects: AbstractComponent[]) {
// Only select selectable objects.
objects = objects.filter(obj => obj.isSelectable());
let bbox: Rect2|null = null;

@@ -326,0 +395,0 @@ for (const object of objects) {

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

new TextTool(editor, localization.textTool, localization),
new PanZoom(editor, PanZoomMode.SinglePointerGestures, localization.anyDevicePanning)
];

@@ -44,0 +45,0 @@ this.tools = [

@@ -6,3 +6,7 @@ {

"exclude": [
"**/*.test.ts"
"**/*.test.ts",
"node_modules/**",
"dist/",
"dist-test/",
"src/testing/"
],

@@ -9,0 +13,0 @@ "excludePrivate": true,

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