diff options
| author | kj_sh604 | 2026-03-15 16:19:35 -0400 |
|---|---|---|
| committer | kj_sh604 | 2026-03-15 16:19:35 -0400 |
| commit | 6ec259a0e71174651bae95d4628138bf6fd68742 (patch) | |
| tree | 5e33c6a5ec091ecabfcb257fdc7b6a88ed8754ac /packages/excalidraw/actions/actionDistribute.tsx | |
| parent | 16c8578b15c727f22921f8a80a56ee4d4e7f2272 (diff) | |
refactor: packages/
Diffstat (limited to 'packages/excalidraw/actions/actionDistribute.tsx')
| -rw-r--r-- | packages/excalidraw/actions/actionDistribute.tsx | 110 |
1 files changed, 110 insertions, 0 deletions
diff --git a/packages/excalidraw/actions/actionDistribute.tsx b/packages/excalidraw/actions/actionDistribute.tsx new file mode 100644 index 0000000..15d33b1 --- /dev/null +++ b/packages/excalidraw/actions/actionDistribute.tsx @@ -0,0 +1,110 @@ +import { + DistributeHorizontallyIcon, + DistributeVerticallyIcon, +} from "../components/icons"; +import { ToolButton } from "../components/ToolButton"; +import type { Distribution } from "../distribute"; +import { distributeElements } from "../distribute"; +import { getNonDeletedElements } from "../element"; +import { isFrameLikeElement } from "../element/typeChecks"; +import type { ExcalidrawElement } from "../element/types"; +import { updateFrameMembershipOfSelectedElements } from "../frame"; +import { t } from "../i18n"; +import { CODES, KEYS } from "../keys"; +import { isSomeElementSelected } from "../scene"; +import { CaptureUpdateAction } from "../store"; +import type { AppClassProperties, AppState } from "../types"; +import { arrayToMap, getShortcutKey } from "../utils"; +import { register } from "./register"; + +const enableActionGroup = (appState: AppState, app: AppClassProperties) => { + const selectedElements = app.scene.getSelectedElements(appState); + return ( + selectedElements.length > 1 && + // TODO enable distributing frames when implemented properly + !selectedElements.some((el) => isFrameLikeElement(el)) + ); +}; + +const distributeSelectedElements = ( + elements: readonly ExcalidrawElement[], + appState: Readonly<AppState>, + app: AppClassProperties, + distribution: Distribution, +) => { + const selectedElements = app.scene.getSelectedElements(appState); + + const updatedElements = distributeElements( + selectedElements, + app.scene.getNonDeletedElementsMap(), + distribution, + ); + + const updatedElementsMap = arrayToMap(updatedElements); + + return updateFrameMembershipOfSelectedElements( + elements.map((element) => updatedElementsMap.get(element.id) || element), + appState, + app, + ); +}; + +export const distributeHorizontally = register({ + name: "distributeHorizontally", + label: "labels.distributeHorizontally", + trackEvent: { category: "element" }, + perform: (elements, appState, _, app) => { + return { + appState, + elements: distributeSelectedElements(elements, appState, app, { + space: "between", + axis: "x", + }), + captureUpdate: CaptureUpdateAction.IMMEDIATELY, + }; + }, + keyTest: (event) => + !event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.H, + PanelComponent: ({ elements, appState, updateData, app }) => ( + <ToolButton + hidden={!enableActionGroup(appState, app)} + type="button" + icon={DistributeHorizontallyIcon} + onClick={() => updateData(null)} + title={`${t("labels.distributeHorizontally")} — ${getShortcutKey( + "Alt+H", + )}`} + aria-label={t("labels.distributeHorizontally")} + visible={isSomeElementSelected(getNonDeletedElements(elements), appState)} + /> + ), +}); + +export const distributeVertically = register({ + name: "distributeVertically", + label: "labels.distributeVertically", + trackEvent: { category: "element" }, + perform: (elements, appState, _, app) => { + return { + appState, + elements: distributeSelectedElements(elements, appState, app, { + space: "between", + axis: "y", + }), + captureUpdate: CaptureUpdateAction.IMMEDIATELY, + }; + }, + keyTest: (event) => + !event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.V, + PanelComponent: ({ elements, appState, updateData, app }) => ( + <ToolButton + hidden={!enableActionGroup(appState, app)} + type="button" + icon={DistributeVerticallyIcon} + onClick={() => updateData(null)} + title={`${t("labels.distributeVertically")} — ${getShortcutKey("Alt+V")}`} + aria-label={t("labels.distributeVertically")} + visible={isSomeElementSelected(getNonDeletedElements(elements), appState)} + /> + ), +}); |
