@tldraw/editor
Version:
tldraw infinite canvas SDK (editor).
233 lines (232 loc) • 10.5 kB
JavaScript
;
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 reparenting_exports = {};
__export(reparenting_exports, {
doesGeometryOverlapPolygon: () => doesGeometryOverlapPolygon,
getDroppedShapesToNewParents: () => getDroppedShapesToNewParents,
kickoutOccludedShapes: () => kickoutOccludedShapes
});
module.exports = __toCommonJS(reparenting_exports);
var import_state = require("@tldraw/state");
var import_utils = require("@tldraw/utils");
var import_Group2d = require("../primitives/geometry/Group2d");
var import_intersect = require("../primitives/intersect");
var import_utils2 = require("../primitives/utils");
function kickoutOccludedShapes(editor, shapeIds, opts) {
const parentsToCheck = /* @__PURE__ */ new Set();
for (const id of shapeIds) {
const shape = editor.getShape(id);
if (!shape) continue;
parentsToCheck.add(shape);
const parent = editor.getShape(shape.parentId);
if (!parent) continue;
parentsToCheck.add(parent);
}
const parentsToLostChildren = /* @__PURE__ */ new Map();
for (const parent of parentsToCheck) {
const childIds = editor.getSortedChildIdsForParent(parent);
if (opts?.filter && !opts.filter(parent)) {
parentsToLostChildren.set(parent, childIds);
} else {
const overlappingChildren = getOverlappingShapes(editor, parent.id, childIds);
if (overlappingChildren.length < childIds.length) {
parentsToLostChildren.set(
parent,
childIds.filter((id) => !overlappingChildren.includes(id))
);
}
}
}
const sortedShapeIds = editor.getCurrentPageShapesSorted().map((s) => s.id);
const parentsToNewChildren = {};
for (const [prevParent, lostChildrenIds] of parentsToLostChildren) {
const lostChildren = (0, import_utils.compact)(lostChildrenIds.map((id) => editor.getShape(id)));
const { reparenting, remainingShapesToReparent } = getDroppedShapesToNewParents(
editor,
lostChildren,
(shape, maybeNewParent) => {
if (opts?.filter && !opts.filter(maybeNewParent)) return false;
return maybeNewParent.id !== prevParent.id && sortedShapeIds.indexOf(maybeNewParent.id) < sortedShapeIds.indexOf(shape.id);
}
);
reparenting.forEach((childrenToReparent, newParentId) => {
if (childrenToReparent.length === 0) return;
if (!parentsToNewChildren[newParentId]) {
parentsToNewChildren[newParentId] = {
parentId: newParentId,
shapeIds: []
};
}
parentsToNewChildren[newParentId].shapeIds.push(...childrenToReparent.map((s) => s.id));
});
if (remainingShapesToReparent.size > 0) {
const newParentId = editor.findShapeAncestor(prevParent, (s) => editor.isShapeOfType(s, "group"))?.id ?? editor.getCurrentPageId();
remainingShapesToReparent.forEach((shape) => {
if (!parentsToNewChildren[newParentId]) {
let insertIndexKey;
const oldParentSiblingIds = editor.getSortedChildIdsForParent(newParentId);
const oldParentIndex = oldParentSiblingIds.indexOf(prevParent.id);
if (oldParentIndex > -1) {
const siblingsIndexAbove = oldParentSiblingIds[oldParentIndex + 1];
const indexKeyAbove = siblingsIndexAbove ? editor.getShape(siblingsIndexAbove).index : (0, import_utils.getIndexAbove)(prevParent.index);
insertIndexKey = (0, import_utils.getIndexBetween)(prevParent.index, indexKeyAbove);
} else {
}
parentsToNewChildren[newParentId] = {
parentId: newParentId,
shapeIds: [],
index: insertIndexKey
};
}
parentsToNewChildren[newParentId].shapeIds.push(shape.id);
});
}
}
editor.run(() => {
Object.values(parentsToNewChildren).forEach(({ parentId, shapeIds: shapeIds2, index }) => {
if (shapeIds2.length === 0) return;
shapeIds2.sort((a, b) => sortedShapeIds.indexOf(a) < sortedShapeIds.indexOf(b) ? -1 : 1);
editor.reparentShapes(shapeIds2, parentId, index);
});
});
}
function getOverlappingShapes(editor, shape, otherShapes) {
if (otherShapes.length === 0) {
return import_state.EMPTY_ARRAY;
}
const parentPageBounds = editor.getShapePageBounds(shape);
if (!parentPageBounds) return import_state.EMPTY_ARRAY;
const parentGeometry = editor.getShapeGeometry(shape);
const parentPageTransform = editor.getShapePageTransform(shape);
const parentPageCorners = parentPageTransform.applyToPoints(parentGeometry.vertices);
const parentPageMaskVertices = editor.getShapeMask(shape);
const parentPagePolygon = parentPageMaskVertices ? (0, import_intersect.intersectPolygonPolygon)(parentPageMaskVertices, parentPageCorners) : parentPageCorners;
if (!parentPagePolygon) return import_state.EMPTY_ARRAY;
return otherShapes.filter((childId) => {
const shapePageBounds = editor.getShapePageBounds(childId);
if (!shapePageBounds || !parentPageBounds.includes(shapePageBounds)) return false;
const parentPolygonInShapeShape = editor.getShapePageTransform(childId).clone().invert().applyToPoints(parentPagePolygon);
const geometry = editor.getShapeGeometry(childId);
return doesGeometryOverlapPolygon(geometry, parentPolygonInShapeShape);
});
}
function doesGeometryOverlapPolygon(geometry, parentCornersInShapeSpace) {
if (geometry instanceof import_Group2d.Group2d) {
return geometry.children.some(
(childGeometry) => doesGeometryOverlapPolygon(childGeometry, parentCornersInShapeSpace)
);
}
const { vertices, center, isFilled, isEmptyLabel, isClosed } = geometry;
if (isEmptyLabel) return false;
if (vertices.some((v) => (0, import_utils2.pointInPolygon)(v, parentCornersInShapeSpace))) {
return true;
}
if (isClosed) {
if (isFilled) {
if ((0, import_utils2.pointInPolygon)(center, parentCornersInShapeSpace)) {
return true;
}
if (parentCornersInShapeSpace.every((v) => (0, import_utils2.pointInPolygon)(v, vertices))) {
return true;
}
}
if ((0, import_intersect.polygonsIntersect)(parentCornersInShapeSpace, vertices)) {
return true;
}
} else {
if ((0, import_intersect.polygonIntersectsPolyline)(parentCornersInShapeSpace, vertices)) {
return true;
}
}
return false;
}
function getDroppedShapesToNewParents(editor, shapes, cb) {
const shapesToActuallyCheck = new Set(shapes);
const movingGroups = /* @__PURE__ */ new Set();
for (const shape of shapes) {
const parent = editor.getShapeParent(shape);
if (parent && editor.isShapeOfType(parent, "group")) {
if (!movingGroups.has(parent)) {
movingGroups.add(parent);
}
}
}
for (const movingGroup of movingGroups) {
const children = (0, import_utils.compact)(
editor.getSortedChildIdsForParent(movingGroup).map((id) => editor.getShape(id))
);
for (const child of children) {
shapesToActuallyCheck.delete(child);
}
shapesToActuallyCheck.add(movingGroup);
}
const shapeGroupIds = /* @__PURE__ */ new Map();
const reparenting = /* @__PURE__ */ new Map();
const remainingShapesToReparent = new Set(shapesToActuallyCheck);
const potentialParentShapes = editor.getCurrentPageShapesSorted().filter(
(s) => editor.getShapeUtil(s).canReceiveNewChildrenOfType?.(s, s.type) && !remainingShapesToReparent.has(s)
);
parentCheck: for (let i = potentialParentShapes.length - 1; i >= 0; i--) {
const parentShape = potentialParentShapes[i];
const parentShapeContainingGroupId = editor.findShapeAncestor(
parentShape,
(s) => editor.isShapeOfType(s, "group")
)?.id;
const parentGeometry = editor.getShapeGeometry(parentShape);
const parentPageTransform = editor.getShapePageTransform(parentShape);
const parentPageMaskVertices = editor.getShapeMask(parentShape);
const parentPageCorners = parentPageTransform.applyToPoints(parentGeometry.vertices);
const parentPagePolygon = parentPageMaskVertices ? (0, import_intersect.intersectPolygonPolygon)(parentPageMaskVertices, parentPageCorners) : parentPageCorners;
if (!parentPagePolygon) continue parentCheck;
const childrenToReparent = [];
shapeCheck: for (const shape of remainingShapesToReparent) {
if (parentShape.id === shape.id) continue shapeCheck;
if (cb && !cb(shape, parentShape)) continue shapeCheck;
if (!shapeGroupIds.has(shape.id)) {
shapeGroupIds.set(
shape.id,
editor.findShapeAncestor(shape, (s) => editor.isShapeOfType(s, "group"))?.id
);
}
const shapeGroupId = shapeGroupIds.get(shape.id);
if (shapeGroupId !== parentShapeContainingGroupId) continue shapeCheck;
if (editor.findShapeAncestor(parentShape, (s) => shape.id === s.id)) continue shapeCheck;
const parentPolygonInShapeSpace = editor.getShapePageTransform(shape).clone().invert().applyToPoints(parentPagePolygon);
if (doesGeometryOverlapPolygon(editor.getShapeGeometry(shape), parentPolygonInShapeSpace)) {
if (!editor.getShapeUtil(parentShape).canReceiveNewChildrenOfType?.(parentShape, shape.type))
continue shapeCheck;
if (shape.parentId !== parentShape.id) {
childrenToReparent.push(shape);
}
remainingShapesToReparent.delete(shape);
continue shapeCheck;
}
}
if (childrenToReparent.length) {
reparenting.set(parentShape.id, childrenToReparent);
}
}
return {
// these are the shapes that will be reparented to new parents
reparenting,
// these are the shapes that will be reparented to the page or their ancestral group
remainingShapesToReparent
};
}
//# sourceMappingURL=reparenting.js.map