UNPKG

tldraw

Version:

A tiny little drawing editor.

182 lines (181 loc) 6.82 kB
import { activeElementShouldCaptureKeys, StateNode, tlenv } from "@tldraw/editor"; import { getTextLabels } from "../../../utils/shapes/shapes.mjs"; import { renderPlaintextFromRichText } from "../../../utils/text/richText.mjs"; import { getHitShapeOnCanvasPointerDown } from "../../selection-logic/getHitShapeOnCanvasPointerDown.mjs"; import { updateHoveredShapeId } from "../../selection-logic/updateHoveredShapeId.mjs"; class EditingShape extends StateNode { static id = "editing_shape"; hitLabelOnShapeForPointerUp = null; info = {}; didPointerDownOnEditingShape = false; isTextInputFocused() { const container = this.editor.getContainer(); return container.contains(document.activeElement) && activeElementShouldCaptureKeys(false); } onEnter(info) { const editingShape = this.editor.getEditingShape(); if (!editingShape) throw Error("Entered editing state without an editing shape"); this.hitLabelOnShapeForPointerUp = null; this.didPointerDownOnEditingShape = false; this.info = info; if (info.isCreatingTextWhileToolLocked) { this.parent.setCurrentToolIdMask("text"); } updateHoveredShapeId(this.editor); this.editor.select(editingShape); } onExit() { const hadEditingShape = !!this.editor.getEditingShapeId(); this.editor.setEditingShape(null); updateHoveredShapeId.cancel(); if (this.info.isCreatingTextWhileToolLocked && hadEditingShape) { this.parent.setCurrentToolIdMask(void 0); this.editor.setCurrentTool("text", {}); } } onPointerMove(info) { if (this.hitLabelOnShapeForPointerUp && this.editor.inputs.getIsDragging()) { if (this.editor.getIsReadonly()) return; if (this.hitLabelOnShapeForPointerUp.isLocked) return; this.editor.select(this.hitLabelOnShapeForPointerUp); this.parent.transition("translating", info); this.hitLabelOnShapeForPointerUp = null; return; } if (this.didPointerDownOnEditingShape && this.editor.inputs.isDragging) { if (this.editor.getIsReadonly()) return; const editingShape = this.editor.getEditingShape(); if (!editingShape || editingShape.isLocked) return; if (!this.isTextInputFocused()) { this.editor.select(editingShape); this.parent.transition("translating", info); this.didPointerDownOnEditingShape = false; return; } this.didPointerDownOnEditingShape = false; } switch (info.target) { case "shape": case "canvas": { updateHoveredShapeId(this.editor); return; } } } onPointerDown(info) { this.hitLabelOnShapeForPointerUp = null; this.didPointerDownOnEditingShape = false; switch (info.target) { // N.B. This bit of logic has a bit of history to it. // There was a PR that got rid of this logic: https://github.com/tldraw/tldraw/pull/4237 // But here we bring it back to help support the new rich text world. // The original issue which is visible in the video attachments in the PR now seem // to have been resolved anyway via some other layer. case "canvas": { const hitShape = getHitShapeOnCanvasPointerDown( this.editor, true /* hitLabels */ ); if (hitShape) { this.onPointerDown({ ...info, shape: hitShape, target: "shape" }); return; } break; } case "shape": { const { shape: selectingShape } = info; const editingShape = this.editor.getEditingShape(); if (!editingShape) { throw Error("Expected an editing shape!"); } const geometry = this.editor.getShapeUtil(selectingShape).getGeometry(selectingShape); const textLabels = getTextLabels(geometry); const textLabel = textLabels.length === 1 ? textLabels[0] : void 0; const isEmptyTextShape = this.editor.isShapeOfType(editingShape, "text") && renderPlaintextFromRichText(this.editor, editingShape.props.richText).trim() === ""; if (textLabel && !isEmptyTextShape) { const pointInShapeSpace = this.editor.getPointInShapeSpace( selectingShape, this.editor.inputs.getCurrentPagePoint() ); if (textLabel.bounds.containsPoint(pointInShapeSpace, 0) && textLabel.hitTestPoint(pointInShapeSpace)) { if (selectingShape.id === editingShape.id) { this.didPointerDownOnEditingShape = true; return; } else { this.hitLabelOnShapeForPointerUp = selectingShape; this.editor.markHistoryStoppingPoint("editing on pointer up"); this.editor.select(selectingShape.id); return; } } } else { if (selectingShape.id === editingShape.id) { if (this.editor.isShapeOfType(selectingShape, "frame")) { this.editor.setEditingShape(null); this.parent.transition("idle", info); } } else { this.parent.transition("pointing_shape", info); return; } return; } break; } } this.parent.transition("idle", info); this.editor.root.handleEvent(info); } onPointerUp(info) { if (this.didPointerDownOnEditingShape) { this.didPointerDownOnEditingShape = false; if (!this.isTextInputFocused()) { this.editor.getRichTextEditor()?.commands.focus("all"); return; } } const hitShape = this.hitLabelOnShapeForPointerUp; if (!hitShape) return; this.hitLabelOnShapeForPointerUp = null; const util = this.editor.getShapeUtil(hitShape); if (hitShape.isLocked) return; if (this.editor.getIsReadonly()) { if (!util.canEditInReadonly(hitShape)) { this.parent.transition("pointing_shape", info); return; } } this.editor.select(hitShape.id); const currentEditingShape = this.editor.getEditingShape(); const isEditToEditAction = currentEditingShape && currentEditingShape.id !== hitShape.id; this.editor.setEditingShape(hitShape.id); const isMobile = tlenv.isIos || tlenv.isAndroid; if (!isMobile || !isEditToEditAction) { this.editor.emit("place-caret", { shapeId: hitShape.id, point: info.point }); } else if (isMobile && isEditToEditAction) { this.editor.emit("select-all-text", { shapeId: hitShape.id }); } updateHoveredShapeId(this.editor); } onComplete(info) { this.editor.getContainer().focus(); this.parent.transition("idle", info); } onCancel(info) { this.editor.getContainer().focus(); this.parent.transition("idle", info); } } export { EditingShape }; //# sourceMappingURL=EditingShape.mjs.map