UNPKG

events-calendar

Version:

A flexible, responsive, and feature-rich calendar component for modern React applications.

1 lines 142 kB
{"version":3,"sources":["../package/EventsCalendar.tsx","../package/components/event/Event.tsx","../package/utils/setTime.ts","../package/utils/constants.ts","../package/utils/isBetween.ts","../package/utils/hasOverlap.ts","../package/utils/updateEvent.ts","../package/utils/getTimeDiff.ts","../package/utils/getTimeLabel.ts","../package/utils/filterByWeek.ts","../package/utils/filterByDate.ts","../package/utils/filterByView.ts","../package/utils/parseRawEvents.ts","../package/utils/getVisibleEvents.ts","../package/utils/arrangeWeekEvents.ts","../package/state/EMPTY_EVENT.tsx","../package/utils/returnValidStartEnd.ts","../package/utils/getPlaceholderEvent.ts","../package/utils/arrangeWeekdayEvents.ts","../package/utils/createDayjsObjFromTime.ts","../package/utils/getBackgroundFromGroups.ts","../package/hooks/useMouseEvent.tsx","../package/hooks/useElementSize.tsx","../package/hooks/useCalendarEvent.tsx","../package/hooks/useEventsCalendar.tsx","../package/state/DEFAULT_STATE.tsx","../package/state/reducer.tsx","../package/hooks/useInitEventsCalendar.tsx","../package/components/event/TimeEvent.tsx","../package/components/event/utils/getClipPath.ts","../package/components/event/utils/getEventStyles.ts","../package/components/event/utils/isBeingDragged.ts","../package/components/event/utils/getOverflowArrows.ts","../package/components/event/utils/getWeekOrDayEventStyles.ts","../package/components/event/AllDayEvent.tsx","../package/components/popover/PopoverContent.tsx","../package/components/popover/useBindPopover.tsx","../package/components/popover/EventsCalendarPopover.tsx","../package/components/event-cell/CellHeader.tsx","../package/components/event-cell/ShowMoreText.tsx","../package/components/event-cell/CellContainer.tsx","../package/components/circular-loader/CircularLoader.tsx","../package/features/header/HeaderButton.tsx","../package/features/header/Controls.tsx","../package/features/header/Navigation.tsx","../package/features/icons/IconChevronLeft.tsx","../package/features/icons/IconChevronRight.tsx","../package/features/header/Header.tsx","../package/features/time-view/TimeView.tsx","../package/features/time-view/hooks/useSetInitialScroll.tsx","../package/features/time-view/utils/getWeekDates.ts","../package/features/time-view/utils/getEventsByDayMap.ts","../package/features/time-view/components/HoursColumn.tsx","../package/features/time-view/components/TimeIndicator.tsx","../package/features/time-view/components/TimeBackground.tsx","../package/features/time-view/components/TimeViewHeader.tsx","../package/features/year-view/components/MonthDate.tsx","../package/features/year-view/utils/generateYearDates.ts","../package/features/year-view/YearView.tsx","../package/features/month-view/MonthView.tsx","../package/features/month-view/components/MonthHeader.tsx","../package/features/month-view/utils/getMaxEvents.ts","../package/features/month-view/utils/getMonthDates.ts","../package/features/month-view/utils/getEventsByWeekMap.ts","../package/features/overflow-card/OverflowCard.tsx"],"sourcesContent":["'use client';\n\nimport { ReactNode, useMemo, useRef } from 'react';\nimport './EventsCalendar.css';\n\nimport {\n\tCalendarView,\n\tEventEditProps,\n\tEventClickArgs,\n\tRawCalendarEvent,\n\tRawCalendarEventBase,\n\tEventsCalendarPopoverProps,\n\tEventsCalendarContextMenuProps,\n} from './types';\n\nimport { EventsCalendarPopover, CircularLoader } from './components';\nimport { CALENDAR_VIEWS, filterByView, parseRawEvents } from './utils';\nimport { MonthView, Header, TimeView, OverflowCard, YearView } from './features';\nimport { useMouseEvent, useInitEventsCalendar, EventsCalendarObject } from './hooks';\n\nexport interface EventsCalendarProps<T extends RawCalendarEventBase = RawCalendarEventBase> {\n\tcalendar?: EventsCalendarObject;\n\tcompact?: boolean;\n\tenableDragCreation?: boolean;\n\tenableRescheduling?: boolean;\n\tevents?: RawCalendarEvent<T>[];\n\tisFetching?: boolean;\n\tnoHeader?: boolean;\n\tpopoverZIndex?: number;\n\tviews?: CalendarView[];\n\tonEventClick?: (props: EventClickArgs<T>) => void;\n\tonEventCreate?: (props: EventEditProps) => void;\n\tonEventReschedule?: (props: EventEditProps) => void;\n\trenderPopover?: (props: EventsCalendarPopoverProps) => ReactNode;\n\trenderContextMenu?: (props: EventsCalendarContextMenuProps) => ReactNode;\n}\n\nexport function EventsCalendar<T extends RawCalendarEvent = RawCalendarEventBase>({\n\tcalendar,\n\tcompact = false,\n\tenableDragCreation = false,\n\tenableRescheduling = false,\n\tevents = [],\n\tpopoverZIndex = 101,\n\tisFetching = false,\n\tnoHeader = false,\n\tonEventClick,\n\tonEventCreate,\n\tonEventReschedule,\n\trenderPopover,\n\trenderContextMenu,\n\tviews = [...CALENDAR_VIEWS],\n}: EventsCalendarProps<T>) {\n\t// Initialise data calendar\n\tconst { activeDate, setActiveDate, view, setView, state, dispatch } = useInitEventsCalendar(calendar);\n\n\t// Parse raw events\n\tconst parsed = useMemo(() => parseRawEvents<T>(events), [events]);\n\n\t// Events in current view\n\tconst calendarEvents = useMemo(() => filterByView<T>(parsed, activeDate, view), [parsed, activeDate, view]);\n\n\t// Overflow events (only all day events are needed in time views)\n\tconst isTimeView = view === 'week' || view === 'day';\n\tconst overflowEvents = isTimeView ? calendarEvents.filter(event => event.isAllDay) : calendarEvents;\n\n\t// Calendar state\n\tconst { eventAnchor, dragActive, eventDragActive, popoverIsOpen, clickedEvent, placeholderEvent } = state;\n\n\t// Popover handlers\n\tconst onClose = () => dispatch({ type: 'reset_calendar' });\n\tconst handleStopDrag = () => {\n\t\tif (dragActive || eventDragActive) dispatch({ type: 'event_create_stop' });\n\t};\n\n\t// Placeholder ref\n\tconst placeholderRef = useRef<HTMLDivElement>(null);\n\n\t// Mouse event handler\n\tconst handleMouseEvent = useMouseEvent({ enableDragCreation, dispatch, state, onEventCreate });\n\n\t// Shared props\n\tconst basicProps = {\n\t\tactiveDate,\n\t\tdispatch,\n\t\tstate,\n\t\tevents: calendarEvents,\n\t};\n\tconst sharedViewProps = {\n\t\t...basicProps,\n\t\tcompact,\n\t\tonEventClick,\n\t\thandleStopDrag,\n\t\tplaceholderRef,\n\t\thandleMouseEvent,\n\t\tonEventReschedule,\n\t\trenderContextMenu,\n\t\tenableRescheduling,\n\t};\n\n\t// Render function\n\tconst renderCurrentView = () => {\n\t\tswitch (view) {\n\t\t\tcase 'year':\n\t\t\t\treturn <YearView {...basicProps} />;\n\t\t\tcase 'month':\n\t\t\t\treturn <MonthView {...sharedViewProps} />;\n\t\t\tcase 'week':\n\t\t\tcase 'day':\n\t\t\t\treturn <TimeView {...sharedViewProps} view={view} />;\n\t\t\tdefault:\n\t\t\t\treturn null;\n\t\t}\n\t};\n\n\treturn (\n\t\t<div className='events-calendar-wrapper'>\n\t\t\t{noHeader ? null : (\n\t\t\t\t<Header view={view} setActiveDate={setActiveDate} setView={setView} activeDate={activeDate} views={views} />\n\t\t\t)}\n\t\t\t<div className='events-calendar' data-withheader={!noHeader} onClick={e => e.stopPropagation()}>\n\t\t\t\t<CircularLoader visible={isFetching} />\n\t\t\t\t{renderCurrentView()}\n\n\t\t\t\t{renderPopover && eventAnchor && (\n\t\t\t\t\t<EventsCalendarPopover isOpen={popoverIsOpen} anchor={eventAnchor} zIndex={popoverZIndex}>\n\t\t\t\t\t\t{renderPopover({ onClose, clickedEvent, newEvent: placeholderEvent })}\n\t\t\t\t\t</EventsCalendarPopover>\n\t\t\t\t)}\n\n\t\t\t\t<OverflowCard\n\t\t\t\t\tstate={state}\n\t\t\t\t\tcompact={compact}\n\t\t\t\t\tdispatch={dispatch}\n\t\t\t\t\tevents={overflowEvents}\n\t\t\t\t\tonEventClick={onEventClick}\n\t\t\t\t\tplaceholderRef={placeholderRef}\n\t\t\t\t\trenderContextMenu={renderContextMenu}\n\t\t\t\t\tenableRescheduling={enableRescheduling}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n","'use client';\n\nimport { Dispatch, ReactNode, RefObject, useRef } from 'react';\nimport { Dayjs } from 'dayjs';\nimport './Event.css';\n\nimport { getTimeDiff, getTimeLabel } from '~/utils';\nimport { useCalendarEvent } from '~/hooks';\nimport {\n\tCalendarState,\n\tCalendarAction,\n\tMinMaxDatesInView,\n\tOrderedCalendarEvent,\n\tEventsCalendarContextMenuProps,\n\tCalendarView,\n\tEventClickArgs,\n} from '~/types';\n\nimport { TimeEvent } from './TimeEvent';\nimport { AllDayEvent } from './AllDayEvent';\nimport { getEventStyles, getWeekOrDayEventStyles, isBeingDragged } from './utils';\n\ninterface EventProps<T> {\n\tview: CalendarView;\n\tenableRescheduling: boolean;\n\tcompact?: boolean;\n\tdate: Dayjs;\n\tdispatch: Dispatch<CalendarAction>;\n\tevent: OrderedCalendarEvent<T>;\n\tisInWeekHeader?: boolean;\n\tisInOverflow?: boolean;\n\tisInDayHeader?: boolean;\n\tminMaxDatesInView?: MinMaxDatesInView;\n\tplaceholderRef: RefObject<HTMLDivElement | null>;\n\tonEventClick?: (props: EventClickArgs<T>) => void;\n\trenderContextMenu?: (props: EventsCalendarContextMenuProps) => ReactNode;\n\tstate: CalendarState;\n}\n\nexport function Event<T>({\n\tview,\n\tenableRescheduling,\n\tcompact = false,\n\tdate,\n\tdispatch,\n\tevent,\n\tisInWeekHeader = false,\n\tisInOverflow = false,\n\tisInDayHeader = false,\n\tminMaxDatesInView,\n\tonEventClick,\n\tplaceholderRef,\n\trenderContextMenu,\n\tstate,\n}: EventProps<T>) {\n\tconst ref = useRef<HTMLDivElement>(null);\n\tconst hasContextMenu = !!renderContextMenu;\n\tconst isInteractive = !!onEventClick || enableRescheduling;\n\tconst isMonthView = view === 'month';\n\tconst isDayView = view === 'day';\n\n\t// Popover handlers\n\tconst openPopover = () => dispatch({ type: 'open_popover' });\n\tconst closePopover = () => dispatch({ type: 'reset_calendar' });\n\n\t// Context menu onClose\n\tconst onClose = () => dispatch({ type: 'reset_calendar' });\n\n\t// Handle event left click (view popover)\n\tconst handleClick = (\n\t\te: React.MouseEvent<HTMLDivElement, MouseEvent>,\n\t\tisPlaceholder: boolean,\n\t\teventRef: RefObject<HTMLDivElement | null>,\n\t\tonEventClick?: (props: EventClickArgs<T>) => void\n\t) => {\n\t\te.stopPropagation();\n\t\tif (isPlaceholder) return;\n\n\t\tif (!eventRef.current) return;\n\n\t\t// Check if popover is open and anchored to the clicked event, we want to\n\t\t// move the popover if the event is clicked twice, but on different weeks)\n\t\tconst isDoubleClick = isActive && state.eventAnchor?.dataset.anchorday === eventRef.current.dataset.anchorday;\n\n\t\t// Overflow popover should close if clicked event was not inside the overflow popover\n\t\tconst overflowShouldClose = !isInOverflow && state.overflowIsOpen;\n\n\t\t// Close popover (this is either a double-click on an open event or an event outside the popover)\n\t\tif (overflowShouldClose) dispatch({ type: 'reset_calendar' });\n\n\t\t// Open popover if there is currently no anchored popover\n\t\tif (!isDoubleClick) {\n\t\t\tdispatch({ type: 'set_clicked_event', event: event, anchor: eventRef.current });\n\t\t}\n\n\t\t// Toggle popover visibility handler\n\t\tconst togglePopover = () => (isDoubleClick ? closePopover() : openPopover());\n\n\t\tonEventClick?.({\n\t\t\tevent,\n\t\t\tisDoubleClick,\n\t\t\teventRef: eventRef.current,\n\t\t\topenPopover,\n\t\t\tclosePopover,\n\t\t\ttogglePopover,\n\t\t});\n\t};\n\n\t// Main event hook\n\tconst { handleContextMenu, isActive, contextIsOpen, refs, floatingStyles, getFloatingProps, closeContextMenu } =\n\t\tuseCalendarEvent({\n\t\t\tdispatch,\n\t\t\tevent,\n\t\t\tstate,\n\t\t\thasContextMenu,\n\t\t\tisInOverflow,\n\t\t});\n\n\t// Current event is placeholder event\n\tconst isPlaceholder = event.id === null;\n\tconst eventRef = isPlaceholder && event.start.isSame(date, 'd') ? placeholderRef : ref;\n\tconst timeDuration = Math.abs(getTimeDiff(event.start, event.end));\n\tconst isShort = timeDuration <= 30;\n\tconst overlapOffset = isDayView ? 120 : 20;\n\tconst dayIndex = isDayView ? 0 : date.day();\n\n\tconst onDragStart = () => {\n\t\tdispatch({ type: 'reset_calendar' });\n\t\tif (enableRescheduling && !isInOverflow) {\n\t\t\tdispatch({\n\t\t\t\ttype: 'event_reschedule_start',\n\t\t\t\tevent: { ...event, dragId: event.id, order: isMonthView ? event.order : 1000 },\n\t\t\t});\n\t\t}\n\t};\n\n\tconst styles = isMonthView\n\t\t? getEventStyles(isInOverflow, event, date, compact, isInWeekHeader, isInDayHeader)\n\t\t: getWeekOrDayEventStyles(event, timeDuration, dayIndex, overlapOffset, isInteractive && isActive);\n\n\treturn (\n\t\t<>\n\t\t\t<div\n\t\t\t\tclassName={isMonthView ? 'events-calendar-month-item-container' : 'events-calendar-week-item-container'}\n\t\t\t\tdata-interactive={isInteractive}\n\t\t\t\tdata-active={isInteractive && isActive}\n\t\t\t\tdata-anchorday={date.format('DD-MMM-YYYY')}\n\t\t\t\tdata-placeholder={isPlaceholder}\n\t\t\t\tdata-dragactive={state.dragActive || state.eventDragActive}\n\t\t\t\tdata-isdragging={isBeingDragged(state, event)}\n\t\t\t\tdata-sm={compact}\n\t\t\t\tdata-time={isMonthView && !event.isAllDay}\n\t\t\t\tdraggable='true'\n\t\t\t\tonClick={e => handleClick(e, isPlaceholder, eventRef, onEventClick)}\n\t\t\t\tonContextMenu={e => handleContextMenu(e, eventRef)}\n\t\t\t\tonDragStart={onDragStart}\n\t\t\t\tref={eventRef}\n\t\t\t\tstyle={styles}\n\t\t\t>\n\t\t\t\t{isMonthView ? (\n\t\t\t\t\t!event.isAllDay ? (\n\t\t\t\t\t\t<TimeEvent event={event} isCompact={compact} />\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<AllDayEvent\n\t\t\t\t\t\t\tdate={date}\n\t\t\t\t\t\t\tevent={event}\n\t\t\t\t\t\t\tisCompact={compact}\n\t\t\t\t\t\t\tisInOverflow={isInOverflow}\n\t\t\t\t\t\t\tminMaxDatesInView={minMaxDatesInView}\n\t\t\t\t\t\t/>\n\t\t\t\t\t)\n\t\t\t\t) : (\n\t\t\t\t\t<div className='events-calendar-item-label' data-short={isShort}>\n\t\t\t\t\t\t<span style={isShort ? undefined : { width: '100%' }}>{event.title}</span>\n\t\t\t\t\t\t<span className='events-calendar-time-text'>{getTimeLabel(event, timeDuration)}</span>\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t\t{contextIsOpen && (\n\t\t\t\t<div ref={refs.setFloating} style={{ ...floatingStyles, zIndex: 2 }} {...getFloatingProps()}>\n\t\t\t\t\t{renderContextMenu && renderContextMenu({ event, closeContextMenu, onClose, openPopover })}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</>\n\t);\n}\n","import { Dayjs } from 'dayjs';\n\n/*\n Function that takes in a date object and a time\n Returns date object with same date, but resets time to input\n*/\nexport function setTime(date: Dayjs, time: Dayjs) {\n return date.hour(time.hour()).minute(time.minute());\n}\n","export const CALENDAR_VIEWS = ['year', 'month', 'week', 'day'] as const;\n\nexport const MONTHS = [\n\t'January',\n\t'February',\n\t'March',\n\t'April',\n\t'May',\n\t'June',\n\t'July',\n\t'August',\n\t'September',\n\t'October',\n\t'November',\n\t'December',\n] as const;\n\nexport const DAY_LABELS_SHORT = ['S', 'M', 'T', 'W', 'T', 'F', 'S'] as const;\n\nexport const DAYS_IN_FULL_MONTH = 42;\n\nexport const DEFAULT_COLOR = '#0ea5e9';\n\nexport const HOURS = [\n\t'1 AM',\n\t'2 AM',\n\t'3 AM',\n\t'4 AM',\n\t'5 AM',\n\t'6 AM',\n\t'7 AM',\n\t'8 AM',\n\t'9 AM',\n\t'10 AM',\n\t'11 AM',\n\t'12 PM',\n\t'1 PM',\n\t'2 PM',\n\t'3 PM',\n\t'4 PM',\n\t'5 PM',\n\t'6 PM',\n\t'7 PM',\n\t'8 PM',\n\t'9 PM',\n\t'10 PM',\n\t'11 PM',\n] as const;\n","import { Dayjs } from 'dayjs';\n\nexport function isBetween(date: Dayjs, testStart: Dayjs, testEnd: Dayjs, startOnly: boolean = false) {\n\treturn (\n\t\t(date.isAfter(testStart) && date.isBefore(testEnd)) ||\n\t\tdate.isSame(testStart, 'd') ||\n\t\t(startOnly ? false : date.isSame(testEnd, 'd'))\n\t);\n}\n","import { Dayjs } from 'dayjs';\nimport { isBetween } from './isBetween';\n\n/*\n Function that returns true if two date ranges overlap\n*/\nexport function hasOverlap(start1: Dayjs, end1: Dayjs, start2: Dayjs, end2: Dayjs) {\n\treturn isBetween(start1, start2, end2) || isBetween(start2, start1, end1) || isBetween(end2, start1, end1);\n}\n","import { Dispatch } from 'react';\nimport { Dayjs } from 'dayjs';\n\nimport { CalendarView, CalendarAction, CalendarState } from '~/types';\n\ninterface updateDragNDropPlaceholderProps {\n\tstate: CalendarState;\n\tdispatch: Dispatch<CalendarAction>;\n\tdate: Dayjs;\n\tview: CalendarView;\n}\n\nexport function updateEvent({ state, dispatch, date, view }: updateDragNDropPlaceholderProps) {\n\tconst timeScale = view === 'month' ? 'day' : 'minute';\n\n\t// After placeholder event is created (for dragging), record where on the event we have clicked (e.g. 2 days/60mins in)\n\tif (state.dragStartOffset === null) {\n\t\tconst dateDiff = date.diff(state.clickedEvent.start, timeScale);\n\t\tdispatch({ type: 'update_event', dragStartOffset: dateDiff });\n\t\treturn;\n\t}\n\n\t// Extract existing values\n\tconst { start, end, startTime, endTime } = state.clickedEvent;\n\n\t// On drag over grid, update the placeholder event\n\tconst newStart = date.subtract(state.dragStartOffset, timeScale);\n\tconst increment = end.diff(start, timeScale);\n\tconst newEnd = newStart.add(increment, timeScale);\n\n\tif (view !== 'month' && !newStart.isSame(newEnd, 'day')) return;\n\n\tdispatch({\n\t\ttype: 'update_event',\n\t\tevent: {\n\t\t\t...state.placeholderEvent,\n\t\t\tstart: view === 'month' ? newStart.hour(start.hour()).minute(start.minute()) : newStart,\n\t\t\tend: view === 'month' ? newEnd.hour(end.hour()).minute(end.minute()) : newEnd,\n\t\t\tstartTime: view === 'month' ? startTime : newStart.format('h:mma'),\n\t\t\tendTime: view === 'month' ? endTime : newEnd.format('h:mma'),\n\t\t},\n\t});\n}\n","import { Dayjs } from 'dayjs';\n\nexport function getTimeDiff(dt1: Dayjs, dt2: Dayjs) {\n const mins1 = dt1.hour() * 60 + dt1.minute();\n const mins2 = dt2.hour() * 60 + dt2.minute();\n return mins2 - mins1;\n}\n","import { CalendarEvent } from '~/types';\n\nexport function getTimeLabel(event: CalendarEvent, timeDuration: number) {\n\tif (!event.start || !event.end) return '';\n\tconst leadingComma = timeDuration <= 30 ? ', ' : '';\n\treturn event.start.isBefore(event.end)\n\t\t? `${leadingComma}${event.start.format('h:mma')} - ${event.end.format('h:mma')}`\n\t\t: `${leadingComma}${event.end.format('h:mma')} - ${event.start.format('h:mma')}`;\n}\n","import { Dayjs } from 'dayjs';\nimport { CalendarEvent } from '~/types';\nimport { hasOverlap } from './hasOverlap';\n\n/*\n Function that returns an array of events that overlap a week with given weekStartDate\n*/\nexport function filterByWeek<T extends CalendarEvent>(array: T[], weekStartDate: Dayjs) {\n\tconst weekEndDate = weekStartDate.add(6, 'd');\n\treturn array.filter(({ start, end }) => {\n\t\tconst eventEnd = end || start; // Take event start if end is null\n\t\treturn hasOverlap(weekStartDate, weekEndDate, start, eventEnd);\n\t});\n}\n","import { Dayjs } from 'dayjs';\n\nimport { CalendarEvent } from '~/types';\nimport { isBetween } from './isBetween';\n\nexport function filterByDate<T extends CalendarEvent>(data: T[], date: Dayjs): T[] {\n\treturn data.filter(({ start, end }) => isBetween(date, start, end));\n}\n","import { Dayjs } from 'dayjs';\nimport { CalendarEvent, CalendarView } from '~/types';\nimport { hasOverlap } from './hasOverlap';\n\n/*\n Function that returns an array of events that overlap the current view (year or month)\n*/\nexport function filterByView<T>(array: CalendarEvent<T>[], activeDate: Dayjs, view: CalendarView) {\n\tconst unit = view === 'year' ? 'year' : 'month';\n\n\tconst filterStart = activeDate.startOf(unit).day(0);\n\tconst filterEnd = activeDate.endOf(unit).day(6);\n\treturn array.filter(({ start, end }) => hasOverlap(filterStart, filterEnd, start, end));\n}\n","import dayjs from 'dayjs';\nimport { CalendarEvent, RawCalendarEvent } from '~/types';\n\nconst getDayjs = (rawDate: dayjs.Dayjs, timeString: string) => {\n\tconst formatted = timeString.replace(/.{2}$/, ' $&').toUpperCase();\n\treturn dayjs(`${rawDate.format('DD-MMM-YYYY')} ${formatted}`);\n};\n\nexport function parseRawEvents<T>(events: RawCalendarEvent<T>[]): CalendarEvent<T>[] {\n\treturn events\n\t\t.filter(event => event.start || event.end)\n\t\t.map((event, index) => {\n\t\t\t// Event start\n\t\t\tlet dayjsStart = event.start ? dayjs(event.start) : dayjs(event.end);\n\t\t\tif (event.startTime) {\n\t\t\t\tdayjsStart = getDayjs(dayjsStart, event.startTime);\n\t\t\t}\n\n\t\t\t// Event end (default to event start)\n\t\t\tlet dayjsEnd = event.end ? dayjs(event.end) : dayjsStart;\n\n\t\t\t// Add end time\n\t\t\tif (event.startTime || event.endTime || event.isAllDay === false) {\n\t\t\t\tif (!event.startTime && !event.endTime) {\n\t\t\t\t\tdayjsEnd = dayjsStart.add(1, 'hour'); // Neither start nor end time\n\t\t\t\t} else if (event.endTime) {\n\t\t\t\t\tdayjsEnd = getDayjs(dayjsEnd, event.endTime); // Only end Time\n\t\t\t\t} else {\n\t\t\t\t\tdayjsEnd = getDayjs(dayjsEnd, event.startTime!).add(1, 'hour'); // Only start time\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst parsedEvent = {\n\t\t\t\t...event,\n\t\t\t\tid: event.id ?? index,\n\t\t\t\ttitle: event.title ?? '',\n\t\t\t\tstart: dayjsStart,\n\t\t\t\tend: dayjsEnd,\n\t\t\t\tisAllDay: event.isAllDay !== false,\n\t\t\t};\n\n\t\t\treturn parsedEvent;\n\t\t});\n}\n","import { Dayjs } from 'dayjs';\nimport { OrderedCalendarEvent } from '~/types';\n\n/*\n Returns the correct number of events for a cell without duplicates in a given week\n*/\nexport function getVisibleEvents<T extends OrderedCalendarEvent>(\n\tdata: T[],\n\tdate: Dayjs,\n\tmaxEvents: number,\n\tisInDayHeader: boolean\n) {\n\treturn data.filter(\n\t\t({ order, start }) => order < maxEvents && (isInDayHeader || date.day() === 0 || date.isSame(start, 'day'))\n\t);\n}\n","import { CalendarEvent, OrderedCalendarEvent } from '~/types';\nimport { hasOverlap } from './hasOverlap';\n\n/**\n * Sorts an array of calendar events with the following priority:\n * 1. Longer events appear first (descending by length in days).\n * 2. If lengths are equal, earlier start dates come first.\n * 3. If start dates are equal, all-day events come before timed events.\n *\n * @template T\n * @param {T[]} weekEventsArray - Array of calendar events to sort.\n * @returns {T[]} A new array sorted according to the above criteria.\n */\nfunction calendarSort<T extends CalendarEvent>(weekEventsArray: T[]) {\n\tconst clone = [...weekEventsArray];\n\n\treturn clone.sort((a, b) => {\n\t\t// Calculate the length of each event in days, including both start and end days\n\t\tconst a_lengthInDays = a.end.diff(a.start, 'day') + 1;\n\t\tconst b_lengthInDays = b.end.diff(b.start, 'day') + 1;\n\n\t\t// Sort order priority:\n\t\t// 1. Longer events come first (descending by length)\n\t\tif (a_lengthInDays > b_lengthInDays) return -1;\n\t\tif (a_lengthInDays < b_lengthInDays) return 1;\n\n\t\t// 2. If lengths are equal, earlier start date comes first\n\t\tif (a.start.isBefore(b.start, 'day')) return -1;\n\t\tif (b.start.isBefore(a.start, 'day')) return 1;\n\n\t\t// 3. If same start date, place all-day events above timed events\n\t\tif (a.isAllDay && !b.isAllDay) return -1;\n\t\tif (!a.isAllDay && b.isAllDay) return 1;\n\n\t\t// 4. If all criteria are equal, keep original order (stable sort)\n\t\treturn 0;\n\t});\n}\n\n/**\n * Finds the lowest non-negative integer slot not present in the given set of occupied slots.\n *\n * @param {Set<number>} overlappingSlots - A set of slot numbers that are already taken.\n * @returns {number} The lowest slot number not in the set, starting from 0.\n */\nfunction findAvailableSlot(overlappingSlots: Set<number>): number {\n\tlet slot = 0;\n\twhile (overlappingSlots.has(slot)) slot++;\n\treturn slot;\n}\n\n/*\n Function that takes an array of events for a single week and arranges them to create a dense fit\n*/\nexport function arrangeWeekEvents<T extends CalendarEvent>(weekEventsArray: T[]): (OrderedCalendarEvent & T)[] {\n\tif (weekEventsArray.length < 2) return weekEventsArray.map(event => ({ ...{ ...event, order: 0, indent: 0 } }));\n\n\t// Sort events by length, then start date, then duration\n\tconst sortedWeekEvents = calendarSort(weekEventsArray);\n\n\t// Extract first event from array and place as in first (top) slot/position\n\tconst [firstEvent, ...followingEvents] = sortedWeekEvents;\n\tconst orderedArray: OrderedCalendarEvent<T>[] = [{ ...firstEvent, order: 0, indent: 0 }];\n\n\t// Now loop through all other events and add them to the ordered array\n\tfollowingEvents.forEach(testEvent => {\n\t\tconst testStart = testEvent.start;\n\t\tconst testEnd = testEvent.end || testStart; // fallback if no end\n\n\t\t// Assign 'slots' for each day cell. Track slots that contain overlapping entries\n\t\tconst overlappingSlots = new Set<number>();\n\n\t\t// Check for overlaps with already placed events\n\t\tfor (const placedEvent of orderedArray) {\n\t\t\tconst placedStart = placedEvent.start;\n\t\t\tconst placedEnd = placedEvent.end || placedStart; // fallback if no end\n\n\t\t\tif (hasOverlap(testStart, testEnd, placedStart, placedEnd)) {\n\t\t\t\toverlappingSlots.add(placedEvent.order);\n\t\t\t}\n\t\t}\n\n\t\t// Find the lowest slot number not taken by overlapping events\n\t\tconst eventOrder = findAvailableSlot(overlappingSlots);\n\n\t\t// Add this event to the ordered array, along with its allocated slot\n\t\torderedArray.push({ ...testEvent, order: eventOrder, indent: 0 });\n\t});\n\treturn orderedArray;\n}\n","import dayjs from 'dayjs';\nimport { OrderedCalendarEvent } from '../types';\n\nexport const EMPTY_EVENT: OrderedCalendarEvent = {\n\tid: null,\n\tdragId: null,\n\ttitle: 'Untitled',\n\tgroups: [],\n\tstart: dayjs(),\n\tend: dayjs(),\n\tstartTime: undefined,\n\tendTime: undefined,\n\tisActive: false,\n\tisAllDay: true,\n\tindent: 0,\n\torder: 0,\n};\n","import { Dayjs } from 'dayjs';\n\nexport function returnValidStartEnd(start: Dayjs, end: Dayjs) {\n\treturn !end ? [start, start] : start.isSame(end) || start.isBefore(end) ? [start, end] : [end, start];\n}\n","import { Dayjs } from 'dayjs';\n\nimport { OrderedCalendarEvent } from '~/types';\nimport { EMPTY_EVENT } from '~/state/EMPTY_EVENT';\n\nimport { returnValidStartEnd } from './returnValidStartEnd';\n\nexport const getPlaceholderEvent = (start: Dayjs, end: Dayjs, isTimeEntry?: boolean, isInitialisation?: boolean) => {\n\tconst [validStart, validEnd] = returnValidStartEnd(start, end);\n\tconst eventEnd = isInitialisation && isTimeEntry ? validStart.add(15, 'minute') : validEnd;\n\n\tconst updatedEvent: OrderedCalendarEvent = {\n\t\t...EMPTY_EVENT,\n\t\tid: null,\n\t\tstart: validStart,\n\t\tend: eventEnd,\n\t\tisActive: true,\n\t\tisAllDay: isTimeEntry ? false : true,\n\t\tstartTime: isTimeEntry ? validStart.format('h:mma') : undefined,\n\t\tendTime: isTimeEntry ? eventEnd.format('h:mma') : undefined,\n\t\torder: isTimeEntry ? 100 : 0,\n\t};\n\treturn updatedEvent;\n};\n","import { Dayjs } from 'dayjs';\n\nimport { CalendarEvent, OrderedCalendarEvent } from '~/types';\n\nimport { setTime } from './setTime';\nimport { getTimeDiff } from './getTimeDiff';\n\n/*\n Function that sorts events by start time, from earliest to latest.\n Only sort arrays with at least 2 events.\n*/\nfunction sortByStart<T extends CalendarEvent>(array: T[]) {\n\tif (array.length < 2) return array;\n\n\treturn array.sort((a, b) => {\n\t\tconst timeDiff = getTimeDiff(a.start, b.start);\n\t\treturn timeDiff > 0 ? -1 : timeDiff < 0 ? 1 : 0;\n\t});\n}\n\n/*\n Function that checks if the start of a test event overlaps with another event\n*/\nfunction overlapsStart(test_start: Dayjs, main_start: Dayjs, main_end: Dayjs) {\n\treturn test_start.isSame(main_start) || (test_start.isAfter(main_start) && test_start.isBefore(main_end));\n}\n\n/*\n Function that takes an array of events for a single day and orders & indents them based on overlaps\n*/\nexport function arrangeWeekdayEvents<T extends CalendarEvent>(\n\tdayEvents: T[],\n\tdate: Dayjs\n): (OrderedCalendarEvent & T)[] {\n\tif (dayEvents.length < 2) return dayEvents.map(event => ({ ...event, order: 0, indent: 0 }));\n\n\t// Sort events by start time\n\tconst sortedEvents = sortByStart(dayEvents);\n\n\t// if (date.day() === 4) {\n\t// \tconsole.log(dayEvents);\n\t// }\n\n\t// Extract first event from array and place it first with no indent\n\tconst [firstEvent, ...followingEvents] = sortedEvents;\n\tconst orderedArray: OrderedCalendarEvent<T>[] = [{ ...firstEvent, order: 0, indent: 0 }];\n\n\t// Now loop through all other events and add them to the ordered array\n\tfollowingEvents.forEach((testEvent, index) => {\n\t\t// Set the time for the next event to the correct day\n\t\tconst tempStart_test_event = setTime(date, testEvent.start);\n\n\t\t// We loop through the current events already added to the ordered array and compare with the test event\n\t\t// If the start of the test event overlaps with another event, we increment the indent by 1\n\t\tlet eventIndent = 0;\n\t\torderedArray.forEach(currEvent => {\n\t\t\t// Set the time for the comparison event to the correct day\n\t\t\tconst tempStart_curr_event = setTime(date, currEvent.start);\n\t\t\tconst tempEnd_curr_event = setTime(date, currEvent.end);\n\n\t\t\tif (overlapsStart(tempStart_test_event, tempStart_curr_event, tempEnd_curr_event)) {\n\t\t\t\teventIndent += 1;\n\t\t\t}\n\t\t});\n\n\t\t// Add this event to the ordered array, noting its order and indent\n\t\t// (the array is already sorted so we use the index for ordering)\n\t\torderedArray.push({ ...testEvent, order: index + 1, indent: eventIndent });\n\t});\n\n\treturn orderedArray;\n}\n","import dayjs from 'dayjs';\n\nexport const createDayjsObjFromTime = (\n\tstartTime: string | null,\n\tendTime: string | null,\n\tstartString: string = '01-Jan-2000',\n\tendString: string = '01-Jan-2000'\n) => {\n\tconst formattedStartTime = startTime?.replace(/.{2}$/, ' $&').toUpperCase() || '12:00 AM';\n\tconst formattedEndTime = endTime?.replace(/.{2}$/, ' $&').toUpperCase() || '12:00 AM';\n\tconst start = dayjs(`${startString} ${formattedStartTime}`);\n\tconst end = dayjs(`${endString} ${formattedEndTime}`);\n\treturn { start, end };\n};\n","import React from 'react';\n\nimport { DEFAULT_COLOR } from '~/utils';\nimport { CalendarGroup } from '~/types';\n\nexport function getBackgroundFromGroups(groups?: CalendarGroup[]): React.CSSProperties {\n\tconst colors = groups?.map(g => g.color).filter(Boolean) ?? [];\n\n\tif (!colors.length) return { backgroundColor: DEFAULT_COLOR };\n\n\tif (colors.length === 1) return { backgroundColor: colors[0] };\n\n\tconst increment = 100 / colors.length;\n\tconst values = colors.map((color, i) => `${color} ${increment * i}% ${increment * i + increment}%`).join(', ');\n\treturn { backgroundImage: `-webkit-linear-gradient(-30deg,${values})` };\n}\n","import { Dispatch } from 'react';\nimport { MouseEventHandler, CalendarAction, CalendarState, EventEditProps } from '~/types';\nimport { Dayjs } from 'dayjs';\n\nimport { getTimeDiff, getPlaceholderEvent, returnValidStartEnd } from '../utils';\n\ninterface useMouseEventProps {\n\tenableDragCreation: boolean;\n\tdispatch: Dispatch<CalendarAction>;\n\tstate: CalendarState;\n\tonEventCreate?: (props: EventEditProps) => void;\n}\n\nconst updatePlaceholder = ({\n\tisTimeEvent,\n\tfirstClickDate,\n\tstart,\n\tdate,\n\tdispatch,\n}: {\n\tisTimeEvent: boolean;\n\tfirstClickDate: Dayjs;\n\tstart: Dayjs;\n\tdate: Dayjs;\n\tdispatch: Dispatch<CalendarAction>;\n}) => {\n\tif (isTimeEvent) {\n\t\tconst tempStart =\n\t\t\tgetTimeDiff(firstClickDate, date) < 0\n\t\t\t\t? returnValidStartEnd(start, firstClickDate.add(15, 'minute'))[1]\n\t\t\t\t: firstClickDate;\n\t\tconst tempEnd = start.hour(date.hour()).minute(date.minute());\n\t\tif (Math.abs(getTimeDiff(tempStart, date)) >= 15) {\n\t\t\tconst updatedEvent = getPlaceholderEvent(tempStart, tempEnd, true);\n\t\t\tdispatch({ type: 'update_event', event: updatedEvent });\n\t\t}\n\t} else {\n\t\tconst updatedEvent = getPlaceholderEvent(firstClickDate, date);\n\t\tdispatch({ type: 'update_event', event: updatedEvent });\n\t}\n};\n\nexport const useMouseEvent = ({ enableDragCreation, dispatch, state, onEventCreate }: useMouseEventProps) => {\n\t// If view only calendar, only close popovers on mousedown\n\tif (!enableDragCreation) {\n\t\treturn (e: React.MouseEvent) => {\n\t\t\tif (e.type === 'mousedown') dispatch({ type: 'reset_calendar' });\n\t\t};\n\t}\n\n\t// Popover handler\n\tconst openPopover = () => dispatch({ type: 'open_popover' });\n\tconst reset = () => dispatch({ type: 'reset_calendar' });\n\n\tconst mouseEventHandler: MouseEventHandler = (e, date, isTimeEvent, placeholderRef) => {\n\t\tconst { dragActive, firstClickDate, placeholderEvent } = state;\n\t\tconst { start } = placeholderEvent;\n\n\t\t// Reset drag event if anything other than left click triggered\n\t\tif (e.button !== 0) {\n\t\t\tif (dragActive) dispatch({ type: 'event_create_stop' });\n\t\t\treturn;\n\t\t}\n\n\t\tswitch (e.type) {\n\t\t\tcase 'mousedown': {\n\t\t\t\tif (state.eventAnchor || state.overflowIsOpen) {\n\t\t\t\t\tdispatch({ type: 'reset_calendar' });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst updatedEvent = getPlaceholderEvent(date, date, isTimeEvent, true);\n\t\t\t\tdispatch({ type: 'event_create_start', date, event: updatedEvent, anchor: placeholderRef.current });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'mouseenter': {\n\t\t\t\tif (e.buttons !== 1 || !dragActive || !firstClickDate) return;\n\t\t\t\tupdatePlaceholder({ isTimeEvent, firstClickDate, start, date, dispatch });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'mouseup': {\n\t\t\t\tif (!dragActive) return;\n\n\t\t\t\tonEventCreate &&\n\t\t\t\t\tonEventCreate({\n\t\t\t\t\t\tclickedEvent: state.clickedEvent,\n\t\t\t\t\t\tnewEvent: state.placeholderEvent,\n\t\t\t\t\t\teventRef: placeholderRef.current!,\n\t\t\t\t\t\topenPopover,\n\t\t\t\t\t\treset,\n\t\t\t\t\t});\n\n\t\t\t\tdispatch({ type: 'event_create_end', anchor: placeholderRef.current });\n\t\t\t}\n\t\t}\n\t};\n\treturn mouseEventHandler;\n};\n","/* eslint-disable react-hooks/exhaustive-deps */\nimport { useEffect, useMemo, useRef, useState } from 'react';\n\ntype ObserverRect = Omit<DOMRectReadOnly, 'toJSON'>;\n\nconst defaultState: ObserverRect = {\n\tx: 0,\n\ty: 0,\n\twidth: 0,\n\theight: 0,\n\ttop: 0,\n\tleft: 0,\n\tbottom: 0,\n\tright: 0,\n};\n\nexport function useResizeObserver<T extends HTMLDivElement = HTMLDivElement>(options?: ResizeObserverOptions) {\n\tconst frameID = useRef(0);\n\tconst ref = useRef<T>(null);\n\n\tconst [rect, setRect] = useState<ObserverRect>(defaultState);\n\n\tconst observer = useMemo(\n\t\t() =>\n\t\t\ttypeof window !== 'undefined'\n\t\t\t\t? new ResizeObserver((entries: ResizeObserverEntry[]) => {\n\t\t\t\t\t\tconst entry = entries[0];\n\n\t\t\t\t\t\tif (entry) {\n\t\t\t\t\t\t\tcancelAnimationFrame(frameID.current);\n\n\t\t\t\t\t\t\tframeID.current = requestAnimationFrame(() => {\n\t\t\t\t\t\t\t\tif (ref.current) {\n\t\t\t\t\t\t\t\t\tsetRect(entry.contentRect);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t: null,\n\t\t[]\n\t);\n\n\tuseEffect(() => {\n\t\tif (ref.current) {\n\t\t\tobserver?.observe(ref.current, options);\n\t\t}\n\n\t\treturn () => {\n\t\t\tobserver?.disconnect();\n\n\t\t\tif (frameID.current) {\n\t\t\t\tcancelAnimationFrame(frameID.current);\n\t\t\t}\n\t\t};\n\t}, [ref.current]);\n\n\treturn [ref!, rect] as const;\n}\n\nexport function useElementSize<T extends HTMLDivElement = HTMLDivElement>(options?: ResizeObserverOptions) {\n\tconst [ref, { width, height }] = useResizeObserver<T>(options);\n\treturn { ref, width, height };\n}\n","'use client';\n\nimport { Dispatch, MouseEvent, RefObject, useState } from 'react';\nimport { useFloating, flip, offset, shift, useDismiss, useInteractions } from '@floating-ui/react';\n\nimport { CalendarEvent, CalendarAction, CalendarState } from '~/types';\n\ninterface useCalendarEventProps<T> {\n\tdispatch: Dispatch<CalendarAction>;\n\tevent: CalendarEvent<T>;\n\tisInOverflow: boolean;\n\thasContextMenu: boolean;\n\tstate: CalendarState;\n}\n\nexport function useCalendarEvent<T>({\n\tdispatch,\n\tevent,\n\tisInOverflow,\n\thasContextMenu,\n\tstate,\n}: useCalendarEventProps<T>) {\n\t// Context menu state\n\tconst [contextIsOpen, setContextIsOpen] = useState(false);\n\tconst closeContextMenu = () => setContextIsOpen(false);\n\n\t// Constants\n\tconst isActive = event.id !== null && state.clickedEvent?.id === event.id;\n\n\tconst { refs, floatingStyles, context } = useFloating({\n\t\topen: contextIsOpen,\n\t\tonOpenChange: setContextIsOpen,\n\t\tmiddleware: [\n\t\t\toffset({ mainAxis: 5, alignmentAxis: 4 }),\n\t\t\tflip({\n\t\t\t\tfallbackPlacements: ['left-start'],\n\t\t\t}),\n\t\t\tshift({ padding: 10 }),\n\t\t],\n\t\tplacement: 'right-start',\n\t\t// strategy: 'fixed',\n\t\t// whileElementsMounted: autoUpdate,\n\t});\n\n\tconst dismiss = useDismiss(context);\n\n\tconst { getFloatingProps } = useInteractions([dismiss]);\n\n\t// Handle event right click (context menu)\n\tconst handleContextMenu = (e: MouseEvent, eventRef: RefObject<HTMLDivElement | null>) => {\n\t\tif (!hasContextMenu) return;\n\t\te.preventDefault();\n\n\t\tif (state.popoverIsOpen || (state.overflowIsOpen && !isInOverflow)) return;\n\n\t\trefs.setPositionReference({\n\t\t\tgetBoundingClientRect() {\n\t\t\t\treturn {\n\t\t\t\t\twidth: 0,\n\t\t\t\t\theight: 0,\n\t\t\t\t\tx: e.clientX,\n\t\t\t\t\ty: e.clientY,\n\t\t\t\t\ttop: e.clientY,\n\t\t\t\t\tright: e.clientX,\n\t\t\t\t\tbottom: e.clientY,\n\t\t\t\t\tleft: e.clientX,\n\t\t\t\t};\n\t\t\t},\n\t\t});\n\n\t\tdispatch({ type: 'open_context_menu', event, anchor: eventRef.current });\n\t\tsetContextIsOpen(true);\n\t};\n\n\treturn {\n\t\thandleContextMenu,\n\t\tisActive,\n\t\tcontextIsOpen,\n\t\trefs,\n\t\tfloatingStyles,\n\t\tgetFloatingProps,\n\t\tcloseContextMenu,\n\t};\n}\n","'use client';\n\nimport dayjs, { Dayjs } from 'dayjs';\nimport { Dispatch, SetStateAction, useEffect, useReducer, useState } from 'react';\n\nimport { DEFAULT_STATE, reducer } from '~/state';\n\nimport { CalendarAction, CalendarState, CalendarView } from '../types';\n\nexport type EventsCalendarObject = {\n\tactiveDate: Dayjs;\n\tsetActiveDate: Dispatch<SetStateAction<Dayjs>>;\n\tview: CalendarView;\n\tsetView: Dispatch<SetStateAction<CalendarView>>;\n\tstate: CalendarState;\n\tdispatch: Dispatch<CalendarAction>;\n};\n\nexport type useEventsCalendarProps = {\n\tisInitialised?: boolean;\n\tinitialDate?: string | number | Date | Dayjs;\n\tinitialView?: CalendarView;\n\tcloseOnClickOutside?: boolean;\n};\n\nexport function useEventsCalendar({\n\tisInitialised = false,\n\tinitialDate = dayjs(),\n\tinitialView = 'month',\n\tcloseOnClickOutside = true,\n}: Partial<useEventsCalendarProps> = {}): EventsCalendarObject {\n\tconst [activeDate, setActiveDate] = useState(dayjs(initialDate));\n\tconst [view, setView] = useState<CalendarView>(initialView);\n\n\tconst [state, dispatch] = useReducer(reducer, DEFAULT_STATE);\n\n\t// Reset calendar on click outside of the react component\n\tuseEffect(() => {\n\t\tif (!closeOnClickOutside || isInitialised) return;\n\t\tconst handleClose = () => dispatch({ type: 'reset_calendar' });\n\n\t\twindow.addEventListener('click', handleClose);\n\t\treturn () => {\n\t\t\twindow.removeEventListener('click', handleClose);\n\t\t};\n\t}, [dispatch, isInitialised, closeOnClickOutside]);\n\n\treturn {\n\t\tactiveDate,\n\t\tsetActiveDate,\n\t\tview,\n\t\tsetView,\n\t\tstate,\n\t\tdispatch,\n\t};\n}\n","import { CalendarState } from '../types';\nimport { EMPTY_EVENT } from './EMPTY_EVENT';\n\nexport const DEFAULT_STATE: CalendarState = {\n\t// View/edit event\n\tclickedEvent: EMPTY_EVENT,\n\tpopoverIsOpen: false,\n\teventAnchor: null,\n\n\t// Drag creation\n\tdragActive: false,\n\tfirstClickDate: null,\n\tplaceholderEvent: EMPTY_EVENT,\n\n\t// Event Drag & drop\n\tdragStartOffset: null,\n\teventDragActive: false,\n\n\t// Overflow Popover\n\toverflowAnchor: null,\n\toverflowIsOpen: false,\n};\n","import { EMPTY_EVENT } from './EMPTY_EVENT';\nimport { DEFAULT_STATE } from './DEFAULT_STATE';\nimport { CalendarAction, CalendarState } from '~/types';\n\nexport function reducer(state: CalendarState, action: CalendarAction): CalendarState {\n\tswitch (action.type) {\n\t\t// Reset to deafult state\n\t\tcase 'reset_calendar': {\n\t\t\treturn DEFAULT_STATE;\n\t\t}\n\n\t\t// Overflow\n\t\tcase 'open_overflow': {\n\t\t\treturn {\n\t\t\t\t...state,\n\t\t\t\t...DEFAULT_STATE,\n\n\t\t\t\toverflowIsOpen: true,\n\t\t\t\toverflowAnchor: action.anchor || state.overflowAnchor,\n\t\t\t};\n\t\t}\n\n\t\t// Popover\n\t\tcase 'open_popover': {\n\t\t\treturn {\n\t\t\t\t...state,\n\t\t\t\tpopoverIsOpen: true,\n\t\t\t};\n\t\t}\n\n\t\t// Context menu\n\t\tcase 'open_context_menu': {\n\t\t\treturn {\n\t\t\t\t...state,\n\t\t\t\tclickedEvent: action.event || state.clickedEvent,\n\t\t\t\teventAnchor: action.anchor || state.eventAnchor,\n\t\t\t};\n\t\t}\n\n\t\t// Calendar event interaction\n\t\tcase 'set_clicked_event': {\n\t\t\treturn {\n\t\t\t\t...state,\n\t\t\t\tclickedEvent: action.event || state.clickedEvent,\n\t\t\t\teventAnchor: action.anchor || state.eventAnchor,\n\t\t\t\tplaceholderEvent: EMPTY_EVENT,\n\t\t\t};\n\t\t}\n\n\t\t// Update event\n\t\tcase 'update_event': {\n\t\t\treturn {\n\t\t\t\t...state,\n\t\t\t\tdragStartOffset: action.dragStartOffset ?? state.dragStartOffset,\n\t\t\t\tplaceholderEvent: { ...state.placeholderEvent, ...action.event, id: null },\n\t\t\t};\n\t\t}\n\n\t\t// Drag creation\n\t\tcase 'event_create_start': {\n\t\t\treturn {\n\t\t\t\t...state,\n\t\t\t\tdragActive: true,\n\t\t\t\tfirstClickDate: action.date || state.firstClickDate,\n\t\t\t\tplaceholderEvent: { ...state.placeholderEvent, ...action.event, isActive: true },\n\t\t\t\teventAnchor: action.anchor || state.eventAnchor,\n\t\t\t};\n\t\t}\n\t\tcase 'event_create_end': {\n\t\t\treturn {\n\t\t\t\t...state,\n\t\t\t\tdragActive: false,\n\t\t\t\tfirstClickDate: null,\n\t\t\t\tplaceholderEvent: { ...state.placeholderEvent, isActive: true },\n\t\t\t\teventAnchor: action.anchor || state.eventAnchor,\n\t\t\t};\n\t\t}\n\t\tcase 'event_create_stop': {\n\t\t\treturn {\n\t\t\t\t...state,\n\t\t\t\tdragActive: false,\n\t\t\t\tfirstClickDate: null,\n\t\t\t\tclickedEvent: EMPTY_EVENT,\n\t\t\t\tplaceholderEvent: EMPTY_EVENT,\n\t\t\t\teventDragActive: false,\n\t\t\t};\n\t\t}\n\n\t\t// Event drag & drop rescheduling\n\t\tcase 'event_reschedule_start': {\n\t\t\treturn {\n\t\t\t\t...state,\n\t\t\t\teventDragActive: true,\n\t\t\t\tclickedEvent: action.event || state.clickedEvent,\n\t\t\t\tplaceholderEvent: {\n\t\t\t\t\t...state.placeholderEvent,\n\t\t\t\t\torder: 0,\n\t\t\t\t\t...action.event,\n\t\t\t\t\tid: null,\n\t\t\t\t\tisActive: true,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tcase 'event_reschedule_end': {\n\t\t\treturn {\n\t\t\t\t...state,\n\t\t\t\teventDragActive: false,\n\t\t\t\tdragStartOffset: null,\n\t\t\t\teventAnchor: action.anchor || state.eventAnchor,\n\t\t\t};\n\t\t}\n\n\t\tdefault:\n\t\t\tconsole.log(`Invalid action type ${action.type} passed to calendar reducer`);\n\t\t\treturn state;\n\t}\n}\n","import { EventsCalendarObject, useEventsCalendar } from './useEventsCalendar';\n\nexport function useInitEventsCalendar(calendar?: EventsCalendarObject): EventsCalendarObject {\n\tconst isInitialised = !!calendar;\n\tconst defaultCalendar = useEventsCalendar({ isInitialised });\n\n\treturn calendar || defaultCalendar;\n}\n","import './TimeEvent.css';\n\nimport { CalendarEvent } from '~/types';\nimport { DEFAULT_COLOR, getBackgroundFromGroups } from '~/utils';\n\ninterface TimeEventProps {\n\tevent: CalendarEvent;\n\tisCompact: boolean;\n}\n\nexport function TimeEvent({ event, isCompact }: TimeEventProps) {\n\t// Destructure event\n\tconst { title, start, groups } = event;\n\n\t// Event background color(s)\n\tconst colorStyles = getBackgroundFromGroups(groups);\n\tconst eventColors = groups?.map(g => g.color).filter(Boolean) ?? [];\n\tconst borderStyle = { border: `1px solid ${eventColors[0] ?? DEFAULT_COLOR}` };\n\n\treturn (\n\t\t<div className='events-calendar-time' data-sm={isCompact} style={borderStyle}>\n\t\t\t<div className='events-calendar-time-dot' style={colorStyles}></div>\n\t\t\t<div className='events-calendar-time-text'>{start.format('h:mma')}</div>\n\t\t\t<span className='events-calendar-time-label'>{title}</span>\n\t\t</div>\n\t);\n}\n","import { OverflowArrowType } from '~/types';\n\nexport const getClipPath = (overflowArrows: OverflowArrowType): string | undefined => {\n\tswitch (overflowArrows) {\n\t\tcase 'both':\n\t\t\treturn 'polygon(10px 0%, calc(100% - 10px) 0%, 100% 50%, calc(100% - 10px) 100%, 10px 100%, 0% 50%)';\n\t\tcase 'left':\n\t\t\treturn 'polygon(10px 0%, 100% 0%, 100% 100%, 10px 100%, 0% 50%)';\n\t\tcase 'right':\n\t\t\treturn 'polygon(0% 0%, calc(100% - 10px) 0%, 100% 50%, calc(100% - 10px) 100%, 0% 100%)';\n\t\tcase 'none':\n\t\t\treturn undefined;\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n};\n","import { CSSProperties } from 'react';\nimport { Dayjs } from 'dayjs';\n\nimport { OrderedCalendarEvent } from '~/types';\n\nexport const getEventStyles = (\n\tisInOverflow: boolean,\n\tevent: OrderedCalendarEvent,\n\tdate: Dayjs,\n\tisCompact: boolean,\n\tisInWeekHeader: boolean,\n\tisInDayHeader: boolean\n) => {\n\tif (isInOverflow) return { width: '100%' };\n\tconst weekStart = event.start.isSame(date, 'w') ? event.start.day() : 0;\n\tconst weekEnd = event.end.isSame(date, 'w') ? event.end.day() : 6;\n\tconst styles: CSSProperties = {\n\t\tposition: 'absolute',\n\t\tleft: isInDayHeader ? '0.125rem' : `calc(${(weekStart * 100) / 7}% + 0.125rem)`,\n\t\ttop: `calc(${isInDayHeader ? 4 : isCompact ? 24 : 26}px + ${event.order * (isCompact ? 21 : 22)}px + ${\n\t\t\tisInWeekHeader ? 6 : 0\n\t\t}px)`,\n\t\twidth: isInDayHeader ? 'calc(100% - 6px)' : `calc(${(100 * (1 + (weekEnd - weekStart))) / 7}% - 0.25rem - 1px)`,\n\t};\n\treturn styles;\n};\n","import { CalendarEvent, CalendarState } from '~/types';\n\nexport function isBeingDragged(state: CalendarState, event: CalendarEvent) {\n\treturn !event.isActive && event.id === state.placeholderEvent.dragId;\n}\n","import { MinMaxDatesInView, OverflowArrowType } from '~/types';\nimport dayjs from 'dayjs';\n\nexport const getOverflowArrows = (\n\tisInOverflow: boolean,\n\tdate: dayjs.Dayjs,\n\tstart: dayjs.Dayjs,\n\tend: dayjs.Dayjs,\n\tminMaxDatesInView?: MinMaxDatesInView\n): OverflowArrowType => {\n\tconst showLeftArrow = isInOverflow\n\t\t? start.isBefore(date, 'day')\n\t\t: !!minMaxDatesInView?.first && start.isBefore(minMaxDatesInView.first, 'day');\n\n\tconst showRightArrow = isInOverflow\n\t\t? end.isAfter(date, 'day')\n\t\t: !!minMaxDatesInView?.last && end.isAfter(minMaxDatesInView.last, 'day');\n\n\treturn showLeftArrow && showRightArrow ? 'both' : showLeftArrow ? 'left' : showRightArrow ? 'right' : 'none';\n};\n","import { OrderedCalendarEvent } from '~/types';\nimport { getBackgroundFromGroups } from '~/utils';\n\nexport const getWeekOrDayEventStyles = (\n\tevent: OrderedCalendarEvent,\n\ttimeDuration: number,\n\tdayIndex: number,\n\toverlapOffset: number,\n\tisActive: boolean\n) => {\n\tconst timeColumnOffset = dayIndex === 0 ? 6 : 0;\n\n\treturn {\n\t\tgridColumnStart: dayIndex + 1,\n\t\tgridRowStart: event.start && event.start.hour() * 4 + Math.round(event.start.minute() / 15) + 1,\n\t\tgridRowEnd: event.end && event.end.hour() * 4 + Math.round(event.end.minute() / 15) + 1,\n\t\theight: 12 * ((timeDuration || 60) / 15) - 2,\n\t\tborderWidth: event.indent > 0 ? (timeDuration > 30 ? '1px' : '0.5px') : 0,\n\t\tmarginLeft: overlapOffset * event.indent + timeColumnOffset,\n\t\twidth: `calc(100% - ${overlapOffset * (event.indent + 1) + timeColumnOffset}px)`,\n\t\tzIndex: isActive ? 100 : 1 + event.order,\n\t\t...getBackgroundFromGroups(event.groups),\n\t};\n};\n","import dayjs from 'dayjs';\nimport './AllDayEvent.css';\n\nimport { getBackgroundFromGroups } from '~/utils';\nimport { CalendarEvent, MinMaxDatesInView } from '~/types';\n\nimport { getClipPath, getOverflowArrows } from './utils';\n\ninterface AllDayEventProps {\n\tdate: dayjs.Dayjs;\n\tevent: CalendarEvent;\n\tminMaxDatesInView?: MinMaxDatesInView;\n\tisCompact: boolean;\n\tisInOverflow: boolean;\n}\n\nexport function AllDayEvent({ date, event, minMaxDatesInView, isCompact, isInOverflow }: AllDayEventProps) {\n\t// Calculate arrows for display in overflow popover\n\tconst overflowArrows = getOverflowArrows(isInOverflow, date, event.start, event.end, minMaxDatesInView);\n\n\t// Event background color(s)\n\tconst clipPath = getClipPath(overflowArrows);\n\tconst colorstyles = getBackgroundFromGroups(event.groups);\n\n\treturn (\n\t\t<div className='events-calendar-all-day-event' style={{ ...colorstyles, clipPath }}>\n\t\t\t<div className='events-calendar-all-day-event-content' data-sm={isCompact}>\n\t\t\t\t<span className='events-calendar-all-day-event-text' data-arrows={overflowArrows}>\n\t\t\t\t\t{event.title}\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n","'use client';\n\nimport { createPortal } from 'react-dom';\nimport { useEffect, useRef, ReactNode } from 'react';\n\nimport { useBindPopover } from './useBindPopover';\n\ninterface PopoverContentProps {\n\tanchor: Element;\n\tzIndex: number;\n\tchildren: ReactNode;\n}\n\nexport function PopoverContent({ anchor, zIndex, children }: PopoverContentProps) {\n\tconst styles = useRef({});\n\tconst { refs, floatingStyles } = useBindPopover({ anchor });\n\n\t// Only apply repositioning styles after element is mounted\n\tuseEffect(() => {\n\t\tsetTimeout(() => {\n\t\t\tstyles.current = { transitionDuration: '250ms', transitionProperty: 'all' };\n\t\t}, 100);\n\t}, []);\n\n\treturn createPortal(\n\t\t<div ref={refs.setFloating} style={{ ...floatingStyles, ...styles.current, zIndex }}>\n\t\t\t<div onClick={e => e.stopPropagation()}>{children}</div>\n\t\t</div>,\n\t\tdocument.body\n\t);\n}\n","'use client';\n\nimport {\n\tuseFloating,\n\tautoPlacement,\n\toffset,\n\tdetectOverflow,\n\tautoUpdate,\n\thide,\n\tElementRects,\n\tElements,\n\tMiddlewareData,\n\tPlacement,\n\tStrategy,\n} from '@floating-ui/react-dom';\nimport { Platform } from '@floating-ui/core';\n\n// Constants\nconst padding = 32;\nconst padding_x = 32;\nconst padding_y = 16;\n\ninterface useBindPopoverProps {\n\