From c8f4a4cb41ea743b58765444ee064aaebca5d8c3 Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Mon, 10 Feb 2025 15:20:18 +0100 Subject: [PATCH] feat: add `props.onDuplicate` (#9117) * feat: add `props.onDuplicate` * docs * clarify docs * fix docs --- .../actions/actionDuplicateSelection.tsx | 16 ++++++++++++-- packages/excalidraw/components/App.tsx | 21 +++++++++++++++++-- packages/excalidraw/index.tsx | 2 ++ packages/excalidraw/types.ts | 16 ++++++++++++++ 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/packages/excalidraw/actions/actionDuplicateSelection.tsx b/packages/excalidraw/actions/actionDuplicateSelection.tsx index 735c5cf233..28dddd6407 100644 --- a/packages/excalidraw/actions/actionDuplicateSelection.tsx +++ b/packages/excalidraw/actions/actionDuplicateSelection.tsx @@ -69,8 +69,20 @@ export const actionDuplicateSelection = register({ } } + const nextState = duplicateElements(elements, appState); + + if (app.props.onDuplicate && nextState.elements) { + const mappedElements = app.props.onDuplicate( + nextState.elements, + elements, + ); + if (mappedElements) { + nextState.elements = mappedElements; + } + } + return { - ...duplicateElements(elements, appState), + ...nextState, storeAction: StoreAction.CAPTURE, }; }, @@ -92,7 +104,7 @@ export const actionDuplicateSelection = register({ const duplicateElements = ( elements: readonly ExcalidrawElement[], appState: AppState, -): Partial => { +): Partial> => { // --------------------------------------------------------------------------- const groupIdMap = new Map(); diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 9699c8a8aa..663b291936 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -3228,7 +3228,14 @@ class App extends React.Component { ); const prevElements = this.scene.getElementsIncludingDeleted(); - const nextElements = [...prevElements, ...newElements]; + let nextElements = [...prevElements, ...newElements]; + + const mappedNewSceneElements = this.props.onDuplicate?.( + nextElements, + prevElements, + ); + + nextElements = mappedNewSceneElements || nextElements; syncMovedIndices(nextElements, arrayToMap(newElements)); @@ -8442,7 +8449,17 @@ class App extends React.Component { } } - const nextSceneElements = [...nextElements, ...elementsToAppend]; + let nextSceneElements: ExcalidrawElement[] = [ + ...nextElements, + ...elementsToAppend, + ]; + + const mappedNewSceneElements = this.props.onDuplicate?.( + nextSceneElements, + elements, + ); + + nextSceneElements = mappedNewSceneElements || nextSceneElements; syncMovedIndices(nextSceneElements, arrayToMap(elementsToAppend)); diff --git a/packages/excalidraw/index.tsx b/packages/excalidraw/index.tsx index 0af660f19e..6c9544a384 100644 --- a/packages/excalidraw/index.tsx +++ b/packages/excalidraw/index.tsx @@ -46,6 +46,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => { onPointerDown, onPointerUp, onScrollChange, + onDuplicate, children, validateEmbeddable, renderEmbeddable, @@ -136,6 +137,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => { onPointerDown={onPointerDown} onPointerUp={onPointerUp} onScrollChange={onScrollChange} + onDuplicate={onDuplicate} validateEmbeddable={validateEmbeddable} renderEmbeddable={renderEmbeddable} aiEnabled={aiEnabled !== false} diff --git a/packages/excalidraw/types.ts b/packages/excalidraw/types.ts index 84fe45308b..4974f4214b 100644 --- a/packages/excalidraw/types.ts +++ b/packages/excalidraw/types.ts @@ -512,6 +512,22 @@ export interface ExcalidrawProps { data: ClipboardData, event: ClipboardEvent | null, ) => Promise | boolean; + /** + * Called when element(s) are duplicated so you can listen or modify as + * needed. + * + * Called when duplicating via mouse-drag, keyboard, paste, library insert + * etc. + * + * Returned elements will be used in place of the next elements + * (you should return all elements, including deleted, and not mutate + * the element if changes are made) + */ + onDuplicate?: ( + nextElements: readonly ExcalidrawElement[], + /** excludes the duplicated elements */ + prevElements: readonly ExcalidrawElement[], + ) => ExcalidrawElement[] | void; renderTopRightUI?: ( isMobile: boolean, appState: UIAppState,