feat: stop storing selection element into `appState.draggingElement`

dwelle/dragginEement-rewrite
dwelle 1 year ago
parent 44d9d5fcac
commit add575a419

@ -170,6 +170,7 @@ export const actionFinalize = register({
: activeTool,
activeEmbeddable: null,
draggingElement: null,
selectionElement: null,
multiElement: null,
editingElement: null,
startBoundElement: null,
@ -196,7 +197,9 @@ export const actionFinalize = register({
keyTest: (event, appState) =>
(event.key === KEYS.ESCAPE &&
(appState.editingLinearElement !== null ||
(!appState.draggingElement && appState.multiElement === null))) ||
(!appState.selectionElement &&
!appState.draggingElement &&
appState.multiElement === null))) ||
((event.key === KEYS.ESCAPE || event.key === KEYS.ENTER) &&
appState.multiElement !== null),
PanelComponent: ({ appState, updateData, data }) => (

@ -21,6 +21,7 @@ const writeData = (
!appState.multiElement &&
!appState.resizingElement &&
!appState.editingElement &&
!appState.selectionElement &&
!appState.draggingElement
) {
const data = updater();

@ -3014,7 +3014,8 @@ class App extends React.Component<AppProps, AppState> {
!event.ctrlKey &&
!event.altKey &&
!event.metaKey &&
this.state.draggingElement === null
!this.state.draggingElement &&
!this.state.selectionElement
) {
const shape = findShapeByKey(event.key);
if (shape) {
@ -3343,6 +3344,7 @@ class App extends React.Component<AppProps, AppState> {
this.setState({
draggingElement: null,
selectionElement: null,
editingElement: null,
});
if (this.state.activeTool.locked) {
@ -4421,8 +4423,7 @@ class App extends React.Component<AppProps, AppState> {
// finger is lifted
if (
event.pointerType === "touch" &&
this.state.draggingElement &&
this.state.draggingElement.type === "freedraw"
this.state.draggingElement?.type === "freedraw"
) {
const element = this.state.draggingElement as ExcalidrawFreeDrawElement;
this.updateScene({
@ -4434,6 +4435,7 @@ class App extends React.Component<AppProps, AppState> {
}
: {}),
appState: {
selectionElement: null,
draggingElement: null,
editingElement: null,
startBoundElement: null,
@ -4561,13 +4563,16 @@ class App extends React.Component<AppProps, AppState> {
// retrieve the latest element as the state may be stale
const pendingImageElement =
this.state.pendingImageElementId &&
this.scene.getElement(this.state.pendingImageElementId);
this.scene.getElement<ExcalidrawImageElement>(
this.state.pendingImageElementId,
);
if (!pendingImageElement) {
return;
}
this.setState({
selectionElement: null,
draggingElement: pendingImageElement,
editingElement: pendingImageElement,
pendingImageElementId: null,
@ -5374,6 +5379,7 @@ class App extends React.Component<AppProps, AppState> {
);
this.scene.addNewElement(element);
this.setState({
selectionElement: null,
draggingElement: element,
editingElement: element,
startBoundElement: boundElement,
@ -5593,6 +5599,7 @@ class App extends React.Component<AppProps, AppState> {
this.scene.addNewElement(element);
this.setState({
selectionElement: null,
draggingElement: element,
editingElement: element,
startBoundElement: boundElement,
@ -5667,12 +5674,13 @@ class App extends React.Component<AppProps, AppState> {
if (element.type === "selection") {
this.setState({
selectionElement: element,
draggingElement: element,
draggingElement: null,
});
} else {
this.scene.addNewElement(element);
this.setState({
multiElement: null,
selectionElement: null,
draggingElement: element,
editingElement: element,
});
@ -5705,6 +5713,7 @@ class App extends React.Component<AppProps, AppState> {
this.setState({
multiElement: null,
selectionElement: null,
draggingElement: frame,
editingElement: frame,
});
@ -5763,7 +5772,9 @@ class App extends React.Component<AppProps, AppState> {
if (this.maybeHandleResize(pointerDownState, event)) {
return;
}
this.maybeDragNewGenericElement(pointerDownState, event);
if (!this.maybeUpdateSelectionElement(pointerDownState, event)) {
this.maybeDragNewGenericElement(pointerDownState, event);
}
});
}
@ -5776,7 +5787,9 @@ class App extends React.Component<AppProps, AppState> {
if (this.maybeHandleResize(pointerDownState, event)) {
return;
}
this.maybeDragNewGenericElement(pointerDownState, event);
if (!this.maybeUpdateSelectionElement(pointerDownState, event)) {
this.maybeDragNewGenericElement(pointerDownState, event);
}
});
}
@ -6132,6 +6145,13 @@ class App extends React.Component<AppProps, AppState> {
}
}
pointerDownState.lastCoords.x = pointerCoords.x;
pointerDownState.lastCoords.y = pointerCoords.y;
if (this.maybeHandleBoxSelection(pointerDownState, event)) {
return;
}
// It is very important to read this.state within each move event,
// otherwise we would read a stale one!
const draggingElement = this.state.draggingElement;
@ -6199,105 +6219,6 @@ class App extends React.Component<AppProps, AppState> {
pointerDownState.lastCoords.y = pointerCoords.y;
this.maybeDragNewGenericElement(pointerDownState, event);
}
if (this.state.activeTool.type === "selection") {
pointerDownState.boxSelection.hasOccurred = true;
const elements = this.scene.getNonDeletedElements();
// box-select line editor points
if (this.state.editingLinearElement) {
LinearElementEditor.handleBoxSelection(
event,
this.state,
this.setState.bind(this),
);
// regular box-select
} else {
let shouldReuseSelection = true;
if (!event.shiftKey && isSomeElementSelected(elements, this.state)) {
if (
pointerDownState.withCmdOrCtrl &&
pointerDownState.hit.element
) {
this.setState((prevState) =>
selectGroupsForSelectedElements(
{
...prevState,
selectedElementIds: {
[pointerDownState.hit.element!.id]: true,
},
},
this.scene.getNonDeletedElements(),
prevState,
this,
),
);
} else {
shouldReuseSelection = false;
}
}
const elementsWithinSelection = getElementsWithinSelection(
elements,
draggingElement,
);
this.setState((prevState) => {
const nextSelectedElementIds = {
...(shouldReuseSelection && prevState.selectedElementIds),
...elementsWithinSelection.reduce(
(acc: Record<ExcalidrawElement["id"], true>, element) => {
acc[element.id] = true;
return acc;
},
{},
),
};
if (pointerDownState.hit.element) {
// if using ctrl/cmd, select the hitElement only if we
// haven't box-selected anything else
if (!elementsWithinSelection.length) {
nextSelectedElementIds[pointerDownState.hit.element.id] = true;
} else {
delete nextSelectedElementIds[pointerDownState.hit.element.id];
}
}
prevState = !shouldReuseSelection
? { ...prevState, selectedGroupIds: {}, editingGroupId: null }
: prevState;
return {
...selectGroupsForSelectedElements(
{
editingGroupId: prevState.editingGroupId,
selectedElementIds: nextSelectedElementIds,
},
this.scene.getNonDeletedElements(),
prevState,
this,
),
// select linear element only when we haven't box-selected anything else
selectedLinearElement:
elementsWithinSelection.length === 1 &&
isLinearElement(elementsWithinSelection[0])
? new LinearElementEditor(
elementsWithinSelection[0],
this.scene,
)
: null,
showHyperlinkPopup:
elementsWithinSelection.length === 1 &&
(elementsWithinSelection[0].link ||
isEmbeddableElement(elementsWithinSelection[0]))
? "info"
: false,
};
});
}
}
});
}
@ -6558,6 +6479,7 @@ class App extends React.Component<AppProps, AppState> {
resetCursor(this.interactiveCanvas);
this.setState((prevState) => ({
draggingElement: null,
selectionElement: null,
activeTool: updateActiveTool(this.state, {
type: "selection",
}),
@ -6576,6 +6498,7 @@ class App extends React.Component<AppProps, AppState> {
} else {
this.setState((prevState) => ({
draggingElement: null,
selectionElement: null,
}));
}
}
@ -6595,140 +6518,137 @@ class App extends React.Component<AppProps, AppState> {
);
this.setState({
draggingElement: null,
selectionElement: null,
});
return;
}
if (draggingElement) {
if (pointerDownState.drag.hasOccurred) {
const sceneCoords = viewportCoordsToSceneCoords(
childEvent,
this.state,
);
if (pointerDownState.drag.hasOccurred) {
const sceneCoords = viewportCoordsToSceneCoords(childEvent, this.state);
// when editing the points of a linear element, we check if the
// linear element still is in the frame afterwards
// if not, the linear element will be removed from its frame (if any)
if (
this.state.selectedLinearElement &&
this.state.selectedLinearElement.isDragging
) {
const linearElement = this.scene.getElement(
this.state.selectedLinearElement.elementId,
);
// when editing the points of a linear element, we check if the
// linear element still is in the frame afterwards
// if not, the linear element will be removed from its frame (if any)
if (
this.state.selectedLinearElement &&
this.state.selectedLinearElement.isDragging
) {
const linearElement = this.scene.getElement(
this.state.selectedLinearElement.elementId,
);
if (linearElement?.frameId) {
const frame = getContainingFrame(linearElement);
if (linearElement?.frameId) {
const frame = getContainingFrame(linearElement);
if (frame && linearElement) {
if (!elementOverlapsWithFrame(linearElement, frame)) {
// remove the linear element from all groups
// before removing it from the frame as well
mutateElement(linearElement, {
groupIds: [],
});
if (frame && linearElement) {
if (!elementOverlapsWithFrame(linearElement, frame)) {
// remove the linear element from all groups
// before removing it from the frame as well
mutateElement(linearElement, {
groupIds: [],
});
this.scene.replaceAllElements(
removeElementsFromFrame(
this.scene.getElementsIncludingDeleted(),
[linearElement],
this.state,
),
);
}
this.scene.replaceAllElements(
removeElementsFromFrame(
this.scene.getElementsIncludingDeleted(),
[linearElement],
this.state,
),
);
}
}
} else {
// update the relationships between selected elements and frames
const topLayerFrame =
this.getTopLayerFrameAtSceneCoords(sceneCoords);
const selectedElements = this.scene.getSelectedElements(this.state);
let nextElements = this.scene.getElementsIncludingDeleted();
const updateGroupIdsAfterEditingGroup = (
elements: ExcalidrawElement[],
) => {
if (elements.length > 0) {
for (const element of elements) {
const index = element.groupIds.indexOf(
this.state.editingGroupId!,
);
}
} else {
// update the relationships between selected elements and frames
const topLayerFrame = this.getTopLayerFrameAtSceneCoords(sceneCoords);
const selectedElements = this.scene.getSelectedElements(this.state);
let nextElements = this.scene.getElementsIncludingDeleted();
const updateGroupIdsAfterEditingGroup = (
elements: ExcalidrawElement[],
) => {
if (elements.length > 0) {
for (const element of elements) {
const index = element.groupIds.indexOf(
this.state.editingGroupId!,
);
mutateElement(
element,
{
groupIds: element.groupIds.slice(0, index),
},
false,
);
}
nextElements.forEach((element) => {
if (
element.groupIds.length &&
getElementsInGroup(
nextElements,
element.groupIds[element.groupIds.length - 1],
).length < 2
) {
mutateElement(
element,
{
groupIds: element.groupIds.slice(0, index),
groupIds: [],
},
false,
);
}
});
nextElements.forEach((element) => {
if (
element.groupIds.length &&
getElementsInGroup(
nextElements,
element.groupIds[element.groupIds.length - 1],
).length < 2
) {
mutateElement(
element,
{
groupIds: [],
},
false,
);
}
});
this.setState({
editingGroupId: null,
});
}
};
if (
topLayerFrame &&
!this.state.selectedElementIds[topLayerFrame.id]
) {
const elementsToAdd = selectedElements.filter(
(element) =>
element.frameId !== topLayerFrame.id &&
isElementInFrame(element, nextElements, this.state),
);
if (this.state.editingGroupId) {
updateGroupIdsAfterEditingGroup(elementsToAdd);
}
this.setState({
editingGroupId: null,
});
}
};
nextElements = addElementsToFrame(
nextElements,
elementsToAdd,
topLayerFrame,
);
} else if (!topLayerFrame) {
if (this.state.editingGroupId) {
const elementsToRemove = selectedElements.filter(
(element) =>
element.frameId &&
!isElementInFrame(element, nextElements, this.state),
);
if (
topLayerFrame &&
!this.state.selectedElementIds[topLayerFrame.id]
) {
const elementsToAdd = selectedElements.filter(
(element) =>
element.frameId !== topLayerFrame.id &&
isElementInFrame(element, nextElements, this.state),
);
updateGroupIdsAfterEditingGroup(elementsToRemove);
}
if (this.state.editingGroupId) {
updateGroupIdsAfterEditingGroup(elementsToAdd);
}
nextElements = updateFrameMembershipOfSelectedElements(
nextElements = addElementsToFrame(
nextElements,
this.state,
this,
elementsToAdd,
topLayerFrame,
);
} else if (!topLayerFrame) {
if (this.state.editingGroupId) {
const elementsToRemove = selectedElements.filter(
(element) =>
element.frameId &&
!isElementInFrame(element, nextElements, this.state),
);
this.scene.replaceAllElements(nextElements);
updateGroupIdsAfterEditingGroup(elementsToRemove);
}
}
nextElements = updateFrameMembershipOfSelectedElements(
nextElements,
this.state,
this,
);
this.scene.replaceAllElements(nextElements);
}
}
if (draggingElement) {
if (draggingElement.type === "frame") {
const elementsInsideFrame = getElementsInNewFrame(
this.scene.getElementsIncludingDeleted(),
@ -7032,8 +6952,7 @@ class App extends React.Component<AppProps, AppState> {
if (
!activeTool.locked &&
activeTool.type !== "freedraw" &&
draggingElement &&
draggingElement.type !== "selection"
draggingElement
) {
this.setState((prevState) => ({
selectedElementIds: makeNextSelectedElementIds(
@ -7072,12 +6991,14 @@ class App extends React.Component<AppProps, AppState> {
resetCursor(this.interactiveCanvas);
this.setState({
draggingElement: null,
selectionElement: null,
suggestedBindings: [],
activeTool: updateActiveTool(this.state, { type: "selection" }),
});
} else {
this.setState({
draggingElement: null,
selectionElement: null,
suggestedBindings: [],
});
}
@ -7876,6 +7797,139 @@ class App extends React.Component<AppProps, AppState> {
);
};
private maybeUpdateSelectionElement = (
pointerDownState: PointerDownState,
event: PointerEvent | KeyboardEvent,
): boolean => {
const { selectionElement } = this.state;
if (!selectionElement || this.state.activeTool.type !== "selection") {
return false;
}
const pointerCoords = pointerDownState.lastCoords;
dragNewElement(
selectionElement,
this.state.activeTool.type,
pointerDownState.origin.x,
pointerDownState.origin.y,
pointerCoords.x,
pointerCoords.y,
distance(pointerDownState.origin.x, pointerCoords.x),
distance(pointerDownState.origin.y, pointerCoords.y),
shouldMaintainAspectRatio(event),
shouldResizeFromCenter(event),
);
return true;
};
private maybeHandleBoxSelection = (
pointerDownState: PointerDownState,
event: PointerEvent,
): boolean => {
const { selectionElement } = this.state;
if (!selectionElement || this.state.activeTool.type !== "selection") {
return false;
}
this.maybeUpdateSelectionElement(pointerDownState, event);
pointerDownState.boxSelection.hasOccurred = true;
const elements = this.scene.getNonDeletedElements();
// box-select line editor points
if (this.state.editingLinearElement) {
LinearElementEditor.handleBoxSelection(
event,
this.state,
this.setState.bind(this),
);
// regular box-select
} else {
let shouldReuseSelection = true;
if (!event.shiftKey && isSomeElementSelected(elements, this.state)) {
if (pointerDownState.withCmdOrCtrl && pointerDownState.hit.element) {
this.setState((prevState) =>
selectGroupsForSelectedElements(
{
...prevState,
selectedElementIds: {
[pointerDownState.hit.element!.id]: true,
},
},
this.scene.getNonDeletedElements(),
prevState,
this,
),
);
} else {
shouldReuseSelection = false;
}
}
const elementsWithinSelection = getElementsWithinSelection(
elements,
selectionElement,
);
this.setState((prevState) => {
const nextSelectedElementIds = {
...(shouldReuseSelection && prevState.selectedElementIds),
...elementsWithinSelection.reduce(
(acc: Record<ExcalidrawElement["id"], true>, element) => {
acc[element.id] = true;
return acc;
},
{},
),
};
if (pointerDownState.hit.element) {
// if using ctrl/cmd, select the hitElement only if we
// haven't box-selected anything else
if (!elementsWithinSelection.length) {
nextSelectedElementIds[pointerDownState.hit.element.id] = true;
} else {
delete nextSelectedElementIds[pointerDownState.hit.element.id];
}
}
prevState = !shouldReuseSelection
? { ...prevState, selectedGroupIds: {}, editingGroupId: null }
: prevState;
return {
...selectGroupsForSelectedElements(
{
editingGroupId: prevState.editingGroupId,
selectedElementIds: nextSelectedElementIds,
},
this.scene.getNonDeletedElements(),
prevState,
this,
),
// select linear element only when we haven't box-selected anything else
selectedLinearElement:
elementsWithinSelection.length === 1 &&
isLinearElement(elementsWithinSelection[0])
? new LinearElementEditor(elementsWithinSelection[0], this.scene)
: null,
showHyperlinkPopup:
elementsWithinSelection.length === 1 &&
(elementsWithinSelection[0].link ||
isEmbeddableElement(elementsWithinSelection[0]))
? "info"
: false,
};
});
}
return true;
};
private maybeDragNewGenericElement = (
pointerDownState: PointerDownState,
event: MouseEvent | KeyboardEvent,
@ -7885,93 +7939,73 @@ class App extends React.Component<AppProps, AppState> {
if (!draggingElement) {
return;
}
if (
draggingElement.type === "selection" &&
this.state.activeTool.type !== "eraser"
) {
dragNewElement(
draggingElement,
this.state.activeTool.type,
pointerDownState.origin.x,
pointerDownState.origin.y,
pointerCoords.x,
pointerCoords.y,
distance(pointerDownState.origin.x, pointerCoords.x),
distance(pointerDownState.origin.y, pointerCoords.y),
shouldMaintainAspectRatio(event),
shouldResizeFromCenter(event),
);
} else {
let [gridX, gridY] = getGridPoint(
pointerCoords.x,
pointerCoords.y,
event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
);
let [gridX, gridY] = getGridPoint(
pointerCoords.x,
pointerCoords.y,
event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
);
const image =
isInitializedImageElement(draggingElement) &&
this.imageCache.get(draggingElement.fileId)?.image;
const aspectRatio =
image && !(image instanceof Promise)
? image.width / image.height
: null;
const image =
isInitializedImageElement(draggingElement) &&
this.imageCache.get(draggingElement.fileId)?.image;
const aspectRatio =
image && !(image instanceof Promise) ? image.width / image.height : null;
this.maybeCacheReferenceSnapPoints(event, [draggingElement]);
this.maybeCacheReferenceSnapPoints(event, [draggingElement]);
const { snapOffset, snapLines } = snapNewElement(
draggingElement,
this.state,
event,
{
x:
pointerDownState.originInGrid.x +
(this.state.originSnapOffset?.x ?? 0),
y:
pointerDownState.originInGrid.y +
(this.state.originSnapOffset?.y ?? 0),
},
{
x: gridX - pointerDownState.originInGrid.x,
y: gridY - pointerDownState.originInGrid.y,
},
);
const { snapOffset, snapLines } = snapNewElement(
draggingElement,
this.state,
event,
{
x:
pointerDownState.originInGrid.x +
(this.state.originSnapOffset?.x ?? 0),
y:
pointerDownState.originInGrid.y +
(this.state.originSnapOffset?.y ?? 0),
},
{
x: gridX - pointerDownState.originInGrid.x,
y: gridY - pointerDownState.originInGrid.y,
},
);
gridX += snapOffset.x;
gridY += snapOffset.y;
gridX += snapOffset.x;
gridY += snapOffset.y;
this.setState({
snapLines,
});
this.setState({
snapLines,
});
dragNewElement(
draggingElement,
this.state.activeTool.type,
pointerDownState.originInGrid.x,
pointerDownState.originInGrid.y,
gridX,
gridY,
distance(pointerDownState.originInGrid.x, gridX),
distance(pointerDownState.originInGrid.y, gridY),
isImageElement(draggingElement)
? !shouldMaintainAspectRatio(event)
: shouldMaintainAspectRatio(event),
shouldResizeFromCenter(event),
aspectRatio,
this.state.originSnapOffset,
);
dragNewElement(
draggingElement,
this.state.activeTool.type,
pointerDownState.originInGrid.x,
pointerDownState.originInGrid.y,
gridX,
gridY,
distance(pointerDownState.originInGrid.x, gridX),
distance(pointerDownState.originInGrid.y, gridY),
isImageElement(draggingElement)
? !shouldMaintainAspectRatio(event)
: shouldMaintainAspectRatio(event),
shouldResizeFromCenter(event),
aspectRatio,
this.state.originSnapOffset,
);
this.maybeSuggestBindingForAll([draggingElement]);
this.maybeSuggestBindingForAll([draggingElement]);
// highlight elements that are to be added to frames on frames creation
if (this.state.activeTool.type === "frame") {
this.setState({
elementsToHighlight: getElementsInResizingFrame(
this.scene.getNonDeletedElements(),
draggingElement as ExcalidrawFrameElement,
this.state,
),
});
}
// highlight elements that are to be added to frames on frames creation
if (this.state.activeTool.type === "frame") {
this.setState({
elementsToHighlight: getElementsInResizingFrame(
this.scene.getNonDeletedElements(),
draggingElement as ExcalidrawFrameElement,
this.state,
),
});
}
};

@ -82,8 +82,9 @@ const getHints = ({ appState, isMobile, device, app }: HintViewerProps) => {
if (activeTool.type === "selection") {
if (
appState.draggingElement?.type === "selection" &&
appState.selectionElement &&
!selectedElements.length &&
!appState.draggingElement &&
!appState.editingElement &&
!appState.editingLinearElement
) {

@ -210,6 +210,7 @@ export const Hyperlink = ({
};
const { x, y } = getCoordsForPopover(element, appState);
if (
appState.selectionElement ||
appState.draggingElement ||
appState.resizingElement ||
appState.isRotating ||

@ -134,10 +134,7 @@ export class LinearElementEditor {
appState: AppState,
setState: React.Component<any, AppState>["setState"],
) {
if (
!appState.editingLinearElement ||
appState.draggingElement?.type !== "selection"
) {
if (!appState.editingLinearElement || !appState.selectionElement) {
return false;
}
const { editingLinearElement } = appState;
@ -149,7 +146,7 @@ export class LinearElementEditor {
}
const [selectionX1, selectionY1, selectionX2, selectionY2] =
getElementAbsoluteCoords(appState.draggingElement);
getElementAbsoluteCoords(appState.selectionElement);
const pointsSceneCoords =
LinearElementEditor.getPointsGlobalCoordinates(element);

@ -5152,35 +5152,7 @@ exports[`regression tests > deselects group of selected elements on pointer down
"currentItemTextAlign": "left",
"cursorButton": "down",
"defaultSidebarDockedPreference": false,
"draggingElement": {
"angle": 0,
"backgroundColor": "transparent",
"boundElements": null,
"fillStyle": "hachure",
"frameId": null,
"groupIds": [],
"height": 0,
"id": "id3",
"isDeleted": false,
"link": null,
"locked": false,
"opacity": 100,
"roughness": 1,
"roundness": {
"type": 2,
},
"seed": 400692809,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "selection",
"updated": 1,
"version": 1,
"versionNonce": 0,
"width": 0,
"x": 500,
"y": 500,
},
"draggingElement": null,
"editingElement": null,
"editingFrame": null,
"editingGroupId": null,
@ -5449,35 +5421,7 @@ exports[`regression tests > deselects group of selected elements on pointer up w
"currentItemTextAlign": "left",
"cursorButton": "up",
"defaultSidebarDockedPreference": false,
"draggingElement": {
"angle": 0,
"backgroundColor": "transparent",
"boundElements": null,
"fillStyle": "hachure",
"frameId": null,
"groupIds": [],
"height": 0,
"id": "id3",
"isDeleted": false,
"link": null,
"locked": false,
"opacity": 100,
"roughness": 1,
"roundness": {
"type": 2,
},
"seed": 400692809,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "selection",
"updated": 1,
"version": 1,
"versionNonce": 0,
"width": 0,
"x": 50,
"y": 50,
},
"draggingElement": null,
"editingElement": null,
"editingFrame": null,
"editingGroupId": null,
@ -5718,35 +5662,7 @@ exports[`regression tests > deselects selected element on pointer down when poin
"currentItemTextAlign": "left",
"cursorButton": "down",
"defaultSidebarDockedPreference": false,
"draggingElement": {
"angle": 0,
"backgroundColor": "transparent",
"boundElements": null,
"fillStyle": "hachure",
"frameId": null,
"groupIds": [],
"height": 0,
"id": "id1",
"isDeleted": false,
"link": null,
"locked": false,
"opacity": 100,
"roughness": 1,
"roundness": {
"type": 2,
},
"seed": 2019559783,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "selection",
"updated": 1,
"version": 1,
"versionNonce": 0,
"width": 0,
"x": 110,
"y": 110,
},
"draggingElement": null,
"editingElement": null,
"editingFrame": null,
"editingGroupId": null,
@ -16262,35 +16178,7 @@ exports[`regression tests > switches from group of selected elements to another
"currentItemTextAlign": "left",
"cursorButton": "down",
"defaultSidebarDockedPreference": false,
"draggingElement": {
"angle": 0,
"backgroundColor": "transparent",
"boundElements": null,
"fillStyle": "hachure",
"frameId": null,
"groupIds": [],
"height": 0,
"id": "id4",
"isDeleted": false,
"link": null,
"locked": false,
"opacity": 100,
"roughness": 1,
"roundness": {
"type": 2,
},
"seed": 493213705,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "selection",
"updated": 1,
"version": 1,
"versionNonce": 0,
"width": 0,
"x": 0,
"y": 0,
},
"draggingElement": null,
"editingElement": null,
"editingFrame": null,
"editingGroupId": null,
@ -16662,35 +16550,7 @@ exports[`regression tests > switches selected element on pointer down > [end of
"currentItemTextAlign": "left",
"cursorButton": "down",
"defaultSidebarDockedPreference": false,
"draggingElement": {
"angle": 0,
"backgroundColor": "transparent",
"boundElements": null,
"fillStyle": "hachure",
"frameId": null,
"groupIds": [],
"height": 0,
"id": "id2",
"isDeleted": false,
"link": null,
"locked": false,
"opacity": 100,
"roughness": 1,
"roundness": {
"type": 2,
},
"seed": 238820263,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 1,
"type": "selection",
"updated": 1,
"version": 1,
"versionNonce": 0,
"width": 0,
"x": 0,
"y": 0,
},
"draggingElement": null,
"editingElement": null,
"editingFrame": null,
"editingGroupId": null,

@ -17,6 +17,7 @@ import {
StrokeRoundness,
ExcalidrawFrameElement,
ExcalidrawEmbeddableElement,
ExcalidrawSelectionElement,
} from "./element/types";
import { Point as RoughPoint } from "roughjs/bin/geometry";
import { LinearElementEditor } from "./element/linearElementEditor";
@ -182,10 +183,25 @@ export type AppState = {
element: NonDeletedExcalidrawElement;
state: "hover" | "active";
} | null;
draggingElement: NonDeletedExcalidrawElement | null;
/** element that's being dragged or created */
draggingElement: Exclude<
NonDeletedExcalidrawElement,
ExcalidrawSelectionElement
> | null;
/**
* Element that's being resized.
* NOTE not set when resizing a group or linear element
*/
resizingElement: NonDeletedExcalidrawElement | null;
/** multi-point linear element when it's being created */
multiElement: NonDeleted<ExcalidrawLinearElement> | null;
selectionElement: NonDeletedExcalidrawElement | null;
/**
* The selection box (we currently use an excalidraw element).
*
* Checking for this attribute is a good way to determine whether the user is
* selecting.
*/
selectionElement: ExcalidrawSelectionElement | null;
isBindingEnabled: boolean;
startBoundElement: NonDeleted<ExcalidrawBindableElement> | null;
suggestedBindings: SuggestedBinding[];
@ -198,9 +214,14 @@ export type AppState = {
};
editingFrame: string | null;
elementsToHighlight: NonDeleted<ExcalidrawElement>[] | null;
// element being edited, but not necessarily added to elements array yet
// (e.g. text element when typing into the input)
/**
* Text that's being element, or new element being created.
*/
editingElement: NonDeletedExcalidrawElement | null;
/**
* Linear element that's being edited (when in the linear element editor).
* Not set when creating multi-point linear element.
*/
editingLinearElement: LinearElementEditor | null;
activeTool: {
/**

Loading…
Cancel
Save