|  | @@ -1,7 +1,13 @@
 | 
	
		
			
				|  |  | -import { VERTICAL_ALIGN } from "../constants";
 | 
	
		
			
				|  |  | -import { getNonDeletedElements, isTextElement } from "../element";
 | 
	
		
			
				|  |  | +import {
 | 
	
		
			
				|  |  | +  BOUND_TEXT_PADDING,
 | 
	
		
			
				|  |  | +  ROUNDNESS,
 | 
	
		
			
				|  |  | +  TEXT_ALIGN,
 | 
	
		
			
				|  |  | +  VERTICAL_ALIGN,
 | 
	
		
			
				|  |  | +} from "../constants";
 | 
	
		
			
				|  |  | +import { getNonDeletedElements, isTextElement, newElement } from "../element";
 | 
	
		
			
				|  |  |  import { mutateElement } from "../element/mutateElement";
 | 
	
		
			
				|  |  |  import {
 | 
	
		
			
				|  |  | +  computeContainerDimensionForBoundText,
 | 
	
		
			
				|  |  |    getBoundTextElement,
 | 
	
		
			
				|  |  |    measureText,
 | 
	
		
			
				|  |  |    redrawTextBoundingBox,
 | 
	
	
		
			
				|  | @@ -13,8 +19,11 @@ import {
 | 
	
		
			
				|  |  |  import {
 | 
	
		
			
				|  |  |    hasBoundTextElement,
 | 
	
		
			
				|  |  |    isTextBindableContainer,
 | 
	
		
			
				|  |  | +  isUsingAdaptiveRadius,
 | 
	
		
			
				|  |  |  } from "../element/typeChecks";
 | 
	
		
			
				|  |  |  import {
 | 
	
		
			
				|  |  | +  ExcalidrawElement,
 | 
	
		
			
				|  |  | +  ExcalidrawLinearElement,
 | 
	
		
			
				|  |  |    ExcalidrawTextContainer,
 | 
	
		
			
				|  |  |    ExcalidrawTextElement,
 | 
	
		
			
				|  |  |  } from "../element/types";
 | 
	
	
		
			
				|  | @@ -129,18 +138,151 @@ export const actionBindText = register({
 | 
	
		
			
				|  |  |        }),
 | 
	
		
			
				|  |  |      });
 | 
	
		
			
				|  |  |      redrawTextBoundingBox(textElement, container);
 | 
	
		
			
				|  |  | -    const updatedElements = elements.slice();
 | 
	
		
			
				|  |  | -    const textElementIndex = updatedElements.findIndex(
 | 
	
		
			
				|  |  | -      (ele) => ele.id === textElement.id,
 | 
	
		
			
				|  |  | -    );
 | 
	
		
			
				|  |  | -    updatedElements.splice(textElementIndex, 1);
 | 
	
		
			
				|  |  | -    const containerIndex = updatedElements.findIndex(
 | 
	
		
			
				|  |  | -      (ele) => ele.id === container.id,
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    return {
 | 
	
		
			
				|  |  | +      elements: pushTextAboveContainer(elements, container, textElement),
 | 
	
		
			
				|  |  | +      appState: { ...appState, selectedElementIds: { [container.id]: true } },
 | 
	
		
			
				|  |  | +      commitToHistory: true,
 | 
	
		
			
				|  |  | +    };
 | 
	
		
			
				|  |  | +  },
 | 
	
		
			
				|  |  | +});
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +const pushTextAboveContainer = (
 | 
	
		
			
				|  |  | +  elements: readonly ExcalidrawElement[],
 | 
	
		
			
				|  |  | +  container: ExcalidrawElement,
 | 
	
		
			
				|  |  | +  textElement: ExcalidrawTextElement,
 | 
	
		
			
				|  |  | +) => {
 | 
	
		
			
				|  |  | +  const updatedElements = elements.slice();
 | 
	
		
			
				|  |  | +  const textElementIndex = updatedElements.findIndex(
 | 
	
		
			
				|  |  | +    (ele) => ele.id === textElement.id,
 | 
	
		
			
				|  |  | +  );
 | 
	
		
			
				|  |  | +  updatedElements.splice(textElementIndex, 1);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  const containerIndex = updatedElements.findIndex(
 | 
	
		
			
				|  |  | +    (ele) => ele.id === container.id,
 | 
	
		
			
				|  |  | +  );
 | 
	
		
			
				|  |  | +  updatedElements.splice(containerIndex + 1, 0, textElement);
 | 
	
		
			
				|  |  | +  return updatedElements;
 | 
	
		
			
				|  |  | +};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +const pushContainerBelowText = (
 | 
	
		
			
				|  |  | +  elements: readonly ExcalidrawElement[],
 | 
	
		
			
				|  |  | +  container: ExcalidrawElement,
 | 
	
		
			
				|  |  | +  textElement: ExcalidrawTextElement,
 | 
	
		
			
				|  |  | +) => {
 | 
	
		
			
				|  |  | +  const updatedElements = elements.slice();
 | 
	
		
			
				|  |  | +  const containerIndex = updatedElements.findIndex(
 | 
	
		
			
				|  |  | +    (ele) => ele.id === container.id,
 | 
	
		
			
				|  |  | +  );
 | 
	
		
			
				|  |  | +  updatedElements.splice(containerIndex, 1);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  const textElementIndex = updatedElements.findIndex(
 | 
	
		
			
				|  |  | +    (ele) => ele.id === textElement.id,
 | 
	
		
			
				|  |  | +  );
 | 
	
		
			
				|  |  | +  updatedElements.splice(textElementIndex, 0, container);
 | 
	
		
			
				|  |  | +  return updatedElements;
 | 
	
		
			
				|  |  | +};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +export const actionCreateContainerFromText = register({
 | 
	
		
			
				|  |  | +  name: "createContainerFromText",
 | 
	
		
			
				|  |  | +  contextItemLabel: "labels.createContainerFromText",
 | 
	
		
			
				|  |  | +  trackEvent: { category: "element" },
 | 
	
		
			
				|  |  | +  predicate: (elements, appState) => {
 | 
	
		
			
				|  |  | +    const selectedElements = getSelectedElements(elements, appState);
 | 
	
		
			
				|  |  | +    return selectedElements.length === 1 && isTextElement(selectedElements[0]);
 | 
	
		
			
				|  |  | +  },
 | 
	
		
			
				|  |  | +  perform: (elements, appState) => {
 | 
	
		
			
				|  |  | +    const selectedElements = getSelectedElements(
 | 
	
		
			
				|  |  | +      getNonDeletedElements(elements),
 | 
	
		
			
				|  |  | +      appState,
 | 
	
		
			
				|  |  |      );
 | 
	
		
			
				|  |  | -    updatedElements.splice(containerIndex + 1, 0, textElement);
 | 
	
		
			
				|  |  | +    const updatedElements = elements.slice();
 | 
	
		
			
				|  |  | +    if (selectedElements.length === 1 && isTextElement(selectedElements[0])) {
 | 
	
		
			
				|  |  | +      const textElement = selectedElements[0];
 | 
	
		
			
				|  |  | +      const container = newElement({
 | 
	
		
			
				|  |  | +        type: "rectangle",
 | 
	
		
			
				|  |  | +        backgroundColor: appState.currentItemBackgroundColor,
 | 
	
		
			
				|  |  | +        boundElements: [
 | 
	
		
			
				|  |  | +          ...(textElement.boundElements || []),
 | 
	
		
			
				|  |  | +          { id: textElement.id, type: "text" },
 | 
	
		
			
				|  |  | +        ],
 | 
	
		
			
				|  |  | +        angle: textElement.angle,
 | 
	
		
			
				|  |  | +        fillStyle: appState.currentItemFillStyle,
 | 
	
		
			
				|  |  | +        strokeColor: appState.currentItemStrokeColor,
 | 
	
		
			
				|  |  | +        roughness: appState.currentItemRoughness,
 | 
	
		
			
				|  |  | +        strokeWidth: appState.currentItemStrokeWidth,
 | 
	
		
			
				|  |  | +        strokeStyle: appState.currentItemStrokeStyle,
 | 
	
		
			
				|  |  | +        roundness:
 | 
	
		
			
				|  |  | +          appState.currentItemRoundness === "round"
 | 
	
		
			
				|  |  | +            ? {
 | 
	
		
			
				|  |  | +                type: isUsingAdaptiveRadius("rectangle")
 | 
	
		
			
				|  |  | +                  ? ROUNDNESS.ADAPTIVE_RADIUS
 | 
	
		
			
				|  |  | +                  : ROUNDNESS.PROPORTIONAL_RADIUS,
 | 
	
		
			
				|  |  | +              }
 | 
	
		
			
				|  |  | +            : null,
 | 
	
		
			
				|  |  | +        opacity: 100,
 | 
	
		
			
				|  |  | +        locked: false,
 | 
	
		
			
				|  |  | +        x: textElement.x - BOUND_TEXT_PADDING,
 | 
	
		
			
				|  |  | +        y: textElement.y - BOUND_TEXT_PADDING,
 | 
	
		
			
				|  |  | +        width: computeContainerDimensionForBoundText(
 | 
	
		
			
				|  |  | +          textElement.width,
 | 
	
		
			
				|  |  | +          "rectangle",
 | 
	
		
			
				|  |  | +        ),
 | 
	
		
			
				|  |  | +        height: computeContainerDimensionForBoundText(
 | 
	
		
			
				|  |  | +          textElement.height,
 | 
	
		
			
				|  |  | +          "rectangle",
 | 
	
		
			
				|  |  | +        ),
 | 
	
		
			
				|  |  | +        groupIds: textElement.groupIds,
 | 
	
		
			
				|  |  | +      });
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      // update bindings
 | 
	
		
			
				|  |  | +      if (textElement.boundElements?.length) {
 | 
	
		
			
				|  |  | +        const linearElementIds = textElement.boundElements
 | 
	
		
			
				|  |  | +          .filter((ele) => ele.type === "arrow")
 | 
	
		
			
				|  |  | +          .map((el) => el.id);
 | 
	
		
			
				|  |  | +        const linearElements = updatedElements.filter((ele) =>
 | 
	
		
			
				|  |  | +          linearElementIds.includes(ele.id),
 | 
	
		
			
				|  |  | +        ) as ExcalidrawLinearElement[];
 | 
	
		
			
				|  |  | +        linearElements.forEach((ele) => {
 | 
	
		
			
				|  |  | +          let startBinding = null;
 | 
	
		
			
				|  |  | +          let endBinding = null;
 | 
	
		
			
				|  |  | +          if (ele.startBinding) {
 | 
	
		
			
				|  |  | +            startBinding = { ...ele.startBinding, elementId: container.id };
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +          if (ele.endBinding) {
 | 
	
		
			
				|  |  | +            endBinding = { ...ele.endBinding, elementId: container.id };
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +          mutateElement(ele, { startBinding, endBinding });
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      mutateElement(textElement, {
 | 
	
		
			
				|  |  | +        containerId: container.id,
 | 
	
		
			
				|  |  | +        verticalAlign: VERTICAL_ALIGN.MIDDLE,
 | 
	
		
			
				|  |  | +        textAlign: TEXT_ALIGN.CENTER,
 | 
	
		
			
				|  |  | +        boundElements: null,
 | 
	
		
			
				|  |  | +      });
 | 
	
		
			
				|  |  | +      redrawTextBoundingBox(textElement, container);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      return {
 | 
	
		
			
				|  |  | +        elements: pushContainerBelowText(
 | 
	
		
			
				|  |  | +          [...elements, container],
 | 
	
		
			
				|  |  | +          container,
 | 
	
		
			
				|  |  | +          textElement,
 | 
	
		
			
				|  |  | +        ),
 | 
	
		
			
				|  |  | +        appState: {
 | 
	
		
			
				|  |  | +          ...appState,
 | 
	
		
			
				|  |  | +          selectedElementIds: {
 | 
	
		
			
				|  |  | +            [container.id]: true,
 | 
	
		
			
				|  |  | +            [textElement.id]: false,
 | 
	
		
			
				|  |  | +          },
 | 
	
		
			
				|  |  | +        },
 | 
	
		
			
				|  |  | +        commitToHistory: true,
 | 
	
		
			
				|  |  | +      };
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |      return {
 | 
	
		
			
				|  |  |        elements: updatedElements,
 | 
	
		
			
				|  |  | -      appState: { ...appState, selectedElementIds: { [container.id]: true } },
 | 
	
		
			
				|  |  | +      appState,
 | 
	
		
			
				|  |  |        commitToHistory: true,
 | 
	
		
			
				|  |  |      };
 | 
	
		
			
				|  |  |    },
 |