|
|
|
@ -95,12 +95,11 @@ export const getElementsCompletelyInFrame = (
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
export const isElementContainingFrame = (
|
|
|
|
|
elements: readonly ExcalidrawElement[],
|
|
|
|
|
element: ExcalidrawElement,
|
|
|
|
|
frame: ExcalidrawFrameLikeElement,
|
|
|
|
|
elementsMap: ElementsMap,
|
|
|
|
|
) => {
|
|
|
|
|
return getElementsWithinSelection(elements, element, elementsMap).some(
|
|
|
|
|
return getElementsWithinSelection([frame], element, elementsMap).some(
|
|
|
|
|
(e) => e.id === frame.id,
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
@ -144,7 +143,7 @@ export const elementOverlapsWithFrame = (
|
|
|
|
|
return (
|
|
|
|
|
elementsAreInFrameBounds([element], frame, elementsMap) ||
|
|
|
|
|
isElementIntersectingFrame(element, frame, elementsMap) ||
|
|
|
|
|
isElementContainingFrame([frame], element, frame, elementsMap)
|
|
|
|
|
isElementContainingFrame(element, frame, elementsMap)
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@ -283,7 +282,7 @@ export const getElementsInResizingFrame = (
|
|
|
|
|
const elementsCompletelyInFrame = new Set([
|
|
|
|
|
...getElementsCompletelyInFrame(allElements, frame, elementsMap),
|
|
|
|
|
...prevElementsInFrame.filter((element) =>
|
|
|
|
|
isElementContainingFrame(allElements, element, frame, elementsMap),
|
|
|
|
|
isElementContainingFrame(element, frame, elementsMap),
|
|
|
|
|
),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
@ -695,61 +694,150 @@ export const isElementInFrame = (
|
|
|
|
|
element: ExcalidrawElement,
|
|
|
|
|
allElementsMap: ElementsMap,
|
|
|
|
|
appState: StaticCanvasAppState,
|
|
|
|
|
opts?: {
|
|
|
|
|
targetFrame?: ExcalidrawFrameLikeElement;
|
|
|
|
|
checkedGroups?: Map<string, boolean>;
|
|
|
|
|
},
|
|
|
|
|
) => {
|
|
|
|
|
const frame = getTargetFrame(element, allElementsMap, appState);
|
|
|
|
|
const frame =
|
|
|
|
|
opts?.targetFrame ?? getTargetFrame(element, allElementsMap, appState);
|
|
|
|
|
|
|
|
|
|
if (!frame) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const _element = isTextElement(element)
|
|
|
|
|
? getContainerElement(element, allElementsMap) || element
|
|
|
|
|
: element;
|
|
|
|
|
|
|
|
|
|
if (frame) {
|
|
|
|
|
// Perf improvement:
|
|
|
|
|
// For an element that's already in a frame, if it's not being dragged
|
|
|
|
|
// then there is no need to refer to geometry (which, yes, is slow) to check if it's in a frame.
|
|
|
|
|
// It has to be in its containing frame.
|
|
|
|
|
if (
|
|
|
|
|
!appState.selectedElementIds[element.id] ||
|
|
|
|
|
!appState.selectedElementsAreBeingDragged
|
|
|
|
|
) {
|
|
|
|
|
return true;
|
|
|
|
|
const setGroupsInFrame = (isInFrame: boolean) => {
|
|
|
|
|
if (opts?.checkedGroups) {
|
|
|
|
|
_element.groupIds.forEach((groupId) => {
|
|
|
|
|
opts.checkedGroups?.set(groupId, isInFrame);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Perf improvement:
|
|
|
|
|
// For an element that's already in a frame, if it's not being dragged
|
|
|
|
|
// then there is no need to refer to geometry (which, yes, is slow) to check if it's in a frame.
|
|
|
|
|
// It has to be in its containing frame.
|
|
|
|
|
if (
|
|
|
|
|
!appState.selectedElementIds[element.id] ||
|
|
|
|
|
!appState.selectedElementsAreBeingDragged
|
|
|
|
|
) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_element.groupIds.length === 0) {
|
|
|
|
|
return elementOverlapsWithFrame(_element, frame, allElementsMap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_element.groupIds.length === 0) {
|
|
|
|
|
return elementOverlapsWithFrame(_element, frame, allElementsMap);
|
|
|
|
|
for (const gid of _element.groupIds) {
|
|
|
|
|
if (opts?.checkedGroups?.has(gid)) {
|
|
|
|
|
return opts.checkedGroups.get(gid)!!;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const allElementsInGroup = new Set(
|
|
|
|
|
_element.groupIds.flatMap((gid) =>
|
|
|
|
|
getElementsInGroup(allElementsMap, gid),
|
|
|
|
|
),
|
|
|
|
|
const allElementsInGroup = new Set(
|
|
|
|
|
_element.groupIds
|
|
|
|
|
.filter((gid) => {
|
|
|
|
|
if (opts?.checkedGroups) {
|
|
|
|
|
return !opts.checkedGroups.has(gid);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
})
|
|
|
|
|
.flatMap((gid) => getElementsInGroup(allElementsMap, gid)),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (appState.editingGroupId && appState.selectedElementsAreBeingDragged) {
|
|
|
|
|
const selectedElements = new Set(
|
|
|
|
|
getSelectedElements(allElementsMap, appState),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (appState.editingGroupId && appState.selectedElementsAreBeingDragged) {
|
|
|
|
|
const selectedElements = new Set(
|
|
|
|
|
getSelectedElements(allElementsMap, appState),
|
|
|
|
|
);
|
|
|
|
|
const editingGroupOverlapsFrame = appState.frameToHighlight !== null;
|
|
|
|
|
|
|
|
|
|
const editingGroupOverlapsFrame = appState.frameToHighlight !== null;
|
|
|
|
|
if (editingGroupOverlapsFrame) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (editingGroupOverlapsFrame) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
selectedElements.forEach((selectedElement) => {
|
|
|
|
|
allElementsInGroup.delete(selectedElement);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
selectedElements.forEach((selectedElement) => {
|
|
|
|
|
allElementsInGroup.delete(selectedElement);
|
|
|
|
|
});
|
|
|
|
|
for (const elementInGroup of allElementsInGroup) {
|
|
|
|
|
if (isFrameLikeElement(elementInGroup)) {
|
|
|
|
|
setGroupsInFrame(false);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const elementInGroup of allElementsInGroup) {
|
|
|
|
|
if (isFrameLikeElement(elementInGroup)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
for (const elementInGroup of allElementsInGroup) {
|
|
|
|
|
if (elementOverlapsWithFrame(elementInGroup, frame, allElementsMap)) {
|
|
|
|
|
setGroupsInFrame(true);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const elementInGroup of allElementsInGroup) {
|
|
|
|
|
if (elementOverlapsWithFrame(elementInGroup, frame, allElementsMap)) {
|
|
|
|
|
return true;
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const shouldApplyFrameClip = (
|
|
|
|
|
element: ExcalidrawElement,
|
|
|
|
|
frame: ExcalidrawFrameLikeElement,
|
|
|
|
|
appState: StaticCanvasAppState,
|
|
|
|
|
elementsMap: ElementsMap,
|
|
|
|
|
checkedGroups?: Map<string, boolean>,
|
|
|
|
|
) => {
|
|
|
|
|
if (!appState.frameRendering || !appState.frameRendering.clip) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// for individual elements, only clip when the element is
|
|
|
|
|
// a. overlapping with the frame, or
|
|
|
|
|
// b. containing the frame, for example when an element is used as a background
|
|
|
|
|
// and is therefore bigger than the frame and completely contains the frame
|
|
|
|
|
const shouldClipElementItself =
|
|
|
|
|
isElementIntersectingFrame(element, frame, elementsMap) ||
|
|
|
|
|
isElementContainingFrame(element, frame, elementsMap);
|
|
|
|
|
|
|
|
|
|
if (shouldClipElementItself) {
|
|
|
|
|
for (const groupId of element.groupIds) {
|
|
|
|
|
checkedGroups?.set(groupId, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if an element is outside the frame, but is part of a group that has some elements
|
|
|
|
|
// "in" the frame, we should clip the element
|
|
|
|
|
if (
|
|
|
|
|
!shouldClipElementItself &&
|
|
|
|
|
element.groupIds.length > 0 &&
|
|
|
|
|
!elementsAreInFrameBounds([element], frame, elementsMap)
|
|
|
|
|
) {
|
|
|
|
|
let shouldClip = false;
|
|
|
|
|
|
|
|
|
|
// if no elements are being dragged, we can skip the geometry check
|
|
|
|
|
// because we know if the element is in the given frame or not
|
|
|
|
|
if (!appState.selectedElementsAreBeingDragged) {
|
|
|
|
|
shouldClip = element.frameId === frame.id;
|
|
|
|
|
for (const groupId of element.groupIds) {
|
|
|
|
|
checkedGroups?.set(groupId, shouldClip);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
shouldClip = isElementInFrame(element, elementsMap, appState, {
|
|
|
|
|
targetFrame: frame,
|
|
|
|
|
checkedGroups,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const groupId of element.groupIds) {
|
|
|
|
|
checkedGroups?.set(groupId, shouldClip);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return shouldClip;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|