---
title: "Vue Event calendar Recurring Events Editor Example | Mobiscroll"
component: "Event calendar"
framework: "Vue"
library_version: "6.1.5"
url: "https://demo.mobiscroll.com/vue/eventcalendar/recurring-event-add-edit-dialog"
---

# Recurring Events Editor - Vue Event calendar demo

Add recurring event management to a Vue event calendar — use preset daily, weekly, monthly, and yearly rules or a custom recurrence editor. For Vue and Nuxt.

- [Live demo](https://demo.mobiscroll.com/vue/eventcalendar/recurring-event-add-edit-dialog)
- [View SFC JS code on GitHub](https://github.com/acidb/mobiscroll-demos-vue/tree/main/src/demos/eventcalendar/calendar-view/recurring-event-add-edit-dialog)
- [View SFC TS code on GitHub](https://github.com/acidb/mobiscroll-demos-vue-ts/tree/main/src/demos/eventcalendar/calendar-view/recurring-event-add-edit-dialog)
- This demo in other frameworks: [JavaScript](https://demo.mobiscroll.com/javascript/eventcalendar/recurring-event-add-edit-dialog.md), [jQuery](https://demo.mobiscroll.com/jquery/eventcalendar/recurring-event-add-edit-dialog.md), [Angular](https://demo.mobiscroll.com/angular/eventcalendar/recurring-event-add-edit-dialog.md), [React](https://demo.mobiscroll.com/react/eventcalendar/recurring-event-add-edit-dialog.md)

## Description

As seen in the [previous example](https://demo.mobiscroll.com/vue/eventcalendar/create-read-update-delete-CRUD), an add/edit form can be created with custom and base event fields. While the CRUD example features delete with undo, this example shows off how to build a fully customizable recurrence editor right in the add/edit screen.

Above some common presets, like **Daily**, **Weekly on X day**, **Monthly on Xth**, **Annually on the same day**, **Every Weekday** a fully custom recurrence editor is implemented.

Copy & paste or delve into the code and understand how to generate recurrence objects or RRULES as seen in this [live rule builder](https://demo.mobiscroll.com/vue/eventcalendar/recurring-events).

## What this demo shows

- A desktop month view event calendar with recurring events already rendered across the month.
- **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 a new event by clicking and dragging across calendar cells or by double-clicking a day cell.
- **Create dialog** Creating an event opens an event editor with fields for title, description, all-day, start date, end date, and recurrence.
- **Date selection** The start and end fields open a date picker with range selection.
- **Recurring presets** The Repeats field opens a dropdown with preset options including Does not repeat, Daily, Weekly, Monthly, Yearly, and Custom.
- **Custom recurrence** Choosing Custom expands the editor with additional controls for defining a custom recurring rule.
- **Event interaction** Hovering over an event label highlights it. This action shows resize handles on both sides, indicating that the event can be resized by clicking and dragging.
- **Event editing** Clicking an existing event opens the same dialog in edit mode with the current event values prefilled.
- **Save and cancel flow** Clicking outside the dialog or pressing Cancel closes the editor without applying changes, while Save applies the changes to the event.
- **Delete flow** In edit mode, the dialog includes a Delete event action for removing the selected event.
- **Recurring delete options** When deleting a recurring event, a confirmation popup appears with options for This event only, This and following events, and All events, plus Cancel and Ok actions.
- **Responsive editor** The event editor adapts to screen size and device type, using touch-friendly controls such as scrollers on smaller or touch devices.
- **Cell hover and selection** Hovering a day cell highlights the day number with a gray background, while clicking an empty part of a day cell selects that day and highlights the day number with a blue background.

## Best for

- **Recurring event management** Apps that need users to create, edit, and delete repeating events directly from a calendar view.
- **Exception handling** Scenarios where users may need to delete a single occurrence, all following occurrences, or the entire recurring series.
- **Business and operational scheduling** Internal tools or customer-facing products for planning appointments, classes, shifts, programs, or other repeating activities.

## API used in this demo

- [`theme`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-themeVariant) - option
- [`labels`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-labels) - option
- [`touchUi`](https://mobiscroll.com/docs/vue/select/api#opt-touchUi) - option
- [`display`](https://mobiscroll.com/docs/vue/popup/api#opt-display) - option
- [`width`](https://mobiscroll.com/docs/vue/popup/api#opt-width) - option
- [`fullScreen`](https://mobiscroll.com/docs/vue/popup/api#opt-fullScreen) - option
- [`touchUi`](https://mobiscroll.com/docs/vue/popup/api#opt-touchUi) - option
- [`cssClass`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-cssClass) - option
- [`cssClass`](https://mobiscroll.com/docs/vue/popup/api#opt-cssClass) - option
- [`view`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-view) - option
- [`data`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-data) - option
- [`clickToCreate`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-clickToCreate) - option
- [`dragToCreate`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-dragToCreate) - option
- [`dragToMove`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-dragToMove) - option
- [`dragToResize`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-dragToResize) - option
- [`selectedDate`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-selectedDate) - option
- [`selected-date-change`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onSelectedDateChange) - event
- [`event-click`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventClick) - event
- [`event-update`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventUpdate) - event
- [`event-create`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventCreate) - event
- [`event-created`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventCreated) - event
- [`event-deleted`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventDeleted) - event
- [`event-updated`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventUpdated) - event
- [`scrollLock`](https://mobiscroll.com/docs/vue/popup/api#opt-scrollLock) - option
- [`height`](https://mobiscroll.com/docs/vue/popup/api#opt-height) - option
- [`headerText`](https://mobiscroll.com/docs/vue/popup/api#opt-headerText) - option
- [`anchor`](https://mobiscroll.com/docs/vue/popup/api#opt-anchor) - option
- [`buttons`](https://mobiscroll.com/docs/vue/popup/api#opt-buttons) - option
- [`isOpen`](https://mobiscroll.com/docs/vue/popup/api#opt-isOpen) - option
- [`responsive`](https://mobiscroll.com/docs/vue/popup/api#opt-responsive) - option
- [`close`](https://mobiscroll.com/docs/vue/popup/api#event-onClose) - event
- [`select`](https://mobiscroll.com/docs/vue/datepicker/api#opt-select) - option
- [`touchUi`](https://mobiscroll.com/docs/vue/datepicker/api#opt-touchUi) - option
- [`startInput`](https://mobiscroll.com/docs/vue/datepicker/api#opt-startInput) - option
- [`endInput`](https://mobiscroll.com/docs/vue/datepicker/api#opt-endInput) - option
- [`showRangeLabels`](https://mobiscroll.com/docs/vue/datepicker/api#opt-showRangeLabels) - option
- [`controls`](https://mobiscroll.com/docs/vue/datepicker/api#opt-controls) - option
- [`responsive`](https://mobiscroll.com/docs/vue/datepicker/api#opt-responsive) - option
- [`label`](https://mobiscroll.com/docs/vue/select/api#opt-label) - option
- [`data`](https://mobiscroll.com/docs/vue/select/api#opt-data) - option
- [`responsive`](https://mobiscroll.com/docs/vue/select/api#opt-responsive) - option
- [`inputStyle`](https://mobiscroll.com/docs/vue/select/api#opt-inputStyle) - option
- [`change`](https://mobiscroll.com/docs/vue/select/api#event-onChange) - event
- [`position`](https://mobiscroll.com/docs/vue/forms/radio#opt-position) - option
- [`display`](https://mobiscroll.com/docs/vue/datepicker/api#opt-display) - option
- [`dateFormat`](https://mobiscroll.com/docs/vue/datepicker/api#localization-dateFormat) - option
- [`returnFormat`](https://mobiscroll.com/docs/vue/datepicker/api#opt-returnFormat) - option
- [`inputStyle`](https://mobiscroll.com/docs/vue/datepicker/api#opt-inputStyle) - option

## Code

### SFC JS

```vue
<script setup>
import {
  formatDate,
  MbscButton,
  MbscDatepicker,
  MbscEventcalendar,
  MbscInput,
  MbscPopup,
  MbscRadio,
  MbscRadioGroup,
  MbscSegmented,
  MbscSegmentedGroup,
  MbscSelect,
  MbscSwitch,
  MbscTextarea,
  setOptions,
  updateRecurringEvent
} from '@mobiscroll/vue'
import { onMounted, ref } from 'vue'

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

const days = [
  {
    name: 'Sunday',
    short: 'SU',
    checked: true
  },
  {
    name: 'Monday',
    short: 'MO',
    checked: false
  },
  {
    name: 'Tuesday',
    short: 'TU',
    checked: false
  },
  {
    name: 'Wednesday',
    short: 'WE',
    checked: false
  },
  {
    name: 'Thursday',
    short: 'TH',
    checked: false
  },
  {
    name: 'Friday',
    short: 'FR',
    checked: false
  },
  {
    name: 'Saturday',
    short: 'SA',
    checked: false
  }
]
const ordinalList = { 1: 'first', 2: 'second', 3: 'third', 4: 'fourth' }
const popupEventTitle = ref('')
const popupEventDescription = ref('')
const popupEventAllDay = ref(true)
const popupEventDates = ref(null)
const calendarSelectedDate = ref(new Date())
const myEvents = ref([
  {
    id: 1,
    start: '2026-10-21T00:00',
    end: '2026-10-24T00:00',
    title: 'Alice OFF',
    allDay: true,
    color: '#67ab0d'
  },
  {
    id: 2,
    start: '18:00',
    end: '20:00',
    recurring: {
      repeat: 'weekly',
      weekDays: 'MO'
    },
    title: 'Football training',
    allDay: false,
    color: '#fd966a'
  },
  {
    id: 3,
    start: '17:00',
    end: '17:30',
    recurring: {
      repeat: 'monthly',
      day: 1
    },
    title: 'Rent payment',
    allDay: false,
    color: '#3ea39e'
  },
  {
    id: 4,
    recurring: {
      repeat: 'yearly',
      day: 6,
      month: 5
    },
    title: 'Dexter BD',
    allDay: true,
    color: '#d00f0f'
  }
])
const myView = {
  calendar: { labels: true }
}
const repeatData = ref([
  {
    value: 'norepeat',
    text: 'Does not repeat'
  },
  {
    value: 'daily',
    text: 'Daily'
  },
  {
    value: 'weekly',
    text: 'Weekly'
  },
  {
    value: 'monthly',
    text: 'Monthly'
  },
  {
    value: 'monthly-pos',
    text: 'Monthly'
  },
  {
    value: 'yearly',
    text: 'Yearly'
  },
  {
    value: 'yearly-pos',
    text: 'Yearly'
  },
  {
    value: 'weekday',
    text: 'Every weekday (Monday to Friday)'
  },
  {
    value: 'custom',
    text: 'Custom'
  }
])
const selectedRepeat = ref('norepeat')
const selectResponsive = {
  xsmall: {
    touchUi: true
  },
  small: {
    touchUi: false
  }
}
const responsivePopup = {
  medium: {
    display: 'anchored',
    width: 510,
    fullScreen: false,
    touchUi: false
  }
}
const weekDays = ref(['SU'])
const showCustomRepeat = ref(false)
const repeatType = ref('daily')
const repeatNr = ref(1)
const monthlyDays = ref([1])
const monthlyDay = ref(1)
const yearlyDays = ref([1])
const yearlyDay = ref(1)
const months = [
  {
    value: 1,
    text: 'January'
  },
  {
    value: 2,
    text: 'February'
  },
  {
    value: 3,
    text: 'March'
  },
  {
    value: 4,
    text: 'April'
  },
  {
    value: 5,
    text: 'May'
  },
  {
    value: 6,
    text: 'June'
  },
  {
    value: 7,
    text: 'July'
  },
  {
    value: 8,
    text: 'August'
  },
  {
    value: 9,
    text: 'September'
  },
  {
    value: 10,
    text: 'October'
  },
  {
    value: 11,
    text: 'November'
  },
  {
    value: 12,
    text: 'December'
  }
]
const selectedMonth = ref(1)
const untilDate = ref(null)
const occurrences = ref(10)
const condition = ref('never')
const tempEvent = ref({})
const recurringEditMode = ref('current')
const newEvent = ref({})
const originalRecurringEvent = ref({})
const eventOccurrence = ref({})
const isEdit = ref(false)
const editFromPopup = ref(false)
const recurringDelete = ref(false)
const recurringActionText = ref('Edit')
const isPopupOpen = ref(false)
const popupHeaderText = ref('')
const isRecurringEditOpen = ref(false)
const popupAnchor = ref(null)
const startRef = ref(null)
const endRef = ref(null)
const popupAddButtons = [
  'cancel',
  {
    handler: () => {
      saveEvent()
    },
    keyCode: 'enter',
    text: 'Add',
    cssClass: 'mbsc-popup-button-primary'
  }
]
const popupEditButtons = [
  'cancel',
  {
    handler: () => {
      if (Object.keys(originalRecurringEvent.value).length !== 0) {
        editFromPopup.value = true
        recurringActionText.value = 'Edit'
        recurringDelete.value = false
        isRecurringEditOpen.value = true
      } else {
        saveEvent()
      }
    },
    keyCode: 'enter',
    text: 'Save',
    cssClass: 'mbsc-popup-button-primary'
  }
]
const datePickerControls = ['date']
const datePickerResponsive = {
  medium: {
    controls: ['calendar'],
    touchUi: false
  }
}
const datetimePickerControls = ['datetime']
const datetimePickerResponsive = {
  medium: {
    controls: ['calendar', 'time'],
    touchUi: false
  }
}
const popupButtons = ref([])
const recurringEditButtons = [
  'cancel',
  {
    text: 'Ok',
    keyCode: 'enter',
    handler: () => {
      if (recurringDelete.value) {
        deleteRecurringEvent()
      } else {
        if (editFromPopup.value) {
          setTempEvent()
        }

        if (recurringEditMode.value === 'current') {
          delete tempEvent.value.id
          delete tempEvent.value.recurring
          delete tempEvent.value.recurringException
        }

        const events = updateRecurringEvent(
          originalRecurringEvent.value,
          eventOccurrence.value,
          editFromPopup.value ? null : newEvent.value,
          editFromPopup.value ? tempEvent.value : null,
          recurringEditMode.value
        )

        // Update event
        let newEventList = [...myEvents.value]
        const index = newEventList.findIndex((x) => x.id === events.updatedEvent.id)
        newEventList[index] = events.updatedEvent

        // Add new event
        if (events.newEvent) {
          newEventList = [...newEventList, events.newEvent]
        }

        myEvents.value = newEventList

        isPopupOpen.value = false
        isRecurringEditOpen.value = false
      }
    },
    cssClass: 'mbsc-popup-button-primary'
  }
]

function getWeekDayNum(date) {
  const year = date.getFullYear()
  const month = date.getMonth()
  const firstDayOfMonth = new Date(year, month, 1)
  const lastDayOfMonth = new Date(year, month + 1, 0)
  let count = 0

  for (let d = firstDayOfMonth; d < lastDayOfMonth; d.setDate(d.getDate() + 7)) {
    if (date >= d) {
      count++
    } else {
      break
    }
  }

  return Math.max(1, count)
}

function deleteRecurringEvent() {
  let currentExceptions = []
  let followingExceptions = []
  switch (recurringEditMode.value) {
    case 'current':
    default:
      currentExceptions = tempEvent.value.recurringException || []
      currentExceptions = [...currentExceptions, tempEvent.value.start]
      originalRecurringEvent.value.recurringException = currentExceptions
      myEvents.value = [...myEvents.value]
      break
    case 'following':
      followingExceptions = tempEvent.value.recurringException || []
      followingExceptions = [...followingExceptions, tempEvent.value.start]
      originalRecurringEvent.value.recurringException = followingExceptions
      originalRecurringEvent.value.recurring.until = tempEvent.value.start
      myEvents.value = [...myEvents.value]
      break
    case 'all':
      myEvents.value = myEvents.value.filter((item) => item.id !== tempEvent.value.id)
      break
  }
  isPopupOpen.value = false
  isRecurringEditOpen.value = false
}

function loadPopupForm(event) {
  const startDate = new Date(event.start)
  popupEventTitle.value = event.title
  popupEventDescription.value = event['description']
  popupEventDates.value = [startDate, event.end]
  untilDate.value = formatDate(
    'YYYY-MM-DD',
    new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate() + 1)
  )
  popupEventAllDay.value = event.allDay || false
  updateCustomForm()
}

function getCustomRule() {
  let recurringRule
  const d = editFromPopup.value ? popupEventDates.value[0] : new Date(tempEvent.value.start)
  const weekday = d.getDay()
  const monthday = d.getDate()
  const weekNr = getWeekDayNum(d)

  if (editFromPopup.value && tempEvent.value.start && tempEvent.value.recurring) {
    switch (selectedRepeat.value) {
      default:
      case 'daily':
        recurringRule = {
          repeat: 'daily'
        }
        break
      case 'weekly':
        recurringRule = {
          repeat: 'weekly',
          weekDays: days[weekday].short
        }
        break
      case 'monthly':
        recurringRule = {
          repeat: 'monthly',
          day: monthday
        }
        break
      case 'monthly-pos':
        recurringRule = {
          repeat: 'monthly',
          weekDays: days[weekday].short,
          pos: weekNr
        }
        break
      case 'yearly':
        recurringRule = {
          repeat: 'yearly',
          day: monthday,
          month: d.getMonth() + 1
        }
        break
      case 'yearly-pos':
        recurringRule = {
          repeat: 'yearly',
          month: d.getMonth() + 1,
          weekDays: days[weekday].short,
          pos: weekNr
        }
        break
    }
  } else {
    switch (selectedRepeat.value) {
      case 'daily':
        recurringRule = { repeat: 'daily' }
        break
      case 'weekly':
        recurringRule = { repeat: 'weekly', weekDays: days[d.getDay()].short }
        break
      case 'monthly':
        recurringRule = { repeat: 'monthly', day: d.getDate() }
        break
      case 'monthly-pos':
        recurringRule = { repeat: 'monthly', weekDays: days[weekday].short, pos: weekNr }
        break
      case 'yearly':
        recurringRule = { repeat: 'yearly', month: d.getMonth() + 1 }
        break
      case 'yearly-pos':
        recurringRule = {
          repeat: 'yearly',
          month: d.getMonth() + 1,
          weekDays: days[weekday].short,
          pos: weekNr
        }
        break
      case 'weekday':
        recurringRule = { repeat: 'weekly', weekDays: 'MO,TU,WE,TH,FR' }
        break
      case 'custom':
      case 'custom-value':
        recurringRule = {
          repeat: repeatType.value,
          interval: repeatNr.value
        }
        switch (repeatType.value) {
          case 'weekly':
            recurringRule.weekDays = weekDays.value.join(',')
            break
          case 'monthly':
            recurringRule.day = monthlyDay.value
            break
          case 'yearly':
            recurringRule.day = yearlyDay.value
            recurringRule.month = selectedMonth.value
            break
        }

        switch (condition.value) {
          case 'until':
            recurringRule.until = untilDate.value
            break
          case 'count':
            recurringRule.count = occurrences.value
            break
        }
        break
    }
  }
  return recurringRule
}

function setTempEvent() {
  tempEvent.value.title = popupEventTitle.value
  tempEvent.value.description = popupEventDescription.value
  tempEvent.value.start = popupEventDates.value[0]
  tempEvent.value.end = popupEventDates.value[1]
  tempEvent.value.allDay = popupEventAllDay.value
  tempEvent.value.recurring = getCustomRule()
}

function saveEvent() {
  setTempEvent()

  if (isEdit.value) {
    // Update the event in the list
    myEvents.value = [...myEvents.value]
    // Here you can update the event in your storage as well
    // ...
  } else {
    // Add the new event to the list
    myEvents.value = [...myEvents.value, tempEvent.value]
    // Here you can add the event to your storage as well
    // ...
  }
  // Navigate the calendar
  navigateTo()
  // Close the popup
  isPopupOpen.value = false
}

function deleteEvent(event) {
  myEvents.value = myEvents.value.filter((item) => item.id !== event.id)
}

// Set custom values to default
function resetCustomValues() {
  repeatType.value = 'daily'
  repeatNr.value = 1
  condition.value = 'never'
  occurrences.value = 10
  selectedMonth.value = 1
  monthlyDay.value = 1
  yearlyDay.value = 1
  weekDays.value = ['SU']
  selectedRepeat.value = 'norepeat'
  showCustomRepeat.value = false
  repeatData.value = repeatData.value.filter((item) => item.value !== 'custom-value')
}

function updateCustomForm() {
  const d = new Date(tempEvent.value.start)
  const weekday = d.getDay()
  const monthday = d.getDate()
  const newData = [...repeatData.value]
  const weekNr = getWeekDayNum(d)

  // Update select texts by selected date
  for (const item of newData) {
    switch (item.value) {
      case 'weekly':
        item.text = 'Weekly on ' + days[weekday].name
        break
      case 'monthly':
        item.text = 'Monthly on day ' + monthday
        break
      case 'yearly':
        item.text = 'Annually on ' + months[d.getMonth()].text + ' ' + monthday
        break
      case 'monthly-pos':
        item.text = 'Monthly on the ' + ordinalList[weekNr] + ' ' + days[weekday].name
        break
      case 'yearly-pos':
        item.text =
          'Annually on the ' +
          ordinalList[weekNr] +
          ' ' +
          days[weekday].name +
          ' of ' +
          months[d.getMonth()].text
        break
      default:
    }
  }

  repeatData.value = newData

  const rec = tempEvent.value.recurring

  if (rec) {
    repeatType.value = rec.repeat
    weekDays.value = rec.repeat === 'weekly' ? rec.weekDays.split(',') : ['SU']
    if (rec.interval) {
      // Set custom text
      let customText = ''

      repeatNr.value = rec.interval

      switch (rec.repeat) {
        case 'daily':
          customText = repeatNr.value > 1 ? 'Every ' + repeatNr.value + ' days' : 'Daily'
          break
        case 'weekly':
          customText = repeatNr.value > 1 ? 'Every ' + repeatNr.value + ' weeks' : 'Weekly'
          customText += ' on ' + rec.weekDays
          break
        case 'monthly':
          monthlyDay.value = rec.day
          customText = repeatNr.value > 1 ? 'Every ' + repeatNr.value + ' months' : 'Monthly'
          customText += ' on day ' + monthlyDay.value
          break
        case 'yearly':
          yearlyDay.value = rec.day
          selectedMonth.value = rec.month
          customText = repeatNr.value > 1 ? 'Every ' + repeatNr.value + ' years' : 'Annualy'
          customText += ' on ' + months[selectedMonth.value - 1].text + ' ' + yearlyDay.value
          break
      }

      if (rec.until) {
        condition.value = 'until'
        untilDate.value = rec.until
        customText += ' until ' + formatDate('MMMM D, YYYY', new Date(untilDate.value))
      } else if (rec.count) {
        condition.value = 'count'
        occurrences.value = rec.count
        customText += ', ' + occurrences.value + ' times'
      } else {
        condition.value = 'never'
      }

      // Add custom value
      repeatData.value = [...repeatData.value, { value: 'custom-value', text: customText }]
      // Set custom value
      selectedRepeat.value = 'custom-value'
    } else if (rec.weekDays === 'MO,TU,WE,TH,FR') {
      selectedRepeat.value = 'weekday'
    } else {
      selectedRepeat.value = rec.repeat
    }
  } else {
    resetCustomValues()
  }
  showCustomRepeat.value =
    selectedRepeat.value === 'custom' || selectedRepeat.value === 'custom-value'
}

// Popuplate data for months
function populateMonthDays(month, type) {
  const day30 = [2, 4, 6, 9, 11]
  const newValues = []

  for (let i = 1; i <= 31; i++) {
    if (!(i === 31 && day30.includes(month)) && !(i === 30 && month === 2)) {
      newValues.push(i)
    }
  }

  if (type === 'monthly') {
    monthlyDays.value = newValues
    monthlyDay.value = 1
  } else {
    yearlyDays.value = newValues
    yearlyDay.value = 1
  }
}

function monthChange(ev) {
  selectedMonth.value = ev.value
  yearlyDay.value = 1
  populateMonthDays(ev.value, 'yearly')
}

function navigateTo() {
  const rec = tempEvent.value.recurring
  const d = new Date(tempEvent.value.start)
  let nextYear = 0

  // Navigate the calendar to the correct view
  if (rec && rec.repeat === 'yearly') {
    if (d.getMonth() + 1 > +rec.month && d.getDay() > +rec.day) {
      nextYear = 1
    }
    calendarSelectedDate.value = new Date(d.getFullYear() + nextYear, rec.month - 1, rec.day)
  } else {
    calendarSelectedDate.value = d
  }
}

function handleEventClick(args) {
  const event = args.event
  isEdit.value = true
  tempEvent.value = event

  // Recurring event
  if (event.recurring) {
    originalRecurringEvent.value = event.original
    eventOccurrence.value = { ...event }

    // Fill popup form with event data
    loadPopupForm(event)
  } else {
    originalRecurringEvent.value = {}
    // Fill popup form with event data
    loadPopupForm(event)
  }

  // Set popup options
  popupHeaderText.value = 'Edit event'
  popupButtons.value = popupEditButtons
  popupAnchor.value = args.domEvent.currentTarget
  // Open the popup
  isPopupOpen.value = true
}

function handleEventUpdate(args) {
  const event = args.event
  if (event.recurring) {
    originalRecurringEvent.value = args.oldEvent
    // We need this on delete
    tempEvent.value = event
    eventOccurrence.value = args.oldEventOccurrence
    if (args.domEvent.keyCode === 46) {
      recurringActionText.value = 'Delete'
      recurringDelete.value = true
      isRecurringEditOpen.value = true
    } else {
      recurringActionText.value = 'Edit'
      recurringDelete.value = false
      isRecurringEditOpen.value = true
    }
    return false
  } else {
    return true
  }
}

function handleEventCreate(args) {
  const originEvent = args.originEvent
  if (originEvent && originEvent.recurring) {
    newEvent.value = args.event
    return false
  } else {
    return true
  }
}

function handleEventCreated(args) {
  isEdit.value = false
  tempEvent.value = args.event
  // Fill popup form with event data
  loadPopupForm(args.event)
  // Set popup options
  popupHeaderText.value = 'New Event'
  popupButtons.value = popupAddButtons
  popupAnchor.value = args.target
  resetCustomValues()
  // Open the popup
  isPopupOpen.value = true
}

function handleEventDeleted(args) {
  deleteEvent(args.event)
}

function handleEventUpdated() {
  // Here you can update the event in your storage as well, after drag & drop or resize
  // ...
}

function handleDelete() {
  if (tempEvent.value.recurring) {
    recurringActionText.value = 'Delete'
    recurringDelete.value = true
    isRecurringEditOpen.value = true
  } else {
    deleteEvent(tempEvent.value)
    isPopupOpen.value = false
  }
}

function handleSelectedDateChange(args) {
  calendarSelectedDate.value = args.date
}

onMounted(() => {
  populateMonthDays(1, 'monthly')
  populateMonthDays(1, 'yearly')
})
</script>

<template>
  <MbscEventcalendar
    :view="myView"
    :data="myEvents"
    clickToCreate="double"
    :dragToCreate="true"
    :dragToMove="true"
    :dragToResize="true"
    :selectedDate="calendarSelectedDate"
    @selected-date-change="handleSelectedDateChange"
    @event-click="handleEventClick"
    @event-update="handleEventUpdate"
    @event-create="handleEventCreate"
    @event-created="handleEventCreated"
    @event-deleted="handleEventDeleted"
    @event-updated="handleEventUpdated"
  />
  <MbscPopup
    cssClass="md-recurring-event-editor-popup"
    display="bottom"
    :contentPadding="false"
    :fullScreen="true"
    :scrollLock="false"
    :height="500"
    :headerText="popupHeaderText"
    :anchor="popupAnchor"
    :buttons="popupButtons"
    :isOpen="isPopupOpen"
    :responsive="responsivePopup"
    @close="isPopupOpen = false"
  >
    <div class="mbsc-form-group">
      <MbscInput label="Title" v-model="popupEventTitle" />
      <MbscTextarea label="Description" v-model="popupEventDescription" />
    </div>
    <div class="mbsc-form-group">
      <MbscSwitch label="All-day" v-model="popupEventAllDay" />
      <MbscInput ref="startRef" label="Starts" />
      <MbscInput ref="endRef" label="Ends" />
      <MbscDatepicker
        v-model="popupEventDates"
        select="range"
        :touchUi="true"
        :startInput="startRef"
        :endInput="endRef"
        :showRangeLabels="false"
        :controls="popupEventAllDay ? datePickerControls : datetimePickerControls"
        :responsive="popupEventAllDay ? datePickerResponsive : datetimePickerResponsive"
      />
      <MbscSelect
        v-model="selectedRepeat"
        label="Repeats"
        :data="repeatData"
        :responsive="selectResponsive"
      />
    </div>
    <div class="mbsc-form-group">
      <div v-if="selectedRepeat === 'custom' || selectedRepeat === 'custom-value'">
        <div>
          <MbscSegmentedGroup v-model="repeatType">
            <MbscSegmented value="daily">Daily</MbscSegmented>
            <MbscSegmented value="weekly">Weekly</MbscSegmented>
            <MbscSegmented value="monthly">Monthly</MbscSegmented>
            <MbscSegmented value="yearly">Yearly</MbscSegmented>
          </MbscSegmentedGroup>

          <div class="md-recurrence-options">
            <span>Repeat every</span>
            <span class="md-recurrence-input md-recurrence-input-nr">
              <MbscInput :min="1" v-model="repeatNr" inputStyle="outline" />
            </span>
            <span v-if="repeatType === 'daily'">days</span>
            <span v-if="repeatType === 'weekly'">weeks</span>
            <span v-if="repeatType === 'monthly'">
              month(s) on day
              <span class="md-recurrence-input md-recurrence-input-nr">
                <MbscSelect
                  :data="monthlyDays"
                  v-model="monthlyDay"
                  className="md-recurrence-input"
                  inputStyle="outline"
                />
              </span>
            </span>
            <span v-if="repeatType === 'yearly'">
              year(s) <br />on day
              <span class="md-recurrence-input md-recurrence-input-nr">
                <MbscSelect
                  :data="yearlyDays"
                  v-model="yearlyDay"
                  className="md-recurrence-input"
                  inputStyle="outline"
                />
              </span>
              <span>of</span>
              <span class="md-recurrence-input">
                <MbscSelect
                  :data="months"
                  v-model="selectedMonth"
                  className="md-recurrence-input"
                  inputStyle="outline"
                  @change="monthChange"
                />
              </span>
            </span>

            <p v-if="repeatType === 'daily'" class="md-recurrence-desc">
              The event will be repeated every day or every x days, depending on the value
            </p>
            <p v-if="repeatType === 'weekly'" class="md-recurrence-desc">
              The event will be repeated every x weeks on specific weekdays
            </p>
            <p v-if="repeatType === 'monthly'" class="md-recurrence-desc">
              The event will be repeated every x month on specific day of the month
            </p>
            <p v-if="repeatType === 'yearly'" class="md-recurrence-desc">
              The event will be repeated every x years on specific day of a specific month
            </p>
          </div>

          <MbscSegmentedGroup v-model="weekDays" v-if="repeatType === 'weekly'" select="multiple">
            <MbscSegmented value="SU">Sun</MbscSegmented>
            <MbscSegmented value="MO">Mon</MbscSegmented>
            <MbscSegmented value="TU">Tue</MbscSegmented>
            <MbscSegmented value="WE">Wed</MbscSegmented>
            <MbscSegmented value="TH">Thu</MbscSegmented>
            <MbscSegmented value="FR">Fri</MbscSegmented>
            <MbscSegmented value="SA">Sat</MbscSegmented>
          </MbscSegmentedGroup>

          <div class="md-recurrence-ends">Ends</div>

          <div class="mbsc-form-group">
            <MbscRadioGroup v-model="condition">
              <MbscRadio
                label="Never"
                position="start"
                description="The event will repeat indefinitely"
                value="never"
              />
              <MbscRadio value="until" position="start" @click="condition = 'until'">
                Until
                <span class="md-recurrence-input">
                  <MbscDatepicker
                    :controls="['calendar']"
                    display="anchored"
                    :touchUi="false"
                    dateFormat="YYYY-MM-DD"
                    returnFormat="iso8601"
                    v-model="untilDate"
                    inputStyle="outline"
                  />
                </span>
                <span class="mbsc-description">
                  The event will run until it reaches a specific date
                </span>
              </MbscRadio>
              <MbscRadio value="count" position="start">
                After
                <span class="md-recurrence-input md-recurrence-input-nr">
                  <MbscInput
                    inputStyle="outline"
                    v-model="occurrences"
                    @click="condition = 'count'"
                  />
                  occurrences
                  <span class="mbsc-description">
                    The event will repeat until it reaches a certain amount of occurrences
                  </span>
                </span>
              </MbscRadio>
            </MbscRadioGroup>
          </div>
        </div>
      </div>
      <div v-if="isEdit" class="mbsc-button-group">
        <MbscButton
          className="mbsc-button-block"
          color="danger"
          variant="outline"
          @click="handleDelete"
        >
          Delete event
        </MbscButton>
      </div>
    </div>
  </MbscPopup>
  <MbscPopup
    display="bottom"
    :contentPadding="false"
    :buttons="recurringEditButtons"
    :isOpen="isRecurringEditOpen"
    @close="isRecurringEditOpen = false"
  >
    <div class="mbsc-form-group">
      <div class="mbsc-form-group-title">{{ recurringActionText }} recurring event</div>
      <MbscRadioGroup v-model="recurringEditMode">
        <MbscRadio label="This event only" value="current" />
        <MbscRadio label="This and following events" value="following" />
        <MbscRadio label="All events" value="all" />
      </MbscRadioGroup>
    </div>
  </MbscPopup>
</template>

<style>
.md-recurring-event-editor-popup .md-recurrence-input .mbsc-textfield-wrapper {
  display: inline-block;
  margin: 3px;
  z-index: 3;
  line-height: normal;
}

.md-recurring-event-editor-popup .md-recurrence-input .mbsc-textfield {
  width: 140px;
  height: 32px;
}

.md-recurring-event-editor-popup .md-recurrence-input-nr .mbsc-textfield {
  width: 90px;
}

.md-recurring-event-editor-popup .md-recurrence-input .mbsc-select-icon {
  top: 50%;
  height: 1.5em;
  margin-top: -0.75em;
}

.md-recurrence-options {
  padding: 0 1em;
  margin: 1em 0;
}

.md-recurrence-ends {
  padding: 0 1em;
  margin: 1.25em 0 -1.25em 0;
}

.md-recurrence-desc {
  font-size: 12px;
  font-weight: normal;
  color: #555;
  margin: 1em 0;
}

.md-hide-elm {
  display: none;
}
</style>
```

### SFC TS

```vue
<script setup lang="ts">
import {
  formatDate,
  MbscButton,
  MbscDatepicker,
  MbscEventcalendar,
  MbscInput,
  MbscPopup,
  MbscRadio,
  MbscRadioGroup,
  MbscSegmented,
  MbscSegmentedGroup,
  MbscSelect,
  MbscSwitch,
  MbscTextarea,
  setOptions,
  updateRecurringEvent
} from '@mobiscroll/vue'
import type {
  MbscCalendarEvent,
  MbscDatepickerControl,
  MbscDatepickerOptions,
  MbscEventcalendarView,
  MbscEventClickEvent,
  MbscEventCreatedEvent,
  MbscEventCreateEvent,
  MbscEventDeletedEvent,
  MbscEventUpdateEvent,
  MbscPopupButton,
  MbscPopupOptions,
  MbscRecurrenceRule,
  MbscResponsiveOptions,
  MbscSelectedDateChangeEvent
} from '@mobiscroll/vue'
import { onMounted, ref } from 'vue'

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

const days = [
  {
    name: 'Sunday',
    short: 'SU',
    checked: true
  },
  {
    name: 'Monday',
    short: 'MO',
    checked: false
  },
  {
    name: 'Tuesday',
    short: 'TU',
    checked: false
  },
  {
    name: 'Wednesday',
    short: 'WE',
    checked: false
  },
  {
    name: 'Thursday',
    short: 'TH',
    checked: false
  },
  {
    name: 'Friday',
    short: 'FR',
    checked: false
  },
  {
    name: 'Saturday',
    short: 'SA',
    checked: false
  }
]
const ordinalList: { [key: number]: string } = { 1: 'first', 2: 'second', 3: 'third', 4: 'fourth' }
const popupEventTitle = ref<string>('')
const popupEventDescription = ref<string>('')
const popupEventAllDay = ref<boolean>(true)
const popupEventDates = ref<any>(null)
const calendarSelectedDate = ref<any>(new Date())
const myEvents = ref<MbscCalendarEvent[]>([
  {
    id: 1,
    start: '2026-10-21T00:00',
    end: '2026-10-24T00:00',
    allDay: true,
    color: '#67ab0d'
  },
  {
    id: 2,
    start: '18:00',
    end: '20:00',
    recurring: {
      repeat: 'weekly',
      weekDays: 'MO'
    },
    title: 'Football training',
    allDay: false,
    color: '#fd966a'
  },
  {
    id: 3,
    start: '17:00',
    end: '17:30',
    recurring: {
      repeat: 'monthly',
      day: 1
    },
    title: 'Rent payment',
    allDay: false,
    color: '#3ea39e'
  },
  {
    id: 4,
    recurring: {
      repeat: 'yearly',
      day: 6,
      month: 5
    },
    title: 'Dexter BD',
    allDay: true,
    color: '#d00f0f'
  }
])
const myView: MbscEventcalendarView = {
  calendar: { labels: true }
}
const repeatData = ref([
  {
    value: 'norepeat',
    text: 'Does not repeat'
  },
  {
    value: 'daily',
    text: 'Daily'
  },
  {
    value: 'weekly',
    text: 'Weekly'
  },
  {
    value: 'monthly',
    text: 'Monthly'
  },
  {
    value: 'monthly-pos',
    text: 'Monthly'
  },
  {
    value: 'yearly',
    text: 'Yearly'
  },
  {
    value: 'yearly-pos',
    text: 'Yearly'
  },
  {
    value: 'weekday',
    text: 'Every weekday (Monday to Friday)'
  },
  {
    value: 'custom',
    text: 'Custom'
  }
])
const selectedRepeat = ref<string>('norepeat')
const selectResponsive = {
  xsmall: {
    touchUi: true
  },
  small: {
    touchUi: false
  }
}
const responsivePopup: MbscResponsiveOptions<MbscPopupOptions> = {
  medium: {
    display: 'anchored',
    width: 510,
    fullScreen: false,
    touchUi: false
  }
}
const weekDays = ref<string[]>(['SU'])
const showCustomRepeat = ref<boolean>(false)
const repeatType = ref<'daily' | 'weekly' | 'monthly' | 'yearly'>('daily')
const repeatNr = ref<number>(1)
const monthlyDays = ref<number[]>([1])
const monthlyDay = ref<number>(1)
const yearlyDays = ref<number[]>([1])
const yearlyDay = ref<number>(1)
const months = [
  {
    value: 1,
    text: 'January'
  },
  {
    value: 2,
    text: 'February'
  },
  {
    value: 3,
    text: 'March'
  },
  {
    value: 4,
    text: 'April'
  },
  {
    value: 5,
    text: 'May'
  },
  {
    value: 6,
    text: 'June'
  },
  {
    value: 7,
    text: 'July'
  },
  {
    value: 8,
    text: 'August'
  },
  {
    value: 9,
    text: 'September'
  },
  {
    value: 10,
    text: 'October'
  },
  {
    value: 11,
    text: 'November'
  },
  {
    value: 12,
    text: 'December'
  }
]
const selectedMonth = ref<number>(1)
const untilDate = ref<any>(null)
const occurrences = ref<number>(10)
const condition = ref<string>('never')
const tempEvent = ref<any>({})
const recurringEditMode = ref<any>('current')
const newEvent = ref<object>({})
const originalRecurringEvent = ref<any>({})
const eventOccurrence = ref<any>({})
const isEdit = ref<boolean>(false)
const editFromPopup = ref<boolean>(false)
const recurringDelete = ref<boolean>(false)
const recurringActionText = ref<string>('Edit')
const isPopupOpen = ref<boolean>(false)
const popupHeaderText = ref<string>('')
const isRecurringEditOpen = ref<boolean>(false)
const popupAnchor = ref<any>(null)
const startRef = ref<any>(null)
const endRef = ref<any>(null)
const popupAddButtons = [
  'cancel',
  {
    handler: () => {
      saveEvent()
    },
    keyCode: 'enter',
    text: 'Add',
    cssClass: 'mbsc-popup-button-primary'
  }
]
const popupEditButtons = [
  'cancel',
  {
    handler: () => {
      if (Object.keys(originalRecurringEvent.value).length !== 0) {
        editFromPopup.value = true
        recurringActionText.value = 'Edit'
        recurringDelete.value = false
        isRecurringEditOpen.value = true
      } else {
        saveEvent()
      }
    },
    keyCode: 'enter',
    text: 'Save',
    cssClass: 'mbsc-popup-button-primary'
  }
]
const datePickerControls: MbscDatepickerControl[] = ['date']
const datePickerResponsive: MbscResponsiveOptions<MbscDatepickerOptions> = {
  medium: {
    controls: ['calendar'],
    touchUi: false
  }
}
const datetimePickerControls: MbscDatepickerControl[] = ['datetime']
const datetimePickerResponsive: MbscResponsiveOptions<MbscDatepickerOptions> = {
  medium: {
    controls: ['calendar', 'time'],
    touchUi: false
  }
}
const popupButtons = ref<any>([])
const recurringEditButtons: Array<MbscPopupButton | string> = [
  'cancel',
  {
    text: 'Ok',
    keyCode: 'enter',
    handler: () => {
      if (recurringDelete.value) {
        deleteRecurringEvent()
      } else {
        if (editFromPopup.value) {
          setTempEvent()
        }

        if (recurringEditMode.value === 'current') {
          delete tempEvent.value.id
          delete tempEvent.value.recurring
          delete tempEvent.value.recurringException
        }

        const events = updateRecurringEvent(
          originalRecurringEvent.value,
          eventOccurrence.value,
          editFromPopup.value ? null : newEvent.value,
          editFromPopup.value ? tempEvent.value : null,
          recurringEditMode.value
        )

        // Update event
        let newEventList = [...myEvents.value]
        const index = newEventList.findIndex((x) => x.id === events.updatedEvent.id)
        newEventList[index] = events.updatedEvent

        // Add new event
        if (events.newEvent) {
          newEventList = [...newEventList, events.newEvent]
        }

        myEvents.value = newEventList

        isPopupOpen.value = false
        isRecurringEditOpen.value = false
      }
    },
    cssClass: 'mbsc-popup-button-primary'
  }
]

function getWeekDayNum(date: Date) {
  const year = date.getFullYear()
  const month = date.getMonth()
  const firstDayOfMonth = new Date(year, month, 1)
  const lastDayOfMonth = new Date(year, month + 1, 0)
  let count = 0

  for (let d = firstDayOfMonth; d < lastDayOfMonth; d.setDate(d.getDate() + 7)) {
    if (date >= d) {
      count++
    } else {
      break
    }
  }

  return Math.max(1, count)
}

function deleteRecurringEvent() {
  let currentExceptions: any = []
  let followingExceptions: any = []
  switch (recurringEditMode.value) {
    case 'current':
    default:
      currentExceptions = tempEvent.value.recurringException || []
      currentExceptions = [...currentExceptions, tempEvent.value.start]
      originalRecurringEvent.value.recurringException = currentExceptions
      myEvents.value = [...myEvents.value]
      break
    case 'following':
      followingExceptions = tempEvent.value.recurringException || []
      followingExceptions = [...followingExceptions, tempEvent.value.start]
      originalRecurringEvent.value.recurringException = followingExceptions
      originalRecurringEvent.value.recurring.until = tempEvent.value.start
      myEvents.value = [...myEvents.value]
      break
    case 'all':
      myEvents.value = myEvents.value.filter((item) => item.id !== tempEvent.value.id)
      break
  }
  isPopupOpen.value = false
  isRecurringEditOpen.value = false
}

function loadPopupForm(event: any) {
  const startDate = new Date(event.start)
  popupEventTitle.value = event.title
  popupEventDescription.value = event['description']
  popupEventDates.value = [startDate, event.end]
  untilDate.value = formatDate(
    'YYYY-MM-DD',
    new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate() + 1)
  )
  popupEventAllDay.value = event.allDay || false
  updateCustomForm()
}

function getCustomRule() {
  let recurringRule: MbscRecurrenceRule = {}
  const d = editFromPopup.value ? popupEventDates.value[0] : new Date(tempEvent.value.start)
  const weekday = d.getDay()
  const monthday = d.getDate()
  const weekNr = getWeekDayNum(d)

  if (editFromPopup.value && tempEvent.value.start && tempEvent.value.recurring) {
    switch (selectedRepeat.value) {
      default:
      case 'daily':
        recurringRule = {
          repeat: 'daily'
        }
        break
      case 'weekly':
        recurringRule = {
          repeat: 'weekly',
          weekDays: days[weekday].short
        }
        break
      case 'monthly':
        recurringRule = {
          repeat: 'monthly',
          day: monthday
        }
        break
      case 'monthly-pos':
        recurringRule = {
          repeat: 'monthly',
          weekDays: days[weekday].short,
          pos: weekNr
        }
        break
      case 'yearly':
        recurringRule = {
          repeat: 'yearly',
          day: monthday,
          month: d.getMonth() + 1
        }
        break
      case 'yearly-pos':
        recurringRule = {
          repeat: 'yearly',
          month: d.getMonth() + 1,
          weekDays: days[weekday].short,
          pos: weekNr
        }
        break
    }
  } else {
    switch (selectedRepeat.value) {
      case 'daily':
        recurringRule = { repeat: 'daily' }
        break
      case 'weekly':
        recurringRule = { repeat: 'weekly', weekDays: days[d.getDay()].short }
        break
      case 'monthly':
        recurringRule = { repeat: 'monthly', day: d.getDate() }
        break
      case 'monthly-pos':
        recurringRule = { repeat: 'monthly', weekDays: days[weekday].short, pos: weekNr }
        break
      case 'yearly':
        recurringRule = { repeat: 'yearly', month: d.getMonth() + 1 }
        break
      case 'yearly-pos':
        recurringRule = {
          repeat: 'yearly',
          month: d.getMonth() + 1,
          weekDays: days[weekday].short,
          pos: weekNr
        }
        break
      case 'weekday':
        recurringRule = { repeat: 'weekly', weekDays: 'MO,TU,WE,TH,FR' }
        break
      case 'custom':
      case 'custom-value':
        recurringRule = {
          repeat: repeatType.value,
          interval: repeatNr.value
        }
        switch (repeatType.value) {
          case 'weekly':
            recurringRule.weekDays = weekDays.value.join(',')
            break
          case 'monthly':
            recurringRule.day = monthlyDay.value
            break
          case 'yearly':
            recurringRule.day = yearlyDay.value
            recurringRule.month = selectedMonth.value
            break
        }

        switch (condition.value) {
          case 'until':
            recurringRule.until = untilDate.value
            break
          case 'count':
            recurringRule.count = occurrences.value
            break
        }
        break
    }
  }
  return recurringRule
}

function setTempEvent() {
  tempEvent.value.title = popupEventTitle.value
  tempEvent.value.description = popupEventDescription.value
  tempEvent.value.start = popupEventDates.value[0]
  tempEvent.value.end = popupEventDates.value[1]
  tempEvent.value.allDay = popupEventAllDay.value
  tempEvent.value.recurring = getCustomRule()
}

function saveEvent() {
  setTempEvent()

  if (isEdit.value) {
    // Update the event in the list
    myEvents.value = [...myEvents.value]
    // Here you can update the event in your storage as well
    // ...
  } else {
    // Add the new event to the list
    myEvents.value = [...myEvents.value, tempEvent.value]
    // Here you can add the event to your storage as well
    // ...
  }
  // Navigate the calendar
  navigateTo()
  // Close the popup
  isPopupOpen.value = false
}

function deleteEvent(event: MbscCalendarEvent) {
  myEvents.value = myEvents.value.filter((item) => item.id !== event.id)
}

// Set custom values to default
function resetCustomValues() {
  repeatType.value = 'daily'
  repeatNr.value = 1
  condition.value = 'never'
  occurrences.value = 10
  selectedMonth.value = 1
  monthlyDay.value = 1
  yearlyDay.value = 1
  weekDays.value = ['SU']
  selectedRepeat.value = 'norepeat'
  showCustomRepeat.value = false
  repeatData.value = repeatData.value.filter((item) => item.value !== 'custom-value')
}

function updateCustomForm() {
  const d = new Date(tempEvent.value.start)
  const weekday = d.getDay()
  const monthday = d.getDate()
  const newData = [...repeatData.value]
  const weekNr = getWeekDayNum(d)

  // Update select texts by selected date
  for (const item of newData) {
    switch (item.value) {
      case 'weekly':
        item.text = 'Weekly on ' + days[weekday].name
        break
      case 'monthly':
        item.text = 'Monthly on day ' + monthday
        break
      case 'yearly':
        item.text = 'Annually on ' + months[d.getMonth()].text + ' ' + monthday
        break
      case 'monthly-pos':
        item.text = 'Monthly on the ' + ordinalList[weekNr] + ' ' + days[weekday].name
        break
      case 'yearly-pos':
        item.text =
          'Annually on the ' +
          ordinalList[weekNr] +
          ' ' +
          days[weekday].name +
          ' of ' +
          months[d.getMonth()].text
        break
      default:
    }
  }

  repeatData.value = newData

  const rec = tempEvent.value.recurring

  if (rec) {
    repeatType.value = rec.repeat
    weekDays.value = rec.repeat === 'weekly' ? rec.weekDays.split(',') : ['SU']
    if (rec.interval) {
      // Set custom text
      let customText = ''

      repeatNr.value = rec.interval

      switch (rec.repeat) {
        case 'daily':
          customText = repeatNr.value > 1 ? 'Every ' + repeatNr.value + ' days' : 'Daily'
          break
        case 'weekly':
          customText = repeatNr.value > 1 ? 'Every ' + repeatNr.value + ' weeks' : 'Weekly'
          customText += ' on ' + rec.weekDays
          break
        case 'monthly':
          monthlyDay.value = rec.day
          customText = repeatNr.value > 1 ? 'Every ' + repeatNr.value + ' months' : 'Monthly'
          customText += ' on day ' + monthlyDay.value
          break
        case 'yearly':
          yearlyDay.value = rec.day
          selectedMonth.value = rec.month
          customText = repeatNr.value > 1 ? 'Every ' + repeatNr.value + ' years' : 'Annualy'
          customText += ' on ' + months[selectedMonth.value - 1].text + ' ' + yearlyDay.value
          break
      }

      if (rec.until) {
        condition.value = 'until'
        untilDate.value = rec.until
        customText += ' until ' + formatDate('MMMM D, YYYY', new Date(untilDate.value))
      } else if (rec.count) {
        condition.value = 'count'
        occurrences.value = rec.count
        customText += ', ' + occurrences.value + ' times'
      } else {
        condition.value = 'never'
      }

      // Add custom value
      repeatData.value = [...repeatData.value, { value: 'custom-value', text: customText }]
      // Set custom value
      selectedRepeat.value = 'custom-value'
    } else if (rec.weekDays === 'MO,TU,WE,TH,FR') {
      selectedRepeat.value = 'weekday'
    } else {
      selectedRepeat.value = rec.repeat
    }
  } else {
    resetCustomValues()
  }
  showCustomRepeat.value =
    selectedRepeat.value === 'custom' || selectedRepeat.value === 'custom-value'
}

// Popuplate data for months
function populateMonthDays(month: number, type: string) {
  const day30 = [2, 4, 6, 9, 11]
  const newValues = []

  for (let i = 1; i <= 31; i++) {
    if (!(i === 31 && day30.includes(month)) && !(i === 30 && month === 2)) {
      newValues.push(i)
    }
  }

  if (type === 'monthly') {
    monthlyDays.value = newValues
    monthlyDay.value = 1
  } else {
    yearlyDays.value = newValues
    yearlyDay.value = 1
  }
}

function monthChange(ev: any) {
  selectedMonth.value = ev.value
  yearlyDay.value = 1
  populateMonthDays(ev.value, 'yearly')
}

function navigateTo() {
  const rec = tempEvent.value.recurring
  const d = new Date(tempEvent.value.start)
  let nextYear = 0

  // Navigate the calendar to the correct view
  if (rec && rec.repeat === 'yearly') {
    if (d.getMonth() + 1 > +rec.month && d.getDay() > +rec.day) {
      nextYear = 1
    }
    calendarSelectedDate.value = new Date(d.getFullYear() + nextYear, rec.month - 1, rec.day)
  } else {
    calendarSelectedDate.value = d
  }
}

function handleEventClick(args: MbscEventClickEvent) {
  const event = args.event
  isEdit.value = true
  tempEvent.value = event

  // Recurring event
  if (event.recurring) {
    originalRecurringEvent.value = event.original
    eventOccurrence.value = { ...event }

    // Fill popup form with event data
    loadPopupForm(event)
  } else {
    originalRecurringEvent.value = {}
    // Fill popup form with event data
    loadPopupForm(event)
  }

  // Set popup options
  popupHeaderText.value = 'Edit event'
  popupButtons.value = popupEditButtons
  popupAnchor.value = args.domEvent.currentTarget
  // Open the popup
  isPopupOpen.value = true
}

function handleEventUpdate(args: MbscEventUpdateEvent) {
  const event = args.event
  if (event.recurring) {
    originalRecurringEvent.value = args.oldEvent
    // We need this on delete
    tempEvent.value = event
    eventOccurrence.value = args.oldEventOccurrence
    if (args.domEvent.keyCode === 46) {
      recurringActionText.value = 'Delete'
      recurringDelete.value = true
      isRecurringEditOpen.value = true
    } else {
      recurringActionText.value = 'Edit'
      recurringDelete.value = false
      isRecurringEditOpen.value = true
    }
    return false
  } else {
    return true
  }
}

function handleEventCreate(args: MbscEventCreateEvent) {
  const originEvent = args.originEvent
  if (originEvent && originEvent.recurring) {
    newEvent.value = args.event
    return false
  } else {
    return true
  }
}

function handleEventCreated(args: MbscEventCreatedEvent) {
  isEdit.value = false
  tempEvent.value = args.event
  // Fill popup form with event data
  loadPopupForm(args.event)
  // Set popup options
  popupHeaderText.value = 'New Event'
  popupButtons.value = popupAddButtons
  popupAnchor.value = args.target
  resetCustomValues()
  // Open the popup
  isPopupOpen.value = true
}

function handleEventDeleted(args: MbscEventDeletedEvent) {
  deleteEvent(args.event)
}

function handleEventUpdated() {
  // Here you can update the event in your storage as well, after drag & drop or resize
  // ...
}

function handleDelete() {
  if (tempEvent.value.recurring) {
    recurringActionText.value = 'Delete'
    recurringDelete.value = true
    isRecurringEditOpen.value = true
  } else {
    deleteEvent(tempEvent.value)
    isPopupOpen.value = false
  }
}

function handleSelectedDateChange(args: MbscSelectedDateChangeEvent) {
  calendarSelectedDate.value = args.date
}

onMounted(() => {
  populateMonthDays(1, 'monthly')
  populateMonthDays(1, 'yearly')
})
</script>

<template>
  <MbscEventcalendar
    :view="myView"
    :data="myEvents"
    clickToCreate="double"
    :dragToCreate="true"
    :dragToMove="true"
    :dragToResize="true"
    :selectedDate="calendarSelectedDate"
    @selected-date-change="handleSelectedDateChange"
    @event-click="handleEventClick"
    @event-update="handleEventUpdate"
    @event-create="handleEventCreate"
    @event-created="handleEventCreated"
    @event-deleted="handleEventDeleted"
    @event-updated="handleEventUpdated"
  />
  <MbscPopup
    cssClass="md-recurring-event-editor-popup"
    display="bottom"
    :contentPadding="false"
    :fullScreen="true"
    :scrollLock="false"
    :height="500"
    :headerText="popupHeaderText"
    :anchor="popupAnchor"
    :buttons="popupButtons"
    :isOpen="isPopupOpen"
    :responsive="responsivePopup"
    @close="isPopupOpen = false"
  >
    <div class="mbsc-form-group">
      <MbscInput label="Title" v-model="popupEventTitle" />
      <MbscTextarea label="Description" v-model="popupEventDescription" />
    </div>
    <div class="mbsc-form-group">
      <MbscSwitch label="All-day" v-model="popupEventAllDay" />
      <MbscInput ref="startRef" label="Starts" />
      <MbscInput ref="endRef" label="Ends" />
      <MbscDatepicker
        v-model="popupEventDates"
        select="range"
        :touchUi="true"
        :startInput="startRef"
        :endInput="endRef"
        :showRangeLabels="false"
        :controls="popupEventAllDay ? datePickerControls : datetimePickerControls"
        :responsive="popupEventAllDay ? datePickerResponsive : datetimePickerResponsive"
      />
      <MbscSelect
        v-model="selectedRepeat"
        label="Repeats"
        :data="repeatData"
        :responsive="selectResponsive"
      />
    </div>
    <div class="mbsc-form-group">
      <div v-if="selectedRepeat === 'custom' || selectedRepeat === 'custom-value'">
        <div>
          <MbscSegmentedGroup v-model="repeatType">
            <MbscSegmented value="daily">Daily</MbscSegmented>
            <MbscSegmented value="weekly">Weekly</MbscSegmented>
            <MbscSegmented value="monthly">Monthly</MbscSegmented>
            <MbscSegmented value="yearly">Yearly</MbscSegmented>
          </MbscSegmentedGroup>

          <div class="md-recurrence-options">
            <span>Repeat every</span>
            <span class="md-recurrence-input md-recurrence-input-nr">
              <MbscInput :min="1" v-model="repeatNr" inputStyle="outline" />
            </span>
            <span v-if="repeatType === 'daily'">days</span>
            <span v-if="repeatType === 'weekly'">weeks</span>
            <span v-if="repeatType === 'monthly'">
              month(s) on day
              <span class="md-recurrence-input md-recurrence-input-nr">
                <MbscSelect
                  :data="monthlyDays"
                  v-model="monthlyDay"
                  className="md-recurrence-input"
                  inputStyle="outline"
                />
              </span>
            </span>
            <span v-if="repeatType === 'yearly'">
              year(s) <br />on day
              <span class="md-recurrence-input md-recurrence-input-nr">
                <MbscSelect
                  :data="yearlyDays"
                  v-model="yearlyDay"
                  className="md-recurrence-input"
                  inputStyle="outline"
                />
              </span>
              <span>of</span>
              <span class="md-recurrence-input">
                <MbscSelect
                  :data="months"
                  v-model="selectedMonth"
                  className="md-recurrence-input"
                  inputStyle="outline"
                  @change="monthChange"
                />
              </span>
            </span>

            <p v-if="repeatType === 'daily'" class="md-recurrence-desc">
              The event will be repeated every day or every x days, depending on the value
            </p>
            <p v-if="repeatType === 'weekly'" class="md-recurrence-desc">
              The event will be repeated every x weeks on specific weekdays
            </p>
            <p v-if="repeatType === 'monthly'" class="md-recurrence-desc">
              The event will be repeated every x month on specific day of the month
            </p>
            <p v-if="repeatType === 'yearly'" class="md-recurrence-desc">
              The event will be repeated every x years on specific day of a specific month
            </p>
          </div>

          <MbscSegmentedGroup v-model="weekDays" v-if="repeatType === 'weekly'" select="multiple">
            <MbscSegmented value="SU">Sun</MbscSegmented>
            <MbscSegmented value="MO">Mon</MbscSegmented>
            <MbscSegmented value="TU">Tue</MbscSegmented>
            <MbscSegmented value="WE">Wed</MbscSegmented>
            <MbscSegmented value="TH">Thu</MbscSegmented>
            <MbscSegmented value="FR">Fri</MbscSegmented>
            <MbscSegmented value="SA">Sat</MbscSegmented>
          </MbscSegmentedGroup>

          <div class="md-recurrence-ends">Ends</div>

          <div class="mbsc-form-group">
            <MbscRadioGroup v-model="condition">
              <MbscRadio
                label="Never"
                position="start"
                description="The event will repeat indefinitely"
                value="never"
              />
              <MbscRadio value="until" position="start" @click="condition = 'until'">
                Until
                <span class="md-recurrence-input">
                  <MbscDatepicker
                    :controls="['calendar']"
                    display="anchored"
                    :touchUi="false"
                    dateFormat="YYYY-MM-DD"
                    returnFormat="iso8601"
                    v-model="untilDate"
                    inputStyle="outline"
                  />
                </span>
                <span class="mbsc-description">
                  The event will run until it reaches a specific date
                </span>
              </MbscRadio>
              <MbscRadio value="count" position="start">
                After
                <span class="md-recurrence-input md-recurrence-input-nr">
                  <MbscInput
                    inputStyle="outline"
                    v-model="occurrences"
                    @click="condition = 'count'"
                  />
                  occurrences
                  <span class="mbsc-description">
                    The event will repeat until it reaches a certain amount of occurrences
                  </span>
                </span>
              </MbscRadio>
            </MbscRadioGroup>
          </div>
        </div>
      </div>
      <div v-if="isEdit" class="mbsc-button-group">
        <MbscButton
          className="mbsc-button-block"
          color="danger"
          variant="outline"
          @click="handleDelete"
        >
          Delete event
        </MbscButton>
      </div>
    </div>
  </MbscPopup>
  <MbscPopup
    display="bottom"
    :contentPadding="false"
    :buttons="recurringEditButtons"
    :isOpen="isRecurringEditOpen"
    @close="isRecurringEditOpen = false"
  >
    <div class="mbsc-form-group">
      <div class="mbsc-form-group-title">{{ recurringActionText }} recurring event</div>
      <MbscRadioGroup v-model="recurringEditMode">
        <MbscRadio label="This event only" value="current" />
        <MbscRadio label="This and following events" value="following" />
        <MbscRadio label="All events" value="all" />
      </MbscRadioGroup>
    </div>
  </MbscPopup>
</template>

<style>
.md-recurring-event-editor-popup .md-recurrence-input .mbsc-textfield-wrapper {
  display: inline-block;
  margin: 3px;
  z-index: 3;
  line-height: normal;
}

.md-recurring-event-editor-popup .md-recurrence-input .mbsc-textfield {
  width: 140px;
  height: 32px;
}

.md-recurring-event-editor-popup .md-recurrence-input-nr .mbsc-textfield {
  width: 90px;
}

.md-recurring-event-editor-popup .md-recurrence-input .mbsc-select-icon {
  top: 50%;
  height: 1.5em;
  margin-top: -0.75em;
}

.md-recurrence-options {
  padding: 0 1em;
  margin: 1em 0;
}

.md-recurrence-ends {
  padding: 0 1em;
  margin: 1.25em 0 -1.25em 0;
}

.md-recurrence-desc {
  font-size: 12px;
  font-weight: normal;
  color: #555;
  margin: 1em 0;
}

.md-hide-elm {
  display: none;
}
</style>
```

## Implementation notes

- Use `view: { calendar: { labels: true } }` with `clickToCreate: 'double'`, `dragToCreate: true`, `dragToMove: true`, `dragToResize: true`. Pre-load 4 default recurring events: "Take vitamins" (daily every day, 10:00-12:00, `#67ab0d`), "Football training" (weekly Monday, 18:00-20:00, `#fd966a`), "Rent payment" (monthly on day 10, 17:00-18:00, `#3ea39e`), "Dexter BD" (yearly June 6, all-day, `#d00f0f`).
- The **add/edit popup** (`Popup`, responsive: anchored 510px on medium, bottom full-screen on mobile) contains: **Title** (`Input`), **Description** (`Textarea`), **All-day** (`Switch`), **Start/End** (`Datepicker` range with `startInput`/`endInput`; controls: `['calendar', 'time']` normally, `['calendar']` all-day; mobile: `['datetime']` / `['date']`), **Repeats** (`Select`), the **custom recurrence editor** section (shown only when repeat is `'custom'` or `'custom-value'`), and a **Delete event** button (edit mode only).
- The **Repeats `Select`** data has these preset values whose display texts update dynamically based on the event's start date: `norepeat` ("Does not repeat"), `daily` ("Daily"), `weekly` ("Weekly on [day name]"), `monthly` ("Monthly on day [N]"), `monthly-pos` ("Monthly on the [ordinal] [day name]"), `yearly` ("Annually on [Month] [day]"), `yearly-pos` ("Annually on the [ordinal] [day name] of [Month]"), `weekday` ("Every weekday (Monday to Friday)"), `custom` ("Custom"). When an existing event has a custom rule with `interval`/`count`/`until`, a `custom-value` entry with generated descriptive text is inserted dynamically.
- **`loadPopupForm(event)`**: populates all popup fields from the event object; sets the default `untilDate` to the day after `event.start`; rebuilds the Repeats data with date-specific labels. For recurring events: if the rule has `interval`/`count`/`until` set `selectedRepeat = 'custom-value'` and add the generated text entry; if `weekDays === 'MO,TU,WE,TH,FR'` → `'weekday'`; if positional (`pos`) → `repeat + '-pos'`; otherwise → `repeat`. Calls `resetCustomValues()` for non-recurring events.
- **Custom recurrence editor**: a `SegmentedGroup` (Daily/Weekly/Monthly/Yearly) picks `repeatType`. Below it: "Repeat every [interval `Input`] [days/weeks/months/years]". For Weekly: a multi-select weekday `SegmentedGroup` (Sun-Sat). For Monthly: day `Select` (1-31). For Yearly: day `Select` + month `Select`. An **Ends** section with a `RadioGroup`: "Never" / "Until [date `Datepicker`]" / "After [count `Input`] occurrences".
- **`getCustomRule()`**: builds the `recurring` object to save on the event. Presets map directly using the event's start date for weekday/day/month values. `weekday` → `{ repeat: 'weekly', weekDays: 'MO,TU,WE,TH,FR' }`. `custom`/`custom-value` → `{ repeat: repeatType, interval: repeatNr, ...weekDays/day/month as needed, ...until/count from condition }`. Returns `null`/`undefined` for `norepeat`.
- `onEventClick` → open popup in **edit mode**: store `originalRecurringEvent = event.original` if recurring, call `loadPopupForm(event)`, set anchor to clicked element, open with "Edit event" header and "Save" button.
- `onEventCreated` → open popup in **add mode**: call `resetCustomValues()`, `loadPopupForm(event)`, open with "New Event" header and "Add" button. **On popup close without confirming** (add mode): refresh the events array to strip the temporary event; for the imperative API, call `inst.removeEvent(event)` in the popup's `onClose` callback.
- `onEventCreate` → if `args.originEvent` is a recurring event: store `args.event` as `newEvent` and `return false` (prevents duplicate creation; the recurrence mode popup will complete the operation).
- `onEventUpdate` → if the updated event is recurring: store `args.oldEvent` as `originalRecurringEvent` and `args.oldEventOccurrence` as `eventOccurrence`; open the **recurrence mode popup** in "Edit" or "Delete" mode (based on `args.isDelete`); `return false`.
- Handle `onEventDeleted` to delete the event by filtering the events array; for the imperative API, handle deletion in the Delete button's click handler with `inst.removeEvent(editedEvent)`.
- **Recurrence mode popup** (separate `Popup`): header "[Edit/Delete] recurring event"; `RadioGroup`: "This event only" / "This and following events" / "All events" (default: `current`); buttons: Cancel / Ok.
  - **Ok → delete** (`deleteRecurringEvent`): `current` - push `event.start` to `recurringException`, update original; `following` - set `original.recurring.until = event.start`, update original; `all` - remove original. Apply changes to the events array; for the imperative API, call `inst.updateEvent(original)` or `inst.removeEvent(original)`.
  - **Ok → edit**: call `updateRecurringEvent(originalRecurringEvent, eventOccurrence, newEvent, updatedEventOrNull, recurringEditMode)`. React imports it from `@mobiscroll/react`, Vue from `@mobiscroll/vue`, Angular from `@mobiscroll/angular`, JS/jQuery use `mobiscroll.updateRecurringEvent`. Returns `{ updatedEvent, newEvent? }` - replace the original in the list and, if `newEvent` exists, append it; for the imperative API, call `inst.updateEvent(result.updatedEvent)` and `inst.addEvent(result.newEvent)`.
  - When triggered from the **popup Save button** (edit path): pass the form-built event as the 4th argument and `null` as the 3rd. When triggered from **drag/drop** (`onEventUpdate` path): pass `null` as 4th and the drag-created `newEvent` as 3rd.
- **Add button**: build event from form state with `getCustomRule()` as `recurring`, append to the events list; JS/jQuery: call `inst.updateEvent(newEvent)` to finalize the temp event. Then call `navigateTo()` and close popup.
- **Save button (non-recurring)**: build updated event from form state, replace in list, call `navigateTo()`, close popup.
- **`navigateTo()`**: for yearly events, compute the correct year and navigate to `recurring.month`/`recurring.day`; for all others, navigate to `event.start`.

## 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-vue.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 vue
   ```

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 vue --trial`.

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

## Related demos

- Previous: [Add, Edit and Delete Events](https://demo.mobiscroll.com/vue/eventcalendar/create-read-update-delete-CRUD.md) - Add full event management to a Vue event calendar — create events by drag or click, edit them in a custom dialog, and delete with undo.
- Next: [Block Past Event Creation](https://demo.mobiscroll.com/vue/eventcalendar/disallow-past-event-creation.md) - Keep past dates read-only in a Vue event calendar — block creation and editing on past days and show toast feedback on failed attempts.
