js-draw
Version:
Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript.
95 lines (94 loc) • 3.93 kB
JavaScript
import { Rect2, Vec2 } from '@js-draw/math';
import { cssPrefix } from './SelectionTool.mjs';
const verticalOffset = 40;
export default class SelectionMenuShortcut {
constructor(parent, viewport, icon, showContextMenu, localization) {
this.parent = parent;
this.viewport = viewport;
this.icon = icon;
this.localization = localization;
this.lastDragPointer = null;
this.element = document.createElement('div');
this.element.classList.add(`${cssPrefix}handle`, `${cssPrefix}selection-menu`);
this.element.style.setProperty('--vertical-offset', `${verticalOffset}px`);
this.onClick = () => {
this.button?.focus({ preventScroll: true });
const anchor = this.getBBoxCanvasCoords().center;
showContextMenu(anchor);
};
this.initUI();
this.updatePosition();
}
initUI() {
const button = document.createElement('button');
this.icon.classList.add('icon');
button.replaceChildren(this.icon);
button.ariaLabel = this.localization.selectionMenu__show;
button.title = button.ariaLabel;
this.button = button;
// To prevent editor event handlers from conflicting with those for the button,
// don't register a [click] handler. An onclick handler can be fired incorrectly
// in this case (in Chrome) after onClick is fired in onDragEnd, leading to a double
// on-click action.
button.onkeydown = (event) => {
if (event.key === 'Enter') {
// .preventDefault prevents [Enter] from activating the first item in the
// selection menu.
event.preventDefault();
this.onClick();
}
};
this.element.appendChild(button);
// Update the bounding box of this in response to the new button.
requestAnimationFrame(() => {
this.updatePosition();
});
}
addTo(container) {
container.appendChild(this.element);
}
remove() {
this.element.remove();
}
getElementScreenSize() {
return Vec2.of(this.element.clientWidth, this.element.clientHeight);
}
/** Gets this menu's bounding box relative to the top left of its parent. */
getBBoxParentCoords() {
const topLeft = Vec2.of(0, -verticalOffset);
const screenSize = this.getElementScreenSize();
return new Rect2(topLeft.x, topLeft.y, screenSize.x, screenSize.y);
}
getBBoxCanvasCoords() {
const parentCanvasRect = this.parent.region;
const toCanvasScale = this.viewport.getSizeOfPixelOnCanvas();
// Don't apply rotation -- rotation is handled by the selection container
const contentCanvasSize = this.getElementScreenSize().times(toCanvasScale);
const handleSizeCanvas = verticalOffset / this.viewport.getScaleFactor();
const topLeft = Vec2.of(parentCanvasRect.x, parentCanvasRect.y - handleSizeCanvas);
const minSize = Vec2.of(48, 48).times(toCanvasScale);
return new Rect2(topLeft.x, topLeft.y, contentCanvasSize.x, contentCanvasSize.y).grownToSize(minSize);
}
updatePosition() {
const bbox = this.getBBoxParentCoords();
// Position within the selection box.
this.element.style.marginLeft = `${bbox.topLeft.x}px`;
this.element.style.marginTop = `${bbox.topLeft.y}px`;
}
containsPoint(canvasPoint) {
return this.getBBoxCanvasCoords().containsPoint(canvasPoint);
}
handleDragStart(pointer) {
this.lastDragPointer = pointer;
return true;
}
handleDragUpdate(pointer) {
this.lastDragPointer = pointer;
}
handleDragEnd() {
if (this.lastDragPointer && this.containsPoint(this.lastDragPointer.canvasPos)) {
this.onClick();
}
this.lastDragPointer = null;
}
}