UNPKG

@tldraw/editor

Version:

tldraw infinite canvas SDK (editor).

233 lines (232 loc) • 10.5 kB
"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 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