@tldraw/editor
Version:
tldraw infinite canvas SDK (editor).
893 lines (892 loc) • 32.9 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
var __typeError = (msg) => {
throw TypeError(msg);
};
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
var __runInitializers = (array, flags, self, value) => {
for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
return value;
};
var __decorateElement = (array, flags, name, decorators, target, extra) => {
var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name]() {
return __privateGet(this, extra);
}, set [name](x) {
return __privateSet(this, extra, x);
} }, name));
k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
for (var i = decorators.length - 1; i >= 0; i--) {
ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
if (k) {
ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
}
it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1;
if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
else if (typeof it !== "object" || it === null) __typeError("Object expected");
else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
}
return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
};
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
var _getVisibleGaps_dec, _getSnappableGapNodes_dec, _getSnappablePoints_dec, _getSnapPointsCache_dec, _init;
import { computed } from "@tldraw/state";
import { assertExists, dedupe, uniqueId } from "@tldraw/utils";
import {
Box,
flipSelectionHandleX,
flipSelectionHandleY,
isSelectionCorner
} from "../../../primitives/Box.mjs";
import { Mat } from "../../../primitives/Mat.mjs";
import { Vec } from "../../../primitives/Vec.mjs";
import { rangeIntersection, rangesOverlap } from "../../../primitives/utils.mjs";
const round = (x) => {
const decimalPlacesTolerance = 8;
return Math.round(x * 10 ** decimalPlacesTolerance) / 10 ** decimalPlacesTolerance;
};
function findAdjacentGaps(gaps, shapeId, gapLength, direction, intersection) {
const matches = gaps.filter(
(gap) => (direction === "forward" ? gap.startNode.id === shapeId : gap.endNode.id === shapeId) && round(gap.length) === round(gapLength) && rangeIntersection(
gap.breadthIntersection[0],
gap.breadthIntersection[1],
intersection[0],
intersection[1]
)
);
if (matches.length === 0) return [];
const nextNodes = /* @__PURE__ */ new Set();
matches.forEach((match) => {
const node = direction === "forward" ? match.endNode.id : match.startNode.id;
if (!nextNodes.has(node)) {
nextNodes.add(node);
const foundGaps = findAdjacentGaps(
gaps,
node,
gapLength,
direction,
rangeIntersection(
match.breadthIntersection[0],
match.breadthIntersection[1],
intersection[0],
intersection[1]
)
);
matches.push(...foundGaps);
}
});
return matches;
}
function dedupeGapSnaps(snaps) {
snaps.sort((a, b) => b.gaps.length - a.gaps.length);
for (let i = snaps.length - 1; i > 0; i--) {
const snap = snaps[i];
for (let j = i - 1; j >= 0; j--) {
const otherSnap = snaps[j];
if (otherSnap.direction === snap.direction && snap.gaps.every(
(gap) => otherSnap.gaps.some(
(otherGap) => round(gap.startEdge[0].x) === round(otherGap.startEdge[0].x) && round(gap.startEdge[0].y) === round(otherGap.startEdge[0].y) && round(gap.startEdge[1].x) === round(otherGap.startEdge[1].x) && round(gap.startEdge[1].y) === round(otherGap.startEdge[1].y)
) && otherSnap.gaps.some(
(otherGap) => round(gap.endEdge[0].x) === round(otherGap.endEdge[0].x) && round(gap.endEdge[0].y) === round(otherGap.endEdge[0].y) && round(gap.endEdge[1].x) === round(otherGap.endEdge[1].x) && round(gap.endEdge[1].y) === round(otherGap.endEdge[1].y)
)
)) {
snaps.splice(i, 1);
break;
}
}
}
}
_getSnapPointsCache_dec = [computed], _getSnappablePoints_dec = [computed], _getSnappableGapNodes_dec = [computed], _getVisibleGaps_dec = [computed];
class BoundsSnaps {
constructor(manager) {
this.manager = manager;
__runInitializers(_init, 5, this);
__publicField(this, "editor");
this.editor = manager.editor;
}
getSnapPointsCache() {
const { editor } = this;
return editor.store.createComputedCache("snapPoints", (shape) => {
const pageTransform = editor.getShapePageTransform(shape.id);
if (!pageTransform) return void 0;
const boundsSnapGeometry = editor.getShapeUtil(shape).getBoundsSnapGeometry(shape);
const snapPoints = boundsSnapGeometry.points ?? editor.getShapeGeometry(shape).bounds.cornersAndCenter;
if (!pageTransform || !snapPoints) return void 0;
return snapPoints.map((point, i) => {
const { x, y } = Mat.applyToPoint(pageTransform, point);
return { x, y, id: `${shape.id}:${i}` };
});
});
}
getSnapPoints(shapeId) {
return this.getSnapPointsCache().get(shapeId) ?? [];
}
getSnappablePoints() {
const snapPointsCache = this.getSnapPointsCache();
const snappableShapes = this.manager.getSnappableShapes();
const result = [];
for (const shapeId of snappableShapes) {
const snapPoints = snapPointsCache.get(shapeId);
if (snapPoints) {
result.push(...snapPoints);
}
}
return result;
}
getSnappableGapNodes() {
return Array.from(this.manager.getSnappableShapes(), (shapeId) => ({
id: shapeId,
pageBounds: assertExists(this.editor.getShapePageBounds(shapeId))
}));
}
getVisibleGaps() {
const horizontal = [];
const vertical = [];
let startNode, endNode;
const sortedShapesOnCurrentPageHorizontal = this.getSnappableGapNodes().sort((a, b) => {
return a.pageBounds.minX - b.pageBounds.minX;
});
for (let i = 0; i < sortedShapesOnCurrentPageHorizontal.length; i++) {
startNode = sortedShapesOnCurrentPageHorizontal[i];
for (let j = i + 1; j < sortedShapesOnCurrentPageHorizontal.length; j++) {
endNode = sortedShapesOnCurrentPageHorizontal[j];
if (
// is there space between the boxes
startNode.pageBounds.maxX < endNode.pageBounds.minX && // and they overlap in the y axis
rangesOverlap(
startNode.pageBounds.minY,
startNode.pageBounds.maxY,
endNode.pageBounds.minY,
endNode.pageBounds.maxY
)
) {
horizontal.push({
startNode,
endNode,
startEdge: [
new Vec(startNode.pageBounds.maxX, startNode.pageBounds.minY),
new Vec(startNode.pageBounds.maxX, startNode.pageBounds.maxY)
],
endEdge: [
new Vec(endNode.pageBounds.minX, endNode.pageBounds.minY),
new Vec(endNode.pageBounds.minX, endNode.pageBounds.maxY)
],
length: endNode.pageBounds.minX - startNode.pageBounds.maxX,
breadthIntersection: rangeIntersection(
startNode.pageBounds.minY,
startNode.pageBounds.maxY,
endNode.pageBounds.minY,
endNode.pageBounds.maxY
)
});
}
}
}
const sortedShapesOnCurrentPageVertical = sortedShapesOnCurrentPageHorizontal.sort((a, b) => {
return a.pageBounds.minY - b.pageBounds.minY;
});
for (let i = 0; i < sortedShapesOnCurrentPageVertical.length; i++) {
startNode = sortedShapesOnCurrentPageVertical[i];
for (let j = i + 1; j < sortedShapesOnCurrentPageVertical.length; j++) {
endNode = sortedShapesOnCurrentPageVertical[j];
if (
// is there space between the boxes
startNode.pageBounds.maxY < endNode.pageBounds.minY && // do they overlap in the x axis
rangesOverlap(
startNode.pageBounds.minX,
startNode.pageBounds.maxX,
endNode.pageBounds.minX,
endNode.pageBounds.maxX
)
) {
vertical.push({
startNode,
endNode,
startEdge: [
new Vec(startNode.pageBounds.minX, startNode.pageBounds.maxY),
new Vec(startNode.pageBounds.maxX, startNode.pageBounds.maxY)
],
endEdge: [
new Vec(endNode.pageBounds.minX, endNode.pageBounds.minY),
new Vec(endNode.pageBounds.maxX, endNode.pageBounds.minY)
],
length: endNode.pageBounds.minY - startNode.pageBounds.maxY,
breadthIntersection: rangeIntersection(
startNode.pageBounds.minX,
startNode.pageBounds.maxX,
endNode.pageBounds.minX,
endNode.pageBounds.maxX
)
});
}
}
}
return { horizontal, vertical };
}
snapTranslateShapes({
lockedAxis,
initialSelectionPageBounds,
initialSelectionSnapPoints,
dragDelta
}) {
const snapThreshold = this.manager.getSnapThreshold();
const visibleSnapPointsNotInSelection = this.getSnappablePoints();
const selectionPageBounds = initialSelectionPageBounds.clone().translate(dragDelta);
const selectionSnapPoints = initialSelectionSnapPoints.map(
({ x, y }, i) => ({
id: "selection:" + i,
x: x + dragDelta.x,
y: y + dragDelta.y
})
);
const otherNodeSnapPoints = visibleSnapPointsNotInSelection;
const nearestSnapsX = [];
const nearestSnapsY = [];
const minOffset = new Vec(snapThreshold, snapThreshold);
this.collectPointSnaps({
minOffset,
nearestSnapsX,
nearestSnapsY,
otherNodeSnapPoints,
selectionSnapPoints
});
this.collectGapSnaps({
selectionPageBounds,
nearestSnapsX,
nearestSnapsY,
minOffset
});
const nudge = new Vec(
lockedAxis === "x" ? 0 : nearestSnapsX[0]?.nudge ?? 0,
lockedAxis === "y" ? 0 : nearestSnapsY[0]?.nudge ?? 0
);
minOffset.x = 0;
minOffset.y = 0;
nearestSnapsX.length = 0;
nearestSnapsY.length = 0;
selectionSnapPoints.forEach((s) => {
s.x += nudge.x;
s.y += nudge.y;
});
selectionPageBounds.translate(nudge);
this.collectPointSnaps({
minOffset,
nearestSnapsX,
nearestSnapsY,
otherNodeSnapPoints,
selectionSnapPoints
});
this.collectGapSnaps({
selectionPageBounds,
nearestSnapsX,
nearestSnapsY,
minOffset
});
const pointSnapsLines = this.getPointSnapLines({
nearestSnapsX,
nearestSnapsY
});
const gapSnapLines = this.getGapSnapLines({
selectionPageBounds,
nearestSnapsX,
nearestSnapsY
});
this.manager.setIndicators([...gapSnapLines, ...pointSnapsLines]);
return { nudge };
}
snapResizeShapes({
initialSelectionPageBounds,
dragDelta,
handle: originalHandle,
isAspectRatioLocked,
isResizingFromCenter
}) {
const snapThreshold = this.manager.getSnapThreshold();
const {
box: unsnappedResizedPageBounds,
scaleX,
scaleY
} = Box.Resize(
initialSelectionPageBounds,
originalHandle,
isResizingFromCenter ? dragDelta.x * 2 : dragDelta.x,
isResizingFromCenter ? dragDelta.y * 2 : dragDelta.y,
isAspectRatioLocked
);
let handle = originalHandle;
if (scaleX < 0) {
handle = flipSelectionHandleX(handle);
}
if (scaleY < 0) {
handle = flipSelectionHandleY(handle);
}
if (isResizingFromCenter) {
unsnappedResizedPageBounds.center = initialSelectionPageBounds.center;
}
const isXLocked = handle === "top" || handle === "bottom";
const isYLocked = handle === "left" || handle === "right";
const selectionSnapPoints = getResizeSnapPointsForHandle(handle, unsnappedResizedPageBounds);
const otherNodeSnapPoints = this.getSnappablePoints();
const nearestSnapsX = [];
const nearestSnapsY = [];
const minOffset = new Vec(snapThreshold, snapThreshold);
this.collectPointSnaps({
minOffset,
nearestSnapsX,
nearestSnapsY,
otherNodeSnapPoints,
selectionSnapPoints
});
const nudge = new Vec(
isXLocked ? 0 : nearestSnapsX[0]?.nudge ?? 0,
isYLocked ? 0 : nearestSnapsY[0]?.nudge ?? 0
);
if (isAspectRatioLocked && isSelectionCorner(handle) && nudge.len() !== 0) {
const primaryNudgeAxis = nearestSnapsX.length && nearestSnapsY.length ? Math.abs(nudge.x) < Math.abs(nudge.y) ? "x" : "y" : nearestSnapsX.length ? "x" : "y";
const ratio = initialSelectionPageBounds.aspectRatio;
if (primaryNudgeAxis === "x") {
nearestSnapsY.length = 0;
nudge.y = nudge.x / ratio;
if (handle === "bottom_left" || handle === "top_right") {
nudge.y = -nudge.y;
}
} else {
nearestSnapsX.length = 0;
nudge.x = nudge.y * ratio;
if (handle === "bottom_left" || handle === "top_right") {
nudge.x = -nudge.x;
}
}
}
const snappedDelta = Vec.Add(dragDelta, nudge);
const { box: snappedResizedPageBounds } = Box.Resize(
initialSelectionPageBounds,
originalHandle,
isResizingFromCenter ? snappedDelta.x * 2 : snappedDelta.x,
isResizingFromCenter ? snappedDelta.y * 2 : snappedDelta.y,
isAspectRatioLocked
);
if (isResizingFromCenter) {
snappedResizedPageBounds.center = initialSelectionPageBounds.center;
}
const snappedSelectionPoints = getResizeSnapPointsForHandle("any", snappedResizedPageBounds);
nearestSnapsX.length = 0;
nearestSnapsY.length = 0;
minOffset.x = 0;
minOffset.y = 0;
this.collectPointSnaps({
minOffset,
nearestSnapsX,
nearestSnapsY,
otherNodeSnapPoints,
selectionSnapPoints: snappedSelectionPoints
});
const pointSnaps = this.getPointSnapLines({
nearestSnapsX,
nearestSnapsY
});
this.manager.setIndicators([...pointSnaps]);
return { nudge };
}
collectPointSnaps({
selectionSnapPoints,
otherNodeSnapPoints,
minOffset,
nearestSnapsX,
nearestSnapsY
}) {
for (const thisSnapPoint of selectionSnapPoints) {
for (const otherSnapPoint of otherNodeSnapPoints) {
const offset = Vec.Sub(thisSnapPoint, otherSnapPoint);
const offsetX = Math.abs(offset.x);
const offsetY = Math.abs(offset.y);
if (round(offsetX) <= round(minOffset.x)) {
if (round(offsetX) < round(minOffset.x)) {
nearestSnapsX.length = 0;
}
nearestSnapsX.push({
type: "points",
points: { thisPoint: thisSnapPoint, otherPoint: otherSnapPoint },
nudge: otherSnapPoint.x - thisSnapPoint.x
});
minOffset.x = offsetX;
}
if (round(offsetY) <= round(minOffset.y)) {
if (round(offsetY) < round(minOffset.y)) {
nearestSnapsY.length = 0;
}
nearestSnapsY.push({
type: "points",
points: { thisPoint: thisSnapPoint, otherPoint: otherSnapPoint },
nudge: otherSnapPoint.y - thisSnapPoint.y
});
minOffset.y = offsetY;
}
}
}
}
collectGapSnaps({
selectionPageBounds,
minOffset,
nearestSnapsX,
nearestSnapsY
}) {
const { horizontal, vertical } = this.getVisibleGaps();
for (const gap of horizontal) {
if (!rangesOverlap(
gap.breadthIntersection[0],
gap.breadthIntersection[1],
selectionPageBounds.minY,
selectionPageBounds.maxY
)) {
continue;
}
const gapMidX = gap.startEdge[0].x + gap.length / 2;
const centerNudge = gapMidX - selectionPageBounds.center.x;
const gapIsLargerThanSelection = gap.length > selectionPageBounds.width;
if (gapIsLargerThanSelection && round(Math.abs(centerNudge)) <= round(minOffset.x)) {
if (round(Math.abs(centerNudge)) < round(minOffset.x)) {
nearestSnapsX.length = 0;
}
minOffset.x = Math.abs(centerNudge);
const snap = {
type: "gap_center",
gap,
nudge: centerNudge
};
const otherCenterSnap = nearestSnapsX.find(({ type }) => type === "gap_center");
const gapBreadthsOverlap = otherCenterSnap && rangeIntersection(
gap.breadthIntersection[0],
gap.breadthIntersection[1],
otherCenterSnap.gap.breadthIntersection[0],
otherCenterSnap.gap.breadthIntersection[1]
);
if (otherCenterSnap && otherCenterSnap.gap.length > gap.length && gapBreadthsOverlap) {
nearestSnapsX[nearestSnapsX.indexOf(otherCenterSnap)] = snap;
} else if (!otherCenterSnap || !gapBreadthsOverlap) {
nearestSnapsX.push(snap);
}
}
const duplicationLeftX = gap.startNode.pageBounds.minX - gap.length;
const selectionRightX = selectionPageBounds.maxX;
const duplicationLeftNudge = duplicationLeftX - selectionRightX;
if (round(Math.abs(duplicationLeftNudge)) <= round(minOffset.x)) {
if (round(Math.abs(duplicationLeftNudge)) < round(minOffset.x)) {
nearestSnapsX.length = 0;
}
minOffset.x = Math.abs(duplicationLeftNudge);
nearestSnapsX.push({
type: "gap_duplicate",
gap,
protrusionDirection: "left",
nudge: duplicationLeftNudge
});
}
const duplicationRightX = gap.endNode.pageBounds.maxX + gap.length;
const selectionLeftX = selectionPageBounds.minX;
const duplicationRightNudge = duplicationRightX - selectionLeftX;
if (round(Math.abs(duplicationRightNudge)) <= round(minOffset.x)) {
if (round(Math.abs(duplicationRightNudge)) < round(minOffset.x)) {
nearestSnapsX.length = 0;
}
minOffset.x = Math.abs(duplicationRightNudge);
nearestSnapsX.push({
type: "gap_duplicate",
gap,
protrusionDirection: "right",
nudge: duplicationRightNudge
});
}
}
for (const gap of vertical) {
if (!rangesOverlap(
gap.breadthIntersection[0],
gap.breadthIntersection[1],
selectionPageBounds.minX,
selectionPageBounds.maxX
)) {
continue;
}
const gapMidY = gap.startEdge[0].y + gap.length / 2;
const centerNudge = gapMidY - selectionPageBounds.center.y;
const gapIsLargerThanSelection = gap.length > selectionPageBounds.height;
if (gapIsLargerThanSelection && round(Math.abs(centerNudge)) <= round(minOffset.y)) {
if (round(Math.abs(centerNudge)) < round(minOffset.y)) {
nearestSnapsY.length = 0;
}
minOffset.y = Math.abs(centerNudge);
const snap = {
type: "gap_center",
gap,
nudge: centerNudge
};
const otherCenterSnap = nearestSnapsY.find(({ type }) => type === "gap_center");
const gapBreadthsOverlap = otherCenterSnap && rangesOverlap(
otherCenterSnap.gap.breadthIntersection[0],
otherCenterSnap.gap.breadthIntersection[1],
gap.breadthIntersection[0],
gap.breadthIntersection[1]
);
if (otherCenterSnap && otherCenterSnap.gap.length > gap.length && gapBreadthsOverlap) {
nearestSnapsY[nearestSnapsY.indexOf(otherCenterSnap)] = snap;
} else if (!otherCenterSnap || !gapBreadthsOverlap) {
nearestSnapsY.push(snap);
}
continue;
}
const duplicationTopY = gap.startNode.pageBounds.minY - gap.length;
const selectionBottomY = selectionPageBounds.maxY;
const duplicationTopNudge = duplicationTopY - selectionBottomY;
if (round(Math.abs(duplicationTopNudge)) <= round(minOffset.y)) {
if (round(Math.abs(duplicationTopNudge)) < round(minOffset.y)) {
nearestSnapsY.length = 0;
}
minOffset.y = Math.abs(duplicationTopNudge);
nearestSnapsY.push({
type: "gap_duplicate",
gap,
protrusionDirection: "top",
nudge: duplicationTopNudge
});
}
const duplicationBottomY = gap.endNode.pageBounds.maxY + gap.length;
const selectionTopY = selectionPageBounds.minY;
const duplicationBottomNudge = duplicationBottomY - selectionTopY;
if (round(Math.abs(duplicationBottomNudge)) <= round(minOffset.y)) {
if (round(Math.abs(duplicationBottomNudge)) < round(minOffset.y)) {
nearestSnapsY.length = 0;
}
minOffset.y = Math.abs(duplicationBottomNudge);
nearestSnapsY.push({
type: "gap_duplicate",
gap,
protrusionDirection: "bottom",
nudge: duplicationBottomNudge
});
}
}
}
getPointSnapLines({
nearestSnapsX,
nearestSnapsY
}) {
const snapGroupsX = {};
const snapGroupsY = {};
if (nearestSnapsX.length > 0) {
for (const snap of nearestSnapsX) {
if (snap.type === "points") {
const key = round(snap.points.otherPoint.x);
if (!snapGroupsX[key]) {
snapGroupsX[key] = [];
}
snapGroupsX[key].push(snap.points);
}
}
}
if (nearestSnapsY.length > 0) {
for (const snap of nearestSnapsY) {
if (snap.type === "points") {
const key = round(snap.points.otherPoint.y);
if (!snapGroupsY[key]) {
snapGroupsY[key] = [];
}
snapGroupsY[key].push(snap.points);
}
}
}
return Object.values(snapGroupsX).concat(Object.values(snapGroupsY)).map((snapGroup) => ({
id: uniqueId(),
type: "points",
points: dedupe(
snapGroup.map((snap) => Vec.From(snap.otherPoint)).concat(snapGroup.map((snap) => Vec.From(snap.thisPoint))),
(a, b) => a.equals(b)
)
}));
}
getGapSnapLines({
selectionPageBounds,
nearestSnapsX,
nearestSnapsY
}) {
const { vertical, horizontal } = this.getVisibleGaps();
const selectionSides = {
top: selectionPageBounds.sides[0],
right: selectionPageBounds.sides[1],
// need bottom and left to be sorted asc, which .sides is not.
bottom: [selectionPageBounds.corners[3], selectionPageBounds.corners[2]],
left: [selectionPageBounds.corners[0], selectionPageBounds.corners[3]]
};
const result = [];
if (nearestSnapsX.length > 0) {
for (const snap of nearestSnapsX) {
if (snap.type === "points") continue;
const {
gap: { breadthIntersection, startEdge, startNode, endNode, length, endEdge }
} = snap;
switch (snap.type) {
case "gap_center": {
const newGapsLength = (length - selectionPageBounds.width) / 2;
const gapBreadthIntersection = rangeIntersection(
breadthIntersection[0],
breadthIntersection[1],
selectionPageBounds.minY,
selectionPageBounds.maxY
);
result.push({
type: "gaps",
direction: "horizontal",
id: uniqueId(),
gaps: [
...findAdjacentGaps(
horizontal,
startNode.id,
newGapsLength,
"backward",
gapBreadthIntersection
),
{
startEdge,
endEdge: selectionSides.left
},
{
startEdge: selectionSides.right,
endEdge
},
...findAdjacentGaps(
horizontal,
endNode.id,
newGapsLength,
"forward",
gapBreadthIntersection
)
]
});
break;
}
case "gap_duplicate": {
const gapBreadthIntersection = rangeIntersection(
breadthIntersection[0],
breadthIntersection[1],
selectionPageBounds.minY,
selectionPageBounds.maxY
);
result.push({
type: "gaps",
direction: "horizontal",
id: uniqueId(),
gaps: snap.protrusionDirection === "left" ? [
{
startEdge: selectionSides.right,
endEdge: startEdge.map(
(v) => v.clone().addXY(-startNode.pageBounds.width, 0)
)
},
{ startEdge, endEdge },
...findAdjacentGaps(
horizontal,
endNode.id,
length,
"forward",
gapBreadthIntersection
)
] : [
...findAdjacentGaps(
horizontal,
startNode.id,
length,
"backward",
gapBreadthIntersection
),
{ startEdge, endEdge },
{
startEdge: endEdge.map(
(v) => v.clone().addXY(snap.gap.endNode.pageBounds.width, 0)
),
endEdge: selectionSides.left
}
]
});
break;
}
}
}
}
if (nearestSnapsY.length > 0) {
for (const snap of nearestSnapsY) {
if (snap.type === "points") continue;
const {
gap: { breadthIntersection, startEdge, startNode, endNode, length, endEdge }
} = snap;
switch (snap.type) {
case "gap_center": {
const newGapsLength = (length - selectionPageBounds.height) / 2;
const gapBreadthIntersection = rangeIntersection(
breadthIntersection[0],
breadthIntersection[1],
selectionPageBounds.minX,
selectionPageBounds.maxX
);
result.push({
type: "gaps",
direction: "vertical",
id: uniqueId(),
gaps: [
...findAdjacentGaps(
vertical,
startNode.id,
newGapsLength,
"backward",
gapBreadthIntersection
),
{
startEdge,
endEdge: selectionSides.top
},
{
startEdge: selectionSides.bottom,
endEdge
},
...findAdjacentGaps(
vertical,
snap.gap.endNode.id,
newGapsLength,
"forward",
gapBreadthIntersection
)
]
});
break;
}
case "gap_duplicate":
{
const gapBreadthIntersection = rangeIntersection(
breadthIntersection[0],
breadthIntersection[1],
selectionPageBounds.minX,
selectionPageBounds.maxX
);
result.push({
type: "gaps",
direction: "vertical",
id: uniqueId(),
gaps: snap.protrusionDirection === "top" ? [
{
startEdge: selectionSides.bottom,
endEdge: startEdge.map(
(v) => v.clone().addXY(0, -startNode.pageBounds.height)
)
},
{ startEdge, endEdge },
...findAdjacentGaps(
vertical,
endNode.id,
length,
"forward",
gapBreadthIntersection
)
] : [
...findAdjacentGaps(
vertical,
startNode.id,
length,
"backward",
gapBreadthIntersection
),
{ startEdge, endEdge },
{
startEdge: endEdge.map(
(v) => v.clone().addXY(0, endNode.pageBounds.height)
),
endEdge: selectionSides.top
}
]
});
}
break;
}
}
}
dedupeGapSnaps(result);
return result;
}
}
_init = __decoratorStart(null);
__decorateElement(_init, 1, "getSnapPointsCache", _getSnapPointsCache_dec, BoundsSnaps);
__decorateElement(_init, 1, "getSnappablePoints", _getSnappablePoints_dec, BoundsSnaps);
__decorateElement(_init, 1, "getSnappableGapNodes", _getSnappableGapNodes_dec, BoundsSnaps);
__decorateElement(_init, 1, "getVisibleGaps", _getVisibleGaps_dec, BoundsSnaps);
__decoratorMetadata(_init, BoundsSnaps);
function getResizeSnapPointsForHandle(handle, selectionPageBounds) {
const { minX, maxX, minY, maxY } = selectionPageBounds;
const result = [];
switch (handle) {
case "top":
case "left":
case "top_left":
case "any":
result.push({
id: "top_left",
handle: "top_left",
x: minX,
y: minY
});
}
switch (handle) {
case "top":
case "right":
case "top_right":
case "any":
result.push({
id: "top_right",
handle: "top_right",
x: maxX,
y: minY
});
}
switch (handle) {
case "bottom":
case "right":
case "bottom_right":
case "any":
result.push({
id: "bottom_right",
handle: "bottom_right",
x: maxX,
y: maxY
});
}
switch (handle) {
case "bottom":
case "left":
case "bottom_left":
case "any":
result.push({
id: "bottom_left",
handle: "bottom_left",
x: minX,
y: maxY
});
}
return result;
}
export {
BoundsSnaps
};
//# sourceMappingURL=BoundsSnaps.mjs.map