mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
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:
parent
1f117995d9
commit
d2181847be
7 changed files with 172 additions and 110 deletions
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue