tldraw
Version:
A tiny little drawing editor.
184 lines (183 loc) • 7.27 kB
JavaScript
import { StateNode } from "@tldraw/editor";
import { getTextLabels } from "../../../utils/shapes/shapes.mjs";
class PointingShape extends StateNode {
static id = "pointing_shape";
hitShape = {};
hitShapeForPointerUp = {};
isDoubleClick = false;
didCtrlOnEnter = false;
didSelectOnEnter = false;
onEnter(info) {
const selectedShapeIds = this.editor.getSelectedShapeIds();
const selectionBounds = this.editor.getSelectionRotatedPageBounds();
const focusedGroupId = this.editor.getFocusedGroupId();
const {
inputs: { currentPagePoint }
} = this.editor;
const { shiftKey, altKey, accelKey } = info;
this.hitShape = info.shape;
this.isDoubleClick = false;
this.didCtrlOnEnter = accelKey;
const outermostSelectingShape = this.editor.getOutermostSelectableShape(info.shape);
const selectedAncestor = this.editor.findShapeAncestor(
outermostSelectingShape,
(parent) => selectedShapeIds.includes(parent.id)
);
if (this.didCtrlOnEnter || // If the shape has an onClick handler
this.editor.getShapeUtil(info.shape).onClick || // ...or if the shape is the focused layer (e.g. group)
outermostSelectingShape.id === focusedGroupId || // ...or if the shape is within the selection
selectedShapeIds.includes(outermostSelectingShape.id) || // ...or if an ancestor of the shape is selected
selectedAncestor || // ...or if the current point is NOT within the selection bounds
selectedShapeIds.length > 1 && selectionBounds?.containsPoint(currentPagePoint)) {
this.didSelectOnEnter = false;
this.hitShapeForPointerUp = outermostSelectingShape;
return;
}
this.didSelectOnEnter = true;
if (shiftKey && !altKey) {
this.editor.cancelDoubleClick();
if (!selectedShapeIds.includes(outermostSelectingShape.id)) {
this.editor.markHistoryStoppingPoint("shift selecting shape");
this.editor.setSelectedShapes([...selectedShapeIds, outermostSelectingShape.id]);
}
} else {
this.editor.markHistoryStoppingPoint("selecting shape");
this.editor.setSelectedShapes([outermostSelectingShape.id]);
}
}
onPointerUp(info) {
const selectedShapeIds = this.editor.getSelectedShapeIds();
const focusedGroupId = this.editor.getFocusedGroupId();
const zoomLevel = this.editor.getZoomLevel();
const {
inputs: { currentPagePoint }
} = this.editor;
const additiveSelectionKey = info.shiftKey || info.accelKey;
const hitShape = this.editor.getShapeAtPoint(currentPagePoint, {
margin: this.editor.options.hitTestMargin / zoomLevel,
hitInside: true,
renderingOnly: true
}) ?? this.hitShape;
const selectingShape = hitShape ? this.editor.getOutermostSelectableShape(hitShape) : this.hitShapeForPointerUp;
if (selectingShape) {
const util = this.editor.getShapeUtil(selectingShape);
if (util.onClick) {
const change = util.onClick?.(selectingShape);
if (change) {
this.editor.markHistoryStoppingPoint("shape on click");
this.editor.updateShapes([change]);
this.parent.transition("idle", info);
return;
}
}
if (selectingShape.id === focusedGroupId) {
if (selectedShapeIds.length > 0) {
this.editor.markHistoryStoppingPoint("clearing shape ids");
this.editor.setSelectedShapes([]);
} else {
this.editor.popFocusedGroupId();
}
this.parent.transition("idle", info);
return;
}
}
if (!this.didSelectOnEnter) {
const outermostSelectableShape = this.editor.getOutermostSelectableShape(
hitShape,
// if a group is selected, we want to stop before reaching that group
// so we can drill down into the group
(parent) => !selectedShapeIds.includes(parent.id)
);
if (selectedShapeIds.includes(outermostSelectableShape.id)) {
if (additiveSelectionKey) {
this.editor.markHistoryStoppingPoint("deselecting on pointer up");
this.editor.deselect(selectingShape);
} else {
if (selectedShapeIds.includes(selectingShape.id)) {
if (selectedShapeIds.length === 1) {
const geometry = this.editor.getShapeUtil(selectingShape).getGeometry(selectingShape);
const textLabels = getTextLabels(geometry);
const textLabel = textLabels.length === 1 ? textLabels[0] : void 0;
if (textLabel) {
const pointInShapeSpace = this.editor.getPointInShapeSpace(
selectingShape,
currentPagePoint
);
if (textLabel.bounds.containsPoint(pointInShapeSpace, 0) && textLabel.hitTestPoint(pointInShapeSpace)) {
this.editor.run(() => {
this.editor.markHistoryStoppingPoint("editing on pointer up");
this.editor.select(selectingShape.id);
const util = this.editor.getShapeUtil(selectingShape);
if (this.editor.getIsReadonly()) {
if (!util.canEditInReadOnly(selectingShape)) {
return;
}
}
this.editor.setEditingShape(selectingShape.id);
this.editor.setCurrentTool("select.editing_shape");
if (this.isDoubleClick) {
this.editor.emit("select-all-text", { shapeId: selectingShape.id });
}
});
return;
}
}
}
this.editor.markHistoryStoppingPoint("selecting on pointer up");
this.editor.select(selectingShape.id);
} else {
this.editor.markHistoryStoppingPoint("selecting on pointer up");
this.editor.select(selectingShape);
}
}
} else if (additiveSelectionKey) {
const ancestors = this.editor.getShapeAncestors(outermostSelectableShape);
this.editor.markHistoryStoppingPoint("shift deselecting on pointer up");
this.editor.setSelectedShapes([
...this.editor.getSelectedShapeIds().filter((id) => !ancestors.find((a) => a.id === id)),
outermostSelectableShape.id
]);
} else {
this.editor.markHistoryStoppingPoint("selecting on pointer up");
this.editor.setSelectedShapes([outermostSelectableShape.id]);
}
}
this.parent.transition("idle", info);
}
onDoubleClick() {
this.isDoubleClick = true;
}
onPointerMove(info) {
if (this.editor.inputs.isDragging) {
if (this.didCtrlOnEnter) {
this.parent.transition("brushing", info);
} else {
this.startTranslating(info);
}
}
}
onLongPress(info) {
this.startTranslating(info);
}
startTranslating(info) {
if (this.editor.getIsReadonly()) return;
this.editor.focus();
this.parent.transition("translating", info);
}
onCancel() {
this.cancel();
}
onComplete() {
this.cancel();
}
onInterrupt() {
this.cancel();
}
cancel() {
this.parent.transition("idle");
}
}
export {
PointingShape
};
//# sourceMappingURL=PointingShape.mjs.map