Cut, Copy, Paste Events
Usage
theme="ios" You can turn auto theming on and let it decide based on the platform.
Usage
themeVariant="light" You can turn auto dark mode on based on system settings.
Usage
clickToCreate={true}
// click to create is on dragToCreate={true}
// drag to create is on dragToMove={true}
// reordering is on dragToResize={true}
// resizing is on eventDelete={true}
// deleting is on Fine-tune drag & drop with these options, which are false by default.
Usage
locale={localeEn} Selecting the locale loads the localized settings and the translated UI text.
Cut, Copy, Paste Events
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.
Demo

Code
View code on GitHub
import { Eventcalendar, Page, Segmented, SegmentedGroup, Select, setOptions, Snackbar, Toast } from '@mobiscroll/react';
import { useCallback, useMemo, useRef, useState } from 'react';
setOptions({
locale: ,
theme: ' ',
themeVariant: ' '
});
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;
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({
locale: ,
theme: ' ',
themeVariant: ' '
});
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;
.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;
}