tldraw
Version:
A tiny little drawing editor.
535 lines (534 loc) • 19.2 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 Idle_exports = {};
__export(Idle_exports, {
GRID_INCREMENT: () => GRID_INCREMENT,
Idle: () => Idle,
MAJOR_NUDGE_FACTOR: () => MAJOR_NUDGE_FACTOR,
MINOR_NUDGE_FACTOR: () => MINOR_NUDGE_FACTOR
});
module.exports = __toCommonJS(Idle_exports);
var import_editor = require("@tldraw/editor");
var import_arrowLabel = require("../../../shapes/arrow/arrowLabel");
var import_getHitShapeOnCanvasPointerDown = require("../../selection-logic/getHitShapeOnCanvasPointerDown");
var import_selectOnCanvasPointerUp = require("../../selection-logic/selectOnCanvasPointerUp");
var import_updateHoveredShapeId = require("../../selection-logic/updateHoveredShapeId");
var import_selectHelpers = require("../selectHelpers");
const SKIPPED_KEYS_FOR_AUTO_EDITING = [
"Delete",
"Backspace",
"[",
"]",
"Enter",
" ",
"Shift",
"Tab"
];
class Idle extends import_editor.StateNode {
static id = "idle";
selectedShapesOnKeyDown = [];
onEnter() {
this.parent.setCurrentToolIdMask(void 0);
(0, import_updateHoveredShapeId.updateHoveredShapeId)(this.editor);
this.selectedShapesOnKeyDown = [];
this.editor.setCursor({ type: "default", rotation: 0 });
}
onExit() {
import_updateHoveredShapeId.updateHoveredShapeId.cancel();
}
onPointerMove() {
(0, import_updateHoveredShapeId.updateHoveredShapeId)(this.editor);
}
onPointerDown(info) {
switch (info.target) {
case "canvas": {
const hitShape = (0, import_getHitShapeOnCanvasPointerDown.getHitShapeOnCanvasPointerDown)(this.editor);
if (hitShape && !hitShape.isLocked) {
this.onPointerDown({
...info,
shape: hitShape,
target: "shape"
});
return;
}
const selectedShapeIds = this.editor.getSelectedShapeIds();
const onlySelectedShape = this.editor.getOnlySelectedShape();
const currentPagePoint = this.editor.inputs.getCurrentPagePoint();
if (selectedShapeIds.length > 1 || onlySelectedShape && !this.editor.getShapeUtil(onlySelectedShape).hideSelectionBoundsBg(onlySelectedShape)) {
if (isPointInRotatedSelectionBounds(this.editor, currentPagePoint)) {
this.onPointerDown({
...info,
target: "selection"
});
return;
}
}
this.parent.transition("pointing_canvas", info);
break;
}
case "shape": {
const { shape } = info;
if (this.editor.isShapeOrAncestorLocked(shape)) {
this.parent.transition("pointing_canvas", info);
break;
}
this.parent.transition("pointing_shape", info);
break;
}
case "handle": {
if (this.editor.getIsReadonly()) break;
if (this.editor.inputs.getAltKey()) {
this.parent.transition("pointing_shape", info);
} else {
this.parent.transition("pointing_handle", info);
}
break;
}
case "selection": {
switch (info.handle) {
case "mobile_rotate":
case "top_left_rotate":
case "top_right_rotate":
case "bottom_left_rotate":
case "bottom_right_rotate": {
if (info.accelKey) {
this.parent.transition("brushing", info);
break;
}
this.parent.transition("pointing_rotate_handle", info);
break;
}
case "top":
case "right":
case "bottom":
case "left":
case "top_left":
case "top_right":
case "bottom_left":
case "bottom_right": {
const onlySelectedShape = this.editor.getOnlySelectedShape();
if (info.ctrlKey && this.editor.canCropShape(onlySelectedShape)) {
this.parent.transition("crop.pointing_crop_handle", info);
} else {
if (info.accelKey) {
this.parent.transition("brushing", info);
break;
}
this.parent.transition("pointing_resize_handle", info);
}
break;
}
default: {
const hoveredShape = this.editor.getHoveredShape();
if (hoveredShape && !this.editor.getSelectedShapeIds().includes(hoveredShape.id) && !hoveredShape.isLocked) {
this.onPointerDown({
...info,
shape: hoveredShape,
target: "shape"
});
return;
}
this.parent.transition("pointing_selection", info);
}
}
break;
}
}
}
onDoubleClick(info) {
if (this.editor.inputs.getShiftKey() || info.phase !== "up") return;
if (info.ctrlKey || info.shiftKey) return;
switch (info.target) {
case "canvas": {
const hoveredShape = this.editor.getHoveredShape();
const currentPagePoint = this.editor.inputs.getCurrentPagePoint();
const hitShape = hoveredShape && !this.editor.isShapeOfType(hoveredShape, "group") ? hoveredShape : this.editor.getSelectedShapeAtPoint(currentPagePoint) ?? this.editor.getShapeAtPoint(currentPagePoint, {
margin: this.editor.options.hitTestMargin / this.editor.getZoomLevel(),
hitInside: false
});
const focusedGroupId = this.editor.getFocusedGroupId();
if (hitShape) {
if (this.editor.isShapeOfType(hitShape, "group")) {
(0, import_selectOnCanvasPointerUp.selectOnCanvasPointerUp)(this.editor, info);
return;
} else {
const parent = this.editor.getShape(hitShape.parentId);
if (parent && this.editor.isShapeOfType(parent, "group")) {
if (focusedGroupId && parent.id === focusedGroupId) {
} else {
(0, import_selectOnCanvasPointerUp.selectOnCanvasPointerUp)(this.editor, info);
return;
}
}
}
this.onDoubleClick({
...info,
shape: hitShape,
target: "shape"
});
return;
}
if (!this.editor.inputs.getShiftKey()) {
this.handleDoubleClickOnCanvas(info);
}
break;
}
case "selection": {
const onlySelectedShape = this.editor.getOnlySelectedShape();
if (onlySelectedShape) {
const util = this.editor.getShapeUtil(onlySelectedShape);
const isEdge = info.handle === "right" || info.handle === "left" || info.handle === "top" || info.handle === "bottom";
const isCorner = info.handle === "top_left" || info.handle === "top_right" || info.handle === "bottom_right" || info.handle === "bottom_left";
if (this.editor.getIsReadonly()) {
if (this.editor.canEditShape(onlySelectedShape, {
type: isCorner ? "double-click-corner" : isEdge ? "double-click-edge" : "double-click"
})) {
this.startEditingShape(
onlySelectedShape,
info,
true
/* select all */
);
}
break;
}
if (isEdge) {
const change = util.onDoubleClickEdge?.(onlySelectedShape, info);
if (change) {
this.editor.markHistoryStoppingPoint("double click edge");
this.editor.updateShapes([change]);
(0, import_editor.kickoutOccludedShapes)(this.editor, [onlySelectedShape.id]);
return;
}
}
if (isCorner) {
const change = util.onDoubleClickCorner?.(onlySelectedShape, info);
if (change) {
this.editor.markHistoryStoppingPoint("double click corner");
this.editor.updateShapes([change]);
(0, import_editor.kickoutOccludedShapes)(this.editor, [onlySelectedShape.id]);
return;
}
}
if (this.editor.canCropShape(onlySelectedShape)) {
this.parent.transition("crop", info);
return;
}
if (this.editor.canEditShape(onlySelectedShape)) {
this.startEditingShape(
onlySelectedShape,
info,
true
/* select all */
);
}
}
break;
}
case "shape": {
const { shape } = info;
const util = this.editor.getShapeUtil(shape);
if (shape.type !== "video" && shape.type !== "embed" && this.editor.getIsReadonly()) break;
if (util.onDoubleClick) {
const change = util.onDoubleClick?.(shape);
if (change) {
this.editor.updateShapes([change]);
return;
}
}
if (util.canCrop(shape) && !this.editor.isShapeOrAncestorLocked(shape)) {
this.editor.markHistoryStoppingPoint("select and crop");
this.editor.select(info.shape?.id);
this.parent.transition("crop", info);
return;
}
if (this.editor.canEditShape(shape)) {
this.startEditingShape(
shape,
info,
true
/* select all */
);
} else {
this.handleDoubleClickOnCanvas(info);
}
break;
}
case "handle": {
if (this.editor.getIsReadonly()) break;
const { shape, handle } = info;
const util = this.editor.getShapeUtil(shape);
const changes = util.onDoubleClickHandle?.(shape, handle);
if (changes) {
this.editor.updateShapes([changes]);
} else {
if (this.editor.canEditShape(shape)) {
this.startEditingShape(
shape,
info,
true
/* select all */
);
}
}
}
}
}
onRightClick(info) {
switch (info.target) {
case "canvas": {
const hoveredShape = this.editor.getHoveredShape();
const hitShape = hoveredShape && !this.editor.isShapeOfType(hoveredShape, "group") ? hoveredShape : this.editor.getShapeAtPoint(this.editor.inputs.getCurrentPagePoint(), {
margin: this.editor.options.hitTestMargin / this.editor.getZoomLevel(),
hitInside: false,
hitLabels: true,
hitLocked: true,
hitFrameInside: true,
renderingOnly: true
});
if (hitShape) {
this.onRightClick({
...info,
shape: hitShape,
target: "shape"
});
return;
}
const selectedShapeIds = this.editor.getSelectedShapeIds();
const onlySelectedShape = this.editor.getOnlySelectedShape();
const currentPagePoint = this.editor.inputs.getCurrentPagePoint();
if (selectedShapeIds.length > 1 || onlySelectedShape && !this.editor.getShapeUtil(onlySelectedShape).hideSelectionBoundsBg(onlySelectedShape)) {
if (isPointInRotatedSelectionBounds(this.editor, currentPagePoint)) {
this.onRightClick({
...info,
target: "selection"
});
return;
}
}
this.editor.selectNone();
break;
}
case "shape": {
const { selectedShapeIds } = this.editor.getCurrentPageState();
const { shape } = info;
const targetShape = this.editor.getOutermostSelectableShape(
shape,
(parent) => !selectedShapeIds.includes(parent.id)
);
if (!selectedShapeIds.includes(targetShape.id) && !this.editor.findShapeAncestor(
targetShape,
(shape2) => selectedShapeIds.includes(shape2.id)
)) {
this.editor.markHistoryStoppingPoint("selecting shape");
this.editor.setSelectedShapes([targetShape.id]);
}
break;
}
}
}
onCancel() {
if (this.editor.getFocusedGroupId() !== this.editor.getCurrentPageId() && this.editor.getSelectedShapeIds().length > 0) {
this.editor.popFocusedGroupId();
} else {
this.editor.markHistoryStoppingPoint("clearing selection");
this.editor.selectNone();
}
}
onKeyDown(info) {
this.selectedShapesOnKeyDown = this.editor.getSelectedShapes();
switch (info.code) {
case "ArrowLeft":
case "ArrowRight":
case "ArrowUp":
case "ArrowDown": {
if (info.accelKey) {
if (info.shiftKey) {
if (info.code === "ArrowDown") {
this.editor.selectFirstChildShape();
} else if (info.code === "ArrowUp") {
this.editor.selectParentShape();
}
} else {
this.editor.selectAdjacentShape(
info.code.replace("Arrow", "").toLowerCase()
);
}
return;
}
this.nudgeSelectedShapes(false);
return;
}
}
if (import_editor.debugFlags["editOnType"].get()) {
if (!SKIPPED_KEYS_FOR_AUTO_EDITING.includes(info.key) && !info.altKey && !info.ctrlKey) {
const onlySelectedShape = this.editor.getOnlySelectedShape();
if (onlySelectedShape && // If it's a note shape, then edit on type
this.editor.isShapeOfType(onlySelectedShape, "note") && // If it's not locked or anything
this.editor.canEditShape(onlySelectedShape)) {
this.startEditingShape(
onlySelectedShape,
{
...info,
target: "shape",
shape: onlySelectedShape
},
true
/* select all */
);
return;
}
}
}
}
onKeyRepeat(info) {
switch (info.code) {
case "ArrowLeft":
case "ArrowRight":
case "ArrowUp":
case "ArrowDown": {
if (info.accelKey) {
this.editor.selectAdjacentShape(
info.code.replace("Arrow", "").toLowerCase()
);
return;
}
this.nudgeSelectedShapes(true);
break;
}
case "Tab": {
const selectedShapes = this.editor.getSelectedShapes();
if (selectedShapes.length && !info.altKey) {
this.editor.selectAdjacentShape(info.shiftKey ? "prev" : "next");
}
break;
}
}
}
onKeyUp(info) {
switch (info.key) {
case "Enter": {
if (!this.selectedShapesOnKeyDown.length) return;
const selectedShapes = this.editor.getSelectedShapes();
if (selectedShapes.every((shape) => this.editor.isShapeOfType(shape, "group"))) {
this.editor.setSelectedShapes(
selectedShapes.flatMap((shape) => this.editor.getSortedChildIdsForParent(shape.id))
);
return;
}
const onlySelectedShape = this.editor.getOnlySelectedShape();
if (onlySelectedShape && this.editor.canEditShape(onlySelectedShape, { type: "press_enter" })) {
this.startEditingShape(
onlySelectedShape,
{
...info,
target: "shape",
shape: onlySelectedShape
},
true
/* select all */
);
return;
}
if (this.editor.canCropShape(onlySelectedShape)) {
this.parent.transition("crop", info);
}
break;
}
case "Tab": {
const selectedShapes = this.editor.getSelectedShapes();
if (selectedShapes.length && !info.altKey) {
this.editor.selectAdjacentShape(info.shiftKey ? "prev" : "next");
}
break;
}
}
}
startEditingShape(shape, info, shouldSelectAll) {
const { editor } = this;
this.editor.markHistoryStoppingPoint("editing shape");
if ((0, import_selectHelpers.hasRichText)(shape)) {
(0, import_selectHelpers.startEditingShapeWithRichText)(editor, shape, { selectAll: shouldSelectAll });
} else {
editor.setEditingShape(shape);
}
this.parent.transition("editing_shape", info);
}
isOverArrowLabelTest(shape) {
if (!shape) return false;
return (0, import_arrowLabel.isOverArrowLabel)(this.editor, shape);
}
handleDoubleClickOnCanvas(info) {
if (this.editor.getIsReadonly()) return;
if (!this.editor.options.createTextOnCanvasDoubleClick) return;
this.editor.markHistoryStoppingPoint("creating text shape");
const id = (0, import_editor.createShapeId)();
const { x, y } = this.editor.inputs.getCurrentPagePoint();
this.editor.createShapes([
{
id,
type: "text",
x,
y,
props: {
richText: (0, import_editor.toRichText)(""),
autoSize: true
}
}
]);
const shape = this.editor.getShape(id);
if (!shape) return;
if (!this.editor.canEditShape(shape)) return;
(0, import_selectHelpers.startEditingShapeWithRichText)(this.editor, id, { info });
}
nudgeSelectedShapes(ephemeral = false) {
const {
editor: {
inputs: { keys }
}
} = this;
const shiftKey = keys.has("ShiftLeft");
const delta = new import_editor.Vec(0, 0);
if (keys.has("ArrowLeft")) delta.x -= 1;
if (keys.has("ArrowRight")) delta.x += 1;
if (keys.has("ArrowUp")) delta.y -= 1;
if (keys.has("ArrowDown")) delta.y += 1;
if (delta.equals(new import_editor.Vec(0, 0))) return;
if (!ephemeral) this.editor.markHistoryStoppingPoint("nudge shapes");
const { gridSize } = this.editor.getDocumentSettings();
const step = this.editor.getInstanceState().isGridMode ? shiftKey ? gridSize * GRID_INCREMENT : gridSize : shiftKey ? MAJOR_NUDGE_FACTOR : MINOR_NUDGE_FACTOR;
const selectedShapeIds = this.editor.getSelectedShapeIds();
this.editor.nudgeShapes(selectedShapeIds, delta.mul(step));
(0, import_editor.kickoutOccludedShapes)(this.editor, selectedShapeIds);
}
}
const MAJOR_NUDGE_FACTOR = 10;
const MINOR_NUDGE_FACTOR = 1;
const GRID_INCREMENT = 5;
function isPointInRotatedSelectionBounds(editor, point) {
const selectionBounds = editor.getSelectionRotatedPageBounds();
if (!selectionBounds) return false;
const selectionRotation = editor.getSelectionRotation();
if (!selectionRotation) return selectionBounds.containsPoint(point);
return (0, import_editor.pointInPolygon)(
point,
selectionBounds.corners.map((c) => import_editor.Vec.RotWith(c, selectionBounds.point, selectionRotation))
);
}
//# sourceMappingURL=Idle.js.map