---
title: "React Event calendar Cut, Copy, Paste Events Example | Mobiscroll"
component: "Event calendar"
framework: "React"
library_version: "6.2.0"
url: "https://demo.mobiscroll.com/react/eventcalendar/cut-copy-paste-events-between-calendars"
---

# Cut, Copy, Paste Events - React Event calendar demo

Transfer events between two React event calendars with cut, copy, paste, and undo — using keyboard shortcuts or a right-click menu. For React and Next.JS.

- [Live demo](https://demo.mobiscroll.com/react/eventcalendar/cut-copy-paste-events-between-calendars)
- [View React JS code on GitHub](https://github.com/acidb/mobiscroll-demos-react/tree/main/src/demos/eventcalendar/calendar-view/cut-copy-paste-events-between-calendars)
- [View React TS code on GitHub](https://github.com/acidb/mobiscroll-demos-react-ts/tree/main/src/demos/eventcalendar/calendar-view/cut-copy-paste-events-between-calendars)
- This demo in other frameworks: [JavaScript](https://demo.mobiscroll.com/javascript/eventcalendar/cut-copy-paste-events-between-calendars.md), [jQuery](https://demo.mobiscroll.com/jquery/eventcalendar/cut-copy-paste-events-between-calendars.md), [Angular](https://demo.mobiscroll.com/angular/eventcalendar/cut-copy-paste-events-between-calendars.md), [Vue](https://demo.mobiscroll.com/vue/eventcalendar/cut-copy-paste-events-between-calendars.md)

## Description

While copy & pasting one or more events is useful, sometimes being able to move events between two separate calendars can improve productivity. How the calendars are laid out depends on the application, but the basic idea is that you will need to be able to tell where the events from the clipboard will be pasted.

In this example this is implemented by tracking the active instance, which is determined by the active tab of the segmented control. With this out of the way, `CTRL`/`CMD`+`C`/`X`/`Z`/`V` handle the copy, cut, undo and paste actions.

**Looking for multiple event selection & bulk operations?** [Take a look at the previous example →](https://demo.mobiscroll.com/react/eventcalendar/event-bulk-actions-edit-delete-update)

## What this demo shows

- A desktop month view event calendar with multiple events and the standard calendar header controls.
- **Calendar header** The header shows the current month and year on the left, and blue month navigation arrows with a `Today` button (for jumping back to the current date) between them on the right. 
- **Event creation** Users can create events by clicking and dragging across day cells or by double-clicking a day cell.
- **Calendar switcher** A segmented control above the calendar lets users switch between the First calendar and the Second calendar.
- **Default state** The First calendar is selected when the demo loads.
- **Selection and transfer** One or more events can be selected in the active calendar and then copied or cut for transfer to the other calendar.
- **Keyboard actions** `CTRL`/`CMD` + `C`, `X`, `Z`, and `V` support copy, cut, undo, and paste actions.
- **Context menu** Right-clicking selected events opens a popup with Copy, Cut, Paste, and Delete actions.
- **Cross-calendar workflow** After selecting events in one calendar, users can switch to the other calendar and paste them there.
- **Day cell states for future days** Hovering a day cell highlights the day number with a gray background, while clicking the empty part of the cell selects the day and highlights the day number with a blue background.
- **Label interaction** Hovering over or clicking an event label selects it and highlights the selected label.
- **Event interaction** Hovering over or selecting an event label highlights it and shows resize handles on both sides, indicating that the event can be resized by clicking and dragging.

## Best for

- **Multi-calendar workflows** Applications where users need to move or duplicate events between separate calendars.
- **Planning tools** Scheduling and planning interfaces that need month-based visibility together with fast event transfer actions.
- **Desktop-heavy use cases** Workflows where keyboard shortcuts and right-click actions help users work more efficiently.
- **Bulk event handling** Scenarios where users may need to select and move multiple events instead of updating them one by one.

## API used in this demo

- [`theme`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-themeVariant) - option
- [`labels`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-labels) - option
- [`view`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-view) - option
- [`clickToCreate`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-clickToCreate) - option
- [`dragToCreate`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-dragToCreate) - option
- [`dragToMove`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-dragToMove) - option
- [`dragToResize`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-dragToResize) - option
- [`selectMultipleEvents`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-selectMultipleEvents) - option
- [`data`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-data) - option
- [`selectedEvents`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-selectedEvents) - option
- [`onSelectedEventsChange`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onSelectedEventsChange) - event
- [`onPageLoading`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onPageLoading) - event
- [`onCellRightClick`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onCellRightClick) - event
- [`onEventRightClick`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventRightClick) - event
- [`onEventDeleted`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventDeleted) - event
- [`touchUi`](https://mobiscroll.com/docs/react/select/api#opt-touchUi) - option
- [`display`](https://mobiscroll.com/docs/react/select/api#opt-display) - option
- [`anchor`](https://mobiscroll.com/docs/react/select/api#opt-anchor) - option
- [`isOpen`](https://mobiscroll.com/docs/react/select/api#opt-isOpen) - option
- [`buttons`](https://mobiscroll.com/docs/react/select/api#opt-buttons) - option
- [`data`](https://mobiscroll.com/docs/react/select/api#opt-data) - option
- [`onChange`](https://mobiscroll.com/docs/react/select/api#event-onChange) - event
- [`onClose`](https://mobiscroll.com/docs/react/select/api#event-onClose) - event
- [`inputProps`](https://mobiscroll.com/docs/react/select/api#opt-inputProps) - option
- [`onClose`](https://mobiscroll.com/docs/react/notifications/toast#event-onClose) - event
- [`onClose`](https://mobiscroll.com/docs/react/notifications/snackbar#event-onClose) - event

## Code

### JSX

```jsx
import { Eventcalendar, Page, Segmented, SegmentedGroup, Select, setOptions, Snackbar, Toast } from '@mobiscroll/react';
import { useCallback, useMemo, useRef, useState } from 'react';

setOptions({
  theme: 'ios',
  themeVariant: 'light'
});

const today = new Date();

function App() {
  const menu = useMemo(
    () => [
      { text: 'Copy', value: 'c' },
      { text: 'Cut', value: 'x' },
      { text: 'Paste', value: 'v' },
      { text: 'Delete', value: 'delete' },
    ],
    [],
  );

  const disabledMenu = useMemo(
    () => [
      { text: 'Copy', value: 'c', disabled: true },
      { text: 'Cut', value: 'x', disabled: true },
      { text: 'Paste', value: 'v' },
      { text: 'Delete', value: 'delete', disabled: true },
    ],
    [],
  );

  const [selectValue, setSelectValue] = useState();
  const [menuAnchor, setMenuAnchor] = useState();
  const [activeCalendar, setActiveCalendar] = useState('first');
  const [cutCalendar, setCutCalendar] = useState('first');
  const [toDate, setToDate] = useState(today);
  const [firstToDate, setFirstToDate] = useState(today);
  const [secondToDate, setSecondToDate] = useState(today);
  const [originDate, setOriginDate] = useState(today);
  const [menuOpen, setMenuOpen] = useState(false);
  const [menuData, setMenuData] = useState(menu);
  const [firstSelectedEvents, setFirstSelectedEvents] = useState([]);
  const [secondSelectedEvents, setSecondSelectedEvents] = useState([]);
  const [selectedEvents, setSelectedEvents] = useState([]);
  const [moveEvents, setMoveEvents] = useState([]);
  const [pastedEvents, setPastedEvents] = useState([]);
  const [deletedEvents, setDeletedEvents] = useState([]);
  const [isToastOpen, setToastOpen] = useState(false);
  const [toastMessage, setToastMessage] = useState('');
  const [isSnackbarOpen, setSnackbarOpen] = useState(false);
  const [snackbarMessage, setSnackbarMessage] = useState('');
  const [snackbarButton, setSnackbarButton] = useState();
  const [firstEvents, setFirstEvents] = useState([
    {
      start: '2026-10-02T09:00',
      end: '2026-10-06T18:00',
      title: 'Business of Software Conference',
      color: '#ff6d42',
    },
    {
      start: '2026-10-10T07:00',
      end: '2026-10-10T08:00',
      title: 'Green box to post office',
      color: '#6e7f29',
    },
    {
      start: '2026-10-15T09:30',
      end: '2026-10-15T10:30',
      title: 'Product team mtg.',
      color: '#f67944',
    },
    {
      start: '2026-10-23T11:00',
      end: '2026-10-23T11:45',
      title: 'Stakeholder mtg.',
      color: '#a144f6',
    },
    {
      start: '2026-10-28T13:00',
      end: '2026-10-28T13:45',
      title: "Lunch @ Butcher's",
      color: '#00aabb',
    },
  ]);
  const [secondEvents, setSecondEvents] = useState([
    {
      start: '2026-10-04T08:45',
      end: '2026-10-04T10:00',
      title: 'Quick mtg. with Martin',
      color: '#de3d83',
    },
    {
      start: '2026-10-08T15:00',
      end: '2026-10-08T16:00',
      title: 'General orientation',
      color: '#a71111',
    },
    {
      start: '2026-10-10T13:00',
      end: '2026-10-14T21:00',
      title: 'Friends binge marathon',
      color: '#7bde83',
    },
    {
      start: '2026-10-23T08:00',
      end: '2026-10-27T09:00',
      title: 'Product team mtg.',
      color: '#913aa7',
    },
  ]);

  const dummyRef = useRef();
  const isMenuOpen = useRef();
  const action = useRef();

  const myView = useMemo(() => ({ calendar: { labels: 'all' } }), []);

  const handleToastClose = useCallback(() => {
    setToastOpen(false);
  }, []);

  const handleSnackbarClose = useCallback(() => {
    setSnackbarOpen(false);
  }, []);

  const handlePageLoading = useCallback(
    (args) => {
      setTimeout(() => {
        if (activeCalendar === 'first') {
          setFirstToDate(args.month);
        } else {
          setSecondToDate(args.month);
        }
        setToDate(args.month);
      });
    },
    [activeCalendar],
  );

  const handleCellRightClick = useCallback(
    (args) => {
      if (!isMenuOpen.current) {
        args.domEvent.preventDefault();
        setMenuData(disabledMenu);
        setMenuAnchor(args.domEvent.target);
        setTimeout(() => {
          setMenuOpen(true);
        });
      }
    },
    [isMenuOpen, disabledMenu],
  );

  const handleEventRightClick = useCallback(
    (args) => {
      const activeEvents = activeCalendar === 'first' ? firstEvents : secondEvents;
      if (activeEvents.length <= 1) {
        if (activeCalendar === 'first') {
          setFirstEvents([args.event]);
        } else {
          setSecondEvents([args.event]);
        }
      }
      if (activeCalendar === 'first' && firstSelectedEvents.length <= 1) {
        setFirstSelectedEvents([args.event]);
      }
      if (activeCalendar === 'second' && secondSelectedEvents.length <= 1) {
        setSecondSelectedEvents([args.event]);
      }
      args.domEvent.preventDefault();
      setMenuData(menu);
      setMenuAnchor(args.domEvent.target);
      setTimeout(() => {
        setMenuOpen(true);
      });
      isMenuOpen.current = true;
    },
    [activeCalendar, firstEvents, secondEvents, firstSelectedEvents, secondSelectedEvents, menu],
  );

  const handleEventDeleted = useCallback(
    (args) => {
      setDeletedEvents(args.events);
      action.current = 'delete';
      setTimeout(() => {
        setSnackbarButton({
          action: () => {
            const activeEvents = activeCalendar === 'first' ? firstEvents : secondEvents;
            let eventsToUpdate = [...activeEvents];
            for (const event of deletedEvents) {
              eventsToUpdate = eventsToUpdate.filter((ev) => ev.id !== event.id);
            }

            if (activeCalendar === 'first') {
              setFirstEvents(eventsToUpdate);
            } else {
              setSecondEvents(eventsToUpdate);
            }

            setDeletedEvents([]);
          },
          text: 'Undo',
        });
        setSnackbarMessage('Event' + (deletedEvents.length === 1 ? '' : 's') + ' deleted');
        setSnackbarOpen(true);
      });
      dummyRef.current.focus();
    },
    [activeCalendar, deletedEvents, firstEvents, secondEvents],
  );

  const getActiveEvents = useCallback(
    () => (activeCalendar === 'first' ? firstEvents : secondEvents),
    [activeCalendar, firstEvents, secondEvents],
  );

  const getActiveSelectedEvents = useCallback(
    () => (activeCalendar === 'first' ? firstSelectedEvents : secondSelectedEvents),
    [activeCalendar, firstSelectedEvents, secondSelectedEvents],
  );

  const monthDiff = useCallback((d1, d2) => d2.getMonth() - d1.getMonth() + 12 * (d2.getFullYear() - d1.getFullYear()), []);

  const pasteEvents = useCallback(() => {
    const activeEvents = getActiveEvents();
    const activeSelectedEvents = selectedEvents;
    let eventsToUpdate = [...activeEvents];
    if (activeSelectedEvents.length > 0) {
      for (const event of activeSelectedEvents) {
        const newEvent = Object.assign({}, event);
        const startDate = new Date(event.start);
        const endDate = new Date(event.end);
        const diff = Math.abs(endDate - startDate);

        newEvent.start = startDate.setMonth(startDate.getMonth() - monthDiff(toDate, originDate));
        newEvent.end = new Date(startDate.getTime() + diff);

        delete newEvent.id;

        eventsToUpdate = [...eventsToUpdate, newEvent];

        setPastedEvents([...pastedEvents, newEvent]);
      }

      if (activeCalendar === 'first') {
        setFirstEvents(eventsToUpdate);
      } else {
        setSecondEvents(eventsToUpdate);
      }

      if (action.current === 'cut') {
        let cutEvs = activeCalendar === cutCalendar ? eventsToUpdate : cutCalendar === 'first' ? firstEvents : secondEvents;
        setMoveEvents([...selectedEvents]);
        for (const event of selectedEvents) {
          cutEvs = cutEvs.filter((ev) => ev.id !== event.id);
        }
        if (cutCalendar === 'first') {
          setFirstEvents(cutEvs);
        } else {
          setSecondEvents(cutEvs);
        }

        setTimeout(() => {
          setSnackbarButton({
            action: () => {
              let revertEvs = cutCalendar === 'first' ? firstEvents : secondEvents;
              for (const event of moveEvents) {
                revertEvs = [...revertEvs, event];
              }
              if (cutCalendar === 'first') {
                setFirstEvents(revertEvs);
              } else {
                setSecondEvents(revertEvs);
              }

              let cutEvs = getActiveEvents();
              for (const event of pastedEvents) {
                cutEvs = cutEvs.filter((ev) => ev.id !== event.id);
              }
              if (activeCalendar === 'first') {
                setFirstEvents(cutEvs);
              } else {
                setSecondEvents(cutEvs);
              }

              setToastMessage('Event' + (selectedEvents.length === 1 ? '' : 's') + ' reverted');
              setToastOpen(true);
            },
            text: 'Undo',
          });
          setSnackbarMessage('Event' + (selectedEvents.length === 1 ? '' : 's') + ' pasted');
          setSnackbarOpen(true);
        });
        dummyRef.current.focus();
      } else {
        setToastMessage('Event' + (activeSelectedEvents.length === 1 ? '' : 's') + ' pasted');
        setToastOpen(true);
      }
      if (action.current !== 'copy') {
        setSelectedEvents([]);
      }
    }
  }, [
    action,
    activeCalendar,
    cutCalendar,
    firstEvents,
    getActiveEvents,
    monthDiff,
    moveEvents,
    originDate,
    pastedEvents,
    secondEvents,
    selectedEvents,
    toDate,
  ]);

  const deleteEvents = useCallback(() => {
    const activeEvents = getActiveEvents();
    let eventsToUpdate = [...activeEvents];
    action.current = 'delete';
    const activeSelectedEvents = getActiveSelectedEvents();

    if (activeSelectedEvents.length > 0) {
      setDeletedEvents(activeSelectedEvents);

      for (const event of activeSelectedEvents) {
        eventsToUpdate = eventsToUpdate.filter((ev) => ev.id !== event.id);
      }

      if (activeCalendar === 'first') {
        setFirstEvents(eventsToUpdate);
      } else {
        setSecondEvents(eventsToUpdate);
      }
      setTimeout(() => {
        setSnackbarButton({
          action: () => {
            for (const event of activeSelectedEvents) {
              eventsToUpdate = [...eventsToUpdate, event];
            }
            if (activeCalendar === 'first') {
              setFirstEvents(eventsToUpdate);
            } else {
              setSecondEvents(eventsToUpdate);
            }
            setDeletedEvents([]);
          },
          text: 'Undo',
        });
        setSnackbarMessage('Event' + (activeSelectedEvents.length === 1 ? '' : 's') + ' deleted');
        setSnackbarOpen(true);
      });
      dummyRef.current.focus();
    }
  }, [activeCalendar, getActiveEvents, getActiveSelectedEvents]);

  const activateAction = useCallback(
    (type) => {
      if (selectedEvents.length > 0) {
        const act = type == 'copy' ? ' copied' : ' cut';
        setOriginDate(toDate);
        setToastMessage('Event' + (selectedEvents.length === 1 ? '' : 's') + act);
        setToastOpen(true);
      }
    },
    [selectedEvents, toDate],
  );

  const copyEvents = useCallback(() => {
    if (activeCalendar === 'first') {
      if (firstSelectedEvents.length > 0) {
        action.current = 'copy';
        setSelectedEvents(firstSelectedEvents);
        activateAction('copy');
      }
    } else {
      if (secondSelectedEvents.length > 0) {
        action.current = 'copy';
        setSelectedEvents(secondSelectedEvents);
        activateAction('copy');
      }
    }
  }, [activateAction, activeCalendar, firstSelectedEvents, secondSelectedEvents]);

  const cutEvents = useCallback(() => {
    if (activeCalendar === 'first') {
      if (firstSelectedEvents.length > 0) {
        action.current = 'cut';
        setSelectedEvents(firstSelectedEvents);
        setCutCalendar(activeCalendar);
        activateAction('cut');
        setDeletedEvents([]);
      }
    } else {
      if (secondSelectedEvents.length > 0) {
        action.current = 'cut';
        setSelectedEvents(secondSelectedEvents);
        setCutCalendar(activeCalendar);
        activateAction('cut');
        setDeletedEvents([]);
      }
    }
  }, [activateAction, activeCalendar, firstSelectedEvents, secondSelectedEvents]);

  const undoEvents = useCallback(() => {
    const activeEvents = getActiveEvents();
    let eventsToUpdate = [...activeEvents];
    if (action.current === 'delete') {
      for (const event of deletedEvents) {
        eventsToUpdate = [...eventsToUpdate, event];
      }
      if (activeCalendar === 'first') {
        setFirstEvents(eventsToUpdate);
      } else {
        setSecondEvents(eventsToUpdate);
      }
      setDeletedEvents([]);
    } else if (action.current === 'cut') {
      let revertEvs = cutCalendar === 'first' ? firstEvents : secondEvents;
      for (const event of moveEvents) {
        revertEvs = [...revertEvs, event];
      }
      if (cutCalendar === 'first') {
        setFirstEvents(revertEvs);
      } else {
        setSecondEvents(revertEvs);
      }

      let cutEvs = getActiveEvents();
      for (const event of pastedEvents) {
        cutEvs = cutEvs.filter((ev) => ev.id !== event.id);
      }
      if (activeCalendar === 'first') {
        setFirstEvents(cutEvs);
      } else {
        setSecondEvents(cutEvs);
      }

      setMoveEvents([]);
      setPastedEvents([]);
    }
  }, [action, activeCalendar, cutCalendar, deletedEvents, firstEvents, getActiveEvents, moveEvents, pastedEvents, secondEvents]);

  const detectAction = useCallback(
    (key) => {
      switch (key) {
        case 'delete': // Delete
          deleteEvents();
          break;
        case 'c': // Copy
          copyEvents();
          break;
        case 'x': // Cut
          cutEvents();
          break;
        case 'z': // Undo
          undoEvents();
          break;
        case 'v': // Paste
          pasteEvents();
          break;
        default:
      }
    },
    [copyEvents, cutEvents, deleteEvents, pasteEvents, undoEvents],
  );

  const handleFirstSelectedEventsChange = useCallback((args) => {
    setFirstSelectedEvents(args.events);
  }, []);

  const handleSecondSelectedEventsChange = useCallback((args) => {
    setSecondSelectedEvents(args.events);
  }, []);

  const handleSelectChange = useCallback(
    (args) => {
      setSelectValue(args.value);
      detectAction(args.value);
    },
    [detectAction],
  );

  const handleSelectClose = useCallback(() => {
    isMenuOpen.current = false;
    setMenuOpen(false);
    // Clear selection
    setSelectValue(null);
  }, []);

  const switchCalendar = useCallback(
    (ev) => {
      setActiveCalendar(ev.target.value);
      if (ev.target.value === 'first') {
        setToDate(firstToDate);
        setSecondSelectedEvents([]);
      } else {
        setToDate(secondToDate);
        setFirstSelectedEvents([]);
      }
    },
    [firstToDate, secondToDate],
  );

  const handleKeyDown = (ev) => {
    if (ev.ctrlKey || ev.metaKey) {
      detectAction(ev.key);
    }
    if (ev.key === 'Delete') {
      detectAction('delete');
    }
  };

  return (
    <Page>
      <div onKeyDown={handleKeyDown}>
        <div className="mbsc-flex-col md-copy-cut-paste">
          <div className="mbsc-flex-none">
            <SegmentedGroup name="controlled" onChange={switchCalendar}>
              <Segmented value="first" checked={activeCalendar === 'first'}>
                First calendar
              </Segmented>
              <Segmented value="second" checked={activeCalendar === 'second'}>
                Second calendar
              </Segmented>
            </SegmentedGroup>
          </div>
          <div className="mbsc-flex-col mbsc-flex-1-1">
            <div id="demo-copy-cut-paste-first-cont" className="mbsc-flex-1-1 md-copy-cut-paste-cont">
              <Eventcalendar
                className={activeCalendar === 'first' ? '' : 'md-hide-calendar'}
                view={myView}
                clickToCreate={true}
                dragToCreate={true}
                dragToMove={true}
                dragToResize={true}
                selectMultipleEvents={true}
                data={firstEvents}
                selectedEvents={firstSelectedEvents}
                onSelectedEventsChange={handleFirstSelectedEventsChange}
                onPageLoading={handlePageLoading}
                onCellRightClick={handleCellRightClick}
                onEventRightClick={handleEventRightClick}
                onEventDeleted={handleEventDeleted}
              />
            </div>
            <div id="demo-copy-cut-paste-second-cont" className="mbsc-flex-1-1 md-copy-cut-paste-cont">
              <Eventcalendar
                className={activeCalendar === 'second' ? '' : 'md-hide-calendar'}
                view={myView}
                clickToCreate={true}
                dragToCreate={true}
                dragToMove={true}
                dragToResize={true}
                selectMultipleEvents={true}
                data={secondEvents}
                selectedEvents={secondSelectedEvents}
                onSelectedEventsChange={handleSecondSelectedEventsChange}
                onPageLoading={handlePageLoading}
                onCellRightClick={handleCellRightClick}
                onEventRightClick={handleEventRightClick}
                onEventDeleted={handleEventDeleted}
              />
            </div>
            <Select
              touchUi={false}
              display="anchored"
              anchor={menuAnchor}
              isOpen={menuOpen}
              buttons={[]}
              data={menuData}
              value={selectValue}
              onChange={handleSelectChange}
              onClose={handleSelectClose}
              inputProps={{ type: 'hidden' }}
            />
          </div>
        </div>
        <div tabIndex={-1} ref={dummyRef}></div>
      </div>
      <Toast isOpen={isToastOpen} message={toastMessage} onClose={handleToastClose} />
      <Snackbar isOpen={isSnackbarOpen} duration={3000} message={snackbarMessage} button={snackbarButton} onClose={handleSnackbarClose} />
    </Page>
  );
}

export default App;
```

### TSX

```tsx
import {
  Eventcalendar,
  MbscCalendarEvent,
  MbscCellClickEvent,
  MbscEventcalendarView,
  MbscEventClickEvent,
  MbscEventDeletedEvent,
  MbscPageLoadingEvent,
  MbscSelectChangeEvent,
  MbscSelectedEventsChangeEvent,
  Page,
  Segmented,
  SegmentedGroup,
  Select,
  setOptions,
  Snackbar,
  Toast,
} from '@mobiscroll/react';
import { ChangeEvent, FC, KeyboardEvent, useCallback, useRef, useState } from 'react';

setOptions({
  theme: 'ios',
  themeVariant: 'light'
});

const today = new Date();

const viewSettings: MbscEventcalendarView = {
  calendar: { labels: 'all' },
};
const menu = [
  { text: 'Copy', value: 'c' },
  { text: 'Cut', value: 'x' },
  { text: 'Paste', value: 'v' },
  { text: 'Delete', value: 'delete' },
];
const disabledMenu = [
  { text: 'Copy', value: 'c', disabled: true },
  { text: 'Cut', value: 'x', disabled: true },
  { text: 'Paste', value: 'v' },
  { text: 'Delete', value: 'delete', disabled: true },
];

const App: FC = () => {
  const [selectValue, setSelectValue] = useState(null);
  const [menuAnchor, setMenuAnchor] = useState<HTMLElement>();
  const [activeCalendar, setActiveCalendar] = useState<string>('first');
  const [cutCalendar, setCutCalendar] = useState<string>('first');
  const [toDate, setToDate] = useState(new Date());
  const [firstToDate, setFirstToDate] = useState(today);
  const [secondToDate, setSecondToDate] = useState(today);
  const [originDate, setOriginDate] = useState(today);
  const [menuOpen, setMenuOpen] = useState<boolean>(false);
  const [menuData, setMenuData] = useState(menu);
  const [firstSelectedEvents, setFirstSelectedEvents] = useState<MbscCalendarEvent[]>([]);
  const [secondSelectedEvents, setSecondSelectedEvents] = useState<MbscCalendarEvent[]>([]);
  const [selectedEvents, setSelectedEvents] = useState<MbscCalendarEvent[]>([]);
  const [moveEvents, setMoveEvents] = useState<MbscCalendarEvent[]>([]);
  const [pastedEvents, setPastedEvents] = useState<MbscCalendarEvent[]>([]);
  const [deletedEvents, setDeletedEvents] = useState<MbscCalendarEvent[]>([]);
  const [isToastOpen, setToastOpen] = useState(false);
  const [toastMessage, setToastMessage] = useState('');
  const [isSnackbarOpen, setSnackbarOpen] = useState(false);
  const [snackbarMessage, setSnackbarMessage] = useState('');
  const [snackbarButton, setSnackbarButton] = useState<{ action: () => void; text: string }>();

  const [firstEvents, setFirstEvents] = useState<MbscCalendarEvent[]>([
    {
      start: '2026-10-02T09:00',
      end: '2026-10-06T18:00',
      title: 'Business of Software Conference',
      color: '#ff6d42',
    },
    {
      start: '2026-10-10T07:00',
      end: '2026-10-10T08:00',
      title: 'Green box to post office',
      color: '#6e7f29',
    },
    {
      start: '2026-10-15T09:30',
      end: '2026-10-15T10:30',
      title: 'Product team mtg.',
      color: '#f67944',
    },
    {
      start: '2026-10-23T11:00',
      end: '2026-10-23T11:45',
      title: 'Stakeholder mtg.',
      color: '#a144f6',
    },
    {
      start: '2026-10-28T13:00',
      end: '2026-10-28T13:45',
      title: "Lunch @ Butcher's",
      color: '#00aabb',
    },
  ]);

  const [secondEvents, setSecondEvents] = useState<MbscCalendarEvent[]>([
    {
      start: '2026-10-04T08:45',
      end: '2026-10-04T10:00',
      title: 'Quick mtg. with Martin',
      color: '#de3d83',
    },
    {
      start: '2026-10-08T15:00',
      end: '2026-10-08T16:00',
      title: 'General orientation',
      color: '#a71111',
    },
    {
      start: '2026-10-10T13:00',
      end: '2026-10-14T21:00',
      title: 'Friends binge marathon',
      color: '#7bde83',
    },
    {
      start: '2026-10-23T08:00',
      end: '2026-10-27T09:00',
      title: 'Product team mtg.',
      color: '#913aa7',
    },
  ]);

  const dummyRef = useRef<HTMLDivElement>(null);
  const isMenuOpen = useRef<boolean>(false);
  const action = useRef<string>(undefined);

  const handleToastClose = useCallback(() => {
    setToastOpen(false);
  }, []);

  const handleSnackbarClose = useCallback(() => {
    setSnackbarOpen(false);
  }, []);

  const handlePageLoading = useCallback(
    (args: MbscPageLoadingEvent) => {
      setTimeout(() => {
        if (activeCalendar === 'first') {
          setFirstToDate(args.month!);
        } else {
          setSecondToDate(args.month!);
        }
        setToDate(args.month!);
      });
    },
    [activeCalendar],
  );

  const handleCellRightClick = useCallback(
    (args: MbscCellClickEvent) => {
      if (!isMenuOpen.current) {
        args.domEvent.preventDefault();
        setMenuData(disabledMenu);
        setMenuAnchor(args.target);
        setTimeout(() => {
          setMenuOpen(true);
        });
      }
    },
    [isMenuOpen],
  );

  const handleEventRightClick = useCallback(
    (args: MbscEventClickEvent) => {
      const activeEvents = activeCalendar === 'first' ? firstEvents : secondEvents;
      if (activeEvents.length <= 1) {
        if (activeCalendar === 'first') {
          setFirstEvents([args.event]);
        } else {
          setSecondEvents([args.event]);
        }
      }
      args.domEvent.preventDefault();
      setMenuData(menu);
      setMenuAnchor(args.domEvent.target);
      setTimeout(() => {
        setMenuOpen(true);
      });
      isMenuOpen.current = true;
    },
    [activeCalendar, firstEvents, secondEvents],
  );

  const handleEventDeleted = useCallback(
    (args: MbscEventDeletedEvent) => {
      setDeletedEvents(args.events!);
      action.current = 'delete';
      setTimeout(() => {
        setSnackbarButton({
          action: () => {
            const activeEvents = activeCalendar === 'first' ? firstEvents : secondEvents;
            let eventsToUpdate = [...activeEvents];
            for (const event of deletedEvents) {
              eventsToUpdate = eventsToUpdate.filter((ev) => ev.id !== event.id);
            }

            if (activeCalendar === 'first') {
              setFirstEvents(eventsToUpdate);
            } else {
              setSecondEvents(eventsToUpdate);
            }

            setDeletedEvents([]);
          },
          text: 'Undo',
        });
        setSnackbarMessage('Event' + (deletedEvents.length === 1 ? '' : 's') + ' deleted');
        setSnackbarOpen(true);
      });
      dummyRef.current!.focus();
    },
    [activeCalendar, deletedEvents, firstEvents, secondEvents],
  );

  const getActiveEvents = useCallback(
    () => (activeCalendar === 'first' ? firstEvents : secondEvents),
    [activeCalendar, firstEvents, secondEvents],
  );

  const getActiveSelectedEvents = useCallback(
    () => (activeCalendar === 'first' ? firstSelectedEvents : secondSelectedEvents),
    [activeCalendar, firstSelectedEvents, secondSelectedEvents],
  );

  const monthDiff = useCallback((d1: Date, d2: Date) => d2.getMonth() - d1.getMonth() + 12 * (d2.getFullYear() - d1.getFullYear()), []);

  const pasteEvents = useCallback(() => {
    const activeEvents = getActiveEvents();
    const activeSelectedEvents = selectedEvents;
    let eventsToUpdate = [...activeEvents];
    if (activeSelectedEvents.length > 0) {
      for (const event of activeSelectedEvents) {
        const newEvent = Object.assign({}, event);
        const startDate = new Date(event.start as string);
        const endDate = new Date(event.end as string);
        const diff = Math.abs(+endDate - +startDate);

        newEvent.start = new Date(startDate.setMonth(startDate.getMonth() - monthDiff(toDate, originDate)));
        newEvent.end = new Date(startDate.getTime() + diff);

        delete newEvent.id;

        eventsToUpdate = [...eventsToUpdate, newEvent];

        setPastedEvents([...pastedEvents, newEvent]);
      }

      if (activeCalendar === 'first') {
        setFirstEvents(eventsToUpdate);
      } else {
        setSecondEvents(eventsToUpdate);
      }

      if (action.current === 'cut') {
        let cutEvs = activeCalendar === cutCalendar ? eventsToUpdate : cutCalendar === 'first' ? firstEvents : secondEvents;
        setMoveEvents([...selectedEvents]);
        for (const event of selectedEvents) {
          cutEvs = cutEvs.filter((ev) => ev.id !== event.id);
        }
        if (cutCalendar === 'first') {
          setFirstEvents(cutEvs);
        } else {
          setSecondEvents(cutEvs);
        }

        setTimeout(() => {
          setSnackbarButton({
            action: () => {
              let revertEvs = cutCalendar === 'first' ? firstEvents : secondEvents;
              for (const event of moveEvents) {
                revertEvs = [...revertEvs, event];
              }
              if (cutCalendar === 'first') {
                setFirstEvents(revertEvs);
              } else {
                setSecondEvents(revertEvs);
              }

              let cutEvs = getActiveEvents();
              for (const event of pastedEvents) {
                cutEvs = cutEvs.filter((ev) => ev.id !== event.id);
              }
              if (activeCalendar === 'first') {
                setFirstEvents(cutEvs);
              } else {
                setSecondEvents(cutEvs);
              }
              setToastMessage('Event' + (selectedEvents.length === 1 ? '' : 's') + ' reverted');
              setToastOpen(true);
            },
            text: 'Undo',
          });
          setSnackbarMessage('Event' + (selectedEvents.length === 1 ? '' : 's') + ' pasted');
          setSnackbarOpen(true);
        });
        dummyRef.current!.focus();
      } else {
        setToastMessage('Event' + (activeSelectedEvents.length === 1 ? '' : 's') + ' pasted');
        setToastOpen(true);
      }
      if (action.current !== 'copy') {
        setSelectedEvents([]);
      }
    }
  }, [
    action,
    activeCalendar,
    cutCalendar,
    firstEvents,
    getActiveEvents,
    monthDiff,
    moveEvents,
    originDate,
    pastedEvents,
    secondEvents,
    selectedEvents,
    toDate,
  ]);

  const deleteEvents = useCallback(() => {
    const activeEvents = getActiveEvents();
    let eventsToUpdate = [...activeEvents];
    action.current = 'delete';
    const activeSelectedEvents = getActiveSelectedEvents();

    if (activeSelectedEvents.length > 0) {
      setDeletedEvents(activeSelectedEvents);

      for (const event of activeSelectedEvents) {
        eventsToUpdate = eventsToUpdate.filter((ev) => ev.id !== event.id);
      }

      if (activeCalendar === 'first') {
        setFirstEvents(eventsToUpdate);
      } else {
        setSecondEvents(eventsToUpdate);
      }
      setTimeout(() => {
        setSnackbarButton({
          action: () => {
            for (const event of activeSelectedEvents) {
              eventsToUpdate = [...eventsToUpdate, event];
            }
            if (activeCalendar === 'first') {
              setFirstEvents(eventsToUpdate);
            } else {
              setSecondEvents(eventsToUpdate);
            }
            setDeletedEvents([]);
          },
          text: 'Undo',
        });
        setSnackbarMessage('Event' + (activeSelectedEvents.length === 1 ? '' : 's') + ' deleted');
        setSnackbarOpen(true);
      });
      dummyRef.current!.focus();
    }
  }, [activeCalendar, getActiveEvents, getActiveSelectedEvents]);

  const activateAction = useCallback(
    (type: string) => {
      if (selectedEvents.length > 0) {
        const act = type == 'copy' ? ' copied' : ' cut';
        setOriginDate(toDate);
        setToastMessage('Event' + (selectedEvents.length === 1 ? '' : 's') + act);
        setToastOpen(true);
      }
    },
    [selectedEvents, toDate],
  );

  const copyEvents = useCallback(() => {
    if (activeCalendar === 'first') {
      if (firstSelectedEvents.length > 0) {
        action.current = 'copy';
        setSelectedEvents(firstSelectedEvents);
        activateAction('copy');
      }
    } else {
      if (secondSelectedEvents.length > 0) {
        action.current = 'copy';
        setSelectedEvents(secondSelectedEvents);
        activateAction('copy');
      }
    }
  }, [activateAction, activeCalendar, firstSelectedEvents, secondSelectedEvents]);

  const cutEvents = useCallback(() => {
    if (activeCalendar === 'first') {
      if (firstSelectedEvents.length > 0) {
        action.current = 'cut';
        setSelectedEvents(firstSelectedEvents);
        setCutCalendar(activeCalendar);
        activateAction('cut');
        setDeletedEvents([]);
      }
    } else {
      if (secondSelectedEvents.length > 0) {
        action.current = 'cut';
        setSelectedEvents(secondSelectedEvents);
        setCutCalendar(activeCalendar);
        activateAction('cut');
        setDeletedEvents([]);
      }
    }
  }, [activateAction, activeCalendar, firstSelectedEvents, secondSelectedEvents]);

  const undoEvents = useCallback(() => {
    const activeEvents = getActiveEvents();
    let eventsToUpdate = [...activeEvents];
    if (action.current === 'delete') {
      for (const event of deletedEvents) {
        eventsToUpdate = [...eventsToUpdate, event];
      }
      if (activeCalendar === 'first') {
        setFirstEvents(eventsToUpdate);
      } else {
        setSecondEvents(eventsToUpdate);
      }
      setDeletedEvents([]);
    } else if (action.current === 'cut') {
      let revertEvs = cutCalendar === 'first' ? firstEvents : secondEvents;
      for (const event of moveEvents) {
        revertEvs = [...revertEvs, event];
      }
      if (cutCalendar === 'first') {
        setFirstEvents(revertEvs);
      } else {
        setSecondEvents(revertEvs);
      }

      let cutEvs = getActiveEvents();
      for (const event of pastedEvents) {
        cutEvs = cutEvs.filter((ev) => ev.id !== event.id);
      }
      if (activeCalendar === 'first') {
        setFirstEvents(cutEvs);
      } else {
        setSecondEvents(cutEvs);
      }

      setMoveEvents([]);
      setPastedEvents([]);
    }
  }, [action, activeCalendar, cutCalendar, deletedEvents, firstEvents, getActiveEvents, moveEvents, pastedEvents, secondEvents]);

  const detectAction = useCallback(
    (key: string) => {
      switch (key) {
        case 'delete': // Delete
          deleteEvents();
          break;
        case 'c': // Copy
          copyEvents();
          break;
        case 'x': // Cut
          cutEvents();
          break;
        case 'z': // Undo
          undoEvents();
          break;
        case 'v': // Paste
          pasteEvents();
          break;
        default:
      }
    },
    [copyEvents, cutEvents, deleteEvents, pasteEvents, undoEvents],
  );

  const onFirstSelectedEventsChange = useCallback((args: MbscSelectedEventsChangeEvent) => {
    setFirstSelectedEvents(args.events);
  }, []);

  const onSecondSelectedEventsChange = useCallback((args: MbscSelectedEventsChangeEvent) => {
    setSecondSelectedEvents(args.events);
  }, []);

  const onSelectChange = useCallback(
    (args: MbscSelectChangeEvent) => {
      setSelectValue(args.value);
      detectAction(args.value);
    },
    [detectAction],
  );

  const onSelectClose = useCallback(() => {
    isMenuOpen.current = false;
    setMenuOpen(false);
    // Clear selection
    setSelectValue(null);
  }, []);

  const switchCalendar = useCallback(
    (ev: ChangeEvent<HTMLInputElement>) => {
      setActiveCalendar(ev.target.value);
      if (ev.target.value === 'first') {
        setToDate(firstToDate);
        setSecondSelectedEvents([]);
      } else {
        setToDate(secondToDate);
        setFirstSelectedEvents([]);
      }
    },
    [firstToDate, secondToDate, setFirstSelectedEvents, setSecondSelectedEvents],
  );

  const handleKeyDown = (ev: KeyboardEvent<HTMLDivElement>) => {
    if (ev.ctrlKey || ev.metaKey) {
      detectAction(ev.key);
    }
    if (ev.key === 'Delete') {
      detectAction('delete');
    }
  };

  return (
    <Page>
      <div onKeyDown={handleKeyDown}>
        <div className="mbsc-flex-col md-copy-cut-paste">
          <div className="mbsc-flex-none">
            <SegmentedGroup name="controlled" onChange={switchCalendar}>
              <Segmented value="first" checked={activeCalendar === 'first'}>
                First calendar
              </Segmented>
              <Segmented value="second" checked={activeCalendar === 'second'}>
                Second calendar
              </Segmented>
            </SegmentedGroup>
          </div>
          <div className="mbsc-flex-col mbsc-flex-1-1">
            <div id="demo-copy-cut-paste-first-cont" className="mbsc-flex-1-1 md-copy-cut-paste-cont">
              <Eventcalendar
                className={activeCalendar === 'first' ? '' : 'md-hide-calendar'}
                view={viewSettings}
                clickToCreate={true}
                dragToCreate={true}
                dragToMove={true}
                dragToResize={true}
                selectMultipleEvents={true}
                data={firstEvents}
                selectedEvents={firstSelectedEvents}
                onSelectedEventsChange={onFirstSelectedEventsChange}
                onPageLoading={handlePageLoading}
                onCellRightClick={handleCellRightClick}
                onEventRightClick={handleEventRightClick}
                onEventDeleted={handleEventDeleted}
              />
            </div>
            <div id="demo-copy-cut-paste-second-cont" className="mbsc-flex-1-1 md-copy-cut-paste-cont">
              <Eventcalendar
                className={activeCalendar === 'second' ? '' : 'md-hide-calendar'}
                view={viewSettings}
                clickToCreate={true}
                dragToCreate={true}
                dragToMove={true}
                dragToResize={true}
                selectMultipleEvents={true}
                data={secondEvents}
                selectedEvents={secondSelectedEvents}
                onSelectedEventsChange={onSecondSelectedEventsChange}
                onPageLoading={handlePageLoading}
                onCellRightClick={handleCellRightClick}
                onEventRightClick={handleEventRightClick}
                onEventDeleted={handleEventDeleted}
              />
            </div>
            <Select
              touchUi={false}
              display="anchored"
              anchor={menuAnchor}
              isOpen={menuOpen}
              buttons={[]}
              data={menuData}
              value={selectValue}
              onChange={onSelectChange}
              onClose={onSelectClose}
              inputProps={{ type: 'hidden' }}
            />
          </div>
        </div>
        <div tabIndex={-1} ref={dummyRef}></div>
      </div>
      <Toast isOpen={isToastOpen} message={toastMessage} onClose={handleToastClose} />
      <Snackbar isOpen={isSnackbarOpen} duration={3000} message={snackbarMessage} button={snackbarButton} onClose={handleSnackbarClose} />
    </Page>
  );
};
export default App;
```

### CSS

```css
.md-copy-cut-paste .mbsc-segmented {
  width: 300px;
  margin-left: auto;
  margin-right: auto;
}

.md-copy-cut-paste .md-hide-calendar {
  visibility: hidden;
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
}
```

## Implementation notes

- Use `view: { calendar: { labels: 'all' } }` (not `true` - `'all'` shows every event label without collapsing). Enable `clickToCreate: true`, `dragToCreate: true`, `dragToMove: true`, `dragToResize: true`, and `selectMultipleEvents: true` on both calendar instances.
- Render two `Eventcalendar` instances in a stacked container. Both are always in the DOM; the inactive one is hidden with a CSS class (`visibility: hidden; position: absolute`). A `SegmentedGroup` above the container switches between "First calendar" and "Second calendar". On switch, toggle visibility, update `activeCalendar`/`activeInst`, restore the inactive calendar's last known `toDate`, and clear the now-inactive calendar's selection.
- Track each calendar's current month separately (`firstToDate`, `secondToDate`) via `onPageLoading` using `args.month`. Also keep a shared `toDate` pointing to the active calendar's current month - this is the paste destination month.
- Maintain clipboard state: `selectedEvents` (events to paste), `action` (`'copy'`, `'cut'`, `'delete'`), `originDate` (the active `toDate` at copy/cut time), `deletedEvents` and `moveEvents`/`pastedEvents` (for undo).
- **Copy** (`Ctrl/Cmd+C`): store the active calendar's selected events in `selectedEvents`, record `originDate = toDate`, set `action = 'copy'`, show toast.
- **Cut** (`Ctrl/Cmd+X`): same as copy but `action = 'cut'`, record `cutCalendar`/`originInst`. Events are not removed until paste.
- **Paste** (`Ctrl/Cmd+V`): for each event in `selectedEvents`, clone it, delete its `id`, then adjust start/end by subtracting `monthDiff(toDate, originDate)` months (preserving duration). Add the cloned event to the active calendar. For **cut**: also remove the originals from the source calendar, show snackbar with "Undo"; for **copy**: show toast. After paste, clear `selectedEvents` only if action was cut.
- **Delete** (`Delete` key or context menu): remove selected events from the active calendar, store them in `deletedEvents`, show snackbar with "Undo" that re-adds them. After triggering, move focus to a hidden dummy element (`tabIndex={-1}`) to prevent the keyboard event from re-firing.
- **Undo** (`Ctrl/Cmd+Z`): if `action === 'delete'`, re-add `deletedEvents` to the source calendar; if `action === 'cut'`, re-add `moveEvents` to the source calendar and remove `pastedEvents` from the destination.
- **Right-click context menu**: a `Select` popup with options `Copy`, `Cut`, `Paste`, `Delete`. On `onEventRightClick`: open with all options enabled; if the event right-clicked is not already among multiple selected events, select just that event. On `onCellRightClick` (empty cell): open with Copy/Cut/Delete disabled, Paste enabled only. Dispatch the chosen action through the same `detectAction(key)` handler as keyboard shortcuts.
- `monthDiff(d1, d2)`: returns `d2.getMonth() - d1.getMonth() + 12 * (d2.getFullYear() - d1.getFullYear())` - used to shift pasted event dates to the destination calendar's current month.

## Run it in your project

1. If not signed up yet, or don't own a license, you can [start a free trial](https://mobiscroll.com/starttrial).

2. Clone the repository:

   ```
   git clone https://github.com/acidb/mobiscroll-demos-react.git
   ```

3. If not installed yet, install the Mobiscroll CLI from NPM:

   ```
   npm install -g @mobiscroll/cli
   ```

4. Config Mobiscroll - open a terminal window in the root directory and run:

   ```
   mobiscroll config react
   ```

5. Install dependencies - open a terminal window in the root directory and run:

   ```
   npm install
   ```

6. Run the project:

   ```
   npm run dev
   ```

On a trial, add `--trial` to the config command: `mobiscroll config react --trial`.

Running a demo locally requires a Mobiscroll trial or a license; setup is per framework at https://mobiscroll.com/docs/react/getting-started/installation

## Related demos

- Previous: [Bulk Actions](https://demo.mobiscroll.com/react/eventcalendar/event-bulk-actions-edit-delete-update.md) - Multi-select events by keyboard in a React calendar - bulk delete, update, or run custom actions via action panel or right-click context menu.
