fix: Always bind to container selected by user (#5880)

* fix: Always bind to container selected by user

* Don't bind to container when using text tool

* adjust z-index for bound text

* fix

* Add spec

* Add test

* Allow double click on transparent container and add spec

* fix spec

* adjust z-index only when binding

* update index

* fix

* add index check

* Update src/scene/Scene.ts

Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
Aakansha Doshi 2022-11-25 15:45:34 +05:30 committed by GitHub
parent 1f117995d9
commit d2181847be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 172 additions and 110 deletions

View file

@ -133,7 +133,6 @@ import {
isInitializedImageElement,
isLinearElement,
isLinearElementType,
isTextBindableContainer,
} from "../element/typeChecks";
import {
ExcalidrawBindableElement,
@ -175,7 +174,6 @@ import { renderScene } from "../renderer/renderScene";
import { invalidateShapeForElement } from "../renderer/renderElement";
import {
calculateScrollCenter,
getTextBindableContainerAtPosition,
getElementsAtPosition,
getElementsWithinSelection,
getNormalizedZoom,
@ -255,6 +253,7 @@ import {
getApproxMinLineWidth,
getBoundTextElement,
getContainerDims,
getTextBindableContainerAtPosition,
isValidTextContainer,
} from "../element/textElement";
import { isHittingElementNotConsideringBoundingBox } from "../element/collision";
@ -1990,10 +1989,14 @@ class App extends React.Component<AppProps, AppState> {
isTextElement(selectedElement) ||
isValidTextContainer(selectedElement)
) {
let container;
if (!isTextElement(selectedElement)) {
container = selectedElement as ExcalidrawTextContainer;
}
this.startTextEditing({
sceneX: selectedElement.x + selectedElement.width / 2,
sceneY: selectedElement.y + selectedElement.height / 2,
shouldBind: true,
container,
});
event.preventDefault();
return;
@ -2317,7 +2320,6 @@ class App extends React.Component<AppProps, AppState> {
const element = this.getElementAtPosition(x, y, {
includeBoundTextElement: true,
});
if (element && isTextElement(element) && !element.isDeleted) {
return element;
}
@ -2394,29 +2396,31 @@ class App extends React.Component<AppProps, AppState> {
private startTextEditing = ({
sceneX,
sceneY,
shouldBind,
insertAtParentCenter = true,
container,
}: {
/** X position to insert text at */
sceneX: number;
/** Y position to insert text at */
sceneY: number;
shouldBind: boolean;
/** whether to attempt to insert at element center if applicable */
insertAtParentCenter?: boolean;
container?: ExcalidrawTextContainer | null;
}) => {
let shouldBindToContainer = false;
let parentCenterPosition =
insertAtParentCenter &&
this.getTextWysiwygSnappedToCenterPosition(
sceneX,
sceneY,
this.state,
this.canvas,
window.devicePixelRatio,
container,
);
if (container && parentCenterPosition) {
shouldBindToContainer = true;
}
let existingTextElement: NonDeleted<ExcalidrawTextElement> | null = null;
let container: ExcalidrawTextContainer | null = null;
const selectedElements = getSelectedElements(
this.scene.getNonDeletedElements(),
@ -2426,7 +2430,7 @@ class App extends React.Component<AppProps, AppState> {
if (selectedElements.length === 1) {
if (isTextElement(selectedElements[0])) {
existingTextElement = selectedElements[0];
} else if (isTextBindableContainer(selectedElements[0], false)) {
} else if (container) {
existingTextElement = getBoundTextElement(selectedElements[0]);
} else {
existingTextElement = this.getTextElementAtPosition(sceneX, sceneY);
@ -2435,26 +2439,7 @@ class App extends React.Component<AppProps, AppState> {
existingTextElement = this.getTextElementAtPosition(sceneX, sceneY);
}
// bind to container when shouldBind is true or
// clicked on center of container
if (
!container &&
!existingTextElement &&
(shouldBind || parentCenterPosition)
) {
container = getTextBindableContainerAtPosition(
this.scene
.getNonDeletedElements()
.filter(
(ele) =>
isTextBindableContainer(ele, false) && !getBoundTextElement(ele),
),
sceneX,
sceneY,
);
}
if (!existingTextElement && container) {
if (!existingTextElement && shouldBindToContainer && container) {
const fontString = {
fontSize: this.state.currentItemFontSize,
fontFamily: this.state.currentItemFontFamily,
@ -2472,12 +2457,10 @@ class App extends React.Component<AppProps, AppState> {
sceneX,
sceneY,
this.state,
this.canvas,
window.devicePixelRatio,
container,
);
}
}
const element = existingTextElement
? existingTextElement
: newTextElement({
@ -2504,7 +2487,7 @@ class App extends React.Component<AppProps, AppState> {
verticalAlign: parentCenterPosition
? VERTICAL_ALIGN.MIDDLE
: DEFAULT_VERTICAL_ALIGN,
containerId: container?.id ?? undefined,
containerId: shouldBindToContainer ? container?.id : undefined,
groupIds: container?.groupIds ?? [],
locked: false,
});
@ -2512,10 +2495,15 @@ class App extends React.Component<AppProps, AppState> {
this.setState({ editingElement: element });
if (!existingTextElement) {
this.scene.replaceAllElements([
...this.scene.getElementsIncludingDeleted(),
element,
]);
if (container && shouldBindToContainer) {
const containerIndex = this.scene.getElementIndex(container.id);
this.scene.insertElementAtIndex(element, containerIndex + 1);
} else {
this.scene.replaceAllElements([
...this.scene.getElementsIncludingDeleted(),
element,
]);
}
// case: creating new text not centered to parent element → offset Y
// so that the text is centered to cursor position
@ -2603,22 +2591,23 @@ class App extends React.Component<AppProps, AppState> {
resetCursor(this.canvas);
if (!event[KEYS.CTRL_OR_CMD] && !this.state.viewModeEnabled) {
const selectedElements = getSelectedElements(
const container = getTextBindableContainerAtPosition(
this.scene.getNonDeletedElements(),
this.state,
sceneX,
sceneY,
);
if (selectedElements.length === 1) {
const selectedElement = selectedElements[0];
if (hasBoundTextElement(selectedElement)) {
sceneX = selectedElement.x + selectedElement.width / 2;
sceneY = selectedElement.y + selectedElement.height / 2;
if (container) {
if (hasBoundTextElement(container)) {
sceneX = container.x + container.width / 2;
sceneY = container.y + container.height / 2;
}
}
this.startTextEditing({
sceneX,
sceneY,
shouldBind: false,
insertAtParentCenter: !event.altKey,
container,
});
}
};
@ -3911,15 +3900,23 @@ class App extends React.Component<AppProps, AppState> {
includeBoundTextElement: true,
});
let container = getTextBindableContainerAtPosition(
this.scene.getNonDeletedElements(),
this.state,
sceneX,
sceneY,
);
if (hasBoundTextElement(element)) {
container = element as ExcalidrawTextContainer;
sceneX = element.x + element.width / 2;
sceneY = element.y + element.height / 2;
}
this.startTextEditing({
sceneX,
sceneY,
shouldBind: false,
insertAtParentCenter: !event.altKey,
container,
});
resetCursor(this.canvas);
@ -6171,21 +6168,11 @@ class App extends React.Component<AppProps, AppState> {
x: number,
y: number,
appState: AppState,
canvas: HTMLCanvasElement | null,
scale: number,
container?: ExcalidrawTextContainer | null,
) {
const elementClickedInside = getTextBindableContainerAtPosition(
this.scene
.getElementsIncludingDeleted()
.filter((element) => !isTextElement(element)),
x,
y,
);
if (elementClickedInside) {
const elementCenterX =
elementClickedInside.x + elementClickedInside.width / 2;
const elementCenterY =
elementClickedInside.y + elementClickedInside.height / 2;
if (container) {
const elementCenterX = container.x + container.width / 2;
const elementCenterY = container.y + container.height / 2;
const distanceToCenter = Math.hypot(
x - elementCenterX,
y - elementCenterY,