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.

Looking for multiple event selection & bulk operations?  Take a look at the previous example →
Get started with this example

Demo

Loading the live demo

Preview in full screen

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

Code

<script setup>
import {
  MbscEventcalendar,
  MbscPage,
  MbscSegmented,
  MbscSegmentedGroup,
  MbscSelect,
  MbscSnackbar,
  MbscToast,
  setOptions 
} from '@mobiscroll/vue'
import { ref } from 'vue'

setOptions({
  locale:  ,
  theme: ' ',
  themeVariant: ' '
})

const today = new Date()

const defaultMenuData = [
  { 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 firstEvents = ref([
  {
    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 = ref([
  {
    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 myView = {
  calendar: { labels: 'all' }
}
const menuData = ref(defaultMenuData)
const selectValue = ref('')
const menuAnchor = ref(null)
const activeCalendar = ref('first')
const cutCalendar = ref('first')
const toDate = ref(today)
const firstToDate = ref(today)
const secondToDate = ref(today)
const originDate = ref(today)
const isMenuOpen = ref(false)
const selectedEvents = ref([])
const moveEvents = ref([])
const pastedEvents = ref([])
const deletedEvents = ref([])
const action = ref('')
const snackbarMessage = ref('')
const snackbarButton = ref(null)
const isSnackbarOpen = ref(false)
const toastMessage = ref(null)
const isToastOpen = ref(false)
const dummyElm = ref(null)
const menuElm = ref(null)
const firstSelectedEvents = ref([])
const secondSelectedEvents = ref([])

function monthDiff(d1, d2) {
  return d2.getMonth() - d1.getMonth() + 12 * (d2.getFullYear() - d1.getFullYear())
}

function pasteEvents() {
  const activeEvents = getActiveEvents()
  const activeSelectedEvents = selectedEvents.value
  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)

      startDate.setMonth(startDate.getMonth() - monthDiff(toDate.value, originDate.value))

      newEvent.start = startDate
      newEvent.end = new Date(startDate.getTime() + diff)

      delete newEvent.id

      eventsToUpdate = [...eventsToUpdate, newEvent]

      pastedEvents.value = [...pastedEvents.value, newEvent]
    }

    if (activeCalendar.value === 'first') {
      firstEvents.value = eventsToUpdate
    } else {
      secondEvents.value = eventsToUpdate
    }

    if (action.value === 'cut') {
      let cutEvs = cutCalendar.value === 'first' ? firstEvents.value : secondEvents.value
      moveEvents.value = [...selectedEvents.value]
      for (const event of selectedEvents.value) {
        cutEvs = cutEvs.filter((ev) => ev.id !== event.id)
      }
      if (cutCalendar.value === 'first') {
        firstEvents.value = cutEvs
      } else {
        secondEvents.value = cutEvs
      }

      snackbarButton.value = {
        action: () => {
          let revertEvs = cutCalendar.value === 'first' ? firstEvents.value : secondEvents.value
          for (const event of moveEvents.value) {
            revertEvs = [...revertEvs, event]
          }
          if (cutCalendar.value === 'first') {
            firstEvents.value = revertEvs
          } else {
            secondEvents.value = revertEvs
          }

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

          toastMessage.value =
            'Event' + (selectedEvents.value.length === 1 ? '' : 's') + ' reverted'
          isToastOpen.value = true
        },
        text: 'Undo'
      }
      snackbarMessage.value = 'Event' + (selectedEvents.value.length === 1 ? '' : 's') + ' pasted'
      dummyElm.value.focus()
      isSnackbarOpen.value = true
    } else {
      toastMessage.value = 'Event' + (activeSelectedEvents.length === 1 ? '' : 's') + ' pasted'
      isToastOpen.value = true
    }
    if (action.value !== 'copy') {
      selectedEvents.value = []
    }
  }
}

function getActiveEvents() {
  return activeCalendar.value === 'first' ? firstEvents.value : secondEvents.value
}

function getActiveSelectedEvents() {
  return activeCalendar.value === 'first' ? firstSelectedEvents.value : secondSelectedEvents.value
}

function deleteEvents() {
  const activeEvents = getActiveEvents()
  let eventsToUpdate = [...activeEvents]
  action.value = 'delete'
  const activeSelectedEvents = getActiveSelectedEvents()

  if (activeSelectedEvents.length > 0) {
    deletedEvents.value = activeSelectedEvents

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

    if (activeCalendar.value === 'first') {
      firstEvents.value = eventsToUpdate
    } else {
      secondEvents.value = eventsToUpdate
    }

    snackbarButton.value = {
      action: () => {
        for (const event of activeSelectedEvents) {
          eventsToUpdate = [...eventsToUpdate, event]
        }
        if (activeCalendar.value === 'first') {
          firstEvents.value = eventsToUpdate
        } else {
          secondEvents.value = eventsToUpdate
        }
        deletedEvents.value = []
      },
      text: 'Undo'
    }
    snackbarMessage.value = 'Event' + (activeSelectedEvents.length === 1 ? '' : 's') + ' deleted'
    dummyElm.value.focus()
    isSnackbarOpen.value = true
  }
}

function activateAction(type) {
  if (selectedEvents.value.length > 0) {
    const act = type == 'copy' ? ' copied' : ' cut'
    originDate.value = toDate.value
    toastMessage.value = 'Event' + (selectedEvents.value.length === 1 ? '' : 's') + act
    isToastOpen.value = true
  }
}

function copyEvents() {
  if (activeCalendar.value === 'first') {
    if (firstSelectedEvents.value.length > 0) {
      action.value = 'copy'
      selectedEvents.value = firstSelectedEvents.value
      activateAction('copy')
    }
  } else {
    if (secondSelectedEvents.value.length > 0) {
      action.value = 'copy'
      selectedEvents.value = secondSelectedEvents.value
      activateAction('copy')
    }
  }
}

function cutEvents() {
  if (activeCalendar.value === 'first') {
    if (firstSelectedEvents.value.length > 0) {
      action.value = 'cut'
      selectedEvents.value = firstSelectedEvents.value
      cutCalendar.value = activeCalendar.value
      activateAction('cut')
      deletedEvents.value = []
    }
  } else {
    if (secondSelectedEvents.value.length > 0) {
      action.value = 'cut'
      selectedEvents.value = secondSelectedEvents.value
      cutCalendar.value = activeCalendar.value
      activateAction('cut')
      deletedEvents.value = []
    }
  }
}

function undoEvents() {
  const activeEvents = getActiveEvents()
  let eventsToUpdate = [...activeEvents]
  if (action.value === 'delete') {
    for (const event of deletedEvents.value) {
      eventsToUpdate = [...eventsToUpdate, event]
    }
    if (activeCalendar.value === 'first') {
      firstEvents.value = eventsToUpdate
    } else {
      secondEvents.value = eventsToUpdate
    }
    deletedEvents.value = []
  } else if (action.value === 'cut') {
    let revertEvs = cutCalendar.value === 'first' ? firstEvents.value : secondEvents.value
    for (const event of moveEvents.value) {
      revertEvs = [...revertEvs, event]
    }
    if (cutCalendar.value === 'first') {
      firstEvents.value = revertEvs
    } else {
      secondEvents.value = revertEvs
    }

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

    pastedEvents.value = []
  }
}

function detectAction(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
  }
}

function switchCalendar(ev) {
  if (ev.target.value === 'first') {
    toDate.value = firstToDate.value
    secondSelectedEvents.value = []
  } else {
    toDate.value = secondToDate.value
    firstSelectedEvents.value = []
  }
}

function handlePageLoading(args) {
  if (activeCalendar.value === 'first') {
    firstToDate.value = args.month
  } else {
    secondToDate.value = args.month
  }
  toDate.value = args.month
}

function handleCellRightClick(args) {
  if (!isMenuOpen.value) {
    args.domEvent.preventDefault()
    menuData.value = disabledMenu
    menuAnchor.value = args.domEvent.target
    isMenuOpen.value = true
  }
}

function handleEventRightClick(args) {
  const activeEvents = activeCalendar.value === 'first' ? firstEvents.value : secondEvents.value
  if (activeEvents.length <= 1) {
    if (activeCalendar.value === 'first') {
      firstEvents.value = [args.event]
    } else {
      secondEvents.value = [args.event]
    }
  }
  args.domEvent.preventDefault()
  menuData.value = defaultMenuData
  menuAnchor.value = args.domEvent.target
  isMenuOpen.value = true
}

function handleEventDeleted(args) {
  deletedEvents.value = args.events
  action.value = 'delete'
  snackbarButton.value = {
    action: () => {
      const activeEvents = activeCalendar.value === 'first' ? firstEvents.value : secondEvents.value
      let eventsToUpdate = [...activeEvents]
      for (const event of deletedEvents.value) {
        eventsToUpdate = eventsToUpdate.filter((ev) => ev.id !== event.id)
      }

      if (activeCalendar.value === 'first') {
        firstEvents.value = eventsToUpdate
      } else {
        secondEvents.value = eventsToUpdate
      }

      deletedEvents.value = []
    },
    text: 'Undo'
  }
  snackbarMessage.value = 'Event' + (deletedEvents.value.length === 1 ? '' : 's') + ' deleted'
  dummyElm.value.focus()
  isSnackbarOpen.value = true
}

function handleFirstSelectedEventsChange(args) {
  firstSelectedEvents.value = args.events
}

function handleSecondSelectedEventsChange(args) {
  secondSelectedEvents.value = args.events
}

function handleSelectChange(args) {
  detectAction(args.value)
}

function handleSelectClose() {
  isMenuOpen.value = false
  selectValue.value = null
}

function handleKeyDown(ev) {
  if (ev.ctrlKey || ev.metaKey) {
    detectAction(ev.key)
  }
  if (ev.key === 'Delete') {
    detectAction('delete')
  }
}
</script>

<template>
  <MbscPage @keydown="handleKeyDown($event)">
    <div class="mbsc-flex-col md-copy-cut-paste">
      <div class="mbsc-flex-none">
        <MbscSegmentedGroup v-model="activeCalendar" @change="switchCalendar($event)">
          <MbscSegmented value="first">First calendar</MbscSegmented>
          <MbscSegmented value="second">Second calendar</MbscSegmented>
        </MbscSegmentedGroup>
      </div>
      <div class="mbsc-flex-col mbsc-flex-1-1">
        <div id="demo-copy-cut-paste-first-cont" class="mbsc-flex-1-1 md-copy-cut-paste-cont">
          <MbscEventcalendar
            :data="firstEvents"
            :selectedEvents="firstSelectedEvents"
            :className="activeCalendar === 'second' ? 'md-hide-calendar' : ''"
            :view="myView"
            :clickToCreate="true"
            :dragToCreate="true"
            :dragToMove="true"
            :dragToResize="true"
            :selectMultipleEvents="true"
            @selected-events-change="handleFirstSelectedEventsChange"
            @page-loading="handlePageLoading"
            @cell-right-click="handleCellRightClick"
            @event-right-click="handleEventRightClick"
            @event-deleted="handleEventDeleted"
          />
        </div>
        <div id="demo-copy-cut-paste-second-cont" class="mbsc-flex-1-1 md-copy-cut-paste-cont">
          <MbscEventcalendar
            :data="secondEvents"
            :selectedEvents="secondSelectedEvents"
            :className="activeCalendar === 'first' ? 'md-hide-calendar' : ''"
            :view="myView"
            :clickToCreate="true"
            :dragToCreate="true"
            :dragToMove="true"
            :dragToResize="true"
            :selectMultipleEvents="true"
            @selected-events-change="handleSecondSelectedEventsChange"
            @page-loading="handlePageLoading"
            @cell-right-click="handleCellRightClick"
            @event-right-click="handleEventRightClick"
            @event-deleted="handleEventDeleted"
          />
        </div>
        <MbscSelect
          ref="menuElm"
          display="anchored"
          v-model="selectValue"
          :touchUi="false"
          :data="menuData"
          :anchor="menuAnchor"
          :buttons="[]"
          :isOpen="isMenuOpen"
          :inputProps="{ type: 'hidden' }"
          @change="handleSelectChange"
          @close="handleSelectClose"
        />
      </div>
    </div>
    <div ref="dummyElm" tabindex="-1"></div>
    <MbscSnackbar
      :message="snackbarMessage"
      :button="snackbarButton"
      :duration="3000"
      :isOpen="isSnackbarOpen"
      @close="isSnackbarOpen = false"
    />
    <MbscToast :message="toastMessage" :isOpen="isToastOpen" @close="isToastOpen = false" />
  </MbscPage>
</template>

<style>
.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;
}
</style>

Duplicate Session

You have signed in on a different machine. To keep everything in sync, you have been signed out of this session.

If you need to invite team members, manage seats and accounts you can do it from the licenses page.

Duplicate Session

We noticed you are logged in on a different machine and have closed your old session.

If you need to invite team members, manage seats and accounts you can do it from the licenses page.