tldraw
Version:
A tiny little drawing editor.
299 lines (298 loc) • 10.8 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var DraggingHandle_exports = {};
__export(DraggingHandle_exports, {
DraggingHandle: () => DraggingHandle
});
module.exports = __toCommonJS(DraggingHandle_exports);
var import_editor = require("@tldraw/editor");
var import_arrowTargetState = require("../../../shapes/arrow/arrowTargetState");
var import_shared = require("../../../shapes/arrow/shared");
class DraggingHandle extends import_editor.StateNode {
static id = "dragging_handle";
shapeId;
initialHandle;
initialAdjacentHandle;
initialPagePoint;
markId;
initialPageTransform;
initialPageRotation;
info;
isPrecise = false;
isPreciseId = null;
pointingId = null;
onEnter(info) {
const { shape, isCreating, creatingMarkId, handle } = info;
this.info = info;
if (typeof info.onInteractionEnd === "string") {
this.parent.setCurrentToolIdMask(info.onInteractionEnd);
}
this.shapeId = shape.id;
this.markId = "";
if (isCreating) {
if (creatingMarkId) {
this.markId = creatingMarkId;
} else {
const markId = this.editor.getMarkIdMatching(
`creating:${this.editor.getOnlySelectedShapeId()}`
);
if (markId) {
this.markId = markId;
}
}
} else {
this.markId = this.editor.markHistoryStoppingPoint("dragging handle");
}
this.initialHandle = (0, import_editor.structuredClone)(handle);
this.initialPageTransform = this.editor.getShapePageTransform(shape);
this.initialPageRotation = this.initialPageTransform.rotation();
this.initialPagePoint = this.editor.inputs.getOriginPagePoint().clone();
this.editor.setCursor({ type: isCreating ? "cross" : "grabbing", rotation: 0 });
const handles = this.editor.getShapeHandles(shape).sort(import_editor.sortByIndex);
const index = handles.findIndex((h) => h.id === info.handle.id);
this.initialAdjacentHandle = null;
if (info.handle.snapReferenceHandleId) {
const customHandle = handles.find((h) => h.id === info.handle.snapReferenceHandleId);
if (customHandle) {
this.initialAdjacentHandle = customHandle;
}
}
if (!this.initialAdjacentHandle) {
for (let i = index + 1; i < handles.length; i++) {
const handle2 = handles[i];
if (handle2.type === "vertex" && handle2.id !== "middle" && handle2.id !== info.handle.id) {
this.initialAdjacentHandle = handle2;
break;
}
}
if (!this.initialAdjacentHandle) {
for (let i = handles.length - 1; i >= 0; i--) {
const handle2 = handles[i];
if (handle2.type === "vertex" && handle2.id !== "middle" && handle2.id !== info.handle.id) {
this.initialAdjacentHandle = handle2;
break;
}
}
}
}
if (this.editor.isShapeOfType(shape, "arrow")) {
const initialBinding = (0, import_shared.getArrowBindings)(this.editor, shape)[info.handle.id];
this.isPrecise = false;
if (initialBinding) {
this.isPrecise = initialBinding.props.isPrecise;
if (this.isPrecise) {
this.isPreciseId = initialBinding.toId;
} else {
this.resetExactTimeout();
}
}
}
const handleDragInfo = {
handle: this.initialHandle,
isPrecise: this.isPrecise,
isCreatingShape: !!this.info.isCreating,
initial: shape
};
const util = this.editor.getShapeUtil(shape);
const startChanges = util.onHandleDragStart?.(shape, handleDragInfo);
if (startChanges) {
this.editor.updateShapes([{ ...startChanges, id: shape.id, type: shape.type }]);
}
this.update();
this.editor.select(this.shapeId);
}
// Only relevant to arrows
exactTimeout = -1;
// Only relevant to arrows
resetExactTimeout() {
const arrowUtil = this.editor.getShapeUtil("arrow");
const timeoutValue = arrowUtil.options.pointingPreciseTimeout;
if (this.exactTimeout !== -1) {
this.clearExactTimeout();
}
this.exactTimeout = this.editor.timers.setTimeout(() => {
if (this.getIsActive() && !this.isPrecise) {
this.isPrecise = true;
this.isPreciseId = this.pointingId;
this.update();
}
this.exactTimeout = -1;
}, timeoutValue);
}
// Only relevant to arrows
clearExactTimeout() {
if (this.exactTimeout !== -1) {
clearTimeout(this.exactTimeout);
this.exactTimeout = -1;
}
}
onPointerMove() {
this.update();
}
onKeyDown() {
this.update();
}
onKeyUp() {
this.update();
}
onPointerUp() {
this.complete();
}
onComplete() {
this.update();
this.complete();
}
onCancel() {
this.cancel();
}
onExit() {
this.parent.setCurrentToolIdMask(void 0);
(0, import_arrowTargetState.clearArrowTargetState)(this.editor);
this.editor.snaps.clearIndicators();
this.editor.setCursor({ type: "default", rotation: 0 });
}
complete() {
this.editor.snaps.clearIndicators();
(0, import_editor.kickoutOccludedShapes)(this.editor, [this.shapeId]);
const shape = this.editor.getShape(this.shapeId);
if (shape) {
const util = this.editor.getShapeUtil(shape);
const handleDragInfo = {
handle: this.initialHandle,
isPrecise: this.isPrecise,
isCreatingShape: !!this.info.isCreating,
initial: this.info.shape
};
const endChanges = util.onHandleDragEnd?.(shape, handleDragInfo);
if (endChanges) {
this.editor.updateShapes([{ ...endChanges, id: shape.id }]);
}
}
const { onInteractionEnd } = this.info;
if (onInteractionEnd) {
if (typeof onInteractionEnd === "string") {
if (this.editor.getInstanceState().isToolLocked && onInteractionEnd) {
this.editor.setCurrentTool(onInteractionEnd, { shapeId: this.shapeId });
return;
}
} else {
onInteractionEnd?.();
return;
}
}
this.parent.transition("idle");
}
cancel() {
const shape = this.editor.getShape(this.shapeId);
if (shape) {
const util = this.editor.getShapeUtil(shape);
const handleDragInfo = {
handle: this.initialHandle,
isPrecise: this.isPrecise,
isCreatingShape: !!this.info.isCreating,
initial: this.info.shape
};
util.onHandleDragCancel?.(shape, handleDragInfo);
}
this.editor.bailToMark(this.markId);
this.editor.snaps.clearIndicators();
const { onInteractionEnd } = this.info;
if (onInteractionEnd) {
if (typeof onInteractionEnd === "string") {
this.editor.setCurrentTool(onInteractionEnd, { shapeId: this.shapeId });
} else {
onInteractionEnd?.();
}
return;
}
this.parent.transition("idle");
}
update() {
const { editor, shapeId, initialPagePoint } = this;
const { initialHandle, initialPageRotation, initialAdjacentHandle } = this;
const isSnapMode = this.editor.user.getIsSnapMode();
const { snaps } = editor;
const currentPagePoint = editor.inputs.getCurrentPagePoint();
const shiftKey = editor.inputs.getShiftKey();
const ctrlKey = editor.inputs.getCtrlKey();
const altKey = editor.inputs.getAltKey();
const pointerVelocity = editor.inputs.getPointerVelocity();
const initial = this.info.shape;
const shape = editor.getShape(shapeId);
if (!shape) return;
const util = editor.getShapeUtil(shape);
const initialBinding = editor.isShapeOfType(shape, "arrow") ? (0, import_shared.getArrowBindings)(editor, shape)[initialHandle.id] : void 0;
let point = currentPagePoint.clone().sub(initialPagePoint).rot(-initialPageRotation).add(initialHandle);
if (shiftKey && initialAdjacentHandle && initialHandle.id !== "middle") {
const angle = import_editor.Vec.Angle(initialAdjacentHandle, point);
const snappedAngle = (0, import_editor.snapAngle)(angle, 24);
const angleDifference = snappedAngle - angle;
point = import_editor.Vec.RotWith(point, initialAdjacentHandle, angleDifference);
}
editor.snaps.clearIndicators();
let nextHandle = { ...initialHandle, x: point.x, y: point.y };
let canSnap = false;
if (initialHandle.canSnap && initialHandle.snapType) {
(0, import_editor.warnOnce)(
"canSnap is deprecated. Cannot use both canSnap and snapType together - snapping disabled. Please use only snapType."
);
} else {
canSnap = initialHandle.canSnap || initialHandle.snapType !== void 0;
}
if (canSnap && (isSnapMode ? !ctrlKey : ctrlKey)) {
const pageTransform = editor.getShapePageTransform(shape.id);
if (!pageTransform) throw Error("Expected a page transform");
const snap = snaps.handles.snapHandle({ currentShapeId: shapeId, handle: nextHandle });
if (snap) {
snap.nudge.rot(-editor.getShapeParentTransform(shape).rotation());
point.add(snap.nudge);
nextHandle = { ...initialHandle, x: point.x, y: point.y };
}
}
const changes = util.onHandleDrag?.(shape, {
handle: nextHandle,
isPrecise: this.isPrecise || altKey,
isCreatingShape: !!this.info.isCreating,
initial
});
const next = { id: shape.id, type: shape.type, ...changes };
if (initialHandle.type === "vertex" && this.editor.isShapeOfType(shape, "arrow")) {
const bindingAfter = (0, import_shared.getArrowBindings)(editor, shape)[initialHandle.id];
if (bindingAfter) {
if (initialBinding?.toId !== bindingAfter.toId) {
this.pointingId = bindingAfter.toId;
this.isPrecise = pointerVelocity.len() < 0.5 || altKey;
this.isPreciseId = this.isPrecise ? bindingAfter.toId : null;
this.resetExactTimeout();
}
} else {
if (initialBinding) {
this.pointingId = null;
this.isPrecise = false;
this.isPreciseId = null;
this.resetExactTimeout();
}
}
}
if (changes) {
editor.updateShapes([next]);
}
}
}
//# sourceMappingURL=DraggingHandle.js.map