Refactoring in pointer down event handler (#1880)
* Refactor: Move context menu touch device handling * Refactor: Move more stuff out of pointer down * Refactor: Move last coords into an object * Refactor: Move scrollbar handling out of pointer down * Refactor: simplify resizing in pointer down * Refactor: further simplify resizing in pointer down * Refactor: clarify clearing selection code * Refactor: move out clearing selection from pointer down * Refactor: further simplify deselection in pointer down
This commit is contained in:
parent
d5e7d08586
commit
5d7020cce6
@ -1900,28 +1900,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
) => {
|
) => {
|
||||||
event.persist();
|
event.persist();
|
||||||
|
|
||||||
// deal with opening context menu on touch devices
|
this.maybeOpenContextMenuAfterPointerDownOnTouchDevices(event);
|
||||||
if (event.pointerType === "touch") {
|
this.maybeCleanupAfterMissingPointerUp(event);
|
||||||
touchMoving = false;
|
|
||||||
|
|
||||||
// open the context menu with the first touch's clientX and clientY
|
|
||||||
// if the touch is not moving
|
|
||||||
touchTimeout = window.setTimeout(() => {
|
|
||||||
if (!touchMoving) {
|
|
||||||
this.openContextMenu({
|
|
||||||
clientX: event.clientX,
|
|
||||||
clientY: event.clientY,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, TOUCH_CTX_MENU_TIMEOUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastPointerUp !== null) {
|
|
||||||
// Unfortunately, sometimes we don't get a pointerup after a pointerdown,
|
|
||||||
// this can happen when a contextual menu or alert is triggered. In order to avoid
|
|
||||||
// being in a weird state, we clean up on the next pointerdown
|
|
||||||
lastPointerUp(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPanning) {
|
if (isPanning) {
|
||||||
return;
|
return;
|
||||||
@ -1933,89 +1913,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
});
|
});
|
||||||
this.savePointer(event.clientX, event.clientY, "down");
|
this.savePointer(event.clientX, event.clientY, "down");
|
||||||
|
|
||||||
// pan canvas on wheel button drag or space+drag
|
if (this.handleCanvasPanUsingWheelOrSpaceDrag(event)) {
|
||||||
if (
|
|
||||||
gesture.pointers.size === 0 &&
|
|
||||||
(event.button === POINTER_BUTTON.WHEEL ||
|
|
||||||
(event.button === POINTER_BUTTON.MAIN && isHoldingSpace))
|
|
||||||
) {
|
|
||||||
isPanning = true;
|
|
||||||
|
|
||||||
let nextPastePrevented = false;
|
|
||||||
const isLinux = /Linux/.test(window.navigator.platform);
|
|
||||||
|
|
||||||
document.documentElement.style.cursor = CURSOR_TYPE.GRABBING;
|
|
||||||
let { clientX: lastX, clientY: lastY } = event;
|
|
||||||
const onPointerMove = withBatchedUpdates((event: PointerEvent) => {
|
|
||||||
const deltaX = lastX - event.clientX;
|
|
||||||
const deltaY = lastY - event.clientY;
|
|
||||||
lastX = event.clientX;
|
|
||||||
lastY = event.clientY;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Prevent paste event if we move while middle clicking on Linux.
|
|
||||||
* See issue #1383.
|
|
||||||
*/
|
|
||||||
if (
|
|
||||||
isLinux &&
|
|
||||||
!nextPastePrevented &&
|
|
||||||
(Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1)
|
|
||||||
) {
|
|
||||||
nextPastePrevented = true;
|
|
||||||
|
|
||||||
/* Prevent the next paste event */
|
|
||||||
const preventNextPaste = (event: ClipboardEvent) => {
|
|
||||||
document.body.removeEventListener(EVENT.PASTE, preventNextPaste);
|
|
||||||
event.stopPropagation();
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Reenable next paste in case of disabled middle click paste for
|
|
||||||
* any reason:
|
|
||||||
* - rigth click paste
|
|
||||||
* - empty clipboard
|
|
||||||
*/
|
|
||||||
const enableNextPaste = () => {
|
|
||||||
setTimeout(() => {
|
|
||||||
document.body.removeEventListener(EVENT.PASTE, preventNextPaste);
|
|
||||||
window.removeEventListener(EVENT.POINTER_UP, enableNextPaste);
|
|
||||||
}, 100);
|
|
||||||
};
|
|
||||||
|
|
||||||
document.body.addEventListener(EVENT.PASTE, preventNextPaste);
|
|
||||||
window.addEventListener(EVENT.POINTER_UP, enableNextPaste);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
scrollX: normalizeScroll(
|
|
||||||
this.state.scrollX - deltaX / this.state.zoom,
|
|
||||||
),
|
|
||||||
scrollY: normalizeScroll(
|
|
||||||
this.state.scrollY - deltaY / this.state.zoom,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const teardown = withBatchedUpdates(
|
|
||||||
(lastPointerUp = () => {
|
|
||||||
lastPointerUp = null;
|
|
||||||
isPanning = false;
|
|
||||||
if (!isHoldingSpace) {
|
|
||||||
setCursorForShape(this.state.elementType);
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
cursorButton: "up",
|
|
||||||
});
|
|
||||||
this.savePointer(event.clientX, event.clientY, "up");
|
|
||||||
window.removeEventListener(EVENT.POINTER_MOVE, onPointerMove);
|
|
||||||
window.removeEventListener(EVENT.POINTER_UP, teardown);
|
|
||||||
window.removeEventListener(EVENT.BLUR, teardown);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
window.addEventListener(EVENT.BLUR, teardown);
|
|
||||||
window.addEventListener(EVENT.POINTER_MOVE, onPointerMove, {
|
|
||||||
passive: true,
|
|
||||||
});
|
|
||||||
window.addEventListener(EVENT.POINTER_UP, teardown);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2027,18 +1925,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
gesture.pointers.set(event.pointerId, {
|
this.updateGestureOnPointerDown(event);
|
||||||
x: event.clientX,
|
|
||||||
y: event.clientY,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (gesture.pointers.size === 2) {
|
|
||||||
gesture.lastCenter = getCenter(gesture.pointers);
|
|
||||||
gesture.initialScale = this.state.zoom;
|
|
||||||
gesture.initialDistance = getDistance(
|
|
||||||
Array.from(gesture.pointers.values()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// fixes pointermove causing selection of UI texts #32
|
// fixes pointermove causing selection of UI texts #32
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -2055,10 +1942,15 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle scrollbars dragging
|
// Handle scrollbars dragging
|
||||||
|
const isOverScrollBarsNow = isOverScrollBars(
|
||||||
|
currentScrollBars,
|
||||||
|
event.clientX,
|
||||||
|
event.clientY,
|
||||||
|
);
|
||||||
const {
|
const {
|
||||||
isOverHorizontalScrollBar,
|
isOverHorizontalScrollBar,
|
||||||
isOverVerticalScrollBar,
|
isOverVerticalScrollBar,
|
||||||
} = isOverScrollBars(currentScrollBars, event.clientX, event.clientY);
|
} = isOverScrollBarsNow;
|
||||||
|
|
||||||
const { x, y } = viewportCoordsToSceneCoords(
|
const { x, y } = viewportCoordsToSceneCoords(
|
||||||
event,
|
event,
|
||||||
@ -2066,58 +1958,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
this.canvas,
|
this.canvas,
|
||||||
window.devicePixelRatio,
|
window.devicePixelRatio,
|
||||||
);
|
);
|
||||||
let lastX = x;
|
const lastCoords = { x, y };
|
||||||
let lastY = y;
|
|
||||||
|
|
||||||
if (
|
if (this.handleDraggingScrollBar(event, lastCoords, isOverScrollBarsNow)) {
|
||||||
(isOverHorizontalScrollBar || isOverVerticalScrollBar) &&
|
|
||||||
!this.state.multiElement
|
|
||||||
) {
|
|
||||||
isDraggingScrollBar = true;
|
|
||||||
lastX = event.clientX;
|
|
||||||
lastY = event.clientY;
|
|
||||||
const onPointerMove = withBatchedUpdates((event: PointerEvent) => {
|
|
||||||
const target = event.target;
|
|
||||||
if (!(target instanceof HTMLElement)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isOverHorizontalScrollBar) {
|
|
||||||
const x = event.clientX;
|
|
||||||
const dx = x - lastX;
|
|
||||||
this.setState({
|
|
||||||
scrollX: normalizeScroll(this.state.scrollX - dx / this.state.zoom),
|
|
||||||
});
|
|
||||||
lastX = x;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isOverVerticalScrollBar) {
|
|
||||||
const y = event.clientY;
|
|
||||||
const dy = y - lastY;
|
|
||||||
this.setState({
|
|
||||||
scrollY: normalizeScroll(this.state.scrollY - dy / this.state.zoom),
|
|
||||||
});
|
|
||||||
lastY = y;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const onPointerUp = withBatchedUpdates(() => {
|
|
||||||
isDraggingScrollBar = false;
|
|
||||||
setCursorForShape(this.state.elementType);
|
|
||||||
lastPointerUp = null;
|
|
||||||
this.setState({
|
|
||||||
cursorButton: "up",
|
|
||||||
});
|
|
||||||
this.savePointer(event.clientX, event.clientY, "up");
|
|
||||||
window.removeEventListener(EVENT.POINTER_MOVE, onPointerMove);
|
|
||||||
window.removeEventListener(EVENT.POINTER_UP, onPointerUp);
|
|
||||||
});
|
|
||||||
|
|
||||||
lastPointerUp = onPointerUp;
|
|
||||||
|
|
||||||
window.addEventListener(EVENT.POINTER_MOVE, onPointerMove);
|
|
||||||
window.addEventListener(EVENT.POINTER_UP, onPointerUp);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2142,6 +1985,14 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
let hitElement: ExcalidrawElement | null = null;
|
let hitElement: ExcalidrawElement | null = null;
|
||||||
let hitElementWasAddedToSelection = false;
|
let hitElementWasAddedToSelection = false;
|
||||||
|
|
||||||
|
if (this.state.elementType !== "selection") {
|
||||||
|
this.setState({
|
||||||
|
selectedElementIds: {},
|
||||||
|
selectedGroupIds: {},
|
||||||
|
editingGroupId: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (this.state.elementType === "selection") {
|
if (this.state.elementType === "selection") {
|
||||||
const elements = globalSceneState.getElements();
|
const elements = globalSceneState.getElements();
|
||||||
const selectedElements = getSelectedElements(elements, this.state);
|
const selectedElements = getSelectedElements(elements, this.state);
|
||||||
@ -2154,17 +2005,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
this.state.zoom,
|
this.state.zoom,
|
||||||
event.pointerType,
|
event.pointerType,
|
||||||
);
|
);
|
||||||
if (elementWithResizeHandler) {
|
if (elementWithResizeHandler != null) {
|
||||||
this.setState({
|
this.setState({ resizingElement: elementWithResizeHandler.element });
|
||||||
resizingElement: elementWithResizeHandler
|
|
||||||
? elementWithResizeHandler.element
|
|
||||||
: null,
|
|
||||||
});
|
|
||||||
resizeHandle = elementWithResizeHandler.resizeHandle;
|
resizeHandle = elementWithResizeHandler.resizeHandle;
|
||||||
document.documentElement.style.cursor = getCursorForResizingElement(
|
|
||||||
elementWithResizeHandler,
|
|
||||||
);
|
|
||||||
isResizingElements = true;
|
|
||||||
}
|
}
|
||||||
} else if (selectedElements.length > 1) {
|
} else if (selectedElements.length > 1) {
|
||||||
resizeHandle = getResizeHandlerFromCoords(
|
resizeHandle = getResizeHandlerFromCoords(
|
||||||
@ -2174,14 +2017,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
this.state.zoom,
|
this.state.zoom,
|
||||||
event.pointerType,
|
event.pointerType,
|
||||||
);
|
);
|
||||||
if (resizeHandle) {
|
|
||||||
document.documentElement.style.cursor = getCursorForResizingElement({
|
|
||||||
resizeHandle,
|
|
||||||
});
|
|
||||||
isResizingElements = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (isResizingElements) {
|
if (resizeHandle) {
|
||||||
|
document.documentElement.style.cursor = getCursorForResizingElement({
|
||||||
|
resizeHandle,
|
||||||
|
});
|
||||||
|
isResizingElements = true;
|
||||||
resizeOffsetXY = getResizeOffsetXY(
|
resizeOffsetXY = getResizeOffsetXY(
|
||||||
resizeHandle,
|
resizeHandle,
|
||||||
selectedElements,
|
selectedElements,
|
||||||
@ -2198,8 +2039,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
selectedElements[0],
|
selectedElements[0],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
if (!isResizingElements) {
|
|
||||||
if (this.state.editingLinearElement) {
|
if (this.state.editingLinearElement) {
|
||||||
const ret = LinearElementEditor.handlePointerDown(
|
const ret = LinearElementEditor.handlePointerDown(
|
||||||
event,
|
event,
|
||||||
@ -2222,27 +2062,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
hitElement ||
|
hitElement ||
|
||||||
getElementAtPosition(elements, this.state, x, y, this.state.zoom);
|
getElementAtPosition(elements, this.state, x, y, this.state.zoom);
|
||||||
|
|
||||||
// clear selection if shift is not clicked
|
this.maybeClearSelectionWhenHittingElement(event, hitElement);
|
||||||
if (
|
|
||||||
!(hitElement && this.state.selectedElementIds[hitElement.id]) &&
|
|
||||||
!event.shiftKey
|
|
||||||
) {
|
|
||||||
this.setState((prevState) => ({
|
|
||||||
selectedElementIds: {},
|
|
||||||
selectedGroupIds: {},
|
|
||||||
editingGroupId:
|
|
||||||
prevState.editingGroupId &&
|
|
||||||
hitElement &&
|
|
||||||
isElementInGroup(hitElement, prevState.editingGroupId)
|
|
||||||
? prevState.editingGroupId
|
|
||||||
: null,
|
|
||||||
}));
|
|
||||||
const { selectedElementIds } = this.state;
|
|
||||||
this.setState({
|
|
||||||
selectedElementIds: {},
|
|
||||||
previousSelectedElementIds: selectedElementIds,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we click on something
|
// If we click on something
|
||||||
if (hitElement) {
|
if (hitElement) {
|
||||||
@ -2289,12 +2109,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
previousSelectedElementIds: selectedElementIds,
|
previousSelectedElementIds: selectedElementIds,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
selectedElementIds: {},
|
|
||||||
selectedGroupIds: {},
|
|
||||||
editingGroupId: null,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.elementType === "text") {
|
if (this.state.elementType === "text") {
|
||||||
@ -2457,21 +2271,21 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
|
|
||||||
if (isOverHorizontalScrollBar) {
|
if (isOverHorizontalScrollBar) {
|
||||||
const x = event.clientX;
|
const x = event.clientX;
|
||||||
const dx = x - lastX;
|
const dx = x - lastCoords.x;
|
||||||
this.setState({
|
this.setState({
|
||||||
scrollX: normalizeScroll(this.state.scrollX - dx / this.state.zoom),
|
scrollX: normalizeScroll(this.state.scrollX - dx / this.state.zoom),
|
||||||
});
|
});
|
||||||
lastX = x;
|
lastCoords.x = x;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOverVerticalScrollBar) {
|
if (isOverVerticalScrollBar) {
|
||||||
const y = event.clientY;
|
const y = event.clientY;
|
||||||
const dy = y - lastY;
|
const dy = y - lastCoords.y;
|
||||||
this.setState({
|
this.setState({
|
||||||
scrollY: normalizeScroll(this.state.scrollY - dy / this.state.zoom),
|
scrollY: normalizeScroll(this.state.scrollY - dy / this.state.zoom),
|
||||||
});
|
});
|
||||||
lastY = y;
|
lastCoords.y = y;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2534,13 +2348,13 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
(appState) => this.setState(appState),
|
(appState) => this.setState(appState),
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
lastX,
|
lastCoords.x,
|
||||||
lastY,
|
lastCoords.y,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (didDrag) {
|
if (didDrag) {
|
||||||
lastX = x;
|
lastCoords.x = x;
|
||||||
lastY = y;
|
lastCoords.y = y;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2905,6 +2719,241 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
window.addEventListener(EVENT.POINTER_UP, onPointerUp);
|
window.addEventListener(EVENT.POINTER_UP, onPointerUp);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private maybeOpenContextMenuAfterPointerDownOnTouchDevices = (
|
||||||
|
event: React.PointerEvent<HTMLCanvasElement>,
|
||||||
|
): void => {
|
||||||
|
// deal with opening context menu on touch devices
|
||||||
|
if (event.pointerType === "touch") {
|
||||||
|
touchMoving = false;
|
||||||
|
|
||||||
|
// open the context menu with the first touch's clientX and clientY
|
||||||
|
// if the touch is not moving
|
||||||
|
touchTimeout = window.setTimeout(() => {
|
||||||
|
if (!touchMoving) {
|
||||||
|
this.openContextMenu({
|
||||||
|
clientX: event.clientX,
|
||||||
|
clientY: event.clientY,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, TOUCH_CTX_MENU_TIMEOUT);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private maybeCleanupAfterMissingPointerUp(
|
||||||
|
event: React.PointerEvent<HTMLCanvasElement>,
|
||||||
|
): void {
|
||||||
|
if (lastPointerUp !== null) {
|
||||||
|
// Unfortunately, sometimes we don't get a pointerup after a pointerdown,
|
||||||
|
// this can happen when a contextual menu or alert is triggered. In order to avoid
|
||||||
|
// being in a weird state, we clean up on the next pointerdown
|
||||||
|
lastPointerUp(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns whether the event is a panning
|
||||||
|
private handleCanvasPanUsingWheelOrSpaceDrag = (
|
||||||
|
event: React.PointerEvent<HTMLCanvasElement>,
|
||||||
|
): boolean => {
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
gesture.pointers.size === 0 &&
|
||||||
|
(event.button === POINTER_BUTTON.WHEEL ||
|
||||||
|
(event.button === POINTER_BUTTON.MAIN && isHoldingSpace))
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
isPanning = true;
|
||||||
|
|
||||||
|
let nextPastePrevented = false;
|
||||||
|
const isLinux = /Linux/.test(window.navigator.platform);
|
||||||
|
|
||||||
|
document.documentElement.style.cursor = CURSOR_TYPE.GRABBING;
|
||||||
|
let { clientX: lastX, clientY: lastY } = event;
|
||||||
|
const onPointerMove = withBatchedUpdates((event: PointerEvent) => {
|
||||||
|
const deltaX = lastX - event.clientX;
|
||||||
|
const deltaY = lastY - event.clientY;
|
||||||
|
lastX = event.clientX;
|
||||||
|
lastY = event.clientY;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Prevent paste event if we move while middle clicking on Linux.
|
||||||
|
* See issue #1383.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
isLinux &&
|
||||||
|
!nextPastePrevented &&
|
||||||
|
(Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1)
|
||||||
|
) {
|
||||||
|
nextPastePrevented = true;
|
||||||
|
|
||||||
|
/* Prevent the next paste event */
|
||||||
|
const preventNextPaste = (event: ClipboardEvent) => {
|
||||||
|
document.body.removeEventListener(EVENT.PASTE, preventNextPaste);
|
||||||
|
event.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reenable next paste in case of disabled middle click paste for
|
||||||
|
* any reason:
|
||||||
|
* - rigth click paste
|
||||||
|
* - empty clipboard
|
||||||
|
*/
|
||||||
|
const enableNextPaste = () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.removeEventListener(EVENT.PASTE, preventNextPaste);
|
||||||
|
window.removeEventListener(EVENT.POINTER_UP, enableNextPaste);
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.body.addEventListener(EVENT.PASTE, preventNextPaste);
|
||||||
|
window.addEventListener(EVENT.POINTER_UP, enableNextPaste);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
scrollX: normalizeScroll(this.state.scrollX - deltaX / this.state.zoom),
|
||||||
|
scrollY: normalizeScroll(this.state.scrollY - deltaY / this.state.zoom),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const teardown = withBatchedUpdates(
|
||||||
|
(lastPointerUp = () => {
|
||||||
|
lastPointerUp = null;
|
||||||
|
isPanning = false;
|
||||||
|
if (!isHoldingSpace) {
|
||||||
|
setCursorForShape(this.state.elementType);
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
cursorButton: "up",
|
||||||
|
});
|
||||||
|
this.savePointer(event.clientX, event.clientY, "up");
|
||||||
|
window.removeEventListener(EVENT.POINTER_MOVE, onPointerMove);
|
||||||
|
window.removeEventListener(EVENT.POINTER_UP, teardown);
|
||||||
|
window.removeEventListener(EVENT.BLUR, teardown);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
window.addEventListener(EVENT.BLUR, teardown);
|
||||||
|
window.addEventListener(EVENT.POINTER_MOVE, onPointerMove, {
|
||||||
|
passive: true,
|
||||||
|
});
|
||||||
|
window.addEventListener(EVENT.POINTER_UP, teardown);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
private updateGestureOnPointerDown(
|
||||||
|
event: React.PointerEvent<HTMLCanvasElement>,
|
||||||
|
): void {
|
||||||
|
gesture.pointers.set(event.pointerId, {
|
||||||
|
x: event.clientX,
|
||||||
|
y: event.clientY,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (gesture.pointers.size === 2) {
|
||||||
|
gesture.lastCenter = getCenter(gesture.pointers);
|
||||||
|
gesture.initialScale = this.state.zoom;
|
||||||
|
gesture.initialDistance = getDistance(
|
||||||
|
Array.from(gesture.pointers.values()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns whether the event is a dragging a scrollbar
|
||||||
|
private handleDraggingScrollBar(
|
||||||
|
event: React.PointerEvent<HTMLCanvasElement>,
|
||||||
|
lastCoords: { x: number; y: number },
|
||||||
|
{
|
||||||
|
isOverHorizontalScrollBar,
|
||||||
|
isOverVerticalScrollBar,
|
||||||
|
}: {
|
||||||
|
isOverHorizontalScrollBar: boolean;
|
||||||
|
isOverVerticalScrollBar: boolean;
|
||||||
|
},
|
||||||
|
): boolean {
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
(isOverHorizontalScrollBar || isOverVerticalScrollBar) &&
|
||||||
|
!this.state.multiElement
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
isDraggingScrollBar = true;
|
||||||
|
lastCoords.x = event.clientX;
|
||||||
|
lastCoords.y = event.clientY;
|
||||||
|
const onPointerMove = withBatchedUpdates((event: PointerEvent) => {
|
||||||
|
const target = event.target;
|
||||||
|
if (!(target instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOverHorizontalScrollBar) {
|
||||||
|
const x = event.clientX;
|
||||||
|
const dx = x - lastCoords.x;
|
||||||
|
this.setState({
|
||||||
|
scrollX: normalizeScroll(this.state.scrollX - dx / this.state.zoom),
|
||||||
|
});
|
||||||
|
lastCoords.x = x;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOverVerticalScrollBar) {
|
||||||
|
const y = event.clientY;
|
||||||
|
const dy = y - lastCoords.y;
|
||||||
|
this.setState({
|
||||||
|
scrollY: normalizeScroll(this.state.scrollY - dy / this.state.zoom),
|
||||||
|
});
|
||||||
|
lastCoords.y = y;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const onPointerUp = withBatchedUpdates(() => {
|
||||||
|
isDraggingScrollBar = false;
|
||||||
|
setCursorForShape(this.state.elementType);
|
||||||
|
lastPointerUp = null;
|
||||||
|
this.setState({
|
||||||
|
cursorButton: "up",
|
||||||
|
});
|
||||||
|
this.savePointer(event.clientX, event.clientY, "up");
|
||||||
|
window.removeEventListener(EVENT.POINTER_MOVE, onPointerMove);
|
||||||
|
window.removeEventListener(EVENT.POINTER_UP, onPointerUp);
|
||||||
|
});
|
||||||
|
|
||||||
|
lastPointerUp = onPointerUp;
|
||||||
|
|
||||||
|
window.addEventListener(EVENT.POINTER_MOVE, onPointerMove);
|
||||||
|
window.addEventListener(EVENT.POINTER_UP, onPointerUp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private maybeClearSelectionWhenHittingElement(
|
||||||
|
event: React.PointerEvent<HTMLCanvasElement>,
|
||||||
|
hitElement: ExcalidrawElement | null,
|
||||||
|
): void {
|
||||||
|
const isHittingASelectedElement =
|
||||||
|
hitElement != null && this.state.selectedElementIds[hitElement.id];
|
||||||
|
|
||||||
|
// clear selection if shift is not clicked
|
||||||
|
if (isHittingASelectedElement || event.shiftKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState((prevState) => ({
|
||||||
|
selectedElementIds: {},
|
||||||
|
selectedGroupIds: {},
|
||||||
|
// Continue editing the same group if the user selected a different
|
||||||
|
// element from it
|
||||||
|
editingGroupId:
|
||||||
|
prevState.editingGroupId &&
|
||||||
|
hitElement != null &&
|
||||||
|
isElementInGroup(hitElement, prevState.editingGroupId)
|
||||||
|
? prevState.editingGroupId
|
||||||
|
: null,
|
||||||
|
}));
|
||||||
|
const { selectedElementIds } = this.state;
|
||||||
|
this.setState({
|
||||||
|
selectedElementIds: {},
|
||||||
|
previousSelectedElementIds: selectedElementIds,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private handleCanvasRef = (canvas: HTMLCanvasElement) => {
|
private handleCanvasRef = (canvas: HTMLCanvasElement) => {
|
||||||
// canvas is null when unmounting
|
// canvas is null when unmounting
|
||||||
if (canvas !== null) {
|
if (canvas !== null) {
|
||||||
|
@ -81,7 +81,7 @@ export const getElementWithResizeHandler = (
|
|||||||
pointerType,
|
pointerType,
|
||||||
);
|
);
|
||||||
return resizeHandle ? { element, resizeHandle } : null;
|
return resizeHandle ? { element, resizeHandle } : null;
|
||||||
}, null as { element: NonDeletedExcalidrawElement; resizeHandle: ReturnType<typeof resizeTest> } | null);
|
}, null as { element: NonDeletedExcalidrawElement; resizeHandle: HandlerRectanglesRet } | null);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getResizeHandlerFromCoords = (
|
export const getResizeHandlerFromCoords = (
|
||||||
|
@ -106,13 +106,16 @@ export const isOverScrollBars = (
|
|||||||
scrollBars: ScrollBars,
|
scrollBars: ScrollBars,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
) => {
|
): {
|
||||||
|
isOverHorizontalScrollBar: boolean;
|
||||||
|
isOverVerticalScrollBar: boolean;
|
||||||
|
} => {
|
||||||
const [isOverHorizontalScrollBar, isOverVerticalScrollBar] = [
|
const [isOverHorizontalScrollBar, isOverVerticalScrollBar] = [
|
||||||
scrollBars.horizontal,
|
scrollBars.horizontal,
|
||||||
scrollBars.vertical,
|
scrollBars.vertical,
|
||||||
].map((scrollBar) => {
|
].map((scrollBar) => {
|
||||||
return (
|
return (
|
||||||
scrollBar &&
|
scrollBar != null &&
|
||||||
scrollBar.x <= x &&
|
scrollBar.x <= x &&
|
||||||
x <= scrollBar.x + scrollBar.width &&
|
x <= scrollBar.x + scrollBar.width &&
|
||||||
scrollBar.y <= y &&
|
scrollBar.y <= y &&
|
||||||
|
Loading…
x
Reference in New Issue
Block a user