mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-09-22 08:50:56 +02:00
display Container settings when text is editing
This commit is contained in:
@@ -1515,59 +1515,143 @@ export const actionChangeContainerBehavior = register({
|
|||||||
name: "changeContainerBehavior",
|
name: "changeContainerBehavior",
|
||||||
label: "labels.container",
|
label: "labels.container",
|
||||||
trackEvent: false,
|
trackEvent: false,
|
||||||
perform: (elements, appState, value) => {
|
perform: (elements, appState, value, app) => {
|
||||||
|
const elementsMap = app.scene.getNonDeletedElementsMap();
|
||||||
|
let selected = getSelectedElements(elements, appState, {
|
||||||
|
includeBoundTextElement: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selected.length === 0 && appState.editingTextElement) {
|
||||||
|
selected = [appState.editingTextElement];
|
||||||
|
}
|
||||||
|
|
||||||
|
const containerIdsToUpdate = new Set<string>();
|
||||||
|
|
||||||
|
// collect directly selected eligible containers
|
||||||
|
for (const el of selected) {
|
||||||
|
if (
|
||||||
|
isFlowchartNodeElement(el) &&
|
||||||
|
getBoundTextElement(el, elementsMap)
|
||||||
|
) {
|
||||||
|
containerIdsToUpdate.add(el.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if none, and exactly one selected text element -> use its container if eligible
|
||||||
|
if (
|
||||||
|
containerIdsToUpdate.size === 0 &&
|
||||||
|
selected.length === 1 &&
|
||||||
|
isTextElement(selected[0]) &&
|
||||||
|
selected[0].containerId
|
||||||
|
) {
|
||||||
|
const container = elementsMap.get(selected[0].containerId);
|
||||||
|
if (
|
||||||
|
container &&
|
||||||
|
isFlowchartNodeElement(container) &&
|
||||||
|
getBoundTextElement(container, elementsMap)
|
||||||
|
) {
|
||||||
|
containerIdsToUpdate.add(container.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (containerIdsToUpdate.size === 0) {
|
||||||
|
// nothing to update
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextElements = elements.map((el) =>
|
||||||
|
containerIdsToUpdate.has(el.id)
|
||||||
|
? newElementWith(el, {
|
||||||
|
containerBehavior: value,
|
||||||
|
})
|
||||||
|
: el,
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
elements: changeProperty(elements, appState, (el) =>
|
elements: nextElements,
|
||||||
newElementWith(el, {
|
|
||||||
containerBehavior: value,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
appState: { ...appState, currentItemContainerBehavior: value },
|
appState: { ...appState, currentItemContainerBehavior: value },
|
||||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, app }) => (
|
PanelComponent: ({ elements, appState, updateData, app }) => {
|
||||||
<fieldset>
|
const elementsMap = app.scene.getNonDeletedElementsMap();
|
||||||
{appState.stylesPanelMode === "full" && (
|
let selected = getSelectedElements(elements, appState, {
|
||||||
<legend>{t("labels.container")}</legend>
|
includeBoundTextElement: true,
|
||||||
)}
|
});
|
||||||
<div className="buttonList">
|
|
||||||
<RadioSelection
|
if (selected.length === 0 && appState.editingTextElement) {
|
||||||
group="container"
|
selected = [appState.editingTextElement];
|
||||||
options={[
|
}
|
||||||
{
|
|
||||||
value: "growing",
|
let targetContainers: ExcalidrawElement[] = [];
|
||||||
text: t("labels.container_growing"),
|
|
||||||
icon: growingContainerIcon,
|
// case 1: one text element selected -> target its container if eligible
|
||||||
},
|
if (
|
||||||
{
|
selected.length === 1 &&
|
||||||
value: "stickyNote",
|
isTextElement(selected[0]) &&
|
||||||
text: t("labels.container_sticky"),
|
selected[0].containerId
|
||||||
icon: stickyNoteIcon,
|
) {
|
||||||
},
|
const container = elementsMap.get(selected[0].containerId);
|
||||||
]}
|
if (
|
||||||
value={getFormValue(
|
container &&
|
||||||
elements,
|
isFlowchartNodeElement(container) &&
|
||||||
app,
|
getBoundTextElement(container, elementsMap)
|
||||||
(element) => element.containerBehavior ?? "growing",
|
) {
|
||||||
(element) =>
|
targetContainers = [container];
|
||||||
isFlowchartNodeElement(element) &&
|
}
|
||||||
Boolean(
|
} else {
|
||||||
getBoundTextElement(
|
// case 2: any eligible containers directly selected
|
||||||
element,
|
targetContainers = selected.filter(
|
||||||
app.scene.getNonDeletedElementsMap(),
|
(el) =>
|
||||||
),
|
isFlowchartNodeElement(el) &&
|
||||||
),
|
getBoundTextElement(el, elementsMap),
|
||||||
(hasSelection) =>
|
);
|
||||||
hasSelection
|
}
|
||||||
|
|
||||||
|
if (targetContainers.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value =
|
||||||
|
reduceToCommonValue(
|
||||||
|
targetContainers,
|
||||||
|
(el) => el.containerBehavior ?? "growing",
|
||||||
|
) ??
|
||||||
|
// mixed selection -> show null so nothing appears selected
|
||||||
|
null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<fieldset>
|
||||||
|
{appState.stylesPanelMode === "full" && (
|
||||||
|
<legend>{t("labels.container")}</legend>
|
||||||
|
)}
|
||||||
|
<div className="buttonList">
|
||||||
|
<RadioSelection
|
||||||
|
group="container"
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
value: "growing",
|
||||||
|
text: t("labels.container_growing"),
|
||||||
|
icon: growingContainerIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "stickyNote",
|
||||||
|
text: t("labels.container_sticky"),
|
||||||
|
icon: stickyNoteIcon,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
value={
|
||||||
|
value ??
|
||||||
|
(targetContainers.length
|
||||||
? null
|
? null
|
||||||
: appState.currentItemContainerBehavior ?? "growing",
|
: appState.currentItemContainerBehavior ?? "growing")
|
||||||
)}
|
}
|
||||||
onChange={(value) => updateData(value)}
|
onChange={(val) => updateData(val)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
),
|
);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const getArrowheadOptions = (flip: boolean) => {
|
const getArrowheadOptions = (flip: boolean) => {
|
||||||
|
@@ -10,6 +10,7 @@ import {
|
|||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
getContainerElement,
|
||||||
hasContainerBehavior,
|
hasContainerBehavior,
|
||||||
isFlowchartNodeElement,
|
isFlowchartNodeElement,
|
||||||
shouldAllowVerticalAlign,
|
shouldAllowVerticalAlign,
|
||||||
@@ -149,6 +150,13 @@ export const SelectedShapeActions = ({
|
|||||||
) {
|
) {
|
||||||
isSingleElementBoundContainer = true;
|
isSingleElementBoundContainer = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const textContainer = targetElements.length === 1 && isTextElement(targetElements[0])
|
||||||
|
? getContainerElement(targetElements[0], elementsMap)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const isStickyNoteContainer = textContainer && isFlowchartNodeElement(textContainer);
|
||||||
|
|
||||||
const isEditingTextOrNewElement = Boolean(
|
const isEditingTextOrNewElement = Boolean(
|
||||||
appState.editingTextElement || appState.newElement,
|
appState.editingTextElement || appState.newElement,
|
||||||
);
|
);
|
||||||
@@ -233,7 +241,7 @@ export const SelectedShapeActions = ({
|
|||||||
renderAction("changeTextAlign")}
|
renderAction("changeTextAlign")}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{shouldAllowVerticalAlign(targetElements, elementsMap) &&
|
{shouldAllowVerticalAlign(targetElements, elementsMap) &&
|
||||||
renderAction("changeVerticalAlign")}
|
renderAction("changeVerticalAlign")}
|
||||||
{(canHaveArrowheads(appState.activeTool.type) ||
|
{(canHaveArrowheads(appState.activeTool.type) ||
|
||||||
@@ -241,6 +249,8 @@ export const SelectedShapeActions = ({
|
|||||||
<>{renderAction("changeArrowhead")}</>
|
<>{renderAction("changeArrowhead")}</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{isStickyNoteContainer && <>{renderAction("changeContainerBehavior")}</>}
|
||||||
|
|
||||||
{renderAction("changeOpacity")}
|
{renderAction("changeOpacity")}
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
@@ -334,6 +344,12 @@ export const CompactShapeActions = ({
|
|||||||
appState.editingTextElement || appState.newElement,
|
appState.editingTextElement || appState.newElement,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const textContainer = targetElements.length === 1 && isTextElement(targetElements[0])
|
||||||
|
? getContainerElement(targetElements[0], elementsMap)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const isStickyNoteContainer = textContainer && isFlowchartNodeElement(textContainer);
|
||||||
|
|
||||||
const showFillIcons =
|
const showFillIcons =
|
||||||
(hasBackground(appState.activeTool.type) &&
|
(hasBackground(appState.activeTool.type) &&
|
||||||
!isTransparent(appState.currentItemBackgroundColor)) ||
|
!isTransparent(appState.currentItemBackgroundColor)) ||
|
||||||
@@ -616,6 +632,7 @@ export const CompactShapeActions = ({
|
|||||||
renderAction("changeTextAlign")}
|
renderAction("changeTextAlign")}
|
||||||
{shouldAllowVerticalAlign(targetElements, elementsMap) &&
|
{shouldAllowVerticalAlign(targetElements, elementsMap) &&
|
||||||
renderAction("changeVerticalAlign")}
|
renderAction("changeVerticalAlign")}
|
||||||
|
{isStickyNoteContainer && <>{renderAction("changeContainerBehavior")}</>}
|
||||||
</div>
|
</div>
|
||||||
</PropertiesPopover>
|
</PropertiesPopover>
|
||||||
)}
|
)}
|
||||||
|
Reference in New Issue
Block a user